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;
21059178a8c7cc80848e5594a9287be91bd924831aChris Banesimport android.os.AsyncTask;
22c6cdc41397bc3ad2c936069af6d448f242790513Chris Banesimport android.support.v4.os.AsyncTaskCompat;
23059178a8c7cc80848e5594a9287be91bd924831aChris Banes
24c6cdc41397bc3ad2c936069af6d448f242790513Chris Banesimport java.util.Arrays;
25059178a8c7cc80848e5594a9287be91bd924831aChris Banesimport java.util.Collections;
26059178a8c7cc80848e5594a9287be91bd924831aChris Banesimport java.util.List;
27059178a8c7cc80848e5594a9287be91bd924831aChris Banes
28059178a8c7cc80848e5594a9287be91bd924831aChris Banes/**
29059178a8c7cc80848e5594a9287be91bd924831aChris Banes * A helper class to extract prominent colors from an image.
30059178a8c7cc80848e5594a9287be91bd924831aChris Banes * <p>
31059178a8c7cc80848e5594a9287be91bd924831aChris Banes * A number of colors with different profiles are extracted from the image:
32059178a8c7cc80848e5594a9287be91bd924831aChris Banes * <ul>
33059178a8c7cc80848e5594a9287be91bd924831aChris Banes *     <li>Vibrant</li>
34059178a8c7cc80848e5594a9287be91bd924831aChris Banes *     <li>Vibrant Dark</li>
35059178a8c7cc80848e5594a9287be91bd924831aChris Banes *     <li>Vibrant Light</li>
36059178a8c7cc80848e5594a9287be91bd924831aChris Banes *     <li>Muted</li>
37059178a8c7cc80848e5594a9287be91bd924831aChris Banes *     <li>Muted Dark</li>
38059178a8c7cc80848e5594a9287be91bd924831aChris Banes *     <li>Muted Light</li>
39059178a8c7cc80848e5594a9287be91bd924831aChris Banes * </ul>
40059178a8c7cc80848e5594a9287be91bd924831aChris Banes * These can be retrieved from the appropriate getter method.
41059178a8c7cc80848e5594a9287be91bd924831aChris Banes *
42059178a8c7cc80848e5594a9287be91bd924831aChris Banes * <p>
43059178a8c7cc80848e5594a9287be91bd924831aChris Banes * Instances can be created with the synchronous factory methods {@link #generate(Bitmap)} and
44059178a8c7cc80848e5594a9287be91bd924831aChris Banes * {@link #generate(Bitmap, int)}.
45059178a8c7cc80848e5594a9287be91bd924831aChris Banes * <p>
46059178a8c7cc80848e5594a9287be91bd924831aChris Banes * These should be called on a background thread, ideally the one in
47059178a8c7cc80848e5594a9287be91bd924831aChris Banes * which you load your images on. Sometimes that is not possible, so asynchronous factory methods
48059178a8c7cc80848e5594a9287be91bd924831aChris Banes * have also been provided: {@link #generateAsync(Bitmap, PaletteAsyncListener)} and
49059178a8c7cc80848e5594a9287be91bd924831aChris Banes * {@link #generateAsync(Bitmap, int, PaletteAsyncListener)}. These can be used as so:
50059178a8c7cc80848e5594a9287be91bd924831aChris Banes *
51059178a8c7cc80848e5594a9287be91bd924831aChris Banes * <pre>
52059178a8c7cc80848e5594a9287be91bd924831aChris Banes * Palette.generateAsync(bitmap, new Palette.PaletteAsyncListener() {
53059178a8c7cc80848e5594a9287be91bd924831aChris Banes *     public void onGenerated(Palette palette) {
54059178a8c7cc80848e5594a9287be91bd924831aChris Banes *         // Do something with colors...
55059178a8c7cc80848e5594a9287be91bd924831aChris Banes *     }
56059178a8c7cc80848e5594a9287be91bd924831aChris Banes * });
57059178a8c7cc80848e5594a9287be91bd924831aChris Banes * </pre>
58059178a8c7cc80848e5594a9287be91bd924831aChris Banes */
59059178a8c7cc80848e5594a9287be91bd924831aChris Banespublic final class Palette {
60059178a8c7cc80848e5594a9287be91bd924831aChris Banes
61059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
62059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * Listener to be used with {@link #generateAsync(Bitmap, PaletteAsyncListener)} or
63059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * {@link #generateAsync(Bitmap, int, PaletteAsyncListener)}
64059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
65059178a8c7cc80848e5594a9287be91bd924831aChris Banes    public interface PaletteAsyncListener {
66059178a8c7cc80848e5594a9287be91bd924831aChris Banes
67059178a8c7cc80848e5594a9287be91bd924831aChris Banes        /**
68059178a8c7cc80848e5594a9287be91bd924831aChris Banes         * Called when the {@link Palette} has been generated.
69059178a8c7cc80848e5594a9287be91bd924831aChris Banes         */
70059178a8c7cc80848e5594a9287be91bd924831aChris Banes        void onGenerated(Palette palette);
71059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
72059178a8c7cc80848e5594a9287be91bd924831aChris Banes
73059178a8c7cc80848e5594a9287be91bd924831aChris Banes    private static final int CALCULATE_BITMAP_MIN_DIMENSION = 100;
74059178a8c7cc80848e5594a9287be91bd924831aChris Banes    private static final int DEFAULT_CALCULATE_NUMBER_COLORS = 16;
75059178a8c7cc80848e5594a9287be91bd924831aChris Banes
76059178a8c7cc80848e5594a9287be91bd924831aChris Banes    private static final float TARGET_DARK_LUMA = 0.26f;
77059178a8c7cc80848e5594a9287be91bd924831aChris Banes    private static final float MAX_DARK_LUMA = 0.45f;
78059178a8c7cc80848e5594a9287be91bd924831aChris Banes
79059178a8c7cc80848e5594a9287be91bd924831aChris Banes    private static final float MIN_LIGHT_LUMA = 0.55f;
80059178a8c7cc80848e5594a9287be91bd924831aChris Banes    private static final float TARGET_LIGHT_LUMA = 0.74f;
81059178a8c7cc80848e5594a9287be91bd924831aChris Banes
82059178a8c7cc80848e5594a9287be91bd924831aChris Banes    private static final float MIN_NORMAL_LUMA = 0.3f;
83059178a8c7cc80848e5594a9287be91bd924831aChris Banes    private static final float TARGET_NORMAL_LUMA = 0.5f;
84059178a8c7cc80848e5594a9287be91bd924831aChris Banes    private static final float MAX_NORMAL_LUMA = 0.7f;
85059178a8c7cc80848e5594a9287be91bd924831aChris Banes
86059178a8c7cc80848e5594a9287be91bd924831aChris Banes    private static final float TARGET_MUTED_SATURATION = 0.3f;
87059178a8c7cc80848e5594a9287be91bd924831aChris Banes    private static final float MAX_MUTED_SATURATION = 0.4f;
88059178a8c7cc80848e5594a9287be91bd924831aChris Banes
89059178a8c7cc80848e5594a9287be91bd924831aChris Banes    private static final float TARGET_VIBRANT_SATURATION = 1f;
90059178a8c7cc80848e5594a9287be91bd924831aChris Banes    private static final float MIN_VIBRANT_SATURATION = 0.35f;
91059178a8c7cc80848e5594a9287be91bd924831aChris Banes
92c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    private static final float WEIGHT_SATURATION = 3f;
93c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    private static final float WEIGHT_LUMA = 6f;
94c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    private static final float WEIGHT_POPULATION = 1f;
95c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
96f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes    private static final float MIN_CONTRAST_TITLE_TEXT = 3.0f;
97f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes    private static final float MIN_CONTRAST_BODY_TEXT = 4.5f;
98f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes
99c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    private final List<Swatch> mSwatches;
100059178a8c7cc80848e5594a9287be91bd924831aChris Banes    private final int mHighestPopulation;
101059178a8c7cc80848e5594a9287be91bd924831aChris Banes
102c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    private Swatch mVibrantSwatch;
103c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    private Swatch mMutedSwatch;
104059178a8c7cc80848e5594a9287be91bd924831aChris Banes
105c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    private Swatch mDarkVibrantSwatch;
106c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    private Swatch mDarkMutedSwatch;
107059178a8c7cc80848e5594a9287be91bd924831aChris Banes
108c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    private Swatch mLightVibrantSwatch;
109c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    private Swatch mLightMutedColor;
110059178a8c7cc80848e5594a9287be91bd924831aChris Banes
111059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
112059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * Generate a {@link Palette} from a {@link Bitmap} using the default number of colors.
113059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
114059178a8c7cc80848e5594a9287be91bd924831aChris Banes    public static Palette generate(Bitmap bitmap) {
115059178a8c7cc80848e5594a9287be91bd924831aChris Banes        return generate(bitmap, DEFAULT_CALCULATE_NUMBER_COLORS);
116059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
117059178a8c7cc80848e5594a9287be91bd924831aChris Banes
118059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
119059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * Generate a {@link Palette} from a {@link Bitmap} using the specified {@code numColors}.
120059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * Good values for {@code numColors} depend on the source image type.
121059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * For landscapes, a good values are in the range 12-16. For images which are largely made up
122059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * of people's faces then this value should be increased to 24-32.
123059178a8c7cc80848e5594a9287be91bd924831aChris Banes     *
124059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * @param numColors The maximum number of colors in the generated palette. Increasing this
125059178a8c7cc80848e5594a9287be91bd924831aChris Banes     *                  number will increase the time needed to compute the values.
126059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
127059178a8c7cc80848e5594a9287be91bd924831aChris Banes    public static Palette generate(Bitmap bitmap, int numColors) {
128c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        checkBitmapParam(bitmap);
129c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        checkNumberColorsParam(numColors);
130059178a8c7cc80848e5594a9287be91bd924831aChris Banes
131059178a8c7cc80848e5594a9287be91bd924831aChris Banes        // First we'll scale down the bitmap so it's shortest dimension is 100px
132059178a8c7cc80848e5594a9287be91bd924831aChris Banes        final Bitmap scaledBitmap = scaleBitmapDown(bitmap);
133059178a8c7cc80848e5594a9287be91bd924831aChris Banes
134059178a8c7cc80848e5594a9287be91bd924831aChris Banes        // Now generate a quantizer from the Bitmap
135059178a8c7cc80848e5594a9287be91bd924831aChris Banes        ColorCutQuantizer quantizer = ColorCutQuantizer.fromBitmap(scaledBitmap, numColors);
136059178a8c7cc80848e5594a9287be91bd924831aChris Banes
137059178a8c7cc80848e5594a9287be91bd924831aChris Banes        // If created a new bitmap, recycle it
138059178a8c7cc80848e5594a9287be91bd924831aChris Banes        if (scaledBitmap != bitmap) {
139059178a8c7cc80848e5594a9287be91bd924831aChris Banes            scaledBitmap.recycle();
140059178a8c7cc80848e5594a9287be91bd924831aChris Banes        }
141059178a8c7cc80848e5594a9287be91bd924831aChris Banes
142059178a8c7cc80848e5594a9287be91bd924831aChris Banes        // Now return a ColorExtractor instance
143059178a8c7cc80848e5594a9287be91bd924831aChris Banes        return new Palette(quantizer.getQuantizedColors());
144059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
145059178a8c7cc80848e5594a9287be91bd924831aChris Banes
146059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
147059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * Generate a {@link Palette} asynchronously. {@link PaletteAsyncListener#onGenerated(Palette)}
148059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * will be called with the created instance. The resulting {@link Palette} is the same as
149059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * what would be created by calling {@link #generate(Bitmap)}.
150059178a8c7cc80848e5594a9287be91bd924831aChris Banes     *
151059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * @param listener Listener to be invoked when the {@link Palette} has been generated.
152059178a8c7cc80848e5594a9287be91bd924831aChris Banes     *
153059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * @return the {@link android.os.AsyncTask} used to asynchronously generate the instance.
154059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
155c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    public static AsyncTask<Bitmap, Void, Palette> generateAsync(
156059178a8c7cc80848e5594a9287be91bd924831aChris Banes            Bitmap bitmap, PaletteAsyncListener listener) {
157059178a8c7cc80848e5594a9287be91bd924831aChris Banes        return generateAsync(bitmap, DEFAULT_CALCULATE_NUMBER_COLORS, listener);
158059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
159059178a8c7cc80848e5594a9287be91bd924831aChris Banes
160059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
161059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * Generate a {@link Palette} asynchronously. {@link PaletteAsyncListener#onGenerated(Palette)}
162059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * will be called with the created instance. The resulting {@link Palette} is the same as what
163059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * would be created by calling {@link #generate(Bitmap, int)}.
164059178a8c7cc80848e5594a9287be91bd924831aChris Banes     *
165059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * @param listener Listener to be invoked when the {@link Palette} has been generated.
166059178a8c7cc80848e5594a9287be91bd924831aChris Banes     *
167059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * @return the {@link android.os.AsyncTask} used to asynchronously generate the instance.
168059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
169c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    public static AsyncTask<Bitmap, Void, Palette> generateAsync(
170059178a8c7cc80848e5594a9287be91bd924831aChris Banes            final Bitmap bitmap, final int numColors, final PaletteAsyncListener listener) {
171c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        checkBitmapParam(bitmap);
172c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        checkNumberColorsParam(numColors);
173c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        checkAsyncListenerParam(listener);
174c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
175c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        return AsyncTaskCompat.executeParallel(
176c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                new AsyncTask<Bitmap, Void, Palette>() {
177c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                    @Override
178c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                    protected Palette doInBackground(Bitmap... params) {
179c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                        return generate(params[0], numColors);
180c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                    }
181c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
182c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                    @Override
183c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                    protected void onPostExecute(Palette colorExtractor) {
184c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                        listener.onGenerated(colorExtractor);
185c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                    }
186c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                }, bitmap);
187059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
188059178a8c7cc80848e5594a9287be91bd924831aChris Banes
189c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    private Palette(List<Swatch> swatches) {
190c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        mSwatches = swatches;
191059178a8c7cc80848e5594a9287be91bd924831aChris Banes        mHighestPopulation = findMaxPopulation();
192059178a8c7cc80848e5594a9287be91bd924831aChris Banes
193c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        mVibrantSwatch = findColor(TARGET_NORMAL_LUMA, MIN_NORMAL_LUMA, MAX_NORMAL_LUMA,
194059178a8c7cc80848e5594a9287be91bd924831aChris Banes                TARGET_VIBRANT_SATURATION, MIN_VIBRANT_SATURATION, 1f);
195059178a8c7cc80848e5594a9287be91bd924831aChris Banes
196c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        mLightVibrantSwatch = findColor(TARGET_LIGHT_LUMA, MIN_LIGHT_LUMA, 1f,
197059178a8c7cc80848e5594a9287be91bd924831aChris Banes                TARGET_VIBRANT_SATURATION, MIN_VIBRANT_SATURATION, 1f);
198059178a8c7cc80848e5594a9287be91bd924831aChris Banes
199c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        mDarkVibrantSwatch = findColor(TARGET_DARK_LUMA, 0f, MAX_DARK_LUMA,
200059178a8c7cc80848e5594a9287be91bd924831aChris Banes                TARGET_VIBRANT_SATURATION, MIN_VIBRANT_SATURATION, 1f);
201059178a8c7cc80848e5594a9287be91bd924831aChris Banes
202c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        mMutedSwatch = findColor(TARGET_NORMAL_LUMA, MIN_NORMAL_LUMA, MAX_NORMAL_LUMA,
203059178a8c7cc80848e5594a9287be91bd924831aChris Banes                TARGET_MUTED_SATURATION, 0f, MAX_MUTED_SATURATION);
204059178a8c7cc80848e5594a9287be91bd924831aChris Banes
205059178a8c7cc80848e5594a9287be91bd924831aChris Banes        mLightMutedColor = findColor(TARGET_LIGHT_LUMA, MIN_LIGHT_LUMA, 1f,
206059178a8c7cc80848e5594a9287be91bd924831aChris Banes                TARGET_MUTED_SATURATION, 0f, MAX_MUTED_SATURATION);
207059178a8c7cc80848e5594a9287be91bd924831aChris Banes
208c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        mDarkMutedSwatch = findColor(TARGET_DARK_LUMA, 0f, MAX_DARK_LUMA,
209059178a8c7cc80848e5594a9287be91bd924831aChris Banes                TARGET_MUTED_SATURATION, 0f, MAX_MUTED_SATURATION);
210059178a8c7cc80848e5594a9287be91bd924831aChris Banes
211059178a8c7cc80848e5594a9287be91bd924831aChris Banes        // Now try and generate any missing colors
212c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        generateEmptySwatches();
213059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
214059178a8c7cc80848e5594a9287be91bd924831aChris Banes
215059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
216c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Returns all of the swatches which make up the palette.
217059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
218c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    public List<Swatch> getSwatches() {
219c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        return Collections.unmodifiableList(mSwatches);
220059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
221059178a8c7cc80848e5594a9287be91bd924831aChris Banes
222059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
223c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Returns the most vibrant swatch in the palette. Might be null.
224059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
225c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    public Swatch getVibrantSwatch() {
226c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        return mVibrantSwatch;
227059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
228059178a8c7cc80848e5594a9287be91bd924831aChris Banes
229059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
230c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Returns a light and vibrant swatch from the palette. Might be null.
231059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
232c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    public Swatch getLightVibrantSwatch() {
233c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        return mLightVibrantSwatch;
234059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
235059178a8c7cc80848e5594a9287be91bd924831aChris Banes
236059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
237c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Returns a dark and vibrant swatch from the palette. Might be null.
238059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
239c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    public Swatch getDarkVibrantSwatch() {
240c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        return mDarkVibrantSwatch;
241059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
242059178a8c7cc80848e5594a9287be91bd924831aChris Banes
243059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
244c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Returns a muted swatch from the palette. Might be null.
245059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
246c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    public Swatch getMutedSwatch() {
247c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        return mMutedSwatch;
248059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
249059178a8c7cc80848e5594a9287be91bd924831aChris Banes
250059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
251c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Returns a muted and light swatch from the palette. Might be null.
252059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
253c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    public Swatch getLightMutedSwatch() {
254059178a8c7cc80848e5594a9287be91bd924831aChris Banes        return mLightMutedColor;
255059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
256059178a8c7cc80848e5594a9287be91bd924831aChris Banes
257059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
258c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Returns a muted and dark swatch from the palette. Might be null.
259c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     */
260c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    public Swatch getDarkMutedSwatch() {
261c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        return mDarkMutedSwatch;
262c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    }
263c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
264c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    /**
265c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Returns the most vibrant color in the palette as an RGB packed int.
266c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     *
267c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * @param defaultColor value to return if the swatch isn't available
268c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     */
269c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    public int getVibrantColor(int defaultColor) {
270c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        return mVibrantSwatch != null ? mVibrantSwatch.getRgb() : defaultColor;
271c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    }
272c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
273c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    /**
274c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Returns a light and vibrant color from the palette as an RGB packed int.
275c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     *
276c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * @param defaultColor value to return if the swatch isn't available
277c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     */
278c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    public int getLightVibrantColor(int defaultColor) {
279c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        return mLightVibrantSwatch != null ? mLightVibrantSwatch.getRgb() : defaultColor;
280c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    }
281c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
282c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    /**
283c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Returns a dark and vibrant color from the palette as an RGB packed int.
284c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     *
285c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * @param defaultColor value to return if the swatch isn't available
286c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     */
287c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    public int getDarkVibrantColor(int defaultColor) {
288c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        return mDarkVibrantSwatch != null ? mDarkVibrantSwatch.getRgb() : defaultColor;
289c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    }
290c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
291c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    /**
292c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Returns a muted color from the palette as an RGB packed int.
293c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     *
294c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * @param defaultColor value to return if the swatch isn't available
295c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     */
296c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    public int getMutedColor(int defaultColor) {
297c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        return mMutedSwatch != null ? mMutedSwatch.getRgb() : defaultColor;
298c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    }
299c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
300c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    /**
301c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Returns a muted and light color from the palette as an RGB packed int.
302c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     *
303c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * @param defaultColor value to return if the swatch isn't available
304c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     */
305c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    public int getLightMutedColor(int defaultColor) {
306c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        return mLightMutedColor != null ? mLightMutedColor.getRgb() : defaultColor;
307c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    }
308c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
309c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    /**
310c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Returns a muted and dark color from the palette as an RGB packed int.
311c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     *
312c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * @param defaultColor value to return if the swatch isn't available
313059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
314c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    public int getDarkMutedColor(int defaultColor) {
315c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        return mDarkMutedSwatch != null ? mDarkMutedSwatch.getRgb() : defaultColor;
316059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
317059178a8c7cc80848e5594a9287be91bd924831aChris Banes
318059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
319c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * @return true if we have already selected {@code swatch}
320059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
321c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    private boolean isAlreadySelected(Swatch swatch) {
322c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        return mVibrantSwatch == swatch || mDarkVibrantSwatch == swatch ||
323c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                mLightVibrantSwatch == swatch || mMutedSwatch == swatch ||
324c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                mDarkMutedSwatch == swatch || mLightMutedColor == swatch;
325059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
326059178a8c7cc80848e5594a9287be91bd924831aChris Banes
327c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    private Swatch findColor(float targetLuma, float minLuma, float maxLuma,
328c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                             float targetSaturation, float minSaturation, float maxSaturation) {
329c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        Swatch max = null;
330059178a8c7cc80848e5594a9287be91bd924831aChris Banes        float maxValue = 0f;
331059178a8c7cc80848e5594a9287be91bd924831aChris Banes
332c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        for (Swatch swatch : mSwatches) {
333c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            final float sat = swatch.getHsl()[1];
334c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            final float luma = swatch.getHsl()[2];
335059178a8c7cc80848e5594a9287be91bd924831aChris Banes
336059178a8c7cc80848e5594a9287be91bd924831aChris Banes            if (sat >= minSaturation && sat <= maxSaturation &&
337059178a8c7cc80848e5594a9287be91bd924831aChris Banes                    luma >= minLuma && luma <= maxLuma &&
338c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                    !isAlreadySelected(swatch)) {
339059178a8c7cc80848e5594a9287be91bd924831aChris Banes                float thisValue = createComparisonValue(sat, targetSaturation, luma, targetLuma,
340c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                        swatch.getPopulation(), mHighestPopulation);
341059178a8c7cc80848e5594a9287be91bd924831aChris Banes                if (max == null || thisValue > maxValue) {
342c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                    max = swatch;
343059178a8c7cc80848e5594a9287be91bd924831aChris Banes                    maxValue = thisValue;
344059178a8c7cc80848e5594a9287be91bd924831aChris Banes                }
345059178a8c7cc80848e5594a9287be91bd924831aChris Banes            }
346059178a8c7cc80848e5594a9287be91bd924831aChris Banes        }
347059178a8c7cc80848e5594a9287be91bd924831aChris Banes
348059178a8c7cc80848e5594a9287be91bd924831aChris Banes        return max;
349059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
350059178a8c7cc80848e5594a9287be91bd924831aChris Banes
351059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
352c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Try and generate any missing swatches from the swatches we did find.
353059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
354c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    private void generateEmptySwatches() {
355c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        if (mVibrantSwatch == null) {
356059178a8c7cc80848e5594a9287be91bd924831aChris Banes            // If we do not have a vibrant color...
357c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            if (mDarkVibrantSwatch != null) {
358059178a8c7cc80848e5594a9287be91bd924831aChris Banes                // ...but we do have a dark vibrant, generate the value by modifying the luma
359c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                final float[] newHsl = copyHslValues(mDarkVibrantSwatch);
360059178a8c7cc80848e5594a9287be91bd924831aChris Banes                newHsl[2] = TARGET_NORMAL_LUMA;
361c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                mVibrantSwatch = new Swatch(ColorUtils.HSLtoRGB(newHsl), 0);
362059178a8c7cc80848e5594a9287be91bd924831aChris Banes            }
363059178a8c7cc80848e5594a9287be91bd924831aChris Banes        }
364059178a8c7cc80848e5594a9287be91bd924831aChris Banes
365c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        if (mDarkVibrantSwatch == null) {
366059178a8c7cc80848e5594a9287be91bd924831aChris Banes            // If we do not have a dark vibrant color...
367c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            if (mVibrantSwatch != null) {
368059178a8c7cc80848e5594a9287be91bd924831aChris Banes                // ...but we do have a vibrant, generate the value by modifying the luma
369c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                final float[] newHsl = copyHslValues(mVibrantSwatch);
370059178a8c7cc80848e5594a9287be91bd924831aChris Banes                newHsl[2] = TARGET_DARK_LUMA;
371c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                mDarkVibrantSwatch = new Swatch(ColorUtils.HSLtoRGB(newHsl), 0);
372059178a8c7cc80848e5594a9287be91bd924831aChris Banes            }
373059178a8c7cc80848e5594a9287be91bd924831aChris Banes        }
374059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
375059178a8c7cc80848e5594a9287be91bd924831aChris Banes
376059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
377c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Find the {@link Swatch} with the highest population value and return the population.
378059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
379059178a8c7cc80848e5594a9287be91bd924831aChris Banes    private int findMaxPopulation() {
380059178a8c7cc80848e5594a9287be91bd924831aChris Banes        int population = 0;
381c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        for (Swatch swatch : mSwatches) {
382c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            population = Math.max(population, swatch.getPopulation());
383059178a8c7cc80848e5594a9287be91bd924831aChris Banes        }
384059178a8c7cc80848e5594a9287be91bd924831aChris Banes        return population;
385059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
386059178a8c7cc80848e5594a9287be91bd924831aChris Banes
387e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes    @Override
388e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes    public boolean equals(Object o) {
389e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes        if (this == o) {
390e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes            return true;
391e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes        }
392e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes        if (o == null || getClass() != o.getClass()) {
393e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes            return false;
394e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes        }
395e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes
396e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes        Palette palette = (Palette) o;
397e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes
398e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes        if (mSwatches != null ? !mSwatches.equals(palette.mSwatches) : palette.mSwatches != null) {
399e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes            return false;
400e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes        }
401e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes        if (mDarkMutedSwatch != null ? !mDarkMutedSwatch.equals(palette.mDarkMutedSwatch)
402e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes                : palette.mDarkMutedSwatch != null) {
403e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes            return false;
404e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes        }
405e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes        if (mDarkVibrantSwatch != null ? !mDarkVibrantSwatch.equals(palette.mDarkVibrantSwatch)
406e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes                : palette.mDarkVibrantSwatch != null) {
407e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes            return false;
408e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes        }
409e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes        if (mLightMutedColor != null ? !mLightMutedColor.equals(palette.mLightMutedColor)
410e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes                : palette.mLightMutedColor != null) {
411e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes            return false;
412e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes        }
413e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes        if (mLightVibrantSwatch != null ? !mLightVibrantSwatch.equals(palette.mLightVibrantSwatch)
414e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes                : palette.mLightVibrantSwatch != null) {
415e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes            return false;
416e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes        }
417e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes        if (mMutedSwatch != null ? !mMutedSwatch.equals(palette.mMutedSwatch)
418e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes                : palette.mMutedSwatch != null) {
419e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes            return false;
420e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes        }
421e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes        if (mVibrantSwatch != null ? !mVibrantSwatch.equals(palette.mVibrantSwatch)
422e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes                : palette.mVibrantSwatch != null) {
423e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes            return false;
424e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes        }
425e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes
426e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes        return true;
427e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes    }
428e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes
429e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes    @Override
430e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes    public int hashCode() {
431e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes        int result = mSwatches != null ? mSwatches.hashCode() : 0;
432e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes        result = 31 * result + (mVibrantSwatch != null ? mVibrantSwatch.hashCode() : 0);
433e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes        result = 31 * result + (mMutedSwatch != null ? mMutedSwatch.hashCode() : 0);
434e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes        result = 31 * result + (mDarkVibrantSwatch != null ? mDarkVibrantSwatch.hashCode() : 0);
435e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes        result = 31 * result + (mDarkMutedSwatch != null ? mDarkMutedSwatch.hashCode() : 0);
436e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes        result = 31 * result + (mLightVibrantSwatch != null ? mLightVibrantSwatch.hashCode() : 0);
437e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes        result = 31 * result + (mLightMutedColor != null ? mLightMutedColor.hashCode() : 0);
438e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes        return result;
439e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes    }
440e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes
441059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
442059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * Scale the bitmap down so that it's smallest dimension is
443059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * {@value #CALCULATE_BITMAP_MIN_DIMENSION}px. If {@code bitmap} is smaller than this, than it
444059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * is returned.
445059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
446059178a8c7cc80848e5594a9287be91bd924831aChris Banes    private static Bitmap scaleBitmapDown(Bitmap bitmap) {
447059178a8c7cc80848e5594a9287be91bd924831aChris Banes        final int minDimension = Math.min(bitmap.getWidth(), bitmap.getHeight());
448059178a8c7cc80848e5594a9287be91bd924831aChris Banes
449059178a8c7cc80848e5594a9287be91bd924831aChris Banes        if (minDimension <= CALCULATE_BITMAP_MIN_DIMENSION) {
450059178a8c7cc80848e5594a9287be91bd924831aChris Banes            // If the bitmap is small enough already, just return it
451059178a8c7cc80848e5594a9287be91bd924831aChris Banes            return bitmap;
452059178a8c7cc80848e5594a9287be91bd924831aChris Banes        }
453059178a8c7cc80848e5594a9287be91bd924831aChris Banes
454059178a8c7cc80848e5594a9287be91bd924831aChris Banes        final float scaleRatio = CALCULATE_BITMAP_MIN_DIMENSION / (float) minDimension;
455059178a8c7cc80848e5594a9287be91bd924831aChris Banes        return Bitmap.createScaledBitmap(bitmap,
456059178a8c7cc80848e5594a9287be91bd924831aChris Banes                Math.round(bitmap.getWidth() * scaleRatio),
457059178a8c7cc80848e5594a9287be91bd924831aChris Banes                Math.round(bitmap.getHeight() * scaleRatio),
458059178a8c7cc80848e5594a9287be91bd924831aChris Banes                false);
459059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
460059178a8c7cc80848e5594a9287be91bd924831aChris Banes
461059178a8c7cc80848e5594a9287be91bd924831aChris Banes    private static float createComparisonValue(float saturation, float targetSaturation,
462059178a8c7cc80848e5594a9287be91bd924831aChris Banes            float luma, float targetLuma,
463059178a8c7cc80848e5594a9287be91bd924831aChris Banes            int population, int highestPopulation) {
464059178a8c7cc80848e5594a9287be91bd924831aChris Banes        return weightedMean(
465c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                invertDiff(saturation, targetSaturation), WEIGHT_SATURATION,
466c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                invertDiff(luma, targetLuma), WEIGHT_LUMA,
467c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                population / (float) highestPopulation, WEIGHT_POPULATION
468059178a8c7cc80848e5594a9287be91bd924831aChris Banes        );
469059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
470059178a8c7cc80848e5594a9287be91bd924831aChris Banes
471059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
472c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Copy a {@link Swatch}'s HSL values into a new float[].
473059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
474c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    private static float[] copyHslValues(Swatch color) {
475059178a8c7cc80848e5594a9287be91bd924831aChris Banes        final float[] newHsl = new float[3];
476059178a8c7cc80848e5594a9287be91bd924831aChris Banes        System.arraycopy(color.getHsl(), 0, newHsl, 0, 3);
477059178a8c7cc80848e5594a9287be91bd924831aChris Banes        return newHsl;
478059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
479059178a8c7cc80848e5594a9287be91bd924831aChris Banes
480059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
481059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * Returns a value in the range 0-1. 1 is returned when {@code value} equals the
482059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * {@code targetValue} and then decreases as the absolute difference between {@code value} and
483059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * {@code targetValue} increases.
484059178a8c7cc80848e5594a9287be91bd924831aChris Banes     *
485059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * @param value the item's value
486059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * @param targetValue the value which we desire
487059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
488059178a8c7cc80848e5594a9287be91bd924831aChris Banes    private static float invertDiff(float value, float targetValue) {
489059178a8c7cc80848e5594a9287be91bd924831aChris Banes        return 1f - Math.abs(value - targetValue);
490059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
491059178a8c7cc80848e5594a9287be91bd924831aChris Banes
492059178a8c7cc80848e5594a9287be91bd924831aChris Banes    private static float weightedMean(float... values) {
493059178a8c7cc80848e5594a9287be91bd924831aChris Banes        float sum = 0f;
494059178a8c7cc80848e5594a9287be91bd924831aChris Banes        float sumWeight = 0f;
495059178a8c7cc80848e5594a9287be91bd924831aChris Banes
496059178a8c7cc80848e5594a9287be91bd924831aChris Banes        for (int i = 0; i < values.length; i += 2) {
497059178a8c7cc80848e5594a9287be91bd924831aChris Banes            float value = values[i];
498059178a8c7cc80848e5594a9287be91bd924831aChris Banes            float weight = values[i + 1];
499059178a8c7cc80848e5594a9287be91bd924831aChris Banes
500059178a8c7cc80848e5594a9287be91bd924831aChris Banes            sum += (value * weight);
501059178a8c7cc80848e5594a9287be91bd924831aChris Banes            sumWeight += weight;
502059178a8c7cc80848e5594a9287be91bd924831aChris Banes        }
503059178a8c7cc80848e5594a9287be91bd924831aChris Banes
504059178a8c7cc80848e5594a9287be91bd924831aChris Banes        return sum / sumWeight;
505059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
506059178a8c7cc80848e5594a9287be91bd924831aChris Banes
507c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    private static void checkBitmapParam(Bitmap bitmap) {
508c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        if (bitmap == null) {
509c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            throw new IllegalArgumentException("bitmap can not be null");
510c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        }
511c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        if (bitmap.isRecycled()) {
512c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            throw new IllegalArgumentException("bitmap can not be recycled");
513c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        }
514c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    }
515c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
516c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    private static void checkNumberColorsParam(int numColors) {
517c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        if (numColors < 1) {
518c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            throw new IllegalArgumentException("numColors must be 1 of greater");
519c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        }
520c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    }
521c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
522c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    private static void checkAsyncListenerParam(PaletteAsyncListener listener) {
523c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        if (listener == null) {
524c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            throw new IllegalArgumentException("listener can not be null");
525c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        }
526c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    }
527c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
528c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    /**
529c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Represents a color swatch generated from an image's palette. The RGB color can be retrieved
530c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * by calling {@link #getRgb()}.
531c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     */
532c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    public static final class Swatch {
533f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        private final int mRed, mGreen, mBlue;
534f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        private final int mRgb;
535f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        private final int mPopulation;
536c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
537f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        private boolean mGeneratedTextColors;
538f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        private int mTitleTextColor;
539f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        private int mBodyTextColor;
540c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
541c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        private float[] mHsl;
542c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
543c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        Swatch(int rgbColor, int population) {
544c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            mRed = Color.red(rgbColor);
545c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            mGreen = Color.green(rgbColor);
546c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            mBlue = Color.blue(rgbColor);
547c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            mRgb = rgbColor;
548c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            mPopulation = population;
549c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        }
550c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
551c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        Swatch(int red, int green, int blue, int population) {
552c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            mRed = red;
553c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            mGreen = green;
554c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            mBlue = blue;
555c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            mRgb = Color.rgb(red, green, blue);
556c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            mPopulation = population;
557c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        }
558c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
559c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        /**
560c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes         * @return this swatch's RGB color value
561c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes         */
562c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        public int getRgb() {
563c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            return mRgb;
564c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        }
565c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
566c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        /**
567c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes         * Return this swatch's HSL values.
568c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes         *     hsv[0] is Hue [0 .. 360)
569c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes         *     hsv[1] is Saturation [0...1]
570c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes         *     hsv[2] is Lightness [0...1]
571c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes         */
572c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        public float[] getHsl() {
573c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            if (mHsl == null) {
574c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                // Lazily generate HSL values from RGB
575c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                mHsl = new float[3];
576c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                ColorUtils.RGBtoHSL(mRed, mGreen, mBlue, mHsl);
577c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            }
578c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            return mHsl;
579c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        }
580c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
581c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        /**
582c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes         * @return the number of pixels represented by this swatch
583c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes         */
584c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        public int getPopulation() {
585c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            return mPopulation;
586c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        }
587c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
588f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        /**
589f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes         * Returns an appropriate color to use for any 'title' text which is displayed over this
590f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes         * {@link Swatch}'s color. This color is guaranteed to have sufficient contrast.
591f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes         */
592f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        public int getTitleTextColor() {
593f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes            ensureTextColorsGenerated();
594f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes            return mTitleTextColor;
595f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        }
596f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes
597f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        /**
598f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes         * Returns an appropriate color to use for any 'body' text which is displayed over this
599f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes         * {@link Swatch}'s color. This color is guaranteed to have sufficient contrast.
600f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes         */
601f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        public int getBodyTextColor() {
602f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes            ensureTextColorsGenerated();
603f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes            return mBodyTextColor;
604f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        }
605f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes
606f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        private void ensureTextColorsGenerated() {
607f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes            if (!mGeneratedTextColors) {
608f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes                mTitleTextColor = ColorUtils.getTextColorForBackground(mRgb,
609f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes                        MIN_CONTRAST_TITLE_TEXT);
610f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes                mBodyTextColor = ColorUtils.getTextColorForBackground(mRgb,
611f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes                        MIN_CONTRAST_BODY_TEXT);
612f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes                mGeneratedTextColors = true;
613f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes            }
614f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes        }
615f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes
616c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        @Override
617c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        public String toString() {
618f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes            return new StringBuilder(getClass().getSimpleName())
619f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes                    .append(" [RGB: #").append(Integer.toHexString(getRgb())).append(']')
620f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes                    .append(" [HSL: ").append(Arrays.toString(getHsl())).append(']')
621f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes                    .append(" [Population: ").append(mPopulation).append(']')
622f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes                    .append(" [Title Text: #").append(Integer.toHexString(mTitleTextColor)).append(']')
623f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes                    .append(" [Body Text: #").append(Integer.toHexString(mBodyTextColor)).append(']')
624f3082d731b24a0cee4305ee6bba168a11c11f068Chris Banes                    .toString();
625c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        }
626e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes
627e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes        @Override
628e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes        public boolean equals(Object o) {
629e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes            if (this == o) {
630e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes                return true;
631e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes            }
632e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes            if (o == null || getClass() != o.getClass()) {
633e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes                return false;
634e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes            }
635e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes
636e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes            Swatch swatch = (Swatch) o;
637e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes            return mPopulation == swatch.mPopulation && mRgb == swatch.mRgb;
638e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes        }
639e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes
640e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes        @Override
641e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes        public int hashCode() {
642e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes            return 31 * mRgb + mPopulation;
643e538500b3daa25cbcd460167fcc82d61f7b03a3cChris Banes        }
644c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    }
645c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
646059178a8c7cc80848e5594a9287be91bd924831aChris Banes}
647