精通
英语
和
开源
,
擅长
开发
与
培训
,
胸怀四海
第一信赖
锐英源精品原创,禁止全文或局部转载,禁止任何形式的非法使用,侵权必究。点名“简易百科”和闲暇巴盗用锐英源原创内容。
最近写大屏界面,在地图(图形局部)交互方面需要提高,看到本文和图像及交互有关,就翻译学习下。请记住,codeproject看不懂,请找锐英源软件。本文里的交互也是codeproject上的大牛提出的,本文作者也是借用了方法。
最近,我的一位刚创办公司的朋友向我介绍了这个网站,该网站做了很多精美的图像编辑。尽管他不知道 XAML 或 Sliverlight,但他认为他们所做的一些事情非常出色,并要求我考虑在 WPF 中做类似的事情。虽然本文仅代表该网站的一小部分可以做(即图像裁剪)我觉得它为你们中可能最终尝试在 WPF/Silverlight 中进行图像编辑应用程序的人概述了一些有用的技术和学习笔记。尽管我不能断然说本文的 100% 都适用于 Silverlight,因为它确实是用 WPF 编写的,但我正在等待使用 Silverlight v1.1 的托管版本。JavaScript 让我很冷(讨厌的东西)。因此,在我玩过它之后,我应该能够编写我知道可以与 Silverlight 一起使用的 WPF 文章。到那时,恐怕如果你想要一个 Silverlight 版本,你只需要自己尝试一个代码端口。
那么这篇文章到底是什么?就像我说的,我的朋友让我在 WPF 中创建一个漂亮的图像裁剪器。所以这就是事实。它是一个图像裁剪控件,可以放置在任何其他 XAML 中并用于裁剪图像。图像的裁剪是通过首先绘制一个形状,然后将形状移动到所需的位置,然后完成并接受裁剪的图像来实现的。
简而言之,就是这样。这是一个简单的图像裁剪器,基本上是用 WPF 编写的。
我实际上非常喜欢这里的图像裁剪,所以我想创建一个尽可能相似的图像。为此,我对自己的核心职能简介如下:
这些是我想要介绍的基本步骤。然而,我对自己施加了一些更多的扩展功能,如下所示:
这些是我为这篇文章设定的任务。在下一节中,我将解释我是如何完成或未能完成这些任务的。
所以我现在要做的是解释前面提到的每个核心/扩展功能。
这很容易做到,我只是对鼠标事件进行了子类化System.Windows.Controls.Canvas并覆盖了鼠标事件,这样当鼠标移动时,一个新的子UIElement类被添加到新的子类Canvas中。基本上每次用户移动鼠标时System.Windows.Shapes.Rectangle,都会使用鼠标坐标添加或调整新鼠标的大小。这与久经考验的旧 .NET 2.0ControlPaint.DrawReversibleFrame()方法的概念类似。通过子类化System.Windows.Controls.Canvas意味着这System.Windows.Controls.Canvas可以在任何代码或 XAML 文件中使用。
注:面向对象的具体实践。
这个子类Canvas的实际演示如下所示:
执行此功能的代码非常简单,如下所示。
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace ImageCropper
{
/// <span class="code-SummaryComment"><summary></span>
/// Provides a Canvas where a rectangle will be drawn
/// that matches the selection area that the user drew
/// on the canvas using the mouse
/// <span class="code-SummaryComment"></summary></span>
public partial class SelectionCanvas : Canvas
{
#region Instance fields
private Point mouseLeftDownPoint;
private Style cropperStyle;
public Shape rubberBand = null;
public readonly RoutedEvent CropImageEvent;
#endregion
#region Events
/// <span class="code-SummaryComment"><summary></span>
/// Raised when the user has drawn a selection area
/// <span class="code-SummaryComment"></summary></span>
public event RoutedEventHandler CropImage
{
add { AddHandler(this.CropImageEvent, value); }
remove { RemoveHandler(this.CropImageEvent, value); }
}
#endregion
#region Ctor
/// <span class="code-SummaryComment"><summary></span>
/// Constructs a new SelectionCanvas, and registers the
/// CropImage event
/// <span class="code-SummaryComment"></summary></span>
public SelectionCanvas()
{
this.CropImageEvent = EventManager.RegisterRoutedEvent
("CropImage", RoutingStrategy.Bubble,
typeof(RoutedEventHandler), typeof(SelectionCanvas));
}
#endregion
#region Public Properties
public Style CropperStyle
{
get { return cropperStyle; }
set { cropperStyle = value; }
}
#endregion
#region Overrides
/// <span class="code-SummaryComment"><summary></span>
/// Captures the mouse
/// <span class="code-SummaryComment"></summary></span>
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
if (!this.IsMouseCaptured)
{
mouseLeftDownPoint = e.GetPosition(this);
this.CaptureMouse();
}
}
/// <span class="code-SummaryComment"><summary></span>
/// Releases the mouse, and raises the CropImage Event
/// <span class="code-SummaryComment"></summary></span>
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonUp(e);
if (this.IsMouseCaptured && rubberBand != null)
{
this.ReleaseMouseCapture();
RaiseEvent(new RoutedEventArgs(this.CropImageEvent, this));
}
}
/// <span class="code-SummaryComment"><summary></span>
/// Creates a child control
/// <span class="code-SummaryComment"><see cref="System.Windows.Shapes.Rectangle">Rectangle</see></span>
/// and adds it to this controls children collection
/// at the co-ordinates the user
/// drew with the mouse
/// <span class="code-SummaryComment"></summary></span>
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (this.IsMouseCaptured)
{
Point currentPoint = e.GetPosition(this);
if (rubberBand == null)
{
rubberBand = new Rectangle();
if (cropperStyle != null)
rubberBand.Style = cropperStyle;
this.Children.Add(rubberBand);
}
double width = Math.Abs(mouseLeftDownPoint.X - currentPoint.X);
double height =
Math.Abs(mouseLeftDownPoint.Y - currentPoint.Y);
double left = Math.Min(mouseLeftDownPoint.X, currentPoint.X);
double top = Math.Min(mouseLeftDownPoint.Y, currentPoint.Y);
rubberBand.Width = width;
rubberBand.Height = height;
Canvas.SetLeft(rubberBand, left);
Canvas.SetTop(rubberBand, top);
}
}
#endregion
}
}
可以看出,crop 区域实际上是一个Rectangle. 我最初有这是一个固定的颜色。但 Josh Smith 建议我将其更改为包含用户允许的样式依赖属性。因此,我在主UcImageCropper上创建了一个CropperStyle依赖属性,UcImageCropper包含此画布和下图所示DragCanvas的。
好吧,这很容易(真的很容易),因为我所做的只是换掉当前selectionCanvas的DragCanvas,小心地从当前的子集合中删除当前的裁剪区域 ( Rectangle) selectionCanvas,并将其添加到DragCanvas.
之所以这么简单,是因为所有的工作都已经由别人完成了,我只是看到了如何使用它的机会。原始文章由Josh Smith撰写,我使用的特定文章在 CodeProject 上托管。它称为在画布中拖动元素。所以感谢乔希。我希望你喜欢在这段代码中使用它。
一旦DragCanvas就位,用户就可以将裁剪区域拖动到他们喜欢的任何地方。高兴时,他们可以使用上下文菜单(右键单击)来保存图像或重新开始。
我的直接想法是使用System.Windows.Documents.Adorner. 对于那些不知道我到底在说什么的人,简单地说,装饰器允许您将额外的功能应用于UIElement旋转、调整大小等。关于此有许多很好的来源,例如:
不幸的是,由于JoSmithDragCanvas使用了鼠标事件,而且漂亮的 MSDNResizeAdorner示例也使用了鼠标事件,所以要让它们正常工作有点困难。为此,我不得不放弃调整裁剪区域的大小。但是,如果有人想试一试,System.Windows.Documents.Adorners 将是要走的路。我的想法是简单地使用ResizeAdorner(MSDN)来装饰当前的裁剪矩形,这样用户不仅可以拖动(感谢DragCanvas)而且可以调整大小。反正就是这个想法。
为了允许用户预览图像被裁剪后的样子,有一个小的弹出窗口允许用户接受或拒绝裁剪。如果用户接受,裁剪后的图像将用作当前图像的新来源。如果用户拒绝裁剪,将使用现有图像而不执行任何裁剪。
重复使用是好的。为此,我已将所有这些功能封装到一个名为的可重用控件中,该控件ucImageCropper可在其他 XAML 文件中使用。
的源代码ucImageCropper如下所示:
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.IO;
#region Explanation of why this .NET3.0 app is using .NET2.0 Dlls
//For some very simple .NET niceties like being able to save a bitmap
//to a filename I have had to use the System.Drawing .NET 2.0 DLL
//
//While this looks possible using something like the following :
//
//RenderTargetBitmap rtb = new RenderTargetBitmap((int)img.width,
//(int)img, 0, 0, PixelFormats.Default);
//rtb.Render(this.inkCanv);
//BmpBitmapEncoder encoder = new BmpBitmapEncoder();
//encoder.Frames.Add(BitmapFrame.Create(rtb));
//encoder.Save(file);
//file.Close();
//
//For this to work I would have needed to used a .NET 3.0 CroppedBitmap
//within the RenderTargetBitmap.Render() method. And as CroppedBitmap
//doesn't inherit from Visual this is not possible.
//
//So if anyone knows how to do this better in .NET 3.0 I am all ears
#endregion
using System.Drawing;
using System.Drawing.Drawing2D;
//Josh Smith excellent DragCanvas
using WPF.JoshSmith.Controls;
namespace ImageCropper
{
/// <span class="code-SummaryComment"><summary></span>
/// Provides a simple Image cropping facility for a WPF image element,
/// where the cropped area may be picked using a rubber band and moved
/// by dragging the rubber band around the image. There is also a popup
/// window from where the user may accept or reject the crop.
/// <span class="code-SummaryComment"></summary></span>
public partial class UcImageCropper : System.Windows.Controls.UserControl
{
#region CropperStyle Dependancy property
/// <span class="code-SummaryComment"><summary></span>
/// A DP for the Crop Rectangle Style
/// <span class="code-SummaryComment"></summary></span>
public Style CropperStyle
{
get { return (Style)GetValue(CropperStyleProperty); }
set { SetValue(CropperStyleProperty, value); }
}
/// <span class="code-SummaryComment"><summary></span>
/// register the DP
/// <span class="code-SummaryComment"></summary></span>
public static readonly DependencyProperty CropperStyleProperty =
DependencyProperty.Register(
"CropperStyle",
typeof(Style),
typeof(UcImageCropper),
new UIPropertyMetadata(null, new PropertyChangedCallback
(OnCropperStyleChanged)));
/// <span class="code-SummaryComment"><summary></span>
/// The callback that actually changes the Style if one was provided
/// <span class="code-SummaryComment"></summary></span>
/// <span class="code-SummaryComment"><param name="depObj">UcImageCropper</param></span>
/// <span class="code-SummaryComment"><param name="e">The event args</param></span>
static void OnCropperStyleChanged(DependencyObject depObj,
DependencyPropertyChangedEventArgs e)
{
Style s = e.NewValue as Style;
if (s != null)
{
UcImageCropper uc = (UcImageCropper)depObj;
uc.selectCanvForImg.CropperStyle = s;
}
}
#endregion
#region Instance fields
private string ImgUrl = "";
private BitmapImage bmpSource = null;
private SelectionCanvas selectCanvForImg = null;
private DragCanvas dragCanvasForImg = null;
private System.Windows.Controls.Image img = null;
private Shape rubberBand;
private double rubberBandLeft;
private double rubberBandTop;
private string tempFileName;
private ContextMenu cmSelectionCanvas;
private RoutedEventHandler cmSelectionCanvasRoutedEventHandler;
private ContextMenu cmDragCanvas;
private RoutedEventHandler cmDragCanvasRoutedEventHandler;
private string fixedTempName = "temp";
private long fixedTempIdx = 1;
private double zoomFactor=1.0;
#endregion
#region Ctor
public UcImageCropper()
{
InitializeComponent();
//this.Unloaded += new RoutedEventHandler
(UcImageCropper_Unloaded);
selectCanvForImg = new SelectionCanvas();
selectCanvForImg.CropImage +=
new RoutedEventHandler(selectCanvForImg_CropImage);
dragCanvasForImg = new DragCanvas();
}
#endregion
#region Public properties
public string ImageUrl
{
get { return this.ImgUrl; }
set
{
zoomFactor = 1.0;
ImgUrl = value;
grdCroppedImage.Visibility = Visibility.Hidden;
createImageSource();
createSelectionCanvas();
//apply the default style if the user of this control
//didn't supply one
if (CropperStyle == null)
{
Style s = gridMain.TryFindResource("defaultCropperStyle")
as Style;
if (s != null)
{
CropperStyle = s;
}
}
}
}
#endregion
#region Private methods
/// <span class="code-SummaryComment"><summary></span>
/// Deletes all occurrences of previous unused temp files from the
/// current temporary path
/// <span class="code-SummaryComment"></summary></span>
/// <span class="code-SummaryComment"><param name="tempPath">The temporary file path</param></span>
/// <span class="code-SummaryComment"><param name="fixedTempName">The file name part to search for</span>
/// <span class="code-SummaryComment"></param></span>
/// <span class="code-SummaryComment"><param name="CurrentFixedTempIdx">The current temp file suffix</span>
/// <span class="code-SummaryComment"></param></span>
public void CleanUp(string tempPath, string fixedTempName,
long CurrentFixedTempIdx)
{
//clean up the single temporary file created
try
{
string filename = "";
for (int i = 0; i < CurrentFixedTempIdx; i++)
{
filename = tempPath + fixedTempName + i.ToString()+".jpg";
File.Delete(filename);
}
}
catch (Exception)
{
}
}
/// <span class="code-SummaryComment"><summary></span>
/// Popup form Cancel clicked, so created the SelectionCanvas
/// to start again
/// <span class="code-SummaryComment"></summary></span>
private void btnCancel_Click(object sender, RoutedEventArgs e)
{
grdCroppedImage.Visibility = Visibility.Hidden;
createSelectionCanvas();
}
/// <span class="code-SummaryComment"><summary></span>
/// Popup form Confirm clicked, so save the file to their
/// desired location
/// <span class="code-SummaryComment"></summary></span>
private void btnConfirm_Click(object sender, RoutedEventArgs e)
{
ImageUrl = tempFileName;
grdCroppedImage.Visibility = Visibility.Hidden;
}
/// <span class="code-SummaryComment"><summary></span>
/// creates the selection canvas, where user can draw
/// selection rectangle
/// <span class="code-SummaryComment"></summary></span>
private void createSelectionCanvas()
{
createImageSource();
selectCanvForImg.Width = bmpSource.Width * zoomFactor;
selectCanvForImg.Height = bmpSource.Height * zoomFactor;
selectCanvForImg.Children.Clear();
selectCanvForImg.rubberBand = null;
selectCanvForImg.Children.Add(img);
svForImg.Width = selectCanvForImg.Width;
svForImg.Height = selectCanvForImg.Height;
svForImg.Content = selectCanvForImg;
createSelectionCanvasMenu();
}
/// <span class="code-SummaryComment"><summary></span>
/// Creates the selection canvas context menu
/// <span class="code-SummaryComment"></summary></span>
private void createSelectionCanvasMenu()
{
cmSelectionCanvas = new ContextMenu();
MenuItem miZoom25 = new MenuItem();
miZoom25.Header = "Zoom 25%";
miZoom25.Tag = "0.25";
MenuItem miZoom50 = new MenuItem();
miZoom50.Header = "Zoom 50%";
miZoom50.Tag = "0.5";
MenuItem miZoom100 = new MenuItem();
miZoom100.Header = "Zoom 100%";
miZoom100.Tag = "1.0";
cmSelectionCanvas.Items.Add(miZoom25);
cmSelectionCanvas.Items.Add(miZoom50);
cmSelectionCanvas.Items.Add(miZoom100);
cmSelectionCanvasRoutedEventHandler =
new RoutedEventHandler(MenuSelectionCanvasOnClick);
cmSelectionCanvas.AddHandler
(MenuItem.ClickEvent, cmSelectionCanvasRoutedEventHandler);
selectCanvForImg.ContextMenu = cmSelectionCanvas;
}
/// <span class="code-SummaryComment"><summary></span>
/// Handles the selection canvas context menu. Which will zoom the
/// current image to either 25,50 or 100%
/// <span class="code-SummaryComment"></summary></span>
private void MenuSelectionCanvasOnClick(object sender,
RoutedEventArgs args)
{
MenuItem item = args.Source as MenuItem;
zoomFactor = double.Parse(item.Tag.ToString());
img.RenderTransform = new ScaleTransform
(zoomFactor, zoomFactor, 0.5, 0.5);
selectCanvForImg.Width = bmpSource.Width * zoomFactor;
selectCanvForImg.Height = bmpSource.Height * zoomFactor;
svForImg.Width = selectCanvForImg.Width;
svForImg.Height = selectCanvForImg.Height;
}
/// <span class="code-SummaryComment"><summary></span>
/// Creates the Image source for the current canvas
/// <span class="code-SummaryComment"></summary></span>
private void createImageSource()
{
bmpSource = new BitmapImage(new Uri(ImgUrl));
img = new System.Windows.Controls.Image();
img.Source = bmpSource;
//if there was a Zoom Factor applied
img.RenderTransform = new ScaleTransform
(zoomFactor, zoomFactor, 0.5, 0.5);
}
/// <span class="code-SummaryComment"><summary></span>
/// creates the drag canvas, where user can drag the
/// selection rectangle
/// <span class="code-SummaryComment"></summary></span>
private void createDragCanvas()
{
dragCanvasForImg.Width = bmpSource.Width * zoomFactor;
dragCanvasForImg.Height = bmpSource.Height * zoomFactor;
svForImg.Width = dragCanvasForImg.Width;
svForImg.Height = dragCanvasForImg.Height;
createImageSource();
createDragCanvasMenu();
selectCanvForImg.Children.Remove(rubberBand);
dragCanvasForImg.Children.Clear();
dragCanvasForImg.Children.Add(img);
dragCanvasForImg.Children.Add(rubberBand);
svForImg.Content = dragCanvasForImg;
}
/// <span class="code-SummaryComment"><summary></span>
/// Creates the drag canvas context menu
/// <span class="code-SummaryComment"></summary></span>
private void createDragCanvasMenu()
{
cmSelectionCanvas.RemoveHandler
(MenuItem.ClickEvent, cmSelectionCanvasRoutedEventHandler);
selectCanvForImg.ContextMenu = null;
cmSelectionCanvas = null;
cmDragCanvas = new ContextMenu();
MenuItem miCancel = new MenuItem();
miCancel.Header = "Cancel";
MenuItem miSave = new MenuItem();
miSave.Header = "Save";
cmDragCanvas.Items.Add(miCancel);
cmDragCanvas.Items.Add(miSave);
cmDragCanvasRoutedEventHandler =
new RoutedEventHandler(MenuDragCanvasOnClick);
cmDragCanvas.AddHandler
(MenuItem.ClickEvent, cmDragCanvasRoutedEventHandler);
dragCanvasForImg.ContextMenu = cmDragCanvas;
}
/// <span class="code-SummaryComment"><summary></span>
/// Handles the selection drag context menu.
/// Which allows user to cancel or save
/// the current cropped area
/// <span class="code-SummaryComment"></summary></span>
private void MenuDragCanvasOnClick
(object sender, RoutedEventArgs args)
{
MenuItem item = args.Source as MenuItem;
switch (item.Header.ToString())
{
case "Save":
SaveCroppedImage();
break;
case "Cancel":
createSelectionCanvas();
break;
default:
break;
}
}
/// <span class="code-SummaryComment"><summary></span>
/// Raised by the <span class="code-SummaryComment"><see cref="selectionCanvas">selectionCanvas</see></span>
/// when the new crop shape (rectangle) has been drawn. This event
/// then replaces the current selectionCanvas with a
<see cref="DragCanvas">DragCanvas</see>
/// which can then be used to drag the crop area around
/// within a Canvas
/// </summary>
private void selectCanvForImg_CropImage
(object sender, RoutedEventArgs e)
{
rubberBand = (Shape)selectCanvForImg.Children[1];
createDragCanvas();
}
/// <summary>
/// User cancelled out of the popup,
/// so go back to showing original image
/// </summary>
private void lblExit_MouseDown
(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
grdCroppedImage.Visibility = Visibility.Hidden;
createSelectionCanvas();
}
/// <summary>
/// Saves the cropped image area to a temp file,
/// and shows a confirmation
/// popup from where the user may accept or reject the cropped image.
/// If they accept the new cropped image
/// will be used as the new image source
/// for the current canvas, if they reject the crop,
/// the existing image will
/// be used for the current canvas
/// </summary>
private void SaveCroppedImage()
{
if (popUpImage.Source!=null)
popUpImage.Source = null;
try
{
rubberBandLeft = Canvas.GetLeft(rubberBand);
rubberBandTop = Canvas.GetTop(rubberBand);
//create a new .NET 2.0 bitmap (which allowing saving)
//based on the bound bitmap URL
using (System.Drawing.Bitmap source =
new System.Drawing.Bitmap(ImgUrl))
{
//create a new .NET 2.0 bitmap (which allowing saving)
//to store cropped image in, should be
//same size as rubberBand element which is the size
//of the area of the original image we want to keep
using (System.Drawing.Bitmap target =
new System.Drawing.Bitmap((int)rubberBand.Width,
(int)rubberBand.Height))
{
//create a new destination rectangle
System.Drawing.RectangleF recDest =
new System.Drawing.RectangleF
(0.0f, 0.0f, (float)target.Width,
(float)target.Height);
//different resolution fix prior to cropping image
float hd = 1.0f / (target.HorizontalResolution /
source.HorizontalResolution);
float vd = 1.0f / (target.VerticalResolution /
source.VerticalResolution);
float hScale = 1.0f / (float)zoomFactor;
float vScale = 1.0f / (float)zoomFactor;
System.Drawing.RectangleF recSrc =
new System.Drawing.RectangleF
((hd * (float)rubberBandLeft) *
hScale, (vd * (float)rubberBandTop) *
vScale, (hd * (float)rubberBand.Width) *
hScale, (vd * (float)rubberBand.Height) *
vScale);
using (System.Drawing.Graphics gfx =
System.Drawing.Graphics.FromImage(target))
{
gfx.DrawImage(source, recDest, recSrc,
System.Drawing.GraphicsUnit.Pixel);
}
//create a new temporary file, and delete
//all old ones prior to this new temp file
//This is a hack that I had to put in,
//due to GDI+ holding on to previous
//file handles used by the Bitmap.Save()
//method the last time this method was run.
//This is a well known issue see
//http://support.microsoft.com/?id=814675 for example
tempFileName = System.IO.Path.GetTempPath();
if (fixedTempIdx >
如果您有一个非常大的源图像,您可能想要修改它,因此您可以使用右键单击上下文菜单(仅在非拖动模式下可用),它允许 25、50 和 100% 的大小。在幕后,正在发生的只是 aSystem.Windows.Media.ScaleTransform 被应用。一个例子如下:
img.RenderTransform = new ScaleTransform(zoomFactor, zoomFactor, 0 . 5 , 0 . 5 )
注:缩放通过此方式能够快速实现。
按着这些次序:
虽然本文中的代码不多,但我做这个很有趣,并希望它对那里的人有用。
我想问一下,如果你喜欢这篇文章,请给它投票,并留下一些评论,因为它让我知道这篇文章是否处于正确的水平,以及它是否包含人们需要知道的内容。
这里没有太多要提及的,因为我认为本文的其余部分几乎涵盖了它。我想要说的一件事是,虽然我真的很喜欢 WPF,但我发现自己仍然需要深入研究 .NET 2.0 来做一些像素级的事情。.NET 3.0中当然有一个System.Windows.Media.Imaging.CroppedBitmap,但是这个类没有提供保存图像的能力,所以不是我想要的。在图像过滤方面,WPF 确实通过使用System.Windows.Media.Imaging.FormatConvertedBitmap该类提供了一些像素级功能。但又不是我所追求的,因为我希望将图像的裁剪保留在某个地方。因此,除非 .NET 映像类能够保存到磁盘,否则我必须改用 .NETBitmap 或Image类。