new layer merging, convert layer to normal mode
[mypaint:mypaint.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 gui.pygtkcompat
10 if gui.pygtkcompat.USE_GTK3:
11     from gi.repository import GdkPixbuf
12
13 import os, sys, zipfile, tempfile, time, traceback
14 join = os.path.join
15 from cStringIO import StringIO
16 import xml.etree.ElementTree as ET
17 from gtk import gdk
18 import gobject, numpy
19 from gettext import gettext as _
20
21 import helpers, tiledsurface, pixbufsurface, backgroundsurface, mypaintlib
22 import command, stroke, layer
23 import brush
24
25 N = tiledsurface.N
26 LOAD_CHUNK_SIZE = 64*1024
27
28 from layer import DEFAULT_COMPOSITE_OP, VALID_COMPOSITE_OPS
29
30
31 class SaveLoadError(Exception):
32     """Expected errors on loading or saving
33
34     Covers stuff like missing permissions or non-existing files.
35
36     """
37     pass
38
39
40 class Document():
41     """
42     This is the "model" in the Model-View-Controller design.
43     (The "view" would be ../gui/tileddrawwidget.py.)
44     It represents everything that the user would want to save.
45
46
47     The "controller" mostly in drawwindow.py.
48     It is possible to use it without any GUI attached (see ../tests/)
49     """
50     # Please note the following difficulty with the undo stack:
51     #
52     #   Most of the time there is an unfinished (but already rendered)
53     #   stroke pending, which has to be turned into a command.Action
54     #   or discarded as empty before any other action is possible.
55     #   (split_stroke)
56
57     def __init__(self, brushinfo=None):
58         if not brushinfo:
59             brushinfo = brush.BrushInfo()
60             brushinfo.load_defaults()
61         self.layers = []
62         self.brush = brush.Brush(brushinfo)
63         self.brush.brushinfo.observers.append(self.brushsettings_changed_cb)
64         self.stroke = None
65         self.canvas_observers = []  #: See `layer_modified_cb()`
66         self.stroke_observers = [] #: See `split_stroke()`
67         self.doc_observers = [] #: See `call_doc_observers()`
68         self.frame_observers = []
69         self.command_stack_observers = []
70         self.symmetry_observers = []  #: See `set_symmetry_axis()`
71         self.__symmetry_axis = None
72         self.clear(True)
73
74         self._frame = [0, 0, 0, 0]
75         self._frame_enabled = False
76
77     def move_current_layer(self, dx, dy):
78         layer = self.layers[self.layer_idx]
79         layer.translate(dx, dy)
80
81     def get_frame(self):
82         return self._frame
83
84     def set_frame(self, x=None, y=None, width=None, height=None):
85         """Set the size of the frame. Pass None to indicate no-change."""
86
87         for i, var in enumerate([x, y, width, height]):
88             if not var is None:
89                 self._frame[i] = int(var)
90
91         for f in self.frame_observers: f()
92
93     def get_frame_enabled(self):
94         return self._frame_enabled
95
96     def set_frame_enabled(self, enabled):
97         self._frame_enabled = enabled
98         for f in self.frame_observers: f()
99     frame_enabled = property(get_frame_enabled)
100
101
102     def call_doc_observers(self):
103         """Announce major structural changes via `doc_observers`.
104
105         This is invoked to announce major structural changes such as the layers
106         changing or a new document being loaded. The callbacks in the list are
107         invoked with a single argument, `self`.
108
109         """
110         for f in self.doc_observers:
111             f(self)
112         return True
113
114
115     def get_symmetry_axis(self):
116         """Gets the active painting symmetry X axis value.
117         """
118         return self.__symmetry_axis
119
120
121     def set_symmetry_axis(self, x):
122         """Sets the active painting symmetry X axis value.
123
124         A value of `None` inactivates symmetrical painting. After setting, all
125         registered `symmetry_observers` are called without arguments.
126         """
127         # TODO: make this undoable?
128         for layer in self.layers:
129             layer.set_symmetry_axis(x)
130         self.__symmetry_axis = x
131         for func in self.symmetry_observers:
132             func()
133
134
135     def clear(self, init=False):
136         self.split_stroke()
137         self.set_symmetry_axis(None)
138         if not init:
139             bbox = self.get_bbox()
140         # throw everything away, including undo stack
141
142         self.command_stack = command.CommandStack()
143         self.command_stack.stack_observers = self.command_stack_observers
144         self.set_background((255, 255, 255))
145         self.layers = []
146         self.layer_idx = None
147         self.add_layer(0)
148         # disallow undo of the first layer
149         self.command_stack.clear()
150         self.unsaved_painting_time = 0.0
151
152         if not init:
153             for f in self.canvas_observers:
154                 f(*bbox)
155
156         self.call_doc_observers()
157
158     def get_current_layer(self):
159         return self.layers[self.layer_idx]
160     layer = property(get_current_layer)
161
162
163     def split_stroke(self):
164         """Splits the current stroke, announcing the newly stacked stroke
165
166         The stroke being drawn is pushed onto to the command stack and the
167         callbacks in the list `self.stroke_observers` are invoked with two
168         arguments: the newly completed stroke, and the brush used. The brush
169         argument is a temporary read-only convenience object.
170
171         This is called every so often when drawing a single long brushstroke on
172         input to allow parts of a long line to be undone.
173
174         """
175         if not self.stroke: return
176         self.stroke.stop_recording()
177         if not self.stroke.empty:
178             cmd = command.Stroke(self, self.stroke,
179                                  self.snapshot_before_stroke)
180             self.command_stack.do(cmd)
181             del self.snapshot_before_stroke
182             self.unsaved_painting_time += self.stroke.total_painting_time
183             for f in self.stroke_observers:
184                 f(self.stroke, self.brush)
185         self.stroke = None
186
187
188     def brushsettings_changed_cb(self, settings, lightweight_settings=set([
189             'radius_logarithmic', 'color_h', 'color_s', 'color_v',
190             'opaque', 'hardness', 'slow_tracking', 'slow_tracking_per_dab'
191             ])):
192         # The lightweight brush settings are expected to change often in
193         # mid-stroke e.g. by heavy keyboard usage. If only those change, we
194         # don't create a new undo step. (And thus also no separate pickable
195         # stroke in the strokemap.)
196         if settings - lightweight_settings:
197             self.split_stroke()
198
199     def select_layer(self, idx):
200         self.do(command.SelectLayer(self, idx))
201
202     def record_layer_move(self, layer, dx, dy):
203         layer_idx = self.layers.index(layer)
204         self.do(command.MoveLayer(self, layer_idx, dx, dy, True))
205
206     def move_layer(self, was_idx, new_idx, select_new=False):
207         self.do(command.ReorderSingleLayer(self, was_idx, new_idx, select_new))
208
209     def duplicate_layer(self, insert_idx=None, name=''):
210         self.do(command.DuplicateLayer(self, insert_idx, name))
211
212     def reorder_layers(self, new_layers):
213         self.do(command.ReorderLayers(self, new_layers))
214
215     def clear_layer(self):
216         if not self.layer.is_empty():
217             self.do(command.ClearLayer(self))
218
219
220     def stroke_to(self, dtime, x, y, pressure, xtilt, ytilt):
221         """Draws a stroke to the current layer with the current brush.
222
223         This is called by GUI code in response to motion events on the canvas -
224         both with and without pressure. If enough time has elapsed,
225         `split_stroke()` is called.
226
227         :param self:
228             This is an object method.
229         :param float dtime:
230             Floating-point number of seconds since the last call to this,
231             function, for motion interpolation etc.
232         :param float x:
233             Document X position of the end-point of this stroke.
234         :param float y:
235             Document Y position of the end-point of this stroke.
236         :param float pressure:
237             Pressure, ranging from 0.0 to 1.0.
238         :param float xtilt:
239             X-axis tilt, ranging from -1.0 to 1.0.
240         :param float ytilt:
241             Y-axis tilt, ranging from -1.0 to 1.0.
242
243         """
244         if not self.stroke:
245             self.stroke = stroke.Stroke()
246             self.stroke.start_recording(self.brush)
247             self.snapshot_before_stroke = self.layer.save_snapshot()
248         self.stroke.record_event(dtime, x, y, pressure, xtilt, ytilt)
249
250         split = self.layer.stroke_to(self.brush, x, y,
251                                 pressure, xtilt, ytilt, dtime)
252
253         if split:
254             self.split_stroke()
255
256
257     def redo_last_stroke_with_different_brush(self, brush):
258         cmd = self.get_last_command()
259         if not isinstance(cmd, command.Stroke):
260             return
261         cmd = self.undo()
262         assert isinstance(cmd, command.Stroke)
263         new_stroke = cmd.stroke.copy_using_different_brush(brush)
264         snapshot_before = self.layer.save_snapshot()
265         new_stroke.render(self.layer._surface)
266         self.do(command.Stroke(self, new_stroke, snapshot_before))
267
268
269     def layer_modified_cb(self, *args):
270         """Forwards region modify notifications (area invalidations)
271
272         GUI code can respond to these notifications by appending callbacks to
273         `self.canvas_observers`. Each callback is invoked with the bounding box
274         of the changed region: ``cb(x, y, w, h)``, or ``cb(0, 0, 0, 0)`` to
275         denote that everything needs to be redrawn.
276
277         See also: `invalidate_all()`.
278
279         """
280         # for now, any layer modification is assumed to be visible
281         for f in self.canvas_observers:
282             f(*args)
283
284
285     def invalidate_all(self):
286         """Marks everything as invalid.
287
288         Invokes the callbacks in `self.canvas_observers` passing the notation
289         for "everything" as arguments. See `layer_modified_cb()` for details.
290
291         """
292         for f in self.canvas_observers:
293             f(0, 0, 0, 0)
294
295
296     def undo(self):
297         self.split_stroke()
298         while 1:
299             cmd = self.command_stack.undo()
300             if not cmd or not cmd.automatic_undo:
301                 return cmd
302
303     def redo(self):
304         self.split_stroke()
305         while 1:
306             cmd = self.command_stack.redo()
307             if not cmd or not cmd.automatic_undo:
308                 return cmd
309
310     def do(self, cmd):
311         self.split_stroke()
312         self.command_stack.do(cmd)
313
314
315     def update_last_command(self, **kwargs):
316         self.split_stroke()
317         return self.command_stack.update_last_command(**kwargs)
318
319
320     def get_last_command(self):
321         self.split_stroke()
322         return self.command_stack.get_last_command()
323
324
325     def get_bbox(self):
326         """Returns the dynamic bounding box of the document.
327
328         This is currently the union of all the bounding boxes of all of the
329         layers. It disregards the user-chosen frame.
330
331         """
332         res = helpers.Rect()
333         for layer in self.layers:
334             # OPTIMIZE: only visible layers...
335             # careful: currently saving assumes that all layers are included
336             bbox = layer.get_bbox()
337             res.expandToIncludeRect(bbox)
338         return res
339
340
341     def get_effective_bbox(self):
342         """Return the effective bounding box of the document.
343
344         If the frame is enabled, this is the bounding box of the frame, 
345         else the (dynamic) bounding box of the document.
346
347         """
348         return self.get_frame() if self.frame_enabled else self.get_bbox()
349
350     def blit_tile_into(self, dst, dst_has_alpha, tx, ty, mipmap_level=0, layers=None, background=None):
351         assert dst_has_alpha is False
352         if layers is None:
353             layers = self.layers
354         if background is None:
355             background = self.background
356
357         assert dst.shape[-1] == 4
358         if dst.dtype == 'uint8':
359             dst_8bit = dst
360             dst = numpy.empty((N, N, 4), dtype='uint16')
361         else:
362             dst_8bit = None
363
364         background.blit_tile_into(dst, dst_has_alpha, tx, ty, mipmap_level)
365
366         for layer in layers:
367             layer.composite_tile(dst, dst_has_alpha, tx, ty, mipmap_level)
368
369         if dst_8bit is not None:
370             mypaintlib.tile_convert_rgbu16_to_rgbu8(dst, dst_8bit)
371
372     def get_rendered_image_behind_current_layer(self, tx, ty):
373         dst = numpy.empty((N, N, 4), dtype='uint16')
374         l = self.layers[0:self.layer_idx]
375         self.blit_tile_into(dst, False, tx, ty, layers=l)
376         return dst
377
378
379     def add_layer(self, insert_idx=None, after=None, name=''):
380         self.do(command.AddLayer(self, insert_idx, after, name))
381
382
383     def remove_layer(self,layer=None):
384         self.do(command.RemoveLayer(self, layer))
385
386
387     def rename_layer(self, layer, name):
388         self.do(command.RenameLayer(self, name, layer))
389
390     def convert_layer_to_normal_mode(self):
391         self.do(command.ConvertLayerToNormalMode(self, self.layer))
392
393     def merge_layer_down(self):
394         dst_idx = self.layer_idx - 1
395         if dst_idx < 0:
396             return False
397         self.do(command.MergeLayer(self, dst_idx))
398         return True
399
400
401     def load_layer_from_pixbuf(self, pixbuf, x=0, y=0):
402         arr = helpers.gdkpixbuf2numpy(pixbuf)
403         s = tiledsurface.Surface()
404         bbox = s.load_from_numpy(arr, x, y)
405         self.do(command.LoadLayer(self, s))
406         return bbox
407
408
409     def load_layer_from_png(self, filename, x=0, y=0, feedback_cb=None):
410         s = tiledsurface.Surface()
411         bbox = s.load_from_png(filename, x, y, feedback_cb)
412         self.do(command.LoadLayer(self, s))
413         return bbox
414
415
416     def set_layer_visibility(self, visible, layer):
417         """Sets the visibility of a layer."""
418         cmd = self.get_last_command()
419         if isinstance(cmd, command.SetLayerVisibility) and cmd.layer is layer:
420             self.update_last_command(visible=visible)
421         else:
422             self.do(command.SetLayerVisibility(self, visible, layer))
423
424
425     def set_layer_locked(self, locked, layer):
426         """Sets the input-locked status of a layer."""
427         cmd = self.get_last_command()
428         if isinstance(cmd, command.SetLayerLocked) and cmd.layer is layer:
429             self.update_last_command(locked=locked)
430         else:
431             self.do(command.SetLayerLocked(self, locked, layer))
432
433
434     def set_layer_opacity(self, opacity, layer=None):
435         """Sets the opacity of a layer.
436
437         If layer=None, works on the current layer.
438
439         """
440         cmd = self.get_last_command()
441         if isinstance(cmd, command.SetLayerOpacity):
442             self.undo()
443         self.do(command.SetLayerOpacity(self, opacity, layer))
444
445
446     def set_layer_compositeop(self, compositeop, layer=None):
447         """Sets the compositing operator for a layer.
448
449         If layer=None, works on the current layer.
450
451         """
452         if compositeop not in VALID_COMPOSITE_OPS:
453             compositeop = DEFAULT_COMPOSITE_OP
454         cmd = self.get_last_command()
455         if isinstance(cmd, command.SetLayerCompositeOp):
456             self.undo()
457         self.do(command.SetLayerCompositeOp(self, compositeop, layer))
458
459
460     def set_background(self, obj):
461         # This is not an undoable action. One reason is that dragging
462         # on the color chooser would get tons of undo steps.
463
464         if not isinstance(obj, backgroundsurface.Background):
465             obj = backgroundsurface.Background(obj)
466         self.background = obj
467
468         self.invalidate_all()
469
470
471     def load_from_pixbuf(self, pixbuf):
472         """Load a document from a pixbuf."""
473         self.clear()
474         bbox = self.load_layer_from_pixbuf(pixbuf)
475         self.set_frame(*bbox)
476
477
478     def is_layered(self):
479         """True if there are more than one nonempty layers."""
480         count = 0
481         for l in self.layers:
482             if not l.is_empty():
483                 count += 1
484         return count > 1
485
486     def is_empty(self):
487         """True if there is only one layer and it is empty."""
488         return len(self.layers) == 1 and self.layer.is_empty()
489
490     def save(self, filename, **kwargs):
491         """Save the document to a file.
492
493         :param str filename:
494             The filename to save to. The extension is used to determine format,
495             and a ``save_*()`` method is chosen to perform the save.
496         :param dict kwargs:
497             Passed on to the chosen save method.
498         :raise SaveLoadError:
499             The error string will be set to something descriptive and
500             presentable to the user.
501
502         """
503         self.split_stroke()
504         junk, ext = os.path.splitext(filename)
505         ext = ext.lower().replace('.', '')
506         save = getattr(self, 'save_' + ext, self._unsupported)
507         try:
508             save(filename, **kwargs)
509         except gobject.GError, e:
510             traceback.print_exc()
511             if e.code == 5:
512                 #add a hint due to a very consfusing error message when there is no space left on device
513                 raise SaveLoadError, _('Unable to save: %s\nDo you have enough space left on the device?') % e.message
514             else:
515                 raise SaveLoadError, _('Unable to save: %s') % e.message
516         except IOError, e:
517             traceback.print_exc()
518             raise SaveLoadError, _('Unable to save: %s') % e.strerror
519         self.unsaved_painting_time = 0.0
520
521
522     def load(self, filename, **kwargs):
523         """Load the document from a file.
524
525         :param str filename:
526             The filename to load from. The extension is used to determine
527             format, and a ``load_*()`` method is chosen to perform the load.
528         :param dict kwargs:
529             Passed on to the chosen loader method.
530         :raise SaveLoadError:
531             The error string will be set to something descriptive and
532             presentable to the user.
533
534         """
535         if not os.path.isfile(filename):
536             raise SaveLoadError, _('File does not exist: %s') % repr(filename)
537         if not os.access(filename,os.R_OK):
538             raise SaveLoadError, _('You do not have the necessary permissions to open file: %s') % repr(filename)
539         junk, ext = os.path.splitext(filename)
540         ext = ext.lower().replace('.', '')
541         load = getattr(self, 'load_' + ext, self._unsupported)
542         try:
543             load(filename, **kwargs)
544         except gobject.GError, e:
545             traceback.print_exc()
546             raise SaveLoadError, _('Error while loading: GError %s') % e
547         except IOError, e:
548             traceback.print_exc()
549             raise SaveLoadError, _('Error while loading: IOError %s') % e
550         self.command_stack.clear()
551         self.unsaved_painting_time = 0.0
552         self.call_doc_observers()
553
554
555     def _unsupported(self, filename, *args, **kwargs):
556         raise SaveLoadError, _('Unknown file format extension: %s') % repr(filename)
557
558     def render_as_pixbuf(self, *args, **kwargs):
559         return pixbufsurface.render_as_pixbuf(self, *args, **kwargs)
560
561     def render_thumbnail(self):
562         t0 = time.time()
563         x, y, w, h = self.get_effective_bbox()
564         if w == 0 or h == 0:
565             # workaround to save empty documents
566             x, y, w, h = 0, 0, tiledsurface.N, tiledsurface.N
567         mipmap_level = 0
568         while mipmap_level < tiledsurface.MAX_MIPMAP_LEVEL and max(w, h) >= 512:
569             mipmap_level += 1
570             x, y, w, h = x/2, y/2, w/2, h/2
571
572         pixbuf = self.render_as_pixbuf(x, y, w, h, mipmap_level=mipmap_level)
573         assert pixbuf.get_width() == w and pixbuf.get_height() == h
574         pixbuf = helpers.scale_proportionally(pixbuf, 256, 256)
575         print 'Rendered thumbnail in', time.time() - t0, 'seconds.'
576         return pixbuf
577
578     def save_png(self, filename, alpha=False, multifile=False, **kwargs):
579         doc_bbox = self.get_effective_bbox()
580         if multifile:
581             self.save_multifile_png(filename, **kwargs)
582         else:
583             if alpha:
584                 tmp_layer = layer.Layer()
585                 for l in self.layers:
586                     l.merge_into(tmp_layer)
587                 tmp_layer.save_as_png(filename, *doc_bbox)
588             else:
589                 pixbufsurface.save_as_png(self, filename, *doc_bbox, alpha=False, **kwargs)
590
591     def save_multifile_png(self, filename, alpha=False, **kwargs):
592         prefix, ext = os.path.splitext(filename)
593         # if we have a number already, strip it
594         l = prefix.rsplit('.', 1)
595         if l[-1].isdigit():
596             prefix = l[0]
597         doc_bbox = self.get_effective_bbox()
598         for i, l in enumerate(self.layers):
599             filename = '%s.%03d%s' % (prefix, i+1, ext)
600             l.save_as_png(filename, *doc_bbox, **kwargs)
601
602     def load_png(self, filename, feedback_cb=None):
603         self.clear()
604         bbox = self.load_layer_from_png(filename, 0, 0, feedback_cb)
605         self.set_frame(*bbox)
606
607     @staticmethod
608     def _pixbuf_from_stream(fp, feedback_cb=None):
609         if gui.pygtkcompat.USE_GTK3:
610             loader = GdkPixbuf.PixbufLoader()
611         else:
612             loader = gdk.PixbufLoader()
613         while True:
614             if feedback_cb is not None:
615                 feedback_cb()
616             buf = fp.read(LOAD_CHUNK_SIZE)
617             if buf == '':
618                 break
619             loader.write(buf)
620         loader.close()
621         return loader.get_pixbuf()
622
623     def load_from_pixbuf_file(self, filename, feedback_cb=None):
624         fp = open(filename, 'rb')
625         pixbuf = self._pixbuf_from_stream(fp, feedback_cb)
626         fp.close()
627         self.load_from_pixbuf(pixbuf)
628
629     load_jpg = load_from_pixbuf_file
630     load_jpeg = load_from_pixbuf_file
631
632     def save_jpg(self, filename, quality=90, **kwargs):
633         x, y, w, h = self.get_effective_bbox()
634         if w == 0 or h == 0:
635             x, y, w, h = 0, 0, N, N # allow to save empty documents
636         pixbuf = self.render_as_pixbuf(x, y, w, h, **kwargs)
637         options = {"quality": str(quality)}
638         gui.pygtkcompat.gdk.pixbuf.save(pixbuf, filename, 'jpeg', **options)
639
640     save_jpeg = save_jpg
641
642     def save_ora(self, filename, options=None, **kwargs):
643         print 'save_ora:'
644         t0 = time.time()
645         tempdir = tempfile.mkdtemp('mypaint')
646         if not isinstance(tempdir, unicode):
647             tempdir = tempdir.decode(sys.getfilesystemencoding())
648         # use .tmp extension, so we don't overwrite a valid file if there is an exception
649         z = zipfile.ZipFile(filename + '.tmpsave', 'w', compression=zipfile.ZIP_STORED)
650         # work around a permission bug in the zipfile library: http://bugs.python.org/issue3394
651         def write_file_str(filename, data):
652             zi = zipfile.ZipInfo(filename)
653             zi.external_attr = 0100644 << 16
654             z.writestr(zi, data)
655         write_file_str('mimetype', 'image/openraster') # must be the first file
656         image = ET.Element('image')
657         stack = ET.SubElement(image, 'stack')
658         x0, y0, w0, h0 = self.get_effective_bbox()
659         a = image.attrib
660         a['w'] = str(w0)
661         a['h'] = str(h0)
662
663         def store_pixbuf(pixbuf, name):
664             tmp = join(tempdir, 'tmp.png')
665             t1 = time.time()
666             gui.pygtkcompat.gdk.pixbuf.save(pixbuf, tmp, 'png')
667             print '  %.3fs pixbuf saving %s' % (time.time() - t1, name)
668             z.write(tmp, name)
669             os.remove(tmp)
670
671         def store_surface(surface, name, rect=[]):
672             tmp = join(tempdir, 'tmp.png')
673             t1 = time.time()
674             surface.save_as_png(tmp, *rect, **kwargs)
675             print '  %.3fs surface saving %s' % (time.time() - t1, name)
676             z.write(tmp, name)
677             os.remove(tmp)
678
679         def add_layer(x, y, opac, surface, name, layer_name, visible=True,
680                       locked=False, selected=False,
681                       compositeop=DEFAULT_COMPOSITE_OP, rect=[]):
682             layer = ET.Element('layer')
683             stack.append(layer)
684             store_surface(surface, name, rect)
685             a = layer.attrib
686             if layer_name:
687                 a['name'] = layer_name
688             a['src'] = name
689             a['x'] = str(x)
690             a['y'] = str(y)
691             a['opacity'] = str(opac)
692             if compositeop not in VALID_COMPOSITE_OPS:
693                 compositeop = DEFAULT_COMPOSITE_OP
694             a['composite-op'] = compositeop
695             if visible:
696                 a['visibility'] = 'visible'
697             else:
698                 a['visibility'] = 'hidden'
699             if locked:
700                 a['edit-locked'] = 'true'
701             if selected:
702                 a['selected'] = 'true'
703             return layer
704
705         for idx, l in enumerate(reversed(self.layers)):
706             if l.is_empty():
707                 continue
708             opac = l.opacity
709             x, y, w, h = l.get_bbox()
710             sel = (idx == self.layer_idx)
711             el = add_layer(x-x0, y-y0, opac, l._surface,
712                            'data/layer%03d.png' % idx, l.name, l.visible,
713                            locked=l.locked, selected=sel,
714                            compositeop=l.compositeop, rect=(x, y, w, h))
715             # strokemap
716             sio = StringIO()
717             l.save_strokemap_to_file(sio, -x, -y)
718             data = sio.getvalue(); sio.close()
719             name = 'data/layer%03d_strokemap.dat' % idx
720             el.attrib['mypaint_strokemap_v2'] = name
721             write_file_str(name, data)
722
723         # save background as layer (solid color or tiled)
724         bg = self.background
725         # save as fully rendered layer
726         x, y, w, h = self.get_bbox()
727         l = add_layer(x-x0, y-y0, 1.0, bg, 'data/background.png', 'background',
728                       locked=True, selected=False,
729                       compositeop=DEFAULT_COMPOSITE_OP,
730                       rect=(x,y,w,h))
731         x, y, w, h = bg.get_pattern_bbox()
732         # save as single pattern (with corrected origin)
733         store_surface(bg, 'data/background_tile.png', rect=(x+x0, y+y0, w, h))
734         l.attrib['background_tile'] = 'data/background_tile.png'
735
736         # preview (256x256)
737         t2 = time.time()
738         print '  starting to render full image for thumbnail...'
739
740         thumbnail_pixbuf = self.render_thumbnail()
741         store_pixbuf(thumbnail_pixbuf, 'Thumbnails/thumbnail.png')
742         print '  total %.3fs spent on thumbnail' % (time.time() - t2)
743
744         helpers.indent_etree(image)
745         xml = ET.tostring(image, encoding='UTF-8')
746
747         write_file_str('stack.xml', xml)
748         z.close()
749         os.rmdir(tempdir)
750         if os.path.exists(filename):
751             os.remove(filename) # windows needs that
752         os.rename(filename + '.tmpsave', filename)
753
754         print '%.3fs save_ora total' % (time.time() - t0)
755
756         return thumbnail_pixbuf
757
758     @staticmethod
759     def __xsd2bool(v):
760         v = str(v).lower()
761         if v in ['true', '1']: return True
762         else: return False
763
764     def load_ora(self, filename, feedback_cb=None):
765         """Loads from an OpenRaster file"""
766         print 'load_ora:'
767         t0 = time.time()
768         tempdir = tempfile.mkdtemp('mypaint')
769         if not isinstance(tempdir, unicode):
770             tempdir = tempdir.decode(sys.getfilesystemencoding())
771         z = zipfile.ZipFile(filename)
772         print 'mimetype:', z.read('mimetype').strip()
773         xml = z.read('stack.xml')
774         image = ET.fromstring(xml)
775         stack = image.find('stack')
776
777         w = int(image.attrib['w'])
778         h = int(image.attrib['h'])
779
780         def get_pixbuf(filename):
781             t1 = time.time()
782
783             try:
784                 fp = z.open(filename, mode='r')
785             except KeyError:
786                 # support for bad zip files (saved by old versions of the GIMP ORA plugin)
787                 fp = z.open(filename.encode('utf-8'), mode='r')
788                 print 'WARNING: bad OpenRaster ZIP file. There is an utf-8 encoded filename that does not have the utf-8 flag set:', repr(filename)
789
790             res = self._pixbuf_from_stream(fp, feedback_cb)
791             fp.close()
792             print '  %.3fs loading %s' % (time.time() - t1, filename)
793             return res
794
795         def get_layers_list(root, x=0,y=0):
796             res = []
797             for item in root:
798                 if item.tag == 'layer':
799                     if 'x' in item.attrib:
800                         item.attrib['x'] = int(item.attrib['x']) + x
801                     if 'y' in item.attrib:
802                         item.attrib['y'] = int(item.attrib['y']) + y
803                     res.append(item)
804                 elif item.tag == 'stack':
805                     stack_x = int( item.attrib.get('x', 0) )
806                     stack_y = int( item.attrib.get('y', 0) )
807                     res += get_layers_list(item, stack_x, stack_y)
808                 else:
809                     print 'Warning: ignoring unsupported tag:', item.tag
810             return res
811
812         self.clear() # this leaves one empty layer
813         no_background = True
814         self.set_frame(width=w, height=h)
815
816         selected_layer = None
817         for layer in get_layers_list(stack):
818             a = layer.attrib
819
820             if 'background_tile' in a:
821                 assert no_background
822                 try:
823                     print a['background_tile']
824                     self.set_background(get_pixbuf(a['background_tile']))
825                     no_background = False
826                     continue
827                 except backgroundsurface.BackgroundError, e:
828                     print 'ORA background tile not usable:', e
829
830             src = a.get('src', '')
831             if not src.lower().endswith('.png'):
832                 print 'Warning: ignoring non-png layer'
833                 continue
834             name = a.get('name', '')
835             x = int(a.get('x', '0'))
836             y = int(a.get('y', '0'))
837             opac = float(a.get('opacity', '1.0'))
838             compositeop = str(a.get('composite-op', DEFAULT_COMPOSITE_OP))
839             if compositeop not in VALID_COMPOSITE_OPS:
840                 compositeop = DEFAULT_COMPOSITE_OP
841             selected = self.__xsd2bool(a.get("selected", 'false'))
842             locked = self.__xsd2bool(a.get("edit-locked", 'false'))
843
844             visible = not 'hidden' in a.get('visibility', 'visible')
845             self.add_layer(insert_idx=0, name=name)
846             t1 = time.time()
847
848             # extract the png form the zip into a file first
849             # the overhead for doing so seems to be neglegible (around 5%)
850             z.extract(src, tempdir)
851             tmp_filename = join(tempdir, src)
852             self.load_layer_from_png(tmp_filename, x, y, feedback_cb)
853             os.remove(tmp_filename)
854
855             layer = self.layers[0]
856
857             self.set_layer_opacity(helpers.clamp(opac, 0.0, 1.0), layer)
858             self.set_layer_compositeop(compositeop, layer)
859             self.set_layer_visibility(visible, layer)
860             self.set_layer_locked(locked, layer)
861             if selected:
862                 selected_layer = layer
863             print '  %.3fs loading and converting layer png' % (time.time() - t1)
864             # strokemap
865             fname = a.get('mypaint_strokemap_v2', None)
866             if fname:
867                 if x % N or y % N:
868                     print 'Warning: dropping non-aligned strokemap'
869                 else:
870                     sio = StringIO(z.read(fname))
871                     layer.load_strokemap_from_file(sio, x, y)
872                     sio.close()
873
874         if len(self.layers) == 1:
875             # no assertion (allow empty documents)
876             print 'Warning: Could not load any layer, document is empty.'
877
878         if len(self.layers) > 1:
879             # remove the still present initial empty top layer
880             self.select_layer(len(self.layers)-1)
881             self.remove_layer()
882             # this leaves the topmost layer selected
883
884         if selected_layer is not None:
885             for i, layer in zip(range(len(self.layers)), self.layers):
886                 if layer is selected_layer:
887                     self.select_layer(i)
888                     break
889
890         z.close()
891
892         # remove empty directories created by zipfile's extract()
893         for root, dirs, files in os.walk(tempdir, topdown=False):
894             for name in dirs:
895                 os.rmdir(os.path.join(root, name))
896         os.rmdir(tempdir)
897
898         print '%.3fs load_ora total' % (time.time() - t0)