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 <andreas.everos@gmail.com>
(cherry picked from commit b5017eebbf)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt
#	app/src/main/java/eu/kanade/tachiyomi/util/system/ImageUtil.kt
This commit is contained in:
vance
2021-02-10 06:54:44 +08:00
committed by Jobobby04
parent c5ef58ac72
commit 37ce2140f3
14 changed files with 189 additions and 4 deletions
@@ -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"
@@ -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)
@@ -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())
@@ -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
}
}
@@ -4,7 +4,7 @@ import android.graphics.drawable.Drawable
import eu.kanade.tachiyomi.source.model.Page
import java.io.InputStream
class ReaderPage(
open class ReaderPage(
index: Int,
url: String = "",
imageUrl: String? = null,
@@ -16,5 +16,5 @@ class ReaderPage(
) : Page(index, url, imageUrl, null) {
lateinit var chapter: ReaderChapter
open lateinit var chapter: ReaderChapter
}
@@ -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
@@ -31,6 +31,7 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.glide.GlideApp
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
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
@@ -256,6 +257,9 @@ class PagerPageHolder(
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { isAnimated ->
if (viewer.config.dualPageSplit) {
openStream = processDualPageSplit(openStream!!)
}
if (!isAnimated) {
// SY -->
if (readerTheme >= 3) {
@@ -291,6 +295,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)
}
// SY -->
private suspend fun setBG(bytesArray: ByteArray): Drawable {
return withContext(Dispatchers.Default) {
@@ -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)
}
}
@@ -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<Any> = emptyList()
var items: MutableList<Any> = 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<InsertPage>().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<out PagerViewer>) {
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()
}
}
@@ -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
@@ -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
@@ -2,11 +2,16 @@ package eu.kanade.tachiyomi.util.system
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Rect
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import eu.kanade.tachiyomi.R
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.InputStream
import java.net.URLConnection
import kotlin.math.abs
@@ -77,6 +82,73 @@ object ImageUtil {
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<Boolean, InputStream> {
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
}
// SY -->
@Suppress("UNUSED_VARIABLE")
fun autoSetBackground(image: Bitmap?, alwaysUseWhite: Boolean, context: Context): Drawable {
@@ -151,6 +151,14 @@
android:textColor="?android:attr/textColorSecondary"
app:layout_constraintTop_toBottomOf="@id/show_page_number" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/dual_page_split"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/pref_dual_page_split"
android:textColor="?android:attr/textColorSecondary"
app:layout_constraintTop_toBottomOf="@id/fullscreen" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/cutout_short"
android:layout_width="match_parent"
@@ -158,7 +166,7 @@
android:text="@string/pref_cutout_short"
android:textColor="?android:attr/textColorSecondary"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/fullscreen"
app:layout_constraintTop_toBottomOf="@id/dual_page_split"
tools:visibility="visible" />
<com.google.android.material.switchmaterial.SwitchMaterial
+1
View File
@@ -252,6 +252,7 @@
<!-- Reader section -->
<string name="pref_fullscreen">Fullscreen</string>
<string name="pref_dual_page_split">Dual page split (ALPHA)</string>
<string name="pref_cutout_short">Show content in cutout area</string>
<string name="pref_lock_orientation">Lock orientation</string>
<string name="pref_page_transitions">Animate page transitions</string>