Browse Source

Merge branch 'Layers'

Billy Barrow 8 years ago
parent
commit
b5e8e9e8f3
7 changed files with 1120 additions and 82 deletions
  1. 178 0
      PF2/Layer.py
  2. 44 0
      PF2/Tools/Blur.py
  3. 82 0
      PF2/VectorMask/Path.py
  4. 55 0
      PF2/VectorMask/__init__.py
  5. 376 64
      PF2/__init__.py
  6. 0 4
      Tool/__init__.py
  7. 385 14
      ui/PF2_Activity.glade

+ 178 - 0
PF2/Layer.py

@@ -0,0 +1,178 @@
+from gi.repository import Gtk, GLib
+import PF2.VectorMask as VectorMask
+import cv2
+
+class Layer:
+    def __init__(self, base, name, on_change):
+        print("init", name)
+        self.mask = VectorMask.VectorMask()
+        self.tools = []
+        self.tool_map = {}
+        self.name = name
+        self.enabled = True
+        self.selected_tool = 0
+        self.editable = not base
+        self.opacity = 1.0
+        self.blend_mode = "additive"
+        if(not self.editable):
+            self.blend_mode = "overlay"
+
+        self.selector_row = None
+
+        self.tool_box = Gtk.FlowBox()
+        self.tool_box.set_selection_mode(Gtk.SelectionMode.NONE)
+        self.tool_box.set_orientation(1)
+
+        self.tool_stack = Gtk.Stack()
+        self.tool_stack.set_transition_type(6)
+        self.tool_stack.set_homogeneous(False)
+
+
+        self.layer_changed = on_change
+
+
+    def add_tool(self, tool):
+        print("add tool", tool)
+        self.tool_box.add(tool.tool_button)
+        self.tool_stack.add(tool.widget)
+        self.tool_map[tool.tool_button] = tool
+
+        tool.tool_button.connect("clicked", self.on_tool_button_clicked)
+        tool.connect_on_change(self.on_tool_change)
+
+        self.tools += [tool,]
+
+        if(len(self.tools) == 1):
+            tool.tool_button.set_active(True)
+
+
+    def on_tool_change(self, tool, prop):
+        self.layer_changed(self)
+
+
+    def on_tool_button_clicked(self, sender):
+        if(sender.get_active()):
+            self.tool_stack.set_visible_child(self.tool_map[sender].widget)
+            for key in self.tool_map:
+                if(key != sender):
+                    key.set_active(False)
+
+        elif(self.tool_stack.get_visible_child() == self.tool_map[sender].widget):
+            sender.set_active(True)
+
+    def get_layer_dict(self):
+        layerDict = {}
+
+        # Get tool values
+        toolDict = {}
+        for tool in self.tools:
+            toolDict[tool.id] = tool.get_properties_as_dict()
+
+        layerDict["tools"] = toolDict
+        layerDict["name"] = self.name
+        layerDict["mask"] = self.mask.get_vector_mask_dict()
+        layerDict["enabled"] = self.enabled
+        layerDict["opacity"] = self.opacity
+        layerDict["blend-mode"] = self.blend_mode
+
+        return layerDict
+
+    def set_from_layer_dict(self, dict):
+        print("set_from_layer_dict", dict)
+        # Load Tool Data
+        for tool in self.tools:
+            if(tool.id in dict["tools"]):
+                GLib.idle_add(tool.load_properties, dict["tools"][tool.id])
+
+        # The base layer only has tools.
+        if(self.editable):
+            # Load Mask Vectors
+            self.mask.set_from_vector_mask_dict(dict["mask"])
+
+            # Load Layer Name
+            self.name = dict["name"]
+
+            # Load Enabled State
+            self.enabled = dict["enabled"]
+
+            # Load Opacity Fraction
+            self.opacity = dict["opacity"]
+
+            # Load Blend Mode
+            self.blend_mode = dict["blend-mode"]
+
+
+    def render_layer(self, baseImage, image, callback=None):
+        # Only process if the layer is enabled
+        if(self.enabled and self.opacity != 0.0):
+            layer = None
+
+            if(self.blend_mode == "overlay"):
+                # We are passed a base image (original)
+                # Make a copy of this to pass through the tools
+                layer = baseImage.copy()
+            else: # if(self.blend_mode == "additive"):
+                # Layer is additive, make copy of current working image
+                layer = image.copy()
+
+            # Process the Layer
+            ntools = len(self.tools)
+            count = 1
+            for tool in self.tools:
+                if (callback != None):
+                    # For showing progress
+                    callback(tool.name, ntools, count - 1)
+
+                # Call the tool's image processing function
+                layer = tool.on_update(layer)
+                count += 1
+
+
+            # Base layer needs no mask processing
+            if(not self.editable):
+                image = layer
+
+            # Here we would blend with the mask
+            else:
+                mask_map = self.mask.get_mask_map()
+                height, width = layer.shape[:2]
+                mask_map = cv2.resize(mask_map, (width, height), interpolation=cv2.INTER_AREA)
+                mask_map = mask_map * self.opacity
+
+                inverted_map = (mask_map * -1) + 1
+                for i in range(0,3):
+                    image[0:, 0:, i] = (layer[0:, 0:, i] * mask_map) + (image[0:, 0:, i] * inverted_map)
+
+        return image
+
+
+
+    def show_all(self):
+
+        self.tool_box.show_all()
+        self.tool_stack.show_all()
+
+        for tool in self.tools:
+            tool.widget.show_all()
+            tool.tool_button.show_all()
+
+
+    def reset_tools(self):
+        for tool in self.tools:
+            tool.reset()
+
+
+    def set_size(self, width, height):
+        self.mask.set_dimentions(width, height)
+
+    def set_opacity(self, opacity):
+        self.opacity = opacity
+        self.on_tool_change(None, None)
+
+    def set_enabled(self, enabled):
+        self.enabled = enabled
+        self.on_tool_change(None, None)
+
+    def set_blending_mode(self, mode):
+        self.blend_mode = mode
+        self.on_tool_change(None, None)

+ 44 - 0
PF2/Tools/Blur.py

@@ -0,0 +1,44 @@
+import cv2
+import numpy
+
+import Tool
+from PF2.Tools import Contrast
+
+
+class Blur(Tool.Tool):
+    def on_init(self):
+        self.id = "blur"
+        self.name = "Blur"
+        self.icon_path = "ui/PF2_Icons/Blur.png"
+        self.properties = [
+            # Detailer
+            Tool.Property("enabled", "Blur", "Header", False, has_toggle=True, has_button=False),
+            Tool.Property("method", "Method", "Combo", 1, options=[
+                "Guassian",
+                "Average",
+                "Median"
+            ]),
+            Tool.Property("strength", "Strength", "Slider", 5, max=100, min=0),
+
+        ]
+
+    def on_update(self, image):
+        im = image
+        if(self.props["enabled"].get_value()):
+            strength = self.props["strength"].get_value()
+            method = self.props["method"].get_value()
+
+            if (strength > 0):
+                blur_size = 2 * round((round(strength) + 1) / 2) - 1
+                if(method == 0):
+                    im = cv2.GaussianBlur(im, (int(blur_size), int(blur_size)), 0)
+
+                elif(method == 1):
+                    im = cv2.blur(im, (int(blur_size), int(blur_size)))
+
+                elif(method == 2):
+                    im = cv2.medianBlur(im, int(blur_size))
+        return im
+
+
+

