[orx-color] Add color histograms
This commit is contained in:
16
orx-color/README.md
Normal file
16
orx-color/README.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# orx-color
|
||||||
|
|
||||||
|
Tools to work with color
|
||||||
|
|
||||||
|
## Color presets
|
||||||
|
|
||||||
|
orx-color adds an extensive list of preset colors to `ColorRGBa`. Check [sources](src/main/kotlin/presets/Colors.kt) for a listing of the preset colors.
|
||||||
|
|
||||||
|
## Color histograms
|
||||||
|
|
||||||
|
orx-color comes with tools to calculate color histograms for images.
|
||||||
|
|
||||||
|
```
|
||||||
|
val histogram = calculateHistogramRGB(image)
|
||||||
|
val colors = histogram.sortedColors()
|
||||||
|
```
|
||||||
30
orx-color/src/demo/kotlin/DemoHistogram01.kt
Normal file
30
orx-color/src/demo/kotlin/DemoHistogram01.kt
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
// Show color histogram of an image
|
||||||
|
|
||||||
|
import org.openrndr.application
|
||||||
|
import org.openrndr.draw.loadImage
|
||||||
|
import org.openrndr.extensions.SingleScreenshot
|
||||||
|
import org.openrndr.extras.color.statistics.calculateHistogramRGB
|
||||||
|
|
||||||
|
fun main() = application {
|
||||||
|
program {
|
||||||
|
// -- this block is for automation purposes only
|
||||||
|
if (System.getProperty("takeScreenshot") == "true") {
|
||||||
|
extend(SingleScreenshot()) {
|
||||||
|
this.outputFile = System.getProperty("screenshotPath")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val image = loadImage("demo-data/images/image-001.png")
|
||||||
|
val histogram = calculateHistogramRGB(image)
|
||||||
|
val colors = histogram.sortedColors()
|
||||||
|
extend {
|
||||||
|
|
||||||
|
drawer.image(image)
|
||||||
|
for (i in 0 until 32) {
|
||||||
|
drawer.fill = colors[i].first
|
||||||
|
drawer.stroke = null
|
||||||
|
drawer.rectangle(i * (width/32.0), height-16.0, width/32.0, 16.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
32
orx-color/src/demo/kotlin/DemoHistogram02.kt
Normal file
32
orx-color/src/demo/kotlin/DemoHistogram02.kt
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
// Show color histogram using non-uniform weighting
|
||||||
|
|
||||||
|
import org.openrndr.application
|
||||||
|
import org.openrndr.draw.loadImage
|
||||||
|
import org.openrndr.extensions.SingleScreenshot
|
||||||
|
import org.openrndr.extras.color.statistics.calculateHistogramRGB
|
||||||
|
import kotlin.math.pow
|
||||||
|
|
||||||
|
fun main() = application {
|
||||||
|
program {
|
||||||
|
// -- this block is for automation purposes only
|
||||||
|
if (System.getProperty("takeScreenshot") == "true") {
|
||||||
|
extend(SingleScreenshot()) {
|
||||||
|
this.outputFile = System.getProperty("screenshotPath")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val image = loadImage("demo-data/images/image-001.png")
|
||||||
|
// -- here we use non-uniform weighting, such that bright colors are prioritized
|
||||||
|
val histogram = calculateHistogramRGB(image, weighting = {
|
||||||
|
((r+g+b)/3.0).pow(2.4)
|
||||||
|
})
|
||||||
|
val colors = histogram.sortedColors()
|
||||||
|
extend {
|
||||||
|
drawer.image(image)
|
||||||
|
for (i in 0 until 32) {
|
||||||
|
drawer.fill = colors[i].first
|
||||||
|
drawer.stroke = null
|
||||||
|
drawer.rectangle(i * (width / 32.0), height - 16.0, width / 32.0, 16.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
orx-color/src/demo/kotlin/DemoHistogram03.kt
Normal file
29
orx-color/src/demo/kotlin/DemoHistogram03.kt
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
// Create a simple rectangle composition based on colors sampled from image
|
||||||
|
|
||||||
|
import org.openrndr.application
|
||||||
|
import org.openrndr.draw.loadImage
|
||||||
|
import org.openrndr.extensions.SingleScreenshot
|
||||||
|
import org.openrndr.extras.color.statistics.calculateHistogramRGB
|
||||||
|
|
||||||
|
fun main() = application {
|
||||||
|
program {
|
||||||
|
// -- this block is for automation purposes only
|
||||||
|
if (System.getProperty("takeScreenshot") == "true") {
|
||||||
|
extend(SingleScreenshot()) {
|
||||||
|
this.outputFile = System.getProperty("screenshotPath")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val image = loadImage("demo-data/images/image-001.png")
|
||||||
|
val histogram = calculateHistogramRGB(image)
|
||||||
|
extend {
|
||||||
|
drawer.image(image)
|
||||||
|
for (j in 0 until height step 32) {
|
||||||
|
for (i in 0 until width step 32) {
|
||||||
|
drawer.stroke = null
|
||||||
|
drawer.fill = histogram.sample()
|
||||||
|
drawer.rectangle(i * 1.0, j * 1.0, 32.0, 32.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
87
orx-color/src/main/kotlin/statistics/Histogram.kt
Normal file
87
orx-color/src/main/kotlin/statistics/Histogram.kt
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
package org.openrndr.extras.color.statistics
|
||||||
|
|
||||||
|
import org.openrndr.color.ColorRGBa
|
||||||
|
import org.openrndr.draw.ColorBuffer
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
private fun ColorRGBa.binIndex(binCount: Int): Triple<Int, Int, Int> {
|
||||||
|
val rb = (r * binCount).toInt().coerceIn(0, binCount - 1)
|
||||||
|
val gb = (g * binCount).toInt().coerceIn(0, binCount - 1)
|
||||||
|
val bb = (b * binCount).toInt().coerceIn(0, binCount - 1)
|
||||||
|
return Triple(rb, gb, bb)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun calculateHistogramRGB(buffer: ColorBuffer,
|
||||||
|
binCount: Int = 16,
|
||||||
|
weighting: ColorRGBa.() -> Double = { 1.0 },
|
||||||
|
downloadShadow: Boolean = true): RGBHistogram {
|
||||||
|
val bins = Array(binCount) { Array(binCount) { DoubleArray(binCount) } }
|
||||||
|
if (downloadShadow) {
|
||||||
|
buffer.shadow.download()
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalWeight = 0.0
|
||||||
|
val s = buffer.shadow
|
||||||
|
for (y in 0 until buffer.height) {
|
||||||
|
for (x in 0 until buffer.width) {
|
||||||
|
val c = s[x, y]
|
||||||
|
val weight = c.weighting()
|
||||||
|
val (rb, gb, bb) = c.binIndex(binCount)
|
||||||
|
bins[rb][gb][bb] += weight
|
||||||
|
totalWeight += weight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalWeight > 0)
|
||||||
|
for (r in 0 until binCount) {
|
||||||
|
for (g in 0 until binCount) {
|
||||||
|
for (b in 0 until binCount) {
|
||||||
|
bins[r][g][b] /= totalWeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return RGBHistogram(bins, binCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class RGBHistogram(val freqs: Array<Array<DoubleArray>>, val binCount: Int) {
|
||||||
|
fun frequency(color: ColorRGBa): Double {
|
||||||
|
val (rb, gb, bb) = color.binIndex(binCount)
|
||||||
|
return freqs[rb][gb][bb]
|
||||||
|
}
|
||||||
|
|
||||||
|
fun color(rBin: Int, gBin: Int, bBin: Int): ColorRGBa =
|
||||||
|
ColorRGBa(rBin / (binCount - 1.0), gBin / (binCount - 1.0), bBin / (binCount - 1.0))
|
||||||
|
|
||||||
|
fun sample(random: Random = Random.Default): ColorRGBa {
|
||||||
|
val x = random.nextDouble()
|
||||||
|
var sum = 0.0
|
||||||
|
for (r in 0 until binCount) {
|
||||||
|
for (g in 0 until binCount) {
|
||||||
|
for (b in 0 until binCount) {
|
||||||
|
sum += freqs[r][g][b]
|
||||||
|
if (sum >= x) {
|
||||||
|
return color(r, g, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return color(binCount - 1, binCount - 1, binCount - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sortedColors(): List<Pair<ColorRGBa, Double>> {
|
||||||
|
val result = mutableListOf<Pair<ColorRGBa, Double>>()
|
||||||
|
for (r in 0 until binCount) {
|
||||||
|
for (g in 0 until binCount) {
|
||||||
|
for (b in 0 until binCount) {
|
||||||
|
result += Pair(
|
||||||
|
ColorRGBa(r / (binCount - 1.0), g / (binCount - 1.0), b / (binCount - 1.0)),
|
||||||
|
freqs[r][g][b]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.sortedByDescending { it.second }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user