内容简介:原文:实现数据的保存与加载。前提是没有以下支持:
原文:
- 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 中文编码
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。