1547 lines
51 KiB
C#
1547 lines
51 KiB
C#
#if UNITY_EDITOR
|
||
|
||
using UnityEngine;
|
||
using UnityEditor;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
|
||
namespace MK.TextureChannelPacker
|
||
{
|
||
public class MKTextureChannelPacker : EditorWindow
|
||
{
|
||
private const string Version = "1.9";
|
||
private const string DefaultOutputFolder = "Assets/MK/MKTextureChannelPacker";
|
||
private const string DefaultOutputName = "NewTexture";
|
||
private const string OutputPathControlName = "MKTextureChannelPacker.OutputPath";
|
||
private const int MaxPreviewSize = 512;
|
||
private const double OutputPathApplyDelay = 0.5d;
|
||
|
||
private static readonly Vector2 MinimumWindowSize = new Vector2(360, 360);
|
||
|
||
private string _outputAssetPath = DefaultOutputFolder + "/" + DefaultOutputName + ".png";
|
||
private string _outputAssetPathEdit = DefaultOutputFolder + "/" + DefaultOutputName + ".png";
|
||
private bool _outputAssetPathApplyPending = false;
|
||
private bool _outputAssetPathFieldFocused = false;
|
||
private double _outputAssetPathLastEditTime = 0d;
|
||
private Vector2 _scrollPos = Vector2.zero;
|
||
|
||
private OutputFormat _outputFormat = OutputFormat.PNG;
|
||
private TextureSize _outputWidth = TextureSize.Size1024;
|
||
private TextureSize _outputHeight = TextureSize.Size1024;
|
||
|
||
private bool _dataTexture = true;
|
||
private bool _generateMipMaps = true;
|
||
|
||
private Texture2D _previewTexture;
|
||
private bool _previewDirty = true;
|
||
private bool _previewGenerationQueued = false;
|
||
private bool _previewExpanded = true;
|
||
private bool _autoPreview = true;
|
||
private float _previewSizePercent = 25f;
|
||
private PreviewMode _previewMode = PreviewMode.RGB;
|
||
|
||
private GUIStyle _titleStyle;
|
||
private GUIStyle _subtitleStyle;
|
||
private GUIStyle _sectionTitleStyle;
|
||
private GUIStyle _cardStyle;
|
||
private GUIStyle _miniHelpStyle;
|
||
private GUIStyle _slotStatusStyle;
|
||
private GUIStyle _createButtonStyle;
|
||
|
||
private bool IsNarrowLayout
|
||
{
|
||
get { return position.width < 560f; }
|
||
}
|
||
|
||
private enum OutputFormat
|
||
{
|
||
PNG = 0,
|
||
#if UNITY_2018_3_OR_NEWER
|
||
TGA = 1
|
||
#endif
|
||
}
|
||
|
||
private enum TextureSize
|
||
{
|
||
Size32 = 32,
|
||
Size64 = 64,
|
||
Size128 = 128,
|
||
Size256 = 256,
|
||
Size512 = 512,
|
||
Size1024 = 1024,
|
||
Size2048 = 2048,
|
||
Size4096 = 4096,
|
||
Size8192 = 8192
|
||
}
|
||
|
||
private enum TextureChannel
|
||
{
|
||
Red = 0,
|
||
Green = 1,
|
||
Blue = 2,
|
||
Alpha = 3
|
||
}
|
||
|
||
private enum ChannelColor
|
||
{
|
||
Black = 0,
|
||
White = 1
|
||
}
|
||
|
||
private enum PreviewMode
|
||
{
|
||
RGB,
|
||
Red,
|
||
Green,
|
||
Blue,
|
||
Alpha
|
||
}
|
||
|
||
private struct SourceCache
|
||
{
|
||
public bool hasTexture;
|
||
public Color32[] pixels;
|
||
public int width;
|
||
public int height;
|
||
public TextureChannel sourceChannel;
|
||
public TextureChannel targetChannel;
|
||
public ChannelColor fallbackColor;
|
||
public bool invert;
|
||
}
|
||
|
||
private Texture2D _sourceTexture0 = null;
|
||
private bool _sourceChannel0Invert = false;
|
||
private TextureChannel _sourceChannel0 = TextureChannel.Red;
|
||
private TextureChannel _targetChannel0 = TextureChannel.Red;
|
||
private ChannelColor _fallbackColor0 = ChannelColor.Black;
|
||
|
||
private Texture2D _sourceTexture1 = null;
|
||
private bool _sourceChannel1Invert = false;
|
||
private TextureChannel _sourceChannel1 = TextureChannel.Green;
|
||
private TextureChannel _targetChannel1 = TextureChannel.Green;
|
||
private ChannelColor _fallbackColor1 = ChannelColor.Black;
|
||
|
||
private Texture2D _sourceTexture2 = null;
|
||
private bool _sourceChannel2Invert = false;
|
||
private TextureChannel _sourceChannel2 = TextureChannel.Blue;
|
||
private TextureChannel _targetChannel2 = TextureChannel.Blue;
|
||
private ChannelColor _fallbackColor2 = ChannelColor.Black;
|
||
|
||
private Texture2D _sourceTexture3 = null;
|
||
private bool _sourceChannel3Invert = false;
|
||
private TextureChannel _sourceChannel3 = TextureChannel.Alpha;
|
||
private TextureChannel _targetChannel3 = TextureChannel.Alpha;
|
||
private ChannelColor _fallbackColor3 = ChannelColor.White;
|
||
|
||
private static readonly string[] TextureSizeLabels =
|
||
{
|
||
"32",
|
||
"64",
|
||
"128",
|
||
"256",
|
||
"512",
|
||
"1024",
|
||
"2048",
|
||
"4096",
|
||
"8192"
|
||
};
|
||
|
||
private static readonly TextureSize[] TextureSizeValues =
|
||
{
|
||
TextureSize.Size32,
|
||
TextureSize.Size64,
|
||
TextureSize.Size128,
|
||
TextureSize.Size256,
|
||
TextureSize.Size512,
|
||
TextureSize.Size1024,
|
||
TextureSize.Size2048,
|
||
TextureSize.Size4096,
|
||
TextureSize.Size8192
|
||
};
|
||
|
||
[MenuItem("Tools/MK/Texture Channel Packer")]
|
||
private static void Init()
|
||
{
|
||
MKTextureChannelPacker window = GetWindow<MKTextureChannelPacker>();
|
||
window.titleContent = new GUIContent("Texture Channel Packer");
|
||
window.minSize = MinimumWindowSize;
|
||
window.ApplyDefaultFloatingSizeOnce();
|
||
window.Show();
|
||
}
|
||
|
||
private void OnEnable()
|
||
{
|
||
minSize = MinimumWindowSize;
|
||
ResetOutputPathToDefault();
|
||
MarkPreviewDirty();
|
||
}
|
||
|
||
private void OnDisable()
|
||
{
|
||
EditorApplication.delayCall -= GenerateQueuedPreviewTexture;
|
||
EditorApplication.update -= UpdatePendingOutputPathEdit;
|
||
|
||
if(_previewTexture != null)
|
||
DestroyImmediate(_previewTexture);
|
||
}
|
||
|
||
private void OnGUI()
|
||
{
|
||
EnsureStyles();
|
||
|
||
DrawHeader();
|
||
|
||
_scrollPos.x = 0f;
|
||
_scrollPos = EditorGUILayout.BeginScrollView(_scrollPos, false, false);
|
||
_scrollPos.x = 0f;
|
||
|
||
EditorGUI.BeginChangeCheck();
|
||
|
||
DrawInputSection();
|
||
|
||
GUILayout.Space(8);
|
||
|
||
DrawOutputSection();
|
||
|
||
if(EditorGUI.EndChangeCheck())
|
||
MarkPreviewDirty();
|
||
|
||
GUILayout.Space(8);
|
||
|
||
DrawPreviewSection();
|
||
|
||
EditorGUILayout.EndScrollView();
|
||
|
||
DrawValidationSection();
|
||
DrawBottomBar();
|
||
|
||
if(_previewExpanded && _autoPreview && _previewDirty && Event.current.type == EventType.Repaint)
|
||
QueuePreviewGeneration();
|
||
}
|
||
|
||
private void ApplyDefaultFloatingSizeOnce()
|
||
{
|
||
string key = ProjectPrefsKey("DefaultWindowSizeApplied");
|
||
|
||
if(EditorPrefs.GetBool(key, false))
|
||
return;
|
||
|
||
Rect mainWindow = EditorGUIUtility.GetMainWindowPosition();
|
||
|
||
float width = Mathf.Clamp(mainWindow.width * 0.45f, 640f, 900f);
|
||
float height = Mathf.Clamp(mainWindow.height * 0.70f, 520f, 820f);
|
||
|
||
position = new Rect(
|
||
mainWindow.x + (mainWindow.width - width) * 0.5f,
|
||
mainWindow.y + (mainWindow.height - height) * 0.5f,
|
||
width,
|
||
height
|
||
);
|
||
|
||
EditorPrefs.SetBool(key, true);
|
||
}
|
||
|
||
private void DrawHeader()
|
||
{
|
||
EditorGUILayout.BeginVertical(_cardStyle);
|
||
|
||
EditorGUILayout.LabelField("Texture Channel Packer", _titleStyle);
|
||
|
||
EditorGUILayout.LabelField(
|
||
"Pack channels from different textures into one mask texture.",
|
||
_subtitleStyle
|
||
);
|
||
|
||
EditorGUILayout.EndVertical();
|
||
}
|
||
|
||
private void DrawInputSection()
|
||
{
|
||
EditorGUILayout.BeginVertical(_cardStyle);
|
||
|
||
EditorGUILayout.LabelField("Input Channels", _sectionTitleStyle);
|
||
EditorGUILayout.LabelField("Each slot writes one source channel into one output channel. Empty texture slots write a constant black or white value.", _miniHelpStyle);
|
||
|
||
GUILayout.Space(6);
|
||
|
||
DrawPackerRow("Slot 1", ref _sourceTexture0, ref _sourceChannel0, ref _sourceChannel0Invert, ref _targetChannel0, ref _fallbackColor0);
|
||
DrawPackerRow("Slot 2", ref _sourceTexture1, ref _sourceChannel1, ref _sourceChannel1Invert, ref _targetChannel1, ref _fallbackColor1);
|
||
DrawPackerRow("Slot 3", ref _sourceTexture2, ref _sourceChannel2, ref _sourceChannel2Invert, ref _targetChannel2, ref _fallbackColor2);
|
||
DrawPackerRow("Slot 4", ref _sourceTexture3, ref _sourceChannel3, ref _sourceChannel3Invert, ref _targetChannel3, ref _fallbackColor3);
|
||
|
||
EditorGUILayout.EndVertical();
|
||
}
|
||
|
||
private void DrawPackerRow
|
||
(
|
||
string title,
|
||
ref Texture2D texture,
|
||
ref TextureChannel sourceChannel,
|
||
ref bool invert,
|
||
ref TextureChannel targetChannel,
|
||
ref ChannelColor fallbackColor
|
||
)
|
||
{
|
||
float oldLabelWidth = EditorGUIUtility.labelWidth;
|
||
EditorGUIUtility.labelWidth = IsNarrowLayout ? 58f : 70f;
|
||
|
||
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
||
|
||
EditorGUILayout.BeginHorizontal();
|
||
|
||
Rect colorRect = GUILayoutUtility.GetRect(8, 8, GUILayout.Width(8), GUILayout.Height(18));
|
||
EditorGUI.DrawRect(colorRect, GetChannelColor(targetChannel));
|
||
|
||
EditorGUILayout.LabelField(title, EditorStyles.boldLabel);
|
||
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
if(IsNarrowLayout)
|
||
DrawPackerRowNarrow(ref texture, ref sourceChannel, ref invert, ref targetChannel, ref fallbackColor);
|
||
else
|
||
DrawPackerRowWide(ref texture, ref sourceChannel, ref invert, ref targetChannel, ref fallbackColor);
|
||
|
||
EditorGUILayout.EndVertical();
|
||
|
||
EditorGUIUtility.labelWidth = oldLabelWidth;
|
||
}
|
||
|
||
private void DrawPackerRowWide
|
||
(
|
||
ref Texture2D texture,
|
||
ref TextureChannel sourceChannel,
|
||
ref bool invert,
|
||
ref TextureChannel targetChannel,
|
||
ref ChannelColor fallbackColor
|
||
)
|
||
{
|
||
EditorGUILayout.BeginHorizontal(GUILayout.Height(76));
|
||
|
||
texture = (Texture2D)EditorGUILayout.ObjectField(
|
||
texture,
|
||
typeof(Texture2D),
|
||
false,
|
||
GUILayout.Width(72),
|
||
GUILayout.Height(72)
|
||
);
|
||
|
||
GUILayout.Space(8);
|
||
|
||
EditorGUILayout.BeginVertical(GUILayout.Height(72));
|
||
|
||
EditorGUILayout.BeginHorizontal(GUILayout.Height(EditorGUIUtility.singleLineHeight));
|
||
|
||
if(texture != null)
|
||
{
|
||
sourceChannel = (TextureChannel)EditorGUILayout.EnumPopup(
|
||
new GUIContent("From", "Source channel read from the input texture."),
|
||
sourceChannel
|
||
);
|
||
|
||
invert = EditorGUILayout.ToggleLeft(
|
||
new GUIContent("Invert", "Invert the source value before writing it."),
|
||
invert,
|
||
GUILayout.Width(72)
|
||
);
|
||
}
|
||
else
|
||
{
|
||
fallbackColor = (ChannelColor)EditorGUILayout.EnumPopup(
|
||
new GUIContent("Fill", "Constant value used when no texture is assigned."),
|
||
fallbackColor
|
||
);
|
||
|
||
using(new EditorGUI.DisabledScope(true))
|
||
EditorGUILayout.ToggleLeft("Invert", false, GUILayout.Width(72));
|
||
}
|
||
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
targetChannel = (TextureChannel)EditorGUILayout.EnumPopup(
|
||
new GUIContent("Write To", "Output channel this slot writes into."),
|
||
targetChannel
|
||
);
|
||
|
||
GUILayout.FlexibleSpace();
|
||
|
||
DrawSlotStatus(texture);
|
||
|
||
EditorGUILayout.EndVertical();
|
||
|
||
EditorGUILayout.EndHorizontal();
|
||
}
|
||
|
||
private void DrawPackerRowNarrow(
|
||
ref Texture2D texture,
|
||
ref TextureChannel sourceChannel,
|
||
ref bool invert,
|
||
ref TextureChannel targetChannel,
|
||
ref ChannelColor fallbackColor
|
||
)
|
||
{
|
||
EditorGUILayout.BeginHorizontal(GUILayout.Height(68));
|
||
|
||
texture = (Texture2D)EditorGUILayout.ObjectField(
|
||
texture,
|
||
typeof(Texture2D),
|
||
false,
|
||
GUILayout.Width(64),
|
||
GUILayout.Height(64)
|
||
);
|
||
|
||
GUILayout.Space(6);
|
||
|
||
EditorGUILayout.BeginVertical(GUILayout.Height(64));
|
||
|
||
if(texture != null)
|
||
{
|
||
sourceChannel = (TextureChannel)EditorGUILayout.EnumPopup(
|
||
new GUIContent("From", "Source channel read from the input texture."),
|
||
sourceChannel
|
||
);
|
||
|
||
targetChannel = (TextureChannel)EditorGUILayout.EnumPopup(
|
||
new GUIContent("To", "Output channel this slot writes into."),
|
||
targetChannel
|
||
);
|
||
|
||
invert = EditorGUILayout.ToggleLeft(
|
||
new GUIContent("Invert", "Invert the source value before writing it."),
|
||
invert
|
||
);
|
||
}
|
||
else
|
||
{
|
||
fallbackColor = (ChannelColor)EditorGUILayout.EnumPopup(
|
||
new GUIContent("Fill", "Constant value used when no texture is assigned."),
|
||
fallbackColor
|
||
);
|
||
|
||
targetChannel = (TextureChannel)EditorGUILayout.EnumPopup(
|
||
new GUIContent("To", "Output channel this slot writes into."),
|
||
targetChannel
|
||
);
|
||
|
||
GUILayout.Space(EditorGUIUtility.singleLineHeight);
|
||
}
|
||
|
||
EditorGUILayout.EndVertical();
|
||
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
DrawSlotStatus(texture);
|
||
}
|
||
|
||
private void DrawSlotStatus(Texture2D texture)
|
||
{
|
||
string text = texture == null
|
||
? "No texture assigned - this slot writes a constant value."
|
||
: texture.name + " | " + texture.width + " × " + texture.height + " px";
|
||
|
||
Rect rect = GUILayoutUtility.GetRect(
|
||
GUIContent.none,
|
||
_slotStatusStyle,
|
||
GUILayout.Height(18),
|
||
GUILayout.ExpandWidth(true)
|
||
);
|
||
|
||
EditorGUI.LabelField(rect, text, _slotStatusStyle);
|
||
}
|
||
|
||
private void DrawOutputSection()
|
||
{
|
||
float oldLabelWidth = EditorGUIUtility.labelWidth;
|
||
EditorGUIUtility.labelWidth = IsNarrowLayout ? 52f : 70f;
|
||
|
||
EditorGUILayout.BeginVertical(_cardStyle);
|
||
|
||
EditorGUILayout.LabelField("Output", _sectionTitleStyle);
|
||
|
||
if(IsNarrowLayout)
|
||
{
|
||
EditorGUILayout.BeginHorizontal();
|
||
|
||
DrawTextureSizePopup(new GUIContent("Width", "Output texture width."), ref _outputWidth);
|
||
DrawTextureSizePopup(new GUIContent("Height", "Output texture height."), ref _outputHeight);
|
||
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
if(GUILayout.Button("Match First Input"))
|
||
MatchFirstInputSize();
|
||
}
|
||
else
|
||
{
|
||
EditorGUILayout.BeginHorizontal();
|
||
|
||
DrawTextureSizePopup(new GUIContent("Width", "Output texture width."), ref _outputWidth);
|
||
DrawTextureSizePopup(new GUIContent("Height", "Output texture height."), ref _outputHeight);
|
||
|
||
if(GUILayout.Button("Match First Input", GUILayout.Width(130)))
|
||
MatchFirstInputSize();
|
||
|
||
EditorGUILayout.EndHorizontal();
|
||
}
|
||
|
||
GUILayout.Space(4);
|
||
|
||
EditorGUILayout.BeginHorizontal();
|
||
|
||
_dataTexture = EditorGUILayout.ToggleLeft(
|
||
new GUIContent("Data Texture", "Disables sRGB on the output texture. Recommended for masks and packed data."),
|
||
_dataTexture
|
||
);
|
||
|
||
_generateMipMaps = EditorGUILayout.ToggleLeft(
|
||
new GUIContent("Mip Maps", "Generate mip maps for the output texture."),
|
||
_generateMipMaps
|
||
);
|
||
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
GUILayout.Space(8);
|
||
|
||
EditorGUILayout.LabelField(
|
||
new GUIContent("Save As", "Project-relative output path. Existing files are overwritten."),
|
||
EditorStyles.boldLabel
|
||
);
|
||
|
||
EditorGUILayout.BeginHorizontal();
|
||
|
||
GUI.SetNextControlName(OutputPathControlName);
|
||
|
||
EditorGUI.BeginChangeCheck();
|
||
|
||
_outputAssetPathEdit = EditorGUILayout.TextField(_outputAssetPathEdit);
|
||
|
||
if(EditorGUI.EndChangeCheck())
|
||
QueueOutputPathEditApply();
|
||
|
||
bool outputPathFieldFocused = GUI.GetNameOfFocusedControl() == OutputPathControlName;
|
||
|
||
if(_outputAssetPathFieldFocused && !outputPathFieldFocused)
|
||
ApplyOutputPathEdit();
|
||
|
||
_outputAssetPathFieldFocused = outputPathFieldFocused;
|
||
|
||
if(GUILayout.Button("Browse", GUILayout.Width(72)))
|
||
SelectOutputFile();
|
||
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
EditorGUILayout.BeginHorizontal();
|
||
|
||
GUILayout.Label("Format", GUILayout.Width(EditorGUIUtility.labelWidth));
|
||
|
||
EditorGUI.BeginChangeCheck();
|
||
|
||
_outputFormat = (OutputFormat)EditorGUILayout.EnumPopup(_outputFormat);
|
||
|
||
if(EditorGUI.EndChangeCheck())
|
||
{
|
||
ApplyOutputPathEdit();
|
||
_outputAssetPath = ChangeOutputPathExtension(_outputAssetPath, _outputFormat);
|
||
_outputAssetPathEdit = _outputAssetPath;
|
||
}
|
||
|
||
EditorGUILayout.EndHorizontal();
|
||
|
||
string finalPath = GetOutputAssetPath();
|
||
|
||
if(File.Exists(finalPath))
|
||
EditorGUILayout.HelpBox("A texture already exists at this path and will be overwritten.", MessageType.Warning);
|
||
else
|
||
EditorGUILayout.LabelField("Output Path", finalPath, EditorStyles.miniLabel);
|
||
|
||
EditorGUILayout.EndVertical();
|
||
|
||
EditorGUIUtility.labelWidth = oldLabelWidth;
|
||
}
|
||
|
||
private void DrawPreviewSection()
|
||
{
|
||
EditorGUILayout.BeginVertical(_cardStyle);
|
||
|
||
if(IsNarrowLayout)
|
||
{
|
||
_previewExpanded = EditorGUILayout.ToggleLeft(
|
||
new GUIContent("Show Preview", "Show or hide the packed texture preview."),
|
||
_previewExpanded,
|
||
EditorStyles.boldLabel
|
||
);
|
||
|
||
using(new EditorGUI.DisabledScope(!_previewExpanded))
|
||
{
|
||
EditorGUILayout.BeginHorizontal();
|
||
|
||
EditorGUI.BeginChangeCheck();
|
||
|
||
_previewMode = (PreviewMode)EditorGUILayout.EnumPopup(_previewMode);
|
||
_autoPreview = GUILayout.Toggle(_autoPreview, "Auto", "Button", GUILayout.Width(52));
|
||
|
||
if(EditorGUI.EndChangeCheck())
|
||
MarkPreviewDirty();
|
||
|
||
if(GUILayout.Button("Refresh", GUILayout.Width(72)))
|
||
QueuePreviewGeneration();
|
||
|
||
EditorGUILayout.EndHorizontal();
|
||
}
|
||
}
|
||
else
|
||
{
|
||
EditorGUILayout.BeginHorizontal();
|
||
|
||
_previewExpanded = EditorGUILayout.ToggleLeft(
|
||
new GUIContent("Show Preview", "Show or hide the packed texture preview."),
|
||
_previewExpanded,
|
||
EditorStyles.boldLabel,
|
||
GUILayout.Width(130)
|
||
);
|
||
|
||
GUILayout.FlexibleSpace();
|
||
|
||
using(new EditorGUI.DisabledScope(!_previewExpanded))
|
||
{
|
||
EditorGUI.BeginChangeCheck();
|
||
|
||
_previewMode = (PreviewMode)EditorGUILayout.EnumPopup(_previewMode, GUILayout.Width(90));
|
||
_autoPreview = GUILayout.Toggle(_autoPreview, "Auto", "Button", GUILayout.Width(52));
|
||
|
||
if(EditorGUI.EndChangeCheck())
|
||
MarkPreviewDirty();
|
||
|
||
if(GUILayout.Button("Refresh", GUILayout.Width(72)))
|
||
QueuePreviewGeneration();
|
||
}
|
||
|
||
EditorGUILayout.EndHorizontal();
|
||
}
|
||
|
||
if(!_previewExpanded)
|
||
{
|
||
EditorGUILayout.EndVertical();
|
||
return;
|
||
}
|
||
|
||
EditorGUILayout.LabelField("Preview of the packed output texture before saving.", _miniHelpStyle);
|
||
|
||
EditorGUI.BeginChangeCheck();
|
||
|
||
_previewSizePercent = EditorGUILayout.Slider(
|
||
new GUIContent("Preview Size", "100% uses the available content width. Height keeps the texture aspect ratio."),
|
||
_previewSizePercent,
|
||
25f,
|
||
100f
|
||
);
|
||
|
||
if(EditorGUI.EndChangeCheck())
|
||
Repaint();
|
||
|
||
if(_previewTexture == null && Event.current.type == EventType.Repaint)
|
||
QueuePreviewGeneration();
|
||
|
||
if(_previewTexture != null)
|
||
{
|
||
float aspect = _previewTexture.width / (float)_previewTexture.height;
|
||
|
||
float contentWidth = Mathf.Max(64f, position.width - 52f);
|
||
float previewWidth = Mathf.Floor(contentWidth * (_previewSizePercent / 100f));
|
||
float previewHeight = Mathf.Floor(previewWidth / aspect);
|
||
|
||
previewWidth = Mathf.Max(32f, previewWidth);
|
||
previewHeight = Mathf.Max(32f, previewHeight);
|
||
|
||
EditorGUILayout.BeginHorizontal();
|
||
GUILayout.FlexibleSpace();
|
||
|
||
Rect rect = GUILayoutUtility.GetRect(
|
||
previewWidth,
|
||
previewHeight,
|
||
GUILayout.Width(previewWidth),
|
||
GUILayout.Height(previewHeight)
|
||
);
|
||
|
||
EditorGUI.DrawPreviewTexture(
|
||
rect,
|
||
_previewTexture,
|
||
null,
|
||
ScaleMode.ScaleToFit
|
||
);
|
||
|
||
GUILayout.FlexibleSpace();
|
||
EditorGUILayout.EndHorizontal();
|
||
}
|
||
|
||
EditorGUILayout.EndVertical();
|
||
}
|
||
|
||
private void QueuePreviewGeneration()
|
||
{
|
||
if(_previewGenerationQueued)
|
||
return;
|
||
|
||
_previewGenerationQueued = true;
|
||
EditorApplication.delayCall += GenerateQueuedPreviewTexture;
|
||
}
|
||
|
||
private void GenerateQueuedPreviewTexture()
|
||
{
|
||
EditorApplication.delayCall -= GenerateQueuedPreviewTexture;
|
||
_previewGenerationQueued = false;
|
||
|
||
if(this == null || !_previewExpanded)
|
||
return;
|
||
|
||
try
|
||
{
|
||
GeneratePreviewTexture();
|
||
}
|
||
catch(System.Exception exception)
|
||
{
|
||
_previewDirty = false;
|
||
Debug.LogException(exception);
|
||
}
|
||
}
|
||
|
||
private void GeneratePreviewTexture()
|
||
{
|
||
int outputWidth = (int)_outputWidth;
|
||
int outputHeight = (int)_outputHeight;
|
||
|
||
int previewWidth;
|
||
int previewHeight;
|
||
|
||
GetPreviewSize(outputWidth, outputHeight, out previewWidth, out previewHeight);
|
||
|
||
Dictionary<string, bool> readableStates = new Dictionary<string, bool>();
|
||
|
||
try
|
||
{
|
||
MakeReadableIfNeeded(_sourceTexture0, readableStates);
|
||
MakeReadableIfNeeded(_sourceTexture1, readableStates);
|
||
MakeReadableIfNeeded(_sourceTexture2, readableStates);
|
||
MakeReadableIfNeeded(_sourceTexture3, readableStates);
|
||
|
||
if(_previewTexture == null || _previewTexture.width != previewWidth || _previewTexture.height != previewHeight)
|
||
{
|
||
if(_previewTexture != null)
|
||
DestroyImmediate(_previewTexture);
|
||
|
||
_previewTexture = new Texture2D(previewWidth, previewHeight, UnityEngine.TextureFormat.RGBA32, false, true);
|
||
_previewTexture.hideFlags = HideFlags.HideAndDontSave;
|
||
}
|
||
|
||
Color32[] pixels = GeneratePackedPixelsFast(previewWidth, previewHeight, false);
|
||
|
||
for(int i = 0; i < pixels.Length; i++)
|
||
pixels[i] = ConvertPreviewColor(pixels[i]);
|
||
|
||
_previewTexture.SetPixels32(pixels);
|
||
_previewTexture.Apply(false, false);
|
||
|
||
_previewDirty = false;
|
||
Repaint();
|
||
}
|
||
finally
|
||
{
|
||
RestoreReadableStates(readableStates);
|
||
}
|
||
}
|
||
|
||
private static void GetPreviewSize(int outputWidth, int outputHeight, out int previewWidth, out int previewHeight)
|
||
{
|
||
if(outputWidth >= outputHeight)
|
||
{
|
||
previewWidth = Mathf.Min(outputWidth, MaxPreviewSize);
|
||
previewHeight = Mathf.Max(1, Mathf.RoundToInt(previewWidth * (outputHeight / (float)outputWidth)));
|
||
}
|
||
else
|
||
{
|
||
previewHeight = Mathf.Min(outputHeight, MaxPreviewSize);
|
||
previewWidth = Mathf.Max(1, Mathf.RoundToInt(previewHeight * (outputWidth / (float)outputHeight)));
|
||
}
|
||
}
|
||
|
||
private Color32 ConvertPreviewColor(Color32 color)
|
||
{
|
||
switch(_previewMode)
|
||
{
|
||
case PreviewMode.Red:
|
||
return new Color32(color.r, color.r, color.r, 255);
|
||
|
||
case PreviewMode.Green:
|
||
return new Color32(color.g, color.g, color.g, 255);
|
||
|
||
case PreviewMode.Blue:
|
||
return new Color32(color.b, color.b, color.b, 255);
|
||
|
||
case PreviewMode.Alpha:
|
||
return new Color32(color.a, color.a, color.a, 255);
|
||
|
||
default:
|
||
return new Color32(color.r, color.g, color.b, 255);
|
||
}
|
||
}
|
||
|
||
private void MarkPreviewDirty()
|
||
{
|
||
_previewDirty = true;
|
||
}
|
||
|
||
private Color32[] GeneratePackedPixelsFast(int width, int height, bool showProgress)
|
||
{
|
||
SourceCache cache0 = CreateSourceCache(_sourceTexture0, _sourceChannel0, _targetChannel0, _fallbackColor0, _sourceChannel0Invert);
|
||
SourceCache cache1 = CreateSourceCache(_sourceTexture1, _sourceChannel1, _targetChannel1, _fallbackColor1, _sourceChannel1Invert);
|
||
SourceCache cache2 = CreateSourceCache(_sourceTexture2, _sourceChannel2, _targetChannel2, _fallbackColor2, _sourceChannel2Invert);
|
||
SourceCache cache3 = CreateSourceCache(_sourceTexture3, _sourceChannel3, _targetChannel3, _fallbackColor3, _sourceChannel3Invert);
|
||
|
||
Color32[] pixels = new Color32[width * height];
|
||
|
||
for(int y = 0; y < height; y++)
|
||
{
|
||
if(showProgress && (y == 0 || y % 64 == 0 || y == height - 1))
|
||
{
|
||
float progress = y / (float)height;
|
||
EditorUtility.DisplayProgressBar("Texture Channel Packer", "Packing raw pixels...", progress);
|
||
}
|
||
|
||
for(int x = 0; x < width; x++)
|
||
{
|
||
byte r = 0;
|
||
byte g = 0;
|
||
byte b = 0;
|
||
byte a = 255;
|
||
|
||
PackRawSlot(cache0, x, y, width, height, ref r, ref g, ref b, ref a);
|
||
PackRawSlot(cache1, x, y, width, height, ref r, ref g, ref b, ref a);
|
||
PackRawSlot(cache2, x, y, width, height, ref r, ref g, ref b, ref a);
|
||
PackRawSlot(cache3, x, y, width, height, ref r, ref g, ref b, ref a);
|
||
|
||
pixels[y * width + x] = new Color32(r, g, b, a);
|
||
}
|
||
}
|
||
|
||
return pixels;
|
||
}
|
||
|
||
private static SourceCache CreateSourceCache(
|
||
Texture2D texture,
|
||
TextureChannel sourceChannel,
|
||
TextureChannel targetChannel,
|
||
ChannelColor fallbackColor,
|
||
bool invert
|
||
)
|
||
{
|
||
SourceCache cache = new SourceCache();
|
||
|
||
cache.hasTexture = texture != null;
|
||
cache.sourceChannel = sourceChannel;
|
||
cache.targetChannel = targetChannel;
|
||
cache.fallbackColor = fallbackColor;
|
||
cache.invert = invert;
|
||
|
||
if(texture != null)
|
||
{
|
||
cache.width = texture.width;
|
||
cache.height = texture.height;
|
||
cache.pixels = texture.GetPixels32();
|
||
}
|
||
|
||
return cache;
|
||
}
|
||
|
||
private static void PackRawSlot(
|
||
SourceCache cache,
|
||
int outputX,
|
||
int outputY,
|
||
int outputWidth,
|
||
int outputHeight,
|
||
ref byte r,
|
||
ref byte g,
|
||
ref byte b,
|
||
ref byte a
|
||
)
|
||
{
|
||
byte value = ReadRawValue(cache, outputX, outputY, outputWidth, outputHeight);
|
||
|
||
switch(cache.targetChannel)
|
||
{
|
||
case TextureChannel.Green:
|
||
g = value;
|
||
break;
|
||
|
||
case TextureChannel.Blue:
|
||
b = value;
|
||
break;
|
||
|
||
case TextureChannel.Alpha:
|
||
a = value;
|
||
break;
|
||
|
||
default:
|
||
r = value;
|
||
break;
|
||
}
|
||
}
|
||
|
||
private static byte ReadRawValue(
|
||
SourceCache cache,
|
||
int outputX,
|
||
int outputY,
|
||
int outputWidth,
|
||
int outputHeight
|
||
)
|
||
{
|
||
if(!cache.hasTexture || cache.pixels == null || cache.width <= 0 || cache.height <= 0)
|
||
{
|
||
byte fallback = cache.fallbackColor == ChannelColor.White ? (byte)255 : (byte)0;
|
||
return cache.invert ? (byte)(255 - fallback) : fallback;
|
||
}
|
||
|
||
int sourceX = outputWidth <= 1
|
||
? 0
|
||
: (int)((long)outputX * (cache.width - 1) / (outputWidth - 1));
|
||
|
||
int sourceY = outputHeight <= 1
|
||
? 0
|
||
: (int)((long)outputY * (cache.height - 1) / (outputHeight - 1));
|
||
|
||
sourceX = Mathf.Clamp(sourceX, 0, cache.width - 1);
|
||
sourceY = Mathf.Clamp(sourceY, 0, cache.height - 1);
|
||
|
||
Color32 color = cache.pixels[sourceY * cache.width + sourceX];
|
||
|
||
byte value;
|
||
|
||
switch(cache.sourceChannel)
|
||
{
|
||
case TextureChannel.Green:
|
||
value = color.g;
|
||
break;
|
||
|
||
case TextureChannel.Blue:
|
||
value = color.b;
|
||
break;
|
||
|
||
case TextureChannel.Alpha:
|
||
value = color.a;
|
||
break;
|
||
|
||
default:
|
||
value = color.r;
|
||
break;
|
||
}
|
||
|
||
return cache.invert ? (byte)(255 - value) : value;
|
||
}
|
||
|
||
private void DrawTextureSizePopup(GUIContent label, ref TextureSize value)
|
||
{
|
||
int index = System.Array.IndexOf(TextureSizeValues, value);
|
||
|
||
if(index < 0)
|
||
index = System.Array.IndexOf(TextureSizeValues, TextureSize.Size1024);
|
||
|
||
index = EditorGUILayout.Popup(label, index, TextureSizeLabels);
|
||
value = TextureSizeValues[index];
|
||
}
|
||
|
||
private void DrawValidationSection()
|
||
{
|
||
EditorGUILayout.BeginVertical(_cardStyle);
|
||
|
||
bool hasMessages = false;
|
||
|
||
if(HasDuplicateOutputChannels())
|
||
{
|
||
hasMessages = true;
|
||
EditorGUILayout.HelpBox("Every output channel must be unique. Two or more slots write into the same channel.", MessageType.Error);
|
||
}
|
||
|
||
if(!IsValidOutputAssetPath())
|
||
{
|
||
hasMessages = true;
|
||
EditorGUILayout.HelpBox("Save As must point to a file inside the project's Assets folder.", MessageType.Error);
|
||
}
|
||
|
||
if(string.IsNullOrWhiteSpace(GetOutputFileName()))
|
||
{
|
||
hasMessages = true;
|
||
EditorGUILayout.HelpBox("Enter a valid file name before creating the texture.", MessageType.Error);
|
||
}
|
||
|
||
if(!HaveMatchingAspectRatios())
|
||
{
|
||
hasMessages = true;
|
||
EditorGUILayout.HelpBox("Input textures use different aspect ratios. Raw pixel packing still works, but different sizes/aspects will remap pixels without filtering.", MessageType.Warning);
|
||
}
|
||
|
||
if(!hasMessages)
|
||
EditorGUILayout.HelpBox("Ready to create texture.", MessageType.Info);
|
||
|
||
EditorGUILayout.EndVertical();
|
||
}
|
||
|
||
private void DrawBottomBar()
|
||
{
|
||
EditorGUILayout.BeginVertical(_cardStyle);
|
||
|
||
using(new EditorGUI.DisabledScope(!CanCreateTexture()))
|
||
{
|
||
if(GUILayout.Button("Create Texture", _createButtonStyle, GUILayout.Height(38)))
|
||
CreateTexture();
|
||
}
|
||
|
||
EditorGUILayout.EndVertical();
|
||
}
|
||
|
||
private void CreateTexture()
|
||
{
|
||
ApplyOutputPathEdit();
|
||
|
||
int outputWidth = (int)_outputWidth;
|
||
int outputHeight = (int)_outputHeight;
|
||
|
||
string outputPath = GetOutputAssetPath();
|
||
|
||
if(!IsValidOutputAssetPath())
|
||
{
|
||
EditorUtility.DisplayDialog("Invalid Output Path", "Please choose a file inside the project's Assets folder.", "OK");
|
||
return;
|
||
}
|
||
|
||
Texture2D outputTexture = null;
|
||
Dictionary<string, bool> readableStates = new Dictionary<string, bool>();
|
||
|
||
try
|
||
{
|
||
EditorUtility.DisplayProgressBar("Texture Channel Packer", "Preparing source textures...", 0.05f);
|
||
|
||
MakeReadableIfNeeded(_sourceTexture0, readableStates);
|
||
MakeReadableIfNeeded(_sourceTexture1, readableStates);
|
||
MakeReadableIfNeeded(_sourceTexture2, readableStates);
|
||
MakeReadableIfNeeded(_sourceTexture3, readableStates);
|
||
|
||
outputTexture = new Texture2D(outputWidth, outputHeight, UnityEngine.TextureFormat.RGBA32, false, true);
|
||
outputTexture.SetPixels32(GeneratePackedPixelsFast(outputWidth, outputHeight, true));
|
||
outputTexture.Apply(false, false);
|
||
|
||
EditorUtility.DisplayProgressBar("Texture Channel Packer", "Saving texture...", 0.95f);
|
||
|
||
SaveOutputTexture(outputTexture, outputPath, Mathf.Max(outputWidth, outputHeight));
|
||
|
||
EditorUtility.FocusProjectWindow();
|
||
|
||
Object savedAsset = AssetDatabase.LoadAssetAtPath<Object>(outputPath);
|
||
Selection.activeObject = savedAsset;
|
||
EditorGUIUtility.PingObject(savedAsset);
|
||
}
|
||
finally
|
||
{
|
||
RestoreReadableStates(readableStates);
|
||
|
||
if(outputTexture != null)
|
||
DestroyImmediate(outputTexture);
|
||
|
||
EditorUtility.ClearProgressBar();
|
||
}
|
||
}
|
||
|
||
private bool CanCreateTexture()
|
||
{
|
||
return !HasDuplicateOutputChannels() && IsValidOutputAssetPath() && !string.IsNullOrWhiteSpace(GetOutputFileName());
|
||
}
|
||
|
||
private bool HasDuplicateOutputChannels()
|
||
{
|
||
return CountChannelUsage(_targetChannel0) > 1 ||
|
||
CountChannelUsage(_targetChannel1) > 1 ||
|
||
CountChannelUsage(_targetChannel2) > 1 ||
|
||
CountChannelUsage(_targetChannel3) > 1;
|
||
}
|
||
|
||
private int CountChannelUsage(TextureChannel channel)
|
||
{
|
||
int count = 0;
|
||
|
||
if(_targetChannel0 == channel)
|
||
count++;
|
||
|
||
if(_targetChannel1 == channel)
|
||
count++;
|
||
|
||
if(_targetChannel2 == channel)
|
||
count++;
|
||
|
||
if(_targetChannel3 == channel)
|
||
count++;
|
||
|
||
return count;
|
||
}
|
||
|
||
private bool HaveMatchingAspectRatios()
|
||
{
|
||
List<Texture2D> textures = new List<Texture2D>();
|
||
|
||
if(_sourceTexture0 != null)
|
||
textures.Add(_sourceTexture0);
|
||
|
||
if(_sourceTexture1 != null)
|
||
textures.Add(_sourceTexture1);
|
||
|
||
if(_sourceTexture2 != null)
|
||
textures.Add(_sourceTexture2);
|
||
|
||
if(_sourceTexture3 != null)
|
||
textures.Add(_sourceTexture3);
|
||
|
||
if(textures.Count <= 1)
|
||
return true;
|
||
|
||
float aspect = textures[0].width / (float)textures[0].height;
|
||
|
||
for(int i = 1; i < textures.Count; i++)
|
||
{
|
||
float otherAspect = textures[i].width / (float)textures[i].height;
|
||
|
||
if(Mathf.Abs(aspect - otherAspect) > 0.001f)
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
private void MatchFirstInputSize()
|
||
{
|
||
Texture2D texture = null;
|
||
|
||
if(_sourceTexture0 != null)
|
||
texture = _sourceTexture0;
|
||
else if(_sourceTexture1 != null)
|
||
texture = _sourceTexture1;
|
||
else if(_sourceTexture2 != null)
|
||
texture = _sourceTexture2;
|
||
else if(_sourceTexture3 != null)
|
||
texture = _sourceTexture3;
|
||
|
||
if(texture == null)
|
||
return;
|
||
|
||
_outputWidth = ClosestTextureSize(texture.width);
|
||
_outputHeight = ClosestTextureSize(texture.height);
|
||
MarkPreviewDirty();
|
||
}
|
||
|
||
private static TextureSize ClosestTextureSize(int size)
|
||
{
|
||
TextureSize closest = TextureSize.Size1024;
|
||
int bestDistance = int.MaxValue;
|
||
|
||
for(int i = 0; i < TextureSizeValues.Length; i++)
|
||
{
|
||
int current = (int)TextureSizeValues[i];
|
||
int distance = Mathf.Abs(current - size);
|
||
|
||
if(distance < bestDistance)
|
||
{
|
||
bestDistance = distance;
|
||
closest = TextureSizeValues[i];
|
||
}
|
||
}
|
||
|
||
return closest;
|
||
}
|
||
|
||
private void SelectOutputFile()
|
||
{
|
||
ApplyOutputPathEdit();
|
||
|
||
string currentPath = GetOutputAssetPath();
|
||
string currentFolder = Path.GetDirectoryName(currentPath);
|
||
string currentName = Path.GetFileNameWithoutExtension(currentPath);
|
||
string extension = GetFileExtension(_outputFormat).TrimStart('.');
|
||
|
||
if(string.IsNullOrEmpty(currentFolder))
|
||
currentFolder = DefaultOutputFolder;
|
||
|
||
currentFolder = NormalizeAssetPath(currentFolder);
|
||
|
||
if(!IsProjectRelativeAssetPath(currentFolder))
|
||
currentFolder = DefaultOutputFolder;
|
||
|
||
string selectedPath = EditorUtility.SaveFilePanelInProject(
|
||
"Save Packed Texture",
|
||
currentName,
|
||
extension,
|
||
"Choose where to save the packed texture.",
|
||
currentFolder
|
||
);
|
||
|
||
if(string.IsNullOrEmpty(selectedPath))
|
||
return;
|
||
|
||
_outputAssetPath = NormalizeAssetPath(selectedPath);
|
||
_outputAssetPathEdit = _outputAssetPath;
|
||
CancelPendingOutputPathEdit();
|
||
UpdateOutputFormatFromPathExtension();
|
||
}
|
||
|
||
private bool IsValidOutputAssetPath()
|
||
{
|
||
string path = GetOutputAssetPath();
|
||
string folder = GetOutputFolderPath();
|
||
|
||
return IsProjectRelativeAssetPath(path) &&
|
||
IsProjectRelativeAssetPath(folder) &&
|
||
!AssetDatabase.IsValidFolder(path);
|
||
}
|
||
|
||
private string GetOutputAssetPath()
|
||
{
|
||
string path = NormalizeAssetPath(_outputAssetPath);
|
||
|
||
if(string.IsNullOrEmpty(path) || path == "Assets")
|
||
path = DefaultOutputFolder + "/" + DefaultOutputName + GetFileExtension(_outputFormat);
|
||
|
||
string extension = GetFileExtension(_outputFormat);
|
||
|
||
if(string.IsNullOrEmpty(Path.GetExtension(path)))
|
||
path += extension;
|
||
else
|
||
path = Path.ChangeExtension(path, extension).Replace("\\", "/");
|
||
|
||
string folder = Path.GetDirectoryName(path);
|
||
|
||
if(string.IsNullOrEmpty(folder))
|
||
folder = DefaultOutputFolder;
|
||
|
||
folder = NormalizeAssetPath(folder);
|
||
|
||
string filename = FilterFilename(Path.GetFileNameWithoutExtension(path));
|
||
|
||
if(string.IsNullOrEmpty(filename))
|
||
filename = DefaultOutputName;
|
||
|
||
return folder + "/" + filename + extension;
|
||
}
|
||
|
||
private string GetOutputFolderPath()
|
||
{
|
||
string path = GetOutputAssetPath();
|
||
string folder = Path.GetDirectoryName(path);
|
||
|
||
if(string.IsNullOrEmpty(folder))
|
||
return DefaultOutputFolder;
|
||
|
||
return NormalizeAssetPath(folder);
|
||
}
|
||
|
||
private string GetOutputFileName()
|
||
{
|
||
return FilterFilename(Path.GetFileNameWithoutExtension(GetOutputAssetPath()));
|
||
}
|
||
|
||
private static string ChangeOutputPathExtension(string path, OutputFormat format)
|
||
{
|
||
path = NormalizeAssetPath(path);
|
||
|
||
if(string.IsNullOrEmpty(path))
|
||
path = DefaultOutputFolder + "/" + DefaultOutputName;
|
||
|
||
return Path.ChangeExtension(path, GetFileExtension(format)).Replace("\\", "/");
|
||
}
|
||
|
||
private void UpdateOutputFormatFromPathExtension()
|
||
{
|
||
string extension = Path.GetExtension(_outputAssetPath).ToLowerInvariant();
|
||
|
||
if(extension == ".png")
|
||
_outputFormat = OutputFormat.PNG;
|
||
|
||
#if UNITY_2018_3_OR_NEWER
|
||
if(extension == ".tga")
|
||
_outputFormat = OutputFormat.TGA;
|
||
#endif
|
||
}
|
||
|
||
private void QueueOutputPathEditApply()
|
||
{
|
||
_outputAssetPathLastEditTime = EditorApplication.timeSinceStartup;
|
||
_outputAssetPathApplyPending = true;
|
||
|
||
EditorApplication.update -= UpdatePendingOutputPathEdit;
|
||
EditorApplication.update += UpdatePendingOutputPathEdit;
|
||
}
|
||
|
||
private void UpdatePendingOutputPathEdit()
|
||
{
|
||
if(!_outputAssetPathApplyPending)
|
||
{
|
||
EditorApplication.update -= UpdatePendingOutputPathEdit;
|
||
return;
|
||
}
|
||
|
||
if(EditorApplication.timeSinceStartup - _outputAssetPathLastEditTime < OutputPathApplyDelay)
|
||
return;
|
||
|
||
ApplyOutputPathEdit();
|
||
Repaint();
|
||
}
|
||
|
||
private void ApplyOutputPathEdit()
|
||
{
|
||
if(!_outputAssetPathApplyPending && _outputAssetPath == _outputAssetPathEdit)
|
||
return;
|
||
|
||
_outputAssetPath = NormalizeAssetPath(_outputAssetPathEdit);
|
||
UpdateOutputFormatFromPathExtension();
|
||
_outputAssetPath = GetOutputAssetPath();
|
||
_outputAssetPathEdit = _outputAssetPath;
|
||
|
||
CancelPendingOutputPathEdit();
|
||
}
|
||
|
||
private void CancelPendingOutputPathEdit()
|
||
{
|
||
_outputAssetPathApplyPending = false;
|
||
EditorApplication.update -= UpdatePendingOutputPathEdit;
|
||
}
|
||
|
||
private void SaveOutputTexture(Texture2D texture, string outputPath, int maxSize)
|
||
{
|
||
outputPath = NormalizeAssetPath(outputPath);
|
||
|
||
string folder = Path.GetDirectoryName(outputPath);
|
||
|
||
if(!string.IsNullOrEmpty(folder))
|
||
EnsureAssetFolderExists(folder.Replace("\\", "/"));
|
||
|
||
#if UNITY_2018_3_OR_NEWER
|
||
if(_outputFormat == OutputFormat.PNG)
|
||
File.WriteAllBytes(outputPath, texture.EncodeToPNG());
|
||
else
|
||
File.WriteAllBytes(outputPath, texture.EncodeToTGA());
|
||
#else
|
||
File.WriteAllBytes(outputPath, texture.EncodeToPNG());
|
||
#endif
|
||
|
||
AssetDatabase.ImportAsset(outputPath);
|
||
|
||
TextureImporter textureImporter = TextureImporter.GetAtPath(outputPath) as TextureImporter;
|
||
|
||
if(textureImporter != null)
|
||
{
|
||
textureImporter.wrapMode = TextureWrapMode.Repeat;
|
||
textureImporter.maxTextureSize = maxSize;
|
||
textureImporter.alphaSource = TextureImporterAlphaSource.FromInput;
|
||
textureImporter.sRGBTexture = !_dataTexture;
|
||
textureImporter.textureCompression = TextureImporterCompression.CompressedHQ;
|
||
textureImporter.mipmapEnabled = _generateMipMaps;
|
||
textureImporter.SaveAndReimport();
|
||
}
|
||
|
||
AssetDatabase.SaveAssets();
|
||
AssetDatabase.Refresh();
|
||
}
|
||
|
||
private static void MakeReadableIfNeeded(Texture2D texture, Dictionary<string, bool> readableStates)
|
||
{
|
||
if(texture == null)
|
||
return;
|
||
|
||
string texturePath = AssetDatabase.GetAssetPath(texture);
|
||
|
||
if(string.IsNullOrEmpty(texturePath))
|
||
return;
|
||
|
||
if(readableStates.ContainsKey(texturePath))
|
||
return;
|
||
|
||
TextureImporter textureImporter = TextureImporter.GetAtPath(texturePath) as TextureImporter;
|
||
|
||
if(textureImporter == null)
|
||
return;
|
||
|
||
readableStates.Add(texturePath, textureImporter.isReadable);
|
||
|
||
if(!textureImporter.isReadable)
|
||
{
|
||
textureImporter.isReadable = true;
|
||
textureImporter.SaveAndReimport();
|
||
}
|
||
}
|
||
|
||
private static void RestoreReadableStates(Dictionary<string, bool> readableStates)
|
||
{
|
||
foreach(KeyValuePair<string, bool> state in readableStates)
|
||
{
|
||
TextureImporter textureImporter = TextureImporter.GetAtPath(state.Key) as TextureImporter;
|
||
|
||
if(textureImporter == null)
|
||
continue;
|
||
|
||
if(textureImporter.isReadable != state.Value)
|
||
{
|
||
textureImporter.isReadable = state.Value;
|
||
textureImporter.SaveAndReimport();
|
||
}
|
||
}
|
||
}
|
||
|
||
private static string GetFileExtension(OutputFormat format)
|
||
{
|
||
#if UNITY_2018_3_OR_NEWER
|
||
return format == OutputFormat.PNG ? ".png" : ".tga";
|
||
#else
|
||
return ".png";
|
||
#endif
|
||
}
|
||
|
||
private static string FilterFilename(string name)
|
||
{
|
||
List<char> invalidChars = new List<char>(Path.GetInvalidFileNameChars());
|
||
List<char> filename = new List<char>();
|
||
|
||
foreach(char c in name)
|
||
{
|
||
if(!invalidChars.Contains(c))
|
||
filename.Add(c);
|
||
}
|
||
|
||
return new string(filename.ToArray()).Trim();
|
||
}
|
||
|
||
private static bool IsProjectRelativeAssetPath(string path)
|
||
{
|
||
if(string.IsNullOrEmpty(path))
|
||
return false;
|
||
|
||
path = NormalizeAssetPath(path);
|
||
|
||
return path == "Assets" || path.StartsWith("Assets/");
|
||
}
|
||
|
||
private static string NormalizeAssetPath(string path)
|
||
{
|
||
if(string.IsNullOrEmpty(path))
|
||
return string.Empty;
|
||
|
||
path = path.Replace("\\", "/").Trim();
|
||
|
||
string dataPath = Application.dataPath.Replace("\\", "/");
|
||
|
||
if(path.StartsWith(dataPath))
|
||
path = "Assets" + path.Substring(dataPath.Length);
|
||
|
||
return path.TrimEnd('/');
|
||
}
|
||
|
||
private static void EnsureAssetFolderExists(string folderPath)
|
||
{
|
||
folderPath = NormalizeAssetPath(folderPath);
|
||
|
||
if(AssetDatabase.IsValidFolder(folderPath))
|
||
return;
|
||
|
||
string[] parts = folderPath.Split('/');
|
||
string current = parts[0];
|
||
|
||
for(int i = 1; i < parts.Length; i++)
|
||
{
|
||
string next = current + "/" + parts[i];
|
||
|
||
if(!AssetDatabase.IsValidFolder(next))
|
||
AssetDatabase.CreateFolder(current, parts[i]);
|
||
|
||
current = next;
|
||
}
|
||
}
|
||
|
||
private static Color GetChannelColor(TextureChannel channel)
|
||
{
|
||
switch(channel)
|
||
{
|
||
case TextureChannel.Red:
|
||
return new Color(0.85f, 0.2f, 0.2f, 1f);
|
||
|
||
case TextureChannel.Green:
|
||
return new Color(0.25f, 0.75f, 0.25f, 1f);
|
||
|
||
case TextureChannel.Blue:
|
||
return new Color(0.25f, 0.45f, 0.9f, 1f);
|
||
|
||
case TextureChannel.Alpha:
|
||
return new Color(0.65f, 0.65f, 0.65f, 1f);
|
||
|
||
default:
|
||
return Color.white;
|
||
}
|
||
}
|
||
|
||
private void ResetOutputPathToDefault()
|
||
{
|
||
_outputAssetPath = DefaultOutputFolder + "/" + DefaultOutputName + GetFileExtension(_outputFormat);
|
||
_outputAssetPath = NormalizeAssetPath(_outputAssetPath);
|
||
_outputAssetPathEdit = _outputAssetPath;
|
||
CancelPendingOutputPathEdit();
|
||
UpdateOutputFormatFromPathExtension();
|
||
}
|
||
|
||
private static string ProjectPrefsKey(string key)
|
||
{
|
||
string projectPath = Application.dataPath.Replace("\\", "/");
|
||
return "MK.TextureChannelPacker." + projectPath + "." + key;
|
||
}
|
||
|
||
private void EnsureStyles()
|
||
{
|
||
if(_titleStyle != null)
|
||
return;
|
||
|
||
_titleStyle = new GUIStyle(EditorStyles.boldLabel)
|
||
{
|
||
fontSize = 18,
|
||
wordWrap = true
|
||
};
|
||
|
||
_subtitleStyle = new GUIStyle(EditorStyles.label)
|
||
{
|
||
wordWrap = true
|
||
};
|
||
|
||
_sectionTitleStyle = new GUIStyle(EditorStyles.boldLabel)
|
||
{
|
||
fontSize = 13
|
||
};
|
||
|
||
_cardStyle = new GUIStyle(EditorStyles.helpBox)
|
||
{
|
||
padding = new RectOffset(12, 12, 10, 10),
|
||
margin = new RectOffset(8, 8, 6, 6)
|
||
};
|
||
|
||
_miniHelpStyle = new GUIStyle(EditorStyles.miniLabel)
|
||
{
|
||
wordWrap = true
|
||
};
|
||
|
||
_slotStatusStyle = new GUIStyle(EditorStyles.miniLabel)
|
||
{
|
||
wordWrap = false,
|
||
clipping = TextClipping.Clip,
|
||
alignment = TextAnchor.MiddleLeft,
|
||
fixedHeight = 18f,
|
||
padding = new RectOffset(2, 2, 0, 0)
|
||
};
|
||
|
||
_createButtonStyle = new GUIStyle(GUI.skin.button)
|
||
{
|
||
fontStyle = FontStyle.Bold,
|
||
fontSize = 13
|
||
};
|
||
}
|
||
}
|
||
}
|
||
|
||
#endif
|