Skip to content

Commit

Permalink
[orx-color] Add generated and verified documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
edwinRNDR committed Jan 18, 2025
1 parent 8d0df40 commit 8ef7264
Show file tree
Hide file tree
Showing 12 changed files with 312 additions and 20 deletions.
135 changes: 135 additions & 0 deletions orx-color/src/commonMain/kotlin/mixing/Spectral.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ import kotlin.math.sqrt
Direct port of https://github.com/rvanwijnen/spectral.js
*/

/**
* Represents spectral power distribution (SPD) coefficients for the cyan channel,
* spanning 38 wavelength samples. This array is used as part of the spectral upsampling
* process to convert a linear RGB color into a reflectance spectrum.
*
* The coefficients are predefined values that define the contribution of the cyan channel
* at specific wavelengths to the resulting reflectance spectrum.
*/
private val SPD_C = doubleArrayOf(
0.96853629,
0.96855103,
Expand Down Expand Up @@ -54,6 +62,11 @@ private val SPD_C = doubleArrayOf(
0.01435408
)

/**
* Represents the spectral power distribution (SPD) data for the magenta color component.
* This array contains 38 predefined values corresponding to specific wavelengths.
* Used in the process of spectral upsampling to compute reflectance spectra from RGB color information.
*/
private val SPD_M = doubleArrayOf(
0.51567122,
0.5401552,
Expand Down Expand Up @@ -95,6 +108,11 @@ private val SPD_M = doubleArrayOf(
0.50083021
)

/**
* Spectral power distribution (SPD) values for the Yellow primary in the spectral upsampling process.
* This array contains 38 precomputed reflectance values corresponding to specific wavelength samples.
* It is used to calculate the reflectance spectrum when converting a linear RGB color to its spectral representation.
*/
private val SPD_Y = doubleArrayOf(
0.02055257,
0.02059936,
Expand Down Expand Up @@ -136,6 +154,17 @@ private val SPD_Y = doubleArrayOf(
0.98350852
)

/**
* A predefined spectral power distribution (SPD) array for the red channel,
* used in the spectral upsampling process to convert linear RGB colors into reflectance spectra.
*
* This array contains 38 values corresponding to specific wavelengths and represents
* the relative spectral contribution of the red channel in the conversion process.
*
* The SPD_R array is utilized in conjunction with other SPDs (e.g., SPD_C, SPD_M, SPD_Y, SPD_G, SPD_B)
* and the weights derived from spectral upsampling to calculate the reflectance spectrum
* for a given color.
*/
private val SPD_R = doubleArrayOf(
0.03147571,
0.03146636,
Expand Down Expand Up @@ -177,6 +206,16 @@ private val SPD_R = doubleArrayOf(
0.98551547
)

/**
* Represents the predefined spectral power distribution (SPD) values for the green component
* used in spectral upsampling of linear RGB colors.
*
* This array contains 38 reflectance values corresponding to specific wavelengths and
* reflects the spectral characteristics of the green primary in the color model.
*
* It is utilized as one of the SPD datasets in the linearToReflectance function, which
* converts linear RGB colors into reflectance spectra.
*/
private val SPD_G = doubleArrayOf(
0.49108579,
0.46944057,
Expand Down Expand Up @@ -218,6 +257,12 @@ private val SPD_G = doubleArrayOf(
0.49889859
)

/**
* Represents the spectral power distribution (SPD) values corresponding to the blue component
* of a linear RGB color. The array contains 38 precomputed reflectance values
* that span a specific range of wavelengths. These values are utilized in the spectral
* upsampling process to map an RGB color to its equivalent spectral reflectance distribution.
*/
private val SPD_B = doubleArrayOf(
0.97901834,
0.97901649,
Expand Down Expand Up @@ -259,6 +304,14 @@ private val SPD_B = doubleArrayOf(
0.0157002
)

/**
* A pre-defined array representing the CIE 1931 Standard Observer's color matching function values for the X component.
* This data is used in color science calculations to transform spectral reflectance data into the CIE XYZ color space.
* The array contains 38 discrete samples corresponding to wavelengths within the visible spectrum.
*
* This constant is specifically used during computations involving spectral data to calculate the X component
* of the CIE XYZ color space via multiplication with a corresponding reflectance spectrum array.
*/
private val CIE_CMF_X = doubleArrayOf(
0.00006469,
0.00021941,
Expand Down Expand Up @@ -300,6 +353,17 @@ private val CIE_CMF_X = doubleArrayOf(
0.00002
)

/**
* Represents the Y-component of the CIE 1931 color matching functions.
*
* The CIE 1931 color matching functions are used to convert spectral power distributions into
* CIE XYZ tristimulus values, which represent a color in a perceptually-uniform color space.
* These functions are defined over 38 discrete wavelength samples, typically covering
* the visible spectrum.
*
* The Y-component corresponds to the luminous efficiency function,
* which describes the sensitivity of human vision to different wavelengths of light.
*/
private val CIE_CMF_Y = doubleArrayOf(
0.00000184,
0.00000621,
Expand Down Expand Up @@ -341,6 +405,15 @@ private val CIE_CMF_Y = doubleArrayOf(
0.00000722
)

/**
* Represents the Z component of the CIE 1931 2° Standard Observer Color Matching Function (CMF).
*
* This array contains precomputed values representing the spectral sensitivity of the human eye's Z cone.
* It is used in color science to convert spectral reflectance data into the Z component of the CIE XYZ color space.
*
* The values in this array correspond to sampling points across the visible light spectrum
* and are used in conjunction with `CIE_CMF_X` and `CIE_CMF_Y` to perform reflectance spectrum to XYZ color conversions.
*/
private val CIE_CMF_Z = doubleArrayOf(
0.00030502,
0.00103681,
Expand Down Expand Up @@ -382,6 +455,12 @@ private val CIE_CMF_Z = doubleArrayOf(
0.0
)

/**
* Converts RGB color values into a form that represents spectral power distribution weights.
*
* @param rgb The RGB color in the form of a `ColorRGBa` object, which is to be converted to spectral weights.
* @return A `DoubleArray` representing the decomposed weights for white, cyan, magenta, yellow, red, green, and blue.
*/
private fun spectralUpsampling(rgb: ColorRGBa): DoubleArray {
var lrgb = rgb.toLinear()
val w = min(min(lrgb.r, lrgb.g), lrgb.b)
Expand All @@ -399,6 +478,14 @@ private fun spectralUpsampling(rgb: ColorRGBa): DoubleArray {
}


/**
* Converts a linear RGB color into a reflectance spectrum represented as a `DoubleArray`.
* The resulting reflectance spectrum spans 38 wavelength samples
* and is computed using spectral upsampling based on predefined spectral distributions.
*
* @param rgb The linear RGB color to be converted, represented as a `ColorRGBa` object.
* @return A `DoubleArray` containing 38 reflectance values corresponding to the wavelengths.
*/
internal fun linearToReflectance(rgb: ColorRGBa): DoubleArray {
val eps = 0.00000001
val weights = spectralUpsampling(rgb)
Expand All @@ -419,6 +506,20 @@ internal fun linearToReflectance(rgb: ColorRGBa): DoubleArray {
return reflectance
}

/**
* Computes the concentration of a component in a linear interpolation based on the specified parameters.
*
* The method calculates a concentration factor `c` using two linear values `l1` and `l2`,
* as well as a blend factor `t`. This can be used in scenarios such as spectral mixing
* or interpolation tasks where weights are dynamically computed.
*
* @param l1 The first linear value, typically derived from reflectance or intensity.
* @param l2 The second linear value, typically derived from reflectance or intensity.
* @param t The blending factor in the range [0.0, 1.0], where 0.0 represents full influence of `l1`
* and 1.0 represents full influence of `l2`.
* @return The computed concentration factor as a `Double`, representing the relative contribution
* of the components based on the blending factor.
*/
private fun linearToConcentration(l1: Double, l2: Double, t: Double): Double {
val t1 = l1 * (1 - t).pow(2.0)
val t2 = l2 * t.pow(2.0)
Expand All @@ -434,6 +535,16 @@ private fun DoubleArray.dot(other: DoubleArray): Double {
return d
}

/**
* Converts a reflectance spectrum represented as a `DoubleArray` to a color in the CIE XYZ color space.
*
* This method calculates the XYZ color by performing a dot product operation between the reflectance spectrum
* and the CIE color matching functions (CIE_CMF_X, CIE_CMF_Y, CIE_CMF_Z). The result is returned as a `ColorXYZa` object.
*
* @param reflectance An array of reflectance spectrum values, typically spanning the visible spectrum.
* This is represented as a `DoubleArray` with 38 wavelength samples.
* @return A `ColorXYZa` object representing the corresponding color in the CIE XYZ color space.
*/
internal fun reflectanceToXYZ(reflectance: DoubleArray): ColorXYZa {
val x = reflectance.dot(CIE_CMF_X)
val y = reflectance.dot(CIE_CMF_Y)
Expand All @@ -443,10 +554,34 @@ internal fun reflectanceToXYZ(reflectance: DoubleArray): ColorXYZa {

private fun pow(x: Double, y: Double): Double = x.pow(y)

/**
* Applies the Saunderson correction to a reflectance value to account for surface reflectance effects.
*
* Saunderson correction adjusts the measured reflectance by considering front surface reflection
* and internal reflections in the material. The correction is based on two coefficients, `k1` and `k2`.
*
* @param rInf The measured reflectance value to be corrected.
* @param k1 The first Saunderson coefficient representing front surface reflection.
* @param k2 The second Saunderson coefficient representing internal reflection effects.
* @return The corrected reflectance value as a `Double`.
*/
internal fun saundersonCorrection(rInf: Double, k1: Double, k2: Double): Double {
return k1 + ((1 - k1) * (1 - k2) * rInf) / (1 - (k2 * rInf))
}

/**
* Blends two colors spectrally by interpolating their reflectance spectra and returns the resulting color.
* This method uses spectral upsampling, Saunderson correction, and concentration factors to compute
* the resulting color in the RGB color space.
*
* @param color1 The first color to be mixed, represented as a `ColorRGBa`.
* @param color2 The second color to be mixed, represented as a `ColorRGBa`.
* @param t The blending factor in the range [0.0, 1.0], where 0.0 represents full influence of `color1`
* and 1.0 represents full influence of `color2`.
* @param k1 The first Saunderson correction coefficient for surface reflection. Default is 0.0.
* @param k2 The second Saunderson correction coefficient for internal reflections. Default is 0.0.
* @return The resulting blended color as a `ColorRGBa`, maintaining the linearity of the first input color (`color1`).
*/
fun mixSpectral(color1: ColorRGBa, color2: ColorRGBa, t: Double, k1: Double = 0.0, k2: Double = 0.0): ColorRGBa {
val lrgb1 = color1.toLinear()
val lrgb2 = color2.toLinear()
Expand Down
57 changes: 44 additions & 13 deletions orx-color/src/commonMain/kotlin/palettes/Classics.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,18 @@ import org.openrndr.color.HueShiftableColor
import org.openrndr.extra.color.tools.shiftHue

/**
* Generate an analogous palette
* @param T the color model to use
* @param hueShift Hue degrees between the first and the last color
* @param steps Number of colors to create
* Generates an analogous color palette based on the current color.
*
* This function creates a sequence of colors by shifting the hue of the current color
* gradually across a specified range of steps, using a particular color model that supports hue shifting.
*
* @param T The color model used for hue shifting.
* Must extend both `HueShiftableColor` and `ColorModel`.
* @param hueShift The total degree shift in hue between the first color and the last color.
* The hue shift is divided among the specified number of steps.
* @param steps The number of colors to include in the palette, including the starting color.
* Defaults to 5.
* @return A list of `ColorRGBa` instances forming the analogous palette.
*/
inline fun <reified T> ColorRGBa.analogous(hueShift: Double, steps: Int = 5): List<ColorRGBa>
where T : HueShiftableColor<T>,
Expand All @@ -18,10 +26,21 @@ inline fun <reified T> ColorRGBa.analogous(hueShift: Double, steps: Int = 5): Li
}

/**
* Generate a split complementary palette in which the receiver is the seed color
* @param T the color model to use
* @param splitFactor a value between 0 and 1 that indicates how much the complementary color should be split
* @param double should a double complementary palette be generated
* Generates a split complementary color palette based on the current `ColorRGBa`.
*
* The method calculates complementary colors that are spread around the complementary
* hue axis of the original color. Depending on the parameters, the result may include
* two or four additional colors in addition to the original color.
*
* @param T The color model and hue shifting capability of the colors to generate.
* @param splitFactor A value between 0.0 and 1.0 that controls the spread of the complementary colors
* around the complementary hue. A higher value increases the angle between
* the colors on the hue wheel, while a lower value decreases it.
* @param double If `true`, the method will generate two additional colors derived by more granular
* shifts within the complementary range. If `false`, a simpler complementary palette
* is returned.
* @return A list of `ColorRGBa` objects representing the split complementary palette, with the original
* color as the first element in the list.
*/
inline fun <reified T> ColorRGBa.splitComplementary(splitFactor: Double, double: Boolean = false): List<ColorRGBa>
where T : HueShiftableColor<T>,
Expand All @@ -38,19 +57,31 @@ inline fun <reified T> ColorRGBa.splitComplementary(splitFactor: Double, double:
}
}


/**
* Generate a triadic palette in which the receiver is the seed color.
* @param T the color model to use
* Generates a triadic color palette based on the current `ColorRGBa`.
*
* Triadic colors are evenly spaced on the color wheel, forming a triangle.
* This method generates two additional colors by evenly shifting the hue of the given color
* at 120° intervals around the hue circle.
*
* @param T The color model and hue shifting capability of the colors to generate.
* @return A list of `ColorRGBa` objects representing the triadic color palette.
*/
inline fun <reified T> ColorRGBa.triadic(): List<ColorRGBa>
where T : HueShiftableColor<T>,
T : ColorModel<T> = splitComplementary<T>(1.0 / 3.0)


/**
* Generate a tetradic palette in which the receiver is the seed color.
* @param T the color model to use
* @param aspectRatio the aspect ratio between even and odd sides
* Generates a tetradic color scheme based on the current color.
* A tetradic color scheme consists of four colors that are equidistant on the color wheel.
*
* @param aspectRatio A double value representing the aspect ratio of the tetradic scheme.
* The aspect ratio determines the angular separation between the colors in the scheme.
* Default is 1.0, resulting in equidistant colors.
* @return A list of `ColorRGBa` instances representing the tetradic color scheme.
* The list includes the original color and three additional colors derived by shifting the hue.
*/
inline fun <reified T> ColorRGBa.tetradic(aspectRatio: Double = 1.0): List<ColorRGBa>
where T : HueShiftableColor<T>,
Expand Down
10 changes: 10 additions & 0 deletions orx-color/src/commonMain/kotlin/spaces/ColorHPLUVa.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ import org.openrndr.math.Vector4
import org.openrndr.math.mixAngle
import kotlin.jvm.JvmRecord

/**
* Represents a color in the HPLUVa (Hue, Perceptual Lightness, Saturation, Alpha) color space.
* This color space is based on perceptual uniformity, making it suitable for operations
* like interpolation, shading, and manipulation of hue, saturation, and lightness values.
*
* @property h The hue component of the color, representing the angle on the color wheel in degrees [0, 360).
* @property s The saturation component of the color, representing the intensity of the color [0.0, 1.0].
* @property l The lightness component of the color, representing the relative brightness [0.0, 1.0].
* @property alpha The alpha (opacity) component of the color, ranging from fully transparent (0.0) to fully opaque (1.0).
*/
@Serializable
@JvmRecord
data class ColorHPLUVa(val h: Double, val s: Double, val l: Double, override val alpha: Double = 1.0) :
Expand Down
10 changes: 9 additions & 1 deletion orx-color/src/commonMain/kotlin/spaces/ColorHSLUVa.kt
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,16 @@ private fun maxChromaForLH(L100: Double, H: Double): Double {
return min
}


/**
* HSLUV color space
* Represents a color in the HSLuv color space with an alpha transparency component.
* HSLuv is a perceptually uniform color space, where hues are uniformly distributed
* and the perception of color is consistent across the spectrum.
*
* @property h The hue of the color in degrees, ranging from 0.0 to 360.0.
* @property s The saturation of the color, ranging from 0.0 to 1.0.
* @property l The luminance of the color, ranging from 0.0 to 1.0.
* @property alpha The alpha transparency value, ranging from 0.0 (fully transparent) to 1.0 (fully opaque).
*/
@Serializable
@JvmRecord
Expand Down
10 changes: 10 additions & 0 deletions orx-color/src/commonMain/kotlin/spaces/ColorOKHSLa.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ import org.openrndr.math.mixAngle
import kotlin.jvm.JvmRecord
import kotlin.math.*

/**
* Represents a color in the OKHSL (hue, saturation, lightness) color space with an alpha channel.
* This color model is based on perceptual uniformity and is useful for hue, saturation, and
* lightness manipulations while maintaining consistency with human vision.
*
* @property h The hue of the color, represented as a value in degrees [0.0, 360.0).
* @property s The saturation of the color, where 0.0 is fully desaturated (gray) and 1.0 is fully saturated.
* @property l The lightness of the color, where 0.0 is completely dark and 1.0 is completely light.
* @property alpha The opacity of the color, where 0.0 is fully transparent and 1.0 is fully opaque.
*/
@Suppress("LocalVariableName")
@Serializable
@JvmRecord
Expand Down
16 changes: 16 additions & 0 deletions orx-color/src/commonMain/kotlin/spaces/ColorOKHSVa.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,22 @@ import org.openrndr.math.mixAngle
import kotlin.jvm.JvmRecord
import kotlin.math.*

/**
* Represents a color in the OKHSVa color model.
*
* The OKHSVa color model is derived from OKLABa and provides a perceptually uniform representation
* of colors using hue (h), saturation (s), value (v), and alpha (opacity).
*
* This class supports operations and transformations such as conversion to and from RGBa,
* hue shifting, saturation adjustment, shading, and algebraic operations like addition, subtraction,
* and scaling. It is ideal for working with colors in contexts requiring accurate color mixing
* and perceptual results.
*
* @property h Hue value in degrees (0.0 - 360.0), representing the color's angle on the color wheel.
* @property s Saturation value (0.0 - 1.0), representing the intensity or purity of the color.
* @property v Value (0.0 - 1.0), representing the color's brightness.
* @property alpha Opacity value (0.0 - 1.0), with 1.0 being fully opaque.
*/
@Suppress("LocalVariableName")
@Serializable
@JvmRecord
Expand Down
Loading

0 comments on commit 8ef7264

Please sign in to comment.