1 # This file is part of MyPaint.
2 # Copyright (C) 2007-2008 by Martin Renold <martinxyz@gmx.ch>
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 2 of the License, or
7 # (at your option) any later version.
12 import tiledsurface, strokemap
13 from tiledsurface import DEFAULT_COMPOSITE_OP
16 """Representation of a layer in the document model.
18 The actual content of the layer is held by the surface implementation.
19 This is an internal detail that very few consumers should care about."""
21 def __init__(self, name="", compositeop=DEFAULT_COMPOSITE_OP):
22 self._surface = tiledsurface.Surface()
27 self.compositeop = compositeop
28 # Called when contents of layer changed,
29 # with the bounding box of the changed region
30 self.content_observers = []
32 # Forward from surface implementation
33 self._surface.observers.append(self._notify_content_observers)
37 def _notify_content_observers(self, *args):
38 for f in self.content_observers:
41 def get_effective_opacity(self):
46 effective_opacity = property(get_effective_opacity)
48 def get_alpha(self, x, y):
49 return self._surface.get_alpha(x, y)
52 return self._surface.get_bbox()
55 return self._surface.is_empty()
57 def save_as_png(self, filename, *args, **kwargs):
58 self._surface.save_as_png(filename, args, kwargs)
61 self.strokes = [] # contains StrokeShape instances (not stroke.Stroke)
64 def load_from_pixbuf(self, pixbuf):
66 self._surface.load_from_data(pixbuf)
68 def render_as_pixbuf(self, *rect, **kwargs):
69 self._surface.render_as_pixbuf(*rect, **kwargs)
71 def save_snapshot(self):
72 return (self.strokes[:], self._surface.save_snapshot(), self.opacity)
74 def load_snapshot(self, data):
75 strokes, data, self.opacity = data
76 self.strokes = strokes[:]
77 self._surface.load_snapshot(data)
79 def add_stroke(self, stroke, snapshot_before):
80 before = snapshot_before[1] # extract surface snapshot
81 after = self._surface.save_snapshot()
82 shape = strokemap.StrokeShape()
83 shape.init_from_snapshots(before, after)
84 shape.brush_string = stroke.brush_settings
85 self.strokes.append(shape)
87 def save_strokemap_to_file(self, f, translate_x, translate_y):
89 for stroke in self.strokes:
90 s = stroke.brush_string
91 # save brush (if not already known)
93 brush2id[s] = len(brush2id)
96 f.write(struct.pack('>I', len(s)))
99 s = stroke.save_to_string(translate_x, translate_y)
101 f.write(struct.pack('>II', brush2id[stroke.brush_string], len(s)))
106 def load_strokemap_from_file(self, f, translate_x, translate_y):
107 assert not self.strokes
112 length, = struct.unpack('>I', f.read(4))
114 brushes.append(zlib.decompress(tmp))
116 brush_id, length = struct.unpack('>II', f.read(2*4))
117 stroke = strokemap.StrokeShape()
119 stroke.init_from_string(tmp, translate_x, translate_y)
120 stroke.brush_string = brushes[brush_id]
121 self.strokes.append(stroke)
125 assert False, 'invalid strokemap'
127 def merge_into(self, dst):
129 Merge this layer into dst, modifying only dst.
131 # We must respect layer visibility, because saving a
132 # transparent PNG just calls this function for each layer.
134 dst.strokes.extend(self.strokes)
135 for tx, ty in dst._surface.get_tiles():
136 surf = dst._surface.get_tile_memory(tx, ty, readonly=False)
137 surf[:,:,:] = dst.effective_opacity * surf[:,:,:]
138 for tx, ty in src._surface.get_tiles():
139 surf = dst._surface.get_tile_memory(tx, ty, readonly=False)
140 src._surface.composite_tile(surf, tx, ty,
141 opacity=self.effective_opacity,
142 mode=self.compositeop)
145 def get_stroke_info_at(self, x, y):
146 x, y = int(x), int(y)
147 for s in reversed(self.strokes):
148 if s.touches_pixel(x, y):
151 def get_last_stroke_info(self):
154 return self.strokes[-1]