From 4ef3f2437bf8dbf008d41a8f1b32d237556f83cc Mon Sep 17 00:00:00 2001 From: Jays2Kings Date: Mon, 6 Mar 2023 20:20:42 -0500 Subject: [PATCH] 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 --- .../tachiyomi/ui/reader/ReaderActivity.kt | 18 +-- .../tachiyomi/ui/reader/model/ReaderPage.kt | 1 + .../ui/reader/viewer/pager/PagerPageHolder.kt | 110 +++++++++++++----- .../reader/viewer/pager/PagerViewerAdapter.kt | 1 + .../kanade/tachiyomi/util/system/ImageUtil.kt | 35 +++--- 5 files changed, 109 insertions(+), 56 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt index b16286fa42..93d1eeb41d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt @@ -454,7 +454,9 @@ class ReaderActivity : BaseActivity() { 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() { 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() { } 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() { 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() { } 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 { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/ReaderPage.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/ReaderPage.kt index 40599e3a54..2df74a5798 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/ReaderPage.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/ReaderPage.kt @@ -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) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt index d396bd6e58..eaf04feaa3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt @@ -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 diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewerAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewerAdapter.kt index 15487cd71a..14e4194a81 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewerAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewerAdapter.kt @@ -190,6 +190,7 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() { firstHalf = null isEndPage = null isStartPage = null + paddedPageConfidence = 0 } } if (viewer.config.splitPages) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/ImageUtil.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/ImageUtil.kt index d239169340..0edbf4826b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/ImageUtil.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/ImageUtil.kt @@ -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()