Skip to content

Commit

Permalink
Normalize hue angles instead of clamping (#60)
Browse files Browse the repository at this point in the history
  • Loading branch information
ajalt authored Apr 17, 2024
1 parent 1ba9b1b commit 0b478a8
Show file tree
Hide file tree
Showing 12 changed files with 101 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,10 @@ interface Color {
val info = space.components[i]
if (values[i] !in info.min..info.max) {
clamped = true
values[i] = values[i].coerceIn(info.min, info.max)
values[i] = when {
info.isPolar -> values[i] % 360
else -> values[i].coerceIn(info.min, info.max)
}
}
}
return if (clamped) space.create(values) else this
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ internal fun polarComponentInfo(
name = it.toString(),
isPolar = it == 'H',
min = if (it == 'H') 0f else l,
max = if (it == 'H') 1f else r
max = if (it == 'H') 360f else r
)
}
add(alphaInfo)
Expand All @@ -88,9 +88,9 @@ internal inline fun <T : Color> T.clamp3(
): T {
val (c1, c2, c3) = space.components
return when {
v1 >= c1.min && v1 <= c1.max
&& v2 >= c2.min && v2 <= c2.max
&& v3 >= c3.min && v3 <= c3.max
v1 in c1.min..c1.max
&& v2 in c2.min..c2.max
&& v3 in c3.min..c3.max
&& alpha in 0f..1f -> this

else -> copy(
Expand All @@ -101,3 +101,49 @@ internal inline fun <T : Color> T.clamp3(
)
}
}

internal inline fun <T : Color> T.clampLeadingHue(
v1: Float,
v2: Float,
v3: Float,
alpha: Float,
copy: (v1: Float, v2: Float, v3: Float, alpha: Float) -> T,
): T {
val (c1, c2, c3) = space.components
return when {
v1 in c1.min..c1.max
&& v2 in c2.min..c2.max
&& v3 in c3.min..c3.max
&& alpha in 0f..1f -> this

else -> copy(
v1 % 360,
v2.coerceIn(c2.min, c2.max),
v3.coerceIn(c3.min, c3.max),
alpha.coerceIn(0f, 1f)
)
}
}

internal inline fun <T : Color> T.clampTrailingHue(
v1: Float,
v2: Float,
v3: Float,
alpha: Float,
copy: (v1: Float, v2: Float, v3: Float, alpha: Float) -> T,
): T {
val (c1, c2, c3) = space.components
return when {
v1 in c1.min..c1.max
&& v2 in c2.min..c2.max
&& v3 in c3.min..c3.max
&& alpha in 0f..1f -> this

else -> copy(
v1.coerceIn(c1.min, c1.max),
v2.coerceIn(c2.min, c2.max),
v3 % 360,
alpha.coerceIn(0f, 1f)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import com.github.ajalt.colormath.Color
import com.github.ajalt.colormath.ColorComponentInfo
import com.github.ajalt.colormath.ColorSpace
import com.github.ajalt.colormath.HueColor
import com.github.ajalt.colormath.internal.clamp3
import com.github.ajalt.colormath.internal.clampLeadingHue
import com.github.ajalt.colormath.internal.doCreate
import com.github.ajalt.colormath.internal.polarComponentInfo

Expand Down Expand Up @@ -52,5 +52,5 @@ data class HPLuv(
override fun toXYZ(): XYZ = toLCHuv().toXYZ()
override fun toHPLuv(): HPLuv = this
override fun toArray(): FloatArray = floatArrayOf(h, p, l, alpha)
override fun clamp(): HPLuv = clamp3(h, p, l, alpha, ::copy)
override fun clamp(): HPLuv = clampLeadingHue(h, p, l, alpha, ::copy)
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.github.ajalt.colormath.model

import com.github.ajalt.colormath.*
import com.github.ajalt.colormath.internal.clamp3
import com.github.ajalt.colormath.internal.clampLeadingHue
import com.github.ajalt.colormath.internal.doCreate
import com.github.ajalt.colormath.internal.normalizeDeg
import com.github.ajalt.colormath.internal.polarComponentInfo
Expand Down Expand Up @@ -66,5 +66,5 @@ data class HSL(override val h: Float, val s: Float, val l: Float, override val a

override fun toHSL() = this
override fun toArray(): FloatArray = floatArrayOf(h, s, l, alpha)
override fun clamp(): HSL = clamp3(h, s, l, alpha, ::copy)
override fun clamp(): HSL = clampLeadingHue(h, s, l, alpha, ::copy)
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ data class HSLuv(
override fun toXYZ(): XYZ = toLCHuv().toXYZ()
override fun toHSLuv(): HSLuv = this
override fun toArray(): FloatArray = floatArrayOf(h, s, l, alpha)
override fun clamp(): HSLuv = clamp3(h, s, l, alpha, ::copy)
override fun clamp(): HSLuv = clampLeadingHue(h, s, l, alpha, ::copy)
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import com.github.ajalt.colormath.Color
import com.github.ajalt.colormath.ColorComponentInfo
import com.github.ajalt.colormath.ColorSpace
import com.github.ajalt.colormath.HueColor
import com.github.ajalt.colormath.internal.clamp3
import com.github.ajalt.colormath.internal.clampLeadingHue
import com.github.ajalt.colormath.internal.doCreate
import com.github.ajalt.colormath.internal.normalizeDeg
import com.github.ajalt.colormath.internal.polarComponentInfo
Expand Down Expand Up @@ -59,5 +59,5 @@ data class HSV(override val h: Float, val s: Float, val v: Float, override val a

override fun toHSV() = this
override fun toArray(): FloatArray = floatArrayOf(h, s, v, alpha)
override fun clamp(): HSV = clamp3(h, s, v, alpha, ::copy)
override fun clamp(): HSV = clampLeadingHue(h, s, v, alpha, ::copy)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ import com.github.ajalt.colormath.Color
import com.github.ajalt.colormath.ColorComponentInfo
import com.github.ajalt.colormath.ColorSpace
import com.github.ajalt.colormath.HueColor
import com.github.ajalt.colormath.internal.clamp3
import com.github.ajalt.colormath.internal.clampLeadingHue
import com.github.ajalt.colormath.internal.doCreate
import com.github.ajalt.colormath.internal.polarComponentInfo
import kotlin.math.roundToInt

/**
* A color model represented with Hue, Whiteness, and Blackness.
Expand Down Expand Up @@ -79,5 +78,5 @@ data class HWB(override val h: Float, val w: Float, val b: Float, override val a

override fun toHWB(): HWB = this
override fun toArray(): FloatArray = floatArrayOf(h, w, b, alpha)
override fun clamp(): HWB = clamp3(h, w, b, alpha, ::copy)
override fun clamp(): HWB = clampLeadingHue(h, w, b, alpha, ::copy)
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,5 +78,5 @@ data class LCHab internal constructor(

override fun toLCHab(): LCHab = this
override fun toArray(): FloatArray = floatArrayOf(l, c, h, alpha)
override fun clamp(): LCHab = clamp3(l, c, h, alpha, ::copy)
override fun clamp(): LCHab = clampTrailingHue(l, c, h, alpha, ::copy)
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,5 +88,5 @@ data class LCHuv internal constructor(

override fun toLCHuv(): LCHuv = this
override fun toArray(): FloatArray = floatArrayOf(l, c, h, alpha)
override fun clamp(): LCHuv = clamp3(l, c, h, alpha, ::copy)
override fun clamp(): LCHuv = clampTrailingHue(l, c, h, alpha, ::copy)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import com.github.ajalt.colormath.Color
import com.github.ajalt.colormath.ColorComponentInfo
import com.github.ajalt.colormath.ColorSpace
import com.github.ajalt.colormath.HueColor
import com.github.ajalt.colormath.internal.clamp3
import com.github.ajalt.colormath.internal.*
import com.github.ajalt.colormath.internal.componentInfoList
import com.github.ajalt.colormath.internal.doCreate
import com.github.ajalt.colormath.internal.fromPolarModel
Expand Down Expand Up @@ -45,5 +45,5 @@ data class Oklch(
override fun toOklab(): Oklab = fromPolarModel(c, h) { a, b -> Oklab(l, a, b, alpha) }
override fun toOklch(): Oklch = this
override fun toArray(): FloatArray = floatArrayOf(l, c, h, alpha)
override fun clamp(): Oklch = clamp3(l, c, h, alpha, ::copy)
override fun clamp(): Oklch = clampTrailingHue(l, c, h, alpha, ::copy)
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package com.github.ajalt.colormath.model

import com.github.ajalt.colormath.roundtripTest
import com.github.ajalt.colormath.shouldEqualColor
import com.github.ajalt.colormath.testColorConversions
import io.kotest.data.blocking.forAll
import io.kotest.data.row
import io.kotest.matchers.types.shouldBeSameInstanceAs
import kotlin.js.JsName
import kotlin.test.Test

Expand All @@ -26,4 +30,18 @@ class HSLTest {
HSL(144.00, 0.50, 0.60) to HSV(144.0, 0.5, 0.8),
HSL(0.00, 0.00, 1.00) to HSV(0.0, 0.0, 1.0),
)

@Test
fun clamp() {
forAll(
row(HSL(0.0, 0.0, 0.0), HSL(0.0, 0.0, 0.0)),
row(HSL(359, 1.0, 1.0), HSL(359, 1.0, 1.0)),
row(HSL(361, 1.0, 1.0), HSL(1, 1.0, 1.0)),
row(HSL(180, 2, 2), HSL(180, 1.0, 1.0)),
) { hsl, ex ->
hsl.clamp().shouldEqualColor(ex)
}
val hsl = HSL(359, .9, .9, .9)
hsl.clamp().shouldBeSameInstanceAs(hsl)
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package com.github.ajalt.colormath.model

import com.github.ajalt.colormath.roundtripTest
import com.github.ajalt.colormath.shouldEqualColor
import com.github.ajalt.colormath.testColorConversions
import io.kotest.data.blocking.forAll
import io.kotest.data.row
import io.kotest.matchers.types.shouldBeSameInstanceAs
import kotlin.js.JsName
import kotlin.test.Test

Expand All @@ -18,4 +22,16 @@ class OklchTest {
Oklab(0.25, 0.5, 0.75) to Oklch(0.25, 0.90138782, 56.30993247),
Oklab(1.0, 1.0, 1.0) to Oklch(1.0, 1.41421356, 45.0),
)

@Test
fun clamp() {
forAll(
row(Oklch(0.0, 0.0, 0.0), Oklch(0.0, 0.0, 0.0)),
row(Oklch(-1, -1, 361, 3), Oklch(0.0, 0.0, 1)),
) { color, ex ->
color.clamp().shouldEqualColor(ex)
}
val oklch = Oklch(.9, .2, 359, .9)
oklch.clamp().shouldBeSameInstanceAs(oklch)
}
}

0 comments on commit 0b478a8

Please sign in to comment.