improved support for psd and other unrecognized formats

This commit is contained in:
Thibault Deckers 2020-09-21 12:48:53 +09:00
parent 5029b19ebe
commit 2f29a970da
5 changed files with 108 additions and 82 deletions

View file

@ -66,6 +66,10 @@ public class ImageByteStreamHandler implements EventChannel.StreamHandler {
handler.post(() -> eventSink.endOfStream()); handler.post(() -> eventSink.endOfStream());
} }
// Supported image formats:
// - Flutter (as of v1.20): JPEG, PNG, GIF, Animated GIF, WebP, Animated WebP, BMP, and WBMP
// - 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() { private void getImage() {
if (mimeType != null && mimeType.startsWith(MimeTypes.VIDEO)) { if (mimeType != null && mimeType.startsWith(MimeTypes.VIDEO)) {
RequestOptions options = new RequestOptions() RequestOptions options = new RequestOptions()
@ -91,42 +95,40 @@ public class ImageByteStreamHandler implements EventChannel.StreamHandler {
} finally { } finally {
Glide.with(activity).clear(target); Glide.with(activity).clear(target);
} }
} else 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 {
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());
} 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 { } else {
ContentResolver cr = activity.getContentResolver(); ContentResolver cr = activity.getContentResolver();
if (MimeTypes.DNG.equals(mimeType) || MimeTypes.HEIC.equals(mimeType) || MimeTypes.HEIF.equals(mimeType)) { try (InputStream is = cr.openInputStream(uri)) {
// as of Flutter v1.20, Dart Image.memory cannot decode DNG/HEIC/HEIF images if (is != null) {
// so we convert the image on platform side first streamBytes(is);
FutureTarget<Bitmap> target = Glide.with(activity) } else {
.asBitmap() error("getImage-image-read-null", "failed to get image from uri=" + uri, null);
.load(uri)
.submit();
try {
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());
} 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)) {
if (is != null) {
streamBytes(is);
} else {
error("getImage-image-read-null", "failed to get image from uri=" + uri, null);
}
} catch (IOException e) {
error("getImage-image-read-exception", "failed to get image from uri=" + uri, e.getMessage());
} }
} catch (IOException e) {
error("getImage-image-read-exception", "failed to get image from uri=" + uri, e.getMessage());
} }
} }
endOfStream(); endOfStream();

View file

