我尝试从CodeProject扩展裁剪控制,能够从磁盘中选择图像,使用Stretch='Uniform'
显示裁剪,并且能够使用宽高比调整裁剪区域。
我已完成所有修改,但我遇到了问题 - 我必须加载两次相同的图像才能获得ActualWidth
的图像控件。
我已经搜索了SO(Why are ActualWidth and ActualHeight 0.0 in this case?)以获得解决方案,但我无法使其正常运行。
以下是我的完整代码:
windows.xaml:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="CroppingTest.WndCroppingTest"
Title="CroppingTest"
Width="900" Height="600" Background="OliveDrab"
SizeChanged="Window_SizeChanged" Loaded="WndCroppingTest_OnLoaded"
>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid>
<Rectangle Fill="White">
<Rectangle.Effect>
<DropShadowEffect Opacity="0.5" />
</Rectangle.Effect>
</Rectangle>
<Image x:Name="Crop" Stretch="Uniform" VerticalAlignment="Center" HorizontalAlignment="Center" />
</Grid>
</Grid>
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Stretch" Width="Auto" Height="Auto" Grid.Column="2">
<StackPanel.Resources>
<Style TargetType="CheckBox">
<Setter Property="Margin" Value="5,5,5,5"/>
</Style>
</StackPanel.Resources>
<Image x:Name="Preview" Width="130" Height="100" Margin="0,5,5,0"/>
<Button Content="Open" HorizontalAlignment="Stretch" Margin="0,10" Click="OnOpen"/>
<Button Content="Save" HorizontalAlignment="Stretch" Margin="0,10" Click="OnSave"/>
</StackPanel>
<TextBlock HorizontalAlignment="Stretch" Margin="5,0,0,5" x:Name="tblkClippingRectangle" VerticalAlignment="Top" Width="Auto" Height="Auto" Grid.Row="1" Foreground="#FFFFFFFF" Text="ClippingRectangle" TextWrapping="Wrap"/>
</Grid>
</Window>
代码背后的代码:
using System;
using System.Diagnostics;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media.Imaging;
using DAP.Adorners;
using Microsoft.Win32;
namespace CroppingTest
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class WndCroppingTest
{
CroppingAdorner _clp;
FrameworkElement _felCur;
public WndCroppingTest()
{
InitializeComponent();
}
private string _s;
public WndCroppingTest(string source)
{
_s = source;
InitializeComponent();
}
private void RemoveCropFromCur()
{
AdornerLayer aly = AdornerLayer.GetAdornerLayer(_felCur);
aly.Remove(_clp);
}
private void AddCropToImage(Image fel)
{
if (_felCur != null)
{
RemoveCropFromCur();
}
Size s = new Size(80,120);
double ratio = s.Width/s.Height;
Rect r = new Rect();
if (ratio < 1)
{
r.Height = fel.ActualHeight;
r.Width = fel.ActualHeight*ratio;
r.Y = 0;
r.X = (fel.ActualWidth - r.Width)/2;
}
else
{
r.Width = fel.ActualWidth;
r.Height = fel.ActualWidth / ratio;
r.X = 0;
r.Y = (fel.ActualHeight - r.Height) / 2;
}
AdornerLayer aly = AdornerLayer.GetAdornerLayer(fel);
_clp = new CroppingAdorner(fel, r,true);
aly.Add(_clp);
Preview.Source = _clp.BpsCrop();
_clp.CropChanged += CropChanged;
_felCur = fel;
}
private void RefreshCropImage()
{
if (_clp != null)
{
Rect rc = _clp.ClippingRectangle;
tblkClippingRectangle.Text = string.Format(
"Clipping Rectangle: ({0:N1}, {1:N1}, {2:N1}, {3:N1})",
rc.Left,
rc.Top,
rc.Right,
rc.Bottom);
Preview.Source = _clp.BpsCrop();
}
}
private void CropChanged(Object sender, RoutedEventArgs rea)
{
RefreshCropImage();
}
private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
{
RefreshCropImage();
}
private void OnOpen(object sender, RoutedEventArgs e)
{
OpenFileDialog openfile = new OpenFileDialog
{
//Filter = "JPEG (*.jpeg)|*.jpeg|PNG (*.png)|*.png|JPG (*.jpg)|*.jpg"
Filter = "Obrazy (*.jpeg, *.png, *.jpg)|*.jpeg;*.png;*.jpg"
};
bool? result = openfile.ShowDialog();
if (result == true)
{
//MessageBox.Show(openfile.FileName);
var source = openfile.FileName;
Crop.Source= new BitmapImage(new Uri(source));
AddCropToImage(Crop);
RefreshCropImage();
}
}
private void OnSave(object sender, RoutedEventArgs e)
{
SaveFileDialog dlg = new SaveFileDialog
{
FileName = "Avatar",
DefaultExt = ".png",
Filter = "PNGi (.png)|*.png"
};
bool? result = dlg.ShowDialog();
if (result == true)
{
string filename = dlg.FileName;
using (var fileStream = new FileStream(filename, FileMode.Create))
{
BitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(_clp.BpsCrop()));
encoder.Save(fileStream);
}
}
}
private void WndCroppingTest_OnLoaded(object sender, RoutedEventArgs e)
{
if (_s != null)
{
Crop.Source = new BitmapImage(new Uri(_s));
AddCropToImage(Crop);
RefreshCropImage();
}
}
}
}
CroppingAdorner:
using System;
using System.Diagnostics;
using System.Drawing;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Brush = System.Windows.Media.Brush;
using Brushes = System.Windows.Media.Brushes;
using Color = System.Windows.Media.Color;
using Image = System.Windows.Controls.Image;
using Pen = System.Windows.Media.Pen;
using Point = System.Drawing.Point;
using Size = System.Windows.Size;
namespace DAP.Adorners
{
public class CroppingAdorner : Adorner
{
#region Private variables
// Width of the thumbs. I know these really aren't "pixels", but px
// is still a good mnemonic.
private const int _cpxThumbWidth = 6;
// PuncturedRect to hold the "Cropping" portion of the adorner
private PuncturedRect _prCropMask;
// Canvas to hold the thumbs so they can be moved in response to the user
private Canvas _cnvThumbs;
// Cropping adorner uses Thumbs for visual elements.
// The Thumbs have built-in mouse input handling.
private CropThumb _crtTopLeft, _crtTopRight, _crtBottomLeft, _crtBottomRight;
//private CropThumb _crtTop, _crtLeft, _crtBottom, _crtRight;
// To store and manage the adorner's visual children.
private VisualCollection _vc;
// DPI for screen
private static double s_dpiX, s_dpiY;
private Size _originalSize, _controlSize;
private Image _i;
private ImageSource _s;
private BitmapImage _b;
#endregion
#region Properties
public Rect ClippingRectangle
{
get
{
return _prCropMask.RectInterior;
}
}
#endregion
#region Routed Events
public static readonly RoutedEvent CropChangedEvent = EventManager.RegisterRoutedEvent(
"CropChanged",
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(CroppingAdorner));
public event RoutedEventHandler CropChanged
{
add
{
AddHandler(CropChangedEvent, value);
}
remove
{
RemoveHandler(CropChangedEvent, value);
}
}
#endregion
#region Dependency Properties
static public DependencyProperty FillProperty = Shape.FillProperty.AddOwner(typeof(CroppingAdorner));
public Brush Fill
{
get { return (Brush)GetValue(FillProperty); }
set { SetValue(FillProperty, value); }
}
private static void FillPropChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
{
CroppingAdorner crp = d as CroppingAdorner;
if (crp != null)
{
crp._prCropMask.Fill = (Brush)args.NewValue;
}
}
#endregion
#region Constructor
static CroppingAdorner()
{
Color clr = Colors.Black;
Graphics g = Graphics.FromHwnd((IntPtr)0);
s_dpiX = g.DpiX;
s_dpiY = g.DpiY;
clr.A = 80;
FillProperty.OverrideMetadata(typeof(CroppingAdorner),
new PropertyMetadata(
new SolidColorBrush(clr),
FillPropChanged));
}
public CroppingAdorner(Image sourceImage, Rect rcInit, bool fixedRatio = false)
: base(sourceImage)
{
_fixedRatio = fixedRatio;
_ratio = rcInit.Width/rcInit.Height;
_i = sourceImage;
_s = sourceImage.Source;
try
{
_b = (BitmapImage) sourceImage.Source;
}
catch (Exception e)
{
Debug.WriteLine(e);
}
try
{
_originalSize = new Size(_b.PixelWidth, _b.PixelHeight);
}
catch (Exception e)
{
_originalSize = new Size(1,1);
}
_controlSize = new Size(sourceImage.ActualWidth, sourceImage.ActualHeight);
_vc = new VisualCollection(this);
_prCropMask = new PuncturedRect();
_prCropMask.IsHitTestVisible = false;
_prCropMask.RectInterior = rcInit;
_prCropMask.Fill = Fill;
_vc.Add(_prCropMask);
_cnvThumbs = new Canvas();
_cnvThumbs.HorizontalAlignment = HorizontalAlignment.Stretch;
_cnvThumbs.VerticalAlignment = VerticalAlignment.Stretch;
_vc.Add(_cnvThumbs);
//BuildCorner(ref _crtTop, Cursors.SizeNS);
//BuildCorner(ref _crtBottom, Cursors.SizeNS);
//BuildCorner(ref _crtLeft, Cursors.SizeWE);
//BuildCorner(ref _crtRight, Cursors.SizeWE);
BuildCorner(ref _crtTopLeft, Cursors.SizeNWSE);
BuildCorner(ref _crtTopRight, Cursors.SizeNESW);
BuildCorner(ref _crtBottomLeft, Cursors.SizeNESW);
BuildCorner(ref _crtBottomRight, Cursors.SizeNWSE);
// Add handlers for Cropping.
_crtBottomLeft.DragDelta += HandleBottomLeft;
_crtBottomRight.DragDelta += HandleBottomRight;
_crtTopLeft.DragDelta += HandleTopLeft;
_crtTopRight.DragDelta += HandleTopRight;
//_crtTop.DragDelta += HandleTop;
//_crtBottom.DragDelta += HandleBottom;
//_crtRight.DragDelta += HandleRight;
//_crtLeft.DragDelta += HandleLeft;
//add eventhandler to drag and drop
sourceImage.MouseLeftButtonDown += Handle_MouseLeftButtonDown;
sourceImage.MouseLeftButtonUp += Handle_MouseLeftButtonUp;
sourceImage.MouseMove += Handle_MouseMove;
// We have to keep the clipping interior withing the bounds of the adorned element
// so we have to track it's size to guarantee that...
FrameworkElement fel = sourceImage;
fel.SizeChanged += AdornedElement_SizeChanged;
}
#endregion
#region Drag and drop handlers
Double OrigenX;
Double OrigenY;
private readonly bool _fixedRatio;
private double _ratio;
// generic handler move selection with Drag'n'Drop
private void HandleDrag(double dx, double dy)
{
Rect rcInterior = _prCropMask.RectInterior;
rcInterior = new Rect(
dx,
dy,
rcInterior.Width,
rcInterior.Height);
_prCropMask.RectInterior = rcInterior;
SetThumbs(_prCropMask.RectInterior);
RaiseEvent(new RoutedEventArgs(CropChangedEvent, this));
}
private void Handle_MouseMove(object sender, MouseEventArgs args)
{
Image Marco = sender as Image;
if (Marco != null && Marco.IsMouseCaptured)
{
Double x = args.GetPosition(Marco).X; //posición actual cursor
Double y = args.GetPosition(Marco).Y;
Double _x = _prCropMask.RectInterior.X; // posición actual esquina superior izq del marco interior
Double _y = _prCropMask.RectInterior.Y;
Double _width = _prCropMask.RectInterior.Width; //dimensiones del marco interior
Double _height = _prCropMask.RectInterior.Height;
//si el click es dentro del marco interior
if (((x > _x) && (x < (_x + _width))) && ((y > _y) && (y < (_y + _height))))
{
//calculamos la diferencia de la posición actual del cursor con respecto al punto de origen del arrastre
//y se la añadimos a la esquina sup. izq. del marco interior.
_x = _x + (x - OrigenX);
_y = _y + (y - OrigenY);
//comprobamos si es posible mover sin salirse del marco exterior por ninguna de sus dimensiones
//no supera el borde izquierdo de la imagen: !(_x < 0)
if (_x < 0)
{
_x = 0;
}
//no supera el borde derecho de la imagen: !((_x + _width) > Marco.Width)
if ((_x + _width) > Marco.ActualWidth)
{
_x = Marco.ActualWidth - _width;
}
//no supera el borde superior de la imagen: !(_y<0)
if (_y < 0)
{
_y = 0;
}
//no supera el borde inferior de la imagen: !((_y + _height) > Marco.Height)
if ((_y + _height) > Marco.ActualHeight)
{
_y = Marco.ActualHeight - _height;
}
//asignamos nuevo punto origen del arrastre y movemos el marco interior
OrigenX = x;
OrigenY = y;
HandleDrag(_x, _y);
}
}
}
private void Handle_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Image Marco = sender as Image;
if (Marco != null)
{
Marco.CaptureMouse();
OrigenX = e.GetPosition(Marco).X; //iniciamos las variables en el punto de origen del arrastre
OrigenY = e.GetPosition(Marco).Y;
}
}
private void Handle_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
Image Marco = sender as Image;
if (Marco != null)
{
Marco.ReleaseMouseCapture();
}
}
#endregion
#region Thumb handlers
// Generic handler for Cropping
private void HandleThumb(
double drcL,
double drcT,
double drcW,
double drcH,
double dx,
double dy)
{
Rect rcInterior = _prCropMask.RectInterior;
if (rcInterior.Width + drcW * dx < 0)
{
dx = -rcInterior.Width / drcW;
}
if (rcInterior.Height + drcH * dy < 0)
{
dy = -rcInterior.Height / drcH;
}
rcInterior = new Rect(
rcInterior.Left + drcL * dx,
rcInterior.Top + drcT * dy,
rcInterior.Width + drcW * dx,
rcInterior.Height + drcH * dy);
if (_fixedRatio)
{
if (_ratio < 1)
{
if (rcInterior.Height > _i.ActualHeight)
{
rcInterior.Height = _i.ActualHeight;
}
rcInterior.Width = rcInterior.Height * _ratio;
}
else
{
if (rcInterior.Width > _i.ActualWidth)
{
rcInterior.Width = _i.ActualWidth;
}
rcInterior.Height = rcInterior.Width / _ratio;
}
}
_prCropMask.RectInterior = rcInterior;
SetThumbs(_prCropMask.RectInterior);
RaiseEvent( new RoutedEventArgs(CropChangedEvent, this));
}
// Handler for Cropping from the bottom-left.
private void HandleBottomLeft(object sender, DragDeltaEventArgs args)
{
if (sender is CropThumb)
{
HandleThumb(
1, 0, -1, 1,
args.HorizontalChange,
args.VerticalChange);
}
}
// Handler for Cropping from the bottom-right.
private void HandleBottomRight(object sender, DragDeltaEventArgs args)
{
if (sender is CropThumb)
{
HandleThumb(
0, 0, 1, 1,
args.HorizontalChange,
args.VerticalChange);
}
}
// Handler for Cropping from the top-right.
private void HandleTopRight(object sender, DragDeltaEventArgs args)
{
if (sender is CropThumb)
{
HandleThumb(
0, 1, 1, -1,
args.HorizontalChange,
args.VerticalChange);
}
}
// Handler for Cropping from the top-left.
private void HandleTopLeft(object sender, DragDeltaEventArgs args)
{
if (sender is CropThumb)
{
HandleThumb(
1, 1, -1, -1,
args.HorizontalChange,
args.VerticalChange);
}
}
#endregion
#region Other handlers
private void AdornedElement_SizeChanged(object sender, SizeChangedEventArgs e)
{
FrameworkElement fel = sender as FrameworkElement;
Rect rcInterior = _prCropMask.RectInterior;
bool fFixupRequired = false;
double
intLeft = rcInterior.Left,
intTop = rcInterior.Top,
intWidth = rcInterior.Width,
intHeight = rcInterior.Height;
if (rcInterior.Left > fel.RenderSize.Width)
{
intLeft = fel.RenderSize.Width;
intWidth = 0;
fFixupRequired = true;
}
if (rcInterior.Top > fel.RenderSize.Height)
{
intTop = fel.RenderSize.Height;
intHeight = 0;
fFixupRequired = true;
}
if (rcInterior.Right > fel.RenderSize.Width)
{
intWidth = Math.Max(0, fel.RenderSize.Width - intLeft);
fFixupRequired = true;
}
if (rcInterior.Bottom > fel.RenderSize.Height)
{
intHeight = Math.Max(0, fel.RenderSize.Height - intTop);
fFixupRequired = true;
}
if (fFixupRequired)
{
_prCropMask.RectInterior = new Rect(intLeft, intTop, intWidth, intHeight);
}
}
#endregion
#region Arranging/positioning
private void SetThumbs(Rect rc)
{
_crtBottomRight.SetPos(rc.Right, rc.Bottom);
_crtTopLeft.SetPos(rc.Left, rc.Top);
_crtTopRight.SetPos(rc.Right, rc.Top);
_crtBottomLeft.SetPos(rc.Left, rc.Bottom);
//_crtTop.SetPos(rc.Left + rc.Width / 2, rc.Top);
//_crtBottom.SetPos(rc.Left + rc.Width / 2, rc.Bottom);
//_crtLeft.SetPos(rc.Left, rc.Top + rc.Height / 2);
//_crtRight.SetPos(rc.Right, rc.Top + rc.Height / 2);
}
// Arrange the Adorners.
protected override Size ArrangeOverride(Size finalSize)
{
Rect rcExterior = new Rect(0, 0, AdornedElement.RenderSize.Width, AdornedElement.RenderSize.Height);
_prCropMask.RectExterior = rcExterior;
Rect rcInterior = _prCropMask.RectInterior;
_prCropMask.Arrange(rcExterior);
SetThumbs(rcInterior);
_cnvThumbs.Arrange(rcExterior);
return finalSize;
}
#endregion
#region Public interface
public BitmapSource BpsCrop()
{
Thickness margin = AdornerMargin();
Rect rcInterior = _prCropMask.RectInterior;
Point pxFromSize = UnitsToPx(rcInterior.Width, rcInterior.Height);
// It appears that CroppedBitmap indexes from the upper left of the margin whereas RenderTargetBitmap renders the
// control exclusive of the margin. Hence our need to take the margins into account here...
Point pxFromPos = UnitsToPx(rcInterior.Left, rcInterior.Top);
Point pxWhole = UnitsToPx(AdornedElement.RenderSize.Width, AdornedElement.RenderSize.Height);
pxFromSize.X = Math.Max(Math.Min(pxWhole.X - pxFromPos.X, pxFromSize.X), 0);
pxFromSize.Y = Math.Max(Math.Min(pxWhole.Y - pxFromPos.Y, pxFromSize.Y), 0);
if (pxFromSize.X == 0 || pxFromSize.Y == 0)
{
return null;
}
var Width = _i.ActualWidth;
var Height = _i.ActualHeight;
int x = (int)(rcInterior.Left * _originalSize.Width / Width);
int y = (int)(rcInterior.Top * _originalSize.Height / Height);
int xx = (int)((rcInterior.Width) * _originalSize.Width / Width);
int yy = (int)((rcInterior.Height) * _originalSize.Height / Height);
Int32Rect rcFrom = new Int32Rect(x, y, xx, yy);
//Int32Rect rcFrom = new Int32Rect(pxFromPos.X, pxFromPos.Y, pxFromSize.X, pxFromSize.Y);
RenderTargetBitmap rtb = new RenderTargetBitmap(pxWhole.X, pxWhole.Y, s_dpiX, s_dpiY, PixelFormats.Default);
rtb.Render(AdornedElement);
try
{
return new CroppedBitmap(_b, rcFrom);
}
catch (Exception e)
{
Debug.WriteLine(e);
return new CroppedBitmap(rtb, new Int32Rect(0,0, 100,100));
}
}
public static Size RelativeSize(double aspectRatio)
{
return (aspectRatio > 1)
? new Size(1, 1 / aspectRatio)
: new Size(aspectRatio, 1);
}
#endregion
#region Helper functions
private Thickness AdornerMargin()
{
Thickness thick = new Thickness(0);
if (AdornedElement is FrameworkElement)
{
thick = ((FrameworkElement)AdornedElement).Margin;
}
return thick;
}
private void BuildCorner(ref CropThumb crt, Cursor crs)
{
if (crt != null) return;
crt = new CropThumb(_cpxThumbWidth);
// Set some arbitrary visual characteristics.
crt.Cursor = crs;
_cnvThumbs.Children.Add(crt);
}
private Point UnitsToPx(double x, double y)
{
return new Point((int)(x * s_dpiX / 96), (int)(y * s_dpiY / 96));
}
#endregion
#region Visual tree overrides
// Override the VisualChildrenCount and GetVisualChild properties to interface with
// the adorner's visual collection.
protected override int VisualChildrenCount { get { return _vc.Count; } }
protected override Visual GetVisualChild(int index) { return _vc[index]; }
#endregion
#region Internal Classes
class CropThumb : Thumb
{
#region Private variables
int _cpx;
#endregion
#region Constructor
internal CropThumb(int cpx)
: base()
{
_cpx = cpx;
}
#endregion
#region Overrides
protected override Visual GetVisualChild(int index)
{
return null;
}
protected override void OnRender(DrawingContext drawingContext)
{
drawingContext.DrawRoundedRectangle(Brushes.White, new Pen(Brushes.Black, 1), new Rect(new Size(_cpx, _cpx)), 1, 1);
}
#endregion
#region Positioning
internal void SetPos(double x, double y)
{
Canvas.SetTop(this, y - _cpx / 2);
Canvas.SetLeft(this, x - _cpx / 2);
}
#endregion
}
#endregion
}
}
和PunctedRect(我不能在此处包含代码,因为它超出了问题长度限制,抱歉添加了链接)
我尝试创建的是可以在Win7上运行的裁剪工具,并允许我选择具有宽高比的图像部分。
正如我之前所写,我尝试修复了ActualWidth问题,但我无法做到。如何解决这个问题?
有人可以建议描述功能的替代(免费)控制吗?有许多WUP(Windows通用平台)应用程序和控件,但我需要Win7兼容。
答案 0 :(得分:2)
在第一次访问ActualWidth之前尝试调用UpdateLayout。
this.UpdateLayout();
来自MSDN:
当您调用此方法时,IsMeasureValid为false或IsArrangeValid为false的元素将调用特定于元素的MeasureCore和ArrangeCore方法,这会强制进行布局更新,并且将验证所有计算的大小。
[...]如果您绝对需要更新尺寸和位置,并且只有在您确定所控制的属性的所有更改都可能影响布局完成后才能调用UpdateLayout。