zoukankan      html  css  js  c++  java
  • 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位图会麻烦些... 没有直接的接口,需要我们自己来生成。生成图片的响应头和响应体有一定的规则,略显麻烦。不过还能接受。剩下的就是性能问题,按像素级别来操作,对于一个大图来说计算量很有压力。

    【实现】

    /**
     * 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来实现的自动下载,所以下载的时候文件类型不会自动识别。

  • 相关阅读:
    保持同步
    将Cent0S 7的网卡名称eno16777736改为eth0
    Linux系统各发行版镜像下载(2)
    Linux系统各发行版镜像下载
    ~/.ssh目录找不到解决方法
    克隆后虚拟机网络配置
    新建的linux虚拟机找不到eth0解决办法
    SecureCRT 7 注册码
    linux运维常用命令
    shell脚本实例(2)
  • 原文地址:https://www.cnblogs.com/jiangxiaobo/p/6016440.html
Copyright © 2011-2022 走看看