1 /*
2 * Copyright 2020 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.drawscope
18
19 import androidx.annotation.FloatRange
20 import androidx.compose.ui.geometry.CornerRadius
21 import androidx.compose.ui.geometry.Offset
22 import androidx.compose.ui.geometry.Size
23 import androidx.compose.ui.geometry.center
24 import androidx.compose.ui.graphics.BlendMode
25 import androidx.compose.ui.graphics.Brush
26 import androidx.compose.ui.graphics.Canvas
27 import androidx.compose.ui.graphics.ClipOp
28 import androidx.compose.ui.graphics.Color
29 import androidx.compose.ui.graphics.ColorFilter
30 import androidx.compose.ui.graphics.FilterQuality
31 import androidx.compose.ui.graphics.ImageBitmap
32 import androidx.compose.ui.graphics.Matrix
33 import androidx.compose.ui.graphics.Paint
34 import androidx.compose.ui.graphics.PaintingStyle
35 import androidx.compose.ui.graphics.Path
36 import androidx.compose.ui.graphics.PathEffect
37 import androidx.compose.ui.graphics.PointMode
38 import androidx.compose.ui.graphics.StrokeCap
39 import androidx.compose.ui.graphics.StrokeJoin
40 import androidx.compose.ui.graphics.drawscope.DrawScope.Companion.DefaultFilterQuality
41 import androidx.compose.ui.graphics.layer.GraphicsLayer
42 import androidx.compose.ui.graphics.requirePrecondition
43 import androidx.compose.ui.unit.Density
44 import androidx.compose.ui.unit.IntOffset
45 import androidx.compose.ui.unit.IntSize
46 import androidx.compose.ui.unit.LayoutDirection
47
48 /**
49 * Implementation of [DrawScope] that issues drawing commands into the specified canvas and bounds
50 * via [CanvasDrawScope.draw]
51 */
52 class CanvasDrawScope : DrawScope {
53
54 @PublishedApi internal val drawParams = DrawParams()
55
56 override val layoutDirection: LayoutDirection
57 get() = drawParams.layoutDirection
58
59 override val density: Float
60 get() = drawParams.density.density
61
62 override val fontScale: Float
63 get() = drawParams.density.fontScale
64
65 override val drawContext =
66 object : DrawContext {
67 override var canvas: Canvas
68 get() = drawParams.canvas
69 set(value) {
70 drawParams.canvas = value
71 }
72
73 override var size: Size
74 get() = drawParams.size
75 set(value) {
76 drawParams.size = value
77 }
78
79 override val transform: DrawTransform = asDrawTransform()
80
81 override var layoutDirection: LayoutDirection
82 get() = drawParams.layoutDirection
83 set(value) {
84 drawParams.layoutDirection = value
85 }
86
87 override var density: Density
88 get() = drawParams.density
89 set(value) {
90 drawParams.density = value
91 }
92
93 override var graphicsLayer: GraphicsLayer? = null
94 }
95
96 /**
97 * Internal [Paint] used only for drawing filled in shapes with a color or gradient This is
98 * lazily allocated on the first drawing command that uses the [Fill] [DrawStyle] and re-used
99 * across subsequent calls
100 */
101 private var fillPaint: Paint? = null
102
103 /**
104 * Internal [Paint] used only for drawing stroked shapes with a color or gradient This is lazily
105 * allocated on the first drawing command that uses the [Stroke] [DrawStyle] and re-used across
106 * subsequent calls
107 */
108 private var strokePaint: Paint? = null
109
110 /** @see [DrawScope.drawLine] */
drawLinenull111 override fun drawLine(
112 brush: Brush,
113 start: Offset,
114 end: Offset,
115 strokeWidth: Float,
116 cap: StrokeCap,
117 pathEffect: PathEffect?,
118 @FloatRange(from = 0.0, to = 1.0) alpha: Float,
119 colorFilter: ColorFilter?,
120 blendMode: BlendMode
121 ) =
122 drawParams.canvas.drawLine(
123 start,
124 end,
125 configureStrokePaint(
126 brush,
127 strokeWidth,
128 Stroke.DefaultMiter,
129 cap,
130 StrokeJoin.Miter,
131 pathEffect,
132 alpha,
133 colorFilter,
134 blendMode
135 )
136 )
137
138 /** @see [DrawScope.drawLine] */
139 override fun drawLine(
140 color: Color,
141 start: Offset,
142 end: Offset,
143 strokeWidth: Float,
144 cap: StrokeCap,
145 pathEffect: PathEffect?,
146 @FloatRange(from = 0.0, to = 1.0) alpha: Float,
147 colorFilter: ColorFilter?,
148 blendMode: BlendMode
149 ) =
150 drawParams.canvas.drawLine(
151 start,
152 end,
153 configureStrokePaint(
154 color,
155 strokeWidth,
156 Stroke.DefaultMiter,
157 cap,
158 StrokeJoin.Miter,
159 pathEffect,
160 alpha,
161 colorFilter,
162 blendMode
163 )
164 )
165
166 /** @see [DrawScope.drawRect] */
167 override fun drawRect(
168 brush: Brush,
169 topLeft: Offset,
170 size: Size,
171 @FloatRange(from = 0.0, to = 1.0) alpha: Float,
172 style: DrawStyle,
173 colorFilter: ColorFilter?,
174 blendMode: BlendMode
175 ) =
176 drawParams.canvas.drawRect(
177 left = topLeft.x,
178 top = topLeft.y,
179 right = topLeft.x + size.width,
180 bottom = topLeft.y + size.height,
181 paint = configurePaint(brush, style, alpha, colorFilter, blendMode)
182 )
183
184 /** @see [DrawScope.drawRect] */
185 override fun drawRect(
186 color: Color,
187 topLeft: Offset,
188 size: Size,
189 @FloatRange(from = 0.0, to = 1.0) alpha: Float,
190 style: DrawStyle,
191 colorFilter: ColorFilter?,
192 blendMode: BlendMode
193 ) =
194 drawParams.canvas.drawRect(
195 left = topLeft.x,
196 top = topLeft.y,
197 right = topLeft.x + size.width,
198 bottom = topLeft.y + size.height,
199 paint = configurePaint(color, style, alpha, colorFilter, blendMode)
200 )
201
202 /** @see [DrawScope.drawImage] */
203 override fun drawImage(
204 image: ImageBitmap,
205 topLeft: Offset,
206 @FloatRange(from = 0.0, to = 1.0) alpha: Float,
207 style: DrawStyle,
208 colorFilter: ColorFilter?,
209 blendMode: BlendMode
210 ) =
211 drawParams.canvas.drawImage(
212 image,
213 topLeft,
214 configurePaint(null, style, alpha, colorFilter, blendMode)
215 )
216
217 /** @see [DrawScope.drawImage] */
218 @Deprecated(
219 "Prefer usage of drawImage that consumes an optional FilterQuality parameter",
220 replaceWith =
221 ReplaceWith(
222 "drawImage(image, srcOffset, srcSize, dstOffset, dstSize, alpha, style," +
223 " colorFilter, blendMode, FilterQuality.Low)",
224 "androidx.compose.ui.graphics.drawscope",
225 "androidx.compose.ui.graphics.FilterQuality"
226 ),
227 level = DeprecationLevel.HIDDEN
228 )
229 override fun drawImage(
230 image: ImageBitmap,
231 srcOffset: IntOffset,
232 srcSize: IntSize,
233 dstOffset: IntOffset,
234 dstSize: IntSize,
235 @FloatRange(from = 0.0, to = 1.0) alpha: Float,
236 style: DrawStyle,
237 colorFilter: ColorFilter?,
238 blendMode: BlendMode
239 ) =
240 drawParams.canvas.drawImageRect(
241 image,
242 srcOffset,
243 srcSize,
244 dstOffset,
245 dstSize,
246 configurePaint(null, style, alpha, colorFilter, blendMode)
247 )
248
249 /** @see [DrawScope.drawImage] */
250 override fun drawImage(
251 image: ImageBitmap,
252 srcOffset: IntOffset,
253 srcSize: IntSize,
254 dstOffset: IntOffset,
255 dstSize: IntSize,
256 @FloatRange(from = 0.0, to = 1.0) alpha: Float,
257 style: DrawStyle,
258 colorFilter: ColorFilter?,
259 blendMode: BlendMode,
260 filterQuality: FilterQuality
261 ) =
262 drawParams.canvas.drawImageRect(
263 image,
264 srcOffset,
265 srcSize,
266 dstOffset,
267 dstSize,
268 configurePaint(null, style, alpha, colorFilter, blendMode, filterQuality)
269 )
270
271 /** @see [DrawScope.drawRoundRect] */
272 override fun drawRoundRect(
273 brush: Brush,
274 topLeft: Offset,
275 size: Size,
276 cornerRadius: CornerRadius,
277 @FloatRange(from = 0.0, to = 1.0) alpha: Float,
278 style: DrawStyle,
279 colorFilter: ColorFilter?,
280 blendMode: BlendMode
281 ) =
282 drawParams.canvas.drawRoundRect(
283 topLeft.x,
284 topLeft.y,
285 topLeft.x + size.width,
286 topLeft.y + size.height,
287 cornerRadius.x,
288 cornerRadius.y,
289 configurePaint(brush, style, alpha, colorFilter, blendMode)
290 )
291
292 /** @see [DrawScope.drawRoundRect] */
293 override fun drawRoundRect(
294 color: Color,
295 topLeft: Offset,
296 size: Size,
297 cornerRadius: CornerRadius,
298 style: DrawStyle,
299 @FloatRange(from = 0.0, to = 1.0) alpha: Float,
300 colorFilter: ColorFilter?,
301 blendMode: BlendMode
302 ) =
303 drawParams.canvas.drawRoundRect(
304 topLeft.x,
305 topLeft.y,
306 topLeft.x + size.width,
307 topLeft.y + size.height,
308 cornerRadius.x,
309 cornerRadius.y,
310 configurePaint(color, style, alpha, colorFilter, blendMode)
311 )
312
313 /** @see [DrawScope.drawCircle] */
314 override fun drawCircle(
315 brush: Brush,
316 radius: Float,
317 center: Offset,
318 @FloatRange(from = 0.0, to = 1.0) alpha: Float,
319 style: DrawStyle,
320 colorFilter: ColorFilter?,
321 blendMode: BlendMode
322 ) =
323 drawParams.canvas.drawCircle(
324 center,
325 radius,
326 configurePaint(brush, style, alpha, colorFilter, blendMode)
327 )
328
329 /** @see [DrawScope.drawCircle] */
330 override fun drawCircle(
331 color: Color,
332 radius: Float,
333 center: Offset,
334 @FloatRange(from = 0.0, to = 1.0) alpha: Float,
335 style: DrawStyle,
336 colorFilter: ColorFilter?,
337 blendMode: BlendMode
338 ) =
339 drawParams.canvas.drawCircle(
340 center,
341 radius,
342 configurePaint(color, style, alpha, colorFilter, blendMode)
343 )
344
345 /** @see [DrawScope.drawOval] */
346 override fun drawOval(
347 brush: Brush,
348 topLeft: Offset,
349 size: Size,
350 @FloatRange(from = 0.0, to = 1.0) alpha: Float,
351 style: DrawStyle,
352 colorFilter: ColorFilter?,
353 blendMode: BlendMode
354 ) =
355 drawParams.canvas.drawOval(
356 left = topLeft.x,
357 top = topLeft.y,
358 right = topLeft.x + size.width,
359 bottom = topLeft.y + size.height,
360 paint = configurePaint(brush, style, alpha, colorFilter, blendMode)
361 )
362
363 /** @see [DrawScope.drawOval] */
364 override fun drawOval(
365 color: Color,
366 topLeft: Offset,
367 size: Size,
368 @FloatRange(from = 0.0, to = 1.0) alpha: Float,
369 style: DrawStyle,
370 colorFilter: ColorFilter?,
371 blendMode: BlendMode
372 ) =
373 drawParams.canvas.drawOval(
374 left = topLeft.x,
375 top = topLeft.y,
376 right = topLeft.x + size.width,
377 bottom = topLeft.y + size.height,
378 paint = configurePaint(color, style, alpha, colorFilter, blendMode)
379 )
380
381 /** @see [DrawScope.drawArc] */
382 override fun drawArc(
383 brush: Brush,
384 startAngle: Float,
385 sweepAngle: Float,
386 useCenter: Boolean,
387 topLeft: Offset,
388 size: Size,
389 @FloatRange(from = 0.0, to = 1.0) alpha: Float,
390 style: DrawStyle,
391 colorFilter: ColorFilter?,
392 blendMode: BlendMode
393 ) =
394 drawParams.canvas.drawArc(
395 left = topLeft.x,
396 top = topLeft.y,
397 right = topLeft.x + size.width,
398 bottom = topLeft.y + size.height,
399 startAngle = startAngle,
400 sweepAngle = sweepAngle,
401 useCenter = useCenter,
402 paint = configurePaint(brush, style, alpha, colorFilter, blendMode)
403 )
404
405 /** @see [DrawScope.drawArc] */
406 override fun drawArc(
407 color: Color,
408 startAngle: Float,
409 sweepAngle: Float,
410 useCenter: Boolean,
411 topLeft: Offset,
412 size: Size,
413 @FloatRange(from = 0.0, to = 1.0) alpha: Float,
414 style: DrawStyle,
415 colorFilter: ColorFilter?,
416 blendMode: BlendMode
417 ) =
418 drawParams.canvas.drawArc(
419 left = topLeft.x,
420 top = topLeft.y,
421 right = topLeft.x + size.width,
422 bottom = topLeft.y + size.height,
423 startAngle = startAngle,
424 sweepAngle = sweepAngle,
425 useCenter = useCenter,
426 paint = configurePaint(color, style, alpha, colorFilter, blendMode)
427 )
428
429 /** @see [DrawScope.drawPath] */
430 override fun drawPath(
431 path: Path,
432 color: Color,
433 @FloatRange(from = 0.0, to = 1.0) alpha: Float,
434 style: DrawStyle,
435 colorFilter: ColorFilter?,
436 blendMode: BlendMode
437 ) =
438 drawParams.canvas.drawPath(
439 path,
440 configurePaint(color, style, alpha, colorFilter, blendMode)
441 )
442
443 /** @see [DrawScope.drawPath] */
444 override fun drawPath(
445 path: Path,
446 brush: Brush,
447 @FloatRange(from = 0.0, to = 1.0) alpha: Float,
448 style: DrawStyle,
449 colorFilter: ColorFilter?,
450 blendMode: BlendMode
451 ) =
452 drawParams.canvas.drawPath(
453 path,
454 configurePaint(brush, style, alpha, colorFilter, blendMode)
455 )
456
457 /** @see [DrawScope.drawPoints] */
458 override fun drawPoints(
459 points: List<Offset>,
460 pointMode: PointMode,
461 color: Color,
462 strokeWidth: Float,
463 cap: StrokeCap,
464 pathEffect: PathEffect?,
465 @FloatRange(from = 0.0, to = 1.0) alpha: Float,
466 colorFilter: ColorFilter?,
467 blendMode: BlendMode
468 ) =
469 drawParams.canvas.drawPoints(
470 pointMode,
471 points,
472 configureStrokePaint(
473 color,
474 strokeWidth,
475 Stroke.DefaultMiter,
476 cap,
477 StrokeJoin.Miter,
478 pathEffect,
479 alpha,
480 colorFilter,
481 blendMode
482 )
483 )
484
485 /** @see [DrawScope.drawPoints] */
486 override fun drawPoints(
487 points: List<Offset>,
488 pointMode: PointMode,
489 brush: Brush,
490 strokeWidth: Float,
491 cap: StrokeCap,
492 pathEffect: PathEffect?,
493 @FloatRange(from = 0.0, to = 1.0) alpha: Float,
494 colorFilter: ColorFilter?,
495 blendMode: BlendMode
496 ) =
497 drawParams.canvas.drawPoints(
498 pointMode,
499 points,
500 configureStrokePaint(
501 brush,
502 strokeWidth,
503 Stroke.DefaultMiter,
504 cap,
505 StrokeJoin.Miter,
506 pathEffect,
507 alpha,
508 colorFilter,
509 blendMode
510 )
511 )
512
513 /**
514 * Draws into the provided [Canvas] with the commands specified in the lambda with this
515 * [DrawScope] as a receiver
516 *
517 * @param density [Density] used to assist in conversions of density independent pixels to raw
518 * pixels to draw
519 * @param layoutDirection [LayoutDirection] of the layout being drawn in.
520 * @param canvas target canvas to render into
521 * @param size bounds relative to the current canvas translation in which the [DrawScope] should
522 * draw within
523 * @param block lambda that is called to issue drawing commands on this [DrawScope]
524 */
525 inline fun draw(
526 density: Density,
527 layoutDirection: LayoutDirection,
528 canvas: Canvas,
529 size: Size,
530 block: DrawScope.() -> Unit
531 ) {
532 // Remember the previous drawing parameters in case we are temporarily re-directing our
533 // drawing to a separate Layer/RenderNode only to draw that content back into the original
534 // Canvas. If there is no previous canvas that was being drawing into, this ends up
535 // resetting these parameters back to defaults defensively
536 val (prevDensity, prevLayoutDirection, prevCanvas, prevSize) = drawParams
537 drawParams.apply {
538 this.density = density
539 this.layoutDirection = layoutDirection
540 this.canvas = canvas
541 this.size = size
542 }
543 canvas.save()
544 this.block()
545 canvas.restore()
546 drawParams.apply {
547 this.density = prevDensity
548 this.layoutDirection = prevLayoutDirection
549 this.canvas = prevCanvas
550 this.size = prevSize
551 }
552 }
553
554 /**
555 * Internal published APIs used to support inline scoped extension methods on DrawScope
556 * directly, without exposing the underlying stateful APIs to conduct the transformations
557 * themselves as inline methods require all methods called within them to be public
558 */
559
560 /**
561 * Helper method to instantiate the paint object on first usage otherwise return the previously
562 * allocated Paint used for drawing filled regions
563 */
obtainFillPaintnull564 private fun obtainFillPaint(): Paint =
565 fillPaint ?: Paint().apply { style = PaintingStyle.Fill }.also { fillPaint = it }
566
567 /**
568 * Helper method to instantiate the paint object on first usage otherwise return the previously
569 * allocated Paint used for drawing strokes
570 */
obtainStrokePaintnull571 private fun obtainStrokePaint(): Paint =
572 strokePaint ?: Paint().apply { style = PaintingStyle.Stroke }.also { strokePaint = it }
573
574 /**
575 * Selects the appropriate [Paint] object based on the style and applies the underlying
576 * [DrawStyle] parameters
577 */
selectPaintnull578 private fun selectPaint(drawStyle: DrawStyle): Paint =
579 when (drawStyle) {
580 Fill -> obtainFillPaint()
581 is Stroke ->
582 obtainStrokePaint().apply {
583 if (strokeWidth != drawStyle.width) strokeWidth = drawStyle.width
584 if (strokeCap != drawStyle.cap) strokeCap = drawStyle.cap
585 if (strokeMiterLimit != drawStyle.miter) strokeMiterLimit = drawStyle.miter
586 if (strokeJoin != drawStyle.join) strokeJoin = drawStyle.join
587 if (pathEffect != drawStyle.pathEffect) pathEffect = drawStyle.pathEffect
588 }
589 }
590
591 /**
592 * Helper method to configure the corresponding [Brush] along with other properties on the
593 * corresponding paint specified by [DrawStyle]
594 */
configurePaintnull595 private fun configurePaint(
596 brush: Brush?,
597 style: DrawStyle,
598 @FloatRange(from = 0.0, to = 1.0) alpha: Float,
599 colorFilter: ColorFilter?,
600 blendMode: BlendMode,
601 filterQuality: FilterQuality = DefaultFilterQuality
602 ): Paint =
603 selectPaint(style).apply {
604 if (brush != null) {
605 brush.applyTo(size, this, alpha)
606 } else {
607 if (this.shader != null) this.shader = null
608 if (this.color != Color.Black) this.color = Color.Black
609 if (this.alpha != alpha) this.alpha = alpha
610 }
611 if (this.colorFilter != colorFilter) this.colorFilter = colorFilter
612 if (this.blendMode != blendMode) this.blendMode = blendMode
613 if (this.filterQuality != filterQuality) this.filterQuality = filterQuality
614 }
615
616 /**
617 * Helper method to configure the corresponding [Color] along with other properties on the
618 * corresponding paint specified by [DrawStyle]
619 */
configurePaintnull620 private fun configurePaint(
621 color: Color,
622 style: DrawStyle,
623 @FloatRange(from = 0.0, to = 1.0) alpha: Float,
624 colorFilter: ColorFilter?,
625 blendMode: BlendMode,
626 filterQuality: FilterQuality = DefaultFilterQuality
627 ): Paint =
628 selectPaint(style).apply {
629 // Modulate the color alpha directly
630 // instead of configuring a separate alpha parameter
631 val targetColor = color.modulate(alpha)
632 if (this.color != targetColor) this.color = targetColor
633 if (this.shader != null) this.shader = null
634 if (this.colorFilter != colorFilter) this.colorFilter = colorFilter
635 if (this.blendMode != blendMode) this.blendMode = blendMode
636 if (this.filterQuality != filterQuality) this.filterQuality = filterQuality
637 }
638
configureStrokePaintnull639 private fun configureStrokePaint(
640 color: Color,
641 strokeWidth: Float,
642 miter: Float,
643 cap: StrokeCap,
644 join: StrokeJoin,
645 pathEffect: PathEffect?,
646 @FloatRange(from = 0.0, to = 1.0) alpha: Float,
647 colorFilter: ColorFilter?,
648 blendMode: BlendMode,
649 filterQuality: FilterQuality = DefaultFilterQuality
650 ) =
651 obtainStrokePaint().apply {
652 // Modulate the color alpha directly
653 // instead of configuring a separate alpha parameter
654 val targetColor = color.modulate(alpha)
655 if (this.color != targetColor) this.color = targetColor
656 if (this.shader != null) this.shader = null
657 if (this.colorFilter != colorFilter) this.colorFilter = colorFilter
658 if (this.blendMode != blendMode) this.blendMode = blendMode
659 if (this.strokeWidth != strokeWidth) this.strokeWidth = strokeWidth
660 if (this.strokeMiterLimit != miter) this.strokeMiterLimit = miter
661 if (this.strokeCap != cap) this.strokeCap = cap
662 if (this.strokeJoin != join) this.strokeJoin = join
663 if (this.pathEffect != pathEffect) this.pathEffect = pathEffect
664 if (this.filterQuality != filterQuality) this.filterQuality = filterQuality
665 }
666
configureStrokePaintnull667 private fun configureStrokePaint(
668 brush: Brush?,
669 strokeWidth: Float,
670 miter: Float,
671 cap: StrokeCap,
672 join: StrokeJoin,
673 pathEffect: PathEffect?,
674 @FloatRange(from = 0.0, to = 1.0) alpha: Float,
675 colorFilter: ColorFilter?,
676 blendMode: BlendMode,
677 filterQuality: FilterQuality = DefaultFilterQuality
678 ) =
679 obtainStrokePaint().apply {
680 if (brush != null) {
681 brush.applyTo(size, this, alpha)
682 } else if (this.alpha != alpha) {
683 this.alpha = alpha
684 }
685 if (this.colorFilter != colorFilter) this.colorFilter = colorFilter
686 if (this.blendMode != blendMode) this.blendMode = blendMode
687 if (this.strokeWidth != strokeWidth) this.strokeWidth = strokeWidth
688 if (this.strokeMiterLimit != miter) this.strokeMiterLimit = miter
689 if (this.strokeCap != cap) this.strokeCap = cap
690 if (this.strokeJoin != join) this.strokeJoin = join
691 if (this.pathEffect != pathEffect) this.pathEffect = pathEffect
692 if (this.filterQuality != filterQuality) this.filterQuality = filterQuality
693 }
694
695 /** Returns a [Color] modulated with the given alpha value */
modulatenull696 private fun Color.modulate(alpha: Float): Color =
697 if (alpha != 1.0f) {
698 copy(alpha = this.alpha * alpha)
699 } else {
700 this
701 }
702
703 /**
704 * Internal parameters to represent the current CanvasDrawScope used to reduce the size of the
705 * inline draw call to avoid bloat of additional assignment calls for each parameter
706 * individually
707 */
708 @PublishedApi
709 @Suppress("DataClassDefinition")
710 internal data class DrawParams(
711 var density: Density = DefaultDensity,
712 var layoutDirection: LayoutDirection = LayoutDirection.Ltr,
713 var canvas: Canvas = EmptyCanvas,
714 var size: Size = Size.Zero
715 )
716 }
717
718 /** Convenience method for creating a [DrawTransform] from the current [DrawContext] */
DrawContextnull719 private fun DrawContext.asDrawTransform(): DrawTransform =
720 object : DrawTransform {
721 override val size: Size
722 get() = this@asDrawTransform.size
723
724 override val center: Offset
725 get() = size.center
726
727 override fun inset(left: Float, top: Float, right: Float, bottom: Float) {
728 this@asDrawTransform.canvas.let {
729 val updatedSize = Size(size.width - (left + right), size.height - (top + bottom))
730 requirePrecondition(updatedSize.width >= 0 && updatedSize.height >= 0) {
731 "Width and height must be greater than or equal to zero"
732 }
733 this@asDrawTransform.size = updatedSize
734 it.translate(left, top)
735 }
736 }
737
738 override fun clipRect(
739 left: Float,
740 top: Float,
741 right: Float,
742 bottom: Float,
743 clipOp: ClipOp
744 ) {
745 this@asDrawTransform.canvas.clipRect(left, top, right, bottom, clipOp)
746 }
747
748 override fun clipPath(path: Path, clipOp: ClipOp) {
749 this@asDrawTransform.canvas.clipPath(path, clipOp)
750 }
751
752 override fun translate(left: Float, top: Float) {
753 this@asDrawTransform.canvas.translate(left, top)
754 }
755
756 override fun rotate(degrees: Float, pivot: Offset) {
757 this@asDrawTransform.canvas.apply {
758 translate(pivot.x, pivot.y)
759 rotate(degrees)
760 translate(-pivot.x, -pivot.y)
761 }
762 }
763
764 override fun scale(scaleX: Float, scaleY: Float, pivot: Offset) {
765 this@asDrawTransform.canvas.apply {
766 translate(pivot.x, pivot.y)
767 scale(scaleX, scaleY)
768 translate(-pivot.x, -pivot.y)
769 }
770 }
771
772 override fun transform(matrix: Matrix) {
773 this@asDrawTransform.canvas.concat(matrix)
774 }
775 }
776