Skip to content

Commit

Permalink
Color inversion in pages color filter #372
Browse files Browse the repository at this point in the history
  • Loading branch information
Koitharu committed Jun 2, 2023
1 parent b1187c6 commit 8c5c7d6
Show file tree
Hide file tree
Showing 12 changed files with 305 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import org.koitharu.kotatsu.core.db.migrations.Migration11To12
import org.koitharu.kotatsu.core.db.migrations.Migration12To13
import org.koitharu.kotatsu.core.db.migrations.Migration13To14
import org.koitharu.kotatsu.core.db.migrations.Migration14To15
import org.koitharu.kotatsu.core.db.migrations.Migration15To16
import org.koitharu.kotatsu.core.db.migrations.Migration1To2
import org.koitharu.kotatsu.core.db.migrations.Migration2To3
import org.koitharu.kotatsu.core.db.migrations.Migration3To4
Expand All @@ -48,7 +49,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 = 15
const val DATABASE_VERSION = 16

@Database(
entities = [
Expand Down Expand Up @@ -100,6 +101,7 @@ val databaseMigrations: Array<Migration>
Migration12To13(),
Migration13To14(),
Migration14To15(),
Migration15To16(),
)

fun MangaDatabase(context: Context): MangaDatabase = Room
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ data class MangaPrefsEntity(
@ColumnInfo(name = "mode") val mode: Int,
@ColumnInfo(name = "cf_brightness") val cfBrightness: Float,
@ColumnInfo(name = "cf_contrast") val cfContrast: Float,
@ColumnInfo(name = "cf_invert") val cfInvert: Boolean,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.koitharu.kotatsu.core.db.migrations

import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase

class Migration15To16 : Migration(15, 16) {

override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE preferences ADD COLUMN `cf_invert` INTEGER NOT NULL DEFAULT 0")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,12 @@ import dagger.Reusable
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import okhttp3.OkHttpClient
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.db.entity.MangaPrefsEntity
import org.koitharu.kotatsu.core.db.entity.toEntities
import org.koitharu.kotatsu.core.db.entity.toEntity
import org.koitharu.kotatsu.core.db.entity.toManga
import org.koitharu.kotatsu.core.db.entity.toMangaTags
import org.koitharu.kotatsu.core.network.MangaHttpClient
import org.koitharu.kotatsu.core.prefs.ReaderMode
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource
Expand All @@ -22,7 +20,6 @@ import javax.inject.Inject

@Reusable
class MangaDataRepository @Inject constructor(
@MangaHttpClient private val okHttpClient: OkHttpClient,
private val db: MangaDatabase,
) {

Expand All @@ -42,6 +39,7 @@ class MangaDataRepository @Inject constructor(
entity.copy(
cfBrightness = colorFilter?.brightness ?: 0f,
cfContrast = colorFilter?.contrast ?: 0f,
cfInvert = colorFilter?.isInverted ?: false,
),
)
}
Expand Down Expand Up @@ -84,8 +82,8 @@ class MangaDataRepository @Inject constructor(
}

private fun MangaPrefsEntity.getColorFilterOrNull(): ReaderColorFilter? {
return if (cfBrightness != 0f || cfContrast != 0f) {
ReaderColorFilter(cfBrightness, cfContrast)
return if (cfBrightness != 0f || cfContrast != 0f || cfInvert) {
ReaderColorFilter(cfBrightness, cfContrast, cfInvert)
} else {
null
}
Expand All @@ -96,5 +94,6 @@ class MangaDataRepository @Inject constructor(
mode = -1,
cfBrightness = 0f,
cfContrast = 0f,
cfInvert = false,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,52 @@ import android.graphics.ColorMatrixColorFilter
class ReaderColorFilter(
val brightness: Float,
val contrast: Float,
val isInverted: Boolean,
) {

val isEmpty: Boolean
get() = brightness == 0f && contrast == 0f
get() = !isInverted && brightness == 0f && contrast == 0f

fun toColorFilter(): ColorMatrixColorFilter {
val cm = ColorMatrix()
val scale = brightness + 1f
cm.setScale(scale, scale, scale, 1f)
if (isInverted) {
cm.inverted()
}
cm.setBrightness(brightness)
cm.setContrast(contrast)
return ColorMatrixColorFilter(cm)
}

private fun ColorMatrix.setBrightness(brightness: Float) {
val scale = brightness + 1f
val matrix = ColorMatrix()
matrix.setScale(scale, scale, scale, 1f)
postConcat(matrix)
}

private fun ColorMatrix.setContrast(contrast: Float) {
val scale = contrast + 1f
val translate = (-.5f * scale + .5f) * 255f
val array = floatArrayOf(
scale, 0f, 0f, 0f, translate,
0f, scale, 0f, 0f, translate,
0f, 0f, scale, 0f, translate,
0f, 0f, 0f, 1f, 0f,
)
val matrix = ColorMatrix(array)
postConcat(matrix)
}

private fun ColorMatrix.inverted() {
val matrix = floatArrayOf(
-1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
0.0f, -1.0f, 0.0f, 1.0f, 1.0f,
0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
0.0f, 0.0f, 0.0f, 1.0f, 0.0f,
)
set(matrix)
}

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
Expand All @@ -27,26 +60,13 @@ class ReaderColorFilter(

if (brightness != other.brightness) return false
if (contrast != other.contrast) return false

return true
return isInverted == other.isInverted
}

override fun hashCode(): Int {
var result = brightness.hashCode()
result = 31 * result + contrast.hashCode()
result = 31 * result + isInverted.hashCode()
return result
}

private fun ColorMatrix.setContrast(contrast: Float) {
val scale = contrast + 1f
val translate = (-.5f * scale + .5f) * 255f
val array = floatArrayOf(
scale, 0f, 0f, 0f, translate,
0f, scale, 0f, 0f, translate,
0f, 0f, scale, 0f, translate,
0f, 0f, 0f, 1f, 0f,
)
val matrix = ColorMatrix(array)
postConcat(matrix)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import android.content.res.Resources
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.widget.CompoundButton
import androidx.activity.viewModels
import androidx.core.graphics.Insets
import androidx.core.view.updateLayoutParams
Expand All @@ -26,6 +27,7 @@ import org.koitharu.kotatsu.core.util.ext.enqueueWith
import org.koitharu.kotatsu.core.util.ext.indicator
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.core.util.ext.setChecked
import org.koitharu.kotatsu.core.util.ext.setValueRounded
import org.koitharu.kotatsu.databinding.ActivityColorFilterBinding
import org.koitharu.kotatsu.parsers.model.Manga
Expand All @@ -39,7 +41,7 @@ import com.google.android.material.R as materialR
class ColorFilterConfigActivity :
BaseActivity<ActivityColorFilterBinding>(),
Slider.OnChangeListener,
View.OnClickListener {
View.OnClickListener, CompoundButton.OnCheckedChangeListener {

@Inject
lateinit var coil: ImageLoader
Expand All @@ -58,6 +60,7 @@ class ColorFilterConfigActivity :
val formatter = PercentLabelFormatter(resources)
viewBinding.sliderContrast.setLabelFormatter(formatter)
viewBinding.sliderBrightness.setLabelFormatter(formatter)
viewBinding.switchInvert.setOnCheckedChangeListener(this)
viewBinding.buttonDone.setOnClickListener(this)
viewBinding.buttonReset.setOnClickListener(this)

Expand All @@ -80,6 +83,10 @@ class ColorFilterConfigActivity :
}
}

override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) {
viewModel.setInversion(isChecked)
}

override fun onClick(v: View) {
when (v.id) {
R.id.button_done -> viewModel.save()
Expand All @@ -103,21 +110,22 @@ class ColorFilterConfigActivity :
private fun onColorFilterChanged(readerColorFilter: ReaderColorFilter?) {
viewBinding.sliderBrightness.setValueRounded(readerColorFilter?.brightness ?: 0f)
viewBinding.sliderContrast.setValueRounded(readerColorFilter?.contrast ?: 0f)
viewBinding.switchInvert.setChecked(readerColorFilter?.isInverted ?: false, false)
viewBinding.imageViewAfter.colorFilter = readerColorFilter?.toColorFilter()
}

private fun onPreviewChanged(preview: MangaPage?) {
if (preview == null) return
ImageRequest.Builder(this@ColorFilterConfigActivity)
.data(preview.url)
.data(preview)
.scale(Scale.FILL)
.decodeRegion()
.tag(preview.source)
.indicator(listOf(viewBinding.progressBefore, viewBinding.progressAfter))
.error(R.drawable.ic_error_placeholder)
.size(ViewSizeResolver(viewBinding.imageViewBefore))
.allowRgb565(false)
.target(ShadowViewTarget(viewBinding.imageViewBefore, viewBinding.imageViewAfter))
.target(DoubleViewTarget(viewBinding.imageViewBefore, viewBinding.imageViewAfter))
.enqueueWith(coil)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,32 @@ class ColorFilterConfigViewModel @Inject constructor(

fun setBrightness(brightness: Float) {
val cf = colorFilter.value
colorFilter.value = ReaderColorFilter(brightness, cf?.contrast ?: 0f).takeUnless { it.isEmpty }
colorFilter.value = ReaderColorFilter(
brightness = brightness,
contrast = cf?.contrast ?: 0f,
isInverted = cf?.isInverted ?: false,
).takeUnless { it.isEmpty }
}

fun setContrast(contrast: Float) {
val cf = colorFilter.value
colorFilter.value = ReaderColorFilter(cf?.brightness ?: 0f, contrast).takeUnless { it.isEmpty }
colorFilter.value = ReaderColorFilter(
brightness = cf?.brightness ?: 0f,
contrast = contrast,
isInverted = cf?.isInverted ?: false,
).takeUnless { it.isEmpty }
}

fun setInversion(invert: Boolean) {
val cf = colorFilter.value
if (invert == cf?.isInverted) {
return
}
colorFilter.value = ReaderColorFilter(
brightness = cf?.brightness ?: 0f,
contrast = cf?.contrast ?: 0f,
isInverted = invert,
).takeUnless { it.isEmpty }
}

fun reset() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import android.graphics.drawable.Drawable
import android.widget.ImageView
import coil.target.ImageViewTarget

class ShadowViewTarget(
view: ImageView,
private val shadowView: ImageView,
) : ImageViewTarget(view) {
class DoubleViewTarget(
primaryView: ImageView,
private val secondaryView: ImageView,
) : ImageViewTarget(primaryView) {

override var drawable: Drawable?
get() = super.drawable
set(value) {
super.drawable = value
shadowView.setImageDrawable(value?.constantState?.newDrawable())
secondaryView.setImageDrawable(value?.constantState?.newDrawable())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class ReaderSettings(
get() = settings.zoomMode

val colorFilter: ReaderColorFilter?
get() = colorFilterFlow.value
get() = colorFilterFlow.value?.takeUnless { it.isEmpty }

val isPagesNumbersEnabled: Boolean
get() = settings.isPagesNumbersEnabled
Expand Down
Loading

0 comments on commit 8c5c7d6

Please sign in to comment.