FileDocCategorySizeDatePackage
ColorCutQuantizer.javaAPI DocAndroid 5.1 API16423Thu Mar 12 22:22:56 GMT 2015android.support.v7.graphics

ColorCutQuantizer

public final class ColorCutQuantizer extends Object
An color quantizer based on the Median-cut algorithm, but optimized for picking out distinct colors rather than representation colors. The color space is represented as a 3-dimensional cube with each dimension being an RGB component. The cube is then repeatedly divided until we have reduced the color space to the requested number of colors. An average color is then generated from each cube. What makes this different to median-cut is that median-cut divided cubes so that all of the cubes have roughly the same population, where this quantizer divides boxes based on their color volume. This means that the color space is divided into distinct colors, rather than representative colors.

Fields Summary
private static final String
LOG_TAG
private final float[]
mTempHsl
private static final float
BLACK_MAX_LIGHTNESS
private static final float
WHITE_MIN_LIGHTNESS
private static final int
COMPONENT_RED
private static final int
COMPONENT_GREEN
private static final int
COMPONENT_BLUE
private final int[]
mColors
private final android.util.SparseIntArray
mColorPopulations
private final List
mQuantizedColors
private static final Comparator
VBOX_COMPARATOR_VOLUME
Comparator which sorts {@link Vbox} instances based on their volume, in descending order
Constructors Summary
private ColorCutQuantizer(ColorHistogram colorHistogram, int maxColors)
Private constructor.

param
colorHistogram histogram representing an image's pixel data
param
maxColors The maximum number of colors that should be in the result palette.

        final int rawColorCount = colorHistogram.getNumberOfColors();
        final int[] rawColors = colorHistogram.getColors();
        final int[] rawColorCounts = colorHistogram.getColorCounts();

        // First, lets pack the populations into a SparseIntArray so that they can be easily
        // retrieved without knowing a color's index
        mColorPopulations = new SparseIntArray(rawColorCount);
        for (int i = 0; i < rawColors.length; i++) {
            mColorPopulations.append(rawColors[i], rawColorCounts[i]);
        }

        // Now go through all of the colors and keep those which we do not want to ignore
        mColors = new int[rawColorCount];
        int validColorCount = 0;
        for (int color : rawColors) {
            if (!shouldIgnoreColor(color)) {
                mColors[validColorCount++] = color;
            }
        }

        if (validColorCount <= maxColors) {
            // The image has fewer colors than the maximum requested, so just return the colors
            mQuantizedColors = new ArrayList<Swatch>();
            for (final int color : mColors) {
                mQuantizedColors.add(new Swatch(color, mColorPopulations.get(color)));
            }
        } else {
            // We need use quantization to reduce the number of colors
            mQuantizedColors = quantizePixels(validColorCount - 1, maxColors);
        }
    
Methods Summary
static android.support.v7.graphics.ColorCutQuantizerfromBitmap(android.graphics.Bitmap bitmap, int maxColors)
Factory-method to generate a {@link ColorCutQuantizer} from a {@link Bitmap} object.

param
bitmap Bitmap to extract the pixel data from
param
maxColors The maximum number of colors that should be in the result palette.


                                           
          
        final int width = bitmap.getWidth();
        final int height = bitmap.getHeight();

        final int[] pixels = new int[width * height];
        bitmap.getPixels(pixels, 0, width, 0, 0, width, height);

        return new ColorCutQuantizer(new ColorHistogram(pixels), maxColors);
    
private java.util.ListgenerateAverageColors(java.util.Collection vboxes)

        ArrayList<Swatch> colors = new ArrayList<Swatch>(vboxes.size());
        for (Vbox vbox : vboxes) {
            Swatch color = vbox.getAverageColor();
            if (!shouldIgnoreColor(color)) {
                // As we're averaging a color box, we can still get colors which we do not want, so
                // we check again here
                colors.add(color);
            }
        }
        return colors;
    
java.util.ListgetQuantizedColors()

return
the list of quantized colors

        return mQuantizedColors;
    
private static booleanisBlack(float[] hslColor)

return
true if the color represents a color which is close to black.

        return hslColor[2] <= BLACK_MAX_LIGHTNESS;
    
private static booleanisNearRedILine(float[] hslColor)

return
true if the color lies close to the red side of the I line.

        return hslColor[0] >= 10f && hslColor[0] <= 37f && hslColor[1] <= 0.82f;
    
private static booleanisWhite(float[] hslColor)

return
true if the color represents a color which is close to white.

        return hslColor[2] >= WHITE_MIN_LIGHTNESS;
    
private voidmodifySignificantOctet(int dimension, int lowerIndex, int upperIndex)
Modify the significant octet in a packed color int. Allows sorting based on the value of a single color component.

see
Vbox#findSplitPoint()

        switch (dimension) {
            case COMPONENT_RED:
                // Already in RGB, no need to do anything
                break;
            case COMPONENT_GREEN:
                // We need to do a RGB to GRB swap, or vice-versa
                for (int i = lowerIndex; i <= upperIndex; i++) {
                    final int color = mColors[i];
                    mColors[i] = Color.rgb((color >> 8) & 0xFF, (color >> 16) & 0xFF, color & 0xFF);
                }
                break;
            case COMPONENT_BLUE:
                // We need to do a RGB to BGR swap, or vice-versa
                for (int i = lowerIndex; i <= upperIndex; i++) {
                    final int color = mColors[i];
                    mColors[i] = Color.rgb(color & 0xFF, (color >> 8) & 0xFF, (color >> 16) & 0xFF);
                }
                break;
        }
    
private java.util.ListquantizePixels(int maxColorIndex, int maxColors)

        // Create the priority queue which is sorted by volume descending. This means we always
        // split the largest box in the queue
        final PriorityQueue<Vbox> pq = new PriorityQueue<Vbox>(maxColors, VBOX_COMPARATOR_VOLUME);

        // To start, offer a box which contains all of the colors
        pq.offer(new Vbox(0, maxColorIndex));

        // Now go through the boxes, splitting them until we have reached maxColors or there are no
        // more boxes to split
        splitBoxes(pq, maxColors);

        // Finally, return the average colors of the color boxes
        return generateAverageColors(pq);
    
private static booleanshouldIgnoreColor(float[] hslColor)

        return isWhite(hslColor) || isBlack(hslColor) || isNearRedILine(hslColor);
    
private booleanshouldIgnoreColor(int color)

        ColorUtils.RGBtoHSL(Color.red(color), Color.green(color), Color.blue(color), mTempHsl);
        return shouldIgnoreColor(mTempHsl);
    
private static booleanshouldIgnoreColor(android.support.v7.graphics.Palette.Swatch color)

        return shouldIgnoreColor(color.getHsl());
    
private voidsplitBoxes(java.util.PriorityQueue queue, int maxSize)
Iterate through the {@link java.util.Queue}, popping {@link ColorCutQuantizer.Vbox} objects from the queue and splitting them. Once split, the new box and the remaining box are offered back to the queue.

param
queue {@link java.util.PriorityQueue} to poll for boxes
param
maxSize Maximum amount of boxes to split

        while (queue.size() < maxSize) {
            final Vbox vbox = queue.poll();

            if (vbox != null && vbox.canSplit()) {
                // First split the box, and offer the result
                queue.offer(vbox.splitBox());
                // Then offer the box back
                queue.offer(vbox);
            } else {
                // If we get here then there are no more boxes to split, so return
                return;
            }
        }