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.annotation.FloatRange;
20
21/**
22 * A class which allows custom selection of colors in a {@link Palette}'s generation. Instances
23 * can be created via the {@link Builder} class.
24 *
25 * <p>To use the target, use the {@link Palette.Builder#addTarget(Target)} API when building a
26 * Palette.</p>
27 */
28public final class Target {
29
30    private static final float TARGET_DARK_LUMA = 0.26f;
31    private static final float MAX_DARK_LUMA = 0.45f;
32
33    private static final float MIN_LIGHT_LUMA = 0.55f;
34    private static final float TARGET_LIGHT_LUMA = 0.74f;
35
36    private static final float MIN_NORMAL_LUMA = 0.3f;
37    private static final float TARGET_NORMAL_LUMA = 0.5f;
38    private static final float MAX_NORMAL_LUMA = 0.7f;
39
40    private static final float TARGET_MUTED_SATURATION = 0.3f;
41    private static final float MAX_MUTED_SATURATION = 0.4f;
42
43    private static final float TARGET_VIBRANT_SATURATION = 1f;
44    private static final float MIN_VIBRANT_SATURATION = 0.35f;
45
46    private static final float WEIGHT_SATURATION = 0.24f;
47    private static final float WEIGHT_LUMA = 0.52f;
48    private static final float WEIGHT_POPULATION = 0.24f;
49
50    private static final int INDEX_MIN = 0;
51    private static final int INDEX_TARGET = 1;
52    private static final int INDEX_MAX = 2;
53
54    private static final int INDEX_WEIGHT_SAT = 0;
55    private static final int INDEX_WEIGHT_LUMA = 1;
56    private static final int INDEX_WEIGHT_POP = 2;
57
58    /**
59     * A target which has the characteristics of a vibrant color which is light in luminance.
60    */
61    public static final Target LIGHT_VIBRANT;
62
63    /**
64     * A target which has the characteristics of a vibrant color which is neither light or dark.
65     */
66    public static final Target VIBRANT;
67
68    /**
69     * A target which has the characteristics of a vibrant color which is dark in luminance.
70     */
71    public static final Target DARK_VIBRANT;
72
73    /**
74     * A target which has the characteristics of a muted color which is light in luminance.
75     */
76    public static final Target LIGHT_MUTED;
77
78    /**
79     * A target which has the characteristics of a muted color which is neither light or dark.
80     */
81    public static final Target MUTED;
82
83    /**
84     * A target which has the characteristics of a muted color which is dark in luminance.
85     */
86    public static final Target DARK_MUTED;
87
88    static {
89        LIGHT_VIBRANT = new Target();
90        setDefaultLightLightnessValues(LIGHT_VIBRANT);
91        setDefaultVibrantSaturationValues(LIGHT_VIBRANT);
92
93        VIBRANT = new Target();
94        setDefaultNormalLightnessValues(VIBRANT);
95        setDefaultVibrantSaturationValues(VIBRANT);
96
97        DARK_VIBRANT = new Target();
98        setDefaultDarkLightnessValues(DARK_VIBRANT);
99        setDefaultVibrantSaturationValues(DARK_VIBRANT);
100
101        LIGHT_MUTED = new Target();
102        setDefaultLightLightnessValues(LIGHT_MUTED);
103        setDefaultMutedSaturationValues(LIGHT_MUTED);
104
105        MUTED = new Target();
106        setDefaultNormalLightnessValues(MUTED);
107        setDefaultMutedSaturationValues(MUTED);
108
109        DARK_MUTED = new Target();
110        setDefaultDarkLightnessValues(DARK_MUTED);
111        setDefaultMutedSaturationValues(DARK_MUTED);
112    }
113
114    private final float[] mSaturationTargets = new float[3];
115    private final float[] mLightnessTargets = new float[3];
116    private final float[] mWeights = new float[3];
117    private boolean mIsExclusive = true; // default to true
118
119    private Target() {
120        setTargetDefaultValues(mSaturationTargets);
121        setTargetDefaultValues(mLightnessTargets);
122        setDefaultWeights();
123    }
124
125    private Target(Target from) {
126        System.arraycopy(from.mSaturationTargets, 0, mSaturationTargets, 0,
127                mSaturationTargets.length);
128        System.arraycopy(from.mLightnessTargets, 0, mLightnessTargets, 0,
129                mLightnessTargets.length);
130        System.arraycopy(from.mWeights, 0, mWeights, 0, mWeights.length);
131    }
132
133    /**
134     * The minimum saturation value for this target.
135     */
136    @FloatRange(from = 0, to = 1)
137    public float getMinimumSaturation() {
138        return mSaturationTargets[INDEX_MIN];
139    }
140
141    /**
142     * The target saturation value for this target.
143     */
144    @FloatRange(from = 0, to = 1)
145    public float getTargetSaturation() {
146        return mSaturationTargets[INDEX_TARGET];
147    }
148
149    /**
150     * The maximum saturation value for this target.
151     */
152    @FloatRange(from = 0, to = 1)
153    public float getMaximumSaturation() {
154        return mSaturationTargets[INDEX_MAX];
155    }
156
157    /**
158     * The minimum lightness value for this target.
159     */
160    @FloatRange(from = 0, to = 1)
161    public float getMinimumLightness() {
162        return mLightnessTargets[INDEX_MIN];
163    }
164
165    /**
166     * The target lightness value for this target.
167     */
168    @FloatRange(from = 0, to = 1)
169    public float getTargetLightness() {
170        return mLightnessTargets[INDEX_TARGET];
171    }
172
173    /**
174     * The maximum lightness value for this target.
175     */
176    @FloatRange(from = 0, to = 1)
177    public float getMaximumLightness() {
178        return mLightnessTargets[INDEX_MAX];
179    }
180
181    /**
182     * Returns the weight of importance that this target places on a color's saturation within
183     * the image.
184     *
185     * <p>The larger the weight, relative to the other weights, the more important that a color
186     * being close to the target value has on selection.</p>
187     *
188     * @see #getTargetSaturation()
189     */
190    public float getSaturationWeight() {
191        return mWeights[INDEX_WEIGHT_SAT];
192    }
193
194    /**
195     * Returns the weight of importance that this target places on a color's lightness within
196     * the image.
197     *
198     * <p>The larger the weight, relative to the other weights, the more important that a color
199     * being close to the target value has on selection.</p>
200     *
201     * @see #getTargetLightness()
202     */
203    public float getLightnessWeight() {
204        return mWeights[INDEX_WEIGHT_LUMA];
205    }
206
207    /**
208     * Returns the weight of importance that this target places on a color's population within
209     * the image.
210     *
211     * <p>The larger the weight, relative to the other weights, the more important that a
212     * color's population being close to the most populous has on selection.</p>
213     */
214    public float getPopulationWeight() {
215        return mWeights[INDEX_WEIGHT_POP];
216    }
217
218    /**
219     * Returns whether any color selected for this target is exclusive for this target only.
220     *
221     * <p>If false, then the color can be selected for other targets.</p>
222     */
223    public boolean isExclusive() {
224        return mIsExclusive;
225    }
226
227    private static void setTargetDefaultValues(final float[] values) {
228        values[INDEX_MIN] = 0f;
229        values[INDEX_TARGET] = 0.5f;
230        values[INDEX_MAX] = 1f;
231    }
232
233    private void setDefaultWeights() {
234        mWeights[INDEX_WEIGHT_SAT] = WEIGHT_SATURATION;
235        mWeights[INDEX_WEIGHT_LUMA] = WEIGHT_LUMA;
236        mWeights[INDEX_WEIGHT_POP] = WEIGHT_POPULATION;
237    }
238
239    void normalizeWeights() {
240        float sum = 0;
241        for (int i = 0, z = mWeights.length; i < z; i++) {
242            float weight = mWeights[i];
243            if (weight > 0) {
244                sum += weight;
245            }
246        }
247        if (sum != 0) {
248            for (int i = 0, z = mWeights.length; i < z; i++) {
249                if (mWeights[i] > 0) {
250                    mWeights[i] /= sum;
251                }
252            }
253        }
254    }
255
256    private static void setDefaultDarkLightnessValues(Target target) {
257        target.mLightnessTargets[INDEX_TARGET] = TARGET_DARK_LUMA;
258        target.mLightnessTargets[INDEX_MAX] = MAX_DARK_LUMA;
259    }
260
261    private static void setDefaultNormalLightnessValues(Target target) {
262        target.mLightnessTargets[INDEX_MIN] = MIN_NORMAL_LUMA;
263        target.mLightnessTargets[INDEX_TARGET] = TARGET_NORMAL_LUMA;
264        target.mLightnessTargets[INDEX_MAX] = MAX_NORMAL_LUMA;
265    }
266
267    private static void setDefaultLightLightnessValues(Target target) {
268        target.mLightnessTargets[INDEX_MIN] = MIN_LIGHT_LUMA;
269        target.mLightnessTargets[INDEX_TARGET] = TARGET_LIGHT_LUMA;
270    }
271
272    private static void setDefaultVibrantSaturationValues(Target target) {
273        target.mSaturationTargets[INDEX_MIN] = MIN_VIBRANT_SATURATION;
274        target.mSaturationTargets[INDEX_TARGET] = TARGET_VIBRANT_SATURATION;
275    }
276
277    private static void setDefaultMutedSaturationValues(Target target) {
278        target.mSaturationTargets[INDEX_TARGET] = TARGET_MUTED_SATURATION;
279        target.mSaturationTargets[INDEX_MAX] = MAX_MUTED_SATURATION;
280    }
281
282    /**
283     * Builder class for generating custom {@link Target} instances.
284     */
285    public final static class Builder {
286        private final Target mTarget;
287
288        /**
289         * Create a new {@link Target} builder from scratch.
290         */
291        public Builder() {
292            mTarget = new Target();
293        }
294
295        /**
296         * Create a new builder based on an existing {@link Target}.
297         */
298        public Builder(Target target) {
299            mTarget = new Target(target);
300        }
301
302        /**
303         * Set the minimum saturation value for this target.
304         */
305        public Builder setMinimumSaturation(@FloatRange(from = 0, to = 1) float value) {
306            mTarget.mSaturationTargets[INDEX_MIN] = value;
307            return this;
308        }
309
310        /**
311         * Set the target/ideal saturation value for this target.
312         */
313        public Builder setTargetSaturation(@FloatRange(from = 0, to = 1) float value) {
314            mTarget.mSaturationTargets[INDEX_TARGET] = value;
315            return this;
316        }
317
318        /**
319         * Set the maximum saturation value for this target.
320         */
321        public Builder setMaximumSaturation(@FloatRange(from = 0, to = 1) float value) {
322            mTarget.mSaturationTargets[INDEX_MAX] = value;
323            return this;
324        }
325
326        /**
327         * Set the minimum lightness value for this target.
328         */
329        public Builder setMinimumLightness(@FloatRange(from = 0, to = 1) float value) {
330            mTarget.mLightnessTargets[INDEX_MIN] = value;
331            return this;
332        }
333
334        /**
335         * Set the target/ideal lightness value for this target.
336         */
337        public Builder setTargetLightness(@FloatRange(from = 0, to = 1) float value) {
338            mTarget.mLightnessTargets[INDEX_TARGET] = value;
339            return this;
340        }
341
342        /**
343         * Set the maximum lightness value for this target.
344         */
345        public Builder setMaximumLightness(@FloatRange(from = 0, to = 1) float value) {
346            mTarget.mLightnessTargets[INDEX_MAX] = value;
347            return this;
348        }
349
350        /**
351         * Set the weight of importance that this target will place on saturation values.
352         *
353         * <p>The larger the weight, relative to the other weights, the more important that a color
354         * being close to the target value has on selection.</p>
355         *
356         * <p>A weight of 0 means that it has no weight, and thus has no
357         * bearing on the selection.</p>
358         *
359         * @see #setTargetSaturation(float)
360         */
361        public Builder setSaturationWeight(@FloatRange(from = 0) float weight) {
362            mTarget.mWeights[INDEX_WEIGHT_SAT] = weight;
363            return this;
364        }
365
366        /**
367         * Set the weight of importance that this target will place on lightness values.
368         *
369         * <p>The larger the weight, relative to the other weights, the more important that a color
370         * being close to the target value has on selection.</p>
371         *
372         * <p>A weight of 0 means that it has no weight, and thus has no
373         * bearing on the selection.</p>
374         *
375         * @see #setTargetLightness(float)
376         */
377        public Builder setLightnessWeight(@FloatRange(from = 0) float weight) {
378            mTarget.mWeights[INDEX_WEIGHT_LUMA] = weight;
379            return this;
380        }
381
382        /**
383         * Set the weight of importance that this target will place on a color's population within
384         * the image.
385         *
386         * <p>The larger the weight, relative to the other weights, the more important that a
387         * color's population being close to the most populous has on selection.</p>
388         *
389         * <p>A weight of 0 means that it has no weight, and thus has no
390         * bearing on the selection.</p>
391         */
392        public Builder setPopulationWeight(@FloatRange(from = 0) float weight) {
393            mTarget.mWeights[INDEX_WEIGHT_POP] = weight;
394            return this;
395        }
396
397        /**
398         * Set whether any color selected for this target is exclusive to this target only.
399         * Defaults to true.
400         *
401         * @param exclusive true if any the color is exclusive to this target, or false is the
402         *                  color can be selected for other targets.
403         */
404        public Builder setExclusive(boolean exclusive) {
405            mTarget.mIsExclusive = exclusive;
406            return this;
407        }
408
409        /**
410         * Builds and returns the resulting {@link Target}.
411         */
412        public Target build() {
413            return mTarget;
414        }
415    }
416
417}