From c57463a053a7741c85d6a09857dbd3ae25a6b587 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abd=C3=B3=20Roig-Maranges?= Date: Tue, 31 Jul 2012 16:43:20 +0200 Subject: [PATCH] Added option to recolor keeping hues of original color. If option recolor-keephue is true, the recoloring algorithm only adjusts the lightness of the original color, keeping the rest of the properties close to the original. When recolor-keephue is set to false, the recoloring is performed as it was before, interpolating linearly between recolor-lightcolor and recolor-darkcolor except for a different weighting for the lightness which is closer to perception. Signed-off-by: Sebastian Ramacher --- callbacks.c | 19 ++++++++ callbacks.h | 13 ++++++ config.c | 2 + render.c | 122 +++++++++++++++++++++++++++++++++++++++--------- zathura.h | 1 + zathurarc.5.rst | 7 +++ 6 files changed, 143 insertions(+), 21 deletions(-) diff --git a/callbacks.c b/callbacks.c index 29bea38..9e7d208 100644 --- a/callbacks.c +++ b/callbacks.c @@ -365,6 +365,25 @@ cb_setting_recolor_change(girara_session_t* session, const char* name, } } +void +cb_setting_recolor_keep_hue_change(girara_session_t* session, const char* name, + girara_setting_type_t UNUSED(type), void* value, void* UNUSED(data)) +{ + g_return_if_fail(value != NULL); + g_return_if_fail(session != NULL); + g_return_if_fail(session->global.data != NULL); + g_return_if_fail(name != NULL); + zathura_t* zathura = session->global.data; + + bool bool_value = *((bool*) value); + + if (zathura->global.recolor_keep_hue != bool_value) { + zathura->global.recolor_keep_hue = bool_value; + render_all(zathura); + } +} + + bool cb_unknown_command(girara_session_t* session, const char* input) { diff --git a/callbacks.h b/callbacks.h index 527b5e9..94feaec 100644 --- a/callbacks.h +++ b/callbacks.h @@ -123,6 +123,19 @@ bool cb_view_resized(GtkWidget* widget, GtkAllocation* allocation, zathura_t* za void cb_setting_recolor_change(girara_session_t* session, const char* name, girara_setting_type_t type, void* value, void* data); +/** + * Emitted when the 'recolor-keephue' setting is changed + * + * @param session Girara session + * @param name Name of the setting ("recolor") + * @param type Type of the setting (BOOLEAN) + * @param value New value + * @param data Custom data + */ +void cb_setting_recolor_keep_hue_change(girara_session_t* session, const char* name, + girara_setting_type_t type, void* value, void* data); + + /** * Unknown command handler which is used to handle the strict numeric goto * command diff --git a/config.c b/config.c index af172d0..191dc00 100644 --- a/config.c +++ b/config.c @@ -130,6 +130,8 @@ config_load_default(zathura_t* zathura) bool_value = false; girara_setting_add(gsession, "recolor", &bool_value, BOOLEAN, false, _("Recolor pages"), cb_setting_recolor_change, NULL); bool_value = false; + girara_setting_add(gsession, "recolor-keephue", &bool_value, BOOLEAN, false, _("When recoloring keep original hue and adjust lightness only"), cb_setting_recolor_keep_hue_change, NULL); + bool_value = false; girara_setting_add(gsession, "scroll-wrap", &bool_value, BOOLEAN, false, _("Wrap scrolling"), NULL, NULL); bool_value = false; girara_setting_add(gsession, "advance-pages-per-row", &bool_value, BOOLEAN, false, _("Advance number of pages per row"), NULL, NULL); diff --git a/render.c b/render.c index 2e464ba..603925e 100644 --- a/render.c +++ b/render.c @@ -89,6 +89,53 @@ render_page(render_thread_t* render_thread, zathura_page_t* page) return true; } +void +color2double(GdkColor* col, double* v) +{ + v[0] = (double) col->red / 65535.; + v[1] = (double) col->green / 65535.; + v[2] = (double) col->blue / 65535.; +} + +/* Returns the maximum possible saturation for given h and l. + Assumes that l is in the interval l1, l2 and corrects the value to + force u=0 on l1 and l2 */ +double +colorumax(double* h, double l, double l1, double l2) +{ + double u, uu, v, vv, lv; + if (h[0] == 0 && h[1] == 0 && h[2] == 0) { + return 0; + } + + lv = (l - l1)/(l2 - l1); /* Remap l to the whole interval 0,1 */ + + u = v = 1000000; + for (int k = 0; k < 3; k++) { + if (h[k] > 0) { + uu = fabs((1-l)/h[k]); + vv = fabs((1-lv)/h[k]); + + if (uu < u) u = uu; + if (vv < v) v = vv; + + } else if (h[k] < 0) { + uu = fabs(l/h[k]); + vv = fabs(lv/h[k]); + + if (uu < u) u = uu; + if (vv < v) v = vv; + } + } + + /* rescale v according to the length of the interval [l1, l2] */ + v = fabs(l2 - l1) * v; + + /* forces the returned value to be 0 on l1 and l2, trying not to distort colors too much */ + return fmin(u, v); +} + + static bool render(zathura_t* zathura, zathura_page_t* page) { @@ -142,34 +189,67 @@ render(zathura_t* zathura, zathura_page_t* page) unsigned char* image = cairo_image_surface_get_data(surface); /* recolor */ + /* 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. + */ + if (zathura->global.recolor == true) { - /* recolor code based on qimageblitz library flatten() function - (http://sourceforge.net/projects/qimageblitz/) */ - int r1 = zathura->ui.colors.recolor_dark_color.red / 257; - int g1 = zathura->ui.colors.recolor_dark_color.green / 257; - int b1 = zathura->ui.colors.recolor_dark_color.blue / 257; - int r2 = zathura->ui.colors.recolor_light_color.red / 257; - int g2 = zathura->ui.colors.recolor_light_color.green / 257; - int b2 = zathura->ui.colors.recolor_light_color.blue / 257; + /* RGB weights for computing lightness. Must sum to one */ + double a[] = {0.30, 0.59, 0.11}; - int min = 0x00; - int max = 0xFF; - int mean = 0x00; - - float sr = ((float) r2 - r1) / (max - min); - float sg = ((float) g2 - g1) / (max - min); - float sb = ((float) b2 - b1) / (max - min); + double l1, l2, l, s, u, t; + double h[3]; + double rgb1[3], rgb2[3], rgb[3]; + + color2double(&zathura->ui.colors.recolor_dark_color, rgb1); + color2double(&zathura->ui.colors.recolor_light_color, rgb2); + l1 = (a[0]*rgb1[0] + a[1]*rgb1[1] + a[2]*rgb1[2]); + l2 = (a[0]*rgb2[0] + a[1]*rgb2[1] + a[2]*rgb2[2]); + for (unsigned int y = 0; y < page_height; y++) { unsigned char* data = image + y * rowstride; - for (unsigned int x = 0; x < page_width; x++) { - mean = (data[0] + data[1] + data[2]) / 3; - data[2] = sr * (mean - min) + r1 + 0.5; - data[1] = sg * (mean - min) + g1 + 0.5; - data[0] = sb * (mean - min) + b1 + 0.5; - data += 4; + for (unsigned int x = 0; x < page_width; x++) { + /* Careful. data color components blue, green, red. */ + rgb[0] = (double) data[2] / 256.; + rgb[1] = (double) data[1] / 256.; + rgb[2] = (double) data[0] / 256.; + + /* compute h, s, l data */ + l = a[0]*rgb[0] + a[1]*rgb[1] + a[2]*rgb[2]; + + h[0] = rgb[0] - l; + h[1] = rgb[1] - l; + h[2] = rgb[2] - l; + + /* u is the maximum possible saturation for given h and l. s is a rescaled saturation between 0 and 1 */ + u = colorumax(h, l, 0, 1); + if (u == 0) s = 0; + else s = 1/u; + + /* Interpolates lightness between light and dark colors. white goes to light, and black goes to dark. */ + t = l; + l = t * (l2 - l1) + l1; + + if (zathura->global.recolor_keep_hue == true) { + /* adjusting lightness keeping hue of current color. white and black go to grays of same ligtness + as light and dark colors. */ + u = colorumax(h, l, l1, l2); + data[2] = (unsigned char)round(255.*(l + s*u * h[0])); + data[1] = (unsigned char)round(255.*(l + s*u * h[1])); + data[0] = (unsigned char)round(255.*(l + s*u * h[2])); + } else { + /* Linear interpolation between dark and light with color ligtness as a parameter */ + data[2] = (unsigned char)round(255.*(t * (rgb2[0] - rgb1[0]) + rgb1[0])); + data[1] = (unsigned char)round(255.*(t * (rgb2[1] - rgb1[1]) + rgb1[1])); + data[0] = (unsigned char)round(255.*(t * (rgb2[2] - rgb1[2]) + rgb1[2])); + } + + data += 4; } } } diff --git a/zathura.h b/zathura.h index f133c1f..bbc8ce6 100644 --- a/zathura.h +++ b/zathura.h @@ -80,6 +80,7 @@ struct zathura_s struct { + bool recolor_keep_hue; /**< Keep hue when recoloring */ bool recolor; /**< Recoloring mode switch */ bool update_page_number; /**< Update current page number */ girara_list_t* marks; /**< Marker */ diff --git a/zathurarc.5.rst b/zathurarc.5.rst index 4c03e20..0994ef9 100644 --- a/zathurarc.5.rst +++ b/zathurarc.5.rst @@ -556,6 +556,13 @@ En/Disables recoloring * Value type: Boolean * Default value: false +recolor-keephue +^^^^^^^^^^^^^^^ +En/Disables keeping original hue when recoloring + +* Value type: Boolean +* Default value: false + recolor-darkcolor ^^^^^^^^^^^^^^^^^ Defines the color value that is used to represent dark colors in recoloring mode