Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions MagickCrop-Package/MagickCrop-Package.wapproj
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
<TargetPlatformVersion>10.0.26100.0</TargetPlatformVersion>
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
<SupportedOSPlatformVersion>10.0.19041.0</SupportedOSPlatformVersion>
<AssetTargetFallback>net9.0-windows$(TargetPlatformVersion);$(AssetTargetFallback)</AssetTargetFallback>
<AssetTargetFallback>net10.0-windows$(TargetPlatformVersion);$(AssetTargetFallback)</AssetTargetFallback>
<DefaultLanguage>en-US</DefaultLanguage>
<AppxPackageSigningEnabled>false</AppxPackageSigningEnabled>
<NoWarn>$(NoWarn);NU1702</NoWarn>
Expand Down Expand Up @@ -162,7 +162,8 @@
<None Include="Package.StoreAssociation.xml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4948" PrivateAssets="all" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.7175" PrivateAssets="all" />
<PackageReference Include="System.Private.Uri" Version="4.3.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MagickCrop\MagickCrop.csproj" />
Expand Down
82 changes: 82 additions & 0 deletions MagickCrop/Controls/PixelPrecisionZoom.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<UserControl
x:Class="MagickCrop.Controls.PixelPrecisionZoom"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Width="152"
Height="152"
IsHitTestVisible="False"
mc:Ignorable="d">
<Grid>
<Border
Background="#CC000000"
BorderBrush="#0066FF"
BorderThickness="3"
CornerRadius="75">
<Grid>
<Grid.Clip>
<EllipseGeometry
Center="73,73"
RadiusX="73"
RadiusY="73" />
</Grid.Clip>
<Viewbox Stretch="Fill">
<Canvas
x:Name="ZoomCanvas"
Width="150"
Height="150"
ClipToBounds="True">
<Image x:Name="ZoomImage" Stretch="None" />
</Canvas>
</Viewbox>

<!-- Crosshair overlay -->
<Canvas>
<!-- Vertical line -->
<Line
Opacity="0.8"
Stroke="Red"
StrokeThickness="1"
X1="75"
X2="75"
Y1="0"
Y2="150" />
<!-- Horizontal line -->
<Line
Opacity="0.8"
Stroke="Red"
StrokeThickness="1"
X1="0"
X2="150"
Y1="75"
Y2="75" />
<!-- Center dot -->
<Ellipse
Canvas.Left="73"
Canvas.Top="73"
Width="4"
Height="4"
Fill="Red"
Opacity="0.9" />
</Canvas>

<!-- Pixel coordinate display -->
<Border
Margin="0,0,0,5"
Padding="4,2"
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Background="#CC000000"
CornerRadius="3">
<TextBlock
x:Name="CoordinateText"
FontSize="10"
FontWeight="Bold"
Foreground="White"
Text="X: 0, Y: 0" />
</Border>
</Grid>
</Border>
</Grid>
</UserControl>
135 changes: 135 additions & 0 deletions MagickCrop/Controls/PixelPrecisionZoom.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace MagickCrop.Controls;

/// <summary>
/// Pixel precision zoom control that displays a magnified view of the image at the cursor position.
/// Provides visual feedback for precise point placement similar to PowerToys Color Picker.
/// </summary>
public partial class PixelPrecisionZoom : UserControl
{
private const double DefaultZoomFactor = 6.0;
private const int DefaultPreviewSize = 150;

/// <summary>
/// Gets or sets the zoom magnification factor.
/// </summary>
public double ZoomFactor { get; set; } = DefaultZoomFactor;

/// <summary>
/// Gets or sets the source image to magnify.
/// </summary>
public ImageSource? SourceImage
{
get => sourceImage;
set
{
sourceImage = value;
UpdateZoomPreview();
}
}
private ImageSource? sourceImage;

/// <summary>
/// Gets or sets the current mouse position in image coordinates.
/// </summary>
public Point CurrentPosition
{
get => currentPosition;
set
{
currentPosition = value;
UpdateZoomPreview();
UpdateCoordinateDisplay();
}
}
private Point currentPosition;

public PixelPrecisionZoom()
{
InitializeComponent();
}

/// <summary>
/// Updates the zoom preview to show the magnified region around the current position.
/// </summary>
private void UpdateZoomPreview()
{
if (sourceImage == null)
return;

try
{
// Create a RenderTargetBitmap to capture the source image
if (sourceImage is BitmapSource bitmapSource)
{
// Calculate the region to capture (centered on current position)
double captureWidth = DefaultPreviewSize / ZoomFactor;
double captureHeight = DefaultPreviewSize / ZoomFactor;

// Create a cropped version of the source
Int32Rect sourceRect = new(
(int)Math.Max(0, currentPosition.X - captureWidth / 2),
(int)Math.Max(0, currentPosition.Y - captureHeight / 2),
(int)Math.Min(captureWidth, bitmapSource.PixelWidth - (currentPosition.X - captureWidth / 2)),
(int)Math.Min(captureHeight, bitmapSource.PixelHeight - (currentPosition.Y - captureHeight / 2))
);

// Ensure valid rectangle
if (sourceRect.Width > 0 && sourceRect.Height > 0 &&
sourceRect.X >= 0 && sourceRect.Y >= 0 &&
sourceRect.X + sourceRect.Width <= bitmapSource.PixelWidth &&
sourceRect.Y + sourceRect.Height <= bitmapSource.PixelHeight)
{
CroppedBitmap croppedBitmap = new(bitmapSource, sourceRect);

// Apply scaling transform
TransformedBitmap transformedBitmap = new(croppedBitmap, new ScaleTransform(ZoomFactor, ZoomFactor));

ZoomImage.Source = transformedBitmap;
}
}
}
catch (Exception)
{
// Silently handle any rendering errors
}
}

/// <summary>
/// Updates the coordinate display with the current position.
/// </summary>
private void UpdateCoordinateDisplay()
{
CoordinateText.Text = $"X: {(int)currentPosition.X}, Y: {(int)currentPosition.Y}";
}

/// <summary>
/// Positions the zoom control near the cursor position without blocking the view.
/// </summary>
/// <param name="cursorPosition">The cursor position in parent coordinates</param>
/// <param name="parentWidth">Width of the parent container</param>
/// <param name="parentHeight">Height of the parent container</param>
public void PositionNearCursor(Point cursorPosition, double parentWidth, double parentHeight)
{
// Offset from cursor to avoid blocking the view
double offsetX = 40;
double offsetY = 40;

double left = cursorPosition.X + offsetX;
double top = cursorPosition.Y - Height - offsetY;

// Keep within parent bounds
if (left + Width > parentWidth)
left = cursorPosition.X - Width - offsetX;

if (top < 0)
top = cursorPosition.Y + offsetY;

// Use Margin for positioning since control is in a Grid, not a Canvas
Margin = new Thickness(left, top, 0, 0);
}
}
Loading
Loading