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