texture stitcher (literally psd layers but im lazy)

This commit is contained in:
Lillith Rose 2026-01-25 17:58:41 -05:00
parent d8a57c3817
commit 2c3589b408
7 changed files with 249 additions and 0 deletions

8
TextureStitcher.meta Normal file
View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: aa7127f4d0f097941933b00115d22660
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,16 @@
{
"name": "TextureStitcher",
"rootNamespace": "",
"references": [],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: dd440ea69f98580448e1719b0b71607d
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,129 @@
using UnityEditor;
using UnityEngine;
using System.IO;
namespace gay.lilyy.TextureStitcher
{
[CreateAssetMenu(fileName = "TextureStitcherSettings", menuName = "Texture Stitcher/Settings")]
public class TextureStitcherSettings : ScriptableObject
{
public Texture2D[] textures;
public string savePath = "Assets/StitchedTexture.png";
}
[CustomEditor(typeof(TextureStitcherSettings))]
public class TextureStitcherEditor : Editor
{
private SerializedProperty texturesProp;
private SerializedProperty savePathProp;
private void OnEnable()
{
texturesProp = serializedObject.FindProperty("textures");
savePathProp = serializedObject.FindProperty("savePath");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.LabelField("Texture Layers (Top → Bottom)", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(savePathProp);
DrawReversedArray(texturesProp);
GUILayout.Space(10);
if (GUILayout.Button("Generate Stitched Texture"))
GenerateStitchedTexture();
serializedObject.ApplyModifiedProperties();
}
private void DrawReversedArray(SerializedProperty array)
{
if (!array.isArray)
return;
// Draw size field (normal Unity behavior)
EditorGUILayout.PropertyField(array.FindPropertyRelative("Array.size"));
// Reverse array in-place
int count = array.arraySize;
for (int i = 0; i < count / 2; i++)
array.MoveArrayElement(i, count - 1 - i);
// Draw array normally
EditorGUILayout.PropertyField(array, GUIContent.none, true);
// Restore original order
for (int i = 0; i < count / 2; i++)
array.MoveArrayElement(i, count - 1 - i);
}
private void GenerateStitchedTexture()
{
TextureStitcherSettings settings = (TextureStitcherSettings)target;
GenerateStitchedTexture(settings);
}
internal static void GenerateStitchedTexture(TextureStitcherSettings settings)
{
int width = 0;
int height = 0;
foreach (var tex in settings.textures)
{
if (!tex) continue;
width = Mathf.Max(width, tex.width);
height = Mathf.Max(height, tex.height);
}
Texture2D output = new Texture2D(width, height, TextureFormat.RGBA32, false);
Color[] pixels = new Color[width * height];
for (int i = 0; i < pixels.Length; i++) pixels[i] = Color.clear;
output.SetPixels(pixels);
// Bottom → top
for (int i = 0; i < settings.textures.Length; i++)
{
var tex = settings.textures[i];
if (!tex) continue;
Texture2D resized = Resize(tex, width, height);
Color[] src = resized.GetPixels();
Color[] dst = output.GetPixels();
for (int p = 0; p < dst.Length; p++)
{
Color s = src[p];
dst[p] = Color.Lerp(dst[p], s, s.a);
}
output.SetPixels(dst);
}
output.Apply();
File.WriteAllBytes(settings.savePath, output.EncodeToPNG());
AssetDatabase.Refresh();
Debug.Log("Stitched texture saved to " + settings.savePath);
}
private static Texture2D Resize(Texture2D src, int width, int height)
{
RenderTexture rt = RenderTexture.GetTemporary(width, height, 0);
Graphics.Blit(src, rt);
RenderTexture.active = rt;
Texture2D result = new Texture2D(width, height, TextureFormat.RGBA32, false);
result.ReadPixels(new Rect(0, 0, width, height), 0, 0);
result.Apply();
RenderTexture.active = null;
RenderTexture.ReleaseTemporary(rt);
return result;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7ebbfd7bebbeb0748bb1bf5a9191377f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,67 @@
using UnityEditor;
using UnityEngine;
using System.Linq;
namespace gay.lilyy.TextureStitcher
{
public class TextureStitcherAutoRegen : AssetPostprocessor
{
private const string MenuPath = "LillithRosePup/Import Regens/Texture Stitcher";
private const string PrefKey = "TextureStitcher.AutoRegen";
[MenuItem(MenuPath)]
private static void Toggle()
{
bool enabled = !EditorPrefs.GetBool(PrefKey, true);
EditorPrefs.SetBool(PrefKey, enabled);
Menu.SetChecked(MenuPath, enabled);
}
[MenuItem(MenuPath, true)]
private static bool ToggleValidate()
{
Menu.SetChecked(MenuPath, EditorPrefs.GetBool(PrefKey, true));
return true;
}
private static bool IsEnabled()
{
return EditorPrefs.GetBool(PrefKey, true);
}
static void OnPostprocessAllAssets(
string[] importedAssets,
string[] deletedAssets,
string[] movedAssets,
string[] movedFromAssetPaths)
{
if (!IsEnabled())
return;
if (importedAssets.Length == 0)
return;
var stitchers = AssetDatabase
.FindAssets("t:TextureStitcherSettings")
.Select(guid =>
AssetDatabase.LoadAssetAtPath<TextureStitcherSettings>(
AssetDatabase.GUIDToAssetPath(guid)))
.Where(s => s && s.textures != null && s.textures.Length > 0)
.ToArray();
if (stitchers.Length == 0)
return;
foreach (string path in importedAssets)
{
var tex = AssetDatabase.LoadAssetAtPath<Texture2D>(path);
if (!tex) continue;
foreach (var stitcher in stitchers)
{
if (stitcher.textures.Contains(tex))
TextureStitcherEditor.GenerateStitchedTexture(stitcher);
}
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f4211ffa1dc66ca43af9c753c61933c1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: