Added texture export options

This commit is contained in:
2025-01-14 19:24:23 +01:00
parent 242d6ce94c
commit 956a55eceb
2 changed files with 158 additions and 61 deletions

View File

@@ -1,3 +1,4 @@
using System.IO;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
using UnityEngine.Rendering; using UnityEngine.Rendering;
@@ -7,7 +8,7 @@ public class QuantizedPreviewWindow : EditorWindow
private Texture2D originalTexture; private Texture2D originalTexture;
private Texture2D quantizedTexture; private Texture2D quantizedTexture;
private Texture2D vramTexture; // New VRAM Texture private Texture2D vramTexture; // New VRAM Texture
private float[] clut; // Changed to 1D array private ushort[] clut; // Changed to 1D array
private ushort[] indexedPixelData; // New field for indexed pixel data private ushort[] indexedPixelData; // New field for indexed pixel data
private int bpp = 4; private int bpp = 4;
private int targetWidth = 128; private int targetWidth = 128;
@@ -45,7 +46,7 @@ public class QuantizedPreviewWindow : EditorWindow
{ {
GUILayout.BeginVertical(); GUILayout.BeginVertical();
GUILayout.Label("Original Texture"); GUILayout.Label("Original Texture");
DrawTexturePreview(originalTexture, previewSize); DrawTexturePreview(originalTexture, previewSize, false);
GUILayout.EndVertical(); GUILayout.EndVertical();
} }
@@ -72,26 +73,78 @@ public class QuantizedPreviewWindow : EditorWindow
GUILayout.Label("Color Lookup Table (CLUT)"); GUILayout.Label("Color Lookup Table (CLUT)");
DrawCLUT(); DrawCLUT();
} }
GUILayout.Space(10);
if (indexedPixelData != null)
{
if (GUILayout.Button("Export pixel data"))
{
string path = EditorUtility.SaveFilePanel(
"Save pixel data",
"",
"pixel_data",
"bin"
);
if (!string.IsNullOrEmpty(path))
{
using (FileStream fileStream = new FileStream(path, FileMode.Create, FileAccess.Write))
using (BinaryWriter writer = new BinaryWriter(fileStream))
{
foreach (ushort value in indexedPixelData)
{
writer.Write(value);
}
}
}
}
}
if (clut != null)
{
if (GUILayout.Button("Export clut data"))
{
string path = EditorUtility.SaveFilePanel(
"Save clut data",
"",
"clut_data",
"bin"
);
if (!string.IsNullOrEmpty(path))
{
using (FileStream fileStream = new FileStream(path, FileMode.Create, FileAccess.Write))
using (BinaryWriter writer = new BinaryWriter(fileStream))
{
foreach (ushort value in clut)
{
writer.Write(value);
}
}
}
}
}
} }
private void GenerateQuantizedPreview() private void GenerateQuantizedPreview()
{ {
Texture2D resizedTexture = ResizeTexture(originalTexture, targetWidth, targetHeight); Texture2D resizedTexture = ResizeTexture(originalTexture, targetWidth, targetHeight);
if (bpp == 16) if (bpp == 16)
{ {
quantizedTexture = ConvertTo16Bpp(resizedTexture); quantizedTexture = ConvertTo16Bpp(resizedTexture);
clut = null; clut = null;
vramTexture = resizedTexture; vramTexture = resizedTexture;
} }
else else
{ {
var (indexedPixels, generatedClut) = ImageQuantizer.Quantize(resizedTexture, bpp, maxKMeans); var (indexedPixels, generatedClut) = ImageQuantizer.Quantize(resizedTexture, bpp, maxKMeans);
indexedPixelData = indexedPixels; indexedPixelData = indexedPixels;
clut = generatedClut; clut = generatedClut;
int pixelSize = bpp == 4 ? 4 : bpp == 8 ? 2 : 1; int pixelSize = bpp == 4 ? 4 : bpp == 8 ? 2 : 1;
quantizedTexture = new Texture2D(resizedTexture.width, resizedTexture.height); quantizedTexture = new Texture2D(resizedTexture.width, resizedTexture.height);
Color[] quantizedColors = new Color[resizedTexture.width * resizedTexture.height]; Color[] quantizedColors = new Color[resizedTexture.width * resizedTexture.height];
@@ -110,18 +163,23 @@ public class QuantizedPreviewWindow : EditorWindow
else if (pixelSize == 2) else if (pixelSize == 2)
{ {
int packedValue = indexedPixelData[pixelIndex]; int packedValue = indexedPixelData[pixelIndex];
index = (packedValue >> ((x % 2) * 8)) & 0xFF; index = (packedValue >> ((x % 2) * 8)) & 0xFF;
} }
else else
{ {
index = indexedPixelData[pixelIndex]; index = indexedPixelData[pixelIndex];
} }
Vector3 color = new Vector3(clut[index * 3], clut[index * 3 + 1], clut[index * 3 + 2]); Vector3 color = new Vector3(
(clut[index] & 31) / 31.0f, // Red: bits 04
((clut[index] >> 5) & 31) / 31.0f, // Green: bits 59
((clut[index] >> 10) & 31) / 31.0f // Blue: bits 1014
);
quantizedColors[y * resizedTexture.width + x] = new Color(color.x, color.y, color.z); quantizedColors[y * resizedTexture.width + x] = new Color(color.x, color.y, color.z);
if ((x % pixelSize) == (pixelSize - 1)) if ((x % pixelSize) == (pixelSize - 1))
{ {
pixelIndex++; pixelIndex++;
@@ -145,11 +203,11 @@ public class QuantizedPreviewWindow : EditorWindow
if (bpp == 4) if (bpp == 4)
{ {
adjustedWidth = Mathf.CeilToInt(width / 4f); adjustedWidth = Mathf.CeilToInt(width / 4f);
} }
else if (bpp == 8) else if (bpp == 8)
{ {
adjustedWidth = Mathf.CeilToInt(width / 2f); adjustedWidth = Mathf.CeilToInt(width / 2f);
} }
Texture2D vramTexture = new Texture2D(adjustedWidth, height); Texture2D vramTexture = new Texture2D(adjustedWidth, height);
@@ -160,9 +218,9 @@ public class QuantizedPreviewWindow : EditorWindow
{ {
int index = indexedData[i]; int index = indexedData[i];
float r = Mathf.Floor((index >> 11) & 31) / 31.0f; float r = (index & 31) / 31.0f; // Red: bits 04
float g = Mathf.Floor((index >> 5) & 31) / 31.0f; float g = ((index >> 5) & 31) / 31.0f; // Green: bits 59
float b = Mathf.Floor(index & 31) / 31.0f; float b = ((index >> 10) & 31) / 31.0f; // Blue: bits 1014
vramColors[i] = new Color(r, g, b); vramColors[i] = new Color(r, g, b);
} }
@@ -174,6 +232,7 @@ public class QuantizedPreviewWindow : EditorWindow
} }
private Texture2D ConvertTo16Bpp(Texture2D source) private Texture2D ConvertTo16Bpp(Texture2D source)
{ {
int width = source.width; int width = source.width;
@@ -187,12 +246,11 @@ public class QuantizedPreviewWindow : EditorWindow
{ {
Color pixel = originalPixels[i]; Color pixel = originalPixels[i];
// Convert to 5 bits per channel (R5G5B5)
float r = Mathf.Floor(pixel.r * 31) / 31.0f; // 5 bits for red float r = Mathf.Floor(pixel.r * 31) / 31.0f; // 5 bits for red
float g = Mathf.Floor(pixel.g * 31) / 31.0f; // 5 bits for green float g = Mathf.Floor(pixel.g * 31) / 31.0f; // 5 bits for green
float b = Mathf.Floor(pixel.b * 31) / 31.0f; // 5 bits for blue float b = Mathf.Floor(pixel.b * 31) / 31.0f; // 5 bits for blue
convertedPixels[i] = new Color(r, g, b, pixel.a); convertedPixels[i] = new Color(r, g, b, pixel.a);
} }
convertedTexture.SetPixels(convertedPixels); convertedTexture.SetPixels(convertedPixels);
@@ -201,10 +259,37 @@ public class QuantizedPreviewWindow : EditorWindow
return convertedTexture; return convertedTexture;
} }
private void DrawTexturePreview(Texture2D texture, int size) private void DrawTexturePreview(Texture2D texture, int size, bool flipY = true)
{ {
Rect rect = GUILayoutUtility.GetRect(size, size, GUILayout.ExpandWidth(false)); Rect rect = GUILayoutUtility.GetRect(size, size, GUILayout.ExpandWidth(false));
EditorGUI.DrawPreviewTexture(rect, texture, null, ScaleMode.ScaleToFit, 0, 0, ColorWriteMask.All);
// Flip the texture on the Y-axis
Texture2D displayedTexture = flipY ? FlipTextureY(texture) : texture;
EditorGUI.DrawPreviewTexture(rect, displayedTexture, null, ScaleMode.ScaleToFit, 0, 0, ColorWriteMask.All);
}
private Texture2D FlipTextureY(Texture2D texture)
{
Color[] originalPixels = texture.GetPixels();
Color[] flippedPixels = new Color[originalPixels.Length];
int width = texture.width;
int height = texture.height;
// Flip the pixels on the Y-axis
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
flippedPixels[(height - y - 1) * width + x] = originalPixels[y * width + x];
}
}
Texture2D flippedTexture = new Texture2D(width, height);
flippedTexture.SetPixels(flippedPixels);
flippedTexture.Apply();
return flippedTexture;
} }
private void DrawCLUT() private void DrawCLUT()
@@ -216,7 +301,7 @@ public class QuantizedPreviewWindow : EditorWindow
GUILayout.Space(10); GUILayout.Space(10);
int totalColors = clut.Length / 3; int totalColors = clut.Length;
int totalRows = Mathf.CeilToInt((float)totalColors / maxColorsPerRow); int totalRows = Mathf.CeilToInt((float)totalColors / maxColorsPerRow);
for (int row = 0; row < totalRows; row++) for (int row = 0; row < totalRows; row++)
@@ -228,7 +313,13 @@ public class QuantizedPreviewWindow : EditorWindow
for (int col = 0; col < colorsInRow; col++) for (int col = 0; col < colorsInRow; col++)
{ {
int index = row * maxColorsPerRow + col; int index = row * maxColorsPerRow + col;
Vector3 color = new Vector3(clut[index * 3], clut[index * 3 + 1], clut[index * 3 + 2]);
Vector3 color = new Vector3(
(clut[index] & 31) / 31.0f, // Red: bits 04
((clut[index] >> 5) & 31) / 31.0f, // Green: bits 59
((clut[index] >> 10) & 31) / 31.0f // Blue: bits 1014
);
Rect rect = GUILayoutUtility.GetRect(swatchSize, swatchSize, GUILayout.ExpandWidth(false)); Rect rect = GUILayoutUtility.GetRect(swatchSize, swatchSize, GUILayout.ExpandWidth(false));
EditorGUI.DrawRect(rect, new Color(color.x, color.y, color.z)); EditorGUI.DrawRect(rect, new Color(color.x, color.y, color.z));
} }
@@ -237,6 +328,7 @@ public class QuantizedPreviewWindow : EditorWindow
} }
} }
private Texture2D ResizeTexture(Texture2D source, int newWidth, int newHeight) private Texture2D ResizeTexture(Texture2D source, int newWidth, int newHeight)
{ {
RenderTexture rt = RenderTexture.GetTemporary(newWidth, newHeight); RenderTexture rt = RenderTexture.GetTemporary(newWidth, newHeight);

View File

@@ -6,7 +6,7 @@ using Random = UnityEngine.Random;
public class ImageQuantizer public class ImageQuantizer
{ {
public static (ushort[], float[]) Quantize(Texture2D image, int bpp, int maxIterations = 10) public static (ushort[], ushort[]) Quantize(Texture2D image, int bpp, int maxIterations = 10)
{ {
int width = image.width; int width = image.width;
int height = image.height; int height = image.height;
@@ -66,64 +66,69 @@ public class ImageQuantizer
centroids = new List<Vector3>(newCentroids); centroids = new List<Vector3>(newCentroids);
} }
int pixelSize = bpp == 4 ? 4 : bpp == 8 ? 2 : 1; int pixelSize = bpp == 4 ? 4 : bpp == 8 ? 2 : 1;
int adjustedWidth = width / pixelSize; int adjustedWidth = width / pixelSize;
ushort[] pixelArray = new ushort[adjustedWidth * height]; ushort[] pixelArray = new ushort[adjustedWidth * height];
ushort packIndex = 0; ushort packIndex = 0;
int bitShift = 0; int bitShift = 0;
for (int i = 0; i < pixelColors.Length; i++) // Loop through pixels and pack the data, flipping along the Y-axis
for (int y = height - 1; y >= 0; y--)
{ {
ushort centroidIndex = assignments[i]; for (int x = 0; x < width; x++)
// For 4bpp, we need to pack 4 indices into a single integer
if (bpp == 4)
{ {
pixelArray[packIndex] |= (ushort)(centroidIndex << (bitShift * 4)); // Shift by 4 bits for each index int pixelIndex = y * width + x;
bitShift++; ushort centroidIndex = assignments[pixelIndex];
// Every 4 indices, move to the next position in the pixelArray // For 4bpp, we need to pack 4 indices into a single integer
if (bitShift == 4) if (bpp == 4)
{ {
bitShift = 0; pixelArray[packIndex] |= (ushort)(centroidIndex << (bitShift * 4));
bitShift++;
if (bitShift == 4)
{
bitShift = 0;
packIndex++;
}
}
// For 8bpp, we need to pack 2 indices into a single integer
else if (bpp == 8)
{
pixelArray[packIndex] |= (ushort)(centroidIndex << (bitShift * 8));
bitShift++;
if (bitShift == 2)
{
bitShift = 0;
packIndex++;
}
}
// For 15bpp, just place each index directly (no packing)
else
{
pixelArray[packIndex] = centroidIndex;
packIndex++; packIndex++;
} }
} }
// For 8bpp, we need to pack 2 indices into a single integer
else if (bpp == 8)
{
pixelArray[packIndex] |= (ushort)(centroidIndex << (bitShift * 8)); // Shift by 8 bits for each index
bitShift++;
// Every 2 indices, move to the next position in the pixelArray
if (bitShift == 2)
{
bitShift = 0;
packIndex++;
}
}
// For 15bpp, just place each index directly (no packing)
else
{
pixelArray[packIndex] = centroidIndex;
packIndex++;
}
} }
// Create the CLUT as a 1D array of RGB values
int actualColors = centroids.Count; int actualColors = centroids.Count;
float[] clut = new float[actualColors * 3]; ushort[] clut = new ushort[actualColors];
for (int i = 0; i < actualColors; i++) for (int i = 0; i < actualColors; i++)
{ {
clut[i * 3 + 0] = centroids[i].x; // Red int red = Mathf.Clamp(Mathf.RoundToInt(centroids[i].x * 31), 0, 31); // 5 bits
clut[i * 3 + 1] = centroids[i].y; // Green int green = Mathf.Clamp(Mathf.RoundToInt(centroids[i].y * 31), 0, 31); // 5 bits
clut[i * 3 + 2] = centroids[i].z; // Blue int blue = Mathf.Clamp(Mathf.RoundToInt(centroids[i].z * 31), 0, 31); // 5 bits
clut[i] = (ushort)((blue << 10) | (green << 5) | red);
} }
return (pixelArray, clut); return (pixelArray, clut);
} }
private static List<Vector3> InitializeCentroids(Texture2D image, int maxColors) private static List<Vector3> InitializeCentroids(Texture2D image, int maxColors)
{ {
List<Vector3> centroids = new List<Vector3>(); List<Vector3> centroids = new List<Vector3>();