• 用Canvas+Javascript FileAPI 实现一个跨平台的图片剪切、滤镜处理、上传下载工具


    直接上代码,其中上传功能需要自己配置允许跨域的文件服务器地址~

    或者将html文件贴到您的站点下同源上传也OK。

    支持:

    不同尺寸图片获取、

    原图缩小放大、

    原图移动、

    选择框大小改变、

    下载选中的区域、

    上传选中的区域、

    几种简单的滤镜(自己添加滤镜函数即可添加滤镜效果)

    移动端适配要点

    ① 替换事件名称

    if(/^.*(Android|iPad|iPhone){1}.*$/.test(navigator.userAgent)){
    eventName={down:"touchstart",move:"touchmove",up:"touchend",click:"tap"};
    }

    ② 移动端touch事件e没有clientX属性,需要做如下处理

    //处理事件,支持移动端
    //e.originalEvent.targetTouches[0].pageX
    function dealE(e){
    e.clientX= e.clientX || e.originalEvent.targetTouches[0].clientX;
    e.clientY= e.clientY || e.originalEvent.targetTouches[0].clientY;
    }

    ③ 移动端浏览器展示网页在手指拖动的过程中是会左右晃悠的,体验十分不好。

    给所有事件都加上

    e.preventDefault();

    ④ 移动端浏览器对File上传支持不好,微信甚至干脆屏蔽了File上传请求

    我的做法是:

    获取图片文件的base64字符串:

     var imgData = $("#res1")[0].toDataURL("png");
            imgData = imgData.replace(/^data:image/(png|jpg);base64,/, "");

    然后自己在后端实现一个文件上传代理,接收base64字符串,拼接body:

     proxyRequest.ContentType = "multipart/form-data; boundary=----WebKitFormBoundaryqwqoxnDz0J0XB2Ti";
            StreamReader reader = new StreamReader(_context.Request.InputStream);
            string base64=reader.ReadToEnd();
            string divider = "----WebKitFormBoundaryqwqoxnDz0J0XB2Ti";
            string content = "";
            content += "--"+divider;
            content += "
    ";
            content += "Content-Disposition: form-data; name="userlogo"; filename="userlogo.png"";
            content += "
    ";
            content += "Content-Type: image/png";
            content += "
    
    ";
            byte[] bytes1 = Encoding.UTF8.GetBytes(content);
            byte[] bytes2 = Convert.FromBase64String(base64);
            byte[] bytes3 = Encoding.UTF8.GetBytes("
    " + "--" + divider + "--
    ");
            byte[] bytes = new byte[bytes1.Length+bytes2.Length+bytes3.Length];
            bytes1.CopyTo(bytes,0);
            bytes2.CopyTo(bytes, bytes1.Length);
            bytes3.CopyTo(bytes, bytes1.Length + bytes2.Length);
            proxyRequest.ContentLength = bytes.Length;

    这样对于移动端浏览器来说这就是一个普通的请求。

    至此,移动端完美支持!

    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>get image</title>
    <style>
        canvas{
            border:solid thin #ccc;
            cursor:pointer;
        }
        #canvasContainer{
            position:relative;
        }
        #picker{
            position:absolute;
            border:solid thin #ccc;
            cursor: move;
            overflow:hidden;
            z-index:2;
        }
        #resize{
             width: 0;
             height: 0;
             border-bottom: 15px solid rgba(200,200,200,0.8);
             border-left: 15px solid transparent;
             right: 0;
             bottom: 0;
             position: absolute;
             cursor: se-resize;
             z-index:3;
        }
    </style>
    <script src="http://libs.baidu.com/jquery/1.9.0/jquery.js"></script>
    <script>
        $(function(){
            var canvas=document.getElementById("container"),
                context=canvas.getContext("2d"),
                //文件服务器地址
                fileServer=null,
                //适配环境,随时修改事件名称
                eventName={down:"mousedown",move:"mousemove",up:"mouseup",click:"click"};
            //////////canvas尺寸配置
            var canvasConfig={
                    //容器canvas尺寸
                    500,
                    height:300,
                    //原图放大/缩小
                    zoom:1,
                    //图片对象
                    img:null,
                    //图片完整显示在canvas容器内的尺寸
                    size:null,
                    //图片绘制偏移,为了原图不移出框外,这个只能是负值or 0
                    offset:{x:0,y:0},
                    //当前应用的滤镜
                    filter:null
            }
            canvas.width=canvasConfig.width;
            canvas.height=canvasConfig.height;
            ///////////设置选择工具配置
            var config={
                //图片选择框当前大小、最大大小、最小大小
                pickerSize:100,
                minSize:50,
                maxSize:200,
                x:canvas.width/2-100/2,
                y:canvas.height/2-100/2
            }
            /////////////结果canvas配置
            var resCanvas=[$("#res1")[0].getContext("2d"),$("#res2")[0].getContext("2d"),$("#res3")[0].getContext("2d")];
            //结果canvas尺寸配置
            var resSize=[100,50,32]
            resSize.forEach(function(size,i){
                $("#res"+(i+1))[0].width=size;
                $("#res"+(i+1))[0].height=size;
            });
            //////// 滤镜配置
            var filters=[];
            filters.push({name:"灰度",func:function(pixelData){
                //r、g、b、a
                //灰度滤镜公式: gray=r*0.3+g*0.59+b*0.11
                var gray;
                for(var i=0;i<canvasConfig.width*canvasConfig.height;i++){
                    gray=pixelData[4*i+0]*0.3+pixelData[4*i+1]*0.59+pixelData[4*i+2]*0.11;
                    pixelData[4*i+0]=gray;
                    pixelData[4*i+1]=gray;
                    pixelData[4*i+2]=gray;
                }
            }});
            filters.push({name:"黑白",func:function(pixelData){
                //r、g、b、a
                //黑白滤镜公式: 0 or 255
                var gray;
                for(var i=0;i<canvasConfig.width*canvasConfig.height;i++){
                    gray=pixelData[4*i+0]*0.3+pixelData[4*i+1]*0.59+pixelData[4*i+2]*0.11;
                    if(gray>255/2){
                        gray=255;
                    }
                    else{
                        gray=0;
                    }
                    pixelData[4*i+0]=gray;
                    pixelData[4*i+1]=gray;
                    pixelData[4*i+2]=gray;
                }
            }});
            filters.push({name:"反色",func:function(pixelData){
                for(var i=0;i<canvasConfig.width*canvasConfig.height;i++){
                    pixelData[i*4+0]=255-pixelData[i*4+0];
                    pixelData[i*4+1]=255-pixelData[i*4+1];
                    pixelData[i*4+2]=255-pixelData[i*4+2];
                }
            }});
            filters.push({name:"",func:null});
            // 添加滤镜按钮
            filters.forEach(function(filter){
                var button=$("<button>"+filter.name+"</button>");
                button.on(eventName.click,function(){
                    canvasConfig.filter=filter.func;
                    //重绘
                    draw(context,canvasConfig.img,canvasConfig.size);
                })
                $("#filters").append(button);
            });
            //下载生成的图片(只下载第一张)
            $("#download").on(eventName.click,function(){
                
                //将mime-type改为image/octet-stream,强制让浏览器直接download
                var _fixType = function(type) {
                    type = type.toLowerCase().replace(/jpg/i, 'jpeg');
                    var r = type.match(/png|jpeg|bmp|gif/)[0];
                    return 'image/' + r;
                };
                var saveFile = function(data, filename){
                    var save_link = document.createElementNS('http://www.w3.org/1999/xhtml', 'a');
                    save_link.href = data;
                    save_link.download = filename;
                    var event = document.createEvent('MouseEvents');
                    event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
                    save_link.dispatchEvent(event);
                };
                var imgData = $("#res1")[0].toDataURL("png");
                imgData = imgData.replace(_fixType("png"),'image/octet-stream');//base64
                saveFile(imgData,"头像created on"+new Date().getTime()+"."+"png");
            });
            //上传图片
            $("#upload").on(eventName.click,function(){
                var imgData = $("#res1")[0].toDataURL("png");
                imgData = imgData.replace(/^data:image/(png|jpg);base64,/, "");
                if(!fileServer){
                    alert("请配置文件服务器地址");
                    return;
                }
                
                var blobBin = atob(imgData);
                var array = [];
                for(var i = 0; i < blobBin.length; i++) {
                    array.push(blobBin.charCodeAt(i));
                }
                var blob=new Blob([new Uint8Array(array)], {type: 'image/png'});
                var file=new File([blob],"userlogo.png", {type: 'image/png'});
                var formdata=new FormData();
                formdata.append("userlogo",file);
                 $.ajax({
                    type: 'POST',
                    url: fileServer,
                    data: formdata,
                    processData: false,
                    contentType: false,
                    success: function (msg) {
                        $("#uploadres").text(JSON.stringify(msg));
                    }
                });
            });
            //绑定选择图片事件
            $("#fileinput").change(function(){
                var file=this.files[0],
                    URL = (window.webkitURL || window.URL),
                    url = URL.createObjectURL(file),
                    img=new Image();
                img.src=url;
                img.onload=function(){
                    canvasConfig.img=img;
                    canvasConfig.size=getFixedSize(img,canvas);
                    draw(context,img,canvasConfig.size);
                    setPicker();
                }
                
            });
            //移动选择框
            //绑定鼠标在选择工具上按下的事件
            $("#picker").on(eventName.down,function(e){
                e.stopPropagation();
                var start={x:e.clientX,y:e.clientY,initX:config.x,initY:config.y};
                $("#canvasContainer").on(eventName.move,function(e){
                    // 将x、y限制在框内
                    config.x=Math.min(Math.max(start.initX+e.clientX-start.x,0),canvasConfig.width-config.pickerSize);
                    config.y=Math.min(Math.max(start.initY+e.clientY-start.y,0),canvasConfig.height-config.pickerSize);
                    setPicker();
                })
            });
            //原图移动事件
            $("#container").on(eventName.down,function(e){
                e.stopPropagation();
                var start={x:e.clientX,y:e.clientY,initX:canvasConfig.offset.x,initY:canvasConfig.offset.y};
                var size=canvasConfig.size;
                $("#canvasContainer").on(eventName.move,function(e){
                    // 将x、y限制在框内
                    // 坐标<0  当图片大于容器  坐标>容器-图片   否则不能移动
                    canvasConfig.offset.x=Math.max(Math.min(start.initX+e.clientX-start.x,0),Math.min(canvasConfig.width-size.width*canvasConfig.zoom,0));
                    canvasConfig.offset.y=Math.max(Math.min(start.initY+e.clientY-start.y,0),Math.min(canvasConfig.height-size.height*canvasConfig.zoom,0));
                    //重绘蒙版
                    draw(context,canvasConfig.img,canvasConfig.size);
                })
            });
            //改变选择框大小事件
            $("#resize").on(eventName.down,function(e){
                e.stopPropagation();
                var start={x:e.clientX,init:config.pickerSize};
                $("#canvasContainer").on(eventName.move,function(e){
                    config.pickerSize= Math.min(Math.max(start.init+e.clientX-start.x,config.minSize),config.maxSize);
                    $("#picker").css({config.pickerSize,height:config.pickerSize});
                    draw(context,canvasConfig.img,canvasConfig.size);
                })
            });
            $(document).on(eventName.up,function(e){
                $("#canvasContainer").unbind(eventName.move);
            })
            //原图放大、缩小
            $("#bigger").on(eventName.click,function(){
                canvasConfig.zoom=Math.min(3,canvasConfig.zoom+0.1);
                //重绘蒙版
                draw(context,canvasConfig.img,canvasConfig.size);
            })
            $("#smaller").on(eventName.click,function(){
                canvasConfig.zoom=Math.max(0.4,canvasConfig.zoom-0.1);
                //重绘蒙版
                draw(context,canvasConfig.img,canvasConfig.size);
            })
            
            // 定位选择工具
            function setPicker(){
                $("#picker").css({config.pickerSize+"px",height:config.pickerSize+"px",
                    top:config.y,left:config.x});
                //重绘蒙版
                draw(context,canvasConfig.img,canvasConfig.size);
            }
            //绘制canvas中的图片和蒙版
            function draw(context,img,size){
                var pickerSize=config.pickerSize,
                    zoom=canvasConfig.zoom,
                    offset=canvasConfig.offset;
                context.clearRect(0,0,canvas.width,canvas.height);
                context.drawImage(img,0,0,img.width,img.height,offset.x,offset.y,size.width*zoom,size.height*zoom);
                //绘制挖洞后的蒙版
                context.save();
                context.beginPath();
                pathRect(context,config.x,config.y,pickerSize,pickerSize);
                context.rect(0,0,canvas.width,canvas.height);
                context.closePath();
                context.fillStyle="rgba(255,255,255,0.9)";
                context.fill();
                context.restore();
                //绘制结果
                var imageData=context.getImageData(config.x,config.y,pickerSize,pickerSize)
                resCanvas.forEach(function(resContext,i){
                    resContext.clearRect(0,0,resSize[i],resSize[i]);
                    resContext.drawImage(canvas,config.x,config.y,pickerSize,pickerSize,0,0,resSize[i],resSize[i]);
                    //添加滤镜效果
                    if(canvasConfig.filter){
                        var imageData=resContext.getImageData(0,0,resSize[i],resSize[i]);
                        var temp=resContext.getImageData(0,0,resSize[i],resSize[i]);// 有的滤镜实现需要temp数据
                        canvasConfig.filter(imageData.data,temp);
                        resContext.putImageData(imageData,0,0,0,0,resSize[i],resSize[i]);
                    }
                });
            }
            //逆时针用路径自己来绘制矩形,这样可以控制方向,以便挖洞
            // 起点x,起点y,宽度,高度
            function pathRect(context,x,y,width,height){
                context.moveTo(x,y);
                context.lineTo(x,y+height);
                context.lineTo(x+width,y+height);
                context.lineTo(x+width,y);
                context.lineTo(x,y);
            }
            // 根据图片和canvas的尺寸,确定图片显示在canvas中的尺寸
            function getFixedSize(img,canvas){
                var cancasRate=canvas.width/canvas.height,
                    imgRate=img.width/img.height,width=img.width,height=img.height;
                if(cancasRate>=imgRate && img.height>canvas.height){
                    height=canvas.height;
                    width=imgRate*height;
                }
                else if(cancasRate<imgRate && img.width>canvas.width){
                    width=canvas.width;
                    height=width/imgRate;
                }
                return {width,height,height}
            }
        });
    </script>
    </head>
    <body>
        <input id="fileinput" type="file" /><br/><br/>
        <div id="canvasContainer">
            <canvas id="container"></canvas>
            <div id="picker">
                <div id="resize"></div>
            </div>
        </div><br/>
        <button id="bigger">原图放大</button><button id="smaller">原图缩小</button>
        <p>结果:</p>
        <div>
            <canvas id="res1"></canvas>
            <canvas id="res2"></canvas>
            <canvas id="res3"></canvas>
            <button id="download"> 下载 </button>
            <button id="upload"> 上传 </button>(demo只上传/下载第一张图片)
            <div id="uploadres"></div>
        </div>
        <p>滤镜:</p>
        <div id="filters"></div>
    </body>
    </html>

     原文地址:http://www.cnblogs.com/tzyy/p/4830439.html

     转载请注明。

  • 相关阅读:
    Linux运维常见故障排查和处理的技巧汇总
    React 页面开发服务规范抽象
    解决 NVM 安装 Node 版本出现 TLS 问题
    解决部署 React 框架 Next.js 到 IIS 上刷新页面出现 404 问题
    正则表达式
    rsa加解密代码实现
    zookeeper介绍(5)快速入门教程
    yii2-adminlte-asset中MenuHelper 菜单伸缩问题
    yii2-admin autocomplete 输出弹出框位置不对
    php curl模拟常用的请求
  • 原文地址:https://www.cnblogs.com/tzyy/p/4830439.html
Copyright © 2020-2023  润新知