| /* |
| * Copyright 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.support.v7.graphics; |
| |
| import android.graphics.Bitmap; |
| import android.graphics.Color; |
| import android.graphics.Rect; |
| import android.os.AsyncTask; |
| import android.support.annotation.ColorInt; |
| import android.support.annotation.Nullable; |
| import android.support.v4.graphics.ColorUtils; |
| 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; |
| |
| /** |
| * 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 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 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, PaletteAsyncListener)} or |
| * {@link #generateAsync(Bitmap, int, PaletteAsyncListener)} |
| */ |
| public interface PaletteAsyncListener { |
| |
| /** |
| * Called when the {@link Palette} has been generated. |
| */ |
| void onGenerated(Palette palette); |
| } |
| |
| private static final int DEFAULT_RESIZE_BITMAP_MAX_DIMENSION = 192; |
| private static final int DEFAULT_CALCULATE_NUMBER_COLORS = 16; |
| |
| private static final float MIN_CONTRAST_TITLE_TEXT = 3.0f; |
| private static final float MIN_CONTRAST_BODY_TEXT = 4.5f; |
| |
| private static final String LOG_TAG = "Palette"; |
| private static final boolean LOG_TIMINGS = false; |
| |
| /** |
| * Start generating a {@link Palette} with the returned {@link Builder} instance. |
| */ |
| public static Builder from(Bitmap bitmap) { |
| return new 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<Swatch> swatches) { |
| return new Builder(swatches).generate(); |
| } |
| |
| /** |
| * @deprecated Use {@link Builder} to generate the Palette. |
| */ |
| @Deprecated |
| public static Palette generate(Bitmap bitmap) { |
| return from(bitmap).generate(); |
| } |
| |
| /** |
| * @deprecated Use {@link Builder} to generate the Palette. |
| */ |
| @Deprecated |
| public static Palette generate(Bitmap bitmap, int numColors) { |
| return from(bitmap).maximumColorCount(numColors).generate(); |
| } |
| |
| /** |
| * @deprecated Use {@link Builder} to generate the Palette. |
| */ |
| @Deprecated |
| public static AsyncTask<Bitmap, Void, Palette> generateAsync( |
| Bitmap bitmap, PaletteAsyncListener listener) { |
| return from(bitmap).generate(listener); |
| } |
| |
| /** |
| * @deprecated Use {@link Builder} to generate the Palette. |
| */ |
| @Deprecated |
| public static AsyncTask<Bitmap, Void, Palette> generateAsync( |
| final Bitmap bitmap, final int numColors, final PaletteAsyncListener listener) { |
| return from(bitmap).maximumColorCount(numColors).generate(listener); |
| } |
| |
| private final List<Swatch> mSwatches; |
| private final Generator mGenerator; |
| |
| private Palette(List<Swatch> swatches, Generator generator) { |
| mSwatches = swatches; |
| mGenerator = generator; |
| } |
| |
| /** |
| * Returns all of the swatches which make up the palette. |
| */ |
| public List<Swatch> getSwatches() { |
| return Collections.unmodifiableList(mSwatches); |
| } |
| |
| /** |
| * Returns the most vibrant swatch in the palette. Might be null. |
| */ |
| @Nullable |
| public Swatch getVibrantSwatch() { |
| return mGenerator.getVibrantSwatch(); |
| } |
| |
| /** |
| * Returns a light and vibrant swatch from the palette. Might be null. |
| */ |
| @Nullable |
| public Swatch getLightVibrantSwatch() { |
| return mGenerator.getLightVibrantSwatch(); |
| } |
| |
| /** |
| * Returns a dark and vibrant swatch from the palette. Might be null. |
| */ |
| @Nullable |
| public Swatch getDarkVibrantSwatch() { |
| return mGenerator.getDarkVibrantSwatch(); |
| } |
| |
| /** |
| * Returns a muted swatch from the palette. Might be null. |
| */ |
| @Nullable |
| public Swatch getMutedSwatch() { |
| return mGenerator.getMutedSwatch(); |
| } |
| |
| /** |
| * Returns a muted and light swatch from the palette. Might be null. |
| */ |
| @Nullable |
| public Swatch getLightMutedSwatch() { |
| return mGenerator.getLightMutedSwatch(); |
| } |
| |
| /** |
| * Returns a muted and dark swatch from the palette. Might be null. |
| */ |
| @Nullable |
| public Swatch getDarkMutedSwatch() { |
| return mGenerator.getDarkMutedSwatch(); |
| } |
| |
| /** |
| * Returns the most vibrant color in the palette as an RGB packed int. |
| * |
| * @param defaultColor value to return if the swatch isn't available |
| */ |
| @ColorInt |
| public int getVibrantColor(@ColorInt int defaultColor) { |
| Swatch swatch = getVibrantSwatch(); |
| return swatch != null ? swatch.getRgb() : 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 |
| */ |
| @ColorInt |
| public int getLightVibrantColor(@ColorInt int defaultColor) { |
| Swatch swatch = getLightVibrantSwatch(); |
| return swatch != null ? swatch.getRgb() : 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 |
| */ |
| @ColorInt |
| public int getDarkVibrantColor(@ColorInt int defaultColor) { |
| Swatch swatch = getDarkVibrantSwatch(); |
| return swatch != null ? swatch.getRgb() : defaultColor; |
| } |
| |
| /** |
| * Returns a muted color from the palette as an RGB packed int. |
| * |
| * @param defaultColor value to return if the swatch isn't available |
| */ |
| @ColorInt |
| public int getMutedColor(@ColorInt int defaultColor) { |
| Swatch swatch = getMutedSwatch(); |
| return swatch != null ? swatch.getRgb() : 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 |
| */ |
| @ColorInt |
| public int getLightMutedColor(@ColorInt int defaultColor) { |
| Swatch swatch = getLightMutedSwatch(); |
| return swatch != null ? swatch.getRgb() : 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 |
| */ |
| @ColorInt |
| public int getDarkMutedColor(@ColorInt int defaultColor) { |
| Swatch swatch = getDarkMutedSwatch(); |
| return swatch != null ? swatch.getRgb() : 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. |
| */ |
| private static Bitmap scaleBitmapDown(Bitmap bitmap, final int targetMaxDimension) { |
| final int maxDimension = Math.max(bitmap.getWidth(), bitmap.getHeight()); |
| |
| if (maxDimension <= targetMaxDimension) { |
| // If the bitmap is small enough already, just return it |
| return bitmap; |
| } |
| |
| final float scaleRatio = targetMaxDimension / (float) maxDimension; |
| return Bitmap.createScaledBitmap(bitmap, |
| Math.round(bitmap.getWidth() * scaleRatio), |
| Math.round(bitmap.getHeight() * scaleRatio), |
| false); |
| } |
| |
| /** |
| * 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; |
| } |
| |
| /** |
| * @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 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 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 && darkBodyAlpha != -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; |
| } |
| |
| Swatch swatch = (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<Swatch> mSwatches; |
| private final 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 Rect mRegion; |
| |
| private Generator mGenerator; |
| |
| /** |
| * Construct a new {@link 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; |
| } |
| |
| /** |
| * Construct a new {@link Builder} using a list of {@link Swatch} instances. |
| * Typically only used for testing. |
| */ |
| public Builder(List<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 {@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> |
| * 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. |
| */ |
| public 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 it's largest dimension matches {@code maxDimension}. 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. |
| */ |
| public Builder resizeBitmapSize(int maxDimension) { |
| mResizeMaxDimension = maxDimension; |
| return this; |
| } |
| |
| /** |
| * 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; |
| } |
| |
| /** |
| * 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. |
| */ |
| public 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)}. |
| */ |
| public Builder clearRegion() { |
| mRegion = null; |
| return this; |
| } |
| |
| /** |
| * Generate and return the {@link Palette} synchronously. |
| */ |
| public Palette generate() { |
| final TimingLogger logger = LOG_TIMINGS |
| ? new TimingLogger(LOG_TAG, "Generation") |
| : null; |
| |
| List<Swatch> swatches; |
| |
| if (mBitmap != null) { |
| // We have a Bitmap so we need to 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); |
| |
| 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 float scale = bitmap.getWidth() / (float) mBitmap.getWidth(); |
| region.left = (int) Math.floor(region.left * scale); |
| region.top = (int) Math.floor(region.top * scale); |
| region.right = (int) Math.ceil(region.right * scale); |
| region.bottom = (int) Math.ceil(region.bottom * scale); |
| } |
| |
| // Now generate a quantizer from the Bitmap |
| final ColorCutQuantizer quantizer = new ColorCutQuantizer( |
| getPixelsFromBitmap(bitmap), |
| mMaxColors, |
| mFilters.isEmpty() ? null : mFilters.toArray(new Filter[mFilters.size()])); |
| |
| // If created a new bitmap, recycle it |
| if (bitmap != mBitmap) { |
| bitmap.recycle(); |
| } |
| swatches = quantizer.getQuantizedColors(); |
| |
| if (logger != null) { |
| logger.addSplit("Color quantization completed"); |
| } |
| } else { |
| // Else we're using the provided swatches |
| 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); |
| |
| if (logger != null) { |
| logger.addSplit("Created Palette"); |
| logger.dumpToLog(); |
| } |
| |
| return p; |
| } |
| |
| /** |
| * Generate the {@link Palette} asynchronously. The provided listener's |
| * {@link PaletteAsyncListener#onGenerated} method will be called with the palette when |
| * generated. |
| */ |
| public AsyncTask<Bitmap, Void, Palette> generate(final PaletteAsyncListener listener) { |
| if (listener == null) { |
| throw new IllegalArgumentException("listener can not be null"); |
| } |
| |
| return AsyncTaskCompat.executeParallel( |
| new AsyncTask<Bitmap, Void, Palette>() { |
| @Override |
| protected Palette doInBackground(Bitmap... params) { |
| return generate(); |
| } |
| |
| @Override |
| protected void onPostExecute(Palette colorExtractor) { |
| listener.onGenerated(colorExtractor); |
| } |
| }, mBitmap); |
| } |
| |
| private int[] getPixelsFromBitmap(Bitmap bitmap) { |
| final int bitmapWidth = bitmap.getWidth(); |
| final int bitmapHeight = bitmap.getHeight(); |
| final int[] pixels = new int[bitmapWidth * bitmapHeight]; |
| |
| if (mRegion == null) { |
| // If we don't have a region, return all of the pixels |
| bitmap.getPixels(pixels, 0, bitmapWidth, 0, 0, bitmapWidth, bitmapHeight); |
| 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(); |
| // First read the pixels within the region |
| bitmap.getPixels(pixels, 0, bitmapWidth, mRegion.left, mRegion.top, |
| regionWidth, regionHeight); |
| // pixels now contains all of the pixels, but not packed together. We need to |
| // iterate through each row and copy them into a new smaller array |
| final int[] subsetPixels = new int[regionWidth * mRegion.height()]; |
| for (int row = mRegion.top; row < mRegion.bottom; row++) { |
| System.arraycopy(pixels, (row * bitmapWidth) + mRegion.left, |
| subsetPixels, row * regionWidth, regionWidth); |
| } |
| 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. |
| */ |
| public abstract void generate(List<Palette.Swatch> swatches); |
| |
| /** |
| * Return the most vibrant {@link Palette.Swatch} |
| */ |
| public Palette.Swatch getVibrantSwatch() { |
| return null; |
| } |
| |
| /** |
| * Return a light and vibrant {@link Palette.Swatch} |
| */ |
| public Palette.Swatch getLightVibrantSwatch() { |
| return null; |
| } |
| |
| /** |
| * 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; |
| } |
| } |
| |
| /** |
| * 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; |
| } |
| }; |
| } |