OSDN Git Service

added experimental SConstruct
authorMartin Renold <martinxyz@gmx.ch>
Sun, 24 Feb 2008 15:56:08 +0000 (15:56 +0000)
committerMartin Renold <martinxyz@gmx.ch>
Sun, 24 Feb 2008 15:56:08 +0000 (15:56 +0000)
added very experimental first steps towards tiled layers

svn://old.homeip.net/code/mypaint@581

12 files changed:
PLAN
SConstruct [new file with mode: 0644]
brush_dab.c
brush_dab.h
composite_benchmark.py [new file with mode: 0644]
drawwindow.py
gtkmybrush.c
gtkmydrawwidget.c
gtkmydrawwidget.h
mydrawwidget.override
mypaint
tile.py [new file with mode: 0644]

diff --git a/PLAN b/PLAN
index e77037f..ee34f51 100644 (file)
--- a/PLAN
+++ b/PLAN
 - canvas rotation
   - (fast rotozoom? also would be nice to use OpenGL if available)
 
+- swap tools
+  - "Apparently Corel Photo Paint uses spacebar for swapping to previous tool."
+  - variants: tool history (like undo), skip tools not painted with, visually show tool switch
+
 - allow dots drawing
   - a dot should draw even without motion
   - is dabs-per-second enough, or is dabs-per-dpressure needed?
diff --git a/SConstruct b/SConstruct
new file mode 100644 (file)
index 0000000..e1bbb74
--- /dev/null
@@ -0,0 +1,90 @@
+import os
+from subprocess import Popen, PIPE
+
+profile = "" # -g -pg
+
+env = Environment(ENV = os.environ)
+env.ParseConfig('pkg-config --cflags --libs gtk+-2.0 pygtk-2.0')
+env.ParseConfig('python-config --cflags --ldflags')
+
+env.Command('brushsettings.h', ['generate.py', 'brushsettings.py'], './generate.py')
+
+defsdir = Popen(['pkg-config', 'pygtk-2.0', '--variable=defsdir'], stdout=PIPE).communicate()[0].strip()
+assert defsdir, 'you need pygtk'
+
+pygtk_codegen = 'pygtk-codegen-2.0'
+pygtk_h2def = '/usr/share/pygtk/2.0/codegen/h2def.py'
+glib_genmarshal = 'glib-genmarshal'
+
+env.Command('mydrawwidget.defs.c', ['mydrawwidget.defs', 'mydrawwidget.override'], '''
+%s --prefix mydrawwidget \
+--register %s/gdk-types.defs \
+--register %s/gtk-types.defs \
+--override mydrawwidget.override \
+mydrawwidget.defs > mydrawwidget.defs.c
+''' % (pygtk_codegen, defsdir, defsdir))
+
+defs_inputs = 'gtkmydrawwidget.h gtkmybrush.h gtkmysurface.h gtkmysurfaceold.h'
+env.Command('mydrawwidget.defs', Split(defs_inputs + ' fix_generated_defs.py'), '''
+python %s %s > mydrawwidget.defs && ./fix_generated_defs.py
+''' % (pygtk_h2def, defs_inputs))
+
+env.Command('mymarshal.h', 'mymarshal.list', glib_genmarshal + ' --prefix=mymarshal --header $SOURCE > $TARGET')
+env.Command('mymarshal.c', 'mymarshal.list', glib_genmarshal + ' --prefix=mymarshal --body   $SOURCE > $TARGET')
+
+sources = '''
+mydrawwidget.defs.c
+mydrawwidgetmodule.c 
+gtkmydrawwidget.c 
+gtkmybrush.c 
+gtkmysurface.c 
+gtkmysurfaceold.c 
+brush_dab.c 
+helpers.c 
+mapping.c 
+mymarshal.c 
+lfd.c 
+gestures.c 
+stroke_recorder.c
+'''
+
+#env.SharedLibrary('mydrawwidget', Split(sources))
+env.LoadableModule('mydrawwidget', Split(sources),
+                   LDMODULEPREFIX='',
+                   )
+
+# gtkmydrawwidget.o:   brushsettings.h gtkmydrawwidget.c gtkmydrawwidget.h
+#      cc $(CFLAGS) -c -o $@ gtkmydrawwidget.c
+# env.
+
+# --- cut here ---
+
+# PROFILE = #-g #-pg
+# CFLAGS = $(PROFILE) -O3 `pkg-config --cflags gtk+-2.0 pygtk-2.0` -Wall -Werror -I/usr/include/python2.3/ -I.
+# LDFLAGS = $(PROFILE) -O3 `pkg-config --libs gtk+-2.0 pygtk-2.0` -Wall -Werror
+# DEFSDIR = `pkg-config --variable=defsdir pygtk-2.0`
+
+# all: mydrawwidget.so
+
+# gtkmydrawwidget.o:   brushsettings.h gtkmydrawwidget.c gtkmydrawwidget.h
+#      cc $(CFLAGS) -c -o $@ gtkmydrawwidget.c
+
+# gtkmybrush.o:        brushsettings.h gtkmybrush.c gtkmybrush.h
+#      cc $(CFLAGS) -c -o $@ gtkmybrush.c
+
+# clean:
+#      rm *.o *.so brushsettings.h mydrawwidget.defs mydrawwidget.defs.c
+
+# mydrawwidget.defs.c: mydrawwidget.defs mydrawwidget.override
+#      pygtk-codegen-2.0 --prefix mydrawwidget \
+#      --register $(DEFSDIR)/gdk-types.defs \
+#      --register $(DEFSDIR)/gtk-types.defs \
+#      --override mydrawwidget.override \
+#      mydrawwidget.defs > mydrawwidget.defs.c
+
+# mydrawwidget.defs: gtkmydrawwidget.h gtkmybrush.h surface.h Makefile
+#      /usr/share/pygtk/2.0/codegen/h2def.py gtkmydrawwidget.h gtkmybrush.h > mydrawwidget.defs
+#      ./caller_owns_return.py mydrawwidget.defs get_nonwhite_as_pixbuf get_as_pixbuf
+
+# mydrawwidget.so: mydrawwidget.defs.c mydrawwidgetmodule.c gtkmydrawwidget.o surface.o gtkmybrush.o brush_dab.o helpers.o
+#      $(CC) $(LDFLAGS) $(CFLAGS) -shared $^ -o $@
index 47c6860..7ade5e4 100644 (file)
@@ -8,7 +8,10 @@
  */
 
 #include <math.h>
