OSDN Git Service

load_ora: load png layers faster, use less memory
[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 from gettext import gettext as _
12
13 import tiledsurface, strokemap
14
15 COMPOSITE_OPS = [
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")),
22     ]
23
24 DEFAULT_COMPOSITE_OP = COMPOSITE_OPS[0][0]
25 VALID_COMPOSITE_OPS = set([n for n, d in COMPOSITE_OPS])
26
27 class Layer:
28     """Representation of a layer in the document model.
29
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."""
32
33     def __init__(self, name="", compositeop=DEFAULT_COMPOSITE_OP):
34         self._surface = tiledsurface.Surface()
35         self.opacity = 1.0
36         self.name = name
37         self.visible = True
38         self.locked = False
39         self.compositeop = compositeop
40         # Called when contents of layer changed,
41         # with the bounding box of the changed region
42         self.content_observers = []
43
44         # Forward from surface implementation
45         self._surface.observers.append(self._notify_content_observers)
46
47         self.clear()
48
49     def _notify_content_observers(self, *args):
50         for f in self.content_observers:
51             f(*args)
52
53     def get_effective_opacity(self):
54         if self.visible:
55             return self.opacity
56         else:
57             return 0.0
58     effective_opacity = property(get_effective_opacity)
59
60     def get_alpha(self, x, y, radius):
61         return self._surface.get_alpha(x, y, radius)
62
63     def get_bbox(self):
64         return self._surface.get_bbox()
65
66     def is_empty(self):
67         return self._surface.is_empty()
68
69     def save_as_png(self, filename, *args, **kwargs):
70         self._surface.save_as_png(filename, *args, **kwargs)
71
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()
78         return split
79
80     def clear(self):
81         self.strokes = [] # contains StrokeShape instances (not stroke.Stroke)
82         self._surface.clear()
83
84     def load_from_surface(self, surface):
85         self.strokes = []
86         self._surface.load_from_surface(surface)
87
88     def render_as_pixbuf(self, *rect, **kwargs):
89         return self._surface.render_as_pixbuf(*rect, **kwargs)
90
91     def save_snapshot(self):
92         return (self.strokes[:], self._surface.save_snapshot(), self.opacity)
93
94     def load_snapshot(self, data):
95         strokes, data, self.opacity = data
96         self.strokes = strokes[:]
97         self._surface.load_snapshot(data)
98
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)
106
107     def save_strokemap_to_file(self, f, translate_x, translate_y):
108         brush2id = {}
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)
114                 s = zlib.compress(s)
115                 f.write('b')
116                 f.write(struct.pack('>I', len(s)))
117                 f.write(s)
118             # save stroke
119             s = stroke.save_to_string(translate_x, translate_y)
120             f.write('s')
121             f.write(struct.pack('>II', brush2id[stroke.brush_string], len(s)))
122             f.write(s)
123         f.write('}')
124
125
126     def load_strokemap_from_file(self, f, translate_x, translate_y):
127         assert not self.strokes
128         brushes = []
129         while True:
130             t = f.read(1)
131             if t == 'b':
132                 length, = struct.unpack('>I', f.read(4))
133                 tmp = f.read(length)
134                 brushes.append(zlib.decompress(tmp))
135             elif t == 's':
136                 brush_id, length = struct.unpack('>II', f.read(2*4))
137                 stroke = strokemap.StrokeShape()
138                 tmp = f.read(length)
139                 stroke.init_from_string(tmp, translate_x, translate_y)
140                 stroke.brush_string = brushes[brush_id]
141                 self.strokes.append(stroke)
142             elif t == '}':
143                 break
144             else:
145                 assert False, 'invalid strokemap'
146
147     def merge_into(self, dst):
148         """
149         Merge this layer into dst, modifying only dst.
150         """
151         # We must respect layer visibility, because saving a
152         # transparent PNG just calls this function for each layer.
153         src = self
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)
163         dst.opacity = 1.0
164
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):
169                 return s
170
171     def get_last_stroke_info(self):
172         if not self.strokes:
173             return None
174         return self.strokes[-1]