fixed Glide loading options, exif thumbnail orientation
This commit is contained in:
parent
de6cecace6
commit
24f9bc1b81
9 changed files with 54 additions and 39 deletions
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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(),
|
||||
),
|
||||
|
|
Loading…
Reference in a new issue