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