[orx-fx] Add Duotone, DuotoneGradient, LumaHalftone, Posterize, RgbToOkLab, OkLabToRgb

This commit is contained in:
Edwin Jakobs
2022-02-07 09:04:17 +01:00
parent 4fa6c05652
commit 52124ea409
20 changed files with 543 additions and 6 deletions

View File

@@ -63,12 +63,19 @@ Most blur effects are opacity preserving
- `ColorCorrection`, corrections for brightness, contrast, saturation and hue
- `ColorLookup`, Color LUT filter
- `ColorMix`, filter implementation of OPENRNDR's color matrix mixing
- `Duotone`, maps luminosity to two colors, very similar to `LumaMap` but uses LAB color interpolation.
- `DuotoneGradient`, a two-point gradient version of `Duotone`
- `LumaMap`, maps luminosity to two colors
- `LumaOpacity`, maps luminosity to opacity but retains source color
- `LumaThreshold`, applies a treshold on the input luminosity and maps to two colors
- `Posterize`, a posterize effect
- `Sepia`, applies a reddish-brown monochrome tint that imitates an old photograph
- `SubtractConstant`, subtract a constant color from the source color
### Color conversion
- `OkLabToRgb`
- `RgbToOkLab`
### Distortion
All distortion effects are opacity preserving
@@ -82,7 +89,8 @@ All distortion effects are opacity preserving
### Dither
- `ADither` - a selection of dithering effects
- `CMYKHalftone` - a configurable CMYK halftoning effect
- `Crosshatch` - crosshatching effect
- `LumaHalftone` - a halftoning effect based on luminosity
### Edges
- `LumaSobel` - A Sobel-kernel based luminosity edge detector
- `EdgesWork` - An edges filter doubling as erosion

View File

