diff --git a/skiko/src/commonMain/kotlin/org/jetbrains/skia/ColorSpace.kt b/skiko/src/commonMain/kotlin/org/jetbrains/skia/ColorSpace.kt index 1ea4bbb50..546f19fa6 100644 --- a/skiko/src/commonMain/kotlin/org/jetbrains/skia/ColorSpace.kt +++ b/skiko/src/commonMain/kotlin/org/jetbrains/skia/ColorSpace.kt @@ -12,10 +12,35 @@ class ColorSpace : Managed { val sRGB = ColorSpace(_nMakeSRGB(), false) val sRGBLinear = ColorSpace(_nMakeSRGBLinear(), false) - val displayP3 = ColorSpace(_nMakeDisplayP3(), false) + val displayP3: ColorSpace = requireNotNull(makeRGB(TransferFunction.sRGB, Matrix33.displayP3ToXYZD50)) + + /** + * Creates a color space from a transfer function and a 3x3 transformation to XYZ D50. + * Matrix33 offers various factories for obtaining a toXYZD50 matrix. + * + * @throws IllegalArgumentException If the transfer function is invalid. + */ + fun makeRGB(transferFunction: TransferFunction, toXYZD50: Matrix33): ColorSpace { + return try { + Stats.onNativeCall() + val ptr = interopScope { + _nMakeRGB(toInterop(transferFunction.asArray()), toInterop(toXYZD50.mat)) + } + require(ptr != NullPointer) { "Invalid transfer function" } + ColorSpace(ptr) + } finally { + reachabilityBarrier(transferFunction) + reachabilityBarrier(toXYZD50) + } + } + } + /** + * **Warning:** This method only converts between transfer functions while completely disregarding the gamut. + * Hence, the result will not be what you expect when the gamuts of the two color spaces differ! + */ fun convert(toColor: ColorSpace?, color: Color4f): Color4f { var to = toColor to = to ?: sRGB @@ -85,6 +110,37 @@ class ColorSpace : Managed { reachabilityBarrier(this) } + val transferFunction: TransferFunction + get() = try { + Stats.onNativeCall() + TransferFunction(withResult(FloatArray(7)) { + _nGetTransferFunction(_ptr, it) + }) + } finally { + reachabilityBarrier(this) + } + + /** The toXYZD50 matrix that converts from this color space's gamut to XYZ adapted to D50. */ + val toXYZD50: Matrix33 + get() = try { + Stats.onNativeCall() + Matrix33(*withResult(FloatArray(9)) { + _nGetToXYZD50(_ptr, it) + }) + } finally { + reachabilityBarrier(this) + } + + override fun nativeEquals(other: Native?): Boolean { + return try { + Stats.onNativeCall() + _nEquals(_ptr, getPtr(other)) + } finally { + reachabilityBarrier(this) + reachabilityBarrier(other) + } + } + private object _FinalizerHolder { val PTR = ColorSpace_nGetFinalizer() } @@ -99,12 +155,12 @@ private external fun _nConvert(fromPtr: NativePointer, toPtr: NativePointer, r: @ExternalSymbolName("org_jetbrains_skia_ColorSpace__1nMakeSRGB") private external fun _nMakeSRGB(): NativePointer -@ExternalSymbolName("org_jetbrains_skia_ColorSpace__1nMakeDisplayP3") -private external fun _nMakeDisplayP3(): NativePointer - @ExternalSymbolName("org_jetbrains_skia_ColorSpace__1nMakeSRGBLinear") private external fun _nMakeSRGBLinear(): NativePointer +@ExternalSymbolName("org_jetbrains_skia_ColorSpace__1nMakeRGB") +private external fun _nMakeRGB(transferFunction: InteropPointer, toXYZD50: InteropPointer): NativePointer + @ExternalSymbolName("org_jetbrains_skia_ColorSpace__1nIsGammaCloseToSRGB") private external fun _nIsGammaCloseToSRGB(ptr: NativePointer): Boolean @@ -113,3 +169,12 @@ private external fun _nIsGammaLinear(ptr: NativePointer): Boolean @ExternalSymbolName("org_jetbrains_skia_ColorSpace__1nIsSRGB") private external fun _nIsSRGB(ptr: NativePointer): Boolean + +@ExternalSymbolName("org_jetbrains_skia_ColorSpace__1nGetTransferFunction") +private external fun _nGetTransferFunction(ptr: NativePointer, result: InteropPointer): NativePointer + +@ExternalSymbolName("org_jetbrains_skia_ColorSpace__1nGetToXYZD50") +private external fun _nGetToXYZD50(ptr: NativePointer, result: InteropPointer): NativePointer + +@ExternalSymbolName("org_jetbrains_skia_ColorSpace__1nEquals") +private external fun _nEquals(ptr: NativePointer, otherPtr: NativePointer): Boolean diff --git a/skiko/src/commonMain/kotlin/org/jetbrains/skia/Matrix33.kt b/skiko/src/commonMain/kotlin/org/jetbrains/skia/Matrix33.kt index 2aad2e170..54c6d23cc 100644 --- a/skiko/src/commonMain/kotlin/org/jetbrains/skia/Matrix33.kt +++ b/skiko/src/commonMain/kotlin/org/jetbrains/skia/Matrix33.kt @@ -1,5 +1,10 @@ package org.jetbrains.skia +import org.jetbrains.skia.impl.InteropPointer +import org.jetbrains.skia.impl.Library.Companion.staticLoad +import org.jetbrains.skia.impl.Stats +import org.jetbrains.skia.impl.withNullableResult +import org.jetbrains.skia.impl.withResult import kotlin.math.PI import kotlin.math.abs import kotlin.math.cos @@ -13,9 +18,8 @@ internal fun Float.toRadians(): Double = this.toDouble() / 180 * PI * Point and vectors with translation, scaling, skewing, rotation, and * perspective. * - * - * Matrix includes a hidden variable that classifies the type of matrix to - * improve performance. Matrix is not thread safe unless getType() is called first. + * 3x3 matrices are also used to characterize the gamut of a color space in the form + * of a conversion matrix to XYZ D50. We call this a toXYZD50 matrix for short. * * @see [https://fiddle.skia.org/c/@Matrix_063](https://fiddle.skia.org/c/@Matrix_063) */ @@ -264,6 +268,66 @@ class Matrix33(vararg mat: Float) { fun makeSkew(sx: Float, sy: Float): Matrix33 { return Matrix33(1f, sx, 0f, sy, 1f, 0f, 0f, 0f, 1f) } + + init { + staticLoad() + } + + // We defensively copy the preset arrays, as otherwise, Matrix33 would expose them to potential mutation: + + /** A toXYZD50 matrix to convert sRGB color into XYZ adapted to D50. Use it to create a color space. */ + val sRGBToXYZD50 get() = Matrix33(*_sRGBToXYZD50) + + /** A toXYZD50 matrix to convert Adobe RGB color into XYZ adapted to D50. Use it to create a color space. */ + val adobeRGBToXYZD50 get() = Matrix33(*_adobeRGBToXYZD50) + + /** A toXYZD50 matrix to convert Display P3 color into XYZ adapted to D50. Use it to create a color space. */ + val displayP3ToXYZD50 get() = Matrix33(*_displayP3ToXYZD50) + + /** A toXYZD50 matrix to convert Rec.2020 color into XYZ adapted to D50. Use it to create a color space. */ + val rec2020ToXYZD50 get() = Matrix33(*_rec2020ToXYZD50) + + /** A toXYZD50 identity matrix. Use it to create a color space. */ + val xyzD50ToXYZD50 get() = Matrix33(*_xyzD50ToXYZD50) + + private val _sRGBToXYZD50 = withResult(FloatArray(9)) { _nGetSRGB(it) } + private val _adobeRGBToXYZD50 = withResult(FloatArray(9)) { _nGetAdobeRGB(it) } + private val _displayP3ToXYZD50 = withResult(FloatArray(9)) { _nGetDisplayP3(it) } + private val _rec2020ToXYZD50 = withResult(FloatArray(9)) { _nGetRec2020(it) } + private val _xyzD50ToXYZD50 = withResult(FloatArray(9)) { _nGetXYZ(it) } + + /** + * Returns a toXYZD50 matrix to adapt XYZ color from given the whitepoint to D50. + * Use it to create a color space. + * + * @throws IllegalArgumentException If the white point is invalid. + */ + fun makeXYZToXYZD50(wx: Float, wy: Float): Matrix33 { + Stats.onNativeCall() + val array = withNullableResult(FloatArray(9)) { + _nAdaptToXYZD50(wx, wy, it) + } + requireNotNull(array) { "Cannot find transformation from the white point to D50" } + return Matrix33(*array) + } + + /** + * Returns a toXYZD50 matrix to convert RGB color into XYZ adapted to D50, + * given the primaries and whitepoint of the RGB model. + * Use it to create a color space. + * + * @throws IllegalArgumentException If the primaries or white point are invalid. + */ + fun makePrimariesToXYZD50( + rx: Float, ry: Float, gx: Float, gy: Float, bx: Float, by: Float, wx: Float, wy: Float + ): Matrix33 { + Stats.onNativeCall() + val array = withNullableResult(FloatArray(9)) { + _nPrimariesToXYZD50(rx, ry, gx, gy, bx, by, wx, wy, it) + } + requireNotNull(array) { "Cannot find transformation from the primaries and white point to XYZ D50" } + return Matrix33(*array) + } } init { @@ -271,3 +335,26 @@ class Matrix33(vararg mat: Float) { this.mat = mat } } + +@ExternalSymbolName("org_jetbrains_skia_Matrix33__1nGetSRGB") +private external fun _nGetSRGB(result: InteropPointer) + +@ExternalSymbolName("org_jetbrains_skia_Matrix33__1nGetAdobeRGB") +private external fun _nGetAdobeRGB(result: InteropPointer) + +@ExternalSymbolName("org_jetbrains_skia_Matrix33__1nGetDisplayP3") +private external fun _nGetDisplayP3(result: InteropPointer) + +@ExternalSymbolName("org_jetbrains_skia_Matrix33__1nGetRec2020") +private external fun _nGetRec2020(result: InteropPointer) + +@ExternalSymbolName("org_jetbrains_skia_Matrix33__1nGetXYZ") +private external fun _nGetXYZ(result: InteropPointer) + +@ExternalSymbolName("org_jetbrains_skia_Matrix33__1nAdaptToXYZD50") +private external fun _nAdaptToXYZD50(wx: Float, wy: Float, result: InteropPointer): Boolean + +@ExternalSymbolName("org_jetbrains_skia_Matrix33__1nPrimariesToXYZD50") +private external fun _nPrimariesToXYZD50( + rx: Float, ry: Float, gx: Float, gy: Float, bx: Float, by: Float, wx: Float, wy: Float, result: InteropPointer +): Boolean diff --git a/skiko/src/commonMain/kotlin/org/jetbrains/skia/TransferFunction.kt b/skiko/src/commonMain/kotlin/org/jetbrains/skia/TransferFunction.kt new file mode 100644 index 000000000..4f93cc2c2 --- /dev/null +++ b/skiko/src/commonMain/kotlin/org/jetbrains/skia/TransferFunction.kt @@ -0,0 +1,167 @@ +package org.jetbrains.skia + +import org.jetbrains.skia.impl.* +import org.jetbrains.skia.impl.Library.Companion.staticLoad + +/** + * A transfer function that maps encoded values to linear values, + * represented by this 7-parameter piecewise function: + * + * linear = sign(encoded) * (c*|encoded| + f) , 0 <= |encoded| < d + * = sign(encoded) * ((a*|encoded| + b)^g + e), d <= |encoded| + * + * A simple gamma transfer function sets g = gamma, a = 1, and the rest = 0. + */ +class TransferFunction( + val g: Float, + val a: Float, + val b: Float, + val c: Float, + val d: Float, + val e: Float, + val f: Float +) { + + companion object { + + init { + staticLoad() + } + + val sRGB = TransferFunction(withResult(FloatArray(7)) { _nGetSRGB(it) }) + val gamma2Dot2 = TransferFunction(withResult(FloatArray(7)) { _nGetGamma2Dot2(it) }) + val linear = TransferFunction(withResult(FloatArray(7)) { _nGetLinear(it) }) + val rec2020 = TransferFunction(withResult(FloatArray(7)) { _nGetRec2020(it) }) + val pq = TransferFunction(withResult(FloatArray(7)) { _nGetPQ(it) }) + val hlg = TransferFunction(withResult(FloatArray(7)) { _nGetHLG(it) }) + + /** + * General form of the SMPTE ST 2084 PQ function. + * + * max(A + B|encoded|^C, 0) + * linear = sign(encoded) * (------------------------) ^ F + * D + E|encoded|^C + */ + fun makePQish(A: Float, B: Float, C: Float, D: Float, E: Float, F: Float): TransferFunction { + Stats.onNativeCall() + return TransferFunction(withResult(FloatArray(7)) { + _nMakePQish(A, B, C, D, E, F, it) + }) + } + + /** + * General form of HLG. + * + * { K * sign(encoded) * ( (R|encoded|)^G ) when 0 <= |encoded| <= 1/R + * linear = { K * sign(encoded) * ( e^(a(|encoded|-c)) + b ) when 1/R < |encoded| + */ + fun makeScaledHLGish(K: Float, R: Float, G: Float, a: Float, b: Float, c: Float): TransferFunction { + Stats.onNativeCall() + return TransferFunction(withResult(FloatArray(7)) { + _nMakeScaledHLGish(K, R, G, a, b, c, it) + }) + } + + } + + val type: TransferFunctionType + get() = try { + Stats.onNativeCall() + TransferFunctionType.values()[interopScope { + _nGetType(toInterop(asArray())) + }] + } finally { + reachabilityBarrier(this) + } + + fun eval(x: Float): Float { + return try { + Stats.onNativeCall() + interopScope { + _nEval(toInterop(asArray()), x) + } + } finally { + reachabilityBarrier(this) + } + } + + fun invert(): TransferFunction? { + return try { + Stats.onNativeCall() + withNullableResult(FloatArray(7)) { + _nInvert(toInterop(asArray()), it) + }?.let { TransferFunction(it) } + } finally { + reachabilityBarrier(this) + } + } + + override fun equals(other: Any?): Boolean { + if (other === this) return true + if (other !is TransferFunction) return false + if (g != other.g) return false + if (a != other.a) return false + if (b != other.b) return false + if (c != other.c) return false + if (d != other.d) return false + if (e != other.e) return false + return f == other.f + } + + override fun hashCode(): Int { + val PRIME = 59 + var result = 1 + result = result * PRIME + g.toBits() + result = result * PRIME + a.toBits() + result = result * PRIME + b.toBits() + result = result * PRIME + c.toBits() + result = result * PRIME + d.toBits() + result = result * PRIME + e.toBits() + result = result * PRIME + f.toBits() + return result + } + + override fun toString(): String { + return "TransferFunction(_g=$g, _a=$a, _b=$b, _c=$c, _d=$d, _e=$e, _f=$f)" + } + + internal constructor(array: FloatArray) : this(array[0], array[1], array[2], array[3], array[4], array[5], array[6]) + + internal fun asArray() = floatArrayOf(g, a, b, c, d, e, f) + +} + +@ExternalSymbolName("org_jetbrains_skia_TransferFunction__1nGetSRGB") +private external fun _nGetSRGB(result: InteropPointer) + +@ExternalSymbolName("org_jetbrains_skia_TransferFunction__1nGetGamma2Dot2") +private external fun _nGetGamma2Dot2(result: InteropPointer) + +@ExternalSymbolName("org_jetbrains_skia_TransferFunction__1nGetLinear") +private external fun _nGetLinear(result: InteropPointer) + +@ExternalSymbolName("org_jetbrains_skia_TransferFunction__1nGetRec2020") +private external fun _nGetRec2020(result: InteropPointer) + +@ExternalSymbolName("org_jetbrains_skia_TransferFunction__1nGetPQ") +private external fun _nGetPQ(result: InteropPointer) + +@ExternalSymbolName("org_jetbrains_skia_TransferFunction__1nGetHLG") +private external fun _nGetHLG(result: InteropPointer) + +@ExternalSymbolName("org_jetbrains_skia_TransferFunction__1nMakePQish") +private external fun _nMakePQish(A: Float, B: Float, C: Float, D: Float, E: Float, F: Float, result: InteropPointer) + +@ExternalSymbolName("org_jetbrains_skia_TransferFunction__1nMakeScaledHLGish") +private external fun _nMakeScaledHLGish( + K: Float, R: Float, G: Float, a: Float, b: Float, c: Float, result: InteropPointer +) + +@ExternalSymbolName("org_jetbrains_skia_TransferFunction__1nGetType") +private external fun _nGetType(transferFunction: InteropPointer): Int + +@ExternalSymbolName("org_jetbrains_skia_TransferFunction__1nEval") +private external fun _nEval(transferFunction: InteropPointer, x: Float): Float + +@ExternalSymbolName("org_jetbrains_skia_TransferFunction__1nInvert") +private external fun _nInvert(transferFunction: InteropPointer, result: InteropPointer): Boolean diff --git a/skiko/src/commonMain/kotlin/org/jetbrains/skia/TransferFunctionType.kt b/skiko/src/commonMain/kotlin/org/jetbrains/skia/TransferFunctionType.kt new file mode 100644 index 000000000..13ee4a636 --- /dev/null +++ b/skiko/src/commonMain/kotlin/org/jetbrains/skia/TransferFunctionType.kt @@ -0,0 +1,9 @@ +package org.jetbrains.skia + +enum class TransferFunctionType { + INVALID, + SRGB_ISH, + PQ_ISH, + HLG_ISH, + HLG_INV_ISH; +} diff --git a/skiko/src/commonTest/kotlin/org/jetbrains/skia/ColorSpaceTest.kt b/skiko/src/commonTest/kotlin/org/jetbrains/skia/ColorSpaceTest.kt new file mode 100644 index 000000000..b20e832b3 --- /dev/null +++ b/skiko/src/commonTest/kotlin/org/jetbrains/skia/ColorSpaceTest.kt @@ -0,0 +1,74 @@ +package org.jetbrains.skia + +import kotlin.test.* + +class ColorSpaceTest { + + @Test + fun getPresets() { + ColorSpace.sRGB + ColorSpace.sRGBLinear + ColorSpace.displayP3 + } + + @Test + fun makeIllegal() { + assertFailsWith { + ColorSpace.makeRGB(TransferFunction(Float.NaN, Float.NaN, 0f, 0f, 0f, 0f, 0f), Matrix33.sRGBToXYZD50) + } + } + + @Test + fun isGammaCloseToSRGB() { + assertTrue(ColorSpace.sRGB.isGammaCloseToSRGB) + assertTrue(ColorSpace.makeRGB(TransferFunction.sRGB, Matrix33.displayP3ToXYZD50).isGammaCloseToSRGB) + assertFalse(ColorSpace.sRGBLinear.isGammaCloseToSRGB) + assertFalse(ColorSpace.makeRGB(TransferFunction.gamma2Dot2, Matrix33.adobeRGBToXYZD50).isGammaCloseToSRGB) + assertFalse(ColorSpace.makeRGB(TransferFunction.hlg, Matrix33.rec2020ToXYZD50).isGammaCloseToSRGB) + } + + @Test + fun isGammaLinear() { + assertTrue(ColorSpace.sRGBLinear.isGammaLinear) + assertTrue(ColorSpace.makeRGB(TransferFunction.linear, Matrix33.adobeRGBToXYZD50).isGammaLinear) + assertFalse(ColorSpace.sRGB.isGammaLinear) + assertFalse(ColorSpace.makeRGB(TransferFunction.gamma2Dot2, Matrix33.displayP3ToXYZD50).isGammaLinear) + assertFalse(ColorSpace.makeRGB(TransferFunction.pq, Matrix33.rec2020ToXYZD50).isGammaLinear) + } + + @Test + fun isSRGB() { + assertTrue(ColorSpace.sRGB.isSRGB) + assertTrue(ColorSpace.makeRGB(TransferFunction.sRGB, Matrix33.sRGBToXYZD50).isSRGB) + assertFalse(ColorSpace.sRGBLinear.isSRGB) + assertFalse(ColorSpace.makeRGB(TransferFunction.sRGB, Matrix33.adobeRGBToXYZD50).isSRGB) + assertFalse(ColorSpace.makeRGB(TransferFunction.gamma2Dot2, Matrix33.sRGBToXYZD50).isSRGB) + assertFalse(ColorSpace.makeRGB(TransferFunction.gamma2Dot2, Matrix33.adobeRGBToXYZD50).isSRGB) + } + + @Test + fun getTransferFunction() { + assertEquals(TransferFunction.sRGB, ColorSpace.sRGB.transferFunction) + assertEquals(TransferFunction.linear, ColorSpace.sRGBLinear.transferFunction) + assertEquals(TransferFunction.sRGB, ColorSpace.makeRGB(TransferFunction.sRGB, Matrix33.displayP3ToXYZD50).transferFunction) + assertEquals(TransferFunction.linear, ColorSpace.makeRGB(TransferFunction.linear, Matrix33.adobeRGBToXYZD50).transferFunction) + assertEquals(TransferFunction.gamma2Dot2, ColorSpace.makeRGB(TransferFunction.gamma2Dot2, Matrix33.rec2020ToXYZD50).transferFunction) + } + + @Test + fun getToXYZD50() { + assertEquals(Matrix33.sRGBToXYZD50, ColorSpace.sRGB.toXYZD50) + assertEquals(Matrix33.displayP3ToXYZD50, ColorSpace.displayP3.toXYZD50) + assertEquals(Matrix33.sRGBToXYZD50, ColorSpace.makeRGB(TransferFunction.rec2020, Matrix33.sRGBToXYZD50).toXYZD50) + assertEquals(Matrix33.adobeRGBToXYZD50, ColorSpace.makeRGB(TransferFunction.linear, Matrix33.adobeRGBToXYZD50).toXYZD50) + assertEquals(Matrix33.rec2020ToXYZD50, ColorSpace.makeRGB(TransferFunction.gamma2Dot2, Matrix33.rec2020ToXYZD50).toXYZD50) + } + + @Test + fun convert() { + val cs = ColorSpace.sRGB + val color = cs.convert(ColorSpace.sRGBLinear, Color4f(1f, 0f, 0f, 1f)) + assertNotEquals(color.r, 0f, 0.0001f) + } + +} diff --git a/skiko/src/commonTest/kotlin/org/jetbrains/skia/Matrix33Test.kt b/skiko/src/commonTest/kotlin/org/jetbrains/skia/Matrix33Test.kt new file mode 100644 index 000000000..197915e57 --- /dev/null +++ b/skiko/src/commonTest/kotlin/org/jetbrains/skia/Matrix33Test.kt @@ -0,0 +1,54 @@ +package org.jetbrains.skia + +import org.jetbrains.skia.tests.assertContentCloseEnough +import kotlin.test.Test +import kotlin.test.assertFailsWith + +class Matrix33Test { + + @Test + fun getToXYZD50Presets() { + Matrix33.sRGBToXYZD50 + Matrix33.adobeRGBToXYZD50 + Matrix33.displayP3ToXYZD50 + Matrix33.rec2020ToXYZD50 + Matrix33.xyzD50ToXYZD50 + } + + @Test + fun xyzD50ToXYZD50PresetCoefficients() { + assertContentCloseEnough(floatArrayOf(1f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 1f), Matrix33.xyzD50ToXYZD50.mat) + } + + @Test + fun makeXYZToXYZD50() { + val matD50 = Matrix33.makeXYZToXYZD50(0.34567f, 0.3585f).mat + assertContentCloseEnough(floatArrayOf(1f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 1f), matD50, 0.001f) + + val matD65 = Matrix33.makeXYZToXYZD50(0.31271f, 0.32902f).mat + val expD65 = floatArrayOf(1.0478f, 0.0229f, -0.0502f, 0.0295f, 0.9905f, -0.0171f, -0.0093f, 0.0151f, 0.7517f) + assertContentCloseEnough(expD65, matD65, 0.001f) + } + + @Test + fun makeIllegalXYZToXYZD50() { + assertFailsWith { + Matrix33.makeXYZToXYZD50(-1f, 0f) + } + } + + @Test + fun makePrimariesToXYZD50() { + val matSRGB = Matrix33.makePrimariesToXYZD50(0.64f, 0.33f, 0.3f, 0.6f, 0.15f, 0.06f, 0.31271f, 0.32902f).mat + val expSRGB = floatArrayOf(0.4360f, 0.3851f, 0.1431f, 0.2224f, 0.7169f, 0.0606f, 0.0139f, 0.0971f, 0.7139f) + assertContentCloseEnough(expSRGB, matSRGB, 0.001f) + } + + @Test + fun makeIllegalPrimariesToXYZD50() { + assertFailsWith { + Matrix33.makePrimariesToXYZD50(-1f, 0f, 0f, 0f, 0f, 0f, 0f, 0f) + } + } + +} diff --git a/skiko/src/commonTest/kotlin/org/jetbrains/skia/TransferFunctionTest.kt b/skiko/src/commonTest/kotlin/org/jetbrains/skia/TransferFunctionTest.kt new file mode 100644 index 000000000..945b73bff --- /dev/null +++ b/skiko/src/commonTest/kotlin/org/jetbrains/skia/TransferFunctionTest.kt @@ -0,0 +1,73 @@ +package org.jetbrains.skia + +import org.jetbrains.skia.tests.assertCloseEnough +import kotlin.test.Test +import kotlin.test.assertEquals + +class TransferFunctionTest { + + @Test + fun getPresets() { + TransferFunction.sRGB.type + TransferFunction.gamma2Dot2.type + TransferFunction.linear.type + TransferFunction.rec2020.type + TransferFunction.pq.type + TransferFunction.hlg.type + } + + @Test + fun linearPresetCoefficients() { + val tf = TransferFunction.linear + assertCloseEnough(1f, tf.g) + assertCloseEnough(1f, tf.a) + assertCloseEnough(0f, tf.b) + assertCloseEnough(0f, tf.c) + assertCloseEnough(0f, tf.d) + assertCloseEnough(0f, tf.e) + assertCloseEnough(0f, tf.f) + } + + @Test + fun gamma2Dot2PresetCoefficients() { + val tf = TransferFunction.gamma2Dot2 + assertCloseEnough(2.2f, tf.g) + assertCloseEnough(1f, tf.a) + assertCloseEnough(0f, tf.b) + assertCloseEnough(0f, tf.c) + assertCloseEnough(0f, tf.d) + assertCloseEnough(0f, tf.e) + assertCloseEnough(0f, tf.f) + } + + @Test + fun presetTypes() { + assertEquals(TransferFunctionType.SRGB_ISH, TransferFunction.sRGB.type) + assertEquals(TransferFunctionType.SRGB_ISH, TransferFunction.gamma2Dot2.type) + assertEquals(TransferFunctionType.SRGB_ISH, TransferFunction.linear.type) + assertEquals(TransferFunctionType.SRGB_ISH, TransferFunction.rec2020.type) + assertEquals(TransferFunctionType.PQ_ISH, TransferFunction.pq.type) + assertEquals(TransferFunctionType.HLG_ISH, TransferFunction.hlg.type) + } + + @Test + fun madeTypes() { + assertEquals(TransferFunctionType.PQ_ISH, TransferFunction.makePQish(-1f, 2f, 0.1f, 10f, -10f, 4f).type) + assertEquals(TransferFunctionType.HLG_ISH, TransferFunction.makeScaledHLGish(3f, 2f, 4f, 10f, 0.1f, 0.5f).type) + } + + @Test + fun eval() { + assertCloseEnough(0.1f, TransferFunction.linear.eval(0.1f), 0.0001f) + assertCloseEnough(0.214f, TransferFunction.sRGB.eval(0.5f), 0.0001f) + assertCloseEnough(0.2176f, TransferFunction.gamma2Dot2.eval(0.5f), 0.0001f) + } + + @Test + fun invertAndEval() { + assertCloseEnough(0.1f, TransferFunction.linear.invert()!!.eval(0.1f), 0.0001f) + assertCloseEnough(0.5f, TransferFunction.sRGB.invert()!!.eval(0.214f), 0.0001f) + assertCloseEnough(0.5f, TransferFunction.gamma2Dot2.invert()!!.eval(0.2176f), 0.0001f) + } + +} diff --git a/skiko/src/commonTest/kotlin/org/jetbrains/skiko/SkiaTest.kt b/skiko/src/commonTest/kotlin/org/jetbrains/skiko/SkiaTest.kt index 74e3e1909..2d98e8a09 100644 --- a/skiko/src/commonTest/kotlin/org/jetbrains/skiko/SkiaTest.kt +++ b/skiko/src/commonTest/kotlin/org/jetbrains/skiko/SkiaTest.kt @@ -1,19 +1,10 @@ package org.jetbrains.skiko -import org.jetbrains.skia.Color4f import org.jetbrains.skia.ColorFilter -import org.jetbrains.skia.ColorSpace import org.jetbrains.skia.impl.Native import kotlin.test.Test class SkiaTest { - @Test - fun `color_conversion`() { - val cs = ColorSpace.sRGB - val color = cs.convert(ColorSpace.sRGBLinear, Color4f(1f, 0f, 0f, 1f)) - require(color.r != 0f) - } - @Test fun `color_table`() { val array = ByteArray(256) diff --git a/skiko/src/jvmMain/cpp/common/ColorSpace.cc b/skiko/src/jvmMain/cpp/common/ColorSpace.cc index dd4b5050a..2106c3d7c 100644 --- a/skiko/src/jvmMain/cpp/common/ColorSpace.cc +++ b/skiko/src/jvmMain/cpp/common/ColorSpace.cc @@ -22,8 +22,15 @@ extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skia_ColorSpaceKt__1nMakeS return reinterpret_cast(ptr); } -extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skia_ColorSpaceKt__1nMakeDisplayP3(JNIEnv* env, jclass jclass) { - SkColorSpace* ptr = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDisplayP3).release(); +extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skia_ColorSpaceKt__1nMakeRGB + (JNIEnv* env, jclass jclass, jfloatArray jtransferFunction, jfloatArray jtoXYZD50) { + jfloat *tf = env->GetFloatArrayElements(jtransferFunction, 0); + jfloat *mat = env->GetFloatArrayElements(jtoXYZD50, 0); + skcms_TransferFunction transferFn = { tf[0], tf[1], tf[2], tf[3], tf[4], tf[5], tf[6] }; + skcms_Matrix3x3 toXYZ = {{{ mat[0], mat[1], mat[2] }, { mat[3], mat[4], mat[5] }, { mat[6], mat[7], mat[8] }}}; + SkColorSpace* ptr = SkColorSpace::MakeRGB(transferFn, toXYZ).release(); + env->ReleaseFloatArrayElements(jtransferFunction, tf, 0); + env->ReleaseFloatArrayElements(jtoXYZD50, mat, 0); return reinterpret_cast(ptr); } @@ -64,3 +71,32 @@ extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skia_ColorSpaceKt__1nIsSRG SkColorSpace* instance = reinterpret_cast(static_cast(ptr)); return instance->isSRGB(); } + +extern "C" JNIEXPORT void JNICALL Java_org_jetbrains_skia_ColorSpaceKt__1nGetTransferFunction + (JNIEnv* env, jclass jclass, jlong ptr, jfloatArray jresult) { + SkColorSpace* instance = reinterpret_cast(static_cast(ptr)); + skcms_TransferFunction tf; + instance->transferFn(&tf); + jfloat array[7] = { tf.g, tf.a, tf.b, tf.c, tf.d, tf.e, tf.f }; + env->SetFloatArrayRegion(jresult, 0, 7, array); +} + +extern "C" JNIEXPORT void JNICALL Java_org_jetbrains_skia_ColorSpaceKt__1nGetToXYZD50 + (JNIEnv* env, jclass jclass, jlong ptr, jfloatArray jresult) { + SkColorSpace* instance = reinterpret_cast(static_cast(ptr)); + skcms_Matrix3x3 toXYZ; + instance->toXYZD50(&toXYZ); + jfloat array[9] = { + toXYZ.vals[0][0], toXYZ.vals[0][1], toXYZ.vals[0][2], + toXYZ.vals[1][0], toXYZ.vals[1][1], toXYZ.vals[1][2], + toXYZ.vals[2][0], toXYZ.vals[2][1], toXYZ.vals[2][2] + }; + env->SetFloatArrayRegion(jresult, 0, 9, array); +} + +extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skia_ColorSpaceKt__1nEquals + (JNIEnv* env, jclass jclass, jlong ptr, jlong otherPtr) { + SkColorSpace* instance = reinterpret_cast(static_cast(ptr)); + SkColorSpace* other = reinterpret_cast(static_cast(otherPtr)); + return SkColorSpace::Equals(instance, other); +} diff --git a/skiko/src/jvmMain/cpp/common/Matrix33.cc b/skiko/src/jvmMain/cpp/common/Matrix33.cc new file mode 100644 index 000000000..7a02e5d4a --- /dev/null +++ b/skiko/src/jvmMain/cpp/common/Matrix33.cc @@ -0,0 +1,56 @@ +#include +#include +#include "SkColorSpace.h" +#include "interop.hh" + +static void copySkcmsMatrix3x3ToJFloatArray(JNIEnv* env, const skcms_Matrix3x3& matrix, jfloatArray& jresult) { + jfloat array[9] = { + matrix.vals[0][0], matrix.vals[0][1], matrix.vals[0][2], + matrix.vals[1][0], matrix.vals[1][1], matrix.vals[1][2], + matrix.vals[2][0], matrix.vals[2][1], matrix.vals[2][2] + }; + env->SetFloatArrayRegion(jresult, 0, 9, array); +} + +extern "C" JNIEXPORT void JNICALL Java_org_jetbrains_skia_Matrix33Kt__1nGetSRGB + (JNIEnv* env, jclass jclass, jfloatArray jresult) { + copySkcmsMatrix3x3ToJFloatArray(env, SkNamedGamut::kSRGB, jresult); +} + +extern "C" JNIEXPORT void JNICALL Java_org_jetbrains_skia_Matrix33Kt__1nGetAdobeRGB + (JNIEnv* env, jclass jclass, jfloatArray jresult) { + copySkcmsMatrix3x3ToJFloatArray(env, SkNamedGamut::kAdobeRGB, jresult); +} + +extern "C" JNIEXPORT void JNICALL Java_org_jetbrains_skia_Matrix33Kt__1nGetDisplayP3 + (JNIEnv* env, jclass jclass, jfloatArray jresult) { + copySkcmsMatrix3x3ToJFloatArray(env, SkNamedGamut::kDisplayP3, jresult); +} + +extern "C" JNIEXPORT void JNICALL Java_org_jetbrains_skia_Matrix33Kt__1nGetRec2020 + (JNIEnv* env, jclass jclass, jfloatArray jresult) { + copySkcmsMatrix3x3ToJFloatArray(env, SkNamedGamut::kRec2020, jresult); +} + +extern "C" JNIEXPORT void JNICALL Java_org_jetbrains_skia_Matrix33Kt__1nGetXYZ + (JNIEnv* env, jclass jclass, jfloatArray jresult) { + copySkcmsMatrix3x3ToJFloatArray(env, SkNamedGamut::kXYZ, jresult); +} + +extern "C" JNIEXPORT jboolean JNICALL Java_org_jetbrains_skia_Matrix33Kt__1nAdaptToXYZD50 + (JNIEnv* env, jclass jclass, jfloat wx, jfloat wy, jfloatArray jresult) { + skcms_Matrix3x3 toXYZD50; + bool success = skcms_AdaptToXYZD50(wx, wy, &toXYZD50); + if (success) + copySkcmsMatrix3x3ToJFloatArray(env, toXYZD50, jresult); + return success; +} + +extern "C" JNIEXPORT jboolean JNICALL Java_org_jetbrains_skia_Matrix33Kt__1nPrimariesToXYZD50 + (JNIEnv* env, jclass jclass, jfloat rx, jfloat ry, jfloat gx, jfloat gy, jfloat bx, jfloat by, jfloat wx, jfloat wy, jfloatArray jresult) { + skcms_Matrix3x3 toXYZD50; + bool success = skcms_PrimariesToXYZD50(rx, ry, gx, gy, bx, by, wx, wy, &toXYZD50); + if (success) + copySkcmsMatrix3x3ToJFloatArray(env, toXYZD50, jresult); + return success; +} diff --git a/skiko/src/jvmMain/cpp/common/TransferFunction.cc b/skiko/src/jvmMain/cpp/common/TransferFunction.cc new file mode 100644 index 000000000..5fc5b4cc8 --- /dev/null +++ b/skiko/src/jvmMain/cpp/common/TransferFunction.cc @@ -0,0 +1,88 @@ +#include +#include +#include "SkColorSpace.h" +#include "interop.hh" + +static void copySkcmsTransferFunctionToJFloatArray(JNIEnv* env, const skcms_TransferFunction& tf, jfloatArray& jresult) { + jfloat array[7] = { tf.g, tf.a, tf.b, tf.c, tf.d, tf.e, tf.f }; + env->SetFloatArrayRegion(jresult, 0, 7, array); +} + +static void copyJFloatArrayToSkcmsTransferFunction(JNIEnv* env, jfloatArray& jtransferFunction, skcms_TransferFunction* result) { + jfloat* tf = env->GetFloatArrayElements(jtransferFunction, 0); + *result = { tf[0], tf[1], tf[2], tf[3], tf[4], tf[5], tf[6] }; + env->ReleaseFloatArrayElements(jtransferFunction, tf, 0); +} + +extern "C" JNIEXPORT void JNICALL Java_org_jetbrains_skia_TransferFunctionKt__1nGetSRGB + (JNIEnv* env, jclass jclass, jfloatArray jresult) { + copySkcmsTransferFunctionToJFloatArray(env, SkNamedTransferFn::kSRGB, jresult); +} + +extern "C" JNIEXPORT void JNICALL Java_org_jetbrains_skia_TransferFunctionKt__1nGetGamma2Dot2 + (JNIEnv* env, jclass jclass, jfloatArray jresult) { + copySkcmsTransferFunctionToJFloatArray(env, SkNamedTransferFn::k2Dot2, jresult); +} + +extern "C" JNIEXPORT void JNICALL Java_org_jetbrains_skia_TransferFunctionKt__1nGetLinear + (JNIEnv* env, jclass jclass, jfloatArray jresult) { + copySkcmsTransferFunctionToJFloatArray(env, SkNamedTransferFn::kLinear, jresult); +} + +extern "C" JNIEXPORT void JNICALL Java_org_jetbrains_skia_TransferFunctionKt__1nGetRec2020 + (JNIEnv* env, jclass jclass, jfloatArray jresult) { + copySkcmsTransferFunctionToJFloatArray(env, SkNamedTransferFn::kRec2020, jresult); +} + +extern "C" JNIEXPORT void JNICALL Java_org_jetbrains_skia_TransferFunctionKt__1nGetPQ + (JNIEnv* env, jclass jclass, jfloatArray jresult) { + copySkcmsTransferFunctionToJFloatArray(env, SkNamedTransferFn::kPQ, jresult); +} + +extern "C" JNIEXPORT void JNICALL Java_org_jetbrains_skia_TransferFunctionKt__1nGetHLG + (JNIEnv* env, jclass jclass, jfloatArray jresult) { + copySkcmsTransferFunctionToJFloatArray(env, SkNamedTransferFn::kHLG, jresult); +} + +extern "C" JNIEXPORT jboolean JNICALL Java_org_jetbrains_skia_TransferFunctionKt__1nMakePQish + (JNIEnv* env, jclass jclass, jfloat A, jfloat B, jfloat C, jfloat D, jfloat E, jfloat F, jfloatArray jresult) { + skcms_TransferFunction transferFn; + bool success = skcms_TransferFunction_makePQish(&transferFn, A, B, C, D, E, F); + if (success) + copySkcmsTransferFunctionToJFloatArray(env, transferFn, jresult); + return success; +} + +extern "C" JNIEXPORT jboolean JNICALL Java_org_jetbrains_skia_TransferFunctionKt__1nMakeScaledHLGish + (JNIEnv* env, jclass jclass, jfloat K, jfloat R, jfloat G, jfloat a, jfloat b, jfloat c, jfloatArray jresult) { + skcms_TransferFunction transferFn; + bool success = skcms_TransferFunction_makeScaledHLGish(&transferFn, K, R, G, a, b, c); + if (success) + copySkcmsTransferFunctionToJFloatArray(env, transferFn, jresult); + return success; +} + +extern "C" JNIEXPORT jint JNICALL Java_org_jetbrains_skia_TransferFunctionKt__1nGetType + (JNIEnv* env, jclass jclass, jfloatArray jtransferFunction) { + skcms_TransferFunction transferFn; + copyJFloatArrayToSkcmsTransferFunction(env, jtransferFunction, &transferFn); + return static_cast(skcms_TransferFunction_getType(&transferFn)); +} + +extern "C" JNIEXPORT jint JNICALL Java_org_jetbrains_skia_TransferFunctionKt__1nEval + (JNIEnv* env, jclass jclass, jfloatArray jtransferFunction, jfloat x) { + skcms_TransferFunction transferFn; + copyJFloatArrayToSkcmsTransferFunction(env, jtransferFunction, &transferFn); + return skcms_TransferFunction_eval(&transferFn, x); +} + +extern "C" JNIEXPORT jboolean JNICALL Java_org_jetbrains_skia_TransferFunctionKt__1nInvert + (JNIEnv* env, jclass jclass, jfloatArray jtransferFunction, jfloatArray jresult) { + skcms_TransferFunction transferFn; + skcms_TransferFunction resultTransferFn; + copyJFloatArrayToSkcmsTransferFunction(env, jtransferFunction, &transferFn); + bool success = skcms_TransferFunction_invert(&transferFn, &resultTransferFn); + if (success) + copySkcmsTransferFunctionToJFloatArray(env, resultTransferFn, jresult); + return success; +} diff --git a/skiko/src/nativeJsMain/cpp/ColorSpace.cc b/skiko/src/nativeJsMain/cpp/ColorSpace.cc index eb33f3cab..fb655b571 100644 --- a/skiko/src/nativeJsMain/cpp/ColorSpace.cc +++ b/skiko/src/nativeJsMain/cpp/ColorSpace.cc @@ -21,8 +21,11 @@ SKIKO_EXPORT KNativePointer org_jetbrains_skia_ColorSpace__1nMakeSRGBLinear() { return reinterpret_cast(ptr); } -SKIKO_EXPORT KNativePointer org_jetbrains_skia_ColorSpace__1nMakeDisplayP3() { - SkColorSpace* ptr = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDisplayP3).release(); +SKIKO_EXPORT KNativePointer org_jetbrains_skia_ColorSpace__1nMakeRGB + (KFloat* tf, KFloat* mat) { + skcms_TransferFunction transferFn = { tf[0], tf[1], tf[2], tf[3], tf[4], tf[5], tf[6] }; + skcms_Matrix3x3 toXYZ = {{{ mat[0], mat[1], mat[2] }, { mat[3], mat[4], mat[5] }, { mat[6], mat[7], mat[8] }}}; + SkColorSpace* ptr = SkColorSpace::MakeRGB(transferFn, toXYZ).release(); return reinterpret_cast(ptr); } @@ -62,3 +65,42 @@ SKIKO_EXPORT KBoolean org_jetbrains_skia_ColorSpace__1nIsSRGB SkColorSpace* instance = reinterpret_cast((ptr)); return instance->isSRGB(); } + + +SKIKO_EXPORT void org_jetbrains_skia_ColorSpace__1nGetTransferFunction + (KNativePointer ptr, KFloat* result) { + SkColorSpace* instance = reinterpret_cast((ptr)); + skcms_TransferFunction tf; + instance->transferFn(&tf); + result[0] = tf.g; + result[1] = tf.a; + result[2] = tf.b; + result[3] = tf.c; + result[4] = tf.d; + result[5] = tf.e; + result[6] = tf.f; +} + +SKIKO_EXPORT void org_jetbrains_skia_ColorSpace__1nGetToXYZD50 + (KNativePointer ptr, KFloat* result) { + SkColorSpace* instance = reinterpret_cast((ptr)); + skcms_Matrix3x3 toXYZ; + instance->toXYZD50(&toXYZ); + result[0] = toXYZ.vals[0][0]; + result[1] = toXYZ.vals[0][1]; + result[2] = toXYZ.vals[0][2]; + result[3] = toXYZ.vals[1][0]; + result[4] = toXYZ.vals[1][1]; + result[5] = toXYZ.vals[1][2]; + result[6] = toXYZ.vals[2][0]; + result[7] = toXYZ.vals[2][1]; + result[8] = toXYZ.vals[2][2]; +} + + +SKIKO_EXPORT KBoolean org_jetbrains_skia_ColorSpace__1nEquals + (KNativePointer ptr, KNativePointer otherPtr) { + SkColorSpace* instance = reinterpret_cast((ptr)); + SkColorSpace* other = reinterpret_cast((otherPtr)); + return SkColorSpace::Equals(instance, other); +} diff --git a/skiko/src/nativeJsMain/cpp/Matrix33.cc b/skiko/src/nativeJsMain/cpp/Matrix33.cc new file mode 100644 index 000000000..4dc8e622e --- /dev/null +++ b/skiko/src/nativeJsMain/cpp/Matrix33.cc @@ -0,0 +1,58 @@ +#include +#include "SkColorSpace.h" +#include "common.h" + +static void copySkcmsMatrix3x3ToKFloatArray(const skcms_Matrix3x3& matrix, KFloat* result) { + result[0] = matrix.vals[0][0]; + result[1] = matrix.vals[0][1]; + result[2] = matrix.vals[0][2]; + result[3] = matrix.vals[1][0]; + result[4] = matrix.vals[1][1]; + result[5] = matrix.vals[1][2]; + result[6] = matrix.vals[2][0]; + result[7] = matrix.vals[2][1]; + result[8] = matrix.vals[2][2]; +} + +SKIKO_EXPORT void org_jetbrains_skia_Matrix33__1nGetSRGB + (KFloat* result) { + copySkcmsMatrix3x3ToKFloatArray(SkNamedGamut::kSRGB, result); +} + +SKIKO_EXPORT void org_jetbrains_skia_Matrix33__1nGetAdobeRGB + (KFloat* result) { + copySkcmsMatrix3x3ToKFloatArray(SkNamedGamut::kAdobeRGB, result); +} + +SKIKO_EXPORT void org_jetbrains_skia_Matrix33__1nGetDisplayP3 + (KFloat* result) { + copySkcmsMatrix3x3ToKFloatArray(SkNamedGamut::kDisplayP3, result); +} + +SKIKO_EXPORT void org_jetbrains_skia_Matrix33__1nGetRec2020 + (KFloat* result) { + copySkcmsMatrix3x3ToKFloatArray(SkNamedGamut::kRec2020, result); +} + +SKIKO_EXPORT void org_jetbrains_skia_Matrix33__1nGetXYZ + (KFloat* result) { + copySkcmsMatrix3x3ToKFloatArray(SkNamedGamut::kXYZ, result); +} + +SKIKO_EXPORT KBoolean org_jetbrains_skia_Matrix33__1nAdaptToXYZD50 + (KFloat wx, KFloat wy, KFloat* result) { + skcms_Matrix3x3 toXYZD50; + bool success = skcms_AdaptToXYZD50(wx, wy, &toXYZD50); + if (success) + copySkcmsMatrix3x3ToKFloatArray(toXYZD50, result); + return success; +} + +SKIKO_EXPORT KBoolean org_jetbrains_skia_Matrix33__1nPrimariesToXYZD50 + (KFloat rx, KFloat ry, KFloat gx, KFloat gy, KFloat bx, KFloat by, KFloat wx, KFloat wy, KFloat* result) { + skcms_Matrix3x3 toXYZD50; + bool success = skcms_PrimariesToXYZD50(rx, ry, gx, gy, bx, by, wx, wy, &toXYZD50); + if (success) + copySkcmsMatrix3x3ToKFloatArray(toXYZD50, result); + return success; +} diff --git a/skiko/src/nativeJsMain/cpp/TransferFunction.cc b/skiko/src/nativeJsMain/cpp/TransferFunction.cc new file mode 100644 index 000000000..7cdf45e30 --- /dev/null +++ b/skiko/src/nativeJsMain/cpp/TransferFunction.cc @@ -0,0 +1,90 @@ +#include +#include "SkColorSpace.h" +#include "common.h" + +static void copySkcmsTransferFunctionToKFloatArray(const skcms_TransferFunction& tf, KFloat* result) { + result[0] = tf.g; + result[1] = tf.a; + result[2] = tf.b; + result[3] = tf.c; + result[4] = tf.d; + result[5] = tf.e; + result[6] = tf.f; +} + +static void copyKFloatArrayToSkcmsTransferFunction(KFloat* tf, skcms_TransferFunction* result) { + *result = { tf[0], tf[1], tf[2], tf[3], tf[4], tf[5], tf[6] }; +} + +SKIKO_EXPORT void org_jetbrains_skia_TransferFunction__1nGetSRGB + (KFloat* result) { + copySkcmsTransferFunctionToKFloatArray(SkNamedTransferFn::kSRGB, result); +} + +SKIKO_EXPORT void org_jetbrains_skia_TransferFunction__1nGetGamma2Dot2 + (KFloat* result) { + copySkcmsTransferFunctionToKFloatArray(SkNamedTransferFn::k2Dot2, result); +} + +SKIKO_EXPORT void org_jetbrains_skia_TransferFunction__1nGetLinear + (KFloat* result) { + copySkcmsTransferFunctionToKFloatArray(SkNamedTransferFn::kLinear, result); +} + +SKIKO_EXPORT void org_jetbrains_skia_TransferFunction__1nGetRec2020 + (KFloat* result) { + copySkcmsTransferFunctionToKFloatArray(SkNamedTransferFn::kRec2020, result); +} + +SKIKO_EXPORT void org_jetbrains_skia_TransferFunction__1nGetPQ + (KFloat* result) { + copySkcmsTransferFunctionToKFloatArray(SkNamedTransferFn::kPQ, result); +} + +SKIKO_EXPORT void org_jetbrains_skia_TransferFunction__1nGetHLG + (KFloat* result) { + copySkcmsTransferFunctionToKFloatArray(SkNamedTransferFn::kHLG, result); +} + +SKIKO_EXPORT KBoolean org_jetbrains_skia_TransferFunction__1nMakePQish + (KFloat A, KFloat B, KFloat C, KFloat D, KFloat E, KFloat F, KFloat* result) { + skcms_TransferFunction transferFn; + bool success = skcms_TransferFunction_makePQish(&transferFn, A, B, C, D, E, F); + if (success) + copySkcmsTransferFunctionToKFloatArray(transferFn, result); + return success; +} + +SKIKO_EXPORT KBoolean org_jetbrains_skia_TransferFunction__1nMakeScaledHLGish + (KFloat K, KFloat R, KFloat G, KFloat a, KFloat b, KFloat c, KFloat* result) { + skcms_TransferFunction transferFn; + bool success = skcms_TransferFunction_makeScaledHLGish(&transferFn, K, R, G, a, b, c); + if (success) + copySkcmsTransferFunctionToKFloatArray(transferFn, result); + return success; +} + +SKIKO_EXPORT KInt org_jetbrains_skia_TransferFunction__1nGetType + (KFloat* transferFunction) { + skcms_TransferFunction transferFn; + copyKFloatArrayToSkcmsTransferFunction(transferFunction, &transferFn); + return static_cast(skcms_TransferFunction_getType(&transferFn)); +} + +SKIKO_EXPORT KInt org_jetbrains_skia_TransferFunction__1nEval + (KFloat* transferFunction, KFloat x) { + skcms_TransferFunction transferFn; + copyKFloatArrayToSkcmsTransferFunction(transferFunction, &transferFn); + return skcms_TransferFunction_eval(&transferFn, x); +} + +SKIKO_EXPORT KBoolean org_jetbrains_skia_TransferFunction__1nInvert + (KFloat* transferFunction, KFloat* result) { + skcms_TransferFunction transferFn; + skcms_TransferFunction resultTransferFn; + copyKFloatArrayToSkcmsTransferFunction(transferFunction, &transferFn); + bool success = skcms_TransferFunction_invert(&transferFn, &resultTransferFn); + if (success) + copySkcmsTransferFunctionToKFloatArray(resultTransferFn, result); + return success; +}