HTML5 Canvas处理头像上传

澳门新葡亰赌995577 11

最近社区系统需要支持移动端,其中涉及到用户头像上传,头像有大中小三种尺寸,在PC端,社区用Flash来处理头像编辑和生成,但该Flash控件的界面不友好而且移动端对Flash的支持不好,考虑到这些问题,最后我们选用Canvas来完成图像尺寸缩放和图片数据获取。

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保持不动,上层显示我们要剪裁的区域)

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

      澳门新葡亰赌995577 1

    -> 上图的剪裁区域可以移动和放大,点击保存就会再用一个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;
  1. 实现显示区域的拖动和显示区域的大小改变。

  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可以实现一些很炫的动画效果

 

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

 

澳门新葡亰赌995577 2

 

 

下面我们先介绍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方法进行描绘,否则看不到任何效果 

澳门新葡亰赌995577 3

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();//开始填充  

澳门新葡亰赌995577 4

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

      context.beiginPath();

      context.moveTo(100,220);

      context.lineTo(100,320);

      context.lineTo(150,320);

澳门新葡亰赌995577,      context.closePath();//关闭路径
,用context.lineTo(100,220)继续画完也可以

      context.lineWidth=3;

      context.stroke();

  澳门新葡亰赌995577 5

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();

       澳门新葡亰赌995577 6

    
眼尖的小伙伴们应该注意到了,四个拐角是圆的,对的,就是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();  

            澳门新葡亰赌995577 7

 

8、画曲线

 

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

 

                         澳门新葡亰赌995577 8

9、裁剪

      //加载图片

      var image=new Image();
      image.src=’..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);

}

        澳门新葡亰赌995577 9

      

 

 

注意:

       
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=’..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>最终效果图:

澳门新葡亰赌995577 10

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

等边处理

头像一般都是正方形,首先我们需要获取图片宽度和高度的最小值,用该最小值作为边长居中裁剪图片,最终得到一个正方形的图片:

var ImageEditor = function() {
    // 用离线canvas处理图片数据
    this.canvas = document.createElement('canvas');
    this.context = this.canvas.getContext('2d');
};
var fn = ImageEditor.prototype;
fn.resizeCanvas = function(width, height) {
    this.canvas.width = width;
    this.canvas.height = height;
};
fn.clipSquareImage = function(url, callback) {
    var that = this,
        img = new Image();
    img.src = url;
    img.onload = function() {
        // 取宽高最小值作为正方形边长
        var eLength = Math.min(img.width, img.height),
            picture = img;
        // canvas不支持局部截屏,截屏前必须先调节canvas的宽高
        that.resizeCanvas(eLength, eLength);
        // 将图片以居中裁剪的方式画到canvas中。
        // drawImage支持9个参数:图片对象,图片上的剪切坐标XY,
        // 剪切宽高,图片在canvas上的坐标XY及图片宽高
        that.context.drawImage(picture,
            (picture.width - eLength) / 2, (picture.height - eLength) / 2,
            eLength, eLength, 0, 0, eLength, eLength);
        // 截屏,即获取base64数据
        callback.call(that, that.canvas.toDataURL('image/png'));
    };
};

HTML5 Canvas 实现图片压缩和裁切

Canvas元素大小限制问题

上述clipSquareImage函数中,由于canvas.toDataURL接口不提供宽高参数,只能够一次性把整个canvas的屏幕数据截取下来,所以在对Canvas截屏前,我们必须先设置Canvas元素的大小。然而移动端拍照的分辨率极高,宽高大多会在3000以上,当我们根据相片宽高的最小值来设置Canvas的尺寸时,Canvas元素的最小宽度也高达到3000以上。

问题在于,每个平台对Canvas的大小都有限制,如果Canvas的宽度或高度任意一个值超过了平台限制,Canvas将无法进行渲染,canvas.toDataURL只能获取一张透明的图片数据。

Maximum size of a canvas
element中提到了部分平台下Canvas的尺寸限制:

chrome          = 32767x32767
iPod Touch 16GB = 1448x1448
iPad Mini       = 2290x2289
iPhone 3        = 1448x1448
iPhone 5        = 2290x2289

参考以上数据,我们先给Canvas设置一个最大的宽度:

var MAX_WIDTH = 1000;

clipSquareImage函数中加入最大宽度的检测,如果超过限制,则创建一个临时的canvas进行图片缩放处理,最后对该临时的Canvas进行居中剪切:

fn.clipSquareImage = function(url, callback) {
    var that = this,
        img = new Image();
    img.src = url;
    img.onload = function() {
         // 取图片宽高和Canvas的最大宽度的最小值作为等边长
        var eLength = Math.min(img.width, img.height, MAX_WIDTH),
            // 剪切对象
            picture = img,
            tempEditor,
            ratio;
            // 如果图片尺寸超出限制
            if (eLength === MAX_WIDTH) {
                // 创建一个临时editor
                tempEditor = new ImageEditor();
                ratio = img.width / img.height;
                // 按图片比例缩放canvas
                img.width < img.height ?
                    tempEditor.resizeCanvas(MAX_WIDTH * ratio, MAX_WIDTH) :
                    tempEditor.resizeCanvas(MAX_WIDTH, MAX_WIDTH / ratio);
                tempEditor.context.drawImage(img, 0, 0, tempEditor.canvas.width, tempEditor.canvas.height);
                // 将临时Canvas作为剪切对象
                picture = tempEditor.canvas;
                eLength = Math.min(tempEditor.canvas.width, tempEditor.canvas.height);
            }
            // 居中剪切
            // ... ...
            // 截屏操作
            // ... ...
    };
};

HTML5 Canvas 实现图片压缩和裁切

Canvas锯齿问题

上面我们已经能够通过Canvas裁剪出一张正方形的图片,接下来我们还需要处理头像图片大中小三种尺寸。在Canvas中,drawImage接口提供非常方便的缩放功能:

var editor = new ImageEditor;
// 将图片缩放到300x300
// drawImage支持5个参数:图片对象,及图片在canvas上的坐标和宽高
editor.context.drawImage(squareImage, 0, 0, 300, 300);

然而大尺寸图片直接用drawImage进行缩小处理会导致图片出现锯齿。在stack
overflow上HTML5 canvas drawImage: how to apply
antialiasing提出了一个方案:对图片进行若干次的等比例缩小,最后再放大到目标尺寸:

澳门新葡亰赌995577 11

参考这个方案,我们可以实现antialiasScale抗锯齿缩放函数:

fn.antialisScale = function(img, width, height) {
    var offlineCanvas = document.createElement('canvas'),
        offlineCtx = offlineCanvas.getContext('2d'),
        sourceWidth = img.width,
        sourceHeight = img.height,
        // 缩小操作的次数
        steps = Math.ceil(Math.log(sourceWidth / width) / Math.log(2)) - 1,
        i;
    // 渲染图片
    offlineCanvas.width = sourceWidth;
    offlineCanvas.height = sourceHeight;
    offlineCtx.drawImage(img, 0, 0, offlineCanvas.width, offlineCanvas.height);
    // 缩小操作
    // 进行steps次的减半缩小
    for(i = 0; i < steps; i++) {
        offlineCtx.drawImage(offlineCanvas, 0, 0,
            offlineCanvas.width * 0.5, offlineCanvas.height * 0.5);
    }
    // 放大操作
    // 进行steps次的两倍放大
    this.context.drawImage(offlineCanvas, 0, 0,
        offlineCanvas.width * Math.pow(0.5, steps), 
        offlineCanvas.height * Math.pow(0.5, steps),
        0, 0, width, height);
};

我们可以用这个函数代替drawImage完成缩放工作,生成头像图片的三种尺寸:

fn.scaleSquareImage = function(url, sizes, callback) {
    var that = this;
    // 先裁剪一个正方形
    that.clipSquareImage(url, sizes, function(data) {
        var squareImage = new Image(),
            result = [],
            i;
        squareImage.src = data;
        // 抗锯齿缩放
        for (i = 0; i < sizes.length; i++) {
            that.antialisScale(squareImage, sizes[i], size[i]);
            result.push(that.canvas.toDataURL('image/png'));    
        }
        callback.call(that, result);
    });
};

前面的话

早些时候用 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 两个方法,接下来先介绍一下这两个方法,如果想直接看结果,可以跳到文章结尾查看完整的例子和代码。

PHP存储base64图片数据

Canvas.toDataURL()获取的默认图像数据格式是:data:image/png;base64, +
base64数据:

data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAADElEQVQImWNgoBMAAABpAAFEI8ARAAAAAElFTkSuQmCC

当把Canvas截屏数据传给后台时,后台需要截断开头的字段data:image/png;base64,,获取后面那串真正的base64数据:

<?php
    $imgData = $_POST['imgData'];
    // 截取有用的部分
    list($type, $imgData) = explode(';', $imgData);
    list(, $imgData)      = explode(',', $imgData);
    // base64 编码中使用了加号,
    // 如果通过url传递base64数据,+号会转换成空格
    $imgData = str_replace(' ', '+', $imgData);
    // 存储文件
    $success = file_put_contents('PATH/XXX.png', base64_decode($imgData));

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 绘制图片的画布高度

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

澳门新葡亰赌995577 12

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

参考

  • Save a Base64 Encoded Canvas image to a png file using
    PHP
  • Html5 canvas drawImage: how to apply
    antialiasing
  • Maximum size of a canvas
    element
  • How to save a PNG image server-side, from a base64 data
    string
  • How to send FormData objects with Ajax-requests in
    jQuery

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 图片。

澳门新葡亰赌995577 13

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

澳门新葡亰赌995577 14

查看原图

换算成图表:

澳门新葡亰赌995577 15

发表评论

电子邮件地址不会被公开。 必填项已用*标注

相关文章

网站地图xml地图