General Fixes to reader + shifting

*Fixed the lag when initally shifting form single to double page (stopped using with ui context)
* Added a confidence to the padding check, since its easier to know if one page belongs to one side of a pair than another sometimes (still not perfect, but it never will be
This commit is contained in:
Jays2Kings 2023-03-06 20:20:42 -05:00
parent 443c581ea6
commit 4ef3f2437b
5 changed files with 109 additions and 56 deletions

View file

@ -454,7 +454,9 @@ class ReaderActivity : BaseActivity<ReaderActivityBinding>() {
outState.putBoolean(::menuVisible.name, menuVisible)
(viewer as? PagerViewer)?.let { pViewer ->
val config = pViewer.config
outState.putBoolean(SHIFT_DOUBLE_PAGES, config.shiftDoublePage)
if (config.doublePages) {
outState.putBoolean(SHIFT_DOUBLE_PAGES, config.shiftDoublePage)
}
if (config.shiftDoublePage && config.doublePages) {
pViewer.getShiftedPage()?.let {
outState.putInt(SHIFTED_PAGE_INDEX, it.index)
@ -605,8 +607,11 @@ class ReaderActivity : BaseActivity<ReaderActivityBinding>() {
fun shiftDoublePages(forceShift: Boolean? = null, page: ReaderPage? = null) {
(viewer as? PagerViewer)?.let { pViewer ->
if (forceShift == pViewer.config.shiftDoublePage) return
if (page != null && pViewer.getShiftedPage() == page) return
if (forceShift == pViewer.config.shiftDoublePage &&
page != null && page == pViewer.getShiftedPage()
) {
return
}
pViewer.config.shiftDoublePage = !pViewer.config.shiftDoublePage
viewModel.state.value.viewerChapters?.let {
pViewer.updateShifting(page)
@ -617,7 +622,7 @@ class ReaderActivity : BaseActivity<ReaderActivityBinding>() {
}
fun isFirstPageFull(): Boolean = viewModel.getCurrentChapter()?.pages?.get(0)?.fullPage == true
fun isFirstPageEnd(): Boolean = viewModel.getCurrentChapter()?.pages?.get(0)?.isEndPage == true
fun getFirstPage(): ReaderPage? = viewModel.getCurrentChapter()?.pages?.get(0)
private fun popToMain() {
if (fromUrl) {
@ -1278,10 +1283,8 @@ class ReaderActivity : BaseActivity<ReaderActivityBinding>() {
private fun shouldShiftDoublePages(currentIndex: Int): Boolean {
val currentChapter = viewModel.getCurrentChapter()
val currentPage = currentChapter?.pages?.get(currentIndex)
return (
currentIndex +
(currentPage?.isEndPage == true && currentPage.fullPage != true).toInt() +
(currentChapter?.pages?.take(currentIndex)?.count { it.alonePage } ?: 0)
) % 2 != 0
}
@ -1299,9 +1302,6 @@ class ReaderActivity : BaseActivity<ReaderActivityBinding>() {
}
indexChapterToShift = null
indexPageToShift = null
} else if (lastShiftDoubleState != null) {
val currentIndex = viewerChapters.currChapter.requestedPage
(viewer as? PagerViewer)?.config?.shiftDoublePage = shouldShiftDoublePages(currentIndex)
}
val currentChapterPageCount = viewerChapters.currChapter.pages?.size ?: 1
binding.readerNav.root.visibility = when {

View file

@ -18,6 +18,7 @@ open class ReaderPage(
var firstHalf: Boolean? = null,
var longPage: Boolean? = null,
var isEndPage: Boolean? = null,
var paddedPageConfidence: Int = 0,
var isStartPage: Boolean? = null,
) : Page(index, url, imageUrl, null) {

View file

@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.reader.viewer.pager
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color
import android.graphics.PointF
@ -25,6 +26,7 @@ import eu.kanade.tachiyomi.ui.reader.viewer.ReaderPageImageView
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressBar
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerConfig.ZoomType
import eu.kanade.tachiyomi.util.system.ImageUtil
import eu.kanade.tachiyomi.util.system.ImageUtil.isPagePadded
import eu.kanade.tachiyomi.util.system.ThemeUtil
import eu.kanade.tachiyomi.util.system.bottomCutoutInset
import eu.kanade.tachiyomi.util.system.dpToPx
@ -474,12 +476,12 @@ class PagerPageHolder(
errorLayout?.isVisible = false
cancelReadImageHeader()
val streamFn = page.stream ?: return
val streamFn2 = extraPage?.stream
var openStream: InputStream? = null
readImageHeaderJob = scope.launchIO {
val streamFn = page.stream ?: return@launchIO
val streamFn2 = extraPage?.stream
var openStream: InputStream? = null
try {
val stream = streamFn().buffered(16)
@ -492,12 +494,12 @@ class PagerPageHolder(
if (viewer.config.readerTheme >= 2) {
val bgType = getBGType(viewer.config.readerTheme, context)
if (page.bg != null && page.bgType == bgType) {
setImage(openStream!!, false, imageConfig)
setImage(openStream, false, imageConfig)
pageView?.background = page.bg
}
// if the user switches to automatic when pages are already cached, the bg needs to be loaded
else {
val bytesArray = openStream!!.readBytes()
val bytesArray = openStream.readBytes()
val bytesStream = bytesArray.inputStream()
setImage(bytesStream, false, imageConfig)
closeStreams(bytesStream)
@ -513,10 +515,10 @@ class PagerPageHolder(
}
}
} else {
setImage(openStream!!, false, imageConfig)
setImage(openStream, false, imageConfig)
}
} else {
setImage(openStream!!, true, imageConfig)
setImage(openStream, true, imageConfig)
if (viewer.config.readerTheme >= 2 && page.bg != null) {
pageView?.background = page.bg
}
@ -634,7 +636,7 @@ class PagerPageHolder(
splitDoublePages()
}
}
withUIContext { progressBar.completeAndFadeOut() }
scope.launchUI { progressBar.completeAndFadeOut() }
return imageStream
}
if (page.longPage == true && viewer.config.splitPages) {
@ -707,7 +709,13 @@ class PagerPageHolder(
val height = imageBitmap.height
val width = imageBitmap.width
val imageBytes2 by lazy { imageStream2.readBytes() }
val isLTR = (viewer !is R2LPagerViewer).xor(viewer.config.invertDoublePages)
if (height < width) {
if (extraPage?.index == 1) {
setExtraPageBitmap(imageBytes2, isLTR)
}
closeStreams(imageStream, imageStream2)
val oldValue = page.fullPage
page.fullPage = true
@ -720,34 +728,53 @@ class PagerPageHolder(
} else {
viewer.splitDoublePages(page)
}
extraPage = null
}
return supportHingeIfThere(imageBytes.inputStream())
}
val isLTR = (viewer !is R2LPagerViewer).xor(viewer.config.invertDoublePages)
var earlyImageBitmap2: Bitmap? = null
if (page.index <= 2 && page.isEndPage == null && page.fullPage == null) {
page.isEndPage = ImageUtil.isPagePadded(imageBitmap, rightSide = !isLTR)
page.paddedPageConfidence = imageBitmap.isPagePadded(rightSide = !isLTR)
page.isEndPage = page.paddedPageConfidence > 0
if (extraPage?.index == 1 && extraPage?.isEndPage == null) {
earlyImageBitmap2 = setExtraPageBitmap(imageBytes2, isLTR)
}
val isFirstPageEndPage by lazy {
viewer.activity.getFirstPage()?.let {
it.isEndPage != true || page.paddedPageConfidence > it.paddedPageConfidence
} != false
}
if (page.index == 1 && page.isEndPage == true && viewer.config.shiftDoublePage &&
!viewer.activity.isFirstPageEnd()
isFirstPageEndPage
) {
shiftDoublePages(false)
return supportHingeIfThere(imageBytes.inputStream())
} else if ((page.isEndPage == true) &&
(if (page.index == 2) !viewer.activity.isFirstPageFull() else true) &&
extraPage?.isEndPage != true
} else if (page.isEndPage == true &&
when (page.index) {
// 3rd page shouldn't shift if page 1 is a spread
2 -> !viewer.activity.isFirstPageFull()
// 2nd page shouldn't shift if page 1 is more likely an end page
1 -> isFirstPageEndPage
// first page shouldn't shift if page 2 is definitely an end page
0 -> extraPage?.run { isEndPage == true && paddedPageConfidence == 3 } != true ||
page.paddedPageConfidence == 3
else -> false
}
) {
shiftDoublePages(true)
extraPage = null
return supportHingeIfThere(imageBytes.inputStream())
}
} else if (!viewer.activity.manuallyShiftedPages && page.index == 0 && page.isEndPage == true) {
} else if (!viewer.activity.manuallyShiftedPages && (page.index == 0 || page.index == 2) &&
page.isEndPage == true && page.paddedPageConfidence == 3
) {
// if for some reason the first page should be by itself but its not, fix that
shiftDoublePages(true)
extraPage = null
return supportHingeIfThere(imageBytes.inputStream())
}
val imageBytes2 = imageStream2.readBytes()
val imageBitmap2 = try {
val imageBitmap2 = earlyImageBitmap2 ?: try {
BitmapFactory.decodeByteArray(imageBytes2, 0, imageBytes2.size)
} catch (e: Exception) {
closeStreams(imageStream, imageStream2)
@ -757,7 +784,7 @@ class PagerPageHolder(
Timber.e("Cannot combine pages ${e.message}")
return supportHingeIfThere(imageBytes.inputStream())
}
withUIContext { progressBar.setProgress(97) }
scope.launchUI { progressBar.setProgress(97) }
val height2 = imageBitmap2.height
val width2 = imageBitmap2.width
@ -775,14 +802,23 @@ class PagerPageHolder(
}
closeStreams(imageStream, imageStream2)
if (extraPage?.index == 1 && extraPage?.isStartPage == null && extraPage?.fullPage == null) {
extraPage?.isStartPage = ImageUtil.isPagePadded(imageBitmap, rightSide = isLTR)
if (extraPage?.isStartPage == true) {
shiftDoublePages(true)
extraPage = null
return supportHingeIfThere(imageBytes.inputStream())
extraPage?.let { extraPage ->
if (extraPage.index <= 2 && extraPage.isEndPage == null &&
extraPage.isStartPage == null && extraPage.fullPage == null
) {
extraPage.paddedPageConfidence = imageBitmap2.isPagePadded(rightSide = isLTR)
extraPage.isStartPage = extraPage.paddedPageConfidence > 0
if (extraPage.isStartPage == true) {
shiftDoublePages(page.index == 0 || viewer.activity.isFirstPageFull())
this.extraPage = null
return supportHingeIfThere(imageBytes.inputStream())
}
}
}
// If page has been removed in another thread, don't show it
if (extraPage == null) {
return supportHingeIfThere(imageBytes.inputStream())
}
return ImageUtil.mergeBitmaps(imageBitmap, imageBitmap2, isLTR, bg, viewer.config.hingeGapSize, context) {
scope.launchUI {
if (it == 100) {
@ -794,6 +830,20 @@ class PagerPageHolder(
}
}
private fun setExtraPageBitmap(imageBytes2: ByteArray, isLTR: Boolean): Bitmap? {
val earlyImageBitmap2 = try {
BitmapFactory.decodeByteArray(imageBytes2, 0, imageBytes2.size)
} catch (_: Exception) {
null
}
val paddedPageConfidence = earlyImageBitmap2?.isPagePadded(rightSide = !isLTR) ?: 0
if (paddedPageConfidence == 3) {
extraPage?.paddedPageConfidence = paddedPageConfidence
extraPage?.isEndPage = true
}
return earlyImageBitmap2
}
private suspend fun supportHingeIfThere(imageStream: InputStream): InputStream {
if (viewer.config.hingeGapSize > 0 && !ImageUtil.isAnimatedAndSupported(imageStream)) {
val imageBytes = imageStream.readBytes()
@ -833,16 +883,16 @@ class PagerPageHolder(
}
}
private suspend fun shiftDoublePages(shift: Boolean) {
delayPageUpdate { viewer.activity.shiftDoublePages(shift) }
private fun shiftDoublePages(shift: Boolean) {
delayPageUpdate { viewer.activity.shiftDoublePages(shift, page) }
}
private suspend fun splitDoublePages() {
private fun splitDoublePages() {
delayPageUpdate { viewer.splitDoublePages(page) }
}
private suspend fun delayPageUpdate(callback: () -> Unit) {
withUIContext {
private fun delayPageUpdate(callback: () -> Unit) {
scope.launchUI {
callback()
if (extraPage?.fullPage == true || page.fullPage == true) {
extraPage = null

View file

@ -190,6 +190,7 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() {
firstHalf = null
isEndPage = null
isStartPage = null
paddedPageConfidence = 0
}
}
if (viewer.config.splitPages) {

View file

@ -622,27 +622,27 @@ object ImageUtil {
abs(color1.blue - color2.blue) < 30
}
/** Returns if this bitmap matches what would be (if rightSide param is true)
* the single left side page, or the second page to read in a RTL book, first in an LTR book
/**
* Returns if this bitmap matches what would be (if rightSide param is true)
* the single left side page, or the second page to read in a RTL book, first in an LTR book.
*
* @param image: bitmap image to check
* @param rightSide: when true, check if its a single left side page, else right side
* @return An int based on confidence, 0 meaning not padded, 1 meaning barely padded,
* 2 meaning likely padded, 3 meaining definitely padded
* @param rightSide: When true, check if its a single left side page, else right side
* */
fun isPagePadded(image: Bitmap, rightSide: Boolean): Boolean {
if (image.isSidePadded(!rightSide, checkWhite = true) ||
image.isSidePadded(!rightSide, checkWhite = false)
) {
return false
fun Bitmap.isPagePadded(rightSide: Boolean): Int {
val booleans = listOf(true, false)
return when {
booleans.any { isSidePadded(!rightSide, checkWhite = it) } -> 0
booleans.any { isSidePadded(rightSide, checkWhite = it) } -> 3
booleans.any { isOneSideMorePadded(rightSide, checkWhite = it) } -> 2
booleans.any { isSidePadded(rightSide, checkWhite = it, halfCheck = true) } -> 1
else -> 0
}
return image.isSidePadded(rightSide, checkWhite = true) ||
image.isSidePadded(rightSide, checkWhite = false) ||
// if neither of the above 2 worked,
// try starting from the vert. middle and see which side has more padding
image.isOneSideMorePadded(rightSide, checkWhite = true) ||
image.isOneSideMorePadded(rightSide, checkWhite = false)
}
private fun Bitmap.isSidePadded(rightSide: Boolean, checkWhite: Boolean): Boolean {
/** Returns if one side has a vertical padding and the other side does not */
private fun Bitmap.isSidePadded(rightSide: Boolean, checkWhite: Boolean, halfCheck: Boolean = false): Boolean {
val left = (width * 0.0275).toInt()
val right = width - left
val paddedSide = if (rightSide) right else left
@ -650,12 +650,13 @@ object ImageUtil {
return (1 until 30).count {
// if all of a side is padded (the left page usually has a white padding on the right when scanned)
getPixel(paddedSide, (height * (it / 30f)).roundToInt()).isWhiteOrDark(checkWhite)
} >= 27 && !(1 until 50).all {
} >= (if (halfCheck) 15 else 27) && !(1 until 50).all {
// and if all of the other side isn't padded
getPixel(unPaddedSide, (height * (it / 50f)).roundToInt()).isWhiteOrDark(checkWhite)
}
}
/** Returns if one side is more padded than the other */
private fun Bitmap.isOneSideMorePadded(rightSide: Boolean, checkWhite: Boolean): Boolean {
val middle = (height * 0.475).roundToInt()
val middle2 = (height * 0.525).roundToInt()