[orx-color] Add color transforms from OPENRNDR, add colorMatrix {}

This commit is contained in:
Edwin Jakobs
2025-02-26 18:01:36 +01:00
parent ef1b8a6493
commit 0046348410
15 changed files with 336 additions and 8 deletions

View File

@@ -45,6 +45,8 @@ kotlin {
implementation(project(":orx-color")) implementation(project(":orx-color"))
implementation(project(":orx-jvm:orx-gui")) implementation(project(":orx-jvm:orx-gui"))
implementation(project(":orx-shade-styles")) implementation(project(":orx-shade-styles"))
implementation(project(":orx-image-fit"))
implementation(project(":orx-shapes"))
} }
} }
} }

View File

@@ -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()
}

View File

@@ -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)
}
}
}
}

View File

@@ -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<OKHSV>(index * 360 / 16.0))
}
drawer.imageFit(image, cell, horizontalPosition = -1.0 + 2.0 * index / 15.0)
}
}
}
}

View File

@@ -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<OKHSV>(index * 360 / 16.0))
}
drawer.imageFit(image, cell, horizontalPosition = -1.0 + 2.0 * index / 15.0)
}
}
}
}

View File

@@ -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)
}
}
}
}
}

View File

@@ -1,10 +1,10 @@
import org.openrndr.application import org.openrndr.application
import org.openrndr.color.ColorRGBa import org.openrndr.color.ColorRGBa
import org.openrndr.draw.ColorType import org.openrndr.draw.ColorType
import org.openrndr.draw.constant
import org.openrndr.draw.createEquivalent import org.openrndr.draw.createEquivalent
import org.openrndr.draw.tint
import org.openrndr.drawImage 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.jumpfill.DirectionalField
import org.openrndr.extra.noise.scatter import org.openrndr.extra.noise.scatter
import org.openrndr.math.IntVector2 import org.openrndr.math.IntVector2

View File

@@ -1,6 +1,7 @@
import org.openrndr.application import org.openrndr.application
import org.openrndr.color.ColorRGBa import org.openrndr.color.ColorRGBa
import org.openrndr.draw.* import org.openrndr.draw.*
import org.openrndr.extra.color.colormatrix.tint
import org.openrndr.extra.jumpfill.* import org.openrndr.extra.jumpfill.*
import kotlin.math.cos import kotlin.math.cos

View File

@@ -1,6 +1,7 @@
import org.openrndr.application import org.openrndr.application
import org.openrndr.color.ColorRGBa import org.openrndr.color.ColorRGBa
import org.openrndr.draw.* import org.openrndr.draw.*
import org.openrndr.extra.color.colormatrix.tint
import org.openrndr.extra.jumpfill.ClusteredField import org.openrndr.extra.jumpfill.ClusteredField
import org.openrndr.extra.jumpfill.DecodeMode import org.openrndr.extra.jumpfill.DecodeMode
import org.openrndr.extra.noise.scatter import org.openrndr.extra.noise.scatter

View File

@@ -15,6 +15,7 @@ kotlin {
@Suppress("UNUSED_VARIABLE") @Suppress("UNUSED_VARIABLE")
val jvmDemo by getting { val jvmDemo by getting {
dependencies { dependencies {
implementation(project(":orx-color"))
implementation(project(":orx-shapes")) implementation(project(":orx-shapes"))
implementation(project(":orx-noise")) implementation(project(":orx-noise"))
} }

View File

@@ -1,7 +1,7 @@
import org.openrndr.application import org.openrndr.application
import org.openrndr.color.ColorRGBa import org.openrndr.color.ColorRGBa
import org.openrndr.draw.grayscale
import org.openrndr.draw.loadImage import org.openrndr.draw.loadImage
import org.openrndr.extra.color.colormatrix.grayscale
import org.openrndr.extra.marchingsquares.findContours import org.openrndr.extra.marchingsquares.findContours
import org.openrndr.math.Vector2 import org.openrndr.math.Vector2
import kotlin.math.PI import kotlin.math.PI

View File

@@ -19,6 +19,11 @@ kotlin {
} }
} }
val jvmDemo by getting {
dependencies {
implementation(project(":orx-color"))
}
}
@Suppress("UNUSED_VARIABLE") @Suppress("UNUSED_VARIABLE")
val jvmTest by getting { val jvmTest by getting {

View File

@@ -1,8 +1,8 @@
import org.openrndr.application import org.openrndr.application
import org.openrndr.color.ColorRGBa import org.openrndr.color.ColorRGBa
import org.openrndr.draw.grayscale
import org.openrndr.draw.tint
import org.openrndr.drawImage 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.watchingImagePath
import org.openrndr.extra.propertywatchers.watchingProperty import org.openrndr.extra.propertywatchers.watchingProperty

View File

@@ -5,6 +5,7 @@ plugins {
dependencies { dependencies {
implementation(libs.openrndr.application) implementation(libs.openrndr.application)
implementation(libs.openrndr.math) implementation(libs.openrndr.math)
implementation(project(":orx-color"))
implementation(project(":orx-noise")) implementation(project(":orx-noise"))
implementation(project(":orx-fx")) implementation(project(":orx-fx"))
implementation(libs.openrndr.filter) implementation(libs.openrndr.filter)

View File

@@ -6,6 +6,7 @@ import org.openrndr.Program
import org.openrndr.ProgramImplementation import org.openrndr.ProgramImplementation
import org.openrndr.color.ColorRGBa import org.openrndr.color.ColorRGBa
import org.openrndr.draw.* import org.openrndr.draw.*
import org.openrndr.extra.color.colormatrix.tint
import org.openrndr.extra.noise.uniformRing import org.openrndr.extra.noise.uniformRing
import org.openrndr.filter.color.delinearize import org.openrndr.filter.color.delinearize
import org.openrndr.filter.color.linearize 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 * 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 * should the accumulator delinearize the output. this should be true when rendering in sRGB
*/ */
var delinearizeOutput = true var delinearizeOutput = false
/** /**
* multisampling setting * multisampling setting
@@ -262,7 +263,6 @@ class TemporalBlur : Extension {
drawer.isolatedWithTarget(result!!) { drawer.isolatedWithTarget(result!!) {
drawer.drawStyle.blendMode = BlendMode.OVER drawer.drawStyle.blendMode = BlendMode.OVER
drawer.clear(ColorRGBa.BLACK) drawer.clear(ColorRGBa.BLACK)
drawer.drawStyle.colorMatrix = tint(ColorRGBa.WHITE.shade((1.0 / samples) * gain)) drawer.drawStyle.colorMatrix = tint(ColorRGBa.WHITE.shade((1.0 / samples) * gain))
drawer.image(accumulator!!.colorBuffer(0)) drawer.image(accumulator!!.colorBuffer(0))