heif/heic support

This commit is contained in:
Thibault Deckers 2020-03-24 09:33:40 +09:00
parent 39e41ae3d1
commit 3baaaa5877
8 changed files with 55 additions and 19 deletions

View file

@ -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

View file

@ -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";

View file

@ -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) {

View file

@ -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,

View file

@ -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(() {});

View file

@ -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,

View file

@ -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)';
}

View file

@ -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)';
}