+#include "Python.h"
+#include "Numeric/arrayobject.h"
 #include "brush_dab.h"
+
 // This actually draws every pixel of the dab.
 // Called from brush_prepare_and_draw_dab.
 // The parameters are only in the header file to avoid duplication.
     ExpandRectToIncludePoint (bbox, bb_x, bb_y);
     ExpandRectToIncludePoint (bbox, bb_x+bb_w-1, bb_y+bb_h-1);
   }
+
   return 1;
 }
 
+
+void draw_brush_dab_on_tiled_surface (PyObject * s, 
+                                      GRand * rng,
+                                      float x, float y, 
+                                      float radius, float opaque, float hardness,
+                                      float color_r, float color_g, float color_b
+                                      ) {
+  float r_fringe;
+  int x0, y0;
+  int x1, y1;
+  int xp, yp;
+  float xx, yy, rr;
+  float radius2, one_over_radius2;
+
+  if (opaque == 0) return;
+  if (radius < 0.1) return;
+  if (hardness == 0) return; // infintly small point, rest transparent
+
+  r_fringe = radius + 1;
+  rr = SQR(radius);
+
+
+  int tx1 = floor(x - r_fringe) / TILE_SIZE;
+  int tx2 = floor(x + r_fringe) / TILE_SIZE;
+  int ty1 = floor(y - r_fringe) / TILE_SIZE;
+  int ty2 = floor(y + r_fringe) / TILE_SIZE;
+  int tx, ty;
+  for (ty = ty1; ty <= ty2; ty++) {
+    for (tx = tx1; tx <= tx2; tx++) {
+      // OPTIMIZE: cache tile buffer pointers, so we don't have to call python for each dab;
+      //           this could be used to return a list of dirty tiles at the same time
+      //           (But profile this code first!)
+      PyObject* tuple;
+      tuple = PyObject_CallMethod(tiled_surface, "getTileMemory", "(ii)", tx, ty);
+      if (!tuple) return;
+      PyObject* rgb   = PyTuple_GET_ITEM(tuple, 0);
+      PyObject* alpha = PyTuple_GET_ITEM(tuple, 1);
+      Py_DECREF(tuple);
+
+      assert(PyArray_DIMS(rgb) == 3);
+      assert(PyArray_DIM(rgb, 0) == TILE_SIZE);
+      assert(PyArray_DIM(rgb, 1) == TILE_SIZE);
+      assert(PyArray_DIM(rgb, 2) == 3);
+
+      assert(PyArray_DIMS(alpha) == 3);
+      assert(PyArray_DIM(alpha, 0) == TILE_SIZE);
+      assert(PyArray_DIM(alpha, 1) == TILE_SIZE);
+      assert(PyArray_DIM(alpha, 2) == 1);
+
+      assert(ISCARRAY(rgb));
+      assert(ISBEHAVED(rgb));
+      assert(ISCARRAY(alpha));
+      assert(ISBEHAVED(alpha));
+
+      float * rgb_p   = (float*)((PyArrayObject*)rgb)->data;
+      float * alpha_p = (float*)((PyArrayObject*)alpha)->data;
+
+      float xc = x - tx*TILE_SIZE;
+      float yc = y - ty*TILE_SIZE;
+
+      int x0 = floor (xc - r_fringe);
+      int y0 = floor (yc - r_fringe);
+      int x1 = ceil (xc + r_fringe);
+      int y1 = ceil (yc + r_fringe);
+      if (x0 < 0) x0 = 0;
+      if (y0 < 0) y0 = 0;
+      if (x1 > TILE_SIZE-1) x1 = TILE_SIZE-1;
+      if (y1 > TILE_SIZE-1) y1 = TILE_SIZE-1;
+
+      for (yp = y0; yp <= y1; yp++) {
+        yy = (yp + 0.5 - y);
+        yy *= yy;
+        for (xp = x0; xp <= x1; xp++) {
+          xx = (xp + 0.5 - x);
+          xx *= xx;
+          rr = (yy + xx) * one_over_radius2;
+          // rr is in range 0.0..1.0*sqrt(2)
+
+          if (rr <= 1.0) {
+            float opa = opaque;
+            if (hardness < 1.0) {
+              if (rr < hardness) {
+                opa *= rr + 1-(rr/hardness);
+                // hardness == 0 is nonsense, excluded above
+              } else {
+                opa *= hardness/(hardness-1)*(rr-1);
+              }
+            }
+            color_r * opa;
+
+            // We are manipulating pixels with premultiplied alpha directly.
+            // This is an "over" operation (opa = topAlpha).
+            //
+            // resultAlpha = topAlpha + (1.0 - topAlpha) * bottomAlpha
+            // resultColor = topColor + (1.0 - topAlpha) * bottomColor
+
+            float opa_ = 1.0 - opa;
+            int idx = yp*TILE_SIZE + xp;
+            alpha_p[idx] = opa + opa_*alpha_p[idx]; 
+            idx *= 3;
+            rgb_p[idx+0] = color_r*opa + opa_*rgb_p[idx+0]; 
+            rgb_p[idx+1] = color_r*opa + opa_*rgb_p[idx+1]; 
+            rgb_p[idx+2] = color_r*opa + opa_*rgb_p[idx+2]; 
+          }
+        }
+      }
+    }
+  }
+}
+
index ce01a62..d8bf1a2 100644 (file)
 #include "gtkmysurfaceold.h"
 #include "helpers.h"
 
