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