+ 82 - 0
PF2/VectorMask/Path.py

@@ -0,0 +1,82 @@
+import cv2
+import numpy
+from gi.repository import Gdk
+
+
+class Path:
+    def __init__(self, brush_size, brush_feather, scale, additive):
+        self.points = []
+        self.brush_size = brush_size
+        self.scale = scale
+        self.brush_feather = brush_feather
+        self.additive = additive
+
+    def add_point(self, x, y, previewShape = None, fill = None):
+        self.points.append([x,y])
+        if(previewShape != None and fill != None and len(self.points) > 1):
+            preview = numpy.zeros(previewShape, dtype=numpy.uint8)
+            points = self.points[-2:]
+            print(points)
+            sx = points[0][0]
+            sy = points[0][1]
+            fx = points[1][0]
+            fy = points[1][1]
+            cv2.line(preview, (sx, sy), (fx, fy), (fill), int(self.brush_size), cv2.LINE_4)
+            return preview
+
+
+    def get_mask_map(self, mask):
+        if(self.additive):
+            return self.draw_additive_path(mask)
+        else:
+            return self.draw_subtractive_path(mask)
+
+    def draw_subtractive_path(self, mask):
+        self.draw_points(mask, self.points, 0)
+        return mask
+
+    def draw_additive_path(self, mask):
+        map = numpy.zeros(mask.shape, dtype=numpy.uint8)
+
+        self.draw_points(map, self.points, 255)
+
+        if(self.brush_feather > 1):
+            blur_size = 2 * round((round(self.brush_feather) + 1) / 2) - 1
+            map = cv2.GaussianBlur(map, (int(blur_size), int(blur_size)), 0)
+            map = map[:, :, numpy.newaxis]
+
+        # Painful workaround for int overflow
+        mask2 = mask.astype(numpy.uint64)
+        mask2 = mask2 + map
+        mask2[mask2 > 255] = 255
+        mask = mask2.astype(numpy.uint8)
+
+        return mask
+
+    def draw_points(self, mask, points, value):
+        for i in range(len(points) - 2):
+            sx = int(self.points[i][0] * self.scale)
+            sy = int(self.points[i][1] * self.scale)
+            fx = int(self.points[i + 1][0] * self.scale)
+            fy = int(self.points[i + 1][1] * self.scale)
+
+            # print("[PATH.PY]", sx, sy, fx, fy)
+            cv2.line(mask, (sx, sy), (fx, fy), (value), int(self.brush_size * self.scale), cv2.LINE_AA)
+
+        return mask
+
+
+    def get_path_dict(self):
+        return {
+            "brush_size": self.brush_size,
+            "scale": self.scale,
+            "brush_feather": self.brush_feather,
+            "additive": self.additive,
+            "points": self.points
+        }
+
+    @staticmethod
+    def new_from_dict(dict):
+        path = Path(dict["brush_size"], dict["brush_feather"], dict["scale"], dict["additive"])
+        path.points = dict["points"]
+        return path

+ 55 - 0
PF2/VectorMask/__init__.py

@@ -0,0 +1,55 @@
+import numpy
+from PF2.VectorMask import Path
+
+
+class VectorMask:
+    def __init__(self):
+        self.paths = []
+        self.width = 0
+        self.height = 0
+
+    def set_dimentions(self, width, height):
+        self.width = width
+        self.height = height
+
+    def get_new_path(self, brush_size, brush_feather, scale, additive):
+        path = Path.Path(brush_size, brush_feather, scale, additive)
+        self.paths.append(path)
+        return path
+
+    def get_mask_map(self):
+        map = numpy.zeros((self.height, self.width, 1), dtype=numpy.uint8)
+
+        print(map.shape)
+
+        for path in self.paths:
+            map = path.get_mask_map(map)
+
+        print(map.shape)
+
+        map32 = map.astype(numpy.float32)
+        map32 = map32 / 255.0
+
+        print(map32.shape)
+
+        return map32
+
+    def get_vector_mask_dict(self):
+        paths = []
+        for path in self.paths:
+            paths.append(path.get_path_dict())
+
+        return {
+            "width": self.width,
+            "height": self.height,
+            "paths": paths
+        }
+
+    def set_from_vector_mask_dict(self, dict):
+        self.width = dict["width"]
+        self.height = dict["height"]
+        paths = []
+        for path in dict["paths"]:
+            paths.append(Path.Path.new_from_dict(path))
+
+        self.paths = paths

+ 376 - 64
PF2/__init__.py

@@ -9,13 +9,14 @@ import numpy
 import getpass
 import getpass
 import os
 import os
 
 
-from PF2 import Histogram
+from PF2 import Histogram, Layer
 from PF2.Tools import BlackWhite
 from PF2.Tools import BlackWhite
 from PF2.Tools import Colours
 from PF2.Tools import Colours
 from PF2.Tools import Contrast
 from PF2.Tools import Contrast
 from PF2.Tools import Details
 from PF2.Tools import Details
 from PF2.Tools import Tonemap
 from PF2.Tools import Tonemap
 from PF2.Tools import HueEqualiser
 from PF2.Tools import HueEqualiser
+from PF2.Tools import Blur
 
 
 
 
 class PF2(Activity.Activity):
 class PF2(Activity.Activity):
@@ -37,6 +38,7 @@ class PF2(Activity.Activity):
         # Get all UI Components
         # Get all UI Components
         self.ui = {}
         self.ui = {}
         components = [
         components = [
+            "main",
             "control_reveal",
             "control_reveal",
             "histogram_reveal",
             "histogram_reveal",
             "layers_reveal",
             "layers_reveal",
@@ -45,10 +47,11 @@ class PF2(Activity.Activity):
             "histogram",
             "histogram",
             "layer_stack",
             "layer_stack",
             "preview",
             "preview",
+            "preview_eventbox",
             "scroll_window",
             "scroll_window",
             "open_button",
             "open_button",
             "original_toggle",
             "original_toggle",
-            "tools",
+            "tool_box_stack",
             "tool_stack",
             "tool_stack",
             "open_window",
             "open_window",
             "open_header",
             "open_header",
@@ -63,7 +66,22 @@ class PF2(Activity.Activity):
             "zoom_toggle",
             "zoom_toggle",
             "zoom_reveal",
             "zoom_reveal",
             "zoom",
             "zoom",
-            "reset"
+            "reset",
+            "mask_draw_toggle",
+            "mask_erase_toggle",
+            "mask_brush_size",
+            "layers_list",
+            "new_layer",
+            "layer_mask_reveal",
+            "add_layer_button",
+            "remove_layer_button",
+            "mask_brush_feather",
+            "mask_brush_feather_scale",
+            "edit_layer_mask_button",
+            "layer_opacity",
+            "layer_opacity_scale",
+            "layer_blend_mode",
+            "viewport"
         ]
         ]
 
 
         for component in components:
         for component in components:
