Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Normalize hue angles instead of clamping #60

Merged
merged 1 commit into from
Apr 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
}
}
Loading