1 /*
2 * Copyright 2019 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 androidx.compose.ui.graphics
18
19 import androidx.annotation.ColorInt
20 import androidx.annotation.FloatRange
21 import androidx.annotation.IntRange
22 import androidx.annotation.Size
23 import androidx.compose.runtime.Immutable
24 import androidx.compose.runtime.Stable
25 import androidx.compose.ui.graphics.colorspace.ColorModel
26 import androidx.compose.ui.graphics.colorspace.ColorSpace
27 import androidx.compose.ui.graphics.colorspace.ColorSpaces
28 import androidx.compose.ui.graphics.colorspace.Rgb
29 import androidx.compose.ui.graphics.colorspace.connect
30 import androidx.compose.ui.util.fastCoerceIn
31 import androidx.compose.ui.util.lerp
32 import kotlin.math.max
33 import kotlin.math.min
34
35 /**
36 * The `Color` class contains color information to be used while painting in [Canvas]. `Color`
37 * supports [ColorSpace]s with 3 [components][ColorSpace.componentCount], plus one for [alpha].
38 *
39 * ### Creating
40 *
41 * `Color` can be created with one of these methods:
42 *
43 * // from 4 separate [Float] components. Alpha and ColorSpace are optional
44 * val rgbaWhiteFloat = Color(red = 1f, green = 1f, blue = 1f, alpha = 1f,
45 * ColorSpace.get(ColorSpaces.Srgb))
46 * // from a 32-bit SRGB color integer
47 * val fromIntWhite = Color(android.graphics.Color.WHITE)
48 * val fromLongBlue = Color(0xFF0000FF)
49 * // from SRGB integer component values. Alpha is optional
50 * val rgbaWhiteInt = Color(red = 0xFF, green = 0xFF, blue = 0xFF, alpha = 0xFF)
51 *
52 * ### Representation
53 *
54 * A `Color` always defines a color using 4 components packed in a single 64 bit long value. One of
55 * these components is always alpha while the other three components depend on the color space's
56 * [color model][ColorModel]. The most common color model is the [RGB][ColorModel.Rgb] model in
57 * which the components represent red, green, and blue values.
58 *
59 * **Component ranges:** the ranges defined in the tables below indicate the ranges that can be
60 * encoded in a color long. They do not represent the actual ranges as they may differ per color
61 * space. For instance, the RGB components of a color in the [Display P3][ColorSpaces.DisplayP3]
62 * color space use the `[0..1]` range. Please refer to the documentation of the various
63 * [color spaces][ColorSpaces] to find their respective ranges.
64 *
65 * **Alpha range:** while alpha is encoded in a color long using a 10 bit integer (thus using a
66 * range of `[0..1023]`), it is converted to and from `[0..1]` float values when decoding and
67 * encoding color longs.
68 *
69 * **sRGB color space:** for compatibility reasons and ease of use, `Color` encoded
70 * [sRGB][ColorSpaces.Srgb] colors do not use the same encoding as other color longs.
71 *
72 * ```
73 * | Component | Name | Size | Range |
74 * |-----------|-------------|---------|-----------------------|
75 * | [RGB][ColorSpace.Model.Rgb] color model |
76 * | R | Red | 16 bits | `[-65504.0, 65504.0]` |
77 * | G | Green | 16 bits | `[-65504.0, 65504.0]` |
78 * | B | Blue | 16 bits | `[-65504.0, 65504.0]` |
79 * | A | Alpha | 10 bits | `[0..1023]` |
80 * | | Color space | 6 bits | `[0..63]` |
81 * | [SRGB][ColorSpaces.Srgb] color space |
82 * | A | Alpha | 8 bits | `[0..255]` |
83 * | R | Red | 8 bits | `[0..255]` |
84 * | G | Green | 8 bits | `[0..255]` |
85 * | B | Blue | 8 bits | `[0..255]` |
86 * | X | Unused | 32 bits | `[0]` |
87 * | [XYZ][ColorSpace.Model.Xyz] color model |
88 * | X | X | 16 bits | `[-65504.0, 65504.0]` |
89 * | Y | Y | 16 bits | `[-65504.0, 65504.0]` |
90 * | Z | Z | 16 bits | `[-65504.0, 65504.0]` |
91 * | A | Alpha | 10 bits | `[0..1023]` |
92 * | | Color space | 6 bits | `[0..63]` |
93 * | [Lab][ColorSpace.Model.Lab] color model |
94 * | L | L | 16 bits | `[-65504.0, 65504.0]` |
95 * | a | a | 16 bits | `[-65504.0, 65504.0]` |
96 * | b | b | 16 bits | `[-65504.0, 65504.0]` |
97 * | A | Alpha | 10 bits | `[0..1023]` |
98 * | | Color space | 6 bits | `[0..63]` |
99 * ```
100 *
101 * The components in this table are listed in encoding order, which is why color longs in the RGB
102 * model are called RGBA colors (even if this doesn't quite hold for the special case of sRGB
103 * colors).
104 *
105 * The color encoding relies on half-precision float values (fp16). If you wish to know more about
106 * the limitations of half-precision float values, please refer to the documentation of the
107 * [Float16] class.
108 *
109 * The values returned by these methods depend on the color space encoded in the color long. The
110 * values are however typically in the `[0..1]` range for RGB colors. Please refer to the
111 * documentation of the various [color spaces][ColorSpaces] for the exact ranges.
112 */
113 @Immutable
114 @kotlin.jvm.JvmInline
115 value class Color(val value: ULong) {
116 /**
117 * Returns this color's color space.
118 *
119 * @return A non-null instance of [ColorSpace]
120 */
121 @Stable
122 val colorSpace: ColorSpace
123 get() = ColorSpaces.getColorSpace((value and 0x3fUL).toInt())
124
125 /**
126 * Converts this color from its color space to the specified color space. The conversion is done
127 * using the default rendering intent as specified by [ColorSpace.connect].
128 *
129 * @param colorSpace The destination color space, cannot be null
130 * @return A non-null color instance in the specified color space
131 */
convertnull132 fun convert(colorSpace: ColorSpace): Color {
133 // If the destination color space is the same as this color's color space,
134 // the connector we get will be the identity connector
135 val connector = this.colorSpace.connect(colorSpace)
136 return connector.transformToColor(this)
137 }
138
139 /**
140 * Returns the value of the red component in the range defined by this color's color space (see
141 * [ColorSpace.getMinValue] and [ColorSpace.getMaxValue]).
142 *
143 * If this color's color model is not [RGB][ColorModel.Rgb], calling this is the first component
144 * of the ColorSpace.
145 *
146 * @see alpha
147 * @see blue
148 * @see green
149 */
150 @Stable
151 val red: Float
152 get() {
153 return if ((value and 0x3fUL) == 0UL) {
154 ((value shr 48) and 0xffUL).toFloat() / 255.0f
155 } else {
156 halfToFloat(((value shr 48) and 0xffffUL).toShort())
157 }
158 }
159
160 /**
161 * Returns the value of the green component in the range defined by this color's color space
162 * (see [ColorSpace.getMinValue] and [ColorSpace.getMaxValue]).
163 *
164 * If this color's color model is not [RGB][ColorModel.Rgb], calling this is the second
165 * component of the ColorSpace.
166 *
167 * @see alpha
168 * @see red
169 * @see blue
170 */
171 @Stable
172 val green: Float
173 get() {
174 return if ((value and 0x3fUL) == 0UL) {
175 ((value shr 40) and 0xffUL).toFloat() / 255.0f
176 } else {
177 halfToFloat(((value shr 32) and 0xffffUL).toShort())
178 }
179 }
180
181 /**
182 * Returns the value of the blue component in the range defined by this color's color space (see
183 * [ColorSpace.getMinValue] and [ColorSpace.getMaxValue]).
184 *
185 * If this color's color model is not [RGB][ColorModel.Rgb], calling this is the third component
186 * of the ColorSpace.
187 *
188 * @see alpha
189 * @see red
190 * @see green
191 */
192 @Stable
193 val blue: Float
194 get() {
195 return if ((value and 0x3fUL) == 0UL) {
196 ((value shr 32) and 0xffUL).toFloat() / 255.0f
197 } else {
198 halfToFloat(((value shr 16) and 0xffffUL).toShort())
199 }
200 }
201
202 /**
203 * Returns the value of the alpha component in the range `[0..1]`.
204 *
205 * @see red
206 * @see green
207 * @see blue
208 */
209 @Stable
210 val alpha: Float
211 get() {
212 return if ((value and 0x3fUL) == 0UL) {
213 ((value shr 56) and 0xffUL).toFloat() / 255.0f
214 } else {
215 ((value shr 6) and 0x3ffUL).toFloat() / 1023.0f
216 }
217 }
218
component1null219 @Suppress("NOTHING_TO_INLINE") @Stable inline operator fun component1(): Float = red
220
221 @Suppress("NOTHING_TO_INLINE") @Stable inline operator fun component2(): Float = green
222
223 @Suppress("NOTHING_TO_INLINE") @Stable inline operator fun component3(): Float = blue
224
225 @Suppress("NOTHING_TO_INLINE") @Stable inline operator fun component4(): Float = alpha
226
227 @Suppress("NOTHING_TO_INLINE") @Stable inline operator fun component5(): ColorSpace = colorSpace
228
229 /**
230 * Copies the existing color, changing only the provided values. The [ColorSpace][colorSpace] of
231 * the returned [Color] is the same as this [colorSpace].
232 */
233 @Stable
234 fun copy(
235 alpha: Float = this.alpha,
236 red: Float = this.red,
237 green: Float = this.green,
238 blue: Float = this.blue
239 ): Color =
240 Color(red = red, green = green, blue = blue, alpha = alpha, colorSpace = this.colorSpace)
241
242 /**
243 * Returns a string representation of the object. This method returns a string equal to the
244 * value of:
245 *
246 * "Color($r, $g, $b, $a, ${colorSpace.name})"
247 *
248 * For instance, the string representation of opaque black in the sRGB color space is equal to
249 * the following value:
250 *
251 * Color(0.0, 0.0, 0.0, 1.0, sRGB IEC61966-2.1)
252 *
253 * @return A non-null string representation of the object
254 */
255 override fun toString(): String {
256 return "Color($red, $green, $blue, $alpha, ${colorSpace.name})"
257 }
258
259 companion object {
260 @Stable val Black = Color(0xFF000000)
261
262 @Stable val DarkGray = Color(0xFF444444)
263
264 @Stable val Gray = Color(0xFF888888)
265
266 @Stable val LightGray = Color(0xFFCCCCCC)
267
268 @Stable val White = Color(0xFFFFFFFF)
269
270 @Stable val Red = Color(0xFFFF0000)
271
272 @Stable val Green = Color(0xFF00FF00)
273
274 @Stable val Blue = Color(0xFF0000FF)
275
276 @Stable val Yellow = Color(0xFFFFFF00)
277
278 @Stable val Cyan = Color(0xFF00FFFF)
279
280 @Stable val Magenta = Color(0xFFFF00FF)
281
282 @Stable val Transparent = Color(0x00000000)
283
284 /**
285 * Because Color is an inline class, this represents an unset value without having to box
286 * the Color. It will be treated as [Transparent] when drawn. A Color can compare with
287 * [Unspecified] for equality or use [isUnspecified] to check for the unset value or
288 * [isSpecified] for any color that isn't [Unspecified].
289 */
290 @Stable val Unspecified = Color(0f, 0f, 0f, 0f, ColorSpaces.Unspecified)
291
292 /**
293 * Return a [Color] from [hue], [saturation], and [value] (HSV representation).
294 *
295 * @param hue The color value in the range (0..360), where 0 is red, 120 is green, and 240
296 * is blue
297 * @param saturation The amount of [hue] represented in the color in the range (0..1), where
298 * 0 has no color and 1 is fully saturated.
299 * @param alpha Alpha channel to apply to the computed color
300 * @param value The strength of the color, where 0 is black.
301 * @param colorSpace The RGB color space used to calculate the Color from the HSV values.
302 */
hsvnull303 fun hsv(
304 hue: Float,
305 saturation: Float,
306 value: Float,
307 alpha: Float = 1f,
308 colorSpace: Rgb = ColorSpaces.Srgb
309 ): Color {
310 requirePrecondition(hue in 0f..360f && saturation in 0f..1f && value in 0f..1f) {
311 "HSV ($hue, $saturation, $value) must be in range (0..360, 0..1, 0..1)"
312 }
313 val red = hsvToRgbComponent(5, hue, saturation, value)
314 val green = hsvToRgbComponent(3, hue, saturation, value)
315 val blue = hsvToRgbComponent(1, hue, saturation, value)
316 return Color(red, green, blue, alpha, colorSpace)
317 }
318
hsvToRgbComponentnull319 private fun hsvToRgbComponent(n: Int, h: Float, s: Float, v: Float): Float {
320 val k = (n.toFloat() + h / 60f) % 6f
321 return v - (v * s * max(0f, minOf(k, 4 - k, 1f)))
322 }
323
324 /**
325 * Return a [Color] from [hue], [saturation], and [lightness] (HSL representation).
326 *
327 * @param hue The color value in the range (0..360), where 0 is red, 120 is green, and 240
328 * is blue
329 * @param saturation The amount of [hue] represented in the color in the range (0..1), where
330 * 0 has no color and 1 is fully saturated.
331 * @param lightness A range of (0..1) where 0 is black, 0.5 is fully colored, and 1 is
332 * white.
333 * @param alpha Alpha channel to apply to the computed color
334 * @param colorSpace The RGB color space used to calculate the Color from the HSL values.
335 */
hslnull336 fun hsl(
337 hue: Float,
338 saturation: Float,
339 lightness: Float,
340 alpha: Float = 1f,
341 colorSpace: Rgb = ColorSpaces.Srgb
342 ): Color {
343 requirePrecondition(hue in 0f..360f && saturation in 0f..1f && lightness in 0f..1f) {
344 "HSL ($hue, $saturation, $lightness) must be in range (0..360, 0..1, 0..1)"
345 }
346 val red = hslToRgbComponent(0, hue, saturation, lightness)
347 val green = hslToRgbComponent(8, hue, saturation, lightness)
348 val blue = hslToRgbComponent(4, hue, saturation, lightness)
349 return Color(red, green, blue, alpha, colorSpace)
350 }
351
hslToRgbComponentnull352 private fun hslToRgbComponent(n: Int, h: Float, s: Float, l: Float): Float {
353 val k = (n.toFloat() + h / 30f) % 12f
354 val a = s * min(l, 1f - l)
355 return l - a * max(-1f, minOf(k - 3, 9 - k, 1f))
356 }
357 }
358 }
359
360 // Same as Color.Unspecified.packedValue, but avoids a getstatic
361 @PublishedApi internal const val UnspecifiedColor = 0x10UL
362
363 /**
364 * Create a [Color] by passing individual [red], [green], [blue], [alpha], and [colorSpace]
365 * components. The default [color space][ColorSpace] is [sRGB][ColorSpaces.Srgb] and the default
366 * [alpha] is `1.0` (opaque).
367 *
368 * If the [red], [green], or [blue] values are outside of the range defined by [colorSpace] (see
369 * [ColorSpace.getMinValue] and [ColorSpace.getMaxValue], these values get clamped appropriately to
370 * be within range.
371 *
372 * @throws IllegalArgumentException If [colorSpace] does not have [ColorSpace.componentCount] equal
373 * to 3.
374 * @throws IllegalArgumentException If [colorSpace] has an [ColorSpace.id] set to
375 * [ColorSpace.MinId].
376 */
377 @Stable
Colornull378 fun Color(
379 red: Float,
380 green: Float,
381 blue: Float,
382 alpha: Float = 1f,
383 colorSpace: ColorSpace = ColorSpaces.Srgb
384 ): Color {
385 if (colorSpace.isSrgb) {
386 val argb =
387 (((alpha.fastCoerceIn(0.0f, 1.0f) * 255.0f + 0.5f).toInt() shl 24) or
388 ((red.fastCoerceIn(0.0f, 1.0f) * 255.0f + 0.5f).toInt() shl 16) or
389 ((green.fastCoerceIn(0.0f, 1.0f) * 255.0f + 0.5f).toInt() shl 8) or
390 (blue.fastCoerceIn(0.0f, 1.0f) * 255.0f + 0.5f).toInt())
391 return Color(argb.toULong() shl 32)
392 }
393
394 requirePrecondition(colorSpace.componentCount == 3) {
395 "Color only works with ColorSpaces with 3 components"
396 }
397
398 val id = colorSpace.id
399 requirePrecondition(id != ColorSpace.MinId) {
400 "Unknown color space, please use a color space in ColorSpaces"
401 }
402
403 val r = floatToHalf(red.fastCoerceIn(colorSpace.getMinValue(0), colorSpace.getMaxValue(0)))
404 val g = floatToHalf(green.fastCoerceIn(colorSpace.getMinValue(1), colorSpace.getMaxValue(1)))
405 val b = floatToHalf(blue.fastCoerceIn(colorSpace.getMinValue(2), colorSpace.getMaxValue(2)))
406 val a = (alpha.fastCoerceIn(0.0f, 1.0f) * 1023.0f + 0.5f).toInt()
407
408 return Color(
409 (((r.toLong() and 0xffffL) shl 48) or
410 ((g.toLong() and 0xffffL) shl 32) or
411 ((b.toLong() and 0xffffL) shl 16) or
412 ((a.toLong() and 0x03ffL) shl 6) or
413 (id.toLong() and 0x003fL))
414 .toULong()
415 )
416 }
417
418 /**
419 * Create a [Color] by passing individual [red], [green], [blue], [alpha], and [colorSpace]
420 * components. This function is equivalent to [Color] but doesn't perform any check/validation of
421 * the parameters. It is meant to be used when the color space and values are known to be valid by
422 * construction, for instance when lerping colors.
423 */
424 @Stable
UncheckedColornull425 internal fun UncheckedColor(
426 red: Float,
427 green: Float,
428 blue: Float,
429 alpha: Float = 1f,
430 colorSpace: ColorSpace = ColorSpaces.Srgb
431 ): Color {
432 if (colorSpace.isSrgb) {
433 val argb =
434 (((alpha * 255.0f + 0.5f).toInt() shl 24) or
435 ((red * 255.0f + 0.5f).toInt() shl 16) or
436 ((green * 255.0f + 0.5f).toInt() shl 8) or
437 (blue * 255.0f + 0.5f).toInt())
438 return Color(argb.toULong() shl 32)
439 }
440
441 val r = floatToHalf(red)
442 val g = floatToHalf(green)
443 val b = floatToHalf(blue)
444
445 val a = (max(0.0f, min(alpha, 1.0f)) * 1023.0f + 0.5f).toInt()
446
447 val id = colorSpace.id
448
449 return Color(
450 (((r.toLong() and 0xffffL) shl 48) or
451 ((g.toLong() and 0xffffL) shl 32) or
452 ((b.toLong() and 0xffffL) shl 16) or
453 ((a.toLong() and 0x03ffL) shl 6) or
454 (id.toLong() and 0x003fL))
455 .toULong()
456 )
457 }
458
459 /**
460 * Creates a new [Color] instance from an ARGB color int. The resulting color is in the
461 * [sRGB][ColorSpaces.Srgb] color space.
462 *
463 * @param color The ARGB color int to create a <code>Color</code> from.
464 * @return A non-null instance of {@link Color}
465 */
466 @Stable
Colornull467 fun Color(@ColorInt color: Int): Color {
468 return Color(color.toULong() shl 32)
469 }
470
471 /**
472 * Creates a new [Color] instance from an ARGB color int. The resulting color is in the
473 * [sRGB][ColorSpaces.Srgb] color space. This is useful for specifying colors with alpha greater
474 * than 0x80 in numeric form without using [Long.toInt]:
475 *
476 * val color = Color(0xFF000080)
477 *
478 * @param color The 32-bit ARGB color int to create a <code>Color</code> from
479 * @return A non-null instance of {@link Color}
480 */
481 @Stable
Colornull482 fun Color(color: Long): Color {
483 return Color((color shl 32).toULong())
484 }
485
486 /**
487 * Creates a new [Color] instance from an ARGB color components. The resulting color is in the
488 * [sRGB][ColorSpaces.Srgb] color space. The default alpha value is `0xFF` (opaque).
489 *
490 * @param red The red component of the color, between 0 and 255.
491 * @param green The green component of the color, between 0 and 255.
492 * @param blue The blue component of the color, between 0 and 255.
493 * @param alpha The alpha component of the color, between 0 and 255.
494 * @return A non-null instance of {@link Color}
495 */
496 @Stable
Colornull497 fun Color(
498 @IntRange(from = 0, to = 0xFF) red: Int,
499 @IntRange(from = 0, to = 0xFF) green: Int,
500 @IntRange(from = 0, to = 0xFF) blue: Int,
501 @IntRange(from = 0, to = 0xFF) alpha: Int = 0xFF
502 ): Color {
503 val color =
504 ((alpha and 0xFF) shl 24) or
505 ((red and 0xFF) shl 16) or
506 ((green and 0xFF) shl 8) or
507 (blue and 0xFF)
508 return Color(color)
509 }
510
511 /**
512 * Linear interpolate between two [Colors][Color], [start] and [stop] with [fraction] fraction
513 * between the two. The [ColorSpace] of the result is always the [ColorSpace][Color.colorSpace] of
514 * [stop]. [fraction] should be between 0 and 1, inclusive. Interpolation is done in the
515 * [ColorSpaces.Oklab] color space.
516 */
517 @Stable
lerpnull518 fun lerp(start: Color, stop: Color, @FloatRange(from = 0.0, to = 1.0) fraction: Float): Color {
519 val colorSpace = ColorSpaces.Oklab
520 val startColor = start.convert(colorSpace)
521 val endColor = stop.convert(colorSpace)
522
523 val startAlpha = startColor.alpha
524 val startL = startColor.red
525 val startA = startColor.green
526 val startB = startColor.blue
527
528 val endAlpha = endColor.alpha
529 val endL = endColor.red
530 val endA = endColor.green
531 val endB = endColor.blue
532
533 // We need to clamp the input fraction since over/undershoot easing curves
534 // can yield fractions outside of the 0..1 range, which would in turn cause
535 // Lab/alpha values to be outside of the valid color range.
536 // Clamping the fraction is cheaper than clamping all 4 components separately.
537 val t = fraction.fastCoerceIn(0.0f, 1.0f)
538 val interpolated =
539 UncheckedColor(
540 lerp(startL, endL, t),
541 lerp(startA, endA, t),
542 lerp(startB, endB, t),
543 lerp(startAlpha, endAlpha, t),
544 colorSpace
545 )
546 return interpolated.convert(stop.colorSpace)
547 }
548
549 /**
550 * Composites [this] color on top of [background] using the Porter-Duff 'source over' mode.
551 *
552 * Both [this] and [background] must not be pre-multiplied, and the resulting color will also not be
553 * pre-multiplied.
554 *
555 * The [ColorSpace] of the result is always the [ColorSpace][Color.colorSpace] of [background].
556 *
557 * @return the [Color] representing [this] composited on top of [background], converted to the color
558 * space of [background].
559 */
560 @Stable
Colornull561 fun Color.compositeOver(background: Color): Color {
562 val fg = this.convert(background.colorSpace)
563
564 val bgA = background.alpha
565 val fgA = fg.alpha
566 val a = fgA + (bgA * (1f - fgA))
567
568 val r = compositeComponent(fg.red, background.red, fgA, bgA, a)
569 val g = compositeComponent(fg.green, background.green, fgA, bgA, a)
570 val b = compositeComponent(fg.blue, background.blue, fgA, bgA, a)
571
572 return UncheckedColor(r, g, b, a, background.colorSpace)
573 }
574
575 /**
576 * Composites the given [foreground component][fgC] over the [background component][bgC], with
577 * foreground and background alphas of [fgA] and [bgA] respectively.
578 *
579 * This uses a pre-calculated composite destination alpha of [a] for efficiency.
580 */
581 @Suppress("NOTHING_TO_INLINE")
compositeComponentnull582 private inline fun compositeComponent(fgC: Float, bgC: Float, fgA: Float, bgA: Float, a: Float) =
583 if (a == 0f) 0f else ((fgC * fgA) + ((bgC * bgA) * (1f - fgA))) / a
584
585 /**
586 * Returns this color's components as a new array. The last element of the array is always the alpha
587 * component.
588 *
589 * @return A new, non-null array whose size is 4
590 */
591 @Size(value = 4)
592 private fun Color.getComponents(): FloatArray = floatArrayOf(red, green, blue, alpha)
593
594 /**
595 * Returns the relative luminance of this color.
596 *
597 * Based on the formula for relative luminance defined in WCAG 2.0, W3C Recommendation 11
598 * December 2008.
599 *
600 * @return A value between 0 (darkest black) and 1 (lightest white)
601 * @throws IllegalArgumentException If the this color's color space does not use the
602 * [RGB][ColorModel.Rgb] color model
603 */
604 @Stable
605 fun Color.luminance(): Float {
606 val colorSpace = colorSpace
607 requirePrecondition(colorSpace.model == ColorModel.Rgb) {
608 "The specified color must be encoded in an RGB color space. " +
609 "The supplied color space is ${colorSpace.model}"
610 }
611
612 val eotf = (colorSpace as Rgb).eotfFunc
613 val r = eotf(red.toDouble())
614 val g = eotf(green.toDouble())
615 val b = eotf(blue.toDouble())
616
617 return (((0.2126 * r) + (0.7152 * g) + (0.0722 * b)).toFloat()).fastCoerceIn(0.0f, 1.0f)
618 }
619
620 /**
621 * Converts this color to an ARGB color int. A color int is always in the [sRGB][ColorSpaces.Srgb]
622 * color space. This implies a color space conversion is applied if needed.
623 *
624 * @return An ARGB color in the sRGB color space
625 */
626 @Stable
627 @ColorInt
Colornull628 fun Color.toArgb(): Int {
629 return (convert(ColorSpaces.Srgb).value shr 32).toInt()
630 }
631
632 /** `false` when this is [Color.Unspecified]. */
633 @Stable
634 inline val Color.isSpecified: Boolean
635 get() = value != UnspecifiedColor
636
637 /** `true` when this is [Color.Unspecified]. */
638 @Stable
639 inline val Color.isUnspecified: Boolean
640 get() = value == UnspecifiedColor
641
642 /**
643 * If this color [isSpecified] then this is returned, otherwise [block] is executed and its result
644 * is returned.
645 */
takeOrElsenull646 inline fun Color.takeOrElse(block: () -> Color): Color = if (isSpecified) this else block()
647
648 /**
649 * Alternative to `() -> Color` that's useful for avoiding boxing.
650 *
651 * Can be used as:
652 *
653 * fun nonBoxedArgs(color: ColorProducer?)
654 */
655 fun interface ColorProducer {
656 /** Return the color */
657 operator fun invoke(): Color
658 }
659