[orx-color] Add color histograms

This commit is contained in:
Edwin Jakobs
2020-08-18 21:56:29 +02:00
parent c76e2148e0
commit 0747cfd01c
5 changed files with 194 additions and 0 deletions

View 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)
}
}
}
}

View 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)
}
}
}
}

View 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)
}
}
}
}
}

View 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 }
}
}