From b5017eebbf0740189f254d5db25df3c54a282e1f Mon Sep 17 00:00:00 2001 From: vance <32921531+rawfh@users.noreply.github.com> Date: Wed, 10 Feb 2021 06:54:44 +0800 Subject: [PATCH] Added dual page split setting (#4252) * Add DualPageSplit option * remove extra line * Split double-page into two pages * Remove !isAnimated check and add (ALPHA) to the label * Fix missing insert pages * Pager cleanup * Add dual split to Webtoon and fix Vertical * Fix L2R/R2L * Add comments and refactor code in ImageUtil * Use a simpler split solution in webtoon mode Co-authored-by: weng <> Co-authored-by: Andreas E --- .../data/preference/PreferenceKeys.kt | 2 + .../data/preference/PreferencesHelper.kt | 2 + .../ui/reader/ReaderSettingsSheet.kt | 1 + .../tachiyomi/ui/reader/model/InsertPage.kt | 10 +++ .../tachiyomi/ui/reader/model/ReaderPage.kt | 4 +- .../ui/reader/viewer/ViewerConfig.kt | 4 + .../ui/reader/viewer/pager/PagerPageHolder.kt | 36 +++++++++ .../ui/reader/viewer/pager/PagerViewer.kt | 5 ++ .../reader/viewer/pager/PagerViewerAdapter.kt | 33 ++++++++- .../viewer/webtoon/WebtoonPageHolder.kt | 8 ++ .../ui/setting/SettingsReaderController.kt | 5 ++ .../kanade/tachiyomi/util/system/ImageUtil.kt | 73 +++++++++++++++++++ .../main/res/layout/reader_settings_sheet.xml | 10 ++- app/src/main/res/values/strings.xml | 1 + 14 files changed, 190 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/InsertPage.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt index e3cca4e80..7c82c921a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt @@ -23,6 +23,8 @@ object PreferenceKeys { const val showPageNumber = "pref_show_page_number_key" + const val dualPageSplit = "pref_dual_page_split" + const val showReadingMode = "pref_show_reading_mode" const val trueColor = "pref_true_color_key" diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt index 9f1ec165c..0b5bae254 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt @@ -89,6 +89,8 @@ class PreferencesHelper(val context: Context) { fun showPageNumber() = flowPrefs.getBoolean(Keys.showPageNumber, true) + fun dualPageSplit() = flowPrefs.getBoolean(Keys.dualPageSplit, false) + fun showReadingMode() = prefs.getBoolean(Keys.showReadingMode, true) fun trueColor() = flowPrefs.getBoolean(Keys.trueColor, false) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderSettingsSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderSettingsSheet.kt index 07b3b3bac..a8896a5bc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderSettingsSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderSettingsSheet.kt @@ -65,6 +65,7 @@ class ReaderSettingsSheet(private val activity: ReaderActivity) : BaseBottomShee binding.backgroundColor.bindToIntPreference(preferences.readerTheme(), R.array.reader_themes_values) binding.showPageNumber.bindToPreference(preferences.showPageNumber()) binding.fullscreen.bindToPreference(preferences.fullscreen()) + binding.dualPageSplit.bindToPreference(preferences.dualPageSplit()) binding.keepscreen.bindToPreference(preferences.keepScreenOn()) binding.longTap.bindToPreference(preferences.readWithLongTap()) binding.alwaysShowChapterTransition.bindToPreference(preferences.alwaysShowChapterTransition()) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/InsertPage.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/InsertPage.kt new file mode 100644 index 000000000..7183ee7d3 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/InsertPage.kt @@ -0,0 +1,10 @@ +package eu.kanade.tachiyomi.ui.reader.model + +class InsertPage(val parent: ReaderPage) : ReaderPage(parent.index, parent.url, parent.imageUrl) { + + override var chapter: ReaderChapter = parent.chapter + + init { + stream = parent.stream + } +} 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 35a10331d..19431a9b9 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 @@ -3,12 +3,12 @@ package eu.kanade.tachiyomi.ui.reader.model import eu.kanade.tachiyomi.source.model.Page import java.io.InputStream -class ReaderPage( +open class ReaderPage( index: Int, url: String = "", imageUrl: String? = null, var stream: (() -> InputStream)? = null ) : Page(index, url, imageUrl, null) { - lateinit var chapter: ReaderChapter + open lateinit var chapter: ReaderChapter } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ViewerConfig.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ViewerConfig.kt index 5f3372668..25b417ceb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ViewerConfig.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ViewerConfig.kt @@ -24,6 +24,7 @@ abstract class ViewerConfig(preferences: PreferencesHelper, private val scope: C var volumeKeysInverted = false var trueColor = false var alwaysShowChapterTransition = true + var dualPageSplit = false var navigationMode = 0 protected set @@ -54,6 +55,9 @@ abstract class ViewerConfig(preferences: PreferencesHelper, private val scope: C preferences.alwaysShowChapterTransition() .register({ alwaysShowChapterTransition = it }) + + preferences.dualPageSplit() + .register({ dualPageSplit = it }, { imagePropertyChangedListener?.invoke() }) } protected abstract fun defaultNavigation(): ViewerNavigation 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 1443ab19c..36e4fff0a 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 @@ -28,6 +28,7 @@ import com.github.chrisbanes.photoview.PhotoView import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.glide.GlideApp import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.ui.reader.model.InsertPage import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressBar import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerConfig.ZoomType @@ -241,6 +242,9 @@ class PagerPageHolder( .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnNext { isAnimated -> + if (viewer.config.dualPageSplit) { + openStream = processDualPageSplit(openStream!!) + } if (!isAnimated) { initSubsamplingImageView().setImage(ImageSource.inputStream(openStream!!)) } else { @@ -253,6 +257,38 @@ class PagerPageHolder( .subscribe({}, {}) } + private fun processDualPageSplit(openStream: InputStream): InputStream { + var inputStream = openStream + val (isDoublePage, stream) = when (page) { + is InsertPage -> Pair(true, inputStream) + else -> ImageUtil.isDoublePage(inputStream) + } + inputStream = stream + if (isDoublePage) { + val side = when { + viewer is L2RPagerViewer && page is InsertPage -> ImageUtil.Side.RIGHT + viewer is R2LPagerViewer && page is InsertPage -> ImageUtil.Side.LEFT + viewer is L2RPagerViewer && page !is InsertPage -> ImageUtil.Side.LEFT + viewer is R2LPagerViewer && page !is InsertPage -> ImageUtil.Side.RIGHT + viewer is VerticalPagerViewer && page !is InsertPage -> ImageUtil.Side.RIGHT + viewer is VerticalPagerViewer && page is InsertPage -> ImageUtil.Side.LEFT + else -> error("We should choose a side!") + } + + if (page !is InsertPage) { + onPageSplit() + } + + inputStream = ImageUtil.splitInHalf(inputStream, side) + } + return inputStream + } + + private fun onPageSplit() { + val newPage = InsertPage(page) + viewer.onPageSplit(page, newPage) + } + /** * Called when the page has an error. */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewer.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewer.kt index e8d64446f..5265423ab 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerViewer.kt @@ -12,6 +12,7 @@ import androidx.viewpager.widget.ViewPager import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition +import eu.kanade.tachiyomi.ui.reader.model.InsertPage import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters import eu.kanade.tachiyomi.ui.reader.viewer.BaseViewer @@ -371,4 +372,8 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer { } return false } + + fun onPageSplit(currentPage: ReaderPage, newPage: InsertPage) { + adapter.onPageSplit(currentPage, newPage, this::class.java) + } } 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 b1dfb70f9..d15d744f3 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 @@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.reader.viewer.pager import android.view.View import android.view.ViewGroup import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition +import eu.kanade.tachiyomi.ui.reader.model.InsertPage import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters @@ -18,7 +19,7 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() { /** * List of currently set items. */ - var items: List = emptyList() + var items: MutableList = mutableListOf() private set var nextTransition: ChapterTransition.Next? = null @@ -80,6 +81,9 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() { } } + // Resets double-page splits, else insert pages get misplaced + items.filterIsInstance().also { items.removeAll(it) } + if (viewer is R2LPagerViewer) { newItems.reverse() } @@ -120,4 +124,31 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() { } return POSITION_NONE } + + fun onPageSplit(current: Any?, newPage: InsertPage, clazz: Class) { + if (current !is ReaderPage) return + + val currentIndex = items.indexOf(current) + + val placeAtIndex = when { + clazz.isAssignableFrom(L2RPagerViewer::class.java) -> currentIndex + 1 + clazz.isAssignableFrom(VerticalPagerViewer::class.java) -> currentIndex + 1 + clazz.isAssignableFrom(R2LPagerViewer::class.java) -> currentIndex + else -> currentIndex + } + + // It will enter a endless cycle of insert pages + if (clazz.isAssignableFrom(R2LPagerViewer::class.java) && items[placeAtIndex - 1] is InsertPage) { + return + } + + // Same here it will enter a endless cycle of insert pages + if (items[placeAtIndex] is InsertPage) { + return + } + + items.add(placeAtIndex, newPage) + + notifyDataSetChanged() + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt index 5d8962339..6268f069d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt @@ -287,6 +287,14 @@ class WebtoonPageHolder( .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnNext { isAnimated -> + if (viewer.config.dualPageSplit) { + val (isDoublePage, stream) = ImageUtil.isDoublePage(openStream!!) + openStream = if (!isDoublePage) { + stream + } else { + ImageUtil.splitAndMerge(stream) + } + } if (!isAnimated) { val subsamplingView = initSubsamplingImageView() subsamplingView.isVisible = true diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsReaderController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsReaderController.kt index 8be74a476..c61e343e3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsReaderController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsReaderController.kt @@ -50,6 +50,11 @@ class SettingsReaderController : SettingsController() { summaryRes = R.string.pref_show_reading_mode_summary defaultValue = true } + switchPreference { + key = Keys.dualPageSplit + titleRes = R.string.pref_dual_page_split + defaultValue = false + } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { switchPreference { key = Keys.trueColor 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 ca6bb9a64..4716fa24c 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 @@ -1,5 +1,11 @@ package eu.kanade.tachiyomi.util.system +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.Canvas +import android.graphics.Rect +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream import java.io.InputStream import java.net.URLConnection @@ -68,4 +74,71 @@ object ImageUtil { GIF("image/gif", "gif"), WEBP("image/webp", "webp") } + + /** + * Check whether the image is a double image (width > height), return the result and original stream + */ + fun isDoublePage(imageStream: InputStream): Pair { + val imageBytes = imageStream.readBytes() + + val options = BitmapFactory.Options().apply { inJustDecodeBounds = true } + BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size, options) + + return Pair(options.outWidth > options.outHeight, ByteArrayInputStream(imageBytes)) + } + + /** + * Extract the 'side' part from imageStream and return it as InputStream. + */ + fun splitInHalf(imageStream: InputStream, side: Side): InputStream { + val imageBytes = imageStream.readBytes() + + val imageBitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size) + val height = imageBitmap.height + val width = imageBitmap.width + + val singlePage = Rect(0, 0, width / 2, height) + + val half = Bitmap.createBitmap(width / 2, height, Bitmap.Config.ARGB_8888) + val part = when (side) { + Side.RIGHT -> Rect(width - width / 2, 0, width, height) + Side.LEFT -> Rect(0, 0, width / 2, height) + } + val canvas = Canvas(half) + canvas.drawBitmap(imageBitmap, part, singlePage, null) + val output = ByteArrayOutputStream() + half.compress(Bitmap.CompressFormat.JPEG, 100, output) + + return ByteArrayInputStream(output.toByteArray()) + } + + /** + * Split the image into left and right parts, then merge them into a new image. + */ + fun splitAndMerge(imageStream: InputStream): InputStream { + val imageBytes = imageStream.readBytes() + + val imageBitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size) + val height = imageBitmap.height + val width = imageBitmap.width + + val result = Bitmap.createBitmap(width / 2, height * 2, Bitmap.Config.ARGB_8888) + val canvas = Canvas(result) + // right -> upper + val rightPart = Rect(width - width / 2, 0, width, height) + val upperPart = Rect(0, 0, width / 2, height) + canvas.drawBitmap(imageBitmap, rightPart, upperPart, null) + // left -> bottom + val leftPart = Rect(0, 0, width / 2, height) + val bottomPart = Rect(0, height, width / 2, height * 2) + canvas.drawBitmap(imageBitmap, leftPart, bottomPart, null) + + val output = ByteArrayOutputStream() + result.compress(Bitmap.CompressFormat.JPEG, 100, output) + return ByteArrayInputStream(output.toByteArray()) + } + + enum class Side { + RIGHT, LEFT + } } diff --git a/app/src/main/res/layout/reader_settings_sheet.xml b/app/src/main/res/layout/reader_settings_sheet.xml index 6750ae499..d28d64fc8 100644 --- a/app/src/main/res/layout/reader_settings_sheet.xml +++ b/app/src/main/res/layout/reader_settings_sheet.xml @@ -151,6 +151,14 @@ android:textColor="?android:attr/textColorSecondary" app:layout_constraintTop_toBottomOf="@id/show_page_number" /> + + Fullscreen + Dual page split (ALPHA) Show content in cutout area Lock orientation Animate page transitions