using System.Collections.Generic; using System.Windows; using System.Windows.Media; using Kniip.Editor.Annotations; namespace Kniip.Editor; /// /// Tekent het knipsel met alle annotaties, plus de selectie en het bijsnijkader. /// Het element is precies zo groot als de afbeelding in pixels; zoomen gebeurt met een LayoutTransform erboven. /// public sealed class AnnotationSurface : FrameworkElement { private static readonly Brush AccentBrush = RenderHelpers.Freeze(new SolidColorBrush(Palette.Accent)); private static readonly Brush GhostHandleBrush = RenderHelpers.Freeze(new SolidColorBrush(Color.FromArgb(170, 255, 255, 255))); private static readonly Brush CropShadeBrush = RenderHelpers.Freeze(new SolidColorBrush(Color.FromArgb(120, 0, 0, 0))); public EditorDocument? Document { get; set; } public Annotation? Selected { get; set; } public Rect? CropRect { get; set; } /// Schaal van de weergave (DIP per afbeeldingspixel), om grepen altijd even groot te tonen. public double ViewScale { get; set; } = 1; public double HandleSize => 9 / ViewScale; public AnnotationSurface() { Focusable = true; FocusVisualStyle = null; ClipToBounds = true; } protected override Size MeasureOverride(Size availableSize) => Document == null ? new Size(0, 0) : new Size(Document.Width, Document.Height); protected override void OnRender(DrawingContext dc) { if (Document == null) { return; } DocumentRenderer.Render(dc, Document); if (Selected != null && !Selected.IsHidden && Document.Items.Contains(Selected)) { DrawSelection(dc, Selected); } if (CropRect is Rect crop) { var full = new RectangleGeometry(new Rect(0, 0, Document.Width, Document.Height)); var shade = Geometry.Combine(full, new RectangleGeometry(crop), GeometryCombineMode.Exclude, null); dc.DrawGeometry(CropShadeBrush, null, shade); dc.DrawRectangle(null, CreateDashedPen(Brushes.White), crop); dc.DrawRectangle(null, CreateDashedPen(AccentBrush, 4), crop); } } private void DrawSelection(DrawingContext dc, Annotation item) { Rect bounds = item.Bounds; double pad = 3 / ViewScale; bounds.Inflate(pad, pad); dc.DrawRectangle(null, CreateDashedPen(AccentBrush), bounds); IReadOnlyList handles = item.GetHandles(); if (handles.Count == 0) { return; } double size = HandleSize; var pen = new Pen(AccentBrush, 1.2 / ViewScale); for (int i = 0; i < handles.Count; i++) { Point h = handles[i]; if (item.IsGhostHandle(i)) { double radius = size * 0.35; dc.DrawEllipse(GhostHandleBrush, pen, h, radius, radius); continue; } dc.DrawRoundedRectangle(Brushes.White, pen, new Rect(h.X - size / 2, h.Y - size / 2, size, size), 1.5 / ViewScale, 1.5 / ViewScale); } } private Pen CreateDashedPen(Brush brush, double offset = 0) => new(brush, 1 / ViewScale) { DashStyle = new DashStyle(new[] { 4.0, 4.0 }, offset) }; public int HitHandle(Annotation item, Point p) { IReadOnlyList handles = item.GetHandles(); double tolerance = HandleSize / 2 + 2 / ViewScale; int ghost = -1; for (int i = handles.Count - 1; i >= 0; i--) { if (System.Math.Abs(handles[i].X - p.X) <= tolerance && System.Math.Abs(handles[i].Y - p.Y) <= tolerance) { if (!item.IsGhostHandle(i)) { return i; } ghost = i; } } return ghost; } }