Add new extensible API to Palette

The buckets are now abstracted into a Target
class, with an associated Builder to allow the
creation of custom targets.

Change-Id: I654a081cef0e836cb867098fc9634eaca5aaf37b
diff --git a/v7/palette/api/current.txt b/v7/palette/api/current.txt
index fac6a55..3b24b77 100644
--- a/v7/palette/api/current.txt
+++ b/v7/palette/api/current.txt
@@ -7,6 +7,7 @@
     method public static deprecated android.support.v7.graphics.Palette generate(android.graphics.Bitmap, int);
     method public static deprecated android.os.AsyncTask<android.graphics.Bitmap, java.lang.Void, android.support.v7.graphics.Palette> generateAsync(android.graphics.Bitmap, android.support.v7.graphics.Palette.PaletteAsyncListener);
     method public static deprecated android.os.AsyncTask<android.graphics.Bitmap, java.lang.Void, android.support.v7.graphics.Palette> generateAsync(android.graphics.Bitmap, int, android.support.v7.graphics.Palette.PaletteAsyncListener);
+    method public int getColorForTarget(android.support.v7.graphics.Target, int);
     method public int getDarkMutedColor(int);
     method public android.support.v7.graphics.Palette.Swatch getDarkMutedSwatch();
     method public int getDarkVibrantColor(int);
@@ -17,7 +18,9 @@
     method public android.support.v7.graphics.Palette.Swatch getLightVibrantSwatch();
     method public int getMutedColor(int);
     method public android.support.v7.graphics.Palette.Swatch getMutedSwatch();
+    method public android.support.v7.graphics.Palette.Swatch getSwatchForTarget(android.support.v7.graphics.Target);
     method public java.util.List<android.support.v7.graphics.Palette.Swatch> getSwatches();
+    method public java.util.List<android.support.v7.graphics.Target> getTargets();
     method public int getVibrantColor(int);
     method public android.support.v7.graphics.Palette.Swatch getVibrantSwatch();
   }
@@ -26,12 +29,15 @@
     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 addTarget(android.support.v7.graphics.Target);
     method public android.support.v7.graphics.Palette.Builder clearFilters();
     method public android.support.v7.graphics.Palette.Builder clearRegion();
+    method public android.support.v7.graphics.Palette.Builder clearTargets();
     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);
+    method public android.support.v7.graphics.Palette.Builder resizeBitmapArea(int);
+    method public deprecated android.support.v7.graphics.Palette.Builder resizeBitmapSize(int);
     method public android.support.v7.graphics.Palette.Builder setRegion(int, int, int, int);
   }
 
@@ -52,5 +58,40 @@
     method public int getTitleTextColor();
   }
 
+  public final class Target {
+    method public float getLightnessWeight();
+    method public float getMaximumLightness();
+    method public float getMaximumSaturation();
+    method public float getMinimumLightness();
+    method public float getMinimumSaturation();
+    method public float getPopulationWeight();
+    method public float getSaturationWeight();
+    method public float getTargetLightness();
+    method public float getTargetSaturation();
+    method public boolean isExclusive();
+    field public static final android.support.v7.graphics.Target DARK_MUTED;
+    field public static final android.support.v7.graphics.Target DARK_VIBRANT;
+    field public static final android.support.v7.graphics.Target LIGHT_MUTED;
+    field public static final android.support.v7.graphics.Target LIGHT_VIBRANT;
+    field public static final android.support.v7.graphics.Target MUTED;
+    field public static final android.support.v7.graphics.Target VIBRANT;
+  }
+
+  public static final class Target.Builder {
+    ctor public Target.Builder();
+    ctor public Target.Builder(android.support.v7.graphics.Target);
+    method public android.support.v7.graphics.Target build();
+    method public android.support.v7.graphics.Target.Builder setExclusive(boolean);
+    method public android.support.v7.graphics.Target.Builder setLightnessWeight(float);
+    method public android.support.v7.graphics.Target.Builder setMaximumLightness(float);
+    method public android.support.v7.graphics.Target.Builder setMaximumSaturation(float);
+    method public android.support.v7.graphics.Target.Builder setMinimumLightness(float);
+    method public android.support.v7.graphics.Target.Builder setMinimumSaturation(float);
+    method public android.support.v7.graphics.Target.Builder setPopulationWeight(float);
+    method public android.support.v7.graphics.Target.Builder setSaturationWeight(float);
+    method public android.support.v7.graphics.Target.Builder setTargetLightness(float);
+    method public android.support.v7.graphics.Target.Builder setTargetSaturation(float);
+  }
+
 }
 
