[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-jvm:orx-gui"))
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)
}
}
}
}
}