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 17 package android.support.v7.graphics; 18 19 import 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 */ 28 public 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 static final int INDEX_MIN = 0; 51 static final int INDEX_TARGET = 1; 52 static final int INDEX_MAX = 2; 53 54 static final int INDEX_WEIGHT_SAT = 0; 55 static final int INDEX_WEIGHT_LUMA = 1; 56 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 final float[] mSaturationTargets = new float[3]; 115 final float[] mLightnessTargets = new float[3]; 116 final float[] mWeights = new float[3]; 117 boolean mIsExclusive = true; // default to true 118 Target()119 Target() { 120 setTargetDefaultValues(mSaturationTargets); 121 setTargetDefaultValues(mLightnessTargets); 122 setDefaultWeights(); 123 } 124 Target(Target from)125 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) getMinimumSaturation()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) getTargetSaturation()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) getMaximumSaturation()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) getMinimumLightness()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) getTargetLightness()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) getMaximumLightness()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 */ getSaturationWeight()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 */ getLightnessWeight()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 */ getPopulationWeight()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 */ isExclusive()223 public boolean isExclusive() { 224 return mIsExclusive; 225 } 226 setTargetDefaultValues(final float[] values)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 setDefaultWeights()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 normalizeWeights()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 setDefaultDarkLightnessValues(Target target)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 setDefaultNormalLightnessValues(Target target)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 setDefaultLightLightnessValues(Target target)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 setDefaultVibrantSaturationValues(Target target)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 setDefaultMutedSaturationValues(Target target)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 */ Builder()291 public Builder() { 292 mTarget = new Target(); 293 } 294 295 /** 296 * Create a new builder based on an existing {@link Target}. 297 */ Builder(Target target)298 public Builder(Target target) { 299 mTarget = new Target(target); 300 } 301 302 /** 303 * Set the minimum saturation value for this target. 304 */ setMinimumSaturation(@loatRangefrom = 0, to = 1) float value)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 */ setTargetSaturation(@loatRangefrom = 0, to = 1) float value)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 */ setMaximumSaturation(@loatRangefrom = 0, to = 1) float value)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 */ setMinimumLightness(@loatRangefrom = 0, to = 1) float value)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 */ setTargetLightness(@loatRangefrom = 0, to = 1) float value)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 */ setMaximumLightness(@loatRangefrom = 0, to = 1) float value)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 */ setSaturationWeight(@loatRangefrom = 0) float weight)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 */ setLightnessWeight(@loatRangefrom = 0) float weight)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 */ setPopulationWeight(@loatRangefrom = 0) float weight)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 */ setExclusive(boolean exclusive)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 */ build()412 public Target build() { 413 return mTarget; 414 } 415 } 416 417 }