canvas保存为data:image扩展功能的实现

【已知】
canvas提供了toDataURL的接口,可以方便的将canvas画布转化成base64编码的image。目前支持的最好的是png格式,jpeg格式的现代浏览器基本也支持,但是支持的不是很好。

【想要的】
往往这么简单直接的接口通常都满足不了需求。我想要的不仅是简单的通过画布生成一个png,我不想新开一个tab,然后还要右键另存为…

我还需要更方便的自由的配置生成的图片的大小,比例等。

另外如果我还要别的图片格式,比如位图bmp,gif等怎么办…

【解决办法】
a)想直接把图片生成后download到本地,其实办法也很简单。直接改图片的mimeType,强制改成steam流类型的。比如‘image/octet-stream’,浏览器就会自动帮我们另存为..

b)图片大小,及比例的可控倒也好办,我们新建一个我们想要大小的canvas,把之前的canvas画布重新按照所要的比例,及大小draw到新的canvas上,然后用新的canvas来toDataURL即可。

c)想要bmp位图会麻烦些… 没有直接的接口,需要我们自己来生成。生成图片的响应头和响应体有一定的规则,略显麻烦。不过还能接受。剩下的就是性能问题,按像素级别来操作,对于一个大图来说计算量很有压力。

【实现】

Js代码

    /** 
     * covert canvas to image 
     * and save the image file 
     */  
      
    var Canvas2Image = function () {  
      
        // check if support sth.  
        var $support = function () {  
            var canvas = document.createElement('canvas'),  
                ctx = canvas.getContext('2d');  
      
            return {  
                canvas: !!ctx,  
                imageData: !!ctx.getImageData,  
                dataURL: !!canvas.toDataURL,  
                btoa: !!window.btoa  
            };  
        }();  
      
        var downloadMime = 'image/octet-stream';  
      
        function scaleCanvas (canvas, width, height) {  
            var w = canvas.width,  
                h = canvas.height;  
            if (width == undefined) {  
                width = w;  
            }  
            if (height == undefined) {  
                height = h;  
            }  
      
            var retCanvas = document.createElement('canvas');  
            var retCtx = retCanvas.getContext('2d');  
            retCanvas.width = width;  
            retCanvas.height = height;  
            retCtx.drawImage(canvas, 0, 0, w, h, 0, 0, width, height);  
            return retCanvas;  
        }  
      
        function getDataURL (canvas, type, width, height) {  
            canvas = scaleCanvas(canvas, width, height);  
            return canvas.toDataURL(type);  
        }  
      
        function saveFile (strData) {  
            document.location.href = strData;  
        }  
      
        function genImage(strData) {  
            var img = document.createElement('img');  
            img.src = strData;  
            return img;  
        }  
        function fixType (type) {  
            type = type.toLowerCase().replace(/jpg/i, 'jpeg');  
            var r = type.match(/png|jpeg|bmp|gif/)[0];  
            return 'image/' + r;  
        }  
        function encodeData (data) {  
            if (!window.btoa) { throw 'btoa undefined' }  
            var str = '';  
            if (typeof data == 'string') {  
                str = data;  
            } else {  
                for (var i = 0; i < data.length; i ++) {  
                    str += String.fromCharCode(data[i]);  
                }  
            }  
      
            return btoa(str);  
        }  
        function getImageData (canvas) {  
            var w = canvas.width,  
                h = canvas.height;  
            return canvas.getContext('2d').getImageData(0, 0, w, h);  
        }  
        function makeURI (strData, type) {  
            return 'data:' + type + ';base64,' + strData;  
        }  
      
      
        /** 
         * create bitmap image 
         * 按照规则生成图片响应头和响应体 
         */  
        var genBitmapImage = function (data) {  
            var imgHeader = [],  
                imgInfoHeader = [];  
              
            var width = data.width,  
                height = data.height;  
      
            imgHeader.push(0x42); // 66 -> B  
            imgHeader.push(0x4d); // 77 -> M  
      
            var fsize = width * height * 3 + 54; // header size:54 bytes  
            imgHeader.push(fsize % 256); // r  
            fsize = Math.floor(fsize / 256);  
            imgHeader.push(fsize % 256); // g  
            fsize = Math.floor(fsize / 256);  
            imgHeader.push(fsize % 256); // b  
            fsize = Math.floor(fsize / 256);  
            imgHeader.push(fsize % 256); // a  
      
            imgHeader.push(0);  
            imgHeader.push(0);  
            imgHeader.push(0);  
            imgHeader.push(0);  
      
            imgHeader.push(54); // offset -> 6  
            imgHeader.push(0);  
            imgHeader.push(0);  
            imgHeader.push(0);  
      
            // info header  
            imgInfoHeader.push(40); // info header size  
            imgInfoHeader.push(0);  
            imgInfoHeader.push(0);  
            imgInfoHeader.push(0);  
      
            // 横向info  
            var _width = width;  
            imgInfoHeader.push(_width % 256);  
            _width = Math.floor(_width / 256);  
            imgInfoHeader.push(_width % 256);  
            _width = Math.floor(_width / 256);  
            imgInfoHeader.push(_width % 256);  
            _width = Math.floor(_width / 256);  
            imgInfoHeader.push(_width % 256);  
      
            // 纵向info  
            var _height = height;  
            imgInfoHeader.push(_height % 256);  
            _height = Math.floor(_height / 256);  
            imgInfoHeader.push(_height % 256);  
            _height = Math.floor(_height / 256);  
            imgInfoHeader.push(_height % 256);  
            _height = Math.floor(_height / 256);  
            imgInfoHeader.push(_height % 256);  
      
            imgInfoHeader.push(1);  
            imgInfoHeader.push(0);  
            imgInfoHeader.push(24); // 24位bitmap  
            imgInfoHeader.push(0);  
      
            // no compression  
            imgInfoHeader.push(0);  
            imgInfoHeader.push(0);  
            imgInfoHeader.push(0);  
            imgInfoHeader.push(0);  
      
            // pixel data  
            var dataSize = width * height * 3;  
            imgInfoHeader.push(dataSize % 256);  
            dataSize = Math.floor(dataSize / 256);  
            imgInfoHeader.push(dataSize % 256);  
            dataSize = Math.floor(dataSize / 256);  
            imgInfoHeader.push(dataSize % 256);  
            dataSize = Math.floor(dataSize / 256);  
            imgInfoHeader.push(dataSize % 256);  
      
            // blank space  
            for (var i = 0; i < 16; i ++) {  
                imgInfoHeader.push(0);  
            }  
      
            var padding = (4 - ((width * 3) % 4)) % 4;  
            var imgData = data.data;  
            var strPixelData = '';  
            var y = height;  
            do {  
                var offsetY = width * (y - 1) * 4;  
                var strPixelRow = '';  
                for (var x = 0; x < width; x ++) {  
                    var offsetX = 4 * x;  
                    strPixelRow += String.fromCharCode(imgData[offsetY + offsetX + 2]);  
                    strPixelRow += String.fromCharCode(imgData[offsetY + offsetX + 1]);  
                    strPixelRow += String.fromCharCode(imgData[offsetY + offsetX]);  
                }  
                for (var n = 0; n < padding; n ++) {  
                    strPixelRow += String.fromCharCode(0);  
                }  
      
                strPixelData += strPixelRow;  
            } while(-- y);  
      
            return (encodeData(imgHeader.concat(imgInfoHeader)) + encodeData(strPixelData));  
      
        };  
      
        /** 
         * saveAsImage 
         * @param canvasElement 
         * @param {String} image type 
         * @param {Number} [optional] png width 
         * @param {Number} [optional] png height 
         */  
        var saveAsImage = function (canvas, width, height, type) {  
            if ($support.canvas && $support.dataURL) {  
                if (type == undefined) { type = 'png'; }  
                type = fixType(type);  
                if (/bmp/.test(type)) {  
                    var data = getImageData(scaleCanvas(canvas, width, height));  
                    var strData = genBitmapImage(data);  
                    saveFile(makeURI(strData, downloadMime));  
                } else {  
                    var strData = getDataURL(canvas, type, width, height);  
                    saveFile(strData.replace(type, downloadMime));  
                }  
              
            }  
        }  
      
        var convertToImage = function (canvas, width, height, type) {  
            if ($support.canvas && $support.dataURL) {  
                if (type == undefined) { type = 'png'; }  
                type = fixType(type);  
      
                if (/bmp/.test(type)) {  
                    var data = getImageData(scaleCanvas(canvas, width, height));  
                    var strData = genBitmapImage(data);  
                    return genImage(makeURI(strData, 'image/bmp'));  
                } else {  
                    var strData = getDataURL(canvas, type, width, height);  
                    return genImage(strData);  
                }  
            }  
        }  
      
      
      
        return {  
            saveAsImage: saveAsImage,  
            saveAsPNG: function (canvas, width, height) {  
                return saveAsImage(canvas, width, height, 'png');  
            },  
            saveAsJPEG: function (canvas, width, height) {  
                return saveAsImage(canvas, width, height, 'jpeg');              
            },  
            saveAsGIF: function (canvas, width, height) {  
                return saveAsImage(canvas, width, height, 'gif')             
            },  
            saveAsBMP: function (canvas, width, height) {  
                return saveAsImage(canvas, width, height, 'bmp');             
            },  
              
            convertToImage: convertToImage,  
            convertToPNG: function (canvas, width, height) {  
                return convertToImage(canvas, width, height, 'png');  
            },  
            convertToJPEG: function (canvas, width, height) {  
                return convertToImage(canvas, width, height, 'jpeg');                 
            },  
            convertToGIF: function (canvas, width, height) {  
                return convertToImage(canvas, width, height, 'gif');                
            },  
            convertToBMP: function (canvas, width, height) {  
                return convertToImage(canvas, width, height, 'bmp');                
            }  
        };  
      
    }();

