fixed glide bitmap rotation for HEIF / DNG

This commit is contained in:
Thibault Deckers 2020-08-28 23:40:09 +09:00
parent 4fdcae3909
commit e9e48c37f4
3 changed files with 48 additions and 34 deletions

View file

@ -5,7 +5,6 @@ import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.os.AsyncTask;
import android.os.Build;
import android.provider.MediaStore;
@ -18,6 +17,7 @@ import androidx.annotation.RequiresApi;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.Key;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.load.resource.bitmap.TransformationUtils;
import com.bumptech.glide.request.FutureTarget;
import com.bumptech.glide.request.RequestOptions;
import com.bumptech.glide.signature.ObjectKey;
@ -28,6 +28,7 @@ import java.util.concurrent.ExecutionException;
import deckers.thibault.aves.decoder.VideoThumbnail;
import deckers.thibault.aves.model.AvesImageEntry;
import deckers.thibault.aves.utils.MimeTypes;
import deckers.thibault.aves.utils.Utils;
import io.flutter.plugin.common.MethodChannel;
@ -136,12 +137,7 @@ public class ImageDecodeTask extends AsyncTask<ImageDecodeTask.Params, Void, Ima
Bitmap bitmap = MediaStore.Images.Thumbnails.getThumbnail(resolver, contentId, MediaStore.Images.Thumbnails.MINI_KIND, null);
// from Android Q, returned thumbnail is already rotated according to EXIF orientation
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q && bitmap != null) {
Integer orientationDegrees = entry.orientationDegrees;
if (orientationDegrees != null && orientationDegrees != 0) {
Matrix matrix = new Matrix();
matrix.postRotate(orientationDegrees);
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
}
bitmap = rotateBitmap(bitmap, entry.orientationDegrees);
}
return bitmap;
}
@ -151,7 +147,6 @@ public class ImageDecodeTask extends AsyncTask<ImageDecodeTask.Params, Void, Ima
AvesImageEntry entry = params.entry;
int width = params.width;
int height = params.height;
// Log.d(LOG_TAG, "getThumbnailByGlide width=" + width + ", path=" + entry.path);
// add signature to ignore cache for images which got modified but kept the same URI
Key signature = new ObjectKey("" + entry.dateModifiedSecs + entry.width + entry.orientationDegrees);
@ -178,12 +173,24 @@ public class ImageDecodeTask extends AsyncTask<ImageDecodeTask.Params, Void, Ima
}
try {
return target.get();
Bitmap bitmap = target.get();
String mimeType = entry.mimeType;
if (MimeTypes.DNG.equals(mimeType) || MimeTypes.HEIC.equals(mimeType) || MimeTypes.HEIF.equals(mimeType)) {
bitmap = rotateBitmap(bitmap, entry.orientationDegrees);
}
return bitmap;
} finally {
Glide.with(activity).clear(target);
}
}
private Bitmap rotateBitmap(Bitmap bitmap, Integer orientationDegrees) {
if (bitmap != null && orientationDegrees != null) {
bitmap = TransformationUtils.rotateImage(bitmap, orientationDegrees);
}
return bitmap;
}
@Override
protected void onPostExecute(Result result) {
MethodChannel.Result r = result.params.result;

View file

@ -3,17 +3,15 @@ package deckers.thibault.aves.channel.streams;
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;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.load.resource.bitmap.TransformationUtils;
import com.bumptech.glide.request.FutureTarget;
import com.bumptech.glide.request.RequestOptions;
import com.bumptech.glide.request.target.Target;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@ -30,6 +28,7 @@ public class ImageByteStreamHandler implements EventChannel.StreamHandler {
private Activity activity;
private Uri uri;
private String mimeType;
private int orientationDegrees;
private EventChannel.EventSink eventSink;
private Handler handler;
@ -40,6 +39,7 @@ public class ImageByteStreamHandler implements EventChannel.StreamHandler {
Map<String, Object> argMap = (Map<String, Object>) arguments;
this.mimeType = (String) argMap.get("mimeType");
this.uri = Uri.parse((String) argMap.get("uri"));
this.orientationDegrees = (int) argMap.get("orientationDegrees");
}
}
@ -74,7 +74,7 @@ public class ImageByteStreamHandler implements EventChannel.StreamHandler {
.asBitmap()
.apply(options)
.load(new VideoThumbnail(activity, uri))
.submit(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL);
.submit();
try {
Bitmap bitmap = target.get();
if (bitmap != null) {
@ -88,23 +88,34 @@ public class ImageByteStreamHandler implements EventChannel.StreamHandler {
}
} catch (Exception e) {
error("getImage-video-exception", "failed to get image from uri=" + uri, e.getMessage());
}
} finally {
Glide.with(activity).clear(target);
}
} else {
ContentResolver cr = activity.getContentResolver();
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
if (MimeTypes.DNG.equals(mimeType) || MimeTypes.HEIC.equals(mimeType) || MimeTypes.HEIF.equals(mimeType)) {
// as of Flutter v1.20, Dart Image.memory cannot decode DNG/HEIC/HEIF images
// so we convert the image on platform side first
FutureTarget<Bitmap> target = Glide.with(activity)
.asBitmap()
.load(uri)
.submit();
try {
ImageDecoder.Source source = ImageDecoder.createSource(cr, uri);
Bitmap bitmap = ImageDecoder.decodeBitmap(source);
Bitmap bitmap = target.get();
if (bitmap != null) {
bitmap = TransformationUtils.rotateImage(bitmap, orientationDegrees);
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);
success(stream.toByteArray());
} catch (IOException e) {
error("getImage-image-decode-exception", "failed to decode image from uri=" + uri, e.getMessage());
} else {
error("getImage-image-decode-null", "failed to get image from uri=" + uri, null);
}
} catch (Exception e) {
error("getImage-image-decode-exception", "failed to get image from uri=" + uri, e.getMessage());
} finally {
Glide.with(activity).clear(target);
}
} else {
try (InputStream is = cr.openInputStream(uri)) {

View file

@ -5,7 +5,6 @@ import android.content.Context;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.provider.MediaStore;
@ -14,6 +13,7 @@ import android.util.Log;
import androidx.annotation.NonNull;
import androidx.exifinterface.media.ExifInterface;
import com.bumptech.glide.load.resource.bitmap.TransformationUtils;
import com.commonsware.cwac.document.DocumentFileCompat;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
@ -196,11 +196,7 @@ public abstract class ImageProvider {
callback.onFailure(new Exception("failed to decode image at path=" + path));
return;
}
Matrix matrix = new Matrix();
int originalWidth = originalImage.getWidth();
int originalHeight = originalImage.getHeight();
matrix.setRotate(clockwise ? 90 : -90, originalWidth >> 1, originalHeight >> 1);
Bitmap rotatedImage = Bitmap.createBitmap(originalImage, 0, 0, originalWidth, originalHeight, matrix, true);
Bitmap rotatedImage = TransformationUtils.rotateImage(originalImage, clockwise ? 90 : -90);
try (FileOutputStream fos = new FileOutputStream(editablePath)) {
rotatedImage.compress(Bitmap.CompressFormat.PNG, 100, fos);
@ -213,8 +209,8 @@ public abstract class ImageProvider {
}
// update fields in media store
@SuppressWarnings("SuspiciousNameCombination") int rotatedWidth = originalHeight;
@SuppressWarnings("SuspiciousNameCombination") int rotatedHeight = originalWidth;
@SuppressWarnings("SuspiciousNameCombination") int rotatedWidth = originalImage.getHeight();
@SuppressWarnings("SuspiciousNameCombination") int rotatedHeight = originalImage.getWidth();
Map<String, Object> newFields = new HashMap<>();
newFields.put("width", rotatedWidth);
newFields.put("height", rotatedHeight);