内容简介:原文:实现数据的保存与加载。前提是没有以下支持:
原文:
- 2018/09/06 Hijacking HTML canvas and PNG images to store arbitrary text data
- 2018/09/20 Retrieving data from hijacked PNG images using HTML canvas and Javascript
需求
实现数据的保存与加载。
前提是没有以下支持:
- Cookie
- LocalStorage
- Server Side Storage
设计
- 使用图片
- 支持几十 KB 数据
- 不需要考虑特定图片格式的处理细节
实现
关于保存为图片的方案选择
排除方案 1
数据 -> JSON -> 动态生成 Data URLs 与 下载链接 。
移动端 Safari 上无效:不认 <a>
标记的 download
属性。
排除方案 2
二维码
编码容易,解码麻烦,需要一个很大的库。
选择方案:Canvas
每三个 ASCII 组成一个 RGB 像素,不足部分用 0 填充。
问题:图片太小的话,不便于点击保存(?原话:this is not easy to tap to save)
方案:预设大小:256 * 256
第一行保留(最后一列顺带着保留了,因为是数据方阵的设计)用来记录元数据,比如数据规模。
可用空间:255 * 255,65025 => 190KB
JSON 字符串 ---TextEncoder---> 字节数组
> (new TextEncoder('utf-8')).encode('Hello World')
Uint8Array(11) [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]
测试数据:
$ curl -X POST -qd "name=胡昂&location=中华人民共和国&job=软件开发工程师" http://httpbin.org/post | jq --tab
主要逻辑:
function createElementFromHTML(html) {
var emptyElement = document.createElement('empty');
emptyElement.innerHTML = html.trim();
return emptyElement.firstChild;
}
var exportedImage = null;
var importedData = null;
function exportData(data) {
// JSON 序列化
var strData = JSON.stringify(data);
// TextEncoder 编码成数字
var uint8array = (new TextEncoder('utf-8')).encode(strData);
// 计算需要多大方阵存储全部数据
var dataSize = Math.ceil(Math.sqrt(uint8array.length / 3));
// 用 255 填充 RGBA 中的 alpha 通道,设置完全不透明
// 否则 alpha = 0,即透明图片)会遇到 RGB 损坏的问题
// https://stackoverflow.com/questions/22384423/canvas-corrupts-rgb-when-alpha-0
var paddedData = new Uint8ClampedArray(dataSize * dataSize * 4);
var idx = 0;
// 每三个数字后加一个 255
for (var i = 0; i < uint8array.length; i += 3) {
var subArray = uint8array.subarray(i, i + 3);
paddedData.set(subArray, idx);
paddedData.set([255], idx + 3);
idx += 4;
}
// 生成图像数据
var imageData = new ImageData(paddedData, dataSize, dataSize);
// 创建一个屏外(off screen)画布
var imgSize = 256;
var canvas = document.createElement('canvas');
canvas.width = canvas.height = imgSize;
// 获取画布上下文
var ctx = canvas.getContext('2d');
// 画布设置:256 * 256 的黑布
ctx.fillStyle = '#AA0000'; // 颜色没关系
ctx.fillRect(0, 0, imgSize, imgSize);
// 用一个像素的 R 值记录数据规模
ctx.fillStyle = 'rgb(' + dataSize + ', 0, 0)';
ctx.fillRect(0, 0, 1, 1);
// 画布填充内容
ctx.putImageData(imageData, 0, 1);
var body = document.getElementsByTagName('body')[0];
exportedImage = canvas.toDataURL();
var downloadedLink = createElementFromHTML('<a id="hiddenLink" href="' + exportedImage + '" style="display:;" download="image.png">Download</a>');
body.appendChild(downloadedLink);
// MDN 中 Element 没有这个方法,可能不通用
// downloadedLink.click();
var e = document.createEvent("MouseEvents");
e.initEvent("click", true, true);
// downloadedLink.dispatchEvent(e); // 无效
var downloadedLink = document.getElementById('hiddenLink');
downloadedLink.dispatchEvent(e);
body.removeChild(downloadedLink);
}
// exportData 的反向操作
function importData(imgSrc, callback) {
var img = new Image();
img.onload = function () {
// 先把图片填充到画布上
var imgSize = img.width;
var canvas = document.createElement('canvas');
canvas.width = canvas.height = imgSize;
var ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
// 获取数据规模
var headerData = ctx.getImageData(0, 0, 1, 1);
var dataSize = headerData.data[0];
// 获取数据
var imageData = ctx.getImageData(0, 1, dataSize, dataSize);
var paddedData = imageData.data;
// 抛弃每一个像素数据中的第四位(RGBA 中的 alpha 通道)
var uint8array = new Uint8Array(paddedData.length / 4 * 3);
var idx = 0;
for (var i = 0; i < paddedData.length - 1; i += 4) {
var subArray = paddedData.subarray(i, i + 3);
uint8array.set(subArray, idx);
idx += 3;
}
// 将最后为 0 的填充数据去掉
var includeBytes = uint8array.length;
for (var i = uint8array.length - 1; i > 0; i--) {
if (uint8array[i] === 0) {
includeBytes--;
} else {
break;
}
}
var data = uint8array.subarray(0, includeBytes);
var strData = (new TextDecoder('utf-8')).decode(data);
try {
importedData = JSON.parse(strData);
if (callback) {
callback();
}
} catch (error) {
if (callback) {
callback(error);
}
}
};
// 可以设置 src 属性为普通 URL 或 Data URL
img.src = imgSrc;
}
运行
以上所述就是小编给大家介绍的《Web 保存数据的特殊方案》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 保存和恢复模型
- Android 文件保存
- 通过按钮单击保存PhpSpreadSheet
- javascript – Backbone.js – 在上一个保存前保存模型POST(创建)而不是PUT(更新)请求时出现问题
- 使用二进制保存用户状态
- dataframe 保存csv 中文编码
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Rationality for Mortals
Gerd Gigerenzer / Oxford University Press, USA / 2008-05-02 / USD 65.00
Gerd Gigerenzer's influential work examines the rationality of individuals not from the perspective of logic or probability, but from the point of view of adaptation to the real world of human behavio......一起来看看 《Rationality for Mortals》 这本书的介绍吧!