Basically, I wrote a native plugin that can directly read png data, and then abused asset preprocessor class to overwrite texture data using that plugin.
Asset preprocessor:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.Runtime.InteropServices;
public class PaletteTexturePostProcessor: AssetPostprocessor{
[System.Serializable]
[StructLayout(LayoutKind.Sequential)]
struct ImageInfo{
public int width;
public int height;
public int colorType;
public int bitDepth;
public int interlace;
public int compression;
public int filter;
public int paletteColors;
};
[DllImport("PalettePlugin", CharSet = CharSet.Unicode)]
static extern int getImageData([MarshalAs (UnmanagedType.LPWStr)]string filepath, byte[] outPalette, byte[] outImageBytes);
[DllImport("PalettePlugin", CharSet = CharSet.Unicode)]
static extern int getImageInfo([MarshalAs (UnmanagedType.LPWStr)]string filepath, [In, Out] ref ImageInfo imageInfo);
bool isIndexedTextureAsset(){
var lowercasePath = assetPath.ToLower();
return lowercasePath.Contains("/indexed/");
}
void OnPreprocessTexture(){
if (!isIndexedTextureAsset())
return;
var importer = (TextureImporter)assetImporter;
if (!importer)
return;
importer.filterMode = FilterMode.Point;
importer.mipmapEnabled = false;
importer.textureType = TextureImporterType.Default;
importer.compressionQuality = 100;
var settings = importer.GetDefaultPlatformTextureSettings();
//settings.format = TextureImporterFormat.
settings.textureCompression = TextureImporterCompression.Uncompressed;
importer.SetPlatformTextureSettings(settings);
}
void OnPostprocessTexture(Texture2D tex){
if (!isIndexedTextureAsset())
return;
var filePath = assetPath;
Debug.LogFormat("filePath: {0}", filePath);
int numChannels = 3;
var info = new ImageInfo();
if (getImageInfo(filePath, ref info) == 0){
Debug.LogFormat("Get image info failed");
return;
}
Debug.LogFormat("Image info: {0}x{1} {2}bpp", info.width, info.height, info.bitDepth);
var palette = new byte[info.paletteColors * numChannels];
var bytes = new byte[info.width * info.height];
if (getImageData(filePath, palette, bytes) == 0){
Debug.LogFormat("Get image data failed");
return;
}
Debug.LogFormat("palette");
for(int i = 0; i < info.paletteColors; i++){
Debug.LogFormat("Entry: {0}, R:{1}, G:{2}, B:{3}", i, palette[i*3], palette[i*3+1], palette[i*3+2]);
}
var colors = tex.GetPixels32();
for(int y = 0; y < info.height; y++){
var dstRowStart = y * info.width;
var srcRowStart = (info.height - 1 - y) * info.width;
for(int x = 0; x < info.width; x++){
var srcOffset = srcRowStart + x;
var dstOffset = dstRowStart + x;
var idx = bytes[srcOffset];
colors[dstOffset].a = idx;
colors[dstOffset].r = idx;
colors[dstOffset].g = idx;
colors[dstOffset].b = idx;
}
}
tex.SetPixels32(colors);
tex.Apply();
}
}
Plugin code:
#include <png.h>
#include <vector>
#include <memory>
#include <functional>
struct ImageInfo{
int width = 0;
int height = 0;
int colorType = 0;
int bitDepth = 0;
int interlace = 0;
int compression = 0;
int filter = 0;
int paletteColors = 0;
};
typedef std::shared_ptr<png_struct> PngPtr;
typedef std::shared_ptr<png_info> PngInfoPtr;
bool processPng(wchar_t* path, std::function<bool(png_structp, png_infop)>callback){
auto f = std::shared_ptr<FILE>(_wfopen(path, L"rb"),
[](FILE* p){
if (p)
fclose(p);
}
);
uint8_t sig[8];
fread(sig, 1, 8, f.get());
if (!png_check_sig(sig, 8))
return false;
auto pngPtr = PngPtr(png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0),
[&](png_structp p){
if (p)
png_destroy_read_struct(&p, 0, 0);
}
);
if (!pngPtr)
return false;
auto infoPtr = PngInfoPtr(
png_create_info_struct(pngPtr.get()),
[&](png_infop p){
if(p)
png_destroy_info_struct(pngPtr.get(), &p);
}
);
if (!infoPtr)
return false;
png_init_io(pngPtr.get(), f.get());
png_set_sig_bytes(pngPtr.get(), 8);
png_read_info(pngPtr.get(), infoPtr.get());
return callback(pngPtr.get(), infoPtr.get());
}
extern "C" int getImageInfo(wchar_t* path, ImageInfo* imageInfo){
int numColors = -1;
auto result = processPng(path, [&](png_structp png, png_infop info){
uint32_t width = 0, height = 0;
png_get_IHDR(png, info,
&width, &height,
&imageInfo->bitDepth, &imageInfo->colorType,
&imageInfo->interlace, &imageInfo->compression,
&imageInfo->filter);
imageInfo->width = (int)width;
imageInfo->height = (int)height;
imageInfo->paletteColors = -1;
if (imageInfo->colorType != PNG_COLOR_TYPE_PALETTE)
return true;
png_set_strip_16(png);
png_set_packing(png);
png_colorp pal = 0;
int numPalette = 0;
png_get_PLTE(png, info, &pal, &numPalette);
imageInfo->paletteColors = numPalette;
return true;
});
return (int)result;
}
extern "C" int getImageData(wchar_t* path, uint8_t* outPalette, uint8_t* outImageBytes){
auto result = processPng(path, [&](png_structp png, png_infop info){
uint32_t width = 0, height = 0;
int bitDepth = 0, colorType = 0, interlace = 0, compression = 0, filter = 0;
png_get_IHDR(png, info, &width, &height, &bitDepth, &colorType, &interlace, &compression, &filter);
if (colorType != PNG_COLOR_TYPE_PALETTE)
return false;
png_set_strip_16(png);
png_set_packing(png);
auto numColors = png_get_palette_max(png, info);
png_colorp pal = 0;
int numPalette = 0;
png_get_PLTE(png, info, &pal, &numPalette);
for(int i = 0; (i < numPalette); i++){
auto cur = pal[i];
outPalette[i*3 + 0] = cur.red;
outPalette[i*3 + 1] = cur.green;
outPalette[i*3 + 2] = cur.blue;
}
png_read_update_info(png, info);
auto rowBytes = png_get_rowbytes(png, info);
auto rowPointers = std::vector<png_bytep>(height);
for(int i = 0; i < height; i++){
rowPointers[i] = outImageBytes + width*i;
}
png_read_image(png, rowPointers.data());
return true;
});
return (int)result;
}
Def file:
LIBRARY
EXPORTS
getImageInfo
getImageData
However, this is a really ugly way to go about it. Would be nice to know if there is a better way to go about it.