mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
flutter-side decode
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <android/bitmap.h>
|
|
||||||
|
|
||||||
JNIEXPORT jlong JNICALL
|
JNIEXPORT jlong JNICALL
|
||||||
Java_app_alextran_immich_images_LocalImagesImpl_allocateNative(
|
Java_app_alextran_immich_images_LocalImagesImpl_allocateNative(
|
||||||
@@ -15,25 +14,15 @@ Java_app_alextran_immich_images_LocalImagesImpl_freeNative(
|
|||||||
free((void *) address);
|
free((void *) address);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jlong JNICALL
|
||||||
|
Java_app_alextran_immich_images_LocalImagesImpl_reallocNative(
|
||||||
|
JNIEnv *env, jclass clazz, jlong address, jint size) {
|
||||||
|
void *ptr = realloc((void *) address, size);
|
||||||
|
return (jlong) ptr;
|
||||||
|
}
|
||||||
|
|
||||||
JNIEXPORT jobject JNICALL
|
JNIEXPORT jobject JNICALL
|
||||||
Java_app_alextran_immich_images_LocalImagesImpl_wrapAsBuffer(
|
Java_app_alextran_immich_images_LocalImagesImpl_wrapAsBuffer(
|
||||||
JNIEnv *env, jclass clazz, jlong address, jint capacity) {
|
JNIEnv *env, jclass clazz, jlong address, jint capacity) {
|
||||||
return (*env)->NewDirectByteBuffer(env, (void *) address, capacity);
|
return (*env)->NewDirectByteBuffer(env, (void *) address, capacity);
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jlong JNICALL
|
|
||||||
Java_app_alextran_immich_images_RemoteImagesImpl_lockBitmapPixels(
|
|
||||||
JNIEnv *env, jclass clazz, jobject bitmap) {
|
|
||||||
void *pixels = NULL;
|
|
||||||
int result = AndroidBitmap_lockPixels(env, bitmap, &pixels);
|
|
||||||
if (result != ANDROID_BITMAP_RESULT_SUCCESS) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return (jlong) pixels;
|
|
||||||
}
|
|
||||||
|
|
||||||
JNIEXPORT void JNICALL
|
|
||||||
Java_app_alextran_immich_images_RemoteImagesImpl_unlockBitmapPixels(
|
|
||||||
JNIEnv *env, jclass clazz, jobject bitmap) {
|
|
||||||
AndroidBitmap_unlockPixels(env, bitmap);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -52,7 +52,8 @@ fun Bitmap.toNativeBuffer(): Map<String, Long> {
|
|||||||
return mapOf(
|
return mapOf(
|
||||||
"pointer" to pointer,
|
"pointer" to pointer,
|
||||||
"width" to width.toLong(),
|
"width" to width.toLong(),
|
||||||
"height" to height.toLong()
|
"height" to height.toLong(),
|
||||||
|
"rowBytes" to (width * 4).toLong()
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
LocalImagesImpl.freeNative(pointer)
|
LocalImagesImpl.freeNative(pointer)
|
||||||
@@ -83,6 +84,9 @@ class LocalImagesImpl(context: Context) : LocalImageApi {
|
|||||||
@JvmStatic
|
@JvmStatic
|
||||||
external fun freeNative(pointer: Long)
|
external fun freeNative(pointer: Long)
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
external fun reallocNative(pointer: Long, size: Int): Long
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
external fun wrapAsBuffer(address: Long, capacity: Int): ByteBuffer
|
external fun wrapAsBuffer(address: Long, capacity: Int): ByteBuffer
|
||||||
}
|
}
|
||||||
@@ -95,7 +99,8 @@ class LocalImagesImpl(context: Context) : LocalImageApi {
|
|||||||
val res = mapOf(
|
val res = mapOf(
|
||||||
"pointer" to image.pointer,
|
"pointer" to image.pointer,
|
||||||
"width" to image.width.toLong(),
|
"width" to image.width.toLong(),
|
||||||
"height" to image.height.toLong()
|
"height" to image.height.toLong(),
|
||||||
|
"rowBytes" to (image.width * 4).toLong()
|
||||||
)
|
)
|
||||||
callback(Result.success(res))
|
callback(Result.success(res))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
package app.alextran.immich.images
|
package app.alextran.immich.images
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Bitmap
|
|
||||||
import android.graphics.BitmapFactory
|
|
||||||
import android.graphics.ColorSpace
|
|
||||||
import android.graphics.ImageDecoder
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.CancellationSignal
|
import android.os.CancellationSignal
|
||||||
import app.alextran.immich.BuildConfig
|
import app.alextran.immich.BuildConfig
|
||||||
import app.alextran.immich.core.SSLConfig
|
import app.alextran.immich.core.SSLConfig
|
||||||
@@ -21,15 +16,14 @@ import okhttp3.Dispatcher
|
|||||||
import org.chromium.net.CronetEngine
|
import org.chromium.net.CronetEngine
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.nio.ByteBuffer
|
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import java.util.concurrent.Executors
|
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
|
|
||||||
data class RemoteRequest(
|
data class RemoteRequest(
|
||||||
val callback: (Result<Map<String, Long>>) -> Unit,
|
val callback: (Result<Map<String, Long>>) -> Unit,
|
||||||
val cancellationSignal: CancellationSignal,
|
val cancellationSignal: CancellationSignal,
|
||||||
|
var pointer: Long = 0L,
|
||||||
)
|
)
|
||||||
|
|
||||||
class UserAgentInterceptor : Interceptor {
|
class UserAgentInterceptor : Interceptor {
|
||||||
@@ -48,7 +42,6 @@ class UserAgentInterceptor : Interceptor {
|
|||||||
|
|
||||||
class RemoteImagesImpl(context: Context) : RemoteImageApi {
|
class RemoteImagesImpl(context: Context) : RemoteImageApi {
|
||||||
private val requestMap = ConcurrentHashMap<Long, RemoteRequest>()
|
private val requestMap = ConcurrentHashMap<Long, RemoteRequest>()
|
||||||
private val lockedBitmaps = ConcurrentHashMap<Long, Bitmap>()
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
appContext = context.applicationContext
|
appContext = context.applicationContext
|
||||||
@@ -62,9 +55,9 @@ class RemoteImagesImpl(context: Context) : RemoteImageApi {
|
|||||||
private const val KEEP_ALIVE_DURATION_MINUTES = 5L
|
private const val KEEP_ALIVE_DURATION_MINUTES = 5L
|
||||||
private const val CACHE_SIZE_BYTES = 1024L * 1024 * 1024
|
private const val CACHE_SIZE_BYTES = 1024L * 1024 * 1024
|
||||||
|
|
||||||
|
private const val INITIAL_BUFFER_SIZE = 64 * 1024
|
||||||
|
|
||||||
val CANCELLED = Result.success<Map<String, Long>>(emptyMap())
|
val CANCELLED = Result.success<Map<String, Long>>(emptyMap())
|
||||||
private val decodePool =
|
|
||||||
Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() / 2 + 1)
|
|
||||||
|
|
||||||
private var appContext: Context? = null
|
private var appContext: Context? = null
|
||||||
private var cacheDir: File? = null
|
private var cacheDir: File? = null
|
||||||
@@ -76,12 +69,6 @@ class RemoteImagesImpl(context: Context) : RemoteImageApi {
|
|||||||
SSLConfig.addListener(::invalidateClient)
|
SSLConfig.addListener(::invalidateClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
external fun lockBitmapPixels(bitmap: Bitmap): Long
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
external fun unlockBitmapPixels(bitmap: Bitmap)
|
|
||||||
|
|
||||||
private fun invalidateClient() {
|
private fun invalidateClient() {
|
||||||
(client as? OkHttpClient)?.let {
|
(client as? OkHttpClient)?.let {
|
||||||
it.dispatcher.cancelAll()
|
it.dispatcher.cancelAll()
|
||||||
@@ -111,7 +98,7 @@ class RemoteImagesImpl(context: Context) : RemoteImageApi {
|
|||||||
.enableQuic(true)
|
.enableQuic(true)
|
||||||
.enableBrotli(true)
|
.enableBrotli(true)
|
||||||
.setStoragePath(storageDir.absolutePath)
|
.setStoragePath(storageDir.absolutePath)
|
||||||
.enableHttpCache(CronetEngine.Builder.HTTP_CACHE_DISK, CACHE_SIZE_BYTES)
|
// .enableHttpCache(CronetEngine.Builder.HTTP_CACHE_DISK, CACHE_SIZE_BYTES)
|
||||||
.setUserAgent(UserAgentInterceptor.USER_AGENT)
|
.setUserAgent(UserAgentInterceptor.USER_AGENT)
|
||||||
.build()
|
.build()
|
||||||
.also { cronetEngine = it }
|
.also { cronetEngine = it }
|
||||||
@@ -168,60 +155,56 @@ class RemoteImagesImpl(context: Context) : RemoteImageApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onResponse(call: Call, response: Response) {
|
override fun onResponse(call: Call, response: Response) {
|
||||||
decodePool.execute {
|
var pointer = 0L
|
||||||
try {
|
var capacity: Int
|
||||||
signal.throwIfCanceled()
|
try {
|
||||||
val bytes = response.takeIf { it.isSuccessful }?.body?.bytes()
|
signal.throwIfCanceled()
|
||||||
?: return@execute callback(Result.failure(IOException(response.toString())))
|
val body = response.takeIf { it.isSuccessful }?.body
|
||||||
signal.throwIfCanceled()
|
?: return callback(Result.failure(IOException(response.toString())))
|
||||||
val bitmap = decodeImage(bytes)
|
|
||||||
signal.throwIfCanceled()
|
|
||||||
|
|
||||||
val pointer = lockBitmapPixels(bitmap)
|
val contentLength = body.contentLength()
|
||||||
if (pointer == 0L) {
|
capacity = if (contentLength > 0) contentLength.toInt() else INITIAL_BUFFER_SIZE
|
||||||
bitmap.recycle()
|
pointer = LocalImagesImpl.allocateNative(capacity)
|
||||||
return@execute callback(Result.failure(RuntimeException("Failed to lock bitmap pixels")))
|
request.pointer = pointer
|
||||||
|
|
||||||
|
var position = 0
|
||||||
|
body.source().use { source ->
|
||||||
|
while (!source.exhausted()) {
|
||||||
|
signal.throwIfCanceled()
|
||||||
|
if (position >= capacity) {
|
||||||
|
capacity = maxOf(capacity * 2, position + 8192)
|
||||||
|
pointer = LocalImagesImpl.reallocNative(pointer, capacity)
|
||||||
|
request.pointer = pointer
|
||||||
|
}
|
||||||
|
val buffer = LocalImagesImpl.wrapAsBuffer(pointer + position, capacity - position)
|
||||||
|
val read = source.read(buffer)
|
||||||
|
if (read == -1) break
|
||||||
|
position += read
|
||||||
}
|
}
|
||||||
|
|
||||||
lockedBitmaps[requestId] = bitmap
|
|
||||||
callback(Result.success(mapOf(
|
|
||||||
"pointer" to pointer,
|
|
||||||
"width" to bitmap.width.toLong(),
|
|
||||||
"height" to bitmap.height.toLong(),
|
|
||||||
"rowBytes" to bitmap.rowBytes.toLong()
|
|
||||||
)))
|
|
||||||
} catch (e: Exception) {
|
|
||||||
val result = if (signal.isCanceled) CANCELLED else Result.failure(e)
|
|
||||||
callback(result)
|
|
||||||
} finally {
|
|
||||||
requestMap.remove(requestId)
|
|
||||||
response.close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
signal.throwIfCanceled()
|
||||||
|
request.pointer = 0L // Transfer ownership to Dart before callback
|
||||||
|
callback(Result.success(mapOf(
|
||||||
|
"pointer" to pointer,
|
||||||
|
"length" to position.toLong()
|
||||||
|
)))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
if (pointer != 0L) LocalImagesImpl.freeNative(pointer)
|
||||||
|
val result = if (signal.isCanceled) CANCELLED else Result.failure(e)
|
||||||
|
callback(result)
|
||||||
|
} finally {
|
||||||
|
requestMap.remove(requestId)
|
||||||
|
response.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun cancelRequest(requestId: Long) {
|
override fun cancelRequest(requestId: Long) {
|
||||||
requestMap.remove(requestId)?.cancellationSignal?.cancel()
|
// Just cancel the signal - memory cleanup happens in onResponse/onFailure
|
||||||
releaseImage(requestId)
|
requestMap[requestId]?.cancellationSignal?.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun releaseImage(requestId: Long) {
|
override fun releaseImage(requestId: Long) {}
|
||||||
val bitmap = lockedBitmaps.remove(requestId) ?: return
|
|
||||||
unlockBitmapPixels(bitmap)
|
|
||||||
bitmap.recycle()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun decodeImage(bytes: ByteArray): Bitmap {
|
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
||||||
ImageDecoder.createSource(ByteBuffer.wrap(bytes)).decodeBitmap()
|
|
||||||
} else {
|
|
||||||
val options = BitmapFactory.Options().apply {
|
|
||||||
inPreferredConfig = Bitmap.Config.ARGB_8888
|
|
||||||
inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.SRGB)
|
|
||||||
}
|
|
||||||
BitmapFactory.decodeByteArray(bytes, 0, bytes.size, options)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,8 +80,6 @@ PODS:
|
|||||||
- Flutter
|
- Flutter
|
||||||
- network_info_plus (0.0.1):
|
- network_info_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- objective_c (0.0.1):
|
|
||||||
- Flutter
|
|
||||||
- package_info_plus (0.4.5):
|
- package_info_plus (0.4.5):
|
||||||
- Flutter
|
- Flutter
|
||||||
- path_provider_foundation (0.0.1):
|
- path_provider_foundation (0.0.1):
|
||||||
@@ -160,7 +158,6 @@ DEPENDENCIES:
|
|||||||
- maplibre_gl (from `.symlinks/plugins/maplibre_gl/ios`)
|
- maplibre_gl (from `.symlinks/plugins/maplibre_gl/ios`)
|
||||||
- native_video_player (from `.symlinks/plugins/native_video_player/ios`)
|
- native_video_player (from `.symlinks/plugins/native_video_player/ios`)
|
||||||
- network_info_plus (from `.symlinks/plugins/network_info_plus/ios`)
|
- network_info_plus (from `.symlinks/plugins/network_info_plus/ios`)
|
||||||
- objective_c (from `.symlinks/plugins/objective_c/ios`)
|
|
||||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||||
@@ -229,8 +226,6 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/native_video_player/ios"
|
:path: ".symlinks/plugins/native_video_player/ios"
|
||||||
network_info_plus:
|
network_info_plus:
|
||||||
:path: ".symlinks/plugins/network_info_plus/ios"
|
:path: ".symlinks/plugins/network_info_plus/ios"
|
||||||
objective_c:
|
|
||||||
:path: ".symlinks/plugins/objective_c/ios"
|
|
||||||
package_info_plus:
|
package_info_plus:
|
||||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||||
path_provider_foundation:
|
path_provider_foundation:
|
||||||
@@ -282,7 +277,6 @@ SPEC CHECKSUMS:
|
|||||||
maplibre_gl: 3c924e44725147b03dda33430ad216005b40555f
|
maplibre_gl: 3c924e44725147b03dda33430ad216005b40555f
|
||||||
native_video_player: b65c58951ede2f93d103a25366bdebca95081265
|
native_video_player: b65c58951ede2f93d103a25366bdebca95081265
|
||||||
network_info_plus: cf61925ab5205dce05a4f0895989afdb6aade5fc
|
network_info_plus: cf61925ab5205dce05a4f0895989afdb6aade5fc
|
||||||
objective_c: 89e720c30d716b036faf9c9684022048eee1eee2
|
|
||||||
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
||||||
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
|
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
|
||||||
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
|
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:ffi';
|
import 'dart:ffi';
|
||||||
import 'dart:io';
|
|
||||||
import 'dart:ui' as ui;
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -35,35 +34,55 @@ abstract class ImageRequest {
|
|||||||
|
|
||||||
void _onCancelled();
|
void _onCancelled();
|
||||||
|
|
||||||
Future<ui.FrameInfo?> _fromPlatformImage(Map<String, int> info, {required bool shouldFree}) async {
|
Future<ui.FrameInfo?> _fromEncodedPlatformImage(int address, int length) async {
|
||||||
final address = info['pointer'];
|
|
||||||
if (address == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
final pointer = Pointer<Uint8>.fromAddress(address);
|
final pointer = Pointer<Uint8>.fromAddress(address);
|
||||||
if (_isCancelled) {
|
if (_isCancelled) {
|
||||||
if (shouldFree) {
|
malloc.free(pointer);
|
||||||
malloc.free(pointer);
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final int actualWidth;
|
|
||||||
final int actualHeight;
|
|
||||||
final int rowBytes;
|
|
||||||
final int actualSize;
|
|
||||||
final ui.ImmutableBuffer buffer;
|
final ui.ImmutableBuffer buffer;
|
||||||
try {
|
try {
|
||||||
actualWidth = info['width']!;
|
buffer = await ImmutableBuffer.fromUint8List(pointer.asTypedList(length));
|
||||||
actualHeight = info['height']!;
|
|
||||||
rowBytes = info['rowBytes'] ?? actualWidth * 4;
|
|
||||||
actualSize = rowBytes * actualHeight;
|
|
||||||
buffer = await ImmutableBuffer.fromUint8List(pointer.asTypedList(actualSize));
|
|
||||||
} finally {
|
} finally {
|
||||||
if (shouldFree) {
|
malloc.free(pointer);
|
||||||
malloc.free(pointer);
|
}
|
||||||
}
|
|
||||||
|
if (_isCancelled) {
|
||||||
|
buffer.dispose();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final descriptor = await ui.ImageDescriptor.encoded(buffer);
|
||||||
|
if (_isCancelled) {
|
||||||
|
buffer.dispose();
|
||||||
|
descriptor.dispose();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final codec = await descriptor.instantiateCodec();
|
||||||
|
if (_isCancelled) {
|
||||||
|
buffer.dispose();
|
||||||
|
descriptor.dispose();
|
||||||
|
codec.dispose();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return await codec.getNextFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<ui.FrameInfo?> _fromDecodedPlatformImage(int address, int width, int height, int rowBytes) async {
|
||||||
|
final pointer = Pointer<Uint8>.fromAddress(address);
|
||||||
|
if (_isCancelled) {
|
||||||
|
malloc.free(pointer);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final size = rowBytes * height;
|
||||||
|
final ui.ImmutableBuffer buffer;
|
||||||
|
try {
|
||||||
|
buffer = await ImmutableBuffer.fromUint8List(pointer.asTypedList(size));
|
||||||
|
} finally {
|
||||||
|
malloc.free(pointer);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_isCancelled) {
|
if (_isCancelled) {
|
||||||
@@ -73,8 +92,8 @@ abstract class ImageRequest {
|
|||||||
|
|
||||||
final descriptor = ui.ImageDescriptor.raw(
|
final descriptor = ui.ImageDescriptor.raw(
|
||||||
buffer,
|
buffer,
|
||||||
width: actualWidth,
|
width: width,
|
||||||
height: actualHeight,
|
height: height,
|
||||||
rowBytes: rowBytes,
|
rowBytes: rowBytes,
|
||||||
pixelFormat: ui.PixelFormat.rgba8888,
|
pixelFormat: ui.PixelFormat.rgba8888,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ class LocalImageRequest extends ImageRequest {
|
|||||||
isVideo: assetType == AssetType.video,
|
isVideo: assetType == AssetType.video,
|
||||||
);
|
);
|
||||||
|
|
||||||
final frame = await _fromPlatformImage(info, shouldFree: true);
|
final frame = await _fromDecodedPlatformImage(info["pointer"]!, info["width"]!, info["height"]!, info["rowBytes"]!);
|
||||||
return frame == null ? null : ImageInfo(image: frame.image, scale: scale);
|
return frame == null ? null : ImageInfo(image: frame.image, scale: scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,15 +13,8 @@ class RemoteImageRequest extends ImageRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final Map<String, int> info = await remoteImageApi.requestImage(uri, headers: headers, requestId: requestId);
|
final Map<String, int> info = await remoteImageApi.requestImage(uri, headers: headers, requestId: requestId);
|
||||||
|
final frame = await _fromEncodedPlatformImage(info["pointer"]!, info["length"]!);
|
||||||
try {
|
return frame == null ? null : ImageInfo(image: frame.image, scale: scale);
|
||||||
final frame = await _fromPlatformImage(info, shouldFree: Platform.isIOS);
|
|
||||||
return frame == null ? null : ImageInfo(image: frame.image, scale: scale);
|
|
||||||
} finally {
|
|
||||||
if (Platform.isAndroid) {
|
|
||||||
unawaited(remoteImageApi.releaseImage(requestId));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class ThumbhashImageRequest extends ImageRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final Map<String, int> info = await localImageApi.getThumbhash(thumbhash);
|
final Map<String, int> info = await localImageApi.getThumbhash(thumbhash);
|
||||||
final frame = await _fromPlatformImage(info, shouldFree: true);
|
final frame = await _fromDecodedPlatformImage(info["pointer"]!, info["width"]!, info["height"]!, info["rowBytes"]!);
|
||||||
return frame == null ? null : ImageInfo(image: frame.image, scale: scale);
|
return frame == null ? null : ImageInfo(image: frame.image, scale: scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user