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