Browse Source

Blend modes

Equbuxu 3 years ago
parent
commit
c8c88ed6f1

+ 158 - 0
src/.editorconfig

@@ -0,0 +1,158 @@
+# To learn more about .editorconfig see https://aka.ms/editorconfigdocs
+###############################
+# Core EditorConfig Options   #
+###############################
+# All files
+[*]
+indent_style = space
+
+# XML project files
+[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
+indent_size = 2
+
+# XML config files
+[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}]
+indent_size = 2
+
+# Code files
+[*.{cs,csx,vb,vbx}]
+indent_size = 4
+insert_final_newline = true
+charset = utf-8-bom
+###############################
+# .NET Coding Conventions     #
+###############################
+[*.{cs,vb}]
+# Organize usings
+dotnet_sort_system_directives_first = true
+# this. preferences
+dotnet_style_qualification_for_field = false:silent
+dotnet_style_qualification_for_property = false:silent
+dotnet_style_qualification_for_method = false:silent
+dotnet_style_qualification_for_event = false:silent
+# Language keywords vs BCL types preferences
+dotnet_style_predefined_type_for_locals_parameters_members = true:silent
+dotnet_style_predefined_type_for_member_access = true:silent
+# Parentheses preferences
+dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
+# Modifier preferences
+dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
+dotnet_style_readonly_field = true:suggestion
+# Expression-level preferences
+dotnet_style_object_initializer = true:suggestion
+dotnet_style_collection_initializer = true:suggestion
+dotnet_style_explicit_tuple_names = true:suggestion
+dotnet_style_null_propagation = true:suggestion
+dotnet_style_coalesce_expression = true:suggestion
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
+dotnet_style_prefer_inferred_tuple_names = true:suggestion
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
+dotnet_style_prefer_auto_properties = true:silent
+dotnet_style_prefer_conditional_expression_over_assignment = true:silent
+dotnet_style_prefer_conditional_expression_over_return = true:silent
+###############################
+# Naming Conventions          #
+###############################
+# Style Definitions
+dotnet_naming_style.pascal_case_style.capitalization             = pascal_case
+# Use PascalCase for constant fields  
+dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols  = constant_fields
+dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
+dotnet_naming_symbols.constant_fields.applicable_kinds            = field
+dotnet_naming_symbols.constant_fields.applicable_accessibilities  = *
+dotnet_naming_symbols.constant_fields.required_modifiers          = const
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+tab_width = 4
+end_of_line = crlf
+dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
+dotnet_style_prefer_compound_assignment = true:suggestion
+dotnet_style_prefer_simplified_interpolation = true:suggestion
+dotnet_style_namespace_match_folder = true:suggestion
+dotnet_style_allow_multiple_blank_lines_experimental = true:silent
+dotnet_style_allow_statement_immediately_after_block_experimental = true:silent
+###############################
+# C# Coding Conventions       #
+###############################
+[*.cs]
+# var preferences
+csharp_style_var_for_built_in_types = true:silent
+csharp_style_var_when_type_is_apparent = true:silent
+csharp_style_var_elsewhere = true:silent
+# Expression-bodied members
+csharp_style_expression_bodied_methods = false:silent
+csharp_style_expression_bodied_constructors = false:silent
+csharp_style_expression_bodied_operators = false:silent
+csharp_style_expression_bodied_properties = true:silent
+csharp_style_expression_bodied_indexers = true:silent
+csharp_style_expression_bodied_accessors = true:silent
+# Pattern matching preferences
+csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
+csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
+# Null-checking preferences
+csharp_style_throw_expression = true:suggestion
+csharp_style_conditional_delegate_call = true:suggestion
+# Modifier preferences
+csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion
+# Expression-level preferences
+csharp_prefer_braces = true:silent
+csharp_style_deconstructed_variable_declaration = true:suggestion
+csharp_prefer_simple_default_expression = true:suggestion
+csharp_style_pattern_local_over_anonymous_function = true:suggestion
+csharp_style_inlined_variable_declaration = true:suggestion
+###############################
+# C# Formatting Rules         #
+###############################
+# New line preferences
+csharp_new_line_before_open_brace = all
+csharp_new_line_before_else = true
+csharp_new_line_before_catch = true
+csharp_new_line_before_finally = true
+csharp_new_line_before_members_in_object_initializers = true
+csharp_new_line_before_members_in_anonymous_types = true
+csharp_new_line_between_query_expression_clauses = true
+# Indentation preferences
+csharp_indent_case_contents = true
+csharp_indent_switch_labels = true
+csharp_indent_labels = flush_left
+# Space preferences
+csharp_space_after_cast = false
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+csharp_space_between_parentheses = false
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_around_binary_operators = before_and_after
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+# Wrapping preferences
+csharp_preserve_single_line_statements = true
+csharp_preserve_single_line_blocks = true
+csharp_style_namespace_declarations= file_scoped:silent
+csharp_using_directive_placement = outside_namespace:silent
+csharp_prefer_simple_using_statement = true:suggestion
+csharp_style_expression_bodied_lambdas = true:silent
+csharp_style_expression_bodied_local_functions = false:silent
+csharp_style_prefer_null_check_over_type_check = true:suggestion
+csharp_style_prefer_index_operator = true:suggestion
+csharp_style_prefer_local_over_anonymous_function = true:suggestion
+csharp_style_prefer_range_operator = true:suggestion
+csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
+csharp_style_prefer_tuple_swap = true:suggestion
+csharp_style_unused_value_assignment_preference = discard_variable:suggestion
+csharp_style_unused_value_expression_statement_preference = discard_variable:silent
+csharp_prefer_static_local_function = true:suggestion
+csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent
+csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent
+csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent
+###############################
+# VB Coding Conventions       #
+###############################
+[*.vb]
+# Modifier preferences
+visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion

