Skip to content

Commit

Permalink
cleanup layer effect applying
Browse files Browse the repository at this point in the history
  • Loading branch information
Zhirkevich Alexander Y authored and Zhirkevich Alexander Y committed Feb 11, 2025
1 parent 55febe0 commit dce4c50
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 140 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package io.github.alexzhirkevich.compottie.internal.effects

import androidx.compose.ui.graphics.Paint
import io.github.alexzhirkevich.compottie.internal.AnimationState
import io.github.alexzhirkevich.compottie.internal.animation.RawProperty
import io.github.alexzhirkevich.compottie.internal.helpers.BooleanIntSerializer
import io.github.alexzhirkevich.compottie.internal.platform.setBlurMaskFilter
import io.github.alexzhirkevich.compottie.internal.utils.getAs
import kotlinx.serialization.Contextual
import kotlinx.serialization.SerialName
Expand All @@ -25,6 +28,18 @@ internal class BlurEffect(
) : LayerEffect() {

val radius get() = values.getAs<EffectValue.Slider>(0)?.value
override fun apply(
paint: Paint,
animationState: AnimationState,
effectState: LayerEffectsState
) {
val radius = radius?.interpolated(animationState)?.takeIf { it > 0f } ?: return

if (paint !== effectState.lastPaint || radius != effectState.blurRadius) {
paint.setBlurMaskFilter(radius)
effectState.blurRadius = radius
}
}

override fun copy(): LayerEffect {
return BlurEffect(values.map(EffectValue<RawProperty<Any>>::copy))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.github.alexzhirkevich.compottie.internal.effects

import androidx.compose.ui.graphics.Paint
import io.github.alexzhirkevich.compottie.internal.AnimationState
import io.github.alexzhirkevich.compottie.internal.animation.RawProperty
import io.github.alexzhirkevich.compottie.internal.helpers.BooleanIntSerializer
import io.github.alexzhirkevich.compottie.internal.utils.getAs
Expand Down Expand Up @@ -29,6 +31,13 @@ internal class DropShadowEffect(
val angle get() = values.getAs<EffectValue.Angle>(2)?.value
val distance get() = values.getAs<EffectValue.Slider>(3)?.value
val blur get() = values.getAs<EffectValue.Slider>(4)?.value
override fun apply(
paint: Paint,
animationState: AnimationState,
effectState: LayerEffectsState
) {
// TODO drop shadow effect
}

override fun copy(): LayerEffect {
return DropShadowEffect(values.map(EffectValue<RawProperty<Any>>::copy))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package io.github.alexzhirkevich.compottie.internal.effects

import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.Paint
import io.github.alexzhirkevich.compottie.internal.AnimationState
import io.github.alexzhirkevich.compottie.internal.animation.RawProperty
import io.github.alexzhirkevich.compottie.internal.helpers.BooleanIntSerializer
import io.github.alexzhirkevich.compottie.internal.utils.getAs
Expand Down Expand Up @@ -27,6 +30,27 @@ internal class FillEffect(
val color get() = values.getAs<EffectValue.Color>(2)?.value

val opacity get() = values.getAs<EffectValue.Slider>(6)?.value
override fun apply(
paint: Paint,
animationState: AnimationState,
effectState: LayerEffectsState
) {
val color = color?.interpolated(animationState)?.let {
it.copy( // don't divide by 100
alpha = it.alpha * (opacity?.interpolated(animationState)?.coerceIn(0f, 1f) ?: 1f)
)
}
if (paint !== effectState.lastPaint || effectState.lastFillColor != color) {

paint.colorFilter = color?.let {
ColorFilter.tint(color)
}
effectState.lastFillFilter = paint.colorFilter
effectState.lastFillColor = color
} else {
paint.colorFilter = effectState.lastFillFilter
}
}

override fun copy(): LayerEffect {
return FillEffect(values.map(EffectValue<RawProperty<Any>>::copy))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.github.alexzhirkevich.compottie.internal.effects

import androidx.compose.ui.graphics.Paint
import io.github.alexzhirkevich.compottie.internal.AnimationState
import io.github.alexzhirkevich.compottie.internal.animation.RawProperty
import kotlinx.serialization.Contextual
import kotlinx.serialization.ExperimentalSerializationApi
Expand All @@ -24,31 +26,33 @@ internal sealed class LayerEffect {
values.associateBy { it.index ?: Int.MIN_VALUE }
}

abstract fun apply(
paint : Paint,
animationState: AnimationState,
effectState: LayerEffectsState
)

abstract fun copy(): LayerEffect

@Serializable
class UnsupportedEffect() : LayerEffect() {
class UnsupportedEffect : LayerEffect() {

// @SerialName("ef")
override val values: List<EffectValue<@Contextual RawProperty<@Contextual Any>>> = emptyList()
override fun apply(
paint: Paint,
animationState: AnimationState,
effectState: LayerEffectsState
) {
}

// @SerialName("nm")
override val name: String? = null

// @SerialName("ix")
override val index: Int? = null

// @SerialName("en")
// @Serializable(with = BooleanIntSerializer::class)
override val enabled: Boolean = true

override fun copy(): LayerEffect {
return UnsupportedEffect(
// values = values.map(EffectValue<RawProperty<*>>::copy),
// name = name,
// index = index,
// enabled = enabled
)
return UnsupportedEffect()
}
}
}
Original file line number Diff line number Diff line change
@@ -1,43 +1,20 @@
package io.github.alexzhirkevich.compottie.internal.effects

import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.ColorMatrix
import androidx.compose.ui.graphics.ColorMatrixColorFilter
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.util.fastForEachReversed
import io.github.alexzhirkevich.compottie.internal.AnimationState
import io.github.alexzhirkevich.compottie.internal.animation.interpolatedNorm
import io.github.alexzhirkevich.compottie.internal.layers.BaseLayer
import io.github.alexzhirkevich.compottie.internal.platform.effects.PlatformDropShadowEffect
import io.github.alexzhirkevich.compottie.internal.platform.effects.applyNativeDropShadowEffect
import io.github.alexzhirkevich.compottie.internal.platform.effects.makeNativeDropShadowEffect
import io.github.alexzhirkevich.compottie.internal.platform.effects.resetEffects
import io.github.alexzhirkevich.compottie.internal.platform.setBlurMaskFilter
import io.github.alexzhirkevich.compottie.internal.utils.degreeToRadians
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.sin

internal class LayerEffectsApplier(
private val layer: BaseLayer
) {
fun applyTo(paint: Paint, animationState: AnimationState, effectState : LayerEffectsState) {

layer.effects.fastForEachReversed {
when (it){
is BlurEffect -> paint.applyBlurEffect(it, animationState, effectState)
is FillEffect -> paint.applyFillEffect(it, animationState, effectState)
is TintEffect -> paint.applyTintEffect(it, animationState, effectState)
is DropShadowEffect -> {
// paint.applyDropShadowEffect(it, animationState, effectState)
}
is LayerEffect.UnsupportedEffect -> {

}
}
it.apply(paint, animationState, effectState)
}

effectState.lastPaint = paint
Expand All @@ -57,107 +34,3 @@ internal class LayerEffectsState {
var tintHash : Int? = null
var tintColorFiter : ColorFilter? = null
}

private fun Paint.applyBlurEffect(
effect: BlurEffect,
animationState: AnimationState,
effectState: LayerEffectsState
) {
val radius = effect.radius?.interpolated(animationState)?.takeIf { it > 0f } ?: return

if (this !== effectState.lastPaint || radius != effectState.blurRadius) {
setBlurMaskFilter(radius)
effectState.blurRadius = radius
}
}

private fun Paint.applyFillEffect(
effect: FillEffect,
animationState: AnimationState,
effectState: LayerEffectsState
) {
val color = effect.color?.interpolated(animationState)?.let {
it.copy( // don't divide by 100
alpha = it.alpha * (effect.opacity?.interpolated(animationState)?.coerceIn(0f, 1f) ?: 1f)
)
}
if (this !== effectState.lastPaint || effectState.lastFillColor != color) {

colorFilter = color?.let {
ColorFilter.tint(color)
}
effectState.lastFillFilter = colorFilter
effectState.lastFillColor = color
} else {
colorFilter = effectState.lastFillFilter
}
}

private fun Paint.applyTintEffect(
effect: TintEffect,
animationState: AnimationState,
effectState: LayerEffectsState
) {
val intensity = effect.intensity?.interpolatedNorm(animationState)
?.coerceIn(0f, 1f) ?: 1f

val black = effect.black?.interpolated(animationState)?.let {
it.copy(alpha = it.alpha * intensity)
} ?: Color.Black
val white = effect.white?.interpolated(animationState)?.let {
it.copy(alpha = it.alpha * intensity)
}

if (black.red != 0f || black.green != 0f || black.blue != 0f)
return //unsupported

val hash = white.hashCode()

if (this === effectState.lastPaint &&
hash == effectState.tintHash &&
effectState.tintColorFiter != null
){
colorFilter = effectState.tintColorFiter
return
}
colorFilter = if (white != null) {
ColorFilter.tint(white, BlendMode.Modulate)
} else null

effectState.tintHash = hash
effectState.tintColorFiter = colorFilter
}

internal fun Paint.applyDropShadowEffect(
effect: DropShadowEffect,
animationState: AnimationState,
effectState: LayerEffectsState,
) {

val directionRad = degreeToRadians(effect.angle?.interpolated(animationState) ?: 0f)

val distance = effect.distance?.interpolated(animationState) ?: 0f
val x = (sin(directionRad)) * distance
val y = (cos(directionRad + PI).toFloat()) * distance

val baseColor = effect.color?.interpolated(animationState) ?: Color.Black

val opacity = effect.opacity?.interpolated(animationState)?.div(100)?.coerceIn(0f, 1f) ?: 1f

val color = baseColor.copy(
opacity * baseColor.alpha,
baseColor.red,
baseColor.green,
baseColor.blue
)
val radius: Float = effect.blur?.interpolated(animationState) ?: 0f

val hash = arrayOf(x, y, color, radius).contentHashCode()

if (effectState.lastPaint !== this || hash != effectState.dropShadowHash || effectState.dropShadowEffect == null) {
effectState.dropShadowEffect = makeNativeDropShadowEffect(radius, x, y, color)
effectState.dropShadowHash = hash
}

applyNativeDropShadowEffect(effectState.dropShadowEffect!!)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
package io.github.alexzhirkevich.compottie.internal.effects

import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.Paint
import io.github.alexzhirkevich.compottie.internal.AnimationState
import io.github.alexzhirkevich.compottie.internal.animation.RawProperty
import io.github.alexzhirkevich.compottie.internal.animation.interpolatedNorm
import io.github.alexzhirkevich.compottie.internal.helpers.BooleanIntSerializer
import io.github.alexzhirkevich.compottie.internal.utils.getAs
import kotlinx.serialization.Contextual
Expand Down Expand Up @@ -34,6 +40,41 @@ internal class TintEffect(
val intensity
get() = values.getAs<EffectValue.Slider>(2)?.value

override fun apply(
paint: Paint,
animationState: AnimationState,
effectState: LayerEffectsState
) {
val intensity = intensity?.interpolatedNorm(animationState)
?.coerceIn(0f, 1f) ?: 1f

val black = black?.interpolated(animationState)?.let {
it.copy(alpha = it.alpha * intensity)
} ?: Color.Black
val white = white?.interpolated(animationState)?.let {
it.copy(alpha = it.alpha * intensity)
}

if (black.red != 0f || black.green != 0f || black.blue != 0f)
return //unsupported

val hash = white.hashCode()

if (paint === effectState.lastPaint &&
hash == effectState.tintHash &&
effectState.tintColorFiter != null
){
paint.colorFilter = effectState.tintColorFiter
return
}
paint.colorFilter = if (white != null) {
ColorFilter.tint(white, BlendMode.Modulate)
} else null

effectState.tintHash = hash
effectState.tintColorFiter = paint.colorFilter
}

override fun copy(): LayerEffect {
return TintEffect(values.map(EffectValue<RawProperty<*>>::copy))
}
Expand Down

0 comments on commit dce4c50

Please sign in to comment.