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 a69bd76a7..0f1b4c31e 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,6 +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.TiffThumbnail import deckers.thibault.aves.decoder.VideoThumbnail import deckers.thibault.aves.utils.BitmapUtils.applyExifOrientation import deckers.thibault.aves.utils.BitmapUtils.getBytes @@ -21,7 +22,6 @@ import deckers.thibault.aves.utils.MimeTypes.isVideo import deckers.thibault.aves.utils.MimeTypes.needRotationAfterContentResolverThumbnail import deckers.thibault.aves.utils.MimeTypes.needRotationAfterGlide import io.flutter.plugin.common.MethodChannel -import org.beyka.tiffbitmapfactory.TiffBitmapFactory class ThumbnailFetcher internal constructor( private val context: Context, @@ -45,9 +45,7 @@ class ThumbnailFetcher internal constructor( var exception: Exception? = null try { - if (mimeType == MimeTypes.TIFF) { - bitmap = getTiff() - } else if ((width == defaultSize || height == defaultSize) && !isFlipped) { + if (mimeType != MimeTypes.TIFF && (width == defaultSize || height == defaultSize) && !isFlipped) { // Fetch low quality thumbnails when size is not specified. // As of Android R, the Media Store content resolver may return a thumbnail // that is automatically rotated according to EXIF orientation, but not flipped, @@ -121,10 +119,11 @@ class ThumbnailFetcher internal constructor( .load(VideoThumbnail(context, uri)) .submit(width, height) } else { + val model: Any = if (mimeType == MimeTypes.TIFF) TiffThumbnail(context, uri) else uri Glide.with(context) .asBitmap() .apply(options) - .load(uri) + .load(model) .submit(width, height) } @@ -138,31 +137,4 @@ class ThumbnailFetcher internal constructor( Glide.with(context).clear(target) } } - - private fun getTiff(): Bitmap? { - // determine sample size - var sampleSize = 1 - context.contentResolver.openFileDescriptor(uri, "r")?.use { descriptor -> - val options = TiffBitmapFactory.Options().apply { - inJustDecodeBounds = true - } - TiffBitmapFactory.decodeFileDescriptor(descriptor.fd, options) - val imageWidth = options.outWidth - val imageHeight = options.outHeight - if (imageHeight > height || imageWidth > width) { - while (imageHeight / (sampleSize * 2) > height && imageWidth / (sampleSize * 2) > width) { - sampleSize *= 2 - } - } - } - - // decode - return context.contentResolver.openFileDescriptor(uri, "r")?.use { descriptor -> - val options = TiffBitmapFactory.Options().apply { - inJustDecodeBounds = false - inSampleSize = sampleSize - } - return TiffBitmapFactory.decodeFileDescriptor(descriptor.fd, options) - } - } } \ 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 new file mode 100644 index 000000000..060b312f6 --- /dev/null +++ b/android/app/src/main/kotlin/deckers/thibault/aves/decoder/TiffThumbnailGlideModule.kt @@ -0,0 +1,91 @@ +package deckers.thibault.aves.decoder + +import android.content.Context +import android.net.Uri +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 org.beyka.tiffbitmapfactory.TiffBitmapFactory +import java.io.InputStream + +@GlideModule +class TiffThumbnailGlideModule : LibraryGlideModule() { + override fun registerComponents(context: Context, glide: Glide, registry: Registry) { + registry.append(TiffThumbnail::class.java, InputStream::class.java, TiffThumbnailLoader.Factory()) + } +} + +class TiffThumbnail(val context: Context, val uri: Uri) + +internal class TiffThumbnailLoader : ModelLoader { + override fun buildLoadData(model: TiffThumbnail, width: Int, height: Int, options: Options): ModelLoader.LoadData { + return ModelLoader.LoadData(ObjectKey(model.uri), TiffThumbnailFetcher(model, width, height)) + } + + override fun handles(tiffThumbnail: TiffThumbnail): Boolean = true + + internal class Factory : ModelLoaderFactory { + override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader = TiffThumbnailLoader() + + override fun teardown() {} + } +} + +internal class TiffThumbnailFetcher(val model: TiffThumbnail, val width: Int, val height: Int) : DataFetcher { + override fun loadData(priority: Priority, callback: DataCallback) { + val context = model.context + val uri = model.uri + + // determine sample size + var sampleSize = 1 + context.contentResolver.openFileDescriptor(uri, "r")?.use { descriptor -> + val options = TiffBitmapFactory.Options().apply { + inJustDecodeBounds = true + } + TiffBitmapFactory.decodeFileDescriptor(descriptor.fd, options) + val imageWidth = options.outWidth + val imageHeight = options.outHeight + if (imageHeight > height || imageWidth > width) { + while (imageHeight / (sampleSize * 2) > height && imageWidth / (sampleSize * 2) > width) { + sampleSize *= 2 + } + } + } + + // decode + val bitmap = context.contentResolver.openFileDescriptor(uri, "r")?.use { descriptor -> + val options = TiffBitmapFactory.Options().apply { + inJustDecodeBounds = false + inSampleSize = sampleSize + } + TiffBitmapFactory.decodeFileDescriptor(descriptor.fd, options) + } + + 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/VideoThumbnailGlideModule.kt b/android/app/src/main/kotlin/deckers/thibault/aves/decoder/VideoThumbnailGlideModule.kt index ac2ccaaed..a6045c773 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 @@ -30,7 +30,7 @@ class VideoThumbnailGlideModule : LibraryGlideModule() { class VideoThumbnail(val context: Context, val uri: Uri) internal class VideoThumbnailLoader : ModelLoader { - override fun buildLoadData(model: VideoThumbnail, width: Int, height: Int, options: Options): ModelLoader.LoadData? { + override fun buildLoadData(model: VideoThumbnail, width: Int, height: Int, options: Options): ModelLoader.LoadData { return ModelLoader.LoadData(ObjectKey(model.uri), VideoThumbnailFetcher(model)) }