图片剪切

canvas剪裁图片并上传,前端一步到位,无需用到后端

背景:

     当前主流的图片剪裁主要有两种实现方式。

     1:flash操作剪裁。2:利用js和dom操作剪裁。

     目前看来这个剪裁主要还是先通过前端上传图片到服务器,然后前端操作后把一些坐标和大小数据传到后台,

然后后台来执行剪裁。我一直觉得这样有很多问题:

     1.必须要先把图片上传到服务器然后才能执行后面的操作

     2.前后端交互太多,需要几次交互数据

 

老的实现方法太low了。我想试试canvas来实现剪裁,就网上搜索了下,是有一些canvas剪裁,类似Jcrop这种。但是我发现好多canvas的插件,

本质还是需要先上传到后台,最后还是后端剪裁,和之前的方式一样,只是用了canvas而已。

 

自己实现前端剪裁一步到位:

  后来我就想了想canvas能存储base64,就用base64传到后端。

      大致思路是这样的:

           -> 表单选择图片

    -> 读取图片,用FileReader获取到原图的base64码

    -> new 一个image,把base64传给src,然后就可以用这个对象

    -> 需要两个canvas,一个canvas是完整的在下层,一个canvas是我们要剪裁的区域在上层

      (因为canvas不能分层,两个重叠的canvas,下层那个canvas保持不动,上层显示我们要剪裁的区域)

      如图:黑色透明的是下层的原图,箭头指向的是上层显示区域。

      

    -> 上图的剪裁区域可以移动和放大,点击保存就会再用一个canvas把剪裁区域 按照原图大小画出来,最后把canvas对象用toDataURL()获取为base64码,就可以上传了。

 

实现起来有一些技术点:

      1.可以自定义 剪裁的图片的比例和最小尺寸,比如下面,设置了原图的宽高必须大于640px,同时剪裁的比例也始终为width :height,当前就是1:1

this._option.crop_min_width = 640;
this._option.crop_min_height = 640;

2.可以自定义 剪裁的容器大小,比如,你只希望它在某个小区域里执行剪裁,设置了这个大小后,会按照正确的比例,把原图缩放在这个容器里供用户操作

this._option.crop_box_width = 300;
 this._option.crop_box_height = 200;

3. 实现显示区域的拖动和显示区域的大小改变。

  4. 需要给剪裁容器包括里面的节点都添加上css3属性 user-select:none。否则会出现拖动的canvas的bug

-webkit-user-select:none;-moz-user-select:none;-o-user-select:none;user-select:none

代码写得很乱,封装的也不好,但是实现了想要的功能,点击保存会显示剪裁的图片按照原图比例,获取到的base64码会在控制台里打印出来。

默认要选择640*640以上的图片,以下是git地址,拉下来试试吧,也许这个方案是一个非常好的方式。

  github地址 https://github.com/zimv/zmCanvasCrop

 

html5 canvas 自定义画图裁剪图片

html5 给我们带来了极大惊喜的canvas标签,有了它我们可以在浏览器客户端处理图片,不需要经过服务器周转。可以实现:

 

1、照片本地处理,ps有的一些基本功能都有

2、结合js可以实现一些很炫的动画效果

 

这篇文章实现一个微信上发图片消息的效果最终效果图:

 

 

 

下面我们先介绍canvas一些基本的用法,这里可能需要一些基本的几何知识,对小伙伴们来说应该不是问题

1、创建一个canvas

       var canvas=document.createElement('canvas');或者获取一个已存在的canvas,var canvas=document.getElementById('canvasid');

             canvas.width=1000;canvas.height=1000;//定义大小

2、创建绘图的上下文

       var context=canvas.getContext('2d');

3、画直线

      context.beiginPath();//开始画图

      context.moveTo(100,50) ;//这个方法类似于我们写字时提笔动作,即把笔提起来,放到指定坐标处

      context.lineTo(100,100);//由(100,50)处画到(100,100)处

      context.lienWidth=2;//定义笔的粗细

      context.strokeStyle='red';//定义笔的颜色

      context.stroke();//以指定的粗细和颜色描绘路径。前面的只是有了路径,必须用stroke方法进行描绘,否则看不到任何效果 

4、画实心三角形

     context.beginPath();

     context.moveTo(100,110);

     context.lineTo(100,210);

     context.lienTo(150,210);

     //context.lineTo(100,110);//这句要不要都无所谓,因为后面的fill方法自动会将路径闭合

     context.fillStyle=‘green’;//填充颜色

     context.fill();//开始填充  

5、画空心三角形(直线加斜线组合)

      context.beiginPath();

      context.moveTo(100,220);

      context.lineTo(100,320);

      context.lineTo(150,320);

      context.closePath();//关闭路径 ,用context.lineTo(100,220)继续画完也可以

      context.lineWidth=3;

      context.stroke();

  

6、画正方形(直线加斜线组合)

      context.beginPath();
      context.moveTo(100,330);
      context.lineJoin='round';
      context.lineTo(100,430);
      context.lineTo(200,430);
      context.lineTo(200,330);
      context.closePath();
      context.lineWidth=10;
      context.strokeStyle='blue';
      context.stroke();

       

     眼尖的小伙伴们应该注意到了,四个拐角是圆的,对的,就是context.lineJoin='round'的功劳,除了round还有bevel(斜角)和miter(尖角),默认miter

7、画圆

      context.beginPath(); 
      context.arc(150500,50,0,2*Math.PI);
      context.lineWidth=2;
      context.strokeStyle='orange';
      context.stroke();  

            

 

8、画曲线

 

     context.beginPath();
     context.moveTo(100,600);
     context.quadraticCurveTo(150,650,100,700);//(150,600)为控制点,(100,700)为曲线终点。可以指定多个控制点,能更精确的控制曲线的走向
     context.stroke();

 

                         

9、裁剪

      //加载图片

      var image=new Image();
      image.src='../images/Penguins.jpg';

     image.onload=function(){

      context.beginPath();

       //画裁剪区域,此处以圆为例

       context.arc(50,50,50,0,2*Math.PI);
       context.clip();//次方法下面的部分为待剪切区域,上面的部分为剪切区域

       context.drawImage(image,0,0,100,100);

}

        

      

 

 

注意:

        1、stroke()方法比较耗性能,如果描绘的样式一样的话建议放在最后执行

        2、用slip方法画裁剪区域过程中不能出现moveTo提笔的操作,否则无法形成完整的区域,剪切的效果大家可以试试。

 

看完以上例子是不是对我们最终要实现的效果有清晰的思路了。

4条直接+4个圆角+2条斜线就可实现。直线和斜线好画,关键在于圆角,有人说直接用lineJoin不就搞定了吗,大家要清楚,lineJoin画出来的圆角角度大小是根据lineWidth确定的,要达到我们要实现的圆角角度,上面画正方形的圆角lineWidth=10,可我们的图片边框要这么粗?显然不符合要求,且难以控制圆角角度。最佳的办法就是用quadraticCurveTo画曲线替换,关键在于确定曲线的三个点:起点,控制点和终点,下面是完整的代码:

 

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>   
    <script type="text/JavaScript">

        
        window.onload=function(){

                var image=new Image();

                image.src='../images/Penguins.jpg';   

                image.onload=function(){

                var canvas=document.createElement('canvas');

                canvas.width=106;

                canvas.height=100;

                context=canvas.getContext('2d');

                context.moveTo(0, 6);
                context.lineTo(0, 100-6);
                context.quadraticCurveTo(0, 100, 6, 100);
                context.lineTo(100-6, 100);
                context.quadraticCurveTo(100, 100, 100, 100-6);
                context.lineTo(100,27);
                context.lineTo(100+5,22);
                context.lineTo(100,17);
                context.lineTo(100, 6);
                context.quadraticCurveTo(100, 0, 100-6, 0);
                context.lineTo(6, 0);
                context.quadraticCurveTo(0, 0, 0, 6);
                context.lineWidth=0.5;

                context.stroke();

                context.clip();

                context.drawImage(image,0,0,106,100);

                document.body.appendChild(canvas);

                }

}
    </script>
</head>
<body style="margin:0px;padding:0px;">
</body>
</html>最终效果图:

当初为实现这个效果,因为刚接触canvas,找了很多资料,网上很多都是介绍规则图形裁剪例子,没有不规则的,最终实现时,万分激动啊,终于可以在聊天发图片时有微信上的的感觉。

HTML5 Canvas 实现图片压缩和裁切

HTML5 Canvas 实现图片压缩和裁切

前面的话

早些时候用 Node-webkit (现在叫 nw.js) 编写过一个辅助前端切图的工具,其中图片处理部分用到了 gm ,gm 虽然功能强大,但用于 Node-webkit 却有点发挥不了用处,gm 强依赖于用户的本地环境安装 imagemagick 和 graphicsmagick ,而安装 imagemagick 和 graphicsmagick 非常不方便,有时候还需要FQ,所以这个工具大多数时候是我自己在玩。

为了降低安装成本,这两天开始研究去掉图片处理功能中的 gm 依赖,替换为 HTML5 Canvas 来实现。

在这之前没有深入研究过 canvas,通过这两天的查资料过程,发现 canvas 的 API 非常丰富,实现本文的功能可以说只用到了 canvas 的冰山一角。

功能实现主要用到了 CanvasRenderingContext2D.drawImage 和 HTMLCanvasElement.toDataURL 两个方法,接下来先介绍一下这两个方法,如果想直接看结果,可以跳到文章结尾查看完整的例子和代码。

CanvasRenderingContext2D.drawImage()

drawImage 方法是 Canvas 2D 对象的方法,作用是将一张图片绘制到 canvas 画布中。

创建一个 Canvas 2D 对象:

var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');

drawImage 有 3 种调用方式:

ctx.drawImage(image, dx, dy);
ctx.drawImage(image, dx, dy, dWidth, dHeight);
ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);

各个参数的意义:

  • image 图片元素,除了图片,还支持其他 3 种格式,分别是 HTMLVideoElement HTMLCanvasElement ImageBitmap ,本文只涉及图片,如果想了解其余格式可以参考这里
  • sx 要绘制到 canvas 画布的源图片区域(矩形)在 X 轴上的偏移量(相对源图片左上角)
  • sy 与 sx 同理,只是换成 Y 轴
  • sWidth 要绘制到 canvas 画布中的源图片区域的宽度,如果没有指定这个值,宽度则是 sx 到图片最右边的距离
  • sHeight 要绘制到画布中的源图片区域的高度,如果没有指定这个值,高度则是 sy 到图片最下边的距离
  • dx 源图片左上角在 canvas 画布 X 轴上的偏移量
  • dy 源图片左上角在画布 Y 轴上的偏移量
  • dWidth 绘制图片的 canvas 画布宽度
  • dHeight 绘制图片的画布高度

是不是有点晕了?下面这张图可以直观地说明它们的关系:

还是不好理解?那换个姿势,可以这么理解:首先用 sx 和 sy 这两个值去定位图片上的坐标,再根据这个坐标点去图片中挖出一个矩形,矩形的宽高就是 sWidth 和 sHeight 了。矩形挖出来了,现在要把它绘制到画布中去,这时用 dx 和 dy 两个值来确定矩形在画布中的坐标位置,再用 dWidth 和 dHeight 确定划出多少画布区域给这个矩形。

HTMLCanvasElement.toDataURL()

toDataURL 是 canvas 画布元素的方法,返回指定图片格式的 data URI,也就是 base64 编码串。

toDataURL 方法最多接受两个参数,并且这两个参数都是可选的:

  • type 图片格式。支持 3 种格式,分别是 image/jpeg image/png image/webp ,默认是 image/png 。其中 image/webp 只有 chrome 才支持。
  • quality 图片质量。0 到 1 之间的数字,并且只在格式为 image/jpeg 或 image/webp 时才有效,如果参数值格式不合法,将会被忽略并使用默认值。

另外,如果对应的 canvas 画布宽度或高度为 0,将会得到字符串 data:, ,若图片格式不是 image/png,却得到一个以 data:image/png 开头的值,则说明不支持此图片格式。

图片质量

对于图片质量参数的默认值,官方文档并没有说明, 这里 提到 Firefox 的默认值是 0.92,我在最新 chrome 浏览器中测试发现大概也是这个数字。不过要想达到各平台统一表现,最好的办法是手动设置此参数。

实现图片压缩的关键代码

HTML:

<canvas id="canvas"></canvas>
<img id="preview" src="">
<img id="source" src="" style="display: none;">

JS:

var canvas = document.getElementById('canvas');
var source = document.getElementById('source');
var preview = document.getElementById('preview');

source.onload = function() {
    var width = source.width;
    var height = source.height;
    var context = canvas.getContext('2d');

    // draw image params
    var sx = 0;
    var sy = 0;
    var sWidth = width;
    var sHeight = height;
    var dx = 0;
    var dy = 0;
    var dWidth = width;
    var dHeight = height;
    var quality = 0.92;

    canvas.width = width;
    canvas.height = height;

    context.drawImage(source, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);

    var dataUrl = canvas.toDataURL('image/jpeg', quality);
    preview.src = dataUrl;
};

source.src = 'house.jpg';

编写了一个简单的 Demo ,可输入质量参数查看压缩结果。

图片压缩结果

用作测试的是一张大小为 146 KB 的 JPG 图片。

测试过程分别使用了 50%, 80%, 92%, 95%, 96%, 97%, 98%, 99%, 100% 这八个质量参数,结果如下:

查看原图

换算成图表:

问题

从图表中可以看到,压缩比为 95% 时与原图大小最接近,此后,随着压缩参数增大直到 98%,增长比较规律,但从 98 到 99 尤其是 99 到 100,增长突然变陡,比原图大小翻了将近 3 倍!

这里存在两个问题:

  • 为什么 95% 是最接近原图的压缩比?这是否普遍规律?
  • 为什么 100% 比原图增大了这么多?

在网上查了一些资料,但并没有找到确切的原因,也没有找到与之相匹配的类似问题。或许是我搜索的方式不对?如果你正好知道,欢迎留言告知。

实现图片裁切的不完全代码

function cropImage(targetCanvas, x, y, width, height) {
    var targetctx = targetCanvas.getContext('2d');
    var targetctxImageData = targetctx.getImageData(x, y, width, height); // sx, sy, sWidth, sHeight

    var c = document.createElement('canvas');
    var ctx = c.getContext('2d');

    c.width = width;
    c.height = height;

    ctx.rect(0, 0, width, height);
    ctx.fillStyle = 'white';
    ctx.fill();
    ctx.putImageData(targetctxImageData, 0, 0); // imageData, dx, dy

    document.getElementById('source2').src = c.toDataURL('image/jpeg', 0.92);
    document.getElementById('source2').style.display = 'initial';
}

以上代码中,getImageData 和 putImageData 都是 Canvas 2D 对象的方法,前者用于获取画布上根据参数指定矩形的像素数据,返回的是一个多维数组。后者则用于将这些像素数据绘制到画布中,同样可以指定画布中的绘制位置。

裁切的原理是通过 canvas A 的 getImageData 方法取出图片中指定区域的像素数据,再用 canvas B 的 putImageData 方法将像素数据绘制到 canvas B 中,并保持 canvas B 的尺寸与取出区域的尺寸一致。canvas B 中的图片就是裁切得到的图片区域块。

比如要裁切女帝的左耳环:

简单量一下距离,就可以用下面的代码实现:

cropImage(canvas, 250, 250, 90, 80)

好了,差不多就是这些。

HTML5 本地裁剪图片并上传至服务器(老梗)

很多情况下用户上传的图片都需要经过裁剪,比如头像啊什么的。但以前实现这类需求都很复杂,往往需要先把图片上传到服务器,然后返回给用户,让用户确定裁剪坐标,发送给服务器,服务器裁剪完再返回给用户,来回需要 5 步。步骤繁琐不说,当很多用户上传图片的时候也很影响服务器性能。

HTML5 的出现让我们可以更方便的实现这一需求。虽然这里所说的技术都貌似有点过时了(前端界的“过时”,你懂的),但还是有些许参考价值。在这里我只说一下要点,具体实现同学们慢慢研究。

下面奉上我自己写的一个demo,在输入框中选好自己服务器 url, 生成好图片后点击 Submit 上传,然后自己去服务器里看看效果吧~~

浏览器要求支持以下 Feature:

代码直接从现有项目移植过来,没有经过“太多的”测试,写的很乱,也没注释,大家就慢慢看吧。。。重点就在 js 脚本的 28 行,clipImage 函数中,同学们可以直接跳过去看。

