- 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?
--- /dev/null
+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 $@
*/
#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];
+ }
+ }
+ }
+ }
+ }
+}
+
#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.
--- /dev/null
+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
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)
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]);
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;
#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;
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);
*_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 *
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
--- /dev/null
+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):
+
+