[orx-integral-image] Pad source image for npot inputs. Add demo
This commit is contained in:
@@ -2,62 +2,3 @@
|
|||||||
|
|
||||||
CPU and GPU-based implementation for integral images (summed area tables)
|
CPU and GPU-based implementation for integral images (summed area tables)
|
||||||
|
|
||||||
#### Usage
|
|
||||||
|
|
||||||
```kotlin
|
|
||||||
val image = colorBuffer( ... )
|
|
||||||
image.shadow.download()
|
|
||||||
val integralImage = IntegralImage.fromColorBufferShadow(image.shadow)
|
|
||||||
|
|
||||||
// -- the sum for a given area can be queried using
|
|
||||||
val sum = integralImage.sum(IntRectangle(20, 20, 100, 100))
|
|
||||||
```
|
|
||||||
|
|
||||||
## Fast Integral Image
|
|
||||||
|
|
||||||
Since v0.0.20 orx-integral-image comes with `FastIntegralImage` which calculates integral images fully on the GPU.
|
|
||||||
|
|
||||||
```kotlin
|
|
||||||
import org.openrndr.application
|
|
||||||
import org.openrndr.color.ColorRGBa
|
|
||||||
import org.openrndr.draw.*
|
|
||||||
import org.openrndr.extra.integralimage.*
|
|
||||||
|
|
||||||
fun main(args: Array<String>) = application {
|
|
||||||
configure {
|
|
||||||
width = 1024
|
|
||||||
height = 1024
|
|
||||||
}
|
|
||||||
program {
|
|
||||||
val fii = FastIntegralImage()
|
|
||||||
val target = colorBuffer(1024, 512, 1.0, ColorFormat.RGBa, ColorType.FLOAT32)
|
|
||||||
val rt = renderTarget(1024, 512) {
|
|
||||||
colorBuffer()
|
|
||||||
}
|
|
||||||
extend {
|
|
||||||
drawer.background(ColorRGBa.PINK)
|
|
||||||
drawer.isolatedWithTarget(rt) {
|
|
||||||
drawer.ortho(rt)
|
|
||||||
drawer.background(ColorRGBa.BLACK)
|
|
||||||
drawer.fill = ColorRGBa.PINK.shade(1.0)
|
|
||||||
drawer.circle(mouse.position, 128.0)
|
|
||||||
}
|
|
||||||
fii.apply(rt.colorBuffer(0),target)
|
|
||||||
|
|
||||||
// -- here we sample from the integral image
|
|
||||||
drawer.shadeStyle = shadeStyle {
|
|
||||||
fragmentTransform = """
|
|
||||||
float w = 128.0;
|
|
||||||
vec2 step = 1.0 / textureSize(image, 0);
|
|
||||||
vec4 t11 = texture(image, va_texCoord0 + step * vec2(w,w));
|
|
||||||
vec4 t01 = texture(image, va_texCoord0 + step * vec2(-w,w));
|
|
||||||
vec4 t00 = texture(image, va_texCoord0 + step * vec2(-w,-w));
|
|
||||||
vec4 t10 = texture(image, va_texCoord0 + step * vec2(w,-w));
|
|
||||||
x_fill = (t11 - t01 - t10 + t00) / (w*w);
|
|
||||||
""".trimIndent()
|
|
||||||
}
|
|
||||||
drawer.image(target)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|||||||
42
orx-integral-image/src/demo/kotlin/DemoFII01.kt
Normal file
42
orx-integral-image/src/demo/kotlin/DemoFII01.kt
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import org.openrndr.application
|
||||||
|
import org.openrndr.color.ColorRGBa
|
||||||
|
import org.openrndr.draw.*
|
||||||
|
import org.openrndr.extra.integralimage.*
|
||||||
|
|
||||||
|
fun main() = application {
|
||||||
|
configure {
|
||||||
|
width = 720
|
||||||
|
height = 720
|
||||||
|
}
|
||||||
|
program {
|
||||||
|
val fii = FastIntegralImage()
|
||||||
|
val target = colorBuffer(width, height, 1.0, ColorFormat.RGBa, ColorType.FLOAT32)
|
||||||
|
val rt = renderTarget(width, height) {
|
||||||
|
colorBuffer()
|
||||||
|
}
|
||||||
|
extend {
|
||||||
|
drawer.clear(ColorRGBa.PINK)
|
||||||
|
drawer.isolatedWithTarget(rt) {
|
||||||
|
drawer.ortho(rt)
|
||||||
|
drawer.clear(ColorRGBa.BLACK)
|
||||||
|
drawer.fill = ColorRGBa.PINK.shade(1.0)
|
||||||
|
drawer.circle(mouse.position, 128.0)
|
||||||
|
}
|
||||||
|
fii.apply(rt.colorBuffer(0), target)
|
||||||
|
|
||||||
|
// -- here we sample from the integral image
|
||||||
|
drawer.shadeStyle = shadeStyle {
|
||||||
|
fragmentTransform = """
|
||||||
|
float w = 64.0;
|
||||||
|
vec2 step = 1.0 / vec2(textureSize(image, 0));
|
||||||
|
vec4 t11 = texture(image, va_texCoord0 + step * vec2(w+1.0,w+1.0));
|
||||||
|
vec4 t01 = texture(image, va_texCoord0 + step * vec2(-w,w+1.0));
|
||||||
|
vec4 t00 = texture(image, va_texCoord0 + step * vec2(-w,-w));
|
||||||
|
vec4 t10 = texture(image, va_texCoord0 + step * vec2(w+1.0,-w));
|
||||||
|
x_fill = (t11 - t01 - t10 + t00) / ((2.0 * w +1.0) * (2.0 * w + 1.0));
|
||||||
|
""".trimIndent()
|
||||||
|
}
|
||||||
|
drawer.image(target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,27 +5,43 @@ import org.openrndr.extra.fx.blend.Passthrough
|
|||||||
|
|
||||||
import org.openrndr.math.Vector2
|
import org.openrndr.math.Vector2
|
||||||
import org.openrndr.resourceUrl
|
import org.openrndr.resourceUrl
|
||||||
|
import org.openrndr.shape.IntRectangle
|
||||||
import org.openrndr.shape.Rectangle
|
import org.openrndr.shape.Rectangle
|
||||||
|
import kotlin.math.ceil
|
||||||
|
import kotlin.math.log
|
||||||
|
|
||||||
|
|
||||||
class FastIntegralImageFilter : Filter(filterShaderFromUrl(resourceUrl(
|
internal class FastIntegralImageFilter : Filter(
|
||||||
"/shaders/gl3/integral-image.frag"
|
filterShaderFromUrl(
|
||||||
))) {
|
resourceUrl(
|
||||||
|
"/shaders/gl3/integral-image.frag"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) {
|
||||||
var passIndex: Int by parameters
|
var passIndex: Int by parameters
|
||||||
var passDirection: Vector2 by parameters
|
var passDirection: Vector2 by parameters
|
||||||
var sampleCount: Int by parameters
|
var sampleCount: Int by parameters
|
||||||
var sampleCountBase: Int by parameters
|
var sampleCountBase: Int by parameters
|
||||||
}
|
}
|
||||||
|
|
||||||
class FastIntegralImage : Filter(filterShaderFromUrl(resourceUrl(
|
/**
|
||||||
"/shaders/gl3/integral-image.frag"
|
* Compute an integral image for the source image
|
||||||
))) {
|
*/
|
||||||
|
class FastIntegralImage : Filter(
|
||||||
|
filterShaderFromUrl(
|
||||||
|
resourceUrl(
|
||||||
|
"/shaders/gl3/integral-image.frag"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) {
|
||||||
private val passthrough = Passthrough()
|
private val passthrough = Passthrough()
|
||||||
|
|
||||||
var intermediate: ColorBuffer? = null
|
var intermediate: ColorBuffer? = null
|
||||||
val filter = FastIntegralImageFilter()
|
var sourceCropped: ColorBuffer? = null
|
||||||
|
var targetPadded: ColorBuffer? = null
|
||||||
|
private val filter = FastIntegralImageFilter()
|
||||||
|
|
||||||
private fun sampleCounts(size:Int, sampleCountBase:Int) : List<Int> {
|
private fun sampleCounts(size: Int, sampleCountBase: Int): List<Int> {
|
||||||
var remainder = size
|
var remainder = size
|
||||||
val sampleCounts = mutableListOf<Int>()
|
val sampleCounts = mutableListOf<Int>()
|
||||||
while (remainder > 0) {
|
while (remainder > 0) {
|
||||||
@@ -41,40 +57,90 @@ class FastIntegralImage : Filter(filterShaderFromUrl(resourceUrl(
|
|||||||
|
|
||||||
override fun apply(source: Array<ColorBuffer>, target: Array<ColorBuffer>, clip: Rectangle?) {
|
override fun apply(source: Array<ColorBuffer>, target: Array<ColorBuffer>, clip: Rectangle?) {
|
||||||
require(clip == null)
|
require(clip == null)
|
||||||
val sampleCountBase = 16
|
require(source[0].isEquivalentTo(target[0], ignoreFormat = true, ignoreType = true))
|
||||||
val xSampleCounts = sampleCounts(source[0].width, sampleCountBase)
|
|
||||||
val ySampleCounts = sampleCounts(source[0].height, sampleCountBase)
|
|
||||||
|
|
||||||
val li = intermediate
|
val npotx = ceil(log(source[0].effectiveWidth.toDouble(), 2.0)).toInt()
|
||||||
if (li == null || (li.width != source[0].width || li.height != source[0].height)) {
|
val npoty = ceil(log(source[0].effectiveHeight.toDouble(), 2.0)).toInt()
|
||||||
intermediate?.destroy()
|
|
||||||
intermediate = colorBuffer(source[0].width, source[0].height, 1.0, ColorFormat.RGBa, ColorType.FLOAT32)
|
val recWidth = 1 shl npotx
|
||||||
|
val recHeight = 1 shl npoty
|
||||||
|
|
||||||
|
if (recWidth != source[0].effectiveWidth || recHeight != source[0].effectiveHeight) {
|
||||||
|
if (sourceCropped?.effectiveWidth != recWidth || sourceCropped?.effectiveHeight != recHeight) {
|
||||||
|
sourceCropped?.destroy()
|
||||||
|
targetPadded?.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sourceCropped == null) {
|
||||||
|
sourceCropped = source[0].createEquivalent(width = recWidth, height = recHeight, contentScale = 1.0)
|
||||||
|
targetPadded = target[0].createEquivalent(
|
||||||
|
width = (recWidth / target[0].contentScale).toInt(),
|
||||||
|
height = (recHeight / target[0].contentScale).toInt(),
|
||||||
|
contentScale = 1.0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
source[0].copyTo(sourceCropped!!,
|
||||||
|
sourceRectangle = IntRectangle(0, 0, source[0].effectiveWidth, source[0].effectiveHeight),
|
||||||
|
targetRectangle = IntRectangle(0, recHeight-source[0].effectiveHeight, source[0].effectiveWidth, source[0].effectiveHeight)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val targets = arrayOf(target, arrayOf(intermediate!!))
|
val sampleCountBase = 16
|
||||||
|
val xSampleCounts = sampleCounts(recWidth, sampleCountBase)
|
||||||
|
val ySampleCounts = sampleCounts(recHeight, sampleCountBase)
|
||||||
|
|
||||||
|
val li = intermediate
|
||||||
|
if (li == null || (li.effectiveWidth != recWidth || li.effectiveHeight != recHeight)) {
|
||||||
|
intermediate?.destroy()
|
||||||
|
intermediate = colorBuffer(recWidth, recHeight, 1.0, ColorFormat.RGBa, ColorType.FLOAT32)
|
||||||
|
}
|
||||||
|
|
||||||
|
val targets = arrayOf(if (targetPadded == null) target else arrayOf(targetPadded!!), arrayOf(intermediate!!))
|
||||||
|
|
||||||
var targetIndex = 0
|
var targetIndex = 0
|
||||||
|
|
||||||
filter.sampleCountBase = sampleCountBase
|
filter.sampleCountBase = sampleCountBase
|
||||||
|
|
||||||
|
/*
|
||||||
|
Perform horizontal steps
|
||||||
|
*/
|
||||||
filter.passDirection = Vector2.UNIT_X
|
filter.passDirection = Vector2.UNIT_X
|
||||||
for (pass in xSampleCounts.indices) {
|
for (pass in xSampleCounts.indices) {
|
||||||
filter.sampleCount = xSampleCounts[pass]
|
filter.sampleCount = xSampleCounts[pass]
|
||||||
filter.passIndex = pass
|
filter.passIndex = pass
|
||||||
filter.apply( if (pass == 0) source else targets[targetIndex%2], targets[(targetIndex+1)%2])
|
filter.apply(
|
||||||
|
if (pass == 0) {
|
||||||
|
if (sourceCropped == null) source else arrayOf(sourceCropped!!)
|
||||||
|
} else targets[targetIndex % 2], targets[(targetIndex + 1) % 2]
|
||||||
|
)
|
||||||
targetIndex++
|
targetIndex++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Perform vertical steps
|
||||||
|
*/
|
||||||
filter.passDirection = Vector2.UNIT_Y
|
filter.passDirection = Vector2.UNIT_Y
|
||||||
for (pass in ySampleCounts.indices) {
|
for (pass in ySampleCounts.indices) {
|
||||||
filter.sampleCount = ySampleCounts[pass]
|
filter.sampleCount = ySampleCounts[pass]
|
||||||
filter.passIndex = pass
|
filter.passIndex = pass
|
||||||
filter.apply( targets[targetIndex%2], targets[(targetIndex+1)%2])
|
filter.apply(targets[targetIndex % 2], targets[(targetIndex + 1) % 2])
|
||||||
targetIndex++
|
targetIndex++
|
||||||
}
|
}
|
||||||
|
|
||||||
if (targetIndex%2 == 1) {
|
// this is a bit wasteful
|
||||||
|
if (targetIndex % 2 == 1) {
|
||||||
passthrough.apply(targets[1], targets[0])
|
passthrough.apply(targets[1], targets[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
When the source is not a power of two we copy from the padded target to the target
|
||||||
|
*/
|
||||||
|
if (targetPadded != null) {
|
||||||
|
targetPadded!!.copyTo(target[0],
|
||||||
|
sourceRectangle = IntRectangle(0, recHeight-source[0].effectiveHeight, source[0].effectiveWidth, source[0].effectiveHeight),
|
||||||
|
targetRectangle = IntRectangle(0, 0, source[0].effectiveWidth, source[0].effectiveHeight)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
#version 330 core
|
|
||||||
|
|
||||||
uniform sampler2D tex0;
|
uniform sampler2D tex0;
|
||||||
in vec2 v_texCoord0;
|
in vec2 v_texCoord0;
|
||||||
out vec4 o_color;
|
out vec4 o_color;
|
||||||
@@ -11,16 +9,18 @@ uniform vec2 passDirection;
|
|||||||
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec2 passOffset = vec2(pow(sampleCountBase, passIndex)) * (1.0/textureSize(tex0, 0)) * passDirection;
|
vec2 passOffset = vec2(
|
||||||
|
pow(float(sampleCountBase),
|
||||||
|
float(passIndex))) * (1.0 / vec2(textureSize(tex0, 0))
|
||||||
|
) * passDirection;
|
||||||
|
|
||||||
vec2 uv0 = v_texCoord0;
|
vec2 uv0 = v_texCoord0;
|
||||||
|
// uv0.y = 1.0 - uv0.y;
|
||||||
vec4 result = vec4(0.0);
|
vec4 result = vec4(0.0);
|
||||||
for (int i = 0; i < sampleCount; ++i) {
|
for (int i = 0; i < sampleCount; ++i) {
|
||||||
vec2 readUV = v_texCoord0 - vec2(i *passOffset);
|
vec2 readUV = v_texCoord0 - vec2(float(i) * passOffset);
|
||||||
float factor = step(0.0, readUV.x) * step(0.0, readUV.y);
|
float factor = step(0.0, readUV.x) * step(0.0, readUV.y);
|
||||||
result += factor * texture(tex0, readUV);
|
result += factor * texture(tex0, readUV);
|
||||||
}
|
}
|
||||||
|
|
||||||
o_color = result;
|
o_color = result;
|
||||||
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user