temp: do not tile multi-track images

This commit is contained in:
Thibault Deckers 2021-01-19 12:19:38 +09:00
parent bd8bc19fa1
commit a37c10a969
9 changed files with 138 additions and 132 deletions

View file

@ -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'

View file

@ -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()

View file

@ -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
}

View file

@ -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<MultiTrackImage, InputStream> {
override fun buildLoadData(model: MultiTrackImage, width: Int, height: Int, options: Options): ModelLoader.LoadData<InputStream> {
return ModelLoader.LoadData(ObjectKey(model.uri), MultiTrackImageFetcher(model, width, height))
}
override fun handles(model: MultiTrackImage): Boolean = true
internal class Factory : ModelLoaderFactory<MultiTrackImage, InputStream> {
override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<MultiTrackImage, InputStream> = MultiTrackThumbnailLoader()
override fun teardown() {}
}
}
internal class MultiTrackImageFetcher(val model: MultiTrackImage, val width: Int, val height: Int) : DataFetcher<InputStream> {
override fun loadData(priority: Priority, callback: DataCallback<in InputStream>) {
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> = InputStream::class.java
override fun getDataSource(): DataSource = DataSource.LOCAL
}

View file

@ -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<MultiTrackThumbnail, InputStream> {
override fun buildLoadData(model: MultiTrackThumbnail, width: Int, height: Int, options: Options): ModelLoader.LoadData<InputStream> {
return ModelLoader.LoadData(ObjectKey(model.uri), MultiTrackThumbnailFetcher(model, width, height))
}
override fun handles(MultiTrackThumbnail: MultiTrackThumbnail): Boolean = true
internal class Factory : ModelLoaderFactory<MultiTrackThumbnail, InputStream> {
override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<MultiTrackThumbnail, InputStream> = MultiTrackThumbnailLoader()
override fun teardown() {}
}
}
internal class MultiTrackThumbnailFetcher(val model: MultiTrackThumbnail, val width: Int, val height: Int) : DataFetcher<InputStream> {
override fun loadData(priority: Priority, callback: DataCallback<in InputStream>) {
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> = InputStream::class.java
override fun getDataSource(): DataSource = DataSource.LOCAL
companion object {
private val LOG_TAG = LogUtils.createTag(MultiTrackThumbnailFetcher::class.java)
}
}

View file

@ -33,7 +33,7 @@ internal class TiffThumbnailLoader : ModelLoader<TiffThumbnail, InputStream> {
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<TiffThumbnail, InputStream> {
override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<TiffThumbnail, InputStream> = TiffThumbnailLoader()

View file

@ -34,7 +34,7 @@ internal class VideoThumbnailLoader : ModelLoader<VideoThumbnail, InputStream> {
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<VideoThumbnail, InputStream> {
override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<VideoThumbnail, InputStream> = VideoThumbnailLoader()

View file

@ -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
}
}

View file

@ -224,7 +224,8 @@ class ImageEntry {
MimeTypes.rw2,
MimeTypes.srw,
].contains(mimeType) &&
!isAnimated;
!isAnimated &&
page == null;
bool get canTile => _supportedByBitmapRegionDecoder || mimeType == MimeTypes.tiff;