用Canvas+Javascript FileAPI 實現一個跨平臺的圖片剪下、濾鏡處理、上傳下載工具

桃子夭夭發表於2015-09-22

直接上程式碼,其中上傳功能需要自己配置允許跨域的檔案伺服器地址~

或者將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 += "\r\n";
        content += "Content-Disposition: form-data; name=\"userlogo\"; filename=\"userlogo.png\"";
        content += "\r\n";
        content += "Content-Type: image/png";
        content += "\r\n\r\n";
        byte[] bytes1 = Encoding.UTF8.GetBytes(content);
        byte[] bytes2 = Convert.FromBase64String(base64);
        byte[] bytes3 = Encoding.UTF8.GetBytes("\r\n" + "--" + divider + "--\r\n");
        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尺寸
                width: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({width: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({width: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: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

 轉載請註明。

相關文章