From efd3c1c2b42ffda0c8f7e5cd9b03fba07eead1ea Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 28 May 2011 18:05:05 -0600 Subject: Handle sRGB gamma correctly. --- libdimension/color.c | 95 ++++++++++++++++++++++++++++++++++++--- libdimension/color_map.c | 17 ++++++- libdimension/dimension/color.h | 4 ++ libdimension/dimension/pigments.h | 14 +++++- libdimension/gl.c | 9 +++- libdimension/pigment_map.c | 17 ++++++- libdimension/png.c | 6 ++- libdimension/tests/render.c | 24 ++++++---- 8 files changed, 162 insertions(+), 24 deletions(-) (limited to 'libdimension') diff --git a/libdimension/color.c b/libdimension/color.c index 6de8d0a..eea005b 100644 --- a/libdimension/color.c +++ b/libdimension/color.c @@ -77,7 +77,7 @@ const dmnsn_color dmnsn_magenta = { }; const dmnsn_color dmnsn_orange = { .R = 1.0, - .G = 0.5, + .G = 0.21404114048223255, .B = 0.0, .trans = 0.0, .filter = 0.0, @@ -97,6 +97,75 @@ const dmnsn_color dmnsn_cyan = { .trans = 0.0, }; +/** Inverse function of sRGB's `C' function, for the reverse conversion. */ +static double +dmnsn_sRGB_C_inv(double CsRGB) +{ + /* + * If C represents R, G, and B, then the Clinear values are now found as + * follows: + * + * { Csrgb/12.92, Csrgb <= 0.04045 + * Clinear = { 1/2.4 + * { ((Csrgb + 0.055)/1.055) , Csrgb > 0.04045 + */ + if (CsRGB == 1.0) { + return 1.0; /* Map 1.0 to 1.0 instead of 0.9999999999999999 */ + } else if (CsRGB <= 0.040449936) { + return CsRGB/12.92; + } else { + return pow((CsRGB + 0.055)/1.055, 2.4); + } +} + +/* Convert from sRGB space */ +dmnsn_color +dmnsn_color_from_sRGB(dmnsn_color color) +{ + dmnsn_color ret = { + .R = dmnsn_sRGB_C_inv(color.R), + .G = dmnsn_sRGB_C_inv(color.G), + .B = dmnsn_sRGB_C_inv(color.B), + .trans = color.trans, + .filter = color.filter, + }; + return ret; +} + +/** sRGB's `C' function. */ +static double +dmnsn_sRGB_C(double Clinear) +{ + /* + * If C represents R, G, and B, then the sRGB values are now found as follows: + * + * { 12.92*Clinear, Clinear <= 0.0031308 + * Csrgb = { 1/2.4 + * { (1.055)*Clinear - 0.055, Clinear > 0.0031308 + */ + if (Clinear == 1.0) { + return 1.0; /* Map 1.0 to 1.0 instead of 0.9999999999999999 */ + } else if (Clinear <= 0.0031308) { + return 12.92*Clinear; + } else { + return 1.055*pow(Clinear, 1.0/2.4) - 0.055; + } +} + +/* Convert to sRGB space */ +dmnsn_color +dmnsn_color_to_sRGB(dmnsn_color color) +{ + dmnsn_color ret = { + .R = dmnsn_sRGB_C(color.R), + .G = dmnsn_sRGB_C(color.G), + .B = dmnsn_sRGB_C(color.B), + .trans = color.trans, + .filter = color.filter, + }; + return ret; +} + /* Greyscale color intensity */ double dmnsn_color_intensity(dmnsn_color color) @@ -111,7 +180,7 @@ dmnsn_color_add(dmnsn_color c1, dmnsn_color c2) { dmnsn_color ret = dmnsn_new_color(c1.R + c2.R, c1.G + c2.G, c1.B + c2.B); - /* Switch back into absolute filter and transmittance space */ + /* Switch into absolute filter and transmittance space */ double n1 = dmnsn_color_intensity(c1), n2 = dmnsn_color_intensity(c2); double f1 = c1.filter*c1.trans, f2 = c2.filter*c2.trans; double t1 = c1.trans - f1, t2 = c2.trans - f2; @@ -142,13 +211,25 @@ dmnsn_color_mul(double n, dmnsn_color color) dmnsn_color dmnsn_color_gradient(dmnsn_color c1, dmnsn_color c2, double n) { - return dmnsn_new_color5( + dmnsn_color ret = dmnsn_new_color( n*(c2.R - c1.R) + c1.R, n*(c2.G - c1.G) + c1.G, - n*(c2.B - c1.B) + c1.B, - n*(c2.trans - c1.trans) + c1.trans, - n*(c2.filter - c1.filter) + c1.filter + n*(c2.B - c1.B) + c1.B ); + + /* Switch into absolute filter and transmittance space */ + double f1 = c1.filter*c1.trans, f2 = c2.filter*c2.trans; + double t1 = c1.trans - f1, t2 = c2.trans - f2; + double f = n*(f2 - f1) + f1; + double t = n*(t2 - t1) + t1; + + /* Switch back */ + ret.trans = f + t; + ret.filter = 0.0; + if (ret.trans >= dmnsn_epsilon) + ret.filter = f/ret.trans; + + return ret; } /* Filters `light' through `filter' */ @@ -165,7 +246,7 @@ dmnsn_filter_light(dmnsn_color light, dmnsn_color filter) ); dmnsn_color ret = dmnsn_color_add(transmitted, filtered); - /* Switch back into absolute filter and transmittance space */ + /* Switch into absolute filter and transmittance space */ double lf = light.filter*light.trans, ff = filter.filter*filter.trans; double lt = light.trans - lf, ft = filter.trans - ff; double f = lf*(dmnsn_color_intensity(filtered) + ft) + lt*ff; diff --git a/libdimension/color_map.c b/libdimension/color_map.c index 6390fe5..e7a25a6 100644 --- a/libdimension/color_map.c +++ b/libdimension/color_map.c @@ -35,6 +35,7 @@ dmnsn_new_color_map(void) typedef struct dmnsn_color_map_payload { dmnsn_pattern *pattern; dmnsn_map *map; + dmnsn_pigment_map_flags flags; } dmnsn_color_map_payload; /** Free a color_map payload. */ @@ -56,7 +57,17 @@ dmnsn_color_map_pigment_fn(const dmnsn_pigment *pigment, dmnsn_vector v) dmnsn_color color1, color2; dmnsn_evaluate_map(payload->map, dmnsn_pattern_value(payload->pattern, v), &n, &color1, &color2); - return dmnsn_color_gradient(color1, color2, n); + + if (payload->flags == DMNSN_PIGMENT_MAP_SRGB) { + color1 = dmnsn_color_to_sRGB(color1); + color2 = dmnsn_color_to_sRGB(color2); + } + dmnsn_color ret = dmnsn_color_gradient(color1, color2, n); + if (payload->flags == DMNSN_PIGMENT_MAP_SRGB) { + ret = dmnsn_color_from_sRGB(ret); + } + + return ret; } /** color_map initialization callback. */ @@ -70,7 +81,8 @@ dmnsn_color_map_initialize_fn(dmnsn_pigment *pigment) } dmnsn_pigment * -dmnsn_new_color_map_pigment(dmnsn_pattern *pattern, dmnsn_map *map) +dmnsn_new_color_map_pigment(dmnsn_pattern *pattern, dmnsn_map *map, + dmnsn_pigment_map_flags flags) { dmnsn_pigment *pigment = dmnsn_new_pigment(); @@ -78,6 +90,7 @@ dmnsn_new_color_map_pigment(dmnsn_pattern *pattern, dmnsn_map *map) = dmnsn_malloc(sizeof(dmnsn_color_map_payload)); payload->pattern = pattern; payload->map = map; + payload->flags = flags; pigment->pigment_fn = dmnsn_color_map_pigment_fn; pigment->initialize_fn = dmnsn_color_map_initialize_fn; diff --git a/libdimension/dimension/color.h b/libdimension/dimension/color.h index 98d1530..d4e0402 100644 --- a/libdimension/dimension/color.h +++ b/libdimension/dimension/color.h @@ -77,6 +77,10 @@ dmnsn_color_is_black(dmnsn_color color) /* Perceptual color manipulation */ +/** Convert from sRGB space. */ +dmnsn_color dmnsn_color_from_sRGB(dmnsn_color color); +/** Convert to sRGB space. */ +dmnsn_color dmnsn_color_to_sRGB(dmnsn_color color); /** Greyscale color intensity. */ double dmnsn_color_intensity(dmnsn_color color); /** Add two colors together. */ diff --git a/libdimension/dimension/pigments.h b/libdimension/dimension/pigments.h index 26b75f8..e62963e 100644 --- a/libdimension/dimension/pigments.h +++ b/libdimension/dimension/pigments.h @@ -44,6 +44,14 @@ dmnsn_pigment *dmnsn_new_canvas_pigment(dmnsn_canvas *canvas); */ dmnsn_map *dmnsn_new_color_map(void); +/** + * pigment_map flags. + */ +typedef enum dmnsn_pigment_map_flags { + DMNSN_PIGMENT_MAP_REGULAR, /**< Calculate linear color gradients. */ + DMNSN_PIGMENT_MAP_SRGB /**< Calculate sRGB color gradients. */ +} dmnsn_pigment_map_flags; + /** * A color-mapped pigment. * @param[in,out] pattern The pattern of the pigment. @@ -51,7 +59,8 @@ dmnsn_map *dmnsn_new_color_map(void); * @return A pigment mapping the pattern to color values. */ dmnsn_pigment *dmnsn_new_color_map_pigment(dmnsn_pattern *pattern, - dmnsn_map *map); + dmnsn_map *map, + dmnsn_pigment_map_flags flags); /** * Construct a pigment map. @@ -66,4 +75,5 @@ dmnsn_map *dmnsn_new_pigment_map(void); * @return A pigment mapping the pattern to other pigments. */ dmnsn_pigment *dmnsn_new_pigment_map_pigment(dmnsn_pattern *pattern, - dmnsn_map *map); + dmnsn_map *map, + dmnsn_pigment_map_flags flags); diff --git a/libdimension/gl.c b/libdimension/gl.c index f5f1c92..a968d75 100644 --- a/libdimension/gl.c +++ b/libdimension/gl.c @@ -82,7 +82,9 @@ dmnsn_gl_write_canvas(const dmnsn_canvas *canvas) for (size_t x = 0; x < width; ++x) { pixel = pixels + 4*(y*width + x); - color = dmnsn_remove_filter(dmnsn_get_pixel(canvas, x, y)); + color = dmnsn_get_pixel(canvas, x, y); + color = dmnsn_remove_filter(color); + color = dmnsn_color_to_sRGB(color); /* Saturate R, G, and B to [0, UINT16_MAX] */ @@ -153,6 +155,7 @@ dmnsn_gl_read_canvas(size_t x0, size_t y0, (double)pixel[2]/UINT16_MAX, (double)pixel[3]/UINT16_MAX, 0.0); + color = dmnsn_color_from_sRGB(color); dmnsn_set_pixel(canvas, x, y, color); } } @@ -167,7 +170,9 @@ dmnsn_gl_optimizer_fn(const dmnsn_canvas *canvas, dmnsn_canvas_optimizer optimizer, size_t x, size_t y) { GLushort *pixel = (GLushort *)optimizer.ptr + 4*(y*canvas->width + x); - dmnsn_color color = dmnsn_remove_filter(dmnsn_get_pixel(canvas, x, y)); + dmnsn_color color = dmnsn_get_pixel(canvas, x, y); + color = dmnsn_remove_filter(color); + color = dmnsn_color_to_sRGB(color); /* Saturate R, G, and B to [0, UINT16_MAX] */ diff --git a/libdimension/pigment_map.c b/libdimension/pigment_map.c index 9864152..e97553f 100644 --- a/libdimension/pigment_map.c +++ b/libdimension/pigment_map.c @@ -53,6 +53,7 @@ dmnsn_new_pigment_map(void) typedef struct dmnsn_pigment_map_payload { dmnsn_pattern *pattern; dmnsn_map *map; + dmnsn_pigment_map_flags flags; } dmnsn_pigment_map_payload; /** Free a pigment_map payload. */ @@ -76,7 +77,17 @@ dmnsn_pigment_map_pigment_fn(const dmnsn_pigment *pigment, dmnsn_vector v) &n, &pigment1, &pigment2); dmnsn_color color1 = pigment1->pigment_fn(pigment1, v); dmnsn_color color2 = pigment2->pigment_fn(pigment2, v); - return dmnsn_color_gradient(color1, color2, n); + + if (payload->flags == DMNSN_PIGMENT_MAP_SRGB) { + color1 = dmnsn_color_to_sRGB(color1); + color2 = dmnsn_color_to_sRGB(color2); + } + dmnsn_color ret = dmnsn_color_gradient(color1, color2, n); + if (payload->flags == DMNSN_PIGMENT_MAP_SRGB) { + ret = dmnsn_color_from_sRGB(ret); + } + + return ret; } /** pigment_map initialization callback. */ @@ -91,7 +102,8 @@ dmnsn_pigment_map_initialize_fn(dmnsn_pigment *pigment) } dmnsn_pigment * -dmnsn_new_pigment_map_pigment(dmnsn_pattern *pattern, dmnsn_map *map) +dmnsn_new_pigment_map_pigment(dmnsn_pattern *pattern, dmnsn_map *map, + dmnsn_pigment_map_flags flags) { dmnsn_pigment *pigment = dmnsn_new_pigment(); @@ -99,6 +111,7 @@ dmnsn_new_pigment_map_pigment(dmnsn_pattern *pattern, dmnsn_map *map) = dmnsn_malloc(sizeof(dmnsn_pigment_map_payload)); payload->pattern = pattern; payload->map = map; + payload->flags = flags; pigment->pigment_fn = dmnsn_pigment_map_pigment_fn; pigment->initialize_fn = dmnsn_pigment_map_initialize_fn; diff --git a/libdimension/png.c b/libdimension/png.c index 5b7f625..6a56a87 100644 --- a/libdimension/png.c +++ b/libdimension/png.c @@ -64,7 +64,9 @@ dmnsn_png_optimizer_fn(const dmnsn_canvas *canvas, dmnsn_color color; uint16_t *pixel = (uint16_t *)optimizer.ptr + 4*(y*canvas->width + x); - color = dmnsn_remove_filter(dmnsn_get_pixel(canvas, x, y)); + color = dmnsn_get_pixel(canvas, x, y); + color = dmnsn_remove_filter(color); + color = dmnsn_color_to_sRGB(color); /* Saturate R, G, and B to [0, UINT16_MAX] */ @@ -272,6 +274,7 @@ dmnsn_png_write_canvas_thread(void *ptr) /* Invert the rows. PNG coordinates are fourth quadrant. */ dmnsn_color color = dmnsn_get_pixel(payload->canvas, x, height - y - 1); color = dmnsn_remove_filter(color); + color = dmnsn_color_to_sRGB(color); /* Saturate R, G, and B to [0, UINT16_MAX] */ @@ -477,6 +480,7 @@ dmnsn_png_read_canvas_thread(void *ptr) } } + color = dmnsn_color_from_sRGB(color); dmnsn_set_pixel(*payload->canvas, x, height - y - 1, color); } diff --git a/libdimension/tests/render.c b/libdimension/tests/render.c index a79c85c..de5b6a4 100644 --- a/libdimension/tests/render.c +++ b/libdimension/tests/render.c @@ -34,9 +34,9 @@ dmnsn_new_test_scene(void) /* Default finish */ scene->default_texture->finish = dmnsn_new_finish_combination( dmnsn_new_ambient_finish( - dmnsn_color_mul(0.1, dmnsn_white) + dmnsn_color_mul(0.01, dmnsn_white) ), - dmnsn_new_diffuse_finish(0.6) + dmnsn_new_diffuse_finish(0.3) ); /* Allocate a canvas */ @@ -74,10 +74,13 @@ dmnsn_new_test_scene(void) dmnsn_pattern *sky_gradient = dmnsn_new_gradient_pattern(dmnsn_y); dmnsn_map *sky_gradient_color_map = dmnsn_new_color_map(); dmnsn_add_map_entry(sky_gradient_color_map, 0.0, &dmnsn_orange); - dmnsn_color background = dmnsn_new_color5(0.0, 0.1, 0.2, 0.1, 1.0); + dmnsn_color background = dmnsn_color_from_sRGB( + dmnsn_new_color5(0.0, 0.1, 0.2, 0.1, 0.0) + ); dmnsn_add_map_entry(sky_gradient_color_map, 0.35, &background); dmnsn_pigment *sky_pigment - = dmnsn_new_color_map_pigment(sky_gradient, sky_gradient_color_map); + = dmnsn_new_color_map_pigment(sky_gradient, sky_gradient_color_map, + DMNSN_PIGMENT_MAP_SRGB); dmnsn_array_push(scene->sky_sphere->pigments, &sky_pigment); /* Light source */ @@ -144,7 +147,8 @@ dmnsn_new_test_scene(void) dmnsn_add_map_entry(gradient_color_map, 1.0, &dmnsn_red); arrow->texture = dmnsn_new_texture(); arrow->texture->pigment - = dmnsn_new_color_map_pigment(gradient, gradient_color_map); + = dmnsn_new_color_map_pigment(gradient, gradient_color_map, + DMNSN_PIGMENT_MAP_SRGB); arrow->texture->trans = dmnsn_matrix_mul( dmnsn_translation_matrix(dmnsn_new_vector(0.0, -1.25, 0.0)), @@ -186,15 +190,19 @@ dmnsn_new_test_scene(void) dmnsn_add_map_entry(checker_color_map, 1.0, &dmnsn_white); dmnsn_pigment *pigment1 = dmnsn_new_solid_pigment(dmnsn_white); dmnsn_pigment *pigment2 - = dmnsn_new_color_map_pigment(checker1, checker_color_map); + = dmnsn_new_color_map_pigment(checker1, checker_color_map, + DMNSN_PIGMENT_MAP_REGULAR); pigment2->trans = dmnsn_scale_matrix(dmnsn_new_vector(1.0/3.0, 1.0/3.0, 1.0/3.0)); dmnsn_map *checker_pigment_map = dmnsn_new_pigment_map(); dmnsn_add_map_entry(checker_pigment_map, 0.0, &pigment1); dmnsn_add_map_entry(checker_pigment_map, 1.0, &pigment2); plane->texture->pigment - = dmnsn_new_pigment_map_pigment(checker2, checker_pigment_map); - plane->texture->pigment->quick_color = dmnsn_new_color(1.0, 0.5, 0.75); + = dmnsn_new_pigment_map_pigment(checker2, checker_pigment_map, + DMNSN_PIGMENT_MAP_REGULAR); + plane->texture->pigment->quick_color = dmnsn_color_from_sRGB( + dmnsn_new_color(1.0, 0.5, 0.75) + ); dmnsn_scene_add_object(scene, plane); return scene; -- cgit v1.2.3