OSDN Git Service

brushlib: colorize brush blendmode with weighting
authorAndrew Chadwick <andrewc-git@piffle.org>
Tue, 8 Nov 2011 19:16:04 +0000 (19:16 +0000)
committerAndrew Chadwick <andrewc-git@piffle.org>
Sun, 1 Jan 2012 00:26:30 +0000 (00:26 +0000)
Add a tinting/colorizing brush blend mode which applies the source's hue
and saturation while retaining the value of the target image.  You can
colorize with aa different colour immediately afterwards if you like,
and theoretically at least, only a little information is lost.

This uses a variant on the Krita way of doing things, similar to that of
PDF implementations using the open spec: the algorithm comes from the
spec addendum.

Colorize mode is weighted. I've used the luma coefficients from the
HDTV/sRGB specs rather than the older SDTV spec, but there shouldn't be
too much to choose between them. Most calibrated monitors approximate
sRGB, so that should be OK.

brushlib/brush.hpp
brushlib/brushsettings.py
brushlib/surface.hpp
gui/brushsettingswindow.py
lib/brushmodes.hpp
lib/tiledsurface.hpp

index 20d4109..458d9df 100644 (file)
@@ -545,7 +545,8 @@ private:
     hsv_to_rgb_float (&color_h, &color_s, &color_v);
     return surface->draw_dab (x, y, radius, color_h, color_s, color_v, opaque, hardness, eraser_target_alpha,
                               states[STATE_ACTUAL_ELLIPTICAL_DAB_RATIO], states[STATE_ACTUAL_ELLIPTICAL_DAB_ANGLE],
-                              settings_value[BRUSH_LOCK_ALPHA]);
+                              settings_value[BRUSH_LOCK_ALPHA],
+                              settings_value[BRUSH_COLORIZE]);
   }
 
   // How many dabs will be drawn between the current and the next (x, y, pressure, +dt) position?
index d2a912b..cc30bcc 100644 (file)
@@ -87,6 +87,7 @@ settings_list = [
     ['direction_filter', _('Direction filter'), False, 0.0, 2.0, 10.0, _("A low value will make the direction input adapt more quickly, a high value will make it smoother")],
 
     ['lock_alpha', _('Lock alpha'), False, 0.0, 0.0, 1.0, _("Do not modify the alpha channel of the layer (paint only where there is paint already)\n 0.0 normal painting\n 0.5 half of the paint gets applied normally\n 1.0 alpha channel fully locked")],
+    ['colorize', _('Colorize'), False, 0.0, 0.0, 1.0, _("Colorize the target layer, setting its hue and saturation from the active brush colour while retaining its value and alpha.")],
     ]
 
 settings_hidden = 'color_h color_s color_v'.split()
index e78f8f6..cf7433c 100644 (file)
@@ -26,7 +26,8 @@ public:
                          float opaque, float hardness = 0.5,
                          float alpha_eraser = 1.0,
                          float aspect_ratio = 1.0, float angle = 0.0,
-                         float lock_alpha = 0.0
+                         float lock_alpha = 0.0,
+                         float colorize = 0.0
                          ) = 0;
 
   virtual void get_color (float x, float y, 
index 5193e58..816997b 100644 (file)
@@ -105,7 +105,7 @@ class Window(windowing.SubWindow):
             {'id' : 'speed',    'title' : _('Speed'),   'settings' : [ 'speed1_slowness', 'speed2_slowness', 'speed1_gamma', 'speed2_gamma', 'offset_by_speed', 'offset_by_speed_slowness' ]},
             {'id' : 'tracking', 'title' : _('Tracking'),'settings' : [ 'slow_tracking', 'slow_tracking_per_dab', 'tracking_noise' ]},
             {'id' : 'stroke',   'title' : _('Stroke'),  'settings' : [ 'stroke_threshold', 'stroke_duration_logarithmic', 'stroke_holdtime' ]},
-            {'id' : 'color',    'title' : _('Color'),   'settings' : [ 'change_color_h', 'change_color_l', 'change_color_hsl_s', 'change_color_v', 'change_color_hsv_s', 'restore_color' ]},
+            {'id' : 'color',    'title' : _('Color'),   'settings' : [ 'change_color_h', 'change_color_l', 'change_color_hsl_s', 'change_color_v', 'change_color_hsv_s', 'restore_color', 'colorize' ]},
             {'id' : 'custom',   'title' : _('Custom'),  'settings' : [ 'custom_input', 'custom_input_slowness' ]}
             ]
 
index 5cc69de..d39f258 100644 (file)
@@ -53,6 +53,132 @@ void draw_dab_pixels_BlendMode_Normal (uint16_t * mask,
   }
 };
 
+
+
+// Colorize: apply the source hue and saturation, retaining the target
+// brightness. Same thing as in the PDF spec addendum but with different Luma
+// coefficients. Colorize should be used at either 1.0 or 0.0, values in
+// between probably aren't very useful. This blend mode retains the target
+// alpha, and any pure whites and blacks in the target layer.
+//
+// Code is still ugly. Using floating-point arithmetic in here, which maybe
+// isn't very efficient.
+
+#define MAX3(a, b, c) ((a)>(b)?MAX((a),(c)):MAX((b),(c)))
+#define MIN3(a, b, c) ((a)<(b)?MIN((a),(c)):MIN((b),(c)))
+
+/*
+// From ITU Rec. BT.601 (SDTV)
+static const float LUMA_RED_COEFF   = 0.3;
+static const float LUMA_GREEN_COEFF = 0.59;
+static const float LUMA_BLUE_COEFF  = 0.11;
+*/
+
+// From ITU Rec. BT.709 (HDTV and sRGB)
+static const float LUMA_RED_COEFF = 0.2126;
+static const float LUMA_GREEN_COEFF = 0.7152;
+static const float LUMA_BLUE_COEFF = 0.0722;
+
+// See also http://en.wikipedia.org/wiki/YCbCr
+
+
+inline static float
+nonsep_lum (const float r,
+              const float g,
+              const float b)
+{
+    return (r*LUMA_RED_COEFF) + (g*LUMA_GREEN_COEFF) + (b*LUMA_BLUE_COEFF);
+}
+
+
+inline static void
+nonsep_clip_inplace(float *r,
+                    float *g,
+                    float *b)
+{
+    float lum = nonsep_lum(*r, *g, *b);
+    float cmin = MIN3(*r, *g, *b);
+    float cmax = MAX3(*r, *g, *b);
+    if (cmin < 0.0) {
+        *r = lum + (((*r - lum) * lum) / (lum - cmin));
+        *g = lum + (((*g - lum) * lum) / (lum - cmin));
+        *b = lum + (((*b - lum) * lum) / (lum - cmin));
+    }
+    if (cmax > 1.0) {
+        *r = lum + (((*r - lum) * (1-lum)) / (cmax - lum));
+        *g = lum + (((*g - lum) * (1-lum)) / (cmax - lum));
+        *b = lum + (((*b - lum) * (1-lum)) / (cmax - lum));
+    }
+}
+
+
+inline static void
+nonsep_apply_lum(const float topr,
+                 const float topg,
+                 const float topb,
+                 const float botlum,
+                 float *botr,
+                 float *botg,
+                 float *botb)
+{
+    float diff = botlum - nonsep_lum(topr, topg, topb);
+    *botr = topr + diff;
+    *botg = topg + diff;
+    *botb = topb + diff;
+    nonsep_clip_inplace(botr, botg, botb);
+}
+
+
+// The method is an implementation of that described in the official Adobe "PDF
+// Blend Modes: Addendum" document, dated January 23, 2006; specifically it's
+// the "Color" nonseparable blend mode. We do however use different
+// coefficients for the Luma value.
+
+void
+draw_dab_pixels_BlendMode_Color (uint16_t * mask,
+                                 uint16_t * rgba, // b/bottom, premult
+                                 uint16_t color_r,  // }
+                                 uint16_t color_g,  // }-- a/top, !premult
+                                 uint16_t color_b,  // }
+                                 uint16_t opacity)
+{
+  while (1) {
+    for (; mask[0]; mask++, rgba+=4) {
+      // De-multiply (and scale)
+      const float scalefact = rgba[3]; //can't work with premult alpha, sadly
+
+      float r = ((float)rgba[0]) / scalefact;
+      float g = ((float)rgba[1]) / scalefact;
+      float b = ((float)rgba[2]) / scalefact;
+
+      // Input luma, based on the target pixel
+      float lum = nonsep_lum(r, g, b);
+
+      // Output RGB generation
+      r = g = b = 0;
+      nonsep_apply_lum( (float)color_r/(1<<15),
+                        (float)color_g/(1<<15),
+                        (float)color_b/(1<<15),
+                        lum, &r, &g, &b         );
+
+      // Re-premult/scale.
+      r *= scalefact;
+      g *= scalefact;
+      b *= scalefact;
+
+      // And combine as normal.
+      uint32_t opa_a = mask[0]*(uint32_t)opacity/(1<<15); // topAlpha
+      uint32_t opa_b = (1<<15)-opa_a; // bottomAlpha
+      rgba[0] = (opa_a*r + opa_b*rgba[0])/(1<<15);
+      rgba[1] = (opa_a*g + opa_b*rgba[1])/(1<<15);
+      rgba[2] = (opa_a*b + opa_b*rgba[2])/(1<<15);
+    }
+    if (!mask[1]) break;
+    rgba += mask[1];
+    mask += 2;
+  }
+};
+
 // This blend mode is used for smudging and erasing.  Smudging
 // allows to "drag" around transparency as if it was a color.  When
 // smuding over a region that is 60% opaque the result will stay 60%
index f72564a..74bd32e 100644 (file)
@@ -222,12 +222,14 @@ public:
                  float opaque, float hardness = 0.5,
                  float color_a = 1.0,
                  float aspect_ratio = 1.0, float angle = 0.0,
-                 float lock_alpha = 0.0
+                 float lock_alpha = 0.0,
+                 float colorize = 0.0
                  ) {
 
     opaque = CLAMP(opaque, 0.0, 1.0);
     hardness = CLAMP(hardness, 0.0, 1.0);
     lock_alpha = CLAMP(lock_alpha, 0.0, 1.0);
+    colorize = CLAMP(colorize, 0.0, 1.0);
     if (radius < 0.1) return false; // don't bother with dabs smaller than 0.1 pixel
     if (hardness == 0.0) return false; // infintly small center point, fully transparent outside
     if (opaque == 0.0) return false;
@@ -246,6 +248,7 @@ public:
     float normal = 1.0;
 
     normal *= 1.0-lock_alpha;
+    normal *= 1.0-colorize;
 
        if (aspect_ratio<1.0) aspect_ratio=1.0;
 
@@ -293,6 +296,11 @@ public:
           draw_dab_pixels_BlendMode_LockAlpha(mask, rgba_p,
                                               color_r_, color_g_, color_b_, lock_alpha*opaque*(1<<15));
         }
+        if (colorize) {
+          draw_dab_pixels_BlendMode_Color(mask, rgba_p,
+                                          color_r_, color_g_, color_b_,
+                                          colorize*opaque*(1<<15));
+        }
       }
     }