http://jsfiddle.net/windwhinny/d5qan0q7/

第一步:获取文件

HTML5 支持从 input[type=file] 元素中直接获取文件信息,也可以读取文件内容。我们用下面代码就可以实现:

$('input[type=file]').change(function(){
    var file=this.files[0];
    // continue ...
});

第二部:读取文件,并生成 Image 元素

这一步就需要用到 FileReader 了,这个类是专门用来读取本地文件的。纯文本或者二进制都可以读取,但是本地文件必须是经过用户允许才能读取,也就是说用户要在input[type=file]中选择了这个文件,你才能读取到它。

通过 FileReader 我们可以将图片文件转化成 DataURL,就是以 data:image/png;base64, 开头的一种URL,然后可以直接放在image.src 里,这样本地图片就显示出来了。

$('input[type=file]').change(function(){
    var file=this.files[0];

    var reader=new FileReader();
    reader.onload=function(){
        // 通过 reader.result 来访问生成的 DataURL
        var url=reader.result;
        setImageURL(url);
    };
    reader.readAsDataURL(file);
});

var image=new Image();
function setImageURL(url){
    image.src=url;
}

Image 就是在 html 里的 <img> 标签,所以可以直接插入到文档流里。

第三步:获取裁剪坐标

这一步没啥好说的,实现的方法也很多,需要获得下面四个裁剪框的坐标:

  • Y坐标
  • X坐标
  • 高度
  • 宽度

如下图所示:

第四部:裁剪图片

这是时候我们就需要用到 canvas 了,canvas 和图片一样,所以新建 canvas 时就要确定其高宽。这里我们还运用到image.naturalHeight 和 image.naturalWidth 这两个属性来获取图片原始尺寸。

将图片放置入 canvas 时需要调用 drawImage ,这个接口参数比较多,在 MDN 上有详细的说明。

drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)

因为我们用 canvas 只是用于裁剪图片的,所以需要新建一个 canvas 让它的尺寸和裁剪之后图片的尺寸相等,此时 canvas 就相当与我们的裁剪框。运用这个函数还可以将大图缩放成小图,同学们自己研究吧。

// 以下四个参数由第三步获得
var x,  
    y,
    width,
    height;

var canvas=$('<canvas width="'+width+'" height="'+height+'"></canvas>')[0],
    ctx=canvas.getContext('2d');

ctx.drawImage(image,x,y,width,height,0,0,width,height);
$(document.body).append(canvas);

将 canvas 加入文档流之后,就可以看到裁剪后的效果了。不过我们还需要将图片上传至服务器里。

第五步:读取裁剪后的图片并上传

这时我们要获取 canvas 中图片的信息,用 toDataURL 就可以转换成上面用到的 DataURL 。 然后取出其中 base64 信息,再用window.atob 转换成由二进制字符串。但 window.atob 转换后的结果仍然是字符串,直接给 Blob 还是会出错。所以又要用Uint8Array 转换一下。总之这里挺麻烦的。。

var data=canvas.toDataURL();

// dataURL 的格式为 “data:image/png;base64,****”,逗号之前都是一些说明性的文字,我们只需要逗号之后的就行了
data=data.split(',')[1];
data=window.atob(data);
var ia = new Uint8Array(data.length);
for (var i = 0; i < data.length; i++) {
    ia[i] = data.charCodeAt(i);
};

// canvas.toDataURL 返回的默认格式就是 image/png
var blob=new Blob([ia], {type:"image/png"});

这时候裁剪后的文件就储存在 blob 里了,我们可以把它当作是普通文件一样,加入到 FormData 里,并上传至服务器了。

FormData 顾名思义,就是用来创建表单数据的,用 append 以键值的形式将数据加入进去即可。但他最大的特点就是可以手工添加文件或者 Blob 类型的数据,Blob 数据也会被当作文件来处理。原生 js 可以直接传递给 xhr.send(fd), jquery 可以放入 data 里请求。

var fd=new FormData();

fd.append('file',blob);
$.ajax({
    url:"your.server.com",
    type:"POST",
    data:fd,
    success:function(){}
});

然后你服务器里应该就可以收到这个文件了~

posted @ 2016-11-06 17:57  jiangxiaobo  阅读(1666)  评论(0编辑  收藏  举报