[orx-fx] Add Canny edge detector

This commit is contained in:
Edwin Jakobs
2022-01-22 21:54:38 +01:00
parent 518fa74a8f
commit 4732494021
5 changed files with 179 additions and 2 deletions

View File

@@ -86,6 +86,7 @@ All distortion effects are opacity preserving
### Edges
- `LumaSobel` - A Sobel-kernel based luminosity edge detector
- `EdgesWork` - An edges filter doubling as erosion
- `Contour` - detects multi-level contours
### Grain
- `FilmGrain` - adds film-like grain to the source input
@@ -106,6 +107,11 @@ All distortion effects are opacity preserving
![DemoFluidDistort01Kt](https://github.com/openrndr/orx/blob/media/orx-fx/images/DemoFluidDistort01Kt.png
<!-- __demos__ -->
## Demos
### DemoBlur01
[source code](src/demo/kotlin/DemoBlur01.kt)
![DemoBlur01Kt](https://raw.githubusercontent.com/openrndr/orx/media/orx-fx/images/DemoBlur01Kt.png)
### DemoFluidDistort01
[source code](src/demo/kotlin/DemoFluidDistort01.kt)

View File

@@ -42,8 +42,6 @@ kotlin {
}
}
collectScreenshots {
ignore.set(listOf("DemoBlur01"))
}
}
}

View File

@@ -0,0 +1,48 @@
package org.openrndr.extra.fx.edges
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.Filter
import org.openrndr.draw.filterShaderFromCode
import org.openrndr.extra.fx.fx_canny_edge_detector
import org.openrndr.extra.parameters.ColorParameter
import org.openrndr.extra.parameters.Description
import org.openrndr.extra.parameters.DoubleParameter
@Description("Canny Edge Detector")
class CannyEdgeDetector : Filter(
filterShaderFromCode(fx_canny_edge_detector, "canny-edge-detector")
) {
@DoubleParameter("threshold 0", 0.0, 100.0, order = 0)
var threshold0: Double by parameters
@DoubleParameter("threshold 1", 0.0, 100.0, order = 1)
var threshold1: Double by parameters
@DoubleParameter("thickness", 0.0, 10.0, order = 2)
var thickness: Double by parameters
@ColorParameter("foreground color", order = 3)
var foregroundColor: ColorRGBa by parameters
@DoubleParameter("foreground opacity", 0.0, 1.0, order = 4)
var foregroundOpacity: Double by parameters
@ColorParameter("background color", order = 5)
var backgroundColor: ColorRGBa by parameters
@DoubleParameter("background opacity", 0.0, 1.0, order = 6)
var backgroundOpacity: Double by parameters
init {
threshold0 = 2.0
threshold1 = 0.0
thickness = 1.0
foregroundColor = ColorRGBa.WHITE
backgroundColor = ColorRGBa.BLACK
backgroundOpacity = 1.0
foregroundOpacity = 1.0
}
}

View File

@@ -0,0 +1,16 @@
import org.openrndr.application
import org.openrndr.draw.createEquivalent
import org.openrndr.draw.loadImage
import org.openrndr.extra.fx.edges.CannyEdgeDetector
fun main() = application {
program {
val image = loadImage("demo-data/images/image-001.png")
val ced = CannyEdgeDetector()
val edges = image.createEquivalent()
extend {
ced.apply(image, edges)
drawer.image(edges)
}
}
}

View File

