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
相关产品推荐
相关产品推荐

