1
0
Fork 0

* Included image and compression tools in repo

* Added better upload of large files (incl. compression)
master
Gordon Williams 2020-05-07 10:04:24 +01:00
parent 5b6d4e9956
commit 0541a66640
9 changed files with 610 additions and 23 deletions

View File

@ -12,3 +12,5 @@ Changed for individual apps are listed in `apps/appname/ChangeLog`
* Rewrite 'getInstalledApps' to minimize RAM usage
* Added code to handle Settings
* Added espruinotools.js for pretokenisation
* Included image and compression tools in repo
* Added better upload of large files (incl. compression)

View File

@ -9,7 +9,7 @@
</div>
<script src="../../lib/customize.js"></script>
<script src="https://espruino.github.io/EspruinoWebTools/imageconverter.js"></script>
<script src="../../lib/imageconverter.js"></script>
<script>
var faces = [

View File

@ -41,8 +41,8 @@
<script src="../../lib/customize.js"></script>
<script src="https://unpkg.com/leaflet@1.0.3/dist/leaflet.js"></script>
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script src="https://espruino.github.io/EspruinoWebTools/heatshrink.js"></script>
<script src="https://espruino.github.io/EspruinoWebTools/imageconverter.js"></script>
<script src="../../lib/heatshrink.js"></script>
<script src="../../lib/imageconverter.js"></script>
<script>
/*

View File

@ -10,8 +10,8 @@
<script src="../../lib/customize.js"></script>
<script src="../../lib/qrcode.min.js"></script><!-- https://davidshimjs.github.io/qrcodejs/ -->
<script src="https://espruino.github.io/EspruinoWebTools/heatshrink.js"></script>
<script src="https://espruino.github.io/EspruinoWebTools/imageconverter.js"></script>
<script src="../../lib/heatshrink.js"></script>
<script src="../../lib/imageconverter.js"></script>
<script>

View File

@ -165,6 +165,7 @@
<script src="js/appinfo.js"></script>
<script src="js/index.js"></script>
<script src="js/espruinotools.js"></script>
<script src="lib/heatshrink.js"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script src="js/pwa.js" defer></script>
</body>

View File

@ -1,7 +1,23 @@
// Converts a string into most efficient way to send to Espruino (either json, base64, or compressed base64)
function toJS(txt) {
var json = JSON.stringify(txt);
var b64 = "atob("+JSON.stringify(btoa(json))+")";
return b64.length < json.length ? b64 : json;
var js = b64.length < json.length ? b64 : json;
if (heatshrink) {
var ua = new Uint8Array(txt.length);
for (var i=0;i<txt.length;i++) ua[i] = txt.charCodeAt(i);
var c = heatshrink.compress(ua);
var cs = "";
for (var i=0;i<c.length;i++)
cs += String.fromCharCode(c[i]);
cs = 'require("heatshrink").decompress(atob("'+btoa(cs)+'"))';
// if it's more than a little smaller, use compressed version
if (cs.length*4 < js.length*3)
js = cs;
}
return js;
}
if ("undefined"!=typeof module)
@ -18,7 +34,7 @@ var AppInfo = {
return new Promise((resolve,reject) => {
// Load all files
Promise.all(app.storage.map(storageFile => {
if (storageFile.content)
if (storageFile.content!==undefined)
return Promise.resolve(storageFile);
else if (storageFile.url)
return options.fileGetter(`apps/${app.id}/${storageFile.url}`).then(content => {
@ -52,14 +68,14 @@ var AppInfo = {
let js = storageFile.content.trim();
if (js.endsWith(";"))
js = js.slice(0,-1);
storageFile.cmd = `\x10require('Storage').write(${toJS(storageFile.name)},${js});`;
storageFile.cmd = `\x10require('Storage').write(${JSON.stringify(storageFile.name)},${js});`;
} else {
let code = storageFile.content;
// write code in chunks, in case it is too big to fit in RAM (fix #157)
var CHUNKSIZE = 4096;
storageFile.cmd = `\x10require('Storage').write(${toJS(storageFile.name)},${toJS(code.substr(0,CHUNKSIZE))},0,${code.length});`;
var CHUNKSIZE = 8192;
storageFile.cmd = `\x10require('Storage').write(${JSON.stringify(storageFile.name)},${toJS(code.substr(0,CHUNKSIZE))},0,${code.length});`;
for (var i=CHUNKSIZE;i<code.length;i+=CHUNKSIZE)
storageFile.cmd += `\n\x10require('Storage').write(${toJS(storageFile.name)},${toJS(code.substr(i,CHUNKSIZE))},${i});`;
storageFile.cmd += `\n\x10require('Storage').write(${JSON.stringify(storageFile.name)},${toJS(code.substr(i,CHUNKSIZE))},${i});`;
}
});
resolve(fileContents);

View File

@ -16,7 +16,7 @@ uploadApp : (app,skipReset) => { // expects an apps.json structure (i.e. with `s
}).then(fileContents => {
return new Promise((resolve,reject) => {
console.log("uploadApp",fileContents.map(f=>f.name).join(", "));
var maxBytes = fileContents.reduce((b,f)=>b+f.content.length, 0)||1;
var maxBytes = fileContents.reduce((b,f)=>b+f.cmd.length, 0)||1;
var currentBytes = 0;
var appInfoFileName = app.id+".info";
@ -37,19 +37,25 @@ uploadApp : (app,skipReset) => { // expects an apps.json structure (i.e. with `s
}
var f = fileContents.shift();
console.log(`Upload ${f.name} => ${JSON.stringify(f.content)}`);
Progress.show({
min:currentBytes / maxBytes,
max:(currentBytes+f.content.length) / maxBytes});
currentBytes += f.content.length;
// Chould check CRC here if needed instead of returning 'OK'...
// E.CRC32(require("Storage").read(${JSON.stringify(app.name)}))
Puck.write(`${f.cmd};Bluetooth.println("OK")\n`,(result) => {
if (!result || result.trim()!="OK") {
Progress.hide({sticky:true});
return reject("Unexpected response "+(result||""));
}
doUploadFiles();
}, true); // wait for a newline
var cmds = f.cmd.split("\n");
function uploadCmd() {
if (!cmds.length) return doUploadFiles();
var cmd = cmds.shift();
Progress.show({
min:currentBytes / maxBytes,
max:(currentBytes+cmd.length) / maxBytes});
currentBytes += cmd.length;
Puck.write(`${cmd};Bluetooth.println("OK")\n`,(result) => {
if (!result || result.trim()!="OK") {
Progress.hide({sticky:true});
return reject("Unexpected response "+(result||""));
}
uploadCmd();
}, true); // wait for a newline
}
uploadCmd();
}
// Start the upload
function doUpload() {

100
lib/heatshrink.js Normal file

File diff suppressed because one or more lines are too long

462
lib/imageconverter.js Normal file
View File

@ -0,0 +1,462 @@
/* Copyright 2020 Gordon Williams, gw@pur3.co.uk
https://github.com/espruino/EspruinoWebTools
*/
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['b'], factory);
} else if (typeof module === 'object' && module.exports) {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory(require('b'));
} else {
// Browser globals (root is window)
root.imageconverter = factory(root.heatshrink);
}
}(typeof self !== 'undefined' ? self : this, function (heatshrink) {
const PALETTE = {
VGA: [0x000000, 0x0000a8, 0x00a800, 0x00a8a8, 0xa80000, 0xa800a8, 0xa85400, 0xa8a8a8, 0x545454, 0x5454fc, 0x54fc54, 0x54fcfc, 0xfc5454, 0xfc54fc, 0xfcfc54, 0xfcfcfc, 0x000000, 0x141414, 0x202020, 0x2c2c2c, 0x383838, 0x444444, 0x505050, 0x606060, 0x707070, 0x808080, 0x909090, 0xa0a0a0, 0xb4b4b4, 0xc8c8c8, 0xe0e0e0, 0xfcfcfc, 0x0000fc, 0x4000fc, 0x7c00fc, 0xbc00fc, 0xfc00fc, 0xfc00bc, 0xfc007c, 0xfc0040, 0xfc0000, 0xfc4000, 0xfc7c00, 0xfcbc00, 0xfcfc00, 0xbcfc00, 0x7cfc00, 0x40fc00, 0x00fc00, 0x00fc40, 0x00fc7c, 0x00fcbc, 0x00fcfc, 0x00bcfc, 0x007cfc, 0x0040fc, 0x7c7cfc, 0x9c7cfc, 0xbc7cfc, 0xdc7cfc, 0xfc7cfc, 0xfc7cdc, 0xfc7cbc, 0xfc7c9c, 0xfc7c7c, 0xfc9c7c, 0xfcbc7c, 0xfcdc7c, 0xfcfc7c, 0xdcfc7c, 0xbcfc7c, 0x9cfc7c, 0x7cfc7c, 0x7cfc9c, 0x7cfcbc, 0x7cfcdc, 0x7cfcfc, 0x7cdcfc, 0x7cbcfc, 0x7c9cfc, 0xb4b4fc, 0xc4b4fc, 0xd8b4fc, 0xe8b4fc, 0xfcb4fc, 0xfcb4e8, 0xfcb4d8, 0xfcb4c4, 0xfcb4b4, 0xfcc4b4, 0xfcd8b4, 0xfce8b4, 0xfcfcb4, 0xe8fcb4, 0xd8fcb4, 0xc4fcb4, 0xb4fcb4, 0xb4fcc4, 0xb4fcd8, 0xb4fce8, 0xb4fcfc, 0xb4e8fc, 0xb4d8fc, 0xb4c4fc, 0x000070, 0x1c0070, 0x380070, 0x540070, 0x700070, 0x700054, 0x700038, 0x70001c, 0x700000, 0x701c00, 0x703800, 0x705400, 0x707000, 0x547000, 0x387000, 0x1c7000, 0x007000, 0x00701c, 0x007038, 0x007054, 0x007070, 0x005470, 0x003870, 0x001c70, 0x383870, 0x443870, 0x543870, 0x603870, 0x703870, 0x703860, 0x703854, 0x703844, 0x703838, 0x704438, 0x705438, 0x706038, 0x707038, 0x607038, 0x547038, 0x447038, 0x387038, 0x387044, 0x387054, 0x387060, 0x387070, 0x386070, 0x385470, 0x384470, 0x505070, 0x585070, 0x605070, 0x685070, 0x705070, 0x705068, 0x705060, 0x705058, 0x705050, 0x705850, 0x706050, 0x706850, 0x707050, 0x687050, 0x607050, 0x587050, 0x507050, 0x507058, 0x507060, 0x507068, 0x507070, 0x506870, 0x506070, 0x505870, 0x000040, 0x100040, 0x200040, 0x300040, 0x400040, 0x400030, 0x400020, 0x400010, 0x400000, 0x401000, 0x402000, 0x403000, 0x404000, 0x304000, 0x204000, 0x104000, 0x004000, 0x004010, 0x004020, 0x004030, 0x004040, 0x003040, 0x002040, 0x001040, 0x202040, 0x282040, 0x302040, 0x382040, 0x402040, 0x402038, 0x402030, 0x402028, 0x402020, 0x402820, 0x403020, 0x403820, 0x404020, 0x384020, 0x304020, 0x284020, 0x204020, 0x204028, 0x204030, 0x204038, 0x204040, 0x203840, 0x203040, 0x202840, 0x2c2c40, 0x302c40, 0x342c40, 0x3c2c40, 0x402c40, 0x402c3c, 0x402c34, 0x402c30, 0x402c2c, 0x40302c, 0x40342c, 0x403c2c, 0x40402c, 0x3c402c, 0x34402c, 0x30402c, 0x2c402c, 0x2c4030, 0x2c4034, 0x2c403c, 0x2c4040, 0x2c3c40, 0x2c3440, 0x2c3040, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0xFFFFFF],
WEB : [0x000000,0x000033,0x000066,0x000099,0x0000cc,0x0000ff,0x003300,0x003333,0x003366,0x003399,0x0033cc,0x0033ff,0x006600,0x006633,0x006666,0x006699,0x0066cc,0x0066ff,0x009900,0x009933,0x009966,0x009999,0x0099cc,0x0099ff,0x00cc00,0x00cc33,0x00cc66,0x00cc99,0x00cccc,0x00ccff,0x00ff00,0x00ff33,0x00ff66,0x00ff99,0x00ffcc,0x00ffff,0x330000,0x330033,0x330066,0x330099,0x3300cc,0x3300ff,0x333300,0x333333,0x333366,0x333399,0x3333cc,0x3333ff,0x336600,0x336633,0x336666,0x336699,0x3366cc,0x3366ff,0x339900,0x339933,0x339966,0x339999,0x3399cc,0x3399ff,0x33cc00,0x33cc33,0x33cc66,0x33cc99,0x33cccc,0x33ccff,0x33ff00,0x33ff33,0x33ff66,0x33ff99,0x33ffcc,0x33ffff,0x660000,0x660033,0x660066,0x660099,0x6600cc,0x6600ff,0x663300,0x663333,0x663366,0x663399,0x6633cc,0x6633ff,0x666600,0x666633,0x666666,0x666699,0x6666cc,0x6666ff,0x669900,0x669933,0x669966,0x669999,0x6699cc,0x6699ff,0x66cc00,0x66cc33,0x66cc66,0x66cc99,0x66cccc,0x66ccff,0x66ff00,0x66ff33,0x66ff66,0x66ff99,0x66ffcc,0x66ffff,0x990000,0x990033,0x990066,0x990099,0x9900cc,0x9900ff,0x993300,0x993333,0x993366,0x993399,0x9933cc,0x9933ff,0x996600,0x996633,0x996666,0x996699,0x9966cc,0x9966ff,0x999900,0x999933,0x999966,0x999999,0x9999cc,0x9999ff,0x99cc00,0x99cc33,0x99cc66,0x99cc99,0x99cccc,0x99ccff,0x99ff00,0x99ff33,0x99ff66,0x99ff99,0x99ffcc,0x99ffff,0xcc0000,0xcc0033,0xcc0066,0xcc0099,0xcc00cc,0xcc00ff,0xcc3300,0xcc3333,0xcc3366,0xcc3399,0xcc33cc,0xcc33ff,0xcc6600,0xcc6633,0xcc6666,0xcc6699,0xcc66cc,0xcc66ff,0xcc9900,0xcc9933,0xcc9966,0xcc9999,0xcc99cc,0xcc99ff,0xcccc00,0xcccc33,0xcccc66,0xcccc99,0xcccccc,0xccccff,0xccff00,0xccff33,0xccff66,0xccff99,0xccffcc,0xccffff,0xff0000,0xff0033,0xff0066,0xff0099,0xff00cc,0xff00ff,0xff3300,0xff3333,0xff3366,0xff3399,0xff33cc,0xff33ff,0xff6600,0xff6633,0xff6666,0xff6699,0xff66cc,0xff66ff,0xff9900,0xff9933,0xff9966,0xff9999,0xff99cc,0xff99ff,0xffcc00,0xffcc33,0xffcc66,0xffcc99,0xffcccc,0xffccff,0xffff00,0xffff33,0xffff66,0xffff99,0xffffcc,0xffffff],
MAC16 : [
0x000000, 0x444444, 0x888888, 0xBBBBBB,
0x996633, 0x663300, 0x006600, 0x00aa00,
0x0099ff, 0x0000cc, 0x330099, 0xff0099,
0xdd0000, 0xff6600, 0xffff00, 0xffffff
],
lookup : function(palette,r,g,b,a, no_transparent) {
if (!no_transparent && a<128) return TRANSPARENT_8BIT;
var maxd = 0xFFFFFF;
var c = 0;
palette.forEach(function(p,n) {
var pr=p>>16;
var pg=(p>>8)&255;
var pb=p&255;
var dr = r-pr;
var dg = g-pg;
var db = b-pb;
var d = dr*dr + dg*dg + db*db;
if (d<maxd) {
c = n;
maxd=d;
}
});
return c;
}
};
var TRANSPARENT_8BIT = 254;
var COL_BPP = {
"1bit":1,
"2bitbw":2,
"4bit":4,
"4bitmac":4,
"vga":8,
"web":8,
"rgb565":16
};
var COL_FROM_RGB = {
"1bit":function(r,g,b) {
var c = (r+g+b) / 3;
var thresh = 128;
return c>thresh;
},
"2bitbw":function(r,g,b) {
var c = (r+g+b) / 3;
c += 31; // rounding
if (c>255)c=255;
return c>>6;
},
"4bit":function(r,g,b,a) {
var thresh = 128;
return (
((r>thresh)?1:0) |
((g>thresh)?2:0) |
((b>thresh)?4:0) |
((a>thresh)?8:0));
},
"4bitmac":function(r,g,b,a) {
return PALETTE.lookup(PALETTE.MAC16,r,g,b,a, true /* no transparency */);
},
"vga":function(r,g,b,a) {
return PALETTE.lookup(PALETTE.VGA,r,g,b,a);
},
"web":function(r,g,b,a) {
return PALETTE.lookup(PALETTE.WEB,r,g,b,a);
},
"rgb565":function(r,g,b,a) {
return (
((r&0xF8)<<8) |
((g&0xFC)<<3) |
((b&0xF8)>>3));
},
};
var COL_TO_RGB = {
"1bit":function(c) {
return c ? 0xFFFFFFFF : 0xFF000000;
},
"2bitbw":function(c) {
c = c&3;
c = c | (c<<2) | (c<<4) | (c<<6);
return 0xFF000000|(c<<16)|(c<<8)|c;
},
"4bit":function(c) {
if (!(c&8)) return 0;
return ((c&1 ? 0xFF0000 : 0xFF000000) |
(c&2 ? 0x00FF00 : 0xFF000000) |
(c&4 ? 0x0000FF : 0xFF000000));
},
"4bitmac":function(c) {
return 0xFF000000|PALETTE.MAC16[c];
},
"vga":function(c) {
if (c==TRANSPARENT_8BIT) return 0;
return 0xFF000000|PALETTE.VGA[c];
},
"web":function(c) {
if (c==TRANSPARENT_8BIT) return 0;
return 0xFF000000|PALETTE.WEB[c];
},
"rgb565":function(c) {
var r = (c>>8)&0xF8;
var g = (c>>3)&0xFC;
var b = (c<<3)&0xF8;
return 0xFF000000|(r<<16)|(g<<8)|b;
},
};
// What Espruino uses by default
var BPP_TO_COLOR_FORMAT = {
1 : "1bit",
2 : "2bitbw",
4 : "4bitmac",
8 : "web",
16 : "rgb565"
};
function clip(x) {
if (x<0) return 0;
if (x>255) return 255;
return x;
}
/*
See 'getOptions' for possible options
*/
function RGBAtoString(rgba, options) {
options = options||{};
if (!rgba) throw new Error("No dataIn specified");
if (!options.width) throw new Error("No Width specified");
if (!options.height) throw new Error("No Height specified");
if ("string"!=typeof options.diffusion)
options.diffusion = "none";
options.compression = options.compression || false;
options.brightness = options.brightness | 0;
options.mode = options.mode || "1bit";
options.output = options.output || "object";
options.inverted = options.inverted || false;
options.transparent = !!options.transparent;
var transparentCol = undefined;
if (options.transparent) {
if (options.mode=="4bit")
transparentCol=0;
if (options.mode=="vga" || options.mode=="web")
transparentCol=TRANSPARENT_8BIT;
}
var bpp = COL_BPP[options.mode];
var bitData = new Uint8Array(((options.width*options.height)*bpp+7)/8);
function readImage() {
var pixels = new Int32Array(options.width*options.height);
var n = 0;
var er=0,eg=0,eb=0;
for (var y=0; y<options.height; y++) {
for (var x=0; x<options.width; x++) {
var r = rgba[n*4];
var g = rgba[n*4+1];
var b = rgba[n*4+2];
var a = rgba[n*4+3];
if (options.diffusion == "random1" ||
options.diffusion == "errorrandom") {
er += Math.random()*48 - 24;
eg += Math.random()*48 - 24;
eb += Math.random()*48 - 24;
} else if (options.diffusion == "random2") {
er += Math.random()*128 - 64;
eg += Math.random()*128 - 64;
eb += Math.random()*128 - 64;
}
if (options.inverted) {
r=255-r;
g=255-g;
b=255-b;
}
r = clip(r + options.brightness + er);
g = clip(g + options.brightness + eg);
b = clip(b + options.brightness + eb);
var isTransparent = a<128;
var c = COL_FROM_RGB[options.mode](r,g,b,a);
if (isTransparent && options.transparent && transparentCol===undefined) {
c = -1;
a = 0;
}
pixels[n] = c;
// error diffusion
var cr = COL_TO_RGB[options.mode](c);
var oa = cr>>>24;
var or = (cr>>16)&255;
var og = (cr>>8)&255;
var ob = cr&255;
if (options.diffusion.startsWith("error") && a>128) {
er = r-or;
eg = g-og;
eb = b-ob;
} else {
er = 0;
eg = 0;
eb = 0;
}
n++;
}
}
return pixels;
}
function writeImage(pixels) {
var n = 0;
for (var y=0; y<options.height; y++) {
for (var x=0; x<options.width; x++) {
var c = pixels[n];
// Write image data
if (bpp==1) bitData[n>>3] |= c ? 128>>(n&7) : 0;
else if (bpp==2) bitData[n>>2] |= c<<((3-(n&3))*2);
else if (bpp==4) bitData[n>>1] |= c<<((n&1)?0:4);
else if (bpp==8) bitData[n] = c;
else if (bpp==16) { bitData[n<<1] = c>>8; bitData[1+(n<<1)] = c&0xFF; }
else throw new Error("Unhandled BPP");
// Write preview
var cr = COL_TO_RGB[options.mode](c);
if (c===transparentCol)
cr = ((((x>>2)^(y>>2))&1)?0xFFFFFF:0); // pixel pattern
var oa = cr>>>24;
var or = (cr>>16)&255;
var og = (cr>>8)&255;
var ob = cr&255;
if (options.rgbaOut) {
options.rgbaOut[n*4] = or;
options.rgbaOut[n*4+1]= og;
options.rgbaOut[n*4+2]= ob;
options.rgbaOut[n*4+3]=255;
}
n++;
}
}
}
var pixels = readImage();
if (options.transparent && transparentCol===undefined && bpp<=16) {
// we have no fixed transparent colour - pick one that's unused
var colors = new Uint32Array(1<<bpp);
// how many colours?
for (var i=0;i<pixels.length;i++)
if (pixels[i]>=0)
colors[pixels[i]]++;
// find an empty one
for (var i=0;i<colors.length;i++)
if (colors[i]==0) {
transparentCol = i;
break;
}
if (transparentCol===undefined) {
console.log("No unused colour found - using 0 for transparency");
for (var i=0;i<pixels.length;i++)
if (pixels[i]<0)
pixels[i]=0;
} else {
for (var i=0;i<pixels.length;i++)
if (pixels[i]<0)
pixels[i]=transparentCol;
}
}
writeImage(pixels);
var strCmd;
if ((options.output=="string") || (options.output=="raw")) {
var transparent = transparentCol!==undefined;
var headerSize = transparent?4:3;
var imgData = new Uint8Array(bitData.length + headerSize);
imgData[0] = options.width;
imgData[1] = options.height;
imgData[2] = bpp + (transparent?128:0);
if (transparent) imgData[3] = transparentCol;
imgData.set(bitData, headerSize);
bitData = imgData;
}
if (options.compression) {
bitData = heatshrink.compress(bitData);
strCmd = 'require("heatshrink").decompress';
} else {
strCmd = 'E.toArrayBuffer';
}
var str = "";
for (n=0; n<bitData.length; n++)
str += String.fromCharCode(bitData[n]);
var imgstr;
if (options.output=="raw") {
imgstr = str;
} else if (options.output=="object") {
imgstr = "{\n";
imgstr += " width : "+options.width+", height : "+options.height+", bpp : "+bpp+",\n";
if (transparentCol!==undefined) imgstr += " transparent : "+transparentCol+",\n";
imgstr += ' buffer : '+strCmd+'(atob("'+btoa(str)+'"))\n';
imgstr += "}";
} else if (options.output=="string") {
imgstr = strCmd+'(atob("'+btoa(str)+'"))';
} else {
throw new Error("Unknown output style");
}
return imgstr;
}
/* Add a checkerboard background to any transparent areas and
make everything nontransparent. expects width/height in optuons */
function RGBAtoCheckerboard(rgba, options) {
var n=0;
for (var y=0; y<options.height; y++) {
for (var x=0; x<options.width; x++) {
var na = rgba[n*4+3]/255;
var a = 1-na;
var chequerboard = ((((x>>2)^(y>>2))&1)?0xFFFFFF:0);
rgba[n*4] = rgba[n*4]*na + chequerboard*a;
rgba[n*4+1] = rgba[n*4+1]*na + chequerboard*a;
rgba[n*4+2] = rgba[n*4+2]*na + chequerboard*a;
rgba[n*4+3] = 255;
n++;
}
}
}
/* RGBAtoString options, PLUS:
updateCanvas: update canvas with the quantized image
*/
function canvastoString(canvas, options) {
options = options||{};
options.width = canvas.width;
options.height = canvas.height;
var ctx = canvas.getContext("2d");
var imageData = ctx.getImageData(0, 0, options.width, options.height);
var rgba = imageData.data;
if (options.updateCanvas)
options.rgbaOut = rgba;
var str = RGBAtoString(rgba, options);
if (options.updateCanvas)
ctx.putImageData(imageData,0,0);
return str;
}
/* RGBAtoString options, PLUS:
*/
function imagetoString(img, options) {
options = options||{};
var canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
var ctx = canvas.getContext("2d");
ctx.drawImage(img,0,0);
return canvastoString(canvas, options);
}
function getOptions() {
return {
width : "int",
height : "int",
rgbaOut : "Uint8Array", // to store quantised data
diffusion : ["none"],
compression : "bool",
transparent : "bool",
brightness : "int",
mode : Object.keys(COL_BPP),
output : ["object","string","raw"],
inverted : "bool",
}
}
/* Decode an Espruino image string into a URL, return undefined if it's not valid.
options = {
transparent : bool // should the image be transparent, or just chequered where transparent?
} */
function stringToImageURL(data, options) {
options = options||{};
var p = 0;
var width = 0|data.charCodeAt(p++);
var height = 0|data.charCodeAt(p++);
var bpp = 0|data.charCodeAt(p++);
var transparentCol = -1;
if (bpp&128) {
bpp &= 127;
transparentCol = 0|data.charCodeAt(p++);
}
var mode = BPP_TO_COLOR_FORMAT[bpp];
if (!mode) return undefined; // unknown format
var bitmapSize = ((width*height*bpp)+7) >> 3;
// If it's the wrong length, it's not a bitmap or it's corrupt!
if (data.length != p+bitmapSize)
return undefined;
// Ok, build the picture
var canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext("2d");
var imageData = ctx.getImageData(0, 0, width, height);
var rgba = imageData.data;
var no = 0;
var nibits = 0;
var nidata = 0;
for (var i=0;i<width*height;i++) {
while (nibits<bpp) {
nidata = (nidata<<8) | data.charCodeAt(p++);
nibits += 8;
}
var c = (nidata>>(nibits-bpp)) & ((1<<bpp)-1);
nibits -= bpp;
var cr = COL_TO_RGB[mode](c);
if (c == transparentCol)
cr = cr & 0xFFFFFF;
rgba[no++] = (cr>>16)&255; // r
rgba[no++] = (cr>>8)&255; // g
rgba[no++] = cr&255; // b
rgba[no++] = cr>>>24; // a
}
if (!options.transparent)
RGBAtoCheckerboard(rgba, {width:width, height:height});
ctx.putImageData(imageData,0,0);
return canvas.toDataURL();
}
// decode an Espruino image string into an HTML string, return undefined if it's not valid. See stringToImageURL
function stringToImageHTML(data, options) {
var url = stringToImageURL(data, options);
if (!url) return undefined;
return '<img src="'+url+'"\>';
}
// =======================================================
return {
RGBAtoString : RGBAtoString,
RGBAtoCheckerboard : RGBAtoCheckerboard,
canvastoString : canvastoString,
imagetoString : imagetoString,
getOptions : getOptions,
stringToImageHTML : stringToImageHTML,
stringToImageURL : stringToImageURL
};
}));