@@ -0,0 +1,109 @@
// https://www.shadertoy.com/view/sdcSz2
// ref: (in japanese)
// https://imagingsolution.net/imaging/canny-edge-detector/
uniform float thickness;
uniform sampler2D tex0;
in vec2 v_texCoord0;
out vec4 o_output;
uniform float threshold0;
uniform float threshold1;
uniform vec4 backgroundColor;
uniform vec4 foregroundColor;
uniform float backgroundOpacity;
uniform float foregroundOpacity;
vec2 iResolution = textureSize(tex0, 0);
vec2 fragCoord = v_texCoord0 * iResolution;
float getAve(vec2 uv){
vec3 rgb = texture(tex0, uv).rgb;
vec3 lum = vec3(0.299, 0.587, 0.114);
return dot(lum, rgb);
}
// Detect edge.
vec4 sobel(vec2 fragCoord, vec2 dir){
vec2 uv = fragCoord/iResolution.xy;
vec2 texel = 1./iResolution.xy;
float np = getAve(uv + (vec2(-1,+1) + dir ) * texel * thickness);
float zp = getAve(uv + (vec2( 0,+1) + dir ) * texel * thickness);
float pp = getAve(uv + (vec2(+1,+1) + dir ) * texel * thickness);
float nz = getAve(uv + (vec2(-1, 0) + dir ) * texel * thickness);
// zz = 0
float pz = getAve(uv + (vec2(+1, 0) + dir ) * texel * thickness);
float nn = getAve(uv + (vec2(-1,-1) + dir ) * texel * thickness);
float zn = getAve(uv + (vec2( 0,-1) + dir ) * texel * thickness);
float pn = getAve(uv + (vec2(+1,-1) + dir ) * texel * thickness);
// np zp pp
// nz zz pz
// nn zn pn
#if 0
float gx = (np*-1. + nz*-2. + nn*-1. + pp*1. + pz*2. + pn*1.);
float gy = (np*-1. + zp*-2. + pp*-1. + nn*1. + zn*2. + pn*1.);
#else
// https://www.shadertoy.com/view/Wds3Rl
float gx = (np*-3. + nz*-10. + nn*-3. + pp*3. + pz*10. + pn*3.);
float gy = (np*-3. + zp*-10. + pp*-3. + nn*3. + zn*10. + pn*3.);
#endif
vec2 G = vec2(gx,gy);
float grad = length(G);
float angle = atan(G.y, G.x);
return vec4(G, grad, angle);
}
// Make edge thinner.
vec2 hysteresisThr(vec2 fragCoord, float mn, float mx){
vec4 edge = sobel(fragCoord, vec2(0));
vec2 dir = vec2(cos(edge.w), sin(edge.w));
dir *= vec2(-1,1); // rotate 90 degrees.
vec4 edgep = sobel(fragCoord, dir);
vec4 edgen = sobel(fragCoord, -dir);
if(edge.z < edgep.z || edge.z < edgen.z ) edge.z = 0.;
return vec2(
(edge.z > mn) ? edge.z : 0.,
(edge.z > mx) ? edge.z : 0.
);
}
float cannyEdge(vec2 fragCoord, float mn, float mx){
vec2 np = hysteresisThr(fragCoord + vec2(-1,+1), mn, mx);
vec2 zp = hysteresisThr(fragCoord + vec2( 0,+1), mn, mx);
vec2 pp = hysteresisThr(fragCoord + vec2(+1,+1), mn, mx);
vec2 nz = hysteresisThr(fragCoord + vec2(-1, 0), mn, mx);
vec2 zz = hysteresisThr(fragCoord + vec2( 0, 0), mn, mx);
vec2 pz = hysteresisThr(fragCoord + vec2(+1, 0), mn, mx);
vec2 nn = hysteresisThr(fragCoord + vec2(-1,-1), mn, mx);
vec2 zn = hysteresisThr(fragCoord + vec2( 0,-1), mn, mx);
vec2 pn = hysteresisThr(fragCoord + vec2(+1,-1), mn, mx);
// np zp pp
// nz zz pz
// nn zn pn
//return min(1., step(1e-3, zz.x) * (zp.y + nz.y + pz.y + zn.y)*8.);
//return min(1., step(1e-3, zz.x) * (np.y + pp.y + nn.y + pn.y)*8.);
return min(1., step(1e-2, zz.x*8.) * smoothstep(.0, .3, np.y + zp.y + pp.y + nz.y + pz.y + nn.y + zn.y + pn.y)*8.);
}
void main(){
float edge = cannyEdge(fragCoord, threshold0, threshold1);
o_output = mix(foregroundColor * foregroundOpacity, backgroundColor * backgroundOpacity, 1.-edge);
}