diff --git a/v7/palette/src/main/java/android/support/v7/graphics/DefaultGenerator.java b/v7/palette/src/main/java/android/support/v7/graphics/DefaultGenerator.java
deleted file mode 100644
index 3ee2bfa..0000000
--- a/v7/palette/src/main/java/android/support/v7/graphics/DefaultGenerator.java
+++ /dev/null
@@ -1,242 +0,0 @@
-/*
- * Copyright 2015 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.support.v4.graphics.ColorUtils;
-import android.support.v7.graphics.Palette.Swatch;
-
-import java.util.List;
-
-class DefaultGenerator extends Palette.Generator {
-
-    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 static final float WEIGHT_SATURATION = 3f;
-    private static final float WEIGHT_LUMA = 6f;
-    private static final float WEIGHT_POPULATION = 1f;
-
-    private List<Swatch> mSwatches;
-
-    private int mHighestPopulation;
-
-    private Swatch mVibrantSwatch;
-    private Swatch mMutedSwatch;
-    private Swatch mDarkVibrantSwatch;
-    private Swatch mDarkMutedSwatch;
-    private Swatch mLightVibrantSwatch;
-    private Swatch mLightMutedSwatch;
-
-    @Override
-    public void generate(final List<Swatch> swatches) {
-        mSwatches = swatches;
-
-        mHighestPopulation = findMaxPopulation();
-
-        generateVariationColors();
-
-        // Now try and generate any missing colors
-        generateEmptySwatches();
-    }
-
-    @Override
-    public Swatch getVibrantSwatch() {
-        return mVibrantSwatch;
-    }
-
-    @Override
-    public Swatch getLightVibrantSwatch() {
-        return mLightVibrantSwatch;
-    }
-
-    @Override
-    public Swatch getDarkVibrantSwatch() {
-        return mDarkVibrantSwatch;
-    }
-
-    @Override
-    public Swatch getMutedSwatch() {
-        return mMutedSwatch;
-    }
-
-    @Override
-    public Swatch getLightMutedSwatch() {
-        return mLightMutedSwatch;
-    }
-
-    @Override
-    public Swatch getDarkMutedSwatch() {
-        return mDarkMutedSwatch;
-    }
-
-    private void generateVariationColors() {
-        mVibrantSwatch = findColorVariation(TARGET_NORMAL_LUMA, MIN_NORMAL_LUMA, MAX_NORMAL_LUMA,
-                TARGET_VIBRANT_SATURATION, MIN_VIBRANT_SATURATION, 1f);
-
-        mLightVibrantSwatch = findColorVariation(TARGET_LIGHT_LUMA, MIN_LIGHT_LUMA, 1f,
-                TARGET_VIBRANT_SATURATION, MIN_VIBRANT_SATURATION, 1f);
-
-        mDarkVibrantSwatch = findColorVariation(TARGET_DARK_LUMA, 0f, MAX_DARK_LUMA,
-                TARGET_VIBRANT_SATURATION, MIN_VIBRANT_SATURATION, 1f);
-
-        mMutedSwatch = findColorVariation(TARGET_NORMAL_LUMA, MIN_NORMAL_LUMA, MAX_NORMAL_LUMA,
-                TARGET_MUTED_SATURATION, 0f, MAX_MUTED_SATURATION);
-
-        mLightMutedSwatch = findColorVariation(TARGET_LIGHT_LUMA, MIN_LIGHT_LUMA, 1f,
-                TARGET_MUTED_SATURATION, 0f, MAX_MUTED_SATURATION);
-
-        mDarkMutedSwatch = findColorVariation(TARGET_DARK_LUMA, 0f, MAX_DARK_LUMA,
-                TARGET_MUTED_SATURATION, 0f, MAX_MUTED_SATURATION);
-    }
-
-    /**
-     * Try and generate any missing swatches from the swatches we did find.
-     */
-    private void generateEmptySwatches() {
-        if (mVibrantSwatch == null) {
-            // If we do not have a vibrant color...
-            if (mDarkVibrantSwatch != null) {
-                // ...but we do have a dark vibrant, generate the value by modifying the luma
-                final float[] newHsl = copyHslValues(mDarkVibrantSwatch);
-                newHsl[2] = TARGET_NORMAL_LUMA;
-                mVibrantSwatch = new Swatch(ColorUtils.HSLToColor(newHsl), 0);
-            }
-        }
-
-        if (mDarkVibrantSwatch == null) {
-            // If we do not have a dark vibrant color...
-            if (mVibrantSwatch != null) {
-                // ...but we do have a vibrant, generate the value by modifying the luma
-                final float[] newHsl = copyHslValues(mVibrantSwatch);
-                newHsl[2] = TARGET_DARK_LUMA;
-                mDarkVibrantSwatch = new Swatch(ColorUtils.HSLToColor(newHsl), 0);
-            }
-        }
-    }
-
-    /**
-     * Find the {@link Palette.Swatch} with the highest population value and return the population.
-     */
-    private int findMaxPopulation() {
-        int population = 0;
-        for (Swatch swatch : mSwatches) {
-            population = Math.max(population, swatch.getPopulation());
-        }
-        return population;
-    }
-
-    private Swatch findColorVariation(float targetLuma, float minLuma, float maxLuma,
-            float targetSaturation, float minSaturation, float maxSaturation) {
-        Swatch max = null;
-        float maxValue = 0f;
-
-        for (Swatch swatch : mSwatches) {
-            final float sat = swatch.getHsl()[1];
-            final float luma = swatch.getHsl()[2];
-
-            if (sat >= minSaturation && sat <= maxSaturation &&
-                    luma >= minLuma && luma <= maxLuma &&
-                    !isAlreadySelected(swatch)) {
-                float value = createComparisonValue(sat, targetSaturation, luma, targetLuma,
-                        swatch.getPopulation(), mHighestPopulation);
-                if (max == null || value > maxValue) {
-                    max = swatch;
-                    maxValue = value;
-                }
-            }
-        }
-
-        return max;
-    }
-
-    /**
-     * @return true if we have already selected {@code swatch}
-     */
-    private boolean isAlreadySelected(Swatch swatch) {
-        return mVibrantSwatch == swatch || mDarkVibrantSwatch == swatch ||
-                mLightVibrantSwatch == swatch || mMutedSwatch == swatch ||
-                mDarkMutedSwatch == swatch || mLightMutedSwatch == swatch;
-    }
-
-    private static float createComparisonValue(float saturation, float targetSaturation,
-            float luma, float targetLuma,
-            int population, int maxPopulation) {
-        return createComparisonValue(saturation, targetSaturation, WEIGHT_SATURATION,
-                luma, targetLuma, WEIGHT_LUMA,
-                population, maxPopulation, WEIGHT_POPULATION);
-    }
-
-    private static float createComparisonValue(
-            float saturation, float targetSaturation, float saturationWeight,
-            float luma, float targetLuma, float lumaWeight,
-            int population, int maxPopulation, float populationWeight) {
-        return weightedMean(
-                invertDiff(saturation, targetSaturation), saturationWeight,
-                invertDiff(luma, targetLuma), lumaWeight,
-                population / (float) maxPopulation, populationWeight
-        );
-    }
-
-    /**
-     * Copy a {@link Swatch}'s HSL values into a new float[].
-     */
-    private static float[] copyHslValues(Swatch 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/main/java/android/support/v7/graphics/Palette.java b/v7/palette/src/main/java/android/support/v7/graphics/Palette.java
index 57f34a6..d0dff30 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
@@ -21,16 +21,20 @@
 import android.graphics.Rect;
 import android.os.AsyncTask;
 import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.v4.graphics.ColorUtils;
 import android.support.v4.os.AsyncTaskCompat;
+import android.support.v4.util.ArrayMap;
 import android.util.Log;
+import android.util.SparseBooleanArray;
 import android.util.TimingLogger;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 
 /**
  * A helper class to extract prominent colors from an image.
@@ -80,7 +84,7 @@
         void onGenerated(Palette palette);
     }
 
-    private static final int DEFAULT_RESIZE_BITMAP_MAX_DIMENSION = 192;
+    private static final int DEFAULT_RESIZE_BITMAP_AREA = 160 * 160;
     private static final int DEFAULT_CALCULATE_NUMBER_COLORS = 16;
 
     private static final float MIN_CONTRAST_TITLE_TEXT = 3.0f;
@@ -140,151 +144,266 @@
     }
 
     private final List<Swatch> mSwatches;
-    private final Generator mGenerator;
+    private final List<Target> mTargets;
 
-    private Palette(List<Swatch> swatches, Generator generator) {
+    private final Map<Target, Swatch> mSelectedSwatches;
+    private final SparseBooleanArray mUsedColors;
+
+    private final int mMaxPopulation;
+
+    private Palette(List<Swatch> swatches, List<Target> targets) {
         mSwatches = swatches;
-        mGenerator = generator;
+        mTargets = targets;
+
+        mUsedColors = new SparseBooleanArray();
+        mSelectedSwatches = new ArrayMap<>();
+
+        mMaxPopulation = findMaxPopulation();
     }
 
     /**
      * Returns all of the swatches which make up the palette.
      */
+    @NonNull
     public List<Swatch> getSwatches() {
         return Collections.unmodifiableList(mSwatches);
     }
 
     /**
+     * Returns the targets used to generate this palette.
+     */
+    @NonNull
+    public List<Target> getTargets() {
+        return Collections.unmodifiableList(mTargets);
+    }
+
+    /**
      * Returns the most vibrant swatch in the palette. Might be null.
+     *
+     * @see Target#VIBRANT
      */
     @Nullable
     public Swatch getVibrantSwatch() {
-        return mGenerator.getVibrantSwatch();
+        return getSwatchForTarget(Target.VIBRANT);
     }
 
     /**
      * Returns a light and vibrant swatch from the palette. Might be null.
+     *
+     * @see Target#LIGHT_VIBRANT
      */
     @Nullable
     public Swatch getLightVibrantSwatch() {
-        return mGenerator.getLightVibrantSwatch();
+        return getSwatchForTarget(Target.LIGHT_VIBRANT);
     }
 
     /**
      * Returns a dark and vibrant swatch from the palette. Might be null.
+     *
+     * @see Target#DARK_VIBRANT
      */
     @Nullable
     public Swatch getDarkVibrantSwatch() {
-        return mGenerator.getDarkVibrantSwatch();
+        return getSwatchForTarget(Target.DARK_VIBRANT);
     }
 
     /**
      * Returns a muted swatch from the palette. Might be null.
+     *
+     * @see Target#MUTED
      */
     @Nullable
     public Swatch getMutedSwatch() {
-        return mGenerator.getMutedSwatch();
+        return getSwatchForTarget(Target.MUTED);
     }
 
     /**
      * Returns a muted and light swatch from the palette. Might be null.
+     *
+     * @see Target#LIGHT_MUTED
      */
     @Nullable
     public Swatch getLightMutedSwatch() {
-        return mGenerator.getLightMutedSwatch();
+        return getSwatchForTarget(Target.LIGHT_MUTED);
     }
 
     /**
      * Returns a muted and dark swatch from the palette. Might be null.
+     *
+     * @see Target#DARK_MUTED
      */
     @Nullable
     public Swatch getDarkMutedSwatch() {
-        return mGenerator.getDarkMutedSwatch();
+        return getSwatchForTarget(Target.DARK_MUTED);
     }
 
     /**
      * Returns the most vibrant color in the palette as an RGB packed int.
      *
      * @param defaultColor value to return if the swatch isn't available
+     * @see #getVibrantSwatch()
      */
     @ColorInt
-    public int getVibrantColor(@ColorInt int defaultColor) {
-        Swatch swatch = getVibrantSwatch();
-        return swatch != null ? swatch.getRgb() : defaultColor;
+    public int getVibrantColor(@ColorInt final int defaultColor) {
+        return getColorForTarget(Target.VIBRANT, defaultColor);
     }
 
     /**
      * Returns a light and vibrant color from the palette as an RGB packed int.
      *
      * @param defaultColor value to return if the swatch isn't available
+     * @see #getLightVibrantSwatch()
      */
     @ColorInt
-    public int getLightVibrantColor(@ColorInt int defaultColor) {
-        Swatch swatch = getLightVibrantSwatch();
-        return swatch != null ? swatch.getRgb() : defaultColor;
+    public int getLightVibrantColor(@ColorInt final int defaultColor) {
+        return getColorForTarget(Target.LIGHT_VIBRANT, defaultColor);
     }
 
     /**
      * Returns a dark and vibrant color from the palette as an RGB packed int.
      *
      * @param defaultColor value to return if the swatch isn't available
+     * @see #getDarkVibrantSwatch()
      */
     @ColorInt
-    public int getDarkVibrantColor(@ColorInt int defaultColor) {
-        Swatch swatch = getDarkVibrantSwatch();
-        return swatch != null ? swatch.getRgb() : defaultColor;
+    public int getDarkVibrantColor(@ColorInt final int defaultColor) {
+        return getColorForTarget(Target.DARK_VIBRANT, defaultColor);
     }
 
     /**
      * Returns a muted color from the palette as an RGB packed int.
      *
      * @param defaultColor value to return if the swatch isn't available
+     * @see #getMutedSwatch()
      */
     @ColorInt
-    public int getMutedColor(@ColorInt int defaultColor) {
-        Swatch swatch = getMutedSwatch();
-        return swatch != null ? swatch.getRgb() : defaultColor;
+    public int getMutedColor(@ColorInt final int defaultColor) {
+        return getColorForTarget(Target.MUTED, defaultColor);
     }
 
     /**
      * Returns a muted and light color from the palette as an RGB packed int.
      *
      * @param defaultColor value to return if the swatch isn't available
+     * @see #getLightMutedSwatch()
      */
     @ColorInt
-    public int getLightMutedColor(@ColorInt int defaultColor) {
-        Swatch swatch = getLightMutedSwatch();
-        return swatch != null ? swatch.getRgb() : defaultColor;
+    public int getLightMutedColor(@ColorInt final int defaultColor) {
+        return getColorForTarget(Target.LIGHT_MUTED, defaultColor);
     }
 
     /**
      * Returns a muted and dark color from the palette as an RGB packed int.
      *
      * @param defaultColor value to return if the swatch isn't available
+     * @see #getDarkMutedSwatch()
      */
     @ColorInt
-    public int getDarkMutedColor(@ColorInt int defaultColor) {
-        Swatch swatch = getDarkMutedSwatch();
-        return swatch != null ? swatch.getRgb() : defaultColor;
+    public int getDarkMutedColor(@ColorInt final int defaultColor) {
+        return getColorForTarget(Target.DARK_MUTED, defaultColor);
     }
 
     /**
-     * Scale the bitmap down so that it's largest dimension is {@code targetMaxDimension}.
-     * If {@code bitmap} is smaller than this, then it is returned.
+     * Returns the selected swatch for the given target from the palette, or {@code null} if one
+     * could not be found.
      */
-    private static Bitmap scaleBitmapDown(Bitmap bitmap, final int targetMaxDimension) {
-        final int maxDimension = Math.max(bitmap.getWidth(), bitmap.getHeight());
+    @Nullable
+    public Swatch getSwatchForTarget(@NonNull final Target target) {
+        return mSelectedSwatches.get(target);
+    }
 
-        if (maxDimension <= targetMaxDimension) {
-            // If the bitmap is small enough already, just return it
-            return bitmap;
+    /**
+     * Returns the selected color for the given target from the palette as an RGB packed int.
+     *
+     * @param defaultColor value to return if the swatch isn't available
+     */
+    @ColorInt
+    public int getColorForTarget(@NonNull final Target target, @ColorInt final int defaultColor) {
+        Swatch swatch = getSwatchForTarget(target);
+        return swatch != null ? swatch.getRgb() : defaultColor;
+    }
+
+    private void generate() {
+        // We need to make sure that the scored targets are generated first. This is so that
+        // inherited targets have something to inherit from
+        for (int i = 0, count = mTargets.size(); i < count; i++) {
+            final Target target = mTargets.get(i);
+            target.normalizeWeights();
+            mSelectedSwatches.put(target, generateScoredTarget(target));
+        }
+        // We now clear out the used colors
+        mUsedColors.clear();
+    }
+
+    private Swatch generateScoredTarget(final Target target) {
+        final Swatch maxScoreSwatch = getMaxScoredSwatchForTarget(target);
+        if (maxScoreSwatch != null && target.isExclusive()) {
+            // If we have a swatch, and the target is exclusive, add the color to the used list
+            mUsedColors.append(maxScoreSwatch.getRgb(), true);
+        }
+        return maxScoreSwatch;
+    }
+
+    private Swatch getMaxScoredSwatchForTarget(final Target target) {
+        float maxScore = 0;
+        Swatch maxScoreSwatch = null;
+        for (int i = 0, count = mSwatches.size(); i < count; i++) {
+            final Swatch swatch = mSwatches.get(i);
+            if (shouldBeScoredForTarget(swatch, target)) {
+                final float score = generateScore(swatch, target);
+                if (maxScoreSwatch == null || score > maxScore) {
+                    maxScoreSwatch = swatch;
+                    maxScore = score;
+                }
+            }
+        }
+        return maxScoreSwatch;
+    }
+
+    private boolean shouldBeScoredForTarget(final Swatch swatch, final Target target) {
+        // Check whether the HSL values are within the correct ranges, and this color hasn't
+        // been used yet.
+        final float hsl[] = swatch.getHsl();
+        return hsl[1] >= target.getMinimumSaturation() && hsl[1] <= target.getMaximumSaturation()
+                && hsl[2] >= target.getMinimumLightness() && hsl[2] <= target.getMaximumLightness()
+                && !mUsedColors.get(swatch.getRgb());
+    }
+
+    private float generateScore(Swatch swatch, Target target) {
+        final float[] hsl = swatch.getHsl();
+
+        float saturationScore = 0;
+        float luminanceScore = 0;
+        float populationScore = 0;
+
+        if (target.getSaturationWeight() > 0) {
+            saturationScore = target.getSaturationWeight()
+                    * (1f - Math.abs(hsl[1] - target.getTargetSaturation()));
+        }
+        if (target.getLightnessWeight() > 0) {
+            luminanceScore = target.getLightnessWeight()
+                    * (1f - Math.abs(hsl[2] - target.getTargetLightness()));
+        }
+        if (target.getPopulationWeight() > 0) {
+            populationScore = target.getPopulationWeight()
+                    * (swatch.getPopulation() / (float) mMaxPopulation);
         }
 
-        final double scaleRatio = targetMaxDimension / (double) maxDimension;
-        return Bitmap.createScaledBitmap(bitmap,
-                (int) Math.ceil(bitmap.getWidth() * scaleRatio),
-                (int) Math.ceil(bitmap.getHeight() * scaleRatio),
-                false);
+        return saturationScore + luminanceScore + populationScore;
+    }
+
+    private int findMaxPopulation() {
+        int max = 0;
+        for (int i = 0, count = mSwatches.size(); i < count; i++) {
+            max = Math.max(mSwatches.get(i).getPopulation(), max);
+        }
+        return max;
+    }
+
+    private static float[] copyHslValues(Swatch color) {
+        final float[] newHsl = new float[3];
+        System.arraycopy(color.getHsl(), 0, newHsl, 0, 3);
+        return newHsl;
     }
 
     /**
@@ -318,6 +437,11 @@
             mPopulation = population;
         }
 
+        Swatch(float[] hsl, int population) {
+            this(ColorUtils.HSLToColor(hsl), population);
+            mHsl = hsl;
+        }
+
         /**
          * @return this swatch's RGB color value
          */
@@ -335,8 +459,8 @@
         public float[] getHsl() {
             if (mHsl == null) {
                 mHsl = new float[3];
-                ColorUtils.RGBToHSL(mRed, mGreen, mBlue, mHsl);
             }
+            ColorUtils.RGBToHSL(mRed, mGreen, mBlue, mHsl);
             return mHsl;
         }
 
@@ -445,13 +569,16 @@
     public static final class Builder {
         private final List<Swatch> mSwatches;
         private final Bitmap mBitmap;
+
+        private final List<Target> mTargets = new ArrayList<>();
+
         private int mMaxColors = DEFAULT_CALCULATE_NUMBER_COLORS;
-        private int mResizeMaxDimension = DEFAULT_RESIZE_BITMAP_MAX_DIMENSION;
+        private int mResizeArea = DEFAULT_RESIZE_BITMAP_AREA;
+        private int mResizeMaxDimension = -1;
+
         private final List<Filter> mFilters = new ArrayList<>();
         private Rect mRegion;
 
-        private Generator mGenerator;
-
         /**
          * Construct a new {@link Builder} using a source {@link Bitmap}
          */
@@ -462,6 +589,14 @@
             mFilters.add(DEFAULT_FILTER);
             mBitmap = bitmap;
             mSwatches = null;
+
+            // Add the default targets
+            mTargets.add(Target.LIGHT_VIBRANT);
+            mTargets.add(Target.VIBRANT);
+            mTargets.add(Target.DARK_VIBRANT);
+            mTargets.add(Target.LIGHT_MUTED);
+            mTargets.add(Target.MUTED);
+            mTargets.add(Target.DARK_MUTED);
         }
 
         /**
@@ -478,15 +613,6 @@
         }
 
         /**
-         * 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.
-         */
-        Builder generator(Generator generator) {
-            mGenerator = generator;
-            return this;
-        }
-
-        /**
          * Set the maximum number of colors to use in the quantization step when using a
          * {@link android.graphics.Bitmap} as the source.
          * <p>
@@ -494,6 +620,7 @@
          * the range 10-16. For images which are largely made up of people's faces then this
          * value should be increased to ~24.
          */
+        @NonNull
         public Builder maximumColorCount(int colors) {
             mMaxColors = colors;
             return this;
@@ -504,13 +631,38 @@
          * If the bitmap's largest dimension is greater than the value specified, then the bitmap
          * will be resized so that it's largest dimension matches {@code maxDimension}. If the
          * bitmap is smaller or equal, the original is used as-is.
+         *
+         * @deprecated Using {@link #resizeBitmapArea(int)} is preferred since it can handle
+         * abnormal aspect ratios more gracefully.
+         *
+         * @param maxDimension the number of pixels that the max dimension should be scaled down to,
+         *                     or any value <= 0 to disable resizing.
+         */
+        @NonNull
+        @Deprecated
+        public Builder resizeBitmapSize(final int maxDimension) {
+            mResizeMaxDimension = maxDimension;
+            mResizeArea = -1;
+            return this;
+        }
+
+        /**
+         * Set the resize value when using a {@link android.graphics.Bitmap} as the source.
+         * If the bitmap's area is greater than the value specified, then the bitmap
+         * will be resized so that it's area matches {@code area}. If the
+         * bitmap is smaller or equal, the original is used as-is.
          * <p>
          * This value has a large effect on the processing time. The larger the resized image is,
          * the greater time it will take to generate the palette. The smaller the image is, the
          * more detail is lost in the resulting image and thus less precision for color selection.
+         *
+         * @param area the number of pixels that the intemediary scaled down Bitmap should cover,
+         *             or any value <= 0 to disable resizing.
          */
-        public Builder resizeBitmapSize(int maxDimension) {
-            mResizeMaxDimension = maxDimension;
+        @NonNull
+        public Builder resizeBitmapArea(final int area) {
+            mResizeArea = area;
+            mResizeMaxDimension = -1;
             return this;
         }
 
@@ -518,6 +670,7 @@
          * Clear all added filters. This includes any default filters added automatically by
          * {@link Palette}.
          */
+        @NonNull
         public Builder clearFilters() {
             mFilters.clear();
             return this;
@@ -529,6 +682,7 @@
          *
          * @param filter filter to add.
          */
+        @NonNull
         public Builder addFilter(Filter filter) {
             if (filter != null) {
                 mFilters.add(filter);
@@ -545,6 +699,7 @@
          * @param right The right side of the rectangle used for the region.
          * @param bottom The bottom of the rectangle used for the region.
          */
+        @NonNull
         public Builder setRegion(int left, int top, int right, int bottom) {
             if (mBitmap != null) {
                 if (mRegion == null) mRegion = new Rect();
@@ -562,14 +717,41 @@
         /**
          * Clear any previously region set via {@link #setRegion(int, int, int, int)}.
          */
+        @NonNull
         public Builder clearRegion() {
             mRegion = null;
             return this;
         }
 
         /**
+         * Add a target profile to be generated in the palette.
+         *
+         * <p>You can retrieve the result via {@link Palette#getSwatchForTarget(Target)}.</p>
+         */
+        @NonNull
+        public Builder addTarget(@NonNull final Target target) {
+            if (!mTargets.contains(target)) {
+                mTargets.add(target);
+            }
+            return this;
+        }
+
+        /**
+         * Clear all added targets. This includes any default targets added automatically by
+         * {@link Palette}.
+         */
+        @NonNull
+        public Builder clearTargets() {
+            if (mTargets != null) {
+                mTargets.clear();
+            }
+            return this;
+        }
+
+        /**
          * Generate and return the {@link Palette} synchronously.
          */
+        @NonNull
         public Palette generate() {
             final TimingLogger logger = LOG_TIMINGS
                     ? new TimingLogger(LOG_TAG, "Generation")
@@ -578,15 +760,10 @@
             List<Swatch> swatches;
 
             if (mBitmap != null) {
-                // We have a Bitmap so we need to quantization to reduce the number of colors
+                // We have a Bitmap so we need to use quantization to reduce the number of colors
 
-                if (mResizeMaxDimension <= 0) {
-                    throw new IllegalArgumentException(
-                            "Minimum dimension size for resizing should should be >= 1");
-                }
-
-                // First we'll scale down the bitmap so it's largest dimension is as specified
-                final Bitmap bitmap = scaleBitmapDown(mBitmap, mResizeMaxDimension);
+                // First we'll scale down the bitmap if needed
+                final Bitmap bitmap = scaleBitmapDown(mBitmap);
 
                 if (logger != null) {
                     logger.addSplit("Processed Bitmap");
@@ -613,6 +790,7 @@
                 if (bitmap != mBitmap) {
                     bitmap.recycle();
                 }
+
                 swatches = quantizer.getQuantizedColors();
 
                 if (logger != null) {
@@ -623,20 +801,10 @@
                 swatches = mSwatches;
             }
 
-            // If we haven't been provided with a generator, use the default
-            if (mGenerator == null) {
-                mGenerator = new DefaultGenerator();
-            }
-
-            // Now call let the Generator do it's thing
-            mGenerator.generate(swatches);
-
-            if (logger != null) {
-                logger.addSplit("Generator.generate() completed");
-            }
-
             // Now create a Palette instance
-            Palette p = new Palette(swatches, mGenerator);
+            final Palette p = new Palette(swatches, mTargets);
+            // And make it generate itself
+            p.generate();
 
             if (logger != null) {
                 logger.addSplit("Created Palette");
@@ -651,6 +819,7 @@
          * {@link PaletteAsyncListener#onGenerated} method will be called with the palette when
          * generated.
          */
+        @NonNull
         public AsyncTask<Bitmap, Void, Palette> generate(final PaletteAsyncListener listener) {
             if (listener == null) {
                 throw new IllegalArgumentException("listener can not be null");
@@ -702,59 +871,34 @@
                 return subsetPixels;
             }
         }
-    }
-
-    static abstract class Generator {
 
         /**
-         * This method will be called with the {@link Palette.Swatch} that represent an image.
-         * You should process this list so that you have appropriate values when the other methods in
-         * class are called.
-         * <p>
-         * This method will probably be called on a background thread.
+         * Scale the bitmap down as needed.
          */
-        public abstract void generate(List<Palette.Swatch> swatches);
+        private Bitmap scaleBitmapDown(final Bitmap bitmap) {
+            double scaleRatio = -1;
 
-        /**
-         * Return the most vibrant {@link Palette.Swatch}
-         */
-        public Palette.Swatch getVibrantSwatch() {
-            return null;
-        }
+            if (mResizeArea > 0) {
+                final int bitmapArea = bitmap.getWidth() * bitmap.getHeight();
+                if (bitmapArea > mResizeArea) {
+                    scaleRatio = mResizeArea / (double) bitmapArea;
+                }
+            } else if (mResizeMaxDimension > 0) {
+                final int maxDimension = Math.max(bitmap.getWidth(), bitmap.getHeight());
+                if (maxDimension > mResizeMaxDimension) {
+                    scaleRatio = mResizeMaxDimension / (double) maxDimension;
+                }
+            }
 
-        /**
-         * Return a light and vibrant {@link Palette.Swatch}
-         */
-        public Palette.Swatch getLightVibrantSwatch() {
-            return null;
-        }
+            if (scaleRatio <= 0) {
+                // Scaling has been disabled or not needed so just return the Bitmap
+                return bitmap;
+            }
 
-        /**
-         * Return a dark and vibrant {@link Palette.Swatch}
-         */
-        public Palette.Swatch getDarkVibrantSwatch() {
-            return null;
-        }
-
-        /**
-         * Return a muted {@link Palette.Swatch}
-         */
-        public Palette.Swatch getMutedSwatch() {
-            return null;
-        }
-
-        /**
-         * Return a muted and light {@link Palette.Swatch}
-         */
-        public Palette.Swatch getLightMutedSwatch() {
-            return null;
-        }
-
-        /**
-         * Return a muted and dark {@link Palette.Swatch}
-         */
-        public Palette.Swatch getDarkMutedSwatch() {
-            return null;
+            return Bitmap.createScaledBitmap(bitmap,
+                    (int) Math.ceil(bitmap.getWidth() * scaleRatio),
+                    (int) Math.ceil(bitmap.getHeight() * scaleRatio),
+                    false);
         }
     }
 
diff --git a/v7/palette/src/main/java/android/support/v7/graphics/Target.java b/v7/palette/src/main/java/android/support/v7/graphics/Target.java
new file mode 100644
index 0000000..8ac8205
--- /dev/null
+++ b/v7/palette/src/main/java/android/support/v7/graphics/Target.java
@@ -0,0 +1,381 @@
+/*
+ * Copyright 2015 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.support.annotation.FloatRange;
+
+/**
+ * A class which allows custom selection of colors in a {@link Palette}'s generation. Instances
+ * can be created via the {@link Builder} class.
+ *
+ * <p>To use the target, use the {@link Palette.Builder#addTarget(Target)} API when building a
+ * Palette.</p>
+ */
+public final class Target {
+
+    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 static final float WEIGHT_SATURATION = 0.24f;
+    private static final float WEIGHT_LUMA = 0.52f;
+    private static final float WEIGHT_POPULATION = 0.24f;
+
+    private static final int INDEX_MIN = 0;
+    private static final int INDEX_TARGET = 1;
+    private static final int INDEX_MAX = 2;
+
+    private static final int INDEX_WEIGHT_SAT = 0;
+    private static final int INDEX_WEIGHT_LUMA = 1;
+    private static final int INDEX_WEIGHT_POP = 2;
+
+    /**
+     * A target which has the characteristics of a vibrant color which is light in luminance.
+    */
+    public static final Target LIGHT_VIBRANT;
+
+    /**
+     * A target which has the characteristics of a vibrant color which is neither light or dark.
+     */
+    public static final Target VIBRANT;
+
+    /**
+     * A target which has the characteristics of a vibrant color which is dark in luminance.
+     */
+    public static final Target DARK_VIBRANT;
+
+    /**
+     * A target which has the characteristics of a muted color which is light in luminance.
+     */
+    public static final Target LIGHT_MUTED;
+
+    /**
+     * A target which has the characteristics of a muted color which is neither light or dark.
+     */
+    public static final Target MUTED;
+
+    /**
+     * A target which has the characteristics of a muted color which is dark in luminance.
+     */
+    public static final Target DARK_MUTED;
+
+    static {
+        LIGHT_VIBRANT = new Target();
+        setDefaultLightLightnessValues(LIGHT_VIBRANT);
+        setDefaultVibrantSaturationValues(LIGHT_VIBRANT);
+
+        VIBRANT = new Target();
+        setDefaultNormalLightnessValues(VIBRANT);
+        setDefaultVibrantSaturationValues(VIBRANT);
+
+        DARK_VIBRANT = new Target();
+        setDefaultDarkLightnessValues(DARK_VIBRANT);
+        setDefaultVibrantSaturationValues(DARK_VIBRANT);
+
+        LIGHT_MUTED = new Target();
+        setDefaultLightLightnessValues(LIGHT_MUTED);
+        setDefaultMutedSaturationValues(LIGHT_MUTED);
+
+        MUTED = new Target();
+        setDefaultNormalLightnessValues(MUTED);
+        setDefaultMutedSaturationValues(MUTED);
+
+        DARK_MUTED = new Target();
+        setDefaultDarkLightnessValues(DARK_MUTED);
+        setDefaultMutedSaturationValues(DARK_MUTED);
+    }
+
+    private final float[] mSaturationTargets = new float[3];
+    private final float[] mLightnessTargets = new float[3];
+    private final float[] mWeights = new float[3];
+    private boolean mIsExclusive = true; // default to true
+
+    private Target() {
+        setTargetDefaultValues(mSaturationTargets);
+        setTargetDefaultValues(mLightnessTargets);
+        setDefaultWeights();
+    }
+
+    private Target(Target from) {
+        System.arraycopy(from.mSaturationTargets, 0, mSaturationTargets, 0,
+                mSaturationTargets.length);
+        System.arraycopy(from.mLightnessTargets, 0, mLightnessTargets, 0,
+                mLightnessTargets.length);
+        System.arraycopy(from.mWeights, 0, mWeights, 0, mWeights.length);
+    }
+
+    /**
+     * The minimum saturation value for this target.
+     */
+    @FloatRange(from = 0, to = 1)
+    public float getMinimumSaturation() {
+        return mSaturationTargets[INDEX_MIN];
+    }
+
+    /**
+     * The target saturation value for this target.
+     */
+    @FloatRange(from = 0, to = 1)
+    public float getTargetSaturation() {
+        return mSaturationTargets[INDEX_TARGET];
+    }
+
+    /**
+     * The maximum saturation value for this target.
+     */
+    @FloatRange(from = 0, to = 1)
+    public float getMaximumSaturation() {
+        return mSaturationTargets[INDEX_MAX];
+    }
+
+    /**
+     * The minimum lightness value for this target.
+     */
+    @FloatRange(from = 0, to = 1)
+    public float getMinimumLightness() {
+        return mLightnessTargets[INDEX_MIN];
+    }
+
+    /**
+     * The target lightness value for this target.
+     */
+    @FloatRange(from = 0, to = 1)
+    public float getTargetLightness() {
+        return mLightnessTargets[INDEX_TARGET];
+    }
+
+    /**
+     * The maximum lightness value for this target.
+     */
+    @FloatRange(from = 0, to = 1)
+    public float getMaximumLightness() {
+        return mLightnessTargets[INDEX_MAX];
+    }
+
+    /**
+     * The weight of important that a color's saturation value has on selection.
+     */
+    public float getSaturationWeight() {
+        return mWeights[INDEX_WEIGHT_SAT];
+    }
+
+    /**
+     * The weight of important that a color's lightness value has on selection.
+     */
+    public float getLightnessWeight() {
+        return mWeights[INDEX_WEIGHT_LUMA];
+    }
+
+    /**
+     * The weight of important that a color's population value has on selection.
+     */
+    public float getPopulationWeight() {
+        return mWeights[INDEX_WEIGHT_POP];
+    }
+
+    /**
+     * Returns whether any color selected for this target is exclusive for this target only.
+     *
+     * <p>If false, then the color can be selected for other targets.</p>
+     */
+    public boolean isExclusive() {
+        return mIsExclusive;
+    }
+
+    private static void setTargetDefaultValues(final float[] values) {
+        values[INDEX_MIN] = 0f;
+        values[INDEX_TARGET] = 0.5f;
+        values[INDEX_MAX] = 1f;
+    }
+
+    private void setDefaultWeights() {
+        mWeights[INDEX_WEIGHT_SAT] = WEIGHT_SATURATION;
+        mWeights[INDEX_WEIGHT_LUMA] = WEIGHT_LUMA;
+        mWeights[INDEX_WEIGHT_POP] = WEIGHT_POPULATION;
+    }
+
+    void normalizeWeights() {
+        float sum = 0;
+        for (int i = 0, z = mWeights.length; i < z; i++) {
+            float weight = mWeights[i];
+            if (weight > 0) {
+                sum += weight;
+            }
+        }
+        if (sum != 0) {
+            for (int i = 0, z = mWeights.length; i < z; i++) {
+                if (mWeights[i] > 0) {
+                    mWeights[i] /= sum;
+                }
+            }
+        }
+    }
+
+    private static void setDefaultDarkLightnessValues(Target target) {
+        target.mLightnessTargets[INDEX_TARGET] = TARGET_DARK_LUMA;
+        target.mLightnessTargets[INDEX_MAX] = MAX_DARK_LUMA;
+    }
+
+    private static void setDefaultNormalLightnessValues(Target target) {
+        target.mLightnessTargets[INDEX_MIN] = MIN_NORMAL_LUMA;
+        target.mLightnessTargets[INDEX_TARGET] = TARGET_NORMAL_LUMA;
+        target.mLightnessTargets[INDEX_MAX] = MAX_NORMAL_LUMA;
+    }
+
+    private static void setDefaultLightLightnessValues(Target target) {
+        target.mLightnessTargets[INDEX_MIN] = MIN_LIGHT_LUMA;
+        target.mLightnessTargets[INDEX_TARGET] = TARGET_LIGHT_LUMA;
+    }
+
+    private static void setDefaultVibrantSaturationValues(Target target) {
+        target.mSaturationTargets[INDEX_MIN] = MIN_VIBRANT_SATURATION;
+        target.mSaturationTargets[INDEX_TARGET] = TARGET_VIBRANT_SATURATION;
+    }
+
+    private static void setDefaultMutedSaturationValues(Target target) {
+        target.mSaturationTargets[INDEX_TARGET] = TARGET_MUTED_SATURATION;
+        target.mSaturationTargets[INDEX_MAX] = MAX_MUTED_SATURATION;
+    }
+
+    /**
+     * Builder class for generating custom {@link Target} instances.
+     */
+    public final static class Builder {
+        private final Target mTarget;
+
+        /**
+         * Create a new {@link Target} builder from scratch.
+         */
+        public Builder() {
+            mTarget = new Target();
+        }
+
+        /**
+         * Create a new builder based on an existing {@link Target}.
+         */
+        public Builder(Target target) {
+            mTarget = new Target(target);
+        }
+
+        /**
+         * Set the minimum saturation value for this target.
+         */
+        public Builder setMinimumSaturation(@FloatRange(from = 0, to = 1) float value) {
+            mTarget.mSaturationTargets[INDEX_MIN] = value;
+            return this;
+        }
+
+        /**
+         * Set the target/ideal saturation value for this target.
+         */
+        public Builder setTargetSaturation(@FloatRange(from = 0, to = 1) float value) {
+            mTarget.mSaturationTargets[INDEX_TARGET] = value;
+            return this;
+        }
+
+        /**
+         * Set the maximum saturation value for this target.
+         */
+        public Builder setMaximumSaturation(@FloatRange(from = 0, to = 1) float value) {
+            mTarget.mSaturationTargets[INDEX_MAX] = value;
+            return this;
+        }
+
+        /**
+         * Set the minimum lightness value for this target.
+         */
+        public Builder setMinimumLightness(@FloatRange(from = 0, to = 1) float value) {
+            mTarget.mLightnessTargets[INDEX_MIN] = value;
+            return this;
+        }
+
+        /**
+         * Set the target/ideal lightness value for this target.
+         */
+        public Builder setTargetLightness(@FloatRange(from = 0, to = 1) float value) {
+            mTarget.mLightnessTargets[INDEX_TARGET] = value;
+            return this;
+        }
+
+        /**
+         * Set the maximum lightness value for this target.
+         */
+        public Builder setMaximumLightness(@FloatRange(from = 0, to = 1) float value) {
+            mTarget.mLightnessTargets[INDEX_MAX] = value;
+            return this;
+        }
+
+        /**
+         * Set the weight of important that a color's saturation value has on selection. A weight
+         * of <= 0 means that it has no weight and is ignored.
+         */
+        public Builder setSaturationWeight(@FloatRange(from = 0) float weight) {
+            mTarget.mWeights[INDEX_WEIGHT_SAT] = weight;
+            return this;
+        }
+
+        /**
+         * Set the weight of important that a color's lightness value has on selection. A weight
+         * of <= 0 means that it has no weight and is ignored.
+         */
+        public Builder setLightnessWeight(@FloatRange(from = 0) float weight) {
+            mTarget.mWeights[INDEX_WEIGHT_LUMA] = weight;
+            return this;
+        }
+
+        /**
+         * Set the weight of important that a color's population value has on selection. A weight
+         * of <= 0 means that it has no weight and is ignored.
+         */
+        public Builder setPopulationWeight(@FloatRange(from = 0) float weight) {
+            mTarget.mWeights[INDEX_WEIGHT_POP] = weight;
+            return this;
+        }
+
+        /**
+         * Set whether any color selected for this target is exclusive to this target only.
+         * Defaults to true.
+         *
+         * @param exclusive true if any the color is exclusive to this target, or false is the
+         *                  color can be selected for other targets.
+         */
+        public Builder setExclusive(boolean exclusive) {
+            mTarget.mIsExclusive = exclusive;
+            return this;
+        }
+
+        /**
+         * Builds and returns the resulting {@link Target}.
+         */
+        public Target build() {
+            return mTarget;
+        }
+    }
+
+}
\ No newline at end of file