zoukankan      html  css  js  c++  java
  • 图片剪切

    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 非常不方便,有时候还需要翻墙,所以这个工具大多数时候是我自己在玩。

    为了降低安装成本,这两天开始研究去掉图片处理功能中的 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(){}
    });
    

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

  • 相关阅读:
    书单
    x&(x1)表达式的意义
    约瑟夫环,杀人游戏(静态循环链表实现)
    我的第一个动态规划程序(试图用递归求斐波拉契数)
    NYOJ 2题 括号配对问题
    为什么 C++不叫作++C? o(∩_∩)o
    文字常量区,字符串常量
    括号匹配(栈实现)
    Mybatis的逆向工程(generator)
    Mybatis学习一(介绍/举例/优化)
  • 原文地址:https://www.cnblogs.com/jiangxiaobo/p/6035825.html
Copyright © 2011-2022 走看看