diff --git a/orx-color/src/commonMain/kotlin/tools/ColorRGBaExtensions.kt b/orx-color/src/commonMain/kotlin/tools/ColorRGBaExtensions.kt new file mode 100644 index 00000000..364a3e52 --- /dev/null +++ b/orx-color/src/commonMain/kotlin/tools/ColorRGBaExtensions.kt @@ -0,0 +1,117 @@ +package org.openrndr.extra.color.tools + +import org.openrndr.color.* +import org.openrndr.extra.color.spaces.* + +inline fun > ColorRGBa.blendWith(other: ColorRGBa, steps: Int): Sequence { + return sequence { + for (step in 0 until steps) { + yield(mixedWith(other, step / (steps - 1.0))) + } + } +} + +inline fun > ColorRGBa.mixedWith(other: ColorRGBa, factor: Double): ColorRGBa { + + val mixed = when (T::class) { + ColorHSLa::class -> this.toHSLa().mix(other.toHSLa(), factor) + ColorHSVa::class -> this.toHSVa().mix(other.toHSVa(), factor) + ColorRGBa::class -> this.mix(other, factor) + + ColorHSLUVa::class -> this.toHSLUVa().mix(other.toHSLUVa(), factor) + ColorOKLABa::class -> this.toOKLABa().mix(other.toOKLABa(), factor) + ColorOKLCHa::class -> this.toOKLCHa().mix(other.toOKLCHa(), factor) + ColorOKHSLa::class -> this.toOKHSLa().mix(other.toOKHSLa(), factor) + ColorOKHSVa::class -> this.toOKHSVa().mix(other.toOKHSVa(), factor) + + ColorLABa::class -> this.toLABa().mix(other.toLABa(), factor) + ColorLUVa::class -> this.toLUVa().mix(other.toLUVa(), factor) + ColorLCHABa::class -> this.toLCHABa().mix(other.toLCHABa(), factor) + ColorLCHUVa::class -> this.toLCHUVa().mix(other.toLCHUVa(), factor) + ColorOKHSLa::class -> this.toOKHSLa().mix(other.toOKHSLa(), factor) + ColorXYZa::class -> this.toXYZa().mix(other.toXYZa(), factor) + ColorXSLUVa::class -> this.toXSLUVa().mix(other.toXSLUVa(), factor) + ColorXSVa::class -> this.toXSVa().mix(other.toXSVa(), factor) + ColorXSLa::class -> this.toXSLa().mix(other.toXSLa(), factor) + else -> error("color model ${T::class} not supported") + }.toRGBa() + + return if (mixed.linearity.isEquivalent(linearity)) { + mixed + } else { + if (linearity.isEquivalent(Linearity.LINEAR)) { + mixed.toLinear() + } else if (linearity.isEquivalent(Linearity.SRGB)) { + mixed.toSRGB() + } else { + mixed + } + } + + return this +} + +inline fun ColorRGBa.saturate(factor: Double): ColorRGBa + where T : SaturatableColor, + T : ConvertibleToColorRGBa { + + val converted = when (T::class) { + ColorHPLUVa::class -> toHPLUVa() + ColorHSLUVa::class -> toHSLUVa() + ColorHSLa::class -> toHSLa() + ColorHSVa::class -> toHSVa() + ColorXSLa::class -> toXSLa() + ColorXSVa::class -> toXSVa() + ColorOKLCHa::class -> toOKLCHa() + ColorOKHSLa::class -> toOKHSLa() + ColorOKHSVa::class -> toOKHSVa() + ColorXSLUVa::class -> toXSLUVa() + ColorOKLCHa::class -> toOKLCHa() + else -> error("Color space ${T::class} not supported") + } + val saturated = (converted.saturate(factor) as ConvertibleToColorRGBa).toRGBa() + + return if (saturated.linearity.isEquivalent(linearity)) { + saturated + } else { + if (linearity.isEquivalent(Linearity.LINEAR)) { + saturated.toLinear() + } else if (linearity.isEquivalent(Linearity.SRGB)) { + saturated.toSRGB() + } else { + saturated + } + } +} + +inline fun ColorRGBa.shiftHue(degrees: Double): ColorRGBa where + T : HueShiftableColor, T : ConvertibleToColorRGBa { + val converted = when (T::class) { + ColorHSLa::class -> toHSLa() + ColorHSVa::class -> toHSVa() + ColorXSLa::class -> toXSLa() + ColorXSVa::class -> toXSVa() + ColorOKLCHa::class -> toOKLCHa() + ColorLCHABa::class -> toLCHABa() + ColorLCHUVa::class -> toLCHABa() + ColorOKHSLa::class -> toOKHSLa() + ColorOKHSVa::class -> toOKHSVa() + ColorHPLUVa::class -> toHPLUVa() + ColorHSLUVa::class -> toHSLUVa() + ColorXSLUVa::class -> toXSLUVa() + else -> error("Color space ${T::class} not supported") + } + val shifted = (converted.shiftHue(degrees) as ConvertibleToColorRGBa).toRGBa() + return if (shifted.linearity.isEquivalent(linearity)) { + shifted + } else { + if (linearity.isEquivalent(Linearity.LINEAR)) { + shifted.toLinear() + } else if (linearity.isEquivalent(Linearity.SRGB)) { + shifted.toSRGB() + } else { + shifted + } + } +} + diff --git a/orx-color/src/commonTest/kotlin/tools/TestColorRGBaExtensions.kt b/orx-color/src/commonTest/kotlin/tools/TestColorRGBaExtensions.kt new file mode 100644 index 00000000..ebe5cd1e --- /dev/null +++ b/orx-color/src/commonTest/kotlin/tools/TestColorRGBaExtensions.kt @@ -0,0 +1,79 @@ +package tools + +import org.openrndr.color.* +import org.openrndr.extra.color.spaces.* +import org.openrndr.extra.color.tools.mixedWith +import org.openrndr.extra.color.tools.saturate +import org.openrndr.extra.color.tools.shiftHue +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class TestColorRGBaExtensions { + @Test + fun testShiftHue0() { + val seed = ColorRGBa.RED.shade(0.5) + val shifted = listOf( + seed.shiftHue(0.0), + seed.shiftHue(0.0), + seed.shiftHue(0.0), + seed.shiftHue(0.0) + ) + for (s in shifted) { + assertEquals(Linearity.SRGB, s.linearity) + assertTrue(seed.toVector4().distanceTo(s.toVector4()) < 1E-4) + } + } + + @Test + fun testShiftHue0Linear() { + val seed = ColorRGBa.RED.shade(0.5).toLinear() + val shifted = listOf( + seed.shiftHue(0.0), + seed.shiftHue(0.0), + seed.shiftHue(0.0), + seed.shiftHue(0.0) + ) + for (s in shifted) { + assertEquals(Linearity.LINEAR, s.linearity) + assertTrue(seed.toVector4().distanceTo(s.toVector4()) < 1E-4) + } + } + + @Test + fun testSaturate1() { + val seed = ColorRGBa.RED.shade(0.5) + val shifted = listOf( + seed.saturate(1.0), + seed.saturate(1.0) + ) + for (s in shifted) { + assertEquals(Linearity.SRGB, s.linearity) + assertTrue(seed.toVector4().distanceTo(s.toVector4()) < 1E-4) + } + } + + @Test + fun testSaturate1Linear() { + val seed = ColorRGBa.RED.shade(0.5).toLinear() + val shifted = listOf( + seed.saturate(1.0), + seed.saturate(1.0) + ) + for (s in shifted) { + assertEquals(Linearity.LINEAR, s.linearity) + assertTrue(seed.toVector4().distanceTo(s.toVector4()) < 1E-4) + } + } + + @Test + fun testMixedWith() { + val seed = ColorRGBa.RED + val mixed = listOf( + seed.mixedWith(ColorRGBa.BLUE, 0.5), + seed.mixedWith(ColorRGBa.BLUE, 0.5), + seed.mixedWith(ColorRGBa.BLUE, 0.5) + ) + } + +} \ No newline at end of file