DefaultGenerator.java revision f78a300c82748e29a3890c8f17a13726aacf33be
1/*
2 * Copyright 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *       http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.support.v7.graphics;
18
19import android.support.v4.graphics.ColorUtils;
20import android.support.v7.graphics.Palette.Swatch;
21
22import java.util.List;
23
24class DefaultGenerator extends Palette.Generator {
25
26    private static final float TARGET_DARK_LUMA = 0.26f;
27    private static final float MAX_DARK_LUMA = 0.45f;
28
29    private static final float MIN_LIGHT_LUMA = 0.55f;
30    private static final float TARGET_LIGHT_LUMA = 0.74f;
31
32    private static final float MIN_NORMAL_LUMA = 0.3f;
33    private static final float TARGET_NORMAL_LUMA = 0.5f;
34    private static final float MAX_NORMAL_LUMA = 0.7f;
35
36    private static final float TARGET_MUTED_SATURATION = 0.3f;
37    private static final float MAX_MUTED_SATURATION = 0.4f;
38
39    private static final float TARGET_VIBRANT_SATURATION = 1f;
40    private static final float MIN_VIBRANT_SATURATION = 0.35f;
41
42    private static final float WEIGHT_SATURATION = 3f;
43    private static final float WEIGHT_LUMA = 6f;
44    private static final float WEIGHT_POPULATION = 1f;
45
46    private List<Swatch> mSwatches;
47
48    private int mHighestPopulation;
49
50    private Swatch mVibrantSwatch;
51    private Swatch mMutedSwatch;
52    private Swatch mDarkVibrantSwatch;
53    private Swatch mDarkMutedSwatch;
54    private Swatch mLightVibrantSwatch;
55    private Swatch mLightMutedSwatch;
56
57    @Override
58    public void generate(final List<Swatch> swatches) {
59        mSwatches = swatches;
60
61        mHighestPopulation = findMaxPopulation();
62
63        generateVariationColors();
64
65        // Now try and generate any missing colors
66        generateEmptySwatches();
67    }
68
69    @Override
70    public Swatch getVibrantSwatch() {
71        return mVibrantSwatch;
72    }
73
74    @Override
75    public Swatch getLightVibrantSwatch() {
76        return mLightVibrantSwatch;
77    }
78
79    @Override
80    public Swatch getDarkVibrantSwatch() {
81        return mDarkVibrantSwatch;
82    }
83
84    @Override
85    public Swatch getMutedSwatch() {
86        return mMutedSwatch;
87    }
88
89    @Override
90    public Swatch getLightMutedSwatch() {
91        return mLightMutedSwatch;
92    }
93
94    @Override
95    public Swatch getDarkMutedSwatch() {
96        return mDarkMutedSwatch;
97    }
98
99    private void generateVariationColors() {
100        mVibrantSwatch = findColorVariation(TARGET_NORMAL_LUMA, MIN_NORMAL_LUMA, MAX_NORMAL_LUMA,
101                TARGET_VIBRANT_SATURATION, MIN_VIBRANT_SATURATION, 1f);
102
103        mLightVibrantSwatch = findColorVariation(TARGET_LIGHT_LUMA, MIN_LIGHT_LUMA, 1f,
104                TARGET_VIBRANT_SATURATION, MIN_VIBRANT_SATURATION, 1f);
105
106        mDarkVibrantSwatch = findColorVariation(TARGET_DARK_LUMA, 0f, MAX_DARK_LUMA,
107                TARGET_VIBRANT_SATURATION, MIN_VIBRANT_SATURATION, 1f);
108
109        mMutedSwatch = findColorVariation(TARGET_NORMAL_LUMA, MIN_NORMAL_LUMA, MAX_NORMAL_LUMA,
110                TARGET_MUTED_SATURATION, 0f, MAX_MUTED_SATURATION);
111
112        mLightMutedSwatch = findColorVariation(TARGET_LIGHT_LUMA, MIN_LIGHT_LUMA, 1f,
113                TARGET_MUTED_SATURATION, 0f, MAX_MUTED_SATURATION);
114
115        mDarkMutedSwatch = findColorVariation(TARGET_DARK_LUMA, 0f, MAX_DARK_LUMA,
116                TARGET_MUTED_SATURATION, 0f, MAX_MUTED_SATURATION);
117    }
118
119    /**
120     * Try and generate any missing swatches from the swatches we did find.
121     */
122    private void generateEmptySwatches() {
123        if (mVibrantSwatch == null) {
124            // If we do not have a vibrant color...
125            if (mDarkVibrantSwatch != null) {
126                // ...but we do have a dark vibrant, generate the value by modifying the luma
127                final float[] newHsl = copyHslValues(mDarkVibrantSwatch);
128                newHsl[2] = TARGET_NORMAL_LUMA;
129                mVibrantSwatch = new Swatch(ColorUtils.HSLToColor(newHsl), 0);
130            }
131        }
132
133        if (mDarkVibrantSwatch == null) {
134            // If we do not have a dark vibrant color...
135            if (mVibrantSwatch != null) {
136                // ...but we do have a vibrant, generate the value by modifying the luma
137                final float[] newHsl = copyHslValues(mVibrantSwatch);
138                newHsl[2] = TARGET_DARK_LUMA;
139                mDarkVibrantSwatch = new Swatch(ColorUtils.HSLToColor(newHsl), 0);
140            }
141        }
142    }
143
144    /**
145     * Find the {@link Palette.Swatch} with the highest population value and return the population.
146     */
147    private int findMaxPopulation() {
148        int population = 0;
149        for (Swatch swatch : mSwatches) {
150            population = Math.max(population, swatch.getPopulation());
151        }
152        return population;
153    }
154
155    private Swatch findColorVariation(float targetLuma, float minLuma, float maxLuma,
156            float targetSaturation, float minSaturation, float maxSaturation) {
157        Swatch max = null;
158        float maxValue = 0f;
159
160        for (Swatch swatch : mSwatches) {
161            final float sat = swatch.getHsl()[1];
162            final float luma = swatch.getHsl()[2];
163
164            if (sat >= minSaturation && sat <= maxSaturation &&
165                    luma >= minLuma && luma <= maxLuma &&
166                    !isAlreadySelected(swatch)) {
167                float value = createComparisonValue(sat, targetSaturation, luma, targetLuma,
168                        swatch.getPopulation(), mHighestPopulation);
169                if (max == null || value > maxValue) {
170                    max = swatch;
171                    maxValue = value;
172                }
173            }
174        }
175
176        return max;
177    }
178
179    /**
180     * @return true if we have already selected {@code swatch}
181     */
182    private boolean isAlreadySelected(Swatch swatch) {
183        return mVibrantSwatch == swatch || mDarkVibrantSwatch == swatch ||
184                mLightVibrantSwatch == swatch || mMutedSwatch == swatch ||
185                mDarkMutedSwatch == swatch || mLightMutedSwatch == swatch;
186    }
187
188    private static float createComparisonValue(float saturation, float targetSaturation,
189            float luma, float targetLuma,
190            int population, int maxPopulation) {
191        return createComparisonValue(saturation, targetSaturation, WEIGHT_SATURATION,
192                luma, targetLuma, WEIGHT_LUMA,
193                population, maxPopulation, WEIGHT_POPULATION);
194    }
195
196    private static float createComparisonValue(
197            float saturation, float targetSaturation, float saturationWeight,
198            float luma, float targetLuma, float lumaWeight,
199            int population, int maxPopulation, float populationWeight) {
200        return weightedMean(
201                invertDiff(saturation, targetSaturation), saturationWeight,
202                invertDiff(luma, targetLuma), lumaWeight,
203                population / (float) maxPopulation, populationWeight
204        );
205    }
206
207    /**
208     * Copy a {@link Swatch}'s HSL values into a new float[].
209     */
210    private static float[] copyHslValues(Swatch color) {
211        final float[] newHsl = new float[3];
212        System.arraycopy(color.getHsl(), 0, newHsl, 0, 3);
213        return newHsl;
214    }
215
216    /**
217     * Returns a value in the range 0-1. 1 is returned when {@code value} equals the
218     * {@code targetValue} and then decreases as the absolute difference between {@code value} and
219     * {@code targetValue} increases.
220     *
221     * @param value the item's value
222     * @param targetValue the value which we desire
223     */
224    private static float invertDiff(float value, float targetValue) {
225        return 1f - Math.abs(value - targetValue);
226    }
227
228    private static float weightedMean(float... values) {
229        float sum = 0f;
230        float sumWeight = 0f;
231
232        for (int i = 0; i < values.length; i += 2) {
233            float value = values[i];
234            float weight = values[i + 1];
235
236            sum += (value * weight);
237            sumWeight += weight;
238        }
239
240        return sum / sumWeight;
241    }
242}
243