@@ -73,26 +91,23 @@ class PF2(Activity.Activity):
 
 
         # Set up tools
         # Set up tools
         self.tools = [
         self.tools = [
-            Contrast.Contrast(),
-            Tonemap.Tonemap(),
-            Details.Details(),
-            Colours.Colours(),
-            HueEqualiser.HueEqualiser(),
-            BlackWhite.BlackWhite(),
+            Contrast.Contrast,
+            Tonemap.Tonemap,
+            Details.Details,
+            Colours.Colours,
+            HueEqualiser.HueEqualiser,
+            BlackWhite.BlackWhite,
+            Blur.Blur
         ]
         ]
 
 
-        self.tool_map = {}
 
 
-        for tool in self.tools:
-            self.ui["tools"].add(tool.tool_button)
-            self.ui["tool_stack"].add(tool.widget)
-            self.tool_map[tool.tool_button] = tool
-            tool.tool_button.connect("clicked", self.on_tool_button_clicked)
-            tool.connect_on_change(self.on_tool_change)
+        # Setup layers
+        self.layers = []
+        self.base_layer = self.create_layer("base", True)
 
 
         # Set the first tool to active
         # Set the first tool to active
-        self.tools[0].tool_button.set_active(True)
-        self.ui["tools"].show_all()
+        # self.tools[0].tool_button.set_active(True)
+        # self.ui["tools"].show_all()
 
 
         # Disable ui components by default
         # Disable ui components by default
         self.ui["original_toggle"].set_sensitive(False)
         self.ui["original_toggle"].set_sensitive(False)
@@ -104,6 +119,7 @@ class PF2(Activity.Activity):
         self.image_path = ""
         self.image_path = ""
         self.bit_depth = 8
         self.bit_depth = 8
         self.image = None
         self.image = None
+        self.image_is_dirty = True
         self.original_image = None
         self.original_image = None
         self.pwidth = 0
         self.pwidth = 0
         self.pheight = 0
         self.pheight = 0
@@ -121,6 +137,12 @@ class PF2(Activity.Activity):
         self.undo_stack = []
         self.undo_stack = []
         self.undo_position = 0
         self.undo_position = 0
         self.undoing = False
         self.undoing = False
+        self.current_layer_path = None
+        self.mousex = 0
+        self.mousey = 0
+        self.mousedown = False
+        self.layer_order = []
+        self.pre_undo_layer_name = "base"
 
 
         # Setup Open Dialog
         # Setup Open Dialog
         self.ui["open_window"].set_transient_for(self.root)
         self.ui["open_window"].set_transient_for(self.root)
@@ -141,18 +163,22 @@ class PF2(Activity.Activity):
         self.ui["undo"].connect("clicked", self.on_undo)
         self.ui["undo"].connect("clicked", self.on_undo)
         self.ui["redo"].connect("clicked", self.on_redo)
         self.ui["redo"].connect("clicked", self.on_redo)
         self.ui["reset"].connect("clicked", self.on_reset)
         self.ui["reset"].connect("clicked", self.on_reset)
+        self.ui["preview_eventbox"].connect('motion-notify-event', self.preview_dragged)
+        self.ui["preview_eventbox"].connect('button-press-event', self.new_path)
+        self.ui["mask_draw_toggle"].connect("toggled", self.mask_draw_toggled)
+        self.ui["mask_erase_toggle"].connect("toggled", self.mask_erase_toggled)
+        self.ui["new_layer"].connect("clicked", self.new_layer_button_clicked)
+        self.ui["layers_list"].connect("row-activated", self.layer_ui_activated)
+        self.ui["add_layer_button"].connect("clicked", self.new_layer_button_clicked)
+        self.ui["remove_layer_button"].connect("clicked", self.remove_layer_button_clicked)
+        self.ui["edit_layer_mask_button"].connect("toggled", self.edit_mask_toggled)
+        self.ui["layer_opacity"].connect("value-changed", self.update_layer_opacity)
+        self.ui["scroll_window"].connect_after("draw", self.draw_ui_brush_circle)
+        self.ui["scroll_window"].connect_after('motion-notify-event', self.mouse_coords_changed)
+        self.ui["mask_brush_size"].connect("value-changed", self.brush_size_changed)
+        self.ui["layer_blend_mode"].connect("changed", self.layer_blend_mode_changed)
 
 
 
 
-    def on_tool_button_clicked(self, sender):
-        if(sender.get_active()):
-            self.ui["tool_stack"].set_visible_child(self.tool_map[sender].widget)
-            for key in self.tool_map:
-                if(key != sender):
-                    key.set_active(False)
-
-        elif(self.ui["tool_stack"].get_visible_child() == self.tool_map[sender].widget):
-            sender.set_active(True)
-
 
 
     def on_open_clicked(self, sender):
     def on_open_clicked(self, sender):
         self.ui["open_window"].show_all()
         self.ui["open_window"].show_all()
@@ -169,6 +195,8 @@ class PF2(Activity.Activity):
         self.ui["original_toggle"].set_sensitive(True)
         self.ui["original_toggle"].set_sensitive(True)
         self.ui["export_image"].set_sensitive(True)
         self.ui["export_image"].set_sensitive(True)
         self.ui["reset"].set_sensitive(True)
         self.ui["reset"].set_sensitive(True)
+        self.ui["new_layer"].set_sensitive(True)
+        self.ui["layers_reveal"].set_reveal_child(False)
         self.is_editing = True
         self.is_editing = True
         if(path == None):
         if(path == None):
             self.image_path = self.ui["open_chooser"].get_filename()
             self.image_path = self.ui["open_chooser"].get_filename()
@@ -181,6 +209,15 @@ class PF2(Activity.Activity):
         self.show_message("Loading Photo…", "Please wait while PhotoFiddle loads your photo", True)
         self.show_message("Loading Photo…", "Please wait while PhotoFiddle loads your photo", True)
         self.root.get_titlebar().set_subtitle("%s…" % (self.image_path))
         self.root.get_titlebar().set_subtitle("%s…" % (self.image_path))
 
 
+        # Delete all layers, except the base
+        for layer in self.layers:
+            if(layer.name != "base"):
+                self.ui["layers_list"].remove(layer.selector_row)
+
+        self.layers = [self.base_layer,]
+
+        self.select_layer(self.base_layer)
+
         thread = threading.Thread(target=self.open_image,
         thread = threading.Thread(target=self.open_image,
                                   args=(w, h))
                                   args=(w, h))
         thread.start()
         thread.start()