+// FIXME remove those includes
+#include "Python.h"
+#include "gtkmydrawwidget.h"
+
+void draw_brush_dab_on_tiled_surface (PyObject * s, 
+                                      GRand * rng,
+                                      float x, float y, 
+                                      float radius, float opaque, float hardness,
+                                      float color_r, float color_g, float color_b
+                                      );
+
 // The bbox (bounding box) can be NULL, if not, it will be expanded to
 // include the surface area which was just painted.
 // Returns 0 if nothing was painted.
diff --git a/composite_benchmark.py b/composite_benchmark.py
new file mode 100644 (file)
index 0000000..16ae29d
--- /dev/null
@@ -0,0 +1,92 @@
+from scipy import *
+#from pylab import *
+from time import time
+
+import gtk
+gdk = gtk.gdk
+
+iterations=100
+N=64
+
+def benchmarkGdkPixbuf():
+    src = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, N, N)  
+    dst = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, N, N)  
+
+    src.get_pixels_array()[:,:,:] = (rand(N,N,4)*255).astype('uint8')
+    dst.get_pixels_array()[:,:,:] = (rand(N,N,4)*255).astype('uint8')
+
+    t = time()
+    for i in xrange(iterations):
+        src.composite(dst, 0, 0, N, N, 0, 0, 1, 1, gtk.gdk.INTERP_NEAREST, 255)
+    return time() - t
+
+def benchmarkSciPy(t='float32'):
+    src = rand(N,N,4).astype(t)
+    dst = rand(N,N,4).astype(t)
+
+    src_rgb = src[:,:,0:3].copy()
+    src_a   = src[:,:,3: ].copy()
+    dst_rgb = dst[:,:,0:3].copy()
+    dst_a   = dst[:,:,3: ].copy()
+
+    t = time()
+    for i in xrange(iterations):
+        src_a_ = 1.0-src_a
+        dst_rgb = src_rgb * src_a + dst_rgb * src_a_
+        dst_a = src_a + dst_a * src_a_
+    return time() - t
+
+def benchmarkSciPyPremulSlice(t='float32'):
+    src = rand(N,N,4).astype(t)
+    dst = rand(N,N,4).astype(t)
+
+    t = time()
+    for i in xrange(iterations):
+        dst = src + dst - dst[:,:,3:]*dst
+    return time() - t
+
+def benchmarkSciPyPremul(t='float32'):
+    src = rand(N,N,4).astype(t)
+    dst = rand(N,N,4).astype(t)
+
+    src_rgb = src[:,:,0:3].copy()
+    src_a   = src[:,:,3: ].copy()
+    dst_rgb = dst[:,:,0:3].copy()
+    dst_a   = dst[:,:,3: ].copy()
+
+    t = time()
+    for i in xrange(iterations):
+        # resultColor = topColor + (1.0 - topAlpha) * bottomColor
+        dst_rgb = src_rgb + dst_rgb - src_a*dst_rgb
+        # resultAlpha = topAlpha + (1.0 - topAlpha) * bottomAlpha
+        dst_a = src_a + dst_a - src_a*dst_a
+    print dst_a.dtype
+    return time() - t
+
+def benchmarkSciPyPremulOpt(t='float32'):
+    src = rand(N,N,4).astype(t)
+    dst = rand(N,N,4).astype(t)
+
+    src_rgb = src[:,:,0:3].copy()
+    src_a   = src[:,:,3: ].copy()
+    dst_rgb = dst[:,:,0:3].copy()
+    dst_a   = dst[:,:,3: ].copy()
+
+    t = time()
+    for i in xrange(iterations):
+        # resultColor = topColor + (1.0 - topAlpha) * bottomColor
+        dst_rgb += src_rgb
+        dst_rgb -= src_a*dst_rgb
+        # resultAlpha = topAlpha + (1.0 - topAlpha) * bottomAlpha
+        dst_a += src_a
+        dst_a -= src_a*dst_a
+    print dst_a.dtype
+    return time() - t
+
+
+a = benchmarkGdkPixbuf()
+print a
+b = benchmarkSciPyPremul()
+print b, b/a
+c = benchmarkSciPyPremulOpt()
+print c, c/a, c/b
index 464b2e0..ebfb997 100644 (file)
@@ -40,6 +40,11 @@ class Window(gtk.Window):
         self.mdw.toolchange_observers.append(self.toolchange_cb)
         self.mdw.connect("gesture-recognized", self.gesture_recognized_cb)
 
