fixed Glide loading options, exif thumbnail orientation

This commit is contained in:
Thibault Deckers 2020-10-15 11:40:29 +09:00
parent de6cecace6
commit 24f9bc1b81
9 changed files with 54 additions and 39 deletions

View file

@ -18,10 +18,9 @@ import androidx.annotation.Nullable;
import androidx.core.content.FileProvider;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.Key;
import com.bumptech.glide.load.DecodeFormat;
import com.bumptech.glide.request.FutureTarget;
import com.bumptech.glide.request.RequestOptions;
import com.bumptech.glide.signature.ObjectKey;
import java.io.ByteArrayOutputStream;
import java.io.File;
@ -173,25 +172,21 @@ public class AppAdapterHandler implements MethodChannel.MethodCallHandler {
.path(String.valueOf(iconResourceId))
.build();
// add signature to ignore cache for images which got modified but kept the same URI
Key signature = new ObjectKey(packageName + size);
RequestOptions options = new RequestOptions()
.signature(signature)
.format(DecodeFormat.PREFER_RGB_565)
.centerCrop()
.override(size, size);
FutureTarget<Bitmap> target = Glide.with(context)
.asBitmap()
.apply(options)
.centerCrop()
.load(uri)
.signature(signature)
.submit(size, size);
try {
Bitmap bmp = target.get();
if (bmp != null) {
Bitmap bitmap = target.get();
if (bitmap != null) {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bmp.compress(Bitmap.CompressFormat.PNG, 100, stream);
bitmap.compress(Bitmap.CompressFormat.PNG, 0, stream);
data = stream.toByteArray();
}
} catch (Exception e) {

View file

@ -17,7 +17,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.Key;
import com.bumptech.glide.load.DecodeFormat;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.request.FutureTarget;
import com.bumptech.glide.request.RequestOptions;
@ -166,10 +166,10 @@ public class ImageDecodeTask extends AsyncTask<ImageDecodeTask.Params, Void, Ima
int width = params.width;
int height = params.height;
// add signature to ignore cache for images which got modified but kept the same URI
Key signature = new ObjectKey("" + dateModifiedSecs + rotationDegrees + isFlipped + width);
RequestOptions options = new RequestOptions()
.signature(signature)
.format(DecodeFormat.PREFER_RGB_565)
// add signature to ignore cache for images which got modified but kept the same URI
.signature(new ObjectKey("" + dateModifiedSecs + rotationDegrees + isFlipped + width))
.override(width, height);
FutureTarget<Bitmap> target;
@ -179,14 +179,12 @@ public class ImageDecodeTask extends AsyncTask<ImageDecodeTask.Params, Void, Ima
.asBitmap()
.apply(options)
.load(new VideoThumbnail(activity, uri))
.signature(signature)
.submit(width, height);
} else {
target = Glide.with(activity)
.asBitmap()
.apply(options)
.load(uri)
.signature(signature)
.submit(width, height);
}

View file

@ -8,6 +8,7 @@ import android.os.Handler;
import android.os.Looper;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.DecodeFormat;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.request.FutureTarget;
import com.bumptech.glide.request.RequestOptions;
@ -73,9 +74,13 @@ public class ImageByteStreamHandler implements EventChannel.StreamHandler {
// - Android: https://developer.android.com/guide/topics/media/media-formats#image-formats
// - Glide: https://github.com/bumptech/glide/blob/master/library/src/main/java/com/bumptech/glide/load/ImageHeaderParser.java
private void getImage() {
// request a fresh image with the highest quality format
RequestOptions options = new RequestOptions()
.format(DecodeFormat.PREFER_ARGB_8888)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.skipMemoryCache(true);
if (MimeTypes.isVideo(mimeType)) {
RequestOptions options = new RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.RESOURCE);
FutureTarget<Bitmap> target = Glide.with(activity)
.asBitmap()
.apply(options)
@ -101,6 +106,7 @@ public class ImageByteStreamHandler implements EventChannel.StreamHandler {
// we convert the image on platform side first, when Dart Image.memory does not support it
FutureTarget<Bitmap> target = Glide.with(activity)
.asBitmap()
.apply(options)
.load(uri)
.submit();
try {
@ -111,8 +117,12 @@ public class ImageByteStreamHandler implements EventChannel.StreamHandler {
if (bitmap != null) {
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);
// Bitmap.CompressFormat.PNG is slower than JPEG, but it allows transparency
if (MimeTypes.canHaveAlpha(mimeType)) {
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
} else {
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);
}
success(stream.toByteArray());
} else {
error("getImage-image-decode-null", "failed to get image from uri=" + uri, null);

View file

@ -9,17 +9,13 @@ import com.bumptech.glide.Glide;
import com.bumptech.glide.GlideBuilder;
import com.bumptech.glide.Registry;
import com.bumptech.glide.annotation.GlideModule;
import com.bumptech.glide.load.DecodeFormat;
import com.bumptech.glide.load.resource.bitmap.ExifInterfaceImageHeaderParser;
import com.bumptech.glide.module.AppGlideModule;
import com.bumptech.glide.request.RequestOptions;
@GlideModule
public class AvesAppGlideModule extends AppGlideModule {
@Override
public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) {
builder.setDefaultRequestOptions(new RequestOptions().format(DecodeFormat.PREFER_RGB_565));
// hide noisy warning (e.g. for images that can't be decoded)
builder.setLogLevel(Log.ERROR);
}

View file

@ -4,6 +4,7 @@ import android.content.ContentResolver
import android.content.ContentUris
import android.content.Context
import android.database.Cursor
import android.graphics.Bitmap
import android.media.MediaMetadataRetriever
import android.net.Uri
import android.os.Build
@ -13,6 +14,7 @@ import androidx.exifinterface.media.ExifInterface
import com.adobe.internal.xmp.XMPException
import com.adobe.internal.xmp.XMPUtils
import com.adobe.internal.xmp.properties.XMPPropertyInfo
import com.bumptech.glide.load.resource.bitmap.TransformationUtils
import com.drew.imaging.ImageMetadataReader
import com.drew.imaging.ImageProcessingException
import com.drew.lang.Rational
@ -43,6 +45,7 @@ import deckers.thibault.aves.metadata.MetadataExtractorHelper.getSafeRational
import deckers.thibault.aves.metadata.MetadataExtractorHelper.getSafeString
import deckers.thibault.aves.metadata.XMP
import deckers.thibault.aves.metadata.XMP.getSafeLocalizedText
import deckers.thibault.aves.utils.BitmapUtils
import deckers.thibault.aves.utils.LogUtils
import deckers.thibault.aves.utils.MimeTypes
import deckers.thibault.aves.utils.MimeTypes.isImage
@ -52,6 +55,7 @@ import deckers.thibault.aves.utils.StorageUtils
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.util.*
import kotlin.math.roundToLong
@ -175,13 +179,12 @@ class MetadataHandler(private val context: Context) : MethodCallHandler {
private fun getCatalogMetadata(call: MethodCall, result: MethodChannel.Result) {
val mimeType = call.argument<String>("mimeType")
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
val extension = call.argument<String>("extension")
if (mimeType == null || uri == null) {
result.error("getCatalogMetadata-args", "failed because of missing arguments", null)
return
}
val metadataMap = HashMap(getCatalogMetadataByMetadataExtractor(uri, mimeType, extension))
val metadataMap = HashMap(getCatalogMetadataByMetadataExtractor(uri, mimeType))
if (isVideo(mimeType)) {
metadataMap.putAll(getVideoCatalogMetadataByMediaMetadataRetriever(uri))
}
@ -190,7 +193,7 @@ class MetadataHandler(private val context: Context) : MethodCallHandler {
result.success(metadataMap)
}
private fun getCatalogMetadataByMetadataExtractor(uri: Uri, mimeType: String, extension: String?): Map<String, Any> {
private fun getCatalogMetadataByMetadataExtractor(uri: Uri, mimeType: String): Map<String, Any> {
val metadataMap = HashMap<String, Any>()
var foundExif = false
@ -518,7 +521,17 @@ class MetadataHandler(private val context: Context) : MethodCallHandler {
try {
StorageUtils.openInputStream(context, uri)?.use { input ->
val exif = ExifInterface(input)
exif.thumbnailBytes?.let { thumbnails.add(it) }
val orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
exif.thumbnailBitmap?.let {
val bitmap = TransformationUtils.rotateImageExif(BitmapUtils.getBitmapPool(context), it, orientation)
if (bitmap != null) {
val stream = 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)
thumbnails.add(stream.toByteArray())
}
}
}
} catch (e: Exception) {
// ExifInterface initialization can fail with a RuntimeException

View file

@ -21,5 +21,6 @@ object BitmapUtils {
return TransformationUtils.centerCrop(getBitmapPool(context), bitmap, size, size)
}
private fun getBitmapPool(context: Context) = Glide.get(context).bitmapPool
@JvmStatic
fun getBitmapPool(context: Context) = Glide.get(context).bitmapPool
}

View file

@ -32,6 +32,14 @@ object MimeTypes {
@JvmStatic
fun isVideo(mimeType: String?) = mimeType != null && mimeType.startsWith(VIDEO)
@JvmStatic
// returns whether the specified MIME type represents
// a raster image format that allows an alpha channel
fun canHaveAlpha(mimeType: String?) = when (mimeType) {
BMP, GIF, ICO, PNG, TIFF, WEBP -> true
else -> false
}
// as of Flutter v1.22.0
@JvmStatic
fun isSupportedByFlutter(mimeType: String, rotationDegrees: Int?, isFlipped: Boolean?) = when (mimeType) {

View file

@ -43,7 +43,6 @@ class MetadataService {
final result = await platform.invokeMethod('getCatalogMetadata', <String, dynamic>{
'mimeType': entry.mimeType,
'uri': entry.uri,
'extension': entry.extension,
}) as Map;
result['contentId'] = entry.contentId;
return CatalogMetadata.fromMap(result);

View file

@ -50,20 +50,15 @@ class _MetadataThumbnailsState extends State<MetadataThumbnails> {
future: _loader,
builder: (context, snapshot) {
if (!snapshot.hasError && snapshot.connectionState == ConnectionState.done && snapshot.data.isNotEmpty) {
// TODO TLAD apply the rotation to Exif thumbnail only, on Android side
final turns = (entry.rotationDegrees / 90).round();
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
return Container(
alignment: AlignmentDirectional.topStart,
padding: EdgeInsets.only(left: 8, top: 8, right: 8, bottom: 4),
child: Wrap(
children: snapshot.data.map((bytes) {
return RotatedBox(
quarterTurns: turns,
child: Image.memory(
bytes,
scale: devicePixelRatio,
),
return Image.memory(
bytes,
scale: devicePixelRatio,
);
}).toList(),
),