Color Reduction Basics

Most printing and displaying image devices cannot reproduce the real colors of an image due to their limited palettes. Consequently, the number of image colors has to be reduced to match the number of palette colors available. The method of color reduction is very simple: every color of the original picture is replaced by an appropriate color from the limited palette that is accessible. Unfortunately, the resulting image can become discolored and less attractive. Notice the difference between the original color image and the 32 color palette-based image:

Original true color image 32 color palette-based image without dithering
Original true color image 32 color palette-based
image without dithering

There are several algorithms which allow you to save images in a more attractive format.

NeuQuant

NeuQuant is a palette generation algorithm. It creates an optimal palette for a given image, what allows significantly reducing image size.

The following image was generated using the HeuQuant algorithm. Images size is 63% of the original image size.

NeuQuant
NeuQuant palette-based image

The really interesting feature of NeuQuant is that it preserves full alpha transparency. So, it allows converting real-color images with transparency to indexed PNG images almost without quality reduction. For example, the following image is reduced by 71%:

Original true color image NeuQuant
Original true color image NeuQuant palette-based image

To use NeuQuant you should set a ColorPaletteType.NeuQuant palette as a ColorManagementProvider.Palette. The following snippet demonstrates how to use the NeuQuant algorithm:

C#
using (var bitmap = new Bitmap(@"Images\in.png"))
{
    bitmap.ColorManagement.Palette = new ColorPalette(ColorPaletteType.NeuQuant);
    bitmap.ColorManagement.Convert(PixelFormat.Format8bppIndexed);
    bitmap.Save(@"Images\Output\out.png");
}

Dithering

Dithering is an algorithm class which allows the creation of color depth illusions in images with a limited color palette. The main idea of these algorithms is the approximation of image colors which are not available in the palette, by organizing pixels in a special way. Dithering introduces patterns into an image which are perceived by the eyes as new colors, but these are colors which are not available in the palette.

One of the dithering parameters is intensity - the ratio between the images with and without dithering. When 0% intensity is used, no dithering is enabled.

There are 3 main algorithms in the dithering class:

Noise Dithering

The noise dithering algorithm builds patterns randomly, adding a random number in the range [-1; 1] to each pixel of the original image. The following picture illustrates the result of noise dithering while creating a 32 color image:

Noise dithering
Noise dithering

Ordered Dithering

This dithering algorithm builds dithering patterns in a very specific way. For simplicity let's imagine that the color palette has only two colors. The human eye tends to average a region around a pixel instead of treating each pixel individually, thus it is possible to create the illusion of many gray levels in a binary image, even if it has only two levels. With 2x2 binary pixel patterns, we can represent 5 shades of gray.

Different gray colors using 2x2 binary pixel patterns

The same method is used when the count of colors in the palette is more than two. The patterns in the ordered dithering algorithm are predefined. The following pictures illustrate the result of ordered dithering with Bayer and Spiral patterns while creating a 32 color image.

Bayer ordered dithering Spiral ordered dithering
Bayer ordered dithering Spiral ordered dithering

Error Diffusion

The error diffusion algorithm builds dithering patterns dynamically based on each pixel. After conversion the pixels of an original image can differ from the same pixels in an output palette-based image. This difference is called a "quantization error". Error diffusion achieves the effect of color emulation by distributing the quantization error to neighboring pixels that have not been processed yet. The following error diffusion filters are used in practice: Floyd-Steinberg, Fan, Burkes, Stucki, Sierra, Jarvice, and Stephenson. These filters use the preset coefficients of error diffusion. The following examples illustrate different diffusion filters.

Original dithering Floyd-Steinberg dithering Fan dithering
Original dithering Floyd-Steinberg
dithering
Fan dithering
Jarvis dithering Stucki dithering Sierra dithering
Jarvis dithering Stucki dithering Sierra dithering
Burkes dithering Stephenson dithering
Burkes dithering Stephenson dithering

All dithering algorithms have advantages and disadvantages. Noise dithering works very quickly but produces noisy images. Ordered dithering approximates color blends using fixed patterns; as a result, solid colors are emphasized and edges appear harder. Error diffusion scatters pixels irregularly, making edges and colors softer, but it takes much more time than the other algorithms.

How to Reduce Color in Graphics Mill

Graphics Mill supports all the dithering techniques above while changing pixel format.

