// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // namespace ImageProcessorCore.Quantizers { using System.Collections.Generic; using System.Threading.Tasks; /// /// Encapsulates methods to calculate the color palette of an image. /// /// The pixel format. /// The packed format. uint, long, float. public abstract class Quantizer : IQuantizer where TColor : IPackedVector where TPacked : struct { /// /// 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; } = -1; /// public byte Threshold { get; 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; byte[] quantizedPixels = new byte[width * height]; List palette; using (PixelAccessor pixels = image.Lock()) { // 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(pixels, width, height); } // Get the palette palette = this.GetPalette(); this.SecondPass(pixels, 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(PixelAccessor 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(PixelAccessor source, byte[] output, int width, int height) { Parallel.For( 0, source.Height, Bootstrapper.Instance.ParallelOptions, y => { for (int x = 0; x < source.Width; x++) { TColor sourcePixel = source[x, y]; output[(y * source.Width) + x] = this.QuantizePixel(sourcePixel); } }); } /// /// 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(TColor 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(TColor pixel); /// /// Retrieve the palette for the quantized image /// /// /// The new color palette /// protected abstract List GetPalette(); } }