[orx-color] Add ColorHSLUVa and ColorHPLUVa
This commit is contained in:
@@ -14,6 +14,10 @@ orx-color comes with tools to calculate color histograms for images.
|
||||
val histogram = calculateHistogramRGB(image)
|
||||
val colors = histogram.sortedColors()
|
||||
```
|
||||
|
||||
## HSLUVa and HPLUVa colorspaces
|
||||
|
||||
Two color spaces are added: `ColorHSLUVa` and `ColorHPLUVa`, they are an implementation of the colorspaces presented at [hsluv.org](http://www.hsluv.org)
|
||||
<!-- __demos__ -->
|
||||
## Demos
|
||||
### DemoHistogram01
|
||||
|
||||
30
orx-color/src/demo/kotlin/DemoHSLUV01.kt
Normal file
30
orx-color/src/demo/kotlin/DemoHSLUV01.kt
Normal file
@@ -0,0 +1,30 @@
|
||||
// Draw rectangles shaded in RGB and HSLUV space
|
||||
|
||||
import org.openrndr.application
|
||||
import org.openrndr.color.ColorRGBa
|
||||
import org.openrndr.extensions.SingleScreenshot
|
||||
import org.openrndr.extras.color.spaces.toHSLUVa
|
||||
|
||||
fun main() {
|
||||
application {
|
||||
program {
|
||||
// -- this block is for automation purposes only
|
||||
if (System.getProperty("takeScreenshot") == "true") {
|
||||
extend(SingleScreenshot()) {
|
||||
this.outputFile = System.getProperty("screenshotPath")
|
||||
}
|
||||
}
|
||||
extend {
|
||||
val color = ColorRGBa.PINK
|
||||
drawer.stroke = null
|
||||
for (i in 0 until 10) {
|
||||
drawer.fill = color.shade(1.0 - i / 10.0)
|
||||
drawer.rectangle(100.0, 100.0 + i * 20.0, 100.0, 20.0)
|
||||
|
||||
drawer.fill = color.toHSLUVa().shade(1.0 - i / 10.0).toRGBa().toSRGB()
|
||||
drawer.rectangle(200.0, 100.0 + i * 20.0, 100.0, 20.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
37
orx-color/src/demo/kotlin/DemoHSLUV02.kt
Normal file
37
orx-color/src/demo/kotlin/DemoHSLUV02.kt
Normal file
@@ -0,0 +1,37 @@
|
||||
// Visualize HSLUV color space
|
||||
|
||||
import org.openrndr.application
|
||||
import org.openrndr.color.ColorRGBa
|
||||
import org.openrndr.extensions.SingleScreenshot
|
||||
import org.openrndr.extras.color.spaces.toHSLUVa
|
||||
import org.openrndr.math.Polar
|
||||
import org.openrndr.math.Vector2
|
||||
|
||||
fun main() {
|
||||
application {
|
||||
program {
|
||||
// -- this block is for automation purposes only
|
||||
if (System.getProperty("takeScreenshot") == "true") {
|
||||
extend(SingleScreenshot()) {
|
||||
this.outputFile = System.getProperty("screenshotPath")
|
||||
}
|
||||
}
|
||||
|
||||
extend {
|
||||
val color = ColorRGBa.RED
|
||||
val hc = color.toHSLUVa()
|
||||
drawer.stroke = null
|
||||
drawer.strokeWeight = 0.0
|
||||
for (h in 0 until 360 step 10) {
|
||||
for (s in 0 until 10) {
|
||||
for (l in 9 downTo 0) {
|
||||
val position = Polar(h.toDouble(), s * 25.0).cartesian + Vector2(width/ 2.0, height / 2.0)
|
||||
drawer.fill = hc.shiftHue(h.toDouble()).saturate(s/9.0).shade((9-l)/4.5).toRGBa().toSRGB()
|
||||
drawer.circle(position, kotlin.math.sqrt(s/10.0)*25.0 * l/9.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
157
orx-color/src/main/kotlin/spaces/ColorHSLUVa.kt
Normal file
157
orx-color/src/main/kotlin/spaces/ColorHSLUVa.kt
Normal file
@@ -0,0 +1,157 @@
|
||||
package org.openrndr.extras.color.spaces
|
||||
|
||||
import org.openrndr.color.ColorLCHUVa
|
||||
import org.openrndr.color.ColorRGBa
|
||||
import java.util.*
|
||||
import kotlin.math.min
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.sqrt
|
||||
|
||||
private val m = arrayOf(
|
||||
doubleArrayOf(3.240969941904521, -1.537383177570093, -0.498610760293),
|
||||
doubleArrayOf(-0.96924363628087, 1.87596750150772, 0.041555057407175),
|
||||
doubleArrayOf(0.055630079696993, -0.20397695888897, 1.056971514242878))
|
||||
|
||||
private val kappa = 903.2962962
|
||||
private val epsilon = 0.0088564516
|
||||
|
||||
private fun getBounds(L: Double): List<DoubleArray>? {
|
||||
val result = ArrayList<DoubleArray>()
|
||||
val sub1 = Math.pow(L + 16, 3.0) / 1560896
|
||||
val sub2 = if (sub1 > epsilon) sub1 else L / kappa
|
||||
for (c in 0..2) {
|
||||
val m1 = m[c][0]
|
||||
val m2 = m[c][1]
|
||||
val m3 = m[c][2]
|
||||
for (t in 0..1) {
|
||||
val top1 = (284517 * m1 - 94839 * m3) * sub2
|
||||
val top2 = (838422 * m3 + 769860 * m2 + 731718 * m1) * L * sub2 - 769860 * t * L
|
||||
val bottom = (632260 * m3 - 126452 * m2) * sub2 + 126452 * t
|
||||
result.add(doubleArrayOf(top1 / bottom, top2 / bottom))
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun intersectLineLine(lineA: DoubleArray, lineB: DoubleArray): Double {
|
||||
return (lineA[1] - lineB[1]) / (lineB[0] - lineA[0])
|
||||
}
|
||||
|
||||
private fun distanceFromPole(point: DoubleArray): Double {
|
||||
return sqrt(point[0].pow(2.0) + point[1].pow(2.0))
|
||||
}
|
||||
|
||||
private fun lengthOfRayUntilIntersect(theta: Double, line: DoubleArray): Length {
|
||||
val length = line[1] / (Math.sin(theta) - line[0] * Math.cos(theta))
|
||||
return Length(length)
|
||||
}
|
||||
|
||||
private class Length(val length: Double) {
|
||||
val greaterEqualZero: Boolean = length >= 0
|
||||
}
|
||||
|
||||
private fun maxSafeChromaForL(L: Double): Double {
|
||||
val bounds = getBounds(L)
|
||||
var min = Double.MAX_VALUE
|
||||
for (i in 0..1) {
|
||||
val m1 = bounds!![i][0]
|
||||
val b1 = bounds[i][1]
|
||||
val line = doubleArrayOf(m1, b1)
|
||||
val x = intersectLineLine(line, doubleArrayOf(-1 / m1, 0.0))
|
||||
val length = distanceFromPole(doubleArrayOf(x, b1 + x * m1))
|
||||
min = min(min, length)
|
||||
}
|
||||
return min
|
||||
}
|
||||
|
||||
fun maxChromaForLH(L: Double, H: Double): Double {
|
||||
val hrad = H / 360 * Math.PI * 2
|
||||
val bounds = getBounds(L)
|
||||
var min = Double.MAX_VALUE
|
||||
for (bound in bounds!!) {
|
||||
val length: Length = lengthOfRayUntilIntersect(hrad, bound)
|
||||
if (length.greaterEqualZero) {
|
||||
min = min(min, length.length)
|
||||
}
|
||||
}
|
||||
return min
|
||||
}
|
||||
|
||||
data class ColorHSLUVa(val h: Double, val s: Double, val l: Double) {
|
||||
fun toLCHUVa(): ColorLCHUVa {
|
||||
if (l > 99.9999999) {
|
||||
ColorLCHUVa(100.0, 0.0, h)
|
||||
}
|
||||
|
||||
if (l < 0.00000001) {
|
||||
ColorLCHUVa(0.0, 0.0, h)
|
||||
}
|
||||
val max = maxChromaForLH(l, h)
|
||||
val c: Double = max / 100 * s
|
||||
|
||||
return ColorLCHUVa(l, c, h)
|
||||
}
|
||||
|
||||
fun shiftHue(shiftInDegrees: Double): ColorHSLUVa {
|
||||
return copy(h = h + (shiftInDegrees))
|
||||
}
|
||||
|
||||
fun shade(factor: Double): ColorHSLUVa = copy(l = l * factor)
|
||||
|
||||
fun saturate(factor: Double): ColorHSLUVa = copy(s = s * factor)
|
||||
|
||||
fun toRGBa(): ColorRGBa {
|
||||
return toLCHUVa().toRGBa()
|
||||
}
|
||||
}
|
||||
|
||||
data class ColorHPLUVa(val h: Double, val s: Double, val l: Double) {
|
||||
fun toLCHUVa(): ColorLCHUVa {
|
||||
if (l > 99.9999999) {
|
||||
return ColorLCHUVa(100.0, 0.0, h)
|
||||
}
|
||||
if (l < 0.00000001) {
|
||||
return ColorLCHUVa(0.0, 0.0, h)
|
||||
}
|
||||
val max = maxSafeChromaForL(l)
|
||||
val c = max / 100 * s
|
||||
return ColorLCHUVa(l, c, h)
|
||||
}
|
||||
fun shiftHue(shiftInDegrees: Double): ColorHPLUVa {
|
||||
return copy(h = h + (shiftInDegrees))
|
||||
}
|
||||
|
||||
fun shade(factor: Double): ColorHPLUVa = copy(l = l * factor)
|
||||
|
||||
fun saturate(factor: Double): ColorHPLUVa = copy(s = s * factor)
|
||||
|
||||
fun toRGBa(): ColorRGBa = toLCHUVa().toRGBa()
|
||||
}
|
||||
|
||||
fun ColorLCHUVa.toHPLUVa(): ColorHPLUVa {
|
||||
if (l > 99.9999999) {
|
||||
return ColorHPLUVa(h, 0.0, 100.0)
|
||||
}
|
||||
if (l < 0.00000001) {
|
||||
return ColorHPLUVa(h, 0.0, 0.0)
|
||||
|
||||
}
|
||||
val max = maxSafeChromaForL(l)
|
||||
val s = c / max * 100
|
||||
return ColorHPLUVa(h, s, l)
|
||||
}
|
||||
|
||||
fun ColorLCHUVa.toHSLUVa(): ColorHSLUVa {
|
||||
if (l > 99.99999) {
|
||||
return ColorHSLUVa(h, 0.0, 100.0)
|
||||
}
|
||||
if (l < 0.000001) {
|
||||
return ColorHSLUVa(h, 0.0, 0.0)
|
||||
}
|
||||
val max = maxChromaForLH(l, h)
|
||||
val s = c / max * 100.0
|
||||
return ColorHSLUVa(h, s, l)
|
||||
}
|
||||
|
||||
fun ColorRGBa.toHSLUVa(): ColorHSLUVa = toLCHUVa().toHSLUVa()
|
||||
fun ColorRGBa.toHPLUVa(): ColorHPLUVa = toLCHUVa().toHPLUVa()
|
||||
Reference in New Issue
Block a user