OSDN Git Service

strokemap: thresholds tuning
authorMartin Renold <martinxyz@gmx.ch>
Sat, 6 Aug 2011 11:54:27 +0000 (13:54 +0200)
committerMartin Renold <martinxyz@gmx.ch>
Sat, 6 Aug 2011 11:57:08 +0000 (13:57 +0200)
Replace ugly thresholding code with more ugly thresholding code.

The previous code did compare the difference of premultiplied
color components, with the result that white strokes had more
weight than black strokes. This at least is fixed now, and
hopefully everything is better tuned, too.

lib/pixops.hpp
lib/strokemap.py

index 4c72274..9c4a0dd 100644 (file)
@@ -388,3 +388,78 @@ void tile_convert_rgba8_to_rgba16(PyObject * src, PyObject * dst) {
     }
   }
 }
+
+// used in strokemap.py
+//
+// Calculates a 1-bit bitmap of the stroke shape using two snapshots
+// of the layer (the layer before and after the stroke).
+//
+// If the alpha increases a lot, we want the stroke to appear in
+// the strokemap, even if the color did not change. If the alpha
+// decreases a lot, we want to ignore the stroke (eraser). If
+// the alpha decreases just a little, but the color changes a
+// lot (eg. heavy smudging or watercolor brushes) we want the
+// stroke still to be pickable.
+//
+// If the layer alpha was (near) zero, we record the stroke even if it
+// is barely visible. This gives a bigger target to point-and-select.
+//
+void tile_perceptual_change_strokemap(PyObject * a, PyObject * b, PyObject * res) {
+
+  assert(PyArray_TYPE(a) == NPY_UINT16);
+  assert(PyArray_TYPE(b) == NPY_UINT16);
+  assert(PyArray_TYPE(res) == NPY_UINT8);
+  assert(PyArray_ISCARRAY(a));
+  assert(PyArray_ISCARRAY(b));
+  assert(PyArray_ISCARRAY(res));
+
+  uint16_t * a_p  = (uint16_t*)PyArray_DATA(a);
+  uint16_t * b_p  = (uint16_t*)PyArray_DATA(b);
+  uint8_t * res_p = (uint8_t*)PyArray_DATA(res);
+
+  for (int y=0; y<TILE_SIZE; y++) {
+    for (int x=0; x<TILE_SIZE; x++) {
+
+      int32_t color_change = 0;
+      // We want to compare a.color with b.color, but we only know
+      // (a.color * a.alpha) and (b.color * b.alpha).  We multiply
+      // each component with the alpha of the other image, so they are
+      // scaled the same and can be compared.
+
+      for (int i=0; i<3; i++) {
+        int32_t a_col = (uint32_t)a_p[i] * b_p[3] / (1<<15); // a.color * a.alpha*b.alpha
+        int32_t b_col = (uint32_t)b_p[i] * a_p[3] / (1<<15); // b.color * a.alpha*b.alpha
+        color_change += abs(b_col - a_col);
+      }
+      // "color_change" is in the range [0, 3*a_a]
+      // if either old or new alpha is (near) zero, "color_change" is (near) zero
+
+      int32_t alpha_old = a_p[3];
+      int32_t alpha_new = b_p[3];
+
+      // Note: the thresholds below are arbitrary choices found to work okay
+
+      // We report a color change only if both old and new color are
+      // well-defined (big enough alpha).
+      bool is_perceptual_color_change = color_change > MAX(alpha_old, alpha_new)/16;
+
+      int32_t alpha_diff = alpha_new - alpha_old; // no abs() here (ignore erasers)
+      // We check the alpha increase relative to the previous alpha.
+      bool is_perceptual_alpha_increase = alpha_diff > (1<<15)/4;
+
+      // this one is responsible for making fat big ugly easy-to-hit pointer targets
+      bool is_big_relative_alpha_increase  = alpha_diff > (1<<15)/64 && alpha_diff > alpha_old/2;
+
+      if (is_perceptual_alpha_increase || is_big_relative_alpha_increase || is_perceptual_color_change) {
+        res_p[0] = 1;
+      } else {
+        res_p[0] = 0;
+      }
+
+      a_p += 4;
+      b_p += 4;
+      res_p += 1;
+    }
+  }
+}
+
index 2f111fd..dde5369 100644 (file)
@@ -9,6 +9,7 @@
 import time, struct
 import zlib
 from numpy import *
+import mypaintlib
 
 import tiledsurface, idletask
 N = tiledsurface.N
@@ -42,22 +43,8 @@ class StrokeShape:
                 a_data = a.get((tx, ty), tiledsurface.transparent_tile).rgba
                 b_data = b.get((tx, ty), tiledsurface.transparent_tile).rgba
 
-                # calculate the "perceptual" amount of difference
-                absdiff = zeros((N, N), 'uint32')
-                for i in range(4): # RGBA
-                    absdiff += abs(a_data[:,:,i].astype('int32') - b_data[:,:,i])
-                # ignore badly visible (parts of) strokes, eg. very faint strokes
-                #
-                # This is an arbitrary threshold. If it is too high, an
-                # ink stroke with slightly different color than the one
-                # below will not be pickable.  If it is too high, barely
-                # visible strokes will make things below unpickable.
-                #
-                threshold = (1<<15)*4 / 16 # require 1/16 of the max difference (also not bad: 1/8)
-                is_different = absdiff > threshold
-                # except if there is no previous stroke below it (that is, layer had zero alpha)
-                is_different |= (absdiff > 0) & (a_data[:,:,3] == 0)
-                data = is_different.astype('uint8')
+                data = empty((N, N), 'uint8')
+                mypaintlib.tile_perceptual_change_strokemap(a_data, b_data, data)
 
                 data_compressed = zlib.compress(data.tostring())
                 self.strokemap[tx, ty] = data_compressed