From f02108fbcd5db027427855634958ff6da70ffb86 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Mon, 3 Mar 2025 20:54:05 +0100 Subject: [PATCH] decoding: RGBA_F16 to ARGB_8888 conversion --- .../deckers/thibault/aves/MainActivity.kt | 2 +- .../thibault/aves/WallpaperActivity.kt | 2 +- .../aves/channel/calls/AppAdapterHandler.kt | 4 +- .../aves/channel/calls/EmbeddedDataHandler.kt | 4 +- .../channel/calls/fetchers/RegionFetcher.kt | 4 +- .../calls/fetchers/SvgRegionFetcher.kt | 4 +- .../calls/fetchers/ThumbnailFetcher.kt | 4 +- .../calls/fetchers/TiffRegionFetcher.kt | 4 +- .../channel/streams/ImageByteStreamHandler.kt | 6 +- .../thibault/aves/decoder/SvgGlideModule.kt | 2 +- .../thibault/aves/decoder/TiffGlideModule.kt | 2 +- .../thibault/aves/model/SourceEntry.kt | 2 +- .../aves/model/provider/ImageProvider.kt | 4 +- .../thibault/aves/utils/BitmapUtils.kt | 78 +++++++++++++++---- .../thibault/aves/utils/StorageUtils.kt | 4 +- lib/services/common/decoding.dart | 5 +- 16 files changed, 91 insertions(+), 40 deletions(-) diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt b/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt index a0ef419ae..8fe051681 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt @@ -19,6 +19,7 @@ import androidx.annotation.RequiresApi import androidx.core.content.pm.ShortcutInfoCompat import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.graphics.drawable.IconCompat +import androidx.core.net.toUri import app.loup.streams_channel.StreamsChannel import deckers.thibault.aves.channel.AvesByteSendingMethodCodec import deckers.thibault.aves.channel.calls.AccessibilityHandler @@ -69,7 +70,6 @@ import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch import java.util.concurrent.CompletableFuture import java.util.concurrent.ConcurrentHashMap -import androidx.core.net.toUri // `FlutterFragmentActivity` because of local auth plugin open class MainActivity : FlutterFragmentActivity() { diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/WallpaperActivity.kt b/android/app/src/main/kotlin/deckers/thibault/aves/WallpaperActivity.kt index c1d634fcc..e06aea9fd 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/WallpaperActivity.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/WallpaperActivity.kt @@ -2,12 +2,12 @@ package deckers.thibault.aves import android.content.Intent import android.net.Uri +import androidx.core.net.toUri import deckers.thibault.aves.channel.calls.AppAdapterHandler import deckers.thibault.aves.model.FieldMap import deckers.thibault.aves.utils.getParcelableExtraCompat import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel -import androidx.core.net.toUri class WallpaperActivity : MainActivity() { private var originalIntent: String? = null diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppAdapterHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppAdapterHandler.kt index 71c68e64d..c0660be05 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppAdapterHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AppAdapterHandler.kt @@ -38,7 +38,7 @@ import deckers.thibault.aves.channel.calls.Coresult.Companion.safe import deckers.thibault.aves.channel.calls.Coresult.Companion.safeSuspend import deckers.thibault.aves.model.FieldMap import deckers.thibault.aves.utils.BitmapUtils -import deckers.thibault.aves.utils.BitmapUtils.getDecodedBytes +import deckers.thibault.aves.utils.BitmapUtils.getRawBytes import deckers.thibault.aves.utils.LogUtils import deckers.thibault.aves.utils.anyCauseIs import deckers.thibault.aves.utils.getApplicationInfoCompat @@ -175,7 +175,7 @@ class AppAdapterHandler(private val context: Context) : MethodCallHandler { try { val bitmap = withContext(Dispatchers.IO) { target.get() } - bytes = bitmap?.getDecodedBytes(recycle = false) + bytes = bitmap?.getRawBytes(recycle = false) } catch (e: Exception) { Log.w(LOG_TAG, "failed to decode app icon for packageName=$packageName", e) } diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/EmbeddedDataHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/EmbeddedDataHandler.kt index c1d21f51a..ac839727a 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/EmbeddedDataHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/EmbeddedDataHandler.kt @@ -22,7 +22,7 @@ import deckers.thibault.aves.model.FieldMap import deckers.thibault.aves.model.provider.ImageProvider import deckers.thibault.aves.model.provider.ImageProviderFactory.getProvider import deckers.thibault.aves.utils.BitmapUtils -import deckers.thibault.aves.utils.BitmapUtils.getDecodedBytes +import deckers.thibault.aves.utils.BitmapUtils.getRawBytes import deckers.thibault.aves.utils.FileUtils.transferFrom import deckers.thibault.aves.utils.LogUtils import deckers.thibault.aves.utils.MimeTypes @@ -74,7 +74,7 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler { val orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL) exif.thumbnailBitmap?.let { bitmap -> TransformationUtils.rotateImageExif(BitmapUtils.getBitmapPool(context), bitmap, orientation)?.let { - it.getDecodedBytes(recycle = false)?.let { bytes -> thumbnails.add(bytes) } + it.getRawBytes(recycle = false)?.let { bytes -> thumbnails.add(bytes) } } } } diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/RegionFetcher.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/RegionFetcher.kt index 7358d11d2..b4ce08252 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/RegionFetcher.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/RegionFetcher.kt @@ -14,7 +14,7 @@ import deckers.thibault.aves.decoder.AvesAppGlideModule import deckers.thibault.aves.decoder.MultiPageImage import deckers.thibault.aves.utils.BitmapRegionDecoderCompat import deckers.thibault.aves.utils.BitmapUtils -import deckers.thibault.aves.utils.BitmapUtils.getDecodedBytes +import deckers.thibault.aves.utils.BitmapUtils.getRawBytes import deckers.thibault.aves.utils.LogUtils import deckers.thibault.aves.utils.MathUtils import deckers.thibault.aves.utils.MemoryUtils @@ -132,7 +132,7 @@ class RegionFetcher internal constructor( bitmap = decoder.decodeRegion(effectiveRect, options) } - val bytes = bitmap?.getDecodedBytes(recycle = true) + val bytes = bitmap?.getRawBytes(recycle = true) if (bytes != null) { result.success(bytes) } else { diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/SvgRegionFetcher.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/SvgRegionFetcher.kt index 9708b1b79..59fb3b25d 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/SvgRegionFetcher.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/SvgRegionFetcher.kt @@ -14,7 +14,7 @@ import com.caverock.androidsvg.SVGParseException import deckers.thibault.aves.metadata.SVGParserBufferedInputStream import deckers.thibault.aves.metadata.SvgHelper.normalizeSize import deckers.thibault.aves.utils.BitmapUtils -import deckers.thibault.aves.utils.BitmapUtils.getDecodedBytes +import deckers.thibault.aves.utils.BitmapUtils.getRawBytes import deckers.thibault.aves.utils.MemoryUtils import deckers.thibault.aves.utils.StorageUtils import io.flutter.plugin.common.MethodChannel @@ -109,7 +109,7 @@ class SvgRegionFetcher internal constructor( svg.renderToCanvas(canvas, renderOptions) bitmap = Bitmap.createBitmap(bitmap, bleedX, bleedY, targetBitmapWidth, targetBitmapHeight) - val bytes = bitmap.getDecodedBytes(recycle = true) + val bytes = bitmap.getRawBytes(recycle = true) result.success(bytes) } catch (e: Exception) { result.error("fetch-read-exception", "failed to initialize region decoder for uri=$uri regionRect=$regionRect", e.message) diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/ThumbnailFetcher.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/ThumbnailFetcher.kt index e8739ba65..2dec1628a 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/ThumbnailFetcher.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/ThumbnailFetcher.kt @@ -16,7 +16,7 @@ import com.bumptech.glide.signature.ObjectKey import deckers.thibault.aves.decoder.AvesAppGlideModule import deckers.thibault.aves.decoder.MultiPageImage import deckers.thibault.aves.utils.BitmapUtils.applyExifOrientation -import deckers.thibault.aves.utils.BitmapUtils.getDecodedBytes +import deckers.thibault.aves.utils.BitmapUtils.getRawBytes import deckers.thibault.aves.utils.MimeTypes import deckers.thibault.aves.utils.MimeTypes.SVG import deckers.thibault.aves.utils.MimeTypes.isVideo @@ -77,7 +77,7 @@ class ThumbnailFetcher internal constructor( } } - val bytes = bitmap?.getDecodedBytes(recycle = false) + val bytes = bitmap?.getRawBytes(recycle = false) if (bytes != null) { result.success(bytes) } else { diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/TiffRegionFetcher.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/TiffRegionFetcher.kt index 953f81551..82f29f483 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/TiffRegionFetcher.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/TiffRegionFetcher.kt @@ -3,7 +3,7 @@ package deckers.thibault.aves.channel.calls.fetchers import android.content.Context import android.graphics.Rect import android.net.Uri -import deckers.thibault.aves.utils.BitmapUtils.getDecodedBytes +import deckers.thibault.aves.utils.BitmapUtils.getRawBytes import io.flutter.plugin.common.MethodChannel import org.beyka.tiffbitmapfactory.DecodeArea import org.beyka.tiffbitmapfactory.TiffBitmapFactory @@ -32,7 +32,7 @@ class TiffRegionFetcher internal constructor( inDecodeArea = DecodeArea(regionRect.left, regionRect.top, regionRect.width(), regionRect.height()) } val bitmap = TiffBitmapFactory.decodeFileDescriptor(fd, options) - val bytes = bitmap?.getDecodedBytes(recycle = true) + val bytes = bitmap?.getRawBytes(recycle = true) if (bytes != null) { result.success(bytes) } else { diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ImageByteStreamHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ImageByteStreamHandler.kt index ea74bb20c..91c88f3fa 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ImageByteStreamHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ImageByteStreamHandler.kt @@ -9,8 +9,8 @@ import androidx.core.net.toUri import com.bumptech.glide.Glide import deckers.thibault.aves.decoder.AvesAppGlideModule import deckers.thibault.aves.utils.BitmapUtils.applyExifOrientation -import deckers.thibault.aves.utils.BitmapUtils.getDecodedBytes import deckers.thibault.aves.utils.BitmapUtils.getEncodedBytes +import deckers.thibault.aves.utils.BitmapUtils.getRawBytes import deckers.thibault.aves.utils.LogUtils import deckers.thibault.aves.utils.MemoryUtils import deckers.thibault.aves.utils.MimeTypes @@ -155,7 +155,7 @@ class ImageByteStreamHandler(private val context: Context, private val arguments if (bitmap != null) { val recycle = false val bytes = if (decoded) { - bitmap.getDecodedBytes(recycle) + bitmap.getRawBytes(recycle) } else { bitmap.getEncodedBytes(canHaveAlpha = MimeTypes.canHaveAlpha(mimeType), recycle = recycle) } @@ -186,7 +186,7 @@ class ImageByteStreamHandler(private val context: Context, private val arguments if (bitmap != null) { val recycle = false val bytes = if (decoded) { - bitmap.getDecodedBytes(recycle) + bitmap.getRawBytes(recycle) } else { bitmap.getEncodedBytes(canHaveAlpha = false, recycle = false) } diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/decoder/SvgGlideModule.kt b/android/app/src/main/kotlin/deckers/thibault/aves/decoder/SvgGlideModule.kt index fc771398e..9f452bfe1 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/decoder/SvgGlideModule.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/decoder/SvgGlideModule.kt @@ -4,6 +4,7 @@ import android.content.Context import android.graphics.Bitmap import android.graphics.Canvas import android.net.Uri +import androidx.core.graphics.createBitmap import com.bumptech.glide.Glide import com.bumptech.glide.Priority import com.bumptech.glide.Registry @@ -22,7 +23,6 @@ import deckers.thibault.aves.metadata.SVGParserBufferedInputStream import deckers.thibault.aves.metadata.SvgHelper.normalizeSize import deckers.thibault.aves.utils.StorageUtils import kotlin.math.ceil -import androidx.core.graphics.createBitmap @GlideModule class SvgGlideModule : LibraryGlideModule() { diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/decoder/TiffGlideModule.kt b/android/app/src/main/kotlin/deckers/thibault/aves/decoder/TiffGlideModule.kt index ee0356782..5e7d4a81e 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/decoder/TiffGlideModule.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/decoder/TiffGlideModule.kt @@ -3,6 +3,7 @@ package deckers.thibault.aves.decoder import android.content.Context import android.graphics.Bitmap import android.net.Uri +import androidx.core.graphics.scale import com.bumptech.glide.Glide import com.bumptech.glide.Priority import com.bumptech.glide.Registry @@ -17,7 +18,6 @@ import com.bumptech.glide.load.model.MultiModelLoaderFactory import com.bumptech.glide.module.LibraryGlideModule import com.bumptech.glide.signature.ObjectKey import org.beyka.tiffbitmapfactory.TiffBitmapFactory -import androidx.core.graphics.scale @GlideModule class TiffGlideModule : LibraryGlideModule() { diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/model/SourceEntry.kt b/android/app/src/main/kotlin/deckers/thibault/aves/model/SourceEntry.kt index 69af47c16..4a3c83ca8 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/model/SourceEntry.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/model/SourceEntry.kt @@ -5,6 +5,7 @@ import android.content.Context import android.graphics.BitmapFactory import android.media.MediaMetadataRetriever import android.net.Uri +import androidx.core.net.toUri import com.drew.metadata.avi.AviDirectory import com.drew.metadata.exif.ExifIFD0Directory import com.drew.metadata.jpeg.JpegDirectory @@ -29,7 +30,6 @@ import deckers.thibault.aves.utils.UriUtils.tryParseId import org.beyka.tiffbitmapfactory.TiffBitmapFactory import java.io.IOException import androidx.exifinterface.media.ExifInterfaceFork as ExifInterface -import androidx.core.net.toUri class SourceEntry { private val origin: Int diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProvider.kt b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProvider.kt index 90b2b3359..39fbf091d 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProvider.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProvider.kt @@ -11,6 +11,7 @@ import android.net.Uri import android.os.Binder import android.os.Build import android.util.Log +import androidx.core.net.toUri import com.bumptech.glide.Glide import com.bumptech.glide.request.FutureTarget import com.commonsware.cwac.document.DocumentFileCompat @@ -32,6 +33,7 @@ import deckers.thibault.aves.metadata.PixyMetaHelper.xmpDocString import deckers.thibault.aves.metadata.metadataextractor.Helper import deckers.thibault.aves.metadata.xmp.GoogleXMP import deckers.thibault.aves.model.AvesEntry +import deckers.thibault.aves.model.EntryFields import deckers.thibault.aves.model.ExifOrientationOp import deckers.thibault.aves.model.FieldMap import deckers.thibault.aves.model.NameConflictResolution @@ -63,8 +65,6 @@ import java.util.Date import java.util.TimeZone import kotlin.math.absoluteValue import androidx.exifinterface.media.ExifInterfaceFork as ExifInterface -import androidx.core.net.toUri -import deckers.thibault.aves.model.EntryFields abstract class ImageProvider { open fun fetchSingle(context: Context, uri: Uri, sourceMimeType: String?, allowUnsized: Boolean, callback: ImageOpCallback) { diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/utils/BitmapUtils.kt b/android/app/src/main/kotlin/deckers/thibault/aves/utils/BitmapUtils.kt index 6f4121cf4..2375511bb 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/utils/BitmapUtils.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/utils/BitmapUtils.kt @@ -4,9 +4,9 @@ import android.content.Context import android.graphics.Bitmap import android.graphics.ColorSpace import android.os.Build +import android.util.Half import android.util.Log import androidx.annotation.RequiresApi -import androidx.core.graphics.createBitmap import com.bumptech.glide.Glide import com.bumptech.glide.load.resource.bitmap.TransformationUtils import deckers.thibault.aves.metadata.Metadata.getExifCode @@ -30,6 +30,8 @@ object BitmapUtils { private const val MAX_8_BITS_FLOAT = 0xff.toFloat() private const val MAX_10_BITS_FLOAT = 0x3ff.toFloat() + private const val RAW_BYTES_TRAILER_LENGTH = INT_BYTE_SIZE * 2 + // bytes per pixel with different bitmap config private const val BPP_ALPHA_8 = 1 private const val BPP_RGB_565 = 2 @@ -59,19 +61,15 @@ object BitmapUtils { return pixelCount * getBytePerPixel(config) } - fun Bitmap.getDecodedBytes(recycle: Boolean): ByteArray? { + fun Bitmap.getRawBytes(recycle: Boolean): ByteArray? { if (!MemoryUtils.canAllocate(byteCount)) { throw Exception("bitmap buffer is $byteCount bytes, which cannot be allocated to a new byte array") } try { - val bytes = ByteBuffer.allocate(byteCount + INT_BYTE_SIZE * 2).apply { + // `ByteBuffer` initial order is always `BIG_ENDIAN` + var bytes = ByteBuffer.allocate(byteCount + RAW_BYTES_TRAILER_LENGTH).apply { copyPixelsToBuffer(this) - // append bitmap size for use by the caller - putInt(width) - putInt(height) - - rewind() }.array() // convert pixel format and color space, if necessary @@ -81,10 +79,14 @@ object BitmapUtils { val connector = ColorSpace.connect(srcColorSpace, dstColorSpace) if (config == Bitmap.Config.ARGB_8888) { if (srcColorSpace != dstColorSpace) { - argb8888toArgb8888(bytes, connector, end = byteCount) + argb8888ToArgb8888(bytes, connector, end = byteCount) } + } else if (config == Bitmap.Config.RGBA_F16) { + rgbaf16ToArgb8888(bytes, connector, end = byteCount) + val newConfigByteCount = byteCount / (BPP_RGBA_F16 / BPP_ARGB_8888) + bytes = bytes.sliceArray(0..= Build.VERSION_CODES.TIRAMISU && config == Bitmap.Config.RGBA_1010102) { - rgba1010102toArgb8888(bytes, connector, end = byteCount) + rgba1010102ToArgb8888(bytes, connector, end = byteCount) } } } @@ -92,6 +94,14 @@ object BitmapUtils { // should not be called before accessing color space or other properties if (recycle) this.recycle() + // append bitmap size for use by the caller to interpret the raw bytes + val trailerOffset = bytes.size - RAW_BYTES_TRAILER_LENGTH + bytes = ByteBuffer.wrap(bytes).apply { + position(trailerOffset) + putInt(width) + putInt(height) + }.array() + return bytes } catch (e: Exception) { Log.e(LOG_TAG, "failed to get bytes from bitmap", e) @@ -111,8 +121,6 @@ object BitmapUtils { } } try { - // the Bitmap raw bytes are not decodable by Flutter - // we need to format them (compress, or add a BMP header) before sending them // `Bitmap.CompressFormat.PNG` is slower than `JPEG`, but it allows transparency // the BMP format allows an alpha channel, but Android decoding seems to ignore it if (canHaveAlpha && hasAlpha()) { @@ -139,8 +147,10 @@ object BitmapUtils { return null } + // convert bytes, without reallocation: + // - from original color space to sRGB. @RequiresApi(Build.VERSION_CODES.O) - private fun argb8888toArgb8888(bytes: ByteArray, connector: ColorSpace.Connector, start: Int = 0, end: Int = bytes.size) { + private fun argb8888ToArgb8888(bytes: ByteArray, connector: ColorSpace.Connector, start: Int = 0, end: Int = bytes.size) { // unpacking from ARGB_8888 and packing to ARGB_8888 // stored as [3,2,1,0] -> [AAAAAAAA BBBBBBBB GGGGGGGG RRRRRRRR] for (i in start.. [AAAAAAAA AAAAAAAA BBBBBBBB BBBBBBBB GGGGGGGG GGGGGGGG RRRRRRRR RRRRRRRR] + val i7 = bytes[i + 7].toInt() + val i6 = bytes[i + 6].toInt() + val i5 = bytes[i + 5].toInt() + val i4 = bytes[i + 4].toInt() + val i3 = bytes[i + 3].toInt() + val i2 = bytes[i + 2].toInt() + val i1 = bytes[i + 1].toInt() + val i0 = bytes[i].toInt() + + val hA = Half((((i7 and 0xff) shl 8) or (i6 and 0xff)).toShort()) + val hB = Half((((i5 and 0xff) shl 8) or (i4 and 0xff)).toShort()) + val hG = Half((((i3 and 0xff) shl 8) or (i2 and 0xff)).toShort()) + val hR = Half((((i1 and 0xff) shl 8) or (i0 and 0xff)).toShort()) + + // components as floats in sRGB + val srgbFloats = connector.transform(hR.toFloat(), hG.toFloat(), hB.toFloat()) + val srgbR = (srgbFloats[0] * 255.0f + 0.5f).toInt() + val srgbG = (srgbFloats[1] * 255.0f + 0.5f).toInt() + val srgbB = (srgbFloats[2] * 255.0f + 0.5f).toInt() + val alpha = (hA.toFloat() * 255.0f + 0.5f).toInt() + + // packing to ARGB_8888 + // stored as [3,2,1,0] -> [AAAAAAAA BBBBBBBB GGGGGGGG RRRRRRRR] + val dstI = i / indexDivider + bytes[dstI + 3] = alpha.toByte() + bytes[dstI + 2] = srgbB.toByte() + bytes[dstI + 1] = srgbG.toByte() + bytes[dstI] = srgbR.toByte() + } + } + // convert bytes, without reallocation: // - from config RGBA_1010102 to ARGB_8888, // - from original color space to sRGB. @RequiresApi(Build.VERSION_CODES.O) - private fun rgba1010102toArgb8888(bytes: ByteArray, connector: ColorSpace.Connector, start: Int = 0, end: Int = bytes.size) { + private fun rgba1010102ToArgb8888(bytes: ByteArray, connector: ColorSpace.Connector, start: Int = 0, end: Int = bytes.size) { val alphaFactor = 255.0f / MAX_2_BITS_FLOAT for (i in start..() diff --git a/lib/services/common/decoding.dart b/lib/services/common/decoding.dart index a8b154dc3..7a83aa3eb 100644 --- a/lib/services/common/decoding.dart +++ b/lib/services/common/decoding.dart @@ -7,9 +7,10 @@ import 'package:flutter/services.dart'; class InteropDecoding { static Future bytesToCodec(Uint8List bytes) async { const trailerLength = 4 * 2; - if (bytes.length < trailerLength) return null; + final byteCount = bytes.length; + if (byteCount < trailerLength) return null; - final trailerOffset = bytes.length - trailerLength; + final trailerOffset = byteCount - trailerLength; final trailer = ByteData.sublistView(bytes, trailerOffset); final bitmapWidth = trailer.getUint32(0); final bitmapHeight = trailer.getUint32(4);