Viewer: TIFF subsampling & tiling

This commit is contained in:
Thibault Deckers 2020-12-01 10:36:12 +09:00
parent 0d946b5a43
commit 4d9df75c46
4 changed files with 69 additions and 17 deletions

View file

@ -10,6 +10,7 @@ import deckers.thibault.aves.model.provider.FieldMap
import deckers.thibault.aves.model.provider.ImageProvider.ImageOpCallback import deckers.thibault.aves.model.provider.ImageProvider.ImageOpCallback
import deckers.thibault.aves.model.provider.ImageProviderFactory.getProvider import deckers.thibault.aves.model.provider.ImageProviderFactory.getProvider
import deckers.thibault.aves.model.provider.MediaStoreImageProvider import deckers.thibault.aves.model.provider.MediaStoreImageProvider
import deckers.thibault.aves.utils.MimeTypes
import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.MethodCallHandler
@ -95,14 +96,24 @@ class ImageFileHandler(private val activity: Activity) : MethodCallHandler {
return return
} }
regionFetcher.fetch( val regionRect = Rect(x, y, x + width, y + height)
uri, when (mimeType) {
mimeType, MimeTypes.TIFF -> TiffRegionFetcher(activity).fetch(
sampleSize, uri,
Rect(x, y, x + width, y + height), sampleSize,
Size(imageWidth, imageHeight), regionRect,
result, page = 0,
) result,
)
else -> regionFetcher.fetch(
uri,
mimeType,
sampleSize,
regionRect,
Size(imageWidth, imageHeight),
result,
)
}
} }
private suspend fun getImageEntry(call: MethodCall, result: MethodChannel.Result) { private suspend fun getImageEntry(call: MethodCall, result: MethodChannel.Result) {

View file

@ -0,0 +1,40 @@
package deckers.thibault.aves.channel.calls
import android.content.Context
import android.graphics.Rect
import android.net.Uri
import deckers.thibault.aves.utils.BitmapUtils.getBytes
import io.flutter.plugin.common.MethodChannel
import org.beyka.tiffbitmapfactory.DecodeArea
import org.beyka.tiffbitmapfactory.TiffBitmapFactory
class TiffRegionFetcher internal constructor(
private val context: Context,
) {
fun fetch(
uri: Uri,
sampleSize: Int,
regionRect: Rect,
page: Int = 0,
result: MethodChannel.Result,
) {
val resolver = context.contentResolver
try {
resolver.openFileDescriptor(uri, "r")?.use { descriptor ->
val options = TiffBitmapFactory.Options().apply {
inDirectoryNumber = page
inSampleSize = sampleSize
inDecodeArea = DecodeArea(regionRect.left, regionRect.top, regionRect.width(), regionRect.height())
}
val bitmap = TiffBitmapFactory.decodeFileDescriptor(descriptor.fd, options)
if (bitmap != null) {
result.success(bitmap.getBytes(canHaveAlpha = true, recycle = true))
} else {
result.error("getRegion-tiff-null", "failed to decode region for uri=$uri page=$page regionRect=$regionRect", null)
}
}
} catch (e: Exception) {
result.error("getRegion-tiff-read-exception", "failed to read from uri=$uri page=$page regionRect=$regionRect", e.message)
}
}
}

View file

@ -95,7 +95,7 @@ class ImageByteStreamHandler(private val activity: Activity, private val argumen
private fun streamImageByGlide(uri: Uri, mimeType: String, rotationDegrees: Int, isFlipped: Boolean) { private fun streamImageByGlide(uri: Uri, mimeType: String, rotationDegrees: Int, isFlipped: Boolean) {
val target = Glide.with(activity) val target = Glide.with(activity)
.asBitmap() .asBitmap()
.apply(options) .apply(glideOptions)
.load(uri) .load(uri)
.submit() .submit()
try { try {
@ -118,7 +118,7 @@ class ImageByteStreamHandler(private val activity: Activity, private val argumen
private fun streamVideoByGlide(uri: Uri) { private fun streamVideoByGlide(uri: Uri) {
val target = Glide.with(activity) val target = Glide.with(activity)
.asBitmap() .asBitmap()
.apply(options) .apply(glideOptions)
.load(VideoThumbnail(activity, uri)) .load(VideoThumbnail(activity, uri))
.submit() .submit()
try { try {
@ -135,7 +135,7 @@ class ImageByteStreamHandler(private val activity: Activity, private val argumen
} }
} }
private fun streamTiffImage(uri: Uri) { private fun streamTiffImage(uri: Uri, page: Int = 0) {
val resolver = activity.contentResolver val resolver = activity.contentResolver
try { try {
var dirCount = 0 var dirCount = 0
@ -148,18 +148,17 @@ class ImageByteStreamHandler(private val activity: Activity, private val argumen
} }
// TODO TLAD handle multipage TIFF // TODO TLAD handle multipage TIFF
if (dirCount > 0) { if (dirCount > page) {
val i = 0
resolver.openFileDescriptor(uri, "r")?.use { descriptor -> resolver.openFileDescriptor(uri, "r")?.use { descriptor ->
val options = TiffBitmapFactory.Options().apply { val options = TiffBitmapFactory.Options().apply {
inJustDecodeBounds = false inJustDecodeBounds = false
inDirectoryNumber = i inDirectoryNumber = page
} }
val bitmap = TiffBitmapFactory.decodeFileDescriptor(descriptor.fd, options) val bitmap = TiffBitmapFactory.decodeFileDescriptor(descriptor.fd, options)
if (bitmap != null) { if (bitmap != null) {
success(bitmap.getBytes(canHaveAlpha = true, recycle = true)) success(bitmap.getBytes(canHaveAlpha = true, recycle = true))
} else { } else {
error("streamImage-tiff-null", "failed to get tiff image (dir=$i) from uri=$uri", null) error("streamImage-tiff-null", "failed to get tiff image (dir=$page) from uri=$uri", null)
} }
} }
} }
@ -192,7 +191,7 @@ class ImageByteStreamHandler(private val activity: Activity, private val argumen
const val bufferSize = 2 shl 17 // 256kB const val bufferSize = 2 shl 17 // 256kB
// request a fresh image with the highest quality format // request a fresh image with the highest quality format
val options = RequestOptions() val glideOptions = RequestOptions()
.format(DecodeFormat.PREFER_ARGB_8888) .format(DecodeFormat.PREFER_ARGB_8888)
.diskCacheStrategy(DiskCacheStrategy.NONE) .diskCacheStrategy(DiskCacheStrategy.NONE)
.skipMemoryCache(true) .skipMemoryCache(true)

View file

@ -178,7 +178,7 @@ class ImageEntry {
// Android's `BitmapRegionDecoder` documentation states that "only the JPEG and PNG formats are supported" // Android's `BitmapRegionDecoder` documentation states that "only the JPEG and PNG formats are supported"
// but in practice (tested on API 25, 27, 29), it successfully decodes the formats listed below, // but in practice (tested on API 25, 27, 29), it successfully decodes the formats listed below,
// and it actually fails to decode GIF, DNG and animated WEBP. Other formats were not tested. // and it actually fails to decode GIF, DNG and animated WEBP. Other formats were not tested.
bool get canTile => bool get _supportedByBitmapRegionDecoder =>
[ [
MimeTypes.heic, MimeTypes.heic,
MimeTypes.heif, MimeTypes.heif,
@ -196,6 +196,8 @@ class ImageEntry {
].contains(mimeType) && ].contains(mimeType) &&
!isAnimated; !isAnimated;
bool get canTile => _supportedByBitmapRegionDecoder || mimeType == MimeTypes.tiff;
bool get isRaw => MimeTypes.rawImages.contains(mimeType); bool get isRaw => MimeTypes.rawImages.contains(mimeType);
bool get isVideo => mimeType.startsWith('video'); bool get isVideo => mimeType.startsWith('video');