Back to list
christian289

checking-image-bounds-transform

by christian289

ClaudeCode와 함께하는 .NET 개발 튜토리얼

1🍴 0📅 Jan 25, 2026

SKILL.md


name: checking-image-bounds-transform description: "Checks and clamps mouse coordinates within transformed image bounds in WPF. Use when implementing measurement tools or annotations that should only work inside Pan/Zoom/Rotate transformed images."

WPF Image Bounds Checking (With Transforms)

A pattern for checking if mouse coordinates are within the image area and clamping coordinates to image bounds when Pan, Zoom, Rotate transforms are applied.

1. Problem Scenario

Requirements

  • Measurement tools, annotation tools in image viewers should only work within the image area
  • Need accurate boundary detection even when image is zoomed, panned, or rotated

Complexity

  • Image control position varies based on Stretch="None", HorizontalAlignment="Center" settings
  • When RenderTransform applies Pan, Zoom, Rotate, calculating actual image position becomes complex

2. Solution Pattern

2.1 Image Bounds Check Method

namespace MyApp.Controls;

using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

public class ImageViewer : Control
{
    // DependencyProperties (omitted)
    public ImageSource? ImageSource { get; set; }
    public double ZoomFactor { get; set; } = 1.0;
    public double PanX { get; set; }
    public double PanY { get; set; }
    public double RotationAngle { get; set; }

    /// <summary>
    /// Check if the given point is within the image area.
    /// </summary>
    /// <param name="point">Point in parent container coordinates</param>
    /// <returns>True if within image area</returns>
    public bool IsPointWithinImage(Point point)
    {
        if (ImageSource is null)
            return false;

        // 1. Original image size
        var imageWidth = ImageSource.Width;
        var imageHeight = ImageSource.Height;

        // 2. Transformed image size (with zoom)
        var transformedWidth = imageWidth * ZoomFactor;
        var transformedHeight = imageHeight * ZoomFactor;

        // 3. Viewer center (if image is center-aligned)
        var viewerCenterX = ActualWidth / 2;
        var viewerCenterY = ActualHeight / 2;

        // 4. Image center (with pan)
        var imageCenterX = viewerCenterX + PanX;
        var imageCenterY = viewerCenterY + PanY;

        // 5. Image bounds (without rotation)
        var left = imageCenterX - transformedWidth / 2;
        var top = imageCenterY - transformedHeight / 2;
        var right = imageCenterX + transformedWidth / 2;
        var bottom = imageCenterY + transformedHeight / 2;

        // 6. If rotated, check by inverse-rotating the point
        if (RotationAngle != 0)
        {
            // Inverse-rotate point around image center
            var radians = -RotationAngle * Math.PI / 180;
            var cos = Math.Cos(radians);
            var sin = Math.Sin(radians);

            var dx = point.X - imageCenterX;
            var dy = point.Y - imageCenterY;

            var rotatedX = dx * cos - dy * sin + imageCenterX;
            var rotatedY = dx * sin + dy * cos + imageCenterY;

            return rotatedX >= left && rotatedX <= right &&
                   rotatedY >= top && rotatedY <= bottom;
        }

        return point.X >= left && point.X <= right &&
               point.Y >= top && point.Y <= bottom;
    }
}

2.2 Coordinate Clamping Method

/// <summary>
/// Clamp the given point to be within the image bounds.
/// </summary>
/// <param name="point">Point in parent container coordinates</param>
/// <returns>Point clamped to image bounds</returns>
public Point ClampPointToImage(Point point)
{
    if (ImageSource is null)
        return point;

    // Original image size
    var imageWidth = ImageSource.Width;
    var imageHeight = ImageSource.Height;

    // Transformed image size (with zoom)
    var transformedWidth = imageWidth * ZoomFactor;
    var transformedHeight = imageHeight * ZoomFactor;

    // Viewer center
    var viewerCenterX = ActualWidth / 2;
    var viewerCenterY = ActualHeight / 2;

    // Image center (with pan)
    var imageCenterX = viewerCenterX + PanX;
    var imageCenterY = viewerCenterY + PanY;

    // Image bounds
    var left = imageCenterX - transformedWidth / 2;
    var top = imageCenterY - transformedHeight / 2;
    var right = imageCenterX + transformedWidth / 2;
    var bottom = imageCenterY + transformedHeight / 2;

    // Clamp point within bounds
    var clampedX = Math.Clamp(point.X, left, right);
    var clampedY = Math.Clamp(point.Y, top, bottom);

    return new Point(clampedX, clampedY);
}

3. Usage Example

3.1 Using in Measurement Tool

// Code-behind
private Point _measureStartPoint;
private bool _isDrawingMeasurement;

