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)
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)
# 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
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
+
+
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()
+
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)
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.
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)
a['x'] = str(x)
a['y'] = str(y)
a['opacity'] = str(opac)
+ a['composite-op'] = str(compositeop)
if visible:
a['visibility'] = 'visible'
else:
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)
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))
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
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
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):
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):
}
}
+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)
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
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():