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