Viewer: TIFF subsampling & tiling
This commit is contained in:
parent
0d946b5a43
commit
4d9df75c46
4 changed files with 69 additions and 17 deletions
|
@ -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.ImageProviderFactory.getProvider
|
||||
import deckers.thibault.aves.model.provider.MediaStoreImageProvider
|
||||
import deckers.thibault.aves.utils.MimeTypes
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
|
||||
|
@ -95,15 +96,25 @@ class ImageFileHandler(private val activity: Activity) : MethodCallHandler {
|
|||
return
|
||||
}
|
||||
|
||||
regionFetcher.fetch(
|
||||
val regionRect = Rect(x, y, x + width, y + height)
|
||||
when (mimeType) {
|
||||
MimeTypes.TIFF -> TiffRegionFetcher(activity).fetch(
|
||||
uri,
|
||||
sampleSize,
|
||||
regionRect,
|
||||
page = 0,
|
||||
result,
|
||||
)
|
||||
else -> regionFetcher.fetch(
|
||||
uri,
|
||||
mimeType,
|
||||
sampleSize,
|
||||
Rect(x, y, x + width, y + height),
|
||||
regionRect,
|
||||
Size(imageWidth, imageHeight),
|
||||
result,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getImageEntry(call: MethodCall, result: MethodChannel.Result) {
|
||||
val mimeType = call.argument<String>("mimeType") // MIME type is optional
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -95,7 +95,7 @@ class ImageByteStreamHandler(private val activity: Activity, private val argumen
|
|||
private fun streamImageByGlide(uri: Uri, mimeType: String, rotationDegrees: Int, isFlipped: Boolean) {
|
||||
val target = Glide.with(activity)
|
||||
.asBitmap()
|
||||
.apply(options)
|
||||
.apply(glideOptions)
|
||||
.load(uri)
|
||||
.submit()
|
||||
try {
|
||||
|
@ -118,7 +118,7 @@ class ImageByteStreamHandler(private val activity: Activity, private val argumen
|
|||
private fun streamVideoByGlide(uri: Uri) {
|
||||
val target = Glide.with(activity)
|
||||
.asBitmap()
|
||||
.apply(options)
|
||||
.apply(glideOptions)
|
||||
.load(VideoThumbnail(activity, uri))
|
||||
.submit()
|
||||
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
|
||||
try {
|
||||
var dirCount = 0
|
||||
|
@ -148,18 +148,17 @@ class ImageByteStreamHandler(private val activity: Activity, private val argumen
|
|||
}
|
||||
|
||||
// TODO TLAD handle multipage TIFF
|
||||
if (dirCount > 0) {
|
||||
val i = 0
|
||||
if (dirCount > page) {
|
||||
resolver.openFileDescriptor(uri, "r")?.use { descriptor ->
|
||||
val options = TiffBitmapFactory.Options().apply {
|
||||
inJustDecodeBounds = false
|
||||
inDirectoryNumber = i
|
||||
inDirectoryNumber = page
|
||||
}
|
||||
val bitmap = TiffBitmapFactory.decodeFileDescriptor(descriptor.fd, options)
|
||||
if (bitmap != null) {
|
||||
success(bitmap.getBytes(canHaveAlpha = true, recycle = true))
|
||||
} 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
|
||||
|
||||
// request a fresh image with the highest quality format
|
||||
val options = RequestOptions()
|
||||
val glideOptions = RequestOptions()
|
||||
.format(DecodeFormat.PREFER_ARGB_8888)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.skipMemoryCache(true)
|
||||
|
|
|
@ -178,7 +178,7 @@ class ImageEntry {
|
|||
// 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,
|
||||
// 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.heif,
|
||||
|
@ -196,6 +196,8 @@ class ImageEntry {
|
|||
].contains(mimeType) &&
|
||||
!isAnimated;
|
||||
|
||||
bool get canTile => _supportedByBitmapRegionDecoder || mimeType == MimeTypes.tiff;
|
||||
|
||||
bool get isRaw => MimeTypes.rawImages.contains(mimeType);
|
||||
|
||||
bool get isVideo => mimeType.startsWith('video');
|
||||
|
|
Loading…
Reference in a new issue