fallback for decoding large panorama

This commit is contained in:
Thibault Deckers 2025-03-05 23:17:22 +01:00
parent 2325501f3f
commit 15fe378107
5 changed files with 22 additions and 19 deletions

View file

@ -84,6 +84,8 @@ internal class TiffFetcher(val model: TiffImage, val width: Int, val height: Int
}
try {
val bitmap: Bitmap? = TiffBitmapFactory.decodeFileDescriptor(fd, options)
// calling `TiffBitmapFactory.closeFd(fd)` after decoding yields a segmentation fault
if (bitmap == null) {
callback.onLoadFailed(Exception("Decoding full TIFF yielded null bitmap"))
} else if (customSize) {

View file

@ -91,7 +91,7 @@ import java.util.regex.Pattern;
import java.util.zip.CRC32;
/*
* Forked from 'androidx.exifinterface:exifinterface:1.4.0-beta01' on 2025/01/21
* Forked from 'androidx.exifinterface:exifinterface:1.4.0'
* Named differently to let ExifInterface be loaded as subdependency.
* cf https://maven.google.com/web/index.html?q=exifinterface#androidx.exifinterface:exifinterface
* cf https://github.com/androidx/androidx/tree/androidx-main/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media

View file

@ -51,15 +51,15 @@ class UriImage extends ImageProvider<UriImage> with EquatableMixin {
// prefer Flutter for animation, as well as niche formats and SVG
// prefer Android for the rest, to rely on device codecs and handle config conversion
bool _canDecodeWithFlutter(String mimeType, bool isAnimated) {
switch(mimeType) {
bool _preferPlatformDecoding(String mimeType, bool isAnimated) {
switch (mimeType) {
case MimeTypes.bmp:
case MimeTypes.wbmp:
case MimeTypes.ico:
case MimeTypes.svg:
return true;
return false;
default:
return isAnimated;
return !isAnimated;
}
}
@ -82,7 +82,16 @@ class UriImage extends ImageProvider<UriImage> with EquatableMixin {
},
);
try {
if (_canDecodeWithFlutter(mimeType, isAnimated)) {
if (_preferPlatformDecoding(mimeType, isAnimated)) {
// get decoded media bytes from platform, and rely on a codec instantiated from raw bytes
final descriptor = await mediaFetchService.getDecodedImage(request);
if (descriptor != null) {
return descriptor.instantiateCodec();
}
debugPrint('failed to load decoded image for mimeType=$mimeType uri=$uri, falling back to loading encoded image');
}
// fallback
// get original media bytes from platform, and rely on a codec instantiated by `ImageProvider`
final bytes = await mediaFetchService.getEncodedImage(request);
if (bytes.isEmpty) {
@ -90,14 +99,6 @@ class UriImage extends ImageProvider<UriImage> with EquatableMixin {
}
final buffer = await ui.ImmutableBuffer.fromUint8List(bytes);
return await decode(buffer);
} else {
// get decoded media bytes from platform, and rely on a codec instantiated from raw bytes
final descriptor = await mediaFetchService.getDecodedImage(request);
if (descriptor == null) {
throw UnreportedStateError('$uri ($mimeType) image loading failed');
}
return descriptor.instantiateCodec();
}
} catch (error) {
// loading may fail if the provided MIME type is incorrect (e.g. the Media Store may report a JPEG as a TIFF)
debugPrint('$runtimeType _loadAsync failed with mimeType=$mimeType, uri=$uri, error=$error');

View file

@ -20,7 +20,7 @@ class VideoControlButtonsPage extends StatelessWidget {
return AvesScaffold(
appBar: AppBar(
automaticallyImplyLeading: !settings.useTvLayout,
title: Text(context.l10n.settingsVideoControlsPageTitle),
title: Text(context.l10n.settingsVideoButtonsTile),
),
body: SafeArea(
child: Selector<Settings, List<EntryAction>>(

View file

@ -58,7 +58,7 @@ class _VideoCoverState extends State<VideoCover> {
Size get videoDisplaySize => widget.videoDisplaySize;
// use the high res photo as cover for the video part of a motion photo
ImageProvider get videoCoverUriImage => mainEntry.isMotionPhoto ? mainEntry.uriImage : entry.uriImage;
ImageProvider get videoCoverUriImage => (mainEntry.isMotionPhoto ? mainEntry : entry).uriImage;
@override
void initState() {