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