Change size array in ColorPalette.Entries

608 views Asked by At

I want to change size in ColorPalette.Entries. If I do like below, I got error. How can change size of Entries in correct way?

Bitmap bm = new Bitmap(Width, Height,PixelFormat.Format8bppIndexed);
var palette = bm.Palette;
var colr = palette.Entries;
Array.Resize(ref colr, 4);

Error:

'ColorPalette.Entries' cannot be assigned to -- it is read only

2

There are 2 answers

4
Jeroen van Langen On

Why would you change the size for 8bppIndex from 256 to 4? The colorindices in the image depends on 256 indices. You don't want an index out of bounds kind of thing in the image. What if the pixel should be index 5? Too bad there isn't a Format2bppIndexed format.

0
Nyerguds On

I actually found a way to do this; png can have variable size palettes, and the .Net framework doesn't pad those when loading the file.

And, constructing a png file in a byte array with customised chunks is fairly easy.

With the code linked there (mostly the WritePngChunk) this will construct a png with the amount of colours you want, load it, and extract its palette object:

private static Byte[] PNG_IDENTIFIER = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
private static Byte[] PNG_BLANK = { 0x08, 0xD7, 0x63, 0x60, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01};

/// <summary>
/// Creates a custom-sized color palette by creating an empty png with a limited palette and extracting its palette.
/// </summary>
/// <param name="colors">The colors to convert into a palette.</param>
/// <returns>A color palette containing the given colors.</returns>
public static ColorPalette GetPalette(Color[] colors)
{
    // Silliest idea ever, but it works.
    const Int32 chunkExtraLen = 0x0C;
    Int32 lenPng = PNG_IDENTIFIER.Length;
    const Int32 lenHdr = 0x0D;
    Int32 lenPal = Math.Min(colors.Length, 0x100) * 3;
    Int32 lenData = PNG_BLANK.Length;
    Int32 fullLen = lenPng + lenHdr + chunkExtraLen + lenPal + chunkExtraLen + lenData + chunkExtraLen + chunkExtraLen;
    Int32 offset = 0;
    Byte[] emptyPng = new Byte[fullLen];
    Array.Copy(PNG_IDENTIFIER, 0, emptyPng, 0, PNG_IDENTIFIER.Length);
    offset += lenPng;
    Byte[] header = new Byte[lenHdr];
    // Width: 1
    header[3] = 1;
    // Heigth: 1
    header[7] = 1;
    // Color depth: 8
    header[8] = 8;
    // Color type: paletted
    header[9] = 3;
    WritePngChunk(emptyPng, offset, "IHDR", header);
    offset += lenHdr + chunkExtraLen;
    // Don't even need to fill this in. We just need the size.
    Byte[] palette = new Byte[lenPal];
    WritePngChunk(emptyPng, offset, "PLTE", palette);
    offset += lenPal + chunkExtraLen;
    WritePngChunk(emptyPng, offset, "IDAT", PNG_BLANK);
    offset += lenData + chunkExtraLen;
    WritePngChunk(emptyPng, offset, "IEND", new Byte[0]);
    using (MemoryStream ms = new MemoryStream(emptyPng))
    // By doing this afterwards we can ensure the alpha is correct too; the PLTE chunk
    // is RGB without alpha, and if there's a tRNS chunk to add alpha, the framework will
    // convert the image to 32 bit for some bizarre reason.
    using (Bitmap loadedImage = new Bitmap(ms))
    {
        ColorPalette pal = loadedImage.Palette;
        for (Int32 i = 0; i < pal.Entries.Length; i++)
            pal.Entries[i] = colors[i];
        return pal;
    }
}

Though, I'm not too sure what you are planning to actually do with this image. I needed this functionality for conversion accuracy on a Nintendo 64 ROM image format, but unless you have very dire space concerns, the palette size will usually not make a difference.

And if you actually edit the image (which is possible using LockBits and Marshal.Copy to manipulate the backing array directly) you'll need to be very careful not to ever use any index value higher than 3 on your image, or the whole thing will probably crash and burn.