需求很常见,就是用户上传头像前进行固定大小的裁剪。
百度一番,找到几个差不多的,
其一 http://download.csdn.net/detail/tianhaosen/7159901,这个的实现方式是截图框大小固定不变,背景图可以通过鼠标拖动和鼠标滚轮缩放,经过测试,这个对图片的裁剪不是很准确,尤其是大图或者靠近图片边缘裁剪的时候会出现较大误差,然后我尝试调整了下截图位置的算法,但多少还是有偏差,无奈只好放弃,有兴趣的同学可以下下来研究研究。
其二 http://blog.csdn.net/jtl309/article/details/50651911,这个的实现方式是背景图片保持大小不变,裁剪框可以通过鼠标拖动进行缩放。于是尝试在这个的基础上进行调整:)
首先,裁剪框的拖动以及缩放作者已经做的很完善了,主要是利用了Thumb控件进行自定义。我的需求是裁剪框要是一个固定大小的正方形,所以我对作者的源码进行了修改。源码中作者是在DragHelperBase.cs中对矩形框的大小进行实时计算,其中有4个ResizeFrom**方法,我在这4个方法的入参中均加入了宽高(out double HeightNew,out double WidthNew),并且在方法的最后一行赋值高和宽相等,这样就保证了裁剪框始终是正方形,另外,作者的这个框框拖动的时候能把框拖到大边框以外,是个小bug,我这边在每次拖动之后都跟Parent.ActualWidth比较一下。ok,算法代码修改完毕,下面就是对样式代码进行了一些调整,这个各位同学根据自己需求改动即可。
主要修改了DragHelperBase.cs代码:
#region ResizeElement
private Rect ResizeElement(CustomThumb HitedThumb, double HorizontalChange, double VerticalChange)
{
#region Get Old Value
if (HitedThumb == null) return Rect.Empty;
Rect TargetActualBound = GetTargetActualBound();
double TopOld = CorrectDoubleValue(TargetActualBound.Y);
double LeftOld = CorrectDoubleValue(TargetActualBound.X);
double WidthOld = CorrectDoubleValue(TargetActualBound.Width);
double HeightOld = CorrectDoubleValue(TargetActualBound.Height);
double TopNew = TopOld;
double LeftNew = LeftOld;
double WidthNew = WidthOld;
double HeightNew = HeightOld;
#endregion
if (HitedThumb.DragDirection == DragDirection.TopLeft
|| HitedThumb.DragDirection == DragDirection.MiddleLeft
|| HitedThumb.DragDirection == DragDirection.BottomLeft)
{
ResizeFromLeft(DragHelperParent, LeftOld, WidthOld, TopOld, HeightOld, HorizontalChange, out LeftNew, out WidthNew, out HeightNew);
}
if (HitedThumb.DragDirection == DragDirection.TopLeft
|| HitedThumb.DragDirection == DragDirection.TopCenter
|| HitedThumb.DragDirection == DragDirection.TopRight)
{
ResizeFromTop(DragHelperParent, LeftOld, WidthOld, TopOld, HeightOld, VerticalChange, out TopNew, out HeightNew, out WidthNew);
}
if (HitedThumb.DragDirection == DragDirection.TopRight
|| HitedThumb.DragDirection == DragDirection.MiddleRight
|| HitedThumb.DragDirection == DragDirection.BottomRight)
{
ResizeFromRight(DragHelperParent, LeftOld, WidthOld,TopOld,HeightOld, HorizontalChange, out WidthNew, out HeightNew);
}
if (HitedThumb.DragDirection == DragDirection.BottomLeft
|| HitedThumb.DragDirection == DragDirection.BottomCenter
|| HitedThumb.DragDirection == DragDirection.BottomRight)
{
ResizeFromBottom(DragHelperParent, LeftOld, WidthOld, TopOld, HeightOld, VerticalChange, out HeightNew, out WidthNew);
}
WidthNew = WidthNew < 0 ? 0 : WidthNew;
HeightNew = HeightNew < 0 ? 0 : HeightNew;
this.Width = WidthNew;
this.Height = HeightNew;
Canvas.SetTop(this, TopNew);
Canvas.SetLeft(this, LeftNew);
return new Rect
{
X = LeftNew,
Y = TopNew,
Width = WidthNew,
Height = HeightNew
};
}
#endregion
#region Resize Base Methods
private static void CalcSize(double h,double w)
{
}
#region ResizeFromTop
private static void ResizeFromTop(FrameworkElement Parent, double LeftOld, double WidthOld, double TopOld, double HeightOld, double VerticalChange, out double TopNew, out double HeightNew, out double WidthNew)
{
double MiniHeight = 10;
double top = TopOld + VerticalChange;
TopNew = ((top + MiniHeight) > (HeightOld + TopOld)) ? HeightOld + TopOld - MiniHeight : top;
TopNew = TopNew < 0 ? 0 : TopNew;
HeightNew = HeightOld + TopOld - TopNew;
HeightNew = CorrectNewHeight(Parent, TopNew, HeightNew);
double tmpWidth = WidthOld;
if (LeftOld+HeightNew>Parent.ActualWidth)
{
HeightNew = tmpWidth;
WidthNew = tmpWidth;
}
else
{
WidthNew = HeightNew;
tmpWidth = HeightNew;
}
}
#endregion
#region ResizeFromLeft
private static void ResizeFromLeft(FrameworkElement Parent, double LeftOld, double WidthOld, double TopOld, double HeightOld, double HorizontalChange, out double LeftNew, out double WidthNew, out double HeightNew)
{
double MiniWidth = 10;
double left = LeftOld + HorizontalChange;
LeftNew = ((left + MiniWidth) > (WidthOld + LeftOld)) ? WidthOld + LeftOld - MiniWidth : left;
LeftNew = LeftNew < 0 ? 0 : LeftNew;
WidthNew = WidthOld + LeftOld - LeftNew;
WidthNew = CorrectNewWidth(Parent, LeftNew, WidthNew);
double tmpHeight = HeightOld;
if (TopOld + WidthNew > Parent.ActualHeight)
{
WidthNew = tmpHeight;
HeightNew = tmpHeight;
}
else
{
HeightNew = WidthNew;
tmpHeight = WidthNew;
}
}
#endregion
#region ResizeFromRight
private static void ResizeFromRight(FrameworkElement Parent, double LeftOld, double WidthOld, double TopOld, double HeightOld, double HorizontalChange, out double WidthNew, out double HeightNew)
{
if (LeftOld + WidthOld + HorizontalChange < Parent.ActualWidth)
{
WidthNew = WidthOld + HorizontalChange;
}
else
{
WidthNew = Parent.ActualWidth - LeftOld;
}
if (TopOld + HeightOld + HorizontalChange < Parent.ActualHeight)
{
HeightNew = HeightOld + HorizontalChange;
}
else
{
HeightNew = Parent.ActualWidth - TopOld;
}
WidthNew = WidthNew < 0 ? 0 : WidthNew;
if (WidthNew<HeightNew)
{
HeightNew = WidthNew;
}
else
{
WidthNew = HeightNew;
}
}
#endregion
#region ResizeFromBottom
private static void ResizeFromBottom(FrameworkElement Parent, double LeftOld, double WidthOld, double TopOld, double HeightOld, double VerticalChange, out double HeightNew, out double WidthNew)
{
if (TopOld + HeightOld + VerticalChange < Parent.ActualWidth)
{
HeightNew = HeightOld + VerticalChange;
}
else
{
HeightNew = Parent.ActualWidth - TopOld;
}
if (LeftOld + WidthOld + VerticalChange < Parent.ActualWidth)
{
WidthNew = WidthOld + VerticalChange;
}
else
{
WidthNew = Parent.ActualWidth - LeftOld;
}
HeightNew = HeightNew < 0 ? 0 : HeightNew;
if (WidthNew < HeightNew)
{
HeightNew = WidthNew;
}
else
{
WidthNew = HeightNew;
}
}
#endregion
现在回到我们自己的项目,把上面修改过的工程生成的dll引用进来,
<Canvas x:Name="canvas" Grid.Row="1" Width="500" Height="500">
<Rectangle Stroke="{StaticResource ButtonBackgroundBrush}"
Fill="Transparent" StrokeThickness="2"
Width="300" Height="300" Canvas.Left="100" Canvas.Top="100"
UICommon:DragControlHelper.IsEditable="True"
UICommon:DragControlHelper.IsSelectable="True"/>
<UICommon:DragControlHelper CornerWidth="6" Background="{StaticResource CancleButtonBackgroudBrush}" BorderBrush="{StaticResource ButtonBackgroundBrush}" DragChanging="DragControlHelper_DragChanging" DragCompleted="DragControlHelper_DragCompleted"/>
</Canvas>
<StackPanel Grid.Row="2" HorizontalAlignment="Center" Orientation="Horizontal">
<Button Content="取消" Name="btnCancel" Width="62" Height="32" Foreground="#FFFFFF" Style="{StaticResource CustomButtonStyle}"
Margin="10" Click="btnCancel_Click" Background="{StaticResource CancleButtonBackgroudBrush}"></Button>
<Button Content="确定" Name="btnConfirm" Width="62" Height="32" Foreground="#FFFFFF" Style="{StaticResource CustomButtonStyle}"
Margin="10" Click="btnConfirm_Click" Background="{StaticResource ButtonBackgroundBrush}"></Button>
</StackPanel>
private void DragControlHelper_DragCompleted(object Sender, UICommon.Controls.DragChangedEventArgs e)
{
newBound = e.NewBound;//拖动完成后的新位置
}
private void btnConfirm_Click(object sender, RoutedEventArgs e)
{
Bitmap map = new Bitmap(choosedImagePath);
int mapHeight = map.Height;
int mapWidth = map.Width;//图片宽高
int actualHeight = 0;
int actualWidth = 0;//Canvas宽高
int offset = 1;
int left = 0;
int top = 0;
int x = (int)newBound.X;
int y = (int)newBound.Y;
if (mapHeight>mapWidth)//此时高度撑满500
{
actualHeight = 500;
actualWidth = 500 * mapWidth / mapHeight;
left = (500 - actualWidth) / 2;
x -= left;
offset = mapHeight / 500d;//需要小数
}
else//此时宽度撑满500
{
actualWidth = 500;
actualHeight = 500 * mapHeight / mapWidth;
top = (500 - actualHeight) / 2;
y -= top;
offset = mapWidth / 500d;
}
//计算截图位置和大小
int w= (int)Math.Round(newBound.Width * offset, MidpointRounding.AwayFromZero);
int h = (int)Math.Round(newBound.Height * offset, MidpointRounding.AwayFromZero);
int startX=(int)Math.Round( x * offset,MidpointRounding.AwayFromZero);
int startY=(int)Math.Round( y * offset,MidpointRounding.AwayFromZero);
var resultMap = KiCut(map,startX,startY, w, h);
if (resultMap==null)
{
Alert alert = new Alert("图片裁剪失败,请重新选择图片。");
alert.Owner = this;
alert.ShowDialog();
return;
}
string savePath = NIM.ToolsAPI.GetLocalAppDataDir() + ConfigHelper.GetSettingStr("appDataDir") + "\\CutImageTemp\\";
if (!Directory.Exists(savePath))
{
Directory.CreateDirectory(savePath);
}
string name = "UserHead_" + DateTimeHelper.DatetimeConvertUnix() + ".jpg";
string path=savePath + name;
if (resultMap.Width!=300)//用户调整了裁剪框大小
{
Bitmap newImage = new Bitmap(300, 300, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
Graphics g = Graphics.FromImage(newImage);
g.DrawImage(resultMap, 0, 0, 300, 300);
g.Dispose();
newImage.Save(path, ImageFormat.Jpeg);
}
else//直接保存
{
resultMap.Save(path, ImageFormat.Jpeg);
}
}
private static Bitmap KiCut(Bitmap b, int StartX, int StartY, int iWidth, int iHeight)
{
if (b == null)
{
return null;
}
try
{
Bitmap bmpOut = new Bitmap(iWidth, iHeight, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
Graphics g = Graphics.FromImage(bmpOut);
g.DrawImage(b, new System.Drawing.Rectangle(0, 0, iWidth, iHeight), new System.Drawing.Rectangle(StartX, StartY, iWidth, iHeight), GraphicsUnit.Pixel);
g.Dispose();
return bmpOut;
}
catch(Exception ex)
{
return null;
}
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
BinaryReader binReader = new BinaryReader(File.Open(choosedImagePath, FileMode.Open));
FileInfo fileInfo = new FileInfo(choosedImagePath);
byte[] bytes = binReader.ReadBytes((int)fileInfo.Length);
binReader.Close();
bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.StreamSource = new MemoryStream(bytes);
bitmap.EndInit();
ImageBrush brush = new ImageBrush(bitmap);
brush.Stretch = Stretch.Uniform;
this.canvas.Background = brush;
}
稍微解释下代码,以上代码只是核心部分,另外newBound在构造函数中需要初始化new Rect(100, 100, 300, 300),因为我这边需要裁剪成300X300的图片,并且我在窗口的Load事件里给Canvas设置了背景图片,也就是用户选择的图片(路径存放在choosedImagePath变量中),并且设置其Stretch属性为Uniform,这样能保证图片在框中自适应正常显示。
虽然我这边默认框大小是300X300,但是一旦用户缩放了裁剪框,我们就需要对裁剪下来的图片进行放大或压缩,然后再做进一步操作(如转码base64、上传至服务器)。