@@ -265,7 +302,7 @@ class PF2(Activity.Activity):
             self.ui["original_toggle"].set_active(False)
             self.ui["original_toggle"].set_active(False)
 
 
 
 
-    def on_tool_change(self, tool, property):
+    def on_layer_change(self, layer):
         if(self.has_loaded):
         if(self.has_loaded):
             if(not self.change_occurred):
             if(not self.change_occurred):
                 self.change_occurred = True
                 self.change_occurred = True
@@ -319,18 +356,20 @@ class PF2(Activity.Activity):
         self.ui["redo"].set_sensitive(len(self.undo_stack)-1 > self.undo_position)
         self.ui["redo"].set_sensitive(len(self.undo_stack)-1 > self.undo_position)
 
 
     def on_undo(self, sender):
     def on_undo(self, sender):
+        self.pre_undo_layer_name = self.get_selected_layer().name
         self.undo_position -= 1
         self.undo_position -= 1
         self.update_undo_state()
         self.update_undo_state()
         self.update_from_undo_stack(self.undo_stack[self.undo_position])
         self.update_from_undo_stack(self.undo_stack[self.undo_position])
 
 
     def on_redo(self, sender):
     def on_redo(self, sender):
+        self.pre_undo_layer_name = self.get_selected_layer().name
         self.undo_position += 1
         self.undo_position += 1
         self.update_undo_state()
         self.update_undo_state()
         self.update_from_undo_stack(self.undo_stack[self.undo_position])
         self.update_from_undo_stack(self.undo_stack[self.undo_position])
 
 
     def on_reset(self, sender):
     def on_reset(self, sender):
-        for tool in self.tools:
-            tool.reset()
+        for layer in self.layers:
+            layer.reset_tools()
 
 
 
 
 
 
@@ -388,6 +427,8 @@ class PF2(Activity.Activity):
         # Resize OPENCV Copy
         # Resize OPENCV Copy
         self.original_image = cv2.resize(self.original_image, (int(nw), int(nh)), interpolation = cv2.INTER_AREA)
         self.original_image = cv2.resize(self.original_image, (int(nw), int(nh)), interpolation = cv2.INTER_AREA)
 
 
+        self.image_is_dirty = True
+
         self.image = numpy.copy(self.original_image)
         self.image = numpy.copy(self.original_image)
 
 
         # Update image
         # Update image
@@ -402,8 +443,9 @@ class PF2(Activity.Activity):
             time.sleep(0.5)
             time.sleep(0.5)
         self.additional_change_occurred = False
         self.additional_change_occurred = False
         GLib.idle_add(self.start_work)
         GLib.idle_add(self.start_work)
-        self.image = numpy.copy(self.original_image)
-        self.image = self.run_stack(self.image)
+        image = numpy.copy(self.original_image)
+        self.image = self.run_stack(image)
+        self.image_is_dirty = False
         if(self.additional_change_occurred):
         if(self.additional_change_occurred):
             self.update_image()
             self.update_image()
         else:
         else:
@@ -420,14 +462,13 @@ class PF2(Activity.Activity):
     def run_stack(self, image, callback=None):
     def run_stack(self, image, callback=None):
         if(not self.running_stack):
         if(not self.running_stack):
             self.running_stack = True
             self.running_stack = True
-            ntools = len(self.tools)
-            count = 1
-            for tool in self.tools:
-                if(callback != None):
-                    # For showing progress
-                    callback(tool.name, ntools, count-1)
-                image = tool.on_update(image)
-                count += 1
+
+            baseImage = image.copy()
+
+            for layer in self.layers:
+                print(layer)
+                image = layer.render_layer(baseImage, image, callback)
+
             self.running_stack = False
             self.running_stack = False
             return image
             return image
 
 
@@ -478,26 +519,26 @@ class PF2(Activity.Activity):
         print(path)
         print(path)
         f = open(path, "w")
         f = open(path, "w")
 
 
-        toolDict = {}
-        for tool in self.tools:
-            toolDict[tool.id] = tool.get_properties_as_dict()
+        layerDict = {}
+        layerOrder = []
+        for layer in self.layers:
+            layerDict[layer.name] = layer.get_layer_dict()
+            layerOrder.append(layer.name)
 
 
         if(not self.undoing) and (self.has_loaded):
         if(not self.undoing) and (self.has_loaded):
             if(len(self.undo_stack)-1 != self.undo_position):
             if(len(self.undo_stack)-1 != self.undo_position):
                 self.undo_stack = self.undo_stack[:self.undo_position+1]
                 self.undo_stack = self.undo_stack[:self.undo_position+1]
-            if(self.undo_stack[self.undo_position] != toolDict):
-                self.undo_stack += [toolDict,]
+            if(self.undo_stack[self.undo_position] != {"layers": layerDict, "layer-order": layerOrder}):
+                self.undo_stack += [{"layers": layerDict, "layer-order": layerOrder},]
                 self.undo_position = len(self.undo_stack)-1
                 self.undo_position = len(self.undo_stack)-1
                 GLib.idle_add(self.update_undo_state)
                 GLib.idle_add(self.update_undo_state)
 
 
+
         data = {
         data = {
             "path":self.image_path,
             "path":self.image_path,
             "format-revision":1,
             "format-revision":1,
-            "layers": {
-                "base":{
-                    "tools": toolDict
-                }
-            }
+            "layers": layerDict,
+            "layer-order": layerOrder
         }
         }
 
 
         f.write(str(data))
         f.write(str(data))
@@ -512,11 +553,13 @@ class PF2(Activity.Activity):
             try:
             try:
                 data = ast.literal_eval(sdata)
                 data = ast.literal_eval(sdata)
                 if(data["format-revision"] == 1):
                 if(data["format-revision"] == 1):
-                    for tool in self.tools:
-                        if(tool.id in data["layers"]["base"]["tools"]):
-                            GLib.idle_add(tool.load_properties,data["layers"]["base"]["tools"][tool.id])
+                    if("layer-order" not in data):
+                        # Backwards compatability
+                        data["layer-order"] = ["base",]
+                    for layer_name in data["layer-order"]:
+                        GLib.idle_add(self.create_layer_with_data, layer_name, data["layers"][layer_name])
 
 
-                    self.undo_stack = [data["layers"]["base"]["tools"],]
+                    self.undo_stack = [data,]
                     self.undo_position = 0
                     self.undo_position = 0
                     loadDefaults = False
                     loadDefaults = False
             except:
             except:
@@ -524,14 +567,18 @@ class PF2(Activity.Activity):
                                                 "The edit file for this photo is corrupted and could not be loaded.")
                                                 "The edit file for this photo is corrupted and could not be loaded.")
 
 
         if(loadDefaults):
         if(loadDefaults):
