mirror of
https://github.com/T8RIN/ImageToolbox.git
synced 2025-05-17 21:45:59 +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.resources)
|
||||
implementation(libs.kotlin.reflect)
|
||||
implementation(libs.toolbox.curves)
|
||||
}
|
@ -265,6 +265,7 @@ interface Filter<Value> : VisibilityOwner {
|
||||
interface LaplacianSimple : SimpleFilter
|
||||
interface MotionBlur : TripleFilter<Int, Float, BlurEdgeMode>
|
||||
interface AutoRemoveRedEyes : Filter<Float>
|
||||
interface ToneCurves : Filter<ToneCurvesParams>
|
||||
}
|
||||
|
||||
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(),
|
||||
UiColorPosterFilter(),
|
||||
UiTriToneFilter(),
|
||||
UiPopArtFilter()
|
||||
UiPopArtFilter(),
|
||||
UiToneCurvesFilter()
|
||||
),
|
||||
listOf(
|
||||
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.RadialTiltShiftParams
|
||||
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.presentation.model.UiFilter
|
||||
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.RadialTiltShiftParamsItem
|
||||
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.WaterParamsItem
|
||||
|
||||
@ -182,6 +184,15 @@ internal fun <T> FilterItemContent(
|
||||
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.toolbox.gpuimage)
|
||||
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.TentBlurFilter
|
||||
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.TopHatFilter
|
||||
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.SoftEleganceVariant -> SoftEleganceVariantFilter(value, lutFilterFactory)
|
||||
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.Candlelight -> CandlelightFilter(value, lutFilterFactory)
|
||||
is Filter.DropBlues -> DropBluesFilter(value, lutFilterFactory)
|
||||
@ -516,7 +517,8 @@ internal class AndroidFilterProvider @Inject constructor(
|
||||
is Filter.SobelSimple -> SobelSimpleFilter(value)
|
||||
is Filter.LaplacianSimple -> LaplacianSimpleFilter(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}")
|
||||
}
|
||||
|
@ -25,8 +25,8 @@ import ru.tech.imageresizershrinker.core.domain.transformation.Transformation
|
||||
import ru.tech.imageresizershrinker.core.filters.domain.model.Filter
|
||||
|
||||
internal class AutoRemoveRedEyesFilter(
|
||||
private val context: Context,
|
||||
override val value: Float = 150f,
|
||||
private val context: Context
|
||||
) : Transformation<Bitmap>, Filter.AutoRemoveRedEyes {
|
||||
|
||||
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
|
||||
|
||||
internal class CubeLutFilter @AssistedInject internal constructor(
|
||||
private val context: Context,
|
||||
override val value: Pair<Float, FileModel> = 1f to FileModel(""),
|
||||
private val context: Context
|
||||
) : Transformation<Bitmap>, Filter.CubeLut {
|
||||
|
||||
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.RadialTiltShiftParams
|
||||
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.WaterParams
|
||||
import kotlin.collections.component1
|
||||
@ -151,6 +152,12 @@ internal fun Any.toPair(): Pair<String, String>? {
|
||||
).joinToString(PROPERTIES_SEPARATOR)
|
||||
}
|
||||
|
||||
is ToneCurvesParams -> {
|
||||
ToneCurvesParams::class.simpleName!! to controlPoints.joinToString(PROPERTIES_SEPARATOR) {
|
||||
it.joinToString(ADDITIONAL_PROPERTIES_SEPARATOR)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -332,3 +351,4 @@ internal fun String.fromPart(type: String): Any {
|
||||
}
|
||||
|
||||
private const val PROPERTIES_SEPARATOR = "$"
|
||||
private const val ADDITIONAL_PROPERTIES_SEPARATOR = "*"
|
@ -8,7 +8,7 @@ versionCode = "173"
|
||||
|
||||
jvmTarget = "17"
|
||||
|
||||
imageToolboxLibs = "3.5.6"
|
||||
imageToolboxLibs = "3.5.7"
|
||||
trickle = "1.2.2"
|
||||
evaluator = "1.0.0"
|
||||
quickie = "1.14.0"
|
||||
|
Reference in New Issue
Block a user