Browse Source

Add "Rotate wide pages to fit" setting for paged reader

Originally authored in #7983

Co-authored-by: timothyng-164 <[email protected]>
arkon 2 years ago
parent
commit
953720472f

+ 19 - 0
app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt

@@ -150,10 +150,12 @@ object SettingsReaderScreen : SearchableSettings {
         val navModePref = readerPreferences.navigationModePager()
         val imageScaleTypePref = readerPreferences.imageScaleType()
         val dualPageSplitPref = readerPreferences.dualPageSplitPaged()
+        val rotateToFitPref = readerPreferences.dualPageRotateToFit()
 
         val navMode by navModePref.collectAsState()
         val imageScaleType by imageScaleTypePref.collectAsState()
         val dualPageSplit by dualPageSplitPref.collectAsState()
+        val rotateToFit by rotateToFitPref.collectAsState()
 
         return Preference.PreferenceGroup(
             title = stringResource(R.string.pager_viewer),
@@ -216,6 +218,10 @@ object SettingsReaderScreen : SearchableSettings {
                 Preference.PreferenceItem.SwitchPreference(
                     pref = dualPageSplitPref,
                     title = stringResource(R.string.pref_dual_page_split),
+                    onValueChanged = {
+                        rotateToFitPref.set(false)
+                        true
+                    },
                 ),
                 Preference.PreferenceItem.SwitchPreference(
                     pref = readerPreferences.dualPageInvertPaged(),
@@ -223,6 +229,19 @@ object SettingsReaderScreen : SearchableSettings {
                     subtitle = stringResource(R.string.pref_dual_page_invert_summary),
                     enabled = dualPageSplit,
                 ),
+                Preference.PreferenceItem.SwitchPreference(
+                    pref = rotateToFitPref,
+                    title = stringResource(R.string.pref_page_rotate),
+                    onValueChanged = {
+                        dualPageSplitPref.set(false)
+                        true
+                    },
+                ),
+                Preference.PreferenceItem.SwitchPreference(
+                    pref = readerPreferences.dualPageRotateToFitInvert(),
+                    title = stringResource(R.string.pref_page_rotate_invert),
+                    enabled = rotateToFit,
+                ),
             ),
         )
     }

+ 4 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderPreferences.kt

@@ -73,6 +73,10 @@ class ReaderPreferences(
 
     fun dualPageInvertWebtoon() = preferenceStore.getBoolean("pref_dual_page_invert_webtoon", false)
 
+    fun dualPageRotateToFit() = preferenceStore.getBoolean("pref_dual_page_rotate", false)
+
+    fun dualPageRotateToFitInvert() = preferenceStore.getBoolean("pref_dual_page_rotate_invert", false)
+
     // endregion
 
     // region Color filter

+ 17 - 2
app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderReadingModeSettings.kt

@@ -92,11 +92,26 @@ class ReaderReadingModeSettings @JvmOverloads constructor(context: Context, attr
         binding.pagerPrefsGroup.cropBorders.bindToPreference(readerPreferences.cropBorders())
 
         binding.pagerPrefsGroup.dualPageSplit.bindToPreference(readerPreferences.dualPageSplitPaged())
-        // Makes it so that dual page invert gets hidden away when dual page split is turned off
         readerPreferences.dualPageSplitPaged()
-            .asHotFlow { binding.pagerPrefsGroup.dualPageInvert.isVisible = it }
+            .asHotFlow {
+                binding.pagerPrefsGroup.dualPageInvert.isVisible = it
+                if (it) {
+                    binding.pagerPrefsGroup.dualPageRotateToFit.isChecked = false
+                }
+            }
             .launchIn((context as ReaderActivity).lifecycleScope)
         binding.pagerPrefsGroup.dualPageInvert.bindToPreference(readerPreferences.dualPageInvertPaged())
+
+        binding.pagerPrefsGroup.dualPageRotateToFit.bindToPreference(readerPreferences.dualPageRotateToFit())
+        readerPreferences.dualPageRotateToFit()
+            .asHotFlow {
+                binding.pagerPrefsGroup.dualPageRotateToFitInvert.isVisible = it
+                if (it) {
+                    binding.pagerPrefsGroup.dualPageSplit.isChecked = false
+                }
+            }
+            .launchIn((context as ReaderActivity).lifecycleScope)
+        binding.pagerPrefsGroup.dualPageRotateToFitInvert.bindToPreference(readerPreferences.dualPageRotateToFitInvert())
     }
 
     /**

+ 6 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ViewerConfig.kt

@@ -37,6 +37,12 @@ abstract class ViewerConfig(readerPreferences: ReaderPreferences, private val sc
     var dualPageInvert = false
         protected set
 
+    var dualPageRotateToFit = false
+        protected set
+
+    var dualPageRotateToFitInvert = false
+        protected set
+
     abstract var navigator: ViewerNavigation
         protected set
 

+ 12 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerConfig.kt

@@ -94,6 +94,18 @@ class PagerConfig(
 
         readerPreferences.dualPageInvertPaged()
             .register({ dualPageInvert = it }, { imagePropertyChangedListener?.invoke() })
+
+        readerPreferences.dualPageRotateToFit()
+            .register(
+                { dualPageRotateToFit = it },
+                { imagePropertyChangedListener?.invoke() },
+            )
+
+        readerPreferences.dualPageRotateToFitInvert()
+            .register(
+                { dualPageRotateToFitInvert = it },
+                { imagePropertyChangedListener?.invoke() },
+            )
     }
 
     private fun zoomTypeFromPreference(value: Int) {

+ 14 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt

@@ -180,6 +180,10 @@ class PagerPageHolder(
     }
 
     private fun process(page: ReaderPage, imageStream: BufferedInputStream): InputStream {
+        if (viewer.config.dualPageRotateToFit) {
+            return rotateDualPage(imageStream)
+        }
+
         if (!viewer.config.dualPageSplit) {
             return imageStream
         }
@@ -198,6 +202,16 @@ class PagerPageHolder(
         return splitInHalf(imageStream)
     }
 
+    private fun rotateDualPage(imageStream: BufferedInputStream): InputStream {
+        val isDoublePage = ImageUtil.isWideImage(imageStream)
+        return if (isDoublePage) {
+            val rotation = if (viewer.config.dualPageRotateToFitInvert) -90f else 90f
+            ImageUtil.rotateImage(imageStream, rotation)
+        } else {
+            imageStream
+        }
+    }
+
     private fun splitInHalf(imageStream: InputStream): InputStream {
         var side = when {
             viewer is L2RPagerViewer && page is InsertPage -> ImageUtil.Side.RIGHT

+ 21 - 1
app/src/main/res/layout/reader_pager_settings.xml

@@ -91,10 +91,30 @@
         android:visibility="gone"
         tools:visibility="visible" />
 
+    <com.google.android.material.materialswitch.MaterialSwitch
+        android:id="@+id/dual_page_rotate_to_fit"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingHorizontal="16dp"
+        android:paddingVertical="16dp"
+        android:text="@string/pref_page_rotate"
+        android:textColor="?android:attr/textColorSecondary" />
+
+    <com.google.android.material.materialswitch.MaterialSwitch
+        android:id="@+id/dual_page_rotate_to_fit_invert"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingHorizontal="16dp"
+        android:paddingVertical="16dp"
+        android:text="@string/pref_page_rotate_invert"
+        android:textColor="?android:attr/textColorSecondary"
+        android:visibility="gone"
+        tools:visibility="visible" />
+
     <androidx.constraintlayout.widget.Group
         android:id="@+id/tapping_prefs_group"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        app:constraint_referenced_ids="pager_nav,tapping_inverted,dual_page_split,dual_page_invert" />
+        app:constraint_referenced_ids="pager_nav,tapping_inverted,dual_page_split,dual_page_invert,dual_page_rotate_to_fit,dual_page_rotate_to_fit_invert" />
 
 </LinearLayout>

+ 18 - 0
core/src/main/java/tachiyomi/core/util/system/ImageUtil.kt

@@ -7,6 +7,7 @@ import android.graphics.Bitmap
 import android.graphics.BitmapFactory
 import android.graphics.BitmapRegionDecoder
 import android.graphics.Color
+import android.graphics.Matrix
 import android.graphics.Rect
 import android.graphics.drawable.ColorDrawable
 import android.graphics.drawable.Drawable
@@ -151,6 +152,23 @@ object ImageUtil {
         return ByteArrayInputStream(output.toByteArray())
     }
 
+    fun rotateImage(imageStream: InputStream, degrees: Float): InputStream {
+        val imageBytes = imageStream.readBytes()
+
+        val imageBitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
+        val rotated = rotateBitMap(imageBitmap, degrees)
+
+        val output = ByteArrayOutputStream()
+        rotated.compress(Bitmap.CompressFormat.JPEG, 100, output)
+
+        return ByteArrayInputStream(output.toByteArray())
+    }
+
+    private fun rotateBitMap(bitmap: Bitmap, degrees: Float): Bitmap {
+        val matrix = Matrix().apply { postRotate(degrees) }
+        return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
+    }
+
     /**
      * Split the image into left and right parts, then merge them into a new image.
      */

+ 2 - 0
i18n/src/main/res/values/strings.xml

@@ -314,6 +314,8 @@
     <string name="pref_dual_page_split">Split wide pages</string>
     <string name="pref_dual_page_invert">Invert split page placement</string>
     <string name="pref_dual_page_invert_summary">If the placement of the split wide pages don\'t match reading direction</string>
+    <string name="pref_page_rotate">Rotate wide pages to fit</string>
+    <string name="pref_page_rotate_invert">Flip orientation of rotated wide pages</string>
     <string name="pref_long_strip_split">Split tall images (BETA)</string>
     <string name="pref_cutout_short">Show content in cutout area</string>
     <string name="pref_page_transitions">Animate page transitions</string>