content: /*
   * Copyright (C) 2017 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 com.android.internal.graphics.palette;
  
  import android.annotation.ColorInt;
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.graphics.Bitmap;
  import android.graphics.Color;
  import android.graphics.Rect;
  import android.os.AsyncTask;
  import android.util.ArrayMap;
  import android.util.Log;
  import android.util.SparseBooleanArray;
  import android.util.TimingLogger;
  
  import com.android.internal.graphics.ColorUtils;
  
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collections;
  import java.util.List;
  import java.util.Map;
  
  
  /**
   * Copied from: /frameworks/support/v7/palette/src/main/java/android/support/v7/
   * graphics/Palette.java
   *
   * 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 are created with a {@link Palette.Builder} which supports several options to tweak the
   * generated Palette. See that class' documentation for more information.
   * <p>
   * Generation should always be completed on a background thread, ideally the one in
   * which you load your image on. {@link Palette.Builder} supports both synchronous and asynchronous
   * generation:
   *
   * <pre>
   * // Synchronous
   * Palette p = Palette.from(bitmap).generate();
   *
   * // Asynchronous
   * Palette.from(bitmap).generate(new PaletteAsyncListener() {
   *     public void onGenerated(Palette p) {
   *         // Use generated instance
   *     }
   * });
   * </pre>
   */
  public final class Palette {
  
      /**
       * Listener to be used with {@link #generateAsync(Bitmap, Palette.PaletteAsyncListener)} or
       * {@link #generateAsync(Bitmap, int, Palette.PaletteAsyncListener)}
       */
      public interface PaletteAsyncListener {
  
          /**
           * Called when the {@link Palette} has been generated.
           */
          void onGenerated(Palette palette);
      }
  
      static final int DEFAULT_RESIZE_BITMAP_AREA = 112 * 112;
      static final int DEFAULT_CALCULATE_NUMBER_COLORS = 16;
  
      static final float MIN_CONTRAST_TITLE_TEXT = 3.0f;
      static final float MIN_CONTRAST_BODY_TEXT = 4.5f;
  
      static final String LOG_TAG = "Palette";
      static final boolean LOG_TIMINGS = false;
  
      /**
       * Start generating a {@link Palette} with the returned {@link Palette.Builder} instance.
       */
      public static Palette.Builder from(Bitmap bitmap) {
          return new Palette.Builder(bitmap);
      }
  
      /**
       * Generate a {@link Palette} from the pre-generated list of {@link Palette.Swatch} swatches.
       * This is useful for testing, or if you want to resurrect a {@link Palette} instance from a
       * list of swatches. Will return null if the {@code swatches} is null.
       */
      public static Palette from(List<Palette.Swatch> swatches) {
          return new Palette.Builder(swatches).generate();
      }
  
      /**
       * @deprecated Use {@link Palette.Builder} to generate the Palette.
       */
      @Deprecated
      public static Palette generate(Bitmap bitmap) {
          return from(bitmap).generate();
      }
  
      /**
       * @deprecated Use {@link Palette.Builder} to generate the Palette.
       */
      @Deprecated
      public static Palette generate(Bitmap bitmap, int numColors) {
          return from(bitmap).maximumColorCount(numColors).generate();
      }
  
      /**
       * @deprecated Use {@link Palette.Builder} to generate the Palette.
       */
      @Deprecated
      public static AsyncTask<Bitmap, Void, Palette> generateAsync(
              Bitmap bitmap, Palette.PaletteAsyncListener listener) {
          return from(bitmap).generate(listener);
      }
  
      /**
       * @deprecated Use {@link Palette.Builder} to generate the Palette.
       */
      @Deprecated
      public static AsyncTask<Bitmap, Void, Palette> generateAsync(
              final Bitmap bitmap, final int numColors, final Palette.PaletteAsyncListener listener) {
          return from(bitmap).maximumColorCount(numColors).generate(listener);
      }
  
      private final List<Palette.Swatch> mSwatches;
      private final List<Target> mTargets;
  
      private final Map<Target, Palette.Swatch> mSelectedSwatches;
      private final SparseBooleanArray mUsedColors;
  
      private final Palette.Swatch mDominantSwatch;
  
      Palette(List<Palette.Swatch> swatches, List<Target> targets) {
          mSwatches = swatches;
          mTargets = targets;
  
          mUsedColors = new SparseBooleanArray();
          mSelectedSwatches = new ArrayMap<>();
  
          mDominantSwatch = findDominantSwatch();
      }
  
      /**
       * Returns all of the swatches which make up the palette.
       */
      @NonNull
      public List<Palette.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 Palette.Swatch getVibrantSwatch() {
          return getSwatchForTarget(Target.VIBRANT);
      }
  
      /**
       * Returns a light and vibrant swatch from the palette. Might be null.
       *
       * @see Target#LIGHT_VIBRANT
       */
      @Nullable
      public Palette.Swatch getLightVibrantSwatch() {
          return getSwatchForTarget(Target.LIGHT_VIBRANT);
      }
  
      /**
       * Returns a dark and vibrant swatch from the palette. Might be null.
       *
       * @see Target#DARK_VIBRANT
       */
      @Nullable
      public Palette.Swatch getDarkVibrantSwatch() {
          return getSwatchForTarget(Target.DARK_VIBRANT);
      }
  
      /**
       * Returns a muted swatch from the palette. Might be null.
       *
       * @see Target#MUTED
       */
      @Nullable
      public Palette.Swatch getMutedSwatch() {
          return getSwatchForTarget(Target.MUTED);
      }
  
      /**
       * Returns a muted and light swatch from the palette. Might be null.
       *
       * @see Target#LIGHT_MUTED
       */
      @Nullable
      public Palette.Swatch getLightMutedSwatch() {
          return getSwatchForTarget(Target.LIGHT_MUTED);
      }
  
      /**
       * Returns a muted and dark swatch from the palette. Might be null.
       *
       * @see Target#DARK_MUTED
       */
      @Nullable
      public Palette.Swatch 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 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 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 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 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 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 final int defaultColor) {
          return getColorForTarget(Target.DARK_MUTED, defaultColor);
      }
  
      /**
       * Returns the selected swatch for the given target from the palette, or {@code null} if one
       * could not be found.
       */
      @Nullable
      public Palette.Swatch getSwatchForTarget(@NonNull final Target target) {
          return mSelectedSwatches.get(target);
      }
  
      /**
       * 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) {
          Palette.Swatch swatch = getSwatchForTarget(target);
          return swatch != null ? swatch.getRgb() : defaultColor;
      }
  
      /**
       * Returns the dominant swatch from the palette.
       *
       * <p>The dominant swatch is defined as the swatch with the greatest population (frequency)
       * within the palette.</p>
       */
      @Nullable
      public Palette.Swatch getDominantSwatch() {
          return mDominantSwatch;
      }
  
      /**
       * Returns the color of the dominant swatch from the palette, as an RGB packed int.
       *
       * @param defaultColor value to return if the swatch isn't available
       * @see #getDominantSwatch()
       */
      @ColorInt
      public int getDominantColor(@ColorInt int defaultColor) {
          return mDominantSwatch != null ? mDominantSwatch.getRgb() : defaultColor;
      }
  
      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 Palette.Swatch generateScoredTarget(final Target target) {
          final Palette.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 Palette.Swatch getMaxScoredSwatchForTarget(final Target target) {
          float maxScore = 0;
          Palette.Swatch maxScoreSwatch = null;
          for (int i = 0, count = mSwatches.size(); i < count; i++) {
              final Palette.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 Palette.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(Palette.Swatch swatch, Target target) {
          final float[] hsl = swatch.getHsl();
  
          float saturationScore = 0;
          float luminanceScore = 0;
          float populationScore = 0;
  
          final int maxPopulation = mDominantSwatch != null ? mDominantSwatch.getPopulation() : 1;
  
          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) maxPopulation);
          }
  
          return saturationScore + luminanceScore + populationScore;
      }
  
      private Palette.Swatch findDominantSwatch() {
          int maxPop = Integer.MIN_VALUE;
          Palette.Swatch maxSwatch = null;
          for (int i = 0, count = mSwatches.size(); i < count; i++) {
              Palette.Swatch swatch = mSwatches.get(i);
              if (swatch.getPopulation() > maxPop) {
                  maxSwatch = swatch;
                  maxPop = swatch.getPopulation();
              }
          }
          return maxSwatch;
      }
  
      private static float[] copyHslValues(Palette.Swatch color) {
          final float[] newHsl = new float[3];
          System.arraycopy(color.getHsl(), 0, newHsl, 0, 3);
          return newHsl;
      }
  
      /**
       * Represents a color swatch generated from an image's palette. The RGB color can be retrieved
       * by calling {@link #getRgb()}.
       */
      public static final class Swatch {
          private final int mRed, mGreen, mBlue;
          private final int mRgb;
          private final int mPopulation;
  
          private boolean mGeneratedTextColors;
          private int mTitleTextColor;
          private int mBodyTextColor;
  
          private float[] mHsl;
  
          public Swatch(@ColorInt int color, int population) {
              mRed = Color.red(color);
              mGreen = Color.green(color);
              mBlue = Color.blue(color);
              mRgb = color;
              mPopulation = population;
          }
  
          Swatch(int red, int green, int blue, int population) {
              mRed = red;
              mGreen = green;
              mBlue = blue;
              mRgb = Color.rgb(red, green, blue);
              mPopulation = population;
          }
  
          Swatch(float[] hsl, int population) {
              this(ColorUtils.HSLToColor(hsl), population);
              mHsl = hsl;
          }
  
          /**
           * @return this swatch's RGB color value
           */
          @ColorInt
          public int getRgb() {
              return mRgb;
          }
  
          /**
           * Return this swatch'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 (mHsl == null) {
                  mHsl = new float[3];
              }
              ColorUtils.RGBToHSL(mRed, mGreen, mBlue, mHsl);
              return mHsl;
          }
  
          /**
           * @return the number of pixels represented by this swatch
           */
          public int getPopulation() {
              return mPopulation;
          }
  
          /**
           * Returns an appropriate color to use for any 'title' text which is displayed over this
           * {@link Palette.Swatch}'s color. This color is guaranteed to have sufficient contrast.
           */
          @ColorInt
          public int getTitleTextColor() {
              ensureTextColorsGenerated();
              return mTitleTextColor;
          }
  
          /**
           * Returns an appropriate color to use for any 'body' text which is displayed over this
           * {@link Palette.Swatch}'s color. This color is guaranteed to have sufficient contrast.
           */
          @ColorInt
          public int getBodyTextColor() {
              ensureTextColorsGenerated();
              return mBodyTextColor;
          }
  
          private void ensureTextColorsGenerated() {
              if (!mGeneratedTextColors) {
                  // First check white, as most colors will be dark
                  final int lightBodyAlpha = ColorUtils.calculateMinimumAlpha(
                          Color.WHITE, mRgb, MIN_CONTRAST_BODY_TEXT);
                  final int lightTitleAlpha = ColorUtils.calculateMinimumAlpha(
                          Color.WHITE, mRgb, MIN_CONTRAST_TITLE_TEXT);
  
                  if (lightBodyAlpha != -1 && lightTitleAlpha != -1) {
                      // If we found valid light values, use them and return
                      mBodyTextColor = ColorUtils.setAlphaComponent(Color.WHITE, lightBodyAlpha);
                      mTitleTextColor = ColorUtils.setAlphaComponent(Color.WHITE, lightTitleAlpha);
                      mGeneratedTextColors = true;
                      return;
                  }
  
                  final int darkBodyAlpha = ColorUtils.calculateMinimumAlpha(
                          Color.BLACK, mRgb, MIN_CONTRAST_BODY_TEXT);
                  final int darkTitleAlpha = ColorUtils.calculateMinimumAlpha(
                          Color.BLACK, mRgb, MIN_CONTRAST_TITLE_TEXT);
  
                  if (darkBodyAlpha != -1 && darkTitleAlpha != -1) {
                      // If we found valid dark values, use them and return
                      mBodyTextColor = ColorUtils.setAlphaComponent(Color.BLACK, darkBodyAlpha);
                      mTitleTextColor = ColorUtils.setAlphaComponent(Color.BLACK, darkTitleAlpha);
                      mGeneratedTextColors = true;
                      return;
                  }
  
                  // If we reach here then we can not find title and body values which use the same
                  // lightness, we need to use mismatched values
                  mBodyTextColor = lightBodyAlpha != -1
                          ? ColorUtils.setAlphaComponent(Color.WHITE, lightBodyAlpha)
                          : ColorUtils.setAlphaComponent(Color.BLACK, darkBodyAlpha);
                  mTitleTextColor = lightTitleAlpha != -1
                          ? ColorUtils.setAlphaComponent(Color.WHITE, lightTitleAlpha)
                          : ColorUtils.setAlphaComponent(Color.BLACK, darkTitleAlpha);
                  mGeneratedTextColors = true;
              }
          }
  
          @Override
          public String toString() {
              return new StringBuilder(getClass().getSimpleName())
                      .append(" [RGB: #").append(Integer.toHexString(getRgb())).append(']')
                      .append(" [HSL: ").append(Arrays.toString(getHsl())).append(']')
                      .append(" [Population: ").append(mPopulation).append(']')
                      .append(" [Title Text: #").append(Integer.toHexString(getTitleTextColor()))
                      .append(']')
                      .append(" [Body Text: #").append(Integer.toHexString(getBodyTextColor()))
                      .append(']').toString();
          }
  
          @Override
          public boolean equals(Object o) {
              if (this == o) {
                  return true;
              }
              if (o == null || getClass() != o.getClass()) {
                  return false;
              }
  
              Palette.Swatch
                      swatch = (Palette.Swatch) o;
              return mPopulation == swatch.mPopulation && mRgb == swatch.mRgb;
          }
  
          @Override
          public int hashCode() {
              return 31 * mRgb + mPopulation;
          }
      }
  
      /**
       * Builder class for generating {@link Palette} instances.
       */
      public static final class Builder {
          private final List<Palette.Swatch> mSwatches;
          private final Bitmap mBitmap;
  
          private final List<Target> mTargets = new ArrayList<>();
  
          private int mMaxColors = DEFAULT_CALCULATE_NUMBER_COLORS;
          private int mResizeArea = DEFAULT_RESIZE_BITMAP_AREA;
          private int mResizeMaxDimension = -1;
  
          private final List<Palette.Filter> mFilters = new ArrayList<>();
          private Rect mRegion;
  
          private Quantizer mQuantizer;
  
          /**
           * Construct a new {@link Palette.Builder} using a source {@link Bitmap}
           */
          public Builder(Bitmap bitmap) {
              if (bitmap == null || bitmap.isRecycled()) {
                  throw new IllegalArgumentException("Bitmap is not valid");
              }
              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);
          }
  
          /**
           * Construct a new {@link Palette.Builder} using a list of {@link Palette.Swatch} instances.
           * Typically only used for testing.
           */
          public Builder(List<Palette.Swatch> swatches) {
              if (swatches == null || swatches.isEmpty()) {
                  throw new IllegalArgumentException("List of Swatches is not valid");
              }
              mFilters.add(DEFAULT_FILTER);
              mSwatches = swatches;
              mBitmap = null;
          }
  
          /**
           * Set the maximum number of colors to use in the quantization step when using a
           * {@link android.graphics.Bitmap} as the source.
           * <p>
           * Good values for depend on the source image type. For landscapes, good values are in
           * 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 Palette.Builder maximumColorCount(int colors) {
              mMaxColors = colors;
              return this;
          }
  
          /**
           * Set the resize value when using a {@link android.graphics.Bitmap} as the source.
           * If the bitmap's largest dimension is greater than the value specified, then the bitmap
           * will be resized so that its 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 Palette.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 its 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 intermediary scaled down Bitmap should cover,
           *             or any value <= 0 to disable resizing.
           */
          @NonNull
          public Palette.Builder resizeBitmapArea(final int area) {
              mResizeArea = area;
              mResizeMaxDimension = -1;
              return this;
          }
  
          /**
           * Clear all added filters. This includes any default filters added automatically by
           * {@link Palette}.
           */
          @NonNull
          public Palette.Builder clearFilters() {
              mFilters.clear();
              return this;
          }
  
          /**
           * Add a filter to be able to have fine grained control over which colors are
           * allowed in the resulting palette.
           *
           * @param filter filter to add.
           */
          @NonNull
          public Palette.Builder addFilter(
                  Palette.Filter filter) {
              if (filter != null) {
                  mFilters.add(filter);
              }
              return this;
          }
  
          /**
           * Set a specific quantization algorithm. {@link ColorCutQuantizer} will
           * be used if unspecified.
           *
           * @param quantizer Quantizer implementation.
           */
          @NonNull
          public Palette.Builder setQuantizer(Quantizer quantizer) {
              mQuantizer = quantizer;
              return this;
          }
  
          /**
           * Set a region of the bitmap to be used exclusively when calculating the palette.
           * <p>This only works when the original input is a {@link Bitmap}.</p>
           *
           * @param left The left side of the rectangle used for the region.
           * @param top The top of the rectangle used for the region.
           * @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 Palette.Builder setRegion(int left, int top, int right, int bottom) {
              if (mBitmap != null) {
                  if (mRegion == null) mRegion = new Rect();
                  // Set the Rect to be initially the whole Bitmap
                  mRegion.set(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
                  // Now just get the intersection with the region
                  if (!mRegion.intersect(left, top, right, bottom)) {
                      throw new IllegalArgumentException("The given region must intersect with "
                              + "the Bitmap's dimensions.");
                  }
              }
              return this;
          }
  
          /**
           * Clear any previously region set via {@link #setRegion(int, int, int, int)}.
           */
          @NonNull
          public Palette.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 Palette.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 Palette.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")
                      : null;
  
              List<Palette.Swatch> swatches;
  
              if (mBitmap != null) {
                  // We have a Bitmap so we need to use quantization to reduce the number of colors
  
                  // First we'll scale down the bitmap if needed
                  final Bitmap bitmap = scaleBitmapDown(mBitmap);
  
                  if (logger != null) {
                      logger.addSplit("Processed Bitmap");
                  }
  
                  final Rect region = mRegion;
                  if (bitmap != mBitmap && region != null) {
                      // If we have a scaled bitmap and a selected region, we need to scale down the
                      // region to match the new scale
                      final double scale = bitmap.getWidth() / (double) mBitmap.getWidth();
                      region.left = (int) Math.floor(region.left * scale);
                      region.top = (int) Math.floor(region.top * scale);
                      region.right = Math.min((int) Math.ceil(region.right * scale),
                              bitmap.getWidth());
                      region.bottom = Math.min((int) Math.ceil(region.bottom * scale),
                              bitmap.getHeight());
                  }
  
                  // Now generate a quantizer from the Bitmap
                  if (mQuantizer == null) {
                      mQuantizer = new ColorCutQuantizer();
                  }
                  mQuantizer.quantize(getPixelsFromBitmap(bitmap),
                              mMaxColors, mFilters.isEmpty() ? null :
                              mFilters.toArray(new Palette.Filter[mFilters.size()]));
  
                  // If created a new bitmap, recycle it
                  if (bitmap != mBitmap) {
                      bitmap.recycle();
                  }
  
                  swatches = mQuantizer.getQuantizedColors();
  
                  if (logger != null) {
                      logger.addSplit("Color quantization completed");
                  }
              } else {
                  // Else we're using the provided swatches
                  swatches = mSwatches;
              }
  
              // Now create a Palette instance
              final Palette p = new Palette(swatches, mTargets);
              // And make it generate itself
              p.generate();
  
              if (logger != null) {
                  logger.addSplit("Created Palette");
                  logger.dumpToLog();
              }
  
              return p;
          }
  
          /**
           * Generate the {@link Palette} asynchronously. The provided listener's
           * {@link Palette.PaletteAsyncListener#onGenerated} method will be called with the palette when
           * generated.
           */
          @NonNull
          public AsyncTask<Bitmap, Void, Palette> generate(final Palette.PaletteAsyncListener listener) {
              if (listener == null) {
                  throw new IllegalArgumentException("listener can not be null");
              }
  
              return new AsyncTask<Bitmap, Void, Palette>() {
                  @Override
                  protected Palette doInBackground(Bitmap... params) {
                      try {
                          return generate();
                      } catch (Exception e) {
                          Log.e(LOG_TAG, "Exception thrown during async generate", e);
                          return null;
                      }
                  }
  
                  @Override
                  protected void onPostExecute(Palette colorExtractor) {
                      listener.onGenerated(colorExtractor);
                  }
              }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, mBitmap);
          }
  
          private int[] getPixelsFromBitmap(Bitmap bitmap) {
              final int bitmapWidth = bitmap.getWidth();
              final int bitmapHeight = bitmap.getHeight();
              final int[] pixels = new int[bitmapWidth * bitmapHeight];
              bitmap.getPixels(pixels, 0, bitmapWidth, 0, 0, bitmapWidth, bitmapHeight);
  
              if (mRegion == null) {
                  // If we don't have a region, return all of the pixels
                  return pixels;
              } else {
                  // If we do have a region, lets create a subset array containing only the region's
                  // pixels
                  final int regionWidth = mRegion.width();
                  final int regionHeight = mRegion.height();
                  // pixels contains all of the pixels, so we need to iterate through each row and
                  // copy the regions pixels into a new smaller array
                  final int[] subsetPixels = new int[regionWidth * regionHeight];
                  for (int row = 0; row < regionHeight; row++) {
                      System.arraycopy(pixels, ((row + mRegion.top) * bitmapWidth) + mRegion.left,
                              subsetPixels, row * regionWidth, regionWidth);
                  }
                  return subsetPixels;
              }
          }
  
          /**
           * Scale the bitmap down as needed.
           */
          private Bitmap scaleBitmapDown(final Bitmap bitmap) {
              double scaleRatio = -1;
  
              if (mResizeArea > 0) {
                  final int bitmapArea = bitmap.getWidth() * bitmap.getHeight();
                  if (bitmapArea > mResizeArea) {
                      scaleRatio = Math.sqrt(mResizeArea / (double) bitmapArea);
                  }
              } else if (mResizeMaxDimension > 0) {
                  final int maxDimension = Math.max(bitmap.getWidth(), bitmap.getHeight());
                  if (maxDimension > mResizeMaxDimension) {
                      scaleRatio = mResizeMaxDimension / (double) maxDimension;
                  }
              }
  
              if (scaleRatio <= 0) {
                  // Scaling has been disabled or not needed so just return the Bitmap
                  return bitmap;
              }
  
              return Bitmap.createScaledBitmap(bitmap,
                      (int) Math.ceil(bitmap.getWidth() * scaleRatio),
                      (int) Math.ceil(bitmap.getHeight() * scaleRatio),
                      false);
          }
      }
  
      /**
       * 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 Palette.Builder#addFilter(Palette.Filter)
           */
          boolean isAllowed(int rgb, float[] hsl);
      }
  
      /**
       * The default filter.
       */
      static final Palette.Filter
              DEFAULT_FILTER = new Palette.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;
          }
      };
  }
  