get thumbnails from mediastore (faster, lower quality)
This commit is contained in:
parent
38a89c5b19
commit
dc14c354a8
6 changed files with 124 additions and 54 deletions
|
@ -1,10 +1,15 @@
|
|||
package deckers.thibault.aves.channelhandlers;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.content.ContentResolver;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.provider.MediaStore;
|
||||
import android.util.Log;
|
||||
import android.util.Size;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.load.Key;
|
||||
|
@ -59,52 +64,100 @@ public class ImageDecodeTask extends AsyncTask<ImageDecodeTask.Params, Void, Ima
|
|||
@Override
|
||||
protected Result doInBackground(Params... params) {
|
||||
Params p = params[0];
|
||||
ImageEntry entry = p.entry;
|
||||
byte[] data = null;
|
||||
Bitmap bitmap = null;
|
||||
if (!this.isCancelled()) {
|
||||
// add signature to ignore cache for images which got modified but kept the same URI
|
||||
Key signature = new ObjectKey("" + entry.getDateModifiedSecs() + entry.getWidth() + entry.getOrientationDegrees());
|
||||
RequestOptions options = new RequestOptions()
|
||||
.signature(signature)
|
||||
.override(p.width, p.height);
|
||||
|
||||
FutureTarget<Bitmap> target;
|
||||
if (entry.isVideo()) {
|
||||
options = options.diskCacheStrategy(DiskCacheStrategy.RESOURCE);
|
||||
target = Glide.with(activity)
|
||||
.asBitmap()
|
||||
.apply(options)
|
||||
.load(new VideoThumbnail(activity, entry.getUri()))
|
||||
.signature(signature)
|
||||
.submit(p.width, p.height);
|
||||
} else {
|
||||
target = Glide.with(activity)
|
||||
.asBitmap()
|
||||
.apply(options)
|
||||
.load(entry.getUri())
|
||||
.signature(signature)
|
||||
.submit(p.width, p.height);
|
||||
}
|
||||
|
||||
try {
|
||||
Bitmap bmp = target.get();
|
||||
if (bmp != null) {
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
bmp.compress(Bitmap.CompressFormat.JPEG, 90, stream);
|
||||
data = stream.toByteArray();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Log.d(LOG_TAG, "getImageBytes with uri=" + entry.getUri() + " interrupted");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
Glide.with(activity).clear(target);
|
||||
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
// bitmap = getBytesByResolverThumbnail(p);
|
||||
// } else {
|
||||
bitmap = getBytesByMediaStoreThumbnail(p);
|
||||
// bitmap = getBytesByGlide(p);
|
||||
// }
|
||||
} else {
|
||||
Log.d(LOG_TAG, "getImageBytes with uri=" + entry.getUri() + " cancelled");
|
||||
Log.d(LOG_TAG, "getImageBytes with uri=" + p.entry.getUri() + " cancelled");
|
||||
}
|
||||
byte[] data = null;
|
||||
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);
|
||||
data = stream.toByteArray();
|
||||
}
|
||||
return new Result(p, data);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.Q)
|
||||
private Bitmap getBytesByResolverThumbnail(Params params) {
|
||||
ImageEntry entry = params.entry;
|
||||
int width = params.width;
|
||||
int height = params.height;
|
||||
|
||||
ContentResolver resolver = activity.getContentResolver();
|
||||
try {
|
||||
return resolver.loadThumbnail(entry.getUri(), new Size(width, height), null);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Bitmap getBytesByMediaStoreThumbnail(Params params) {
|
||||
ImageEntry entry = params.entry;
|
||||
long contentId = entry.getContentId();
|
||||
|
||||
ContentResolver resolver = activity.getContentResolver();
|
||||
try {
|
||||
if (entry.isVideo()) {
|
||||
return MediaStore.Video.Thumbnails.getThumbnail(resolver, contentId, MediaStore.Video.Thumbnails.MINI_KIND, null);
|
||||
} else {
|
||||
return MediaStore.Images.Thumbnails.getThumbnail(resolver, contentId, MediaStore.Images.Thumbnails.MINI_KIND, null);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Bitmap getBytesByGlide(Params params) {
|
||||
ImageEntry entry = params.entry;
|
||||
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("" + entry.getDateModifiedSecs() + entry.getWidth() + entry.getOrientationDegrees());
|
||||
RequestOptions options = new RequestOptions()
|
||||
.signature(signature)
|
||||
.override(width, height);
|
||||
|
||||
FutureTarget<Bitmap> target;
|
||||
if (entry.isVideo()) {
|
||||
options = options.diskCacheStrategy(DiskCacheStrategy.RESOURCE);
|
||||
target = Glide.with(activity)
|
||||
.asBitmap()
|
||||
.apply(options)
|
||||
.load(new VideoThumbnail(activity, entry.getUri()))
|
||||
.signature(signature)
|
||||
.submit(width, height);
|
||||
} else {
|
||||
target = Glide.with(activity)
|
||||
.asBitmap()
|
||||
.apply(options)
|
||||
.load(entry.getUri())
|
||||
.signature(signature)
|
||||
.submit(width, height);
|
||||
}
|
||||
|
||||
try {
|
||||
return target.get();
|
||||
} catch (InterruptedException e) {
|
||||
Log.d(LOG_TAG, "getImageBytes with uri=" + entry.getUri() + " interrupted");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
Glide.with(activity).clear(target);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Result result) {
|
||||
MethodChannel.Result r = result.params.result;
|
||||
|
|
|
@ -109,9 +109,9 @@ public class MetadataHandler implements MethodChannel.MethodCallHandler {
|
|||
} catch (ImageProcessingException e) {
|
||||
getAllVideoMetadataFallback(call, result);
|
||||
} catch (FileNotFoundException e) {
|
||||
result.error("getAllMetadata-filenotfound", "failed to get metadata for path=" + path + " (" + e.getMessage() + ")", null);
|
||||
result.error("getAllMetadata-filenotfound", "failed to get metadata for path=" + path, e.getMessage());
|
||||
} catch (Exception e) {
|
||||
result.error("getAllMetadata-exception", "failed to get metadata for path=" + path, e);
|
||||
result.error("getAllMetadata-exception", "failed to get metadata for path=" + path, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -144,7 +144,7 @@ public class MetadataHandler implements MethodChannel.MethodCallHandler {
|
|||
|
||||
result.success(metadataMap);
|
||||
} catch (Exception e) {
|
||||
result.error("getAllVideoMetadataFallback-exception", "failed to get metadata for path=" + path, e);
|
||||
result.error("getAllVideoMetadataFallback-exception", "failed to get metadata for path=" + path, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -233,16 +233,16 @@ public class MetadataHandler implements MethodChannel.MethodCallHandler {
|
|||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
result.error("getCatalogMetadata-exception", "failed to get video metadata for path=" + path, e);
|
||||
result.error("getCatalogMetadata-exception", "failed to get video metadata for path=" + path, e.getMessage());
|
||||
}
|
||||
}
|
||||
result.success(metadataMap);
|
||||
} catch (ImageProcessingException e) {
|
||||
result.error("getCatalogMetadata-imageprocessing", "failed to get metadata for path=" + path + " (" + e.getMessage() + ")", null);
|
||||
result.error("getCatalogMetadata-imageprocessing", "failed to get metadata for path=" + path, e.getMessage());
|
||||
} catch (FileNotFoundException e) {
|
||||
result.error("getCatalogMetadata-filenotfound", "failed to get metadata for path=" + path + " (" + e.getMessage() + ")", null);
|
||||
result.error("getCatalogMetadata-filenotfound", "failed to get metadata for path=" + path, e.getMessage());
|
||||
} catch (Exception e) {
|
||||
result.error("getCatalogMetadata-exception", "failed to get metadata for path=" + path, e);
|
||||
result.error("getCatalogMetadata-exception", "failed to get metadata for path=" + path, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -274,11 +274,11 @@ public class MetadataHandler implements MethodChannel.MethodCallHandler {
|
|||
}
|
||||
result.success(metadataMap);
|
||||
} catch (ImageProcessingException e) {
|
||||
result.error("getOverlayMetadata-imageprocessing", "failed to get metadata for path=" + path + " (" + e.getMessage() + ")", null);
|
||||
result.error("getOverlayMetadata-imageprocessing", "failed to get metadata for path=" + path, e.getMessage());
|
||||
} catch (FileNotFoundException e) {
|
||||
result.error("getOverlayMetadata-filenotfound", "failed to get metadata for path=" + path + " (" + e.getMessage() + ")", null);
|
||||
result.error("getOverlayMetadata-filenotfound", "failed to get metadata for path=" + path, e.getMessage());
|
||||
} catch (Exception e) {
|
||||
result.error("getOverlayMetadata-exception", "failed to get metadata for path=" + path, e);
|
||||
result.error("getOverlayMetadata-exception", "failed to get metadata for path=" + path, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -87,6 +87,10 @@ public class ImageEntry {
|
|||
return uri;
|
||||
}
|
||||
|
||||
public long getContentId() {
|
||||
return contentId;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getPath() {
|
||||
return path;
|
||||
|
|
|
@ -194,6 +194,11 @@ public abstract class ImageProvider {
|
|||
}
|
||||
|
||||
Bitmap originalImage = BitmapFactory.decodeFile(path);
|
||||
if (originalImage == null) {
|
||||
Log.e(LOG_TAG, "failed to decode image at path=" + path);
|
||||
callback.onFailure();
|
||||
return;
|
||||
}
|
||||
Matrix matrix = new Matrix();
|
||||
int originalWidth = originalImage.getWidth();
|
||||
int originalHeight = originalImage.getHeight();
|
||||
|
@ -207,14 +212,14 @@ public abstract class ImageProvider {
|
|||
ParcelFileDescriptor pfd = activity.getContentResolver().openFileDescriptor(uri, "rw");
|
||||
if (pfd != null) fd = pfd.getFileDescriptor();
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.w(LOG_TAG, "failed to get file descriptor for document at uri=" + path, e);
|
||||
Log.e(LOG_TAG, "failed to get file descriptor for document at uri=" + path, e);
|
||||
}
|
||||
if (fd != null) {
|
||||
try (FileOutputStream fos = new FileOutputStream(fd)) {
|
||||
rotatedImage.compress(Bitmap.CompressFormat.PNG, 100, fos);
|
||||
rotated = true;
|
||||
} catch (IOException e) {
|
||||
Log.w(LOG_TAG, "failed to save rotated image to document at uri=" + path, e);
|
||||
Log.e(LOG_TAG, "failed to save rotated image to document at uri=" + path, e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -222,7 +227,7 @@ public abstract class ImageProvider {
|
|||
rotatedImage.compress(Bitmap.CompressFormat.PNG, 100, fos);
|
||||
rotated = true;
|
||||
} catch (IOException e) {
|
||||
Log.w(LOG_TAG, "failed to save rotated image to path=" + path, e);
|
||||
Log.e(LOG_TAG, "failed to save rotated image to path=" + path, e);
|
||||
}
|
||||
}
|
||||
if (!rotated) {
|
||||
|
|
|
@ -68,6 +68,7 @@ class _HomePageState extends State<HomePage> {
|
|||
Future<void> setup() async {
|
||||
debugPrint('$runtimeType setup start, elapsed=${_stopwatch.elapsed}');
|
||||
// TODO reduce permission check time
|
||||
// TODO TLAD ask android.permission.ACCESS_MEDIA_LOCATION (unredacted EXIF with scoped storage)
|
||||
final permissions = await PermissionHandler().requestPermissions([
|
||||
PermissionGroup.storage
|
||||
]); // 350ms
|
||||
|
|
|
@ -76,7 +76,14 @@ class ImagePreviewState extends State<ImagePreview> with AfterInitMixin {
|
|||
future: _byteLoader,
|
||||
builder: (futureContext, AsyncSnapshot<Uint8List> snapshot) {
|
||||
final bytes = (snapshot.connectionState == ConnectionState.done && !snapshot.hasError) ? snapshot.data : kTransparentImage;
|
||||
return bytes.isNotEmpty ? widget.builder(bytes) : Icon(Icons.error);
|
||||
return bytes.isNotEmpty
|
||||
? widget.builder(bytes)
|
||||
: Center(
|
||||
child: Icon(
|
||||
Icons.error,
|
||||
color: Colors.blueGrey,
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue