From 2ce864be00f5e34232ff33d2376b3219cdf66649 Mon Sep 17 00:00:00 2001 From: Zach Kost-Smith Date: Tue, 4 Apr 2023 06:57:04 -0400 Subject: Add hue contribution of background color to inverted midnight mode From the comments in the PR, @smithzvk explains the changes: > I was bugged that it didn't pick up the hue from the background, > however, so I made the all color a/b parameters start from the a/b > parameters of the BG color. This makes the PDF background (white) > map to your BG color and PDF foreground (black) map to your FG > color, which can be set to your theme background making it seem > pretty seamless. Note that if you set your BG and FG to a color > theme without zero saturation (a gray tone of some sort), then you > recover the original behavior. > > I also reinstated the cached transforms for background and the last > color computed. > > I also refactored the loop to move the switch statement outside of > the loop. It is more repetitive, but it was needed to get rid of > some warnings about potentially uninitialized variables. It should > also be faster as the branch was removed from the loop. > > I also ditched the gamma correction as it seemed to be doing the > wrong thing and I never had a theoretical reasoning for it. I can > get similar results if I just set my BG color to something like > gray5 instead of black. Barely a difference in background but the > hinting on the fonts is showing again. The root of my issue may > have always been some kind of display calibration issue. Closes: #197 --- server/epdfinfo.c | 131 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 78 insertions(+), 53 deletions(-) diff --git a/server/epdfinfo.c b/server/epdfinfo.c index ef14e41..6d3e14e 100644 --- a/server/epdfinfo.c +++ b/server/epdfinfo.c @@ -473,15 +473,19 @@ image_recolor (cairo_surface_t * surface, const PopplerColor * fg, { /* Performs one of two kinds of image recoloring depending on the value of usecolors: - 1 -> uses a representation of a rgb color as follows: - - a lightness scalar (between 0,1), which is a weighted average of r, g, b, - - a hue vector, which indicates a radian direction from the grey axis, - inside the equal lightness plane. - - a saturation scalar between 0,1. It is 0 when grey, 1 when the color is - in the boundary of the rgb cube. - - 2 -> Invert the perceived lightness in the image while maintaining hue. - */ + 1 -> Bg-Fg Interpolation: maps source document colors to colors + interpolated between the background and foreground values in + pdf-view-midnight-colors via the lightness of the source color. This + discards hue information but allows you to fit your color theme + perfectly. + + 2 -> Hue-Preserving interpolation: same as above, similar to above, but + attempts to preserve hue while still respecting the background and + foreground colors. This is done by matching source document white and + black to the specified background and foreground as above, but mixes + hue/saturation with the background color. This preserves hue but is + more expensive. + */ const unsigned int page_width = cairo_image_surface_get_width (surface); const unsigned int page_height = cairo_image_surface_get_height (surface); @@ -508,41 +512,28 @@ image_recolor (cairo_surface_t * surface, const PopplerColor * fg, .g = rgb_bg.g - rgb_fg.g, .b = rgb_bg.b - rgb_fg.b }; - const double fg_l = a[0] * rgb_fg.r + a[1] * rgb_fg.g + a[2] * rgb_fg.b; - const double bg_l = a[0] * rgb_bg.r + a[1] * rgb_bg.g + a[2] * rgb_bg.b; - const double diff_l = fg_l - bg_l; - - /* The Oklab transform is expensive, precompute white->black and have a single - entry cache to speed up computation */ - struct color white = {.r = 1.0, .g = 1.0, .b = 1.0}; - struct color black = {.r = 0.0, .g = 0.0, .b = 0.0}; - struct color precomputed_rgb = white; - struct color precomputed_inv_rgb = black; - unsigned int y; - for (y = 0; y < page_height * rowstride; y += rowstride) + switch (usecolors) { - unsigned char *data = image + y; + case 0: + break; + case 1: + { + unsigned int y; + for (y = 0; y < page_height * rowstride; y += rowstride) + { + unsigned char *data = image + y; - unsigned int x; - for (x = 0; x < page_width; x++, data += 4) - { - /* Careful. data color components blue, green, red. */ - struct color rgb = { - .r = (double) data[2] / 256., - .g = (double) data[1] / 256., - .b = (double) data[0] / 256. - }; - - switch (usecolors) - { - case 0: - /* No image recoloring requested. Do nothing in this case. - Should never be called as we should never call with unless - usecolors != 0. */ - break; - case 1: + unsigned int x; + for (x = 0; x < page_width; x++, data += 4) { + /* Careful. data color components blue, green, red. */ + struct color rgb = { + .r = (double) data[2] / 256., + .g = (double) data[1] / 256., + .b = (double) data[0] / 256. + }; + /* Linear interpolation between bg and fg based on the perceptual lightness measure l */ /* compute h, s, l data */ @@ -557,14 +548,44 @@ image_recolor (cairo_surface_t * surface, const PopplerColor * fg, data[0] = (unsigned char) round (255. * (l * rgb_diff.b + rgb_fg.b)); } - break; - case 2: + } + } + break; + case 2: + { + /* If using the Oklab transform, it is relatively expensive. Precompute + white->background and black->foreground and have a single entry cache to + speed up computation */ + const struct color white = {.r = 1.0, .g = 1.0, .b = 1.0}; + struct color precomputed_rgb = white; + struct color precomputed_inv_rgb = rgb_bg; + + /* Must match the transformation of colors below. */ + struct color oklab_fg = rgb2oklab(rgb_fg); + struct color oklab_bg = rgb2oklab(rgb_bg); + + const double oklab_diff_l = oklab_fg.l - oklab_bg.l; + + unsigned int y; + for (y = 0; y < page_height * rowstride; y += rowstride) + { + unsigned char *data = image + y; + + unsigned int x; + for (x = 0; x < page_width; x++, data += 4) { + /* Careful. data color components blue, green, red. */ + struct color rgb = { + .r = (double) data[2] / 256., + .g = (double) data[1] / 256., + .b = (double) data[0] / 256. + }; + /* Convert to Oklab coordinates, invert perceived lightness, convert back to RGB. */ if (color_equal(white, rgb)) { - rgb = black; + rgb = rgb_bg; } else if (color_equal(precomputed_rgb, rgb)) { @@ -575,12 +596,16 @@ image_recolor (cairo_surface_t * surface, const PopplerColor * fg, struct color oklab = rgb2oklab(rgb); precomputed_rgb = rgb; - /* Gamma correction. Shouldn't be necessary, but colors - * 'feel' too dark and fonts too thin otherwise. */ - oklab.l = pow(oklab.l, 1.8); - /* Invert the perceived lightness, and scales it */ - oklab.l = bg_l + diff_l * (1.0 - oklab.l); + double l = oklab.l; + double inv_l = 1.0 - l; + oklab.l = oklab_bg.l + oklab_diff_l * inv_l; + + /* Have a and b parameters (which encode hue and saturation) + start at the background value and interpolate up to + foreground */ + oklab.a = (oklab.a + oklab_bg.a * l + oklab_fg.a * inv_l); + oklab.b = (oklab.b + oklab_bg.b * l + oklab_fg.b * inv_l); rgb = oklab2rgb(oklab); @@ -591,11 +616,11 @@ image_recolor (cairo_surface_t * surface, const PopplerColor * fg, data[1] = (unsigned char) round(255. * rgb.g); data[0] = (unsigned char) round(255. * rgb.b); } - break; - default: - internal_error ("image_recolor switch fell through"); - } - } + } + } + break; + default: + internal_error ("image_recolor switch fell through"); } } -- cgit v1.0