+        # TEST
+        import tile
+        self.tiled_surface = tile.TiledLayer()
+        self.mdw.set_tiled_surface(self.tiled_surface)
+
         self.statusbar = sb = gtk.Statusbar()
         vbox.pack_end(sb, expand=False)
 
index 70398d6..bea8221 100644 (file)
@@ -511,6 +511,14 @@ int brush_prepare_and_draw_dab (GtkMyBrush * b, GtkMySurfaceOld * s, Rect * bbox
     if (hardness > 1.0) hardness = 1.0;
     if (hardness < 0.0) hardness = 0.0;
 
+    // FIXME: don't use global variable
+    if (tiled_surface) {
+      draw_brush_dab_on_tiled_surface (tiled_surface, b->rng, 
+                                       x, y, radius, opaque, hardness,
+                                       c[0] / 255.0, c[1] / 255.0, c[2] / 255.0);
+
+    }
+    
     return draw_brush_dab (s, bbox, b->rng, 
                            x, y, radius, opaque, hardness,
                            c[0], c[1], c[2]);
index 77f658e..418fb3d 100644 (file)
@@ -407,6 +407,16 @@ gtk_my_draw_widget_set_brush (GtkMyDrawWidget *mdw, GtkMyBrush * brush)
   return brush_old;
 }
 
+PyObject * tiled_surface;
+
+void gtk_my_draw_widget_set_tiled_surface (GtkMyDrawWidget *mdw, PyObject * tiled_surface_)
+{
+  // FIXME: remove globals...
+  Py_CLEAR(tiled_surface);
+  Py_INCREF(tiled_surface_);
+  tiled_surface = tiled_surface_;
+}
+
 void gtk_my_draw_widget_allow_dragging (GtkMyDrawWidget *mdw, int allow)
 {
   mdw->allow_dragging = allow;
index b6f62ed..138de18 100644 (file)
@@ -15,6 +15,8 @@
 #include <gtk/gtkdrawingarea.h>
 #include <gdk-pixbuf/gdk-pixbuf.h>
 
+#include "Python.h"
+
 #include "gtkmysurfaceold.h"
 #include "gtkmybrush.h"
 
 typedef struct _GtkMyDrawWidget       GtkMyDrawWidget;
 typedef struct _GtkMyDrawWidgetClass  GtkMyDrawWidgetClass;
 
+// FIXME: move to better place, also it is a duplication of tile.py
+#define TILE_SIZE 64
+extern PyObject * tiled_surface;
+
 struct _GtkMyDrawWidget
 {
   GtkDrawingArea widget;
@@ -67,6 +73,7 @@ GtkWidget* gtk_my_draw_widget_new        (void);
 
 void gtk_my_draw_widget_clear (GtkMyDrawWidget *mdw);
 GtkMyBrush* gtk_my_draw_widget_set_brush (GtkMyDrawWidget *mdw, GtkMyBrush * brush);
+void gtk_my_draw_widget_set_tiled_surface (GtkMyDrawWidget *mdw, PyObject * tiled_surface);
 void gtk_my_draw_widget_set_viewport (GtkMyDrawWidget *mdw, float x, float y);
 float gtk_my_draw_widget_get_viewport_x (GtkMyDrawWidget *mdw);
 float gtk_my_draw_widget_get_viewport_y (GtkMyDrawWidget *mdw);
index 051aa39..81034f6 100644 (file)
@@ -18,6 +18,7 @@ ignore-glob
   *_get_type
 ignore brush_stroke_to 
 ignore brush_reset 
+ignore gtk_my_surface_old_get_nonwhite_region  
 %%
 override gtk_my_draw_widget_stop_recording noargs
 static PyObject *
diff --git a/mypaint b/mypaint
index bebcd3f..1ef8422 100755 (executable)
--- a/mypaint
+++ b/mypaint
@@ -33,7 +33,7 @@ if os.path.basename(dir_install) == 'bin':
     sys.path.insert(0, share + '/python')
 else:
     # we are not installed, use .libs/mydrawwidget.so
-    sys.path.insert(0,'.libs')
+    #sys.path.insert(0,'.libs')
     prefix=None
     share='.'
     # checking for import error below
diff --git a/tile.py b/tile.py
new file mode 100644 (file)
index 0000000..8c557cc
--- /dev/null
+++ b/tile.py
@@ -0,0 +1,39 @@
+from scipy import *
+from pylab import *
+
+
+N = 64
+
+class Tile:
+    def __init__(self):
+        self.rgb   = zeros((N, N, 3), 'float32')
+        self.alpha = zeros((N, N, 1), 'float32')
+        
+    #def composite(self, other):
+        # resultColor = topColor + (1.0 - topAlpha) * bottomColor
+    #    self.rgb = other.alpha * other.rgb + (1.0-other.alpha) * self.rgb
+    #    self.alpha = other.alpha + (1.0-other.alpha)*self.alpha
+
+class TiledLayer:
+    def __init__(self):
+        self.tiledict = {}
+        self.alpha = 1.0
+
+    def getTileMemory(self, x, y):
+        t = self.tiledict.get((x, y))
+        if t is None:
+            print 'allocating tile', (x, y)
+            t = Tile()
+            self.tiledict[(x, y)] = t
+        return t.rgb, t.alpha
+        
+    def tiles(self, x, y, w, h):
+        for xx in xrange(x/Tile.N, (x+w)/Tile.N+1):
+            for yy in xrange(y/Tile.N, (x+h)/Tile.N+1):
+                tile = self.tiledict.get((xx, yy), None)
+                if tile is not None:
+                    yield xx*Tile.N, yy*Tile.N, tile
+
+    #def composite(self, dst):
+        
+