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.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) {
|
||||||
|
|
|
@ -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) {
|
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)
|
||||||
|
|
|
@ -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');
|
||||||
|
|
Loading…
Reference in a new issue