branch: elpa/pdf-tools
commit 8623dc3e38a2952fa87c1f6972b88d58e22f4c99
Author: Ziv Scully <[email protected]>
Commit: GitHub <[email protected]>
feat(midnight-mode): add gamma correction and fix RGB bugs
Add gamma correction options for better contrast control in midnight mode:
- pdf-view-midnight-gamma: nonlinearly scale lightness (default 1.0)
- pdf-view-midnight-gamma-before-invert: control gamma application order
Bug fixes:
- Fix RGB normalization (255 not 256) - was causing incorrect background
color
- Add missing black→foreground fast path (comment existed but code was
missing)
Closes: #329
Author: @vizziv
---
lisp/pdf-info.el | 4 ++++
lisp/pdf-view.el | 25 +++++++++++++++++++++++++
server/epdfinfo.c | 55 ++++++++++++++++++++++++++++++++++++++++++++-----------
server/epdfinfo.h | 4 ++++
4 files changed, 77 insertions(+), 11 deletions(-)
diff --git a/lisp/pdf-info.el b/lisp/pdf-info.el
index db1dc7e9e81..47d308d7b77 100644
--- a/lisp/pdf-info.el
+++ b/lisp/pdf-info.el
@@ -572,6 +572,8 @@ interrupted."
(cl-case key
((:render/printed)
(setq value (equal value "1")))
+ ((:render/gammabeforeinvert)
+ (setq value (equal value "1")))
((:render/usecolors)
(setq value (ignore-errors
(let ((int-val (cl-parse-integer value)))
@@ -1734,6 +1736,8 @@ Returns a list \(LEFT TOP RIGHT BOT\)."
soptions))
((:render/printed)
(push (if value 1 0) soptions))
+ ((:render/gammabeforeinvert)
+ (push (if value 1 0) soptions))
((:render/usecolors)
;; 0 -> original color
;; 1 -> recolor document to grayscale mapping black to
diff --git a/lisp/pdf-view.el b/lisp/pdf-view.el
index e94762b24cd..dc2e0be8387 100644
--- a/lisp/pdf-view.el
+++ b/lisp/pdf-view.el
@@ -133,6 +133,29 @@ Nevertheless, this seems to work well in most cases."
:group 'pdf-view
:type 'boolean)
+(defcustom pdf-view-midnight-gamma 1.0
+ "In midnight mode, nonlinearly scale lightness.
+
+Values less than 1 increase lightness, values more than 1 decrease
+lightness (unless `pdf-view-midnight-gamma-before-invert' is non-nil, in
+which reverses the effect direction)."
+ :group 'pdf-view
+ :type 'float)
+
+(defcustom pdf-view-midnight-gamma-before-invert nil
+ "In midnight mode, whether to scale lightness before inverting.
+
+If non-nil, this inverts the direction of the effect of
+`pdf-view-midnight-gamma', i.e. values more than 1 increase lightness
+instead of decreasing it. This option is provided because it results in
+different behaviors near the ends of the lightness scale. For example,
+if this option is nil (the default), then a gamma values less than 1
+significantly lighten colors very close to black. One gets a less
+extreme effect by setting this option to non-nil and using gamma values
+greater than 1."
+ :group 'pdf-view
+ :type 'boolean)
+
(defcustom pdf-view-change-page-hook nil
"Hook run after changing to another page, but before displaying it.
@@ -1339,6 +1362,8 @@ The colors are determined by the variable
(pdf-info-setoptions
:render/foreground (or (car pdf-view-midnight-colors)
"black")
:render/background (or (cdr pdf-view-midnight-colors)
"white")
+ :render/gamma pdf-view-midnight-gamma
+ :render/gammabeforeinvert
pdf-view-midnight-gamma-before-invert
:render/usecolors
(if pdf-view-midnight-invert
;; If midnight invert is enabled, pass "2" indicating
diff --git a/server/epdfinfo.c b/server/epdfinfo.c
index 29793d6b42d..6ad59c6b0c5 100644
--- a/server/epdfinfo.c
+++ b/server/epdfinfo.c
@@ -483,7 +483,8 @@ static inline gboolean color_equal(struct color a, struct
color b)
static void
image_recolor (cairo_surface_t * surface, const PopplerColor * fg,
- const PopplerColor * bg, int usecolors)
+ const PopplerColor * bg, int usecolors,
+ double gamma, int gammabeforeinvert)
{
/* Performs one of two kinds of image recoloring depending on the value of
usecolors:
@@ -543,9 +544,9 @@ image_recolor (cairo_surface_t * surface, const
PopplerColor * fg,
{
/* 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.
+ .r = (double) data[2] / 255.,
+ .g = (double) data[1] / 255.,
+ .b = (double) data[0] / 255.
};
/* Linear interpolation between bg and fg based on the
@@ -571,6 +572,7 @@ image_recolor (cairo_surface_t * surface, const
PopplerColor * fg,
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};
+ const struct color black = {.r = 0.0, .g = 0.0, .b = 0.0};
struct color precomputed_rgb = white;
struct color precomputed_inv_rgb = rgb_bg;
@@ -578,8 +580,6 @@ image_recolor (cairo_surface_t * surface, const
PopplerColor * fg,
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)
{
@@ -590,9 +590,9 @@ image_recolor (cairo_surface_t * surface, const
PopplerColor * fg,
{
/* 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.
+ .r = (double) data[2] / 255.,
+ .g = (double) data[1] / 255.,
+ .b = (double) data[0] / 255.
};
/* Convert to Oklab coordinates, invert perceived lightness,
@@ -601,6 +601,10 @@ image_recolor (cairo_surface_t * surface, const
PopplerColor * fg,
{
rgb = rgb_bg;
}
+ else if (color_equal(black, rgb))
+ {
+ rgb = rgb_fg;
+ }
else if (color_equal(precomputed_rgb, rgb))
{
rgb = precomputed_inv_rgb;
@@ -613,7 +617,20 @@ image_recolor (cairo_surface_t * surface, const
PopplerColor * fg,
/* Invert the perceived lightness, and scales it */
double l = oklab.l;
double inv_l = 1.0 - l;
- oklab.l = oklab_bg.l + oklab_diff_l * inv_l;
+
+ /* Nonlinearly scale lightness */
+ if (gammabeforeinvert)
+ {
+ l = pow(l, gamma);
+ inv_l = 1.0 - l;
+ }
+ else
+ {
+ inv_l = pow(inv_l, gamma);
+ l = 1.0 - inv_l;
+ }
+
+ oklab.l = oklab_bg.l * l + oklab_fg.l * inv_l;
/* Have a and b parameters (which encode hue and
saturation)
start at the background value and interpolate up to
@@ -706,7 +723,8 @@ image_render_page(PopplerDocument *pdf, PopplerPage *page,
cairo_paint (cr);
if (options && (options->usecolors))
- image_recolor (surface, &options->fg, &options->bg, options->usecolors);
+ image_recolor (surface, &options->fg, &options->bg, options->usecolors,
+ options->gamma, options->gammabeforeinvert);
cairo_destroy (cr);
@@ -1119,6 +1137,15 @@ command_arg_parse_arg (const epdfinfo_t *ctx, const char
*arg,
error_msg, "Expected 0 or 1:%s", arg);
cmd_arg->value.flag = *arg == '1';
break;
+ case ARG_DOUBLE:
+ {
+ char *endptr;
+ double n = strtod (arg, &endptr);
+ cerror_if_not (! (*endptr),
+ error_msg, "Expected double (floating point): %s", arg);
+ cmd_arg->value.scalar = n;
+ }
+ break;
case ARG_NONEMPTY_STRING:
cerror_if_not (*arg, error_msg, "Non-empty string expected");
/* fall through */
@@ -1260,6 +1287,9 @@ command_arg_print(const command_arg_t *arg)
case ARG_BOOL:
printf ("%d", arg->value.flag ? 1 : 0);
break;
+ case ARG_DOUBLE:
+ printf ("%f", arg->value.scalar);
+ break;
case ARG_NONEMPTY_STRING: /* fall */
case ARG_STRING:
print_response_string (arg->value.string, NONE);
@@ -1311,6 +1341,7 @@ command_arg_type_size(command_arg_type_t type)
case ARG_INVALID: return 0;
case ARG_DOC: return sizeof (arg.value.doc);
case ARG_BOOL: return sizeof (arg.value.flag);
+ case ARG_DOUBLE: return sizeof (arg.value.scalar);
case ARG_NONEMPTY_STRING: /* fall */
case ARG_STRING: return sizeof (arg.value.string);
case ARG_NATNUM: return sizeof (arg.value.natnum);
@@ -3667,6 +3698,8 @@ const document_option_t document_options [] =
DEC_DOPT (":render/printed", ARG_BOOL, render.printed),
DEC_DOPT (":render/foreground", ARG_COLOR, render.fg),
DEC_DOPT (":render/background", ARG_COLOR, render.bg),
+ DEC_DOPT (":render/gamma", ARG_DOUBLE, render.gamma),
+ DEC_DOPT (":render/gammabeforeinvert", ARG_BOOL, render.gammabeforeinvert),
};
const command_arg_type_t cmd_getoptions_spec[] =
diff --git a/server/epdfinfo.h b/server/epdfinfo.h
index 542b577657b..ed6abd15e91 100644
--- a/server/epdfinfo.h
+++ b/server/epdfinfo.h
@@ -165,6 +165,7 @@ typedef enum
ARG_INVALID = 0,
ARG_DOC,
ARG_BOOL,
+ ARG_DOUBLE,
ARG_STRING,
ARG_NONEMPTY_STRING,
ARG_NATNUM,
@@ -188,6 +189,8 @@ typedef struct
PopplerColor bg, fg;
gboolean usecolors;
gboolean printed;
+ gboolean gammabeforeinvert;
+ gdouble gamma;
} render_options_t;
typedef struct
@@ -214,6 +217,7 @@ typedef struct
union
{
gboolean flag;
+ gdouble scalar;
const char *string;
long natnum;
document_t *doc;