Palette.java revision c6cdc41397bc3ad2c936069af6d448f242790513
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
96c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    private final List<Swatch> mSwatches;
97059178a8c7cc80848e5594a9287be91bd924831aChris Banes    private final int mHighestPopulation;
98059178a8c7cc80848e5594a9287be91bd924831aChris Banes
99c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    private Swatch mVibrantSwatch;
100c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    private Swatch mMutedSwatch;
101059178a8c7cc80848e5594a9287be91bd924831aChris Banes
102c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    private Swatch mDarkVibrantSwatch;
103c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    private Swatch mDarkMutedSwatch;
104059178a8c7cc80848e5594a9287be91bd924831aChris Banes
105c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    private Swatch mLightVibrantSwatch;
106c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    private Swatch mLightMutedColor;
107059178a8c7cc80848e5594a9287be91bd924831aChris Banes
108059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
109059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * Generate a {@link Palette} from a {@link Bitmap} using the default number of colors.
110059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
111059178a8c7cc80848e5594a9287be91bd924831aChris Banes    public static Palette generate(Bitmap bitmap) {
112059178a8c7cc80848e5594a9287be91bd924831aChris Banes        return generate(bitmap, DEFAULT_CALCULATE_NUMBER_COLORS);
113059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
114059178a8c7cc80848e5594a9287be91bd924831aChris Banes
115059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
116059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * Generate a {@link Palette} from a {@link Bitmap} using the specified {@code numColors}.
117059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * Good values for {@code numColors} depend on the source image type.
118059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * For landscapes, a good values are in the range 12-16. For images which are largely made up
119059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * of people's faces then this value should be increased to 24-32.
120059178a8c7cc80848e5594a9287be91bd924831aChris Banes     *
121059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * @param numColors The maximum number of colors in the generated palette. Increasing this
122059178a8c7cc80848e5594a9287be91bd924831aChris Banes     *                  number will increase the time needed to compute the values.
123059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
124059178a8c7cc80848e5594a9287be91bd924831aChris Banes    public static Palette generate(Bitmap bitmap, int numColors) {
125c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        checkBitmapParam(bitmap);
126c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        checkNumberColorsParam(numColors);
127059178a8c7cc80848e5594a9287be91bd924831aChris Banes
128059178a8c7cc80848e5594a9287be91bd924831aChris Banes        // First we'll scale down the bitmap so it's shortest dimension is 100px
129059178a8c7cc80848e5594a9287be91bd924831aChris Banes        final Bitmap scaledBitmap = scaleBitmapDown(bitmap);
130059178a8c7cc80848e5594a9287be91bd924831aChris Banes
131059178a8c7cc80848e5594a9287be91bd924831aChris Banes        // Now generate a quantizer from the Bitmap
132059178a8c7cc80848e5594a9287be91bd924831aChris Banes        ColorCutQuantizer quantizer = ColorCutQuantizer.fromBitmap(scaledBitmap, numColors);
133059178a8c7cc80848e5594a9287be91bd924831aChris Banes
134059178a8c7cc80848e5594a9287be91bd924831aChris Banes        // If created a new bitmap, recycle it
135059178a8c7cc80848e5594a9287be91bd924831aChris Banes        if (scaledBitmap != bitmap) {
136059178a8c7cc80848e5594a9287be91bd924831aChris Banes            scaledBitmap.recycle();
137059178a8c7cc80848e5594a9287be91bd924831aChris Banes        }
138059178a8c7cc80848e5594a9287be91bd924831aChris Banes
139059178a8c7cc80848e5594a9287be91bd924831aChris Banes        // Now return a ColorExtractor instance
140059178a8c7cc80848e5594a9287be91bd924831aChris Banes        return new Palette(quantizer.getQuantizedColors());
141059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
142059178a8c7cc80848e5594a9287be91bd924831aChris Banes
143059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
144059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * Generate a {@link Palette} asynchronously. {@link PaletteAsyncListener#onGenerated(Palette)}
145059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * will be called with the created instance. The resulting {@link Palette} is the same as
146059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * what would be created by calling {@link #generate(Bitmap)}.
147059178a8c7cc80848e5594a9287be91bd924831aChris Banes     *
148059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * @param listener Listener to be invoked when the {@link Palette} has been generated.
149059178a8c7cc80848e5594a9287be91bd924831aChris Banes     *
150059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * @return the {@link android.os.AsyncTask} used to asynchronously generate the instance.
151059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
152c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    public static AsyncTask<Bitmap, Void, Palette> generateAsync(
153059178a8c7cc80848e5594a9287be91bd924831aChris Banes            Bitmap bitmap, PaletteAsyncListener listener) {
154059178a8c7cc80848e5594a9287be91bd924831aChris Banes        return generateAsync(bitmap, DEFAULT_CALCULATE_NUMBER_COLORS, listener);
155059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
156059178a8c7cc80848e5594a9287be91bd924831aChris Banes
157059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
158059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * Generate a {@link Palette} asynchronously. {@link PaletteAsyncListener#onGenerated(Palette)}
159059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * will be called with the created instance. The resulting {@link Palette} is the same as what
160059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * would be created by calling {@link #generate(Bitmap, int)}.
161059178a8c7cc80848e5594a9287be91bd924831aChris Banes     *
162059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * @param listener Listener to be invoked when the {@link Palette} has been generated.
163059178a8c7cc80848e5594a9287be91bd924831aChris Banes     *
164059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * @return the {@link android.os.AsyncTask} used to asynchronously generate the instance.
165059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
166c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    public static AsyncTask<Bitmap, Void, Palette> generateAsync(
167059178a8c7cc80848e5594a9287be91bd924831aChris Banes            final Bitmap bitmap, final int numColors, final PaletteAsyncListener listener) {
168c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        checkBitmapParam(bitmap);
169c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        checkNumberColorsParam(numColors);
170c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        checkAsyncListenerParam(listener);
171c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
172c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        return AsyncTaskCompat.executeParallel(
173c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                new AsyncTask<Bitmap, Void, Palette>() {
174c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                    @Override
175c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                    protected Palette doInBackground(Bitmap... params) {
176c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                        return generate(params[0], numColors);
177c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                    }
178c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
179c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                    @Override
180c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                    protected void onPostExecute(Palette colorExtractor) {
181c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                        listener.onGenerated(colorExtractor);
182c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                    }
183c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                }, bitmap);
184059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
185059178a8c7cc80848e5594a9287be91bd924831aChris Banes
186c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    private Palette(List<Swatch> swatches) {
187c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        mSwatches = swatches;
188059178a8c7cc80848e5594a9287be91bd924831aChris Banes        mHighestPopulation = findMaxPopulation();
189059178a8c7cc80848e5594a9287be91bd924831aChris Banes
190c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        mVibrantSwatch = findColor(TARGET_NORMAL_LUMA, MIN_NORMAL_LUMA, MAX_NORMAL_LUMA,
191059178a8c7cc80848e5594a9287be91bd924831aChris Banes                TARGET_VIBRANT_SATURATION, MIN_VIBRANT_SATURATION, 1f);
192059178a8c7cc80848e5594a9287be91bd924831aChris Banes
193c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        mLightVibrantSwatch = findColor(TARGET_LIGHT_LUMA, MIN_LIGHT_LUMA, 1f,
194059178a8c7cc80848e5594a9287be91bd924831aChris Banes                TARGET_VIBRANT_SATURATION, MIN_VIBRANT_SATURATION, 1f);
195059178a8c7cc80848e5594a9287be91bd924831aChris Banes
196c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        mDarkVibrantSwatch = findColor(TARGET_DARK_LUMA, 0f, MAX_DARK_LUMA,
197059178a8c7cc80848e5594a9287be91bd924831aChris Banes                TARGET_VIBRANT_SATURATION, MIN_VIBRANT_SATURATION, 1f);
198059178a8c7cc80848e5594a9287be91bd924831aChris Banes
199c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        mMutedSwatch = findColor(TARGET_NORMAL_LUMA, MIN_NORMAL_LUMA, MAX_NORMAL_LUMA,
200059178a8c7cc80848e5594a9287be91bd924831aChris Banes                TARGET_MUTED_SATURATION, 0f, MAX_MUTED_SATURATION);
201059178a8c7cc80848e5594a9287be91bd924831aChris Banes
202059178a8c7cc80848e5594a9287be91bd924831aChris Banes        mLightMutedColor = findColor(TARGET_LIGHT_LUMA, MIN_LIGHT_LUMA, 1f,
203059178a8c7cc80848e5594a9287be91bd924831aChris Banes                TARGET_MUTED_SATURATION, 0f, MAX_MUTED_SATURATION);
204059178a8c7cc80848e5594a9287be91bd924831aChris Banes
205c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        mDarkMutedSwatch = findColor(TARGET_DARK_LUMA, 0f, MAX_DARK_LUMA,
206059178a8c7cc80848e5594a9287be91bd924831aChris Banes                TARGET_MUTED_SATURATION, 0f, MAX_MUTED_SATURATION);
207059178a8c7cc80848e5594a9287be91bd924831aChris Banes
208059178a8c7cc80848e5594a9287be91bd924831aChris Banes        // Now try and generate any missing colors
209c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        generateEmptySwatches();
210059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
211059178a8c7cc80848e5594a9287be91bd924831aChris Banes
212059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
213c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Returns all of the swatches which make up the palette.
214059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
215c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    public List<Swatch> getSwatches() {
216c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        return Collections.unmodifiableList(mSwatches);
217059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
218059178a8c7cc80848e5594a9287be91bd924831aChris Banes
219059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
220c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Returns the most vibrant swatch in the palette. Might be null.
221059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
222c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    public Swatch getVibrantSwatch() {
223c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        return mVibrantSwatch;
224059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
225059178a8c7cc80848e5594a9287be91bd924831aChris Banes
226059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
227c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Returns a light and vibrant swatch from the palette. Might be null.
228059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
229c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    public Swatch getLightVibrantSwatch() {
230c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        return mLightVibrantSwatch;
231059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
232059178a8c7cc80848e5594a9287be91bd924831aChris Banes
233059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
234c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Returns a dark and vibrant swatch from the palette. Might be null.
235059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
236c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    public Swatch getDarkVibrantSwatch() {
237c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        return mDarkVibrantSwatch;
238059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
239059178a8c7cc80848e5594a9287be91bd924831aChris Banes
240059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
241c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Returns a muted swatch from the palette. Might be null.
242059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
243c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    public Swatch getMutedSwatch() {
244c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        return mMutedSwatch;
245059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
246059178a8c7cc80848e5594a9287be91bd924831aChris Banes
247059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
248c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Returns a muted and light swatch from the palette. Might be null.
249059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
250c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    public Swatch getLightMutedSwatch() {
251059178a8c7cc80848e5594a9287be91bd924831aChris Banes        return mLightMutedColor;
252059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
253059178a8c7cc80848e5594a9287be91bd924831aChris Banes
254059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
255c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Returns a muted and dark swatch from the palette. Might be null.
256c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     */
257c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    public Swatch getDarkMutedSwatch() {
258c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        return mDarkMutedSwatch;
259c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    }
260c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
261c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    /**
262c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Returns the most vibrant color in the palette as an RGB packed int.
263c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     *
264c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * @param defaultColor value to return if the swatch isn't available
265c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     */
266c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    public int getVibrantColor(int defaultColor) {
267c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        return mVibrantSwatch != null ? mVibrantSwatch.getRgb() : defaultColor;
268c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    }
269c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
270c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    /**
271c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Returns a light and vibrant color from the palette as an RGB packed int.
272c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     *
273c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * @param defaultColor value to return if the swatch isn't available
274c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     */
275c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    public int getLightVibrantColor(int defaultColor) {
276c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        return mLightVibrantSwatch != null ? mLightVibrantSwatch.getRgb() : defaultColor;
277c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    }
278c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
279c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    /**
280c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Returns a dark and vibrant color from the palette as an RGB packed int.
281c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     *
282c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * @param defaultColor value to return if the swatch isn't available
283c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     */
284c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    public int getDarkVibrantColor(int defaultColor) {
285c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        return mDarkVibrantSwatch != null ? mDarkVibrantSwatch.getRgb() : defaultColor;
286c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    }
287c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
288c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    /**
289c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Returns a muted color from the palette as an RGB packed int.
290c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     *
291c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * @param defaultColor value to return if the swatch isn't available
292c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     */
293c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    public int getMutedColor(int defaultColor) {
294c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        return mMutedSwatch != null ? mMutedSwatch.getRgb() : defaultColor;
295c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    }
296c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
297c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    /**
298c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Returns a muted and light color from the palette as an RGB packed int.
299c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     *
300c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * @param defaultColor value to return if the swatch isn't available
301c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     */
302c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    public int getLightMutedColor(int defaultColor) {
303c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        return mLightMutedColor != null ? mLightMutedColor.getRgb() : defaultColor;
304c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    }
305c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
306c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    /**
307c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Returns a muted and dark color from the palette as an RGB packed int.
308c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     *
309c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * @param defaultColor value to return if the swatch isn't available
310059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
311c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    public int getDarkMutedColor(int defaultColor) {
312c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        return mDarkMutedSwatch != null ? mDarkMutedSwatch.getRgb() : defaultColor;
313059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
314059178a8c7cc80848e5594a9287be91bd924831aChris Banes
315059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
316c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * @return true if we have already selected {@code swatch}
317059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
318c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    private boolean isAlreadySelected(Swatch swatch) {
319c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        return mVibrantSwatch == swatch || mDarkVibrantSwatch == swatch ||
320c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                mLightVibrantSwatch == swatch || mMutedSwatch == swatch ||
321c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                mDarkMutedSwatch == swatch || mLightMutedColor == swatch;
322059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
323059178a8c7cc80848e5594a9287be91bd924831aChris Banes
324c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    private Swatch findColor(float targetLuma, float minLuma, float maxLuma,
325c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                             float targetSaturation, float minSaturation, float maxSaturation) {
326c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        Swatch max = null;
327059178a8c7cc80848e5594a9287be91bd924831aChris Banes        float maxValue = 0f;
328059178a8c7cc80848e5594a9287be91bd924831aChris Banes
329c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        for (Swatch swatch : mSwatches) {
330c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            final float sat = swatch.getHsl()[1];
331c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            final float luma = swatch.getHsl()[2];
332059178a8c7cc80848e5594a9287be91bd924831aChris Banes
333059178a8c7cc80848e5594a9287be91bd924831aChris Banes            if (sat >= minSaturation && sat <= maxSaturation &&
334059178a8c7cc80848e5594a9287be91bd924831aChris Banes                    luma >= minLuma && luma <= maxLuma &&
335c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                    !isAlreadySelected(swatch)) {
336059178a8c7cc80848e5594a9287be91bd924831aChris Banes                float thisValue = createComparisonValue(sat, targetSaturation, luma, targetLuma,
337c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                        swatch.getPopulation(), mHighestPopulation);
338059178a8c7cc80848e5594a9287be91bd924831aChris Banes                if (max == null || thisValue > maxValue) {
339c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                    max = swatch;
340059178a8c7cc80848e5594a9287be91bd924831aChris Banes                    maxValue = thisValue;
341059178a8c7cc80848e5594a9287be91bd924831aChris Banes                }
342059178a8c7cc80848e5594a9287be91bd924831aChris Banes            }
343059178a8c7cc80848e5594a9287be91bd924831aChris Banes        }
344059178a8c7cc80848e5594a9287be91bd924831aChris Banes
345059178a8c7cc80848e5594a9287be91bd924831aChris Banes        return max;
346059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
347059178a8c7cc80848e5594a9287be91bd924831aChris Banes
348059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
349c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Try and generate any missing swatches from the swatches we did find.
350059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
351c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    private void generateEmptySwatches() {
352c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        if (mVibrantSwatch == null) {
353059178a8c7cc80848e5594a9287be91bd924831aChris Banes            // If we do not have a vibrant color...
354c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            if (mDarkVibrantSwatch != null) {
355059178a8c7cc80848e5594a9287be91bd924831aChris Banes                // ...but we do have a dark vibrant, generate the value by modifying the luma
356c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                final float[] newHsl = copyHslValues(mDarkVibrantSwatch);
357059178a8c7cc80848e5594a9287be91bd924831aChris Banes                newHsl[2] = TARGET_NORMAL_LUMA;
358c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                mVibrantSwatch = new Swatch(ColorUtils.HSLtoRGB(newHsl), 0);
359059178a8c7cc80848e5594a9287be91bd924831aChris Banes            }
360059178a8c7cc80848e5594a9287be91bd924831aChris Banes        }
361059178a8c7cc80848e5594a9287be91bd924831aChris Banes
362c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        if (mDarkVibrantSwatch == null) {
363059178a8c7cc80848e5594a9287be91bd924831aChris Banes            // If we do not have a dark vibrant color...
364c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            if (mVibrantSwatch != null) {
365059178a8c7cc80848e5594a9287be91bd924831aChris Banes                // ...but we do have a vibrant, generate the value by modifying the luma
366c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                final float[] newHsl = copyHslValues(mVibrantSwatch);
367059178a8c7cc80848e5594a9287be91bd924831aChris Banes                newHsl[2] = TARGET_DARK_LUMA;
368c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                mDarkVibrantSwatch = new Swatch(ColorUtils.HSLtoRGB(newHsl), 0);
369059178a8c7cc80848e5594a9287be91bd924831aChris Banes            }
370059178a8c7cc80848e5594a9287be91bd924831aChris Banes        }
371059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
372059178a8c7cc80848e5594a9287be91bd924831aChris Banes
373059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
374c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Find the {@link Swatch} with the highest population value and return the population.
375059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
376059178a8c7cc80848e5594a9287be91bd924831aChris Banes    private int findMaxPopulation() {
377059178a8c7cc80848e5594a9287be91bd924831aChris Banes        int population = 0;
378c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        for (Swatch swatch : mSwatches) {
379c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            population = Math.max(population, swatch.getPopulation());
380059178a8c7cc80848e5594a9287be91bd924831aChris Banes        }
381059178a8c7cc80848e5594a9287be91bd924831aChris Banes        return population;
382059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
383059178a8c7cc80848e5594a9287be91bd924831aChris Banes
384059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
385059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * Scale the bitmap down so that it's smallest dimension is
386059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * {@value #CALCULATE_BITMAP_MIN_DIMENSION}px. If {@code bitmap} is smaller than this, than it
387059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * is returned.
388059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
389059178a8c7cc80848e5594a9287be91bd924831aChris Banes    private static Bitmap scaleBitmapDown(Bitmap bitmap) {
390059178a8c7cc80848e5594a9287be91bd924831aChris Banes        final int minDimension = Math.min(bitmap.getWidth(), bitmap.getHeight());
391059178a8c7cc80848e5594a9287be91bd924831aChris Banes
392059178a8c7cc80848e5594a9287be91bd924831aChris Banes        if (minDimension <= CALCULATE_BITMAP_MIN_DIMENSION) {
393059178a8c7cc80848e5594a9287be91bd924831aChris Banes            // If the bitmap is small enough already, just return it
394059178a8c7cc80848e5594a9287be91bd924831aChris Banes            return bitmap;
395059178a8c7cc80848e5594a9287be91bd924831aChris Banes        }
396059178a8c7cc80848e5594a9287be91bd924831aChris Banes
397059178a8c7cc80848e5594a9287be91bd924831aChris Banes        final float scaleRatio = CALCULATE_BITMAP_MIN_DIMENSION / (float) minDimension;
398059178a8c7cc80848e5594a9287be91bd924831aChris Banes        return Bitmap.createScaledBitmap(bitmap,
399059178a8c7cc80848e5594a9287be91bd924831aChris Banes                Math.round(bitmap.getWidth() * scaleRatio),
400059178a8c7cc80848e5594a9287be91bd924831aChris Banes                Math.round(bitmap.getHeight() * scaleRatio),
401059178a8c7cc80848e5594a9287be91bd924831aChris Banes                false);
402059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
403059178a8c7cc80848e5594a9287be91bd924831aChris Banes
404059178a8c7cc80848e5594a9287be91bd924831aChris Banes    private static float createComparisonValue(float saturation, float targetSaturation,
405059178a8c7cc80848e5594a9287be91bd924831aChris Banes            float luma, float targetLuma,
406059178a8c7cc80848e5594a9287be91bd924831aChris Banes            int population, int highestPopulation) {
407059178a8c7cc80848e5594a9287be91bd924831aChris Banes        return weightedMean(
408c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                invertDiff(saturation, targetSaturation), WEIGHT_SATURATION,
409c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                invertDiff(luma, targetLuma), WEIGHT_LUMA,
410c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                population / (float) highestPopulation, WEIGHT_POPULATION
411059178a8c7cc80848e5594a9287be91bd924831aChris Banes        );
412059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
413059178a8c7cc80848e5594a9287be91bd924831aChris Banes
414059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
415c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Copy a {@link Swatch}'s HSL values into a new float[].
416059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
417c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    private static float[] copyHslValues(Swatch color) {
418059178a8c7cc80848e5594a9287be91bd924831aChris Banes        final float[] newHsl = new float[3];
419059178a8c7cc80848e5594a9287be91bd924831aChris Banes        System.arraycopy(color.getHsl(), 0, newHsl, 0, 3);
420059178a8c7cc80848e5594a9287be91bd924831aChris Banes        return newHsl;
421059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
422059178a8c7cc80848e5594a9287be91bd924831aChris Banes
423059178a8c7cc80848e5594a9287be91bd924831aChris Banes    /**
424059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * Returns a value in the range 0-1. 1 is returned when {@code value} equals the
425059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * {@code targetValue} and then decreases as the absolute difference between {@code value} and
426059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * {@code targetValue} increases.
427059178a8c7cc80848e5594a9287be91bd924831aChris Banes     *
428059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * @param value the item's value
429059178a8c7cc80848e5594a9287be91bd924831aChris Banes     * @param targetValue the value which we desire
430059178a8c7cc80848e5594a9287be91bd924831aChris Banes     */
431059178a8c7cc80848e5594a9287be91bd924831aChris Banes    private static float invertDiff(float value, float targetValue) {
432059178a8c7cc80848e5594a9287be91bd924831aChris Banes        return 1f - Math.abs(value - targetValue);
433059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
434059178a8c7cc80848e5594a9287be91bd924831aChris Banes
435059178a8c7cc80848e5594a9287be91bd924831aChris Banes    private static float weightedMean(float... values) {
436059178a8c7cc80848e5594a9287be91bd924831aChris Banes        float sum = 0f;
437059178a8c7cc80848e5594a9287be91bd924831aChris Banes        float sumWeight = 0f;
438059178a8c7cc80848e5594a9287be91bd924831aChris Banes
439059178a8c7cc80848e5594a9287be91bd924831aChris Banes        for (int i = 0; i < values.length; i += 2) {
440059178a8c7cc80848e5594a9287be91bd924831aChris Banes            float value = values[i];
441059178a8c7cc80848e5594a9287be91bd924831aChris Banes            float weight = values[i + 1];
442059178a8c7cc80848e5594a9287be91bd924831aChris Banes
443059178a8c7cc80848e5594a9287be91bd924831aChris Banes            sum += (value * weight);
444059178a8c7cc80848e5594a9287be91bd924831aChris Banes            sumWeight += weight;
445059178a8c7cc80848e5594a9287be91bd924831aChris Banes        }
446059178a8c7cc80848e5594a9287be91bd924831aChris Banes
447059178a8c7cc80848e5594a9287be91bd924831aChris Banes        return sum / sumWeight;
448059178a8c7cc80848e5594a9287be91bd924831aChris Banes    }
449059178a8c7cc80848e5594a9287be91bd924831aChris Banes
450c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    private static void checkBitmapParam(Bitmap bitmap) {
451c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        if (bitmap == null) {
452c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            throw new IllegalArgumentException("bitmap can not be null");
453c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        }
454c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        if (bitmap.isRecycled()) {
455c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            throw new IllegalArgumentException("bitmap can not be recycled");
456c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        }
457c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    }
458c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
459c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    private static void checkNumberColorsParam(int numColors) {
460c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        if (numColors < 1) {
461c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            throw new IllegalArgumentException("numColors must be 1 of greater");
462c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        }
463c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    }
464c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
465c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    private static void checkAsyncListenerParam(PaletteAsyncListener listener) {
466c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        if (listener == null) {
467c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            throw new IllegalArgumentException("listener can not be null");
468c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        }
469c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    }
470c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
471c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    /**
472c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * Represents a color swatch generated from an image's palette. The RGB color can be retrieved
473c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     * by calling {@link #getRgb()}.
474c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes     */
475c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    public static final class Swatch {
476c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
477c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        final int mRed, mGreen, mBlue;
478c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        final int mRgb;
479c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        final int mPopulation;
480c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
481c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        private float[] mHsl;
482c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
483c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        Swatch(int rgbColor, int population) {
484c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            mRed = Color.red(rgbColor);
485c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            mGreen = Color.green(rgbColor);
486c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            mBlue = Color.blue(rgbColor);
487c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            mRgb = rgbColor;
488c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            mPopulation = population;
489c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        }
490c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
491c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        Swatch(int red, int green, int blue, int population) {
492c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            mRed = red;
493c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            mGreen = green;
494c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            mBlue = blue;
495c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            mRgb = Color.rgb(red, green, blue);
496c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            mPopulation = population;
497c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        }
498c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
499c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        /**
500c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes         * @return this swatch's RGB color value
501c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes         */
502c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        public int getRgb() {
503c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            return mRgb;
504c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        }
505c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
506c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        /**
507c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes         * Return this swatch's HSL values.
508c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes         *     hsv[0] is Hue [0 .. 360)
509c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes         *     hsv[1] is Saturation [0...1]
510c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes         *     hsv[2] is Lightness [0...1]
511c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes         */
512c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        public float[] getHsl() {
513c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            if (mHsl == null) {
514c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                // Lazily generate HSL values from RGB
515c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                mHsl = new float[3];
516c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                ColorUtils.RGBtoHSL(mRed, mGreen, mBlue, mHsl);
517c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            }
518c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            return mHsl;
519c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        }
520c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
521c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        /**
522c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes         * @return the number of pixels represented by this swatch
523c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes         */
524c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        public int getPopulation() {
525c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            return mPopulation;
526c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        }
527c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
528c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        @Override
529c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        public String toString() {
530c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes            return new StringBuilder(getClass().getSimpleName()).append(" ")
531c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                    .append("[").append(Integer.toHexString(getRgb())).append(']')
532c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                    .append("[HSL: ").append(Arrays.toString(getHsl())).append(']')
533c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes                    .append("[Population: ").append(mPopulation).append(']').toString();
534c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes        }
535c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes    }
536c6cdc41397bc3ad2c936069af6d448f242790513Chris Banes
537059178a8c7cc80848e5594a9287be91bd924831aChris Banes}
538