@@ -33,6 +33,7 @@ kotlin {
defaultSourceSet {
kotlin.srcDir("src/demo")
dependencies {
implementation(project(":orx-color"))
implementation(project(":orx-camera"))
implementation("org.openrndr:openrndr-application:$openrndrVersion")
implementation("org.openrndr:openrndr-extensions:$openrndrVersion")
@@ -66,6 +67,8 @@ kotlin {
val commonMain by getting {
dependencies {
implementation(project(":orx-parameters"))
implementation(project(":orx-shader-phrases"))
implementation(project(":orx-color"))
implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:$kotlinxSerializationVersion")
implementation("org.openrndr:openrndr-application:$openrndrVersion")
implementation("org.openrndr:openrndr-draw:$openrndrVersion")

View File

@@ -1,9 +1,22 @@
package org.openrndr.extra.fx.color
import org.openrndr.draw.Filter
import org.openrndr.extra.fx.*
import org.openrndr.extra.fx.fx_rgb_to_oklab
import org.openrndr.extra.fx.fx_rgb_to_ycbcr
import org.openrndr.extra.fx.fx_ycbcr_to_rgb
import org.openrndr.extra.fx.mppFilterShader
import org.openrndr.extra.shaderphrases.preprocess
import org.openrndr.extras.color.phrases.ColorPhraseBook
class RgbToYCbcr : Filter(mppFilterShader(fx_rgb_to_ycbcr, "rgb-to-ycbcr"))
class YcbcrToRgb : Filter(mppFilterShader(fx_ycbcr_to_rgb, "ycbcr_to_rgb"))
class YcbcrToRgb : Filter(mppFilterShader(fx_ycbcr_to_rgb, "ycbcr_to_rgb"))
class RgbToOkLab : Filter(mppFilterShader(run {
ColorPhraseBook.register()
fx_rgb_to_oklab.preprocess()
}, "rgb-to-oklab"))
class OkLabToRgb : Filter(mppFilterShader(run {
ColorPhraseBook.register()
fx_oklab_to_rgb.preprocess()
}, "oklab-to-rgb"))

View File

@@ -0,0 +1,37 @@
package org.openrndr.extra.fx.color
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.Filter
import org.openrndr.draw.filterShaderFromCode
import org.openrndr.extra.fx.fx_duotone
import org.openrndr.extra.parameters.BooleanParameter
import org.openrndr.extra.parameters.ColorParameter
import org.openrndr.extra.parameters.Description
import org.openrndr.extra.shaderphrases.preprocess
import org.openrndr.extras.color.phrases.ColorPhraseBook
import org.openrndr.extras.color.presets.CORAL
import org.openrndr.extras.color.presets.DARK_GRAY
import org.openrndr.extras.color.presets.NAVY
@Description("Duotone")
class Duotone : Filter(filterShaderFromCode(run {
ColorPhraseBook.register()
fx_duotone.preprocess()
}, "duotone")) {
@ColorParameter("background", order = 0)
var backgroundColor: ColorRGBa by parameters
@ColorParameter("foreground", order = 1)
var foregroundColor: ColorRGBa by parameters
@BooleanParameter("LAB interpolation", order = 2)
var labInterpolation: Boolean by parameters
init {
backgroundColor = ColorRGBa.NAVY
foregroundColor = ColorRGBa.CORAL
labInterpolation = true
}
}

View File

@@ -0,0 +1,49 @@
package org.openrndr.extra.fx.color
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.Filter
import org.openrndr.draw.filterShaderFromCode
import org.openrndr.extra.fx.fx_duotone_gradient
import org.openrndr.extra.parameters.BooleanParameter
import org.openrndr.extra.parameters.ColorParameter
import org.openrndr.extra.parameters.Description
import org.openrndr.extra.parameters.DoubleParameter
import org.openrndr.extra.shaderphrases.preprocess
import org.openrndr.extras.color.phrases.ColorPhraseBook
import org.openrndr.extras.color.presets.CORAL
import org.openrndr.extras.color.presets.NAVY
@Description("Duotone Gradient")
class DuotoneGradient : Filter(filterShaderFromCode(run {
ColorPhraseBook.register()
fx_duotone_gradient.preprocess()
}, "duotone-gradient")) {
@ColorParameter("background 0", order = 0)
var backgroundColor0: ColorRGBa by parameters
@ColorParameter("foreground 0", order = 1)
var foregroundColor0: ColorRGBa by parameters
@ColorParameter("background 1", order = 2)
var backgroundColor1: ColorRGBa by parameters
@ColorParameter("foreground 1", order = 3)
var foregroundColor1: ColorRGBa by parameters
@BooleanParameter("LAB interpolation", order = 4)
var labInterpolation: Boolean by parameters
@DoubleParameter("rotation", -180.0, 180.0, order = 5)
var rotation: Double by parameters
init {
backgroundColor0 = ColorRGBa.NAVY
foregroundColor0 = ColorRGBa.CORAL
backgroundColor1 = ColorRGBa.BLACK
foregroundColor1 = ColorRGBa.WHITE
rotation = 0.0
labInterpolation = true
}
}

View File

@@ -0,0 +1,32 @@
package org.openrndr.extra.fx.color
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.Filter
import org.openrndr.draw.filterShaderFromCode
import org.openrndr.extra.fx.fx_duotone
import org.openrndr.extra.fx.fx_posterize
import org.openrndr.extra.parameters.BooleanParameter
import org.openrndr.extra.parameters.ColorParameter
import org.openrndr.extra.parameters.Description
import org.openrndr.extra.parameters.IntParameter
import org.openrndr.extra.shaderphrases.preprocess
import org.openrndr.extras.color.phrases.ColorPhraseBook
import org.openrndr.extras.color.presets.CORAL
import org.openrndr.extras.color.presets.DARK_GRAY
import org.openrndr.extras.color.presets.NAVY
@Description("Posterize")
class Posterize : Filter(filterShaderFromCode(fx_posterize, "posterize")) {
@IntParameter("levels", 2, 32, order = 0)
var levels: Int by parameters
@IntParameter("window", 1, 8, order = 0)
var window: Int by parameters
init {
levels = 4
window = 1
}
}

View File

@@ -43,7 +43,8 @@ class Lenses : Filter(mppFilterShader(fx_lenses, "block-repeat")) {
rows = 6
columns = 8
distort = 0.0
scale = 1.0
rotation = 0.0
bicubicFiltering = true
}
}

View File

@@ -0,0 +1,53 @@
package org.openrndr.extra.fx.dither
import org.openrndr.draw.Filter
import org.openrndr.draw.filterShaderFromCode
import org.openrndr.extra.fx.fx_luma_halftone
import org.openrndr.extra.parameters.BooleanParameter
import org.openrndr.extra.parameters.Description
import org.openrndr.extra.parameters.DoubleParameter
@Description("Luma Halftone")
class LumaHalftone: Filter(filterShaderFromCode(fx_luma_halftone, "luma-halftone")) {
@DoubleParameter("scale", 1.0, 30.0, precision = 4)
var scale: Double by parameters
@DoubleParameter("threshold", 0.0, 1.0, precision = 4)
var threshold: Double by parameters
@DoubleParameter("rotation", -180.0, 180.0)
var rotation: Double by parameters
@DoubleParameter("freq0", 1.0, 400.0)
var freq0: Double by parameters
@DoubleParameter("freq1", 1.0, 400.0)
var freq1: Double by parameters
@DoubleParameter("gain1", -2.0, 2.0)
var gain1: Double by parameters
@DoubleParameter("phase0", -1.0, 1.0)
var phase0: Double by parameters
@DoubleParameter("phase1", -1.0, 1.0)
var phase1: Double by parameters
@BooleanParameter("invert")
var invert: Boolean by parameters
init {
scale = 3.0
rotation = 0.0
threshold = 0.5
freq1 = 20.0
freq0 = 10.0
gain1 = 0.1
phase0 = 0.0
phase1 = 0.0
invert = true
}
}

View File

@@ -0,0 +1,26 @@
import org.openrndr.extra.fx.color.Duotone
import org.openrndr.application
import org.openrndr.draw.createEquivalent
import org.openrndr.draw.loadImage
import org.openrndr.math.mod_
fun main() {
application {
program {
val image = loadImage("demo-data/images/image-001.png")
val filteredImage = image.createEquivalent()
val duotone = Duotone()
extend {
duotone.labInterpolation = seconds.mod_(2.0) < 1.0
duotone.apply(image, filteredImage)
drawer.image(filteredImage)
}
}
}
}

View File

@@ -0,0 +1,25 @@
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.createEquivalent
import org.openrndr.draw.loadImage
import org.openrndr.extra.fx.color.DuotoneGradient
fun main() = application {
program {
val image = loadImage("demo-data/images/image-001.png")
val filteredImage = image.createEquivalent()
val duotone = DuotoneGradient()
duotone.labInterpolation = false
extend {
duotone.labInterpolation = true
duotone.backgroundColor0 = ColorRGBa.BLACK
duotone.foregroundColor0 = ColorRGBa.RED
duotone.backgroundColor1 = ColorRGBa.BLUE
duotone.foregroundColor1 = ColorRGBa.WHITE
duotone.rotation = seconds * 45.0
duotone.apply(image, filteredImage)
drawer.image(filteredImage)
}
}
}

View File

@@ -0,0 +1,25 @@
import org.openrndr.extra.fx.color.Duotone
import org.openrndr.application
import org.openrndr.draw.createEquivalent
import org.openrndr.draw.loadImage
import org.openrndr.extra.fx.color.Posterize
import org.openrndr.math.mod_
fun main() {
application {
program {
val image = loadImage("demo-data/images/image-001.png")
val filteredImage = image.createEquivalent()
val posterize = Posterize()
extend {
posterize.levels = 2
posterize.apply(image, filteredImage)
drawer.image(filteredImage)
}
}
}
}

View File

@@ -0,0 +1,26 @@
import org.openrndr.application
import org.openrndr.draw.createEquivalent
import org.openrndr.draw.loadImage
import org.openrndr.extra.fx.dither.LumaHalftone
import org.openrndr.math.mod_
fun main() {
application {
program {
val image = loadImage("demo-data/images/image-001.png")
val filteredImage = image.createEquivalent()
val lumaHalftone = LumaHalftone()
extend {
lumaHalftone.rotation = -15.0
lumaHalftone.freq0 = 100.0
lumaHalftone.gain1 = 1.0
lumaHalftone.threshold = 0.5
lumaHalftone.phase0 = seconds*0.1
lumaHalftone.phase1 = -seconds*0.1
lumaHalftone.apply(image, filteredImage)
lumaHalftone.invert = seconds.mod_(2.0) < 1.0
drawer.image(filteredImage)
}
}
}
}

View File

@@ -0,0 +1,27 @@
import org.openrndr.extra.fx.color.RgbToOkLab
import org.openrndr.extra.fx.color.OkLabToRgb
import org.openrndr.application
import org.openrndr.draw.ColorType
import org.openrndr.draw.createEquivalent
import org.openrndr.draw.loadImage
/**
* This demonstrates converting a [ColorBuffer] from and to (OK)LAB color space using the [RgbToOkLab] and [OkLabToRgb]
* filters. The (OK)Lab representation is signed and requires a floating point representation.
*/
fun main() {
application {
program {
val rgbToOkLab = RgbToOkLab()
val okLabToRgb = OkLabToRgb()
val image = loadImage("demo-data/images/image-001.png")
val labImage = image.createEquivalent(type = ColorType.FLOAT32)
rgbToOkLab.apply(image, labImage)
okLabToRgb.apply(labImage, image)
extend {
drawer.image(image)
}
}
}
}

View File

@@ -0,0 +1,64 @@
#pragma import color.oklab_to_linear_rgb
#pragma import color.linear_rgb_to_oklab
#pragma import color.linear_rgb_to_srgb
#pragma import color.srgb_to_linear_rgb
uniform vec4 tint;
in vec2 v_texCoord0;
uniform sampler2D tex0;
uniform vec4 backgroundColor0;
uniform vec4 foregroundColor0;
uniform vec4 backgroundColor1;
uniform vec4 foregroundColor1;
uniform bool labInterpolation;
uniform float rotation;
out vec4 o_color;
void main() {
vec4 c = texture(tex0, v_texCoord0);
if (c.a != 0.0) {
c.rgb /= c.a;
}
float ca = cos(radians(rotation));
float sa = sin(radians(rotation));
mat2 rm = mat2(vec2(ca, sa), vec2(-sa, ca));
float f = (rm * (v_texCoord0 - vec2(0.5)) + vec2(0.5)).x;
vec4 bg0 = backgroundColor0;
bg0.rgb *= backgroundColor0.a;
vec4 fg0 = foregroundColor0;
fg0.rgb *= foregroundColor0.a;
vec4 bg1 = backgroundColor1;
bg1.rgb *= backgroundColor1.a;
vec4 fg1 = foregroundColor1;
fg1.rgb *= foregroundColor1.a;
if (!labInterpolation) {
vec4 bg = mix(bg0, bg1, f);
vec4 fg = mix(fg0, fg1, f);
o_color = mix(bg, fg, c.r) * c.a;
} else {
bg0 = srgb_to_linear_rgb(bg0);
bg0 = linear_rgb_to_oklab(bg0);
fg0 = srgb_to_linear_rgb(fg0);
fg0 = linear_rgb_to_oklab(fg0);
bg1 = srgb_to_linear_rgb(bg1);
bg1 = linear_rgb_to_oklab(bg1);
fg1 = srgb_to_linear_rgb(fg1);
fg1 = linear_rgb_to_oklab(fg1);
vec4 bg = mix(bg0, bg1, f);
vec4 fg = mix(fg0, fg1, f);
vec4 m = mix(bg, fg, c.r);
m = oklab_to_linear_rgb(m);
m *= c.a;
m = linear_rgb_to_srgb(m);
o_color = m;
}
}

View File

@@ -0,0 +1,38 @@
#pragma import color.oklab_to_linear_rgb
#pragma import color.linear_rgb_to_oklab
#pragma import color.linear_rgb_to_srgb
#pragma import color.srgb_to_linear_rgb
uniform vec4 tint;
in vec2 v_texCoord0;
uniform sampler2D tex0;
uniform vec4 backgroundColor;
uniform vec4 foregroundColor;
uniform bool labInterpolation;
out vec4 o_color;
void main() {
vec4 c = texture(tex0, v_texCoord0);
if (c.a != 0.0) {
c.rgb /= c.a;
}
vec4 bg = backgroundColor;
bg.rgb *= backgroundColor.a;
vec4 fg = foregroundColor;
fg.rgb *= foregroundColor.a;
if (!labInterpolation) {
o_color = mix(bg, fg, c.r) * c.a;
} else {
bg = srgb_to_linear_rgb(bg);
bg = linear_rgb_to_oklab(bg);
fg = srgb_to_linear_rgb(fg);
fg = linear_rgb_to_oklab(fg);
vec4 m = mix(bg, fg, c.r);
m = oklab_to_linear_rgb(m);
m *= c.a;
m = linear_rgb_to_srgb(m);
o_color = m;
}
}

View File

@@ -0,0 +1,12 @@
#pragma import color.oklab_to_linear_rgb
#pragma import color.linear_rgb_to_srgb
out vec4 o_output;
in vec2 v_texCoord0;
uniform sampler2D tex0;
void main() {
vec4 lab = texture(tex0, v_texCoord0);
vec4 rgba = oklab_to_linear_rgb(lab);
o_output = linear_rgb_to_srgb(rgba);
}

View File

@@ -0,0 +1,26 @@
in vec2 v_texCoord0;
uniform int window;
uniform sampler2D tex0;
uniform int levels;
out vec4 o_output;
void main() {
vec4 c = texture(tex0, v_texCoord0);
vec2 step = 1.0 / textureSize(tex0, 0);
float w = 0.0;
vec3 s = vec3(0.0);
for (int v = -window; v <= window; ++v) {
for (int u = -window; u <= window; ++u) {
vec4 c = texture(tex0, v_texCoord0 + (step/(2*window)) * vec2(u,v) );
if (c.a != 0.0) {
c.rgb /= c.a;
}
vec3 q = min(floor(c.rgb * (levels))/(levels-1), vec3(1.0));
s += q;
w += 1.0;
}
}
vec3 q = s / w;
o_output = vec4(q*c.a, c.a);
}

View File

@@ -0,0 +1,12 @@
#pragma import color.linear_rgb_to_oklab
#pragma import color.srgb_to_linear_rgb
out vec4 o_output;
in vec2 v_texCoord0;
uniform sampler2D tex0;
void main() {
vec4 srgba = texture(tex0, v_texCoord0);
vec4 rgba = srgb_to_linear_rgb(srgba);
o_output = linear_rgb_to_oklab(rgba);
}

View File

@@ -128,10 +128,8 @@ float snoise(vec3 v)
}
vec3 segment(vec3 t, int x, int y) {
float sx = x == 0? t.x : floor(t.x * x) / x;
float sy = y == 0? t.y : floor(t.y * y) / y;
return vec3(sx,sy, t.z);
}

View File

@@ -0,0 +1,62 @@
uniform float scale;
uniform float rotation;
in vec2 v_texCoord0;
uniform sampler2D tex0;
out vec4 o_color;
uniform float threshold;
uniform float freq0;
uniform float freq1;
uniform float gain1;
uniform float phase0;
uniform float phase1;
uniform bool invert;
float cosine_sample(vec2 uv){
float ca = cos(radians(rotation));
float sa = sin(radians(rotation));
vec2 ts = textureSize(tex0, 0);
mat2 rm = mat2(1.0, 0.0, 0.0, ts.x/ts.y) * mat2(vec2(ca, sa), vec2(-sa, ca)) * mat2(1.0, 0.0, 0.0, ts.y/ts.x);
vec2 cuv = (rm * (uv - vec2(0.5))) + vec2(0.5);
float m = fract(phase0 + cuv.x*freq0 + cos(cuv.y*freq1+phase1*3.141592653)*gain1);
vec4 c = texture(tex0, v_texCoord0);
if (c.a != 0.0) {
c.rgb /= c.a;
}
float l = dot(vec3(1.0/3.0), c.rgb);
if (invert) {
l = 1 - l;
}
float t = 0.0;
t = step(threshold, l * m);
return t;
}
float cosine_halftone(vec2 uv) {
int w = 3;
vec2 step = 1.0 / textureSize(tex0, 0);
step /= (2*w);
float weight = 0.0;
float sum = 0.0;
for (int v = -w; v <= w; ++v) {
for (int u = -w; u <= w; ++u) {
sum += cosine_sample(uv + step * vec2(u, v));
weight+=1;
}
}
return sum / weight;
}
void main() {
vec4 c = texture(tex0, v_texCoord0);
float t = cosine_halftone(v_texCoord0);
if (invert) {
t = 1 - t;
}
o_color = vec4(t, t, t, 1.0) * c.a;
}