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.
11 from gettext import gettext as _
13 import tiledsurface, strokemap
16 # (internal-name, display-name)
17 ("svg:src-over", _("Normal")),
18 ("svg:multiply", _("Multiply")),
19 ("svg:color-burn", _("Burn")),
20 ("svg:color-dodge", _("Dodge")),
21 ("svg:screen", _("Screen")),
24 DEFAULT_COMPOSITE_OP = COMPOSITE_OPS[0][0]
25 VALID_COMPOSITE_OPS = set([n for n, d in COMPOSITE_OPS])
28 """Representation of a layer in the document model.
30 The actual content of the layer is held by the surface implementation.
31 This is an internal detail that very few consumers should care about."""
33 def __init__(self, name="", compositeop=DEFAULT_COMPOSITE_OP):
34 self._surface = tiledsurface.Surface()
39 self.compositeop = compositeop
40 # Called when contents of layer changed,
41 # with the bounding box of the changed region
42 self.content_observers = []
44 # Forward from surface implementation
45 self._surface.observers.append(self._notify_content_observers)
49 def _notify_content_observers(self, *args):
50 for f in self.content_observers:
53 def get_effective_opacity(self):
58 effective_opacity = property(get_effective_opacity)
60 def get_alpha(self, x, y, radius):
61 return self._surface.get_alpha(x, y, radius)
64 return self._surface.get_bbox()
67 return self._surface.is_empty()
69 def save_as_png(self, filename, *args, **kwargs):
70 self._surface.save_as_png(filename, *args, **kwargs)
72 def stroke_to(self, brush, x, y, pressure, xtilt, ytilt, dtime):
73 """Render a part of a stroke."""
74 self._surface.begin_atomic()
75 split = brush.stroke_to(self._surface, x, y,
76 pressure, xtilt, ytilt, dtime)
77 self._surface.end_atomic()
81 self.strokes = [] # contains StrokeShape instances (not stroke.Stroke)
84 def load_from_surface(self, surface):
86 self._surface.load_from_surface(surface)
88 def render_as_pixbuf(self, *rect, **kwargs):
89 return self._surface.render_as_pixbuf(*rect, **kwargs)
91 def save_snapshot(self):
92 return (self.strokes[:], self._surface.save_snapshot(), self.opacity)
94 def load_snapshot(self, data):
95 strokes, data, self.opacity = data
96 self.strokes = strokes[:]
97 self._surface.load_snapshot(data)
99 def add_stroke(self, stroke, snapshot_before):
100 before = snapshot_before[1] # extract surface snapshot
101 after = self._surface.save_snapshot()
102 shape = strokemap.StrokeShape()
103 shape.init_from_snapshots(before, after)
104 shape.brush_string = stroke.brush_settings
105 self.strokes.append(shape)
107 def save_strokemap_to_file(self, f, translate_x, translate_y):
109 for stroke in self.strokes:
110 s = stroke.brush_string
111 # save brush (if not already known)
112 if s not in brush2id:
113 brush2id[s] = len(brush2id)
116 f.write(struct.pack('>I', len(s)))
119 s = stroke.save_to_string(translate_x, translate_y)
121 f.write(struct.pack('>II', brush2id[stroke.brush_string], len(s)))
126 def load_strokemap_from_file(self, f, translate_x, translate_y):
127 assert not self.strokes
132 length, = struct.unpack('>I', f.read(4))
134 brushes.append(zlib.decompress(tmp))
136 brush_id, length = struct.unpack('>II', f.read(2*4))
137 stroke = strokemap.StrokeShape()
139 stroke.init_from_string(tmp, translate_x, translate_y)
140 stroke.brush_string = brushes[brush_id]
141 self.strokes.append(stroke)
145 assert False, 'invalid strokemap'
147 def merge_into(self, dst):
149 Merge this layer into dst, modifying only dst.
151 # We must respect layer visibility, because saving a
152 # transparent PNG just calls this function for each layer.
154 dst.strokes.extend(self.strokes)
155 for tx, ty in dst._surface.get_tiles():
156 surf = dst._surface.get_tile_memory(tx, ty, readonly=False)
157 surf[:,:,:] = dst.effective_opacity * surf[:,:,:]
158 for tx, ty in src._surface.get_tiles():
159 surf = dst._surface.get_tile_memory(tx, ty, readonly=False)
160 src._surface.composite_tile(surf, tx, ty,
161 opacity=self.effective_opacity,
162 mode=self.compositeop)
165 def get_stroke_info_at(self, x, y):
166 x, y = int(x), int(y)
167 for s in reversed(self.strokes):
168 if s.touches_pixel(x, y):
171 def get_last_stroke_info(self):
174 return self.strokes[-1]