// MIT License - Copyright (c) 2025 wallstop
// Full license text: https://github.com/wallstop/unity-helpers/blob/main/LICENSE
namespace WallstopStudios.UnityHelpers.Editor.Sprites
{
#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEngine;
using WallstopStudios.UnityHelpers.Core.Helper;
///
/// Public API to apply SpriteSettings profiles to assets. Mirrors the window logic
/// but can be called from tests and scripts without UI.
///
public static class SpriteSettingsApplierAPI
{
public sealed class PreparedProfile
{
public SpriteSettings settings;
public SpriteSettings.MatchMode mode;
public string nameLower;
public string patternLower;
public string extWithDot;
public Regex regex;
public int priority;
}
public static List PrepareProfiles(List profiles)
{
List result = new(profiles?.Count ?? 0);
if (profiles == null)
{
return result;
}
for (int i = 0; i < profiles.Count; i++)
{
SpriteSettings s = profiles[i];
if (s == null)
{
continue;
}
string trimmedPattern = string.IsNullOrEmpty(s.matchPattern)
? null
: s.matchPattern.Trim();
PreparedProfile p = new()
{
settings = s,
mode = s.matchBy,
nameLower = string.IsNullOrEmpty(s.name) ? null : s.name.ToLowerInvariant(),
patternLower = string.IsNullOrEmpty(trimmedPattern)
? null
: trimmedPattern.ToLowerInvariant(),
extWithDot =
string.IsNullOrEmpty(trimmedPattern) ? null
: trimmedPattern.StartsWith(".") ? trimmedPattern
: "." + trimmedPattern,
priority = s.priority,
};
if (
s.matchBy == SpriteSettings.MatchMode.Regex
&& !string.IsNullOrEmpty(trimmedPattern)
)
{
try
{
p.regex = new Regex(
trimmedPattern,
RegexOptions.IgnoreCase | RegexOptions.Compiled
);
}
catch
{
p.regex = null;
}
}
result.Add(p);
}
return result;
}
private static string SanitizePath(string p)
{
return string.IsNullOrEmpty(p) ? p : p.SanitizePath();
}
///
/// Finds the highest-priority profile matching the asset path.
///
/// The asset path to match. Returns null if null/empty.
/// Prepared profiles to search. Returns null if null/empty.
/// The matching settings, or null if no match or invalid input.
public static SpriteSettings FindMatchingSettings(
string assetPath,
List prepared
)
{
assetPath = SanitizePath(assetPath);
if (string.IsNullOrEmpty(assetPath))
{
return null;
}
if (prepared == null || prepared.Count == 0)
{
return null;
}
string fileName = Path.GetFileName(assetPath);
string fileNameLower = fileName.ToLowerInvariant();
string pathLower = assetPath.ToLowerInvariant();
string ext = Path.GetExtension(assetPath);
SpriteSettings best = null;
int bestPriority = int.MinValue;
for (int i = 0; i < prepared.Count; i++)
{
PreparedProfile p = prepared[i];
bool matches = false;
switch (p.mode)
{
#pragma warning disable CS0618 // Type or member is obsolete
case SpriteSettings.MatchMode.None:
#pragma warning restore CS0618 // Type or member is obsolete
break;
case SpriteSettings.MatchMode.Any:
matches =
string.IsNullOrEmpty(p.nameLower)
|| fileNameLower.Contains(p.nameLower);
break;
case SpriteSettings.MatchMode.NameContains:
matches =
!string.IsNullOrEmpty(p.patternLower)
&& fileNameLower.Contains(p.patternLower);
break;
case SpriteSettings.MatchMode.PathContains:
matches =
!string.IsNullOrEmpty(p.patternLower)
&& pathLower.Contains(p.patternLower);
break;
case SpriteSettings.MatchMode.Extension:
matches =
!string.IsNullOrEmpty(p.extWithDot)
&& string.Equals(ext, p.extWithDot, StringComparison.OrdinalIgnoreCase);
break;
case SpriteSettings.MatchMode.Regex:
matches = p.regex != null && p.regex.IsMatch(assetPath);
break;
}
if (!matches)
{
continue;
}
if (best == null || p.priority > bestPriority)
{
best = p.settings;
bestPriority = p.priority;
}
}
return best;
}
///
/// Determines if applying sprite settings would change texture import settings.
///
/// The asset path to check. Returns false if null/empty/missing.
/// Prepared profiles to search for matching settings. Returns false if null.
/// Optional buffer for reading texture settings. If null, a new one will be created.
/// True if changes would occur, false otherwise.
public static bool WillTextureSettingsChange(
string assetPath,
List prepared,
TextureImporterSettings buffer = null
)
{
assetPath = SanitizePath(assetPath);
TextureImporter textureImporter = AssetImporter.GetAtPath(assetPath) as TextureImporter;
if (textureImporter == null)
{
return false;
}
// Use Unity's canonical assetPath for matching to avoid path separator issues.
string realPath = textureImporter.assetPath;
SpriteSettings spriteData = FindMatchingSettings(realPath, prepared);
if (spriteData == null)
{
return false;
}
bool changed = false;
if (spriteData.applyPixelsPerUnit)
{
changed |= textureImporter.spritePixelsPerUnit != spriteData.pixelsPerUnit;
}
if (spriteData.applyPivot)
{
changed |= textureImporter.spritePivot != spriteData.pivot;
}
if (spriteData.applyGenerateMipMaps)
{
changed |= textureImporter.mipmapEnabled != spriteData.generateMipMaps;
}
if (spriteData.applyCrunchCompression)
{
changed |= textureImporter.crunchedCompression != spriteData.useCrunchCompression;
}
if (spriteData.applyCompression)
{
changed |= textureImporter.textureCompression != spriteData.compressionLevel;
}
buffer ??= new TextureImporterSettings();
textureImporter.ReadTextureSettings(buffer);
if (spriteData.applyTextureType)
{
changed |= textureImporter.textureType != spriteData.textureType;
}
if (spriteData.applyPivot)
{
changed |= buffer.spriteAlignment != (int)SpriteAlignment.Custom;
}
if (spriteData.applyAlphaIsTransparency)
{
changed |= buffer.alphaIsTransparency != spriteData.alphaIsTransparency;
}
if (spriteData.applyReadWriteEnabled)
{
changed |= buffer.readable != spriteData.readWriteEnabled;
}
if (spriteData.applySpriteMode)
{
changed |= textureImporter.spriteImportMode != spriteData.spriteMode;
changed |= buffer.spriteMode != (int)spriteData.spriteMode;
}
if (spriteData.applyExtrudeEdges)
{
changed |= buffer.spriteExtrude != spriteData.extrudeEdges;
}
if (spriteData.applyWrapMode)
{
changed |= buffer.wrapMode != spriteData.wrapMode;
}
if (spriteData.applyFilterMode)
{
changed |= buffer.filterMode != spriteData.filterMode;
}
return changed;
}
///
/// Applies sprite settings to texture import settings.
///
/// The asset path to update. Returns false if null/empty/missing.
/// Prepared profiles to search for matching settings. Returns false if null.
/// The texture importer that was updated, or null if no update occurred.
/// Optional buffer for reading/writing texture settings. If null, a new one will be created.
/// True if changes were applied, false otherwise.
public static bool TryUpdateTextureSettings(
string assetPath,
List prepared,
out TextureImporter textureImporter,
TextureImporterSettings buffer = null
)
{
assetPath = SanitizePath(assetPath);
textureImporter = AssetImporter.GetAtPath(assetPath) as TextureImporter;
if (textureImporter == null)
{
return false;
}
// Use Unity's canonical assetPath for matching to avoid path separator issues.
string realPath = textureImporter.assetPath;
SpriteSettings spriteData = FindMatchingSettings(realPath, prepared);
if (spriteData == null)
{
return false;
}
bool changed = false;
bool settingsChanged = false;
bool undoRecorded = false;
TextureImporter localTextureImporter = textureImporter;
buffer ??= new TextureImporterSettings();
textureImporter.ReadTextureSettings(buffer);
if (spriteData.applyTextureType)
{
if (textureImporter.textureType != spriteData.textureType)
{
EnsureUndoRecorded();
textureImporter.textureType = spriteData.textureType;
changed = true;
}
}
if (spriteData.applySpriteMode)
{
if (textureImporter.spriteImportMode != spriteData.spriteMode)
{
EnsureUndoRecorded();
textureImporter.spriteImportMode = spriteData.spriteMode;
changed = true;
}
if (buffer.spriteMode != (int)spriteData.spriteMode)
{
EnsureUndoRecorded();
buffer.spriteMode = (int)spriteData.spriteMode;
settingsChanged = true;
}
}
if (spriteData.applyPixelsPerUnit)
{
if (textureImporter.spritePixelsPerUnit != spriteData.pixelsPerUnit)
{
EnsureUndoRecorded();
textureImporter.spritePixelsPerUnit = spriteData.pixelsPerUnit;
changed = true;
}
if (buffer.spritePixelsPerUnit != spriteData.pixelsPerUnit)
{
EnsureUndoRecorded();
buffer.spritePixelsPerUnit = spriteData.pixelsPerUnit;
settingsChanged = true;
}
}
if (spriteData.applyPivot)
{
if (textureImporter.spritePivot != spriteData.pivot)
{
EnsureUndoRecorded();
textureImporter.spritePivot = spriteData.pivot;
changed = true;
}
if (buffer.spriteAlignment != (int)SpriteAlignment.Custom)
{
EnsureUndoRecorded();
buffer.spriteAlignment = (int)SpriteAlignment.Custom;
settingsChanged = true;
}
if (buffer.spritePivot != spriteData.pivot)
{
EnsureUndoRecorded();
buffer.spritePivot = spriteData.pivot;
settingsChanged = true;
}
}
if (spriteData.applyGenerateMipMaps)
{
if (textureImporter.mipmapEnabled != spriteData.generateMipMaps)
{
EnsureUndoRecorded();
textureImporter.mipmapEnabled = spriteData.generateMipMaps;
changed = true;
}
if (buffer.mipmapEnabled != spriteData.generateMipMaps)
{
EnsureUndoRecorded();
buffer.mipmapEnabled = spriteData.generateMipMaps;
settingsChanged = true;
}
}
if (spriteData.applyCrunchCompression)
{
if (textureImporter.crunchedCompression != spriteData.useCrunchCompression)
{
EnsureUndoRecorded();
textureImporter.crunchedCompression = spriteData.useCrunchCompression;
changed = true;
}
}
if (spriteData.applyCompression)
{
if (textureImporter.textureCompression != spriteData.compressionLevel)
{
EnsureUndoRecorded();
textureImporter.textureCompression = spriteData.compressionLevel;
changed = true;
}
}
if (spriteData.applyAlphaIsTransparency)
{
if (textureImporter.alphaIsTransparency != spriteData.alphaIsTransparency)
{
EnsureUndoRecorded();
textureImporter.alphaIsTransparency = spriteData.alphaIsTransparency;
changed = true;
}
if (buffer.alphaIsTransparency != spriteData.alphaIsTransparency)
{
EnsureUndoRecorded();
buffer.alphaIsTransparency = spriteData.alphaIsTransparency;
settingsChanged = true;
}
}
if (spriteData.applyReadWriteEnabled)
{
if (textureImporter.isReadable != spriteData.readWriteEnabled)
{
EnsureUndoRecorded();
textureImporter.isReadable = spriteData.readWriteEnabled;
changed = true;
}
if (buffer.readable != spriteData.readWriteEnabled)
{
EnsureUndoRecorded();
buffer.readable = spriteData.readWriteEnabled;
settingsChanged = true;
}
}
if (spriteData.applyExtrudeEdges)
{
if (buffer.spriteExtrude != spriteData.extrudeEdges)
{
EnsureUndoRecorded();
buffer.spriteExtrude = spriteData.extrudeEdges;
settingsChanged = true;
}
}
if (spriteData.applyWrapMode)
{
if (textureImporter.wrapMode != spriteData.wrapMode)
{
EnsureUndoRecorded();
textureImporter.wrapMode = spriteData.wrapMode;
changed = true;
}
if (buffer.wrapMode != spriteData.wrapMode)
{
EnsureUndoRecorded();
buffer.wrapMode = spriteData.wrapMode;
settingsChanged = true;
}
}
if (spriteData.applyFilterMode)
{
if (textureImporter.filterMode != spriteData.filterMode)
{
EnsureUndoRecorded();
textureImporter.filterMode = spriteData.filterMode;
changed = true;
}
if (buffer.filterMode != spriteData.filterMode)
{
EnsureUndoRecorded();
buffer.filterMode = spriteData.filterMode;
settingsChanged = true;
}
}
if (settingsChanged)
{
EnsureUndoRecorded();
textureImporter.SetTextureSettings(buffer);
}
return changed || settingsChanged;
void EnsureUndoRecorded()
{
if (undoRecorded)
{
return;
}
Undo.RecordObject(localTextureImporter, "Apply Sprite Settings");
undoRecorded = true;
}
}
}
#endif
}