You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WPF中Canvas绘图保存异常:点击生成的绿点位置不正确

问题分析与解决方案

你遇到的核心问题是显示图像与源图像尺寸不匹配导致的坐标缩放偏差:你的Image控件被拉伸到Canvas的实际尺寸显示,但保存时却用源图像的尺寸创建RenderTargetBitmap,将Canvas的内容(包括绿点)强行拉伸到源图像尺寸,最终导致绿点位置错位。

同时你已经通过ConvertWPFPointToImagePoint得到了源图像中的正确点击坐标,我们可以直接利用这个坐标来修正保存逻辑。


推荐解决方案:直接在源图像上绘制绿点(精准无偏差)

这种方法跳过Canvas的渲染,直接基于源图像的像素坐标绘制绿点,完全避免缩放带来的位置误差,同时保证保存图像的质量与源图像一致。

修正后的保存代码

public void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    DrawEllipse();
    // 修正语法错误:补全方法调用的闭合括号
    Point point = Mouse.GetPosition(SelectedPartImage).ConvertWPFPointToImagePoint(
        SelectedPartImageSource.PixelWidth,
        SelectedPartImageSource.PixelHeight,
        SelectedPartImage.ActualWidth,
        SelectedPartImage.ActualHeight);

    SelectPositionSuccess = CheckAlpha((int)point.X, (int)point.Y);
    EllipseClick.Visibility = SelectPositionSuccess ? Visibility.Visible : Visibility.Hidden;

    if (SelectPositionSuccess)
    {
        // 创建DrawingVisual用于绘制源图像和绿点
        DrawingVisual drawingVisual = new DrawingVisual();
        using (DrawingContext dc = drawingVisual.RenderOpen())
        {
            // 1. 绘制原始源图像
            dc.DrawImage(SelectedPartImageSource, 
                new Rect(0, 0, SelectedPartImageSource.PixelWidth, SelectedPartImageSource.PixelHeight));
            
            // 2. 计算绿点在源图像中的尺寸(保持显示时的视觉比例)
            double scaleX = SelectedPartImageSource.PixelWidth / SelectedPartImage.ActualWidth;
            double scaleY = SelectedPartImageSource.PixelHeight / SelectedPartImage.ActualHeight;
            double ellipseRadiusX = (EllipseClick.Width / 2) * scaleX;
            double ellipseRadiusY = (EllipseClick.Height / 2) * scaleY;
            
            // 3. 在源图像的正确像素位置绘制绿点(居中对齐点击坐标)
            dc.DrawEllipse(
                (SolidColorBrush)EllipseClick.Fill, 
                null, 
                new Point(point.X, point.Y), 
                ellipseRadiusX, 
                ellipseRadiusY);
        }

        // 渲染并保存图像
        RenderTargetBitmap rtb = new RenderTargetBitmap(
            SelectedPartImageSource.PixelWidth,
            SelectedPartImageSource.PixelHeight,
            96d, 96d,
            PixelFormats.Default);
        rtb.Render(drawingVisual);

        // 使用using自动释放内存流资源
        using (MemoryStream ms = new MemoryStream())
        {
            PngBitmapEncoder pngEncoder = new PngBitmapEncoder();
            pngEncoder.Frames.Add(BitmapFrame.Create(rtb));
            pngEncoder.Save(ms);
            
            System.IO.File.WriteAllBytes("test.png", ms.ToArray());
        }
    }
}

备选方案:修正Canvas渲染的尺寸匹配(适合保留Canvas其他元素的场景)

如果你需要保留Canvas上的其他元素,可以先渲染Canvas的实际显示尺寸,再将其缩放回源图像尺寸。但这种方法可能会因二次缩放导致轻微的图像质量损失:

if (SelectPositionSuccess)
{
    // 1. 渲染Canvas的实际显示尺寸
    RenderTargetBitmap rtb = new RenderTargetBitmap(
        (int)ImageCanvas.ActualWidth,
        (int)ImageCanvas.ActualHeight,
        96d, 96d,
        PixelFormats.Default);
    rtb.Render(ImageCanvas);

    // 2. 将渲染结果缩放回源图像尺寸
    TransformedBitmap scaledBitmap = new TransformedBitmap(rtb,
        new ScaleTransform(
            SelectedPartImageSource.PixelWidth / ImageCanvas.ActualWidth,
            SelectedPartImageSource.PixelHeight / ImageCanvas.ActualHeight));

    // 3. 保存缩放后的图像
    using (MemoryStream ms = new MemoryStream())
    {
        PngBitmapEncoder pngEncoder = new PngBitmapEncoder();
        pngEncoder.Frames.Add(BitmapFrame.Create(scaledBitmap));
        pngEncoder.Save(ms);
        
        System.IO.File.WriteAllBytes("test.png", ms.ToArray());
    }
}

额外注意事项

  1. 修复了原代码中ConvertWPFPointToImagePoint方法调用的语法错误(缺少闭合括号)。
  2. 改用using语句管理MemoryStream,避免资源泄漏。
  3. 方案一中的绿点尺寸会根据源图像与显示图像的比例自动缩放,保证视觉上的一致性。

内容的提问来源于stack exchange,提问作者Nekedev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 09:34:22