Image wrap a CoilImage (#588)
* Image wrap a CoilImage * Make ImageSource sealed --------- Co-authored-by: GG Bond <whatever@tagkong.com>
This commit is contained in:
1 parent
6e73fcc8c9
commit
5d116d9477
8 files changed
+79
-77
No files matched your search
+3
-4
@@ -9,7 +9,6 @@ import android.graphics.Paint
|
||||
import android.graphics.PointF
|
||||
import android.graphics.Rect
|
||||
import android.graphics.RectF
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.util.AttributeSet
|
||||
import android.util.TypedValue
|
||||
import android.view.GestureDetector
|
||||
@@ -231,10 +230,10 @@ open class SubsamplingScaleImageView @JvmOverloads constructor(
|
||||
*
|
||||
* @param bitmap Image source.
|
||||
*/
|
||||
fun setImage(bitmap: BitmapDrawable) {
|
||||
fun setImage(bitmap: Bitmap, rect: Rect) {
|
||||
reset(true)
|
||||
srcRect = Rect(bitmap.bounds)
|
||||
onImageLoaded(bitmap.bitmap)
|
||||
srcRect = Rect(rect)
|
||||
onImageLoaded(bitmap)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,6 +24,7 @@ import android.os.ParcelFileDescriptor
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.hippo.ehviewer.Settings.archivePasswds
|
||||
import com.hippo.ehviewer.image.ByteBufferSource
|
||||
import com.hippo.ehviewer.image.Image
|
||||
import com.hippo.ehviewer.jni.closeArchive
|
||||
import com.hippo.ehviewer.jni.extractToByteBuffer
|
||||
@@ -110,7 +111,7 @@ class ArchivePageLoader(context: Context, private val uri: Uri, passwdProvider:
|
||||
val buffer = extractToByteBuffer(index)
|
||||
buffer ?: return
|
||||
check(buffer.isDirect)
|
||||
val src = object : Image.ByteBufferSource {
|
||||
val src = object : ByteBufferSource {
|
||||
override val source: ByteBuffer = buffer
|
||||
|
||||
override fun close() {
|
||||
|
||||
@@ -22,16 +22,16 @@ import android.graphics.Bitmap
|
||||
import android.graphics.ColorSpace
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.Animatable
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.graphics.drawable.toDrawable
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import coil3.BitmapImage
|
||||
import coil3.DrawableImage
|
||||
import coil3.Image as CoilImage
|
||||
import coil3.asCoilImage
|
||||
import coil3.executeBlocking
|
||||
import coil3.imageLoader
|
||||
import coil3.request.CachePolicy
|
||||
@@ -58,45 +58,41 @@ import splitties.init.appCtx
|
||||
|
||||
private val CropAspect = 0.1..100.0
|
||||
|
||||
class Image private constructor(drawable: Drawable, private val src: AutoCloseable) {
|
||||
val size = drawable.run { intrinsicHeight * intrinsicWidth * 4 * if (this is Animatable) 4 else 1 }
|
||||
private val intrinsicRect = drawable.run { Rect(0, 0, intrinsicWidth, intrinsicHeight) }
|
||||
|
||||
var mObtainedDrawable: Drawable? = if (drawable is Animatable) {
|
||||
// Cannot crop animated image's border
|
||||
drawable
|
||||
class Image private constructor(image: CoilImage, private val src: AutoCloseable) {
|
||||
val size = image.size
|
||||
private val intrinsicRect = image.run { Rect(0, 0, width, height) }
|
||||
val rect: Rect = if (Settings.cropBorder.value && image is BitmapImage) {
|
||||
val bitmap = image.bitmap
|
||||
val array = detectBorder(bitmap)
|
||||
val r = Rect(array[0], array[1], array[2], array[3])
|
||||
val aspectAfterCrop = r.height().toFloat() / r.width()
|
||||
if (aspectAfterCrop in CropAspect) r else intrinsicRect
|
||||
} else {
|
||||
if (Settings.cropBorder.value) {
|
||||
val bitmap = (drawable as BitmapDrawable).bitmap
|
||||
val array = detectBorder(bitmap)
|
||||
val rect = with(bitmap) {
|
||||
val r = Rect(array[0], array[1], array[2], array[3])
|
||||
val aspectAfterCrop = r.height().toFloat() / r.width()
|
||||
if (aspectAfterCrop in CropAspect) r else intrinsicRect
|
||||
}
|
||||
intrinsicRect
|
||||
}
|
||||
|
||||
val final = if (isAtLeastQ) {
|
||||
// Upload to Graphical Buffer to accelerate render
|
||||
bitmap.copy(Bitmap.Config.HARDWARE, false).apply {
|
||||
bitmap.recycle()
|
||||
}
|
||||
} else {
|
||||
bitmap
|
||||
}
|
||||
var innerImage: CoilImage? = if (image is BitmapImage && isAtLeastQ) {
|
||||
val bitmap = image.bitmap
|
||||
|
||||
final.toDrawable(appCtx.resources).apply { bounds = rect }
|
||||
} else {
|
||||
drawable.apply { bounds = intrinsicRect }
|
||||
}
|
||||
// Upload to Graphical Buffer to accelerate render
|
||||
bitmap.copy(Bitmap.Config.HARDWARE, false).apply {
|
||||
bitmap.recycle()
|
||||
}.asCoilImage()
|
||||
} else {
|
||||
image
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun recycle() {
|
||||
(mObtainedDrawable as? Animatable)?.stop()
|
||||
(mObtainedDrawable as? BitmapDrawable)?.bitmap?.recycle()
|
||||
mObtainedDrawable?.callback = null
|
||||
if (mObtainedDrawable is Animatable) src.close()
|
||||
mObtainedDrawable = null
|
||||
when (val image = innerImage ?: return) {
|
||||
is DrawableImage -> {
|
||||
(image.drawable as Animatable).stop()
|
||||
image.drawable.callback = null
|
||||
src.close()
|
||||
}
|
||||
is BitmapImage -> image.bitmap.recycle()
|
||||
}
|
||||
innerImage = null
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -110,7 +106,7 @@ class Image private constructor(drawable: Drawable, private val src: AutoCloseab
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
lateinit var colorSpace: ColorSpace
|
||||
|
||||
private suspend fun Either<ByteBufferSource, UniFileSource>.decodeCoil(): Drawable {
|
||||
private suspend fun Either<ByteBufferSource, UniFileSource>.decodeCoil(): CoilImage {
|
||||
val req = appCtx.imageRequest {
|
||||
onLeft { data(it.source) }
|
||||
onRight { data(it.source.uri) }
|
||||
@@ -125,14 +121,13 @@ class Image private constructor(drawable: Drawable, private val src: AutoCloseab
|
||||
}
|
||||
memoryCachePolicy(CachePolicy.DISABLED)
|
||||
}
|
||||
val image = when (val result = appCtx.imageLoader.execute(req)) {
|
||||
return when (val result = appCtx.imageLoader.execute(req)) {
|
||||
is SuccessResult -> result.image
|
||||
is ErrorResult -> throw result.throwable
|
||||
}
|
||||
return image.asDrawable(appCtx.resources)
|
||||
}
|
||||
|
||||
suspend fun decode(src: AutoCloseable): Image? {
|
||||
suspend fun decode(src: ImageSource): Image? {
|
||||
return runCatching {
|
||||
when (src) {
|
||||
is UniFileSource -> {
|
||||
@@ -152,21 +147,19 @@ class Image private constructor(drawable: Drawable, private val src: AutoCloseab
|
||||
}
|
||||
}
|
||||
}
|
||||
val drawable = src.right().decodeCoil()
|
||||
if (drawable !is Animatable) src.close()
|
||||
Image(drawable, src)
|
||||
val image = src.right().decodeCoil()
|
||||
if (image is BitmapImage) src.close()
|
||||
Image(image, src)
|
||||
}
|
||||
|
||||
is ByteBufferSource -> {
|
||||
if (isAtLeastP && !isAtLeastU) {
|
||||
rewriteGifSource(src.source)
|
||||
}
|
||||
val drawable = src.left().decodeCoil()
|
||||
if (drawable !is Animatable) src.close()
|
||||
Image(drawable, src)
|
||||
val image = src.left().decodeCoil()
|
||||
if (image is BitmapImage) src.close()
|
||||
Image(image, src)
|
||||
}
|
||||
|
||||
else -> TODO("Unsupported")
|
||||
}
|
||||
}.onFailure {
|
||||
src.close()
|
||||
@@ -188,14 +181,16 @@ class Image private constructor(drawable: Drawable, private val src: AutoCloseab
|
||||
return (image as BitmapImage).bitmap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface UniFileSource : AutoCloseable {
|
||||
val source: UniFile
|
||||
}
|
||||
sealed interface ImageSource : AutoCloseable
|
||||
|
||||
interface ByteBufferSource : AutoCloseable {
|
||||
val source: ByteBuffer
|
||||
}
|
||||
interface UniFileSource : ImageSource {
|
||||
val source: UniFile
|
||||
}
|
||||
|
||||
interface ByteBufferSource : ImageSource {
|
||||
val source: ByteBuffer
|
||||
}
|
||||
|
||||
private external fun detectBorder(bitmap: Bitmap): IntArray
|
||||
@@ -24,7 +24,7 @@ import com.hippo.ehviewer.client.getImageKey
|
||||
import com.hippo.ehviewer.coil.read
|
||||
import com.hippo.ehviewer.coil.suspendEdit
|
||||
import com.hippo.ehviewer.download.downloadLocation
|
||||
import com.hippo.ehviewer.image.Image.UniFileSource
|
||||
import com.hippo.ehviewer.image.UniFileSource
|
||||
import com.hippo.ehviewer.util.FileUtils
|
||||
import com.hippo.ehviewer.util.sendTo
|
||||
import com.hippo.unifile.UniFile
|
||||
|
||||
@@ -19,7 +19,7 @@ abstract class PageLoader {
|
||||
} else {
|
||||
(OSUtils.appMaxMemory / 3 * 2).toInt()
|
||||
},
|
||||
sizeOf = { _, v -> v.size },
|
||||
sizeOf = { _, v -> v.size.toInt() },
|
||||
onEntryRemoved = { _, _, o, _ -> o.recycle() },
|
||||
)
|
||||
val mPages by lazy {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package eu.kanade.tachiyomi.ui.reader.viewer
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.PointF
|
||||
import android.graphics.Rect
|
||||
import android.graphics.RectF
|
||||
import android.graphics.drawable.Animatable
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.util.AttributeSet
|
||||
import android.view.GestureDetector
|
||||
@@ -18,10 +19,13 @@ import androidx.annotation.StyleRes
|
||||
import androidx.appcompat.widget.AppCompatImageView
|
||||
import androidx.core.os.postDelayed
|
||||
import androidx.core.view.isVisible
|
||||
import coil3.BitmapImage
|
||||
import coil3.DrawableImage
|
||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.Companion.EASE_OUT_QUAD
|
||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.Companion.SCALE_TYPE_CENTER_INSIDE
|
||||
import com.github.chrisbanes.photoview.PhotoView
|
||||
import com.hippo.ehviewer.image.Image
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
||||
import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonSubsamplingImageView
|
||||
import eu.kanade.tachiyomi.util.system.animatorDurationScale
|
||||
@@ -120,14 +124,16 @@ open class ReaderPageImageView @JvmOverloads constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun setImage(drawable: Drawable, config: Config) {
|
||||
fun setImage(drawable: Image, config: Config) {
|
||||
this.config = config
|
||||
if (drawable is Animatable) {
|
||||
val image = drawable.innerImage ?: return
|
||||
if (image is DrawableImage) {
|
||||
prepareAnimatedImageView()
|
||||
setAnimatedImage(drawable, config)
|
||||
} else {
|
||||
setAnimatedImage(image.drawable, config)
|
||||
}
|
||||
if (image is BitmapImage) {
|
||||
prepareNonAnimatedImageView()
|
||||
setNonAnimatedImage(drawable, config)
|
||||
setNonAnimatedImage(image.bitmap, drawable.rect, config)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,7 +239,8 @@ open class ReaderPageImageView @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
private fun setNonAnimatedImage(
|
||||
image: Drawable,
|
||||
image: Bitmap,
|
||||
bounds: Rect,
|
||||
config: Config,
|
||||
) = (pageView as? SubsamplingScaleImageView)?.apply {
|
||||
setDoubleTapZoomDuration(config.zoomDuration.getSystemScaledDuration())
|
||||
@@ -254,7 +261,7 @@ open class ReaderPageImageView @JvmOverloads constructor(
|
||||
},
|
||||
)
|
||||
|
||||
setImage(image as BitmapDrawable)
|
||||
setImage(image, bounds)
|
||||
isVisible = true
|
||||
}
|
||||
|
||||
|
||||
@@ -2,11 +2,11 @@ package eu.kanade.tachiyomi.ui.reader.viewer.pager
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.Animatable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.view.LayoutInflater
|
||||
import androidx.core.view.isVisible
|
||||
import coil3.BitmapImage
|
||||
import com.hippo.ehviewer.databinding.ReaderErrorBinding
|
||||
import com.hippo.ehviewer.image.Image
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
||||
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderPageImageView
|
||||
@@ -97,7 +97,7 @@ class PagerPageHolder(
|
||||
setDownloading()
|
||||
}
|
||||
Page.State.READY -> {
|
||||
page.image?.mObtainedDrawable?.let { setImage(it) }
|
||||
page.image?.let { setImage(it) }
|
||||
cancelProgressJob()
|
||||
}
|
||||
Page.State.ERROR -> {
|
||||
@@ -148,12 +148,12 @@ class PagerPageHolder(
|
||||
/**
|
||||
* Called when the page is ready.
|
||||
*/
|
||||
private fun setImage(drawable: Drawable) {
|
||||
private fun setImage(image: Image) {
|
||||
progressIndicator.setProgress(0)
|
||||
progressIndicator.hide()
|
||||
errorLayout?.root?.isVisible = false
|
||||
setImage(
|
||||
drawable,
|
||||
image,
|
||||
Config(
|
||||
zoomDuration = viewer.config.doubleTapAnimDuration,
|
||||
minimumScaleType = viewer.config.imageScaleType,
|
||||
@@ -162,7 +162,7 @@ class PagerPageHolder(
|
||||
landscapeZoom = viewer.config.landscapeZoom,
|
||||
),
|
||||
)
|
||||
if (drawable !is Animatable) {
|
||||
if (image.innerImage is BitmapImage) {
|
||||
pageBackground = background
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,7 +1,6 @@
|
||||
package eu.kanade.tachiyomi.ui.reader.viewer.webtoon
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||
@@ -10,6 +9,7 @@ import android.widget.FrameLayout
|
||||
import androidx.core.view.isVisible
|
||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||
import com.hippo.ehviewer.databinding.ReaderErrorBinding
|
||||
import com.hippo.ehviewer.image.Image
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
||||
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderPageImageView
|
||||
@@ -145,7 +145,7 @@ class WebtoonPageHolder(
|
||||
setDownloading()
|
||||
}
|
||||
Page.State.READY -> {
|
||||
page?.image?.mObtainedDrawable?.let { setImage(it) }
|
||||
page?.image?.let { setImage(it) }
|
||||
cancelProgressJob()
|
||||
}
|
||||
Page.State.ERROR -> {
|
||||
@@ -194,7 +194,7 @@ class WebtoonPageHolder(
|
||||
/**
|
||||
* Called when the page is ready.
|
||||
*/
|
||||
private fun setImage(drawable: Drawable) {
|
||||
private fun setImage(drawable: Image) {
|
||||
progressIndicator.setProgress(0)
|
||||
progressIndicator.hide()
|
||||
removeErrorLayout()
|
||||
|
||||
Reference in new issue
Block a user