From d99d9008dd6130762898bcf35f32e24136ba7dc5 Mon Sep 17 00:00:00 2001 From: FooIbar <118464521+FooIbar@users.noreply.github.com> Date: Fri, 13 Oct 2023 19:18:43 +0800 Subject: [PATCH] Do not modify the original file when rewriting GIF Make it a decode-time thing. --- app/src/main/cpp/gifutils.c | 16 ++-- .../ehviewer/gallery/ArchivePageLoader.kt | 8 +- .../java/com/hippo/ehviewer/image/Image.kt | 87 +++++++++++-------- .../com/hippo/ehviewer/spider/SpiderDen.kt | 11 +-- 4 files changed, 65 insertions(+), 57 deletions(-) diff --git a/app/src/main/cpp/gifutils.c b/app/src/main/cpp/gifutils.c index bd962275e..27cb6b291 100644 --- a/app/src/main/cpp/gifutils.c +++ b/app/src/main/cpp/gifutils.c @@ -49,13 +49,19 @@ Java_com_hippo_ehviewer_image_ImageKt_rewriteGifSource(JNIEnv *env, jclass clazz doRewrite(addr, size); } -JNIEXPORT void JNICALL -Java_com_hippo_ehviewer_image_ImageKt_rewriteGifSource2(JNIEnv *env, jclass clazz, jint fd) { +JNIEXPORT jobject JNICALL +Java_com_hippo_ehviewer_image_ImageKt_mmap(JNIEnv *env, jclass clazz, jint fd) { struct stat64 st; fstat64(fd, &st); size_t size = st.st_size; - byte *addr = mmap64(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (addr == MAP_FAILED) return; - doRewrite(addr, size); + byte *addr = mmap64(0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); + if (addr == MAP_FAILED) return NULL; + return (*env)->NewDirectByteBuffer(env, addr, size); +} + +JNIEXPORT void JNICALL +Java_com_hippo_ehviewer_image_ImageKt_munmap(JNIEnv *env, jclass clazz, jobject buffer) { + byte *addr = (*env)->GetDirectBufferAddress(env, buffer); + size_t size = (*env)->GetDirectBufferCapacity(env, buffer); munmap(addr, size); } diff --git a/app/src/main/java/com/hippo/ehviewer/gallery/ArchivePageLoader.kt b/app/src/main/java/com/hippo/ehviewer/gallery/ArchivePageLoader.kt index 74b2c8d0c..959befd0b 100644 --- a/app/src/main/java/com/hippo/ehviewer/gallery/ArchivePageLoader.kt +++ b/app/src/main/java/com/hippo/ehviewer/gallery/ArchivePageLoader.kt @@ -21,9 +21,7 @@ import android.os.ParcelFileDescriptor import android.util.Log import com.hippo.ehviewer.Settings.archivePasswds import com.hippo.ehviewer.image.Image -import com.hippo.ehviewer.image.rewriteGifSource import com.hippo.ehviewer.util.FileUtils -import com.hippo.ehviewer.util.isAtLeastU import com.hippo.unifile.UniFile import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineStart @@ -102,12 +100,8 @@ class ArchivePageLoader(context: Context, private val uri: Uri, passwdFlow: Flow val buffer = extractToByteBuffer(index) buffer ?: return check(buffer.isDirect) - if (!isAtLeastU) { - rewriteGifSource(buffer) - } val src = object : Image.ByteBufferSource { - override val source - get() = buffer!! + override val source: ByteBuffer = buffer override fun close() { releaseByteBuffer(buffer) diff --git a/app/src/main/java/com/hippo/ehviewer/image/Image.kt b/app/src/main/java/com/hippo/ehviewer/image/Image.kt index 91b91197c..c83774365 100644 --- a/app/src/main/java/com/hippo/ehviewer/image/Image.kt +++ b/app/src/main/java/com/hippo/ehviewer/image/Image.kt @@ -33,13 +33,13 @@ import androidx.annotation.RequiresApi import coil.decode.BitmapFactoryDecoder import coil.decode.DecodeUtils import coil.decode.ImageSource -import coil.decode.isGif import coil.request.Options import coil.size.Scale import coil.size.Size import com.hippo.ehviewer.R import com.hippo.ehviewer.util.isAtLeastO import com.hippo.ehviewer.util.isAtLeastP +import com.hippo.ehviewer.util.isAtLeastU import com.hippo.unifile.UniFile import com.hippo.unifile.openInputStream import okio.buffer @@ -48,8 +48,8 @@ import splitties.init.appCtx import java.nio.ByteBuffer import kotlin.math.min -class Image private constructor(private val src: AutoCloseable) { - var mObtainedDrawable: Drawable? = null +class Image private constructor(drawable: Drawable, private val src: AutoCloseable) { + var mObtainedDrawable: Drawable? = drawable private set val size: Int @@ -92,28 +92,58 @@ class Image private constructor(private val src: AutoCloseable) { @RequiresApi(Build.VERSION_CODES.O) lateinit var colorSpace: ColorSpace - suspend fun decode(src: UniFileSource): Image? { + suspend fun decode(src: AutoCloseable): Image? { return runCatching { - Image(src).apply { - mObtainedDrawable = if (isAtLeastP) { - decodeDrawable(src.source.imageSource) - } else { - ImageSource(src.source.openInputStream().source().buffer(), appCtx).use { - if (DecodeUtils.isGif(it.source())) { - TODO("Unsupported") + when (src) { + is UniFileSource -> { + if (isAtLeastP) { + if (!isAtLeastU) { + val buffer = src.source.openFileDescriptor("rw").use { + mmap(it.fd)!! + } + val source = object : ByteBufferSource { + override val source = buffer + override fun close() { + munmap(buffer) + src.close() + } + } + decode(source) } else { - val options = Options( - appCtx, - colorSpace = if (isAtLeastO) colorSpace else null, - size = Size(targetWidth, targetHeight), - scale = Scale.FILL, - ) + val drawable = decodeDrawable(src.source.imageSource) + if (drawable !is Animatable) src.close() + Image(drawable, src) + } + } else { + val options = Options( + appCtx, + colorSpace = if (isAtLeastO) colorSpace else null, + size = Size(targetWidth, targetHeight), + scale = Scale.FILL, + ) + val drawable = ImageSource(src.source.openInputStream().source().buffer(), appCtx).use { BitmapFactoryDecoder(it, options).decode().drawable } + src.close() + Image(drawable, src) } - }.also { - if (it !is Animatable) src.close() } + + is ByteBufferSource -> { + if (isAtLeastP) { + if (!isAtLeastU) { + rewriteGifSource(src.source) + } + val source = ImageDecoder.createSource(src.source) + val drawable = decodeDrawable(source) + if (drawable !is Animatable) src.close() + Image(drawable, src) + } else { + TODO("Unsupported") + } + } + + else -> TODO("Unsupported") } }.onFailure { src.close() @@ -121,22 +151,6 @@ class Image private constructor(private val src: AutoCloseable) { }.getOrNull() } - fun decode(src: ByteBufferSource) = runCatching { - Image(src).apply { - mObtainedDrawable = if (isAtLeastP) { - val source = ImageDecoder.createSource(src.source) - decodeDrawable(source) - } else { - TODO("Unsupported") - }.also { - if (it !is Animatable) src.close() - } - } - }.onFailure { - src.close() - it.printStackTrace() - }.getOrNull() - @RequiresApi(Build.VERSION_CODES.P) private fun decodeDrawable(src: Source) = ImageDecoder.decodeDrawable(src) { decoder, info, _ -> if (Build.VERSION.SDK_INT == Build.VERSION_CODES.P) { @@ -184,4 +198,5 @@ class Image private constructor(private val src: AutoCloseable) { } external fun rewriteGifSource(buffer: ByteBuffer) -external fun rewriteGifSource2(fd: Int) +external fun mmap(fd: Int): ByteBuffer? +external fun munmap(buffer: ByteBuffer) diff --git a/app/src/main/java/com/hippo/ehviewer/spider/SpiderDen.kt b/app/src/main/java/com/hippo/ehviewer/spider/SpiderDen.kt index b28ed1f7b..c4b1a9360 100644 --- a/app/src/main/java/com/hippo/ehviewer/spider/SpiderDen.kt +++ b/app/src/main/java/com/hippo/ehviewer/spider/SpiderDen.kt @@ -31,9 +31,7 @@ import com.hippo.ehviewer.cronet.execute import com.hippo.ehviewer.download.downloadLocation import com.hippo.ehviewer.gallery.SUPPORT_IMAGE_EXTENSIONS import com.hippo.ehviewer.image.Image.UniFileSource -import com.hippo.ehviewer.image.rewriteGifSource2 import com.hippo.ehviewer.util.FileUtils -import com.hippo.ehviewer.util.isAtLeastU import com.hippo.ehviewer.util.isCronetSupported import com.hippo.ehviewer.util.sendTo import com.hippo.unifile.UniFile @@ -162,13 +160,8 @@ class SpiderDen(mGalleryInfo: GalleryInfo) { } private suspend fun saveResponseMeta(index: Int, ext: String, length: Long, fops: suspend (UniFile) -> Unit): Boolean { - suspend fun realFops(f: UniFile) = fops(f).apply { - if (ext.lowercase() == "gif" && !isAtLeastU) { - f.openFileDescriptor("rw").use { rewriteGifSource2(it.fd) } - } - } findDownloadFileForIndex(index, ext)?.run { - realFops(this) + fops(this) return true } @@ -177,7 +170,7 @@ class SpiderDen(mGalleryInfo: GalleryInfo) { val key = getImageKey(mGid, index) return sCache.suspendEdit(key) { metadata.toFile().writeText(ext) - realFops(UniFile.fromFile(data.toFile())!!) + fops(UniFile.fromFile(data.toFile())!!) } } return false