-            for tool in self.tools:
-                GLib.idle_add(tool.reset)
+            for layer in self.layers:
+                for tool in layer.tools:
+                    GLib.idle_add(tool.reset)
+
+            layerDict = {}
+            layerOrder = []
+            for layer in self.layers:
+                layerDict[layer.name] = layer.get_layer_dict()
+                layerOrder.append(layer.name)
 
 
-            toolDict = {}
-            for tool in self.tools:
-                toolDict[tool.id] = tool.get_properties_as_dict()
 
 
-            self.undo_stack = [toolDict, ]
+            self.undo_stack = [{"layers": layerDict, "layer-order": layerOrder}, ]
             self.undo_position = 0
             self.undo_position = 0
 
 
         GLib.idle_add(self.update_undo_state)
         GLib.idle_add(self.update_undo_state)
@@ -539,11 +586,26 @@ class PF2(Activity.Activity):
         self.undoing = False
         self.undoing = False
 
 
 
 
+    def create_layer_with_data(self, layer, data):
+        if (layer == "base"):
+            self.base_layer.set_from_layer_dict(data)
+        else:
+            GLib.idle_add(self.show_layers)
+            ilayer = self.create_layer(layer, False)
+            ilayer.set_from_layer_dict(data)
+
+
     def update_from_undo_stack(self, data):
     def update_from_undo_stack(self, data):
         self.undoing = True
         self.undoing = True
-        for tool in self.tools:
-            if (tool.id in data):
-                tool.load_properties(data[tool.id])
+        self.delete_all_editable_layers()
+        for layer_name in data["layer-order"]:
+            if (layer_name == "base"):
+                self.base_layer.set_from_layer_dict(data["layers"][layer_name])
+            else:
+                ilayer = self.create_layer(layer_name, False, layer_name == self.pre_undo_layer_name)
+                ilayer.set_from_layer_dict(data["layers"][layer_name])
+                self.show_layers()
+
 
 
     def get_export_image(self, w, h):
     def get_export_image(self, w, h):
         GLib.idle_add(self.on_export_started)
         GLib.idle_add(self.on_export_started)
@@ -561,7 +623,257 @@ class PF2(Activity.Activity):
 
 
 
 
 
 
+    ## Layers Stuff ##
+
+    def preview_dragged(self, widget, event):
+
+        x, y = widget.translate_coordinates(self.ui["preview"], event.x, event.y)
+
+        draw = self.ui["mask_draw_toggle"].get_active()
+        erase = self.ui["mask_erase_toggle"].get_active()
+        layer = self.get_selected_layer()
+        if((draw or erase) and layer.editable and self.current_layer_path):
+
+            if (x < 0.0):
+                x = 0
+            if (y < 0.0):
+                y = 0
+
+            pwidth = self.pimage.get_width()
+            pheight = self.pimage.get_height()
 
 
+            if (x > pwidth):
+                x = pwidth
+            if (y > pheight):
+                y = pheight
 
 
+            print(x, y)
 
 
+            if(not self.image_is_dirty):
+                fill = (0, 0, 255)
+                if(erase):
+                    fill = (255, 0, 0)
 
 
+                preview = self.current_layer_path.add_point(int(x), int(y), (pheight, pwidth, 3), fill)
+
+                # Bits per pixel
+                bpp = float(str(self.image.dtype).replace("uint", "").replace("float", ""))
+                # Pixel value range
+                np = float(2 ** bpp - 1)
+
+                self.image[preview == 255] = np
+                cv2.imwrite("/tmp/phf2-preview-%s-drag.png" % getpass.getuser(), self.image)
+                temppbuf = GdkPixbuf.Pixbuf.new_from_file("/tmp/phf2-preview-%s-drag.png" % getpass.getuser())
+                self.ui["preview"].set_from_pixbuf(temppbuf)
+
+            self.on_layer_change(layer)
+
+            self.mouse_down_coords_changed(widget, event)
+            return True
+
+    def new_path(self, widget, event):
+        draw = self.ui["mask_draw_toggle"].get_active()
+        erase = self.ui["mask_erase_toggle"].get_active()
+        layer = self.get_selected_layer()
+        if((draw or erase) and layer.editable):
+            print(self.pimage.get_width(), self.pimage.get_height())
+            width = self.pimage.get_width()
+            size = self.ui["mask_brush_size"].get_value()
+            feather = self.ui["mask_brush_feather"].get_value()
+            self.current_layer_path = layer.mask.get_new_path(size, feather, float(self.awidth)/float(width), draw)
+
+
+    def mask_draw_toggled(self, widget):
+        self.ui["mask_erase_toggle"].set_active(not widget.get_active())
+
+    def mask_erase_toggled(self, widget):
+        self.ui["mask_draw_toggle"].set_active(not widget.get_active())
+        self.ui["mask_brush_feather_scale"].set_sensitive(not widget.get_active())
+
+
+    def edit_mask_toggled(self, widget):
+        self.ui["layer_mask_reveal"].set_reveal_child(widget.get_active())
+        self.ui["mask_draw_toggle"].set_active(widget.get_active())
+        self.ui["mask_erase_toggle"].set_active(False)
+        self.ui["scroll_window"].queue_draw()
+
+    def update_layer_opacity(self, sender):
+        layer = self.get_selected_layer()
+        layer.set_opacity(sender.get_value())
+
+
+    def create_layer(self, layer_name, is_base, select = False):
+        layer = Layer.Layer(is_base, layer_name, self.on_layer_change)
+        for tool in self.tools:
+            tool_instance = tool()
+            layer.add_tool(tool_instance)
+
+        self.layers += [layer,]
+
+        GLib.idle_add(self.create_layer_ui, layer, select)
+
+        return layer
+
+    def create_layer_ui(self, layer, select):
+        layer_box = Gtk.HBox()
+        layer_box.set_hexpand(False)
+        layer_box.set_halign(Gtk.Align.START)
+
+        layer_toggle = Gtk.CheckButton()
+        layer_toggle.set_sensitive(layer.editable)
+        layer_toggle.set_active(layer.enabled)
+        layer_toggle.set_hexpand(False)
+        layer_toggle.set_halign(Gtk.Align.START)
+        layer_toggle.set_margin_right(8)
+        layer_toggle.set_margin_left(8)
+        layer_toggle.set_margin_top(4)
+        layer_toggle.set_margin_bottom(4)
+        layer_toggle.connect("toggled", self.toggle_layer, layer)
+
+        layer_label = Gtk.Label()
+        layer_label.set_label(layer.name)
+        if(layer.name == "base"):
+            layer_label.set_label("Base Layer")
+        layer_label.set_hexpand(True)
+        layer_label.set_halign(Gtk.Align.FILL)
+        layer_label.set_margin_top(4)
+        layer_label.set_margin_bottom(4)
+
+        layer_box.add(layer_toggle)
+        layer_box.add(layer_label)
+
+        layer.show_all()
+        layer_box.show_all()
+
+        self.ui["layers_list"].add(layer_box)
+
+        layer.selector_row = layer_box.get_parent()
+
+        self.ui["tool_box_stack"].add(layer.tool_box)
+        self.ui["tool_stack"].add(layer.tool_stack)
+
+        if(select):
+            self.select_layer(layer)
+
+
+    def layer_ui_activated(self, widget, row):
+        layer_index = row.get_index()
+        self.ui["tool_stack"].set_visible_child(self.layers[layer_index].tool_stack)
+        self.ui["tool_box_stack"].set_visible_child(self.layers[layer_index].tool_box)
+        self.ui["remove_layer_button"].set_sensitive(self.layers[layer_index].editable)
+        self.ui["edit_layer_mask_button"].set_sensitive(self.layers[layer_index].editable)
+        self.ui["layer_opacity_scale"].set_sensitive(self.layers[layer_index].editable)
+        self.ui["layer_blend_mode"].set_sensitive(self.layers[layer_index].editable)
+        self.ui["layer_blend_mode"].set_active(["additive", "overlay"].index(self.layers[layer_index].blend_mode))
+        self.ui["layer_opacity"].set_value(self.layers[layer_index].opacity)
+        if(self.ui["edit_layer_mask_button"].get_active()):
+            self.ui["edit_layer_mask_button"].set_active(self.layers[layer_index].editable)
+
+    def toggle_layer(self, sender, layer):
+        layer.set_enabled(sender.get_active())
+
+
+    def new_layer_button_clicked(self, widget):
+        self.show_layers()
+        # Allocate an un-used layer name
+        layer_number = len(self.layers)
+        layer_name = "Layer %i" % layer_number
+
+        while(self.layer_exists(layer_name)):
+            layer_number += 1
+            layer_name = "Layer %i" % layer_number
+
+        # Create the layer
+        layer = self.create_layer(layer_name, False)
+        layer.set_size(self.awidth, self.aheight)
+
+        # Save changes
+        threading.Thread(target=self.save_image_data).start()
+        self.on_layer_change(layer)
+
+
+    def remove_layer_button_clicked(self, widget):
+        layer_row = self.ui["layers_list"].get_selected_row()
+
+        selected_index = 1
+
+        new_layers = []
+        for layer in self.layers:
+            if(layer.selector_row != layer_row):
+                new_layers += [layer]
+            else:
+                selected_index = self.layers.index(layer)
+
+        self.ui["layers_list"].remove(layer_row)
+        self.layers = new_layers
+
+        # Select next layer
+        self.select_layer(self.layers[selected_index -1])
+
+        if (len(self.layers) == 1):
+            self.ui["layers_reveal"].set_reveal_child(False)
+
+        if(widget != None):
+            # Only do this if the layer was actualy deleted by the user
+            # and not by the undo-redo system for example
+
+            # Save changes
+            threading.Thread(target=self.save_image_data).start()
+
+            self.on_layer_change(self.get_selected_layer())
+
+
+    def get_selected_layer(self):
+        layer_row = self.ui["layers_list"].get_selected_row()
+
+        for layer in self.layers:
+            if (layer.selector_row == layer_row):
+                return layer
+
+    def layer_exists(self, layer_name):
+        for layer in self.layers:
+            if(layer.name == layer_name):
+                return True
+        return False
+
+    def show_layers(self):
+        self.ui["layers_reveal"].set_reveal_child(True)
+
+    def select_layer(self, layer):
+        self.ui["layers_list"].select_row(layer.selector_row)
+        self.layer_ui_activated(self.ui["layers_list"], layer.selector_row)
+
+    def delete_all_editable_layers(self):
+        count = len(self.layers) -1
+        while(len(self.layers) != 1):
+            self.select_layer(self.layers[1])
+            self.remove_layer_button_clicked(None)
+
+
+    def draw_ui_brush_circle(self, widget, context):
+        drawing = self.ui["edit_layer_mask_button"].get_active()
+        if(drawing):
+            size = self.ui["mask_brush_size"].get_value()
+            if(self.mousedown):
+                context.set_source_rgb(255, 0, 0)
+            else:
+                context.set_source_rgb(255, 255, 255)
+            context.arc(self.mousex, self.mousey, size/2.0, 0.0, 2 * numpy.pi);
+            context.stroke()
+
+    def mouse_coords_changed(self, widget, event):
+        self.mousedown = False
+        self.mousex, self.mousey = event.x, event.y
+        widget.queue_draw()
+
+    def mouse_down_coords_changed(self, widget, event):
+        self.mousedown = True
+        self.mousex, self.mousey = widget.translate_coordinates(self.ui["scroll_window"], event.x, event.y)
+        widget.queue_draw()
+
+    def brush_size_changed(self, sender):
+        self.ui["scroll_window"].queue_draw()
+
+    def layer_blend_mode_changed(self, sender):
+        layer = self.get_selected_layer()
+        layer.set_blending_mode(sender.get_active_text().lower())

