diff --git a/orx-color/build.gradle.kts b/orx-color/build.gradle.kts index 6660b7b2..a882c767 100644 --- a/orx-color/build.gradle.kts +++ b/orx-color/build.gradle.kts @@ -45,6 +45,8 @@ kotlin { implementation(project(":orx-color")) implementation(project(":orx-jvm:orx-gui")) implementation(project(":orx-shade-styles")) + implementation(project(":orx-image-fit")) + implementation(project(":orx-shapes")) } } } diff --git a/orx-color/src/commonMain/kotlin/colormatrix/ColorTransforms.kt b/orx-color/src/commonMain/kotlin/colormatrix/ColorTransforms.kt new file mode 100644 index 00000000..b6299e9b --- /dev/null +++ b/orx-color/src/commonMain/kotlin/colormatrix/ColorTransforms.kt @@ -0,0 +1,138 @@ +package org.openrndr.extra.color.colormatrix + +import org.openrndr.color.ColorRGBa +import org.openrndr.math.Matrix55 + +/** + * Creates a 5x5 matrix based on the given color values. + * + * @param color The color represented as an instance of ColorRGBa, where the r, g, b, and alpha + * components will be used to modify the matrix. + * @param ignoreAlpha A boolean flag indicating whether the alpha component of the color should be ignored. + * If true, the alpha value in the matrix will be set to 0.0. Defaults to true. + * @return A 5x5 matrix (Matrix55) with the r, g, b components set in the corresponding matrix columns, + * and the alpha value determined by the ignoreAlpha parameter. + */ +fun constant(color: ColorRGBa, ignoreAlpha: Boolean = true): Matrix55 { + return Matrix55.IDENTITY.copy(c4r0 = color.r, c4r1 = color.g, c4r2 = color.b, + c4r3 = if (ignoreAlpha) 0.0 else color.alpha + ) +} + +/** + * Applies a color tint transformation and returns a 5x5 matrix representing the transformation. + * + * @param color The `ColorRGBa` instance containing the red, green, blue, and alpha values of the color tint to apply. + * @return A 5x5 transformation matrix with the color tint applied based on the provided color. + */ +fun tint(color: ColorRGBa): Matrix55 { + return Matrix55(c0r0 = color.r, c1r1 = color.g, c2r2 = color.b, c3r3 = color.alpha, c4r4 = 1.0) +} + +/** + * A lazily initialized 5x5 matrix (Matrix55) representing a transformation matrix. + * The matrix is configured with specific coefficient values to perform an inversion transformation. + */ +val invert: Matrix55 by lazy { + Matrix55(c0r0 = -1.0, c1r1 = -1.0, c2r2 = -1.0, c3r3 = 1.0, c4r0 = 1.0, c4r1 = 1.0, c4r2 = 1.0, c4r3 = 0.0, c4r4 = 1.0) +} + +/** + * Creates a grayscale transformation matrix with the specified red, green, and blue coefficients. + * + * @param r The coefficient for the red channel. Default is 0.33. + * @param g The coefficient for the green channel. Default is 0.33. + * @param b The coefficient for the blue channel. Default is 0.33. + * @return A 5x5 matrix representing the grayscale transformation. + */ +fun grayscale(r: Double = 0.33, g: Double = 0.33, b: Double = 0.33): Matrix55 { + return Matrix55( + c0r0 = r, c1r0 = g, c2r0 = b, + c0r1 = r, c1r1 = g, c2r1 = b, + c0r2 = r, c1r2 = g, c2r2 = b, + c3r3 = 1.0, + c4r4 = 1.0) +} + +class ColorMatrixBuilder() { + @PublishedApi + internal var matrix = Matrix55.IDENTITY + + /** + * Applies a grayscale transformation to the current color matrix using the specified red, green, and blue coefficients. + * + * @param r The coefficient for the red channel. Default is 1.0/3.0. + * @param g The coefficient for the green channel. Default is 1.0/3.0. + * @param b The coefficient for the blue channel. Default is 1.0/3.0. + */ + fun grayscale(r: Double = 1.0/3.0, g: Double = 1.0/3.0, b: Double = 1.0/3.0) { + matrix *= org.openrndr.extra.color.colormatrix.grayscale(r, g, b) + } + + /** + * Adds a constant color transformation to the current color matrix. + * + * @param color The color to be added, represented as an instance of `ColorRGBa` with red, green, blue, and alpha components. + * @param ignoreAlpha A boolean flag indicating whether to ignore the alpha component of the color. + * If true, the alpha value in the matrix will be set to 0.0. Defaults to true. + */ + fun addConstant(color: ColorRGBa, ignoreAlpha: Boolean = true) { + matrix *= org.openrndr.extra.color.colormatrix.constant(color, ignoreAlpha) + } + + + /** + * Inverts the specified color channels in the current color matrix. + * + * @param invertR A boolean indicating whether to invert the red channel. Default is true. + * @param invertG A boolean indicating whether to invert the green channel. Default is true. + * @param invertB A boolean indicating whether to invert the blue channel. Default is true. + */ + fun invert(invertR: Boolean = true, invertG: Boolean = true, invertB: Boolean = true) { + matrix *= Matrix55( + c0r0 = if (invertR) -1.0 else 1.0, + c1r1 = if (invertG) -1.0 else 1.0, + c2r2 = if (invertB) -1.0 else 1.0, + c3r3 = 1.0, + c4r0 = if (invertR) 1.0 else 0.0, + c4r1 = if (invertG) 1.0 else 0.0, + c4r2 = if (invertB) 1.0 else 0.0, + c4r3 = 0.0, + c4r4 = 1.0 + ) + } + /** + * Applies a tint transformation to the color matrix using the specified color. + * + * @param color The `ColorRGBa` instance specifying the tint color, including its red, green, blue, and alpha components. + */ + fun tint(color: ColorRGBa) { + matrix *= org.openrndr.extra.color.colormatrix.tint(color) + } + + /** + * Multiplies the current transformation matrix with the specified 5x5 matrix. + * + * @param matrix A 5x5 matrix (Matrix55) to multiply with the current matrix. + */ + fun multiply(matrix: Matrix55) { + this.matrix *= matrix + } + + fun build(): Matrix55 { + return matrix + } +} + +/** + * Constructs a 5x5 color transformation matrix using the specified transformations + * defined within a [ColorMatrixBuilder] DSL. + * + * @param builder A lambda function with a receiver of type [ColorMatrixBuilder] used + * to define the series of color matrix transformations to apply. + * @return A [Matrix55] instance representing the resulting color transformation matrix + * after applying all specified operations in the builder. + */ +fun colorMatrix(builder: ColorMatrixBuilder.() -> Unit): Matrix55 { + return ColorMatrixBuilder().apply(builder).build() +} \ No newline at end of file diff --git a/orx-color/src/jvmDemo/kotlin/colormatrix/DemoColorMatrix01.kt b/orx-color/src/jvmDemo/kotlin/colormatrix/DemoColorMatrix01.kt new file mode 100644 index 00000000..b0ee8d4d --- /dev/null +++ b/orx-color/src/jvmDemo/kotlin/colormatrix/DemoColorMatrix01.kt @@ -0,0 +1,38 @@ +package colormatrix + +import org.openrndr.application +import org.openrndr.draw.loadImage +import org.openrndr.extra.color.colormatrix.colorMatrix +import org.openrndr.extra.imageFit.imageFit +import org.openrndr.extra.shapes.primitives.grid + +/** + * This demo modifies the displayed image in each grid cell + * using color matrix transformations to demonstrate color channel inversions based on + * the grid cell's index. The image is adjusted to fit within each grid cell while maintaining + * alignment. + * + * Functionality: + * - Loads an image from the specified file path. + * - Splits the drawing area into an evenly spaced 4x2 grid. + * - Applies different color matrix inversions (red, green, blue) based on the position index. + * - Fits the image into each grid cell while providing horizontal alignment adjustments. + */ +fun main() = application { + configure { + width = 720 + height = 720 + } + program { + val image = loadImage("demo-data/images/image-001.png") + extend { + val cells = drawer.bounds.grid(4, 2).flatten() + for ((index, cell) in cells.withIndex()) { + drawer.drawStyle.colorMatrix = colorMatrix { + invert(index and 1 != 0, index and 2 != 0, index and 4 != 0) + } + drawer.imageFit(image, cell, horizontalPosition = -0.5) + } + } + } +} \ No newline at end of file diff --git a/orx-color/src/jvmDemo/kotlin/colormatrix/DemoColorMatrix02.kt b/orx-color/src/jvmDemo/kotlin/colormatrix/DemoColorMatrix02.kt new file mode 100644 index 00000000..b1e31296 --- /dev/null +++ b/orx-color/src/jvmDemo/kotlin/colormatrix/DemoColorMatrix02.kt @@ -0,0 +1,41 @@ +package colormatrix + +import org.openrndr.application +import org.openrndr.color.ColorRGBa +import org.openrndr.draw.loadImage +import org.openrndr.extra.color.colormatrix.colorMatrix +import org.openrndr.extra.color.spaces.OKHSV +import org.openrndr.extra.color.tools.shiftHue +import org.openrndr.extra.imageFit.imageFit +import org.openrndr.extra.shapes.primitives.grid + +/** + * This demo modifies the displayed image in each grid cell + * using color matrix transformations to demonstrate color channel inversions based on + * the grid cell's index. The image is adjusted to fit within each grid cell while maintaining + * alignment. + * + * Functionality: + * - Loads an image from the specified file path. + * - Splits the drawing area into an evenly spaced 4x2 grid. + * - Applies different color matrix inversions (red, green, blue) based on the position index. + * - Fits the image into each grid cell while providing horizontal alignment adjustments. + */ +fun main() = application { + configure { + width = 720 + height = 720 + } + program { + val image = loadImage("demo-data/images/image-001.png") + extend { + val cells = drawer.bounds.grid(16, 1).flatten() + for ((index, cell) in cells.withIndex()) { + drawer.drawStyle.colorMatrix = colorMatrix { + tint(ColorRGBa.RED.shiftHue(index * 360 / 16.0)) + } + drawer.imageFit(image, cell, horizontalPosition = -1.0 + 2.0 * index / 15.0) + } + } + } +} \ No newline at end of file diff --git a/orx-color/src/jvmDemo/kotlin/colormatrix/DemoColorMatrix03.kt b/orx-color/src/jvmDemo/kotlin/colormatrix/DemoColorMatrix03.kt new file mode 100644 index 00000000..d7bfde0f --- /dev/null +++ b/orx-color/src/jvmDemo/kotlin/colormatrix/DemoColorMatrix03.kt @@ -0,0 +1,44 @@ +package colormatrix + +import org.openrndr.application +import org.openrndr.color.ColorRGBa +import org.openrndr.draw.loadImage +import org.openrndr.extra.color.colormatrix.colorMatrix +import org.openrndr.extra.color.spaces.OKHSV +import org.openrndr.extra.color.tools.shiftHue +import org.openrndr.extra.imageFit.imageFit +import org.openrndr.extra.shapes.primitives.grid + + +/** + * Entry point for an application demonstrating the use of color matrix transformations on an image. + * + * The program initializes a graphical application with a resolution of 720x720 pixels + * and processes an image to display it in a series of grid cells, applying a hue shift + * transformation based on the index of each cell. + * + * Key features: + * - Loads an image from a specified file path. + * - Configures the drawing area to consist of a horizontal grid with 16 cells. + * - Applies a color tint transformation utilizing the red channel, shifting its hue progressively + * per cell index to create a colorful gradient effect. + * - Adjusts the positions of the images within each grid cell for aesthetic alignment. + */ +fun main() = application { + configure { + width = 720 + height = 720 + } + program { + val image = loadImage("demo-data/images/image-001.png") + extend { + val cells = drawer.bounds.grid(16, 1).flatten() + for ((index, cell) in cells.withIndex()) { + drawer.drawStyle.colorMatrix = colorMatrix { + tint(ColorRGBa.RED.shiftHue(index * 360 / 16.0)) + } + drawer.imageFit(image, cell, horizontalPosition = -1.0 + 2.0 * index / 15.0) + } + } + } +} \ No newline at end of file diff --git a/orx-color/src/jvmDemo/kotlin/colormatrix/DemoColorMatrix04.kt b/orx-color/src/jvmDemo/kotlin/colormatrix/DemoColorMatrix04.kt new file mode 100644 index 00000000..db55388f --- /dev/null +++ b/orx-color/src/jvmDemo/kotlin/colormatrix/DemoColorMatrix04.kt @@ -0,0 +1,56 @@ +package colormatrix + +import org.openrndr.application +import org.openrndr.color.ColorRGBa +import org.openrndr.draw.loadImage +import org.openrndr.extra.color.colormatrix.colorMatrix +import org.openrndr.extra.color.spaces.OKHSV +import org.openrndr.extra.color.tools.shiftHue +import org.openrndr.extra.imageFit.imageFit +import org.openrndr.extra.shapes.primitives.grid + + +/** + * Entry point of a graphical application that demonstrates the use of color matrix + * transformations on an image displayed within a grid layout. + * + * Overview: + * - Initializes a window with a resolution of 720x720 pixels. + * - Loads an image from the specified file path. + * - Splits the drawing canvas into a 7x1 grid of cells. + * - In each grid cell, applies custom grayscale transformations to the image using + * a color matrix. The grayscale transformation coefficients for red, green, and blue + * channels are computed based on the index of the grid cell. + * - Displays the adjusted image in each grid cell with horizontal alignment modifications + * to position the images dynamically based on their index within the grid. + */ +fun main() = application { + configure { + width = 720 + height = 720 + } + program { + val image = loadImage("demo-data/images/image-001.png") + extend { + val cells = drawer.bounds.grid(7, 2) + for ((rowIndex, row) in cells.withIndex()) { + for ((index, cell) in row.withIndex()) { + drawer.drawStyle.colorMatrix = colorMatrix { + var r = if ((index + 1) and 1 != 0) 1.0 else 0.0 + var g = if ((index + 1) and 2 != 0) 1.0 else 0.0 + var b = if ((index + 1) and 4 != 0) 1.0 else 0.0 + val sum = r + g + b + r /= sum + g /= sum + b /= sum + grayscale(r, g, b) + if (rowIndex == 1) { + invert() + } + } + drawer.imageFit(image, cell, horizontalPosition = -0.5) + } + } + } + } +} \ No newline at end of file diff --git a/orx-jumpflood/src/jvmDemo/kotlin/DemoDirectionField02.kt b/orx-jumpflood/src/jvmDemo/kotlin/DemoDirectionField02.kt index 0c3b929a..9c101f4a 100644 --- a/orx-jumpflood/src/jvmDemo/kotlin/DemoDirectionField02.kt +++ b/orx-jumpflood/src/jvmDemo/kotlin/DemoDirectionField02.kt @@ -1,10 +1,10 @@ import org.openrndr.application import org.openrndr.color.ColorRGBa import org.openrndr.draw.ColorType -import org.openrndr.draw.constant import org.openrndr.draw.createEquivalent -import org.openrndr.draw.tint import org.openrndr.drawImage +import org.openrndr.extra.color.colormatrix.constant +import org.openrndr.extra.color.colormatrix.tint import org.openrndr.extra.jumpfill.DirectionalField import org.openrndr.extra.noise.scatter import org.openrndr.math.IntVector2 diff --git a/orx-jumpflood/src/jvmDemo/kotlin/DemoVoronoi02.kt b/orx-jumpflood/src/jvmDemo/kotlin/DemoVoronoi02.kt index 125c67c3..f3efaead 100644 --- a/orx-jumpflood/src/jvmDemo/kotlin/DemoVoronoi02.kt +++ b/orx-jumpflood/src/jvmDemo/kotlin/DemoVoronoi02.kt @@ -1,6 +1,7 @@ import org.openrndr.application import org.openrndr.color.ColorRGBa import org.openrndr.draw.* +import org.openrndr.extra.color.colormatrix.tint import org.openrndr.extra.jumpfill.* import kotlin.math.cos diff --git a/orx-jumpflood/src/jvmDemo/kotlin/DemoVoronoi03.kt b/orx-jumpflood/src/jvmDemo/kotlin/DemoVoronoi03.kt index 200b7a1f..c694ee83 100644 --- a/orx-jumpflood/src/jvmDemo/kotlin/DemoVoronoi03.kt +++ b/orx-jumpflood/src/jvmDemo/kotlin/DemoVoronoi03.kt @@ -1,6 +1,7 @@ import org.openrndr.application import org.openrndr.color.ColorRGBa import org.openrndr.draw.* +import org.openrndr.extra.color.colormatrix.tint import org.openrndr.extra.jumpfill.ClusteredField import org.openrndr.extra.jumpfill.DecodeMode import org.openrndr.extra.noise.scatter diff --git a/orx-marching-squares/build.gradle.kts b/orx-marching-squares/build.gradle.kts index 327da5ab..c0ee33c2 100644 --- a/orx-marching-squares/build.gradle.kts +++ b/orx-marching-squares/build.gradle.kts @@ -15,6 +15,7 @@ kotlin { @Suppress("UNUSED_VARIABLE") val jvmDemo by getting { dependencies { + implementation(project(":orx-color")) implementation(project(":orx-shapes")) implementation(project(":orx-noise")) } diff --git a/orx-marching-squares/src/jvmDemo/kotlin/FindContours04.kt b/orx-marching-squares/src/jvmDemo/kotlin/FindContours04.kt index c5098b27..3700de28 100644 --- a/orx-marching-squares/src/jvmDemo/kotlin/FindContours04.kt +++ b/orx-marching-squares/src/jvmDemo/kotlin/FindContours04.kt @@ -1,7 +1,7 @@ import org.openrndr.application import org.openrndr.color.ColorRGBa -import org.openrndr.draw.grayscale import org.openrndr.draw.loadImage +import org.openrndr.extra.color.colormatrix.grayscale import org.openrndr.extra.marchingsquares.findContours import org.openrndr.math.Vector2 import kotlin.math.PI diff --git a/orx-property-watchers/build.gradle.kts b/orx-property-watchers/build.gradle.kts index ebbb178f..6b2940d7 100644 --- a/orx-property-watchers/build.gradle.kts +++ b/orx-property-watchers/build.gradle.kts @@ -19,6 +19,11 @@ kotlin { } } + val jvmDemo by getting { + dependencies { + implementation(project(":orx-color")) + } + } @Suppress("UNUSED_VARIABLE") val jvmTest by getting { diff --git a/orx-property-watchers/src/jvmDemo/kotlin/DemoImagePathWatcher01.kt b/orx-property-watchers/src/jvmDemo/kotlin/DemoImagePathWatcher01.kt index 4c025bec..3145feb5 100644 --- a/orx-property-watchers/src/jvmDemo/kotlin/DemoImagePathWatcher01.kt +++ b/orx-property-watchers/src/jvmDemo/kotlin/DemoImagePathWatcher01.kt @@ -1,8 +1,8 @@ import org.openrndr.application import org.openrndr.color.ColorRGBa -import org.openrndr.draw.grayscale -import org.openrndr.draw.tint import org.openrndr.drawImage +import org.openrndr.extra.color.colormatrix.grayscale +import org.openrndr.extra.color.colormatrix.tint import org.openrndr.extra.propertywatchers.watchingImagePath import org.openrndr.extra.propertywatchers.watchingProperty diff --git a/orx-temporal-blur/build.gradle.kts b/orx-temporal-blur/build.gradle.kts index 51bf95bd..5e2e26b5 100644 --- a/orx-temporal-blur/build.gradle.kts +++ b/orx-temporal-blur/build.gradle.kts @@ -5,6 +5,7 @@ plugins { dependencies { implementation(libs.openrndr.application) implementation(libs.openrndr.math) + implementation(project(":orx-color")) implementation(project(":orx-noise")) implementation(project(":orx-fx")) implementation(libs.openrndr.filter) diff --git a/orx-temporal-blur/src/main/kotlin/TemporalBlur.kt b/orx-temporal-blur/src/main/kotlin/TemporalBlur.kt index 5be604de..0909b789 100644 --- a/orx-temporal-blur/src/main/kotlin/TemporalBlur.kt +++ b/orx-temporal-blur/src/main/kotlin/TemporalBlur.kt @@ -6,6 +6,7 @@ import org.openrndr.Program import org.openrndr.ProgramImplementation import org.openrndr.color.ColorRGBa import org.openrndr.draw.* +import org.openrndr.extra.color.colormatrix.tint import org.openrndr.extra.noise.uniformRing import org.openrndr.filter.color.delinearize import org.openrndr.filter.color.linearize @@ -78,12 +79,12 @@ class TemporalBlur : Extension { /** * should the accumulator linearize the input. this should be true when rendering in sRGB */ - var linearizeInput = true + var linearizeInput = false /** * should the accumulator delinearize the output. this should be true when rendering in sRGB */ - var delinearizeOutput = true + var delinearizeOutput = false /** * multisampling setting @@ -262,7 +263,6 @@ class TemporalBlur : Extension { drawer.isolatedWithTarget(result!!) { drawer.drawStyle.blendMode = BlendMode.OVER - drawer.clear(ColorRGBa.BLACK) drawer.drawStyle.colorMatrix = tint(ColorRGBa.WHITE.shade((1.0 / samples) * gain)) drawer.image(accumulator!!.colorBuffer(0))