1059178a8c7cc80848e5594a9287be91bd924831aChris Banes/*
2059178a8c7cc80848e5594a9287be91bd924831aChris Banes * Copyright 2014 The Android Open Source Project
3059178a8c7cc80848e5594a9287be91bd924831aChris Banes *
4059178a8c7cc80848e5594a9287be91bd924831aChris Banes * Licensed under the Apache License, Version 2.0 (the "License");
5059178a8c7cc80848e5594a9287be91bd924831aChris Banes * you may not use this file except in compliance with the License.
6059178a8c7cc80848e5594a9287be91bd924831aChris Banes * You may obtain a copy of the License at
7059178a8c7cc80848e5594a9287be91bd924831aChris Banes *
8059178a8c7cc80848e5594a9287be91bd924831aChris Banes *       http://www.apache.org/licenses/LICENSE-2.0
9059178a8c7cc80848e5594a9287be91bd924831aChris Banes *
10059178a8c7cc80848e5594a9287be91bd924831aChris Banes * Unless required by applicable law or agreed to in writing, software
11059178a8c7cc80848e5594a9287be91bd924831aChris Banes * distributed under the License is distributed on an "AS IS" BASIS,
12059178a8c7cc80848e5594a9287be91bd924831aChris Banes * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13059178a8c7cc80848e5594a9287be91bd924831aChris Banes * See the License for the specific language governing permissions and
14059178a8c7cc80848e5594a9287be91bd924831aChris Banes * limitations under the License.
15059178a8c7cc80848e5594a9287be91bd924831aChris Banes */
16059178a8c7cc80848e5594a9287be91bd924831aChris Banes
17059178a8c7cc80848e5594a9287be91bd924831aChris Banespackage android.support.v7.graphics;
18059178a8c7cc80848e5594a9287be91bd924831aChris Banes
19059178a8c7cc80848e5594a9287be91bd924831aChris Banesimport android.graphics.Bitmap;
20c6cdc41397bc3ad2c936069af6d448f242790513Chris Banesimport android.graphics.Color;
21ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banesimport android.graphics.Rect;
22059178a8c7cc80848e5594a9287be91bd924831aChris Banesimport android.os.AsyncTask;
231a4412ccda7b3e7818bdeceb60cc1e5ca9a65e34Chris Banesimport android.support.annotation.ColorInt;
24c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banesimport android.support.annotation.NonNull;
251a4412ccda7b3e7818bdeceb60cc1e5ca9a65e34Chris Banesimport android.support.annotation.Nullable;
26711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banesimport android.support.v4.graphics.ColorUtils;
27c6cdc41397bc3ad2c936069af6d448f242790513Chris Banesimport android.support.v4.os.AsyncTaskCompat;
28c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banesimport android.support.v4.util.ArrayMap;
298f6ea77d463e9bb5cf70aaa4339316d13ac4a36cChris Banesimport android.util.Log;
30c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banesimport android.util.SparseBooleanArray;
317aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banesimport android.util.TimingLogger;
32059178a8c7cc80848e5594a9287be91bd924831aChris Banes
332407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banesimport java.util.ArrayList;
34c6cdc41397bc3ad2c936069af6d448f242790513Chris Banesimport java.util.Arrays;
35059178a8c7cc80848e5594a9287be91bd924831aChris Banesimport java.util.Collections;
36059178a8c7cc80848e5594a9287be91bd924831aChris Banesimport java.util.List;
37c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banesimport java.util.Map;
38059178a8c7cc80848e5594a9287be91bd924831aChris Banes
39059178a8c7cc80848e5594a9287be91bd924831aChris Banes/**
40059178a8c7cc80848e5594a9287be91bd924831aChris Banes * A helper class to extract prominent colors from an image.
41059178a8c7cc80848e5594a9287be91bd924831aChris Banes * <p>
42059178a8c7cc80848e5594a9287be91bd924831aChris Banes * A number of colors with different profiles are extracted from the image:
43059178a8c7cc80848e5594a9287be91bd924831aChris Banes * <ul>
44059178a8c7cc80848e5594a9287be91bd924831aChris Banes *     <li>Vibrant</li>
45059178a8c7cc80848e5594a9287be91bd924831aChris Banes *     <li>Vibrant Dark</li>
46059178a8c7cc80848e5594a9287be91bd924831aChris Banes *     <li>Vibrant Light</li>
47059178a8c7cc80848e5594a9287be91bd924831aChris Banes *     <li>Muted</li>
48059178a8c7cc80848e5594a9287be91bd924831aChris Banes *     <li>Muted Dark</li>
49059178a8c7cc80848e5594a9287be91bd924831aChris Banes *     <li>Muted Light</li>
50059178a8c7cc80848e5594a9287be91bd924831aChris Banes * </ul>
51059178a8c7cc80848e5594a9287be91bd924831aChris Banes * These can be retrieved from the appropriate getter method.
52059178a8c7cc80848e5594a9287be91bd924831aChris Banes *
53059178a8c7cc80848e5594a9287be91bd924831aChris Banes * <p>
547aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes * Instances are created with a {@link Builder} which supports several options to tweak the
557aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes * generated Palette. See that class' documentation for more information.
56059178a8c7cc80848e5594a9287be91bd924831aChris Banes * <p>
577aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes * Generation should always be completed on a background thread, ideally the one in
587aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes * which you load your image on. {@link Builder} supports both synchronous and asynchronous
597aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes * generation:
60059178a8c7cc80848e5594a9287be91bd924831aChris Banes *
61059178a8c7cc80848e5594a9287be91bd924831aChris Banes * <pre>
627aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes * // Synchronous
637aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes * Palette p = Palette.from(bitmap).generate();
647aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes *
657aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes * // Asynchronous
667aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes * Palette.from(bitmap).generate(new PaletteAsyncListener() {
677aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes *     public void onGenerated(Palette p) {
687aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes *         // Use generated instance
69059178a8c7cc80848e5594a9287be91bd924831aChris Banes *     }
70059178a8c7cc80848e5594a9287be91bd924831aChris Banes * });
71059178a8c7cc80848e5594a9287be91bd924831aChris Banes * </pre>
72059178a8c7cc80848e5594a9287be91bd924831aChris Banes */
73059178a8c7cc80848e5594a9287be91bd924831aChris Banespublic final class Palette {
74059178a8c7cc80848e5594a9287be91bd924831aChris Banes
75059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
76059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * Listener to be used with {@link #generateAsync(Bitmap, PaletteAsyncListener)} or
77059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * {@link #generateAsync(Bitmap, int, PaletteAsyncListener)}
78059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
79059178a8c7cc80848e5594a9287be91bd924831aChris Banes    public interface PaletteAsyncListener {
80059178a8c7cc80848e5594a9287be91bd924831aChris Banes
81059178a8c7cc80848e5594a9287be91bd924831aChris Banes        /**
82059178a8c7cc80848e5594a9287be91bd924831aChris Banes         * Called when the {@link Palette} has been generated.
83059178a8c7cc80848e5594a9287be91bd924831aChris Banes         */
84059178a8c7cc80848e5594a9287be91bd924831aChris Banes        void onGenerated(Palette palette);
85059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
86059178a8c7cc80848e5594a9287be91bd924831aChris Banes
87c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes    private static final int DEFAULT_RESIZE_BITMAP_AREA = 160 * 160;
88059178a8c7cc80848e5594a9287be91bd924831aChris Banes    private static final int DEFAULT_CALCULATE_NUMBER_COLORS = 16;
89059178a8c7cc80848e5594a9287be91bd924831aChris Banes
90f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes    private static final float MIN_CONTRAST_TITLE_TEXT = 3.0f;
91f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes    private static final float MIN_CONTRAST_BODY_TEXT = 4.5f;
92f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes
937aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes    private static final String LOG_TAG = "Palette";
947aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes    private static final boolean LOG_TIMINGS = false;
95059178a8c7cc80848e5594a9287be91bd924831aChris Banes
967aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes    /**
977aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes     * Start generating a {@link Palette} with the returned {@link Builder} instance.
987aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes     */
997aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes    public static Builder from(Bitmap bitmap) {
1007aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes        return new Builder(bitmap);
1017aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes    }
102059178a8c7cc80848e5594a9287be91bd924831aChris Banes
1037aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes    /**
1047aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes     * Generate a {@link Palette} from the pre-generated list of {@link Palette.Swatch} swatches.
1057aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes     * This is useful for testing, or if you want to resurrect a {@link Palette} instance from a
1067aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes     * list of swatches. Will return null if the {@code swatches} is null.
1077aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes     */
1087aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes    public static Palette from(List<Swatch> swatches) {
1097aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes        return new Builder(swatches).generate();
1107aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes    }
111059178a8c7cc80848e5594a9287be91bd924831aChris Banes
112059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
1137aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes     * @deprecated Use {@link Builder} to generate the Palette.
114059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
1157aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes    @Deprecated
116059178a8c7cc80848e5594a9287be91bd924831aChris Banes    public static Palette generate(Bitmap bitmap) {
1177aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes        return from(bitmap).generate();
118059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
119059178a8c7cc80848e5594a9287be91bd924831aChris Banes
120059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
1217aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes     * @deprecated Use {@link Builder} to generate the Palette.
122059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
1237aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes    @Deprecated
124059178a8c7cc80848e5594a9287be91bd924831aChris Banes    public static Palette generate(Bitmap bitmap, int numColors) {
1257aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes        return from(bitmap).maximumColorCount(numColors).generate();
126059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
127059178a8c7cc80848e5594a9287be91bd924831aChris Banes
128059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
1297aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes     * @deprecated Use {@link Builder} to generate the Palette.
130059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
1317aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes    @Deprecated
132c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    public static AsyncTask<Bitmap, Void, Palette> generateAsync(
133059178a8c7cc80848e5594a9287be91bd924831aChris Banes            Bitmap bitmap, PaletteAsyncListener listener) {
1347aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes        return from(bitmap).generate(listener);
135059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
136059178a8c7cc80848e5594a9287be91bd924831aChris Banes
137059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
1387aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes     * @deprecated Use {@link Builder} to generate the Palette.
139059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
1407aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes    @Deprecated
141c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    public static AsyncTask<Bitmap, Void, Palette> generateAsync(
142059178a8c7cc80848e5594a9287be91bd924831aChris Banes            final Bitmap bitmap, final int numColors, final PaletteAsyncListener listener) {
1437aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes        return from(bitmap).maximumColorCount(numColors).generate(listener);
144059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
145059178a8c7cc80848e5594a9287be91bd924831aChris Banes
1467aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes    private final List<Swatch> mSwatches;
147c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes    private final List<Target> mTargets;
148d517c8e7bf3106eae27ed00435164020c052308eChris Banes
149c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes    private final Map<Target, Swatch> mSelectedSwatches;
150c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes    private final SparseBooleanArray mUsedColors;
151c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes
152c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes    private final int mMaxPopulation;
153c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes
154c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes    private Palette(List<Swatch> swatches, List<Target> targets) {
155c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        mSwatches = swatches;
156c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        mTargets = targets;
157c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes
158c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        mUsedColors = new SparseBooleanArray();
159c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        mSelectedSwatches = new ArrayMap<>();
160c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes
161c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        mMaxPopulation = findMaxPopulation();
162059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
163059178a8c7cc80848e5594a9287be91bd924831aChris Banes
164059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
165c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Returns all of the swatches which make up the palette.
166059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
167c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes    @NonNull
168c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    public List<Swatch> getSwatches() {
169c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        return Collections.unmodifiableList(mSwatches);
170059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
171059178a8c7cc80848e5594a9287be91bd924831aChris Banes
172059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
173c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes     * Returns the targets used to generate this palette.
174c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes     */
175c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes    @NonNull
176c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes    public List<Target> getTargets() {
177c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        return Collections.unmodifiableList(mTargets);
178c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes    }
179c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes
180c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes    /**
181c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Returns the most vibrant swatch in the palette. Might be null.
182c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes     *
183c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes     * @see Target#VIBRANT
184059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
1851a4412ccda7b3e7818bdeceb60cc1e5ca9a65e34Chris Banes    @Nullable
186c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    public Swatch getVibrantSwatch() {
187c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        return getSwatchForTarget(Target.VIBRANT);
188059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
189059178a8c7cc80848e5594a9287be91bd924831aChris Banes
190059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
191c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Returns a light and vibrant swatch from the palette. Might be null.
192c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes     *
193c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes     * @see Target#LIGHT_VIBRANT
194059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
1951a4412ccda7b3e7818bdeceb60cc1e5ca9a65e34Chris Banes    @Nullable
196c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    public Swatch getLightVibrantSwatch() {
197c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        return getSwatchForTarget(Target.LIGHT_VIBRANT);
198059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
199059178a8c7cc80848e5594a9287be91bd924831aChris Banes
200059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
201c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Returns a dark and vibrant swatch from the palette. Might be null.
202c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes     *
203c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes     * @see Target#DARK_VIBRANT
204059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
2051a4412ccda7b3e7818bdeceb60cc1e5ca9a65e34Chris Banes    @Nullable
206c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    public Swatch getDarkVibrantSwatch() {
207c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        return getSwatchForTarget(Target.DARK_VIBRANT);
208059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
209059178a8c7cc80848e5594a9287be91bd924831aChris Banes
210059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
211c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Returns a muted swatch from the palette. Might be null.
212c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes     *
213c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes     * @see Target#MUTED
214059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
2151a4412ccda7b3e7818bdeceb60cc1e5ca9a65e34Chris Banes    @Nullable
216c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    public Swatch getMutedSwatch() {
217c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        return getSwatchForTarget(Target.MUTED);
218059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
219059178a8c7cc80848e5594a9287be91bd924831aChris Banes
220059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
221c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Returns a muted and light swatch from the palette. Might be null.
222c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes     *
223c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes     * @see Target#LIGHT_MUTED
224059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
2251a4412ccda7b3e7818bdeceb60cc1e5ca9a65e34Chris Banes    @Nullable
226c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    public Swatch getLightMutedSwatch() {
227c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        return getSwatchForTarget(Target.LIGHT_MUTED);
228059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
229059178a8c7cc80848e5594a9287be91bd924831aChris Banes
230059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
231c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Returns a muted and dark swatch from the palette. Might be null.
232c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes     *
233c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes     * @see Target#DARK_MUTED
234c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     */
2351a4412ccda7b3e7818bdeceb60cc1e5ca9a65e34Chris Banes    @Nullable
236c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    public Swatch getDarkMutedSwatch() {
237c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        return getSwatchForTarget(Target.DARK_MUTED);
238c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    }
239c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
240c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    /**
241c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Returns the most vibrant color in the palette as an RGB packed int.
242c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     *
243c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * @param defaultColor value to return if the swatch isn't available
244c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes     * @see #getVibrantSwatch()
245c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     */
2461a4412ccda7b3e7818bdeceb60cc1e5ca9a65e34Chris Banes    @ColorInt
247c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes    public int getVibrantColor(@ColorInt final int defaultColor) {
248c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        return getColorForTarget(Target.VIBRANT, defaultColor);
249c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    }
250c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
251c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    /**
252c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Returns a light and vibrant color from the palette as an RGB packed int.
253c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     *
254c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * @param defaultColor value to return if the swatch isn't available
255c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes     * @see #getLightVibrantSwatch()
256c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     */
2571a4412ccda7b3e7818bdeceb60cc1e5ca9a65e34Chris Banes    @ColorInt
258c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes    public int getLightVibrantColor(@ColorInt final int defaultColor) {
259c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        return getColorForTarget(Target.LIGHT_VIBRANT, defaultColor);
260c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    }
261c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
262c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    /**
263c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Returns a dark and vibrant color from the palette as an RGB packed int.
264c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     *
265c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * @param defaultColor value to return if the swatch isn't available
266c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes     * @see #getDarkVibrantSwatch()
267c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     */
2681a4412ccda7b3e7818bdeceb60cc1e5ca9a65e34Chris Banes    @ColorInt
269c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes    public int getDarkVibrantColor(@ColorInt final int defaultColor) {
270c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        return getColorForTarget(Target.DARK_VIBRANT, defaultColor);
271c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    }
272c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
273c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    /**
274c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Returns a muted color from the palette as an RGB packed int.
275c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     *
276c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * @param defaultColor value to return if the swatch isn't available
277c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes     * @see #getMutedSwatch()
278c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     */
2791a4412ccda7b3e7818bdeceb60cc1e5ca9a65e34Chris Banes    @ColorInt
280c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes    public int getMutedColor(@ColorInt final int defaultColor) {
281c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        return getColorForTarget(Target.MUTED, defaultColor);
282c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    }
283c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
284c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    /**
285c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Returns a muted and light color from the palette as an RGB packed int.
286c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     *
287c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * @param defaultColor value to return if the swatch isn't available
288c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes     * @see #getLightMutedSwatch()
289c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     */
2901a4412ccda7b3e7818bdeceb60cc1e5ca9a65e34Chris Banes    @ColorInt
291c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes    public int getLightMutedColor(@ColorInt final int defaultColor) {
292c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        return getColorForTarget(Target.LIGHT_MUTED, defaultColor);
293c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    }
294c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
295c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    /**
296c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Returns a muted and dark color from the palette as an RGB packed int.
297c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     *
298c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * @param defaultColor value to return if the swatch isn't available
299c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes     * @see #getDarkMutedSwatch()
300059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
3011a4412ccda7b3e7818bdeceb60cc1e5ca9a65e34Chris Banes    @ColorInt
302c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes    public int getDarkMutedColor(@ColorInt final int defaultColor) {
303c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        return getColorForTarget(Target.DARK_MUTED, defaultColor);
304c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes    }
305c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes
306c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes    /**
307c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes     * Returns the selected swatch for the given target from the palette, or {@code null} if one
308c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes     * could not be found.
309c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes     */
310c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes    @Nullable
311c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes    public Swatch getSwatchForTarget(@NonNull final Target target) {
312c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        return mSelectedSwatches.get(target);
313059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
314059178a8c7cc80848e5594a9287be91bd924831aChris Banes
315059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
316c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes     * Returns the selected color for the given target from the palette as an RGB packed int.
317c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes     *
318c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes     * @param defaultColor value to return if the swatch isn't available
319059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
320c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes    @ColorInt
321c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes    public int getColorForTarget(@NonNull final Target target, @ColorInt final int defaultColor) {
322c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        Swatch swatch = getSwatchForTarget(target);
323c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        return swatch != null ? swatch.getRgb() : defaultColor;
324c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes    }
325059178a8c7cc80848e5594a9287be91bd924831aChris Banes
326c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes    private void generate() {
327c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        // We need to make sure that the scored targets are generated first. This is so that
328c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        // inherited targets have something to inherit from
329c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        for (int i = 0, count = mTargets.size(); i < count; i++) {
330c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes            final Target target = mTargets.get(i);
331c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes            target.normalizeWeights();
332c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes            mSelectedSwatches.put(target, generateScoredTarget(target));
333059178a8c7cc80848e5594a9287be91bd924831aChris Banes        }
334c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        // We now clear out the used colors
335c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        mUsedColors.clear();
336c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes    }
337059178a8c7cc80848e5594a9287be91bd924831aChris Banes
338c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes    private Swatch generateScoredTarget(final Target target) {
339c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        final Swatch maxScoreSwatch = getMaxScoredSwatchForTarget(target);
340c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        if (maxScoreSwatch != null && target.isExclusive()) {
341c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes            // If we have a swatch, and the target is exclusive, add the color to the used list
342c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes            mUsedColors.append(maxScoreSwatch.getRgb(), true);
343c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        }
344c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        return maxScoreSwatch;
345c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes    }
346c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes
347c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes    private Swatch getMaxScoredSwatchForTarget(final Target target) {
348c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        float maxScore = 0;
349c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        Swatch maxScoreSwatch = null;
350c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        for (int i = 0, count = mSwatches.size(); i < count; i++) {
351c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes            final Swatch swatch = mSwatches.get(i);
352c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes            if (shouldBeScoredForTarget(swatch, target)) {
353c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes                final float score = generateScore(swatch, target);
354c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes                if (maxScoreSwatch == null || score > maxScore) {
355c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes                    maxScoreSwatch = swatch;
356c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes                    maxScore = score;
357c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes                }
358c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes            }
359c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        }
360c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        return maxScoreSwatch;
361c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes    }
362c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes
363c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes    private boolean shouldBeScoredForTarget(final Swatch swatch, final Target target) {
364c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        // Check whether the HSL values are within the correct ranges, and this color hasn't
365c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        // been used yet.
366c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        final float hsl[] = swatch.getHsl();
367c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        return hsl[1] >= target.getMinimumSaturation() && hsl[1] <= target.getMaximumSaturation()
368c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes                && hsl[2] >= target.getMinimumLightness() && hsl[2] <= target.getMaximumLightness()
369c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes                && !mUsedColors.get(swatch.getRgb());
370c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes    }
371c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes
372c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes    private float generateScore(Swatch swatch, Target target) {
373c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        final float[] hsl = swatch.getHsl();
374c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes
375c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        float saturationScore = 0;
376c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        float luminanceScore = 0;
377c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        float populationScore = 0;
378c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes
379c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        if (target.getSaturationWeight() > 0) {
380c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes            saturationScore = target.getSaturationWeight()
381c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes                    * (1f - Math.abs(hsl[1] - target.getTargetSaturation()));
382c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        }
383c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        if (target.getLightnessWeight() > 0) {
384c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes            luminanceScore = target.getLightnessWeight()
385c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes                    * (1f - Math.abs(hsl[2] - target.getTargetLightness()));
386c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        }
387c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        if (target.getPopulationWeight() > 0) {
388c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes            populationScore = target.getPopulationWeight()
389c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes                    * (swatch.getPopulation() / (float) mMaxPopulation);
390c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        }
391c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes
392c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        return saturationScore + luminanceScore + populationScore;
393c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes    }
394c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes
395c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes    private int findMaxPopulation() {
396c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        int max = 0;
397c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        for (int i = 0, count = mSwatches.size(); i < count; i++) {
398c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes            max = Math.max(mSwatches.get(i).getPopulation(), max);
399c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        }
400c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        return max;
401c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes    }
402c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes
403c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes    private static float[] copyHslValues(Swatch color) {
404c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        final float[] newHsl = new float[3];
405c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        System.arraycopy(color.getHsl(), 0, newHsl, 0, 3);
406c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        return newHsl;
407059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
408059178a8c7cc80848e5594a9287be91bd924831aChris Banes
409c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    /**
410c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Represents a color swatch generated from an image's palette. The RGB color can be retrieved
411c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * by calling {@link #getRgb()}.
412c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     */
413c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    public static final class Swatch {
414f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        private final int mRed, mGreen, mBlue;
415f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        private final int mRgb;
416f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        private final int mPopulation;
417c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
418f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        private boolean mGeneratedTextColors;
419f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        private int mTitleTextColor;
420f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        private int mBodyTextColor;
421c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
422c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        private float[] mHsl;
423c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
4241a4412ccda7b3e7818bdeceb60cc1e5ca9a65e34Chris Banes        public Swatch(@ColorInt int color, int population) {
425d517c8e7bf3106eae27ed00435164020c052308eChris Banes            mRed = Color.red(color);
426d517c8e7bf3106eae27ed00435164020c052308eChris Banes            mGreen = Color.green(color);
427d517c8e7bf3106eae27ed00435164020c052308eChris Banes            mBlue = Color.blue(color);
428d517c8e7bf3106eae27ed00435164020c052308eChris Banes            mRgb = color;
429c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            mPopulation = population;
430c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        }
431c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
432c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        Swatch(int red, int green, int blue, int population) {
433c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            mRed = red;
434c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            mGreen = green;
435c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            mBlue = blue;
436c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            mRgb = Color.rgb(red, green, blue);
437c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            mPopulation = population;
438c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        }
439c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
440c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        Swatch(float[] hsl, int population) {
441c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes            this(ColorUtils.HSLToColor(hsl), population);
442c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes            mHsl = hsl;
443c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        }
444c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes
445c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        /**
446c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes         * @return this swatch's RGB color value
447c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes         */
4481a4412ccda7b3e7818bdeceb60cc1e5ca9a65e34Chris Banes        @ColorInt
449c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        public int getRgb() {
450c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            return mRgb;
451c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        }
452c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
453c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        /**
454c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes         * Return this swatch's HSL values.
455c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes         *     hsv[0] is Hue [0 .. 360)
456c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes         *     hsv[1] is Saturation [0...1]
457c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes         *     hsv[2] is Lightness [0...1]
458c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes         */
459c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        public float[] getHsl() {
460c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            if (mHsl == null) {
461c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                mHsl = new float[3];
462c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            }
463c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes            ColorUtils.RGBToHSL(mRed, mGreen, mBlue, mHsl);
464c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            return mHsl;
465c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        }
466c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
467c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        /**
468c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes         * @return the number of pixels represented by this swatch
469c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes         */
470c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        public int getPopulation() {
471c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            return mPopulation;
472c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        }
473c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
474f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        /**
475f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes         * Returns an appropriate color to use for any 'title' text which is displayed over this
476f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes         * {@link Swatch}'s color. This color is guaranteed to have sufficient contrast.
477f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes         */
4781a4412ccda7b3e7818bdeceb60cc1e5ca9a65e34Chris Banes        @ColorInt
479f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        public int getTitleTextColor() {
480f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes            ensureTextColorsGenerated();
481f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes            return mTitleTextColor;
482f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        }
483f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes
484f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        /**
485f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes         * Returns an appropriate color to use for any 'body' text which is displayed over this
486f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes         * {@link Swatch}'s color. This color is guaranteed to have sufficient contrast.
487f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes         */
4881a4412ccda7b3e7818bdeceb60cc1e5ca9a65e34Chris Banes        @ColorInt
489f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        public int getBodyTextColor() {
490f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes            ensureTextColorsGenerated();
491f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes            return mBodyTextColor;
492f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        }
493f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes
494f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        private void ensureTextColorsGenerated() {
495f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes            if (!mGeneratedTextColors) {
496c95f023926f2b73e257b9635567cea05214d1252Chris Banes                // First check white, as most colors will be dark
497711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes                final int lightBodyAlpha = ColorUtils.calculateMinimumAlpha(
498711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes                        Color.WHITE, mRgb, MIN_CONTRAST_BODY_TEXT);
499711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes                final int lightTitleAlpha = ColorUtils.calculateMinimumAlpha(
500711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes                        Color.WHITE, mRgb, MIN_CONTRAST_TITLE_TEXT);
501c95f023926f2b73e257b9635567cea05214d1252Chris Banes
502711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes                if (lightBodyAlpha != -1 && lightTitleAlpha != -1) {
503c95f023926f2b73e257b9635567cea05214d1252Chris Banes                    // If we found valid light values, use them and return
504711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes                    mBodyTextColor = ColorUtils.setAlphaComponent(Color.WHITE, lightBodyAlpha);
505711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes                    mTitleTextColor = ColorUtils.setAlphaComponent(Color.WHITE, lightTitleAlpha);
506c95f023926f2b73e257b9635567cea05214d1252Chris Banes                    mGeneratedTextColors = true;
507c95f023926f2b73e257b9635567cea05214d1252Chris Banes                    return;
508c95f023926f2b73e257b9635567cea05214d1252Chris Banes                }
509c95f023926f2b73e257b9635567cea05214d1252Chris Banes
510711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes                final int darkBodyAlpha = ColorUtils.calculateMinimumAlpha(
511711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes                        Color.BLACK, mRgb, MIN_CONTRAST_BODY_TEXT);
512711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes                final int darkTitleAlpha = ColorUtils.calculateMinimumAlpha(
513711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes                        Color.BLACK, mRgb, MIN_CONTRAST_TITLE_TEXT);
514c95f023926f2b73e257b9635567cea05214d1252Chris Banes
515711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes                if (darkBodyAlpha != -1 && darkBodyAlpha != -1) {
516c95f023926f2b73e257b9635567cea05214d1252Chris Banes                    // If we found valid dark values, use them and return
517711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes                    mBodyTextColor = ColorUtils.setAlphaComponent(Color.BLACK, darkBodyAlpha);
518711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes                    mTitleTextColor = ColorUtils.setAlphaComponent(Color.BLACK, darkTitleAlpha);
519c95f023926f2b73e257b9635567cea05214d1252Chris Banes                    mGeneratedTextColors = true;
520c95f023926f2b73e257b9635567cea05214d1252Chris Banes                    return;
521c95f023926f2b73e257b9635567cea05214d1252Chris Banes                }
522c95f023926f2b73e257b9635567cea05214d1252Chris Banes
523c95f023926f2b73e257b9635567cea05214d1252Chris Banes                // If we reach here then we can not find title and body values which use the same
524c95f023926f2b73e257b9635567cea05214d1252Chris Banes                // lightness, we need to use mismatched values
525711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes                mBodyTextColor = lightBodyAlpha != -1
526711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes                        ? ColorUtils.setAlphaComponent(Color.WHITE, lightBodyAlpha)
527711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes                        : ColorUtils.setAlphaComponent(Color.BLACK, darkBodyAlpha);
528711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes                mTitleTextColor = lightTitleAlpha != -1
529711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes                        ? ColorUtils.setAlphaComponent(Color.WHITE, lightTitleAlpha)
530711c3df64595e2404ff6aa642ee5303f510e1dcbChris Banes                        : ColorUtils.setAlphaComponent(Color.BLACK, darkTitleAlpha);
531f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes                mGeneratedTextColors = true;
532f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes            }
533f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        }
534f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes
535c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        @Override
536c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        public String toString() {
537f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes            return new StringBuilder(getClass().getSimpleName())
538f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes                    .append(" [RGB: #").append(Integer.toHexString(getRgb())).append(']')
539f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes                    .append(" [HSL: ").append(Arrays.toString(getHsl())).append(']')
540f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes                    .append(" [Population: ").append(mPopulation).append(']')
54147919ca1370ed2fb3be1b772c0c0bff2848e7965Chris Banes                    .append(" [Title Text: #").append(Integer.toHexString(getTitleTextColor()))
54247919ca1370ed2fb3be1b772c0c0bff2848e7965Chris Banes                    .append(']')
54347919ca1370ed2fb3be1b772c0c0bff2848e7965Chris Banes                    .append(" [Body Text: #").append(Integer.toHexString(getBodyTextColor()))
54447919ca1370ed2fb3be1b772c0c0bff2848e7965Chris Banes                    .append(']').toString();
545c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        }
546e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes
547e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes        @Override
548e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes        public boolean equals(Object o) {
549e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes            if (this == o) {
550e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes                return true;
551e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes            }
552e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes            if (o == null || getClass() != o.getClass()) {
553e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes                return false;
554e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes            }
555e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes
556e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes            Swatch swatch = (Swatch) o;
557e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes            return mPopulation == swatch.mPopulation && mRgb == swatch.mRgb;
558e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes        }
559e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes
560e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes        @Override
561e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes        public int hashCode() {
562e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes            return 31 * mRgb + mPopulation;
563e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes        }
564c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    }
565c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
5667aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes    /**
5677aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes     * Builder class for generating {@link Palette} instances.
5687aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes     */
5697aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes    public static final class Builder {
570ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes        private final List<Swatch> mSwatches;
571ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes        private final Bitmap mBitmap;
572c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes
573c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        private final List<Target> mTargets = new ArrayList<>();
574c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes
5757aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes        private int mMaxColors = DEFAULT_CALCULATE_NUMBER_COLORS;
576c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        private int mResizeArea = DEFAULT_RESIZE_BITMAP_AREA;
577c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        private int mResizeMaxDimension = -1;
578c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes
5792407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes        private final List<Filter> mFilters = new ArrayList<>();
580ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes        private Rect mRegion;
5817aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes
5827aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes        /**
5837aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes         * Construct a new {@link Builder} using a source {@link Bitmap}
5847aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes         */
5857aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes        public Builder(Bitmap bitmap) {
5867aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes            if (bitmap == null || bitmap.isRecycled()) {
5877aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes                throw new IllegalArgumentException("Bitmap is not valid");
5887aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes            }
589ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes            mFilters.add(DEFAULT_FILTER);
5907aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes            mBitmap = bitmap;
591ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes            mSwatches = null;
592c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes
593c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes            // Add the default targets
594c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes            mTargets.add(Target.LIGHT_VIBRANT);
595c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes            mTargets.add(Target.VIBRANT);
596c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes            mTargets.add(Target.DARK_VIBRANT);
597c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes            mTargets.add(Target.LIGHT_MUTED);
598c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes            mTargets.add(Target.MUTED);
599c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes            mTargets.add(Target.DARK_MUTED);
6007aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes        }
6017aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes
6027aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes        /**
6037aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes         * Construct a new {@link Builder} using a list of {@link Swatch} instances.
6047aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes         * Typically only used for testing.
6057aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes         */
6067aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes        public Builder(List<Swatch> swatches) {
6077aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes            if (swatches == null || swatches.isEmpty()) {
6087aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes                throw new IllegalArgumentException("List of Swatches is not valid");
6097aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes            }
6102407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes            mFilters.add(DEFAULT_FILTER);
611ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes            mSwatches = swatches;
612ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes            mBitmap = null;
6132407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes        }
6142407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes
6157aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes        /**
6167aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes         * Set the maximum number of colors to use in the quantization step when using a
6177aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes         * {@link android.graphics.Bitmap} as the source.
6187aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes         * <p>
6197aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes         * Good values for depend on the source image type. For landscapes, good values are in
6207aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes         * the range 10-16. For images which are largely made up of people's faces then this
6217aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes         * value should be increased to ~24.
6227aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes         */
623c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        @NonNull
6247aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes        public Builder maximumColorCount(int colors) {
6257aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes            mMaxColors = colors;
6267aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes            return this;
6277aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes        }
6287aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes
6297aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes        /**
6307aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes         * Set the resize value when using a {@link android.graphics.Bitmap} as the source.
6317aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes         * If the bitmap's largest dimension is greater than the value specified, then the bitmap
6327aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes         * will be resized so that it's largest dimension matches {@code maxDimension}. If the
6337aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes         * bitmap is smaller or equal, the original is used as-is.
634c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes         *
635c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes         * @deprecated Using {@link #resizeBitmapArea(int)} is preferred since it can handle
636c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes         * abnormal aspect ratios more gracefully.
637c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes         *
638c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes         * @param maxDimension the number of pixels that the max dimension should be scaled down to,
639c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes         *                     or any value <= 0 to disable resizing.
640c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes         */
641c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        @NonNull
642c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        @Deprecated
643c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        public Builder resizeBitmapSize(final int maxDimension) {
644c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes            mResizeMaxDimension = maxDimension;
645c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes            mResizeArea = -1;
646c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes            return this;
647c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        }
648c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes
649c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        /**
650c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes         * Set the resize value when using a {@link android.graphics.Bitmap} as the source.
651c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes         * If the bitmap's area is greater than the value specified, then the bitmap
652c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes         * will be resized so that it's area matches {@code area}. If the
653c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes         * bitmap is smaller or equal, the original is used as-is.
6547aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes         * <p>
6557aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes         * This value has a large effect on the processing time. The larger the resized image is,
6567aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes         * the greater time it will take to generate the palette. The smaller the image is, the
6577aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes         * more detail is lost in the resulting image and thus less precision for color selection.
658c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes         *
659c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes         * @param area the number of pixels that the intemediary scaled down Bitmap should cover,
660c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes         *             or any value <= 0 to disable resizing.
6617aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes         */
662c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        @NonNull
663c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        public Builder resizeBitmapArea(final int area) {
664c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes            mResizeArea = area;
665c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes            mResizeMaxDimension = -1;
6667aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes            return this;
6677aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes        }
6687aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes
6697aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes        /**
6702407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes         * Clear all added filters. This includes any default filters added automatically by
6712407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes         * {@link Palette}.
6722407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes         */
673c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        @NonNull
6742407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes        public Builder clearFilters() {
6752407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes            mFilters.clear();
6762407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes            return this;
6772407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes        }
6782407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes
6792407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes        /**
6803f5d16ca39def4a1a15097b243d3811b5551edc4Chris Banes         * Add a filter to be able to have fine grained control over which colors are
6812407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes         * allowed in the resulting palette.
6822407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes         *
6832407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes         * @param filter filter to add.
6842407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes         */
685c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        @NonNull
6862407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes        public Builder addFilter(Filter filter) {
6872407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes            if (filter != null) {
6882407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes                mFilters.add(filter);
6892407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes            }
6902407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes            return this;
6912407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes        }
6922407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes
6932407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes        /**
694ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes         * Set a region of the bitmap to be used exclusively when calculating the palette.
695ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes         * <p>This only works when the original input is a {@link Bitmap}.</p>
696ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes         *
697ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes         * @param left The left side of the rectangle used for the region.
698ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes         * @param top The top of the rectangle used for the region.
699ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes         * @param right The right side of the rectangle used for the region.
700ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes         * @param bottom The bottom of the rectangle used for the region.
701ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes         */
702c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        @NonNull
703ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes        public Builder setRegion(int left, int top, int right, int bottom) {
704ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes            if (mBitmap != null) {
705ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes                if (mRegion == null) mRegion = new Rect();
706ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes                // Set the Rect to be initially the whole Bitmap
707ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes                mRegion.set(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
708ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes                // Now just get the intersection with the region
709ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes                if (!mRegion.intersect(left, top, right, bottom)) {
710ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes                    throw new IllegalArgumentException("The given region must intersect with "
711ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes                            + "the Bitmap's dimensions.");
712ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes                }
713ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes            }
714ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes            return this;
715ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes        }
716ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes
717ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes        /**
718ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes         * Clear any previously region set via {@link #setRegion(int, int, int, int)}.
719ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes         */
720c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        @NonNull
721ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes        public Builder clearRegion() {
722ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes            mRegion = null;
723ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes            return this;
724ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes        }
725ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes
726ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes        /**
727c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes         * Add a target profile to be generated in the palette.
728c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes         *
729c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes         * <p>You can retrieve the result via {@link Palette#getSwatchForTarget(Target)}.</p>
730c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes         */
731c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        @NonNull
732c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        public Builder addTarget(@NonNull final Target target) {
733c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes            if (!mTargets.contains(target)) {
734c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes                mTargets.add(target);
735c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes            }
736c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes            return this;
737c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        }
738c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes
739c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        /**
740c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes         * Clear all added targets. This includes any default targets added automatically by
741c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes         * {@link Palette}.
742c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes         */
743c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        @NonNull
744c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        public Builder clearTargets() {
745c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes            if (mTargets != null) {
746c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes                mTargets.clear();
747c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes            }
748c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes            return this;
749c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        }
750c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes
751c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        /**
7527aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes         * Generate and return the {@link Palette} synchronously.
7537aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes         */
754c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        @NonNull
7557aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes        public Palette generate() {
7567aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes            final TimingLogger logger = LOG_TIMINGS
7577aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes                    ? new TimingLogger(LOG_TAG, "Generation")
7587aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes                    : null;
7597aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes
7607aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes            List<Swatch> swatches;
7617aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes
7627aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes            if (mBitmap != null) {
763c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes                // We have a Bitmap so we need to use quantization to reduce the number of colors
7647aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes
765c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes                // First we'll scale down the bitmap if needed
766c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes                final Bitmap bitmap = scaleBitmapDown(mBitmap);
7677aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes
7687aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes                if (logger != null) {
7697aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes                    logger.addSplit("Processed Bitmap");
7707aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes                }
7717aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes
772ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes                final Rect region = mRegion;
773ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes                if (bitmap != mBitmap && region != null) {
774ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes                    // If we have a scaled bitmap and a selected region, we need to scale down the
775ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes                    // region to match the new scale
7768c021e5b9750e2fd1b728a2a5578af66a46d4a28Chris Banes                    final double scale = bitmap.getWidth() / (double) mBitmap.getWidth();
777ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes                    region.left = (int) Math.floor(region.left * scale);
778ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes                    region.top = (int) Math.floor(region.top * scale);
7798c021e5b9750e2fd1b728a2a5578af66a46d4a28Chris Banes                    region.right = Math.min((int) Math.ceil(region.right * scale),
7808c021e5b9750e2fd1b728a2a5578af66a46d4a28Chris Banes                            bitmap.getWidth());
7818c021e5b9750e2fd1b728a2a5578af66a46d4a28Chris Banes                    region.bottom = Math.min((int) Math.ceil(region.bottom * scale),
7828c021e5b9750e2fd1b728a2a5578af66a46d4a28Chris Banes                            bitmap.getHeight());
783ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes                }
7842407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes
785ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes                // Now generate a quantizer from the Bitmap
786ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes                final ColorCutQuantizer quantizer = new ColorCutQuantizer(
787a79b35d1ce6b82a2e3437bfb45754e6257eaea87Chris Banes                        getPixelsFromBitmap(bitmap),
788ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes                        mMaxColors,
7892407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes                        mFilters.isEmpty() ? null : mFilters.toArray(new Filter[mFilters.size()]));
7907aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes
7917aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes                // If created a new bitmap, recycle it
792ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes                if (bitmap != mBitmap) {
793ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes                    bitmap.recycle();
7947aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes                }
795c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes
7967aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes                swatches = quantizer.getQuantizedColors();
7977aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes
7987aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes                if (logger != null) {
7997aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes                    logger.addSplit("Color quantization completed");
8007aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes                }
8017aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes            } else {
8027aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes                // Else we're using the provided swatches
8037aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes                swatches = mSwatches;
8047aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes            }
8057aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes
8067aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes            // Now create a Palette instance
807c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes            final Palette p = new Palette(swatches, mTargets);
808c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes            // And make it generate itself
809c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes            p.generate();
8107aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes
8117aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes            if (logger != null) {
8127aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes                logger.addSplit("Created Palette");
8137aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes                logger.dumpToLog();
8147aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes            }
8157aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes
8167aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes            return p;
8177aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes        }
8187aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes
8197aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes        /**
8207aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes         * Generate the {@link Palette} asynchronously. The provided listener's
8217aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes         * {@link PaletteAsyncListener#onGenerated} method will be called with the palette when
8227aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes         * generated.
8237aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes         */
824c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        @NonNull
8257aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes        public AsyncTask<Bitmap, Void, Palette> generate(final PaletteAsyncListener listener) {
8267aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes            if (listener == null) {
8277aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes                throw new IllegalArgumentException("listener can not be null");
8287aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes            }
8297aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes
8307aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes            return AsyncTaskCompat.executeParallel(
8317aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes                    new AsyncTask<Bitmap, Void, Palette>() {
8327aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes                        @Override
8337aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes                        protected Palette doInBackground(Bitmap... params) {
8348f6ea77d463e9bb5cf70aaa4339316d13ac4a36cChris Banes                            try {
8358f6ea77d463e9bb5cf70aaa4339316d13ac4a36cChris Banes                                return generate();
8368f6ea77d463e9bb5cf70aaa4339316d13ac4a36cChris Banes                            } catch (Exception e) {
8378f6ea77d463e9bb5cf70aaa4339316d13ac4a36cChris Banes                                Log.e(LOG_TAG, "Exception thrown during async generate", e);
8388f6ea77d463e9bb5cf70aaa4339316d13ac4a36cChris Banes                                return null;
8398f6ea77d463e9bb5cf70aaa4339316d13ac4a36cChris Banes                            }
8407aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes                        }
8417aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes
8427aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes                        @Override
8437aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes                        protected void onPostExecute(Palette colorExtractor) {
8447aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes                            listener.onGenerated(colorExtractor);
8457aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes                        }
8467aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes                    }, mBitmap);
8477aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes        }
848ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes
849a79b35d1ce6b82a2e3437bfb45754e6257eaea87Chris Banes        private int[] getPixelsFromBitmap(Bitmap bitmap) {
850ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes            final int bitmapWidth = bitmap.getWidth();
851ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes            final int bitmapHeight = bitmap.getHeight();
852ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes            final int[] pixels = new int[bitmapWidth * bitmapHeight];
853ec183966232468c02440a5d81764c3c1a076e217Chris Banes            bitmap.getPixels(pixels, 0, bitmapWidth, 0, 0, bitmapWidth, bitmapHeight);
854ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes
855ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes            if (mRegion == null) {
856ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes                // If we don't have a region, return all of the pixels
857ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes                return pixels;
858ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes            } else {
859ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes                // If we do have a region, lets create a subset array containing only the region's
860ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes                // pixels
861ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes                final int regionWidth = mRegion.width();
862ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes                final int regionHeight = mRegion.height();
863ec183966232468c02440a5d81764c3c1a076e217Chris Banes                // pixels contains all of the pixels, so we need to iterate through each row and
864ec183966232468c02440a5d81764c3c1a076e217Chris Banes                // copy the regions pixels into a new smaller array
865d27ec041be0a4251176e5daaea526f231b03f38eChris Banes                final int[] subsetPixels = new int[regionWidth * regionHeight];
866d27ec041be0a4251176e5daaea526f231b03f38eChris Banes                for (int row = 0; row < regionHeight; row++) {
867d27ec041be0a4251176e5daaea526f231b03f38eChris Banes                    System.arraycopy(pixels, ((row + mRegion.top) * bitmapWidth) + mRegion.left,
868ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes                            subsetPixels, row * regionWidth, regionWidth);
869ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes                }
870ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes                return subsetPixels;
871ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes            }
872ce4bafdad56c6c7ad49d6a97674d7c8891ab7b45Chris Banes        }
8737aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes
8747aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes        /**
875c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes         * Scale the bitmap down as needed.
8767aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes         */
877c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes        private Bitmap scaleBitmapDown(final Bitmap bitmap) {
878c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes            double scaleRatio = -1;
8797aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes
880c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes            if (mResizeArea > 0) {
881c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes                final int bitmapArea = bitmap.getWidth() * bitmap.getHeight();
882c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes                if (bitmapArea > mResizeArea) {
883c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes                    scaleRatio = mResizeArea / (double) bitmapArea;
884c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes                }
885c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes            } else if (mResizeMaxDimension > 0) {
886c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes                final int maxDimension = Math.max(bitmap.getWidth(), bitmap.getHeight());
887c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes                if (maxDimension > mResizeMaxDimension) {
888c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes                    scaleRatio = mResizeMaxDimension / (double) maxDimension;
889c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes                }
890c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes            }
8917aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes
892c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes            if (scaleRatio <= 0) {
893c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes                // Scaling has been disabled or not needed so just return the Bitmap
894c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes                return bitmap;
895c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes            }
8967aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes
897c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes            return Bitmap.createScaledBitmap(bitmap,
898c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes                    (int) Math.ceil(bitmap.getWidth() * scaleRatio),
899c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes                    (int) Math.ceil(bitmap.getHeight() * scaleRatio),
900c5f1ad7670d601b806b2d1e5e4fa1ae7efd7fc69Chris Banes                    false);
9017aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes        }
9027aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes    }
9037aa3688908034b80e3e4a86b62f4cf2fc9ecc3a5Chris Banes
9042407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes    /**
9052407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes     * A Filter provides a mechanism for exercising fine-grained control over which colors
9062407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes     * are valid within a resulting {@link Palette}.
9072407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes     */
9082407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes    public interface Filter {
9092407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes        /**
9102407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes         * Hook to allow clients to be able filter colors from resulting palette.
9112407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes         *
9122407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes         * @param rgb the color in RGB888.
9132407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes         * @param hsl HSL representation of the color.
9142407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes         *
9152407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes         * @return true if the color is allowed, false if not.
9162407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes         *
9172407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes         * @see Builder#addFilter(Filter)
9182407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes         */
9192407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes        boolean isAllowed(int rgb, float[] hsl);
9202407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes    }
9212407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes
9222407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes    /**
9232407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes     * The default filter.
9242407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes     */
9252407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes    private static final Filter DEFAULT_FILTER = new Filter() {
9262407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes        private static final float BLACK_MAX_LIGHTNESS = 0.05f;
9272407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes        private static final float WHITE_MIN_LIGHTNESS = 0.95f;
9282407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes
9292407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes        @Override
9302407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes        public boolean isAllowed(int rgb, float[] hsl) {
9312407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes            return !isWhite(hsl) && !isBlack(hsl) && !isNearRedILine(hsl);
9322407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes        }
9332407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes
9342407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes        /**
9352407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes         * @return true if the color represents a color which is close to black.
9362407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes         */
9372407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes        private boolean isBlack(float[] hslColor) {
9382407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes            return hslColor[2] <= BLACK_MAX_LIGHTNESS;
9392407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes        }
9402407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes
9412407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes        /**
9422407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes         * @return true if the color represents a color which is close to white.
9432407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes         */
9442407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes        private boolean isWhite(float[] hslColor) {
9452407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes            return hslColor[2] >= WHITE_MIN_LIGHTNESS;
9462407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes        }
9472407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes
9482407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes        /**
9492407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes         * @return true if the color lies close to the red side of the I line.
9502407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes         */
9512407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes        private boolean isNearRedILine(float[] hslColor) {
9522407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes            return hslColor[0] >= 10f && hslColor[0] <= 37f && hslColor[1] <= 0.82f;
9532407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes        }
9542407aa65d62cfc1ec6ffffae726dec45021dd9a2Chris Banes    };
955059178a8c7cc80848e5594a9287be91bd924831aChris Banes}
956