heif/heic support
This commit is contained in:
parent
39e41ae3d1
commit
3baaaa5877
8 changed files with 55 additions and 19 deletions
|
@ -1,7 +1,11 @@
|
|||
package deckers.thibault.aves.channelhandlers;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ContentResolver;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.ImageDecoder;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
|
@ -15,6 +19,7 @@ import java.util.Map;
|
|||
import deckers.thibault.aves.model.ImageEntry;
|
||||
import deckers.thibault.aves.model.provider.ImageProvider;
|
||||
import deckers.thibault.aves.model.provider.ImageProviderFactory;
|
||||
import deckers.thibault.aves.utils.MimeTypes;
|
||||
import io.flutter.plugin.common.MethodCall;
|
||||
import io.flutter.plugin.common.MethodChannel;
|
||||
|
||||
|
@ -66,12 +71,26 @@ public class ImageFileHandler implements MethodChannel.MethodCallHandler {
|
|||
}
|
||||
|
||||
private void readAsBytes(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
|
||||
String uri = call.argument("uri");
|
||||
String mimeType = call.argument("mimeType");
|
||||
String uriString = call.argument("uri");
|
||||
|
||||
byte[] data = null;
|
||||
try (InputStream is = activity.getContentResolver().openInputStream(Uri.parse(uri))) {
|
||||
ContentResolver cr = activity.getContentResolver();
|
||||
Uri uri = Uri.parse(uriString);
|
||||
try (InputStream is = cr.openInputStream(uri)) {
|
||||
if (is != null) {
|
||||
data = getBytes(is);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && (MimeTypes.HEIC.equals(mimeType) || MimeTypes.HEIF.equals(mimeType))) {
|
||||
// as of Flutter v1.15.17, Dart Image.memory cannot decode HEIF/HEIC images
|
||||
// so we convert the image using Android native decoder
|
||||
ImageDecoder.Source source = ImageDecoder.createSource(cr, uri);
|
||||
Bitmap bitmap = ImageDecoder.decodeBitmap(source);
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
// we compress the bitmap because Dart Image.memory cannot decode the raw bytes
|
||||
// Bitmap.CompressFormat.PNG is slower than JPEG
|
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, stream);
|
||||
data = stream.toByteArray();
|
||||
}
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
// ignore
|
||||
|
|
|
@ -3,6 +3,8 @@ package deckers.thibault.aves.utils;
|
|||
public class MimeTypes {
|
||||
public static final String IMAGE = "image";
|
||||
public static final String GIF = "image/gif";
|
||||
public static final String HEIC = "image/heic";
|
||||
public static final String HEIF = "image/heif";
|
||||
public static final String JPEG = "image/jpeg";
|
||||
public static final String PNG = "image/png";
|
||||
public static final String SVG = "image/svg+xml";
|
||||
|
|
|
@ -29,10 +29,11 @@ class ImageFileService {
|
|||
return null;
|
||||
}
|
||||
|
||||
static Future<Uint8List> readAsBytes(String uri) async {
|
||||
static Future<Uint8List> readAsBytes(String uri, String mimeType) async {
|
||||
try {
|
||||
final result = await platform.invokeMethod('readAsBytes', <String, dynamic>{
|
||||
'uri': uri,
|
||||
'mimeType': mimeType,
|
||||
});
|
||||
return result as Uint8List;
|
||||
} on PlatformException catch (e) {
|
||||
|
|
|
@ -85,7 +85,8 @@ class Thumbnail extends StatelessWidget {
|
|||
height: extent,
|
||||
child: SvgPicture(
|
||||
UriPicture(
|
||||
entry.uri,
|
||||
uri: entry.uri,
|
||||
mimeType: entry.mimeType,
|
||||
colorFilter: Constants.svgColorFilter,
|
||||
),
|
||||
width: extent,
|
||||
|
|
|
@ -416,7 +416,10 @@ class _FullscreenVerticalPageViewState extends State<FullscreenVerticalPageView>
|
|||
}
|
||||
|
||||
void _onImageChange() async {
|
||||
await UriImage(entry.uri).evict();
|
||||
await UriImage(
|
||||
uri: entry.uri,
|
||||
mimeType: entry.mimeType,
|
||||
).evict();
|
||||
if (entry.path != null) await FileImage(File(entry.path)).evict();
|
||||
// rebuild to refresh the Image inside ImagePage
|
||||
setState(() {});
|
||||
|
|
|
@ -66,7 +66,8 @@ class ImageView extends StatelessWidget {
|
|||
return PhotoView.customChild(
|
||||
child: SvgPicture(
|
||||
UriPicture(
|
||||
entry.uri,
|
||||
uri: entry.uri,
|
||||
mimeType: entry.mimeType,
|
||||
colorFilter: Constants.svgColorFilter,
|
||||
),
|
||||
placeholderBuilder: placeholderBuilder,
|
||||
|
@ -83,7 +84,10 @@ class ImageView extends StatelessWidget {
|
|||
return PhotoView(
|
||||
// key includes size and orientation to refresh when the image is rotated
|
||||
key: ValueKey('${entry.orientationDegrees}_${entry.width}_${entry.height}_${entry.path}'),
|
||||
imageProvider: UriImage(entry.uri),
|
||||
imageProvider: UriImage(
|
||||
uri: entry.uri,
|
||||
mimeType: entry.mimeType,
|
||||
),
|
||||
loadingBuilder: (context, event) => placeholderBuilder(context),
|
||||
backgroundDecoration: backgroundDecoration,
|
||||
heroAttributes: heroAttributes,
|
||||
|
|
|
@ -6,11 +6,14 @@ import 'package:flutter/foundation.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class UriImage extends ImageProvider<UriImage> {
|
||||
const UriImage(this.uri, {this.scale = 1.0})
|
||||
: assert(uri != null),
|
||||
const UriImage({
|
||||
@required this.uri,
|
||||
@required this.mimeType,
|
||||
this.scale = 1.0,
|
||||
}) : assert(uri != null),
|
||||
assert(scale != null);
|
||||
|
||||
final String uri;
|
||||
final String uri, mimeType;
|
||||
|
||||
final double scale;
|
||||
|
||||
|
@ -25,7 +28,7 @@ class UriImage extends ImageProvider<UriImage> {
|
|||
codec: _loadAsync(key, decode),
|
||||
scale: key.scale,
|
||||
informationCollector: () sync* {
|
||||
yield ErrorDescription('Uri: $uri');
|
||||
yield ErrorDescription('uri=$uri, mimeType=$mimeType');
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -33,7 +36,7 @@ class UriImage extends ImageProvider<UriImage> {
|
|||
Future<ui.Codec> _loadAsync(UriImage key, DecoderCallback decode) async {
|
||||
assert(key == this);
|
||||
|
||||
final Uint8List bytes = await ImageFileService.readAsBytes(uri);
|
||||
final Uint8List bytes = await ImageFileService.readAsBytes(uri, mimeType);
|
||||
if (bytes.lengthInBytes == 0) {
|
||||
return null;
|
||||
}
|
||||
|
@ -51,5 +54,5 @@ class UriImage extends ImageProvider<UriImage> {
|
|||
int get hashCode => hashValues(uri, scale);
|
||||
|
||||
@override
|
||||
String toString() => '${objectRuntimeType(this, 'UriImage')}("$uri", scale: $scale)';
|
||||
String toString() => '${objectRuntimeType(this, 'UriImage')}(uri=$uri, mimeType=$mimeType, scale=$scale)';
|
||||
}
|
||||
|
|
|
@ -5,11 +5,14 @@ import 'package:flutter_svg/flutter_svg.dart';
|
|||
import 'package:pedantic/pedantic.dart';
|
||||
|
||||
class UriPicture extends PictureProvider<UriPicture> {
|
||||
const UriPicture(this.uri, {this.colorFilter}) : assert(uri != null);
|
||||
const UriPicture({
|
||||
@required this.uri,
|
||||
@required this.mimeType,
|
||||
this.colorFilter,
|
||||
}) : assert(uri != null);
|
||||
|
||||
final String uri;
|
||||
final String uri, mimeType;
|
||||
|
||||
/// The [ColorFilter], if any, to use when drawing this picture.
|
||||
final ColorFilter colorFilter;
|
||||
|
||||
@override
|
||||
|
@ -20,14 +23,14 @@ class UriPicture extends PictureProvider<UriPicture> {
|
|||
@override
|
||||
PictureStreamCompleter load(UriPicture key, {PictureErrorListener onError}) {
|
||||
return OneFramePictureStreamCompleter(_loadAsync(key, onError: onError), informationCollector: () sync* {
|
||||
yield DiagnosticsProperty<String>('Uri', uri);
|
||||
yield DiagnosticsProperty<String>('uri', uri);
|
||||
});
|
||||
}
|
||||
|
||||
Future<PictureInfo> _loadAsync(UriPicture key, {PictureErrorListener onError}) async {
|
||||
assert(key == this);
|
||||
|
||||
final data = await ImageFileService.readAsBytes(uri);
|
||||
final data = await ImageFileService.readAsBytes(uri, mimeType);
|
||||
if (data == null || data.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
@ -51,5 +54,5 @@ class UriPicture extends PictureProvider<UriPicture> {
|
|||
int get hashCode => hashValues(uri, colorFilter);
|
||||
|
||||
@override
|
||||
String toString() => '${objectRuntimeType(this, 'UriPicture')}("$uri", colorFilter: $colorFilter)';
|
||||
String toString() => '${objectRuntimeType(this, 'UriPicture')}(uri=$uri, mimeType=$mimeType, colorFilter=$colorFilter)';
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue