原文:Windows Phone – 裁剪图片 (Crop Image)

最近在处理图像的功能,对於图像的比例我也不是非常的清楚,因此,在编辑图片上花了不少时间。

该篇文章主要说明的是:如何对图片选择需要的范围进行裁剪(Crop Image)

如果App里想要用到图像,通常我们会使用PhotoChooserTask来取得图像并加上一个Image Control显示内容,

程式码如下:

void AddImageBtn_Click(object sender, RoutedEventArgs e)

{

    // 呼叫PhotoTask取得指定的图示

    PhotoChooserTask tTask = new PhotoChooserTask();

    tTask.Completed += PhotoTask_Completed;

    tTask.Show();

}

?

void PhotoTask_Completed(object sender, PhotoResult e)

{

    if (e.Error != null)

        MessageBox.Show(e.Error.Message, "Error", MessageBoxButton.OK);

    else

    {

        if (e.ChosenPhoto == null) return;

        BitmapImage tBitMap = new BitmapImage();

        tBitMap.SetSource(e.ChosenPhoto);

        image1.Source = tBitMap;

    }

}

但是这样的内容就跟原来选到的内容是一样的,那如果我只想要某一块的内容呢?就需要加点程式来处理了。

?

参考<Custom image cropping in Windows Phone 7 - Part 1 of 2>与<Custom image cropping in Windows Phone 7 - Part 2 of 2>

这两篇的内容说明如何透过手指要裁剪的指定范围,其主要的概念分成几个部分:

?

(1) 采用整个LayoutRoot为底图,上方加入一个Image控件;并且加入4个按钮

???? 最重要的是该Image控件,定义了要裁剪的对象;透过PhotoChooserTask取得要裁剪的图像;

???? 4个按钮,其任务为了裁剪图像。

?

?

(2) 绘制一个方框,搭配手指移动调整要裁剪的范围

???? 增加透过手指的Touch输入,调整Rectangle的大小,搭配其中一个Accept的按钮执行透过该Rectangle裁剪图像。

???? 首先,根据参考文件的Part 1介绍了透过手指的滑动,调整Rectangle的大小范围,如下:

void SetPicture()

{

    Rectangle rect = new Rectangle();

    rect.Opacity = .5;

    rect.Fill = new SolidColorBrush(Colors.White);

    rect.Height = image1.Height;

    rect.MaxHeight = image1.Height;

    rect.MaxWidth = image1.Width;

    rect.Width = image1.Width;

    rect.Stroke = new SolidColorBrush(Colors.Red);

    rect.StrokeThickness = 2;

    rect.Margin = image1.Margin;

    rect.ManipulationDelta += new EventHandler<ManipulationDeltaEventArgs>(rect_ManipulationDelta);

?

    LayoutRoot.Children.Add(rect);

}

?

void rect_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)

{

    // 实现移动时,手指往外移动范围加大;往内移动范围缩小的效果。

    Rectangle r = (Rectangle)sender;

    // 利用 -= 的方式来调整

    r.Width -= e.DeltaManipulation.Translation.X;

    r.Height -= e.DeltaManipulation.Translation.Y;            

}

?????? 里面滑动的事件主要注册了ManipulationDelta,该事件为主要操作的Trigger,往下先补充有关Rectangle的操作说明。

???

载入图片不是最重要的部分,最重要是怎麽操作Rectangle与最後根据Rectangle的大小对应至图片中的范围进行裁剪,

往下先针对Rectangle的操作加以说明:

?

Rectangle

??? 绘制一个矩形的类别,可以具有Stoke(笔触)与Fill(填充)。

类型 名称 说明
Properties Opacity 设定或取得对象的透明程度。
Properties Fill 设定或取得如何绘制内部ShapeBrush。 (Inherited from Shape.)
Properties Stroke 设定或取得如何绘制指定Shape轮廓的Brush。 (Inherited from Shape.)
Properties StrokeThickness 设定或取得如何绘制指定Shape笔划轮廓的宽度。 (Inherited from Shape.)
Event ManipulationDelta 发生於当输入设备(例如:手指)开始操作UIElement时。(Inherited from UIElement.)

??? 透过ManipulationDelta负责处理当input device(输入设备)开始操作的UIElement开始,去修改目前Rectangle的大小,

进一步去调整要裁剪的范围。对於如何处理ManipulationDelta的事件,参考<How to: Handle Manipulation Events>来加以说明:

?

透过控制Manipulation事件对於touch与multitouch输入产生回应,进而对物件进行move、scale的调整。该事件由UIElement所提供。

在Windows Phone里manipulation events支援三种类型:

?ManipulationCompleted

??? 该事件发生於manipulation与inertia(惯性)完成时。

?

?ManipulationStarted

??? 该事件发生於当有input device (例如:touch)开始在UIElement进行manipulation。

?

?ManipulationDelta

??? 该事件发生於当input device在操作UIElment过程中改变了位置。例如:touch该UIElement时,由A这个位置移动到另一个位置。

??? 该事件在操作期间会发生多次,主要是因为touch deivce未离开UIElement之前该事件的触法会一直存在。

??? 那麽在操作时程式要怎麽处理呢,如下范例:

public Page2()

{

    InitializeComponent();

    // 初始化

    Initialization();

}

?

private TransformGroup transformGroup;

private TranslateTransform translation;

private ScaleTransform scale;

?

private void Initialization()

{

    this.transformGroup = new TransformGroup();

    this.translation = new TranslateTransform();

    this.scale = new ScaleTransform();

    // 注册要处理的效果 transliation与scale

    this.transformGroup.Children.Add(this.scale);

    this.transformGroup.Children.Add(this.translation);

    this.rectangle.RenderTransform = this.transformGroup;

    // 注册manipulation的事件

    this.ManipulationDelta += this.PhoneApplicationPage_ManipulationDelta;

}

?

private void PhoneApplicationPage_ManipulationDelta(object sender, 

    System.Windows.Input.ManipulationDeltaEventArgs e)

{

    // Scale the rectangle.

    //this.scale.ScaleX *= e.DeltaManipulation.Scale.X;

    //this.scale.ScaleY *= e.DeltaManipulation.Scale.Y;

?

    // Move the rectangle.

    this.translation.X += e.DeltaManipulation.Translation.X;

    this.translation.Y += e.DeltaManipulation.Translation.Y;

}

????? 在ManipulationDelta事件中,透过参数ManipluationDeltaEventArgs取得操作的效果值,配合公式完成UIElement的移动与Scale调整。

????? 更多有关手势操作的内容可以参考<Windows Phone 7 - 浅谈手势(Gestures)运作>的说明。

?

?

(3) 执行裁剪的重要逻辑,保持移动与缩放的值,重新计算实际要剪下的范围

????? 在这个部分,根据原文的说明将Rectangle的移动与大小的计算分开,增加了许多变数来加以协助,往下便依步骤说明如何调整:

?

3-1. 增加变数协助逻辑计算

// 标记目前是否为移动的状态

private bool isMove = false;

// 暂存Translation的X与Y值

private double trX = 0;

private double trY = 0;

// 独立Rectangle用於保存目前要裁剪的范围

private Rectangle r;

?

3-2. 调整拥有Image的LayoutRoot.width/height与Image相同

void SetPicture()

{

    Rectangle rect = new Rectangle();

    rect.Opacity = .5;

    rect.Fill = new SolidColorBrush(Colors.White);

    rect.Height = image1.Height;

    rect.MaxHeight = image1.Height;

    rect.MaxWidth = image1.Width;

    rect.Width = image1.Width;

    rect.Stroke = new SolidColorBrush(Colors.Red);

    rect.StrokeThickness = 2;

    rect.Margin = image1.Margin;

    rect.ManipulationDelta += new EventHandler<ManipulationDeltaEventArgs>(rect_ManipulationDelta);

?

    LayoutRoot.Children.Add(rect);

?

    // 增加整个LayoutRoot的width/height与image相同,

    // 这样Rectangle就可以使用与image相同的coordinate。

    LayoutRoot.Width = image1.Width;

    LayoutRoot.Height = image1.Height;

}

??? 在原有的SetPicture()加入两行指令,让LayoutRoot的width、height与Image相同,这样LayoutRoot才可以与Image的coordinate对齐。

??? 进行裁剪时才能对在相同的Point上。

?

3-3. 调整ManipulationDelta的事件处理逻辑

void rect_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)

{

    // 利用Rectangle的建立通用的Transform

    GeneralTransform gt = ((Rectangle)sender).TransformToVisual(LayoutRoot);

    Point p = gt.Transform(new Point(0, 0));

?

    // 计算LayoutRoot与Rectangle的间距:X、Y

    int intermediateValueY = (int)((LayoutRoot.Height - ((Rectangle)sender).Height));

    int intermediateValueX = (int)((LayoutRoot.Width - ((Rectangle)sender).Width));

    Rectangle croppingRectangle = (Rectangle)sender;

?

    if (isMove)

    {

        // 负责移动的逻辑

        TranslateTransform tr = new TranslateTransform();

        trX += (int)e.DeltaManipulation.Translation.X;

        trY += (int)e.DeltaManipulation.Translation.Y;

?

        // 识别移动後的Y是否小於间距范围

        // True:给予最小参数

        // False:大於间距范围,给予最大间距

        if (trY < (-intermediateValueY / 2))

        {

            trY = (-intermediateValueY / 2);

        }

        else if (trY > (intermediateValueY / 2))

        {

            trY = (intermediateValueY / 2);

        }

?

        // 识别移动後的X是否小於间距范围

        // True:给予最小参数

        // False:大於间距范围,给予最大间距

        if (trX < (-intermediateValueX / 2))

        {

            trX = (-intermediateValueX / 2);

        }

        else if (trX > (intermediateValueX / 2))

        {

            trX = (intermediateValueX / 2);

        }

?

        // 修改为新的X,Y

        tr.X = trX;

        tr.Y = trY;

        croppingRectangle.RenderTransform = tr;

    }

    else

    {

        // 负责大小缩放的逻辑,>=0 代表手指往右移动,相反的往左移动,修改Width

        if (p.X >= 0)

        {

            // 识别此次移动的X, 是否小於等於区间范围

            if (p.X <= intermediateValueX)

            {

                croppingRectangle.Width -= (int)e.DeltaManipulation.Translation.X;

            }

            else

            {

                croppingRectangle.Width -= (p.X - intermediateValueX);

            }

        }

        else

        {

            croppingRectangle.Width -= Math.Abs(p.X);

        }

?

        // 负责大小缩放的逻辑,>=0 代表手指往下移动,相反的往上移动,修改Height

        if (p.Y >= 0)

        {

            if (p.Y <= intermediateValueY)

            {

                croppingRectangle.Height -= (int)e.DeltaManipulation.Translation.Y;

            }

            else

            {

                croppingRectangle.Height -= (p.Y - intermediateValueY);

            }

        }

        else

        {

            croppingRectangle.Height -= Math.Abs(p.Y);

        }

    }

    //

}

??? 此段的逻辑相对复杂许多,主要是搭配Rectangle透过TransformToVisual(LayoutRoot)将LayoutRoot的座标转换为指定的视觉物件。

??? 再透过GeneralTransform重新取得目前移动後的Point;

??? 接着计算此次移动所造成LayoutRoot与Rectangle在X、Y的间距,接着识别目前是为移动模式还是缩放模式,进一步依手指移动的范围

??? 进行X、Y(则建立一个新的TranslateTransform根据计算移动的范围进行调整)或Width、Height(直接调整Rectangle物件)。

?

3-4. 利用Rectangle的范围进行裁剪图片

/// <summary>

/// 利用Rectangle取得Image要调整至新大小的范围。

/// </summary>

void ClipImage()

{            

    // 取得画面上的Rectangle

    r = (Rectangle)(from c in LayoutRoot.Children where c.Opacity == .5 select c).First();

?

    // 利用Rectangle建立RectangleGeometry指定要用来裁剪的大小

    RectangleGeometry geo = new RectangleGeometry();

    GeneralTransform gt = r.TransformToVisual(LayoutRoot);

    Point p = gt.Transform(new Point(0, 0));

    geo.Rect = new Rect(p.X, p.Y, r.Width, r.Height);

    

    // 对image进行裁剪

    image1.Clip = geo;

    

    r.Visibility = System.Windows.Visibility.Collapsed;

?

    // 将image移动到裁剪的座标

    TranslateTransform t = new TranslateTransform();

    t.X = -p.X;

    t.Y = -p.Y;

    image1.RenderTransform = t;

}

??? 先取得Rectangle物件,再一次使用GeneralTransform取得LayoutRoot的座标转换为指定的视觉物件後,建立一个RectangleGeometry

??? 将取得的座标指定给该矩形类别,并且指定它的维度。接着指定image.Clip的值,让image进行裁剪形成我们选择的范围

??? 最後搭配TrasnslateTransform移动裁剪好的图示至预设位置。

??? 裁剪好的图示要怎麽储存呢,请参考下方的程式内容:

/// <summary>

/// 将画面撷取下来产生档案。

/// </summary>

/// <param name="element"></param>

void WriteBitmap(FrameworkElement element, string filename)

{

    WriteableBitmap wBitmap = new WriteableBitmap(element, null);

?

    using (MemoryStream stream = new MemoryStream())

    {

        wBitmap.SaveJpeg(stream, (int)element.Width, (int)element.Height, 0, 100);

?

        using (var local = new IsolatedStorageFileStream(filename, 

            FileMode.Create, IsolatedStorageFile.GetUserStoreForApplication()))

        {

            local.Write(stream.GetBuffer(), 0, stream.GetBuffer().Length);

        }

    }

}

?

GeneralTransform

??? 提供物件的通用转换支援,例如点和矩形。这是个抽象类别。本篇用到Transform()方法转换指定的点,然後传回结果。

?

TranslateTransform

??? 平移(移动)物件2D x-y座标系统。

?

RectangleGeometry

??? 描述二维矩型的类别。Rect属性用於设定/取得矩形的维度。

?

Image

??? 负责处理图像的显示、调整与效果。针对本篇用到的项目加以说明:

类型 名称 说明
Properties Clip Gets or sets the Geometry used to define the outline of the contents of a UIElement. (Inherited from UIElement.)
Properties RenderTransform Gets or sets transform information that affects the rendering position of a UIElement. (Inherited from UIElement.)

?

=====

以上为说明如何实作Crop Image的方式,但是如果参考原文的程式码,它是限制在固定的image物件width/height,

对於实际图像大小有些差异,所以我做了一个简单的调整:

?

(1) 调整放入图片时将原始图片加上萤幕比例的Size调整

void task_Completed(object sender, PhotoResult e)

{

    BitmapImage image = new BitmapImage();

    image.SetSource(e.ChosenPhoto);

    image1.Source = image;

    // 增加图像适用萤幕比例

    Fit(image.PixelWidth, image.PixelHeight);

    SetPicture();

}

?

/// <summary>

/// 依画面大小进行图像的比例缩放。

/// </summary>

/// <param name="width">图像width</param>

/// <param name="height">图像height</param>

private void Fit(double width, double height)

{

    double tScreenWidth = Application.Current.Host.Content.ActualWidth; 

    double tScreenHeight = Application.Current.Host.Content.ActualHeight;

    // 图像比例换算

    image1.Height = (tScreenWidth / width) * height;

    image1.Width = tScreenWidth;

}

记得要将xaml中指定image的width/height给去掉喔。

?

(2) 修改储存图片的方式,改为直接储存图像而不是全画面

/// <summary>

/// 储存实际图片的方法。

/// </summary>

/// <param name="element"></param>

void WriteBitmap(Image element)

{

    int tWidth = (int)element.Clip.Bounds.Width;

    int tHeight = (int)element.Clip.Bounds.Height;

    // 重新绘制一个新的WriteableBitmap

    WriteableBitmap wBitmap = new WriteableBitmap(tWidth, tHeight);

    // 加上图像来源与使用的RenderTransform

    wBitmap.Render(element, element.RenderTransform);

    wBitmap.Invalidate();

?

    using (MemoryStream stream = new MemoryStream())

    {

        wBitmap.SaveJpeg(stream, tWidth, tHeight, 0, 100);

?

        using (var local = new IsolatedStorageFileStream("myImage1.jpg",

            FileMode.Create, IsolatedStorageFile.GetUserStoreForApplication()))

        {

            local.Write(stream.GetBuffer(), 0, stream.GetBuffer().Length);

        }

    }

}

?

[范例程式]

Page3.xaml为实际的范例程式。

?

[补充]

1. Rotate transforms are not supported on Windows Phone.

2. Manipulation events are supported by default on Windows Phone, so the IsManipulationEnabled property is not supported.

3. Inertia events are not supported in this release of Silverlight for Windows Phone.

4. 在Windows Phone里gesture events支援三种类型:

?? ?Tap:发生於当在该UIElement执行了Tag gesture。

?? ?DoubleTap:发生於当在该UIElement执行了DoubleTap gesture。

?? ?Hold:发生於当在该UIElement执行了Hold gesture。

======

本篇主要介绍在图像处理的撷取与编辑,不过里面有些内容可能不是解释的非常好,尤其是相关图像处理的部分,请大家多多包涵。

如果有写错的地方也请大家加以指导,谢谢。

?

References

?Custom image cropping in Windows Phone 7 - Part 1 of 2

?Custom image cropping in Windows Phone 7 - Part 2 of 2

?How to Crop an Image using the WriteableBitmap class?

?Silverlight Control to Crop Images

?Cropping an image in Silverlight 3

?How to cut a part of image in C#

?Cropping Images in Windows Phone 7 & Custom Image Cropping - Windows Phone 7

?Cropping an image stored locally

?Cropping and Scaling Images

?Gesture Support for Windows Phone & Windows Phone 7 Gestures

?WPF: How to apply a GeneralTransform to a Geometry data and return the new geometry?

?[Silverlight入门系列]用TransformToVisual和Transform取得元素绝对位置(Location)

?Silverlight - How to load and clip an image in code

?Cropping Images in Windows Phone 7 (重要重新绘制writeablebitmap)

?

Dotblogs 的标签: Windows Phone

enjoy developing application and learning new technology time.

DotBlogs Tags:

Windows Phone

posted on
2013/6/24 22:15
|

我要推荐

|
阅读数 : 1575

| 文章分类 [

Windows Phone

]

|
订阅

Windows Phone – 裁剪图片 (Crop Image)的更多相关文章

  1. 如何兼容所有Android版本选择照片或拍照然后裁剪图片--基于FileProvider和动态权限的实现

    我们知道, Android操作系统一直在进化. 虽然说系统是越来越安全, 可靠, 但是对于开发者而言, 开发难度是越来越大的, 需要注意的兼容性问题, 也越来越多. 就比如在Android平台上拍照或 ...

  2. cropper(裁剪图片)插件使用(案例)

    公司发布微信H5前端阵子刚刚弄好的H5端的图片上传插件,现在有需要裁剪图片.前端找了一个插件---cropper 本人对这插件不怎么熟悉,这个案例最好用在一个页面只有一个上传图片的功能上而且只适合单个 ...

  3. cropper实现基本的裁剪图片并上传

    使用cropper之前需要先引用 cropper.css 和 cropper.js cropper 官网:https://fengyuanchen.github.io/cropper/ cropper ...

  4. jquery photoClip支持手机端,PC端 本地裁剪图片后上传插件

    支持手机,PC最好的是jquery photoClip插件,下载地址&示例:https://github.com/topoadmin/photoClip demo.html 代码: <! ...

  5. android 开发 实现一个进入相机拍照后裁剪图片或者进入相册选中裁剪图片的功能

    实现思维路径: 以进入相机拍照的思维路线为例子: 1.进入app 2.判断之前是否保存头像,如果有就显示历史图像 (下面代码中在getOldAvatar();方法中执行这个逻辑) 3.点击更换图像的B ...

  6. jquery.cropper 裁剪图片上传

    https://github.com/fengyuanchen/cropper 1.必要的文件引用: <script src="/path/to/jquery.js"> ...

  7. MVC使用JCrop上传、裁剪图片

    JCrop用来裁剪图片,本篇想体验的是: 在视图页上传图片: 上传成功,跳转到另外一个编辑视图页,使用JCrop对该图片裁剪,并保存图片到指定文件夹: 裁剪成功后,在主视图页显示裁剪图片: 当然,实际 ...

  8. .mat转成.npy文件+Python(Pytorch)压缩裁剪图片

    需求:现有数据文件V1.mat,里面包含多个数据集,现需将里面的images数据集提取出来,然后进行压缩裁剪成指定大小 V1.mat数据集目录: 1.从mat文件中提取数据(使用Python) V1. ...

  9. Android 从本地图库或拍照后裁剪图片并设置头像

    在QQ和微信等应用都会有设置头像,一般都是从本地图库选取或相机拍照,然后再截图自己喜欢的部分,然后设置.最后一步把截取好的图片再保存到本地,来保存头像.为了大家使用方便,我把自己完整的代码贴出来,大家 ...

随机推荐

  1. android在Canvas使用drawBitmap画一幅画

    1.画图的主要方法 //Bitmap:图片对象,left:向左偏移.top: 顶部偏移     drawBitmap(Bitmap bitmap, float left, float top, Pai ...

  2. BPL vs. DLL

    第一部分:有关包的介绍 一般我们编写编译一个DELPHI应用程序时,会产生一个EXE文件,也就是一个独立的WINDOWS应用程序.很重要的一点:区别于Visual Basic,DELPHI产生的是预先 ...

  3. git/github初级运用自如 (转)

    三 . 设置用户信息 这一步不是很重要,貌似不设置也行,但github官方步骤中有,所以这里也提一下. 在git中设置用户名,邮箱 $ git config --global user.name &q ...

  4. GNU libmicrohttpd 0.9.29 发布 - 开源中国社区

    GNU libmicrohttpd 0.9.29 发布 - 开源中国社区 GNU libmicrohttpd 0.9.29 发布

  5. redhat linux使用Centos yum源

    redhat Linux如果是没有购买红帽许可的话是不能使用redhat的yum源的,但是可以通过修改使之能使用Centos的yum源. 步骤一:删除redhat的yum [root@localhos ...

  6. Indiegogo: An International Crowdfunding Platform to Raise Money

    Indiegogo: An International Crowdfunding Platform to Raise Money The world's funding platform. Fund ...

  7. pygame在安装过程中无法找到videodev.h错误

    首先参考<ubuntu 安装 pygame 非常好玩的东西>.在运行sudo python setup.py时.出现 linux/videodev.h:No such file or di ...

  8. Android应用开发-小巫CSDN博客clientJsoup篇

    Android应用开发-小巫CSDN博客clientJsoup篇 距上一篇博客已经过去了两个星期,小巫也认为很抱歉,由于在忙着做另外一个项目,差点儿抽不出空来,这不小巫会把剩下的博文全部在国庆补上.本 ...

  9. PCB板蛇形走线有什么作用

    PCB板蛇形走线有什么作用  PCB上的不论什么一条走线在通过高频信号的情况下都会对该信号造成时延时,蛇形走线的主要作用是补偿"同一组相关"信号线中延时较小的部分,这些部分一般是没 ...

  10. swfobject.js的简单配置

    因为工作需要在网页中迁入flash,开发过程中,发现直接使用embed自己开发的话需要考虑各种兼容性,也比较麻烦, 网上也找了几个相关的插件,比较使用之下,发现swfobject.js这一款还是蛮不错 ...