How to optimize a Gif's color palette?

4

I have a method that generates images through an array of bytes that I get in the database.

The generated files are between 15 and 130 kb in the gif format, they were bigger but it diminishes the dimensions a little.

But I need all files to be at most 100kb and I do not know how to optimize the color palette by reducing the number to achieve this size reduction.

My method is this:

    try
    {
        System.Drawing.Image imageArquivo = this.byteArrayToImage(arqB);            
        imageArquivo = ScaleImage(imageArquivo, 450, 600);
        imageArquivo.Save(saveLocation, System.Drawing.Imaging.ImageFormat.Gif);
        imageArquivo.Dispose();
        fichaExportacao.Exportado = 1;
        msgLog_Imagem = "    - Arquivo gerado na pasta " + saveLocation + "";
        itensExportados++;
    }
    
asked by anonymous 13.06.2014 / 16:37

2 answers

2

As explained in the comments:

  • The image is composed of a white background and some texts / signatures / lines
  • It is not harmful to use gray tones for the output image

So I created a function that transforms a color image (RGB) into 16 shades of gray, and then exports to a GIF with a palette composed of 16 linearly spaced gray tones:

//Cria uma paleta com os 16 tons de cinza desejados (distribuídos linearmente)
private static readonly System.Windows.Media.Imaging.BitmapPalette Gray4Palette = new System.Windows.Media.Imaging.BitmapPalette(new List<System.Windows.Media.Color>(new System.Windows.Media.Color[] {
    System.Windows.Media.Color.FromArgb(0xFF, 0x00, 0x00, 0x00),
    System.Windows.Media.Color.FromArgb(0xFF, 0x11, 0x11, 0x11),
    System.Windows.Media.Color.FromArgb(0xFF, 0x22, 0x22, 0x22),
    System.Windows.Media.Color.FromArgb(0xFF, 0x33, 0x33, 0x33),
    System.Windows.Media.Color.FromArgb(0xFF, 0x44, 0x44, 0x44),
    System.Windows.Media.Color.FromArgb(0xFF, 0x55, 0x55, 0x55),
    System.Windows.Media.Color.FromArgb(0xFF, 0x66, 0x66, 0x66),
    System.Windows.Media.Color.FromArgb(0xFF, 0x77, 0x77, 0x77),
    System.Windows.Media.Color.FromArgb(0xFF, 0x88, 0x88, 0x88),
    System.Windows.Media.Color.FromArgb(0xFF, 0x99, 0x99, 0x99),
    System.Windows.Media.Color.FromArgb(0xFF, 0xAA, 0xAA, 0xAA),
    System.Windows.Media.Color.FromArgb(0xFF, 0xBB, 0xBB, 0xBB),
    System.Windows.Media.Color.FromArgb(0xFF, 0xCC, 0xCC, 0xCC),
    System.Windows.Media.Color.FromArgb(0xFF, 0xDD, 0xDD, 0xDD),
    System.Windows.Media.Color.FromArgb(0xFF, 0xEE, 0xEE, 0xEE),
    System.Windows.Media.Color.FromArgb(0xFF, 0xFF, 0xFF, 0xFF)
}));

private static void SaveGifGray4(string saveLocation, Image image)
{
    //Obtém um array com os pixels originais da imagem, no formato 32bpp
    System.Drawing.Imaging.BitmapData data = (image as Bitmap).LockBits(new Rectangle(0, 0, image.Width, image.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppRgb);
    int stride = data.Stride;

    //Formato das componentes em originalPixels:
    //0 1 2 3, 4 5 6 7, ....
    //r g b a, r g b a, ....
    byte[] originalPixels = new byte[stride * image.Height];
    System.Runtime.InteropServices.Marshal.Copy(data.Scan0, originalPixels, 0, originalPixels.Length);
    (image as Bitmap).UnlockBits(data);

    //Cria o array que irá armazenar os índices da paleta
    byte[] pixels = new byte[image.Width * image.Height];

    //Converte a imagem de 32bpp para 16 tons de cinza
    int offsetOut = 0;
    for (int y = 0; y < image.Height; y++)
    {
        int offsetIn = y * stride;
        for (int x = 0; x < image.Width; x++, offsetIn += 4, offsetOut++)
        {
            byte r = originalPixels[offsetIn], g = originalPixels[offsetIn + 1], b = originalPixels[offsetIn + 2];
            //Converte o RGB em um tom de cinza, conforme a intensidade luminosa relativa da cor
            int grayI = (((int)((0.2126f * (float)r) + (0.7152f * (float)g) + (0.0722f * (float)b)) >> 4) & 15);
            //Reduz para 16 possíveis valores
            pixels[offsetOut] = (byte)((255 * grayI) / 15);
        }
    }

    //Codifica a imagem para um arquivo GIF
    System.Windows.Media.Imaging.GifBitmapEncoder encoder = new System.Windows.Media.Imaging.GifBitmapEncoder();
    encoder.Palette = Gray4Palette;
    //Adiciona o único frame do GIF, a partir dos pixels já processados
    encoder.Frames.Add(System.Windows.Media.Imaging.BitmapFrame.Create(System.Windows.Media.Imaging.BitmapSource.Create(image.Width, image.Height, 96, 96, System.Windows.Media.PixelFormats.Indexed8, Gray4Palette, pixels, image.Width)));
    //Grava o arquivo através de um stream
    System.IO.FileStream stream = new System.IO.FileStream(saveLocation, System.IO.FileMode.Create, System.IO.FileAccess.ReadWrite, System.IO.FileShare.None, 1024);
    encoder.Save(stream);
    stream.Close();
    stream.Dispose();
}

Because of the stretch involving GIF compression with more control and with more options:

System.Windows.Media.Imaging.GifBitmapEncoder encoder...

You need to add three references to the project, for three% of the .NET Framework%:

  • WindowsBase
  • PresentationCore
  • System.Xaml
14.06.2014 / 21:47
3

I believe you can use Encoder.ColorDepth to determine the maximum color depth (in bits).

   try
    {
        Encoder encoder = Encoder.ColorDepth;
        EncoderParameters encoderParams = new EncoderParameters(1);
        // Considerando 24 bits por pixel. Você pode alterar esse valor.
        EncoderParameter encoderParam = new EncoderParameter(encoder, 24L);
        encoderParams.Param[0] = encoderParam;

        System.Drawing.Image imageArquivo = this.byteArrayToImage(arqB);         
        imageArquivo = ScaleImage(imageArquivo, 450, 600);
        ImageCodecInfo gifCodecInfo = GetEncoderInfo("image/gif");
        imageArquivo.Save(saveLocation, gifCodecInfo, encoderParams);
        imageArquivo.Dispose();
        fichaExportacao.Exportado = 1;
        msgLog_Imagem = "    - Arquivo gerado na pasta " + saveLocation + "";
        itensExportados++;
    }

Gif CodecInfo method of obtaining:

private static ImageCodecInfo GetEncoderInfo(String mimeType)
{
    int j;
    ImageCodecInfo[] encoders;
    encoders = ImageCodecInfo.GetImageEncoders();
    for(j = 0; j < encoders.Length; ++j)
    {
        if(encoders[j].MimeType == mimeType)
            return encoders[j];
    }
    return null;
}
    
13.06.2014 / 18:50