From a37c10a96917ae96f3f1cdc2e99edbc7f86f1168 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Tue, 19 Jan 2021 12:19:38 +0900 Subject: [PATCH] temp: do not tile multi-track images --- android/app/build.gradle | 2 +- .../aves/channel/calls/ThumbnailFetcher.kt | 4 +- .../channel/streams/ImageByteStreamHandler.kt | 4 +- .../decoder/MultiTrackImageGlideModule.kt | 75 +++++++++++ .../decoder/MultiTrackThumbnailGlideModule.kt | 124 ------------------ .../aves/decoder/TiffThumbnailGlideModule.kt | 2 +- .../aves/decoder/VideoThumbnailGlideModule.kt | 2 +- .../thibault/aves/metadata/MultiTrackMedia.kt | 54 ++++++++ lib/model/image_entry.dart | 3 +- 9 files changed, 138 insertions(+), 132 deletions(-) create mode 100644 android/app/src/main/kotlin/deckers/thibault/aves/decoder/MultiTrackImageGlideModule.kt delete mode 100644 android/app/src/main/kotlin/deckers/thibault/aves/decoder/MultiTrackThumbnailGlideModule.kt create mode 100644 android/app/src/main/kotlin/deckers/thibault/aves/metadata/MultiTrackMedia.kt diff --git a/android/app/build.gradle b/android/app/build.gradle index 7fd1f982e..47c92f2f2 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -98,7 +98,7 @@ repositories { dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9' - implementation 'androidx.core:core-ktx:1.5.0-alpha05' // v1.5.0-alpha02+ for ShortcutManagerCompat.setDynamicShortcuts + implementation 'androidx.core:core-ktx:1.5.0-beta01' // v1.5.0-alpha02+ for ShortcutManagerCompat.setDynamicShortcuts implementation 'androidx.exifinterface:exifinterface:1.3.2' implementation 'com.commonsware.cwac:document:0.4.1' implementation 'com.drewnoakes:metadata-extractor:2.15.0' diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/ThumbnailFetcher.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/ThumbnailFetcher.kt index 57fa44102..d3c4d4605 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/ThumbnailFetcher.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/ThumbnailFetcher.kt @@ -13,7 +13,7 @@ import com.bumptech.glide.load.DecodeFormat import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.signature.ObjectKey -import deckers.thibault.aves.decoder.MultiTrackThumbnail +import deckers.thibault.aves.decoder.MultiTrackImage import deckers.thibault.aves.decoder.TiffThumbnail import deckers.thibault.aves.decoder.VideoThumbnail import deckers.thibault.aves.utils.BitmapUtils.applyExifOrientation @@ -128,7 +128,7 @@ class ThumbnailFetcher internal constructor( val model: Any = if (tiffFetch) { TiffThumbnail(context, uri, page ?: 0) } else if (multiTrackFetch) { - MultiTrackThumbnail(context, uri, page ?: 0) + MultiTrackImage(context, uri, page ?: 0) } else uri Glide.with(context) .asBitmap() diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ImageByteStreamHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ImageByteStreamHandler.kt index 1f3a155de..8a73fa1a2 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ImageByteStreamHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ImageByteStreamHandler.kt @@ -9,7 +9,7 @@ import com.bumptech.glide.Glide import com.bumptech.glide.load.DecodeFormat import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.request.RequestOptions -import deckers.thibault.aves.decoder.MultiTrackThumbnail +import deckers.thibault.aves.decoder.MultiTrackImage import deckers.thibault.aves.decoder.VideoThumbnail import deckers.thibault.aves.utils.BitmapUtils.applyExifOrientation import deckers.thibault.aves.utils.BitmapUtils.getBytes @@ -118,7 +118,7 @@ class ImageByteStreamHandler(private val activity: Activity, private val argumen private fun streamImageByGlide(uri: Uri, page: Int?, mimeType: String, rotationDegrees: Int, isFlipped: Boolean) { val model: Any = if (isHeifLike(mimeType) && page != null) { - MultiTrackThumbnail(activity, uri, page) + MultiTrackImage(activity, uri, page) } else { uri } diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/decoder/MultiTrackImageGlideModule.kt b/android/app/src/main/kotlin/deckers/thibault/aves/decoder/MultiTrackImageGlideModule.kt new file mode 100644 index 000000000..36b2d544a --- /dev/null +++ b/android/app/src/main/kotlin/deckers/thibault/aves/decoder/MultiTrackImageGlideModule.kt @@ -0,0 +1,75 @@ +package deckers.thibault.aves.decoder + +import android.content.Context +import android.net.Uri +import android.os.Build +import com.bumptech.glide.Glide +import com.bumptech.glide.Priority +import com.bumptech.glide.Registry +import com.bumptech.glide.annotation.GlideModule +import com.bumptech.glide.load.DataSource +import com.bumptech.glide.load.Options +import com.bumptech.glide.load.data.DataFetcher +import com.bumptech.glide.load.data.DataFetcher.DataCallback +import com.bumptech.glide.load.model.ModelLoader +import com.bumptech.glide.load.model.ModelLoaderFactory +import com.bumptech.glide.load.model.MultiModelLoaderFactory +import com.bumptech.glide.module.LibraryGlideModule +import com.bumptech.glide.signature.ObjectKey +import deckers.thibault.aves.metadata.MultiTrackMedia +import deckers.thibault.aves.utils.BitmapUtils.getBytes +import java.io.InputStream + + +@GlideModule +class MultiTrackImageGlideModule : LibraryGlideModule() { + override fun registerComponents(context: Context, glide: Glide, registry: Registry) { + registry.append(MultiTrackImage::class.java, InputStream::class.java, MultiTrackThumbnailLoader.Factory()) + } +} + +class MultiTrackImage(val context: Context, val uri: Uri, val trackIndex: Int) + +internal class MultiTrackThumbnailLoader : ModelLoader { + override fun buildLoadData(model: MultiTrackImage, width: Int, height: Int, options: Options): ModelLoader.LoadData { + return ModelLoader.LoadData(ObjectKey(model.uri), MultiTrackImageFetcher(model, width, height)) + } + + override fun handles(model: MultiTrackImage): Boolean = true + + internal class Factory : ModelLoaderFactory { + override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader = MultiTrackThumbnailLoader() + + override fun teardown() {} + } +} + +internal class MultiTrackImageFetcher(val model: MultiTrackImage, val width: Int, val height: Int) : DataFetcher { + override fun loadData(priority: Priority, callback: DataCallback) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { + callback.onLoadFailed(Exception("unsupported Android version")) + return + } + + val context = model.context + val uri = model.uri + val trackIndex = model.trackIndex + + val bitmap = MultiTrackMedia.getImage(context, uri, trackIndex) + if (bitmap == null) { + callback.onLoadFailed(Exception("null bitmap")) + } else { + callback.onDataReady(bitmap.getBytes()?.inputStream()) + } + } + + // already cleaned up in loadData and ByteArrayInputStream will be GC'd + override fun cleanup() {} + + // cannot cancel + override fun cancel() {} + + override fun getDataClass(): Class = InputStream::class.java + + override fun getDataSource(): DataSource = DataSource.LOCAL +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/decoder/MultiTrackThumbnailGlideModule.kt b/android/app/src/main/kotlin/deckers/thibault/aves/decoder/MultiTrackThumbnailGlideModule.kt deleted file mode 100644 index 3d96d8b61..000000000 --- a/android/app/src/main/kotlin/deckers/thibault/aves/decoder/MultiTrackThumbnailGlideModule.kt +++ /dev/null @@ -1,124 +0,0 @@ -package deckers.thibault.aves.decoder - -import android.content.Context -import android.graphics.Bitmap -import android.media.MediaExtractor -import android.media.MediaFormat -import android.net.Uri -import android.os.Build -import android.util.Log -import com.bumptech.glide.Glide -import com.bumptech.glide.Priority -import com.bumptech.glide.Registry -import com.bumptech.glide.annotation.GlideModule -import com.bumptech.glide.load.DataSource -import com.bumptech.glide.load.Options -import com.bumptech.glide.load.data.DataFetcher -import com.bumptech.glide.load.data.DataFetcher.DataCallback -import com.bumptech.glide.load.model.ModelLoader -import com.bumptech.glide.load.model.ModelLoaderFactory -import com.bumptech.glide.load.model.MultiModelLoaderFactory -import com.bumptech.glide.module.LibraryGlideModule -import com.bumptech.glide.signature.ObjectKey -import deckers.thibault.aves.utils.BitmapUtils.getBytes -import deckers.thibault.aves.utils.LogUtils -import deckers.thibault.aves.utils.MimeTypes -import deckers.thibault.aves.utils.StorageUtils -import java.io.InputStream - - -@GlideModule -class MultiTrackThumbnailGlideModule : LibraryGlideModule() { - override fun registerComponents(context: Context, glide: Glide, registry: Registry) { - registry.append(MultiTrackThumbnail::class.java, InputStream::class.java, MultiTrackThumbnailLoader.Factory()) - } -} - -class MultiTrackThumbnail(val context: Context, val uri: Uri, val trackIndex: Int) - -internal class MultiTrackThumbnailLoader : ModelLoader { - override fun buildLoadData(model: MultiTrackThumbnail, width: Int, height: Int, options: Options): ModelLoader.LoadData { - return ModelLoader.LoadData(ObjectKey(model.uri), MultiTrackThumbnailFetcher(model, width, height)) - } - - override fun handles(MultiTrackThumbnail: MultiTrackThumbnail): Boolean = true - - internal class Factory : ModelLoaderFactory { - override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader = MultiTrackThumbnailLoader() - - override fun teardown() {} - } -} - -internal class MultiTrackThumbnailFetcher(val model: MultiTrackThumbnail, val width: Int, val height: Int) : DataFetcher { - override fun loadData(priority: Priority, callback: DataCallback) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { - callback.onLoadFailed(Exception("unsupported Android version")) - return - } - - val context = model.context - val uri = model.uri - val trackIndex = model.trackIndex - - val imageIndex = trackIndexToImageIndex(context, uri, trackIndex) - if (imageIndex == null) { - callback.onLoadFailed(Exception("no image index")) - return - } - - val bitmap: Bitmap? - - val retriever = StorageUtils.openMetadataRetriever(context, uri) ?: return - try { - bitmap = retriever.getImageAtIndex(imageIndex) - } catch (e: Exception) { - callback.onLoadFailed(e) - return - } finally { - // cannot rely on `MediaMetadataRetriever` being `AutoCloseable` on older APIs - retriever.release() - } - - if (bitmap == null) { - callback.onLoadFailed(Exception("null bitmap")) - } else { - callback.onDataReady(bitmap.getBytes()?.inputStream()) - } - } - - private fun trackIndexToImageIndex(context: Context, uri: Uri, trackIndex: Int): Int? { - val extractor = MediaExtractor() - try { - extractor.setDataSource(context, uri, null) - val trackCount = extractor.trackCount - if (trackIndex < trackCount) { - var imageIndex = 0 - for (i in 0 until trackIndex) { - val mimeType = extractor.getTrackFormat(i).getString(MediaFormat.KEY_MIME) - if (MimeTypes.isImage(mimeType)) imageIndex++ - } - return imageIndex - } - } catch (e: Exception) { - Log.w(LOG_TAG, "failed to get image index for uri=$uri, trackIndex=$trackIndex", e) - } finally { - extractor.release() - } - return null - } - - // already cleaned up in loadData and ByteArrayInputStream will be GC'd - override fun cleanup() {} - - // cannot cancel - override fun cancel() {} - - override fun getDataClass(): Class = InputStream::class.java - - override fun getDataSource(): DataSource = DataSource.LOCAL - - companion object { - private val LOG_TAG = LogUtils.createTag(MultiTrackThumbnailFetcher::class.java) - } -} \ No newline at end of file diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/decoder/TiffThumbnailGlideModule.kt b/android/app/src/main/kotlin/deckers/thibault/aves/decoder/TiffThumbnailGlideModule.kt index 30c3627c2..529547d1e 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/decoder/TiffThumbnailGlideModule.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/decoder/TiffThumbnailGlideModule.kt @@ -33,7 +33,7 @@ internal class TiffThumbnailLoader : ModelLoader { return ModelLoader.LoadData(ObjectKey(model.uri), TiffThumbnailFetcher(model, width, height)) } - override fun handles(tiffThumbnail: TiffThumbnail): Boolean = true + override fun handles(model: TiffThumbnail): Boolean = true internal class Factory : ModelLoaderFactory { override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader = TiffThumbnailLoader() diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/decoder/VideoThumbnailGlideModule.kt b/android/app/src/main/kotlin/deckers/thibault/aves/decoder/VideoThumbnailGlideModule.kt index a6045c773..7a39b7b0a 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/decoder/VideoThumbnailGlideModule.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/decoder/VideoThumbnailGlideModule.kt @@ -34,7 +34,7 @@ internal class VideoThumbnailLoader : ModelLoader { return ModelLoader.LoadData(ObjectKey(model.uri), VideoThumbnailFetcher(model)) } - override fun handles(videoThumbnail: VideoThumbnail): Boolean = true + override fun handles(model: VideoThumbnail): Boolean = true internal class Factory : ModelLoaderFactory { override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader = VideoThumbnailLoader() diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/MultiTrackMedia.kt b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/MultiTrackMedia.kt new file mode 100644 index 000000000..afc7f4976 --- /dev/null +++ b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/MultiTrackMedia.kt @@ -0,0 +1,54 @@ +package deckers.thibault.aves.metadata + +import android.content.Context +import android.graphics.Bitmap +import android.media.MediaExtractor +import android.media.MediaFormat +import android.net.Uri +import android.os.Build +import android.util.Log +import androidx.annotation.RequiresApi +import deckers.thibault.aves.utils.LogUtils +import deckers.thibault.aves.utils.MimeTypes +import deckers.thibault.aves.utils.StorageUtils + +object MultiTrackMedia { + private val LOG_TAG = LogUtils.createTag(MultiTrackMedia::class.java) + + @RequiresApi(Build.VERSION_CODES.P) + fun getImage(context: Context, uri: Uri, trackIndex: Int): Bitmap? { + val imageIndex = trackIndexToImageIndex(context, uri, trackIndex) ?: return null + + val retriever = StorageUtils.openMetadataRetriever(context, uri) ?: return null + try { + return retriever.getImageAtIndex(imageIndex) + } catch (e: Exception) { + Log.w(LOG_TAG, "failed to extract image from uri=$uri trackIndex=$trackIndex imageIndex=$imageIndex", e) + } finally { + // cannot rely on `MediaMetadataRetriever` being `AutoCloseable` on older APIs + retriever.release() + } + return null + } + + private fun trackIndexToImageIndex(context: Context, uri: Uri, trackIndex: Int): Int? { + val extractor = MediaExtractor() + try { + extractor.setDataSource(context, uri, null) + val trackCount = extractor.trackCount + if (trackIndex < trackCount) { + var imageIndex = 0 + for (i in 0 until trackIndex) { + val mimeType = extractor.getTrackFormat(i).getString(MediaFormat.KEY_MIME) + if (MimeTypes.isImage(mimeType)) imageIndex++ + } + return imageIndex + } + } catch (e: Exception) { + Log.w(LOG_TAG, "failed to get image index for uri=$uri, trackIndex=$trackIndex", e) + } finally { + extractor.release() + } + return null + } +} \ No newline at end of file diff --git a/lib/model/image_entry.dart b/lib/model/image_entry.dart index a275e8108..079c72782 100644 --- a/lib/model/image_entry.dart +++ b/lib/model/image_entry.dart @@ -224,7 +224,8 @@ class ImageEntry { MimeTypes.rw2, MimeTypes.srw, ].contains(mimeType) && - !isAnimated; + !isAnimated && + page == null; bool get canTile => _supportedByBitmapRegionDecoder || mimeType == MimeTypes.tiff;