Merge "Add filter API to Palette" into lmp-mr1-ub-dev
diff --git a/v7/palette/api/current.txt b/v7/palette/api/current.txt
index d92c0dd..1b6c745 100644
--- a/v7/palette/api/current.txt
+++ b/v7/palette/api/current.txt
@@ -25,12 +25,18 @@
   public static final class Palette.Builder {
     ctor public Palette.Builder(android.graphics.Bitmap);
     ctor public Palette.Builder(java.util.List<android.support.v7.graphics.Palette.Swatch>);
+    method public android.support.v7.graphics.Palette.Builder addFilter(android.support.v7.graphics.Palette.Filter);
+    method public android.support.v7.graphics.Palette.Builder clearFilters();
     method public android.support.v7.graphics.Palette generate();
     method public android.os.AsyncTask<android.graphics.Bitmap, java.lang.Void, android.support.v7.graphics.Palette> generate(android.support.v7.graphics.Palette.PaletteAsyncListener);
     method public android.support.v7.graphics.Palette.Builder maximumColorCount(int);
     method public android.support.v7.graphics.Palette.Builder resizeBitmapSize(int);
   }
 
+  public static abstract interface Palette.Filter {
+    method public abstract boolean isAllowed(int, float[]);
+  }
+
   public static abstract interface Palette.PaletteAsyncListener {
     method public abstract void onGenerated(android.support.v7.graphics.Palette);
   }
diff --git a/v7/palette/src/main/java/android/support/v7/graphics/ColorCutQuantizer.java b/v7/palette/src/main/java/android/support/v7/graphics/ColorCutQuantizer.java
index 9355502..3b89748 100644
--- a/v7/palette/src/main/java/android/support/v7/graphics/ColorCutQuantizer.java
+++ b/v7/palette/src/main/java/android/support/v7/graphics/ColorCutQuantizer.java
@@ -16,7 +16,6 @@
 
 package android.support.v7.graphics;
 
-import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.support.v4.graphics.ColorUtils;
 import android.support.v7.graphics.Palette.Swatch;
@@ -47,9 +46,6 @@
     private static final String LOG_TAG = "ColorCutQuantizer";
     private static final boolean LOG_TIMINGS = false;
 
-    private static final float BLACK_MAX_LIGHTNESS = 0.05f;
-    private static final float WHITE_MIN_LIGHTNESS = 0.95f;
-
     private static final int COMPONENT_RED = -3;
     private static final int COMPONENT_GREEN = -2;
     private static final int COMPONENT_BLUE = -1;
@@ -61,33 +57,20 @@
     final int[] mHistogram;
     final List<Swatch> mQuantizedColors;
     final TimingLogger mTimingLogger;
+    final Palette.Filter[] mFilters;
 
     private final float[] mTempHsl = new float[3];
 
     /**
-     * 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[] pixels = new int[width * height];
-        bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
-
-        return new ColorCutQuantizer(pixels, maxColors);
-    }
-
-    /**
-     * Private constructor.
+     * Constructor.
      *
      * @param pixels histogram representing an image's pixel data
      * @param maxColors The maximum number of colors that should be in the result palette.
+     * @param filters Set of filters to use in the quantization stage
      */
-    private ColorCutQuantizer(final int[] pixels, final int maxColors) {
+    ColorCutQuantizer(final int[] pixels, final int maxColors, final Palette.Filter[] filters) {
         mTimingLogger = LOG_TIMINGS ? new TimingLogger(LOG_TAG, "Creation") : null;
+        mFilters = filters;
 
         final int[] hist = mHistogram = new int[1 << (QUANTIZE_WORD_WIDTH * 3)];
         for (int i = 0; i < pixels.length; i++) {
@@ -443,37 +426,24 @@
     }
 
     private boolean shouldIgnoreColor(int color565) {
-        ColorUtils.colorToHSL(approximateToRgb888(color565), mTempHsl);
-        return shouldIgnoreColor(mTempHsl);
+        final int rgb = approximateToRgb888(color565);
+        ColorUtils.colorToHSL(rgb, mTempHsl);
+        return shouldIgnoreColor(rgb, mTempHsl);
     }
 
-    private static boolean shouldIgnoreColor(Swatch color) {
-        return shouldIgnoreColor(color.getHsl());
+    private boolean shouldIgnoreColor(Swatch color) {
+        return shouldIgnoreColor(color.getRgb(), color.getHsl());
     }
 
-    private static boolean shouldIgnoreColor(float[] hslColor) {
-        return isWhite(hslColor) || isBlack(hslColor) || isNearRedILine(hslColor);
-    }
-
-    /**
-     * @return true if the color represents a color which is close to black.
-     */
-    private static boolean isBlack(float[] hslColor) {
-        return hslColor[2] <= BLACK_MAX_LIGHTNESS;
-    }
-
-    /**
-     * @return true if the color represents a color which is close to white.
-     */
-    private static boolean isWhite(float[] hslColor) {
-        return hslColor[2] >= WHITE_MIN_LIGHTNESS;
-    }
-
-    /**
-     * @return true if the color lies close to the red side of the I line.
-     */
-    private static boolean isNearRedILine(float[] hslColor) {
-        return hslColor[0] >= 10f && hslColor[0] <= 37f && hslColor[1] <= 0.82f;
+    private boolean shouldIgnoreColor(int rgb, float[] hsl) {
+        if (mFilters != null && mFilters.length > 0) {
+            for (int i = 0, count = mFilters.length; i < count; i++) {
+                if (!mFilters[i].isAllowed(rgb, hsl)) {
+                    return true;
+                }
+            }
+        }
+        return false;
     }
 
     /**
diff --git a/v7/palette/src/main/java/android/support/v7/graphics/Palette.java b/v7/palette/src/main/java/android/support/v7/graphics/Palette.java
index 7a4e7ee..c3f3e78 100644
--- a/v7/palette/src/main/java/android/support/v7/graphics/Palette.java
+++ b/v7/palette/src/main/java/android/support/v7/graphics/Palette.java
@@ -23,6 +23,7 @@
 import android.support.v4.os.AsyncTaskCompat;
 import android.util.TimingLogger;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
@@ -427,6 +428,7 @@
         private Bitmap mBitmap;
         private int mMaxColors = DEFAULT_CALCULATE_NUMBER_COLORS;
         private int mResizeMaxDimension = DEFAULT_RESIZE_BITMAP_MAX_DIMENSION;
+        private final List<Filter> mFilters = new ArrayList<>();
 
         private Generator mGenerator;
 
@@ -434,6 +436,7 @@
          * Construct a new {@link Builder} using a source {@link Bitmap}
          */
         public Builder(Bitmap bitmap) {
+            this();
             if (bitmap == null || bitmap.isRecycled()) {
                 throw new IllegalArgumentException("Bitmap is not valid");
             }
@@ -445,12 +448,17 @@
          * Typically only used for testing.
          */
         public Builder(List<Swatch> swatches) {
+            this();
             if (swatches == null || swatches.isEmpty()) {
                 throw new IllegalArgumentException("List of Swatches is not valid");
             }
             mSwatches = swatches;
         }
 
+        private Builder() {
+            mFilters.add(DEFAULT_FILTER);
+        }
+
         /**
          * Set the {@link Generator} to use when generating the {@link Palette}. If this is called
          * with {@code null} then the default generator will be used.
@@ -489,6 +497,28 @@
         }
 
         /**
+         * Clear all added filters. This includes any default filters added automatically by
+         * {@link Palette}.
+         */
+        public Builder clearFilters() {
+            mFilters.clear();
+            return this;
+        }
+
+        /**
+         * Add a filter to be able to have fine grained controlled over the colors which are
+         * allowed in the resulting palette.
+         *
+         * @param filter filter to add.
+         */
+        public Builder addFilter(Filter filter) {
+            if (filter != null) {
+                mFilters.add(filter);
+            }
+            return this;
+        }
+
+        /**
          * Generate and return the {@link Palette} synchronously.
          */
         public Palette generate() {
@@ -514,8 +544,13 @@
                 }
 
                 // Now generate a quantizer from the Bitmap
-                ColorCutQuantizer quantizer = ColorCutQuantizer
-                        .fromBitmap(scaledBitmap, mMaxColors);
+                final int width = scaledBitmap.getWidth();
+                final int height = scaledBitmap.getHeight();
+                final int[] pixels = new int[width * height];
+                scaledBitmap.getPixels(pixels, 0, width, 0, 0, width, height);
+
+                final ColorCutQuantizer quantizer = new ColorCutQuantizer(pixels, mMaxColors,
+                        mFilters.isEmpty() ? null : mFilters.toArray(new Filter[mFilters.size()]));
 
                 // If created a new bitmap, recycle it
                 if (scaledBitmap != mBitmap) {
@@ -633,4 +668,55 @@
         }
     }
 
+    /**
+     * A Filter provides a mechanism for exercising fine-grained control over which colors
+     * are valid within a resulting {@link Palette}.
+     */
+    public interface Filter {
+        /**
+         * Hook to allow clients to be able filter colors from resulting palette.
+         *
+         * @param rgb the color in RGB888.
+         * @param hsl HSL representation of the color.
+         *
+         * @return true if the color is allowed, false if not.
+         *
+         * @see Builder#addFilter(Filter)
+         */
+        boolean isAllowed(int rgb, float[] hsl);
+    }
+
+    /**
+     * The default filter.
+     */
+    private static final Filter DEFAULT_FILTER = new Filter() {
+        private static final float BLACK_MAX_LIGHTNESS = 0.05f;
+        private static final float WHITE_MIN_LIGHTNESS = 0.95f;
+
+        @Override
+        public boolean isAllowed(int rgb, float[] hsl) {
+            return !isWhite(hsl) && !isBlack(hsl) && !isNearRedILine(hsl);
+        }
+
+        /**
+         * @return true if the color represents a color which is close to black.
+         */
+        private boolean isBlack(float[] hslColor) {
+            return hslColor[2] <= BLACK_MAX_LIGHTNESS;
+        }
+
+        /**
+         * @return true if the color represents a color which is close to white.
+         */
+        private boolean isWhite(float[] hslColor) {
+            return hslColor[2] >= WHITE_MIN_LIGHTNESS;
+        }
+
+        /**
+         * @return true if the color lies close to the red side of the I line.
+         */
+        private boolean isNearRedILine(float[] hslColor) {
+            return hslColor[0] >= 10f && hslColor[0] <= 37f && hslColor[1] <= 0.82f;
+        }
+    };
 }