DO NOT MERGE New Palette support library

Allows the generation of color palette from an Android
Bitmap. Items from the palette can then be used to
enhance the UI.

On my Nexus 5, a Palette instance take 10-20ms to
generate.

Change-Id: I8e16c2c9027c260a1f0ff353affa930cfa2f2c95
(cherry picked from commit 059178a8c7cc80848e5594a9287be91bd924831a)
diff --git a/settings.gradle b/settings.gradle
index b92434f..5444b81 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -13,6 +13,9 @@
 include ':support-mediarouter-v7'
 project(':support-mediarouter-v7').projectDir = new File(rootDir, 'v7/mediarouter')
 
+include ':support-palette-v7'
+project(':support-palette-v7').projectDir = new File(rootDir, 'v7/palette')
+
 include ':support-recyclerview-v7'
 project(':support-recyclerview-v7').projectDir = new File(rootDir, 'v7/recyclerview')
 
diff --git a/v7/palette/.classpath b/v7/palette/.classpath
new file mode 100644
index 0000000..43cb38c
--- /dev/null
+++ b/v7/palette/.classpath
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+    <classpathentry kind="src" path="src"/>
+    <classpathentry kind="src" path="gen"/>
+    <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
+    <classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
+    <classpathentry kind="output" path="bin/classes"/>
+</classpath>
diff --git a/v7/palette/.project b/v7/palette/.project
new file mode 100644
index 0000000..76e11a6
--- /dev/null
+++ b/v7/palette/.project
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+    <name>android-support-v7-palette</name>
+    <comment></comment>
+    <projects>
+    </projects>
+    <buildSpec>
+        <buildCommand>
+            <name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
+            <arguments>
+            </arguments>
+        </buildCommand>
+        <buildCommand>
+            <name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
+            <arguments>
+            </arguments>
+        </buildCommand>
+        <buildCommand>
+            <name>org.eclipse.jdt.core.javabuilder</name>
+            <arguments>
+            </arguments>
+        </buildCommand>
+        <buildCommand>
+            <name>com.android.ide.eclipse.adt.ApkBuilder</name>
+            <arguments>
+            </arguments>
+        </buildCommand>
+    </buildSpec>
+    <natures>
+        <nature>com.android.ide.eclipse.adt.AndroidNature</nature>
+        <nature>org.eclipse.jdt.core.javanature</nature>
+    </natures>
+</projectDescription>
diff --git a/v7/palette/Android.mk b/v7/palette/Android.mk
new file mode 100644
index 0000000..8d75853
--- /dev/null
+++ b/v7/palette/Android.mk
@@ -0,0 +1,22 @@
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+# Here is the final static library that apps can link against.
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-v7-palette
+LOCAL_SDK_VERSION := 7
+LOCAL_SRC_FILES := $(call all-java-files-under,src)
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/v7/palette/AndroidManifest.xml b/v7/palette/AndroidManifest.xml
new file mode 100644
index 0000000..20e14c2
--- /dev/null
+++ b/v7/palette/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.support.v7.palette">
+    <uses-sdk android:minSdkVersion="7"/>
+    <application>
+    </application>
+</manifest>
diff --git a/v7/palette/README.txt b/v7/palette/README.txt
new file mode 100644
index 0000000..4a8b5e4
--- /dev/null
+++ b/v7/palette/README.txt
@@ -0,0 +1 @@
+Library Project including Palette for color extraction from images
diff --git a/v7/palette/build.gradle b/v7/palette/build.gradle
new file mode 100644
index 0000000..3d195ff
--- /dev/null
+++ b/v7/palette/build.gradle
@@ -0,0 +1,13 @@
+apply plugin: 'android-library'
+
+archivesBaseName = 'palette-v7'
+
+android {
+    compileSdkVersion 7
+    buildToolsVersion "19.0.1"
+
+    sourceSets {
+        main.manifest.srcFile 'AndroidManifest.xml'
+        main.java.srcDir 'src'
+    }
+}
diff --git a/v7/palette/project.properties b/v7/palette/project.properties
new file mode 100644
index 0000000..1e106c3
--- /dev/null
+++ b/v7/palette/project.properties
@@ -0,0 +1,15 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-7
+android.library=true
\ No newline at end of file
diff --git a/v7/palette/src/android/support/v7/graphics/ColorCutQuantizer.java b/v7/palette/src/android/support/v7/graphics/ColorCutQuantizer.java
new file mode 100644
index 0000000..2e1ce30
--- /dev/null
+++ b/v7/palette/src/android/support/v7/graphics/ColorCutQuantizer.java
@@ -0,0 +1,437 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.graphics;
+
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.util.SparseIntArray;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.List;
+import java.util.PriorityQueue;
+
+/**
+ * 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.
+ */
+final class ColorCutQuantizer {
+
+    private static final String LOG_TAG = ColorCutQuantizer.class.getSimpleName();
+
+    private final float[] mTempHsl = new float[3];
+
+    private static final int BLACK_FILTER = 22;
+    private static final int WHITE_FILTER = 237;
+
+    private static final int COMPONENT_RED = -3;
+    private static final int COMPONENT_GREEN = -2;
+    private static final int COMPONENT_BLUE = -1;
+
+    private final int[] mColors;
+    private final SparseIntArray mColorPopulations;
+
+    private final List<PaletteItem> mQuantizedColors;
+
+    /**
+     * 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.
+     */
+    static ColorCutQuantizer fromBitmap(Bitmap bitmap, int maxColors) {
+        final int width = bitmap.getWidth();
+        final int height = bitmap.getHeight();
+
+        final int[] rgbPixels = new int[width * height];
+        bitmap.getPixels(rgbPixels, 0, width, 0, 0, width, height);
+
+        return new ColorCutQuantizer(rgbPixels, maxColors);
+    }
+
+    /**
+     * Private constructor.
+     *
+     * @param pixels array of rgb packed ints
+     * @param maxColors The maximum number of colors that should be in the result palette.
+     */
+    ColorCutQuantizer(int[] pixels, int maxColors) {
+        final ColorHistogram colorHist = new ColorHistogram(pixels);
+        final int rawColorCount = colorHist.getNumberOfColors();
+        final int[] rawColors = colorHist.getColors();
+        final int[] rawColorCounts = colorHist.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<PaletteItem>();
+            for (final int color : mColors) {
+                mQuantizedColors.add(new PaletteItem(color, mColorPopulations.get(color)));
+            }
+        } else {
+            // We need use quantization to reduce the number of colors
+            mQuantizedColors = quantizePixels(validColorCount - 1, maxColors);
+        }
+    }
+
+    /**
+     * @return the list of quantized colors
+     */
+    List<PaletteItem> getQuantizedColors() {
+        return mQuantizedColors;
+    }
+
+    private List<PaletteItem> 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);
+    }
+
+    /**
+     * 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
+     */
+    private void splitBoxes(final PriorityQueue<Vbox> queue, final int maxSize) {
+        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;
+            }
+        }
+    }
+
+    private List<PaletteItem> generateAverageColors(Collection<Vbox> vboxes) {
+        ArrayList<PaletteItem> colors = new ArrayList<PaletteItem>(vboxes.size());
+        for (Vbox vbox : vboxes) {
+            PaletteItem 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;
+    }
+
+    /**
+     * Represents a tightly fitting box around a color space.
+     */
+    private class Vbox {
+        private int lowerIndex;
+        private int upperIndex;
+
+        private int minRed, maxRed;
+        private int minGreen, maxGreen;
+        private int minBlue, maxBlue;
+
+        Vbox(int lowerIndex, int upperIndex) {
+            this.lowerIndex = lowerIndex;
+            this.upperIndex = upperIndex;
+            fitBox();
+        }
+
+        int getVolume() {
+            return (maxRed - minRed + 1) * (maxGreen - minGreen + 1) * (maxBlue - minBlue + 1);
+        }
+
+        boolean canSplit() {
+            return getColorCount() > 1;
+        }
+
+        int getColorCount() {
+            return upperIndex - lowerIndex;
+        }
+
+        /**
+         * Recomputes the boundaries of this box to tightly fit the colors within the box.
+         */
+        void fitBox() {
+            // Reset the min and max to opposite values
+            minRed = minGreen = minBlue = 0xFF;
+            maxRed = maxGreen = maxBlue = 0x0;
+
+            for (int i = lowerIndex; i <= upperIndex; i++) {
+                final int color = mColors[i];
+                int r = Color.red(color);
+                int g = Color.green(color);
+                int b = Color.blue(color);
+                if (r > maxRed) {
+                    maxRed = r;
+                }
+                if (r < minRed) {
+                    minRed = r;
+                }
+                if (g > maxGreen) {
+                    maxGreen = g;
+                }
+                if (g < minGreen) {
+                    minGreen = g;
+                }
+                if (b > maxBlue) {
+                    maxBlue = b;
+                }
+                if (b < minBlue) {
+                    minBlue = b;
+                }
+            }
+        }
+
+        /**
+         * Split this color box at the mid-point along it's longest dimension
+         *
+         * @return the new ColorBox
+         */
+        Vbox splitBox() {
+            if (!canSplit()) {
+                throw new IllegalStateException("Can not split a box with only 1 color");
+            }
+
+            // find median along the longest dimension
+            final int splitPoint = findSplitPoint();
+
+            Vbox newBox = new Vbox(splitPoint + 1, upperIndex);
+
+            // Now change this box's upperIndex and recompute the color boundaries
+            upperIndex = splitPoint;
+            fitBox();
+
+            return newBox;
+        }
+
+        /**
+         * @return the dimension which this box is largest in
+         */
+        int getLongestColorDimension() {
+            final int redLength = maxRed - minRed;
+            final int greenLength = maxGreen - minGreen;
+            final int blueLength = maxBlue - minBlue;
+
+            if (redLength >= greenLength && redLength >= blueLength) {
+                return COMPONENT_RED;
+            } else if (greenLength >= redLength && greenLength >= blueLength) {
+                return COMPONENT_GREEN;
+            } else {
+                return COMPONENT_BLUE;
+            }
+        }
+
+        /**
+         * Finds the point within this box's lowerIndex and upperIndex index of where to split.
+         *
+         * This is calculated by finding the longest color dimension, and then sorting the
+         * sub-array based on that dimension value in each color. The colors are then iterated over
+         * until a color is found with at least the midpoint of the whole box's dimension midpoint.
+         *
+         * @return the index of the colors array to split from
+         */
+        int findSplitPoint() {
+            final int longestDimension = getLongestColorDimension();
+
+            // We need to sort the colors in this box based on the longest color dimension.
+            // As we can't use a Comparator to define the sort logic, we modify each color so that
+            // it's most significant is the desired dimension
+            modifySignificantOctet(longestDimension, lowerIndex, upperIndex);
+
+            // Now sort...
+            Arrays.sort(mColors, lowerIndex, upperIndex + 1);
+
+            // Now revert all of the colors so that they are packed as RGB again
+            modifySignificantOctet(longestDimension, lowerIndex, upperIndex);
+
+            final int dimensionMidPoint = midPoint(longestDimension);
+
+            for (int i = lowerIndex; i < upperIndex; i++)  {
+                final int color = mColors[i];
+
+                switch (longestDimension) {
+                    case COMPONENT_RED:
+                        if (Color.red(color) >= dimensionMidPoint) {
+                            return i;
+                        }
+                        break;
+                    case COMPONENT_GREEN:
+                        if (Color.green(color) >= dimensionMidPoint) {
+                            return i;
+                        }
+                        break;
+                    case COMPONENT_BLUE:
+                        if (Color.blue(color) > dimensionMidPoint) {
+                            return i;
+                        }
+                        break;
+                }
+            }
+
+            return lowerIndex;
+        }
+
+        /**
+         * @return the average color of this box.
+         */
+        PaletteItem getAverageColor() {
+            int redSum = 0;
+            int greenSum = 0;
+            int blueSum = 0;
+            int totalPopulation = 0;
+
+            for (int i = lowerIndex; i <= upperIndex; i++) {
+                final int color = mColors[i];
+                final int colorPopulation = mColorPopulations.get(color);
+
+                totalPopulation += colorPopulation;
+                redSum += colorPopulation * Color.red(color);
+                greenSum += colorPopulation * Color.green(color);
+                blueSum += colorPopulation * Color.blue(color);
+            }
+
+            final int redAverage = Math.round(redSum / (float) totalPopulation);
+            final int greenAverage = Math.round(greenSum / (float) totalPopulation);
+            final int blueAverage = Math.round(blueSum / (float) totalPopulation);
+
+            return new PaletteItem(redAverage, greenAverage, blueAverage, totalPopulation);
+        }
+
+        /**
+         * @return the midpoint of this box in the given {@code dimension}
+         */
+        int midPoint(int dimension) {
+            switch (dimension) {
+                case COMPONENT_RED:
+                default:
+                    return (minRed + maxRed) / 2;
+                case COMPONENT_GREEN:
+                    return (minGreen + maxGreen) / 2;
+                case COMPONENT_BLUE:
+                    return (minBlue + maxBlue) / 2;
+            }
+        }
+    }
+
+    /**
+     * Modify the significant octet in a packed color int. Allows sorting based on the value of a
+     * single color component.
+     *
+     * @see Vbox#findSplitPoint()
+     */
+    private void modifySignificantOctet(final int dimension, int lowIndex, int highIndex) {
+        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 = lowIndex; i <= highIndex; 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 = lowIndex; i <= highIndex; i++) {
+                    final int color = mColors[i];
+                    mColors[i] = Color.rgb(color & 0xFF, (color >> 8) & 0xFF, (color >> 16) & 0xFF);
+                }
+                break;
+        }
+    }
+
+    private boolean shouldIgnoreColor(PaletteItem color) {
+        return isWhite(color.getRgb()) || isBlack(color.getRgb()) || isSkinTone(color.getRgb());
+    }
+
+    private boolean shouldIgnoreColor(int color) {
+        return isWhite(color) || isBlack(color) || isSkinTone(color);
+    }
+
+    /**
+     * @return true if the color represents a color which is close to black.
+     */
+    private boolean isBlack(int color) {
+        return ((Color.red(color) + Color.green(color) + Color.blue(color)) / 3f) <= BLACK_FILTER;
+    }
+
+    /**
+     * @return true if the color represents a color which is close to white.
+     */
+    private boolean isWhite(int color) {
+        return ((Color.red(color) + Color.green(color) + Color.blue(color)) / 3f) >= WHITE_FILTER;
+    }
+
+    /**
+     * @return true if the color represents a skin tone.
+     */
+    private boolean isSkinTone(int color) {
+        ColorUtils.RGBtoHSL(Color.red(color), Color.green(color), Color.blue(color), mTempHsl);
+        return mTempHsl[0] >= 10f && mTempHsl[0] <= 37f && mTempHsl[1] <= 0.82f;
+    }
+
+    private static final Comparator<Vbox> VBOX_COMPARATOR_VOLUME = new Comparator<Vbox>() {
+        @Override
+        public int compare(Vbox lhs, Vbox rhs) {
+            return rhs.getVolume() - lhs.getVolume();
+        }
+    };
+
+}
diff --git a/v7/palette/src/android/support/v7/graphics/ColorHistogram.java b/v7/palette/src/android/support/v7/graphics/ColorHistogram.java
new file mode 100644
index 0000000..a196f0a
--- /dev/null
+++ b/v7/palette/src/android/support/v7/graphics/ColorHistogram.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.graphics;
+
+import java.util.Arrays;
+
+/**
+ * Class which provides a histogram for RGB values.
+ */
+final class ColorHistogram {
+
+    private final int[] mColors;
+    private final int[] mColorCounts;
+    private final int mNumberColors;
+
+    /**
+     * A new {@link ColorHistogram} instance.
+     *
+     * @param imagePixels array of image contents
+     */
+    ColorHistogram(final int[] imagePixels) {
+        // Lets take a copy of the original pixels
+        final int[] pixels = new int[imagePixels.length];
+        System.arraycopy(imagePixels, 0, pixels, 0, pixels.length);
+
+        // Sort the pixels to enable counting below
+        Arrays.sort(pixels);
+
+        mNumberColors = countDistinctColors(pixels);
+
+        // Create arrays
+        mColors = new int[mNumberColors];
+        mColorCounts = new int[mNumberColors];
+
+        countFrequencies(pixels);
+    }
+
+    /**
+     * @return number of distinct colors in the image.
+     */
+    int getNumberOfColors() {
+        return mNumberColors;
+    }
+
+    /**
+     * @return an array containing all of the distinct colors in the image.
+     */
+    int[] getColors() {
+        return mColors;
+    }
+
+    /**
+     * @return an array containing the frequency of a distinct colors within the image.
+     */
+    int[] getColorCounts() {
+        return mColorCounts;
+    }
+
+    static int countDistinctColors(final int[] pixels) {
+        if (pixels.length == 0) {
+            return 0;
+        }
+
+        // If we have at least 1 pixel, we have a minimum of 1 color
+        int colorCount = 1;
+        int currentColor = pixels[0];
+
+        // Now iterate from the second pixel to the end, population distinct colors
+        for (int i = 1; i < pixels.length; i++) {
+            // If we encounter a new color, increase the population
+            if (pixels[i] != currentColor) {
+                currentColor = pixels[i];
+                colorCount++;
+            }
+        }
+
+        return colorCount;
+    }
+
+    private void countFrequencies(final int[] pixels) {
+        if (pixels.length == 0) {
+            return;
+        }
+
+        int currentColorIndex = 0;
+        int currentColor = pixels[0];
+
+        mColors[currentColorIndex] = currentColor;
+        mColorCounts[currentColorIndex] = 1;
+
+        // Now iterate from the second pixel to the end, population distinct colors
+        for (int i = 1; i < pixels.length; i++) {
+            if (pixels[i] == currentColor) {
+                // We've hit the same color as before, increase population
+                mColorCounts[currentColorIndex]++;
+            } else {
+                // We've hit a new color, increase index
+                currentColor = pixels[i];
+
+                currentColorIndex++;
+                mColors[currentColorIndex] = currentColor;
+                mColorCounts[currentColorIndex] = 1;
+            }
+        }
+    }
+
+}
diff --git a/v7/palette/src/android/support/v7/graphics/ColorUtils.java b/v7/palette/src/android/support/v7/graphics/ColorUtils.java
new file mode 100644
index 0000000..34b075e
--- /dev/null
+++ b/v7/palette/src/android/support/v7/graphics/ColorUtils.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.graphics;
+
+import android.graphics.Color;
+
+final class ColorUtils {
+
+    private ColorUtils() {}
+
+    /**
+     * @return luma value according to to XYZ color space in the range 0.0 - 1.0
+     */
+    static float calculateXyzLuma(int color) {
+        return (0.2126f * Color.red(color) +
+                0.7152f * Color.green(color) +
+                0.0722f * Color.blue(color)) / 255f;
+    }
+
+    static float calculateContrast(int color1, int color2) {
+        return Math.abs(ColorUtils.calculateXyzLuma(color1) - ColorUtils.calculateXyzLuma(color2));
+    }
+
+    static void RGBtoHSL(int r, int g, int b, float[] hsl) {
+        final float rf = r / 255f;
+        final float gf = g / 255f;
+        final float bf = b / 255f;
+
+        final float max = Math.max(rf, Math.max(gf, bf));
+        final float min = Math.min(rf, Math.min(gf, bf));
+        final float deltaMaxMin = max - min;
+
+        float h, s;
+        float l = (max + min) / 2f;
+
+        if (max == min) {
+            // Monochromatic
+            h = s = 0f;
+        } else {
+            if (max == rf) {
+                h = ((gf - bf) / deltaMaxMin) % 6f;
+            } else if (max == gf) {
+                h = ((bf - rf) / deltaMaxMin) + 2f;
+            } else {
+                h = ((rf - gf) / deltaMaxMin) + 4f;
+            }
+
+            s =  deltaMaxMin / (1f - Math.abs(2f * l - 1f));
+        }
+
+        hsl[0] = (h * 60f) % 360f;
+        hsl[1] = s;
+        hsl[2] = l;
+    }
+
+    static int HSLtoRGB (float[] hsl) {
+        final float h = hsl[0];
+        final float s = hsl[1];
+        final float l = hsl[2];
+
+        final float c = (1f - Math.abs(2 * l - 1f)) * s;
+        final float m = l - 0.5f * c;
+        final float x = c * (1f - Math.abs((h / 60f % 2f) - 1f));
+
+        final int hueSegment = (int) h / 60;
+
+        int r = 0, g = 0, b = 0;
+
+        switch (hueSegment) {
+            case 0:
+                r = Math.round(255 * (c + m));
+                g = Math.round(255 * (x + m));
+                b = Math.round(255 * m);
+                break;
+            case 1:
+                r = Math.round(255 * (x + m));
+                g = Math.round(255 * (c + m));
+                b = Math.round(255 * m);
+                break;
+            case 2:
+                r = Math.round(255 * m);
+                g = Math.round(255 * (c + m));
+                b = Math.round(255 * (x + m));
+                break;
+            case 3:
+                r = Math.round(255 * m);
+                g = Math.round(255 * (x + m));
+                b = Math.round(255 * (c + m));
+                break;
+            case 4:
+                r = Math.round(255 * (x + m));
+                g = Math.round(255 * m);
+                b = Math.round(255 * (c + m));
+                break;
+            case 5:
+            case 6:
+                r = Math.round(255 * (c + m));
+                g = Math.round(255 * m);
+                b = Math.round(255 * (x + m));
+                break;
+        }
+
+        r = Math.max(0, Math.min(255, r));
+        g = Math.max(0, Math.min(255, g));
+        b = Math.max(0, Math.min(255, b));
+
+        return Color.rgb(r, g, b);
+    }
+
+}
diff --git a/v7/palette/src/android/support/v7/graphics/Palette.java b/v7/palette/src/android/support/v7/graphics/Palette.java
new file mode 100644
index 0000000..9e57760
--- /dev/null
+++ b/v7/palette/src/android/support/v7/graphics/Palette.java
@@ -0,0 +1,392 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.graphics;
+
+import android.graphics.Bitmap;
+import android.os.AsyncTask;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A helper class to extract prominent colors from an image.
+ * <p>
+ * A number of colors with different profiles are extracted from the image:
+ * <ul>
+ *     <li>Vibrant</li>
+ *     <li>Vibrant Dark</li>
+ *     <li>Vibrant Light</li>
+ *     <li>Muted</li>
+ *     <li>Muted Dark</li>
+ *     <li>Muted Light</li>
+ * </ul>
+ * These can be retrieved from the appropriate getter method.
+ *
+ * <p>
+ * Instances can be created with the synchronous factory methods {@link #generate(Bitmap)} and
+ * {@link #generate(Bitmap, int)}.
+ * <p>
+ * These should be called on a background thread, ideally the one in
+ * which you load your images on. Sometimes that is not possible, so asynchronous factory methods
+ * have also been provided: {@link #generateAsync(Bitmap, PaletteAsyncListener)} and
+ * {@link #generateAsync(Bitmap, int, PaletteAsyncListener)}. These can be used as so:
+ *
+ * <pre>
+ * Palette.generateAsync(bitmap, new Palette.PaletteAsyncListener() {
+ *     @Override
+ *     public void onGenerated(Palette palette) {
+ *         // Do something with colors...
+ *     }
+ * });
+ * </pre>
+ */
+public final class Palette {
+
+    /**
+     * Listener to be used with {@link #generateAsync(Bitmap, PaletteAsyncListener)} or
+     * {@link #generateAsync(Bitmap, int, PaletteAsyncListener)}
+     */
+    public interface PaletteAsyncListener {
+
+        /**
+         * Called when the {@link Palette} has been generated.
+         */
+        void onGenerated(Palette palette);
+    }
+
+    private static final int CALCULATE_BITMAP_MIN_DIMENSION = 100;
+    private static final int DEFAULT_CALCULATE_NUMBER_COLORS = 16;
+
+    private static final float TARGET_DARK_LUMA = 0.26f;
+    private static final float MAX_DARK_LUMA = 0.45f;
+
+    private static final float MIN_LIGHT_LUMA = 0.55f;
+    private static final float TARGET_LIGHT_LUMA = 0.74f;
+
+    private static final float MIN_NORMAL_LUMA = 0.3f;
+    private static final float TARGET_NORMAL_LUMA = 0.5f;
+    private static final float MAX_NORMAL_LUMA = 0.7f;
+
+    private static final float TARGET_MUTED_SATURATION = 0.3f;
+    private static final float MAX_MUTED_SATURATION = 0.4f;
+
+    private static final float TARGET_VIBRANT_SATURATION = 1f;
+    private static final float MIN_VIBRANT_SATURATION = 0.35f;
+
+    private final List<PaletteItem> mPallete;
+    private final int mHighestPopulation;
+
+    private PaletteItem mVibrantColor;
+    private PaletteItem mMutedColor;
+
+    private PaletteItem mDarkVibrantColor;
+    private PaletteItem mDarkMutedColor;
+
+    private PaletteItem mLightVibrantColor;
+    private PaletteItem mLightMutedColor;
+
+    /**
+     * Generate a {@link Palette} from a {@link Bitmap} using the default number of colors.
+     */
+    public static Palette generate(Bitmap bitmap) {
+        return generate(bitmap, DEFAULT_CALCULATE_NUMBER_COLORS);
+    }
+
+    /**
+     * Generate a {@link Palette} from a {@link Bitmap} using the specified {@code numColors}.
+     * Good values for {@code numColors} depend on the source image type.
+     * For landscapes, a good values are in the range 12-16. For images which are largely made up
+     * of people's faces then this value should be increased to 24-32.
+     *
+     * @param numColors The maximum number of colors in the generated palette. Increasing this
+     *                  number will increase the time needed to compute the values.
+     */
+    public static Palette generate(Bitmap bitmap, int numColors) {
+        if (bitmap == null) {
+            throw new IllegalArgumentException("bitmap can not be null");
+        }
+
+        // First we'll scale down the bitmap so it's shortest dimension is 100px
+        final Bitmap scaledBitmap = scaleBitmapDown(bitmap);
+
+        // Now generate a quantizer from the Bitmap
+        ColorCutQuantizer quantizer = ColorCutQuantizer.fromBitmap(scaledBitmap, numColors);
+
+        // If created a new bitmap, recycle it
+        if (scaledBitmap != bitmap) {
+            scaledBitmap.recycle();
+        }
+
+        // Now return a ColorExtractor instance
+        return new Palette(quantizer.getQuantizedColors());
+    }
+
+    /**
+     * Generate a {@link Palette} asynchronously. {@link PaletteAsyncListener#onGenerated(Palette)}
+     * will be called with the created instance. The resulting {@link Palette} is the same as
+     * what would be created by calling {@link #generate(Bitmap)}.
+     *
+     * @param listener Listener to be invoked when the {@link Palette} has been generated.
+     *
+     * @return the {@link android.os.AsyncTask} used to asynchronously generate the instance.
+     */
+    public static AsyncTask<Void, Void, Palette> generateAsync(
+            Bitmap bitmap, PaletteAsyncListener listener) {
+        return generateAsync(bitmap, DEFAULT_CALCULATE_NUMBER_COLORS, listener);
+    }
+
+    /**
+     * Generate a {@link Palette} asynchronously. {@link PaletteAsyncListener#onGenerated(Palette)}
+     * will be called with the created instance. The resulting {@link Palette} is the same as what
+     * would be created by calling {@link #generate(Bitmap, int)}.
+     *
+     * @param listener Listener to be invoked when the {@link Palette} has been generated.
+     *
+     * @return the {@link android.os.AsyncTask} used to asynchronously generate the instance.
+     */
+    public static AsyncTask<Void, Void, Palette> generateAsync(
+            final Bitmap bitmap, final int numColors, final PaletteAsyncListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener can not be null");
+        }
+
+        AsyncTask<Void, Void, Palette> task = new AsyncTask<Void, Void, Palette>() {
+            @Override
+            protected Palette doInBackground(Void... voids) {
+                return generate(bitmap, numColors);
+            }
+
+            @Override
+            protected void onPostExecute(Palette colorExtractor) {
+                super.onPostExecute(colorExtractor);
+                listener.onGenerated(colorExtractor);
+            }
+        };
+        task.execute();
+        return task;
+    }
+
+    private Palette(List<PaletteItem> palette) {
+        mPallete = palette;
+        mHighestPopulation = findMaxPopulation();
+
+        mVibrantColor = findColor(TARGET_NORMAL_LUMA, MIN_NORMAL_LUMA, MAX_NORMAL_LUMA,
+                TARGET_VIBRANT_SATURATION, MIN_VIBRANT_SATURATION, 1f);
+
+        mLightVibrantColor = findColor(TARGET_LIGHT_LUMA, MIN_LIGHT_LUMA, 1f,
+                TARGET_VIBRANT_SATURATION, MIN_VIBRANT_SATURATION, 1f);
+
+        mDarkVibrantColor = findColor(TARGET_DARK_LUMA, 0f, MAX_DARK_LUMA,
+                TARGET_VIBRANT_SATURATION, MIN_VIBRANT_SATURATION, 1f);
+
+        mMutedColor = findColor(TARGET_NORMAL_LUMA, MIN_NORMAL_LUMA, MAX_NORMAL_LUMA,
+                TARGET_MUTED_SATURATION, 0f, MAX_MUTED_SATURATION);
+
+        mLightMutedColor = findColor(TARGET_LIGHT_LUMA, MIN_LIGHT_LUMA, 1f,
+                TARGET_MUTED_SATURATION, 0f, MAX_MUTED_SATURATION);
+
+        mDarkMutedColor = findColor(TARGET_DARK_LUMA, 0f, MAX_DARK_LUMA,
+                TARGET_MUTED_SATURATION, 0f, MAX_MUTED_SATURATION);
+
+        // Now try and generate any missing colors
+        generateEmptyColors();
+    }
+
+    /**
+     * The total palette of colors which make up the image.
+     */
+    public List<PaletteItem> getPallete() {
+        return Collections.unmodifiableList(mPallete);
+    }
+
+    /**
+     * Returns the most vibrant color in the image. Might be null.
+     */
+    public PaletteItem getVibrantColor() {
+        return mVibrantColor;
+    }
+
+    /**
+     * Returns a light and vibrant color from the image. Might be null.
+     */
+    public PaletteItem getLightVibrantColor() {
+        return mLightVibrantColor;
+    }
+
+    /**
+     * Returns a dark and vibrant color from the image. Might be null.
+     */
+    public PaletteItem getDarkVibrantColor() {
+        return mDarkVibrantColor;
+    }
+
+    /**
+     * Returns a muted color from the image. Might be null.
+     */
+    public PaletteItem getMutedColor() {
+        return mMutedColor;
+    }
+
+    /**
+     * Returns a muted and light color from the image. Might be null.
+     */
+    public PaletteItem getLightMutedColor() {
+        return mLightMutedColor;
+    }
+
+    /**
+     * Returns a muted and dark color from the image. Might be null.
+     */
+    public PaletteItem getDarkMutedColor() {
+        return mDarkMutedColor;
+    }
+
+    /**
+     * @return true if we have already selected {@code item}
+     */
+    private boolean isAlreadySelected(PaletteItem item) {
+        return mVibrantColor == item || mDarkVibrantColor == item || mLightVibrantColor == item ||
+                mMutedColor == item || mDarkMutedColor == item || mLightMutedColor == item;
+    }
+
+    private PaletteItem findColor(float targetLuma, float minLuma, float maxLuma,
+                                float targetSaturation, float minSaturation, float maxSaturation) {
+        PaletteItem max = null;
+        float maxValue = 0f;
+
+        for (PaletteItem paletteItem : mPallete) {
+            final float sat = paletteItem.getHsl()[1];
+            final float luma = paletteItem.getHsl()[2];
+
+            if (sat >= minSaturation && sat <= maxSaturation &&
+                    luma >= minLuma && luma <= maxLuma &&
+                    !isAlreadySelected(paletteItem)) {
+                float thisValue = createComparisonValue(sat, targetSaturation, luma, targetLuma,
+                        paletteItem.getPopulation(), mHighestPopulation);
+                if (max == null || thisValue > maxValue) {
+                    max = paletteItem;
+                    maxValue = thisValue;
+                }
+            }
+        }
+
+        return max;
+    }
+
+    /**
+     * Try and generate any missing colors from the colors we did find.
+     */
+    private void generateEmptyColors() {
+        if (mVibrantColor == null) {
+            // If we do not have a vibrant color...
+            if (mDarkVibrantColor != null) {
+                // ...but we do have a dark vibrant, generate the value by modifying the luma
+                final float[] newHsl = copyHslValues(mDarkVibrantColor);
+                newHsl[2] = TARGET_NORMAL_LUMA;
+                mVibrantColor = new PaletteItem(ColorUtils.HSLtoRGB(newHsl), 0);
+            }
+        }
+
+        if (mDarkVibrantColor == null) {
+            // If we do not have a dark vibrant color...
+            if (mVibrantColor != null) {
+                // ...but we do have a vibrant, generate the value by modifying the luma
+                final float[] newHsl = copyHslValues(mVibrantColor);
+                newHsl[2] = TARGET_DARK_LUMA;
+                mDarkVibrantColor = new PaletteItem(ColorUtils.HSLtoRGB(newHsl), 0);
+            }
+        }
+    }
+
+    /**
+     * Find the {@link PaletteItem} with the highest population value and return the population.
+     */
+    private int findMaxPopulation() {
+        int population = 0;
+        for (PaletteItem item : mPallete) {
+            population = Math.max(population, item.getPopulation());
+        }
+        return population;
+    }
+
+    /**
+     * Scale the bitmap down so that it's smallest dimension is
+     * {@value #CALCULATE_BITMAP_MIN_DIMENSION}px. If {@code bitmap} is smaller than this, than it
+     * is returned.
+     */
+    private static Bitmap scaleBitmapDown(Bitmap bitmap) {
+        final int minDimension = Math.min(bitmap.getWidth(), bitmap.getHeight());
+
+        if (minDimension <= CALCULATE_BITMAP_MIN_DIMENSION) {
+            // If the bitmap is small enough already, just return it
+            return bitmap;
+        }
+
+        final float scaleRatio = CALCULATE_BITMAP_MIN_DIMENSION / (float) minDimension;
+        return Bitmap.createScaledBitmap(bitmap,
+                Math.round(bitmap.getWidth() * scaleRatio),
+                Math.round(bitmap.getHeight() * scaleRatio),
+                false);
+    }
+
+    private static float createComparisonValue(float saturation, float targetSaturation,
+            float luma, float targetLuma,
+            int population, int highestPopulation) {
+        return weightedMean(
+                invertDiff(saturation, targetSaturation), 3f,
+                invertDiff(luma, targetLuma), 6.5f,
+                population / (float) highestPopulation, 0.5f
+        );
+    }
+
+    /**
+     * Copy a {@link PaletteItem}'s HSL values into a new float[].
+     */
+    private static float[] copyHslValues(PaletteItem color) {
+        final float[] newHsl = new float[3];
+        System.arraycopy(color.getHsl(), 0, newHsl, 0, 3);
+        return newHsl;
+    }
+
+    /**
+     * Returns a value in the range 0-1. 1 is returned when {@code value} equals the
+     * {@code targetValue} and then decreases as the absolute difference between {@code value} and
+     * {@code targetValue} increases.
+     *
+     * @param value the item's value
+     * @param targetValue the value which we desire
+     */
+    private static float invertDiff(float value, float targetValue) {
+        return 1f - Math.abs(value - targetValue);
+    }
+
+    private static float weightedMean(float... values) {
+        float sum = 0f;
+        float sumWeight = 0f;
+
+        for (int i = 0; i < values.length; i += 2) {
+            float value = values[i];
+            float weight = values[i + 1];
+
+            sum += (value * weight);
+            sumWeight += weight;
+        }
+
+        return sum / sumWeight;
+    }
+
+}
diff --git a/v7/palette/src/android/support/v7/graphics/PaletteItem.java b/v7/palette/src/android/support/v7/graphics/PaletteItem.java
new file mode 100644
index 0000000..ce73059
--- /dev/null
+++ b/v7/palette/src/android/support/v7/graphics/PaletteItem.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.graphics;
+
+import android.graphics.Color;
+
+import java.util.Arrays;
+
+/**
+ * Represents a color generated from an image's palette. The RGB color can be retrieved by
+ * calling {@link #getRgb()}.
+ */
+public final class PaletteItem {
+
+    final int red, green, blue;
+    final int rgb;
+    final int population;
+
+    private float[] hsl;
+
+    PaletteItem(int rgbColor, int population) {
+        this.red = Color.red(rgbColor);
+        this.green = Color.green(rgbColor);
+        this.blue = Color.blue(rgbColor);
+        this.rgb = rgbColor;
+        this.population = population;
+    }
+
+    PaletteItem(int red, int green, int blue, int population) {
+        this.red = red;
+        this.green = green;
+        this.blue = blue;
+        this.rgb = Color.rgb(red, green, blue);
+        this.population = population;
+    }
+
+    /**
+     * @return this item's RGB color value
+     */
+    public int getRgb() {
+        return rgb;
+    }
+
+    /**
+     * Return this item's HSL values.
+     *     hsv[0] is Hue [0 .. 360)
+     *     hsv[1] is Saturation [0...1]
+     *     hsv[2] is Lightness [0...1]
+     */
+    public float[] getHsl() {
+        if (hsl == null) {
+            // Lazily generate HSL values from RGB
+            hsl = new float[3];
+            ColorUtils.RGBtoHSL(red, green, blue, hsl);
+        }
+        return hsl;
+    }
+
+    /**
+     * @return the number of pixels represented by this color
+     */
+    int getPopulation() {
+        return population;
+    }
+
+    public String toString() {
+        return new StringBuilder(getClass().getSimpleName()).append(" ")
+                .append("[").append(Integer.toHexString(getRgb())).append(']')
+                .append("[HSL: ").append(Arrays.toString(getHsl())).append(']')
+                .append("[Population: ").append(population).append(']').toString();
+    }
+}