diff --git a/tests/color_util.c b/tests/color_util.c index 52250781..511b8d9e 100644 --- a/tests/color_util.c +++ b/tests/color_util.c @@ -23,18 +23,90 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - #include "config.h" #include #include "color_util.h" #include +#include +#include +#include "shared/helpers.h" + +struct color_tone_curve { + enum transfer_fn fn; + enum transfer_fn inv_fn; + + /* LCMS2 API */ + int internal_type; + double param[5]; +}; + +const struct color_tone_curve arr_curves[] = { + { + .fn = TRANSFER_FN_SRGB_EOTF, + .inv_fn = TRANSFER_FN_SRGB_EOTF_INVERSE, + .internal_type = 4, + .param = { 2.4, 1. / 1.055, 0.055 / 1.055, 1. / 12.92, 0.04045 } , + }, + { + .fn = TRANSFER_FN_ADOBE_RGB_EOTF, + .inv_fn = TRANSFER_FN_ADOBE_RGB_EOTF_INVERSE, + .internal_type = 1, + .param = { 563./256., 0.0, 0.0, 0.0 , 0.0 } , + }, + { + .fn = TRANSFER_FN_POWER2_4_EOTF, + .inv_fn = TRANSFER_FN_POWER2_4_EOTF_INVERSE, + .internal_type = 1, + .param = { 2.4, 0.0, 0.0, 0.0 , 0.0 } , + } + +}; + +bool +find_tone_curve_type(enum transfer_fn fn, int *type, double params[5]) +{ + const int size_arr = ARRAY_LENGTH(arr_curves); + const struct color_tone_curve *curve; + for (curve = &arr_curves[0]; curve < &arr_curves[size_arr]; curve++ ) { + if (curve->fn == fn ) + *type = curve->internal_type; + else if (curve->inv_fn == fn) + *type = -curve->internal_type; + else + continue; + + memcpy(params, curve->param, sizeof(curve->param)); + return true; + } + + return false; +} + +/** + * NaN comes out as is + *This function is not intended for hiding NaN. + */ static float -sRGB_EOTF(float e) +ensure_unit_range(float v) { - assert(e >= 0.0f); - assert(e <= 1.0f); + const float tol = 1e-5f; + const float lim_lo = -tol; + const float lim_hi = 1.0f + tol; + assert(v >= lim_lo); + if (v < 0.0f) + return 0.0f; + assert(v <= lim_hi); + if (v > 1.0f) + return 1.0f; + return v; +} + +static float +sRGB_EOTF(float e) +{ + e = ensure_unit_range(e); if (e <= 0.04045) return e / 12.92; else @@ -44,15 +116,40 @@ sRGB_EOTF(float e) static float sRGB_EOTF_inv(float o) { - assert(o >= 0.0f); - assert(o <= 1.0f); - + o = ensure_unit_range(o); if (o <= 0.04045 / 12.92) return o * 12.92; else return pow(o, 1.0 / 2.4) * 1.055 - 0.055; } +static float +AdobeRGB_EOTF(float e) +{ + e = ensure_unit_range(e); + return pow(e, 563./256.); +} + +static float +AdobeRGB_EOTF_inv(float o) +{ + o = ensure_unit_range(o); + return pow(o, 256./563.); +} + +static float +Power2_4_EOTF(float e) +{ + e = ensure_unit_range(e); + return pow(e, 2.4); +} + +static float +Power2_4_EOTF_inv(float o) +{ + o = ensure_unit_range(o); + return pow(o, 1./2.4); +} void sRGB_linearize(struct color_float *cf) @@ -62,6 +159,35 @@ sRGB_linearize(struct color_float *cf) cf->b = sRGB_EOTF(cf->b); } +static float +apply_tone_curve(enum transfer_fn fn, float r) +{ + float ret = 0; + + switch(fn) { + case TRANSFER_FN_SRGB_EOTF: + ret = sRGB_EOTF(r); + break; + case TRANSFER_FN_SRGB_EOTF_INVERSE: + ret = sRGB_EOTF_inv(r); + break; + case TRANSFER_FN_ADOBE_RGB_EOTF: + ret = AdobeRGB_EOTF(r); + break; + case TRANSFER_FN_ADOBE_RGB_EOTF_INVERSE: + ret = AdobeRGB_EOTF_inv(r); + break; + case TRANSFER_FN_POWER2_4_EOTF: + ret = Power2_4_EOTF(r); + break; + case TRANSFER_FN_POWER2_4_EOTF_INVERSE: + ret = Power2_4_EOTF_inv(r); + break; + } + + return ret; +} + void sRGB_delinearize(struct color_float *cf) { @@ -82,3 +208,37 @@ a8r8g8b8_to_float(uint32_t v) return cf; } + +void +process_pixel_using_pipeline(enum transfer_fn pre_curve, + const struct lcmsMAT3 *mat, + enum transfer_fn post_curve, + const struct color_float *in, + struct color_float *out) +{ + int i, j; + float rgb_in[3]; + float out_blend[3]; + float tmp; + + rgb_in[0] = in->r; + rgb_in[1] = in->g; + rgb_in[2] = in->b; + + for (i = 0; i < 3; i++) + rgb_in[i] = apply_tone_curve(pre_curve, rgb_in[i]); + + for (i = 0; i < 3; i++) { + tmp = 0.0f; + for (j = 0; j < 3; j++) + tmp += rgb_in[j] * mat->v[j].n[i]; + out_blend[i] = tmp; + } + + for (i = 0; i < 3; i++) + out_blend[i] = apply_tone_curve(post_curve, out_blend[i]); + + out->r = out_blend[0]; + out->g = out_blend[1]; + out->b = out_blend[2]; +} diff --git a/tests/color_util.h b/tests/color_util.h index a9cfd02d..96f50049 100644 --- a/tests/color_util.h +++ b/tests/color_util.h @@ -25,18 +25,62 @@ */ #include - +#include struct color_float { float r, g, b, a; }; +struct lcmsVEC3 { + float n[3]; +}; + +struct lcmsMAT3 { + struct lcmsVEC3 v[3]; +}; + +enum transfer_fn { + TRANSFER_FN_SRGB_EOTF, + TRANSFER_FN_SRGB_EOTF_INVERSE, + TRANSFER_FN_ADOBE_RGB_EOTF, + TRANSFER_FN_ADOBE_RGB_EOTF_INVERSE, + TRANSFER_FN_POWER2_4_EOTF, + TRANSFER_FN_POWER2_4_EOTF_INVERSE, +}; + +/* + * A helper to lay out a matrix in the natural writing order in code + * instead of needing to transpose in your mind every time you read it. + * The matrix is laid out as written: + * ⎡ a11 a12 a13 ⎤ + * ⎢ a21 a22 a23 ⎥ + * ⎣ a31 a32 a33 ⎦ + * where the first digit is row and the second digit is column. + */ +#define LCMSMAT3(a11, a12, a13, \ + a21, a22, a23, \ + a31, a32, a33) ((struct lcmsMAT3) \ + { /* Each vector is a column => looks like a transpose */ \ + .v[0] = { .n = { a11, a21, a31} }, \ + .v[1] = { .n = { a12, a22, a32} }, \ + .v[2] = { .n = { a13, a23, a33} }, \ + }) + void sRGB_linearize(struct color_float *cf); void sRGB_delinearize(struct color_float *cf); - struct color_float a8r8g8b8_to_float(uint32_t v); + +bool +find_tone_curve_type(enum transfer_fn fn, int *type, double params[5]); + +void +process_pixel_using_pipeline(enum transfer_fn pre_curve, + const struct lcmsMAT3 *mat, + enum transfer_fn post_curve, + const struct color_float *in, + struct color_float *out);