+ 0 - 4
Tool/__init__.py

@@ -34,7 +34,6 @@ class Tool:
                     # Add a Separator
                     # Add a Separator
                     separator = Gtk.Separator()
                     separator = Gtk.Separator()
                     separator.set_margin_top(6)
                     separator.set_margin_top(6)
-                    separator.show()
                     self.widget.attach(separator, 0, vpos, 3, 1)
                     self.widget.attach(separator, 0, vpos, 3, 1)
                     vpos += 1
                     vpos += 1
 
 
@@ -159,11 +158,8 @@ class Tool:
 
 
         separator = Gtk.Separator()
         separator = Gtk.Separator()
         separator.set_margin_top(6)
         separator.set_margin_top(6)
-        separator.show()
         self.widget.attach(separator, 0, vpos, 3, 1)
         self.widget.attach(separator, 0, vpos, 3, 1)
 
 
-        self.widget.show_all()
-
         self.tool_button = Gtk.ToggleButton()
         self.tool_button = Gtk.ToggleButton()
         self.tool_button.set_tooltip_text(self.name)
         self.tool_button.set_tooltip_text(self.name)
         icon = Gtk.Image()
         icon = Gtk.Image()

+ 385 - 14
ui/PF2_Activity.glade

@@ -94,6 +94,25 @@
       </object>
       </object>
     </child>
     </child>
   </object>
   </object>
+  <object class="GtkAdjustment" id="PF2_layer_opacity">
+    <property name="upper">1</property>
+    <property name="value">1</property>
+    <property name="step_increment">0.10000000000000001</property>
+    <property name="page_increment">0.5</property>
+  </object>
+  <object class="GtkAdjustment" id="PF2_mask_brush_feather">
+    <property name="upper">200</property>
+    <property name="value">10</property>
+    <property name="step_increment">1</property>
+    <property name="page_increment">10</property>
+  </object>
+  <object class="GtkAdjustment" id="PF2_mask_brush_size">
+    <property name="lower">1</property>
+    <property name="upper">200</property>
+    <property name="value">20</property>
+    <property name="step_increment">1</property>
+    <property name="page_increment">10</property>
+  </object>
   <object class="GtkBox" id="PF2_main">
   <object class="GtkBox" id="PF2_main">
     <property name="visible">True</property>
     <property name="visible">True</property>
     <property name="can_focus">False</property>
     <property name="can_focus">False</property>
