exif thumbnail decoding: use raw image descriptor in Flutter on decoded bytes from Android
This commit is contained in:
parent
c1a99d9be5
commit
152b942f57
4 changed files with 72 additions and 15 deletions
|
@ -10,7 +10,6 @@ import com.adobe.internal.xmp.XMPUtils
|
|||
import com.bumptech.glide.load.resource.bitmap.TransformationUtils
|
||||
import com.drew.metadata.xmp.XmpDirectory
|
||||
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
|
||||
import deckers.thibault.aves.channel.calls.Coresult.Companion.safeSuspend
|
||||
import deckers.thibault.aves.metadata.Metadata
|
||||
import deckers.thibault.aves.metadata.MultiPage
|
||||
import deckers.thibault.aves.metadata.metadataextractor.Helper
|
||||
|
@ -23,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.getEncodedBytes
|
||||
import deckers.thibault.aves.utils.BitmapUtils.getDecodedBytes
|
||||
import deckers.thibault.aves.utils.FileUtils.transferFrom
|
||||
import deckers.thibault.aves.utils.LogUtils
|
||||
import deckers.thibault.aves.utils.MimeTypes
|
||||
|
@ -47,7 +46,7 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler {
|
|||
|
||||
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
||||
when (call.method) {
|
||||
"getExifThumbnails" -> ioScope.launch { safeSuspend(call, result, ::getExifThumbnails) }
|
||||
"getExifThumbnails" -> ioScope.launch { safe(call, result, ::getExifThumbnails) }
|
||||
"extractGoogleDeviceItem" -> ioScope.launch { safe(call, result, ::extractGoogleDeviceItem) }
|
||||
"extractJpegMpfItem" -> ioScope.launch { safe(call, result, ::extractJpegMpfItem) }
|
||||
"extractMotionPhotoImage" -> ioScope.launch { safe(call, result, ::extractMotionPhotoImage) }
|
||||
|
@ -58,7 +57,7 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler {
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun getExifThumbnails(call: MethodCall, result: MethodChannel.Result) {
|
||||
private fun getExifThumbnails(call: MethodCall, result: MethodChannel.Result) {
|
||||
val mimeType = call.argument<String>("mimeType")
|
||||
val uri = call.argument<String>("uri")?.toUri()
|
||||
val sizeBytes = call.argument<Number>("sizeBytes")?.toLong()
|
||||
|
@ -75,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.getEncodedBytes(canHaveAlpha = false, recycle = false)?.let { bytes -> thumbnails.add(bytes) }
|
||||
it.getDecodedBytes(recycle = false)?.let { bytes -> thumbnails.add(bytes) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
45
lib/image_providers/descriptor_provider.dart
Normal file
45
lib/image_providers/descriptor_provider.dart
Normal file
|
@ -0,0 +1,45 @@
|
|||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
@immutable
|
||||
class DescriptorImageProvider extends ImageProvider<DescriptorImageProvider> {
|
||||
const DescriptorImageProvider(this.descriptor, {this.scale = 1.0});
|
||||
|
||||
final ui.ImageDescriptor descriptor;
|
||||
final double scale;
|
||||
|
||||
@override
|
||||
Future<DescriptorImageProvider> obtainKey(ImageConfiguration configuration) {
|
||||
return SynchronousFuture<DescriptorImageProvider>(this);
|
||||
}
|
||||
|
||||
@override
|
||||
ImageStreamCompleter loadImage(DescriptorImageProvider key, ImageDecoderCallback decode) {
|
||||
return MultiFrameImageStreamCompleter(
|
||||
codec: _loadAsync(key, decode: decode),
|
||||
scale: key.scale,
|
||||
debugLabel: 'DescriptorImageProvider(${describeIdentity(key.descriptor)})',
|
||||
);
|
||||
}
|
||||
|
||||
Future<ui.Codec> _loadAsync(DescriptorImageProvider key, {required ImageDecoderCallback decode}) async {
|
||||
assert(key == this);
|
||||
return descriptor.instantiateCodec();
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
return other is DescriptorImageProvider && other.descriptor == descriptor && other.scale == scale;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(descriptor.hashCode, scale);
|
||||
|
||||
@override
|
||||
String toString() => '${objectRuntimeType(this, 'DescriptorImageProvider')}(${describeIdentity(descriptor)}, scale: ${scale.toStringAsFixed(1)})';
|
||||
}
|
|
@ -1,10 +1,13 @@
|
|||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/services/common/decoding.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:aves/theme/text.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
abstract class EmbeddedDataService {
|
||||
Future<List<Uint8List>> getExifThumbnails(AvesEntry entry);
|
||||
Future<List<ui.ImageDescriptor?>> getExifThumbnails(AvesEntry entry);
|
||||
|
||||
Future<Map> extractGoogleDeviceItem(AvesEntry entry, String dataUri);
|
||||
|
||||
|
@ -23,14 +26,20 @@ class PlatformEmbeddedDataService implements EmbeddedDataService {
|
|||
static const _platform = MethodChannel('deckers.thibault/aves/embedded');
|
||||
|
||||
@override
|
||||
Future<List<Uint8List>> getExifThumbnails(AvesEntry entry) async {
|
||||
Future<List<ui.ImageDescriptor?>> getExifThumbnails(AvesEntry entry) async {
|
||||
try {
|
||||
final result = await _platform.invokeMethod('getExifThumbnails', <String, dynamic>{
|
||||
'mimeType': entry.mimeType,
|
||||
'uri': entry.uri,
|
||||
'sizeBytes': entry.sizeBytes,
|
||||
});
|
||||
if (result != null) return (result as List).cast<Uint8List>();
|
||||
if (result != null) {
|
||||
final descriptors = <ui.ImageDescriptor?>[];
|
||||
await Future.forEach((result as List).cast<Uint8List>(), (bytes) async {
|
||||
descriptors.add(await InteropDecoding.bytesToCodec(bytes));
|
||||
});
|
||||
return descriptors;
|
||||
}
|
||||
} on PlatformException catch (e, stack) {
|
||||
await reportService.recordError(e, stack);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:aves/image_providers/descriptor_provider.dart';
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -18,7 +19,7 @@ class MetadataThumbnails extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _MetadataThumbnailsState extends State<MetadataThumbnails> {
|
||||
late Future<List<Uint8List>> _loader;
|
||||
late Future<List<ui.ImageDescriptor?>> _loader;
|
||||
|
||||
AvesEntry get entry => widget.entry;
|
||||
|
||||
|
@ -32,7 +33,7 @@ class _MetadataThumbnailsState extends State<MetadataThumbnails> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder<List<Uint8List>>(
|
||||
return FutureBuilder<List<ui.ImageDescriptor?>>(
|
||||
future: _loader,
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasError && snapshot.connectionState == ConnectionState.done && snapshot.data!.isNotEmpty) {
|
||||
|
@ -40,10 +41,13 @@ class _MetadataThumbnailsState extends State<MetadataThumbnails> {
|
|||
alignment: AlignmentDirectional.topStart,
|
||||
padding: const EdgeInsets.only(left: 8, top: 8, right: 8, bottom: 4),
|
||||
child: Wrap(
|
||||
children: snapshot.data!.map((bytes) {
|
||||
return Image.memory(
|
||||
bytes,
|
||||
children: snapshot.data!.map((descriptor) {
|
||||
if (descriptor == null) return const SizedBox();
|
||||
return Image(
|
||||
image: DescriptorImageProvider(
|
||||
descriptor,
|
||||
scale: MediaQuery.devicePixelRatioOf(context),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
|
|
Loading…
Reference in a new issue