modes: Fix the (somewhat dubious) compat code
[mypaint:achadwick-mypaint.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 ## Imports
10
11 import gi
12 from gi.repository import GdkPixbuf
13
14 import re
15 import struct
16 import zlib
17 import numpy
18 from numpy import *
19 import logging
20 import os
21 from cStringIO import StringIO
22 import time
23 import zipfile
24 logger = logging.getLogger(__name__)
25 import tempfile
26 import shutil
27 import xml.etree.ElementTree as ET
28
29 from gettext import gettext as _
30
31 import tiledsurface
32 import pixbufsurface
33 import strokemap
34 import mypaintlib
35 import helpers
36
37 from tiledsurface import OPENRASTER_BLEND_MODES
38 from tiledsurface import OPENRASTER_COMPOSITE_MODES
39 from tiledsurface import OPENRASTER_LEGACY_MODES
40 from tiledsurface import DEFAULT_BLEND_MODE
41 from tiledsurface import DEFAULT_COMPOSITE_MODE
42 from tiledsurface import DEFAULT_LEGACY_MODE
43
44
45 ## Module constants
46
47 LOAD_CHUNK_SIZE = 64*1024
48
49
50 ## Class defs
51
52 ## Basic interface for a renderable layer & docs
53
54
55 class LayerBase (object):
56     """Base class defining the layer API
57
58     Layers support two similar tile-based methods which are used for two
59     distinct rendering cases: _blitting_ (unconditional copying without flags)
60     and _compositing_ (conditional alpha-compositing which respects flags like
61     opacity and layer mode). Rendering for the display is supported using the
62     compositing pathway and is coordinated via the `RootLayerStack`. Exporting
63     layers is handled via the blitting pathway, which for layer stacks
64     involves compositing the stacks' contents together to render an effective
65     image.
66
67     """
68
69     ## Class constants
70
71     #TRANSLATORS: Base name used for new untitled *generic* layers. Short.
72     UNTITLED_NAME = _(u"Layer")
73
74     #TRANSLATORS: Template for creating unique names
75     UNIQUE_NAME_TEMPLATE = _(u'%(name)s %(number)d')
76
77     #TRANSLATORS: Regex matching suffix numbers in assigned unique names.
78     UNIQUE_NAME_REGEX = re.compile(_('^(.*?)\\s*(\\d+)$'))
79
80
81     assert UNIQUE_NAME_REGEX.match(UNIQUE_NAME_TEMPLATE % {
82                                      "name": UNTITLED_NAME,
83                                      "number": 42,
84                                    })
85
86
87     ## Construction, loading, other lifecycle stuff
88
89     def __init__(self, name="", rootstack=None, **kwargs):
90         """Construct a new layer
91
92         :param name: The name for the new layer.
93         :param rootstack: If specified, assign a unique name from here.
94         :type rootstack: RootLayerStack
95         :param **kwargs: Ignored.
96
97         All layer subclasses must permit construction without parameters.
98         """
99         super(LayerBase, self).__init__()
100         #: Opacity of the layer (1 - alpha)
101         self.opacity = 1.0
102         #: The layer's name, for display purposes.
103         self.name = name
104         #: Whether the layer is visible (forced opacity 0 when invisible)
105         self.visible = True
106         #: Whether the layer is locked (locked layers cannot be changed)
107         self.locked = False
108         #: How this layer combines colors with its backdrop
109         self.blend_mode = DEFAULT_BLEND_MODE
110         #: How the color blend result is alpha composited onto the backdrop
111         self.composite_mode = DEFAULT_COMPOSITE_MODE
112         #: True if the layer was marked as selected when loaded.
113         self.initially_selected = False
114         #: List of content observers (see _notify_content_observers())
115         self.content_observers = []
116
117         if rootstack is not None:
118             self.assign_unique_name(rootstack.get_names())
119
120
121     @property
122     def compositeop(self):
123         """Legacy OpenRaster compositing 'op' inspired by early SVG specs"""
124         for opname, (b_name, c_name) in OPENRASTER_LEGACY_MODES.iteritems():
125             b = OPENRASTER_BLEND_MODES[b_name]
126             c = OPENRASTER_COMPOSITE_MODES[c_name]
127             if (b, c) == (self.blend_mode, self.composite_mode):
128                 return opname
129         return DEFAULT_LEGACY_MODE
130
131     def _notify_content_observers(self, *args):
132         """Notifies registered content observers
133
134         Observer callbacks in `self.content_observers` are invoked via this
135         method when the contents of the layer change, with the bounding box of
136         the changed region (x, y, w, h).
137         """
138         for func in self.content_observers:
139             func(*args)
140
141
142     @classmethod
143     def new_from_openraster(cls, orazip, elem, tempdir, feedback_cb,
144                             x=0, y=0, **kwargs):
145         """Constructs and returns a layer from an open OpenRaster zipfile
146
147         This implementation just creates a new instance of its class and calls
148         `load_from_openraster()` on it. This should suffice for all subclasses
149         which support parameterless construction.
150         """
151         layer = cls()
152         layer.load_from_openraster(orazip, elem, tempdir, feedback_cb,
153                                    x=x, y=y, **kwargs)
154         return layer
155
156
157     def load_from_openraster(self, orazip, elem, tempdir, feedback_cb,
158                              x=0, y=0, **kwargs):
159         """Loads layer data from an open OpenRaster zipfile
160
161         :param orazip: An OpenRaster zipfile, opened for extracting
162         :type orazip: zipfile.ZipFile
163         :param elem: <layer/> or <stack/> element to load (stack.xml)
164         :type elem: xml.etree.ElementTree.Element
165         :param tempdir: A temporary working directory
166         :param feedback_cb: Callback invoked to provide feedback to the user
167         :param x: X offset of the top-left point for image data
168         :param y: Y offset of the top-left point for image data
169         :param **kwargs: Extensibility
170
171         The base implementation loads the common layer flags from a `<layer/>`
172         or `<stack/>` element, but does nothing more than that. Loading layer
173         data from the zipfile or recursing into stack contents is deferred to
174         subclasses.
175         """
176         attrs = elem.attrib
177         self.name = unicode(attrs.get('name', ''))
178         self.opacity = helpers.clamp(float(attrs.get('opacity', '1.0')),
179                                       0.0, 1.0)
180
181         legacy_mode = str(attrs.get('composite-op', DEFAULT_LEGACY_MODE))
182         if legacy_mode not in OPENRASTER_LEGACY_MODES:
183             legacy_mode = DEFAULT_LEGACY_MODE
184         blend, comp = OPENRASTER_LEGACY_MODES[legacy_mode]
185
186         blend = str(attrs.get("blend", blend))
187         blend = OPENRASTER_BLEND_MODES.get(blend, DEFAULT_BLEND_MODE)
188         self.blend_mode = blend
189
190         comp = str(attrs.get("composite", comp))
191         comp = OPENRASTER_COMPOSITE_MODES.get(comp, DEFAULT_COMPOSITE_MODE)
192         self.composite_mode = comp
193
194         visible = attrs.get('visibility', 'visible').lower()
195         self.visible = (visible != "hidden")
196
197         locked = attrs.get("edit-locked", 'false').lower()
198         self.locked = helpers.xsd2bool(locked)
199
200         selected = attrs.get("selected", 'false').lower()
201         self.initially_selected = helpers.xsd2bool(selected)
202
203
204     def copy(self):
205         """Returns an independent copy of the layer, for Duplicate Layer
206
207         Everything about the returned layer must be a completely independent
208         copy of the original data. If the layer can be worked on, working on it
209         must leave the original layer unaffected.
210
211         This base class implementation can be reused/extended by subclasses if
212         they support zero-argument construction. This implementation uses the
213         `save_snapshot()` and `load_snapshot()` methods.
214         """
215         layer = self.__class__()
216         layer.load_snapshot(self.save_snapshot())
217         return layer
218
219
220     def assign_unique_name(self, existing):
221         """(Re)assigns a unique name to the layer, for use when copying
222
223         :param existing: If present, existing names used for uniquification
224         :param existing: writable set
225         """
226         blank = re.compile(r'^\s*$')
227         if self.name is None or blank.match(self.name):
228             self.name = self.UNTITLED_NAME
229         if self.name not in existing:
230             existing.add(self.name)
231             return
232
233         existing_base2num = {}
234         for name in existing:
235             match = self.UNIQUE_NAME_REGEX.match(name)
236             if match is not None:
237                 base = unicode(match.group(1))
238                 num = int(match.group(2))
239             else:
240                 base = unicode(self.name)
241                 num = 0
242             num = max(num, existing_base2num.get(base, 0))
243             existing_base2num[base] = num
244
245         match = self.UNIQUE_NAME_REGEX.match(self.name)
246         if match is not None:
247             base = unicode(match.group(1))
248         else:
249             base = unicode(self.name)
250         num = existing_base2num.get(base, 0) + 1
251         name = self.UNIQUE_NAME_TEMPLATE % { "name": base,
252                                              "number": num, }
253         assert self.UNIQUE_NAME_REGEX.match(name)
254         assert name not in existing
255         self.name = name
256         existing.add(name)
257
258
259     def clear(self):
260         """Clears the layer"""
261         pass
262
263
264     ## Info methods
265
266     def get_icon_name(self):
267         """The name of the icon to display for the layer
268
269         Ideally symbolic. A value of `None` means that no icon should be
270         displayed.
271         """
272         return None
273
274     @property
275     def effective_opacity(self):
276         """The opacity used when compositing a layer: zero if invisible
277
278         This must match the appearance given by `composite_tile()` when it is
279         called with no `layers` list, even if that method uses other means to
280         determine how or whether to write its output. The base class's
281         effective opacity is zero because the base `composite_tile()` does
282         nothing.
283         """
284         # XXX Is this whole mechanism at all right for composite
285         # XXX  modes like XOR?
286         return 0.0
287
288     def get_alpha(self, x, y, radius):
289         """Gets the average alpha within a certain radius at a point
290
291         :param x: model X coordinate
292         :param y: model Y coordinate
293         :param radius: radius over which to average
294         :rtype: float
295
296         The return value is not affected by the layer opacity, effective or
297         otherwise. This is used by `Document.pick_layer()` and friends to test
298         whether there's anything significant present at a particular point.
299         The default alpha at a point is zero.
300         """
301         return 0.0
302
303     def get_bbox(self):
304         """Returns the inherent (data) bounding box of the layer
305
306         :rtype: lib.helpers.Rect
307
308         The returned rectangle is tile-aligned. It's just a default (zero-size)
309         rect in the base implementation.
310         """
311         return helpers.Rect()
312
313     def get_full_redraw_bbox(self):
314         """Returns the full update notification bounding box of the layer
315
316         :rtype: lib.helpers.Rect
317
318         This is the bounding box which should be used for redrawing if a
319         layer-wide property like opacity or blend mode changes. Normally this
320         is the layer's data bounding box, which allows the GUI to skip empty
321         tiles when redrawing the layer stack. If instead the layer's
322         compositing mode means that an opacity of zero affects the backdrop
323         regardless, then the returned bbox is a zero-size rectangle, which is
324         the signal for a full redraw.
325         """
326         mode_info = mypaintlib.composite_mode_get_info(self.composite_mode)
327         if mode_info["zero_alpha_has_effect"]:
328             return helpers.Rect()
329         else:
330             return self.get_bbox()
331
332     def is_empty(self):
333         """Tests whether the surface is empty
334
335         Always true in the base implementation.
336         """
337         return True
338
339     def get_paintable(self):
340         """True if this layer currently accepts painting brushstrokes
341
342         Always false in the base implementation.
343         """
344         return False
345
346     def get_fillable(self):
347         """True if this layer currently accepts flood fill
348
349         Always false in the base implementation.
350         """
351         return False
352
353     def get_stroke_info_at(self, x, y):
354         """Return the brushstroke at a given point
355
356         :param x: X coordinate to pick from, in model space.
357         :param y: Y coordinate to pick from, in model space.
358         :rtype: lib.strokemap.StrokeShape or None
359
360         Returns None for the base class.
361         """
362         return None
363
364     def get_last_stroke_info(self):
365         """Return the most recently painted stroke
366
367         :rtype lib.strokemap.StrokeShape or None
368
369         Returns None for the base class.
370         """
371         return None
372
373     def get_mode_normalizable(self):
374         """True if this layer currently accepts normalize_mode()"""
375         return False
376
377     def get_trimmable(self):
378         """True if this layer currently accepts trim()"""
379         return False
380
381
382     ## Flood fill
383
384     def flood_fill(self, x, y, color, bbox, tolerance, dst_layer=None):
385         """Fills a point on the surface with a colour
386
387         See `PaintingLayer.flood_fill() for parameters and semantics. The base
388         implementation does nothing.
389         """
390         pass
391
392
393     ## Rendering
394
395
396     def blit_tile_into(self, dst, dst_has_alpha, tx, ty, mipmap_level=0,
397                        **kwargs):
398         """Unconditionally copy one tile's data into an array without options
399
400         The visibility, opacity, and compositing mode flags of this layer must
401         be ignored, or take default values. If the layer has sub-layers, they
402         must be composited together as an isolated group (i.e. over an empty
403         backdrop) using their `composite_tile()` method. It is the result of
404         this compositing which is blitted, ignoring this layer's visibility,
405         opacity, and compositing mode flags.
406
407         :param dst: Target tile array (uint16, NxNx4, 15-bit scaled ints)
408         :type dst: numpy.ndarray
409         :param dst_has_alpha: the alpha channel in dst should be preserved
410         :type dst_has_alpha: bool
411         :param tx: Tile X coordinate, in model tile space
412         :type tx: int
413         :param ty: Tile Y coordinate, in model tile space
414         :type ty: int
415         :param mipmap_level: layer mipmap level to use
416         :type mipmap_level: int
417         :param **kwargs: extensibility...
418
419         The base implementation does nothing.
420         """
421         pass
422
423
424     def composite_tile( self, dst, dst_has_alpha, tx, ty, mipmap_level=0,
425                         layers=None, previewing=None, **kwargs ):
426         """Composite a tile's data into an array, respecting flags/layers list
427
428         Unlike `blit_tile_into()`, the visibility, opacity, and compositing
429         mode flags of this layer must be respected.  It otherwise works just
430         like `blit_tile_into()`, but may make a local decision about whether
431         to render as an isolated group.  This method uses the same parameters
432         as `blit_tile_into()`, with two additions:
433
434         :param layers: the set of layers to render
435         :type layers: set of layers, or None
436         :param previewing: the layer currently being previewed
437         :type previewing: layer
438
439         If `layers` is defined, it identifies the layers which are to be
440         rendered: certain special rendering modes require this. For layers
441         other then the root stack, layers should not render themselves if
442         omitted from a defined `layers`.
443
444         When `previewing` is set, layers must still be obeyed.  The preview
445         layer should be rendered in with normal blending and compositing modes,
446         and with full opacity. This rendering mode is used for layer blink
447         previewing.
448
449         The base implementation does nothing.
450         """
451         pass
452
453
454     def render_as_pixbuf(self, *rect, **kwargs):
455         """Renders this layer as a pixbuf
456
457         :param *rect: rectangle to save, as a 4-tuple
458         :param **kwargs: passed to pixbufsurface.render_as_pixbuf()
459         :rtype: Gdk.Pixbuf
460         """
461         raise NotImplementedError
462
463
464     ## Translation
465
466     def get_move(self, x, y):
467         """Get a translation/move object for this layer
468
469         :param x: Model X position of the start of the move
470         :param y: Model X position of the start of the move
471         :returns: A move object
472         """
473         raise NotImplementedError
474
475
476     def translate(self, dx, dy):
477         """Translate a layer non-interactively
478
479         :param dx: Horizontal offset in model coordinates
480         :param dy: Vertical offset in model coordinates
481
482         The base implementation uses `get_move()` and the object it returns.
483         """
484         move = self.get_move(0, 0)
485         move.update(dx, dy)
486         move.process(n=-1)
487         move.cleanup()
488
489
490     ## Standard stuff
491
492     def __repr__(self):
493         if self.name:
494             return "<%s %r>" % (self.__class__.__name__, self.name)
495         else:
496             return "<%s>" % (self.__class__.__name__)
497
498     def __nonzero__(self):
499         return True
500
501     def __eq__(self, layer):
502         return self is layer
503
504
505     ## Saving
506
507
508     def save_as_png(self, filename, *rect, **kwargs):
509         """Save to a named PNG file
510
511         :param filename: filename to save to
512         :param *rect: rectangle to save, as a 4-tuple
513         :param **kwargs: passed to pixbufsurface.save_as_png()
514         :rtype: Gdk.Pixbuf
515
516         The base implementation does nothing.
517         """
518         pass
519
520
521     def save_to_openraster(self, orazip, tmpdir, path,
522                            canvas_bbox, frame_bbox, **kwargs):
523         """Saves the layer's data into an open OpenRaster ZipFile
524
525         :param orazip: a `zipfile.ZipFile` open for write
526         :param tmpdir: path to a temp dir, removed after the save
527         :param path: Unique path of the layer, for encoding in filenames
528         :type path: tuple of ints
529         :param canvas_bbox: Bounding box of all layers, in absolute coords
530         :type canvas_bbox: tuple
531         :param frame_bbox: Bounding box of the image being saved
532         :type frame_bbox: tuple
533         :param **kwargs: Keyword args used by the save implementation
534         :returns: ``<layer/>`` or ``<stack/>`` element describing data written
535         :rtype: xml.etree.ElementTree.Element
536
537         There are three bounding boxes which need to considered. The inherent
538         bbox of the layer as returned by `get_bbox()` is always tile aligned
539         and refers to absolute model coordinates, as is `canvas_bbox`.
540
541         All of the above bbox's coordinates are defined relative to the canvas
542         origin. However, when saving, the data written must be translated so
543         that `frame_bbox`'s top left corner defines the origin (0, 0), of the
544         saved OpenRaster file. The width and height of `frame_bbox` determine
545         the saved image's dimensions.
546
547         More than one file may be written to the zipfile. The etree element
548         returned should describe everything that was written.
549
550         Paths must be unique sequences of ints, but are not necessarily valid
551         RootLayerStack paths. It's faked for the normally unaddressable
552         background layer right now, for example.
553         """
554         # XXX: Is there a need to pass x and y denoting the alignment of the
555         # XXX: current (sub)stack?
556         raise NotImplementedError
557
558
559     def _get_stackxml_element(self, frame_bbox, tag):
560         """Internal: basic layer info for .ora saving as an etree Element"""
561         x0, y0 = frame_bbox[0:2]
562         bx, by, bw, bh = self.get_bbox()
563         elem = ET.Element(tag)
564         attrs = elem.attrib
565         if self.name:
566             attrs["name"] = str(self.name)
567         attrs["x"] = str(bx - x0)
568         attrs["y"] = str(by - y0)
569         attrs["opacity"] = str(self.opacity)
570         if self.initially_selected:
571             attrs["selected"] = "true"
572         if self.locked:
573             attrs["edit-locked"] = "true"
574         if self.visible:
575             attrs["visibility"] = "visible"
576         else:
577             attrs["visibility"] = "hidden"
578         # For backwards compatibility, write a legacy composite op
579         compositeop = self.compositeop
580         attrs["composite-op"] = str(compositeop)
581         # New model blending and compositing
582         mode = self.blend_mode
583         attrs["blend"] = mypaintlib.blend_mode_get_info(mode)["name"]
584         mode = self.composite_mode
585         attrs["composite"] = mypaintlib.composite_mode_get_info(mode)["name"]
586         return elem
587
588
589     ## Painting symmetry axis
590
591
592     def set_symmetry_axis(self, center_x):
593         """Sets the surface's painting symmetry axis
594
595         :param center_x: Model X coordinate of the axis of symmetry. Set
596                to None to remove the axis of symmetry
597         :type x: `float` or `None`
598
599         This is only useful for paintable layers. Received strokes are
600         reflected in the symmetry axis when it is set.
601
602         the base implementation does nothing.
603         """
604         pass
605
606
607     ## Snapshot
608
609
610     def save_snapshot(self):
611         """Snapshots the state of the layer, for undo purposes
612
613         The returned data should be considered opaque, useful only as a
614         memento to be restored with load_snapshot().
615         """
616         return _LayerBaseSnapshot(self)
617
618
619     def load_snapshot(self, sshot):
620         """Restores the layer from snapshot data"""
621         sshot.restore_to_layer(self)
622
623
624     ## Trimming
625
626
627     def trim(self, rect):
628         """Trim the layer to a rectangle, discarding data outside it
629
630         :param rect: A trimming rectangle in model coordinates
631         :type rect: tuple (x, y, w, h)
632
633         The base implementation does nothing.
634         """
635         pass
636
637
638     ## Type-specific actions
639
640     def activate_layertype_action(self):
641         """Perform the special action associated with this layer type
642
643         This corresponds to the user clicking on the layer's type icon, as
644         returned by `self.get_icon_name()`. The default action does nothing.
645         """
646         pass
647
648
649     ## Merging
650
651     def can_merge_down_from(self, layer):
652         """True if merge_down_from() will work with a given layer"""
653         return False
654
655
656
657 class _LayerBaseSnapshot (object):
658     """Base snapshot implementation
659
660     Snapshots are stored in commands, and used to implement undo and redo.
661     They must be independent copies of the data, although copy-on-write
662     semantics are fine. Snapshot objects must be complete enough clones of the
663     layer's data for duplication to work.
664     """
665
666     def __init__(self, layer):
667         super(_LayerBaseSnapshot, self).__init__()
668         self.opacity = layer.opacity
669         self.name = layer.name
670         self.blend_mode = layer.blend_mode
671         self.composite_mode = layer.composite_mode
672         self.opacity = layer.opacity
673         self.visible = layer.visible
674         self.locked = layer.locked
675         self.content_observers = layer.content_observers[:]
676
677     def restore_to_layer(self, layer):
678         layer.opacity = self.opacity
679         layer.name = self.name
680         layer.blend_mode = self.blend_mode
681         layer.composite_mode = self.composite_mode
682         layer.opacity = self.opacity
683         layer.visible = self.visible
684         layer.locked = self.locked
685         layer.content_observers = self.content_observers[:]
686
687
688 class LoadError (Exception):
689     """Raised when loading to indicate that a layer cannot be loaded"""
690     pass
691
692
693 ## Stacks of layers
694
695
696 class LayerStack (LayerBase):
697     """Reorderable stack of editable layers"""
698
699     ## Class constants
700
701     #TRANSLATORS: "Untitled layer stack" (or "... group"). Short string please!
702     UNTITLED_NAME = _("Group")
703
704
705     ## Construction and other lifecycle stuff
706
707
708     def __init__(self, **kwargs):
709         self._layers = []  # must be done before supercall
710         super(LayerStack, self).__init__(**kwargs)
711         #: Explicit isolation flag
712         self.isolated = False
713         #: Whether the layer is expanded or collapsed
714         self.expanded = True
715         # Blank background, for use in rendering
716         N = tiledsurface.N
717         blank_arr = numpy.zeros((N, N, 4), dtype='uint16')
718         self._blank_bg_surface = tiledsurface.Background(blank_arr)
719
720
721     def load_from_openraster(self, orazip, elem, tempdir, feedback_cb,
722                              x=0, y=0, **kwargs):
723         """Load this layer from an open .ora file"""
724         if elem.tag != "stack":
725             raise LoadError, "<stack/> expected"
726         super(LayerStack, self) \
727             .load_from_openraster(orazip, elem, tempdir, feedback_cb,
728                                   x=x, y=y, **kwargs)
729         self.clear()
730         x += int(elem.attrib.get("x", 0))
731         y += int(elem.attrib.get("y", 0))
732         self.isolated = (str(elem.attrib.get("isolation", "auto")).lower()
733                          != "auto")
734         for child_elem in reversed(elem.findall("./*")):
735             assert child_elem is not elem
736             self.load_child_layer_from_openraster(orazip, child_elem, tempdir,
737                                                   feedback_cb, x=x, y=y,
738                                                   **kwargs)
739
740
741     def load_child_layer_from_openraster(self, orazip, elem, tempdir,
742                                          feedback_cb, x=0, y=0, **kwargs):
743         """Loads and appends a single child layer from an open .ora file"""
744         try:
745             child = layer_new_from_openraster(orazip, elem, tempdir,
746                                               feedback_cb, x=x, y=y, **kwargs)
747         except LoadError:
748             logger.warning("Skipping non-loadable layer")
749         if child is None:
750             logger.warning("Skipping empty layer")
751             return
752         self._layers.append(child)
753
754
755     def clear(self):
756         super(LayerStack, self).clear()
757         self._layers = []
758
759
760     def assign_unique_name(self, existing):
761         super(LayerStack, self).assign_unique_name(existing)
762         for layer in self._layers:
763             layer.assign_unique_name(existing)
764
765
766     def __repr__(self):
767         """String representation of a stack
768
769         >>> repr(LayerStack(name='test'))
770         "<LayerStack 'test' []>"
771         """
772         if self.name:
773             return '<%s %r %r>' % (self.__class__.__name__, self.name,
774                                    self._layers)
775         else:
776             return '<%s %r>' % (self.__class__.__name__, self._layers)
777
778
779     ## Basic list-of-layers access
780
781     def __len__(self):
782         """Return the number of layers in the stack
783
784         >>> stack = LayerStack()
785         >>> len(stack)
786         0
787         >>> stack.append(LayerBase())
788         >>> len(stack)
789         1
790         """
791         return len(self._layers)
792
793
794     def __iter__(self):
795         return iter(self._layers)
796
797
798     def append(self, layer):
799         self._layers.append(layer)
800
801     def remove(self, layer):
802         return self._layers.remove(layer)
803
804     def pop(self, index=None):
805         if index is None:
806             return self._layers.pop()
807         else:
808             return self._layers.pop(index)
809
810     def insert(self, index, layer):
811         self._layers.insert(index, layer)
812
813     def __setitem__(self, index, layer):
814         self._layers[index] = layer
815
816     def __getitem__(self, index):
817         return self._layers[index]
818
819
820     def index(self, layer):
821         return self._layers.index(layer)
822
823
824     ## Info methods
825
826     def get_auto_isolation(self):
827         """Whether the stack, of itself, needs rendering as an isolated group
828
829         :returns: True if the layer requires isolated rendering regardless of
830           the value its `isolated` flag.
831         :rtype: bool
832
833         The layer model proposed by Compositing and Blending Level 1 implies
834         that group isolation happens automatically when group invariance breaks
835         due to particular properties of the group element alone.
836
837         ref: http://www.w3.org/TR/compositing-1/#csscompositingrules_SVG
838
839         The `LayerStack.isolated` flag which forces group isolation must also
840         be consulted when deciding how a `LayerStack` is to be rendered.
841         """
842         # Although composite_mode is here by inference.
843         return ( self.opacity < 1.0 or
844                  self.composite_mode != DEFAULT_COMPOSITE_MODE or
845                  self.blend_mode != DEFAULT_BLEND_MODE )
846
847     def get_bbox(self):
848         """Returns the inherent (data) bounding box of the layer"""
849         result = helpers.Rect()
850         for layer in self._layers:
851             result.expandToIncludeRect(layer.get_bbox())
852         return result
853
854     def get_full_redraw_bbox(self):
855         """Returns the full update notification bounding box of the layer"""
856         result = super(LayerStack, self).get_full_redraw_bbox()
857         if result.w == 0 or result.h == 0:
858             return result
859         for layer in self._layers:
860             bbox = layer.get_full_redraw_bbox()
861             if bbox.w == 0 or bbox.h == 0:
862                 return bbox
863             result.expandToIncludeRect(bbox)
864         return result
865
866     def is_empty(self):
867         return len(self._layers) == 0
868
869     @property
870     def effective_opacity(self):
871         """The opacity used when compositing a layer: zero if invisible"""
872         # Mirror what composite_tile does.
873         if self.visible:
874             return self.opacity
875         else:
876             return 0.0
877
878     ## Rendering
879
880     def blit_tile_into( self, dst, dst_has_alpha, tx, ty, mipmap_level=0,
881                         **kwargs ):
882         """Unconditionally copy one tile's data into an array"""
883         N = tiledsurface.N
884         tmp = numpy.zeros((N, N, 4), dtype='uint16')
885         for layer in self._layers:
886             layer.composite_tile(tmp, True, tx, ty, mipmap_level,
887                                  layers=None, **kwargs)
888         if dst.dtype == 'uint16':
889             mypaintlib.tile_copy_rgba16_into_rgba16(tmp, dst)
890         elif dst.dtype == 'uint8':
891             if dst_has_alpha:
892                 mypaintlib.tile_convert_rgba16_to_rgba8(tmp, dst)
893             else:
894                 mypaintlib.tile_convert_rgbu16_to_rgbu8(tmp, dst)
895         else:
896             raise ValueError, ('Unsupported destination buffer type %r' %
897                                dst.dtype)
898
899
900     def composite_tile( self, dst, dst_has_alpha, tx, ty, mipmap_level=0,
901                         layers=None, previewing=None, **kwargs):
902         """Composite a tile's data into an array, respecting flags/layers list"""
903         blend_mode = self.blend_mode
904         composite_mode = self.composite_mode
905         opacity = self.opacity
906         if layers is not None:
907             if self not in layers:
908                 return
909             # If this is the layer to be previewed, show all child layers
910             # as the layer data.
911             if self is previewing:
912                 layers.update(self._layers)
913         elif not self.visible:
914             return
915         # Render each child layer in turn
916         isolated = self.isolated or self.get_auto_isolation()
917         if isolated or previewing:
918             N = tiledsurface.N
919             tmp = numpy.zeros((N, N, 4), dtype='uint16')
920             for layer in self._layers:
921                 p = previewing
922                 if self is previewing:
923                     p = layer
924                 layer.composite_tile(tmp, True, tx, ty, mipmap_level,
925                                      layers=layers, previewing=p,
926                                      **kwargs)
927             if previewing is not None:
928                 blend_mode = DEFAULT_BLEND_MODE
929                 composite_mode = DEFAULT_COMPOSITE_MODE
930                 opacity = 1.0
931             mypaintlib.tile_blend_composite(tmp, dst, dst_has_alpha, opacity,
932                                             blend_mode, composite_mode)
933         else:
934             for layer in self._layers:
935                 p = previewing
936                 if self is previewing:
937                     p = layer
938                 layer.composite_tile(dst, dst_has_alpha, tx, ty, mipmap_level,
939                                      layers=layers, previewing=p, **kwargs)
940
941
942     def render_as_pixbuf(self, *args, **kwargs):
943         return pixbufsurface.render_as_pixbuf(self, *args, **kwargs)
944
945
946     ## Flood fill
947
948     def flood_fill(self, x, y, color, bbox, tolerance, dst_layer=None):
949         """Fills a point on the surface with a colour (into other only!)
950
951         See `PaintingLayer.flood_fill() for parameters and semantics. Layer
952         stacks only support flood-filling into other layers because they are
953         not surface backed.
954         """
955         assert dst_layer is not self
956         assert dst_layer is not None
957         src = tiledsurface.TileRequestWrapper(self)
958         dst = dst_layer._surface
959         tiledsurface.flood_fill(src, x, y, color, bbox, tolerance, dst)
960
961     def get_fillable(self):
962         """False! Stacks can't be filled interactively or directly."""
963         return False
964
965
966     ## Moving
967
968
969     def get_move(self, x, y):
970         """Get a translation/move object for this layer"""
971         return LayerStackMove(self._layers, x, y)
972
973
974     ## Saving
975
976     def save_as_png(self, filename, *rect, **kwargs):
977         """Save to a named PNG file"""
978         if 'alpha' not in kwargs:
979             kwargs['alpha'] = True
980         pixbufsurface.save_as_png(self, filename, *rect, **kwargs)
981
982
983     def save_to_openraster(self, orazip, tmpdir, path,
984                            canvas_bbox, frame_bbox, **kwargs):
985         """Saves the stack's data into an open OpenRaster ZipFile"""
986         stack_elem = self._get_stackxml_element(frame_bbox, "stack")
987
988         # Saving uses the same origin for all layers regardless of nesting
989         # depth, that of the frame. It's more compatible, and closer to
990         # MyPaint's internal model. Sub-stacks therefore get the default offset,
991         # which is zero.
992         del stack_elem.attrib["x"]
993         del stack_elem.attrib["y"]
994
995         for layer_idx, layer in reversed(list(enumerate(self._layers))):
996             if layer.is_empty():
997                 continue
998             layer_path = tuple(list(path) + [layer_idx])
999             layer_elem = layer.save_to_openraster(orazip, tmpdir, layer_path,
1000                                                   canvas_bbox, frame_bbox,
1001                                                   **kwargs)
1002             stack_elem.append(layer_elem)
1003
1004         isolated = "isolated" if self.isolated else "auto"
1005         stack_elem.attrib["isolation"] = isolated
1006
1007         return stack_elem
1008
1009
1010     ## Snapshotting
1011
1012     def save_snapshot(self):
1013         """Snapshots the state of the layer, for undo purposes"""
1014         return _LayerStackSnapshot(self)
1015
1016
1017     ## Trimming
1018
1019     def trim(self, rect):
1020         """Trim the layer to a rectangle, discarding data outside it"""
1021         for layer in self._layers:
1022             layer.trim(rect)
1023
1024
1025     ## Type-specific action
1026
1027     def activate_layertype_action(self):
1028         self.expanded = not self.expanded
1029
1030     def get_icon_name(self):
1031         return "mypaint-layers-symbolic"
1032
1033
1034 class _LayerStackSnapshot (_LayerBaseSnapshot):
1035
1036     def __init__(self, layer):
1037         super(_LayerStackSnapshot, self).__init__(layer)
1038         self.isolated = layer.isolated
1039         self.expanded = layer.expanded
1040         self.layer_snaps = [l.save_snapshot() for l in layer._layers]
1041         self.layer_classes = [l.__class__ for l in layer._layers]
1042
1043     def restore_to_layer(self, layer):
1044         super(_LayerStackSnapshot, self).restore_to_layer(layer)
1045         layer.isolated = self.isolated
1046         layer.expanded = self.expanded
1047         layer._layers = []
1048         for layer_class, snap in zip(self.layer_classes, self.layer_snaps):
1049             child = layer_class()
1050             child.load_snapshot(snap)
1051             layer._layers.append(child)
1052
1053
1054 class LayerStackMove (object):
1055     """Move object wrapper for layer stacks"""
1056
1057     def __init__(self, layers, x, y):
1058         super(LayerStackMove, self).__init__()
1059         self._moves = []
1060         for layer in layers:
1061             self._moves.append(layer.get_move(x, y))
1062
1063     def update(self, dx, dy):
1064         for move in self._moves:
1065             move.update(dx, dy)
1066
1067     def cleanup(self):
1068         for move in self._moves:
1069             move.cleanup()
1070
1071     def process(self, n=200):
1072         n = max(20, int(n / len(self._moves)))
1073         incomplete = False
1074         for move in self._moves:
1075             incomplete = move.process(n=n) or incomplete
1076         return incomplete
1077
1078
1079 class RootLayerStack (LayerStack):
1080     """Specialized document root layer stack
1081
1082     Adds a "hidden" background layer to the basic LayerStack implementation,
1083     plus the main rendering loop, management of special viewing modes,
1084     management of the layer selection, layer access by paths which address the
1085     tree of layer stacks, and path manipulation.
1086
1087     One of these is instantiated for the running app as part of the primary
1088     lib.document.Document object, and handles anything that needs oversight of
1089     the tree structure to operate. The descendent layers of this object are
1090     those that are presented as user-addressable layers in the Layers panel.
1091     """
1092
1093     ## Class constants
1094
1095     BUBBLE_DEFAULT_COLLAPSE = False   #: Collapse a bubbling stack by default
1096     BUBBLE_DEFAULT_EXPAND = True    #: Expand stacks bubbled into by default
1097
1098
1099     ## Initialization
1100
1101     def __init__(self, doc, **kwargs):
1102         """Construct, as part of a model
1103
1104         :param doc: The model document. May be None for testing.
1105         :param doc: lib.document.Document
1106         """
1107         super(RootLayerStack, self).__init__(**kwargs)
1108         self._doc = doc
1109         # Background
1110         self._default_background = (255, 255, 255)
1111         self._background_layer = BackgroundLayer(self._default_background)
1112         self._background_visible = True
1113         # Special rendering state
1114         self._current_layer_solo = False
1115         self._current_layer_previewing = False
1116         # Current layer
1117         self._current_path = ()
1118
1119     def clear(self):
1120         """Clear the layer and set the default background"""
1121         super(RootLayerStack, self).clear()
1122         self.set_background(self._default_background)
1123
1124
1125     ## Info methods
1126
1127     def get_names(self):
1128         """Returns the set of unique names of all descendents"""
1129         return set((l.name for l in self.deepiter()))
1130
1131     ## Rendering: root stack API
1132
1133
1134     def _get_render_background(self):
1135         """True if the internal background will be rendered by render_into()
1136
1137         :rtype: bool
1138         """
1139
1140         # Layer-solo mode should probably *not* render without the background.
1141         # While it's intended to be used for showing what a layer contains by
1142         # itself, part of that involves showing what effect the the layer's
1143         # mode has. Layer-solo over real alpha checks doesn't permit that.
1144         # Users can always turn background visibility on or off with the UI if
1145         # they wish to preview it both ways, however.
1146
1147         return self._background_visible and not self._current_layer_previewing
1148
1149         # Conversely, current-layer-preview is intended to *blink* very
1150         # visibly to notify the user, so always turn off the background for
1151         # that.
1152
1153     def get_render_produces_transparent_pixels(self):
1154         """True if the render can emit transparent pixels
1155
1156         :rtype: bool
1157
1158         The UI should draw its own checquered background in this case and
1159         expect `render_into()` to write RGBA data with lots of transparent
1160         areas.
1161         """
1162         if not self._get_render_background():
1163             return True
1164         for path, layer in self._enum_render_layers(isolated_children=False):
1165             info = mypaintlib.composite_mode_get_info(layer.composite_mode)
1166             if info.get("can_decrease_alpha", False):
1167                 return True
1168         return False
1169
1170
1171     def _enum_render_layers(self, isolated_children=True):
1172         """Enumerate layers to be rendered with paths, preorder
1173
1174         :param isolated_children: Include descendents of isolated groups
1175         :returns: List of (path, layer) for the selected layers
1176         :rtype: list
1177
1178         If `isolated_children` is ``False``, then only the layers which would
1179         composite directly over the internal background layer are returned.
1180         """
1181         enumeration = []
1182         if self._current_layer_previewing or self._current_layer_solo:
1183             path = self.get_current_path()
1184             while len(path) > 0:
1185                 enumeration.insert(0, (path, self.deepget(path)))
1186                 path = path[:-1]
1187         else:
1188             skip_parents = set()
1189             for (path, layer) in self.deepenumerate():
1190                 parent_path = path[:-1]
1191                 skip = False
1192                 if layer.visible:
1193                     if parent_path not in skip_parents:
1194                         enumeration.append((path, layer))
1195                     if ( (not isolated_children) and
1196                          isinstance(layer, LayerStack) ):
1197                         if layer.isolated or layer.get_auto_isolation():
1198                             skip = True
1199                 else:
1200                     skip = True
1201                 if skip:
1202                     skip_parents.add(path)
1203         return enumeration
1204
1205
1206     def get_render_layers(self, implicit=False):
1207         """Get the set of layers to be rendered as used by render_into()
1208
1209         :param implicit: if true, return None if visible flags should be used
1210         :type implicit: bool
1211         :return: The set of layers which render_into() would use
1212         :rtype: set or None
1213
1214         Implicit mode is used internally by render_into(). If it is enabled,
1215         this method returns ``None`` if each descendent layer's ``visible``
1216         flag is to be used to determine this.  When disabled, the flag is
1217         tested here, which requires an extra iteration.
1218         """
1219         if implicit and not (self._current_layer_previewing or
1220                              self._current_layer_solo):
1221             return None
1222         return set((l for (p, l) in self._enum_render_layers()))
1223
1224
1225     def render_into(self, surface, tiles, mipmap_level, overlay=None):
1226         """Tiled rendering: used for display only
1227
1228         :param surface: target rgba8 surface
1229         :type surface: lib.pixbufsurface.Surface
1230         :param tiles: tile coords, (tx, ty), to render
1231         :type tiles: list
1232         :param mipmap_level: layer and surface mipmap level to use
1233         :type mipmap_level: int
1234         :param overlay: overlay layer to render (stroke highlighting)
1235         :type overlay: SurfaceBackedLayer
1236
1237         """
1238         # Decide a rendering mode
1239         background = self._get_render_background()
1240         dst_has_alpha = self.get_render_produces_transparent_pixels()
1241         layers = self.get_render_layers(implicit=True)
1242         previewing = None
1243         solo = None
1244         if self._current_layer_previewing:
1245             previewing = self.current
1246         if self._current_layer_solo:
1247             solo = self.current
1248         # Blit loop. Could this be done in C++?
1249         for tx, ty in tiles:
1250             with surface.tile_request(tx, ty, readonly=False) as dst:
1251                 self.composite_tile( dst, dst_has_alpha, tx, ty, mipmap_level,
1252                                      layers=layers, background=background,
1253                                      overlay=overlay, previewing=previewing,
1254                                      solo=solo)
1255     def render_thumbnail(self, bbox, **options):
1256         """Renders a 256x256 thumbnail of the stack
1257
1258         :param bbox: Bounding box to make a thumbnail of
1259         :type bbox: tuple
1260         :param **options: Passed to `render_as_pixbuf()`.
1261         :rtype: GtkPixbuf
1262         """
1263         x, y, w, h = bbox
1264         if w == 0 or h == 0:
1265             # workaround to save empty documents
1266             x, y, w, h = 0, 0, tiledsurface.N, tiledsurface.N
1267         mipmap_level = 0
1268         while ( mipmap_level < tiledsurface.MAX_MIPMAP_LEVEL and
1269                 max(w, h) >= 512 ):
1270             mipmap_level += 1
1271             x, y, w, h = x/2, y/2, w/2, h/2
1272         pixbuf = self.render_as_pixbuf(x, y, w, h, mipmap_level=mipmap_level,
1273                                        **options)
1274         assert pixbuf.get_width() == w and pixbuf.get_height() == h
1275         return helpers.scale_proportionally(pixbuf, 256, 256)
1276
1277
1278     ## Rendering: common layer API
1279
1280     def blit_tile_into( self, dst, dst_has_alpha, tx, ty, mipmap_level=0,
1281                         **kwargs ):
1282         """Unconditionally copy one tile's data into an array
1283
1284         The root layer stack implementation just uses `composite_tile()` due
1285         to its lack of conditionality.
1286         """
1287         self.composite_tile( dst, dst_has_alpha, tx, ty,
1288                              mipmap_level=mipmap_level, **kwargs )
1289
1290
1291     def composite_tile( self, dst, dst_has_alpha, tx, ty, mipmap_level=0,
1292                         layers=None, background=None, overlay=None,
1293                         **kwargs ):
1294         """Composite a tile's data into an array, respecting flags/layers list
1295
1296         The root layer stack implementation accepts the parameters documented
1297         in `BaseLayer.composite_tile()`, and also consumes:
1298
1299         :param background: Whether to render the background layer
1300         :type background: bool or None
1301         :param overlay: Layer supporting 15-bit scaled-int tile composition
1302         :type overlay: BaseLayer
1303
1304         If `background` is None, an internal default will be used.
1305
1306         The root layer has flags which ensure it is always visible, so the
1307         result is generally indistinguishable from `blit_tile_into()`. However
1308         the rendering loop, `render_into()`, calls this method and sometimes
1309         passes in a zero-alpha `background` for special rendering modes which
1310         need isolated rendering.
1311
1312         As a further extension to the base API, `dst` may be an 8bpp array. A
1313         temporary 15-bit scaled int array is used for compositing in this
1314         case, and the output is converted to 8bpp.
1315         """
1316         if background is None:
1317             background = self._get_render_background()
1318
1319         if background:
1320             background_surface = self._background_layer._surface
1321         else:
1322             background_surface = self._blank_bg_surface
1323
1324         assert dst.shape[-1] == 4
1325         if dst.dtype == 'uint8':
1326             dst_8bit = dst
1327             N = tiledsurface.N
1328             dst = numpy.empty((N, N, 4), dtype='uint16')
1329         else:
1330             dst_8bit = None
1331
1332         background_surface.blit_tile_into(dst, dst_has_alpha, tx, ty,
1333                                           mipmap_level)
1334
1335         for layer in self._layers:
1336             layer.composite_tile(dst, dst_has_alpha, tx, ty, mipmap_level,
1337                                  layers=layers, **kwargs)
1338         if overlay:
1339             overlay.composite_tile(dst, dst_has_alpha, tx, ty, mipmap_level,
1340                                    layers=set([overlay]), **kwargs)
1341
1342
1343         if dst_8bit is not None:
1344             if dst_has_alpha:
1345                 mypaintlib.tile_convert_rgba16_to_rgba8(dst, dst_8bit)
1346             else:
1347                 mypaintlib.tile_convert_rgbu16_to_rgbu8(dst, dst_8bit)
1348
1349
1350     ## Current layer
1351
1352     def get_current_path(self):
1353         """Get the current layer's path
1354
1355         :rtype: tuple
1356         """
1357         return self._current_path
1358
1359     def set_current_path(self, path):
1360         """Set the current layer path
1361
1362         :param path: The path to use; will be trimmed until it fits
1363         :type path: tuple
1364         """
1365         # Try to use as much of the specified path as possible
1366         p = tuple(path)
1367         while len(p) > 0:
1368             layer = self.deepget(p)
1369             if layer is not None:
1370                 self._current_path = p
1371                 # Expand the tree up to the point chosen
1372                 while len(p) > 0:
1373                     p = p[:-1]
1374                     parent = self.deepget(p)
1375                     assert isinstance(parent, LayerStack)
1376                     parent.expanded = True
1377                 return
1378             p = p[:-1]
1379         # Fallback cases
1380         if len(self._layers) > 0:
1381             self._current_path = (0,)
1382         else:
1383             raise ValueError, 'Invalid path %r' % (path,)
1384
1385     current_path = property(get_current_path, set_current_path)
1386
1387
1388     def get_current(self):
1389         """Get the current layer (also exposed as a read-only property)"""
1390         layer = self.deepget(self._current_path)
1391         assert layer is not self
1392         assert layer is not None
1393         return layer
1394
1395     current = property(get_current)
1396
1397
1398     ## The background layer
1399
1400     @property
1401     def background_layer(self):
1402         """The background layer (accessor)"""
1403         return self._background_layer
1404
1405     def set_background(self, obj, make_default=False):
1406         """Set the background layer's surface from an object
1407
1408         :param obj: Background layer, or an RGB triple (uint8), or a
1409            HxWx4 or HxWx3 numpy array which can be either uint8 or uint16.
1410         :type obj: layer.BackgroundLayer or tuple or numpy array
1411         :param make_default: Whether to set the default background for
1412           clear() too.
1413         :type make_default: bool
1414         """
1415         if isinstance(obj, BackgroundLayer):
1416             obj = obj._surface
1417         if not isinstance(obj, tiledsurface.Background):
1418             if isinstance(obj, GdkPixbuf.Pixbuf):
1419                 obj = helpers.gdkpixbuf2numpy(obj)
1420             obj = tiledsurface.Background(obj)
1421         self._background_layer.set_surface(obj)
1422         if make_default:
1423             self._default_background = obj
1424         if not self._background_visible:
1425             self._background_visible = True
1426             if self._doc:
1427                 self._doc.call_doc_observers()
1428         if self._doc:
1429             self._doc.invalidate_all()
1430
1431
1432     def set_background_visible(self, value):
1433         """Sets whether the background is visible"""
1434         value = bool(value)
1435         old_value = self._background_visible
1436         self._background_visible = value
1437         if value != old_value and self._doc:
1438             self._doc.call_doc_observers()
1439             self._doc.invalidate_all()
1440
1441
1442     def get_background_visible(self):
1443         """Gets whether the background is visible"""
1444         return bool(self._background_visible)
1445
1446     ## Layer Solo toggle (not saved)
1447
1448     def get_current_layer_solo(self):
1449         """Layer-solo state for the document"""
1450         return self._current_layer_solo
1451
1452     def set_current_layer_solo(self, value):
1453         """Layer-solo state for the document"""
1454         # TODO: use the user_initiated hack to make this undoable
1455         value = bool(value)
1456         old_value = self._current_layer_solo
1457         self._current_layer_solo = value
1458         if value != old_value:
1459             self._doc.call_doc_observers()
1460             self._doc.invalidate_all()
1461
1462
1463     ## Current layer temporary previewing state (not saved, used for blinking)
1464
1465
1466     def get_current_layer_previewing(self):
1467         """Layer-previewing state, as used when blinking a layer"""
1468         return self._current_layer_previewing
1469
1470
1471     def set_current_layer_previewing(self, value):
1472         """Layer-previewing state, as used when blinking a layer"""
1473         value = bool(value)
1474         old_value = self._current_layer_previewing
1475         self._current_layer_previewing = value
1476         if value != old_value:
1477             self._doc.call_doc_observers()
1478             self._doc.invalidate_all()
1479
1480
1481     ## Layer path manipulation
1482
1483
1484     def path_above(self, path):
1485         """Return the path for the layer stacked above a given path
1486
1487         :param path: a layer path
1488         :type path: list or tuple
1489         :return: the layer above `path` in walk order
1490         :rtype: tuple
1491
1492         >>> root, leaves = _make_test_stack()
1493         >>> root.path_above([0, 0])
1494         (0, 1)
1495         >>> root.path_above([0, 1])
1496         (0,)
1497         >>> root.path_above([0])
1498         (1,)
1499         >>> root.path_above([1])
1500
1501         """
1502         paths = [tuple(p) for p,l in self.deepenumerate(postorder=True)]
1503         idx = paths.index(tuple(path))
1504         idx += 1
1505         if idx >= len(paths):
1506             return None
1507         return paths[idx]
1508
1509
1510     def path_below(self, path):
1511         """Return the path for the layer stacked below a given path
1512
1513         :param path: a layer path
1514         :type path: list or tuple
1515         :return: the layer above `path` in stacking order
1516         :rtype: tuple or None
1517
1518         >>> root, leaves = _make_test_stack()
1519         >>> root.path_below([0, 1])
1520         (0, 0)
1521         >>> root.path_below([0, 0])
1522         (1,)
1523         >>> root.path_below([1])
1524         (0,)
1525         >>> root.path_below((0,))
1526         """
1527         paths = [tuple(p) for p,l in self.deepenumerate(postorder=True)]
1528         idx = paths.index(tuple(path))
1529         idx -= 1
1530         if idx < 0:
1531             return None
1532         return paths[idx]
1533
1534
1535     ## Layer bubbling
1536
1537     def _bubble_layer(self, path, collapse, expand, upstack):
1538         """Move a layer through the stack, preserving its tree structure
1539
1540         Parameters and returns are the same as for `bubble_layer_up()` (and
1541         down), with the following addition:
1542
1543         :param upstack: Direction: true to bubble up, false to bubble down
1544
1545         """
1546         path = list(path)
1547         if len(path) == 0:
1548             raise ValueError, "Cannot reposition the root of the stack"
1549         parent_path, index = path[:-1], path[-1]
1550         parent = self.deepget(parent_path, self)
1551         assert index < len(parent)
1552         assert index > -1
1553
1554         # Collapse sub-stacks when bubbling them.
1555         # Seems to make the UI a little easier to understand.
1556         if collapse:
1557             layer = self.deepget(path)
1558             assert layer is not None
1559             if isinstance(layer, LayerStack):
1560                 layer.expanded = False
1561
1562         # The layer to be moved may already be at the end of its stack
1563         # in the direction we want; if so, remove it then insert it
1564         # one place beyond its parent in the bubble direction.
1565         end_index = (len(parent) - 1) if upstack else 0
1566         if index == end_index:
1567             if parent is self:
1568                 return False
1569             grandparent_path = parent_path[:-1]
1570             grandparent = self.deepget(grandparent_path, self)
1571             parent_index = grandparent.index(parent)
1572             layer = parent.pop(index)
1573             beyond_parent_index = parent_index
1574             if upstack:
1575                 beyond_parent_index += 1
1576             grandparent.insert(beyond_parent_index, layer)
1577             if expand:
1578                 grandparent.expanded = True
1579             return True
1580
1581         # Move the layer within its current parent
1582         new_index = index + (1 if upstack else -1)
1583         if new_index < len(parent) and new_index > -1:
1584             # A sibling layer is already at the intended position
1585             sibling = parent[new_index]
1586             if isinstance(sibling, LayerStack):
1587                 # Ascend: remove & put at the near end of the sibling stack
1588                 layer = parent.pop(index)
1589                 if upstack:
1590                     sibling.insert(0, layer)
1591                 else:
1592                     sibling.append(layer)
1593                 if expand:
1594                     sibling.expanded = True
1595                 return True
1596             else:
1597                 # Swap positions with the sibling layer
1598                 layer = parent[index]
1599                 parent[new_index] = layer
1600                 parent[index] = sibling
1601                 return True
1602         else:
1603             # Nothing there, move to the end of this branch
1604             layer = parent.pop(index)
1605             if upstack:
1606                 parent.append(layer)
1607             else:
1608                 parent.insert(0, layer)
1609             return True
1610
1611
1612     def bubble_layer_up(self, path, collapse=BUBBLE_DEFAULT_COLLAPSE,
1613                         expand=BUBBLE_DEFAULT_EXPAND):
1614         """Move a layer up through the stack, preserving its tree structure
1615
1616         :param path: Layer path identifying the layer to move
1617         :param collapse: If true, collapse the moved layer if it's a stack
1618         :param expand: If true, expand sub-stacks moved into
1619         :returns: True if the stack structure was modified
1620
1621         Bubbling follows the layout of the tree and preserves its structure
1622         apart from the layers touched by the move, so it can be driven by the
1623         keyboard usefully. `bubble_layer_down()` is the exact inverse of this
1624         operation.
1625
1626         These methods assume the existence of a UI which lays out layers from
1627         bottom to top with a postorder traversal.  If the path identifies a
1628         substack, the substack is moved as a whole.
1629         """
1630         return self._bubble_layer(path, collapse, expand, True)
1631
1632
1633     def bubble_layer_down(self, path, collapse=BUBBLE_DEFAULT_COLLAPSE,
1634                           expand=BUBBLE_DEFAULT_EXPAND):
1635         """Move a layer down through the stack, preserving its tree structure
1636
1637         This is the inverse operation to bubbling a layer up. Parameters and
1638         return values are the same as those for `bubble_layer_up()`.
1639         """
1640         return self._bubble_layer(path, collapse, expand, False)
1641
1642
1643     ## Simplified tree storage and access
1644
1645     # We use a path concept that's similar to GtkTreePath's, but almost like a
1646     # key/value store if this is the root layer stack.
1647
1648     def deepiter(self):
1649         """Iterates across all descendents of the stack
1650
1651         >>> stack, leaves = _make_test_stack()
1652         >>> len(list(stack.deepiter()))
1653         6
1654         >>> len(set(stack.deepiter())) == len(list(stack.deepiter())) # no dups
1655         True
1656         >>> stack not in stack.deepiter()
1657         True
1658         >>> [] not in stack.deepiter()
1659         True
1660         >>> leaves[0] in stack.deepiter()
1661         True
1662         """
1663         queue = [self]
1664         while len(queue) > 0:
1665             layer = queue.pop(0)
1666             if layer is not self:
1667                 yield layer
1668             if isinstance(layer, LayerStack):
1669                 for child in reversed(layer._layers):
1670                     queue.insert(0, child)
1671
1672
1673     def deepenumerate(self, postorder=False):
1674         """Enumerates the structure of a stack in depth
1675
1676         :param postorder: If true, use a post-order traversal.
1677
1678         >>> stack, leaves = _make_test_stack()
1679         >>> [a[0] for a in stack.deepenumerate()]
1680         [(0,), (0, 0), (0, 1), (1,), (1, 0), (1, 1)]
1681         >>> [a[0] for a in stack.deepenumerate(postorder=True)]
1682         [(0, 0), (0, 1), (0,), (1, 0), (1, 1), (1,)]
1683         >>> set(leaves) - set([a[1] for a in stack.deepenumerate()])
1684         set([])
1685         """
1686         queue = [([], self)]
1687         walked = set()
1688         while len(queue) > 0:
1689             path, layer = queue.pop(0)
1690             is_stack = isinstance(layer, LayerStack)
1691             if (not is_stack) or (not postorder) or (layer in walked):
1692                 if layer is not self:
1693                     yield (tuple(path), layer)
1694             if is_stack:
1695                 if (not postorder) or layer not in walked:
1696                     for i, child in enumerate(layer._layers):
1697                         queue.insert(i, (path + [i], child))
1698                     if postorder:
1699                         walked.add(layer)
1700                         queue.insert(len(layer._layers), (path, layer))
1701
1702
1703     def deepget(self, path, default=None):
1704         """Gets a layer based on its path
1705
1706         >>> stack, leaves = _make_test_stack()
1707         >>> stack.deepget(()) is stack
1708         True
1709         >>> stack.deepget((0,1))
1710         <PaintingLayer '01'>
1711         >>> stack.deepget((0,))
1712         <LayerStack '0' [<PaintingLayer '00'>, ...]>
1713         >>> stack.deepget((0,11), "missing")
1714         'missing'
1715
1716         """
1717         if len(path) == 0:
1718             return self
1719         unused_path = list(path)
1720         layer = self
1721         while len(unused_path) > 0:
1722             idx = unused_path.pop(0)
1723             if abs(idx) > len(layer._layers)-1:
1724                 return default
1725             layer = layer._layers[idx]
1726             if unused_path:
1727                 if not isinstance(layer, LayerStack):
1728                     return default
1729             else:
1730                 return layer
1731         return default
1732
1733
1734     def deepinsert(self, path, layer):
1735         """Inserts a layer before the final index in path
1736
1737         :param path: an insertion path: see below
1738         :type path: iterable of integers
1739         :param layer: the layer to insert
1740         :type layer: LayerBase
1741
1742         Deepinsert cannot create sub-stacks. Every element of `path` before
1743         the final element must be a valid `list`-style ``[]`` index into an
1744         existing stack along the chain being addressed, starting with the
1745         root.  The final element may be any index which `list.insert()`
1746         accepts.  Negative final indices, and final indices greater than the
1747         number of layers in the addressed stack are quite valid in `path`.
1748
1749         >>> stack, leaves = _make_test_stack()
1750         >>> layer = PaintingLayer('foo')
1751         >>> stack.deepinsert((0,2), layer)
1752         >>> stack.deepget((0,-1)) is layer
1753         True
1754         >>> stack = RootLayerStack(doc=None)
1755         >>> layer = PaintingLayer('bar')
1756         >>> stack.deepinsert([0], layer)
1757         >>> stack.deepget([0]) is layer
1758         True
1759         """
1760         if len(path) == 0:
1761             raise IndexError, 'Cannot insert after the root'
1762         unused_path = list(path)
1763         stack = self
1764         while len(unused_path) > 0:
1765             idx = unused_path.pop(0)
1766             if not isinstance(stack, LayerStack):
1767                 raise IndexError, ("All nonfinal elements of %r must "
1768                                    "identify a stack" % (path,))
1769             if unused_path:
1770                 stack = stack._layers[idx]
1771             else:
1772                 stack.insert(idx, layer)
1773                 return
1774         assert (len(unused_path) > 0), ("deepinsert() should never exhaust "
1775                                         "the path")
1776
1777
1778     def deeppop(self, path):
1779         """Removes a layer by its path
1780
1781         >>> stack, leaves = _make_test_stack()
1782         >>> stack.deeppop(())
1783         Traceback (most recent call last):
1784         ...
1785         IndexError: Cannot pop the root stack
1786         >>> stack.deeppop([0])
1787         <LayerStack '0' [<PaintingLayer '00'>, <PaintingLayer '01'>]>
1788         >>> stack.deeppop((0,1))
1789         <PaintingLayer '11'>
1790         >>> stack.deeppop((0,2))
1791         Traceback (most recent call last):
1792         ...
1793         IndexError: ...
1794         """
1795         if len(path) == 0:
1796             raise IndexError, "Cannot pop the root stack"
1797         parent_path = path[:-1]
1798         child_index = path[-1]
1799         if len(parent_path) == 0:
1800             parent = self
1801         else:
1802             parent = self.deepget(parent_path)
1803         return parent.pop(child_index)
1804
1805
1806     def deepremove(self, layer):
1807         """Removes a layer from any of the root's descendents
1808
1809         >>> stack, leaves = _make_test_stack()
1810         >>> stack.deepremove(leaves[3])
1811         >>> stack.deepremove(leaves[2])
1812         >>> stack.deepremove(stack.deepget([0]))
1813         >>> stack
1814         <RootLayerStack [<LayerStack '1' []>]>
1815         >>> stack.deepremove(leaves[3])
1816         Traceback (most recent call last):
1817         ...
1818         ValueError: Layer is not in the root stack or any descendent
1819         """
1820         if layer is self:
1821             raise ValueError, "Cannot remove the root stack"
1822         for path, descendent_layer in self.deepenumerate():
1823             assert len(path) > 0
1824             if descendent_layer is not layer:
1825                 continue
1826             parent_path = path[:-1]
1827             if len(parent_path) == 0:
1828                 parent = self
1829             else:
1830                 parent = self.deepget(parent_path)
1831             return parent.remove(layer)
1832         raise ValueError, "Layer is not in the root stack or any descendent"
1833
1834
1835     def deepindex(self, layer):
1836         """Return a path for a layer by searching the stack tree
1837
1838         >>> stack, leaves = _make_test_stack()
1839         >>> stack.deepindex(stack)
1840         []
1841         >>> [stack.deepindex(l) for l in leaves]
1842         [(0, 0), (0, 1), (1, 0), (1, 1)]
1843         """
1844         if layer is self:
1845             return []
1846         for path, ly in self.deepenumerate():
1847             if ly is layer:
1848                 return path
1849         return None
1850
1851
1852     ## Convenience methods for commands
1853
1854
1855     def canonpath(self, index=None, layer=None, path=None,
1856                   usecurrent=False, uselowest=False):
1857         """Verify and return the path for a layer from various criteria
1858
1859         :param index: index of the layer in deepenumerate() order
1860         :param layer: a layer, which must be a descendent of this root
1861         :param path: a layer path
1862         :param usecurrent: if true, use the current path on failure/fallthru
1863         :return: a new, verified path referring to an existing layer
1864         :rtype: tuple
1865
1866         The returned path is guaranteed to refer to an existing layer, and be
1867         the path in its most canonical form. If no matching layer exists, a
1868         ValueError is raised.
1869         """
1870         if path is not None:
1871             layer = self.deepget(path)
1872             if layer is self:
1873                 raise ValueError, ("path=%r is root: must be descendent"
1874                                    % (path,))
1875             if layer is not None:
1876                 path = self.deepindex(layer)
1877                 assert self.deepget(path) is layer
1878                 return path
1879             elif not usecurrent:
1880                 raise ValueError, "layer not found with path=%r" % (path,)
1881         elif index is not None:
1882             if index < 0:
1883                 raise ValueError, "negative layer index %r" % (index,)
1884             for i, (path, layer) in enumerate(self.deepenumerate()):
1885                 if i == index:
1886                     assert self.deepget(path) is layer
1887                     return path
1888             if not usecurrent:
1889                 raise ValueError, "layer not found with index=%r" % (index,)
1890         elif layer is not None:
1891             if layer is self:
1892                 raise ValueError, "layer is root stack: must be descendent"
1893             path = self.deepindex(layer)
1894             if path is not None:
1895                 assert self.deepget(path) is layer
1896                 return path
1897             elif not usecurrent:
1898                 raise ValueError, "layer=%r not found" % (layer,)
1899         # Criterion failed. Try fallbacks.
1900         if usecurrent:
1901             path = self.get_current_path()
1902             layer = self.deepget(path)
1903             if layer is not None:
1904                 assert layer is not self, ("The current layer path refers to "
1905                                            "the root stack.")
1906                 path = self.deepindex(layer)
1907                 assert self.deepget(path) is layer
1908                 return path
1909             if not uselowest:
1910                 raise ValueError, ("Invalid current path; uselowest "
1911                                    "might work but not specified")
1912         if uselowest:
1913             if len(self) > 0:
1914                 path = (0,)
1915                 assert self.deepget(path) is not None
1916                 return path
1917             else:
1918                 raise ValueError, "Invalid current path; stack is empty"
1919         raise TypeError, ("No layer/index/path criterion, and no fallbacks")
1920
1921     def layers_below(self, path=None, layer=None):
1922         """Iterates over all layers below a layer or path, in render order"""
1923         assert not (path is None and layer is None)
1924         for e_path, e_layer in self.deepenumerate(postorder=False):
1925             if e_layer is layer or e_path == path:
1926                 break
1927             yield e_layer
1928
1929     def get_backdrop_func(self, path):
1930         """Returns a function which renders a layer's backdrop for a tile"""
1931         layers_behind = set(self.layers_below(path))
1932         N = tiledsurface.N
1933         def _get_bg(tx, ty):
1934             dst = numpy.empty((N, N, 4), dtype='uint16')
1935             self.composite_tile(dst, True, tx, ty, layers=layers_behind,
1936                                 background=None)
1937             return dst
1938         return _get_bg
1939
1940     def get_merge_down_target_path(self):
1941         """Returns the target layer path for Merge Down, or None
1942
1943         :returns: A valid path to merge the current layer into with Merge Down
1944         :rtype: tuple or None
1945
1946         The target layer is the member of the current layer's stack lying below
1947         it, and to be valid for Merge Down it must be a painting layer. If no
1948         valid target layer exists, None is returned.
1949         """
1950         current_path = self.current_path
1951         current_layer = self.current
1952         if not isinstance(current_layer, PaintingLayer):
1953             # The layer needs to support conversion to Normal mode
1954             return None
1955         parent_path = current_path[:-1]
1956         parent_layer = self.deepget(parent_path, self)
1957         current_idx = parent_layer.index(current_layer)
1958         target_idx = current_idx - 1
1959         if target_idx < 0:
1960             # Nothing below this layer at the current level.
1961             return None
1962         target_layer = parent_layer[target_idx]
1963         if not isinstance(target_layer, PaintingLayer):
1964             # The layer needs to support conversion to Normal mode as well
1965             # as being surface-backed.
1966             return None
1967         # Target is valid for merge.
1968         return tuple(list(parent_path) + [target_idx])
1969
1970
1971     ## Loading
1972
1973     def load_from_openraster(self, orazip, elem, tempdir, feedback_cb,
1974                              x=0, y=0, **kwargs):
1975         """Load this layer from an open .ora file"""
1976         self._no_background = True
1977         super(RootLayerStack, self) \
1978             .load_from_openraster(orazip, elem, tempdir, feedback_cb,
1979                                   x=x, y=y, **kwargs)
1980         del self._no_background
1981         # Select a suitable working layer
1982         num_loaded = 0
1983         selected_path = None
1984         for path, loaded_layer in self.deepenumerate():
1985             if loaded_layer.initially_selected:
1986                 selected_path = path
1987             num_loaded += 1
1988         logger.debug("Loaded %d layer(s)" % num_loaded)
1989         if num_loaded == 0:
1990             logger.error('Could not load any layer, document is empty.')
1991             logger.info('Adding an empty painting layer')
1992             empty_layer = PaintingLayer()
1993             self.append(empty_layer)
1994             selected_path = [0]
1995         assert len(self._layers) > 0
1996         if not selected_path:
1997             selected_path = [max(0, len(self._layers)-1)]
1998         self.set_current_path(selected_path)
1999
2000
2001     def load_child_layer_from_openraster(self, orazip, elem, tempdir,
2002                                          feedback_cb, x=0, y=0, **kwargs):
2003         """Loads and appends a single child layer from an open .ora file"""
2004         attrs = elem.attrib
2005         # Handle MyPaint's special background tile notation
2006         bg_src = attrs.get('background_tile', None)
2007         if bg_src:
2008             assert self._no_background, "Only one background is permitted"
2009             try:
2010                 logger.debug("background tile: %r", bg_src)
2011                 bg_pixbuf = pixbuf_from_zipfile(orazip, bg_src, feedback_cb)
2012                 self.set_background(bg_pixbuf)
2013                 self._no_background = False
2014                 return
2015             except tiledsurface.BackgroundError, e:
2016                 logger.warning('ORA background tile not usable: %r', e)
2017         super(RootLayerStack, self) \
2018             .load_child_layer_from_openraster(orazip, elem, tempdir,
2019                                               feedback_cb, x=x, y=y, **kwargs)
2020
2021     ## Saving
2022
2023     def save_to_openraster(self, orazip, tmpdir, path, canvas_bbox,
2024                            frame_bbox, **kwargs):
2025         """Saves the stack's data into an open OpenRaster ZipFile"""
2026         stack_elem = super(RootLayerStack, self) \
2027             .save_to_openraster( orazip, tmpdir, path, canvas_bbox,
2028                                  frame_bbox, **kwargs )
2029         # Save background
2030         bg_layer = self.background_layer
2031         bg_layer.initially_selected = False
2032         bg_path = (len(self._layers),)
2033         bg_elem = bg_layer.save_to_openraster( orazip, tmpdir, bg_path,
2034                                                canvas_bbox, frame_bbox,
2035                                                **kwargs )
2036         stack_elem.append(bg_elem)
2037
2038         return stack_elem
2039
2040
2041 ## Layers with data
2042
2043
2044 class SurfaceBackedLayer (LayerBase):
2045     """Minimal Surface-backed layer implementation
2046
2047     This minimal implementation is backed by a surface, which is used for
2048     rendering by by the main application; subclasses are free to choose whether
2049     they consider the surface to be the canonical source of layer data or
2050     something else with the surface being just a preview.
2051     """
2052
2053     ## Class constants: capabilities
2054
2055     #: Whether the surface can be painted to (if not locked)
2056     IS_PAINTABLE = False
2057
2058     #: Whether the surface can be filled (if not locked)
2059     IS_FILLABLE = False
2060
2061     #: Suffixes allowed in load_from_openraster()
2062     ALLOWED_SUFFIXES = []
2063
2064
2065     ## Initialization
2066
2067     def __init__(self, surface=None, **kwargs):
2068         """Construct a new SurfaceBackedLayer
2069
2070         :param surface: Surface to use, overriding the default.
2071         :param **kwargs: passed to superclass.
2072
2073         If `surface` is specified, content observers will not be attached, and
2074         the layer will not be cleared during construction. The default is to
2075         instantiate and use a new unobserved `tiledsurface.Surface`.
2076         """
2077         super(SurfaceBackedLayer, self).__init__(**kwargs)
2078
2079         # Pluggable surface implementation
2080         # Only connect observers if using the default tiled surface
2081         if surface is None:
2082             self._surface = tiledsurface.Surface()
2083             self._surface.observers.append(self._notify_content_observers)
2084         else:
2085             self._surface = surface
2086
2087         # Clear if we created our own surface
2088         if surface is None:
2089             self.clear()
2090
2091
2092     def load_from_surface(self, surface):
2093         """Load the backing surface image's tiles from another surface"""
2094         self._surface.load_from_surface(surface)
2095
2096     def load_from_strokeshape(self, strokeshape):
2097         """Load image tiles from a strokemap.StrokeShape"""
2098         strokeshape.render_to_surface(self._surface)
2099
2100
2101     ## Loading
2102
2103     def load_from_openraster(self, orazip, elem, tempdir, feedback_cb,
2104                              x=0, y=0, extract_and_keep=False, **kwargs):
2105         """Loads layer flags and bitmap/surface data from a .ora zipfile
2106
2107         :param extract_and_keep: Set to true to extract and keep a copy
2108
2109         The normal behaviour is to load the data file directly from `orazip`
2110         without using a temporary file.  If `extract_and_keep` is set, an
2111         alternative method is used which extracts
2112
2113             os.path.join(tempdir, elem.attrib["src"])
2114
2115         and reads from that. The caller is then free to do what it likes with
2116         this file.
2117         """
2118         # Load layer flags
2119         super(SurfaceBackedLayer, self) \
2120             .load_from_openraster(orazip, elem, tempdir, feedback_cb,
2121                                   x=x, y=y, **kwargs)
2122         # Read bitmap content into the surface
2123         attrs = elem.attrib
2124         src = attrs.get("src", None)
2125         src_rootname, src_ext = os.path.splitext(src)
2126         src_rootname = os.path.basename(src_rootname)
2127         src_ext = src_ext.lower()
2128         x += int(attrs.get('x', 0))
2129         y += int(attrs.get('y', 0))
2130         logger.debug("Loading %r at %+d%+d", src_rootname, x, y)
2131         t0 = time.time()
2132         suffixes = self.ALLOWED_SUFFIXES
2133         if src_ext not in suffixes:
2134             logger.error("Cannot load PaintingLayers from a %r", src_ext)
2135             raise LoadError, "Only %r are supported" % (suffixes,)
2136         if extract_and_keep:
2137             orazip.extract(src, path=tempdir)
2138             tmp_filename = os.path.join(tempdir, src)
2139             self.load_surface_from_pixbuf_file(tmp_filename, x, y, feedback_cb)
2140         else:
2141             pixbuf = pixbuf_from_zipfile(orazip, src, feedback_cb=feedback_cb)
2142             self.load_surface_from_pixbuf(pixbuf, x=x, y=y)
2143         t1 = time.time()
2144         logger.debug('%.3fs loading and converting src %r for %r',
2145                      t1 - t0, src_ext, src_rootname)
2146
2147
2148     def load_surface_from_pixbuf_file(self, filename, x=0, y=0,
2149                                       feedback_cb=None):
2150         """Loads the layer's surface from any file which GdkPixbuf can open"""
2151         fp = open(filename, 'rb')
2152         pixbuf = pixbuf_from_stream(fp, feedback_cb)
2153         fp.close()
2154         return self.load_surface_from_pixbuf(pixbuf, x, y)
2155
2156
2157     def load_surface_from_pixbuf(self, pixbuf, x=0, y=0):
2158         """Loads the layer's surface from a GdkPixbuf"""
2159         arr = helpers.gdkpixbuf2numpy(pixbuf)
2160         surface = tiledsurface.Surface()
2161         bbox = surface.load_from_numpy(arr, x, y)
2162         self.load_from_surface(surface)
2163         return bbox
2164
2165
2166     def clear(self):
2167         """Clears the layer"""
2168         self._surface.clear()
2169
2170
2171     ## Info methods
2172
2173     @property
2174     def effective_opacity(self):
2175         """The opacity used when compositing a layer: zero if invisible"""
2176         # Mirror what composite_tile does.
2177         if self.visible:
2178             return self.opacity
2179         else:
2180             return 0.0
2181
2182     def get_alpha(self, x, y, radius):
2183         """Gets the average alpha within a certain radius at a point"""
2184         return self._surface.get_alpha(x, y, radius)
2185
2186
2187     def get_bbox(self):
2188         """Returns the inherent (data) bounding box of the layer"""
2189         return self._surface.get_bbox()
2190
2191
2192     def is_empty(self):
2193         """Tests whether the surface is empty"""
2194         return self._surface.is_empty()
2195
2196
2197     def get_paintable(self):
2198         """True if this layer currently accepts painting brushstrokes"""
2199         return self.IS_PAINTABLE and not self.locked
2200
2201
2202     def get_fillable(self):
2203         """True if this layer currently accepts flood fill"""
2204         return self.IS_FILLABLE and not self.locked
2205
2206     ## Flood fill
2207
2208     def flood_fill(self, x, y, color, bbox, tolerance, dst_layer=None):
2209         """Fills a point on the surface with a colour
2210
2211         See `PaintingLayer.flood_fill() for parameters and semantics. This
2212         implementation does nothing.
2213         """
2214         pass
2215
2216
2217     ## Rendering
2218
2219
2220     def blit_tile_into(self, dst, dst_has_alpha, tx, ty, mipmap_level=0,
2221                        **kwargs):
2222         """Unconditionally copy one tile's data into an array without options
2223
2224         The minimal surface-based implementation composites one tile of the
2225         backing surface over the array dst, modifying only dst.
2226         """
2227         self._surface.composite_tile( dst, dst_has_alpha, tx, ty,
2228                                       mipmap_level=mipmap_level,
2229                                       opacity=1, blend_mode=DEFAULT_BLEND_MODE,
2230                                       composite_mode=DEFAULT_COMPOSITE_MODE)
2231
2232
2233     def composite_tile(self, dst, dst_has_alpha, tx, ty, mipmap_level=0,
2234                        layers=None, previewing=None, **kwargs):
2235         """Composite a tile's data into an array, respecting flags/layers list
2236
2237         The minimal surface-based implementation composites one tile of the
2238         backing surface over the array dst, modifying only dst.
2239         """
2240         blend_mode = self.blend_mode
2241         composite_mode = self.composite_mode
2242         opacity = self.opacity
2243         if layers is not None:
2244             if self not in layers:
2245                 return
2246         elif not self.visible:
2247             return
2248         if self is previewing:
2249             blend_mode = DEFAULT_BLEND_MODE
2250             composite_mode = DEFAULT_COMPOSITE_MODE
2251             opacity = 1.0
2252         self._surface.composite_tile( dst, dst_has_alpha, tx, ty,
2253                                       mipmap_level=mipmap_level,
2254                                       opacity=opacity,
2255                                       blend_mode=blend_mode,
2256                                       composite_mode=composite_mode )
2257
2258
2259     def render_as_pixbuf(self, *rect, **kwargs):
2260         """Renders this layer as a pixbuf"""
2261         return self._surface.render_as_pixbuf(*rect, **kwargs)
2262
2263
2264     ## Translating
2265
2266
2267     def get_move(self, x, y):
2268         """Get a translation/move object for this layer
2269
2270         :param x: Model X position of the start of the move
2271         :param y: Model X position of the start of the move
2272         :returns: A move object
2273
2274         Subclasses should extend this minimal implementation to provide
2275         additional functionality for moving things other than the surface tiles
2276         around.
2277         """
2278         return self._surface.get_move(x, y)
2279
2280
2281     ## Layer merging
2282
2283     def merge_down_from(self, src_layer, **kwargs):
2284         """Merge another layer's data down into this layer
2285
2286         :param src_layer: The source layer
2287         :param **kwargs: Currently ignored
2288
2289         The minimal implementation only merges surface tiles. The destination
2290         layer must therefore always be surface-backed. After this operation,
2291         the destination layer's opacity is set to 1.0 and it is made visible.
2292         """
2293         self.normalize_opacity()
2294         for tx, ty in src_layer._surface.get_tiles():
2295             with self._surface.tile_request(tx, ty, readonly=False) as dst:
2296                 src_layer.composite_tile(dst, True, tx, ty)
2297
2298     def can_merge_down_from(self, layer):
2299         """True if merge_down_from() will work with a given layer"""
2300         if layer is self:
2301             return False
2302         elif layer is None:
2303             return False
2304         return isinstance(layer, SurfaceBackedLayer)
2305
2306
2307     ## Layer normalization
2308
2309     def normalize_mode(self, get_bg):
2310         """Normalize composite/blend modes and opacity, retaining appearance
2311
2312         This results in a layer with unchanged appearance, but made visible if
2313         it isn't already, with an opacity of 1.0, and with a normal/src-over
2314         blending mode. Note that this method produces a ghost image of the
2315         backdrop in the normalized layer in most cases.
2316
2317         :param get_bg: A backdrop-getter function
2318
2319         The `get_bg` function has the signature ``get_bg(tx, ty)`` and returns
2320         a 16-bit RGBA NumPy array containing the usual fix15_t data. It should
2321         produce the underlying backdrop to be picked up by the normalized
2322         image.
2323         """
2324         if ( self.blend_mode == mypaintlib.BlendModeNormal and
2325              self.composite_mode == mypaintlib.CompositeModeSourceOver and
2326              self.effective_opacity == 1.0 ):
2327             return # optimization for merging layers
2328         N = tiledsurface.N
2329         tmp = empty((N, N, 4), dtype='uint16')
2330         for tx, ty in self._surface.get_tiles():
2331             bg = get_bg(tx, ty)
2332             # tmp = bg + layer (composited with its mode)
2333             mypaintlib.tile_copy_rgba16_into_rgba16(bg, tmp)
2334             self.composite_tile(tmp, False, tx, ty)
2335             # overwrite layer data with composited result
2336             with self._surface.tile_request(tx, ty, readonly=False) as dst:
2337                 mypaintlib.tile_copy_rgba16_into_rgba16(tmp, dst)
2338                 dst[:,:,3] = 0 # minimize alpha (discard original alpha)
2339                 # recalculate layer in normal mode
2340                 mypaintlib.tile_flat2rgba(dst, bg)
2341         self.opacity = 1.0
2342         self.visible = True
2343         self.blend_mode = mypaintlib.BlendModeNormal
2344         self.composite_mode = mypaintlib.CompositeModeSourceOver
2345
2346     def get_mode_normalizable(self):
2347         """True if this layer currently accepts normalize_mode()"""
2348         return True
2349         #or possibly self.opacity < 1.0 or self.compositeop != "svg:src-over"?
2350
2351     def normalize_opacity(self):
2352         """Normalizes the opacity of this layer to 1 without changing its look
2353
2354         This results in a layer with unchanged appearance, but made visible if
2355         it isn't already and with an opacity of 1.0.
2356         """
2357         opacity = self.effective_opacity
2358         if opacity < 1.0:
2359             for tx, ty in self._surface.get_tiles():
2360                 with self._surface.tile_request(tx, ty, readonly=False) as t:
2361                     t *= opacity
2362         self.opacity = 1.0
2363         self.visible = True
2364
2365
2366     ## Saving
2367
2368
2369     def save_as_png(self, filename, *rect, **kwargs):
2370         """Save to a named PNG file
2371
2372         :param filename: filename to save to
2373         :param *rect: rectangle to save, as a 4-tuple
2374         :param **kwargs: passed to pixbufsurface.save_as_png()
2375         :rtype: Gdk.Pixbuf
2376         """
2377         self._surface.save_as_png(filename, *rect, **kwargs)
2378
2379
2380     def save_to_openraster(self, orazip, tmpdir, path,
2381                            canvas_bbox, frame_bbox, **kwargs):
2382         """Saves the layer's data into an open OpenRaster ZipFile"""
2383         rect = self.get_bbox()
2384         return self._save_rect_to_ora( orazip, tmpdir, "layer", path,
2385                                        frame_bbox, rect, **kwargs )
2386
2387     @staticmethod
2388     def _make_refname(prefix, path, suffix, sep='-'):
2389         """Internal: standardized filename for something wiith a path"""
2390         assert "." in suffix
2391         path_ref = sep.join([("%02d" % (n,)) for n in path])
2392         if not suffix.startswith("."):
2393             suffix = sep + suffix
2394         return "".join([prefix, sep, path_ref, suffix])
2395
2396
2397     def _save_rect_to_ora( self, orazip, tmpdir, prefix, path,
2398                            frame_bbox, rect, **kwargs ):
2399         """Internal: saves a rectangle of the surface to an ORA zip"""
2400         # Write PNG data via a tempfile
2401         pngname = self._make_refname(prefix, path, ".png")
2402         pngpath = os.path.join(tmpdir, pngname)
2403         t0 = time.time()
2404         self.save_as_png(pngpath, *rect, **kwargs)
2405         t1 = time.time()
2406         logger.debug('%.3fs surface saving %r', t1-t0, pngname)
2407         # Archive and remove
2408         storepath = "data/%s" % (pngname,)
2409         orazip.write(pngpath, storepath)
2410         os.remove(pngpath)
2411         # Return details
2412         elem = self._get_stackxml_element(frame_bbox, "layer")
2413         elem.attrib["src"] = storepath
2414         return elem
2415
2416
2417     ## Painting symmetry axis
2418
2419
2420     def set_symmetry_axis(self, center_x):
2421         """Sets the surface's painting symmetry axis"""
2422         if center_x is None:
2423             self._surface.set_symmetry_state(False, 0.0)
2424         else:
2425             self._surface.set_symmetry_state(True, center_x)
2426
2427
2428     ## Snapshots
2429
2430
2431     def save_snapshot(self):
2432         """Snapshots the state of the layer, for undo purposes"""
2433         return _SurfaceBackedLayerSnapshot(self)
2434
2435
2436     ## Trimming
2437
2438     def get_trimmable(self):
2439         return True
2440
2441     def trim(self, rect):
2442         """Trim the layer to a rectangle, discarding data outside it
2443
2444         :param rect: A trimming rectangle in model coordinates
2445         :type rect: tuple (x, y, w, h)
2446
2447         Only complete tiles are discarded by this method.
2448         """
2449         self._surface.trim(rect)
2450
2451
2452 class _SurfaceBackedLayerSnapshot (_LayerBaseSnapshot):
2453     """Minimal layer implementation's snapshot
2454
2455     Snapshots are stored in commands, and used to implement undo and redo.
2456     They must be independent copies of the data, although copy-on-write
2457     semantics are fine. Snapshot objects don't have to be _full and exact_
2458     clones of the layer's data, but they do need to capture _inherent_
2459     qualities of the layer. Mere metadata can be ignored. For the base
2460     layer implementation, this means the surface tiles and the layer's
2461     opacity.
2462     """
2463
2464     def __init__(self, layer):
2465         super(_SurfaceBackedLayerSnapshot, self).__init__(layer)
2466         self.surface_sshot = layer._surface.save_snapshot()
2467
2468     def restore_to_layer(self, layer):
2469         super(_SurfaceBackedLayerSnapshot, self).restore_to_layer(layer)
2470         layer._surface.load_snapshot(self.surface_sshot)
2471
2472
2473 class BackgroundLayer (SurfaceBackedLayer):
2474     """Background layer, with a repeating tiled image"""
2475
2476     # NOTE: by convention only, there is just a single non-editable background
2477     # layer. Background layers cannot be manipulated by the user except through
2478     # the background dialog.
2479
2480     # NOTE: this could be generalized as a repeating tile for general use in
2481     # the layers stack, extending the ExternalLayer concept. Think textures!
2482     # Might need src-in compositing for that though.
2483
2484     def __init__(self, bg, **kwargs):
2485         super(BackgroundLayer, self).__init__(**kwargs)
2486         if isinstance(bg, tiledsurface.Background):
2487             surface = bg
2488         else:
2489             surface = tiledsurface.Background(bg)
2490         super(BackgroundLayer, self).__init__(name="background",
2491                                               surface=surface)
2492         self.locked = False
2493         self.visible = True
2494         self.opacity = 1.0
2495
2496     def copy(self):
2497         raise NotImplementedError, "BackgroundLayer cannot be copied yet"
2498
2499     def save_snapshot(self):
2500         raise NotImplementedError, "BackgroundLayer cannot be snapshotted yet"
2501
2502     def load_snapshot(self):
2503         raise NotImplementedError, "BackgroundLayer cannot be snapshotted yet"
2504
2505     def set_surface(self, surface):
2506         """Sets the surface from a tiledsurface.Background"""
2507         assert isinstance(surface, tiledsurface.Background)
2508         self._surface = surface
2509
2510     def save_to_openraster(self, orazip, tmpdir, path,
2511                            canvas_bbox, frame_bbox, **kwargs):
2512         # Save as a regular layer for other apps.
2513         # Background surfaces repeat, so this will fit the frame.
2514         # XXX But we use the canvas bbox and always have. Why?
2515         # XXX - Presumably it's for origin alignment.
2516         # XXX - Inefficient for small frames.
2517         # XXX - I suspect rect should be redone with (w,h) granularity
2518         # XXX   and be based on the frame bbox.
2519         rect = canvas_bbox
2520         elem = super(BackgroundLayer, self)\
2521             ._save_rect_to_ora( orazip, tmpdir, "background", path,
2522                                 frame_bbox, rect, **kwargs )
2523         # Also save as single pattern (with corrected origin)
2524         x0, y0 = frame_bbox[0:2]
2525         x, y, w, h = self.get_bbox()
2526         rect = (x+x0, y+y0, w, h)
2527
2528         pngname = self._make_refname("background", path, "tile.png")
2529         tmppath = os.path.join(tmpdir, pngname)
2530         t0 = time.time()
2531         self._surface.save_as_png(tmppath, *rect, **kwargs)
2532         t1 = time.time()
2533         storename = 'data/%s' % (pngname,)
2534         logger.debug('%.3fs surface saving %s', t1 - t0, storename)
2535         orazip.write(tmppath, storename)
2536         os.remove(tmppath)
2537         elem.attrib['background_tile'] = storename
2538         return elem
2539
2540
2541 class ExternalLayer (SurfaceBackedLayer):
2542     """A layer which is stored as a tempfile in a non-MyPaint format
2543
2544     External layers add the name of the tempfile to the base implementation.
2545     The internal surface is used to display a bitmap preview of the layer, but
2546     this cannot be edited.
2547     """
2548
2549     ## Class constants
2550
2551     IS_FILLABLE = False
2552     IS_PAINTABLE = False
2553     ALLOWED_SUFFIXES = []
2554
2555
2556     ## Construction
2557
2558     def __init__(self, **kwargs):
2559         """Construct, with blank internal fields"""
2560         super(ExternalLayer, self).__init__(**kwargs)
2561         self._basename = None
2562         self._workdir = None
2563         self._x = None
2564         self._y = None
2565
2566     def set_workdir(self, workdir):
2567         """Sets the working directory (i.e. to doc's tempdir)
2568
2569         This is where working copies are created and cached during operation.
2570         """
2571         self._workdir = workdir
2572
2573
2574     def load_from_openraster(self, orazip, elem, tempdir, feedback_cb,
2575                              x=0, y=0, **kwargs):
2576         """Loads layer data and attrs from an OpenRaster zipfile
2577
2578         Using this method also sets the working directory for the layer to
2579         tempdir.
2580         """
2581         # Load layer flags and raster data
2582         super(ExternalLayer, self) \
2583             .load_from_openraster(orazip, elem, tempdir, feedback_cb,
2584                                   x=x, y=y, extract_and_keep=True, **kwargs)
2585         # Use the extracted file as the zero revision, and record layer
2586         # working parameters.
2587         attrs = elem.attrib
2588         src = attrs.get("src", None)
2589         src_rootname, src_ext = os.path.splitext(src)
2590         src_ext = src_ext.lower()
2591         tmp_filename = os.path.join(tempdir, src)
2592         if not os.path.exists(tmp_filename):
2593             raise LoadError, ("tmpfile missing after extract_and_keep: %r" %
2594                               (tmp_filename,))
2595         rev0_fd, rev0_filename = tempfile.mkstemp(suffix=src_ext, dir=tempdir)
2596         os.close(rev0_fd)
2597         os.rename(tmp_filename, rev0_filename)
2598         self._basename = os.path.basename(rev0_filename)
2599         self._workdir = tempdir
2600         self._x = x + int(attrs.get('x', 0))
2601         self._y = y + int(attrs.get('y', 0))
2602
2603
2604     ## Snapshots
2605
2606     def save_snapshot(self):
2607         """Snapshots the state of the layer and its strokemap for undo"""
2608         return _ExternalLayerSnapshot(self)
2609
2610     ## Moving
2611
2612
2613     def get_move(self, x, y):
2614         """Start a new move for the external layer"""
2615         surface_move = super(ExternalLayer, self).get_move(x, y)
2616         return ExternalLayerMove(self, surface_move)
2617
2618
2619     ## Trimming (no-op for external layers)
2620
2621     def get_trimmable(self):
2622         return False
2623
2624     def trim(self, rect):
2625         """Override: external layers have no useful trim(), so do nothing"""
2626         pass
2627
2628
2629     ## Saving
2630
2631
2632     def save_to_openraster(self, orazip, tmpdir, path,
2633                            canvas_bbox, frame_bbox, **kwargs):
2634         """Saves the working file to an OpenRaster zipfile"""
2635         # No supercall in this override, but the base implementation's
2636         # attributes method is useful.
2637         elem = self._get_stackxml_element(frame_bbox, "layer")
2638         attrs = elem.attrib
2639         # Store the managed layer position rather than one based on the
2640         # surface's tiles bbox, however.
2641         x0, y0 = frame_bbox[0:2]
2642         attrs["x"] = str(self._x - x0)
2643         attrs["y"] = str(self._y - y0)
2644         # Pick a suitable name to store under.
2645         src_path = os.path.join(self._workdir, self._basename)
2646         src_rootname, src_ext = os.path.splitext(src_path)
2647         src_ext = src_ext.lower()
2648         storename = self._make_refname("layer", path, src_ext)
2649         storepath = "data/%s" % (storename,)
2650         # Archive (but do not remove) the managed tempfile
2651         t0 = time.time()
2652         orazip.write(src_path, storepath)
2653         t1 = time.time()
2654         # Return details of what was written.
2655         attrs["src"] = unicode(storepath)
2656         return elem
2657
2658
2659 class _ExternalLayerSnapshot (_SurfaceBackedLayerSnapshot):
2660     """Snapshot subclass for external layers"""
2661
2662     def __init__(self, layer):
2663         super(_ExternalLayerSnapshot, self).__init__(layer)
2664         self.basename = self._copy_working_file( layer._basename,
2665                                                  layer._workdir )
2666         self.workdir = layer._workdir
2667         self.x = layer._x
2668         self.y = layer._y
2669
2670     def restore_to_layer(self, layer):
2671         super(_ExternalLayerSnapshot, self).restore_to_layer(layer)
2672         layer._basename = self._copy_working_file( self.basename,
2673                                                    self.workdir )
2674         layer._workdir = self.workdir
2675         layer._x = self.x
2676         layer._y = self.y
2677
2678     @staticmethod
2679     def _copy_working_file(old_basename, workdir):
2680         old_filename = os.path.join(workdir, old_basename)
2681         rootname, ext = os.path.splitext(old_basename)
2682         old_fp = open(old_filename, 'rb')
2683         new_fp = tempfile.NamedTemporaryFile( dir=workdir,
2684                                               suffix=ext, mode="w+b",
2685                                               delete=False )
2686         shutil.copyfileobj(old_fp, new_fp)
2687         new_basename = os.path.basename(new_fp.name)
2688         logger.debug( "Copied %r to %r within %r...",
2689                       old_basename, new_basename, workdir )
2690         new_fp.close()
2691         old_fp.close()
2692         return new_basename
2693
2694     def __del__(self):
2695         self.cleanup()
2696
2697     def cleanup(self):
2698         sshot_copy = os.path.join(self.workdir, self.basename)
2699         if os.path.exists(sshot_copy):
2700             logger.debug("Cleanup: removing %r from %r",
2701                          self.basename, self.workdir)
2702             os.remove(sshot_copy)
2703         else:
2704             logger.debug("Cleanup: %r was already removed from %r",
2705                          self.basename, self.workdir)
2706
2707
2708 class ExternalLayerMove (object):
2709     """Move object wrapper for external layers"""
2710
2711     def __init__(self, layer, surface_move):
2712         super(ExternalLayerMove, self).__init__()
2713         self._wrapped = surface_move
2714         self._layer = layer
2715         self._start_x = layer._x
2716         self._start_y = layer._y
2717
2718     def update(self, dx, dy):
2719         self._layer._x = int(round(self._start_x + dx))
2720         self._layer._y = int(round(self._start_y + dy))
2721         self._wrapped.update(dx, dy)
2722
2723     def cleanup(self):
2724         self._wrapped.cleanup()
2725
2726     def process(self, n=200):
2727         return self._wrapped.process(n)
2728
2729
2730
2731 class VectorLayer (ExternalLayer):
2732     """SVG-based vector layer"""
2733
2734     #TRANSLATORS: Name used for new untitled vector/SVG/Inkscape layers. Short.
2735     UNTITLED_NAME = _(u"Vector Layer")
2736
2737     ALLOWED_SUFFIXES = [".svg"]
2738
2739     # activate_layertype_action() should invoke inkscape. Modally?
2740
2741     def get_icon_name(self):
2742         return "mypaint-layer-vector-symbolic"
2743
2744
2745