Skip to content

Commit

Permalink
Update sketch, zoomimage and add support for vault image subsampling
Browse files Browse the repository at this point in the history
Signed-off-by: IacobIonut01 <paulionut2003@gmail.com>
  • Loading branch information
IacobIonut01 committed Nov 11, 2024
1 parent bd65c92 commit b803aa6
Show file tree
Hide file tree
Showing 17 changed files with 570 additions and 298 deletions.
6 changes: 4 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ android {
applicationId = "com.dot.gallery"
minSdk = 30
targetSdk = 35
versionCode = 31006
versionCode = 31007
versionName = "3.1.0"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
Expand Down Expand Up @@ -200,7 +200,9 @@ dependencies {
// Sketch
implementation(libs.sketch.compose)
implementation(libs.sketch.view)
implementation(libs.sketch.animated)
implementation(libs.sketch.animated.gif)
implementation(libs.sketch.animated.heif)
implementation(libs.sketch.animated.webp)
implementation(libs.sketch.extensions.compose)
implementation(libs.sketch.http.ktor)
implementation(libs.sketch.svg)
Expand Down
4 changes: 0 additions & 4 deletions app/src/main/kotlin/com/dot/gallery/GalleryApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,10 @@ import com.github.panpf.sketch.PlatformContext
import com.github.panpf.sketch.SingletonSketch
import com.github.panpf.sketch.Sketch
import com.github.panpf.sketch.cache.DiskCache
import com.github.panpf.sketch.decode.supportAnimatedGif
import com.github.panpf.sketch.decode.supportAnimatedHeif
import com.github.panpf.sketch.decode.supportAnimatedWebp
import com.github.panpf.sketch.decode.supportSvg
import com.github.panpf.sketch.decode.supportVideoFrame
import com.github.panpf.sketch.http.KtorStack
import com.github.panpf.sketch.request.supportPauseLoadWhenScrolling
import com.github.panpf.sketch.request.supportSaveCellularTraffic
import com.github.panpf.sketch.util.appCacheDirectory
Expand All @@ -32,13 +30,11 @@ import javax.inject.Inject
class GalleryApp : Application(), SingletonSketch.Factory, Configuration.Provider {

override fun createSketch(context: PlatformContext): Sketch = Sketch.Builder(this).apply {
httpStack(KtorStack())
components {
supportSaveCellularTraffic()
supportPauseLoadWhenScrolling()
supportSvg()
supportVideoFrame()
supportAnimatedGif()
supportAnimatedWebp()
supportAnimatedHeif()
supportHeifDecoder()
Expand Down
54 changes: 38 additions & 16 deletions app/src/main/kotlin/com/dot/gallery/core/decoder/DecoderExt.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package com.dot.gallery.core.decoder

import android.graphics.Bitmap
import com.github.panpf.sketch.asSketchImage
import com.github.panpf.sketch.asImage
import com.github.panpf.sketch.decode.DecodeResult
import com.github.panpf.sketch.decode.ImageInfo
import com.github.panpf.sketch.decode.internal.appliedResize
import com.github.panpf.sketch.decode.internal.createScaledTransformed
import com.github.panpf.sketch.request.RequestContext
import com.github.panpf.sketch.source.DataSource
Expand All @@ -13,6 +12,34 @@ import com.github.panpf.sketch.util.computeScaleMultiplierWithOneSide
import okio.buffer
import kotlin.math.roundToInt

inline fun DataSource.getImageInfo(
requestContext: RequestContext,
mimeType: String,
getSize: (ByteArray) -> android.util.Size?
): ImageInfo {
openSource().use { src ->
val sourceData = src.buffer().readByteArray()
val originalSizeDecoded = getSize(sourceData) ?: android.util.Size(0, 0)
val size = if (requestContext.size == Size.Origin) {
Size(originalSizeDecoded.width, originalSizeDecoded.height)
} else {
val scale = computeScaleMultiplierWithOneSide(
sourceSize = Size(originalSizeDecoded.width, originalSizeDecoded.height),
targetSize = requestContext.size,
)
Size(
width = (originalSizeDecoded.width * scale).roundToInt(),
height = (originalSizeDecoded.height * scale).roundToInt()
)
}
return ImageInfo(
width = size.width,
height = size.height,
mimeType = mimeType,
)
}
}

inline fun DataSource.withCustomDecoder(
requestContext: RequestContext,
mimeType: String,
Expand All @@ -21,11 +48,10 @@ inline fun DataSource.withCustomDecoder(
): DecodeResult = openSource().use { src ->
val sourceData = src.buffer().readByteArray()

val imageInfo: ImageInfo
var transformeds: List<String>? = null
val originalSizeDecoded = getSize(sourceData) ?: android.util.Size(0, 0)
val originalSize = Size(originalSizeDecoded.width, originalSizeDecoded.height)
val targetSize = requestContext.size!!
val targetSize = requestContext.size
val scale = computeScaleMultiplierWithOneSide(
sourceSize = originalSize,
targetSize = targetSize,
Expand All @@ -34,12 +60,13 @@ inline fun DataSource.withCustomDecoder(
transformeds = listOf(createScaledTransformed(scale))
}

val imageInfo = getImageInfo(
requestContext = requestContext,
mimeType = mimeType,
getSize = getSize
)

val decodedImage = if (requestContext.size == Size.Origin) {
imageInfo = ImageInfo(
width = originalSize.width,
height = originalSize.height,
mimeType = mimeType,
)
decodeSampled(
sourceData,
originalSize.width,
Expand All @@ -50,11 +77,6 @@ inline fun DataSource.withCustomDecoder(
width = (originalSize.width * scale).roundToInt(),
height = (originalSize.height * scale).roundToInt()
)
imageInfo = ImageInfo(
width = dstSize.width,
height = dstSize.height,
mimeType = mimeType,
)
decodeSampled(
sourceData,
dstSize.width,
Expand All @@ -64,11 +86,11 @@ inline fun DataSource.withCustomDecoder(

val resize = requestContext.computeResize(imageInfo.size)
DecodeResult(
image = decodedImage.asSketchImage(),
image = decodedImage.asImage(),
imageInfo = imageInfo,
dataFrom = dataFrom,
resize = resize,
transformeds = transformeds,
extras = null
).appliedResize(requestContext)
)
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,16 @@
package com.dot.gallery.core.decoder

import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.BitmapRegionDecoder
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import androidx.exifinterface.media.ExifInterface
import com.dot.gallery.BuildConfig
import com.dot.gallery.feature_node.data.data_source.KeychainHolder
import com.dot.gallery.feature_node.domain.model.EncryptedMedia
import com.github.panpf.sketch.ComponentRegistry
import com.github.panpf.sketch.Image
import com.github.panpf.sketch.asSketchImage
import com.github.panpf.sketch.asImage
import com.github.panpf.sketch.decode.DecodeConfig
import com.github.panpf.sketch.decode.Decoder
import com.github.panpf.sketch.decode.ImageInfo
import com.github.panpf.sketch.decode.ImageInvalidException
import com.github.panpf.sketch.decode.internal.DecodeHelper
import com.github.panpf.sketch.decode.internal.ExifOrientationHelper
import com.github.panpf.sketch.decode.internal.HelperDecoder
import com.github.panpf.sketch.decode.internal.ImageFormat
import com.github.panpf.sketch.decode.internal.newDecodeConfigByQualityParams
import com.github.panpf.sketch.decode.internal.supportBitmapRegionDecoder
import com.github.panpf.sketch.fetch.FetchResult
import com.github.panpf.sketch.request.ImageRequest
Expand All @@ -28,9 +19,6 @@ import com.github.panpf.sketch.request.get
import com.github.panpf.sketch.source.DataSource
import com.github.panpf.sketch.source.FileDataSource
import com.github.panpf.sketch.util.Rect
import com.github.panpf.sketch.util.Size
import com.github.panpf.sketch.util.toAndroidRect
import java.io.IOException

fun ComponentRegistry.Builder.supportVaultDecoder(): ComponentRegistry.Builder = apply {
addDecoder(EncryptedBitmapFactoryDecoder.Factory())
Expand Down Expand Up @@ -74,118 +62,46 @@ open class EncryptedBitmapFactoryDecoder(
}
}

/**
* Decode encrypted bitmap using BitmapFactory
*/
@Throws(IOException::class)
private fun DataSource.decodeEncryptedBitmap(keychainHolder: KeychainHolder, options: BitmapFactory.Options? = null): Bitmap? {
return with(this as FileDataSource) {
val encryptedFile = path.toFile()
val encryptedMedia = with(keychainHolder) {
encryptedFile.decrypt<EncryptedMedia>()
}
BitmapFactory.decodeByteArray(
encryptedMedia.bytes,
0,
encryptedMedia.bytes.size,
options.apply { this?.outMimeType = encryptedMedia.mimeType }
)
}
}

/**
* Use EncryptedBitmapRegionDecoder to decode part of a bitmap region
*/
@Throws(IOException::class)
private fun DataSource.decodeEncryptedRegionBitmap(
keychainHolder: KeychainHolder,
srcRect: android.graphics.Rect,
options: BitmapFactory.Options? = null
): Bitmap? {
return with(this as FileDataSource) {
val encryptedFile = path.toFile()
val encryptedMedia = with(keychainHolder) {
encryptedFile.decrypt<EncryptedMedia>()
}
val regionDecoder = if (VERSION.SDK_INT >= VERSION_CODES.S) {
BitmapRegionDecoder.newInstance(encryptedMedia.bytes, 0, encryptedMedia.bytes.size)
} else {
@Suppress("DEPRECATION")
BitmapRegionDecoder.newInstance(encryptedMedia.bytes, 0, encryptedMedia.bytes.size, false)
}
try {
regionDecoder.decodeRegion(srcRect, options.apply { this?.outMimeType = encryptedMedia.mimeType })
} finally {
regionDecoder.recycle()
}
}
}

@Throws(IOException::class)
fun DataSource.readEncryptedExifOrientation(keychainHolder: KeychainHolder): Int {
return with(this as FileDataSource) {
val encryptedFile = path.toFile()
val encryptedMedia = with(keychainHolder) {
encryptedFile.decrypt<EncryptedMedia>()
}
encryptedMedia.bytes.inputStream().use {
ExifInterface(it).getAttributeInt(
ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_UNDEFINED
)
}
}
}

private class EncryptedBitmapFactoryDecodeHelper(val request: ImageRequest, private val dataSource: DataSource) :
DecodeHelper {

private val keychainHolder = KeychainHolder(request.context)

override val imageInfo: ImageInfo by lazy { decodeImageInfo() }
override val imageInfo: ImageInfo by lazy {
dataSource.readEncryptedImageInfo(keychainHolder, exifOrientationHelper)
}
override val supportRegion: Boolean by lazy {
ImageFormat.parseMimeType(imageInfo.mimeType)?.supportBitmapRegionDecoder() == true
// The result returns null, which means unknown, but future versions may support it, so it is still worth trying.
supportBitmapRegionDecoder(imageInfo.mimeType) ?: true
}

private val exifOrientation: Int by lazy { dataSource.readEncryptedExifOrientation(keychainHolder) }
private val exifOrientationHelper by lazy { ExifOrientationHelper(exifOrientation) }

override fun decode(sampleSize: Int): Image {
val config = request.newDecodeConfigByQualityParams(imageInfo.mimeType).apply {
inSampleSize = sampleSize
val decodeConfig = DecodeConfig(request, imageInfo.mimeType, isOpaque = false).apply {
this.sampleSize = sampleSize
}
val options = config.toBitmapOptions()
val bitmap = dataSource.decodeEncryptedBitmap(keychainHolder, options)
?: throw ImageInvalidException("Invalid image. decode return null")
val image = bitmap.asSketchImage()
val correctedImage = exifOrientationHelper.applyToImage(image) ?: image
return correctedImage
val bitmap = dataSource.decodeEncryptedBitmap(
keychainHolder = keychainHolder,
config = decodeConfig,
exifOrientationHelper = exifOrientationHelper
)
return bitmap.asImage()
}

override fun decodeRegion(region: Rect, sampleSize: Int): Image {
val config = request.newDecodeConfigByQualityParams(imageInfo.mimeType).apply {
inSampleSize = sampleSize
val decodeConfig = DecodeConfig(request, imageInfo.mimeType, isOpaque = false).apply {
this.sampleSize = sampleSize
}
val options = config.toBitmapOptions()
val originalRegion =
exifOrientationHelper.applyToRect(region, imageInfo.size, reverse = true)
val bitmap = dataSource.decodeEncryptedRegionBitmap(keychainHolder, originalRegion.toAndroidRect(), options)
?: throw ImageInvalidException("Invalid image. region decode return null")
val image = bitmap.asSketchImage()
val correctedImage = exifOrientationHelper.applyToImage(image) ?: image
return correctedImage
}

private fun decodeImageInfo(): ImageInfo {
val boundOptions = BitmapFactory.Options().apply {
inJustDecodeBounds = true
}
dataSource.decodeEncryptedBitmap(keychainHolder, boundOptions)
val mimeType = boundOptions.outMimeType
val imageSize = Size(width = boundOptions.outWidth, height = boundOptions.outWidth)
val correctedImageSize = exifOrientationHelper.applyToSize(imageSize)

return ImageInfo(size = correctedImageSize, mimeType = mimeType)
val bitmap = dataSource.decodeEncryptedRegionBitmap(
keychainHolder = keychainHolder,
srcRect = region,
config = decodeConfig,
imageSize = imageInfo.size,
exifOrientationHelper = exifOrientationHelper
)
return bitmap.asImage()
}

override fun close() {
Expand Down
Loading

0 comments on commit b803aa6

Please sign in to comment.