AndroidImageBitmap.android.kt
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.compose.ui.graphics
import android.graphics.Bitmap
import android.os.Build
import android.util.DisplayMetrics
import androidx.annotation.RequiresApi
import androidx.compose.ui.graphics.colorspace.ColorSpace
import androidx.compose.ui.graphics.colorspace.ColorSpaces
/**
* Create an [ImageBitmap] from the given [Bitmap]. Note this does
* not create a copy of the original [Bitmap] and changes to it
* will modify the returned [ImageBitmap]
*/
fun Bitmap.asImageBitmap(): ImageBitmap = AndroidImageBitmap(this)
internal actual fun ActualImageBitmap(
width: Int,
height: Int,
config: ImageBitmapConfig,
hasAlpha: Boolean,
colorSpace: ColorSpace
): ImageBitmap {
val bitmapConfig = config.toBitmapConfig()
val bitmap: Bitmap
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
bitmap = Api26Bitmap.createBitmap(width, height, config, hasAlpha, colorSpace)
} else {
bitmap = Bitmap.createBitmap(
null as DisplayMetrics?,
width,
height,
bitmapConfig
)
bitmap.setHasAlpha(hasAlpha)
}
return AndroidImageBitmap(bitmap)
}
/**
* @Throws UnsupportedOperationException if this [ImageBitmap] is not backed by an
* android.graphics.Bitmap
*/
fun ImageBitmap.asAndroidBitmap(): Bitmap =
when (this) {
is AndroidImageBitmap -> bitmap
else -> throw UnsupportedOperationException("Unable to obtain android.graphics.Bitmap")
}
internal class AndroidImageBitmap(internal val bitmap: Bitmap) : ImageBitmap {
override val width: Int
get() = bitmap.width
override val height: Int
get() = bitmap.height
override val config: ImageBitmapConfig
get() = bitmap.config.toImageConfig()
override val colorSpace: ColorSpace
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
with(Api26Bitmap) {
bitmap.composeColorSpace()
}
} else {
ColorSpaces.Srgb
}
override fun readPixels(
buffer: IntArray,
startX: Int,
startY: Int,
width: Int,
height: Int,
bufferOffset: Int,
stride: Int
) {
// Internal Android implementation that copies the pixels from the underlying
// android.graphics.Bitmap if the configuration supports it
val androidBitmap = asAndroidBitmap()
var recycleTarget = false
val targetBitmap =
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O ||
androidBitmap.config != Bitmap.Config.HARDWARE
) {
androidBitmap
} else {
// Because we are creating a copy for the purposes of reading pixels out of it
// be sure to recycle this temporary bitmap when we are finished with it.
recycleTarget = true
// Pixels of a hardware bitmap cannot be queried directly so make a copy
// of it into a configuration that can be queried
// Passing in false for the isMutable parameter as we only intend to read pixel
// information from the bitmap
androidBitmap.copy(Bitmap.Config.ARGB_8888, false)
}
targetBitmap.getPixels(
buffer,
bufferOffset,
stride,
startX,
startY,
width,
height
)
// Recycle the target if we are done with it
if (recycleTarget) {
targetBitmap.recycle()
}
}
override val hasAlpha: Boolean
get() = bitmap.hasAlpha()
override fun prepareToDraw() {
bitmap.prepareToDraw()
}
}
internal fun ImageBitmapConfig.toBitmapConfig(): Bitmap.Config {
// Cannot utilize when statements with enums that may have different sets of supported
// values between the compiled SDK and the platform version of the device.
// As a workaround use if/else statements
// See https://youtrack.jetbrains.com/issue/KT-30473 for details
return if (this == ImageBitmapConfig.Argb8888) {
Bitmap.Config.ARGB_8888
} else if (this == ImageBitmapConfig.Alpha8) {
Bitmap.Config.ALPHA_8
} else if (this == ImageBitmapConfig.Rgb565) {
Bitmap.Config.RGB_565
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && this == ImageBitmapConfig.F16) {
Bitmap.Config.RGBA_F16
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && this == ImageBitmapConfig.Gpu) {
Bitmap.Config.HARDWARE
} else {
Bitmap.Config.ARGB_8888
}
}
internal fun Bitmap.Config.toImageConfig(): ImageBitmapConfig {
// Cannot utilize when statements with enums that may have different sets of supported
// values between the compiled SDK and the platform version of the device.
// As a workaround use if/else statements
// See https://youtrack.jetbrains.com/issue/KT-30473 for details
@Suppress("DEPRECATION")
return if (this == Bitmap.Config.ALPHA_8) {
ImageBitmapConfig.Alpha8
} else if (this == Bitmap.Config.RGB_565) {
ImageBitmapConfig.Rgb565
} else if (this == Bitmap.Config.ARGB_4444) {
ImageBitmapConfig.Argb8888 // Always upgrade to Argb_8888
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && this == Bitmap.Config.RGBA_F16) {
ImageBitmapConfig.F16
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && this == Bitmap.Config.HARDWARE) {
ImageBitmapConfig.Gpu
} else {
ImageBitmapConfig.Argb8888
}
}
/**
* Make Lint happy
* Separate class to contain all API calls that require API level 26 to assist in dead code
* elimination during compilation time
*/
private class Api26Bitmap {
@RequiresApi(Build.VERSION_CODES.O)
companion object {
internal fun createBitmap(
width: Int,
height: Int,
bitmapConfig: ImageBitmapConfig,
hasAlpha: Boolean,
colorSpace: ColorSpace
): Bitmap {
// Note intentionally ignoring density in all cases
return Bitmap.createBitmap(
null,
width,
height,
bitmapConfig.toBitmapConfig(),
hasAlpha,
colorSpace.toFrameworkColorSpace()
)
}
internal fun Bitmap.composeColorSpace() =
colorSpace?.composeColorSpace() ?: ColorSpaces.Srgb
internal fun ColorSpace.toFrameworkColorSpace(): android.graphics.ColorSpace {
val frameworkNamedSpace = when (this) {
ColorSpaces.Srgb -> android.graphics.ColorSpace.Named.SRGB
ColorSpaces.Aces -> android.graphics.ColorSpace.Named.ACES
ColorSpaces.Acescg -> android.graphics.ColorSpace.Named.ACESCG
ColorSpaces.AdobeRgb -> android.graphics.ColorSpace.Named.ADOBE_RGB
ColorSpaces.Bt2020 -> android.graphics.ColorSpace.Named.BT2020
ColorSpaces.Bt709 -> android.graphics.ColorSpace.Named.BT709
ColorSpaces.CieLab -> android.graphics.ColorSpace.Named.CIE_LAB
ColorSpaces.CieXyz -> android.graphics.ColorSpace.Named.CIE_XYZ
ColorSpaces.DciP3 -> android.graphics.ColorSpace.Named.DCI_P3
ColorSpaces.DisplayP3 -> android.graphics.ColorSpace.Named.DISPLAY_P3
ColorSpaces.ExtendedSrgb -> android.graphics.ColorSpace.Named.EXTENDED_SRGB
ColorSpaces.LinearExtendedSrgb ->
android.graphics.ColorSpace.Named.LINEAR_EXTENDED_SRGB
ColorSpaces.LinearSrgb -> android.graphics.ColorSpace.Named.LINEAR_SRGB
ColorSpaces.Ntsc1953 -> android.graphics.ColorSpace.Named.NTSC_1953
ColorSpaces.ProPhotoRgb -> android.graphics.ColorSpace.Named.PRO_PHOTO_RGB
ColorSpaces.SmpteC -> android.graphics.ColorSpace.Named.SMPTE_C
else -> android.graphics.ColorSpace.Named.SRGB
}
return android.graphics.ColorSpace.get(frameworkNamedSpace)
}
internal fun android.graphics.ColorSpace.composeColorSpace(): ColorSpace {
return when (this) {
android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.SRGB) ->
ColorSpaces.Srgb
android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.ACES) ->
ColorSpaces.Aces
android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.ACESCG) ->
ColorSpaces.Acescg
android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.ADOBE_RGB) ->
ColorSpaces.AdobeRgb
android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.BT2020) ->
ColorSpaces.Bt2020
android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.BT709) ->
ColorSpaces.Bt709
android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.CIE_LAB) ->
ColorSpaces.CieLab
android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.CIE_XYZ) ->
ColorSpaces.CieXyz
android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.DCI_P3) ->
ColorSpaces.DciP3
android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.DISPLAY_P3) ->
ColorSpaces.DisplayP3
android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.EXTENDED_SRGB) ->
ColorSpaces.ExtendedSrgb
android.graphics.ColorSpace.get(
android.graphics.ColorSpace.Named.LINEAR_EXTENDED_SRGB
) ->
ColorSpaces.LinearExtendedSrgb
android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.LINEAR_SRGB) ->
ColorSpaces.LinearSrgb
android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.NTSC_1953) ->
ColorSpaces.Ntsc1953
android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.PRO_PHOTO_RGB) ->
ColorSpaces.ProPhotoRgb
android.graphics.ColorSpace.get(android.graphics.ColorSpace.Named.SMPTE_C) ->
ColorSpaces.SmpteC
else -> ColorSpaces.Srgb
}
}
}
}