@@ -112,15 +131,23 @@
         <property name="vexpand">True</property>
         <property name="vexpand">True</property>
         <property name="shadow_type">in</property>
         <property name="shadow_type">in</property>
         <child>
         <child>
-          <object class="GtkViewport">
+          <object class="GtkViewport" id="PF2_viewport">
             <property name="visible">True</property>
             <property name="visible">True</property>
             <property name="can_focus">False</property>
             <property name="can_focus">False</property>
             <child>
             <child>
-              <object class="GtkImage" id="PF2_preview">
+              <object class="GtkEventBox" id="PF2_preview_eventbox">
                 <property name="visible">True</property>
                 <property name="visible">True</property>
                 <property name="can_focus">False</property>
                 <property name="can_focus">False</property>
-                <property name="hexpand">True</property>
-                <property name="pixbuf">logo.png</property>
+                <property name="above_child">True</property>
+                <child>
+                  <object class="GtkImage" id="PF2_preview">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="halign">center</property>
+                    <property name="valign">center</property>
+                    <property name="pixbuf">logo.png</property>
+                  </object>
+                </child>
               </object>
               </object>
             </child>
             </child>
           </object>
           </object>
@@ -319,9 +346,48 @@
                                 <property name="position">0</property>
                                 <property name="position">0</property>
                               </packing>
                               </packing>
                             </child>
                             </child>
+                            <child>
+                              <object class="GtkBox">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="spacing">8</property>
+                                <child>
+                                  <object class="GtkLabel">
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">False</property>
+                                    <property name="label" translatable="yes">Blending Mode</property>
+                                  </object>
+                                  <packing>
+                                    <property name="expand">False</property>
+                                    <property name="fill">True</property>
+                                    <property name="position">0</property>
+                                  </packing>
+                                </child>
+                                <child>
+                                  <object class="GtkComboBoxText" id="PF2_layer_blend_mode">
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">False</property>
+                                    <items>
+                                      <item translatable="yes">Additive</item>
+                                      <item translatable="yes">Overlay</item>
+                                    </items>
+                                  </object>
+                                  <packing>
+                                    <property name="expand">True</property>
+                                    <property name="fill">True</property>
+                                    <property name="position">1</property>
+                                  </packing>
+                                </child>
+                              </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">True</property>
+                                <property name="position">1</property>
+                              </packing>
+                            </child>
                             <child>
                             <child>
                               <object class="GtkScrolledWindow">
                               <object class="GtkScrolledWindow">
-                                <property name="height_request">150</property>
+                                <property name="height_request">100</property>
                                 <property name="visible">True</property>
                                 <property name="visible">True</property>
                                 <property name="can_focus">True</property>
                                 <property name="can_focus">True</property>
                                 <property name="hscrollbar_policy">never</property>
                                 <property name="hscrollbar_policy">never</property>
@@ -331,7 +397,7 @@
                                     <property name="visible">True</property>
                                     <property name="visible">True</property>
                                     <property name="can_focus">False</property>
                                     <property name="can_focus">False</property>
                                     <child>
                                     <child>
-                                      <object class="GtkListBox">
+                                      <object class="GtkListBox" id="PF2_layers_list">
                                         <property name="visible">True</property>
                                         <property name="visible">True</property>
                                         <property name="can_focus">False</property>
                                         <property name="can_focus">False</property>
                                       </object>
                                       </object>
@@ -339,6 +405,283 @@
                                   </object>
                                   </object>
                                 </child>
                                 </child>
                               </object>
                               </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">True</property>
+                                <property name="position">2</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkBox">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="spacing">8</property>
+                                <child>
+                                  <object class="GtkLabel">
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">False</property>
+                                    <property name="label" translatable="yes">Opacity</property>
+                                  </object>
+                                  <packing>
+                                    <property name="expand">False</property>
+                                    <property name="fill">True</property>
+                                    <property name="position">0</property>
+                                  </packing>
+                                </child>
+                                <child>
+                                  <object class="GtkScale" id="PF2_layer_opacity_scale">
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">True</property>
+                                    <property name="adjustment">PF2_layer_opacity</property>
+                                    <property name="round_digits">1</property>
+                                    <property name="digits">2</property>
+                                    <property name="value_pos">right</property>
+                                  </object>
+                                  <packing>
+                                    <property name="expand">True</property>
+                                    <property name="fill">True</property>
+                                    <property name="position">1</property>
+                                  </packing>
+                                </child>
+                                <child>
+                                  <object class="GtkButtonBox">
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">False</property>
+                                    <property name="margin_left">10</property>
+                                    <property name="spacing">6</property>
+                                    <property name="layout_style">end</property>
+                                    <child>
+                                      <object class="GtkToggleButton" id="PF2_edit_layer_mask_button">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="receives_default">True</property>
+                                        <child>
+                                          <object class="GtkImage">
+                                            <property name="visible">True</property>
+                                            <property name="can_focus">False</property>
+                                            <property name="icon_name">document-edit-symbolic</property>
+                                          </object>
+                                        </child>
+                                      </object>
+                                      <packing>
+                                        <property name="expand">True</property>
+                                        <property name="fill">True</property>
+                                        <property name="position">0</property>
+                                        <property name="non_homogeneous">True</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkButton" id="PF2_add_layer_button">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="receives_default">True</property>
+                                        <child>
+                                          <object class="GtkImage">
+                                            <property name="visible">True</property>
+                                            <property name="can_focus">False</property>
+                                            <property name="icon_name">list-add-symbolic</property>
+                                          </object>
+                                        </child>
+                                      </object>
+                                      <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">False</property>
+                                        <property name="position">1</property>
+                                        <property name="non_homogeneous">True</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkButton" id="PF2_remove_layer_button">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="receives_default">True</property>
+                                        <child>
+                                          <object class="GtkImage">
+                                            <property name="visible">True</property>
+                                            <property name="can_focus">False</property>
+                                            <property name="icon_name">list-remove-symbolic</property>
+                                          </object>
+                                        </child>
+                                      </object>
+                                      <packing>
+                                        <property name="expand">False</property>
+                                        <property name="fill">False</property>
+                                        <property name="position">2</property>
+                                        <property name="non_homogeneous">True</property>
+                                      </packing>
+                                    </child>
+                                  </object>
+                                  <packing>
+                                    <property name="expand">False</property>
+                                    <property name="fill">False</property>
+                                    <property name="position">2</property>
+                                  </packing>
+                                </child>
+                              </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">True</property>
+                                <property name="position">3</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkSeparator">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                              </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">True</property>
+                                <property name="position">4</property>
+                              </packing>
+                            </child>
+                          </object>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkRevealer" id="PF2_layer_mask_reveal">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="transition_type">slide-up</property>
+                        <child>
+                          <object class="GtkBox">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="orientation">vertical</property>
+                            <property name="spacing">6</property>
+                            <child>
+                              <object class="GtkLabel">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="halign">start</property>
+                                <property name="label" translatable="yes">&lt;b&gt;Layer Mask&lt;/b&gt;</property>
+                                <property name="use_markup">True</property>
+                              </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">True</property>
+                                <property name="position">0</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkBox">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="orientation">vertical</property>
+                                <child>
+                                  <object class="GtkButtonBox">
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">False</property>
+                                    <property name="margin_left">8</property>
+                                    <property name="margin_right">8</property>
+                                    <property name="margin_top">8</property>
+                                    <property name="margin_bottom">8</property>
+                                    <property name="layout_style">expand</property>
+                                    <child>
+                                      <object class="GtkToggleButton" id="PF2_mask_draw_toggle">
+                                        <property name="label" translatable="yes">Draw</property>
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="receives_default">True</property>
+                                      </object>
+                                      <packing>
+                                        <property name="expand">True</property>
+                                        <property name="fill">True</property>
+                                        <property name="position">1</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkToggleButton" id="PF2_mask_erase_toggle">
+                                        <property name="label" translatable="yes">Erase</property>
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="receives_default">True</property>
+                                      </object>
+                                      <packing>
+                                        <property name="expand">True</property>
+                                        <property name="fill">True</property>
+                                        <property name="position">1</property>
+                                      </packing>
+                                    </child>
+                                  </object>
+                                  <packing>
+                                    <property name="expand">False</property>
+                                    <property name="fill">True</property>
+                                    <property name="position">0</property>
+                                  </packing>
+                                </child>
+                                <child>
+                                  <object class="GtkGrid">
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">False</property>
+                                    <property name="margin_bottom">8</property>
+                                    <property name="column_spacing">8</property>
+                                    <child>
+                                      <object class="GtkScale">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="hexpand">True</property>
+                                        <property name="adjustment">PF2_mask_brush_size</property>
+                                        <property name="round_digits">1</property>
+                                        <property name="value_pos">right</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">1</property>
+                                        <property name="top_attach">0</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="halign">end</property>
+                                        <property name="label" translatable="yes">Brush Size</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">0</property>
+                                        <property name="top_attach">0</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkLabel">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">False</property>
+                                        <property name="halign">end</property>
+                                        <property name="label" translatable="yes">Brush Feather</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">0</property>
+                                        <property name="top_attach">1</property>
+                                      </packing>
+                                    </child>
+                                    <child>
+                                      <object class="GtkScale" id="PF2_mask_brush_feather_scale">
+                                        <property name="visible">True</property>
+                                        <property name="can_focus">True</property>
+                                        <property name="hexpand">True</property>
+                                        <property name="adjustment">PF2_mask_brush_feather</property>
+                                        <property name="round_digits">1</property>
+                                        <property name="value_pos">right</property>
+                                      </object>
+                                      <packing>
+                                        <property name="left_attach">1</property>
+                                        <property name="top_attach">1</property>
+                                      </packing>
+                                    </child>
+                                  </object>
+                                  <packing>
+                                    <property name="expand">False</property>
+                                    <property name="fill">True</property>
+                                    <property name="position">1</property>
+                                  </packing>
+                                </child>
+                              </object>
                               <packing>
                               <packing>
                                 <property name="expand">False</property>
                                 <property name="expand">False</property>
                                 <property name="fill">True</property>
                                 <property name="fill">True</property>
