diff --git a/app/src/main/java/eu/kanade/presentation/reader/settings/ColorFilterPage.kt b/app/src/main/java/eu/kanade/presentation/reader/settings/ColorFilterPage.kt
index cb8f5578b..916ef77de 100644
--- a/app/src/main/java/eu/kanade/presentation/reader/settings/ColorFilterPage.kt
+++ b/app/src/main/java/eu/kanade/presentation/reader/settings/ColorFilterPage.kt
@@ -117,6 +117,10 @@ internal fun ColumnScope.ColorFilterPage(screenModel: ReaderSettingsScreenModel)
label = stringResource(MR.strings.pref_inverted_colors),
pref = screenModel.preferences.invertedColors(),
)
+ CheckboxItem(
+ label = stringResource(MR.strings.pref_flip_horizontally),
+ pref = screenModel.preferences.flipHorizontally(),
+ )
}
private fun getColorValue(currentColor: Int, color: Int, mask: Long, bitShift: Int): Int {
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderPreferences.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderPreferences.kt
index 63162788c..d4eb5f19e 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderPreferences.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderPreferences.kt
@@ -109,6 +109,8 @@ class ReaderPreferences(
fun invertedColors() = preferenceStore.getBoolean("pref_inverted_colors", false)
+ fun flipHorizontally() = preferenceStore.getBoolean("flip_horizontally", false)
+
// endregion
// region Controls
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerConfig.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerConfig.kt
index b319fd928..38164d21a 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerConfig.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerConfig.kt
@@ -48,6 +48,9 @@ class PagerConfig(
var landscapeZoom = false
private set
+ var flipHorizontally = false
+ private set
+
init {
readerPreferences.readerTheme()
.register(
@@ -106,6 +109,9 @@ class PagerConfig(
{ dualPageRotateToFitInvert = it },
{ imagePropertyChangedListener?.invoke() },
)
+
+ readerPreferences.flipHorizontally()
+ .register({ flipHorizontally = it }, { imagePropertyChangedListener?.invoke() })
}
private fun zoomTypeFromPreference(value: Int) {
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 71d499c38..4e4204247 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
@@ -174,6 +174,10 @@ class PagerPageHolder(
}
private fun process(page: ReaderPage, imageSource: BufferedSource): BufferedSource {
+ if (viewer.config.flipHorizontally) {
+ return ImageUtil.flipImage(imageSource, true, false)
+ }
+
if (viewer.config.dualPageRotateToFit) {
return rotateDualPage(imageSource)
}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonConfig.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonConfig.kt
index f55478862..7fbf2b77e 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonConfig.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonConfig.kt
@@ -44,6 +44,9 @@ class WebtoonConfig(
val theme = readerPreferences.readerTheme().get()
+ var flipHorizontally = false
+ private set
+
init {
readerPreferences.cropBordersWebtoon()
.register({ imageCropBorders = it }, { imagePropertyChangedListener?.invoke() })
@@ -96,6 +99,9 @@ class WebtoonConfig(
.distinctUntilChanged()
.onEach { themeChangedListener?.invoke() }
.launchIn(scope)
+
+ readerPreferences.flipHorizontally()
+ .register({ flipHorizontally = it }, { imagePropertyChangedListener?.invoke() })
}
override var navigator: ViewerNavigation = defaultNavigation()
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 488db7bb6..3c162b0fb 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
@@ -213,6 +213,10 @@ class WebtoonPageHolder(
}
private fun process(imageSource: BufferedSource): BufferedSource {
+ if (viewer.config.flipHorizontally) {
+ return ImageUtil.flipImage(imageSource, true, false)
+ }
+
if (viewer.config.dualPageRotateToFit) {
return rotateDualPage(imageSource)
}
diff --git a/core/common/src/main/kotlin/tachiyomi/core/common/util/system/ImageUtil.kt b/core/common/src/main/kotlin/tachiyomi/core/common/util/system/ImageUtil.kt
index f5e9a8098..a9408b687 100644
--- a/core/common/src/main/kotlin/tachiyomi/core/common/util/system/ImageUtil.kt
+++ b/core/common/src/main/kotlin/tachiyomi/core/common/util/system/ImageUtil.kt
@@ -163,11 +163,26 @@ object ImageUtil {
return output
}
+ fun flipImage(imageSource: BufferedSource, flipX: Boolean, flipY: Boolean): BufferedSource {
+ val imageBitmap = BitmapFactory.decodeStream(imageSource.inputStream())
+ val flipped = flipBitMap(imageBitmap, flipX, flipY)
+
+ val output = Buffer()
+ flipped.compress(Bitmap.CompressFormat.JPEG, 100, output.outputStream())
+
+ return output
+ }
+
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)
}
+ private fun flipBitMap(bitmap: Bitmap, flipX: Boolean, flipY: Boolean): Bitmap {
+ val matrix = Matrix().apply { postScale(if (flipX) -1f else 1f, if (flipY) -1f else 1f) }
+ 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.
*/
diff --git a/i18n/src/commonMain/resources/MR/base/strings.xml b/i18n/src/commonMain/resources/MR/base/strings.xml
index bd30e2900..598f5a8a5 100644
--- a/i18n/src/commonMain/resources/MR/base/strings.xml
+++ b/i18n/src/commonMain/resources/MR/base/strings.xml
@@ -375,6 +375,7 @@
Custom brightness
Grayscale
Inverted
+ Flip horizontally
Custom color filter
Color filter blend mode
Overlay