Click here to Skip to main content
15,886,639 members
Articles / Programming Languages / C#

A Simple - Yet Quite Powerful - Palette Quantizer in C#

Rate me:
Please Sign up or sign in to vote.
4.98/5 (104 votes)
28 Jul 2012CPOL21 min read 336.3K   12.8K   168  
A palette quantizer based on human perception
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using SimplePaletteQuantizer.ColorCaches.Common;
using SimplePaletteQuantizer.Helpers;

namespace SimplePaletteQuantizer.Quantizers.MedianCut
{
    internal class MedianCutCube
    {
        #region | Fields |

        // red bounds
        private Int32 redLowBound;
        private Int32 redHighBound;

        // green bounds
        private Int32 greenLowBound;
        private Int32 greenHighBound;

        // blue bounds
        private Int32 blueLowBound;
        private Int32 blueHighBound;

        private readonly ICollection<Int32> colorList;

        #endregion

        #region | Properties |

        /// <summary>
        /// Gets the color model.
        /// </summary>
        public ColorModel ColorModel { get; private set; }

        /// <summary>
        /// Gets or sets the index of the palette.
        /// </summary>
        /// <value>The index of the palette.</value>
        public Int32 PaletteIndex { get; private set; }

        #endregion

        #region | Constructors |

        /// <summary>
        /// Initializes a new instance of the <see cref="MedianCutCube"/> class.
        /// </summary>
        /// <param name="colors">The colors.</param>
        public MedianCutCube(ICollection<Int32> colors)
        {
            ColorModel = ColorModel.RedGreenBlue;
            colorList = colors;
            Shrink();
        }

        #endregion

        #region | Calculated properties |

        /// <summary>
        /// Gets the size of the red side of this cube.
        /// </summary>
        /// <value>The size of the red side of this cube.</value>
        public Int32 RedSize
        {
            get { return redHighBound - redLowBound; }
        }

        /// <summary>
        /// Gets the size of the green side of this cube.
        /// </summary>
        /// <value>The size of the green side of this cube.</value>
        public Int32 GreenSize
        {
            get { return greenHighBound - greenLowBound; }
        }

        /// <summary>
        /// Gets the size of the blue side of this cube.
        /// </summary>
        /// <value>The size of the blue side of this cube.</value>
        public Int32 BlueSize
        {
            get { return blueHighBound - blueLowBound; }
        }

        /// <summary>
        /// Gets the average color from the colors contained in this cube.
        /// </summary>
        /// <value>The average color.</value>
        public Color Color
        {
            get
            {
                Int32 red = 0, green = 0, blue = 0;

                foreach (Int32 argb in colorList)
                {
                    Color color = Color.FromArgb(argb);
                    red += ColorModelHelper.GetComponentA(ColorModel, color);
                    green += ColorModelHelper.GetComponentB(ColorModel, color);
                    blue += ColorModelHelper.GetComponentC(ColorModel, color);
                }

                red = colorList.Count == 0 ? 0 : red / colorList.Count;
                green = colorList.Count == 0 ? 0 : green / colorList.Count;
                blue = colorList.Count == 0 ? 0 : blue / colorList.Count;

                // ColorModelHelper.HSBtoRGB(Convert.ToInt32(red/ColorModelHelper.HueFactor), green / 255.0f, blue / 255.0f);

                Color result = Color.FromArgb(255, red, green, blue);
                return result;
            }
        }

        #endregion

        #region | Methods |

        /// <summary>
        /// Shrinks this cube to the least dimensions that covers all the colors in the RGB space.
        /// </summary>
        private void Shrink()
        {
            redLowBound = greenLowBound = blueLowBound = 255;
            redHighBound = greenHighBound = blueHighBound = 0;

            foreach (Int32 argb in colorList)
            {
                Color color = Color.FromArgb(argb);

                Int32 red = ColorModelHelper.GetComponentA(ColorModel, color);
                Int32 green = ColorModelHelper.GetComponentB(ColorModel, color);
                Int32 blue = ColorModelHelper.GetComponentC(ColorModel, color);

                if (red < redLowBound) redLowBound = red;
                if (red > redHighBound) redHighBound = red;
                if (green < greenLowBound) greenLowBound = green;
                if (green > greenHighBound) greenHighBound = green;
                if (blue < blueLowBound) blueLowBound = blue;
                if (blue > blueHighBound) blueHighBound = blue;
            }
        }

        /// <summary>
        /// Splits this cube's color list at median index, and returns two newly created cubes.
        /// </summary>
        /// <param name="componentIndex">Index of the component (red = 0, green = 1, blue = 2).</param>
        /// <param name="firstMedianCutCube">The first created cube.</param>
        /// <param name="secondMedianCutCube">The second created cube.</param>
        public void SplitAtMedian(Byte componentIndex, out MedianCutCube firstMedianCutCube, out MedianCutCube secondMedianCutCube)
        {
            List<Int32> colors;

            switch (componentIndex)
            {
                // red colors
                case 0:
                    colors = colorList.OrderBy(argb => ColorModelHelper.GetComponentA(ColorModel, Color.FromArgb(argb))).ToList();
                    break;

                // green colors
                case 1:
                    colors = colorList.OrderBy(argb => ColorModelHelper.GetComponentB(ColorModel, Color.FromArgb(argb))).ToList();
                    break;

                // blue colors
                case 2:
                    colors = colorList.OrderBy(argb => ColorModelHelper.GetComponentC(ColorModel, Color.FromArgb(argb))).ToList();
                    break;

                default:
                    throw new NotSupportedException("Only three color components are supported (R, G and B).");

            }

            // retrieves the median index (a half point)
            Int32 medianIndex = colorList.Count >> 1;

            // creates the two half-cubes
            firstMedianCutCube = new MedianCutCube(colors.GetRange(0, medianIndex));
            secondMedianCutCube = new MedianCutCube(colors.GetRange(medianIndex, colors.Count - medianIndex));
        }

        /// <summary>
        /// Assigns a palette index to this cube, to be later found by a GetPaletteIndex method.
        /// </summary>
        /// <param name="newPaletteIndex">The palette index to be assigned to this cube.</param>
        public void SetPaletteIndex(Int32 newPaletteIndex)
        {
            PaletteIndex = newPaletteIndex;
        }

        /// <summary>
        /// Determines whether the color is in the space of this cube.
        /// </summary>
        /// <param name="color">The color to be checked, if it's contained in this cube.</param>
        /// <returns>if true a color is in the space of this cube, otherwise returns false.</returns>
        public Boolean IsColorIn(Color color)
        {
            Int32 red = ColorModelHelper.GetComponentA(ColorModel, color);
            Int32 green = ColorModelHelper.GetComponentB(ColorModel, color);
            Int32 blue = ColorModelHelper.GetComponentC(ColorModel, color);

            return (red >= redLowBound && red <= redHighBound) &&
                   (green >= greenLowBound && green <= greenHighBound) &&
                   (blue >= blueLowBound && blue <= blueHighBound);
        }

        #endregion
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Czech Republic Czech Republic
Contacts: EMAIL - smartk8@gmail.com

Comments and Discussions