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

  • 相关阅读:
    hi.baidu.com 百度流量统计
    Autofac is designed to track and dispose of resources for you.
    IIS Manager could not load type for module provider 'SharedConfig' that is declared in administration.config
    How to create and manage configuration backups in Internet Information Services 7.0
    定制swagger的UI
    NSwag在asp.net web api中的使用,基于Global.asax
    NSwag Tutorial: Integrate the NSwag toolchain into your ASP.NET Web API project
    JS变量对象详解
    JS执行上下文(执行环境)详细图解
    JS内存空间详细图解
  • 原文地址:https://www.cnblogs.com/jiangxiaobo/p/6016440.html
Copyright © 2011-2022 走看看