1 /*
<lambda>null2  * Copyright (C) 2024 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.ink.brush
18 
19 import androidx.annotation.ColorInt
20 import androidx.annotation.ColorLong
21 import androidx.annotation.FloatRange
22 import androidx.annotation.RestrictTo
23 import androidx.ink.brush.color.Color as ComposeColor
24 import androidx.ink.brush.color.toArgb
25 import androidx.ink.nativeloader.NativeLoader
26 import androidx.ink.nativeloader.UsedByNative
27 import kotlin.Float
28 import kotlin.jvm.JvmStatic
29 
30 /**
31  * Defines how stroke inputs are interpreted to create the visual representation of a stroke.
32  *
33  * The type completely describes how inputs are used to create stroke meshes, and how those meshes
34  * should be drawn by stroke renderers. In an analogous way to "font" and "font family", a [Brush]
35  * can be considered an instance of a [BrushFamily] with a particular [color], [size], and an extra
36  * parameter controlling visual fidelity, called [epsilon].
37  */
38 @Suppress("NotCloseable") // Finalize is only used to free the native peer.
39 public class Brush
40 private constructor(
41     /** A handle to the underlying native [Brush] object. */
42     @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public val nativePointer: Long,
43 
44     /** The [BrushFamily] for this brush. See [StockBrushes] for available [BrushFamily] values. */
45     public val family: BrushFamily,
46 ) {
47 
48     @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
49     public val composeColor: ComposeColor =
50         // Caching this because the native call is slow. Still doing the round-trip on construction
51         // to
52         // ensure this is excercised by tests and that deserialized brushes are consistent with
53         // newly
54         // constructed brushes.
55         ComposeColor(BrushNative.computeComposeColorLong(nativePointer).toULong())
56 
57     /**
58      * The overall thickness of strokes created with a given brush, in the same units as the stroke
59      * coordinate system. This must be at least as big as [epsilon].
60      */
61     @get:FloatRange(
62         from = 0.0,
63         fromInclusive = false,
64         to = Double.POSITIVE_INFINITY,
65         toInclusive = false,
66     )
67     public val size: Float
68         get() = BrushNative.getSize(nativePointer)
69 
70     /**
71      * The smallest distance for which two points should be considered visually distinct for stroke
72      * generation geometry purposes. Effectively, it is the visual fidelity of strokes created with
73      * this brush, where any (lack of) visual fidelity can be observed by a user the further zoomed
74      * in they are on the stroke. Lower values of [epsilon] result in higher fidelity strokes at the
75      * cost of somewhat higher memory usage. This value, like [size], is in the same units as the
76      * stroke coordinate system. A size of 0.1 physical pixels at the default zoom level is a good
77      * starting point that can tolerate a reasonable amount of zooming in with high quality visual
78      * results.
79      */
80     @get:FloatRange(
81         from = 0.0,
82         fromInclusive = false,
83         to = Double.POSITIVE_INFINITY,
84         toInclusive = false,
85     )
86     public val epsilon: Float
87         get() = BrushNative.getEpsilon(nativePointer)
88 
89     internal constructor(
90         family: BrushFamily,
91         composeColor: ComposeColor,
92         @FloatRange(
93             from = 0.0,
94             fromInclusive = false,
95             to = Double.POSITIVE_INFINITY,
96             toInclusive = false,
97         )
98         size: Float,
99         @FloatRange(
100             from = 0.0,
101             fromInclusive = false,
102             to = Double.POSITIVE_INFINITY,
103             toInclusive = false,
104         )
105         epsilon: Float,
106     ) : this(
107         composeColor.toColorInInkSupportedColorSpace().let { convertedColor ->
108             BrushNative.create(
109                 family.nativePointer,
110                 convertedColor.red,
111                 convertedColor.green,
112                 convertedColor.blue,
113                 convertedColor.alpha,
114                 convertedColor.colorSpace.toInkColorSpaceId(),
115                 size,
116                 epsilon,
117             )
118         },
119         family,
120     )
121 
122     /**
123      * The default color of a [Brush] is pure black. To set a custom color, use
124      * [createWithColorLong] or [createWithColorIntArgb].
125      */
126     public constructor(
127         family: BrushFamily,
128         @FloatRange(
129             from = 0.0,
130             fromInclusive = false,
131             to = Double.POSITIVE_INFINITY,
132             toInclusive = false,
133         )
134         size: Float,
135         @FloatRange(
136             from = 0.0,
137             fromInclusive = false,
138             to = Double.POSITIVE_INFINITY,
139             toInclusive = false,
140         )
141         epsilon: Float,
142     ) : this(family, DEFAULT_COMPOSE_COLOR, size, epsilon)
143 
144     /**
145      * The brush color as a [ColorLong], which can express colors in several different color spaces.
146      * sRGB and Display P3 are supported; a color in any other color space will be converted to
147      * Display P3.
148      */
149     public val colorLong: Long
150         @ColorLong get(): Long = composeColor.value.toLong()
151 
152     /**
153      * The brush color as a [ColorInt], which can only express colors in the sRGB color space. For
154      * clients that want to support wide-gamut colors, use [colorLong].
155      */
156     public val colorIntArgb: Int
157         @ColorInt get(): Int = composeColor.toArgb()
158 
159     // Base implementation of copy() that all public versions call.
160     private fun copy(family: BrushFamily, color: ComposeColor, size: Float, epsilon: Float): Brush {
161         return if (
162             family == this.family &&
163                 color == this.composeColor &&
164                 size == this.size &&
165                 epsilon == this.epsilon
166         ) {
167             // For a pure copy, return the same object, since it is immutable.
168             this
169         } else {
170             Brush(family, color, size, epsilon)
171         }
172     }
173 
174     /**
175      * Creates a copy of `this` and allows named properties to be altered while keeping the rest
176      * unchanged. To change the color, use [copyWithColorLong] or [copyWithColorIntArgb].
177      */
178     @JvmOverloads
179     public fun copy(
180         family: BrushFamily = this.family,
181         @FloatRange(
182             from = 0.0,
183             fromInclusive = false,
184             to = Double.POSITIVE_INFINITY,
185             toInclusive = false,
186         )
187         size: Float = this.size,
188         @FloatRange(
189             from = 0.0,
190             fromInclusive = false,
191             to = Double.POSITIVE_INFINITY,
192             toInclusive = false,
193         )
194         epsilon: Float = this.epsilon,
195     ): Brush = copy(family, this.composeColor, size, epsilon)
196 
197     /**
198      * Creates a copy of `this` and allows named properties to be altered while keeping the rest
199      * unchanged. The color is specified as a [ColorLong], which can encode several different color
200      * spaces. sRGB and Display P3 are supported; a color in any other color space will be converted
201      * to Display P3.
202      *
203      * Some libraries (notably Jetpack UI Graphics) use [ULong] for [ColorLong]s, so the caller must
204      * call [ULong.toLong] on such a value before passing it to this method.
205      */
206     @JvmOverloads
207     public fun copyWithColorLong(
208         @ColorLong colorLong: Long,
209         family: BrushFamily = this.family,
210         @FloatRange(
211             from = 0.0,
212             fromInclusive = false,
213             to = Double.POSITIVE_INFINITY,
214             toInclusive = false,
215         )
216         size: Float = this.size,
217         @FloatRange(
218             from = 0.0,
219             fromInclusive = false,
220             to = Double.POSITIVE_INFINITY,
221             toInclusive = false,
222         )
223         epsilon: Float = this.epsilon,
224     ): Brush = copy(family, ComposeColor(colorLong.toULong()), size, epsilon)
225 
226     /**
227      * Creates a copy of `this` and allows named properties to be altered while keeping the rest
228      * unchanged. The color is specified as a [ColorInt], which is in the sRGB color space by
229      * definition. Note that the [ColorInt] channel order puts alpha first (in the most significant
230      * byte).
231      *
232      * Kotlin interprets integer literals greater than `0x7fffffff` as [Long]s, so callers that want
233      * to specify a literal [ColorInt] with alpha >= 0x80 must call [Long.toInt] on the literal.
234      */
235     @JvmOverloads
236     public fun copyWithColorIntArgb(
237         @ColorInt colorIntArgb: Int,
238         family: BrushFamily = this.family,
239         @FloatRange(
240             from = 0.0,
241             fromInclusive = false,
242             to = Double.POSITIVE_INFINITY,
243             toInclusive = false,
244         )
245         size: Float = this.size,
246         @FloatRange(
247             from = 0.0,
248             fromInclusive = false,
249             to = Double.POSITIVE_INFINITY,
250             toInclusive = false,
251         )
252         epsilon: Float = this.epsilon,
253     ): Brush = copy(family, ComposeColor(colorIntArgb), size, epsilon)
254 
255     /**
256      * Returns a [Builder] with values set equivalent to `this`. Java developers, use the returned
257      * builder to build a copy of a Brush. Kotlin developers, see [copy] method.
258      */
259     public fun toBuilder(): Builder =
260         Builder().setFamily(family).setComposeColor(composeColor).setSize(size).setEpsilon(epsilon)
261 
262     /**
263      * Builder for [Brush].
264      *
265      * Use Brush.Builder to construct a [Brush] with default values, overriding only as needed.
266      */
267     public class Builder {
268         private var family: BrushFamily? = null
269         private var composeColor: ComposeColor = DEFAULT_COMPOSE_COLOR
270 
271         @FloatRange(
272             from = 0.0,
273             fromInclusive = false,
274             to = Double.POSITIVE_INFINITY,
275             toInclusive = false,
276         )
277         private var size: Float? = null
278 
279         @FloatRange(
280             from = 0.0,
281             fromInclusive = false,
282             to = Double.POSITIVE_INFINITY,
283             toInclusive = false,
284         )
285         private var epsilon: Float? = null
286 
287         /**
288          * Sets the [BrushFamily] for this brush. See [StockBrushes] for available [BrushFamily]
289          * values.
290          */
291         public fun setFamily(family: BrushFamily): Builder {
292             this.family = family
293             return this
294         }
295 
296         internal fun setComposeColor(color: ComposeColor): Builder {
297             this.composeColor = color
298             return this
299         }
300 
301         /**
302          * Sets the color using a [ColorLong], which can encode several different color spaces. sRGB
303          * and Display P3 are supported; a color in any other color space will be converted to
304          * Display P3.
305          *
306          * Some libraries (notably Jetpack UI Graphics) use [ULong] for [ColorLong]s, so the caller
307          * must call [ULong.toLong] on such a value before passing it to this method.
308          */
309         public fun setColorLong(@ColorLong colorLong: Long): Builder {
310             this.composeColor = ComposeColor(colorLong.toULong())
311             return this
312         }
313 
314         /**
315          * Sets the color using a [ColorInt], which is in the sRGB color space by definition. Note
316          * that the [ColorInt] channel order puts alpha first (in the most significant byte).
317          *
318          * Kotlin interprets integer literals greater than `0x7fffffff` as [Long]s, so Kotlin
319          * callers that want to specify a literal [ColorInt] with alpha >= 0x80 must call
320          * [Long.toInt] on the literal.
321          */
322         public fun setColorIntArgb(@ColorInt colorIntArgb: Int): Builder {
323             this.composeColor = ComposeColor(colorIntArgb)
324             return this
325         }
326 
327         public fun setSize(
328             @FloatRange(
329                 from = 0.0,
330                 fromInclusive = false,
331                 to = Double.POSITIVE_INFINITY,
332                 toInclusive = false,
333             )
334             size: Float
335         ): Builder {
336             this.size = size
337             return this
338         }
339 
340         public fun setEpsilon(
341             @FloatRange(
342                 from = 0.0,
343                 fromInclusive = false,
344                 to = Double.POSITIVE_INFINITY,
345                 toInclusive = false,
346             )
347             epsilon: Float
348         ): Builder {
349             this.epsilon = epsilon
350             return this
351         }
352 
353         public fun build(): Brush =
354             Brush(
355                 family =
356                     checkNotNull(family) {
357                         "brush family must be specified before calling build()"
358                     },
359                 composeColor = composeColor,
360                 size = checkNotNull(size) { "brush size must be specified before calling build()" },
361                 epsilon =
362                     checkNotNull(epsilon) {
363                         "brush epsilon must be specified before calling build()"
364                     },
365             )
366     }
367 
368     override fun equals(other: Any?): Boolean {
369         if (this === other) return true
370         if (other !is Brush) return false
371 
372         if (family != other.family) return false
373         if (composeColor != other.composeColor) return false
374         if (size != other.size) return false
375         if (epsilon != other.epsilon) return false
376 
377         return true
378     }
379 
380     // NOMUTANTS -- not testing exact hashCode values, just that equality implies the same hashCode.
381     override fun hashCode(): Int {
382         var result = family.hashCode()
383         result = 31 * result + composeColor.hashCode()
384         result = 31 * result + size.hashCode()
385         result = 31 * result + epsilon.hashCode()
386         return result
387     }
388 
389     override fun toString(): String {
390         return "Brush(family=$family, color=$composeColor, size=$size, epsilon=$epsilon)"
391     }
392 
393     /** Delete native Brush memory. */
394     protected fun finalize() {
395         // NOMUTANTS -- Not tested post garbage collection.
396         BrushNative.free(nativePointer)
397     }
398 
399     public companion object {
400         private val DEFAULT_COMPOSE_COLOR = ComposeColor.Black
401 
402         /**
403          * Construct a [BrushPaint] from an unowned heap-allocated native pointer to a C++
404          * `BrushPaint`. Kotlin wrapper objects nested under the [BrushPaint] are initialized
405          * similarly using their own [wrapNative] methods, passing those pointers to newly
406          * copy-constructed heap-allocated objects. That avoids the need to call Kotlin constructors
407          * for those objects from C++.
408          */
409         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
410         public fun wrapNative(unownedNativePointer: Long): Brush {
411             return Brush(
412                 unownedNativePointer,
413                 BrushFamily.wrapNative(BrushNative.newCopyOfBrushFamily(unownedNativePointer)),
414             )
415         }
416 
417         /**
418          * Returns a new [Brush] with the color specified by a [ColorLong], which can encode several
419          * different color spaces. sRGB and Display P3 are supported; a color in any other color
420          * space will be converted to Display P3.
421          *
422          * Some libraries (notably Jetpack UI Graphics) use [ULong] for [ColorLong]s, so the caller
423          * must call [ULong.toLong] on such a value before passing it to this method.
424          */
425         @JvmStatic
426         public fun createWithColorLong(
427             family: BrushFamily,
428             @ColorLong colorLong: Long,
429             size: Float,
430             epsilon: Float,
431         ): Brush = Brush(family, ComposeColor(colorLong.toULong()), size, epsilon)
432 
433         /**
434          * Returns a new [Brush] with the color specified by a [ColorInt], which is in the sRGB
435          * color space by definition. Note that the [ColorInt] channel order puts alpha first (in
436          * the most significant byte).
437          *
438          * Kotlin interprets integer literals greater than `0x7fffffff` as [Long]s, so callers that
439          * want to specify a literal [ColorInt] with alpha >= 0x80 must call [Long.toInt] on the
440          * literal.
441          */
442         @JvmStatic
443         public fun createWithColorIntArgb(
444             family: BrushFamily,
445             @ColorInt colorIntArgb: Int,
446             size: Float,
447             epsilon: Float,
448         ): Brush = Brush(family, ComposeColor(colorIntArgb), size, epsilon)
449 
450         /** Returns a new [Brush.Builder]. */
451         @JvmStatic public fun builder(): Builder = Builder()
452     }
453 }
454 
455 /** Singleton wrapper around native JNI calls. */
456 @UsedByNative
457 private object BrushNative {
458     init {
459         NativeLoader.load()
460     }
461 
462     /** Create underlying native object and return reference for all subsequent native calls. */
463     @UsedByNative
createnull464     public external fun create(
465         familyNativePointer: Long,
466         colorRed: Float,
467         colorGreen: Float,
468         colorBlue: Float,
469         colorAlpha: Float,
470         colorSpace: Int,
471         size: Float,
472         epsilon: Float,
473     ): Long
474 
475     /** Release the underlying memory allocated in [create]. */
476     @UsedByNative public external fun free(nativePointer: Long)
477 
478     @UsedByNative public external fun computeComposeColorLong(nativePointer: Long): Long
479 
480     /** This is a callback used by computeComposeColorLong. */
481     @UsedByNative
482     public fun composeColorLongFromComponents(
483         colorSpaceId: Int,
484         redGammaCorrected: Float,
485         greenGammaCorrected: Float,
486         blueGammaCorrected: Float,
487         alpha: Float,
488     ): Long =
489         ComposeColor(
490                 redGammaCorrected,
491                 greenGammaCorrected,
492                 blueGammaCorrected,
493                 alpha,
494                 colorSpace = composeColorSpaceFromInkColorSpaceId(colorSpaceId),
495             )
496             .value
497             .toLong()
498 
499     @UsedByNative public external fun getSize(nativePointer: Long): Float
500 
501     @UsedByNative public external fun getEpsilon(nativePointer: Long): Float
502 
503     /**
504      * Returns a new, unowned native pointer to a copy of the `BrushFamily` for the pointed-at
505      * native `Brush`.
506      */
507     @UsedByNative public external fun newCopyOfBrushFamily(nativePointer: Long): Long
508 }
509