Browse Source

Reference layer color picker support and UI improvements

Equbuxu 2 years ago
parent
commit
04aa35b1ed

+ 10 - 5
src/ChunkyImageLibTest/RectITests.cs

@@ -30,7 +30,8 @@ public class RectITests
     public void FromTwoPoints_DiagonalsCombinations_ReturnsStandardizedRects()
     {
         RectI refR = new RectI(3, 4, 8 - 3, 9 - 4);
-        Span<RectI> rects = stackalloc RectI[] {
+        Span<RectI> rects = stackalloc RectI[] 
+        {
             RectI.FromTwoPoints(new VecI(3, 4), new VecI(8, 9)),
             RectI.FromTwoPoints(new VecI(8, 9), new VecI(3, 4)),
             RectI.FromTwoPoints(new VecI(8, 9), new VecI(3, 4)),
@@ -241,7 +242,8 @@ public class RectITests
     public void IntersectsWithInclusive_BasicRects_ReturnsTrue()
     {
         RectI rect = new RectI(960, 540, 1920, 1080);
-        Span<RectI> rects = stackalloc RectI[] {
+        Span<RectI> rects = stackalloc RectI[] 
+        {
             rect.Offset(1920, 1080),
             rect.Offset(-1920, 0).Inflate(-1).Offset(1, 0),
             rect.Offset(0, 1080).Inflate(-1).Offset(0, -1),
@@ -256,7 +258,8 @@ public class RectITests
     public void IntersectsWithInclusive_BasicRects_ReturnsFalse()
     {
         RectI rect = new RectI(960, 540, 1920, 1080);
-        Span<RectI> rects = stackalloc RectI[] {
+        Span<RectI> rects = stackalloc RectI[] 
+        {
             rect.Offset(1921, 1080),
             rect.Offset(-1921, 0).Inflate(-1).Offset(1, 0),
             rect.Offset(0, 1081).Inflate(-1).Offset(0, -1)
@@ -269,7 +272,8 @@ public class RectITests
     public void IntersectsWithExclusive_BasicRects_ReturnsTrue()
     {
         RectI rect = new RectI(960, 540, 1920, 1080);
-        Span<RectI> rects = stackalloc RectI[] {
+        Span<RectI> rects = stackalloc RectI[] 
+        {
             rect.Offset(1920 - 1, 1080 - 1),
             rect.Offset(-1920, 0).Inflate(-1).Offset(2, 0),
             rect.Offset(0, 1080).Inflate(-1).Offset(0, -2),
@@ -284,7 +288,8 @@ public class RectITests
     public void IntersectsWithExclusive_BasicRects_ReturnsFalse()
     {
         RectI rect = new RectI(960, 540, 1920, 1080);
-        Span<RectI> rects = stackalloc RectI[] {
+        Span<RectI> rects = stackalloc RectI[] 
+        {
             rect.Offset(1920, 1080),
             rect.Offset(-1920, 0).Inflate(-1).Offset(1, 0),
             rect.Offset(0, 1080).Inflate(-1).Offset(0, -1),

+ 1 - 0
src/Custom.ruleset

@@ -28,6 +28,7 @@
     <Rule Id="SA1112" Action="None" />
     <Rule Id="SA1117" Action="None" />
     <Rule Id="SA1119" Action="None" />
+    <Rule Id="SA1121" Action="None" />
     <Rule Id="SA1122" Action="None" />
     <Rule Id="SA1124" Action="None" />
     <Rule Id="SA1127" Action="None" />

+ 4 - 4
src/PixiEditor.ChangeableDocument.Gen/Helpers.cs

@@ -6,7 +6,7 @@ namespace PixiEditor.ChangeableDocument.Gen;
 
 internal static class Helpers
 {
-    private static SymbolDisplayFormat TypeWithGenerics =
+    private static SymbolDisplayFormat typeWithGenerics =
         new SymbolDisplayFormat(genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters | SymbolDisplayGenericsOptions.IncludeTypeConstraints);
     public static string CreateMakeChangeAction(MethodInfo changeConstructorInfo)
     {
@@ -77,8 +77,8 @@ bool PixiEditor.ChangeableDocument.Actions.IStartOrUpdateChangeAction.IsChangeTy
     public static string CreateEndChangeAction(MethodInfo changeConstructorInfo)
     {
         string actionName = "End" + changeConstructorInfo.ContainingClass.Name.Split('_')[0] + "_Action";
-        return $@"
-namespace PixiEditor.ChangeableDocument.Actions.Generated;
+        return 
+$@"namespace PixiEditor.ChangeableDocument.Actions.Generated;
 
 public record class {actionName} : PixiEditor.ChangeableDocument.Actions.IEndChangeAction
 {{
@@ -95,7 +95,7 @@ public record class {actionName} : PixiEditor.ChangeableDocument.Actions.IEndCha
         List<TypeWithName> variables = method.Parameters.Select(static parameter =>
         {
             return new TypeWithName(
-                parameter.Type.ToDisplayString(TypeWithGenerics),
+                parameter.Type.ToDisplayString(typeWithGenerics),
                 parameter.Type.ContainingNamespace.ToDisplayString(),
                 parameter.Name,
                 parameter.NullableAnnotation is NullableAnnotation.Annotated

+ 10 - 4
src/PixiEditor.ChangeableDocument.Gen/UpdateableChangeActionGenerator.cs

@@ -9,8 +9,8 @@ public class UpdateableChangeActionGenerator : IIncrementalGenerator
     private const string AttributesNamespace = "PixiEditor.ChangeableDocument.Actions.Attributes";
     private const string ConstructorAttribute = "GenerateUpdateableChangeActionsAttribute";
     private const string UpdateMethodAttribute = "UpdateChangeMethodAttribute";
-    private static NamespacedType ConstructorAttributeType = new NamespacedType(ConstructorAttribute, AttributesNamespace);
-    private static NamespacedType UpdateMethodAttributeType = new NamespacedType(UpdateMethodAttribute, AttributesNamespace);
+    private static NamespacedType constructorAttributeType = new NamespacedType(ConstructorAttribute, AttributesNamespace);
+    private static NamespacedType updateMethodAttributeType = new NamespacedType(UpdateMethodAttribute, AttributesNamespace);
 
     private static Result<(IMethodSymbol, IMethodSymbol, ClassDeclarationSyntax)>? TransformSyntax
         (GeneratorSyntaxContext context, CancellationToken cancelToken)
@@ -20,7 +20,7 @@ public class UpdateableChangeActionGenerator : IIncrementalGenerator
         // make sure we are actually working with a constructor
         if (context.Node is ConstructorDeclarationSyntax constructor)
         {
-            if (!Helpers.MethodHasAttribute(context, cancelToken, constructor, ConstructorAttributeType))
+            if (!Helpers.MethodHasAttribute(context, cancelToken, constructor, constructorAttributeType))
                 return null;
             containingClass = (ClassDeclarationSyntax)constructor.Parent!;
             constructorSyntax = constructor;
@@ -42,13 +42,15 @@ public class UpdateableChangeActionGenerator : IIncrementalGenerator
         var members = containingClass.Members.Where(node => node is MethodDeclarationSyntax).ToList();
         const string errorMessage = $"Update method isn't marked with {UpdateMethodAttribute}";
         if (!members.Any())
+        {
             return Result<(IMethodSymbol, IMethodSymbol, ClassDeclarationSyntax)>.Error
                 (errorMessage, containingClass.SyntaxTree, containingClass.Span);
+        }
         foreach (var member in members)
         {
             cancelToken.ThrowIfCancellationRequested();
             var method = (MethodDeclarationSyntax)member;
-            bool hasAttr = Helpers.MethodHasAttribute(context, cancelToken, method, UpdateMethodAttributeType);
+            bool hasAttr = Helpers.MethodHasAttribute(context, cancelToken, method, updateMethodAttributeType);
             if (hasAttr)
             {
                 methodSyntax = method;
@@ -73,8 +75,10 @@ public class UpdateableChangeActionGenerator : IIncrementalGenerator
         (Result<(IMethodSymbol, IMethodSymbol, ClassDeclarationSyntax)>? prevResult, CancellationToken cancelToken)
     {
         if (prevResult!.Value.ErrorText is not null)
+        {
             return Result<(NamedSourceCode, NamedSourceCode)>.Error
                 (prevResult.Value.ErrorText, prevResult.Value.SyntaxTree!, (TextSpan)prevResult.Value.Span!);
+        }
         var (constructor, update, containingClass) = prevResult.Value.Value;
 
         var constructorInfo = Helpers.ExtractMethodInfo(constructor!);
@@ -82,8 +86,10 @@ public class UpdateableChangeActionGenerator : IIncrementalGenerator
 
         var maybeStartUpdateAction = Helpers.CreateStartUpdateChangeAction(constructorInfo, updateInfo, containingClass);
         if (maybeStartUpdateAction.ErrorText is not null)
+        {
             return Result<(NamedSourceCode, NamedSourceCode)>.Error
                 (maybeStartUpdateAction.ErrorText, maybeStartUpdateAction.SyntaxTree!, (TextSpan)maybeStartUpdateAction.Span!);
+        }
 
         var endAction = Helpers.CreateEndChangeAction(constructorInfo);
 

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changeables/IChangeable.cs

@@ -3,4 +3,4 @@
 internal interface IChangeable
 {
 
-};
+}

+ 2 - 1
src/PixiEditor.ChangeableDocument/Changeables/Interfaces/IReadOnlyDocument.cs

@@ -4,7 +4,8 @@ using PixiEditor.DrawingApi.Core.Numerics;
 namespace PixiEditor.ChangeableDocument.Changeables.Interfaces;
 
 public interface IReadOnlyDocument
-{    /// <summary>
+{    
+    /// <summary>
     /// The root folder of the document
     /// </summary>
     IReadOnlyFolder StructureRoot { get; }

+ 1 - 1
src/PixiEditor.ChangeableDocument/Changes/Change.cs

@@ -32,4 +32,4 @@ internal abstract class Change : IDisposable
     public abstract OneOf<None, IChangeInfo, List<IChangeInfo>> Revert(Document target);
 
     public virtual void Dispose() { }
-};
+}

+ 18 - 0
src/PixiEditor/Helpers/Converters/BoolToHiddenVisibilityConverter.cs

@@ -0,0 +1,18 @@
+using System.Globalization;
+using System.Windows;
+
+namespace PixiEditor.Helpers.Converters;
+
+internal class BoolToHiddenVisibilityConverter : SingleInstanceConverter<BoolToHiddenVisibilityConverter>
+{
+    public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+    {
+        bool boolean = (bool)value;
+        return boolean ? Visibility.Visible : Visibility.Hidden;
+    }
+
+    public override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+    {
+        throw new NotImplementedException();
+    }
+}

+ 14 - 0
src/PixiEditor/Helpers/Extensions/ColorHelpers.cs

@@ -10,4 +10,18 @@ internal static class ColorHelpers
 
     public static Color ToOpaqueMediaColor(this BackendColor color) => Color.FromRgb(color.R, color.G, color.B);
     public static Color ToColor(this BackendColor color) => Color.FromArgb(color.A, color.R, color.G, color.B);
+    
+    public static BackendColor BlendColors(BackendColor bottomColor, BackendColor topColor)
+    {
+        if (topColor.A is < 255 and > 0)
+        {
+            byte r = (byte)((topColor.R * topColor.A / 255) + (bottomColor.R * bottomColor.A * (255 - topColor.A) / (255 * 255)));
+            byte g = (byte)((topColor.G * topColor.A / 255) + (bottomColor.G * bottomColor.A * (255 - topColor.A) / (255 * 255)));
+            byte b = (byte)((topColor.B * topColor.A / 255) + (bottomColor.B * bottomColor.A * (255 - topColor.A) / (255 * 255)));
+            byte a = (byte)(topColor.A + (bottomColor.A * (255 - topColor.A) / 255));
+            return new BackendColor(r, g, b, a);
+        }
+
+        return topColor.A == 255 ? topColor : bottomColor;
+    }
 }

+ 30 - 7
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/ColorPickerToolExecutor.cs

@@ -1,22 +1,45 @@
-using PixiEditor.Models.Enums;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.Models.Enums;
+using PixiEditor.ViewModels.SubViewModels.Tools.Tools;
 
 namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
 
+#nullable enable
 internal class ColorPickerToolExecutor : UpdateableChangeExecutor
 {
+    private bool includeReference;
+    private bool includeCanvas;
+    private DocumentScope scope;
+    private ColorsViewModel? colorsViewModel;
+
     public override ExecutionState Start()
     {
-        ColorsViewModel colorsViewModel = ViewModelMain.Current?.ColorsSubViewModel;
-        
-        if(document is null || controller is null)
-        {
+        colorsViewModel = ViewModelMain.Current?.ColorsSubViewModel;
+        ColorPickerToolViewModel? tool = ViewModelMain.Current?.ToolsSubViewModel.GetTool<ColorPickerToolViewModel>();
+
+        if (colorsViewModel is null || tool is null)
             return ExecutionState.Error;
-        }
+
+        scope = tool.Mode;
+        includeReference = tool.PickFromReferenceLayer && document!.ReferenceLayerViewModel.ReferenceBitmap is not null;
+        includeCanvas = tool.PickFromCanvas;
         
-        colorsViewModel.PrimaryColor = document.PickColor(controller.LastPixelPosition, false);
+        colorsViewModel.PrimaryColor = document.PickColor(controller.LastPrecisePosition, scope, includeReference, includeCanvas);
         return ExecutionState.Success;
     }
 
+    public override void OnPrecisePositionChange(VecD pos)
+    {
+        if (!includeReference)
+            return;
+        colorsViewModel.PrimaryColor = document.PickColor(pos, scope, includeReference, includeCanvas);
+    }
+
+    public override void OnPixelPositionChange(VecI pos)
+    {
+        colorsViewModel.PrimaryColor = document.PickColor(pos, scope, includeReference, includeCanvas);
+    }
+
     public override void OnLeftMouseButtonUp()
     {
         onEnded?.Invoke(this);

+ 40 - 4
src/PixiEditor/ViewModels/SubViewModels/Document/DocumentViewModel.cs

@@ -1,5 +1,5 @@
-using System.Collections.Immutable;
-using System.IO;
+using System.IO;
+using System.Windows;
 using System.Windows.Media;
 using System.Windows.Media.Imaging;
 using ChunkyImageLib;
@@ -19,6 +19,7 @@ using PixiEditor.Models.Controllers;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.DocumentModels;
 using PixiEditor.Models.DocumentModels.Public;
+using PixiEditor.Models.Enums;
 using PixiEditor.Views.UserControls.SymmetryOverlay;
 using Color = PixiEditor.DrawingApi.Core.ColorsImpl.Color;
 using Colors = PixiEditor.DrawingApi.Core.ColorsImpl.Colors;
@@ -319,14 +320,49 @@ internal partial class DocumentViewModel : NotifyableObject
         return (output, bounds);
     }
 
-    public Color PickColor(VecI pos, bool fromAllLayers)
+    public Color PickColor(VecD pos, DocumentScope scope, bool includeReference, bool includeCanvas)
+    {
+        if (scope == DocumentScope.SingleLayer && includeReference && includeCanvas)
+            includeReference = false;
+        
+        if (includeCanvas && includeReference)
+        {
+            Color canvasColor = PickColorFromCanvas((VecI)pos, scope);
+            Color? referenceColor = PickColorFromReferenceLayer(pos);
+            if (referenceColor is null)
+                return canvasColor;
+            return ColorHelpers.BlendColors((Color)referenceColor, canvasColor);
+        }
+        if (includeCanvas)
+            return PickColorFromCanvas((VecI)pos, scope);
+        if (includeReference)
+            return PickColorFromReferenceLayer(pos) ?? Colors.Transparent;
+        return Colors.Transparent;
+    }
+
+    public Color? PickColorFromReferenceLayer(VecD pos)
+    {
+        WriteableBitmap? bitmap = ReferenceLayerViewModel.ReferenceBitmap; 
+        if (bitmap is null)
+            return null;
+        
+        Matrix matrix = ReferenceLayerViewModel.ReferenceTransformMatrix;
+        matrix.Invert();
+        var transformed = matrix.Transform(new System.Windows.Point(pos.X, pos.Y));
+
+        if (transformed.X < 0 || transformed.Y < 0 || transformed.X >= bitmap.Width || transformed.Y >= bitmap.Height)
+            return null;
+        return bitmap.GetPixel((int)transformed.X, (int)transformed.Y).ToColor();
+    }
+
+    public Color PickColorFromCanvas(VecI pos, DocumentScope scope)
     {
         // there is a tiny chance that the image might get disposed by another thread
         try
         {
             // it might've been a better idea to implement this function asynchronously
             // via a passthrough action to avoid all the try catches
-            if (fromAllLayers)
+            if (scope == DocumentScope.AllLayers)
             {
                 VecI chunkPos = OperationHelper.GetChunkPos(pos, ChunkyImage.FullChunkSize);
                 return ChunkRenderer.MergeWholeStructure(chunkPos, ChunkResolution.Full, Internals.Tracker.Document.StructureRoot)

+ 7 - 6
src/PixiEditor/ViewModels/SubViewModels/Tools/ToolSettings/Settings/EnumSetting.cs

@@ -31,16 +31,17 @@ internal sealed class EnumSetting<TEnum> : Setting<TEnum, ComboBox>
     /// </summary>
     public override TEnum Value
     {
-        get => (TEnum)(SettingControl.SelectedItem as ComboBoxItem).Tag;
+        get => Enum.GetValues<TEnum>()[SelectedIndex];
         set
         {
-            for (var i = 0; i < SettingControl.Items.Count; i++)
-            {
-                var item = SettingControl.Items[i] as ComboBoxItem;
+            var values = Enum.GetValues<TEnum>();
 
-                if (item.Tag.Equals(value))
+            for (var i = 0; i < values.Length; i++)
+            {
+                if (values[i].Equals(value))
                 {
                     SelectedIndex = i;
+                    break;
                 }
             }
 
@@ -62,7 +63,7 @@ internal sealed class EnumSetting<TEnum> : Setting<TEnum, ComboBox>
     public EnumSetting(string name, string label, TEnum defaultValue)
         : this(name, label)
     {
-        base.Value = defaultValue;
+        Value = defaultValue;
     }
 
     private static ComboBox GenerateDropdown()

+ 0 - 0
src/PixiEditor/ViewModels/SubViewModels/Tools/Tool.cs → src/PixiEditor/ViewModels/SubViewModels/Tools/ToolViewModel.cs


+ 32 - 20
src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/ColorPickerToolViewModel.cs

@@ -2,6 +2,8 @@
 using ChunkyImageLib.DataHolders;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.Models.Commands.Attributes.Commands;
+using PixiEditor.Models.Enums;
+using PixiEditor.ViewModels.SubViewModels.Tools.ToolSettings.Toolbars;
 using PixiEditor.Views.UserControls.BrushShapeOverlay;
 
 namespace PixiEditor.ViewModels.SubViewModels.Tools.Tools;
@@ -11,17 +13,35 @@ internal class ColorPickerToolViewModel : ToolViewModel
 {
     private readonly string defaultActionDisplay = "Click to pick colors. Hold Ctrl to hide the canvas. Hold Shift to hide the reference layer";
 
-    public ColorPickerToolViewModel()
-    {
-        ActionDisplay = defaultActionDisplay;
-    }
-
     public override bool HideHighlight => true;
 
     public override BrushShape BrushShape => BrushShape.Pixel;
 
     public override string Tooltip => $"Picks the primary color from the canvas. ({Shortcut})";
 
+    private bool pickFromCanvas = true;
+    public bool PickFromCanvas
+    {
+        get => pickFromCanvas; 
+        private set => SetProperty(ref pickFromCanvas, value);
+    }
+    
+    private bool pickFromReferenceLayer = true;
+    public bool PickFromReferenceLayer
+    {
+        get => pickFromReferenceLayer; 
+        private set => SetProperty(ref pickFromReferenceLayer, value);
+    }
+
+    [Settings.Enum("Scope", DocumentScope.AllLayers)]
+    public DocumentScope Mode => GetValue<DocumentScope>();
+
+    public ColorPickerToolViewModel()
+    {
+        ActionDisplay = defaultActionDisplay;
+        Toolbar = ToolbarFactory.Create<ColorPickerToolViewModel, EmptyToolbar>();
+    }
+
     public override void OnLeftMouseButtonDown(VecD pos)
     {
         ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument?.Tools.UseColorPickerTool();
@@ -29,32 +49,24 @@ internal class ColorPickerToolViewModel : ToolViewModel
 
     public override void UpdateActionDisplay(bool ctrlIsDown, bool shiftIsDown, bool altIsDown)
     {
-        /*
-        if (!IsActive)
-        {
-            _bitmapManager.HideReferenceLayer = false;
-            _bitmapManager.OnlyReferenceLayer = false;
-            return;
-        }
-
         if (ctrlIsDown)
         {
-            _bitmapManager.HideReferenceLayer = false;
-            _bitmapManager.OnlyReferenceLayer = true;
+            PickFromCanvas = false;
+            PickFromReferenceLayer = true;
             ActionDisplay = "Click to pick colors from the reference layer.";
         }
         else if (shiftIsDown)
         {
-            _bitmapManager.HideReferenceLayer = true;
-            _bitmapManager.OnlyReferenceLayer = false;
+            PickFromCanvas = true;
+            PickFromReferenceLayer = false;
             ActionDisplay = "Click to pick colors from the canvas.";
             return;
         }
         else
         {
-            _bitmapManager.HideReferenceLayer = false;
-            _bitmapManager.OnlyReferenceLayer = false;
+            PickFromCanvas = true;
+            PickFromReferenceLayer = true;
             ActionDisplay = defaultActionDisplay;
-        }*/
+        }
     }
 }

+ 15 - 0
src/PixiEditor/ViewModels/ToolVM.cs

@@ -0,0 +1,15 @@
+using System.Windows.Markup;
+
+namespace PixiEditor.ViewModels;
+
+internal class ToolVM : MarkupExtension
+{
+    public string TypeName { get; set; }
+
+    public ToolVM(string typeName) => TypeName = typeName;
+
+    public override object ProvideValue(IServiceProvider serviceProvider)
+    {
+        return ViewModelMain.Current?.ToolsSubViewModel.ToolSet?.Where(tool => tool.GetType().Name == TypeName).FirstOrDefault();
+    }
+}

+ 92 - 95
src/PixiEditor/Views/UserControls/Layers/ReferenceLayer.xaml

@@ -16,48 +16,110 @@
         <i:Interaction.Behaviors>
             <behaviors:ClearFocusOnClickBehavior/>
         </i:Interaction.Behaviors>
-        <Grid>
-            <Grid Background="Transparent"/>
-            <Grid Grid.Row="0"  VerticalAlignment="Center">
-                <Grid.ColumnDefinitions>
-                    <ColumnDefinition Width="30"/>
-                    <ColumnDefinition Width="*"/>
-                </Grid.ColumnDefinitions>
-                <Grid Visibility="{Binding Document.ReferenceLayerViewModel.ReferenceBitmap, ElementName=uc, Converter={converters:NullToVisibilityConverter}}"  Grid.ColumnSpan="2" Grid.RowSpan="2" Panel.ZIndex="5">
-                    <Grid Cursor="Hand" Visibility="{Binding ElementName=visibilityCheckbox, Path=IsChecked, Converter={InverseBoolToVisibilityConverter}}"  Background="Transparent">
+        <DockPanel Background="Transparent">
+            <CheckBox Focusable="False" Panel.ZIndex="10" Name="visibilityCheckbox" Margin="0,0,5,0" Height="16" HorizontalAlignment="Right" DockPanel.Dock="Right">
+                <CheckBox.Triggers>
+                    <EventTrigger RoutedEvent="CheckBox.Checked">
+                        <BeginStoryboard>
+                            <Storyboard>
+                                <DoubleAnimation Storyboard.TargetName="mainDockPanel" Storyboard.TargetProperty="Height" From="40" To="0" Duration="0:0:0.15"/>
+                            </Storyboard>
+                        </BeginStoryboard>
+                    </EventTrigger>
+                    <EventTrigger RoutedEvent="CheckBox.Unchecked">
+                        <BeginStoryboard>
+                            <Storyboard>
+                                <DoubleAnimation Storyboard.TargetName="mainDockPanel" Storyboard.TargetProperty="Height" From="0" To="40" Duration="0:0:0.15"/>
+                            </Storyboard>
+                        </BeginStoryboard>
+                    </EventTrigger>
+
+                </CheckBox.Triggers>
+                <CheckBox.Template>
+                    <ControlTemplate TargetType="{x:Type CheckBox}">
+                        <StackPanel Orientation="Horizontal" Focusable="False">
+                            <Image Focusable="False" Width="14" Cursor="Hand" x:Name="checkboxImage" Source="/Images/ChevronDown.png">
+                                <Image.RenderTransform>
+                                    <RotateTransform Angle="0"/>
+                                </Image.RenderTransform>
+                            </Image>
+                            <ContentPresenter Focusable="False"/>
+                        </StackPanel>
+                        <ControlTemplate.Triggers>
+                            <Trigger Property="IsChecked" Value="True">
+                                <Setter TargetName="checkboxImage" Property="RenderTransform">
+                                    <Setter.Value>
+                                        <RotateTransform Angle="180" CenterX="7" CenterY="4"/>
+                                    </Setter.Value>
+                                </Setter>
+                            </Trigger>
+                        </ControlTemplate.Triggers>
+                    </ControlTemplate>
+                </CheckBox.Template>
+            </CheckBox>
+
+            <Grid Height="40" x:Name="mainDockPanel">
+                <Grid 
+                    Visibility="{Binding Document.ReferenceLayerViewModel.ReferenceBitmap, ElementName=uc, Converter={converters:NullToVisibilityConverter}}" 
+                    Panel.ZIndex="5">
+                    <Grid Cursor="Hand" Visibility="{Binding ElementName=visibilityCheckbox, Path=IsChecked, Converter={InverseBoolToVisibilityConverter}}" Background="Transparent">
+                        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" DockPanel.Dock="Left">
+                            <Image Margin="5 0 5 0" Width="20" Source="/Images/Layer-add.png"
+                               Visibility="{Binding Document.ReferenceLayerViewModel.ReferenceBitmap, ElementName=uc, Converter={converters:NullToVisibilityConverter}}"/>
+
+                            <TextBlock IsEnabled="{Binding ElementName=uc, Path=IsEnabled}" 
+                                        Margin="0 0 5 0" Foreground="White" 
+                                        FontSize="15" VerticalAlignment="Center">Add Reference Layer</TextBlock>
+                        </StackPanel>
                         <i:Interaction.Triggers>
                             <i:EventTrigger EventName="MouseUp">
                                 <i:InvokeCommandAction Command="{cmds:Command PixiEditor.Layer.ImportReferenceLayer}"
-                                    PassEventArgsToCommand="True"/>
+                                        PassEventArgsToCommand="True"/>
                             </i:EventTrigger>
                         </i:Interaction.Triggers>
                     </Grid>
                 </Grid>
-                <Grid Grid.Column="0" Height="16" Name="layerVisibilityCheckboxGrid">
-                    <CheckBox 
-                        Visibility="{Binding Document.ReferenceLayerViewModel.ReferenceBitmap, 
-                                             ElementName=uc, 
-                                             Converter={converters:NotNullToVisibilityConverter}}"
-                        Style="{StaticResource ImageCheckBox}" VerticalAlignment="Center"
-                        IsThreeState="False" HorizontalAlignment="Center" 
-                        IsChecked="{Binding Path=Document.ReferenceLayerViewModel.IsVisibleBindable, Mode=TwoWay, ElementName=uc}"/>
-                </Grid>
-                <StackPanel Name="middleStackPanel" Height="40" Orientation="Horizontal" Grid.Column="1" HorizontalAlignment="Left">
-                    <Button Cursor="Hand" Grid.Column="1"
+
+                <DockPanel Grid.Row="0" VerticalAlignment="Center" Height="40"
+                           Visibility="{Binding Document.ReferenceLayerViewModel.ReferenceBitmap, ElementName=uc, Converter={converters:NotNullToVisibilityConverter}}" >
+                    <Grid Height="16" Name="layerVisibilityCheckboxGrid" DockPanel.Dock="Left" Margin="10,0,5,0">
+                        <CheckBox 
+                            Style="{StaticResource ImageCheckBox}" VerticalAlignment="Center"
+                            IsThreeState="False" HorizontalAlignment="Center" 
+                            IsChecked="{Binding Path=Document.ReferenceLayerViewModel.IsVisibleBindable, Mode=TwoWay, ElementName=uc}"/>
+                    </Grid>
+                    <Border 
+                        HorizontalAlignment="Left" DockPanel.Dock="Left"
+                        Width="30" Height="30"
+                        BorderThickness="1" 
+                        BorderBrush="Black"
+                        Background="{StaticResource MainColor}"
+                        Margin="5, 0, 10, 0">
+                        <Image Source="{Binding Document.ReferenceLayerViewModel.ReferenceBitmap,ElementName=uc}" Stretch="Uniform" Width="26" Height="26"
+                               RenderOptions.BitmapScalingMode="HighQuality" IsHitTestVisible="False"/>
+                    </Border>
+                    <Button Cursor="Hand" Grid.Column="1" DockPanel.Dock="Right"
+                                Command="{cmds:Command PixiEditor.Layer.DeleteReferenceLayer}"
+                                Style="{StaticResource ImageButtonStyle}" 
+                                RenderOptions.BitmapScalingMode="HighQuality"
+                                Margin="3,0,5,0"
+                                Width="20" Height="20" HorizontalAlignment="Right">
+                        <Button.Background>
+                            <ImageBrush ImageSource="/Images/Trash.png"/>
+                        </Button.Background>
+                    </Button>
+                    <Button Cursor="Hand" DockPanel.Dock="Right"
                             Command="{cmds:Command PixiEditor.Layer.ResetReferenceLayerPosition}"
-                            Visibility="{Binding Document.ReferenceLayerViewModel.ReferenceBitmap, ElementName=uc, Converter={BoolToVisibilityConverter}}" 
                             Style="{StaticResource ImageButtonStyle}" 
                             ToolTip="Reset reference layer position"
                             RenderOptions.BitmapScalingMode="HighQuality"
-                            Margin="3"
                             Width="20" Height="20" HorizontalAlignment="Right">
                         <Button.Background>
                             <ImageBrush ImageSource="/Images/Layout.png"/>
                         </Button.Background>
                     </Button>
-                    <Button Cursor="Hand" Grid.Column="1"
+                    <Button Cursor="Hand" DockPanel.Dock="Right"
                             Command="{cmds:Command PixiEditor.Layer.TransformReferenceLayer}"
-                            Visibility="{Binding Document.ReferenceLayerViewModel.ReferenceBitmap, ElementName=uc, Converter={BoolToVisibilityConverter}}" 
                             Style="{StaticResource ImageButtonStyle}" 
                             ToolTip="Transform reference layer"
                             RenderOptions.BitmapScalingMode="HighQuality"
@@ -66,76 +128,11 @@
                             <ImageBrush ImageSource="/Images/Tools/MoveImage.png"/>
                         </Button.Background>
                     </Button>
-                    <Border HorizontalAlignment="Left" 
-                            Visibility="{Binding Document.ReferenceLayerViewModel.ReferenceBitmap, ElementName=uc, Converter={converters:NotNullToVisibilityConverter}}" 
-                            Width="30" Height="30"
-                            BorderThickness="1" BorderBrush="Black"
-                            Background="{StaticResource MainColor}"
-                            Margin="5, 0, 10, 0">
-                        <Image Source="{Binding Document.ReferenceLayerViewModel.ReferenceBitmap,ElementName=uc}" Stretch="Uniform" Width="26" Height="26"
-                           RenderOptions.BitmapScalingMode="HighQuality" IsHitTestVisible="False"/>
-                    </Border>
-                    <Image Margin="30 0 5 0" Width="20" Source="/Images/Layer-add.png"  
-                           Visibility="{Binding Document.ReferenceLayerViewModel.ReferenceBitmap, ElementName=uc, Converter={converters:NullToVisibilityConverter}}"/>
-
-                    <local1:PrependTextBlock IsEnabled="{Binding ElementName=uc, Path=IsEnabled}" 
-                                             Margin="0 0 5 0" Prepend="Add " Foreground="White" 
-                                             HidePrepend="{Binding Document.ReferenceLayerViewModel.ReferenceBitmap, ElementName=uc, Converter={converters:NotNullToBoolConverter}}"
-                                             FontSize="15" VerticalAlignment="Center" Text="Reference Layer" />
-                    <Button Cursor="Hand" Grid.Column="1" 
-                            Command="{cmds:Command PixiEditor.Layer.DeleteReferenceLayer}"
-                            Visibility="{Binding Document.ReferenceLayerViewModel.ReferenceBitmap, ElementName=uc, Converter={BoolToVisibilityConverter}}" 
-                            Style="{StaticResource ImageButtonStyle}" 
-                            RenderOptions.BitmapScalingMode="HighQuality"
-                            Width="20" Height="20" HorizontalAlignment="Right">
-                        <Button.Background>
-                            <ImageBrush ImageSource="/Images/Trash.png"/>
-                        </Button.Background>
-                    </Button>
-                </StackPanel>
-                <CheckBox Focusable="False" Panel.ZIndex="10" Name="visibilityCheckbox" Grid.Column="1" Margin="0,0,5,0" Height="16" HorizontalAlignment="Right">
-                    <CheckBox.Triggers>
-                        <EventTrigger RoutedEvent="CheckBox.Checked">
-                            <BeginStoryboard>
-                                <Storyboard>
-                                    <DoubleAnimation Storyboard.TargetName="middleStackPanel" Storyboard.TargetProperty="Height" From="40" To="0" Duration="0:0:0.15"/>
-                                    <DoubleAnimation Storyboard.TargetName="layerVisibilityCheckboxGrid" Storyboard.TargetProperty="Height" From="16" To="0" Duration="0:0:0.15"/>
-                                </Storyboard>
-                            </BeginStoryboard>
-                        </EventTrigger>
-                        <EventTrigger RoutedEvent="CheckBox.Unchecked">
-                            <BeginStoryboard>
-                                <Storyboard>
-                                    <DoubleAnimation Storyboard.TargetName="middleStackPanel" Storyboard.TargetProperty="Height" From="0" To="40" Duration="0:0:0.15"/>
-                                    <DoubleAnimation Storyboard.TargetName="layerVisibilityCheckboxGrid" Storyboard.TargetProperty="Height" From="0" To="16" Duration="0:0:0.15"/>
-                                </Storyboard>
-                            </BeginStoryboard>
-                        </EventTrigger>
-
-                    </CheckBox.Triggers>
-                    <CheckBox.Template>
-                        <ControlTemplate TargetType="{x:Type CheckBox}">
-                            <StackPanel Orientation="Horizontal" Focusable="False">
-                                <Image Focusable="False" Width="14" Cursor="Hand" x:Name="checkboxImage" Source="/Images/ChevronDown.png">
-                                    <Image.RenderTransform>
-                                        <RotateTransform Angle="0"/>
-                                    </Image.RenderTransform>
-                                </Image>
-                                <ContentPresenter Focusable="False"/>
-                            </StackPanel>
-                            <ControlTemplate.Triggers>
-                                <Trigger Property="IsChecked" Value="True">
-                                    <Setter TargetName="checkboxImage" Property="RenderTransform">
-                                        <Setter.Value>
-                                            <RotateTransform Angle="180" CenterX="7" CenterY="4"/>
-                                        </Setter.Value>
-                                    </Setter>
-                                </Trigger>
-                            </ControlTemplate.Triggers>
-                        </ControlTemplate>
-                    </CheckBox.Template>
-                </CheckBox>
+                    <TextBlock IsEnabled="{Binding ElementName=uc, Path=IsEnabled}" HorizontalAlignment="Center"
+                                Margin="0 0 5 0" Foreground="White" 
+                                FontSize="15" VerticalAlignment="Center">Reference Layer</TextBlock>
+                </DockPanel>
             </Grid>
-        </Grid>
+        </DockPanel>
     </Border>
 </UserControl>

+ 2 - 1
src/PixiEditor/Views/UserControls/PreviewWindow.xaml.cs

@@ -5,6 +5,7 @@ using System.Windows.Media;
 using BackendColor = PixiEditor.DrawingApi.Core.ColorsImpl.Color;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.ViewModels.SubViewModels.Document;
+using PixiEditor.Models.Enums;
 
 namespace PixiEditor.Views.UserControls;
 
@@ -136,7 +137,7 @@ internal partial class PreviewWindow : UserControl
 
         ColorCursorPosition = newPos;
 
-        BackendColor color = Document.PickColor(new(x, y), true);
+        BackendColor color = Document.PickColor(new(x, y), DocumentScope.AllLayers, false, true);
         ColorCursorColor = Color.FromArgb(color.A, color.R, color.G, color.B);
     }
 }

+ 3 - 2
src/PixiEditor/Views/UserControls/Viewport.xaml

@@ -147,12 +147,12 @@
                     </ImageBrush>
                 </Border.Background>
                 <Grid>
-                    <Canvas>
+                    <Canvas Visibility="{Binding Source={vm:ToolVM ColorPickerToolViewModel}, Path=PickFromReferenceLayer, Converter={converters:BoolToVisibilityConverter}}">
                         <Image
                             Width="{Binding Document.ReferenceLayerViewModel.ReferenceBitmap.Width}"
                             Height="{Binding Document.ReferenceLayerViewModel.ReferenceBitmap.Height}"
                             Source="{Binding Document.ReferenceLayerViewModel.ReferenceBitmap, Mode=OneWay}"
-                            Visibility="{Binding Document.ReferenceLayerViewModel.IsVisibleBindable, Converter={converters:BoolToVisibilityConverter}}"
+                            Visibility="{Binding Document.ReferenceLayerViewModel.IsVisibleBindable, Converter={converters:BoolToHiddenVisibilityConverter}}"
                             SizeChanged="OnReferenceImageSizeChanged"
                             RenderOptions.BitmapScalingMode="{Binding ReferenceLayerScale, Converter={converters:ScaleToBitmapScalingModeConverter}}">
                             <Image.RenderTransform>
@@ -168,6 +168,7 @@
                         Width="{Binding Document.Width}"
                         Height="{Binding Document.Height}"
                         Source="{Binding TargetBitmap}"
+                        Visibility="{Binding Source={vm:ToolVM ColorPickerToolViewModel}, Path=PickFromCanvas, Converter={converters:BoolToHiddenVisibilityConverter}}"
                         RenderOptions.BitmapScalingMode="{Binding Zoombox.Scale, Converter={converters:ScaleToBitmapScalingModeConverter}}"/>
                     <sym:SymmetryOverlay
                         IsHitTestVisible="{Binding ZoomMode, Converter={converters:ZoomModeToHitTestVisibleConverter}}"