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.painter
18 
19 import androidx.compose.ui.geometry.Size
20 import androidx.compose.ui.graphics.ColorFilter
21 import androidx.compose.ui.graphics.FilterQuality
22 import androidx.compose.ui.graphics.ImageBitmap
23 import androidx.compose.ui.graphics.drawscope.DrawScope
24 import androidx.compose.ui.unit.IntOffset
25 import androidx.compose.ui.unit.IntSize
26 import androidx.compose.ui.unit.toSize
27 import androidx.compose.ui.util.fastRoundToInt
28 
29 /**
30  * [Painter] implementation used to draw an [ImageBitmap] into the provided canvas This
31  * implementation can handle applying alpha and [ColorFilter] to it's drawn result
32  *
33  * @param image The [ImageBitmap] to draw
34  * @param srcOffset Optional offset relative to [image] used to draw a subsection of the
35  *   [ImageBitmap]. By default this uses the origin of [image]
36  * @param srcSize Optional dimensions representing size of the subsection of [image] to draw Both
37  *   the offset and size must have the following requirements:
38  * 1) Left and top bounds must be greater than or equal to zero
39  * 2) Source size must be greater than zero
40  * 3) Source size must be less than or equal to the dimensions of [image]
41  *
42  * @param filterQuality Sampling algorithm applied to the [image] when it is scaled and drawn into
43  *   the destination. The default is [FilterQuality.Low] which scales using a bilinear sampling
44  *   algorithm
45  */
BitmapPainternull46 fun BitmapPainter(
47     image: ImageBitmap,
48     srcOffset: IntOffset = IntOffset.Zero,
49     srcSize: IntSize = IntSize(image.width, image.height),
50     filterQuality: FilterQuality = FilterQuality.Low
51 ): BitmapPainter =
52     BitmapPainter(image, srcOffset, srcSize).apply { this.filterQuality = filterQuality }
53 
54 /**
55  * [Painter] implementation used to draw an [ImageBitmap] into the provided canvas This
56  * implementation can handle applying alpha and [ColorFilter] to it's drawn result
57  *
58  * @param image The [ImageBitmap] to draw
59  * @param srcOffset Optional offset relative to [image] used to draw a subsection of the
60  *   [ImageBitmap]. By default this uses the origin of [image]
61  * @param srcSize Optional dimensions representing size of the subsection of [image] to draw Both
62  *   the offset and size must have the following requirements:
63  * 1) Left and top bounds must be greater than or equal to zero
64  * 2) Source size must be greater than zero
65  * 3) Source size must be less than or equal to the dimensions of [image]
66  */
67 class BitmapPainter(
68     private val image: ImageBitmap,
69     private val srcOffset: IntOffset = IntOffset.Zero,
70     private val srcSize: IntSize = IntSize(image.width, image.height)
71 ) : Painter() {
72 
73     // Not ideal, however, in order to maintain binary compatibility, leave this as an
74     // internal var that we can conditionally configure from a function constructor
75     // above.
76     // Unfortunately we cannot modify the primary constructor to introduce this optional
77     // parameter and we cannot introduce a secondary constructor that includes this parameter
78     // due to how Kotlin handles constructors with inline class parameters to maintain Java
79     // compatibility. See https://youtrack.jetbrains.com/issue/KT-31980
80     internal var filterQuality: FilterQuality = FilterQuality.Low
81 
82     private val size: IntSize = validateSize(srcOffset, srcSize)
83 
84     private var alpha: Float = 1.0f
85 
86     private var colorFilter: ColorFilter? = null
87 
onDrawnull88     override fun DrawScope.onDraw() {
89         drawImage(
90             image,
91             srcOffset,
92             srcSize,
93             dstSize =
94                 IntSize(
95                     this@onDraw.size.width.fastRoundToInt(),
96                     this@onDraw.size.height.fastRoundToInt()
97                 ),
98             alpha = alpha,
99             colorFilter = colorFilter,
100             filterQuality = filterQuality
101         )
102     }
103 
104     /** Return the dimension of the underlying [ImageBitmap] as it's intrinsic width and height */
105     override val intrinsicSize: Size
106         get() = size.toSize()
107 
applyAlphanull108     override fun applyAlpha(alpha: Float): Boolean {
109         this.alpha = alpha
110         return true
111     }
112 
applyColorFilternull113     override fun applyColorFilter(colorFilter: ColorFilter?): Boolean {
114         this.colorFilter = colorFilter
115         return true
116     }
117 
validateSizenull118     private fun validateSize(srcOffset: IntOffset, srcSize: IntSize): IntSize {
119         @Suppress("ExceptionMessage")
120         require(
121             srcOffset.x >= 0 &&
122                 srcOffset.y >= 0 &&
123                 srcSize.width >= 0 &&
124                 srcSize.height >= 0 &&
125                 srcSize.width <= image.width &&
126                 srcSize.height <= image.height
127         )
128         return srcSize
129     }
130 
equalsnull131     override fun equals(other: Any?): Boolean {
132         if (this === other) return true
133         if (other !is BitmapPainter) return false
134 
135         if (image != other.image) return false
136         if (srcOffset != other.srcOffset) return false
137         if (srcSize != other.srcSize) return false
138         if (filterQuality != other.filterQuality) return false
139         return true
140     }
141 
hashCodenull142     override fun hashCode(): Int {
143         var result = image.hashCode()
144         result = 31 * result + srcOffset.hashCode()
145         result = 31 * result + srcSize.hashCode()
146         result = 31 * result + filterQuality.hashCode()
147         return result
148     }
149 
toStringnull150     override fun toString(): String {
151         return "BitmapPainter(image=$image, srcOffset=$srcOffset, srcSize=$srcSize, " +
152             "filterQuality=$filterQuality)"
153     }
154 }
155