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
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?
– carlosrafaelgn
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.
– carlosrafaelgn
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.
– Joao Paulo
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 ;)
– carlosrafaelgn
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.
– Joao Paulo
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?
– carlosrafaelgn
In grayscale I think I would also attend.
– Joao Paulo
Okay, I’ll submit an answer :)
– carlosrafaelgn