OSDN Git Service

layer composite ops: initial implementation
authorsigetch <sigetch@gmail.com>
Wed, 7 Sep 2011 03:11:00 +0000 (12:11 +0900)
committerAndrew Chadwick <andrewc-git@piffle.org>
Sat, 24 Sep 2011 19:12:52 +0000 (20:12 +0100)
Initial implementation of layer composite-ops. Color-dodge is not
implemented yet.

gui/layerswindow.py
lib/command.py
lib/document.py
lib/layer.py
lib/pixops.hpp
lib/tiledsurface.py

index 49f551d..91f5b03 100644 (file)
@@ -57,11 +57,17 @@ class ToolWidget (gtk.VBox):
         view.append_column(col)
 
         # Common controls
+        self.layer_mode = gtk.combo_box_new_text()
+        # FIXME: layer modes must be managed in some module or class, and should not be written hard coded.
+        for t in ["normal", "multiply", "screen", "dodge", "burn"]:
+            self.layer_mode.append_text(t)
+        
         adj = gtk.Adjustment(lower=0, upper=100, step_incr=1, page_incr=10)
         self.opacity_scale = gtk.HScale(adj)
         self.opacity_scale.set_value_pos(gtk.POS_RIGHT)
         opacity_lbl = gtk.Label(_('Opacity:'))
         opacity_hbox = gtk.HBox()
+        opacity_hbox.pack_start(self.layer_mode, expand=False)
         opacity_hbox.pack_start(opacity_lbl, expand=False)
         opacity_hbox.pack_start(self.opacity_scale, expand=True)
 
@@ -111,6 +117,7 @@ class ToolWidget (gtk.VBox):
         doc = app.doc.model
         doc.doc_observers.append(self.update)
         self.opacity_scale.connect('value-changed', self.on_opacity_changed)
+        self.layer_mode.connect('changed', self.on_layer_mode_changed)
 
         self.is_updating = False
         self.update(doc)
@@ -138,6 +145,17 @@ class ToolWidget (gtk.VBox):
 
         # Update the common widgets
         self.opacity_scale.set_value(current_layer.opacity*100)
+        mode  = current_layer.compositeop
+        if mode == "over":
+            mode = "normal"
+        layer_mode_combo = self.layer_mode
+        model = layer_mode_combo.get_model()
+        def find_iter(model, path, iter, data):
+            value = model.get_value(iter, 0)
+            if value == mode:
+                layer_mode_combo.set_active_iter(iter)
+#        self.layer_mode.
+        model.foreach(find_iter, None)
         self.is_updating = False
 
 
@@ -315,3 +333,17 @@ class ToolWidget (gtk.VBox):
             pixbuf = self.app.pixmaps.lock_open
         renderer.set_property("pixbuf", pixbuf)
 
+
+    def on_layer_mode_changed(self,*ignored):
+        if self.is_updating:
+            return
+        self.is_updating = True
+        doc = self.app.doc.model
+        mode = self.layer_mode.get_active_text()
+        if mode == "normal":
+            mode = "over"
+        print ('Change layer mode to : %s' % mode)
+        doc.set_layer_compositeop(mode)
+        self.is_updating = False
+
+
index 92cb4ef..2297702 100644 (file)
@@ -347,3 +347,26 @@ class SetLayerOpacity(Action):
         self._notify_canvas_observers([l])
         self._notify_document_observers()
 
+class SetLayerCompositeOp(Action):
+    def __init__(self, doc, compositeop, layer=None):
+        self.doc = doc
+        self.new_compositeop = compositeop
+        self.layer = layer
+    def redo(self):
+        if self.layer:
+            l = self.layer
+        else:
+            l = self.doc.layer
+        self.old_compositeop = l.compositeop
+        l.compositeop = self.new_compositeop
+        self._notify_canvas_observers([l])
+        self._notify_document_observers()
+    def undo(self):
+        if self.layer:
+            l = self.layer
+        else:
+            l = self.doc.layer
+        l.compositeop = self.old_compositeop
+        self._notify_canvas_observers([l])
+        self._notify_document_observers()
+
index 412d013..950d688 100644 (file)
@@ -270,7 +270,8 @@ class Document():
 
         for layer in layers:
             surface = layer.surface
-            surface.composite_tile_over(dst, tx, ty, mipmap_level=mipmap_level, opacity=layer.effective_opacity)
+            #surface.composite_tile_over(dst, tx, ty, mipmap_level=mipmap_level, opacity=layer.effective_opacity)
+            surface.composite_tile(dst, tx, ty, mipmap_level=mipmap_level, opacity=layer.effective_opacity, mode=layer.compositeop)
 
         mypaintlib.tile_convert_rgb16_to_rgb8(dst, dst_8bit)
 
@@ -313,6 +314,13 @@ class Document():
             self.undo()
         self.do(command.SetLayerOpacity(self, opacity, layer))
 
+    def set_layer_compositeop(self, compositeop, layer=None):
+        """Sets the composition-operation of a layer. If layer=None, works on the current layer"""
+        cmd = self.get_last_command()
+        if isinstance(cmd, command.SetLayerCompositeOp):
+            self.undo()
+        self.do(command.SetLayerCompositeOp(self, compositeop, layer))
+
     def set_background(self, obj):
         # This is not an undoable action. One reason is that dragging
         # on the color chooser would get tons of undo steps.
@@ -492,7 +500,7 @@ class Document():
             z.write(tmp, name)
             os.remove(tmp)
 
-        def add_layer(x, y, opac, surface, name, layer_name, visible=True, rect=[]):
+        def add_layer(x, y, opac, surface, name, layer_name, visible=True, compositeop='over', rect=[]):
             layer = ET.Element('layer')
             stack.append(layer)
             store_surface(surface, name, rect)
@@ -503,6 +511,7 @@ class Document():
             a['x'] = str(x)
             a['y'] = str(y)
             a['opacity'] = str(opac)
+            a['composite-op'] = str(compositeop)
             if visible:
                 a['visibility'] = 'visible'
             else:
@@ -514,7 +523,7 @@ class Document():
                 continue
             opac = l.opacity
             x, y, w, h = l.surface.get_bbox()
-            el = add_layer(x-x0, y-y0, opac, l.surface, 'data/layer%03d.png' % idx, l.name, l.visible, rect=(x, y, w, h))
+            el = add_layer(x-x0, y-y0, opac, l.surface, 'data/layer%03d.png' % idx, l.name, l.visible, l.compositeop, rect=(x, y, w, h))
             # strokemap
             sio = StringIO()
             l.save_strokemap_to_file(sio, -x, -y)
@@ -527,7 +536,7 @@ class Document():
         bg = self.background
         # save as fully rendered layer
         x, y, w, h = self.get_bbox()
-        l = add_layer(x-x0, y-y0, 1.0, bg, 'data/background.png', 'background', rect=(x,y,w,h))
+        l = add_layer(x-x0, y-y0, 1.0, bg, 'data/background.png', 'background', 'normal', rect=(x,y,w,h))
         x, y, w, h = bg.get_pattern_bbox()
         # save as single pattern (with corrected origin)
         store_surface(bg, 'data/background_tile.png', rect=(x+x0, y+y0, w, h))
@@ -635,6 +644,7 @@ class Document():
             x = int(a.get('x', '0'))
             y = int(a.get('y', '0'))
             opac = float(a.get('opacity', '1.0'))
+            compositeop = a.get('composite-op', 'over')
             visible = not 'hidden' in a.get('visibility', 'visible')
             self.add_layer(insert_idx=0, name=name)
             last_pixbuf = pixbuf
@@ -643,6 +653,7 @@ class Document():
             layer = self.layers[0]
 
             self.set_layer_opacity(helpers.clamp(opac, 0.0, 1.0), layer)
+            self.set_layer_compositeop(compositeop, layer)
             self.set_layer_visibility(visible, layer)
             print '  %.3fs converting pixbuf to layer format' % (time.time() - t1)
             # strokemap
index 28c1ff5..0b96dc5 100644 (file)
@@ -12,12 +12,13 @@ from numpy import *
 import tiledsurface, strokemap
 
 class Layer:
-    def __init__(self,name=""):
+    def __init__(self,name="",compositeop = 'over'):
         self.surface = tiledsurface.Surface()
         self.opacity = 1.0
         self.name = name
         self.visible = True
         self.locked = False
+        self.compositeop = compositeop
         self.clear()
 
     def get_effective_opacity(self):
@@ -104,7 +105,8 @@ class Layer:
             surf[:,:,:] = dst.effective_opacity * surf[:,:,:]
         for tx, ty in src.surface.get_tiles():
             surf = dst.surface.get_tile_memory(tx, ty, readonly=False)
-            src.surface.composite_tile_over(surf, tx, ty, opacity=self.effective_opacity)
+            #src.surface.composite_tile_over(surf, tx, ty, opacity=self.effective_opacity)
+            src.surface.composite_tile(surf, tx, ty, opacity=self.effective_opacity, compositeop=self.compositeop)
         dst.opacity = 1.0
 
     def get_stroke_info_at(self, x, y):
index 89c6934..d244349 100644 (file)
@@ -112,6 +112,214 @@ void tile_composite_rgba16_over_rgb16(PyObject * src, PyObject * dst, float alph
   }
 }
 
+void tile_composite_rgba16_multiply_rgb16(PyObject * src, PyObject * dst, float alpha) {
+#ifdef HEAVY_DEBUG
+  assert(PyArray_DIM(src, 0) == TILE_SIZE);
+  assert(PyArray_DIM(src, 1) == TILE_SIZE);
+  assert(PyArray_DIM(src, 2) == 4);
+  assert(PyArray_TYPE(src) == NPY_UINT16);
+  assert(PyArray_ISCARRAY(src));
+
+  assert(PyArray_DIM(dst, 0) == TILE_SIZE);
+  assert(PyArray_DIM(dst, 1) == TILE_SIZE);
+  assert(PyArray_DIM(dst, 2) == 3);
+  assert(PyArray_TYPE(dst) == NPY_UINT16);
+  assert(PyArray_ISBEHAVED(dst));
+#endif
+  
+  PyArrayObject* dst_arr = ((PyArrayObject*)dst);
+#ifdef HEAVY_DEBUG
+  assert(dst_arr->strides[1] == 3*sizeof(uint16_t));
+  assert(dst_arr->strides[2] ==   sizeof(uint16_t));
+#endif
+
+  uint32_t opac  = alpha * (1<<15) + 0.5;
+  opac = CLAMP(opac, 0, 1<<15);
+  if (opac == 0) return;
+
+  uint16_t * src_p  = (uint16_t*)((PyArrayObject*)src)->data;
+  char * p = dst_arr->data;
+  for (int y=0; y<TILE_SIZE; y++) {
+    uint16_t  * dst_p  = (uint16_t*) (p);
+    for (int x=0; x<TILE_SIZE; x++) {
+      // resultAlpha = 1.0 (thus it does not matter if resultColor is premultiplied alpha or not)
+      // resultColor = topColor + (1.0 - topAlpha) * bottomColor
+      const uint32_t one_minus_topAlpha = (1<<15) - src_p[3]*opac/(1<<15);
+      const uint32_t src_col0 = ((uint32_t) src_p[0]*opac) >> 15;
+      const uint32_t src_col1 = ((uint32_t) src_p[1]*opac) >> 15;
+      const uint32_t src_col2 = ((uint32_t) src_p[2]*opac) >> 15;
+      dst_p[0] = CLAMP(((uint32_t)src_col0*dst_p[0] + one_minus_topAlpha*dst_p[0]) / (1<<15), 0, 1<<15);
+      dst_p[1] = CLAMP(((uint32_t)src_col1*dst_p[1] + one_minus_topAlpha*dst_p[1]) / (1<<15), 0, 1<<15);
+      dst_p[2] = CLAMP(((uint32_t)src_col2*dst_p[2] + one_minus_topAlpha*dst_p[2]) / (1<<15), 0, 1<<15);
+      src_p += 4;
+      dst_p += 3;
+    }
+    p += dst_arr->strides[0];
+  }
+}
+
+void tile_composite_rgba16_screen_rgb16(PyObject * src, PyObject * dst, float alpha) {
+#ifdef HEAVY_DEBUG
+  assert(PyArray_DIM(src, 0) == TILE_SIZE);
+  assert(PyArray_DIM(src, 1) == TILE_SIZE);
+  assert(PyArray_DIM(src, 2) == 4);
+  assert(PyArray_TYPE(src) == NPY_UINT16);
+  assert(PyArray_ISCARRAY(src));
+
+  assert(PyArray_DIM(dst, 0) == TILE_SIZE);
+  assert(PyArray_DIM(dst, 1) == TILE_SIZE);
+  assert(PyArray_DIM(dst, 2) == 3);
+  assert(PyArray_TYPE(dst) == NPY_UINT16);
+  assert(PyArray_ISBEHAVED(dst));
+#endif
+  
+  PyArrayObject* dst_arr = ((PyArrayObject*)dst);
+#ifdef HEAVY_DEBUG
+  assert(dst_arr->strides[1] == 3*sizeof(uint16_t));
+  assert(dst_arr->strides[2] ==   sizeof(uint16_t));
+#endif
+
+  uint32_t opac  = alpha * (1<<15) + 0.5;
+  opac = CLAMP(opac, 0, 1<<15);
+  if (opac == 0) return;
+
+  uint16_t * src_p  = (uint16_t*)((PyArrayObject*)src)->data;
+  char * p = dst_arr->data;
+  for (int y=0; y<TILE_SIZE; y++) {
+    uint16_t  * dst_p  = (uint16_t*) (p);
+    for (int x=0; x<TILE_SIZE; x++) {
+      // resultAlpha = 1.0 (thus it does not matter if resultColor is premultiplied alpha or not)
+      // resultColor = topColor + (1.0 - topAlpha) * bottomColor
+      const uint32_t col0   = ((uint32_t)src_p[0] * opac) + (((uint32_t)dst_p[0]) << 15);
+      const uint32_t col1   = ((uint32_t)src_p[1] * opac) + (((uint32_t)dst_p[1]) << 15);
+      const uint32_t col2   = ((uint32_t)src_p[2] * opac) + (((uint32_t)dst_p[2]) << 15);
+      const uint32_t src_col0 = ((uint32_t)src_p[0] * opac) / (1<<15);
+      const uint32_t src_col1 = ((uint32_t)src_p[1] * opac) / (1<<15);
+      const uint32_t src_col2 = ((uint32_t)src_p[2] * opac) / (1<<15);
+      dst_p[0] = (col0 - ((uint32_t)src_col0*dst_p[0])) / (1<<15);
+      dst_p[1] = (col1 - ((uint32_t)src_col1*dst_p[1])) / (1<<15);
+      dst_p[2] = (col2 - ((uint32_t)src_col2*dst_p[2])) / (1<<15);
+      src_p += 4;
+      dst_p += 3;
+    }
+    p += dst_arr->strides[0];
+  }
+}
+
+void tile_composite_rgba16_dodge_rgb16(PyObject * src, PyObject * dst, float alpha) {
+#ifdef HEAVY_DEBUG
+  assert(PyArray_DIM(src, 0) == TILE_SIZE);
+  assert(PyArray_DIM(src, 1) == TILE_SIZE);
+  assert(PyArray_DIM(src, 2) == 4);
+  assert(PyArray_TYPE(src) == NPY_UINT16);
+  assert(PyArray_ISCARRAY(src));
+
+  assert(PyArray_DIM(dst, 0) == TILE_SIZE);
+  assert(PyArray_DIM(dst, 1) == TILE_SIZE);
+  assert(PyArray_DIM(dst, 2) == 3);
+  assert(PyArray_TYPE(dst) == NPY_UINT16);
+  assert(PyArray_ISBEHAVED(dst));
+#endif
+  
+  PyArrayObject* dst_arr = ((PyArrayObject*)dst);
+#ifdef HEAVY_DEBUG
+  assert(dst_arr->strides[1] == 3*sizeof(uint16_t));
+  assert(dst_arr->strides[2] ==   sizeof(uint16_t));
+#endif
+
+  uint32_t opac  = alpha * (1<<15) + 0.5;
+  opac = CLAMP(opac, 0, 1<<15);
+  if (opac == 0) return;
+
+  uint16_t * src_p  = (uint16_t*)((PyArrayObject*)src)->data;
+  char * p = dst_arr->data;
+  for (int y=0; y<TILE_SIZE; y++) {
+    uint16_t  * dst_p  = (uint16_t*) (p);
+    for (int x=0; x<TILE_SIZE; x++) {
+      // resultAlpha = 1.0 (thus it does not matter if resultColor is premultiplied alpha or not)
+      // resultColor = topColor + (1.0 - topAlpha) * bottomColor
+      const uint32_t topAlpha32 = CLAMP((uint32_t)src_p[3]*opac,0,1<<30);
+      const uint32_t topAlpha   = CLAMP((topAlpha32 >> 15), 0, 1<<15);
+      const uint32_t one_minus_topAlpha = (1<<15) - topAlpha;
+      for (int c=0; c < 3; c++) {
+        const uint32_t topAlpha_minus_src = topAlpha32 - (uint32_t)src_p[c]*topAlpha;
+        if ((topAlpha_minus_src >> 15) == 0 && dst_p[c] == 0) {
+          dst_p[c] = 0;
+        } else if ((topAlpha_minus_src >> 15) == 0) {
+          dst_p[c] = CLAMP((topAlpha32 + (uint32_t)dst_p[c] * one_minus_topAlpha)>>15, 0, (1<<15));
+        } else {
+          const uint32_t dst_times_topAlpha = (uint32_t)dst_p[c]*topAlpha;
+          if (dst_times_topAlpha > topAlpha_minus_src)
+            dst_p[c] = CLAMP(topAlpha, 0, 1<<15);
+          else
+            dst_p[c] = CLAMP((uint32_t)topAlpha * (dst_times_topAlpha >> 15) / (topAlpha_minus_src >> 15), 0, 1<<15);
+        }
+      }
+      src_p += 4;
+      dst_p += 3;
+    }
+    p += dst_arr->strides[0];
+  }
+}
+
+void tile_composite_rgba16_burn_rgb16(PyObject * src, PyObject * dst, float alpha) {
+#ifdef HEAVY_DEBUG
+  assert(PyArray_DIM(src, 0) == TILE_SIZE);
+  assert(PyArray_DIM(src, 1) == TILE_SIZE);
+  assert(PyArray_DIM(src, 2) == 4);
+  assert(PyArray_TYPE(src) == NPY_UINT16);
+  assert(PyArray_ISCARRAY(src));
+
+  assert(PyArray_DIM(dst, 0) == TILE_SIZE);
+  assert(PyArray_DIM(dst, 1) == TILE_SIZE);
+  assert(PyArray_DIM(dst, 2) == 3);
+  assert(PyArray_TYPE(dst) == NPY_UINT16);
+  assert(PyArray_ISBEHAVED(dst));
+#endif
+  
+  PyArrayObject* dst_arr = ((PyArrayObject*)dst);
+#ifdef HEAVY_DEBUG
+  assert(dst_arr->strides[1] == 3*sizeof(uint16_t));
+  assert(dst_arr->strides[2] ==   sizeof(uint16_t));
+#endif
+
+  uint32_t opac  = alpha * (1<<15) + 0.5;
+  opac = CLAMP(opac, 0, 1<<15);
+  if (opac == 0) return;
+
+  uint16_t * src_p  = (uint16_t*)((PyArrayObject*)src)->data;
+  char * p = dst_arr->data;
+  for (int y=0; y<TILE_SIZE; y++) {
+    uint16_t  * dst_p  = (uint16_t*) (p);
+    for (int x=0; x<TILE_SIZE; x++) {
+      // resultAlpha = 1.0 (thus it does not matter if resultColor is premultiplied alpha or not)
+      // resultColor = topColor + (1.0 - topAlpha) * bottomColor
+      const uint32_t topAlpha32 = (uint32_t)src_p[3]*opac;
+      const uint32_t topAlpha   = topAlpha32 >> 15;
+      const uint32_t one_minus_topAlpha = (1<<15) - topAlpha;
+      for (int c=0; c<3; c++) {
+        if (src_p[c] == 0 && dst_p[c] >= (1 << 15) - 1) {
+          dst_p[c] = 1<<15;
+        } else if (src_p[0] == 0) {
+          dst_p[c] = (dst_p[c] * one_minus_topAlpha)/(1<<15);
+        } else {
+          const uint32_t src_col32 = (uint32_t)src_p[c]*opac;
+          const uint32_t one_minus_dstcol = (1<<15) - dst_p[c];
+          if (one_minus_dstcol * topAlpha > src_col32)
+            dst_p[c] = (one_minus_topAlpha * dst_p[c]) / (1<<15);
+          else {
+            const uint32_t min_value = one_minus_dstcol * topAlpha / (src_col32>>15);
+            dst_p[c] = (topAlpha32 - topAlpha * min_value + one_minus_topAlpha * dst_p[c]) / (1<<15);
+          }
+        }
+      }
+      src_p += 4;
+      dst_p += 3;
+    }
+    p += dst_arr->strides[0];
+  }
+}
+
 // used to copy the background before starting to composite over it
 //
 // simply array copying (numpy assignment operator is about 13 times slower, sadly)
