OSDN Git Service

Move COMPOSITE_OPT definitions to lib/layer.py
[mypaint-anime/master.git] / lib / document.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 os, zipfile, tempfile, time, traceback
10 join = os.path.join
11 from cStringIO import StringIO
12 import xml.etree.ElementTree as ET
13 from gtk import gdk
14 import gobject, numpy
15 from gettext import gettext as _
16
17 import helpers, tiledsurface, pixbufsurface, backgroundsurface, mypaintlib
18 import command, stroke, layer
19 import brush
20
21 N = tiledsurface.N
22 LOAD_CHUNK_SIZE = 64*1024
23
24 from layer import DEFAULT_COMPOSITE_OP, VALID_COMPOSITE_OPS
25
26 class SaveLoadError(Exception):
27     """Expected errors on loading or saving, like missing permissions or non-existing files."""
28     pass
29
30 class Document():
31     """
32     This is the "model" in the Model-View-Controller design.
33     (The "view" would be ../gui/tileddrawwidget.py.)
34     It represents everything that the user would want to save.
35
36
37     The "controller" mostly in drawwindow.py.
38     It is possible to use it without any GUI attached (see ../tests/)
39     """
40     # Please note the following difficulty with the undo stack:
41     #
42     #   Most of the time there is an unfinished (but already rendered)
43     #   stroke pending, which has to be turned into a command.Action
44     #   or discarded as empty before any other action is possible.
45     #   (split_stroke)
46
47     def __init__(self, brushinfo=None):
48         if not brushinfo:
49             brushinfo = brush.BrushInfo()
50             brushinfo.load_defaults()
51         self.brush = brush.Brush(brushinfo)
52         self.stroke = None
53         self.canvas_observers = []
54         self.stroke_observers = [] # callback arguments: stroke, brush (brush is a temporary read-only convenience object)
55         self.doc_observers = []
56         self.frame_observers = []
57         self.command_stack_observers = []
58         self.clear(True)
59
60         self._frame = [0, 0, 0, 0]
61         self._frame_enabled = False
62         # Used by move_frame() to accumulate values
63         self._frame_dx = 0.0
64         self._frame_dy = 0.0
65
66     def get_frame(self):
67         return self._frame
68
69     def move_frame(self, dx=0.0, dy=0.0):
70         """Move the frame. Accumulates changes and moves the frame once
71         the accumulated change reaches the minimum move step."""
72         # FIXME: Should be 1 (pixel aligned), not tile aligned
73         # This is due to PNG saving having to be tile aligned
74         min_step = N
75
76         def round_to_n(value, n):
77             return int(round(value/n)*n)
78
79         x, y, w, h = self.get_frame()
80
81         self._frame_dx += dx
82         self._frame_dy += dy
83         step_x = round_to_n(self._frame_dx, min_step)
84         step_y = round_to_n(self._frame_dy, min_step)
85
86         if step_x:
87             self.set_frame(x=x+step_x)
88             self._frame_dx -= step_x
89
90         if step_y:
91             self.set_frame(y=y+step_y)
92             self._frame_dy -= step_y
93
94     def set_frame(self, x=None, y=None, width=None, height=None):
95         """Set the size of the frame. Pass None to indicate no-change."""
96
97         for i, var in enumerate([x, y, width, height]):
98             if not var is None:
99                 # FIXME: must be aligned to tile size due to PNG saving
100                 assert not var % N, "Frame size must be aligned to tile size"
101                 self._frame[i] = var
102
103         for f in self.frame_observers: f()
104
105     def get_frame_enabled(self):
106         return self._frame_enabled
107
108     def set_frame_enabled(self, enabled):
109         self._frame_enabled = enabled
110         for f in self.frame_observers: f()
111     frame_enabled = property(get_frame_enabled)
112
113     def call_doc_observers(self):
114         for f in self.doc_observers:
115             f(self)
116         return True
117
118     def clear(self, init=False):
119         self.split_stroke()
120         if not init:
121             bbox = self.get_bbox()
122         # throw everything away, including undo stack
123
124         self.command_stack = command.CommandStack()
125         self.command_stack.stack_observers = self.command_stack_observers
126         self.set_background((255, 255, 255))
127         self.layers = []
128         self.layer_idx = None
129         self.add_layer(0)
130         # disallow undo of the first layer
131         self.command_stack.clear()
132         self.unsaved_painting_time = 0.0
133
134         if not init:
135             for f in self.canvas_observers:
136                 f(*bbox)
137
138         self.call_doc_observers()
139
140     def get_current_layer(self):
141         return self.layers[self.layer_idx]
142     layer = property(get_current_layer)
143
144     def split_stroke(self):
145         if not self.stroke: return
146         self.stroke.stop_recording()
147         if not self.stroke.empty:
148             self.command_stack.do(command.Stroke(self, self.stroke, self.snapshot_before_stroke))
149             del self.snapshot_before_stroke
150             self.unsaved_painting_time += self.stroke.total_painting_time
151             for f in self.stroke_observers:
152                 f(self.stroke, self.brush)
153         self.stroke = None
154
155     def select_layer(self, idx):
156         self.do(command.SelectLayer(self, idx))
157
158     def move_layer(self, was_idx, new_idx, select_new=False):
159         self.do(command.MoveLayer(self, was_idx, new_idx, select_new))
160
161     def duplicate_layer(self, insert_idx=None, name=''):
162         self.do(command.DuplicateLayer(self, insert_idx, name))
163
164     def reorder_layers(self, new_layers):
165         self.do(command.ReorderLayers(self, new_layers))
166
167     def clear_layer(self):
168         if not self.layer.is_empty():
169             self.do(command.ClearLayer(self))
170
171     def stroke_to(self, dtime, x, y, pressure, xtilt,ytilt):
172         if not self.stroke:
173             self.stroke = stroke.Stroke()
174             self.stroke.start_recording(self.brush)
175             self.snapshot_before_stroke = self.layer.save_snapshot()
176         self.stroke.record_event(dtime, x, y, pressure, xtilt,ytilt)
177
178         l = self.layer
179         l._surface.begin_atomic()
180         split = self.brush.stroke_to (l._surface, x, y, pressure, xtilt,ytilt, dtime)
181         l._surface.end_atomic()
182
183         if split:
184             self.split_stroke()
185
186     def redo_last_stroke_with_different_brush(self, brush):
187         cmd = self.get_last_command()
188         if not isinstance(cmd, command.Stroke):
189             return
190         cmd = self.undo()
191         assert isinstance(cmd, command.Stroke)
192         new_stroke = cmd.stroke.copy_using_different_brush(brush)
193         snapshot_before = self.layer.save_snapshot()
194         new_stroke.render(self.layer._surface)
195         self.do(command.Stroke(self, new_stroke, snapshot_before))
196
197     def straight_line(self, src, dst):
198         self.split_stroke()
199         self.brush.reset() # reset dynamic states (eg. filtered velocity)
200
201         duration = 3.0
202         pressure = 0.3
203         N = 1000
204         x = numpy.linspace(src[0], dst[0], N)
205         y = numpy.linspace(src[1], dst[1], N)
206         # rest the brush in src for a minute, to avoid interpolation
207         # from the upper left corner (states are zero) (FIXME: the
208         # brush should handle this on its own, maybe?)
209         self.stroke_to(60.0, x[0], y[0], 0.0, 0.0, 0.0)
210         for i in xrange(N):
211             self.stroke_to(duration/N, x[i], y[i], pressure, 0.0, 0.0)
212         self.split_stroke()
213         self.brush.reset()
214
215
216     def layer_modified_cb(self, *args):
217         # for now, any layer modification is assumed to be visible
218         for f in self.canvas_observers:
219             f(*args)
220
221     def invalidate_all(self):
222         for f in self.canvas_observers:
223             f(0, 0, 0, 0)
224
225     def undo(self):
226         self.split_stroke()
227         while 1:
228             cmd = self.command_stack.undo()
229             if not cmd or not cmd.automatic_undo:
230                 return cmd
231
232     def redo(self):
233         self.split_stroke()
234         while 1:
235             cmd = self.command_stack.redo()
236             if not cmd or not cmd.automatic_undo:
237                 return cmd
238
239     def do(self, cmd):
240         self.split_stroke()
241         self.command_stack.do(cmd)
242
243     def get_last_command(self):
244         self.split_stroke()
245         return self.command_stack.get_last_command()
246
247     def get_bbox(self):
248         res = helpers.Rect()
249         for layer in self.layers:
250             # OPTIMIZE: only visible layers...
251             # careful: currently saving assumes that all layers are included
252             bbox = layer.get_bbox()
253             res.expandToIncludeRect(bbox)
254         return res
255
256     def get_effective_bbox(self):
257         """Return the effective bounding box of the document.
258         If the frame is enabled, this is the bounding box of the frame, 
259         else the (dynamic) bounding box of the document."""
260         return self.get_frame() if self.frame_enabled else self.get_bbox()
261
262     def blit_tile_into(self, dst_8bit, tx, ty, mipmap_level=0, layers=None, background=None):
263         if layers is None:
264             layers = self.layers
265         if background is None:
266             background = self.background
267
268         assert dst_8bit.dtype == 'uint8'
269         dst = numpy.empty((N, N, 3), dtype='uint16')
270
271         background.blit_tile_into(dst, tx, ty, mipmap_level)
272
273         for layer in layers:
274             surface = layer._surface
275             #surface.composite_tile_over(dst, tx, ty, mipmap_level=mipmap_level, opacity=layer.effective_opacity)
276             surface.composite_tile(dst, tx, ty, mipmap_level=mipmap_level, opacity=layer.effective_opacity, mode=layer.compositeop)
277
278         mypaintlib.tile_convert_rgb16_to_rgb8(dst, dst_8bit)
279
280     def add_layer(self, insert_idx=None, after=None, name=''):
281         self.do(command.AddLayer(self, insert_idx, after, name))
282
283     def remove_layer(self,layer=None):
284         if len(self.layers) > 1:
285             self.do(command.RemoveLayer(self,layer))
286         else:
287             self.clear_layer()
288
289     def merge_layer_down(self):
290         dst_idx = self.layer_idx - 1
291         if dst_idx < 0:
292             return False
293         self.do(command.MergeLayer(self, dst_idx))
294         return True
295
296     def load_layer_from_pixbuf(self, pixbuf, x=0, y=0):
297         arr = helpers.gdkpixbuf2numpy(pixbuf)
298         self.do(command.LoadLayer(self, arr, x, y))
299
300     def set_layer_visibility(self, visible, layer):
301         cmd = self.get_last_command()
302         if isinstance(cmd, command.SetLayerVisibility) and cmd.layer is layer:
303             self.undo()
304         self.do(command.SetLayerVisibility(self, visible, layer))
305
306     def set_layer_locked(self, locked, layer):
307         cmd = self.get_last_command()
308         if isinstance(cmd, command.SetLayerLocked) and cmd.layer is layer:
309             self.undo()
310         self.do(command.SetLayerLocked(self, locked, layer))
311
312     def set_layer_opacity(self, opacity, layer=None):
313         """Sets the opacity of a layer. If layer=None, works on the current layer"""
314         cmd = self.get_last_command()
315         if isinstance(cmd, command.SetLayerOpacity):
316             self.undo()
317         self.do(command.SetLayerOpacity(self, opacity, layer))
318
319     def set_layer_compositeop(self, compositeop, layer=None):
320         """Sets the composition-operation of a layer. If layer=None, works on the current layer"""
321         if compositeop not in VALID_COMPOSITE_OPS:
322             compositeop = DEFAULT_COMPOSITE_OP
323         cmd = self.get_last_command()
324         if isinstance(cmd, command.SetLayerCompositeOp):
325             self.undo()
326         self.do(command.SetLayerCompositeOp(self, compositeop, layer))
327
328     def set_background(self, obj):
329         # This is not an undoable action. One reason is that dragging
330         # on the color chooser would get tons of undo steps.
331
332         if not isinstance(obj, backgroundsurface.Background):
333             obj = backgroundsurface.Background(obj)
334         self.background = obj
335
336         self.invalidate_all()
337
338     def load_from_pixbuf(self, pixbuf):
339         """Load a document from a pixbuf."""
340         self.clear()
341         self.load_layer_from_pixbuf(pixbuf)
342         self.set_frame(*self.get_bbox())
343
344     def is_layered(self):
345         count = 0
346         for l in self.layers:
347             if not l.is_empty():
348                 count += 1
349         return count > 1
350
351     def is_empty(self):
352         return len(self.layers) == 1 and self.layer.is_empty()
353
354     def save(self, filename, **kwargs):
355         self.split_stroke()
356         junk, ext = os.path.splitext(filename)
357         ext = ext.lower().replace('.', '')
358         save = getattr(self, 'save_' + ext, self.unsupported)
359         try:
360             save(filename, **kwargs)
361         except gobject.GError, e:
362             traceback.print_exc()
363             if e.code == 5:
364                 #add a hint due to a very consfusing error message when there is no space left on device
365                 raise SaveLoadError, _('Unable to save: %s\nDo you have enough space left on the device?') % e.message
366             else:
367                 raise SaveLoadError, _('Unable to save: %s') % e.message
368         except IOError, e:
369             traceback.print_exc()
370             raise SaveLoadError, _('Unable to save: %s') % e.strerror
371         self.unsaved_painting_time = 0.0
372
373     def load(self, filename, **kwargs):
374         if not os.path.isfile(filename):
375             raise SaveLoadError, _('File does not exist: %s') % repr(filename)
376         if not os.access(filename,os.R_OK):
377             raise SaveLoadError, _('You do not have the necessary permissions to open file: %s') % repr(filename)
378         junk, ext = os.path.splitext(filename)
379         ext = ext.lower().replace('.', '')
380         load = getattr(self, 'load_' + ext, self.unsupported)
381         try:
382             load(filename, **kwargs)
383         except gobject.GError, e:
384             traceback.print_exc()
385             raise SaveLoadError, _('Error while loading: GError %s') % e
386         except IOError, e:
387             traceback.print_exc()
388             raise SaveLoadError, _('Error while loading: IOError %s') % e
389         self.command_stack.clear()
390         self.unsaved_painting_time = 0.0
391         self.call_doc_observers()
392
393     def unsupported(self, filename, *args, **kwargs):
394         raise SaveLoadError, _('Unknown file format extension: %s') % repr(filename)
395
396     def render_as_pixbuf(self, *args, **kwargs):
397         return pixbufsurface.render_as_pixbuf(self, *args, **kwargs)
398
399     def render_thumbnail(self):
400         t0 = time.time()
401         x, y, w, h = self.get_effective_bbox()
402         if w == 0 or h == 0:
403             # workaround to save empty documents
404             x, y, w, h = 0, 0, tiledsurface.N, tiledsurface.N
405         mipmap_level = 0
406         while mipmap_level < tiledsurface.MAX_MIPMAP_LEVEL and max(w, h) >= 512:
407             mipmap_level += 1
408             x, y, w, h = x/2, y/2, w/2, h/2
409
410         pixbuf = self.render_as_pixbuf(x, y, w, h, mipmap_level=mipmap_level)
411         assert pixbuf.get_width() == w and pixbuf.get_height() == h
412         pixbuf = helpers.scale_proportionally(pixbuf, 256, 256)
413         print 'Rendered thumbnail in', time.time() - t0, 'seconds.'
414         return pixbuf
415
416     def save_png(self, filename, alpha=False, multifile=False, **kwargs):
417         doc_bbox = self.get_effective_bbox()
418         if multifile:
419             self.save_multifile_png(filename, **kwargs)
420         else:
421             if alpha:
422                 tmp_layer = layer.Layer()
423                 for l in self.layers:
424                     l.merge_into(tmp_layer)
425                 tmp_layer.save_as_png(filename, *doc_bbox)
426             else:
427                 pixbufsurface.save_as_png(self, filename, *doc_bbox, alpha=False, **kwargs)
428
429     def save_multifile_png(self, filename, alpha=False, **kwargs):
430         prefix, ext = os.path.splitext(filename)
431         # if we have a number already, strip it
432         l = prefix.rsplit('.', 1)
433         if l[-1].isdigit():
434             prefix = l[0]
435         doc_bbox = self.get_effective_bbox()
436         for i, l in enumerate(self.layers):
437             filename = '%s.%03d%s' % (prefix, i+1, ext)
438             l.save_as_png(filename, *doc_bbox, **kwargs)
439
440     @staticmethod
441     def _pixbuf_from_stream(fp, feedback_cb=None):
442         loader = gdk.PixbufLoader()
443         while True:
444             if feedback_cb is not None:
445                 feedback_cb()
446             buf = fp.read(LOAD_CHUNK_SIZE)
447             if buf == '':
448                 break
449             loader.write(buf)
450         loader.close()
451         return loader.get_pixbuf()
452
453     def load_from_pixbuf_file(self, filename, feedback_cb=None):
454         fp = open(filename, 'rb')
455         pixbuf = self._pixbuf_from_stream(fp, feedback_cb)
456         fp.close()
457         self.load_from_pixbuf(pixbuf)
458
459     load_png = load_from_pixbuf_file
460     load_jpg = load_from_pixbuf_file
461     load_jpeg = load_from_pixbuf_file
462
463     def save_jpg(self, filename, quality=90, **kwargs):
464         x, y, w, h = self.get_effective_bbox()
465         if w == 0 or h == 0:
466             x, y, w, h = 0, 0, N, N # allow to save empty documents
467         pixbuf = self.render_as_pixbuf(x, y, w, h, **kwargs)
468         pixbuf.save(filename, 'jpeg', options={'quality':str(quality)})
469
470     save_jpeg = save_jpg
471
472     def save_ora(self, filename, options=None, **kwargs):
473         print 'save_ora:'
474         t0 = time.time()
475         tempdir = tempfile.mkdtemp('mypaint')
476         # use .tmp extension, so we don't overwrite a valid file if there is an exception
477         z = zipfile.ZipFile(filename + '.tmpsave', 'w', compression=zipfile.ZIP_STORED)
478         # work around a permission bug in the zipfile library: http://bugs.python.org/issue3394
479         def write_file_str(filename, data):
480             zi = zipfile.ZipInfo(filename)
481             zi.external_attr = 0100644 << 16
482             z.writestr(zi, data)
483         write_file_str('mimetype', 'image/openraster') # must be the first file
484         image = ET.Element('image')
485         stack = ET.SubElement(image, 'stack')
486         x0, y0, w0, h0 = self.get_effective_bbox()
487         a = image.attrib
488         a['w'] = str(w0)
489         a['h'] = str(h0)
490
491         def store_pixbuf(pixbuf, name):
492             tmp = join(tempdir, 'tmp.png')
493             t1 = time.time()
494             pixbuf.save(tmp, 'png')
495             print '  %.3fs pixbuf saving %s' % (time.time() - t1, name)
496             z.write(tmp, name)
497             os.remove(tmp)
498
499         def store_surface(surface, name, rect=[]):
500             tmp = join(tempdir, 'tmp.png')
501             t1 = time.time()
502             surface.save_as_png(tmp, *rect, **kwargs)
503             print '  %.3fs surface saving %s' % (time.time() - t1, name)
504             z.write(tmp, name)
505             os.remove(tmp)
506
507         def add_layer(x, y, opac, surface, name, layer_name, visible=True, compositeop=DEFAULT_COMPOSITE_OP, rect=[]):
508             layer = ET.Element('layer')
509             stack.append(layer)
510             store_surface(surface, name, rect)
511             a = layer.attrib
512             if layer_name:
513                 a['name'] = layer_name
514             a['src'] = name
515             a['x'] = str(x)
516             a['y'] = str(y)
517             a['opacity'] = str(opac)
518             if compositeop not in VALID_COMPOSITE_OPS:
519                 compositeop = DEFAULT_COMPOSITE_OP
520             a['composite-op'] = compositeop
521             if visible:
522                 a['visibility'] = 'visible'
523             else:
524                 a['visibility'] = 'hidden'
525             return layer
526
527         for idx, l in enumerate(reversed(self.layers)):
528             if l.is_empty():
529                 continue
530             opac = l.opacity
531             x, y, w, h = l.get_bbox()
532             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))
533             # strokemap
534             sio = StringIO()
535             l.save_strokemap_to_file(sio, -x, -y)
536             data = sio.getvalue(); sio.close()
537             name = 'data/layer%03d_strokemap.dat' % idx
538             el.attrib['mypaint_strokemap_v2'] = name
539             write_file_str(name, data)
540
541         # save background as layer (solid color or tiled)
542         bg = self.background
543         # save as fully rendered layer
544         x, y, w, h = self.get_bbox()
545         l = add_layer(x-x0, y-y0, 1.0, bg, 'data/background.png', 'background',
546                       DEFAULT_COMPOSITE_OP, rect=(x,y,w,h))
547         x, y, w, h = bg.get_pattern_bbox()
548         # save as single pattern (with corrected origin)
549         store_surface(bg, 'data/background_tile.png', rect=(x+x0, y+y0, w, h))
550         l.attrib['background_tile'] = 'data/background_tile.png'
551
552         # preview (256x256)
553         t2 = time.time()
554         print '  starting to render full image for thumbnail...'
555
556         thumbnail_pixbuf = self.render_thumbnail()
557         store_pixbuf(thumbnail_pixbuf, 'Thumbnails/thumbnail.png')
558         print '  total %.3fs spent on thumbnail' % (time.time() - t2)
559
560         helpers.indent_etree(image)
561         xml = ET.tostring(image, encoding='UTF-8')
562
563         write_file_str('stack.xml', xml)
564         z.close()
565         os.rmdir(tempdir)
566         if os.path.exists(filename):
567             os.remove(filename) # windows needs that
568         os.rename(filename + '.tmpsave', filename)
569
570         print '%.3fs save_ora total' % (time.time() - t0)
571
572         return thumbnail_pixbuf
573
574     def load_ora(self, filename, feedback_cb=None):
575         """Loads from an OpenRaster file"""
576         print 'load_ora:'
577         t0 = time.time()
578         z = zipfile.ZipFile(filename)
579         print 'mimetype:', z.read('mimetype').strip()
580         xml = z.read('stack.xml')
581         image = ET.fromstring(xml)
582         stack = image.find('stack')
583
584         w = int(image.attrib['w'])
585         h = int(image.attrib['h'])
586
587         def round_up_to_n(value, n):
588             assert value >= 0, "function undefined for negative numbers"
589
590             residual = value % n
591             if residual:
592                 value = value - residual + n
593             return int(value)
594
595         def get_pixbuf(filename):
596             t1 = time.time()
597
598             try:
599                 fp = z.open(filename, mode='r')
600             except KeyError:
601                 # support for bad zip files (saved by old versions of the GIMP ORA plugin)
602                 fp = z.open(filename.encode('utf-8'), mode='r')
603                 print 'WARNING: bad OpenRaster ZIP file. There is an utf-8 encoded filename that does not have the utf-8 flag set:', repr(filename)
604
605             res = self._pixbuf_from_stream(fp, feedback_cb)
606             fp.close()
607             print '  %.3fs loading %s' % (time.time() - t1, filename)
608             return res
609
610         def get_layers_list(root, x=0,y=0):
611             res = []
612             for item in root:
613                 if item.tag == 'layer':
614                     if 'x' in item.attrib:
615                         item.attrib['x'] = int(item.attrib['x']) + x
616                     if 'y' in item.attrib:
617                         item.attrib['y'] = int(item.attrib['y']) + y
618                     res.append(item)
619                 elif item.tag == 'stack':
620                     stack_x = int( item.attrib.get('x', 0) )
621                     stack_y = int( item.attrib.get('y', 0) )
622                     res += get_layers_list(item, stack_x, stack_y)
623                 else:
624                     print 'Warning: ignoring unsupported tag:', item.tag
625             return res
626
627         self.clear() # this leaves one empty layer
628         no_background = True
629         # FIXME: don't require tile alignment for frame
630         self.set_frame(width=round_up_to_n(w, N), height=round_up_to_n(h, N))
631
632         for layer in get_layers_list(stack):
633             a = layer.attrib
634
635             if 'background_tile' in a:
636                 assert no_background
637                 try:
638                     print a['background_tile']
639                     self.set_background(get_pixbuf(a['background_tile']))
640                     no_background = False
641                     continue
642                 except backgroundsurface.BackgroundError, e:
643                     print 'ORA background tile not usable:', e
644
645             src = a.get('src', '')
646             if not src.lower().endswith('.png'):
647                 print 'Warning: ignoring non-png layer'
648                 continue
649             pixbuf = get_pixbuf(src)
650             name = a.get('name', '')
651             x = int(a.get('x', '0'))
652             y = int(a.get('y', '0'))
653             opac = float(a.get('opacity', '1.0'))
654             compositeop = str(a.get('composite-op', DEFAULT_COMPOSITE_OP))
655             if compositeop not in VALID_COMPOSITE_OPS:
656                 compositeop = DEFAULT_COMPOSITE_OP
657
658             visible = not 'hidden' in a.get('visibility', 'visible')
659             self.add_layer(insert_idx=0, name=name)
660             last_pixbuf = pixbuf
661             t1 = time.time()
662             self.load_layer_from_pixbuf(pixbuf, x, y)
663             layer = self.layers[0]
664
665             self.set_layer_opacity(helpers.clamp(opac, 0.0, 1.0), layer)
666             self.set_layer_compositeop(compositeop, layer)
667             self.set_layer_visibility(visible, layer)
668             print '  %.3fs converting pixbuf to layer format' % (time.time() - t1)
669             # strokemap
670             fname = a.get('mypaint_strokemap_v2', None)
671             if fname:
672                 if x % N or y % N:
673                     print 'Warning: dropping non-aligned strokemap'
674                 else:
675                     sio = StringIO(z.read(fname))
676                     layer.load_strokemap_from_file(sio, x, y)
677                     sio.close()
678
679         if len(self.layers) == 1:
680             # no assertion (allow empty documents)
681             print 'Warning: Could not load any layer, document is empty.'
682
683         if no_background:
684             # recognize solid or tiled background layers, at least those that mypaint <= 0.7.1 saves
685             t1 = time.time()
686             p = last_pixbuf
687             if not p.get_has_alpha() and p.get_width() % N == 0 and p.get_height() % N == 0:
688                 tiles = self.layers[0]._surface.tiledict.values()
689                 if len(tiles) > 1:
690                     all_equal = True
691                     for tile in tiles[1:]:
692                         if (tile.rgba != tiles[0].rgba).any():
693                             all_equal = False
694                             break
695                     if all_equal:
696                         arr = helpers.gdkpixbuf2numpy(p)
697                         tile = arr[0:N,0:N,:]
698                         self.set_background(tile.copy())
699                         self.select_layer(0)
700                         self.remove_layer()
701             print '  %.3fs recognizing tiled background' % (time.time() - t1)
702
703         if len(self.layers) > 1:
704             # remove the still present initial empty top layer
705             self.select_layer(len(self.layers)-1)
706             self.remove_layer()
707             # this leaves the topmost layer selected
708
709         print '%.3fs load_ora total' % (time.time() - t0)