/* 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 (dthresh; }, "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>>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>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<=0) colors[pixels[i]]++; // find an empty one for (var i=0;i>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>(nibits-bpp)) & ((1<>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 ''; } // ======================================================= return { RGBAtoString : RGBAtoString, RGBAtoCheckerboard : RGBAtoCheckerboard, canvastoString : canvastoString, imagetoString : imagetoString, getOptions : getOptions, stringToImageHTML : stringToImageHTML, stringToImageURL : stringToImageURL }; }));