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;

Reply via email to