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