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}