diff --git a/orx-fx/README.md b/orx-fx/README.md index 71cf88c8..5a4c87ea 100644 --- a/orx-fx/README.md +++ b/orx-fx/README.md @@ -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 diff --git a/orx-fx/build.gradle.kts b/orx-fx/build.gradle.kts index 84de5d30..26067fd7 100644 --- a/orx-fx/build.gradle.kts +++ b/orx-fx/build.gradle.kts @@ -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") diff --git a/orx-fx/src/commonMain/kotlin/color/Colorspaces.kt b/orx-fx/src/commonMain/kotlin/color/Colorspaces.kt index 65318c49..7ed18b24 100644 --- a/orx-fx/src/commonMain/kotlin/color/Colorspaces.kt +++ b/orx-fx/src/commonMain/kotlin/color/Colorspaces.kt @@ -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")) \ No newline at end of file +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")) diff --git a/orx-fx/src/commonMain/kotlin/color/Duotone.kt b/orx-fx/src/commonMain/kotlin/color/Duotone.kt new file mode 100644 index 00000000..2464eaa8 --- /dev/null +++ b/orx-fx/src/commonMain/kotlin/color/Duotone.kt @@ -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 + } +} \ No newline at end of file diff --git a/orx-fx/src/commonMain/kotlin/color/DuotoneGradient.kt b/orx-fx/src/commonMain/kotlin/color/DuotoneGradient.kt new file mode 100644 index 00000000..a67a64b6 --- /dev/null +++ b/orx-fx/src/commonMain/kotlin/color/DuotoneGradient.kt @@ -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 + } +} \ No newline at end of file diff --git a/orx-fx/src/commonMain/kotlin/color/Posterize.kt b/orx-fx/src/commonMain/kotlin/color/Posterize.kt new file mode 100644 index 00000000..6e0241f2 --- /dev/null +++ b/orx-fx/src/commonMain/kotlin/color/Posterize.kt @@ -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 + } +} \ No newline at end of file diff --git a/orx-fx/src/commonMain/kotlin/distort/Lenses.kt b/orx-fx/src/commonMain/kotlin/distort/Lenses.kt index bfb81c2d..3e8f2d64 100644 --- a/orx-fx/src/commonMain/kotlin/distort/Lenses.kt +++ b/orx-fx/src/commonMain/kotlin/distort/Lenses.kt @@ -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 } } diff --git a/orx-fx/src/commonMain/kotlin/dither/LumaHalftone.kt b/orx-fx/src/commonMain/kotlin/dither/LumaHalftone.kt new file mode 100644 index 00000000..34ed1456 --- /dev/null +++ b/orx-fx/src/commonMain/kotlin/dither/LumaHalftone.kt @@ -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 + } +} \ No newline at end of file diff --git a/orx-fx/src/demo/kotlin/DemoColorDuotone01.kt b/orx-fx/src/demo/kotlin/DemoColorDuotone01.kt new file mode 100644 index 00000000..b5a6a7b4 --- /dev/null +++ b/orx-fx/src/demo/kotlin/DemoColorDuotone01.kt @@ -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) + + } + + } + + } +} \ No newline at end of file diff --git a/orx-fx/src/demo/kotlin/DemoColorDuotoneGradient01.kt b/orx-fx/src/demo/kotlin/DemoColorDuotoneGradient01.kt new file mode 100644 index 00000000..1cbffdac --- /dev/null +++ b/orx-fx/src/demo/kotlin/DemoColorDuotoneGradient01.kt @@ -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) + } + } +} \ No newline at end of file diff --git a/orx-fx/src/demo/kotlin/DemoColorPosterize01.kt b/orx-fx/src/demo/kotlin/DemoColorPosterize01.kt new file mode 100644 index 00000000..ce265c02 --- /dev/null +++ b/orx-fx/src/demo/kotlin/DemoColorPosterize01.kt @@ -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) + + } + + } + + } +} \ No newline at end of file diff --git a/orx-fx/src/demo/kotlin/DemoDitherLumaHalftone01.kt b/orx-fx/src/demo/kotlin/DemoDitherLumaHalftone01.kt new file mode 100644 index 00000000..25f4cfce --- /dev/null +++ b/orx-fx/src/demo/kotlin/DemoDitherLumaHalftone01.kt @@ -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) + } + } + } +} \ No newline at end of file diff --git a/orx-fx/src/demo/kotlin/DemoOkLab01.kt b/orx-fx/src/demo/kotlin/DemoOkLab01.kt new file mode 100644 index 00000000..917b258d --- /dev/null +++ b/orx-fx/src/demo/kotlin/DemoOkLab01.kt @@ -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) + } + } + } +} \ No newline at end of file diff --git a/orx-fx/src/shaders/glsl/color/duotone-gradient.frag b/orx-fx/src/shaders/glsl/color/duotone-gradient.frag new file mode 100644 index 00000000..23c6a356 --- /dev/null +++ b/orx-fx/src/shaders/glsl/color/duotone-gradient.frag @@ -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; + } +} \ No newline at end of file diff --git a/orx-fx/src/shaders/glsl/color/duotone.frag b/orx-fx/src/shaders/glsl/color/duotone.frag new file mode 100644 index 00000000..8750cefc --- /dev/null +++ b/orx-fx/src/shaders/glsl/color/duotone.frag @@ -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; + } +} \ No newline at end of file diff --git a/orx-fx/src/shaders/glsl/color/oklab-to-rgb.frag b/orx-fx/src/shaders/glsl/color/oklab-to-rgb.frag new file mode 100644 index 00000000..501e03f6 --- /dev/null +++ b/orx-fx/src/shaders/glsl/color/oklab-to-rgb.frag @@ -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); +} \ No newline at end of file diff --git a/orx-fx/src/shaders/glsl/color/posterize.frag b/orx-fx/src/shaders/glsl/color/posterize.frag new file mode 100644 index 00000000..0f0e771e --- /dev/null +++ b/orx-fx/src/shaders/glsl/color/posterize.frag @@ -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); +} \ No newline at end of file diff --git a/orx-fx/src/shaders/glsl/color/rgb-to-oklab.frag b/orx-fx/src/shaders/glsl/color/rgb-to-oklab.frag new file mode 100644 index 00000000..41192b3f --- /dev/null +++ b/orx-fx/src/shaders/glsl/color/rgb-to-oklab.frag @@ -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); +} \ No newline at end of file diff --git a/orx-fx/src/shaders/glsl/distort/perturb.frag b/orx-fx/src/shaders/glsl/distort/perturb.frag index b0213d2b..d1c83125 100644 --- a/orx-fx/src/shaders/glsl/distort/perturb.frag +++ b/orx-fx/src/shaders/glsl/distort/perturb.frag @@ -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); } diff --git a/orx-fx/src/shaders/glsl/dither/luma-halftone.frag b/orx-fx/src/shaders/glsl/dither/luma-halftone.frag new file mode 100644 index 00000000..23da91f7 --- /dev/null +++ b/orx-fx/src/shaders/glsl/dither/luma-halftone.frag @@ -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; +} \ No newline at end of file