// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // namespace ImageProcessorCore.Quantizers { using System; using System.Collections.Generic; /// /// Encapsulates methods to calculate the color palette of an image. /// public abstract class Quantizer : IQuantizer { /// /// Flag used to indicate whether a single pass or two passes are needed for quantization. /// private readonly bool singlePass; /// /// Initializes a new instance of the class. /// /// /// If true, the quantization only needs to loop through the source pixels once /// /// /// If you construct this class with a true value for singlePass, then the code will, when quantizing your image, /// only call the 'QuantizeImage' function. If two passes are required, the code will call 'InitialQuantizeImage' /// and then 'QuantizeImage'. /// protected Quantizer(bool singlePass) { this.singlePass = singlePass; } /// /// Gets or sets the transparency index. /// public int TransparentIndex { get; protected set; } /// public virtual QuantizedImage Quantize(ImageBase image, int maxColors) { Guard.NotNull(image, nameof(image)); // Get the size of the source image int height = image.Height; int width = image.Width; // Call the FirstPass function if not a single pass algorithm. // For something like an Octree quantizer, this will run through // all image pixels, build a data structure, and create a palette. if (!this.singlePass) { this.FirstPass(image, width, height); } byte[] quantizedPixels = new byte[width * height]; // Get the pallete List palette = this.GetPalette(); this.SecondPass(image, quantizedPixels, width, height); return new QuantizedImage(width, height, palette.ToArray(), quantizedPixels, this.TransparentIndex); } /// /// Execute the first pass through the pixels in the image /// /// The source data /// The width in pixels of the image. /// The height in pixels of the image. protected virtual void FirstPass(ImageBase source, int width, int height) { // Loop through each row for (int y = 0; y < height; y++) { // And loop through each column for (int x = 0; x < width; x++) { // Now I have the pixel, call the FirstPassQuantize function... this.InitialQuantizePixel(source[x, y]); } } } /// /// Execute a second pass through the bitmap /// /// The source image. /// The output pixel array /// The width in pixels of the image /// The height in pixels of the image protected virtual void SecondPass(ImageBase source, byte[] output, int width, int height) { int i = 0; // Convert the first pixel, so that I have values going into the loop. // Implicit cast here from Color. Bgra32 previousPixel = source[0, 0]; byte pixelValue = this.QuantizePixel(previousPixel); output[0] = pixelValue; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { Bgra32 sourcePixel = source[x, y]; // Check if this is the same as the last pixel. If so use that value // rather than calculating it again. This is an inexpensive optimization. if (sourcePixel != previousPixel) { // Quantize the pixel pixelValue = this.QuantizePixel(sourcePixel); // And setup the previous pointer previousPixel = sourcePixel; } output[i++] = pixelValue; } } } /// /// Override this to process the pixel in the first pass of the algorithm /// /// The pixel to quantize /// /// This function need only be overridden if your quantize algorithm needs two passes, /// such as an Octree quantizer. /// protected virtual void InitialQuantizePixel(Bgra32 pixel) { } /// /// Override this to process the pixel in the second pass of the algorithm /// /// The pixel to quantize /// /// The quantized value /// protected abstract byte QuantizePixel(Bgra32 pixel); /// /// Retrieve the palette for the quantized image /// /// /// The new color palette /// protected abstract List GetPalette(); } }