decode TIFF thumbnails via Glide module
This commit is contained in:
parent
9e13fdeea7
commit
daa30b3e0c
3 changed files with 96 additions and 33 deletions
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<TiffThumbnail, InputStream> {
|
||||
override fun buildLoadData(model: TiffThumbnail, width: Int, height: Int, options: Options): ModelLoader.LoadData<InputStream> {
|
||||
return ModelLoader.LoadData(ObjectKey(model.uri), TiffThumbnailFetcher(model, width, height))
|
||||
}
|
||||
|
||||
override fun handles(tiffThumbnail: TiffThumbnail): Boolean = true
|
||||
|
||||
internal class Factory : ModelLoaderFactory<TiffThumbnail, InputStream> {
|
||||
override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<TiffThumbnail, InputStream> = TiffThumbnailLoader()
|
||||
|
||||
override fun teardown() {}
|
||||
}
|
||||
}
|
||||
|
||||
internal class TiffThumbnailFetcher(val model: TiffThumbnail, val width: Int, val height: Int) : DataFetcher<InputStream> {
|
||||
override fun loadData(priority: Priority, callback: DataCallback<in InputStream>) {
|
||||
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> = InputStream::class.java
|
||||
|
||||
override fun getDataSource(): DataSource = DataSource.LOCAL
|
||||
}
|
|
@ -30,7 +30,7 @@ class VideoThumbnailGlideModule : LibraryGlideModule() {
|
|||
class VideoThumbnail(val context: Context, val uri: Uri)
|
||||
|
||||
internal class VideoThumbnailLoader : ModelLoader<VideoThumbnail, InputStream> {
|
||||
override fun buildLoadData(model: VideoThumbnail, width: Int, height: Int, options: Options): ModelLoader.LoadData<InputStream>? {
|
||||
override fun buildLoadData(model: VideoThumbnail, width: Int, height: Int, options: Options): ModelLoader.LoadData<InputStream> {
|
||||
return ModelLoader.LoadData(ObjectKey(model.uri), VideoThumbnailFetcher(model))
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue