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