How to optimize the color palette of a Gif?

Asked

Viewed 396 times

4

I have a method that generates images through an array of bytes that I take from the database.

The generated files are between 15 and 130 kb in gif format, they were bigger but slightly decreases the sizes.

But I need all the files to be at most 100kb and I don’t know how to optimize the color palette, reducing the number to achieve this size reduction.

This is my method:

    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++;
    }
  • I just did a series of tests and realized that for an image with dimensions 450x600, the value of 100kb is very restrictive. I compared several images, and the result was the following: PNG file size ALWAYS <= GIF size. However, this size was not always <= 100kb. Mainly because Encoder uses a fixed color palette, and applies dithering. With this, the file size increases due to noise. Can you increase the limit in bytes or decrease the dimensions? You can post an example photo for me to think of a strategy?

  • It did not fit in the other comment, but I got the images to be <= 100kb, but without dithering, which ruined the quality of the generated image. I haven’t tested it yet, but the way I see it, without dithering, in order for the quality to go up "a little", it would be necessary to change the palette used by Encoder, and make it use a palette created specifically for each image, through the quantization process. There is a good library for this at Codeproject.

  • Cool carlos. I can not put image here because they are confidential. It is already well at the limit of the dimensions. I think the only solution would really be to create a unique palette for each image, but by enqto I do not know how to do. The other option is that they accept JPG. In jpg I managed to put all below 50kb inclusive.

  • I asked for an image example to determine if JPG can be acceptable. Are graphics with few colors, screenshots, or are images like nature/people/places? JPG is only good for this last category. For the first two, it might be acceptable, but the artifacts compression will be highlighted and PNG will have a better result. On how to use a palette for each image, the example of Codeproject really is pretty cool ;)

  • Eh an image of a coupon to fill, white background with fields to fill, as name, position, signature etc. The quality 45% of jpg is acceptable and allows to see right the signatures and texts. Monday I’ll try that example you passed.

  • Oops, no problem. So, if the image was in grayscale would solve your problem? If I post an answer quantizing your image to grayscale, with a palette with a few colors, you would accept?

  • In grayscale I think I would also attend.

  • Okay, I’ll submit an answer :)

Show 3 more comments

2 answers

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++;
    }

Method of obtaining Codecinfo from Gif:

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;
}
  • Interesting. But there’s no Overload for these three parameters. image File.Save(savelocation, System.Drawing.Imaging.ImageFormat.Gif, myEncoderParameters); This one is for example: imageFile.Save(string filename, Imagecodecinfo Encoder, Encoderparameters encoderParams)

  • You can use the gif encoding, then: Imagecodecinfo gifCodecInfo = Getencoderinfo("image/gif");

  • Where does Getencoderinfo come from()?

  • I found it. I’ll try it.

  • Amaury, I tried to change the values of the parameter, but it did not have any effect: Encoderparameter encoderParam = new Encoderparameter(Encoder, 24L); I tested 12L, I tested 256, 2, 1... What is the appropriate way for me to have minimum quality?

  • I’ve never tested it, so I can’t answer you properly. I suggest, then, that you try another "Encoder.Quality" instead of Encoder.ColorDepth. The syntax is the same, but the parameter (in the case of 24L) represents the quality level. Try using lower values to see if it works.

  • I tested Quality and Compression, but they only served with JPG as well. I’m still looking for a solution on reducing GIF color palettes. Maybe I end up using this code with JPG even, but I can’t mark it as the right answer because it doesn’t do what the question asks. Anyway thank you very much!

Show 2 more comments

2


As explained in the comments:

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

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

//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 that involves compression for GIF with more control and options:

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

It is necessary to add three references to the project, for three Assemblies of the . NET Framework:

  • Windowsbase
  • Presentationcore
  • System.Xaml

Browser other questions tagged

You are not signed in. Login or sign up in order to post.