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.compose.ui.geometry.Offset
20 import androidx.compose.ui.geometry.Rect
21 import androidx.compose.ui.graphics.internal.JvmDefaultWithCompatibility
22 import androidx.compose.ui.unit.IntOffset
23 import androidx.compose.ui.unit.IntSize
24 
25 /** Create a new Canvas instance that targets its drawing commands to the provided [ImageBitmap] */
Canvasnull26 fun Canvas(image: ImageBitmap): Canvas = ActualCanvas(image)
27 
28 internal expect fun ActualCanvas(image: ImageBitmap): Canvas
29 
30 expect class NativeCanvas
31 
32 /**
33  * Saves a copy of the current transform and clip on the save stack and executes the provided lambda
34  * with the current transform applied. Once the lambda has been executed, the transformation is
35  * popped from the stack, undoing the transformation.
36  *
37  * See also:
38  *
39  * [Canvas.saveLayer], which does the same thing but additionally also groups the commands
40  */
41 /* expect */ inline fun Canvas.withSave(block: () -> Unit) {
42     try {
43         save()
44         block()
45     } finally {
46         restore()
47     }
48 }
49 
50 /**
51  * Saves a copy of the current transform and clip on the save stack, and then creates a new group
52  * which subsequent calls will become a part of. When the lambda is executed and the save stack is
53  * popped, the group will be flattened into a layer and have the given `paint`'s [Paint.colorFilter]
54  * and [Paint.blendMode] applied.
55  *
56  * This lets you create composite effects, for example making a group of drawing commands
57  * semi-transparent. Without using [Canvas.saveLayer], each part of the group would be painted
58  * individually, so where they overlap would be darker than where they do not. By using
59  * [Canvas.saveLayer] to group them together, they can be drawn with an opaque color at first, and
60  * then the entire group can be made transparent using the [Canvas.saveLayer]'s paint.
61  *
62  * ## Using saveLayer with clips
63  *
64  * When a rectangular clip operation (from [Canvas.clipRect]) is not axis-aligned with the raster
65  * buffer, or when the clip operation is not rectalinear (e.g. because it is a rounded rectangle
66  * clip created by [Canvas.clipPath]), the edge of the clip needs to be anti-aliased.
67  *
68  * If two draw calls overlap at the edge of such a clipped region, without using [Canvas.saveLayer],
69  * the first drawing will be anti-aliased with the background first, and then the second will be
70  * anti-aliased with the result of blending the first drawing and the background. On the other hand,
71  * if [Canvas.saveLayer] is used immediately after establishing the clip, the second drawing will
72  * cover the first in the layer, and thus the second alone will be anti-aliased with the background
73  * when the layer is clipped and composited (when lambda is finished executing).
74  *
75  * ## Performance considerations
76  *
77  * Generally speaking, [Canvas.saveLayer] is relatively expensive.
78  *
79  * There are a several different hardware architectures for GPUs (graphics processing units, the
80  * hardware that handles graphics), but most of them involve batching commands and reordering them
81  * for performance. When layers are used, they cause the rendering pipeline to have to switch render
82  * target (from one layer to another). Render target switches can flush the GPU's command buffer,
83  * which typically means that optimizations that one could get with larger batching are lost. Render
84  * target switches also generate a lot of memory churn because the GPU needs to copy out the current
85  * frame buffer contents from the part of memory that's optimized for writing, and then needs to
86  * copy it back in once the previous render target (layer) is restored.
87  *
88  * See also:
89  * * [Canvas.save], which saves the current state, but does not create a new layer for subsequent
90  *   commands.
91  * * [BlendMode], which discusses the use of [Paint.blendMode] with [saveLayer].
92  */
93 @Suppress("DEPRECATION")
withSaveLayernull94 inline fun Canvas.withSaveLayer(bounds: Rect, paint: Paint, block: () -> Unit) {
95     try {
96         saveLayer(bounds, paint)
97         block()
98     } finally {
99         restore()
100     }
101 }
102 
103 /**
104  * Add a rotation (in degrees clockwise) to the current transform at the given pivot point. The
105  * pivot coordinate remains unchanged by the rotation transformation
106  *
107  * @param degrees to rotate clockwise
108  * @param pivotX The x-coord for the pivot point
109  * @param pivotY The y-coord for the pivot point
110  */
rotatenull111 fun Canvas.rotate(degrees: Float, pivotX: Float, pivotY: Float) {
112     if (degrees == 0.0f) return
113     translate(pivotX, pivotY)
114     rotate(degrees)
115     translate(-pivotX, -pivotY)
116 }
117 
118 /**
119  * Add a rotation (in radians clockwise) to the current transform at the given pivot point. The
120  * pivot coordinate remains unchanged by the rotation transformation
121  *
122  * @param radians Rotation transform to apply to the [Canvas]
123  * @param pivotX The x-coord for the pivot point
124  * @param pivotY The y-coord for the pivot point
125  */
Canvasnull126 fun Canvas.rotateRad(radians: Float, pivotX: Float = 0.0f, pivotY: Float = 0.0f) {
127     rotate(degrees(radians), pivotX, pivotY)
128 }
129 
130 /**
131  * Add an axis-aligned scale to the current transform, scaling by the first argument in the
132  * horizontal direction and the second in the vertical direction at the given pivot coordinate. The
133  * pivot coordinate remains unchanged by the scale transformation.
134  *
135  * If [sy] is unspecified, [sx] will be used for the scale in both directions.
136  *
137  * @param sx The amount to scale in X
138  * @param sy The amount to scale in Y
139  * @param pivotX The x-coord for the pivot point
140  * @param pivotY The y-coord for the pivot point
141  */
Canvasnull142 fun Canvas.scale(sx: Float, sy: Float = sx, pivotX: Float, pivotY: Float) {
143     if (sx == 1.0f && sy == 1.0f) return
144     translate(pivotX, pivotY)
145     scale(sx, sy)
146     translate(-pivotX, -pivotY)
147 }
148 
149 /** Return an instance of the native primitive that implements the Canvas interface */
150 expect val Canvas.nativeCanvas: NativeCanvas
151 
152 @JvmDefaultWithCompatibility
153 interface Canvas {
154 
155     /**
156      * Saves a copy of the current transform and clip on the save stack.
157      *
158      * Call [restore] to pop the save stack.
159      *
160      * See also:
161      * * [saveLayer], which does the same thing but additionally also groups the commands done until
162      *   the matching [restore].
163      */
savenull164     fun save()
165 
166     /**
167      * Pops the current save stack, if there is anything to pop. Otherwise, does nothing.
168      *
169      * Use [save] and [saveLayer] to push state onto the stack.
170      *
171      * If the state was pushed with with [saveLayer], then this call will also cause the new layer
172      * to be composited into the previous layer.
173      */
174     fun restore()
175 
176     /**
177      * Saves a copy of the current transform and clip on the save stack, and then creates a new
178      * group which subsequent calls will become a part of. When the save stack is later popped, the
179      * group will be flattened into a layer and have the given `paint`'s [Paint.colorFilter] and
180      * [Paint.blendMode] applied.
181      *
182      * This lets you create composite effects, for example making a group of drawing commands
183      * semi-transparent. Without using [saveLayer], each part of the group would be painted
184      * individually, so where they overlap would be darker than where they do not. By using
185      * [saveLayer] to group them together, they can be drawn with an opaque color at first, and then
186      * the entire group can be made transparent using the [saveLayer]'s paint.
187      *
188      * Call [restore] to pop the save stack and apply the paint to the group.
189      *
190      * ## Using saveLayer with clips
191      *
192      * When a rectangular clip operation (from [clipRect]) is not axis-aligned with the raster
193      * buffer, or when the clip operation is not rectalinear (e.g. because it is a rounded rectangle
194      * clip created by [clipPath], the edge of the clip needs to be anti-aliased.
195      *
196      * If two draw calls overlap at the edge of such a clipped region, without using [saveLayer],
197      * the first drawing will be anti-aliased with the background first, and then the second will be
198      * anti-aliased with the result of blending the first drawing and the background. On the other
199      * hand, if [saveLayer] is used immediately after establishing the clip, the second drawing will
200      * cover the first in the layer, and thus the second alone will be anti-aliased with the
201      * background when the layer is clipped and composited (when [restore] is called).
202      *
203      * (Incidentally, rather than using [clipPath] with a rounded rectangle defined in a path to
204      * draw rounded rectangles like this, prefer the [drawRoundRect] method.
205      *
206      * ## Performance considerations
207      *
208      * Generally speaking, [saveLayer] is relatively expensive.
209      *
210      * There are a several different hardware architectures for GPUs (graphics processing units, the
211      * hardware that handles graphics), but most of them involve batching commands and reordering
212      * them for performance. When layers are used, they cause the rendering pipeline to have to
213      * switch render target (from one layer to another). Render target switches can flush the GPU's
214      * command buffer, which typically means that optimizations that one could get with larger
215      * batching are lost. Render target switches also generate a lot of memory churn because the GPU
216      * needs to copy out the current frame buffer contents from the part of memory that's optimized
217      * for writing, and then needs to copy it back in once the previous render target (layer) is
218      * restored.
219      *
220      * See also:
221      * * [save], which saves the current state, but does not create a new layer for subsequent
222      *   commands.
223      * * [BlendMode], which discusses the use of [Paint.blendMode] with [saveLayer].
224      */
225     fun saveLayer(bounds: Rect, paint: Paint)
226 
227     /**
228      * Add a translation to the current transform, shifting the coordinate space horizontally by the
229      * first argument and vertically by the second argument.
230      */
231     fun translate(dx: Float, dy: Float)
232 
233     /**
234      * Add an axis-aligned scale to the current transform, scaling by the first argument in the
235      * horizontal direction and the second in the vertical direction.
236      *
237      * If [sy] is unspecified, [sx] will be used for the scale in both directions.
238      *
239      * @param sx The amount to scale in X
240      * @param sy The amount to scale in Y
241      */
242     fun scale(sx: Float, sy: Float = sx)
243 
244     /**
245      * Add a rotation (in degrees clockwise) to the current transform
246      *
247      * @param degrees to rotate clockwise
248      */
249     fun rotate(degrees: Float)
250 
251     /**
252      * Add an axis-aligned skew to the current transform, with the first argument being the
253      * horizontal skew in degrees clockwise around the origin, and the second argument being the
254      * vertical skew in degrees clockwise around the origin.
255      */
256     fun skew(sx: Float, sy: Float)
257 
258     /**
259      * Add an axis-aligned skew to the current transform, with the first argument being the
260      * horizontal skew in radians clockwise around the origin, and the second argument being the
261      * vertical skew in radians clockwise around the origin.
262      */
263     fun skewRad(sxRad: Float, syRad: Float) {
264         skew(degrees(sxRad), degrees(syRad))
265     }
266 
267     /**
268      * Multiply the current transform by the specified 4⨉4 transformation matrix specified as a list
269      * of values in column-major order.
270      */
concatnull271     fun concat(matrix: Matrix)
272 
273     /**
274      * Reduces the clip region to the intersection of the current clip and the given rectangle.
275      *
276      * Use [ClipOp.Difference] to subtract the provided rectangle from the current clip.
277      */
278     @Suppress("DEPRECATION")
279     fun clipRect(rect: Rect, clipOp: ClipOp = ClipOp.Intersect) =
280         clipRect(rect.left, rect.top, rect.right, rect.bottom, clipOp)
281 
282     /**
283      * Reduces the clip region to the intersection of the current clip and the given bounds.
284      *
285      * Use [ClipOp.Difference] to subtract the provided rectangle from the current clip.
286      *
287      * @param left Left bound of the clip region
288      * @param top Top bound of the clip region
289      * @param right Right bound of the clip region
290      * @param bottom Bottom bound of the clip region
291      * @param clipOp Clipping operation to conduct on the given bounds, defaults to
292      *   [ClipOp.Intersect]
293      */
294     fun clipRect(
295         left: Float,
296         top: Float,
297         right: Float,
298         bottom: Float,
299         clipOp: ClipOp = ClipOp.Intersect
300     )
301 
302     /** Reduces the clip region to the intersection of the current clip and the given [Path]. */
303     fun clipPath(path: Path, clipOp: ClipOp = ClipOp.Intersect)
304 
305     /**
306      * Draws a line between the given points using the given paint. The line is stroked, the value
307      * of the [Paint.style] is ignored for this call.
308      *
309      * The `p1` and `p2` arguments are interpreted as offsets from the origin.
310      */
311     fun drawLine(p1: Offset, p2: Offset, paint: Paint)
312 
313     /**
314      * Draws a rectangle with the given [Paint]. Whether the rectangle is filled or stroked (or
315      * both) is controlled by [Paint.style].
316      */
317     fun drawRect(rect: Rect, paint: Paint) =
318         drawRect(
319             left = rect.left,
320             top = rect.top,
321             right = rect.right,
322             bottom = rect.bottom,
323             paint = paint
324         )
325 
326     /**
327      * Draws a rectangle with the given [Paint]. Whether the rectangle is filled or stroked (or
328      * both) is controlled by [Paint.style].
329      *
330      * @param left The left bound of the rectangle
331      * @param top The top bound of the rectangle
332      * @param right The right bound of the rectangle
333      * @param bottom The bottom bound of the rectangle
334      * @param paint Paint used to color the rectangle with a fill or stroke
335      */
336     fun drawRect(left: Float, top: Float, right: Float, bottom: Float, paint: Paint)
337 
338     /**
339      * Draws a rounded rectangle with the given [Paint]. Whether the rectangle is filled or stroked
340      * (or both) is controlled by [Paint.style].
341      */
342     fun drawRoundRect(
343         left: Float,
344         top: Float,
345         right: Float,
346         bottom: Float,
347         radiusX: Float,
348         radiusY: Float,
349         paint: Paint
350     )
351 
352     /**
353      * Draws an axis-aligned oval that fills the given axis-aligned rectangle with the given
354      * [Paint]. Whether the oval is filled or stroked (or both) is controlled by [Paint.style].
355      */
356     fun drawOval(rect: Rect, paint: Paint) =
357         drawOval(
358             left = rect.left,
359             top = rect.top,
360             right = rect.right,
361             bottom = rect.bottom,
362             paint = paint
363         )
364 
365     /**
366      * Draws an axis-aligned oval that fills the given bounds provided with the given [Paint].
367      * Whether the rectangle is filled or stroked (or both) is controlled by [Paint.style].
368      *
369      * @param left The left bound of the rectangle
370      * @param top The top bound of the rectangle
371      * @param right The right bound of the rectangle
372      * @param bottom The bottom bound of the rectangle
373      * @param paint Paint used to color the rectangle with a fill or stroke
374      */
375     fun drawOval(left: Float, top: Float, right: Float, bottom: Float, paint: Paint)
376 
377     /**
378      * Draws a circle centered at the point given by the first argument and that has the radius
379      * given by the second argument, with the [Paint] given in the third argument. Whether the
380      * circle is filled or stroked (or both) is controlled by [Paint.style].
381      */
382     fun drawCircle(center: Offset, radius: Float, paint: Paint)
383 
384     /**
385      * Draw an arc scaled to fit inside the given rectangle. It starts from startAngle degrees
386      * around the oval up to startAngle + sweepAngle degrees around the oval, with zero degrees
387      * being the point on the right hand side of the oval that crosses the horizontal line that
388      * intersects the center of the rectangle and with positive angles going clockwise around the
389      * oval. If useCenter is true, the arc is closed back to the center, forming a circle sector.
390      * Otherwise, the arc is not closed, forming a circle segment.
391      *
392      * This method is optimized for drawing arcs and should be faster than [Path.arcTo].
393      */
394     fun drawArc(
395         rect: Rect,
396         startAngle: Float,
397         sweepAngle: Float,
398         useCenter: Boolean,
399         paint: Paint
400     ) =
401         drawArc(
402             left = rect.left,
403             top = rect.top,
404             right = rect.right,
405             bottom = rect.bottom,
406             startAngle = startAngle,
407             sweepAngle = sweepAngle,
408             useCenter = useCenter,
409             paint = paint
410         )
411 
412     /**
413      * Draw an arc scaled to fit inside the given rectangle. It starts from startAngle degrees
414      * around the oval up to startAngle + sweepAngle degrees around the oval, with zero degrees
415      * being the point on the right hand side of the oval that crosses the horizontal line that
416      * intersects the center of the rectangle and with positive angles going clockwise around the
417      * oval. If useCenter is true, the arc is closed back to the center, forming a circle sector.
418      * Otherwise, the arc is not closed, forming a circle segment.
419      *
420      * This method is optimized for drawing arcs and should be faster than [Path.arcTo].
421      *
422      * @param left Left bound of the arc
423      * @param top Top bound of the arc
424      * @param right Right bound of the arc
425      * @param bottom Bottom bound of the arc
426      * @param startAngle Starting angle of the arc relative to 3 o'clock
427      * @param sweepAngle Sweep angle in degrees clockwise
428      * @param useCenter Flag indicating whether or not to include the center of the oval in the
429      * @param paint Paint used to draw the arc. arc, and close it if it is being stroked. This will
430      *   draw a wedge.
431      */
432     fun drawArc(
433         left: Float,
434         top: Float,
435         right: Float,
436         bottom: Float,
437         startAngle: Float,
438         sweepAngle: Float,
439         useCenter: Boolean,
440         paint: Paint
441     )
442 
443     /**
444      * Draw an arc scaled to fit inside the given rectangle. It starts from startAngle radians
445      * around the oval up to startAngle + sweepAngle radians around the oval, with zero radians
446      * being the point on the right hand side of the oval that crosses the horizontal line that
447      * intersects the center of the rectangle and with positive angles going clockwise around the
448      * oval. If useCenter is true, the arc is closed back to the center, forming a circle sector.
449      * Otherwise, the arc is not closed, forming a circle segment.
450      *
451      * This method is optimized for drawing arcs and should be faster than [Path.arcTo].
452      */
453     fun drawArcRad(
454         rect: Rect,
455         startAngleRad: Float,
456         sweepAngleRad: Float,
457         useCenter: Boolean,
458         paint: Paint
459     ) {
460         drawArc(rect, degrees(startAngleRad), degrees(sweepAngleRad), useCenter, paint)
461     }
462 
463     /**
464      * Draws the given [Path] with the given [Paint]. Whether this shape is filled or stroked (or
465      * both) is controlled by [Paint.style]. If the path is filled, then subpaths within it are
466      * implicitly closed (see [Path.close]).
467      */
drawPathnull468     fun drawPath(path: Path, paint: Paint)
469 
470     /**
471      * Draws the given [ImageBitmap] into the canvas with its top-left corner at the given [Offset].
472      * The image is composited into the canvas using the given [Paint].
473      */
474     fun drawImage(image: ImageBitmap, topLeftOffset: Offset, paint: Paint)
475 
476     /**
477      * Draws the subset of the given image described by the `src` argument into the canvas in the
478      * axis-aligned rectangle given by the `dst` argument.
479      *
480      * This might sample from outside the `src` rect by up to half the width of an applied filter.
481      *
482      * @param image ImageBitmap to draw
483      * @param srcOffset: Optional offset representing the top left offset of the source image to
484      *   draw, this defaults to the origin of [image]
485      * @param srcSize: Optional dimensions of the source image to draw relative to [srcOffset], this
486      *   defaults the width and height of [image]
487      * @param dstOffset: Offset representing the top left offset of the destination image to draw
488      * @param dstSize: Dimensions of the destination to draw
489      * @param paint Paint used to composite the [ImageBitmap] pixels into the canvas
490      */
491     fun drawImageRect(
492         image: ImageBitmap,
493         srcOffset: IntOffset = IntOffset.Zero,
494         srcSize: IntSize = IntSize(image.width, image.height),
495         dstOffset: IntOffset = IntOffset.Zero,
496         dstSize: IntSize = srcSize,
497         paint: Paint
498     )
499 
500     /**
501      * Draws a sequence of points according to the given [PointMode].
502      *
503      * The `points` argument is interpreted as offsets from the origin.
504      *
505      * See also:
506      * * [drawRawPoints], which takes `points` as a [FloatArray] rather than a [List<Offset>].
507      */
508     fun drawPoints(pointMode: PointMode, points: List<Offset>, paint: Paint)
509 
510     /**
511      * Draws a sequence of points according to the given [PointMode].
512      *
513      * The `points` argument is interpreted as a list of pairs of floating point numbers, where each
514      * pair represents an x and y offset from the origin.
515      *
516      * See also:
517      * * [drawPoints], which takes `points` as a [List<Offset>] rather than a [List<Float32List>].
518      */
519     fun drawRawPoints(pointMode: PointMode, points: FloatArray, paint: Paint)
520 
521     fun drawVertices(vertices: Vertices, blendMode: BlendMode, paint: Paint)
522 
523     /**
524      * Enables Z support which defaults to disabled. This allows layers drawn with different
525      * elevations to be rearranged based on their elevation. It also enables rendering of shadows.
526      *
527      * @see disableZ
528      */
529     fun enableZ()
530 
531     /**
532      * Disables Z support, preventing any layers drawn after this point from being visually
533      * reordered or having shadows rendered. This is not impacted by any [save] or [restore] calls
534      * as it is not considered part of the matrix or clip.
535      *
536      * @see enableZ
537      */
538     fun disableZ()
539 }
540