+ 21 - 0
src/PixiEditor.ChangeableDocument/Actions/Properties/SetStructureMemberBlendMode_Action.cs

@@ -0,0 +1,21 @@
+using PixiEditor.ChangeableDocument.Changes;
+using PixiEditor.ChangeableDocument.Changes.Properties;
+using PixiEditor.ChangeableDocument.Enums;
+
+namespace PixiEditor.ChangeableDocument.Actions.Properties;
+public record class SetStructureMemberBlendMode_Action : IMakeChangeAction
+{
+    public BlendMode BlendMode { get; }
+    public Guid GuidValue { get; }
+
+    public SetStructureMemberBlendMode_Action(BlendMode blendMode, Guid guidValue)
+    {
+        BlendMode = blendMode;
+        GuidValue = guidValue;
+    }
+
+    Change IMakeChangeAction.CreateCorrespondingChange()
+    {
+        return new StructureMemberBlendMode_Change(BlendMode, GuidValue);
+    }
+}

+ 1 - 0
src/PixiEditor.ChangeableDocument/Actions/Structure/CreateStructureMember_Action.cs

@@ -1,5 +1,6 @@
 using PixiEditor.ChangeableDocument.Changes;
 using PixiEditor.ChangeableDocument.Changes;
 using PixiEditor.ChangeableDocument.Changes.Structure;
 using PixiEditor.ChangeableDocument.Changes.Structure;
+using PixiEditor.ChangeableDocument.Enums;
 
 
 namespace PixiEditor.ChangeableDocument.Actions.Structure;
 namespace PixiEditor.ChangeableDocument.Actions.Structure;
 
 

+ 5 - 0
src/PixiEditor.ChangeableDocument/ChangeInfos/StructureMemberBlendMode_ChangeInfo.cs

@@ -0,0 +1,5 @@
+namespace PixiEditor.ChangeableDocument.ChangeInfos;
+public record class StructureMemberBlendMode_ChangeInfo : IChangeInfo
+{
+    public Guid GuidValue { get; init; }
+}

+ 2 - 0
src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyStructureMember.cs

@@ -1,4 +1,5 @@
 using ChunkyImageLib;
 using ChunkyImageLib;
+using PixiEditor.ChangeableDocument.Enums;
 
 
 namespace PixiEditor.ChangeableDocument.Changeables.Interfaces
 namespace PixiEditor.ChangeableDocument.Changeables.Interfaces
 {
 {
@@ -8,6 +9,7 @@ namespace PixiEditor.ChangeableDocument.Changeables.Interfaces
         string Name { get; }
         string Name { get; }
         Guid GuidValue { get; }
         Guid GuidValue { get; }
         float Opacity { get; }
         float Opacity { get; }
+        BlendMode BlendMode { get; }
         IReadOnlyChunkyImage? ReadOnlyMask { get; }
         IReadOnlyChunkyImage? ReadOnlyMask { get; }
     }
     }
 }
 }

+ 2 - 0
src/PixiEditor.ChangeableDocument/Changeables/StructureMember.cs

