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
18
19 import android.graphics.Bitmap
20 import android.graphics.BitmapFactory
21 import android.os.Build
22 import android.util.DisplayMetrics
23 import androidx.annotation.RequiresApi
24 import androidx.compose.ui.graphics.colorspace.ColorSpace
25 import androidx.compose.ui.graphics.colorspace.ColorSpaces
26
27 /**
28 * Create an [ImageBitmap] from the given [Bitmap]. Note this does not create a copy of the original
29 * [Bitmap] and changes to it will modify the returned [ImageBitmap]
30 */
asImageBitmapnull31 fun Bitmap.asImageBitmap(): ImageBitmap = AndroidImageBitmap(this)
32
33 internal actual fun createImageBitmap(bytes: ByteArray): ImageBitmap {
34 return BitmapFactory.decodeByteArray(bytes, 0, bytes.size).asImageBitmap()
35 }
36
ActualImageBitmapnull37 internal actual fun ActualImageBitmap(
38 width: Int,
39 height: Int,
40 config: ImageBitmapConfig,
41 hasAlpha: Boolean,
42 colorSpace: ColorSpace
43 ): ImageBitmap {
44 val bitmapConfig = config.toBitmapConfig()
45 val bitmap: Bitmap
46 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
47 bitmap = Api26Bitmap.createBitmap(width, height, config, hasAlpha, colorSpace)
48 } else {
49 bitmap = Bitmap.createBitmap(null as DisplayMetrics?, width, height, bitmapConfig)
50 bitmap.setHasAlpha(hasAlpha)
51 }
52 return AndroidImageBitmap(bitmap)
53 }
54
55 /**
56 * @Throws UnsupportedOperationException if this [ImageBitmap] is not backed by an
57 * android.graphics.Bitmap
58 */
asAndroidBitmapnull59 fun ImageBitmap.asAndroidBitmap(): Bitmap =
60 when (this) {
61 is AndroidImageBitmap -> bitmap
62 else -> throw UnsupportedOperationException("Unable to obtain android.graphics.Bitmap")
63 }
64
65 internal class AndroidImageBitmap(internal val bitmap: Bitmap) : ImageBitmap {
66
67 override val width: Int
68 get() = bitmap.width
69
70 override val height: Int
71 get() = bitmap.height
72
73 override val config: ImageBitmapConfig
74 get() = bitmap.config!!.toImageConfig()
75
76 override val colorSpace: ColorSpace
77 get() =
78 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
<lambda>null79 with(Api26Bitmap) { bitmap.composeColorSpace() }
80 } else {
81 ColorSpaces.Srgb
82 }
83
readPixelsnull84 override fun readPixels(
85 buffer: IntArray,
86 startX: Int,
87 startY: Int,
88 width: Int,
89 height: Int,
90 bufferOffset: Int,
91 stride: Int
92 ) {
93 // Internal Android implementation that copies the pixels from the underlying
94 // android.graphics.Bitmap if the configuration supports it
95 val androidBitmap = asAndroidBitmap()
96 var recycleTarget = false
97 val targetBitmap =
98 if (
99 Build.VERSION.SDK_INT < Build.VERSION_CODES.O ||
100 androidBitmap.config != Bitmap.Config.HARDWARE
101 ) {
102 androidBitmap
103 } else {
104 // Because we are creating a copy for the purposes of reading pixels out of it
105 // be sure to recycle this temporary bitmap when we are finished with it.
106 recycleTarget = true
107
108 // Pixels of a hardware bitmap cannot be queried directly so make a copy
109 // of it into a configuration that can be queried
110 // Passing in false for the isMutable parameter as we only intend to read pixel
111 // information from the bitmap
112 androidBitmap.copy(Bitmap.Config.ARGB_8888, false)
113 }
114
115 targetBitmap.getPixels(buffer, bufferOffset, stride, startX, startY, width, height)
116 // Recycle the target if we are done with it
117 if (recycleTarget) {
118 targetBitmap.recycle()
119 }
120 }
121
122 override val hasAlpha: Boolean
123 get() = bitmap.hasAlpha()
124
prepareToDrawnull125 override fun prepareToDraw() {
126 bitmap.prepareToDraw()
127 }
128 }
129
toBitmapConfignull130 internal fun ImageBitmapConfig.toBitmapConfig(): Bitmap.Config {
131 // Cannot utilize when statements with enums that may have different sets of supported
132 // values between the compiled SDK and the platform version of the device.
133 // As a workaround use if/else statements
134 // See https://youtrack.jetbrains.com/issue/KT-30473 for details
135 return if (this == ImageBitmapConfig.Argb8888) {
136 Bitmap.Config.ARGB_8888
137 } else if (this == ImageBitmapConfig.Alpha8) {
138 Bitmap.Config.ALPHA_8
139 } else if (this == ImageBitmapConfig.Rgb565) {
140 Bitmap.Config.RGB_565
141 } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && this == ImageBitmapConfig.F16) {
142 Bitmap.Config.RGBA_F16
143 } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && this == ImageBitmapConfig.Gpu) {
144 Bitmap.Config.HARDWARE
145 } else {
146 Bitmap.Config.ARGB_8888
147 }
148 }
149
toImageConfignull150 internal fun Bitmap.Config.toImageConfig(): ImageBitmapConfig {
151 // Cannot utilize when statements with enums that may have different sets of supported
152 // values between the compiled SDK and the platform version of the device.
153 // As a workaround use if/else statements
154 // See https://youtrack.jetbrains.com/issue/KT-30473 for details
155 @Suppress("DEPRECATION")
156 return if (this == Bitmap.Config.ALPHA_8) {
157 ImageBitmapConfig.Alpha8
158 } else if (this == Bitmap.Config.RGB_565) {
159 ImageBitmapConfig.Rgb565
160 } else if (this == Bitmap.Config.ARGB_4444) {
161 ImageBitmapConfig.Argb8888 // Always upgrade to Argb_8888
162 } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && this == Bitmap.Config.RGBA_F16) {
163 ImageBitmapConfig.F16
164 } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && this == Bitmap.Config.HARDWARE) {
165 ImageBitmapConfig.Gpu
166 } else {
167 ImageBitmapConfig.Argb8888
168 }
169 }
170
171 /**
172 * Make Lint happy Separate class to contain all API calls that require API level 26 to assist in
173 * dead code elimination during compilation time
174 */
175 @RequiresApi(Build.VERSION_CODES.O)
176 internal object Api26Bitmap {
177 @JvmStatic
createBitmapnull178 internal fun createBitmap(
179 width: Int,
180 height: Int,
181 bitmapConfig: ImageBitmapConfig,
182 hasAlpha: Boolean,
183 colorSpace: ColorSpace
184 ): Bitmap {
185 // Note intentionally ignoring density in all cases
186 return Bitmap.createBitmap(
187 null,
188 width,
189 height,
190 bitmapConfig.toBitmapConfig(),
191 hasAlpha,
192 colorSpace.toAndroidColorSpace()
193 )
194 }
195
196 @JvmStatic
composeColorSpacenull197 internal fun Bitmap.composeColorSpace() = colorSpace?.toComposeColorSpace() ?: ColorSpaces.Srgb
198 }
199