mirror of
https://github.com/T8RIN/ImageToolbox.git
synced 2025-08-06 15:49:35 +08:00
Added Tone curves as filter by #1838
This commit is contained in:
@ -28,4 +28,5 @@ dependencies {
|
|||||||
implementation(projects.core.ui)
|
implementation(projects.core.ui)
|
||||||
implementation(projects.core.resources)
|
implementation(projects.core.resources)
|
||||||
implementation(libs.kotlin.reflect)
|
implementation(libs.kotlin.reflect)
|
||||||
|
implementation(libs.toolbox.curves)
|
||||||
}
|
}
|
@ -265,6 +265,7 @@ interface Filter<Value> : VisibilityOwner {
|
|||||||
interface LaplacianSimple : SimpleFilter
|
interface LaplacianSimple : SimpleFilter
|
||||||
interface MotionBlur : TripleFilter<Int, Float, BlurEdgeMode>
|
interface MotionBlur : TripleFilter<Int, Float, BlurEdgeMode>
|
||||||
interface AutoRemoveRedEyes : Filter<Float>
|
interface AutoRemoveRedEyes : Filter<Float>
|
||||||
|
interface ToneCurves : Filter<ToneCurvesParams>
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SimpleFilter : Filter<Unit>
|
interface SimpleFilter : Filter<Unit>
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
package ru.tech.imageresizershrinker.core.filters.domain.model
|
||||||
|
|
||||||
|
import com.t8rin.curves.ImageCurvesEditorState
|
||||||
|
|
||||||
|
data class ToneCurvesParams(
|
||||||
|
val controlPoints: List<List<Float>>
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
val Default by lazy {
|
||||||
|
ToneCurvesParams(ImageCurvesEditorState.Default.controlPoints)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -144,7 +144,8 @@ sealed class UiFilter<T>(
|
|||||||
UiPosterizeFilter(),
|
UiPosterizeFilter(),
|
||||||
UiColorPosterFilter(),
|
UiColorPosterFilter(),
|
||||||
UiTriToneFilter(),
|
UiTriToneFilter(),
|
||||||
UiPopArtFilter()
|
UiPopArtFilter(),
|
||||||
|
UiToneCurvesFilter()
|
||||||
),
|
),
|
||||||
listOf(
|
listOf(
|
||||||
UiLUT512x512Filter(),
|
UiLUT512x512Filter(),
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
package ru.tech.imageresizershrinker.core.filters.presentation.model
|
||||||
|
|
||||||
|
import ru.tech.imageresizershrinker.core.filters.domain.model.Filter
|
||||||
|
import ru.tech.imageresizershrinker.core.filters.domain.model.FilterParam
|
||||||
|
import ru.tech.imageresizershrinker.core.filters.domain.model.ToneCurvesParams
|
||||||
|
import ru.tech.imageresizershrinker.core.resources.R
|
||||||
|
|
||||||
|
class UiToneCurvesFilter(
|
||||||
|
override val value: ToneCurvesParams = ToneCurvesParams.Default
|
||||||
|
) : UiFilter<ToneCurvesParams>(
|
||||||
|
title = R.string.tone_curves,
|
||||||
|
paramsInfo = listOf(
|
||||||
|
FilterParam(R.string.values, 0f..0f)
|
||||||
|
),
|
||||||
|
value = value
|
||||||
|
), Filter.ToneCurves
|
@ -29,6 +29,7 @@ import ru.tech.imageresizershrinker.core.filters.domain.model.LinearGaussianPara
|
|||||||
import ru.tech.imageresizershrinker.core.filters.domain.model.LinearTiltShiftParams
|
import ru.tech.imageresizershrinker.core.filters.domain.model.LinearTiltShiftParams
|
||||||
import ru.tech.imageresizershrinker.core.filters.domain.model.RadialTiltShiftParams
|
import ru.tech.imageresizershrinker.core.filters.domain.model.RadialTiltShiftParams
|
||||||
import ru.tech.imageresizershrinker.core.filters.domain.model.SideFadeParams
|
import ru.tech.imageresizershrinker.core.filters.domain.model.SideFadeParams
|
||||||
|
import ru.tech.imageresizershrinker.core.filters.domain.model.ToneCurvesParams
|
||||||
import ru.tech.imageresizershrinker.core.filters.domain.model.WaterParams
|
import ru.tech.imageresizershrinker.core.filters.domain.model.WaterParams
|
||||||
import ru.tech.imageresizershrinker.core.filters.presentation.model.UiFilter
|
import ru.tech.imageresizershrinker.core.filters.presentation.model.UiFilter
|
||||||
import ru.tech.imageresizershrinker.core.filters.presentation.widget.filterItem.BooleanItem
|
import ru.tech.imageresizershrinker.core.filters.presentation.widget.filterItem.BooleanItem
|
||||||
@ -43,6 +44,7 @@ import ru.tech.imageresizershrinker.core.filters.presentation.widget.filterItem.
|
|||||||
import ru.tech.imageresizershrinker.core.filters.presentation.widget.filterItem.PairItem
|
import ru.tech.imageresizershrinker.core.filters.presentation.widget.filterItem.PairItem
|
||||||
import ru.tech.imageresizershrinker.core.filters.presentation.widget.filterItem.RadialTiltShiftParamsItem
|
import ru.tech.imageresizershrinker.core.filters.presentation.widget.filterItem.RadialTiltShiftParamsItem
|
||||||
import ru.tech.imageresizershrinker.core.filters.presentation.widget.filterItem.SideFadeRelativeItem
|
import ru.tech.imageresizershrinker.core.filters.presentation.widget.filterItem.SideFadeRelativeItem
|
||||||
|
import ru.tech.imageresizershrinker.core.filters.presentation.widget.filterItem.ToneCurvesParamsItem
|
||||||
import ru.tech.imageresizershrinker.core.filters.presentation.widget.filterItem.TripleItem
|
import ru.tech.imageresizershrinker.core.filters.presentation.widget.filterItem.TripleItem
|
||||||
import ru.tech.imageresizershrinker.core.filters.presentation.widget.filterItem.WaterParamsItem
|
import ru.tech.imageresizershrinker.core.filters.presentation.widget.filterItem.WaterParamsItem
|
||||||
|
|
||||||
@ -182,6 +184,15 @@ internal fun <T> FilterItemContent(
|
|||||||
previewOnly = previewOnly
|
previewOnly = previewOnly
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is ToneCurvesParams -> {
|
||||||
|
ToneCurvesParamsItem(
|
||||||
|
value = value,
|
||||||
|
filter = filter.cast(),
|
||||||
|
onFilterChange = onFilterChange,
|
||||||
|
previewOnly = previewOnly
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,94 @@
|
|||||||
|
package ru.tech.imageresizershrinker.core.filters.presentation.widget.filterItem
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import coil3.imageLoader
|
||||||
|
import coil3.request.ImageRequest
|
||||||
|
import coil3.toBitmap
|
||||||
|
import com.t8rin.curves.ImageCurvesEditor
|
||||||
|
import com.t8rin.curves.ImageCurvesEditorState
|
||||||
|
import ru.tech.imageresizershrinker.core.filters.domain.model.ToneCurvesParams
|
||||||
|
import ru.tech.imageresizershrinker.core.filters.presentation.model.UiFilter
|
||||||
|
import ru.tech.imageresizershrinker.core.resources.R
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun ToneCurvesParamsItem(
|
||||||
|
value: ToneCurvesParams,
|
||||||
|
filter: UiFilter<ToneCurvesParams>,
|
||||||
|
onFilterChange: (value: ToneCurvesParams) -> Unit,
|
||||||
|
previewOnly: Boolean
|
||||||
|
) {
|
||||||
|
val editorState: MutableState<ImageCurvesEditorState> =
|
||||||
|
remember { mutableStateOf(ImageCurvesEditorState(value.controlPoints)) }
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.padding(8.dp)
|
||||||
|
) {
|
||||||
|
var bitmap by remember {
|
||||||
|
mutableStateOf<Bitmap?>(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
LaunchedEffect(context) {
|
||||||
|
bitmap = context.imageLoader.execute(
|
||||||
|
ImageRequest.Builder(context)
|
||||||
|
.data(R.drawable.filter_preview_source)
|
||||||
|
.build()
|
||||||
|
).image?.toBitmap()
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageCurvesEditor(
|
||||||
|
bitmap = bitmap,
|
||||||
|
state = editorState.value,
|
||||||
|
curvesSelectionText = {
|
||||||
|
Text(
|
||||||
|
text = when (it) {
|
||||||
|
0 -> stringResource(R.string.all)
|
||||||
|
1 -> stringResource(R.string.color_red)
|
||||||
|
2 -> stringResource(R.string.color_green)
|
||||||
|
3 -> stringResource(R.string.color_blue)
|
||||||
|
else -> ""
|
||||||
|
},
|
||||||
|
style = MaterialTheme.typography.bodySmall
|
||||||
|
)
|
||||||
|
},
|
||||||
|
imageObtainingTrigger = false,
|
||||||
|
onImageObtained = { },
|
||||||
|
//shape = RoundedCornerShape(4.dp),
|
||||||
|
containerModifier = Modifier.fillMaxWidth(),
|
||||||
|
onStateChange = {
|
||||||
|
onFilterChange(
|
||||||
|
ToneCurvesParams(
|
||||||
|
controlPoints = it.controlPoints
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (previewOnly) {
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.matchParentSize(),
|
||||||
|
color = Color.Transparent,
|
||||||
|
content = {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -34,4 +34,5 @@ dependencies {
|
|||||||
implementation(libs.trickle)
|
implementation(libs.trickle)
|
||||||
implementation(libs.toolbox.gpuimage)
|
implementation(libs.toolbox.gpuimage)
|
||||||
implementation(libs.toolbox.opencvTools)
|
implementation(libs.toolbox.opencvTools)
|
||||||
|
implementation(libs.toolbox.curves)
|
||||||
}
|
}
|
@ -246,6 +246,7 @@ import ru.tech.imageresizershrinker.feature.filters.data.model.SunriseFilter
|
|||||||
import ru.tech.imageresizershrinker.feature.filters.data.model.SwirlDistortionFilter
|
import ru.tech.imageresizershrinker.feature.filters.data.model.SwirlDistortionFilter
|
||||||
import ru.tech.imageresizershrinker.feature.filters.data.model.TentBlurFilter
|
import ru.tech.imageresizershrinker.feature.filters.data.model.TentBlurFilter
|
||||||
import ru.tech.imageresizershrinker.feature.filters.data.model.ThresholdFilter
|
import ru.tech.imageresizershrinker.feature.filters.data.model.ThresholdFilter
|
||||||
|
import ru.tech.imageresizershrinker.feature.filters.data.model.ToneCurvesFilter
|
||||||
import ru.tech.imageresizershrinker.feature.filters.data.model.ToonFilter
|
import ru.tech.imageresizershrinker.feature.filters.data.model.ToonFilter
|
||||||
import ru.tech.imageresizershrinker.feature.filters.data.model.TopHatFilter
|
import ru.tech.imageresizershrinker.feature.filters.data.model.TopHatFilter
|
||||||
import ru.tech.imageresizershrinker.feature.filters.data.model.TriToneFilter
|
import ru.tech.imageresizershrinker.feature.filters.data.model.TriToneFilter
|
||||||
@ -490,7 +491,7 @@ internal class AndroidFilterProvider @Inject constructor(
|
|||||||
is Filter.SoftElegance -> SoftEleganceFilter(value, lutFilterFactory)
|
is Filter.SoftElegance -> SoftEleganceFilter(value, lutFilterFactory)
|
||||||
is Filter.SoftEleganceVariant -> SoftEleganceVariantFilter(value, lutFilterFactory)
|
is Filter.SoftEleganceVariant -> SoftEleganceVariantFilter(value, lutFilterFactory)
|
||||||
is Filter.PaletteTransferVariant -> paletteTransferVariantFilterFactory(value)
|
is Filter.PaletteTransferVariant -> paletteTransferVariantFilterFactory(value)
|
||||||
is Filter.CubeLut -> CubeLutFilter(value, context)
|
is Filter.CubeLut -> CubeLutFilter(context, value)
|
||||||
is Filter.BleachBypass -> BleachBypassFilter(value, lutFilterFactory)
|
is Filter.BleachBypass -> BleachBypassFilter(value, lutFilterFactory)
|
||||||
is Filter.Candlelight -> CandlelightFilter(value, lutFilterFactory)
|
is Filter.Candlelight -> CandlelightFilter(value, lutFilterFactory)
|
||||||
is Filter.DropBlues -> DropBluesFilter(value, lutFilterFactory)
|
is Filter.DropBlues -> DropBluesFilter(value, lutFilterFactory)
|
||||||
@ -516,7 +517,8 @@ internal class AndroidFilterProvider @Inject constructor(
|
|||||||
is Filter.SobelSimple -> SobelSimpleFilter(value)
|
is Filter.SobelSimple -> SobelSimpleFilter(value)
|
||||||
is Filter.LaplacianSimple -> LaplacianSimpleFilter(value)
|
is Filter.LaplacianSimple -> LaplacianSimpleFilter(value)
|
||||||
is Filter.MotionBlur -> MotionBlurFilter(value)
|
is Filter.MotionBlur -> MotionBlurFilter(value)
|
||||||
is Filter.AutoRemoveRedEyes -> AutoRemoveRedEyesFilter(value, context)
|
is Filter.AutoRemoveRedEyes -> AutoRemoveRedEyesFilter(context, value)
|
||||||
|
is Filter.ToneCurves -> ToneCurvesFilter(context, value)
|
||||||
|
|
||||||
else -> throw IllegalArgumentException("No filter implementation for interface ${filter::class.simpleName}")
|
else -> throw IllegalArgumentException("No filter implementation for interface ${filter::class.simpleName}")
|
||||||
}
|
}
|
||||||
|
@ -25,8 +25,8 @@ import ru.tech.imageresizershrinker.core.domain.transformation.Transformation
|
|||||||
import ru.tech.imageresizershrinker.core.filters.domain.model.Filter
|
import ru.tech.imageresizershrinker.core.filters.domain.model.Filter
|
||||||
|
|
||||||
internal class AutoRemoveRedEyesFilter(
|
internal class AutoRemoveRedEyesFilter(
|
||||||
|
private val context: Context,
|
||||||
override val value: Float = 150f,
|
override val value: Float = 150f,
|
||||||
private val context: Context
|
|
||||||
) : Transformation<Bitmap>, Filter.AutoRemoveRedEyes {
|
) : Transformation<Bitmap>, Filter.AutoRemoveRedEyes {
|
||||||
|
|
||||||
override val cacheKey: String
|
override val cacheKey: String
|
||||||
|
@ -29,8 +29,8 @@ import ru.tech.imageresizershrinker.core.domain.transformation.Transformation
|
|||||||
import ru.tech.imageresizershrinker.core.filters.domain.model.Filter
|
import ru.tech.imageresizershrinker.core.filters.domain.model.Filter
|
||||||
|
|
||||||
internal class CubeLutFilter @AssistedInject internal constructor(
|
internal class CubeLutFilter @AssistedInject internal constructor(
|
||||||
|
private val context: Context,
|
||||||
override val value: Pair<Float, FileModel> = 1f to FileModel(""),
|
override val value: Pair<Float, FileModel> = 1f to FileModel(""),
|
||||||
private val context: Context
|
|
||||||
) : Transformation<Bitmap>, Filter.CubeLut {
|
) : Transformation<Bitmap>, Filter.CubeLut {
|
||||||
|
|
||||||
override val cacheKey: String
|
override val cacheKey: String
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
package ru.tech.imageresizershrinker.feature.filters.data.model
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.t8rin.curves.GPUImageToneCurveFilter
|
||||||
|
import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter
|
||||||
|
import ru.tech.imageresizershrinker.core.filters.domain.model.Filter
|
||||||
|
import ru.tech.imageresizershrinker.core.filters.domain.model.ToneCurvesParams
|
||||||
|
|
||||||
|
internal class ToneCurvesFilter(
|
||||||
|
private val context: Context,
|
||||||
|
override val value: ToneCurvesParams = ToneCurvesParams.Default,
|
||||||
|
) : GPUFilterTransformation(context), Filter.ToneCurves {
|
||||||
|
|
||||||
|
override val cacheKey: String
|
||||||
|
get() = (value to context).hashCode().toString()
|
||||||
|
|
||||||
|
override fun createFilter(): GPUImageFilter = GPUImageToneCurveFilter(value.controlPoints)
|
||||||
|
}
|
@ -30,6 +30,7 @@ import ru.tech.imageresizershrinker.core.filters.domain.model.LinearTiltShiftPar
|
|||||||
import ru.tech.imageresizershrinker.core.filters.domain.model.PopArtBlendingMode
|
import ru.tech.imageresizershrinker.core.filters.domain.model.PopArtBlendingMode
|
||||||
import ru.tech.imageresizershrinker.core.filters.domain.model.RadialTiltShiftParams
|
import ru.tech.imageresizershrinker.core.filters.domain.model.RadialTiltShiftParams
|
||||||
import ru.tech.imageresizershrinker.core.filters.domain.model.SideFadeParams
|
import ru.tech.imageresizershrinker.core.filters.domain.model.SideFadeParams
|
||||||
|
import ru.tech.imageresizershrinker.core.filters.domain.model.ToneCurvesParams
|
||||||
import ru.tech.imageresizershrinker.core.filters.domain.model.TransferFunc
|
import ru.tech.imageresizershrinker.core.filters.domain.model.TransferFunc
|
||||||
import ru.tech.imageresizershrinker.core.filters.domain.model.WaterParams
|
import ru.tech.imageresizershrinker.core.filters.domain.model.WaterParams
|
||||||
import kotlin.collections.component1
|
import kotlin.collections.component1
|
||||||
@ -151,6 +152,12 @@ internal fun Any.toPair(): Pair<String, String>? {
|
|||||||
).joinToString(PROPERTIES_SEPARATOR)
|
).joinToString(PROPERTIES_SEPARATOR)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is ToneCurvesParams -> {
|
||||||
|
ToneCurvesParams::class.simpleName!! to controlPoints.joinToString(PROPERTIES_SEPARATOR) {
|
||||||
|
it.joinToString(ADDITIONAL_PROPERTIES_SEPARATOR)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -297,6 +304,18 @@ internal fun Pair<String, String>.fromPair(): Any? {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
name == ToneCurvesParams::class.simpleName -> {
|
||||||
|
val controlPoints = value.split(PROPERTIES_SEPARATOR).map { valueString ->
|
||||||
|
valueString.split(ADDITIONAL_PROPERTIES_SEPARATOR).map {
|
||||||
|
it.toFloatOrNull() ?: 0f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ToneCurvesParams(
|
||||||
|
controlPoints = controlPoints
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -332,3 +351,4 @@ internal fun String.fromPart(type: String): Any {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private const val PROPERTIES_SEPARATOR = "$"
|
private const val PROPERTIES_SEPARATOR = "$"
|
||||||
|
private const val ADDITIONAL_PROPERTIES_SEPARATOR = "*"
|
@ -8,7 +8,7 @@ versionCode = "173"
|
|||||||
|
|
||||||
jvmTarget = "17"
|
jvmTarget = "17"
|
||||||
|
|
||||||
imageToolboxLibs = "3.5.6"
|
imageToolboxLibs = "3.5.7"
|
||||||
trickle = "1.2.2"
|
trickle = "1.2.2"
|
||||||
evaluator = "1.0.0"
|
evaluator = "1.0.0"
|
||||||
quickie = "1.14.0"
|
quickie = "1.14.0"
|
||||||
|
Reference in New Issue
Block a user