ColorCutQuantizerpublic 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_VOLUMEComparator which sorts {@link Vbox} instances based on their volume, in descending order |
Constructors Summary |
---|
private ColorCutQuantizer(ColorHistogram colorHistogram, int maxColors)Private constructor.
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.ColorCutQuantizer | fromBitmap(android.graphics.Bitmap bitmap, int maxColors)Factory-method to generate a {@link ColorCutQuantizer} from a {@link Bitmap} object.
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.List | generateAverageColors(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.List | getQuantizedColors()
return mQuantizedColors;
| private static boolean | isBlack(float[] hslColor)
return hslColor[2] <= BLACK_MAX_LIGHTNESS;
| private static boolean | isNearRedILine(float[] hslColor)
return hslColor[0] >= 10f && hslColor[0] <= 37f && hslColor[1] <= 0.82f;
| private static boolean | isWhite(float[] hslColor)
return hslColor[2] >= WHITE_MIN_LIGHTNESS;
| private void | modifySignificantOctet(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.
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.List | quantizePixels(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 boolean | shouldIgnoreColor(float[] hslColor)
return isWhite(hslColor) || isBlack(hslColor) || isNearRedILine(hslColor);
| private boolean | shouldIgnoreColor(int color)
ColorUtils.RGBtoHSL(Color.red(color), Color.green(color), Color.blue(color), mTempHsl);
return shouldIgnoreColor(mTempHsl);
| private static boolean | shouldIgnoreColor(android.support.v7.graphics.Palette.Swatch color)
return shouldIgnoreColor(color.getHsl());
| private void | splitBoxes(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.
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;
}
}
|
|