[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)
|
||||
|
||||
#### 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.resourceUrl
|
||||
import org.openrndr.shape.IntRectangle
|
||||
import org.openrndr.shape.Rectangle
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.log
|
||||
|
||||
|
||||
class FastIntegralImageFilter : Filter(filterShaderFromUrl(resourceUrl(
|
||||
"/shaders/gl3/integral-image.frag"
|
||||
))) {
|
||||
internal class FastIntegralImageFilter : Filter(
|
||||
filterShaderFromUrl(
|
||||
resourceUrl(
|
||||
"/shaders/gl3/integral-image.frag"
|
||||
)
|
||||
)
|
||||
) {
|
||||
var passIndex: Int by parameters
|
||||
var passDirection: Vector2 by parameters
|
||||
var sampleCount: 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()
|
||||
|
||||
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
|
||||
val sampleCounts = mutableListOf<Int>()
|
||||
while (remainder > 0) {
|
||||
@@ -41,40 +57,90 @@ class FastIntegralImage : Filter(filterShaderFromUrl(resourceUrl(
|
||||
|
||||
override fun apply(source: Array<ColorBuffer>, target: Array<ColorBuffer>, clip: Rectangle?) {
|
||||
require(clip == null)
|
||||
val sampleCountBase = 16
|
||||
val xSampleCounts = sampleCounts(source[0].width, sampleCountBase)
|
||||
val ySampleCounts = sampleCounts(source[0].height, sampleCountBase)
|
||||
require(source[0].isEquivalentTo(target[0], ignoreFormat = true, ignoreType = true))
|
||||
|
||||
val li = intermediate
|
||||
if (li == null || (li.width != source[0].width || li.height != source[0].height)) {
|
||||
intermediate?.destroy()
|
||||
intermediate = colorBuffer(source[0].width, source[0].height, 1.0, ColorFormat.RGBa, ColorType.FLOAT32)
|
||||
val npotx = ceil(log(source[0].effectiveWidth.toDouble(), 2.0)).toInt()
|
||||
val npoty = ceil(log(source[0].effectiveHeight.toDouble(), 2.0)).toInt()
|
||||
|
||||
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
|
||||
|
||||
filter.sampleCountBase = sampleCountBase
|
||||
|
||||
/*
|
||||
Perform horizontal steps
|
||||
*/
|
||||
filter.passDirection = Vector2.UNIT_X
|
||||
for (pass in xSampleCounts.indices) {
|
||||
filter.sampleCount = xSampleCounts[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++
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Perform vertical steps
|
||||
*/
|
||||
filter.passDirection = Vector2.UNIT_Y
|
||||
for (pass in ySampleCounts.indices) {
|
||||
filter.sampleCount = ySampleCounts[pass]
|
||||
filter.passIndex = pass
|
||||
filter.apply( targets[targetIndex%2], targets[(targetIndex+1)%2])
|
||||
filter.apply(targets[targetIndex % 2], targets[(targetIndex + 1) % 2])
|
||||
targetIndex++
|
||||
}
|
||||
|
||||
if (targetIndex%2 == 1) {
|
||||
// this is a bit wasteful
|
||||
if (targetIndex % 2 == 1) {
|
||||
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;
|
||||
in vec2 v_texCoord0;
|
||||
out vec4 o_color;
|
||||
@@ -11,16 +9,18 @@ uniform vec2 passDirection;
|
||||
|
||||
|
||||
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;
|
||||
// uv0.y = 1.0 - uv0.y;
|
||||
vec4 result = vec4(0.0);
|
||||
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);
|
||||
result += factor * texture(tex0, readUV);
|
||||
}
|
||||
|
||||
o_color = result;
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user