private void RulerOverlay_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    if (sender is RulerOverlay overlay)
    {
        var clickPoint = e.GetPosition(overlay);

        // Only start measurement within image area
        if (!ImageViewer.IsPointWithinImage(clickPoint))
            return;

        _measureStartPoint = clickPoint;
        _isDrawingMeasurement = true;

        overlay.IsDrawing = true;
        overlay.CurrentStartPoint = _measureStartPoint;
        overlay.CurrentEndPoint = _measureStartPoint;

        overlay.CaptureMouse();
    }
}

private void RulerOverlay_MouseMove(object sender, MouseEventArgs e)
{
    if (_isDrawingMeasurement && sender is RulerOverlay overlay)
    {
        var currentPoint = e.GetPosition(overlay);

        // Clamp end point to image area
        var clampedPoint = ImageViewer.ClampPointToImage(currentPoint);
        overlay.CurrentEndPoint = clampedPoint;
    }
}

private void RulerOverlay_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
    if (_isDrawingMeasurement && sender is RulerOverlay overlay)
    {
        var endPoint = e.GetPosition(overlay);

        // Clamp end point to image area
        endPoint = ImageViewer.ClampPointToImage(endPoint);

        // Complete measurement
        overlay.AddMeasurement(_measureStartPoint, endPoint);

        overlay.IsDrawing = false;
        _isDrawingMeasurement = false;
        overlay.ReleaseMouseCapture();
    }
}

4. Rotation Transform Principle

4.1 Inverse Rotation

When an image is rotated, click coordinates must be transformed to the image's original coordinate system:

Actual click position → Inverse rotate → Check bounds in pre-rotation coordinate system
// Check click position on 45-degree rotated image

// Relative coordinates from image center
var dx = point.X - imageCenterX;
var dy = point.Y - imageCenterY;

// Inverse rotation (-45 degrees)
var radians = -RotationAngle * Math.PI / 180;  // -45 → -0.785 rad
var cos = Math.Cos(radians);  // approx 0.707
var sin = Math.Sin(radians);  // approx -0.707

// Apply rotation matrix
var rotatedX = dx * cos - dy * sin + imageCenterX;
var rotatedY = dx * sin + dy * cos + imageCenterY;

4.2 Visual Explanation

        Rotated Image          After Inverse Rotation

          ◇                      □
         /  \                    |  |
        /    \                   |  |
        \    /       →           |  |
         \  /                    |  |
          ◇                      □

    Transform click coord    Check against rectangle

5. Combining with Other Transforms

5.1 Flip (Horizontal/Vertical)

Flip doesn't affect coordinate checking (bounds remain the same):

// Flip only mirrors content, bounds remain the same
public bool FlipHorizontal { get; set; }
public bool FlipVertical { get; set; }

// Flip is ignored in bounds calculation

5.2 Scale (Zoom In/Out)

Already reflected via ZoomFactor:

var transformedWidth = imageWidth * ZoomFactor;
var transformedHeight = imageHeight * ZoomFactor;

5.3 Pan (Translation)

Reflected in image center coordinates:

var imageCenterX = viewerCenterX + PanX;
var imageCenterY = viewerCenterY + PanY;

6. Consistency with RenderTransform

6.1 Transform Order in XAML

<Image x:Name="PART_Image"
       RenderTransformOrigin="0.5,0.5">
    <Image.RenderTransform>
        <TransformGroup>
            <!-- 1. Flip -->
            <ScaleTransform ScaleX="{Binding FlipHorizontal, ...}"
                            ScaleY="{Binding FlipVertical, ...}" />
            <!-- 2. Rotate -->
            <RotateTransform Angle="{Binding RotationAngle, ...}" />
            <!-- 3. Zoom -->
            <ScaleTransform ScaleX="{Binding ZoomFactor, ...}"
                            ScaleY="{Binding ZoomFactor, ...}" />
            <!-- 4. Pan -->
            <TranslateTransform X="{Binding PanX, ...}"
                                Y="{Binding PanY, ...}" />
        </TransformGroup>
    </Image.RenderTransform>
</Image>

6.2 Order in Bounds Calculation

Apply transforms in the same order in code:

// 1. Apply Zoom to original size
var transformedWidth = imageWidth * ZoomFactor;
var transformedHeight = imageHeight * ZoomFactor;

// 2. Apply Pan to center
var imageCenterX = viewerCenterX + PanX;
var imageCenterY = viewerCenterY + PanY;

// 3. Calculate bounds
var left = imageCenterX - transformedWidth / 2;
// ...

// 4. Apply Rotate (check coordinates via inverse transform)
if (RotationAngle != 0)
{
    // Inverse rotation...
}

7. Checklist

  • IsPointWithinImage(): Check image area before starting click
  • ClampPointToImage(): Clamp coordinates during/after drag
  • Rotation handling: Transform coordinates via inverse rotation before bounds check
  • Transform order consistency: Maintain same order in XAML and code
  • Verify RenderTransformOrigin="0.5,0.5" setting

8. References

Score

Total Score

65/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

+10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

1ヶ月以内に更新

+10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

+5

Reviews

💬

Reviews coming soon