Yellow color filter in reader

This commit is contained in:
Koitharu
2025-07-05 10:08:13 +03:00
parent add5c7dc17
commit aafefffc27
12 changed files with 114 additions and 20 deletions

View File

@@ -42,6 +42,7 @@ import org.koitharu.kotatsu.core.db.migrations.Migration23To24
import org.koitharu.kotatsu.core.db.migrations.Migration24To23
import org.koitharu.kotatsu.core.db.migrations.Migration24To25
import org.koitharu.kotatsu.core.db.migrations.Migration25To26
import org.koitharu.kotatsu.core.db.migrations.Migration26To27
import org.koitharu.kotatsu.core.db.migrations.Migration2To3
import org.koitharu.kotatsu.core.db.migrations.Migration3To4
import org.koitharu.kotatsu.core.db.migrations.Migration4To5
@@ -69,7 +70,7 @@ import org.koitharu.kotatsu.tracker.data.TrackEntity
import org.koitharu.kotatsu.tracker.data.TrackLogEntity
import org.koitharu.kotatsu.tracker.data.TracksDao
const val DATABASE_VERSION = 26
const val DATABASE_VERSION = 27
@Database(
entities = [
@@ -140,6 +141,7 @@ fun getDatabaseMigrations(context: Context): Array<Migration> = arrayOf(
Migration24To23(),
Migration24To25(),
Migration25To26(),
Migration26To27(),
)
fun MangaDatabase(context: Context): MangaDatabase = Room

View File

@@ -26,6 +26,7 @@ data class MangaPrefsEntity(
@ColumnInfo(name = "cf_contrast") val cfContrast: Float,
@ColumnInfo(name = "cf_invert") val cfInvert: Boolean,
@ColumnInfo(name = "cf_grayscale") val cfGrayscale: Boolean,
@ColumnInfo(name = "cf_book") val cfBookEffect: Boolean,
@ColumnInfo(name = "title_override") val titleOverride: String?,
@ColumnInfo(name = "cover_override") val coverUrlOverride: String?,
@ColumnInfo(name = "content_rating_override") val contentRatingOverride: String?,

View File

@@ -0,0 +1,11 @@
package org.koitharu.kotatsu.core.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration26To27 : Migration(26, 27) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE preferences ADD COLUMN cf_book INTEGER NOT NULL DEFAULT 0")
}
}

View File

@@ -197,8 +197,14 @@ class MangaDataRepository @Inject constructor(
}
private fun MangaPrefsEntity.getColorFilterOrNull(): ReaderColorFilter? {
return if (cfBrightness != 0f || cfContrast != 0f || cfInvert || cfGrayscale) {
ReaderColorFilter(cfBrightness, cfContrast, cfInvert, cfGrayscale)
return if (cfBrightness != 0f || cfContrast != 0f || cfInvert || cfGrayscale || cfBookEffect) {
ReaderColorFilter(
brightness = cfBrightness,
contrast = cfContrast,
isInverted = cfInvert,
isGrayscale = cfGrayscale,
isBookBackground = cfBookEffect
)
} else {
null
}
@@ -219,10 +225,11 @@ class MangaDataRepository @Inject constructor(
private fun newEntity(mangaId: Long) = MangaPrefsEntity(
mangaId = mangaId,
mode = -1,
cfBrightness = 0f,
cfContrast = 0f,
cfInvert = false,
cfGrayscale = false,
cfBrightness = ReaderColorFilter.EMPTY.brightness,
cfContrast = ReaderColorFilter.EMPTY.contrast,
cfInvert = ReaderColorFilter.EMPTY.isInverted,
cfGrayscale = ReaderColorFilter.EMPTY.isGrayscale,
cfBookEffect = ReaderColorFilter.EMPTY.isBookBackground,
titleOverride = null,
coverUrlOverride = null,
contentRatingOverride = null,

View File

@@ -401,19 +401,29 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
var readerColorFilter: ReaderColorFilter?
get() = runCatching {
val brightness = prefs.getFloat(KEY_CF_BRIGHTNESS, ReaderColorFilter.EMPTY.brightness)
val contrast = prefs.getFloat(KEY_CF_CONTRAST, ReaderColorFilter.EMPTY.contrast)
val inverted = prefs.getBoolean(KEY_CF_INVERTED, ReaderColorFilter.EMPTY.isInverted)
val grayscale = prefs.getBoolean(KEY_CF_GRAYSCALE, ReaderColorFilter.EMPTY.isGrayscale)
ReaderColorFilter(brightness, contrast, inverted, grayscale).takeUnless { it.isEmpty }
ReaderColorFilter(
brightness = prefs.getFloat(KEY_CF_BRIGHTNESS, ReaderColorFilter.EMPTY.brightness),
contrast = prefs.getFloat(KEY_CF_CONTRAST, ReaderColorFilter.EMPTY.contrast),
isInverted = prefs.getBoolean(KEY_CF_INVERTED, ReaderColorFilter.EMPTY.isInverted),
isGrayscale = prefs.getBoolean(KEY_CF_GRAYSCALE, ReaderColorFilter.EMPTY.isGrayscale),
isBookBackground = prefs.getBoolean(KEY_CF_BOOK, ReaderColorFilter.EMPTY.isBookBackground),
).takeUnless { it.isEmpty }
}.getOrNull()
set(value) {
prefs.edit {
val cf = value ?: ReaderColorFilter.EMPTY
putFloat(KEY_CF_BRIGHTNESS, cf.brightness)
putFloat(KEY_CF_CONTRAST, cf.contrast)
putBoolean(KEY_CF_INVERTED, cf.isInverted)
putBoolean(KEY_CF_GRAYSCALE, cf.isGrayscale)
if (value != null) {
putFloat(KEY_CF_BRIGHTNESS, value.brightness)
putFloat(KEY_CF_CONTRAST, value.contrast)
putBoolean(KEY_CF_INVERTED, value.isInverted)
putBoolean(KEY_CF_GRAYSCALE, value.isGrayscale)
putBoolean(KEY_CF_BOOK, value.isBookBackground)
} else {
remove(KEY_CF_BRIGHTNESS)
remove(KEY_CF_CONTRAST)
remove(KEY_CF_INVERTED)
remove(KEY_CF_GRAYSCALE)
remove(KEY_CF_BOOK)
}
}
}
@@ -740,6 +750,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
const val KEY_CF_CONTRAST = "cf_contrast"
const val KEY_CF_INVERTED = "cf_inverted"
const val KEY_CF_GRAYSCALE = "cf_grayscale"
const val KEY_CF_BOOK = "cf_book"
const val KEY_PAGES_TAB = "pages_tab"
const val KEY_DETAILS_TAB = "details_tab"
const val KEY_DETAILS_LAST_TAB = "details_last_tab"

View File

@@ -1,5 +1,7 @@
package org.koitharu.kotatsu.reader.domain
import android.content.res.ColorStateList
import android.graphics.Color
import android.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter
@@ -8,10 +10,11 @@ data class ReaderColorFilter(
val contrast: Float,
val isInverted: Boolean,
val isGrayscale: Boolean,
val isBookBackground: Boolean,
) {
val isEmpty: Boolean
get() = !isGrayscale && !isInverted && brightness == 0f && contrast == 0f
get() = !isGrayscale && !isInverted && !isBookBackground && brightness == 0f && contrast == 0f
fun toColorFilter(): ColorMatrixColorFilter {
val cm = ColorMatrix()
@@ -23,9 +26,19 @@ data class ReaderColorFilter(
}
cm.setBrightness(brightness)
cm.setContrast(contrast)
if (isBookBackground) {
cm.addBookEffect()
}
return ColorMatrixColorFilter(cm)
}
fun getBackgroundTint(): ColorStateList? = if (isBookBackground) {
val color = Color.rgb(255, 255, (255 * BOOK_BLUE_FACTOR).toInt())
ColorStateList.valueOf(color)
} else {
null
}
private fun ColorMatrix.setBrightness(brightness: Float) {
val scale = brightness + 1f
val matrix = ColorMatrix()
@@ -60,13 +73,26 @@ data class ReaderColorFilter(
setSaturation(0f)
}
private fun ColorMatrix.addBookEffect() {
val removeBlueMatrix = floatArrayOf(
1f, 0f, 0f, 0f, 0f,
0f, 1f, 0f, 0f, 0f,
0f, 0f, BOOK_BLUE_FACTOR, 0f, 0f,
0f, 0f, 0f, 1f, 0f,
)
postConcat(ColorMatrix(removeBlueMatrix))
}
companion object {
private const val BOOK_BLUE_FACTOR = 0.92f
val EMPTY = ReaderColorFilter(
brightness = 0.0f,
contrast = 0.0f,
isInverted = false,
isGrayscale = false,
isBookBackground = false,
)
}
}

View File

@@ -53,6 +53,7 @@ class ColorFilterConfigActivity :
viewBinding.sliderBrightness.setLabelFormatter(formatter)
viewBinding.switchInvert.setOnCheckedChangeListener(this)
viewBinding.switchGrayscale.setOnCheckedChangeListener(this)
viewBinding.switchBook.setOnCheckedChangeListener(this)
viewBinding.buttonDone.setOnClickListener(this)
viewBinding.buttonReset.setOnClickListener(this)
@@ -93,6 +94,7 @@ class ColorFilterConfigActivity :
when (buttonView.id) {
R.id.switch_invert -> viewModel.setInversion(isChecked)
R.id.switch_grayscale -> viewModel.setGrayscale(isChecked)
R.id.switch_book -> viewModel.setBookEffect(isChecked)
}
}
@@ -120,6 +122,7 @@ class ColorFilterConfigActivity :
viewBinding.sliderContrast.setValueRounded(readerColorFilter?.contrast ?: 0f)
viewBinding.switchInvert.setChecked(readerColorFilter?.isInverted == true, false)
viewBinding.switchGrayscale.setChecked(readerColorFilter?.isGrayscale == true, false)
viewBinding.switchBook.setChecked(readerColorFilter?.isBookBackground == true, false)
viewBinding.imageViewAfter.colorFilter = readerColorFilter?.toColorFilter()
}

View File

@@ -56,6 +56,10 @@ class ColorFilterConfigViewModel @Inject constructor(
updateColorFilter { it.copy(isGrayscale = grayscale) }
}
fun setBookEffect(book: Boolean) {
updateColorFilter { it.copy(isBookBackground = book) }
}
fun reset() {
colorFilter.value = null
}

View File

@@ -57,6 +57,11 @@ data class ReaderSettings(
fun applyBackground(view: View) {
view.background = background.resolve(view.context)
view.backgroundTintList = if (background.isLight(view.context)) {
colorFilter?.getBackgroundTint()
} else {
null
}
}
fun isPagesCropEnabled(isWebtoon: Boolean) = if (isWebtoon) {

View File

@@ -191,6 +191,18 @@
app:layout_constraintStart_toEndOf="@id/guideline_vertical"
app:layout_constraintTop_toBottomOf="@id/textView_contrast" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/switch_book"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_normal"
android:layout_marginTop="8dp"
android:text="@string/book_effect"
android:textAppearance="?textAppearanceTitleMedium"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/guideline_vertical"
app:layout_constraintTop_toBottomOf="@id/slider_contrast" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View File

@@ -170,15 +170,26 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textView_contrast" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/switch_book"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/book_effect"
android:textAppearance="?textAppearanceTitleMedium"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/slider_contrast" />
<Button
android:id="@+id/button_reset"
style="?materialButtonOutlinedStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginTop="16dp"
android:text="@string/reset"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/slider_contrast" />
app:layout_constraintTop_toBottomOf="@id/switch_book" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -853,4 +853,5 @@
<string name="reader_multitask_summary">Allows you to keep multiple readers with different manga open at the same time</string>
<string name="theme_name_itsuka">Itsuka</string>
<string name="theme_name_totoro">Totoro</string>
<string name="book_effect">Yellowish background (blue filter)</string>
</resources>