Grayscale color filter
This commit is contained in:
@@ -7,13 +7,17 @@ data class ReaderColorFilter(
|
||||
val brightness: Float,
|
||||
val contrast: Float,
|
||||
val isInverted: Boolean,
|
||||
val isGrayscale: Boolean,
|
||||
) {
|
||||
|
||||
val isEmpty: Boolean
|
||||
get() = !isInverted && brightness == 0f && contrast == 0f
|
||||
get() = !isGrayscale && !isInverted && brightness == 0f && contrast == 0f
|
||||
|
||||
fun toColorFilter(): ColorMatrixColorFilter {
|
||||
val cm = ColorMatrix()
|
||||
if (isGrayscale) {
|
||||
cm.grayscale()
|
||||
}
|
||||
if (isInverted) {
|
||||
cm.inverted()
|
||||
}
|
||||
@@ -49,6 +53,20 @@ data class ReaderColorFilter(
|
||||
0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
|
||||
0.0f, 0.0f, 0.0f, 1.0f, 0.0f,
|
||||
)
|
||||
set(matrix)
|
||||
postConcat(ColorMatrix(matrix))
|
||||
}
|
||||
|
||||
private fun ColorMatrix.grayscale() {
|
||||
setSaturation(0f)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
val EMPTY = ReaderColorFilter(
|
||||
brightness = 0.0f,
|
||||
contrast = 0.0f,
|
||||
isInverted = false,
|
||||
isGrayscale = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import coil.ImageLoader
|
||||
import coil.request.ImageRequest
|
||||
import coil.size.Scale
|
||||
import coil.size.ViewSizeResolver
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.slider.LabelFormatter
|
||||
import com.google.android.material.slider.Slider
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
@@ -62,6 +63,7 @@ class ColorFilterConfigActivity :
|
||||
viewBinding.sliderContrast.setLabelFormatter(formatter)
|
||||
viewBinding.sliderBrightness.setLabelFormatter(formatter)
|
||||
viewBinding.switchInvert.setOnCheckedChangeListener(this)
|
||||
viewBinding.switchGrayscale.setOnCheckedChangeListener(this)
|
||||
viewBinding.buttonDone.setOnClickListener(this)
|
||||
viewBinding.buttonReset.setOnClickListener(this)
|
||||
|
||||
@@ -84,18 +86,16 @@ class ColorFilterConfigActivity :
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) {
|
||||
viewModel.setInversion(isChecked)
|
||||
override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) {
|
||||
when (buttonView.id) {
|
||||
R.id.switch_invert -> viewModel.setInversion(isChecked)
|
||||
R.id.switch_grayscale -> viewModel.setGrayscale(isChecked)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClick(v: View) {
|
||||
when (v.id) {
|
||||
R.id.button_done -> if (viewBinding.checkBoxGlobal.isChecked) {
|
||||
viewModel.saveGlobally()
|
||||
} else {
|
||||
viewModel.save()
|
||||
}
|
||||
|
||||
R.id.button_done -> showSaveConfirmation()
|
||||
R.id.button_reset -> viewModel.reset()
|
||||
}
|
||||
}
|
||||
@@ -113,10 +113,23 @@ class ColorFilterConfigActivity :
|
||||
}
|
||||
}
|
||||
|
||||
fun showSaveConfirmation() {
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setTitle(R.string.apply)
|
||||
.setMessage(R.string.color_correction_apply_text)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(R.string.this_manga) { _, _ ->
|
||||
viewModel.saveGlobally()
|
||||
}.setNeutralButton(R.string.globally) { _, _ ->
|
||||
viewModel.save()
|
||||
}.show()
|
||||
}
|
||||
|
||||
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.switchGrayscale.setChecked(readerColorFilter?.isGrayscale ?: false, false)
|
||||
viewBinding.imageViewAfter.colorFilter = readerColorFilter?.toColorFilter()
|
||||
}
|
||||
|
||||
@@ -138,6 +151,8 @@ class ColorFilterConfigActivity :
|
||||
private fun onLoadingChanged(isLoading: Boolean) {
|
||||
viewBinding.sliderContrast.isEnabled = !isLoading
|
||||
viewBinding.sliderBrightness.isEnabled = !isLoading
|
||||
viewBinding.switchInvert.isEnabled = !isLoading
|
||||
viewBinding.switchGrayscale.isEnabled = !isLoading
|
||||
viewBinding.buttonDone.isEnabled = !isLoading
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.koitharu.kotatsu.reader.ui.colorfilter
|
||||
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
@@ -8,7 +7,7 @@ import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.util.ext.call
|
||||
|
||||
class ColorFilterConfigBackPressedDispatcher(
|
||||
private val context: Context,
|
||||
private val activity: ColorFilterConfigActivity,
|
||||
private val viewModel: ColorFilterConfigViewModel,
|
||||
) : OnBackPressedCallback(true), DialogInterface.OnClickListener {
|
||||
|
||||
@@ -24,12 +23,12 @@ class ColorFilterConfigBackPressedDispatcher(
|
||||
when (which) {
|
||||
DialogInterface.BUTTON_NEGATIVE -> viewModel.onDismiss.call(Unit)
|
||||
DialogInterface.BUTTON_NEUTRAL -> dialog.dismiss()
|
||||
DialogInterface.BUTTON_POSITIVE -> viewModel.save()
|
||||
DialogInterface.BUTTON_POSITIVE -> activity.showSaveConfirmation()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showConfirmation() {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
MaterialAlertDialogBuilder(activity)
|
||||
.setTitle(R.string.color_correction)
|
||||
.setMessage(R.string.text_unsaved_changes_prompt)
|
||||
.setNegativeButton(R.string.discard, this)
|
||||
|
||||
@@ -44,33 +44,19 @@ class ColorFilterConfigViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
fun setBrightness(brightness: Float) {
|
||||
val cf = colorFilter.value
|
||||
colorFilter.value = ReaderColorFilter(
|
||||
brightness = brightness,
|
||||
contrast = cf?.contrast ?: 0f,
|
||||
isInverted = cf?.isInverted ?: false,
|
||||
).takeUnless { it.isEmpty }
|
||||
updateColorFilter { it.copy(brightness = brightness) }
|
||||
}
|
||||
|
||||
fun setContrast(contrast: Float) {
|
||||
val cf = colorFilter.value
|
||||
colorFilter.value = ReaderColorFilter(
|
||||
brightness = cf?.brightness ?: 0f,
|
||||
contrast = contrast,
|
||||
isInverted = cf?.isInverted ?: false,
|
||||
).takeUnless { it.isEmpty }
|
||||
updateColorFilter { it.copy(contrast = contrast) }
|
||||
}
|
||||
|
||||
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 }
|
||||
updateColorFilter { it.copy(isInverted = invert) }
|
||||
}
|
||||
|
||||
fun setGrayscale(grayscale: Boolean) {
|
||||
updateColorFilter { it.copy(isGrayscale = grayscale) }
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
@@ -85,7 +71,18 @@ class ColorFilterConfigViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
fun saveGlobally() {
|
||||
settings.readerColorFilter = colorFilter.value
|
||||
onDismiss.call(Unit)
|
||||
launchLoadingJob(Dispatchers.Default) {
|
||||
settings.readerColorFilter = colorFilter.value
|
||||
if (mangaDataRepository.getColorFilter(manga.id) != null) {
|
||||
mangaDataRepository.saveColorFilter(manga, colorFilter.value)
|
||||
}
|
||||
onDismiss.call(Unit)
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun updateColorFilter(block: (ReaderColorFilter) -> ReaderColorFilter) {
|
||||
colorFilter.value = block(
|
||||
colorFilter.value ?: ReaderColorFilter.EMPTY,
|
||||
).takeUnless { it.isEmpty }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,10 +102,10 @@ class ReaderSettings(
|
||||
AppSettings.KEY_READER_BACKGROUND,
|
||||
AppSettings.KEY_32BIT_COLOR,
|
||||
AppSettings.KEY_READER_OPTIMIZE,
|
||||
AppSettings.KEY_CF_ENABLED,
|
||||
AppSettings.KEY_CF_CONTRAST,
|
||||
AppSettings.KEY_CF_BRIGHTNESS,
|
||||
AppSettings.KEY_CF_INVERTED,
|
||||
AppSettings.KEY_CF_GRAYSCALE,
|
||||
)
|
||||
|
||||
override suspend fun emit(value: ReaderColorFilter?) {
|
||||
|
||||
Reference in New Issue
Block a user