There are two ways to convert pixels to an indexed pixel format:

  • Using the Convert(PixelFormat) method. This method is easy to use, it has only one argument - the target pixel format. You can specify a dithering parameters using the Dithering and DitheringIntensity properties, and type of palette to be utilized for conversion using the Palette property.
  • Using the ColorConverter transform class. You can use the Dithering and DitheringIntensity properties to set dithering settings and the Palette property to specify an image's palette.

Here is a simple example which converts an image to an 8-bit indexed image with default dithering parameters:

C#
using (var bitmap = new Bitmap(@"Images\DitheringMain.jpg"))
{
    bitmap.ColorManagement.Dithering = DitheringType.None;
    bitmap.ColorManagement.Palette = ColorPalette.Create(bitmap, 32);
    bitmap.ColorManagement.Convert(PixelFormat.Format8bppIndexed);
    bitmap.Save(@"Images\Output\out.jpg");
}

The code below demonstrates how to modify all dithering settings. All the images on this page where generated with it.

C#
using (var bitmap = new Bitmap(@"Images\DitheringMain.jpg"))
using (var colorConverter = new ColorConverter())
{
    colorConverter.Palette = ColorPalette.Create(bitmap, 32);
    colorConverter.DestinationPixelFormat = PixelFormat.Format8bppIndexed;
    colorConverter.Opacity = 1;

    //Without dithering
    colorConverter.Dithering = DitheringType.None;
    using (var newbitmap = colorConverter.Apply(bitmap))
        newbitmap.Save(@"Images\Output\Dithering_None_x_x_x_32.png");

    //White noise
    colorConverter.Dithering = DitheringType.Noise;
    colorConverter.DitheringIntensity = 0.5f; 
    using (var newbitmap = colorConverter.Apply(bitmap))
        newbitmap.Save(@"Images\Output\Dithering_Noise_50_x_x_32.png");

    //Ordered dithering
    colorConverter.DitheringIntensity = 1;
    colorConverter.Dithering = DitheringType.OrderedBayer2;
    using (var newbitmap = colorConverter.Apply(bitmap))
        newbitmap.Save(@"Images\Output\Dithering_Ordered_100_Bayer_2_32.png");

    colorConverter.Dithering = DitheringType.OrderedBayer4;
    using (var newbitmap = colorConverter.Apply(bitmap))
        newbitmap.Save(@"Images\Output\Dithering_Ordered_100_Bayer_4_32.png");

    colorConverter.Dithering = DitheringType.OrderedSpiral4;
    colorConverter.DitheringIntensity = 0.1f; 
    using (var newbitmap = colorConverter.Apply(bitmap))
        newbitmap.Save(@"Images\Output\Dithering_Ordered_100_Spiral_4_32.png");

    //Error diffusion
    colorConverter.Dithering = DitheringType.Original;
    colorConverter.DitheringIntensity = 0.5f; 
    using (var newbitmap = colorConverter.Apply(bitmap))
        newbitmap.Save(@"Images\Output\Dithering_Original_100_x_x_32.png");

    colorConverter.Dithering = DitheringType.FloydSteinberg;
    using (var newbitmap = colorConverter.Apply(bitmap))
        newbitmap.Save(@"Images\Output\Dithering_FloydSteinberg_100_x_x_32.png");

    colorConverter.Dithering = DitheringType.Fan;
    using (var newbitmap = colorConverter.Apply(bitmap))
        newbitmap.Save(@"Images\Output\Dithering_Fan_100_x_x_32.png");

    colorConverter.Dithering = DitheringType.Jarvis;
    using (var newbitmap = colorConverter.Apply(bitmap))
        newbitmap.Save(@"Images\Output\Dithering_Jarvis_100_x_x_32.png");

    colorConverter.Dithering = DitheringType.Stucki;
    using (var newbitmap = colorConverter.Apply(bitmap))
        newbitmap.Save(@"Images\Output\Dithering_Stucki_100_x_x_32.png");

    colorConverter.Dithering = DitheringType.Sierra;
    using (var newbitmap = colorConverter.Apply(bitmap))
        newbitmap.Save(@"Images\Output\Dithering_Sierra_100_x_x_32.png");

    colorConverter.Dithering = DitheringType.Burkes;
    using (var newbitmap = colorConverter.Apply(bitmap))
        newbitmap.Save(@"Images\Output\Dithering_Burkes_100_x_x_32.png");

    colorConverter.Dithering = DitheringType.Stephenson;
    using (var newbitmap = colorConverter.Apply(bitmap))
        newbitmap.Save(@"Images\Output\Dithering_Stephenson_100_x_x_32.png");
}

See Also

Reference

Manual