@ -20,6 +20,7 @@ import com.drew.metadata.exif.ExifIFD0Directory;
import com.drew.metadata.jpeg.JpegDirectory; import com.drew.metadata.jpeg.JpegDirectory;
import com.drew.metadata.mp4.Mp4Directory; import com.drew.metadata.mp4.Mp4Directory;
import com.drew.metadata.mp4.media.Mp4VideoDirectory; import com.drew.metadata.mp4.media.Mp4VideoDirectory;
import com.drew.metadata.photoshop.PsdHeaderDirectory;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -191,48 +192,69 @@ public class SourceImageEntry {
try (InputStream is = StorageUtils.openInputStream(context, uri)) { try (InputStream is = StorageUtils.openInputStream(context, uri)) {
Metadata metadata = ImageMetadataReader.readMetadata(is); Metadata metadata = ImageMetadataReader.readMetadata(is);
if (MimeTypes.JPEG.equals(sourceMimeType)) { switch (sourceMimeType) {
for (JpegDirectory dir : metadata.getDirectoriesOfType(JpegDirectory.class)) { case MimeTypes.JPEG:
if (dir.containsTag(JpegDirectory.TAG_IMAGE_WIDTH)) { for (JpegDirectory dir : metadata.getDirectoriesOfType(JpegDirectory.class)) {
width = dir.getInt(JpegDirectory.TAG_IMAGE_WIDTH); if (dir.containsTag(JpegDirectory.TAG_IMAGE_WIDTH)) {
width = dir.getInt(JpegDirectory.TAG_IMAGE_WIDTH);
}
if (dir.containsTag(JpegDirectory.TAG_IMAGE_HEIGHT)) {
height = dir.getInt(JpegDirectory.TAG_IMAGE_HEIGHT);
}
} }
if (dir.containsTag(JpegDirectory.TAG_IMAGE_HEIGHT)) { break;
height = dir.getInt(JpegDirectory.TAG_IMAGE_HEIGHT); case MimeTypes.MP4:
for (Mp4VideoDirectory dir : metadata.getDirectoriesOfType(Mp4VideoDirectory.class)) {
if (dir.containsTag(Mp4VideoDirectory.TAG_WIDTH)) {
width = dir.getInt(Mp4VideoDirectory.TAG_WIDTH);
}
if (dir.containsTag(Mp4VideoDirectory.TAG_HEIGHT)) {
height = dir.getInt(Mp4VideoDirectory.TAG_HEIGHT);
}
} }
for (Mp4Directory dir : metadata.getDirectoriesOfType(Mp4Directory.class)) {
if (dir.containsTag(Mp4Directory.TAG_DURATION)) {
durationMillis = dir.getLong(Mp4Directory.TAG_DURATION);
}
}
break;
case MimeTypes.AVI:
for (AviDirectory dir : metadata.getDirectoriesOfType(AviDirectory.class)) {
if (dir.containsTag(AviDirectory.TAG_WIDTH)) {
width = dir.getInt(AviDirectory.TAG_WIDTH);
}
if (dir.containsTag(AviDirectory.TAG_HEIGHT)) {
height = dir.getInt(AviDirectory.TAG_HEIGHT);
}
if (dir.containsTag(AviDirectory.TAG_DURATION)) {
durationMillis = dir.getLong(AviDirectory.TAG_DURATION);
}
}
break;
case MimeTypes.PSD:
for (PsdHeaderDirectory dir : metadata.getDirectoriesOfType(PsdHeaderDirectory.class)) {
if (dir.containsTag(PsdHeaderDirectory.TAG_IMAGE_WIDTH)) {
width = dir.getInt(PsdHeaderDirectory.TAG_IMAGE_WIDTH);
}
if (dir.containsTag(PsdHeaderDirectory.TAG_IMAGE_HEIGHT)) {
height = dir.getInt(PsdHeaderDirectory.TAG_IMAGE_HEIGHT);
}
}
break;
}
for (ExifIFD0Directory dir : metadata.getDirectoriesOfType(ExifIFD0Directory.class)) {
if (dir.containsTag(ExifIFD0Directory.TAG_IMAGE_WIDTH)) {
width = dir.getInt(ExifIFD0Directory.TAG_IMAGE_WIDTH);
} }
for (ExifIFD0Directory dir : metadata.getDirectoriesOfType(ExifIFD0Directory.class)) { if (dir.containsTag(ExifIFD0Directory.TAG_IMAGE_HEIGHT)) {
if (dir.containsTag(ExifIFD0Directory.TAG_ORIENTATION)) { height = dir.getInt(ExifIFD0Directory.TAG_IMAGE_HEIGHT);
orientationDegrees = getOrientationDegreesForExifCode(dir.getInt(ExifIFD0Directory.TAG_ORIENTATION));
}
if (dir.containsTag(ExifIFD0Directory.TAG_DATETIME)) {
sourceDateTakenMillis = dir.getDate(ExifIFD0Directory.TAG_DATETIME, null, TimeZone.getDefault()).getTime();
}
} }
} else if (MimeTypes.MP4.equals(sourceMimeType)) { if (dir.containsTag(ExifIFD0Directory.TAG_ORIENTATION)) {
for (Mp4VideoDirectory dir : metadata.getDirectoriesOfType(Mp4VideoDirectory.class)) { orientationDegrees = getOrientationDegreesForExifCode(dir.getInt(ExifIFD0Directory.TAG_ORIENTATION));
if (dir.containsTag(Mp4VideoDirectory.TAG_WIDTH)) {
width = dir.getInt(Mp4VideoDirectory.TAG_WIDTH);
}
if (dir.containsTag(Mp4VideoDirectory.TAG_HEIGHT)) {
height = dir.getInt(Mp4VideoDirectory.TAG_HEIGHT);
}
} }
for (Mp4Directory dir : metadata.getDirectoriesOfType(Mp4Directory.class)) { if (dir.containsTag(ExifIFD0Directory.TAG_DATETIME)) {
if (dir.containsTag(Mp4Directory.TAG_DURATION)) { sourceDateTakenMillis = dir.getDate(ExifIFD0Directory.TAG_DATETIME, null, TimeZone.getDefault()).getTime();
durationMillis = dir.getLong(Mp4Directory.TAG_DURATION);
}
}
} else if (MimeTypes.AVI.equals(sourceMimeType)) {
for (AviDirectory dir : metadata.getDirectoriesOfType(AviDirectory.class)) {
if (dir.containsTag(AviDirectory.TAG_WIDTH)) {
width = dir.getInt(AviDirectory.TAG_WIDTH);
}
if (dir.containsTag(AviDirectory.TAG_HEIGHT)) {
height = dir.getInt(AviDirectory.TAG_HEIGHT);
}
if (dir.containsTag(AviDirectory.TAG_DURATION)) {
durationMillis = dir.getLong(AviDirectory.TAG_DURATION);
}
} }
} }
} catch (IOException | ImageProcessingException | MetadataException | NoClassDefFoundError e) { } catch (IOException | ImageProcessingException | MetadataException | NoClassDefFoundError e) {

View file

@ -180,20 +180,13 @@ public class MediaStoreImageProvider extends ImageProvider {
// they are valid but miss some attributes, such as width, height, orientation // they are valid but miss some attributes, such as width, height, orientation
SourceImageEntry entry = new SourceImageEntry(entryMap).fillPreCatalogMetadata(context); SourceImageEntry entry = new SourceImageEntry(entryMap).fillPreCatalogMetadata(context);
entryMap = entry.toMap(); entryMap = entry.toMap();
width = entry.width != null ? entry.width : 0;
height = entry.height != null ? entry.height : 0;
} }
if ((width <= 0 || height <= 0) && needSize(mimeType)) { newEntryHandler.handleEntry(entryMap);
// this is probably not a real image, like "/storage/emulated/0", so we skip it if (newEntryCount % 30 == 0) {
Log.w(LOG_TAG, "failed to get size for uri=" + itemUri + ", path=" + path + ", mimeType=" + mimeType); Thread.sleep(10);
} else {
newEntryHandler.handleEntry(entryMap);
if (newEntryCount % 30 == 0) {
Thread.sleep(10);
}
newEntryCount++;
} }
newEntryCount++;
} }
} }
cursor.close(); cursor.close();

View file

@ -8,6 +8,7 @@ public class MimeTypes {
public static final String HEIF = "image/heif"; public static final String HEIF = "image/heif";
public static final String JPEG = "image/jpeg"; public static final String JPEG = "image/jpeg";
public static final String PNG = "image/png"; public static final String PNG = "image/png";
public static final String PSD = "image/x-photoshop";
public static final String SVG = "image/svg+xml"; public static final String SVG = "image/svg+xml";
public static final String WEBP = "image/webp"; public static final String WEBP = "image/webp";

View file

@ -50,7 +50,15 @@ class MimeFilter extends CollectionFilter {
}; };
static String displayType(String mime) { static String displayType(String mime) {
return mime.toUpperCase().replaceFirst(RegExp('.*/(X-)?'), '').replaceFirst('+XML', '').replaceFirst('VND.', ''); final patterns = [
RegExp('.*/'), // remove type, keep subtype
RegExp('(X-|VND.)'), // noisy prefixes
'+XML', // noisy suffix
RegExp('ADOBE[-\.]'), // for DNG, PSD...
];
mime = mime.toUpperCase();
patterns.forEach((pattern) => mime = mime.replaceFirst(pattern, ''));
return mime;
} }
@override @override