【Demo】
http://hongru.github.com/proj/canvas2image/index.html
可以试着在canvas上涂涂画画,然后保存看看。如果用bmp格式的话,需要支持 btoa 的base64编码,关于base64编码规则可看上一篇博文

【不完美的地方】
1)jpeg接口本身就不完善,当canvas没有填充颜色或图片时,保存的jpeg由于是直接由png的alpha通道强制转换过来的,所以在png的透明部分在jpeg里面就是黑色的。

2)gif的限制太多。且可用性不大,有png就够了

3)bmp位图生成,计算量稍显大了。

4)由于是强制改mimeType来实现的自动下载,所以下载的时候文件类型不会自动识别。

 

原文:http://www.cnblogs.com/hongru/archive/2012/01/14/2322540.html

 

index.html

Html代码

    <!doctype html>  
    <html>  
    <meta charset="utf-8" />  
    <script src="canvas2image.js"></script>  
    <style>  
        .doc {  
            width: 604px;  
            margin: 0 auto;  
        }  
        canvas {  
            display: block;  
            border: 2px solid #888;  
        }  
    </style>  
    <body>  
    <div class="doc">  
        <canvas width="600" height="400" id="cvs"></canvas>  
        <div>  
            <p>  
                <button id="save">save</button> or <button id="convert">convert to</button> as:   
                <select id="sel">  
                    <option value="png">png</option>  
                    <option value="jpeg">jpeg</option>  
                    <option value="bmp">bmp</option>  
                </select><br/>  
                width : <input type="number" value="300" id="imgW" /><br/>  
                height : <input type="number" value="200" id="imgH" />  
            </p>  
      
        </div>  
        <div id="imgs">  
              
        </div>  
    </div>  
    <script>  
        var canvas, ctx, bMouseIsDown = false, iLastX, iLastY,  
            $save, $imgs,  
            $convert, $imgW, $imgH,  
            $sel;  
        function init () {  
            canvas = document.getElementById('cvs');  
            ctx = canvas.getContext('2d');  
            $save = document.getElementById('save');  
            $convert = document.getElementById('convert');  
            $sel = document.getElementById('sel');  
            $imgs = document.getElementById('imgs');  
            $imgW = document.getElementById('imgW');  
            $imgH = document.getElementById('imgH');  
            bind();  
            draw();  
        }  
        function bind () {  
            canvas.onmousedown = function(e) {  
                bMouseIsDown = true;  
                iLastX = e.clientX - canvas.offsetLeft + (window.pageXOffset||document.body.scrollLeft||document.documentElement.scrollLeft);  
                iLastY = e.clientY - canvas.offsetTop + (window.pageYOffset||document.body.scrollTop||document.documentElement.scrollTop);  
            }  
            canvas.onmouseup = function() {  
                bMouseIsDown = false;  
                iLastX = -1;  
                iLastY = -1;  
            }  
            canvas.onmousemove = function(e) {  
                if (bMouseIsDown) {  
                    var iX = e.clientX - canvas.offsetLeft + (window.pageXOffset||document.body.scrollLeft||document.documentElement.scrollLeft);  
                    var iY = e.clientY - canvas.offsetTop + (window.pageYOffset||document.body.scrollTop||document.documentElement.scrollTop);  
                    ctx.moveTo(iLastX, iLastY);  
                    ctx.lineTo(iX, iY);  
                    ctx.stroke();  
                    iLastX = iX;  
                    iLastY = iY;  
                }  
            };  
              
            $save.onclick = function (e) {  
                var type = $sel.value,  
                    w = $imgW.value,  
                    h = $imgH.value;  
                Canvas2Image.saveAsImage(canvas, w, h, type);  
            }  
            $convert.onclick = function (e) {  
                var type = $sel.value,  
                    w = $imgW.value,  
                    h = $imgH.value;  
                $imgs.appendChild(Canvas2Image.convertToImage(canvas, w, h, type))  
            }  
              
        }  
        function draw () {  
            ctx.fillStyle = '#ffffff';  
            ctx.fillRect(0, 0, 600, 400);  
            ctx.fillStyle = 'red';  
            ctx.fillRect(100, 100, 50, 50);  
        }  
          
          
        onload = init;  
    </script>  
    </body>  
    </html>

 

本文:canvas保存为data:image扩展功能的实现

 

Leave a Reply