@@ -362,7 +705,7 @@
                       <packing>
                       <packing>
                         <property name="expand">False</property>
                         <property name="expand">False</property>
                         <property name="fill">True</property>
                         <property name="fill">True</property>
-                        <property name="position">1</property>
+                        <property name="position">2</property>
                       </packing>
                       </packing>
                     </child>
                     </child>
                     <child>
                     <child>
@@ -386,13 +729,15 @@
                           </packing>
                           </packing>
                         </child>
                         </child>
                         <child>
                         <child>
-                          <object class="GtkFlowBox" id="PF2_tools">
+                          <object class="GtkStack" id="PF2_tool_box_stack">
                             <property name="visible">True</property>
                             <property name="visible">True</property>
                             <property name="can_focus">False</property>
                             <property name="can_focus">False</property>
                             <property name="halign">start</property>
                             <property name="halign">start</property>
                             <property name="valign">start</property>
                             <property name="valign">start</property>
-                            <property name="orientation">vertical</property>
-                            <property name="selection_mode">none</property>
+                            <property name="transition_type">crossfade</property>
+                            <child>
+                              <placeholder/>
+                            </child>
                           </object>
                           </object>
                           <packing>
                           <packing>
                             <property name="expand">False</property>
                             <property name="expand">False</property>
@@ -415,7 +760,7 @@
                       <packing>
                       <packing>
                         <property name="expand">False</property>
                         <property name="expand">False</property>
                         <property name="fill">True</property>
                         <property name="fill">True</property>
-                        <property name="position">2</property>
+                        <property name="position">3</property>
                       </packing>
                       </packing>
                     </child>
                     </child>
                     <child>
                     <child>
@@ -435,7 +780,7 @@
                                 <property name="can_focus">False</property>
                                 <property name="can_focus">False</property>
                                 <property name="hhomogeneous">False</property>
                                 <property name="hhomogeneous">False</property>
                                 <property name="vhomogeneous">False</property>
                                 <property name="vhomogeneous">False</property>
-                                <property name="transition_type">slide-left-right</property>
+                                <property name="transition_type">crossfade</property>
                                 <child>
                                 <child>
                                   <placeholder/>
                                   <placeholder/>
                                 </child>
                                 </child>
@@ -447,7 +792,7 @@
                       <packing>
                       <packing>
                         <property name="expand">True</property>
                         <property name="expand">True</property>
                         <property name="fill">True</property>
                         <property name="fill">True</property>
-                        <property name="position">3</property>
+                        <property name="position">4</property>
                       </packing>
                       </packing>
                     </child>
                     </child>
                   </object>
                   </object>
@@ -574,6 +919,32 @@
             <property name="position">3</property>
             <property name="position">3</property>
           </packing>
           </packing>
         </child>
         </child>
+        <child>
+          <object class="GtkModelButton" id="PF2_new_layer">
+            <property name="visible">True</property>
+            <property name="sensitive">False</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <property name="tooltip_text" translatable="yes">Reset all settings for this image</property>
+            <property name="text" translatable="yes">New Layer</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">4</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkSeparator">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">5</property>
+          </packing>
+        </child>
         <child>
         <child>
           <object class="GtkModelButton" id="PF2_reset">
           <object class="GtkModelButton" id="PF2_reset">
             <property name="visible">True</property>
             <property name="visible">True</property>
@@ -586,7 +957,7 @@
           <packing>
           <packing>
             <property name="expand">False</property>
             <property name="expand">False</property>
             <property name="fill">True</property>
             <property name="fill">True</property>
-            <property name="position">4</property>
+            <property name="position">6</property>
           </packing>
           </packing>
         </child>
         </child>
       </object>
       </object>