Search Unity

Replace image palette upon import.

Discussion in 'Scripting' started by neginfinity, May 24, 2017.

  1. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,566
    I have several pngs with indexed color and I'm looking for a sane way to automatically replace image palette upon import - BEFORE unity converts it to rgb data.

    My first thought was asset preprocessor, BUT it looks like it would require System.Graphics (which are, as far as I can tell, unsupported in unity), and it doesn't look like there's a way to overwrite image data before it is imported:
    https://docs.unity3d.com/ScriptReference/AssetPostprocessor.OnPreprocessTexture.html

    Restrictions:
    1. No non-free asset store packages.
    2. No trying to detect palette index based on rgb data. (because palette may have duplicate entries)

    Any ideas?
     
  2. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,566
    So, any ideas?

    Basically, I need a way to access some raw image manipulation routines from unity. System.Drawing.Graphics are not available, and libpng functions are not exposed to C# script.
     
  3. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,566
    Alright, I "Solved" the issue in the most horrible way possible:

    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.

    Code:
    Asset preprocessor:
    Code (csharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5. using UnityEditor;
    6. using System.Runtime.InteropServices;
    7.  
    8. public class PaletteTexturePostProcessor: AssetPostprocessor{
    9.     [System.Serializable]
    10.     [StructLayout(LayoutKind.Sequential)]
    11.     struct ImageInfo{
    12.         public int width;
    13.         public int height;
    14.         public int colorType;
    15.         public int bitDepth;
    16.         public int interlace;
    17.         public int compression;
    18.         public int filter;
    19.         public int paletteColors;
    20.     };
    21.  
    22.     [DllImport("PalettePlugin", CharSet = CharSet.Unicode)]
    23.     static extern int getImageData([MarshalAs (UnmanagedType.LPWStr)]string filepath, byte[] outPalette, byte[] outImageBytes);
    24.  
    25.     [DllImport("PalettePlugin", CharSet = CharSet.Unicode)]
    26.     static extern int getImageInfo([MarshalAs (UnmanagedType.LPWStr)]string filepath, [In, Out] ref ImageInfo imageInfo);
    27.  
    28.     bool isIndexedTextureAsset(){
    29.         var lowercasePath = assetPath.ToLower();
    30.         return lowercasePath.Contains("/indexed/");
    31.     }
    32.  
    33.     void OnPreprocessTexture(){
    34.         if (!isIndexedTextureAsset())
    35.             return;
    36.         var importer = (TextureImporter)assetImporter;
    37.         if (!importer)
    38.             return;
    39.         importer.filterMode = FilterMode.Point;
    40.         importer.mipmapEnabled = false;
    41.         importer.textureType = TextureImporterType.Default;
    42.         importer.compressionQuality = 100;
    43.         var settings = importer.GetDefaultPlatformTextureSettings();
    44.         //settings.format = TextureImporterFormat.
    45.         settings.textureCompression = TextureImporterCompression.Uncompressed;
    46.         importer.SetPlatformTextureSettings(settings);
    47.     }
    48.  
    49.     void OnPostprocessTexture(Texture2D tex){
    50.         if (!isIndexedTextureAsset())
    51.             return;
    52.  
    53.         var filePath = assetPath;
    54.         Debug.LogFormat("filePath: {0}", filePath);
    55.         int numChannels = 3;
    56.         var info = new ImageInfo();
    57.         if (getImageInfo(filePath, ref info) == 0){
    58.             Debug.LogFormat("Get image info failed");
    59.             return;
    60.         }
    61.         Debug.LogFormat("Image info: {0}x{1} {2}bpp", info.width, info.height, info.bitDepth);
    62.  
    63.         var palette = new byte[info.paletteColors * numChannels];
    64.         var bytes = new byte[info.width * info.height];
    65.  
    66.         if (getImageData(filePath, palette, bytes) == 0){
    67.             Debug.LogFormat("Get image data failed");
    68.             return;
    69.         }
    70.  
    71.         Debug.LogFormat("palette");
    72.         for(int i = 0; i < info.paletteColors; i++){
    73.             Debug.LogFormat("Entry: {0}, R:{1}, G:{2}, B:{3}", i, palette[i*3], palette[i*3+1], palette[i*3+2]);
    74.         }
    75.  
    76.         var colors = tex.GetPixels32();
    77.  
    78.         for(int y = 0; y < info.height; y++){
    79.             var dstRowStart = y * info.width;
    80.             var srcRowStart = (info.height - 1 - y) * info.width;
    81.             for(int x = 0; x < info.width; x++){
    82.                 var srcOffset = srcRowStart + x;
    83.                 var dstOffset = dstRowStart + x;
    84.  
    85.                 var idx = bytes[srcOffset];
    86.  
    87.                 colors[dstOffset].a = idx;
    88.                 colors[dstOffset].r = idx;
    89.                 colors[dstOffset].g = idx;
    90.                 colors[dstOffset].b = idx;
    91.             }
    92.         }
    93.  
    94.         tex.SetPixels32(colors);
    95.         tex.Apply();
    96.     }
    97. }
    98.  
    99.  
    Plugin code:
    Code (csharp):
    1.  
    2. #include <png.h>
    3. #include <vector>
    4. #include <memory>
    5. #include <functional>
    6.  
    7. struct ImageInfo{
    8.     int width = 0;
    9.     int height = 0;
    10.     int colorType = 0;
    11.     int bitDepth = 0;
    12.     int interlace = 0;
    13.     int compression = 0;
    14.     int filter = 0;
    15.     int paletteColors = 0;
    16. };
    17.  
    18. typedef std::shared_ptr<png_struct> PngPtr;
    19. typedef std::shared_ptr<png_info> PngInfoPtr;
    20.  
    21. bool processPng(wchar_t* path, std::function<bool(png_structp, png_infop)>callback){
    22.     auto f = std::shared_ptr<FILE>(_wfopen(path, L"rb"),
    23.         [](FILE* p){
    24.             if (p)
    25.                 fclose(p);
    26.         }
    27.     );
    28.  
    29.     uint8_t sig[8];
    30.     fread(sig, 1, 8, f.get());
    31.     if (!png_check_sig(sig, 8))
    32.         return false;
    33.  
    34.     auto pngPtr = PngPtr(png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0),
    35.         [&](png_structp p){
    36.             if (p)
    37.                 png_destroy_read_struct(&p, 0, 0);
    38.         }
    39.     );
    40.     if (!pngPtr)
    41.         return false;
    42.  
    43.     auto infoPtr = PngInfoPtr(
    44.         png_create_info_struct(pngPtr.get()),
    45.         [&](png_infop p){
    46.             if(p)
    47.                 png_destroy_info_struct(pngPtr.get(), &p);
    48.         }
    49.     );
    50.     if (!infoPtr)
    51.         return false;
    52.  
    53.     png_init_io(pngPtr.get(), f.get());
    54.     png_set_sig_bytes(pngPtr.get(), 8);
    55.     png_read_info(pngPtr.get(), infoPtr.get());
    56.  
    57.     return callback(pngPtr.get(), infoPtr.get());
    58. }
    59.  
    60. extern "C" int getImageInfo(wchar_t* path, ImageInfo* imageInfo){
    61.     int numColors = -1;
    62.     auto result = processPng(path, [&](png_structp png, png_infop info){
    63.         uint32_t width = 0, height = 0;
    64.         png_get_IHDR(png, info,
    65.             &width, &height,
    66.             &imageInfo->bitDepth, &imageInfo->colorType,
    67.             &imageInfo->interlace, &imageInfo->compression,
    68.             &imageInfo->filter);
    69.      
    70.         imageInfo->width = (int)width;
    71.         imageInfo->height = (int)height;
    72.         imageInfo->paletteColors = -1;
    73.         if (imageInfo->colorType != PNG_COLOR_TYPE_PALETTE)
    74.             return true;
    75.  
    76.         png_set_strip_16(png);
    77.         png_set_packing(png);
    78.  
    79.         png_colorp pal = 0;
    80.         int numPalette = 0;
    81.         png_get_PLTE(png, info, &pal, &numPalette);
    82.         imageInfo->paletteColors = numPalette;
    83.         return true;
    84.     });
    85.  
    86.     return (int)result;
    87. }
    88.  
    89. extern "C" int getImageData(wchar_t* path, uint8_t* outPalette, uint8_t* outImageBytes){
    90.     auto result = processPng(path, [&](png_structp png, png_infop info){
    91.         uint32_t width = 0, height = 0;
    92.         int bitDepth = 0, colorType = 0, interlace = 0, compression = 0, filter = 0;
    93.         png_get_IHDR(png, info, &width, &height, &bitDepth, &colorType, &interlace, &compression, &filter);
    94.  
    95.         if (colorType != PNG_COLOR_TYPE_PALETTE)
    96.             return false;
    97.  
    98.         png_set_strip_16(png);
    99.         png_set_packing(png);
    100.         auto numColors = png_get_palette_max(png, info);
    101.  
    102.         png_colorp pal = 0;
    103.         int numPalette = 0;
    104.         png_get_PLTE(png, info, &pal, &numPalette);
    105.         for(int i = 0; (i < numPalette); i++){
    106.             auto cur = pal[i];
    107.             outPalette[i*3 + 0] = cur.red;
    108.             outPalette[i*3 + 1] = cur.green;
    109.             outPalette[i*3 + 2] = cur.blue;
    110.         }
    111.  
    112.         png_read_update_info(png, info);
    113.  
    114.         auto rowBytes = png_get_rowbytes(png, info);
    115.         auto rowPointers = std::vector<png_bytep>(height);
    116.         for(int i = 0; i < height; i++){
    117.             rowPointers[i] = outImageBytes + width*i;
    118.         }
    119.         png_read_image(png, rowPointers.data());
    120.  
    121.         return true;
    122.     });
    123.     return (int)result;
    124. }
    125.  
    126.  
    Def file:
    Code (csharp):
    1.  
    2. LIBRARY
    3.  
    4. EXPORTS
    5. getImageInfo
    6. getImageData
    7.  
    8.  

    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.

    Perhaps, someone from unity can chime in? @Andy-Touch, @superpig or someone else?