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.vector
18 
19 import androidx.compose.ui.graphics.BlendMode
20 import androidx.compose.ui.graphics.Canvas
21 import androidx.compose.ui.graphics.Color
22 import androidx.compose.ui.graphics.ColorFilter
23 import androidx.compose.ui.graphics.ImageBitmap
24 import androidx.compose.ui.graphics.ImageBitmapConfig
25 import androidx.compose.ui.graphics.drawscope.CanvasDrawScope
26 import androidx.compose.ui.graphics.drawscope.DrawScope
27 import androidx.compose.ui.internal.checkPrecondition
28 import androidx.compose.ui.unit.Density
29 import androidx.compose.ui.unit.IntSize
30 import androidx.compose.ui.unit.LayoutDirection
31 import androidx.compose.ui.unit.toSize
32 
33 /**
34  * Creates a drawing environment that directs its drawing commands to an [ImageBitmap] which can be
35  * drawn directly in another [DrawScope] instance. This is useful to cache complicated drawing
36  * commands across frames especially if the content has not changed. Additionally some drawing
37  * operations such as rendering paths are done purely in software so it is beneficial to cache the
38  * result and render the contents directly through a texture as done by [DrawScope.drawImage]
39  */
40 internal class DrawCache {
41 
42     @PublishedApi internal var mCachedImage: ImageBitmap? = null
43     private var cachedCanvas: Canvas? = null
44     private var scopeDensity: Density? = null
45     private var layoutDirection: LayoutDirection = LayoutDirection.Ltr
46     private var size: IntSize = IntSize.Zero
47     private var config: ImageBitmapConfig = ImageBitmapConfig.Argb8888
48 
49     private val cacheScope = CanvasDrawScope()
50 
51     /**
52      * Draw the contents of the lambda with receiver scope into an [ImageBitmap] with the provided
53      * size. If the same size is provided across calls, the same [ImageBitmap] instance is re-used
54      * and the contents are cleared out before drawing content in it again
55      */
drawCachedImagenull56     fun drawCachedImage(
57         config: ImageBitmapConfig,
58         size: IntSize,
59         density: Density,
60         layoutDirection: LayoutDirection,
61         block: DrawScope.() -> Unit
62     ) {
63         this.scopeDensity = density
64         this.layoutDirection = layoutDirection
65         var targetImage = mCachedImage
66         var targetCanvas = cachedCanvas
67         if (
68             targetImage == null ||
69                 targetCanvas == null ||
70                 size.width > targetImage.width ||
71                 size.height > targetImage.height ||
72                 this.config != config
73         ) {
74             targetImage = ImageBitmap(size.width, size.height, config = config)
75             targetCanvas = Canvas(targetImage)
76 
77             mCachedImage = targetImage
78             cachedCanvas = targetCanvas
79             this.config = config
80         }
81         this.size = size
82         cacheScope.draw(density, layoutDirection, targetCanvas, size.toSize()) {
83             clear()
84             block()
85         }
86         targetImage.prepareToDraw()
87     }
88 
89     /** Draw the cached content into the provided [DrawScope] instance */
drawIntonull90     fun drawInto(target: DrawScope, alpha: Float = 1.0f, colorFilter: ColorFilter? = null) {
91         val targetImage = mCachedImage
92         checkPrecondition(targetImage != null) {
93             "drawCachedImage must be invoked first before attempting to draw the result " +
94                 "into another destination"
95         }
96         target.drawImage(targetImage, srcSize = size, alpha = alpha, colorFilter = colorFilter)
97     }
98 
99     /**
100      * Helper method to clear contents of the draw environment from the given bounds of the
101      * DrawScope
102      */
DrawScopenull103     private fun DrawScope.clear() {
104         drawRect(color = Color.Black, blendMode = BlendMode.Clear)
105     }
106 }
107