Skip to content

Commit

Permalink
refactor (COLOR) use percent on alpha and luminance
Browse files Browse the repository at this point in the history
change objects, add some Int-factories, push the change to the modules
that use color (mainly interpolate)
  • Loading branch information
pmariac authored and gzoritchak committed Nov 20, 2018
1 parent f0e1de5 commit 1c61151
Show file tree
Hide file tree
Showing 23 changed files with 311 additions and 260 deletions.
Expand Up @@ -2,7 +2,7 @@ package io.data2viz.color

import io.data2viz.math.*

data class ColorStop(val percent:Double, val color: Color)
data class ColorStop(val percent:Percent, val color: Color)

interface ColorOrGradient

Expand All @@ -25,7 +25,7 @@ interface Color : ColorOrGradient {
val r:Int
val g:Int
val b:Int
val alpha:Double
val alpha:Percent
val rgbHex:String

/**
Expand Down Expand Up @@ -57,7 +57,7 @@ interface Color : ColorOrGradient {
fun darken(strength: Double = 1.0):Color
fun saturate(strength: Double = 1.0):Color
fun desaturate(strength: Double = 1.0):Color
fun withAlpha(alpha: Double):Color
fun withAlpha(alpha: Percent):Color

/**
* Change the perceived lightness of the color and return a new Color.
Expand Down
Expand Up @@ -28,7 +28,7 @@ internal fun RgbColor.toLaba(): LabColor {
val x = xyz2lab((0.4124564f * labB + 0.3575761f * labA + 0.1804375f * labL) / Xn)
val y = xyz2lab((0.2126729f * labB + 0.7151522f * labA + 0.0721750f * labL) / Yn)
val z = xyz2lab((0.0193339f * labB + 0.1191920f * labA + 0.9503041f * labL) / Zn)
return Colors.lab(116.0 * y - 16, 500.0 * (x - y), 200.0 * (y - z), alpha)
return Colors.lab((116.0 * y - 16).pct, 500.0 * (x - y), 200.0 * (y - z), alpha)
}

internal fun RgbColor.toHsla(): HslColor {
Expand All @@ -53,12 +53,12 @@ internal fun RgbColor.toHsla(): HslColor {
} else {
s = if (l > 0 && l < 1) .0 else h
}
return Colors.hsl(h.deg, s, l, alpha)
return Colors.hsl(h.deg, Percent(s), Percent(l), alpha)
}

internal fun LabColor.toRgba(): RgbColor {
// map CIE LAB to CIE XYZ
var y = (labL + 16) / 116f
var y = ((labL.value * 100.0) + 16) / 116f
var x = y + (labA / 500f)
var z = y - (labB / 200f)
y = Yn * lab2xyz(y)
Expand All @@ -78,18 +78,18 @@ internal fun LabColor.toRgba(): RgbColor {
internal fun HslColor.toRgba(): RgbColor =
if (isAchromatic()) // achromatic
Colors.rgb(
(l * 255).roundToInt(),
(l * 255).roundToInt(),
(l * 255).roundToInt(),
(l.value * 255).roundToInt(),
(l.value * 255).roundToInt(),
(l.value * 255).roundToInt(),
alpha
)
else {
val q = if (l < 0.5f) l * (1 + s) else l + s - l * s
val p = 2 * l - q
val q = if (l < 50.pct) l * (100.pct + s) else l + s - l * s
val p = Percent(2 * (l - q).value)
Colors.rgb(
(hue2rgb(p, q, h + angle120deg) * 255).roundToInt(),
(hue2rgb(p, q, h) * 255).roundToInt(),
(hue2rgb(p, q, h - angle120deg) * 255).roundToInt(),
(hue2rgb(p.value, q.value, h + angle120deg) * 255).roundToInt(),
(hue2rgb(p.value, q.value, h) * 255).roundToInt(),
(hue2rgb(p.value, q.value, h - angle120deg) * 255).roundToInt(),
alpha
)
}
Expand Down
23 changes: 17 additions & 6 deletions color/d2v-color-common/src/main/kotlin/io/data2viz/color/Colors.kt
Expand Up @@ -4,6 +4,8 @@ package io.data2viz.color

import io.data2viz.geom.Point
import io.data2viz.math.Angle
import io.data2viz.math.Percent
import io.data2viz.math.pct


// TODO : move to common and remove expect when available
Expand All @@ -18,27 +20,36 @@ object Colors {
* Instantiate a color using its rgb Int value and alpha. Should be used with hexadecimal literal.
* @sample: `Colors.rgb(0x0b0b0b, .5)`
*/
fun rgb(rgb:Int, alpha: Double = 1.0): RgbColor = RgbColor(rgb, alpha)
fun rgb(rgb:Int, alpha: Percent = 100.pct): RgbColor = RgbColor(rgb, alpha)

/**
* Instantiate a color using its r,g,b Int values and alpha
* @sample: `Colors.rgb(128, 128, 128, .5)`
*/
fun rgb(red: Int, green: Int, blue: Int, alpha: Double = 1.0): RgbColor {
fun rgb(red: Int, green: Int, blue: Int, alpha: Percent = 100.pct): RgbColor {
val rgb = (red.coerceIn(0, 255) shl 16) + (green.coerceIn(0, 255) shl 8) + blue.coerceIn(0, 255)
return rgb(rgb, alpha)
}

fun lab(lightness: Double, aComponent: Double, bComponent: Double, alpha: Double = 1.0) =
fun lab(lightness: Percent, aComponent: Double, bComponent: Double, alpha: Percent = 100.pct) =
LabColor(lightness, aComponent, bComponent, alpha)

fun hsl(hue: Angle, saturation: Double, lightness: Double, alpha: Double = 1.0) =
fun lab(lightness: Percent, aComponent: Int, bComponent: Int, alpha: Percent = 100.pct) =
LabColor(lightness, aComponent.toDouble(), bComponent.toDouble(), alpha)

fun hsl(hue: Angle, saturation: Percent, lightness: Percent, alpha: Percent = 100.pct) =
HslColor(hue, saturation, lightness, alpha)

fun hcl(hue: Angle, chroma: Double, luminance: Double, alpha: Double = 1.0) =
fun hcl(hue: Angle, chroma: Double, luminance: Percent, alpha: Percent = 100.pct) =
HclColor(hue, chroma, luminance, alpha)

fun lch(luminance: Double, chroma: Double, hue: Angle, alpha: Double = 1.0) = hcl(hue, chroma, luminance, alpha)
fun hcl(hue: Angle, chroma: Int, luminance: Percent, alpha: Percent = 100.pct) =
HclColor(hue, chroma.toDouble(), luminance, alpha)

fun lch(luminance: Percent, chroma: Double, hue: Angle, alpha: Percent = 100.pct) =
hcl(hue, chroma, luminance, alpha)

fun lch(luminance: Percent, chroma: Int, hue: Angle, alpha: Percent = 100.pct) = hcl(hue, chroma, luminance, alpha)


/***************************** GRADIENTS *********************************************************/
Expand Down
Expand Up @@ -18,10 +18,10 @@ import kotlin.math.max
class HclColor

@Deprecated("Deprecated", ReplaceWith("Colors.hcl(h,c,l,alpha)", "io.data2viz.colors.Colors"))
internal constructor(val h: Angle, val c: Double, lightness: Double, a: Double = 1.0) : Color {
internal constructor(val h: Angle, val c: Double, lightness: Percent, a: Percent = 100.pct) : Color {

val l = lightness//.coerceIn(.0, 100.0)
override val alpha = a.coerceIn(.0, 1.0)
override val alpha = a.coerceToDefault()

override val rgb = toRgb().rgb
override val rgba = toRgb().rgba
Expand All @@ -38,14 +38,14 @@ internal constructor(val h: Angle, val c: Double, lightness: Double, a: Double =
override fun toHcl(): HclColor = this
override fun toHsl(): HslColor = toLab().toHsl()

override fun brighten(strength: Double): Color = Colors.hcl(h, c, (l + (Kn * strength)), alpha)
override fun darken(strength: Double): Color = Colors.hcl(h, c, (l - (Kn * strength)), alpha)
override fun brighten(strength: Double): Color = Colors.hcl(h, c, (l + (Kn * strength).pct), alpha)
override fun darken(strength: Double): Color = Colors.hcl(h, c, (l - (Kn * strength).pct), alpha)
override fun saturate(strength: Double): Color = Colors.hcl(h, max(.0, (c + (Kn * strength))), l, alpha)
override fun desaturate(strength: Double): Color = Colors.hcl(h, max(.0, (c - (Kn * strength))), l, alpha)
override fun withAlpha(alpha: Double) = Colors.hcl(h, c, l, alpha)
override fun withAlpha(alpha: Percent) = Colors.hcl(h, c, l, alpha)
override fun withHue(hue: Angle) = Colors.hcl(hue, c, l, alpha)

fun isAchromatic() = (c == .0) || (l <= .0) || (l >= 100.0)
fun isAchromatic() = (c == .0) || (l.value <= .0) || (l.value >= 1.0)

override fun equals(other: Any?): Boolean {
if (this === other) return true
Expand Down
Expand Up @@ -12,12 +12,12 @@ import io.data2viz.math.*
*/
class HslColor
@Deprecated("Deprecated", ReplaceWith("Colors.hsl(hue,saturation,luminance,a)", "io.data2viz.colors.Colors"))
internal constructor(hue: Angle, saturation: Double, lightness: Double, a: Double = 1.0) : Color {
internal constructor(hue: Angle, saturation: Percent, lightness: Percent, a: Percent = 100.pct) : Color {

val h = hue.normalize()
val s = saturation.coerceIn(.0, 1.0)
val l = lightness.coerceIn(.0, 1.0)
override val alpha = a.coerceIn(.0, 1.0)
val s = saturation.coerceToDefault()
val l = lightness.coerceToDefault()
override val alpha = a.coerceToDefault()

override val rgb = toRgb().rgb
override val rgba = toRgb().rgba
Expand All @@ -38,10 +38,10 @@ internal constructor(hue: Angle, saturation: Double, lightness: Double, a: Doubl
override fun darken(strength: Double): Color = toRgb().darken(strength)
override fun saturate(strength: Double): Color = toRgb().saturate(strength)
override fun desaturate(strength: Double): Color = toRgb().desaturate(strength)
override fun withAlpha(alpha: Double) = Colors.hsl(h, s, l, alpha)
override fun withAlpha(alpha: Percent) = Colors.hsl(h, s, l, alpha)
override fun withHue(hue: Angle) = toHcl().withHue(hue)

fun isAchromatic() = (s == .0) || (l <= .0) || (l >= 1.0)
fun isAchromatic() = (s.value == .0) || (l.value <= .0) || (l.value >= 1.0)

override fun equals(other: Any?): Boolean {
if (this === other) return true
Expand All @@ -59,5 +59,5 @@ internal constructor(hue: Angle, saturation: Double, lightness: Double, a: Doubl
return result
}

override fun toString() = "HSL(${h.deg}°, ${s*100}%, ${l*100}%, alpha=$alpha)"
override fun toString() = "HSL(${h.deg}°, $s, $l, alpha=$alpha)"
}
Expand Up @@ -12,13 +12,13 @@ import io.data2viz.math.*
*/
class LabColor
@Deprecated("Deprecated", ReplaceWith("Colors.lab(labL,labA,labB,alpha)", "io.data2viz.colors.Colors"))
internal constructor(lightness: Double, aComponent: Double, bComponent: Double, a: Double = 1.0) : Color {
internal constructor(lightness: Percent, aComponent: Double, bComponent: Double, a: Percent = 100.pct) : Color {

// TODO : need to choose behavior: if values are coerced the results are not what expected when referring to test values from chroma.js
val labL = lightness//.coerceIn(.0, 100.0)
val labA = aComponent//.coerceIn(-128.0, 128.0)
val labB = bComponent//.coerceIn(-128.0, 128.0)
override val alpha = a.coerceIn(.0, 1.0)
override val alpha = a.coerceToDefault()

override val rgb = toRgb().rgb
override val rgba = toRgb().rgba
Expand All @@ -35,11 +35,11 @@ internal constructor(lightness: Double, aComponent: Double, bComponent: Double,
override fun toHcl(): HclColor = toHcla()
override fun toHsl(): HslColor = toRgb().toHsl()

override fun brighten(strength: Double): Color = Colors.lab((labL + (Kn * strength)), labA, labB, alpha)
override fun darken(strength: Double): Color = Colors.lab((labL - (Kn * strength)), labA, labB, alpha)
override fun brighten(strength: Double): Color = Colors.lab((labL + (Kn * strength).pct), labA, labB, alpha)
override fun darken(strength: Double): Color = Colors.lab((labL - (Kn * strength).pct), labA, labB, alpha)
override fun saturate(strength: Double): Color = toHcl().saturate(strength)
override fun desaturate(strength: Double): Color = toHcl().desaturate(strength)
override fun withAlpha(alpha: Double) = Colors.lab(labL, labA, labB, alpha)
override fun withAlpha(alpha: Percent) = Colors.lab(labL, labA, labB, alpha)
override fun withHue(hue: Angle) = toHcl().withHue(hue)

override fun equals(other: Any?): Boolean {
Expand Down
Expand Up @@ -3,6 +3,8 @@
package io.data2viz.color

import io.data2viz.geom.Point
import io.data2viz.math.Percent
import io.data2viz.math.pct

// TODO : move to "core.geom" ?
// TODO : remove access to x1, y1, x2, y2
Expand All @@ -29,13 +31,13 @@ interface HasStartAndEnd {

data class LinearGradientFirstColorBuilder
internal constructor(val start: Point, val end: Point) {
fun withColor(startColor: Color, percent: Double = .0): LinearGradientSecondColorBuilder =
fun withColor(startColor: Color, percent: Percent = 0.pct): LinearGradientSecondColorBuilder =
LinearGradientSecondColorBuilder(this, ColorStop(percent, startColor))
}

data class LinearGradientSecondColorBuilder
internal constructor(val builder: LinearGradientFirstColorBuilder, val firstColor: ColorStop) {
fun andColor(color: Color, percent: Double = 1.0): LinearGradient = LinearGradient()
fun andColor(color: Color, percent: Percent = 100.pct): LinearGradient = LinearGradient()
.apply {
x1 = builder.start.x
y1 = builder.start.y
Expand All @@ -59,8 +61,8 @@ internal constructor() : Gradient, HasStartAndEnd {
override val colorStops: List<ColorStop>
get() = colors.toList()

fun andColor(color: Color, percent: Double): LinearGradient {
colors.add(ColorStop(percent.coerceIn(.0, 1.0), color))
fun andColor(color: Color, percent: Percent): LinearGradient {
colors.add(ColorStop(percent.coerceToDefault(), color))
return this
}
}
Expand Up @@ -3,6 +3,8 @@
package io.data2viz.color

import io.data2viz.geom.Point
import io.data2viz.math.Percent
import io.data2viz.math.pct

// TODO : move to "core.geom" ?
// TODO : remove access to cx, cy, leave only access to center
Expand All @@ -20,13 +22,13 @@ interface HasCenter {

data class RadialGradientFirstColorBuilder
internal constructor(val center: Point, val radius: Double) {
fun withColor(startColor: Color, percent: Double = .0): RadialGradientSecondColorBuilder =
fun withColor(startColor: Color, percent: Percent = 0.pct): RadialGradientSecondColorBuilder =
RadialGradientSecondColorBuilder(this, ColorStop(percent, startColor))
}

data class RadialGradientSecondColorBuilder
internal constructor(val builder: RadialGradientFirstColorBuilder, val firstColor: ColorStop) {
fun andColor(color: Color, percent: Double = 1.0): RadialGradient = RadialGradient()
fun andColor(color: Color, percent: Percent = 100.pct): RadialGradient = RadialGradient()
.apply {
cx = builder.center.x
cy = builder.center.y
Expand All @@ -49,8 +51,8 @@ constructor(): Gradient, HasCenter {
override val colorStops: List<ColorStop>
get() = colors.toList()

fun andColor(color: Color, percent: Double): RadialGradient {
colors.add(ColorStop(percent.coerceIn(.0, 1.0), color))
fun andColor(color: Color, percent: Percent): RadialGradient {
colors.add(ColorStop(percent.coerceToDefault(), color))
return this
}
}
@@ -1,6 +1,8 @@
package io.data2viz.color

import io.data2viz.math.Angle
import io.data2viz.math.Percent
import io.data2viz.math.pct

/**
* Implementation of Color as an rgb integer and an alpha channel.
Expand All @@ -12,9 +14,9 @@ import io.data2viz.math.Angle
*/
class RgbColor
@Deprecated("Use factory function or Int.col extension.", ReplaceWith("Colors.rgb(rgb,a)", "io.data2viz.colors.Colors"))
constructor(override val rgb: Int, a: Double = 1.0) : Color {
constructor(override val rgb: Int, a: Percent = 100.pct) : Color {

override val alpha = a.coerceIn(.0, 1.0)
override val alpha = a.coerceToDefault()

override val r: Int
get() = (rgb shr 16) and 0xff
Expand Down Expand Up @@ -70,7 +72,7 @@ class RgbColor
override val rgba: String
get() = "rgba($r, $g, $b, $alpha)"

override fun withAlpha(alpha: Double) = Colors.rgb(rgb, alpha)
override fun withAlpha(alpha: Percent) = Colors.rgb(rgb, alpha)

override fun equals(other: Any?): Boolean {
if (this === other) return true
Expand Down

0 comments on commit 1c61151

Please sign in to comment.