improved support for raw formats
This commit is contained in:
parent
71d7262b74
commit
8ca648b94a
12 changed files with 142 additions and 58 deletions
|
@ -198,12 +198,14 @@ public class ImageDecodeTask extends AsyncTask<ImageDecodeTask.Params, Void, Ima
|
|||
|
||||
@Override
|
||||
protected void onPostExecute(Result result) {
|
||||
MethodChannel.Result r = result.params.result;
|
||||
String uri = result.params.entry.uri.toString();
|
||||
Params params = result.params;
|
||||
MethodChannel.Result r = params.result;
|
||||
AvesImageEntry entry = params.entry;
|
||||
String uri = entry.uri.toString();
|
||||
if (result.data != null) {
|
||||
r.success(result.data);
|
||||
} else {
|
||||
r.error("getThumbnail-null", "failed to get thumbnail for uri=" + uri, null);
|
||||
r.error("getThumbnail-null", "failed to get thumbnail for uri=" + uri + ", path=" + entry.path, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,8 @@ import com.bumptech.glide.request.RequestOptions;
|
|||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import deckers.thibault.aves.decoder.VideoThumbnail;
|
||||
|
@ -32,6 +34,17 @@ public class ImageByteStreamHandler implements EventChannel.StreamHandler {
|
|||
private EventChannel.EventSink eventSink;
|
||||
private Handler handler;
|
||||
|
||||
private static final List<String> flutterSupportedTypes = Arrays.asList(
|
||||
MimeTypes.JPEG,
|
||||
MimeTypes.PNG,
|
||||
MimeTypes.GIF,
|
||||
MimeTypes.WEBP,
|
||||
MimeTypes.BMP,
|
||||
MimeTypes.WBMP,
|
||||
MimeTypes.ICO,
|
||||
MimeTypes.SVG
|
||||
);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public ImageByteStreamHandler(Activity activity, Object arguments) {
|
||||
this.activity = activity;
|
||||
|
@ -95,9 +108,8 @@ public class ImageByteStreamHandler implements EventChannel.StreamHandler {
|
|||
} finally {
|
||||
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
|
||||
} else if (!flutterSupportedTypes.contains(mimeType)) {
|
||||
// we convert the image on platform side first, when Dart Image.memory does not support it
|
||||
FutureTarget<Bitmap> target = Glide.with(activity)
|
||||
.asBitmap()
|
||||
.load(uri)
|
||||
|
|
|
@ -59,7 +59,7 @@ public class SourceImageEntry {
|
|||
this.sourceMimeType = (String) map.get("sourceMimeType");
|
||||
this.width = (int) map.get("width");
|
||||
this.height = (int) map.get("height");
|
||||
this.rotationDegrees = (int) map.get("orientationDegrees");
|
||||
this.rotationDegrees = (int) map.get("rotationDegrees");
|
||||
this.sizeBytes = toLong(map.get("sizeBytes"));
|
||||
this.title = (String) map.get("title");
|
||||
this.dateModifiedSecs = toLong(map.get("dateModifiedSecs"));
|
||||
|
|
|
@ -163,7 +163,7 @@ public class MediaStoreImageProvider extends ImageProvider {
|
|||
put("uri", itemUri.toString());
|
||||
put("path", path);
|
||||
put("sourceMimeType", mimeType);
|
||||
put("orientationDegrees", orientationColumn != -1 ? cursor.getInt(orientationColumn) : 0);
|
||||
put("rotationDegrees", orientationColumn != -1 ? cursor.getInt(orientationColumn) : 0);
|
||||
put("sizeBytes", cursor.getLong(sizeColumn));
|
||||
put("title", cursor.getString(titleColumn));
|
||||
put("dateModifiedSecs", dateModifiedSecs);
|
||||
|
|
|
@ -2,16 +2,25 @@ package deckers.thibault.aves.utils
|
|||
|
||||
object MimeTypes {
|
||||
const val IMAGE = "image"
|
||||
const val DNG = "image/x-adobe-dng" // .dng
|
||||
|
||||
const val BMP = "image/bmp"
|
||||
const val GIF = "image/gif"
|
||||
const val HEIC = "image/heic"
|
||||
const val HEIF = "image/heif"
|
||||
const val ICO = "image/x-icon"
|
||||
const val JPEG = "image/jpeg"
|
||||
const val PNG = "image/png"
|
||||
const val PSD = "image/x-photoshop" // .psd
|
||||
const val SVG = "image/svg+xml" // .svg
|
||||
const val WBMP = "image/vnd.wap.wbmp"
|
||||
const val WEBP = "image/webp"
|
||||
|
||||
const val HEIC = "image/heic"
|
||||
const val HEIF = "image/heif"
|
||||
const val PSD = "image/x-photoshop" // .psd
|
||||
|
||||
const val DNG = "image/x-adobe-dng" // .dng
|
||||
|
||||
const val SVG = "image/svg+xml" // .svg
|
||||
|
||||
const val VIDEO = "video"
|
||||
|
||||
const val AVI = "video/avi"
|
||||
const val MP2T = "video/mp2t" // .m2ts
|
||||
const val MP4 = "video/mp4"
|
||||
|
|
|
@ -52,9 +52,9 @@ class MimeFilter extends CollectionFilter {
|
|||
static String displayType(String mime) {
|
||||
final patterns = [
|
||||
RegExp('.*/'), // remove type, keep subtype
|
||||
RegExp('(X-|VND.)'), // noisy prefixes
|
||||
RegExp('(X-|VND.(WAP.)?)'), // noisy prefixes
|
||||
'+XML', // noisy suffix
|
||||
RegExp('ADOBE[-\.]'), // for DNG, PSD...
|
||||
RegExp('ADOBE\.'), // for PSD
|
||||
];
|
||||
mime = mime.toUpperCase();
|
||||
patterns.forEach((pattern) => mime = mime.replaceFirst(pattern, ''));
|
||||
|
|
|
@ -90,7 +90,7 @@ class ImageEntry {
|
|||
sourceMimeType: map['sourceMimeType'] as String,
|
||||
width: map['width'] as int ?? 0,
|
||||
height: map['height'] as int ?? 0,
|
||||
orientationDegrees: map['orientationDegrees'] as int,
|
||||
orientationDegrees: map['orientationDegrees'] as int ?? 0,
|
||||
sizeBytes: map['sizeBytes'] as int,
|
||||
sourceTitle: map['title'] as String,
|
||||
dateModifiedSecs: map['dateModifiedSecs'] as int,
|
||||
|
@ -165,7 +165,7 @@ class ImageEntry {
|
|||
// guess whether this is a photo, according to file type (used as a hint to e.g. display megapixels)
|
||||
bool get isPhoto => [MimeTypes.heic, MimeTypes.heif, MimeTypes.jpeg].contains(mimeType) || isRaw;
|
||||
|
||||
bool get isRaw => [MimeTypes.dng].contains(mimeType);
|
||||
bool get isRaw => MimeTypes.rawImages.contains(mimeType);
|
||||
|
||||
bool get isVideo => mimeType.startsWith('video');
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
class MimeTypes {
|
||||
static const String anyImage = 'image/*';
|
||||
static const String dng = 'image/x-adobe-dng';
|
||||
|
||||
static const String gif = 'image/gif';
|
||||
static const String heic = 'image/heic';
|
||||
static const String heif = 'image/heif';
|
||||
|
@ -9,8 +9,33 @@ class MimeTypes {
|
|||
static const String svg = 'image/svg+xml';
|
||||
static const String webp = 'image/webp';
|
||||
|
||||
static const String arw = 'image/x-sony-arw';
|
||||
static const String cr2 = 'image/x-canon-cr2';
|
||||
static const String crw = 'image/x-canon-crw';
|
||||
static const String dcr = 'image/x-kodak-dcr';
|
||||
static const String dng = 'image/x-adobe-dng';
|
||||
static const String erf = 'image/x-epson-erf';
|
||||
static const String k25 = 'image/x-kodak-k25';
|
||||
static const String kdc = 'image/x-kodak-kdc';
|
||||
static const String mrw = 'image/x-minolta-mrw';
|
||||
static const String nef = 'image/x-nikon-nef';
|
||||
static const String nrw = 'image/x-nikon-nrw';
|
||||
static const String orf = 'image/x-olympus-orf';
|
||||
static const String pef = 'image/x-pentax-pef';
|
||||
static const String raf = 'image/x-fuji-raf';
|
||||
static const String raw = 'image/x-panasonic-raw';
|
||||
static const String rw2 = 'image/x-panasonic-rw2';
|
||||
static const String sr2 = 'image/x-sony-sr2';
|
||||
static const String srf = 'image/x-sony-srf';
|
||||
static const String srw = 'image/x-samsung-srw';
|
||||
static const String x3f = 'image/x-sigma-x3f';
|
||||
|
||||
static const String anyVideo = 'video/*';
|
||||
|
||||
static const String avi = 'video/avi';
|
||||
static const String mp2t = 'video/mp2t'; // .m2ts
|
||||
static const String mp4 = 'video/mp4';
|
||||
|
||||
// groups
|
||||
static const List<String> rawImages = [arw, cr2, crw, dcr, dng, erf, k25, kdc, mrw, nef, nrw, orf, pef, raf, raw, rw2, sr2, srf, srw, x3f];
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@ class Constants {
|
|||
|
||||
static const pointNemo = Tuple2(-48.876667, -123.393333);
|
||||
|
||||
static const int infoGroupMaxValueLength = 140;
|
||||
|
||||
static const List<Dependency> androidDependencies = [
|
||||
Dependency(
|
||||
name: 'CWAC-Document',
|
||||
|
|
41
lib/widgets/common/aves_expansion_tile.dart
Normal file
41
lib/widgets/common/aves_expansion_tile.dart
Normal file
|
@ -0,0 +1,41 @@
|
|||
import 'package:aves/widgets/common/highlight_title.dart';
|
||||
import 'package:expansion_tile_card/expansion_tile_card.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AvesExpansionTile extends StatelessWidget {
|
||||
final String title;
|
||||
final List<Widget> children;
|
||||
final ValueNotifier<String> expandedNotifier;
|
||||
|
||||
const AvesExpansionTile({
|
||||
@required this.title,
|
||||
@required this.children,
|
||||
this.expandedNotifier,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Theme(
|
||||
data: Theme.of(context).copyWith(
|
||||
// color used by the `ExpansionTileCard` for selected text and icons
|
||||
accentColor: Colors.white,
|
||||
),
|
||||
child: ExpansionTileCard(
|
||||
key: Key('tilecard-$title'),
|
||||
value: title,
|
||||
expandedNotifier: expandedNotifier,
|
||||
title: HighlightTitle(
|
||||
title,
|
||||
fontSize: 18,
|
||||
),
|
||||
children: [
|
||||
Divider(thickness: 1, height: 1),
|
||||
SizedBox(height: 4),
|
||||
...children,
|
||||
],
|
||||
baseColor: Colors.grey[900],
|
||||
expandedColor: Colors.grey[850],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -5,11 +5,12 @@ import 'package:aves/model/image_entry.dart';
|
|||
import 'package:aves/model/image_metadata.dart';
|
||||
import 'package:aves/model/metadata_db.dart';
|
||||
import 'package:aves/services/metadata_service.dart';
|
||||
import 'package:aves/utils/constants.dart';
|
||||
import 'package:aves/widgets/common/aves_expansion_tile.dart';
|
||||
import 'package:aves/widgets/common/icons.dart';
|
||||
import 'package:aves/widgets/common/image_providers/thumbnail_provider.dart';
|
||||
import 'package:aves/widgets/common/image_providers/uri_picture_provider.dart';
|
||||
import 'package:aves/widgets/fullscreen/info/info_page.dart';
|
||||
import 'package:aves/widgets/settings/settings_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
@ -260,7 +261,7 @@ class _FullscreenDebugPageState extends State<FullscreenDebugPage> {
|
|||
static const millisecondTimestampKeys = ['datetaken', 'datetime'];
|
||||
|
||||
Widget _buildContentResolverTabView() {
|
||||
Widget builder(BuildContext context, AsyncSnapshot<Map> snapshot) {
|
||||
Widget builder(BuildContext context, AsyncSnapshot<Map> snapshot, String title) {
|
||||
if (snapshot.hasError) return Text(snapshot.error.toString());
|
||||
if (snapshot.connectionState != ConnectionState.done) return SizedBox.shrink();
|
||||
final data = SplayTreeMap.of(snapshot.data.map((k, v) {
|
||||
|
@ -277,21 +278,30 @@ class _FullscreenDebugPageState extends State<FullscreenDebugPage> {
|
|||
}
|
||||
return MapEntry(key, value);
|
||||
}));
|
||||
return InfoRowGroup(data);
|
||||
return AvesExpansionTile(
|
||||
title: title,
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 8, right: 8, bottom: 8),
|
||||
child: InfoRowGroup(
|
||||
data,
|
||||
maxValueLength: Constants.infoGroupMaxValueLength,
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return ListView(
|
||||
padding: EdgeInsets.all(16),
|
||||
padding: EdgeInsets.all(8),
|
||||
children: [
|
||||
SectionTitle('Content Resolver (Media Store)'),
|
||||
FutureBuilder<Map>(
|
||||
future: _contentResolverMetadataLoader,
|
||||
builder: builder,
|
||||
builder: (context, snapshot) => builder(context, snapshot, 'Content Resolver'),
|
||||
),
|
||||
SectionTitle('Exif Interface'),
|
||||
FutureBuilder<Map>(
|
||||
future: _exifInterfaceMetadataLoader,
|
||||
builder: builder,
|
||||
builder: (context, snapshot) => builder(context, snapshot, 'Exif Interface'),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
@ -3,12 +3,12 @@ import 'dart:collection';
|
|||
|
||||
import 'package:aves/model/image_entry.dart';
|
||||
import 'package:aves/services/metadata_service.dart';
|
||||
import 'package:aves/widgets/common/highlight_title.dart';
|
||||
import 'package:aves/utils/constants.dart';
|
||||
import 'package:aves/widgets/common/aves_expansion_tile.dart';
|
||||
import 'package:aves/widgets/common/icons.dart';
|
||||
import 'package:aves/widgets/fullscreen/info/info_page.dart';
|
||||
import 'package:aves/widgets/fullscreen/info/metadata_thumbnail.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:expansion_tile_card/expansion_tile_card.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MetadataSectionSliver extends StatefulWidget {
|
||||
|
@ -33,8 +33,6 @@ class _MetadataSectionSliverState extends State<MetadataSectionSliver> with Auto
|
|||
|
||||
bool get isVisible => widget.visibleNotifier.value;
|
||||
|
||||
static const int maxValueLength = 140;
|
||||
|
||||
// directory names from metadata-extractor
|
||||
static const exifThumbnailDirectory = 'Exif Thumbnail'; // from metadata-extractor
|
||||
static const xmpDirectory = 'XMP'; // from metadata-extractor
|
||||
|
@ -86,37 +84,22 @@ class _MetadataSectionSliverState extends State<MetadataSectionSliver> with Auto
|
|||
}
|
||||
if (index < untitledDirectoryCount + 1) {
|
||||
final dir = directoriesWithoutTitle[index - 1];
|
||||
return InfoRowGroup(dir.tags, maxValueLength: maxValueLength);
|
||||
return InfoRowGroup(dir.tags, maxValueLength: Constants.infoGroupMaxValueLength);
|
||||
}
|
||||
final dir = directoriesWithTitle[index - 1 - untitledDirectoryCount];
|
||||
return Theme(
|
||||
data: Theme.of(context).copyWith(
|
||||
// color used by the `ExpansionTileCard` for selected text and icons
|
||||
accentColor: Colors.white,
|
||||
),
|
||||
child: ExpansionTileCard(
|
||||
key: Key('tilecard-${dir.name}'),
|
||||
value: dir.name,
|
||||
expandedNotifier: _expandedDirectoryNotifier,
|
||||
title: HighlightTitle(
|
||||
dir.name,
|
||||
fontSize: 18,
|
||||
return AvesExpansionTile(
|
||||
title: dir.name,
|
||||
expandedNotifier: _expandedDirectoryNotifier,
|
||||
children: [
|
||||
if (dir.name == exifThumbnailDirectory) MetadataThumbnails(source: MetadataThumbnailSource.exif, entry: entry),
|
||||
if (dir.name == xmpDirectory) MetadataThumbnails(source: MetadataThumbnailSource.xmp, entry: entry),
|
||||
if (dir.name == videoDirectory) MetadataThumbnails(source: MetadataThumbnailSource.embedded, entry: entry),
|
||||
Container(
|
||||
alignment: Alignment.topLeft,
|
||||
padding: EdgeInsets.only(left: 8, right: 8, bottom: 8),
|
||||
child: InfoRowGroup(dir.tags, maxValueLength: Constants.infoGroupMaxValueLength),
|
||||
),
|
||||
children: [
|
||||
Divider(thickness: 1, height: 1),
|
||||
SizedBox(height: 4),
|
||||
if (dir.name == exifThumbnailDirectory) MetadataThumbnails(source: MetadataThumbnailSource.exif, entry: entry),
|
||||
if (dir.name == xmpDirectory) MetadataThumbnails(source: MetadataThumbnailSource.xmp, entry: entry),
|
||||
if (dir.name == videoDirectory) MetadataThumbnails(source: MetadataThumbnailSource.embedded, entry: entry),
|
||||
Container(
|
||||
alignment: Alignment.topLeft,
|
||||
padding: EdgeInsets.only(left: 8, right: 8, bottom: 8),
|
||||
child: InfoRowGroup(dir.tags, maxValueLength: maxValueLength),
|
||||
),
|
||||
],
|
||||
baseColor: Colors.grey[900],
|
||||
expandedColor: Colors.grey[850],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
childCount: 1 + _metadata.length,
|
||||
|
|
Loading…
Reference in a new issue