Notes.

Base64 的原理、实现及应用

2015-11-17 • ☕️ 3 min read

简介

Base64是一种基于 64 个可打印字符来表示二进制数据的表示方法。由于 2 的 6 次方等于 64,所以每 6 个比特为一个单元,对应某个可打印字符。三个字节有 24 个比特,对应于 4 个Base64单元,即 3 个字节需要用 4 个可打印字符来表示。它可用来作为电子邮件的传输编码。在Base64中的可打印字符包括字母A-Za-z、数字0-9,这样共有 62 个字符,此外的两个可打印符号在不同的系统中而不同,一般为+/

转换原理

Base64 的直接数据源是二进制序列 (Binary Sequence)。当然,你也可以将图片、文本和音视频转换成二进制序列,再然后转换为 Base64 编码。我们这里讨论的是如何将二进制转换为 Base64 编码,对于如何将图片,文本和音视频转换为二进制序列敬请期待。

在转换前,先定义一张索引表,这张表规定了如何转换。 索引 转换的时候我们先将二进制序列分组,每 6 个比特为一组。但是如果编码的字节数不能被 3 整除,那么最后就会多出 1 个或两个字节,可以使用下面的方法进行处理:先使用 0 字节值在末尾补足,使其能够被 3 整除,然后再进行 base64 的编码。在编码后的 base64 文本后加上一个或两个’=‘号,代表补足的字节数。也就是说,当最后剩余一个八位字节(一个 byte)时,最后一个 6 位的 base64 字节块有四位是 0 值,最后附加上两个等号;如果最后剩余两个八位字节(2 个 byte)时,最后一个 6 位的 base 字节块有两位是 0 值,最后附加一个等号。 参考下表: 索引

用 JavaScript 实现 Base64

原理明白了以后,实现起来就很容易了。

define(function(require, exports, module) {

    var code = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".split(""); //索引表

    /**
     * @author natumsol@gmail.com
     * @description 将二进制序列转换为 Base64 编码
     * @param  {String}
     * @return {String}
     */
    function binToBase64(bitString) {
        var result = "";
        var tail = bitString.length % 6;
        var bitStringTemp1 = bitString.substr(0, bitString.length - tail);
        var bitStringTemp2 = bitString.substr(bitString.length - tail, tail);
        for (var i = 0; i < bitStringTemp1.length; i += 6) {
            var index = parseInt(bitStringTemp1.substr(i, 6), 2);
            result += code[index];
        }
        bitStringTemp2 += new Array(7 - tail).join("0");
        if (tail) {
            result += code[parseInt(bitStringTemp2, 2)];
            result += new Array((6 - tail) / 2 + 1).join("=");
        }
        return result;
    }

    /**
     * @author natumsol@gmail.com
     * @description 将 base64 编码转换为二进制序列
     * @param  {String}
     * @return {String}
     */
    function base64ToBin(str) {
        var bitString = "";
        var tail = 0;
        for (var i = 0; i < str.length; i++) {
            if (str[i] != "=") {
                var decode = code.indexOf(str[i]).toString(2);
                bitString += (new Array(7 - decode.length)).join("0") + decode;
            } else {
                tail++;
            }
        }
        return bitString.substr(0, bitString.length - tail * 2);
    }

    /**
     * @description 将字符转换为二进制序列
     * @param  {String} str
     * @return {String}    
     */
    function stringToBin(str) {
        var result = "";
        for (var i = 0; i < str.length; i++) {
            var charCode = str.charCodeAt(i).toString(2);
            result += (new Array(9 - charCode.length).join("0") + charCode);
        }
        return result;
    }
    /**
     * @description 将二进制序列转换为字符串
     * @param {String} Bin
     */
    function BinToStr(Bin) {
        var result = "";
        for (var i = 0; i < Bin.length; i += 8) {
            result += String.fromCharCode(parseInt(Bin.substr(i, 8), 2));
        }
        return result;
    }
    exports.base64 = function(str) {
        return binToBase64(stringToBin(str));
    }
    exports.decodeBase64 = function(str) {
        return BinToStr(base64ToBin(str));
    }
})

将图片数据进行 Base64 编码

将图片数据转换为 Base64,首先要获取到图片的二进制数据。图片的二进制数据可以通过canvas接口得到。具体实现为:

function getCanvas(w, h) {
		var c = document.createElement('canvas');
		c.width = w;
		c.height = h;
		return c;
	}

function getPixels(img) {
	var c = getCanvas(img.width, img.height);
	var ctx = c.getContext('2d');
	ctx.drawImage(img, 0, 0);
	return ctx.getImageData(0, 0, c.width, c.height);
}

取到图片的二进制数据后,接下来就要进行编码了。因为图片不仅包含像素信息,还包含长度,宽度信息。所以在编码像素信息的同时也应将宽度和高度信息按某一约定进行编码,我是这样处理的:

  1. 将图片的像素数值数据转换为二进制序列;
  2. 将宽度和高度信息组合成字符串$$width,height$$,转换为二进制序列;
  3. 将图片像素信息的二进制序列和图片宽高度的二进制序列组合起来,然后再进行 Base64 的编码

具体实现为:

function img2Base64(img) {
	var imgData = getPixels(img).data;
	var imgWidth = getPixels(img).width;
	var imgHeight = getPixels(img).height;
	var bin = "";
	for (var i = 0; i < imgData.length; i++) {
		bin += base.numToString(imgData[i]);
	}
	bin = bin + base.stringToBin("$$" + imgWidth + "," + imgHeight + "$$");
	return base.binToBase64(bin);
}

将图片 Base64 数据进行解码

解码是编码的逆过程。过程大致为:

  1. 将图片的 Base64 信息进行解码,得到包含图片像素信息和宽高度信息的二进制序列;
  2. 然后将这个二进制序列解码成字符串,获取高度和宽度信息;
  3. 去除二进制序列中的高度和宽度信息,得到像素信息;
  4. 根据像素信息生成像素矩阵;
  5. 根据像素矩阵、宽度和高度创建图片对象ImageData
  6. 利用putImageData将图像绘制出来。

具体的代码实现为:

function paint(imgData) {
		var canvas = document.getElementById("myCanvas");
		var ctx = canvas.getContext("2d");
		ctx.fillRect(0, 0, imgData.width, imgData.height);
		ctx.putImageData(imgData, 0, 0);
	}

function base642img(data) {
	var str = base.BinToStr(base.base64ToBin(data));
	var imgWidth = str.match(/\$\$(\d+),(\d+)\$\$$/, "")[1];
	var imgHeight = str.match(/\$\$(\d+),(\d+)\$\$$/, "")[2]
	var imgData = base.base64ToBin(data).replace(base.stringToBin("$$" + imgWidth + "," + imgHeight + "$$"), "");

	var ImageDataArray = new Uint8ClampedArray(imgWidth * imgHeight * 4);
	for (var i = 0; i < ImageDataArray.length; i++) {
		ImageDataArray[i] = parseInt(imgData.substr(i * 8, 8), 2);
	}
	return new ImageData(ImageDataArray, imgWidth, imgHeight);

}

DEMO 演示

在线演示: DEMO 源码请移步 Github

截图


Natumsol

Personal blog by Natumsol.
Note thoughts and experience.