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:
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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user