@@ -1,5 +1,6 @@
 using ChunkyImageLib;
 using ChunkyImageLib;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
+using PixiEditor.ChangeableDocument.Enums;
 
 
 namespace PixiEditor.ChangeableDocument.Changeables
 namespace PixiEditor.ChangeableDocument.Changeables
 {
 {
@@ -8,6 +9,7 @@ namespace PixiEditor.ChangeableDocument.Changeables
         public float Opacity { get; set; } = 1f;
         public float Opacity { get; set; } = 1f;
         public bool IsVisible { get; set; } = true;
         public bool IsVisible { get; set; } = true;
         public string Name { get; set; } = "Unnamed";
         public string Name { get; set; } = "Unnamed";
+        public BlendMode BlendMode { get; set; } = BlendMode.Normal;
         public Guid GuidValue { get; init; }
         public Guid GuidValue { get; init; }
         public ChunkyImage? Mask { get; set; } = null;
         public ChunkyImage? Mask { get; set; } = null;
         public IReadOnlyChunkyImage? ReadOnlyMask => Mask;
         public IReadOnlyChunkyImage? ReadOnlyMask => Mask;

+ 43 - 0
src/PixiEditor.ChangeableDocument/Changes/Properties/StructureMemberBlendMode_Change.cs

@@ -0,0 +1,43 @@
+using PixiEditor.ChangeableDocument.Changeables;
+using PixiEditor.ChangeableDocument.ChangeInfos;
+using PixiEditor.ChangeableDocument.Enums;
+
+namespace PixiEditor.ChangeableDocument.Changes.Properties;
+internal class StructureMemberBlendMode_Change : Change
+{
+    private BlendMode originalBlendMode;
+    private readonly BlendMode newBlendMode;
+    private readonly Guid targetGuid;
+
+    public StructureMemberBlendMode_Change(BlendMode newBlendMode, Guid targetGuid)
+    {
+        this.newBlendMode = newBlendMode;
+        this.targetGuid = targetGuid;
+    }
+
+    public override void Initialize(Document target)
+    {
+        var member = target.FindMemberOrThrow(targetGuid);
+        originalBlendMode = member.BlendMode;
+    }
+
+    public override IChangeInfo? Apply(Document target, out bool ignoreInUndo)
+    {
+        var member = target.FindMemberOrThrow(targetGuid);
+        member.BlendMode = newBlendMode;
+        ignoreInUndo = false;
+        return new StructureMemberBlendMode_ChangeInfo() { GuidValue = targetGuid };
+    }
+
+    public override IChangeInfo? Revert(Document target)
+    {
+        var member = target.FindMemberOrThrow(targetGuid);
+        member.BlendMode = originalBlendMode;
+        return new StructureMemberBlendMode_ChangeInfo() { GuidValue = targetGuid };
+    }
+
+    public override bool IsMergeableWith(Change other)
+    {
+        return other is StructureMemberBlendMode_Change;
+    }
+}

+ 1 - 0
src/PixiEditor.ChangeableDocument/Changes/Structure/CreateStructureMember_Change.cs

@@ -1,5 +1,6 @@
 using PixiEditor.ChangeableDocument.Changeables;
 using PixiEditor.ChangeableDocument.Changeables;
 using PixiEditor.ChangeableDocument.ChangeInfos;
 using PixiEditor.ChangeableDocument.ChangeInfos;
+using PixiEditor.ChangeableDocument.Enums;
 
 
 namespace PixiEditor.ChangeableDocument.Changes.Structure
 namespace PixiEditor.ChangeableDocument.Changes.Structure
 {
 {

+ 21 - 0
src/PixiEditor.ChangeableDocument/Enums/BlendMode.cs

@@ -0,0 +1,21 @@
+namespace PixiEditor.ChangeableDocument.Enums;
+public enum BlendMode
+{
+    Normal,
+    Darken,
+    Multiply,
+    ColorBurn,
+    Lighten,
+    Screen,
+    ColorDodge,
+    LinearDodge,
+    Overlay,
+    SoftLight,
+    HardLight,
+    Difference,
+    Exclusion,
+    Hue,
+    Saturation,
+    Luminosity,
+    Color,
+}

+ 1 - 1
src/PixiEditor.ChangeableDocument/StructureMemberType.cs → src/PixiEditor.ChangeableDocument/Enums/StructureMemberType.cs

@@ -1,4 +1,4 @@
-namespace PixiEditor.ChangeableDocument
+namespace PixiEditor.ChangeableDocument.Enums
 {
 {
     public enum StructureMemberType
     public enum StructureMemberType
     {
     {

+ 29 - 0
src/PixiEditor.ChangeableDocument/Rendering/ChunkRenderer.cs

@@ -1,6 +1,7 @@
 using ChunkyImageLib;
 using ChunkyImageLib;
 using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.DataHolders;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
+using PixiEditor.ChangeableDocument.Enums;
 using SkiaSharp;
 using SkiaSharp;
 
 
 namespace PixiEditor.ChangeableDocument.Rendering
 namespace PixiEditor.ChangeableDocument.Rendering
@@ -20,6 +21,31 @@ namespace PixiEditor.ChangeableDocument.Rendering
             return RenderChunkRecursively(pos, resolution, 0, root, layers);
             return RenderChunkRecursively(pos, resolution, 0, root, layers);
         }
         }
 
 
+        private static SKBlendMode GetSKBlendMode(BlendMode blendMode)
+        {
+            return blendMode switch
+            {
+                BlendMode.Normal => SKBlendMode.SrcOver,
+                BlendMode.Darken => SKBlendMode.Darken,
+                BlendMode.Multiply => SKBlendMode.Multiply,
+                BlendMode.ColorBurn => SKBlendMode.ColorBurn,
+                BlendMode.Lighten => SKBlendMode.Lighten,
+                BlendMode.Screen => SKBlendMode.Screen,
+                BlendMode.ColorDodge => SKBlendMode.ColorDodge,
+                BlendMode.LinearDodge => SKBlendMode.Plus,
+                BlendMode.Overlay => SKBlendMode.Overlay,
+                BlendMode.SoftLight => SKBlendMode.SoftLight,
+                BlendMode.HardLight => SKBlendMode.HardLight,
+                BlendMode.Difference => SKBlendMode.Difference,
+                BlendMode.Exclusion => SKBlendMode.Exclusion,
+                BlendMode.Hue => SKBlendMode.Hue,
+                BlendMode.Saturation => SKBlendMode.Saturation,
+                BlendMode.Luminosity => SKBlendMode.Luminosity,
+                BlendMode.Color => SKBlendMode.Color,
+                _ => SKBlendMode.SrcOver,
+            };
+        }
+
         private static Chunk RenderChunkRecursively(Vector2i chunkPos, ChunkResolution resolution, int depth, IReadOnlyFolder folder, HashSet<Guid>? visibleLayers)
         private static Chunk RenderChunkRecursively(Vector2i chunkPos, ChunkResolution resolution, int depth, IReadOnlyFolder folder, HashSet<Guid>? visibleLayers)
         {
         {
             Chunk targetChunk = Chunk.Create(resolution);
             Chunk targetChunk = Chunk.Create(resolution);
@@ -39,6 +65,7 @@ namespace PixiEditor.ChangeableDocument.Rendering
                     if (layer.ReadOnlyMask is null)
                     if (layer.ReadOnlyMask is null)
                     {
                     {
                         PaintToDrawChunksWith.Color = new SKColor(255, 255, 255, (byte)Math.Round(child.Opacity * 255));
                         PaintToDrawChunksWith.Color = new SKColor(255, 255, 255, (byte)Math.Round(child.Opacity * 255));
+                        PaintToDrawChunksWith.BlendMode = GetSKBlendMode(layer.BlendMode);
                         layer.ReadOnlyLayerImage.DrawLatestChunkOn(chunkPos, resolution, targetChunk.Surface.SkiaSurface, new(0, 0), PaintToDrawChunksWith);
                         layer.ReadOnlyLayerImage.DrawLatestChunkOn(chunkPos, resolution, targetChunk.Surface.SkiaSurface, new(0, 0), PaintToDrawChunksWith);
                     }
                     }
                     else
                     else
@@ -50,6 +77,7 @@ namespace PixiEditor.ChangeableDocument.Rendering
                             layer.ReadOnlyMask.DrawLatestChunkOn(chunkPos, resolution, tempChunk.Surface.SkiaSurface, new(0, 0), ClippingPaint);
                             layer.ReadOnlyMask.DrawLatestChunkOn(chunkPos, resolution, tempChunk.Surface.SkiaSurface, new(0, 0), ClippingPaint);
 
 
                             PaintToDrawChunksWith.Color = new SKColor(255, 255, 255, (byte)Math.Round(child.Opacity * 255));
                             PaintToDrawChunksWith.Color = new SKColor(255, 255, 255, (byte)Math.Round(child.Opacity * 255));
+                            PaintToDrawChunksWith.BlendMode = GetSKBlendMode(layer.BlendMode);
                             targetChunk.Surface.SkiaSurface.Canvas.DrawSurface(tempChunk.Surface.SkiaSurface, 0, 0, PaintToDrawChunksWith);
                             targetChunk.Surface.SkiaSurface.Canvas.DrawSurface(tempChunk.Surface.SkiaSurface, 0, 0, PaintToDrawChunksWith);
                         }
                         }
                     }
                     }
@@ -64,6 +92,7 @@ namespace PixiEditor.ChangeableDocument.Rendering
                         innerFolder.ReadOnlyMask.DrawLatestChunkOn(chunkPos, resolution, renderedChunk.Surface.SkiaSurface, new(0, 0), ClippingPaint);
                         innerFolder.ReadOnlyMask.DrawLatestChunkOn(chunkPos, resolution, renderedChunk.Surface.SkiaSurface, new(0, 0), ClippingPaint);
 
 
                     PaintToDrawChunksWith.Color = new SKColor(255, 255, 255, (byte)Math.Round(child.Opacity * 255));
                     PaintToDrawChunksWith.Color = new SKColor(255, 255, 255, (byte)Math.Round(child.Opacity * 255));
+                    PaintToDrawChunksWith.BlendMode = GetSKBlendMode(innerFolder.BlendMode);
                     renderedChunk.DrawOnSurface(targetChunk.Surface.SkiaSurface, new(0, 0), PaintToDrawChunksWith);
                     renderedChunk.DrawOnSurface(targetChunk.Surface.SkiaSurface, new(0, 0), PaintToDrawChunksWith);
                     continue;
                     continue;
                 }
                 }

+ 6 - 1
src/PixiEditorPrototype.sln

@@ -15,7 +15,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChunkyImageLibVis", "Chunky
 EndProject
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PixiEditor.Zoombox", "PixiEditor.Zoombox\PixiEditor.Zoombox.csproj", "{232E58B6-8080-4725-8541-98BFCFE23A1C}"
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PixiEditor.Zoombox", "PixiEditor.Zoombox\PixiEditor.Zoombox.csproj", "{232E58B6-8080-4725-8541-98BFCFE23A1C}"
 EndProject
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChunkyImageLibBenchmark", "ChunkyImageLibBenchmark\ChunkyImageLibBenchmark.csproj", "{3998B3EB-F11F-4637-A135-FBC3CCA24299}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChunkyImageLibBenchmark", "ChunkyImageLibBenchmark\ChunkyImageLibBenchmark.csproj", "{3998B3EB-F11F-4637-A135-FBC3CCA24299}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{2AF84829-B00E-4B10-B010-C9306C6DF278}"
+	ProjectSection(SolutionItems) = preProject
+		.editorconfig = .editorconfig
+	EndProjectSection
 EndProject
 EndProject
 Global
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution

+ 88 - 0
src/PixiEditorPrototype/CustomControls/BlendModeComboBox.cs

@@ -0,0 +1,88 @@
+using System.Collections.Generic;
+using System.Windows;
+using System.Windows.Controls;
+using PixiEditor.ChangeableDocument.Enums;
+
+namespace PixiEditorPrototype.CustomControls;
+internal class BlendModeComboBox : ComboBox
+{
+    public static readonly DependencyProperty SelectedBlendModeProperty = DependencyProperty.Register(
+        nameof(SelectedBlendMode), typeof(BlendMode), typeof(BlendModeComboBox),
+        new PropertyMetadata(BlendMode.Normal, OnBlendModeChange));
+
+    public BlendMode SelectedBlendMode
+    {
+        get { return (BlendMode)GetValue(SelectedBlendModeProperty); }
+        set { SetValue(SelectedBlendModeProperty, value); }
+    }
+
+    private bool ignoreDepPropChange = false;
+    private bool ignoreSelectionChange = false;
+
+    public BlendModeComboBox()
+    {
+        AddItems();
+        SelectionChanged += OnSelectionChange;
+    }
+
+    private void OnSelectionChange(object sender, SelectionChangedEventArgs e)
+    {
+        if (ignoreSelectionChange || e.AddedItems.Count == 0 || e.AddedItems[0] is not ComboBoxItem item || item.Tag is not BlendMode mode)
+            return;
+        ignoreDepPropChange = true;
+        SelectedBlendMode = mode;
+        ignoreDepPropChange = false;
+    }
+
+    private static void OnBlendModeChange(DependencyObject obj, DependencyPropertyChangedEventArgs args)
+    {
+        var combobox = (BlendModeComboBox)obj;
+        if (combobox.ignoreDepPropChange)
+            return;
+        foreach (var item in combobox.Items)
+        {
+            if (item is not ComboBoxItem cbItem)
+                continue;
+            if ((BlendMode)cbItem.Tag == (BlendMode)args.NewValue)
+            {
+                combobox.ignoreSelectionChange = true;
+                combobox.SelectedItem = item;
+                combobox.ignoreSelectionChange = false;
+                break;
+            }
+        }
+    }
+
+    private void AddItems()
+    {
+        var items = new List<UIElement>() {
+            new ComboBoxItem() { Content = "Normal", Tag = BlendMode.Normal },
+            new Separator(),
+            new ComboBoxItem() { Content = "Darken", Tag = BlendMode.Darken },
+            new ComboBoxItem() { Content = "Multiply", Tag = BlendMode.Multiply },
+            new ComboBoxItem() { Content = "Color Burn", Tag = BlendMode.ColorBurn },
+            new Separator(),
+            new ComboBoxItem() { Content = "Lighten", Tag = BlendMode.Lighten },
+            new ComboBoxItem() { Content = "Screen", Tag = BlendMode.Screen },
+            new ComboBoxItem() { Content = "Color Dodge", Tag = BlendMode.ColorDodge },
+            new ComboBoxItem() { Content = "Linear Dodge (Add)", Tag = BlendMode.LinearDodge },
+            new Separator(),
+            new ComboBoxItem() { Content = "Overlay", Tag = BlendMode.Overlay },
+            new ComboBoxItem() { Content = "Soft Light", Tag = BlendMode.SoftLight },
+            new ComboBoxItem() { Content = "Hard Light", Tag = BlendMode.HardLight },
+            new Separator(),
+            new ComboBoxItem() { Content = "Difference", Tag = BlendMode.Difference },
+            new ComboBoxItem() { Content = "Exclusion", Tag = BlendMode.Exclusion },
+            new Separator(),
+            new ComboBoxItem() { Content = "Hue", Tag = BlendMode.Hue },
+            new ComboBoxItem() { Content = "Saturation", Tag = BlendMode.Saturation },
+            new ComboBoxItem() { Content = "Luminosity", Tag = BlendMode.Luminosity },
+            new ComboBoxItem() { Content = "Color", Tag = BlendMode.Color }
+        };
+        foreach (var item in items)
+        {
+            Items.Add(item);
+        }
+        SelectedIndex = 0;
+    }
+}

+ 3 - 3
src/PixiEditorPrototype/Models/DocumentStructureHelper.cs

@@ -1,8 +1,8 @@
-using PixiEditor.ChangeableDocument;
+using System;
+using System.Collections.Generic;
 using PixiEditor.ChangeableDocument.Actions.Structure;
 using PixiEditor.ChangeableDocument.Actions.Structure;
+using PixiEditor.ChangeableDocument.Enums;
 using PixiEditorPrototype.ViewModels;
 using PixiEditorPrototype.ViewModels;
-using System;
-using System.Collections.Generic;
 
 
 namespace PixiEditorPrototype.Models
 namespace PixiEditorPrototype.Models
 {
 {

+ 13 - 4
src/PixiEditorPrototype/Models/DocumentUpdater.cs

@@ -1,11 +1,11 @@
-using ChunkyImageLib.DataHolders;
+using System;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using ChunkyImageLib.DataHolders;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.ChangeInfos;
 using PixiEditor.ChangeableDocument.ChangeInfos;
 using PixiEditorPrototype.ViewModels;
 using PixiEditorPrototype.ViewModels;
 using SkiaSharp;
 using SkiaSharp;
-using System;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
 
 
 namespace PixiEditorPrototype.Models
 namespace PixiEditorPrototype.Models
 {
 {
@@ -53,9 +53,18 @@ namespace PixiEditorPrototype.Models
                 case StructureMemberMask_ChangeInfo info:
                 case StructureMemberMask_ChangeInfo info:
                     ProcessStructureMemberMask(info);
                     ProcessStructureMemberMask(info);
                     break;
                     break;
+                case StructureMemberBlendMode_ChangeInfo info:
+                    ProcessStructureMemberBlendMode(info);
+                    break;
             }
             }
         }
         }
 
 
+        private void ProcessStructureMemberBlendMode(StructureMemberBlendMode_ChangeInfo info)
+        {
+            var memberVm = helper.StructureHelper.FindOrThrow(info.GuidValue);
+            memberVm.RaisePropertyChanged(nameof(memberVm.BlendMode));
+        }
+
         private void ProcessStructureMemberMask(StructureMemberMask_ChangeInfo info)
         private void ProcessStructureMemberMask(StructureMemberMask_ChangeInfo info)
         {
         {
             var memberVm = helper.StructureHelper.FindOrThrow(info.GuidValue);
             var memberVm = helper.StructureHelper.FindOrThrow(info.GuidValue);

+ 5 - 4
src/PixiEditorPrototype/Models/Rendering/WriteableBitmapUpdater.cs

@@ -1,4 +1,7 @@
-using ChunkyImageLib;
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using ChunkyImageLib;
 using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.DataHolders;
 using ChunkyImageLib.Operations;
 using ChunkyImageLib.Operations;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
@@ -6,9 +9,6 @@ using PixiEditor.ChangeableDocument.ChangeInfos;
 using PixiEditor.ChangeableDocument.Rendering;
 using PixiEditor.ChangeableDocument.Rendering;
 using PixiEditorPrototype.Models.Rendering.RenderInfos;
 using PixiEditorPrototype.Models.Rendering.RenderInfos;
 using SkiaSharp;
 using SkiaSharp;
-using System;
-using System.Collections.Generic;
-using System.Threading.Tasks;
 
 
 namespace PixiEditorPrototype.Models.Rendering
 namespace PixiEditorPrototype.Models.Rendering
 {
 {
@@ -78,6 +78,7 @@ namespace PixiEditorPrototype.Models.Rendering
                     case MoveStructureMember_ChangeInfo:
                     case MoveStructureMember_ChangeInfo:
                     case Size_ChangeInfo:
                     case Size_ChangeInfo:
                     case StructureMemberMask_ChangeInfo:
                     case StructureMemberMask_ChangeInfo:
+                    case StructureMemberBlendMode_ChangeInfo:
                         AddAllChunks(affectedChunks);
                         AddAllChunks(affectedChunks);
                         break;
                         break;
                     case StructureMemberOpacity_ChangeInfo opacityChangeInfo:
                     case StructureMemberOpacity_ChangeInfo opacityChangeInfo:

+ 9 - 9
src/PixiEditorPrototype/ViewModels/DocumentViewModel.cs

@@ -1,5 +1,11 @@
-using ChunkyImageLib.DataHolders;
-using PixiEditor.ChangeableDocument;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Windows;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using ChunkyImageLib.DataHolders;
 using PixiEditor.ChangeableDocument.Actions.Drawing;
 using PixiEditor.ChangeableDocument.Actions.Drawing;
 using PixiEditor.ChangeableDocument.Actions.Drawing.Rectangle;
 using PixiEditor.ChangeableDocument.Actions.Drawing.Rectangle;
 using PixiEditor.ChangeableDocument.Actions.Drawing.Selection;
 using PixiEditor.ChangeableDocument.Actions.Drawing.Selection;
@@ -7,16 +13,10 @@ using PixiEditor.ChangeableDocument.Actions.Properties;
 using PixiEditor.ChangeableDocument.Actions.Root;
 using PixiEditor.ChangeableDocument.Actions.Root;
 using PixiEditor.ChangeableDocument.Actions.Structure;
 using PixiEditor.ChangeableDocument.Actions.Structure;
 using PixiEditor.ChangeableDocument.Actions.Undo;
 using PixiEditor.ChangeableDocument.Actions.Undo;
+using PixiEditor.ChangeableDocument.Enums;
 using PixiEditor.Zoombox;
 using PixiEditor.Zoombox;
 using PixiEditorPrototype.Models;
 using PixiEditorPrototype.Models;
 using SkiaSharp;
 using SkiaSharp;
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Linq;
-using System.Windows;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
 
 
 namespace PixiEditorPrototype.ViewModels
 namespace PixiEditorPrototype.ViewModels
 {
 {

+ 11 - 3
src/PixiEditorPrototype/ViewModels/StructureMemberViewModel.cs

@@ -1,14 +1,16 @@
-using PixiEditor.ChangeableDocument.Actions.Properties;
+using System;
+using System.ComponentModel;
+using PixiEditor.ChangeableDocument.Actions.Properties;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Interfaces;
+using PixiEditor.ChangeableDocument.Enums;
 using PixiEditorPrototype.Models;
 using PixiEditorPrototype.Models;
-using System;
-using System.ComponentModel;
 
 
 namespace PixiEditorPrototype.ViewModels
 namespace PixiEditorPrototype.ViewModels
 {
 {
     internal abstract class StructureMemberViewModel : INotifyPropertyChanged
     internal abstract class StructureMemberViewModel : INotifyPropertyChanged
     {
     {
         private IReadOnlyStructureMember member;
         private IReadOnlyStructureMember member;
+
         public event PropertyChangedEventHandler? PropertyChanged;
         public event PropertyChangedEventHandler? PropertyChanged;
         public DocumentViewModel Document { get; }
         public DocumentViewModel Document { get; }
         private DocumentHelpers Helpers { get; }
         private DocumentHelpers Helpers { get; }
@@ -25,6 +27,12 @@ namespace PixiEditorPrototype.ViewModels
             set => Helpers.ActionAccumulator.AddFinishedActions(new SetStructureMemberVisibility_Action(value, member.GuidValue));
             set => Helpers.ActionAccumulator.AddFinishedActions(new SetStructureMemberVisibility_Action(value, member.GuidValue));
         }
         }
 
 
+        public BlendMode BlendMode
+        {
+            get => member.BlendMode;
+            set => Helpers.ActionAccumulator.AddFinishedActions(new SetStructureMemberBlendMode_Action(value, member.GuidValue));
+        }
+
         public bool IsSelected { get; set; }
         public bool IsSelected { get; set; }
         public bool ShouldDrawOnMask { get; set; }
         public bool ShouldDrawOnMask { get; set; }
 
 

+ 2 - 0
src/PixiEditorPrototype/Views/MainWindow.xaml

@@ -8,6 +8,7 @@
         xmlns:beh="clr-namespace:PixiEditorPrototype.Behaviors"
         xmlns:beh="clr-namespace:PixiEditorPrototype.Behaviors"
         xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
         xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
         xmlns:pe="clr-namespace:PixiEditorPrototype"
         xmlns:pe="clr-namespace:PixiEditorPrototype"
+        xmlns:controls="clr-namespace:PixiEditorPrototype.CustomControls"
         xmlns:zoombox="clr-namespace:PixiEditor.Zoombox;assembly=PixiEditor.Zoombox"
         xmlns:zoombox="clr-namespace:PixiEditor.Zoombox;assembly=PixiEditor.Zoombox"
         xmlns:models="clr-namespace:PixiEditorPrototype.Models"
         xmlns:models="clr-namespace:PixiEditorPrototype.Models"
         xmlns:colorpicker="clr-namespace:ColorPicker;assembly=ColorPicker"
         xmlns:colorpicker="clr-namespace:ColorPicker;assembly=ColorPicker"
@@ -31,6 +32,7 @@
                 <StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Margin="0,5,0,0">
                 <StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Margin="0,5,0,0">
                     <Button Margin="5,0" Command="{Binding ActiveDocument.CreateMaskCommand}" Width="80">Create Mask</Button>
                     <Button Margin="5,0" Command="{Binding ActiveDocument.CreateMaskCommand}" Width="80">Create Mask</Button>
                     <Button Margin="5,0" Command="{Binding ActiveDocument.DeleteMaskCommand}" Width="80">Delete Mask</Button>
                     <Button Margin="5,0" Command="{Binding ActiveDocument.DeleteMaskCommand}" Width="80">Delete Mask</Button>
+                    <controls:BlendModeComboBox Margin="5,0" Width="80" SelectedBlendMode="{Binding ActiveDocument.SelectedStructureMember.BlendMode, Mode=TwoWay}"/>
                 </StackPanel>
                 </StackPanel>
                 <DockPanel DockPanel.Dock="Top" HorizontalAlignment="Stretch" Margin="0,5,0,5">
                 <DockPanel DockPanel.Dock="Top" HorizontalAlignment="Stretch" Margin="0,5,0,5">
                     <Button Width="80" Margin="5,0" Command="{Binding ActiveDocument.CombineCommand}">Merge</Button>
                     <Button Width="80" Margin="5,0" Command="{Binding ActiveDocument.CombineCommand}">Merge</Button>