OSDN Git Service

Make TiledSurface an internal member of Layer
[mypaint-anime/master.git] / lib / layer.py
1 # This file is part of MyPaint.
2 # Copyright (C) 2007-2008 by Martin Renold <martinxyz@gmx.ch>
3 #
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.
8
9 import struct, zlib
10 from numpy import *
11
12 import tiledsurface, strokemap
13 from tiledsurface import DEFAULT_COMPOSITE_OP
14
15 class Layer:
16     """Representation of a layer in the document model.
17
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."""
20
21     def __init__(self, name="", compositeop=DEFAULT_COMPOSITE_OP):
22         self._surface = tiledsurface.Surface()
23         self.opacity = 1.0
24         self.name = name
25         self.visible = True
26         self.locked = False
27         self.compositeop = compositeop
28         # Called when contents of layer changed,
29         # with the bounding box of the changed region
30         self.content_observers = []
31
32         # Forward from surface implementation
33         self._surface.observers.append(self._notify_content_observers)
34
35         self.clear()
36
37     def _notify_content_observers(self, *args):
38         for f in self.content_observers:
39             f(*args)
40
41     def get_effective_opacity(self):
42         if self.visible:
43             return self.opacity
44         else:
45             return 0.0
46     effective_opacity = property(get_effective_opacity)
47
48     def get_alpha(self, x, y):
49         return self._surface.get_alpha(x, y)
50
51     def get_bbox(self):
52         return self._surface.get_bbox()
53
54     def is_empty(self):
55         return self._surface.is_empty()
56
57     def save_as_png(self, filename, *args, **kwargs):
58         self._surface.save_as_png(filename, args, kwargs)
59
60     def clear(self):
61         self.strokes = [] # contains StrokeShape instances (not stroke.Stroke)
62         self._surface.clear()
63
64     def load_from_pixbuf(self, pixbuf):
65         self.strokes = []
66         self._surface.load_from_data(pixbuf)
67
68     def render_as_pixbuf(self, *rect, **kwargs):
69         self._surface.render_as_pixbuf(*rect, **kwargs)
70
71     def save_snapshot(self):
72         return (self.strokes[:], self._surface.save_snapshot(), self.opacity)
73
74     def load_snapshot(self, data):
75         strokes, data, self.opacity = data
76         self.strokes = strokes[:]
77         self._surface.load_snapshot(data)
78
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)
86
87     def save_strokemap_to_file(self, f, translate_x, translate_y):
88         brush2id = {}
89         for stroke in self.strokes:
90             s = stroke.brush_string
91             # save brush (if not already known)
92             if s not in brush2id:
93                 brush2id[s] = len(brush2id)
94                 s = zlib.compress(s)
95                 f.write('b')
96                 f.write(struct.pack('>I', len(s)))
97                 f.write(s)
98             # save stroke
99             s = stroke.save_to_string(translate_x, translate_y)
100             f.write('s')
101             f.write(struct.pack('>II', brush2id[stroke.brush_string], len(s)))
102             f.write(s)
103         f.write('}')
104
105
106     def load_strokemap_from_file(self, f, translate_x, translate_y):
107         assert not self.strokes
108         brushes = []
109         while True:
110             t = f.read(1)
111             if t == 'b':
112                 length, = struct.unpack('>I', f.read(4))
113                 tmp = f.read(length)
114                 brushes.append(zlib.decompress(tmp))
115             elif t == 's':
116                 brush_id, length = struct.unpack('>II', f.read(2*4))
117                 stroke = strokemap.StrokeShape()
118                 tmp = f.read(length)
119                 stroke.init_from_string(tmp, translate_x, translate_y)
120                 stroke.brush_string = brushes[brush_id]
121                 self.strokes.append(stroke)
122             elif t == '}':
123                 break
124             else:
125                 assert False, 'invalid strokemap'
126
127     def merge_into(self, dst):
128         """
129         Merge this layer into dst, modifying only dst.
130         """
131         # We must respect layer visibility, because saving a
132         # transparent PNG just calls this function for each layer.
133         src = self
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)
143         dst.opacity = 1.0
144
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):
149                 return s
150
151     def get_last_stroke_info(self):
152         if not self.strokes:
153             return None
154         return self.strokes[-1]