Skip to content

Feature/color picker pixel ruler - #95

Merged
dimitar-radenkov merged 4 commits into
masterfrom
feature/color-picker-pixel-ruler
May 3, 2026
Merged

Feature/color picker pixel ruler#95
dimitar-radenkov merged 4 commits into
masterfrom
feature/color-picker-pixel-ruler

Conversation

@dimitar-radenkov

Copy link
Copy Markdown
Owner

No description provided.

dimitar-radenkov and others added 3 commits May 3, 2026 11:48
Color Picker: hover loupe shows 11x11 magnified region with hex label;
click samples the pixel from the background capture, sets ActiveColor,
copies hex to clipboard, shows toast, and reverts to previous tool.

Pixel Ruler: drag to draw a line with adaptive tick marks and a live
pixel-distance label; committed as a single undoable canvas element.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Incremented version.json from 5.5 to 5.6 to prepare for the next release. No other changes were made.
Copilot AI review requested due to automatic review settings May 3, 2026 08:55
@codecov-commenter

codecov-commenter commented May 3, 2026

Copy link
Copy Markdown

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds two new annotation-time utilities to the overlay workflow: a color picker with a magnified loupe/hex copy flow, and a pixel ruler annotation for measuring on-screen distances. It extends the existing annotation infrastructure so these tools fit into the same renderer, interaction controller, and toolbar system used by the rest of Pointframe.

Changes:

  • Add ColorPicker and PixelRuler tools to the overlay annotation toolbar and tool model.
  • Extend the renderer/controller pipeline to support pixel sampling, loupe cropping, and ruler rendering.
  • Bump the application version to 5.6.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
version.json Bumps the app version for this feature release.
Pointframe/ViewModels/OverlayViewModel.cs Removes duplicate DPI properties so overlay state relies on the shared annotation model.
Pointframe/ViewModels/AnnotationViewModel.cs Adds shared DPI fields, previous-tool tracking, and pixel-ruler shape parameter creation.
Pointframe/Services/Handlers/PixelRulerShapeHandler.cs Introduces the new ruler drawing handler with ticks, endpoints, and label rendering.
Pointframe/Services/Handlers/ColorPickerShapeHandler.cs Adds a no-op handler placeholder for the color picker tool.
Pointframe/Services/AnnotationCanvasRenderer.cs Adds background format conversion, pixel sampling/loupe cropping helpers, and registers new tool handlers.
Pointframe/Services/AnnotationCanvasInteractionController.cs Adds color-picker click handling and loupe-position updates during pointer movement.
Pointframe/OverlayWindow.xaml.cs Wires overlay callbacks for color picking, loupe updates, pointer movement, and Escape behavior.
Pointframe/OverlayWindow.xaml Adds toolbar buttons for the new tools and the color-picker loupe UI.
Pointframe/OverlayWindow.ColorPicker.cs Implements loupe positioning/update logic and toolbar resync helper methods.
Pointframe/Models/ShapeParameters.cs Defines the new PixelRulerShapeParameters record.
Pointframe/AnnotationTool.cs Extends the annotation tool enum with ColorPicker and PixelRuler.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +42 to +43
_onColorPicked?.Invoke(color ?? Colors.Transparent, point);
_viewModel.RevertToPreviousTool();
Comment on lines +46 to +47
var px = Math.Clamp((int)Math.Round(dipPoint.X * _dpiX), 0, _backgroundCapture.PixelWidth - 1);
var py = Math.Clamp((int)Math.Round(dipPoint.Y * _dpiY), 0, _backgroundCapture.PixelHeight - 1);
Comment on lines +62 to +70
var x = Math.Max(0, cx - halfPixels);
var y = Math.Max(0, cy - halfPixels);
var w = Math.Min(halfPixels * 2 + 1, _backgroundCapture.PixelWidth - x);
var h = Math.Min(halfPixels * 2 + 1, _backgroundCapture.PixelHeight - y);
if (w <= 0 || h <= 0)
{
return null;
}

Comment on lines +353 to +356
<RadioButton Style="{StaticResource ToolBtn}"
GroupName="AnnotTool"
Tag="ColorPicker" Click="Tool_Click" ToolTip="Color Picker"
AutomationProperties.AutomationId="OverlayWindow.Tool.ColorPicker">
Comment on lines +369 to +372
<RadioButton Style="{StaticResource ToolBtn}"
GroupName="AnnotTool"
Tag="PixelRuler" Click="Tool_Click" ToolTip="Pixel Ruler"
AutomationProperties.AutomationId="OverlayWindow.Tool.PixelRuler">
return;
}

_container.Children.Clear();
Comment on lines +179 to +185
AnnotationTool.PixelRuler => new PixelRulerShapeParameters(
P1: DragStart,
P2: DragCurrent,
Color: color,
Thickness: thick,
DpiX: DpiX,
DpiY: DpiY),
Comment thread Pointframe/OverlayWindow.ColorPicker.cs Outdated
Comment on lines +35 to +49
// Position loupe near cursor, flip if near an edge
const double Offset = 18;
const double LoupeW = 120;
const double LoupeH = 140;

var lx = dipPoint.Value.X + Offset;
var ly = dipPoint.Value.Y - LoupeH - Offset;
if (ly < 0)
{
ly = dipPoint.Value.Y + Offset;
}

if (lx + LoupeW > ActualWidth)
{
lx = dipPoint.Value.X - LoupeW - Offset;
Comment thread Pointframe/OverlayWindow.xaml.cs Outdated
_vm.RevertToPreviousTool();
SyncToolbarToSelectedTool();
UpdateLoupe(null);
AnnotationCanvas.Cursor = Cursors.Cross;
Comment on lines +39 to +74
public Color? SamplePixelColor(Point dipPoint)
{
if (_backgroundCapture is null)
{
return null;
}

var px = Math.Clamp((int)Math.Round(dipPoint.X * _dpiX), 0, _backgroundCapture.PixelWidth - 1);
var py = Math.Clamp((int)Math.Round(dipPoint.Y * _dpiY), 0, _backgroundCapture.PixelHeight - 1);
var bytes = new byte[4];
_backgroundCapture.CopyPixels(new Int32Rect(px, py, 1, 1), bytes, 4, 0);
return Color.FromRgb(bytes[2], bytes[1], bytes[0]); // BGRA → RGB
}

public BitmapSource? CropLoupeRegion(Point dipCenter, int halfPixels)
{
if (_backgroundCapture is null)
{
return null;
}

var cx = (int)Math.Round(dipCenter.X * _dpiX);
var cy = (int)Math.Round(dipCenter.Y * _dpiY);
var x = Math.Max(0, cx - halfPixels);
var y = Math.Max(0, cy - halfPixels);
var w = Math.Min(halfPixels * 2 + 1, _backgroundCapture.PixelWidth - x);
var h = Math.Min(halfPixels * 2 + 1, _backgroundCapture.PixelHeight - y);
if (w <= 0 || h <= 0)
{
return null;
}

var crop = new CroppedBitmap(_backgroundCapture, new Int32Rect(x, y, w, h));
crop.Freeze();
return crop;
}
@dimitar-radenkov
dimitar-radenkov merged commit 208b446 into master May 3, 2026
5 checks passed
@dimitar-radenkov
dimitar-radenkov deleted the feature/color-picker-pixel-ruler branch May 3, 2026 11:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants