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

UWP在图片上绘制笔迹并保存到数据库时图片缩放异常如何解决?

问题描述

首先声明:我对UWP和C#比较陌生(我之前使用过C和C++,但大部分编码经验集中在Java,且已经有好几年没有规律进行编码了)。我现在想要开发一个简单的应用,实现以下用户功能:

  • 选择一张图片
  • 在选中的图片上进行绘制
  • 将处理后的图片保存到数据库

我参考了多份示例代码,现在已经有了一个接近可用的版本:我可以从文件系统加载图片,在图片上绘制,也能将结果保存到数据库,但保存的图片缩放比例不正确,仅能得到原图的一小部分,就好像只有图片的左上角被裁剪了下来。

现有代码

视图XAML代码

<Page
    x:Class="CadastroPacientes.Views.FotoView"
    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"
    mc:Ignorable="d"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    <Grid>
        <StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
            <Grid>
                <Image x:Name="image" Width="600" Height="600"/>
                <InkCanvas x:Name="ink" Width="600" Height="600"/>
            </Grid>
            <InkToolbar TargetInkCanvas="{x:Bind ink}"/>
            <StackPanel Orientation="Horizontal">
                <Button 
                    Content="Escolher imagem"
                    VerticalAlignment="Top" 
                    Margin="10,10,0,0"
                    Click="EscolherImagem_Click"/>
                <Button 
                    Content="Salvar"
                    Margin="10,10,0,0"
                    VerticalAlignment="Top" 
                    Click="SalvarImagem_Click"/>
            </StackPanel>
        </StackPanel>
    </Grid>
</Page>

后台C#代码

using System;
using System.Collections.Generic;
using Windows.UI.Xaml.Controls;
using Microsoft.Graphics.Canvas;
using Windows.Foundation;
using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.Storage.Streams;
using Windows.UI;
using Windows.UI.Input.Inking;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media.Imaging;
using Windows.UI.Xaml.Navigation;
using Windows.UI.Core;
using CadastroPacientes.Modelo;
using CadastroPacientes.ViewModel;
using System.IO;
using System.Threading.Tasks;
using Windows.Graphics.Imaging;
using CadastroPacientes.Converters;

namespace CadastroPacientes.Views
{
    sealed partial class FotoView : Page
    {
        FotoViewModel ViewModel = new FotoViewModel();

        StorageFile InputFile;
        
        BitmapImage bitmapImage;

        SoftwareBitmap softBM;

        public FotoView()
        {

            this.InitializeComponent();
            ink.InkPresenter.InputDeviceTypes = CoreInputDeviceTypes.Mouse | CoreInputDeviceTypes.Touch;
            var attr = new InkDrawingAttributes();
            attr.Color = Colors.Red;
            attr.IgnorePressure = true;
            attr.PenTip = PenTipShape.Circle;
            attr.Size = new Size(4, 10);
            ink.InkPresenter.UpdateDefaultDrawingAttributes(attr);
        }


        private async void EscolherImagem_Click(object sender, RoutedEventArgs e)
        {
            var picker = new FileOpenPicker();
            picker.ViewMode = PickerViewMode.Thumbnail;
            picker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
            picker.FileTypeFilter.Add(".jpg");
            InputFile = await picker.PickSingleFileAsync();
            if(InputFile == null)
            {
                //the user cancelled the operation
                return;
            }

            // Ensure the stream is disposed once the image is loaded
            using (IRandomAccessStream fileStream = await InputFile.OpenAsync(Windows.Storage.FileAccessMode.Read))
            {
                // Create the decoder from the stream
                BitmapDecoder decoder = await BitmapDecoder.CreateAsync(fileStream);

                // Get the SoftwareBitmap representation of the file
                softBM = await decoder.GetSoftwareBitmapAsync();

                if (softBM.BitmapPixelFormat != BitmapPixelFormat.Bgra8 ||
                    softBM.BitmapAlphaMode == BitmapAlphaMode.Straight)
                {
                    softBM = SoftwareBitmap.Convert(softBM, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
                }



                var source = new SoftwareBitmapSource();

                await source.SetBitmapAsync(softBM);



                // Set the source of the Image control
                image.Source = source;
            }

        }

        private async void SalvarImagem_Click(object sender, RoutedEventArgs e)
        {

            CanvasDevice device = CanvasDevice.GetSharedDevice();
            CanvasRenderTarget renderTarget = new CanvasRenderTarget(device, (int)ink.ActualWidth, (int)ink.ActualHeight, 96);

            using (var ds = renderTarget.CreateDrawingSession())
            {
                ds.Clear(Colors.White);
                ds.DrawImage(CanvasBitmap.CreateFromSoftwareBitmap(ds, softBM));
                // then draw contents of your ink canvas over it
                ds.DrawInk(ink.InkPresenter.StrokeContainer.GetStrokes());
            }

            // save results
            IRandomAccessStream stream = new InMemoryRandomAccessStream(); 
            await renderTarget.SaveAsync(stream, CanvasBitmapFileFormat.Jpeg);
            var readStream = stream.AsStreamForRead();
            var byteArray = new byte[readStream.Length];
            readStream.Read(byteArray, 0, byteArray.Length);
            ViewModel.salvarFoto(byteArray, InputFile.Name);
            
            this.Frame.Navigate(typeof(ConsultaView), this.ViewModel.Consulta);
        }

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            if (e.Parameter != null && e.Parameter is ConsultaViewModel)
            {
                ConsultaViewModel cvm = (ConsultaViewModel)e.Parameter;
                this.ViewModel.Consulta = cvm.Model;
                this.ViewModel.foto = cvm.SelectedFoto;

                if(this.ViewModel.foto != null)
                {
                    this.LoadImage();
                }
            }
        }

        private async void LoadImage()
        {
            //image selected for edition
            ByteToBitmapImageConverter converter = new ByteToBitmapImageConverter();
            BitmapImage selectedImage = await converter.ConvertByteToImage(this.ViewModel.foto.Image);
            // Set the source of the Image control
            image.Source = selectedImage;
        }
    }
}

我认为我需要给SoftwareBitmap对象设置类似bitmapImage.DecodePixelHeight = (int)ink.Height的参数,但我不知道该怎么实现。另外如果我可以直接把BitmapImage绘制到DrawingSession里,也能解决这个问题。


解决方案

裁剪问题的根源是:你的Image和InkCanvas控件固定为600x600,加载原图时会自动缩放适配控件尺寸,但保存时你直接把原始尺寸的SoftwareBitmap画到600x600的RenderTarget上,没有做缩放处理,自然只会显示原图左上角的部分。

你只需要修改SalvarImagem_Click方法里绘制图片的逻辑,指定缩放后的目标绘制区域即可:

private async void SalvarImagem_Click(object sender, RoutedEventArgs e)
{
    CanvasDevice device = CanvasDevice.GetSharedDevice();
    CanvasRenderTarget renderTarget = new CanvasRenderTarget(device, (int)ink.ActualWidth, (int)ink.ActualHeight, 96);

    using (var ds = renderTarget.CreateDrawingSession())
    {
        ds.Clear(Colors.White);
        // 计算等比缩放的绘制区域,和Image控件显示逻辑保持一致
        var canvasBitmap = CanvasBitmap.CreateFromSoftwareBitmap(ds, softBM);
        float targetWidth = (float)ink.ActualWidth;
        float targetHeight = (float)ink.ActualHeight;
        float scale = Math.Min(targetWidth / canvasBitmap.Size.Width, targetHeight / canvasBitmap.Size.Height);
        float scaledWidth = canvasBitmap.Size.Width * scale;
        float scaledHeight = canvasBitmap.Size.Height * scale;
        // 居中绘制避免拉伸变形
        float x = (targetWidth - scaledWidth) / 2;
        float y = (targetHeight - scaledHeight) / 2;
        ds.DrawImage(canvasBitmap, new Rect(x, y, scaledWidth, scaledHeight));
        
        // 绘制墨迹的原有逻辑保留
        ds.DrawInk(ink.InkPresenter.StrokeContainer.GetStrokes());
    }

    // 其余保存逻辑保持不变
    IRandomAccessStream stream = new InMemoryRandomAccessStream(); 
    await renderTarget.SaveAsync(stream, CanvasBitmapFileFormat.Jpeg);
    var readStream = stream.AsStreamForRead();
    var byteArray = new byte[readStream.Length];
    readStream.Read(byteArray, 0, byteArray.Length);
    ViewModel.salvarFoto(byteArray, InputFile.Name);
    
    this.Frame.Navigate(typeof(ConsultaView), this.ViewModel.Consulta);
}

如果需要在加载阶段就把原图解码到600x600的尺寸减少内存占用,你也可以在EscolherImagem_Click方法里修改解码逻辑:

using (IRandomAccessStream fileStream = await InputFile.OpenAsync(Windows.Storage.FileAccessMode.Read))
{
    BitmapDecoder decoder = await BitmapDecoder.CreateAsync(fileStream);
    // 新增解码尺寸设置,提前缩放到控件尺寸
    var transform = new BitmapTransform()
    {
        ScaledWidth = 600,
        ScaledHeight = 600,
        InterpolationMode = BitmapInterpolationMode.Linear
    };
    softBM = await decoder.GetSoftwareBitmapAsync(decoder.BitmapPixelFormat, decoder.BitmapAlphaMode, transform, ExifOrientationMode.RespectExifOrientation, ColorManagementMode.DoNotColorManage);
    // 原有格式转换逻辑保留
    if (softBM.BitmapPixelFormat != BitmapPixelFormat.Bgra8 ||
        softBM.BitmapAlphaMode == BitmapAlphaMode.Straight)
    {
        softBM = SoftwareBitmap.Convert(softBM, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
    }
    // 其余原有加载逻辑保持不变
    var source = new SoftwareBitmapSource();
    await source.SetBitmapAsync(softBM);
    image.Source = source;
}

内容的提问来源于stack exchange,提问作者Pedro Ivo Dantas

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.09 10:45:04