index 5c61929..5855dfa 100644 (file)
@@ -127,6 +127,20 @@ class Surface(mypaintlib.TiledSurface):
         else:
             mypaintlib.tile_convert_rgba16_to_rgba8(src, dst)
 
+    def composite_tile(self, dst, tx, ty, mipmap_level=0, opacity=1.0, mode='normal'):
+        if mode == 'normal' or mode == 'over':
+            return self.composite_tile_over(dst, tx, ty, mipmap_level, opacity)
+        elif mode == 'multiply':
+            return self.composite_tile_multiply(dst, tx, ty, mipmap_level, opacity)
+        elif mode == 'screen':
+            return self.composite_tile_screen(dst, tx, ty, mipmap_level, opacity)
+        elif mode == 'color-burn' or mode == 'burn':
+            return self.composite_tile_burn(dst, tx, ty, mipmap_level, opacity)
+        elif mode == 'color-dodge' or mode == 'dodge':
+            return self.composite_tile_dodge(dst, tx, ty, mipmap_level, opacity)
+        else:
+            raise NotImplementedError
+
     def composite_tile_over(self, dst, tx, ty, mipmap_level=0, opacity=1.0, mode=0):
         """
         composite one tile of this surface over the array dst, modifying only dst
@@ -148,6 +162,90 @@ class Surface(mypaintlib.TiledSurface):
         else:
             raise NotImplementedError
 
+    def composite_tile_multiply(self, dst, tx, ty, mipmap_level=0, opacity=1.0, mode=0):
+        """
+        composite one tile of this surface over the array dst, modifying only dst
+        """
+
+        if self.mipmap_level < mipmap_level:
+            return self.mipmap.composite_tile_multiply(dst, tx, ty, mipmap_level, opacity)
+        if not (tx,ty) in self.tiledict:
+            return
+        src = self.get_tile_memory(tx, ty, readonly=True)
+        if dst.shape[2] == 4 and dst.dtype == 'uint16':
+            # rarely used (for merging layers, also when exporting a transparent PNGs)
+            # src (premultiplied) MULTIPLY dst (premultiplied)
+            # dstColor = srcColor * dstColor + (1.0 - srcAlpha) * dstColor
+            one_minus_srcAlpha = (1<<15) - (opacity * src[:,:,3:4]).astype('uint32')
+            dst[:,:,:] = dst[:,:,:] * src[:,:,:] + ((one_minus_srcAlpha * dst[:,:,:]) >> 15).astype('uint16')
+        elif dst.shape[2] == 3 and dst.dtype == 'uint16':
+            mypaintlib.tile_composite_rgba16_multiply_rgb16(src, dst, opacity)
+        else:
+            raise NotImplementedError
+
+    def composite_tile_screen(self, dst, tx, ty, mipmap_level=0, opacity=1.0, mode=0):
+        """
+        composite one tile of this surface over the array dst, modifying only dst
+        """
+
+        if self.mipmap_level < mipmap_level:
+            return self.mipmap.composite_tile_screen(dst, tx, ty, mipmap_level, opacity)
+        if not (tx,ty) in self.tiledict:
+            return
+        src = self.get_tile_memory(tx, ty, readonly=True)
+        if dst.shape[2] == 4 and dst.dtype == 'uint16':
+            # rarely used (for merging layers, also when exporting a transparent PNGs)
+            # src (premultiplied) SCREEN dst (premultiplied)
+            # dstColor = srcColor + (1.0 - srcAlpha) * dstColor
+            one_minus_srcAlpha = (1<<15) - (opacity * src[:,:,3:4]).astype('uint32')
+            dst[:,:,:] = opacity * src[:,:,:] + dst[:,:,:] - ((src[:,:,:] * dst[:,:,:]) >> 15).astype('uint16')
+        elif dst.shape[2] == 3 and dst.dtype == 'uint16':
+            mypaintlib.tile_composite_rgba16_screen_rgb16(src, dst, opacity)
+        else:
+            raise NotImplementedError
+
+    def composite_tile_burn(self, dst, tx, ty, mipmap_level=0, opacity=1.0, mode=0):
+        """
+        composite one tile of this surface over the array dst, modifying only dst
+        """
+
+        if self.mipmap_level < mipmap_level:
+            return self.mipmap.composite_tile_burn(dst, tx, ty, mipmap_level, opacity)
+        if not (tx,ty) in self.tiledict:
+            return
+        src = self.get_tile_memory(tx, ty, readonly=True)
+        if dst.shape[2] == 4 and dst.dtype == 'uint16':
+            # rarely used (for merging layers, also when exporting a transparent PNGs)
+            # src (premultiplied) OVER dst (premultiplied)
+            # dstColor = srcColor + (1.0 - srcAlpha) * dstColor
+            one_minus_srcAlpha = (1<<15) - (opacity * src[:,:,3:4]).astype('uint32')
+            dst[:,:,:] = opacity * src[:,:,:] + ((one_minus_srcAlpha * dst[:,:,:]) >> 15).astype('uint16')
+        elif dst.shape[2] == 3 and dst.dtype == 'uint16':
+            mypaintlib.tile_composite_rgba16_burn_rgb16(src, dst, opacity)
+        else:
+            raise NotImplementedError
+
+    def composite_tile_dodge(self, dst, tx, ty, mipmap_level=0, opacity=1.0, mode=0):
+        """
+        composite one tile of this surface over the array dst, modifying only dst
+        """
+
+        if self.mipmap_level < mipmap_level:
+            return self.mipmap.composite_tile_dodge(dst, tx, ty, mipmap_level, opacity)
+        if not (tx,ty) in self.tiledict:
+            return
+        src = self.get_tile_memory(tx, ty, readonly=True)
+        if dst.shape[2] == 4 and dst.dtype == 'uint16':
+            # rarely used (for merging layers, also when exporting a transparent PNGs)
+            # src (premultiplied) OVER dst (premultiplied)
+            # dstColor = srcColor + (1.0 - srcAlpha) * dstColor
+            one_minus_srcAlpha = (1<<15) - (opacity * src[:,:,3:4]).astype('uint32')
+            dst[:,:,:] = opacity * src[:,:,:] + ((one_minus_srcAlpha * dst[:,:,:]) >> 15).astype('uint16')
+        elif dst.shape[2] == 3 and dst.dtype == 'uint16':
+            mypaintlib.tile_composite_rgba16_dodge_rgb16(src, dst, opacity)
+        else:
+            raise NotImplementedError
+
     def save_snapshot(self):
         sshot = SurfaceSnapshot()
         for t in self.tiledict.itervalues():