Refactor Image source provider

Skia use SKMemoryStream to read file mapping data
SKMemoryStream always use memcpy to emulate posix read stream behaviour, cannot take advantages of file mapping reading

* https://github.com/google/skia/blob/1f193df9b393d50da39570dab77a0bb5d28ec8ef/src/core/SkStream.cpp#L335
This commit is contained in:
tarsin committed 2023-02-13 18:21:57 +08:00
1 parent 0e6156e51c
commit d30379251d
5 files changed
+47 -85

No files matched your search

@@ -18,6 +18,7 @@
package com.hippo package com.hippo
import android.content.Context import android.content.Context
import android.graphics.ImageDecoder
import android.net.Uri import android.net.Uri
import com.hippo.image.Image import com.hippo.image.Image
import java.nio.ByteBuffer import java.nio.ByteBuffer
@@ -40,15 +41,15 @@ class UriArchiveAccessor(ctx: Context, uri: Uri) {
pfd.close() pfd.close()
} }
fun getImageSource(index: Int): Image.ByteBufferSource? { fun getImageSource(index: Int): Image.CloseableSource? {
val buffer = extractToByteBuffer(index) val buffer = extractToByteBuffer(index)
buffer ?: return null buffer ?: return null
check(buffer.isDirect) check(buffer.isDirect)
Image.rewriteGifSource(buffer) Image.rewriteGifSource(buffer)
return object : Image.ByteBufferSource { val source = ImageDecoder.createSource(buffer)
override fun getByteBuffer(): ByteBuffer { return object : Image.CloseableSource {
return buffer override val source: ImageDecoder.Source
} get() = source
override fun close() { override fun close() {
releaseByteBuffer(buffer) releaseByteBuffer(buffer)
@@ -54,7 +54,7 @@ public class ArchivePageLoader extends PageLoader2 {
public static PVLock pv = new PVLock(0); public static PVLock pv = new PVLock(0);
private final UriArchiveAccessor archiveAccessor; private final UriArchiveAccessor archiveAccessor;
private final Stack<Integer> requests = new Stack<>(); private final Stack<Integer> requests = new Stack<>();
private final LinkedHashMap<Integer, Image.ByteBufferSource> streams = new LinkedHashMap<>(); private final LinkedHashMap<Integer, Image.CloseableSource> streams = new LinkedHashMap<>();
private final Thread[] decodeThread = new Thread[]{ private final Thread[] decodeThread = new Thread[]{
new Thread(new DecodeTask()), new Thread(new DecodeTask()),
new Thread(new DecodeTask()), new Thread(new DecodeTask()),
@@ -303,7 +303,7 @@ public class ArchivePageLoader extends PageLoader2 {
} }
} }
Image.ByteBufferSource src = archiveAccessor.getImageSource(index); Image.CloseableSource src = archiveAccessor.getImageSource(index);
synchronized (streams) { synchronized (streams) {
streams.put(index, src); streams.put(index, src);
@@ -318,7 +318,7 @@ public class ArchivePageLoader extends PageLoader2 {
public void run() { public void run() {
while (!Thread.currentThread().isInterrupted()) { while (!Thread.currentThread().isInterrupted()) {
int index; int index;
Image.ByteBufferSource src; Image.CloseableSource src;
synchronized (streams) { synchronized (streams) {
if (streams.isEmpty()) { if (streams.isEmpty()) {
try { try {
@@ -329,8 +329,8 @@ public class ArchivePageLoader extends PageLoader2 {
continue; continue;
} }
Iterator<Map.Entry<Integer, Image.ByteBufferSource>> iterator = streams.entrySet().iterator(); Iterator<Map.Entry<Integer, Image.CloseableSource>> iterator = streams.entrySet().iterator();
Map.Entry<Integer, Image.ByteBufferSource> entry = iterator.next(); Map.Entry<Integer, Image.CloseableSource> entry = iterator.next();
iterator.remove(); iterator.remove();
index = entry.getKey(); index = entry.getKey();
src = entry.getValue(); src = entry.getValue();
@@ -15,12 +15,11 @@
*/ */
package com.hippo.ehviewer.spider package com.hippo.ehviewer.spider
import android.graphics.ImageDecoder
import coil.decode.DecodeUtils import coil.decode.DecodeUtils
import coil.decode.FrameDelayRewritingSource import coil.decode.FrameDelayRewritingSource
import coil.decode.isGif import coil.decode.isGif
import coil.disk.DiskCache import coil.disk.DiskCache
import com.hippo.Native.mapFd
import com.hippo.Native.unmapDirectByteBuffer
import com.hippo.ehviewer.EhApplication import com.hippo.ehviewer.EhApplication
import com.hippo.ehviewer.EhApplication.Companion.application import com.hippo.ehviewer.EhApplication.Companion.application
import com.hippo.ehviewer.EhDB import com.hippo.ehviewer.EhDB
@@ -30,7 +29,7 @@ import com.hippo.ehviewer.client.EhRequestBuilder
import com.hippo.ehviewer.client.EhUtils.getSuitableTitle import com.hippo.ehviewer.client.EhUtils.getSuitableTitle
import com.hippo.ehviewer.client.data.GalleryInfo import com.hippo.ehviewer.client.data.GalleryInfo
import com.hippo.ehviewer.gallery.PageLoader2 import com.hippo.ehviewer.gallery.PageLoader2
import com.hippo.image.Image.ByteBufferSource import com.hippo.image.Image.CloseableSource
import com.hippo.unifile.RawFile import com.hippo.unifile.RawFile
import com.hippo.unifile.UniFile import com.hippo.unifile.UniFile
import com.hippo.yorozuya.FileUtils import com.hippo.yorozuya.FileUtils
@@ -43,13 +42,12 @@ import java.io.File
import java.io.FileInputStream import java.io.FileInputStream
import java.io.FileOutputStream import java.io.FileOutputStream
import java.io.IOException import java.io.IOException
import java.io.RandomAccessFile
import java.nio.ByteBuffer
import java.nio.channels.FileChannel import java.nio.channels.FileChannel
import java.util.Locale import java.util.Locale
import kotlin.io.path.readText import kotlin.io.path.readText
private val client = EhApplication.okHttpClient private val client = EhApplication.okHttpClient
private val contentResolver = application.contentResolver
class SpiderDen(private val mGalleryInfo: GalleryInfo) { class SpiderDen(private val mGalleryInfo: GalleryInfo) {
private val mGid: Long = mGalleryInfo.gid private val mGid: Long = mGalleryInfo.gid
@@ -180,7 +178,9 @@ class SpiderDen(private val mGalleryInfo: GalleryInfo) {
suspend fun doSave(chan: FileChannel): Long { suspend fun doSave(chan: FileChannel): Long {
var receivedSize: Long = 0 var receivedSize: Long = 0
body.source().use { responseSource -> body.source().use { responseSource ->
val source = if (DecodeUtils.isGif(responseSource)) FrameDelayRewritingSource(responseSource).buffer() else responseSource val source = if (DecodeUtils.isGif(responseSource)) FrameDelayRewritingSource(
responseSource
).buffer() else responseSource
source.use { source.use {
while (true) { while (true) {
currentCoroutineContext().ensureActive() currentCoroutineContext().ensureActive()
@@ -278,29 +278,19 @@ class SpiderDen(private val mGalleryInfo: GalleryInfo) {
?.name.let { FileUtils.getExtensionFromFilename(it) } ?.name.let { FileUtils.getExtensionFromFilename(it) }
} }
fun getImageSource(index: Int): ByteBufferSource? { fun getImageSource(index: Int): CloseableSource? {
if (mMode == SpiderQueen.MODE_READ) { if (mMode == SpiderQueen.MODE_READ) {
val key = EhCacheKeyFactory.getImageKey(mGid, index) val key = EhCacheKeyFactory.getImageKey(mGid, index)
val snapshot: DiskCache.Snapshot? = sCache[key] val snapshot: DiskCache.Snapshot? = sCache[key]
if (snapshot != null) { if (snapshot != null) {
try { val source = ImageDecoder.createSource(snapshot.data.toFile())
val file = RandomAccessFile(snapshot.data.toFile(), "rw") return object : CloseableSource {
val chan = file.channel override val source: ImageDecoder.Source
val map = chan.map(FileChannel.MapMode.PRIVATE, 0, file.length()) get() = source
return object : ByteBufferSource {
override fun getByteBuffer(): ByteBuffer {
return map
}
@Throws(Exception::class) override fun close() {
override fun close() { snapshot.close()
chan.close()
file.close()
snapshot.close()
}
} }
} catch (e: Throwable) {
e.printStackTrace()
} }
} }
} }
@@ -312,46 +302,21 @@ class SpiderDen(private val mGalleryInfo: GalleryInfo) {
} }
} }
if (file is RawFile) { if (file is RawFile) {
try { val source = ImageDecoder.createSource(file.mFile)
val file2 = RandomAccessFile(file.mFile, "rw") return object : CloseableSource {
val chan = file2.channel override val source: ImageDecoder.Source
val map = chan.map(FileChannel.MapMode.PRIVATE, 0, file2.length()) get() = source
return object : ByteBufferSource {
override fun getByteBuffer(): ByteBuffer {
return map
}
@Throws(Exception::class) override fun close() {}
override fun close() {
chan.close()
file2.close()
}
}
} catch (e: Throwable) {
e.printStackTrace()
} }
} }
if (file != null) { if (file != null) {
try { val source = ImageDecoder.createSource(contentResolver, file.uri)
val pfd = application.contentResolver.openFileDescriptor(file.uri, "rw") return object : CloseableSource {
val map = mapFd(pfd!!.fd, pfd.statSize) override val source: ImageDecoder.Source
if (map != null) { get() = source
return object : ByteBufferSource {
override fun getByteBuffer(): ByteBuffer {
return map
}
@Throws(Exception::class) override fun close() {}
override fun close() {
unmapDirectByteBuffer(map)
pfd.close()
}
}
} else {
pfd.close()
}
} catch (e: Throwable) {
e.printStackTrace()
} }
} }
return null return null
@@ -855,7 +855,7 @@ public final class SpiderQueen implements Runnable {
continue; continue;
} }
Image.ByteBufferSource src = mSpiderDen.getImageSource(index); Image.CloseableSource src = mSpiderDen.getImageSource(index);
if (src == null) { if (src == null) {
resetDecodeIndex(); resetDecodeIndex();
// Can't find the file, it might be removed from cache, // Can't find the file, it might be removed from cache,
+12 -16
View File
@@ -21,7 +21,7 @@ import android.graphics.ColorSpace
import android.graphics.ImageDecoder import android.graphics.ImageDecoder
import android.graphics.ImageDecoder.ImageInfo import android.graphics.ImageDecoder.ImageInfo
import android.graphics.ImageDecoder.Source import android.graphics.ImageDecoder.Source
import android.graphics.drawable.Animatable import android.graphics.drawable.AnimatedImageDrawable
import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.Build import android.os.Build
@@ -30,9 +30,9 @@ import com.hippo.ehviewer.R
import java.nio.ByteBuffer import java.nio.ByteBuffer
import kotlin.math.min import kotlin.math.min
class Image private constructor(source: Source, private var src: ByteBufferSource?) { class Image private constructor(private val src: CloseableSource) {
var mObtainedDrawable: Drawable? = var mObtainedDrawable: Drawable? =
ImageDecoder.decodeDrawable(source) { decoder: ImageDecoder, info: ImageInfo, _: Source -> ImageDecoder.decodeDrawable(src.source) { decoder: ImageDecoder, info: ImageInfo, _: Source ->
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.P) { if (Build.VERSION.SDK_INT == Build.VERSION_CODES.P) {
// Allocating hardware bitmap may cause a crash on framework versions prior to Android Q // Allocating hardware bitmap may cause a crash on framework versions prior to Android Q
decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE
@@ -43,23 +43,21 @@ class Image private constructor(source: Source, private var src: ByteBufferSourc
) )
}.also { }.also {
(it as? BitmapDrawable)?.run { (it as? BitmapDrawable)?.run {
src?.close() src.close()
src = null
} }
} }
private set private set
val size: Int val size: Int
get() = mObtainedDrawable!!.run { intrinsicHeight * intrinsicWidth * 4 * if (this is Animatable) 4 else 1 } get() = mObtainedDrawable!!.run { intrinsicHeight * intrinsicWidth * 4 * if (this is AnimatedImageDrawable) 4 else 1 }
@Synchronized @Synchronized
fun recycle() { fun recycle() {
(mObtainedDrawable as? Animatable)?.stop() (mObtainedDrawable as? AnimatedImageDrawable)?.stop()
(mObtainedDrawable as? BitmapDrawable)?.bitmap?.recycle() (mObtainedDrawable as? BitmapDrawable)?.bitmap?.recycle()
mObtainedDrawable?.callback = null mObtainedDrawable?.callback = null
mObtainedDrawable = null mObtainedDrawable = null
src?.close() (mObtainedDrawable as? AnimatedImageDrawable)?.let { src.close() }
src = null
} }
companion object { companion object {
@@ -91,10 +89,9 @@ class Image private constructor(source: Source, private var src: ByteBufferSourc
) )
@JvmStatic @JvmStatic
fun decode(src: ByteBufferSource): Image? { fun decode(src: CloseableSource): Image? {
val directBuffer = src.getByteBuffer()
return runCatching { return runCatching {
Image(ImageDecoder.createSource(directBuffer), src) Image(src)
}.onFailure { }.onFailure {
src.close() src.close()
it.printStackTrace() it.printStackTrace()
@@ -105,8 +102,7 @@ class Image private constructor(source: Source, private var src: ByteBufferSourc
external fun rewriteGifSource(buffer: ByteBuffer) external fun rewriteGifSource(buffer: ByteBuffer)
} }
interface ByteBufferSource : AutoCloseable { interface CloseableSource : AutoCloseable {
// A read-write direct bytebuffer, it may in native heap or file mapping val source: Source
fun getByteBuffer(): ByteBuffer
} }
} }