├── LICENSE ├── README.md ├── copyzpl.css ├── package.json ├── pako.js ├── zpl-image.html └── zpl-image.js /LICENSE: -------------------------------------------------------------------------------- 1 | zpl-image 2 | 3 | Copyright 2019 Mark Warren 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # zpl-image 3 | 4 | A pure javascript module that converts images to either Z64-encoded or ACS-encoded GRF bitmaps for use with ZPL. 5 | The term ACS (Alternative Compression Scheme) denotes the run-length compression algorithm described in the section 6 | of the ZPL Reference Manual titled "Alternative Data Compression Scheme". Z64 typically gives better compression 7 | but is not available on all printers (especially older ones). The ACS encoding should work on any printer made 8 | since the mid 90s, maybe earlier. 9 | 10 | This module provides the following features: 11 | 12 | - Works in both node.js and modern browsers. 13 | - Converts the image to grayscale, then applies a user-supplied blackness 14 | threshold to decide which pixels are black. 15 | - Optionally removes any empty/white space around the edges of the image. 16 | - Optionally rotates the image to one of the orthogonal angles. This step 17 | is often necessary as ZPL does not provide the ability to rotate an image 18 | during formatting. 19 | - Converts the monochrome image to a GRF bitmap. 20 | - Converts the GRF bitmap to either Z64 or ACS encoding. 21 | - For Z64, zlib in node.js or pako.js in the browser is used for compression. 22 | 23 | The blackness threshold is specified as an integer between 1 and 99 (think of it as a 24 | gray percentage). Pixels darker than the gray% are converted to black. The default is 50. 25 | 26 | Rotation is specified as one of the values: 27 | 28 | - `'N'` : No rotation, the default. 29 | - `'L'` : Left, 90 degrees counter-clockwise rotation. 30 | - `'R'` : Right, 90 degrees clockwise rotation. 31 | - `'I'` : Inverted, 180 degrees rotation. 32 | - `'B'` : Same as `'L'` but named to match the ZPL notation. 33 | 34 | Blackness and rotation are passed via an options object. For example, to specify 35 | a black threshold of 56% and rotation of -90 degrees, you would pass in: 36 | 37 | ```javascript 38 | { black:56, rotate:'L' } 39 | ``` 40 | 41 | Trimming of empty space around the image is enabled by default. To disable, specify 42 | the option `notrim:true`. 43 | 44 | ## Demo 45 | 46 | Included with this module is the file `zpl-image.html`. You can run it directly 47 | from the browser using the `file://` scheme. It lets you drag and drop an image 48 | and then interactively adjust the blackness threshold and rotation. 49 | 50 | When you are satisfied with the results, select either Z64 or ACS encoding and 51 | click the clipboard icon to copy the ZPL. The ZPL will have the following format: 52 | 53 | ``` 54 | ^FX filename.ext (WxHpx, X-Rotate, XX% Black)^FS 55 | ^GFA,grflen,grflen,rowlen,...ASCII-armored-encoding... 56 | ``` 57 | 58 | `^FX ... ^FS` is a ZPL comment. 59 | 60 | `^GF` is the ZPL command for use-once image rendering (that is, the image is not 61 | saved to the printer for later recall by other label formats). 62 | 63 | The rendered image displayed on the page is the actual data decoded and then drawn 64 | to a canvas. If you are interested in that bit of functionality, look for `z64ToCanvas` 65 | and `acsToCanvas` in the `zpl-image.html` file. 66 | 67 | ## Generic Browser Usage 68 | 69 | To use in the browser, include the following two scripts: 70 | 71 | ```html 72 | 73 | 74 | ``` 75 | 76 | There is a version of pako.js included with this module, but it will not be updated 77 | frequently. It is primarily intended for the demo html file but should be sufficient 78 | for production use. 79 | 80 | ```javascript 81 | // Works with and elements or any element that is 82 | // compatible with CanvasRenderingContext2D.drawImage(). 83 | let img = document.getElementById('image'); 84 | let res = imageToZ64(img); // Uses all defaults 85 | 86 | // res.length is the uncompressed GRF length. 87 | // res.rowlen is the GRF row length. 88 | // res.z64 is the Z64 encoded string. 89 | let zpl = `^GFA,${res.length},${res.length},${res.rowlen},${res.z64}`; 90 | ``` 91 | 92 | An alternative for when you already have the pixel values in RGBA format 93 | (either in a Uint8Array or Array of integers clamped to 0..255) is 94 | `rgbaToZ64()`. This function is the lower-level converter used 95 | by both node.js and `imageToZ64()`. See the node.js section for more details. 96 | 97 | ```javascript 98 | // `rgba` is an array of RGBA values. 99 | // `width` is the width of the image, in pixels. 100 | // The return value is the same as above. 101 | let res = rgbaToZ64(rgba, width, { black:55, rotate:'I' }); 102 | ``` 103 | 104 | The same interfaces exist for ACS encoding, using the functions `imageToACS()` and 105 | `rgbaToACS()`. The returned object from each function is identical to the above, with 106 | the exception that the encoded text is in the `acs` property instead of `z64`. 107 | 108 | ## RequireJS Browser Usage 109 | 110 | This is untested but the module exports are wrapped in a UMD, so in theory you 111 | should be able to use this with RequireJS. The exports are the same as with the 112 | generic browser usage: 113 | 114 | ```javascript 115 | // Use the Z64 interface 116 | const { imageToZ64, rgbaToZ64 } = require("zpl-image"); 117 | 118 | // Or the ACS interface 119 | const { imageToACS, rgbaToACS } = require("zpl-image"); 120 | ``` 121 | 122 | ## Node.js Usage 123 | 124 | The exports from `require("zpl-image")` are the functions `rgbaToZ64()` and 125 | `rgbaToACS()`. 126 | 127 | ```javascript 128 | // The Z64 interface 129 | const rgbaToZ64 = require("zpl-image").rgbaToZ64; 130 | 131 | // The ACS interface 132 | const rgbaToACS = require("zpl-image").rgbaToACS; 133 | 134 | ``` 135 | 136 | Both methods take two or three parameters: 137 | 138 | ``` 139 | rgbaToZ64(rgba, width [, opts]) 140 | rgbaToACS(rgba, width [, opts]) 141 | ``` 142 | 143 | `rgba` is an array-like object with length equal to `width * height * 4`. 144 | An array-like object can be a Buffer, Uint8Array, or Array of integers 145 | clamped to 0..255. `width` and `height` are the dimensions of the image, in pixels. 146 | Each "quad" of the RGBA array is structured as: 147 | 148 | ```javascript 149 | rgba[i] // red 0..255 150 | rgba[i+1] // green 0..255 151 | rgba[i+2] // blue 0..255 152 | rgba[i+3] // alpha (0 == fully transparent, 255 == fully opaque) 153 | ``` 154 | 155 | Because of the varied nature of the node.js ecosystem, zpl-image does not include 156 | any dependencies for image modules. You need to decide what types of images to 157 | support and which image processing package(s) to use. Below are some simple 158 | examples showing three different image modules: 159 | 160 | - [pngjs](https://www.npmjs.com/package/pngjs) 161 | - [omggif](https://www.npmjs.com/package/omggif) 162 | - [jpeg-js](https://www.npmjs.com/package/jpeg-js) 163 | 164 | All of the following examples show Z64 encoding but can be switched to ACS 165 | by simply renaming `Z64` to `ACS`. 166 | 167 | ## pngjs (PNG Conversion) 168 | 169 | [pngjs](https://www.npmjs.com/package/pngjs) 170 | 171 | ```javascript 172 | // Synchronous pngjs usage 173 | const fs = require('fs'); 174 | const PNG = require('pngjs').PNG; 175 | const rgbaToZ64 = require('zpl-image').rgbaToZ64; 176 | 177 | let buf = fs.readFileSync('tux.png'); 178 | let png = PNG.sync.read(buf); 179 | let res = rgbaToZ64(png.data, png.width, { black:53 }); 180 | 181 | // res.length is the uncompressed GRF length. 182 | // res.rowlen is the GRF row length. 183 | // res.z64 is the Z64 encoded string. 184 | let zpl = `^GFA,${res.length},${res.length},${res.rowlen},${res.z64}`; 185 | ``` 186 | 187 | ```javascript 188 | // Async pngjs usage 189 | const fs = require('fs'); 190 | const PNG = require('pngjs').PNG; 191 | const rgbaToZ64 = require('zpl-image').rgbaToZ64; 192 | 193 | fs.createReadStream('tux.png') 194 | .pipe(new PNG({ filterType: 4 })) 195 | .on('parsed', function() { 196 | // res is the same as above 197 | let res = rgbaToZ64(this.data, this.width, { black:52, rotate:'R' }); 198 | }); 199 | ``` 200 | 201 | ## omggif (GIF Conversion) 202 | 203 | [omggif](https://www.npmjs.com/package/omggif) 204 | 205 | ```javascript 206 | const fs = require('fs'); 207 | const GIF = require('omggif'); 208 | const rgbaToZ64 = require('zpl-image').rgbaToZ64; 209 | 210 | let buf = fs.readFileSync('tux.gif'); 211 | let gif = new GIF.GifReader(buf); 212 | let rgba = Buffer.alloc(gif.width * gif.height * 4); 213 | 214 | // Decode only the first frame 215 | gif.decodeAndBlitFrameRGBA(0, rgba); 216 | 217 | let res = rgbaToZ64(rgba, gif.width, { black:47 }); 218 | ``` 219 | 220 | ## jpeg-js (JPEG Conversion) 221 | 222 | [jpeg-js](https://www.npmjs.com/package/jpeg-js) 223 | 224 | ```javascript 225 | const fs = require('fs'); 226 | const JPG = require('jpeg-js'); 227 | const rgbaToZ64 = require('zpl-image').rgbaToZ64; 228 | 229 | let buf = fs.readFileSync('tux.jpg'); 230 | let jpg = JPG.decode(buf); 231 | let res = rgbaToZ64(jpg.data, jpg.width, { black:51, rotate:'I' }); 232 | ``` 233 | 234 | ## MIT License 235 | 236 | -------------------------------------------------------------------------------- /copyzpl.css: -------------------------------------------------------------------------------- 1 | 2 | /* Keep the unwieldy data-url out of the main code */ 3 | #copyzpl { 4 | position: absolute; 5 | top: -2ex; 6 | width: 48px; 7 | height: 48px; 8 | border: 1px solid #aaa; 9 | border-radius: 4px; 10 | background-color: #fff; 11 | background-position:50% 50%; 12 | background-repeat: no-repeat; 13 | } 14 | #copyzpl:hover { 15 | background-color: #99beff; 16 | } 17 | #copyzpl:active { 18 | background-color: #4c8dff; 19 | } 20 | #copyzpl { 21 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAnCAYAAAAcsCj6AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsIAAA7CARUoSoAAAAT5SURBVFhHtVdLbBtVFJ1fZvwZx6k99sSf1FVKPlWaBJKS8JVAlBIQqKpEJGDXCgnBogLBPguQKoRYIbFg0xWsK5AQFUWIRSVgUVEVyI8ShygORsqniZ2xx/PhvOfx1FOHJA2TI12/9+5789679557Z8wy+wfb0zOc3iqvntW2t5+s6TVLjkW/b0+nv/nj55+WMW076/xBb2+vEgqHr3Acp2Nosyxro1+TI5Frp554pru+ykdks7l3yQGCIFjRWPwrRVG+5Hm+goMtVe38zFnmD2zbZmOx+HXSTSY7L4+++Koy/tIraiAUukJ0gUBgbmpqiqOL9wDvtDticnJS3CiVnvrw0qUXKtXK2Zquy6Io5reKy8p6YXHI1KtjhmFkOZ4Xfr31W1WWo1a5vLWCRw8Wz4EHx4aDIfkm3GiSmEFF4mbBlRZxLfpUR+agM4LB4Myz586dpA//ByhLs9nsc6VS6QNd149i2OyatkqlErEsa1/uIgiHw3cQgpozJLDa2tqmoX+jUCjMMvF4/GEo/sYEve1hiShKhXgq1c9ZFvNarVZToaSAmxheEOrC7xriHUGeaTwPVztahtH1ago/H3O6Xul3dIwoSaXM0aNvh9vj40TkaOw8Nlh3pvcEiWkymZzgJGkQMtTV1fUyvPePM81omtZDfH4VfWq2qqqfonXjhblBkKOIbouLdhJczkR7BNIAm87lLjQIB1Ld9pKB437Hr1UfMEwkkdiUJOkv3JIEntlDLKTMzUQi0UwYOyjLMw3XomXZkCx/u10qnSYKVU1dLBZXPqGzwMTEhDQ9nx/ZKm9lONt2LyeJYsBkGdswdFLmKFiLNcOR0Hx+fv4Whu6le06cGL89O/sjmM6EQqFFRpYj30FPTU50pt+iqzywcT0Iqk1DSFWhlaVJR8VJs2Z0d/edauQrDvzTE8NkMvU6WeQnegeGH28c2BJDlAyDtrbtixAEQmKEdhx4DhRFXiLt0OjoKCxfAutoGbtfQTFfyh479gjZyzaYZhJ5DzQA0q4sLw8jZxQEmj2IYJvU5tqdPrIXSqNLLAJvWmA9adRM5of2aPQGgqzBUrwFZB39yr2yy9wvinLkBtnLyUEK9DmwVL6GPg2qqqbP0xkfcbz/5KMgDTGEsHSBWOje4DBgWYgiw5AKBCIxFvLZdpO0gcGRkVHcZonc7CCC6rSUyeUoaQSB5qcD2xtDpAsd+0kaQZICMMp97XhJg+uRxk/SmLpOvhZcK1k8dLVcLp8hA5DmQrFYuEz0uVxOwheA90L7BIq4tbi4WEXXPj4w8NjC9PR1WM6g0nhL22GwtLuptMHyBQ7WtrzW/STNPbDxiWG1pIWfpGkGyGMTC5vTggbXT9KILCuQ1kVzDFOZzEWiI5XeDyEYHhk7A6Po/i2vJ5bj6G0Ii/0QAq1S2W70CTwHms7bwk9U66XNBSltd49vKXL/HwL9RKkDlvJgMe9ayQn1SuMnONZsa8QTsJAWpmtXrVp9yOn6hoqmuXviYJsNhuWvtXLpeaKAuVpnKvVFpL1jVjd0ja5ywTNwgISXi2napjcusAMfzFLNMnQbiUh0sEnXSqWu9dXV91AiA0SHdJljYjHlo/r8XYGvfZPmfZVE4nM+FlOL+H/xtGkaCpSHhrAsz4Y7Ot4kDOK6+/pGNtfW3lnb2HjANs37/8u0C1DKmJiizISDwffz+fzcv7cGZ55/jBKaAAAAAElFTkSuQmCC); 22 | } 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zpl-image", 3 | "version": "0.2.0", 4 | "description": "A pure javascript module that converts PNG, JPEG, and GIF to Z64-encoded GRF bitmaps for use with ZPL.", 5 | "main": "zpl-image.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/metafloor/zpl-image.git" 12 | }, 13 | "keywords": [ 14 | "image", 15 | "ZPL", 16 | "Z64", 17 | "GRF", 18 | "PNG", 19 | "JPEG", 20 | "GIF" 21 | ], 22 | "author": "Mark Warren", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/metafloor/zpl-image/issues" 26 | }, 27 | "homepage": "https://github.com/metafloor/zpl-image", 28 | "directories": { 29 | "test": "tests" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /zpl-image.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Image to ZPL 6 | 94 | 95 | 96 | 97 | 288 | 289 | 290 |
Image to ZPL
291 |
292 |
293 |
Drop image here
294 |
295 | 296 |
Image Rotation 297 | 299 | 301 | 303 | 305 | 306 | 307 |
Black Threshold 308 | 309 | 1..99 310 | 312 |
ZPL Format 313 | 315 | 317 |
318 |
319 | 320 | 321 |
322 | 323 | 324 |
325 |
326 | 327 | 330 |
331 | 332 | 333 | 334 | -------------------------------------------------------------------------------- /zpl-image.js: -------------------------------------------------------------------------------- 1 | 2 | (function (root, factory) { 3 | if (typeof define === 'function' && define.amd) { 4 | define([], factory); 5 | } else if (typeof module === 'object' && module.exports) { 6 | module.exports = factory(); 7 | } else { 8 | // generic browser usage 9 | let ex = factory(); 10 | for (let id in ex) { 11 | root[id] = ex[id]; 12 | } 13 | } 14 | }(typeof self !== 'undefined' ? self : this, function () { 15 | 16 | const zlib = typeof process == 'object' && typeof process.release == 'object' && 17 | process.release.name == 'node' ? require('zlib') : null; 18 | 19 | const hexmap = (()=> { 20 | let arr = Array(256); 21 | for (let i = 0; i < 16; i++) { 22 | arr[i] = '0' + i.toString(16); 23 | } 24 | for (let i = 16; i < 256; i++) { 25 | arr[i] = i.toString(16); 26 | } 27 | return arr; 28 | })(); 29 | 30 | // DOM-specialized version for browsers. 31 | function imageToZ64(img, opts) { 32 | // Draw the image to a temp canvas so we can access its RGBA data 33 | let cvs = document.createElement('canvas'); 34 | let ctx = cvs.getContext('2d'); 35 | 36 | cvs.width = +img.width || img.offsetWidth; 37 | cvs.height = +img.height || img.offsetHeight; 38 | ctx.imageSmoothingQuality = 'high'; // in case canvas needs to scale image 39 | ctx.drawImage(img, 0, 0, cvs.width, cvs.height); 40 | 41 | let pixels = ctx.getImageData(0, 0, cvs.width, cvs.height); 42 | return rgbaToZ64(pixels.data, pixels.width, opts); 43 | } 44 | 45 | // DOM-specialized version for browsers. 46 | function imageToACS(img, opts) { 47 | // Draw the image to a temp canvas so we can access its RGBA data 48 | let cvs = document.createElement('canvas'); 49 | let ctx = cvs.getContext('2d'); 50 | 51 | cvs.width = +img.width || img.offsetWidth; 52 | cvs.height = +img.height || img.offsetHeight; 53 | ctx.imageSmoothingQuality = 'high'; // in case canvas needs to scale image 54 | ctx.drawImage(img, 0, 0, cvs.width, cvs.height); 55 | 56 | let pixels = ctx.getImageData(0, 0, cvs.width, cvs.height); 57 | return rgbaToACS(pixels.data, pixels.width, opts); 58 | } 59 | 60 | // Uses zlib on node.js, pako.js in the browser. 61 | // 62 | // `rgba` can be a Uint8Array or Buffer, or an Array of integers between 0 and 255. 63 | // `width` is the image width, in pixels 64 | // `opts` is an options object: 65 | // `black` is the blackness percent between 1..99, default 50. 66 | // `rotate` is one of: 67 | // 'N' no rotation (default) 68 | // 'L' rotate 90 degrees counter-clockwise 69 | // 'R' rotate 90 degrees clockwise 70 | // 'I' rotate 180 degrees (inverted) 71 | // 'B' same as 'L' 72 | function rgbaToZ64(rgba, width, opts) { 73 | opts = opts || {}; 74 | width = width|0; 75 | if (!width || width < 0) { 76 | throw new Error('Invalid width'); 77 | } 78 | let height = ~~(rgba.length / width / 4); 79 | 80 | // Create a monochome image, cropped to remove padding. 81 | // The return is a Uint8Array with extra properties width and height. 82 | let mono = monochrome(rgba, width, height, +opts.black || 50, opts.notrim); 83 | 84 | let buf; 85 | switch (opts.rotate) { 86 | case 'R': buf = right(mono); break; 87 | case 'B': 88 | case 'L': buf = left(mono); break; 89 | case 'I': buf = invert(mono); break; 90 | default: buf = normal(mono); break; 91 | } 92 | 93 | // Compress and base64 encode 94 | let imgw = buf.width; 95 | let imgh = buf.height; 96 | let rowl = ~~((imgw + 7) / 8); 97 | let b64; 98 | if (zlib) { 99 | b64 = zlib.deflateSync(buf).toString('base64'); 100 | } else { 101 | b64 = u8tob64(pako.deflate(buf)); 102 | } 103 | 104 | // Example usage of the return value `rv`: 105 | // '^GFA,' + rv.length + ',' + rv.length + ',' + rv.rowlen + ',' + rv.z64 106 | return { 107 | length: buf.length, // uncompressed number of bytes 108 | rowlen: rowl, // number of packed bytes per row 109 | width: imgw, // rotated image width in pixels 110 | height: imgh, // rotated image height in pixels 111 | z64: ':Z64:' + b64 + ':' + crc16(b64), 112 | }; 113 | } 114 | 115 | // Implements the Alternative Data Compression Scheme as described in the ref manual. 116 | // 117 | // `rgba` can be a Uint8Array or Buffer, or an Array of integers between 0 and 255. 118 | // `width` is the image width, in pixels 119 | // `opts` is an options object: 120 | // `black` is the blackness percent between 1..99, default 50. 121 | // `rotate` is one of: 122 | // 'N' no rotation (default) 123 | // 'L' rotate 90 degrees counter-clockwise 124 | // 'R' rotate 90 degrees clockwise 125 | // 'I' rotate 180 degrees (inverted) 126 | // 'B' same as 'L' 127 | function rgbaToACS(rgba, width, opts) { 128 | opts = opts || {}; 129 | width = width|0; 130 | if (!width || width < 0) { 131 | throw new Error('Invalid width'); 132 | } 133 | let height = ~~(rgba.length / width / 4); 134 | 135 | // Create a monochome image, cropped to remove padding. 136 | // The return is a Uint8Array with extra properties width and height. 137 | let mono = monochrome(rgba, width, height, +opts.black || 50, opts.notrim); 138 | 139 | let buf; 140 | switch (opts.rotate) { 141 | case 'R': buf = right(mono); break; 142 | case 'B': 143 | case 'L': buf = left(mono); break; 144 | case 'I': buf = invert(mono); break; 145 | default: buf = normal(mono); break; 146 | } 147 | 148 | // Encode in hex and apply the "Alternative Data Compression Scheme" 149 | // 150 | // G H I J K L M N O P Q R S T U V W X Y 151 | // 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 152 | // 153 | // g h i j k l m n o p q r s t u v w x y z 154 | // 20 40 60 80 100 120 140 160 180 200 220 240 260 280 300 320 340 360 380 400 155 | // 156 | let imgw = buf.width; 157 | let imgh = buf.height; 158 | let rowl = ~~((imgw + 7) / 8); 159 | 160 | let hex = ''; 161 | for (let i = 0, l = buf.length; i < l; i++) { 162 | hex += hexmap[buf[i]]; 163 | } 164 | let acs = ''; 165 | let re = /([0-9a-fA-F])\1{2,}/g; 166 | let match = re.exec(hex); 167 | let offset = 0; 168 | while (match) { 169 | acs += hex.substring(offset, match.index); 170 | let l = match[0].length; 171 | while (l >= 400) { 172 | acs += 'z'; 173 | l -= 400; 174 | } 175 | if (l >= 20) { 176 | acs += '_ghijklmnopqrstuvwxy'[((l / 20)|0)]; 177 | l = l % 20; 178 | } 179 | if (l) { 180 | acs += '_GHIJKLMNOPQRSTUVWXY'[l]; 181 | } 182 | acs += match[1]; 183 | offset = re.lastIndex; 184 | match = re.exec(hex); 185 | } 186 | acs += hex.substr(offset); 187 | 188 | // Example usage of the return value `rv`: 189 | // '^GFA,' + rv.length + ',' + rv.length + ',' + rv.rowlen + ',' + rv.acs 190 | return { 191 | length: buf.length, // uncompressed number of bytes 192 | rowlen: rowl, // number of packed bytes per row 193 | width: imgw, // rotated image width in pixels 194 | height: imgh, // rotated image height in pixels 195 | acs: acs, 196 | }; 197 | } 198 | 199 | // Normal, unrotated case 200 | function normal(mono) { 201 | let width = mono.width; 202 | let height = mono.height; 203 | 204 | let buf = new Uint8Array(~~((width + 7) / 8) * height); 205 | let idx = 0; // index into buf 206 | let byte = 0; // current byte of image data 207 | let bitx = 0; // bit index 208 | for (let i = 0, n = mono.length; i < n; i++) { 209 | byte |= mono[i] << (7 - (bitx++ & 7)); 210 | 211 | if (bitx == width || !(bitx & 7)) { 212 | buf[idx++] = byte; 213 | byte = 0; 214 | if (bitx == width) { 215 | bitx = 0; 216 | } 217 | } 218 | } 219 | buf.width = width; 220 | buf.height = height; 221 | return buf; 222 | } 223 | 224 | // Inverted 180 degrees 225 | function invert(mono) { 226 | let width = mono.width; 227 | let height = mono.height; 228 | 229 | let buf = new Uint8Array(~~((width + 7) / 8) * height); 230 | let idx = 0; // index into buf 231 | let byte = 0; // current byte of image data 232 | let bitx = 0; // bit index 233 | for (let i = mono.length-1; i >= 0; i--) { 234 | byte |= mono[i] << (7 - (bitx++ & 7)); 235 | 236 | if (bitx == width || !(bitx & 7)) { 237 | buf[idx++] = byte; 238 | byte = 0; 239 | if (bitx == width) { 240 | bitx = 0; 241 | } 242 | } 243 | } 244 | buf.width = width; 245 | buf.height = height; 246 | return buf; 247 | } 248 | 249 | // Rotate 90 degrees counter-clockwise 250 | function left(mono) { 251 | let width = mono.width; 252 | let height = mono.height; 253 | 254 | let buf = new Uint8Array(~~((height + 7) / 8) * width); 255 | let idx = 0; // index into buf 256 | let byte = 0; // current byte of image data 257 | for (let x = width - 1; x >= 0; x--) { 258 | let bitx = 0; // bit index 259 | for (let y = 0; y < height; y++) { 260 | byte |= mono[y * width + x] << (7 - (bitx++ & 7)); 261 | 262 | if (y == height-1 || !(bitx & 7)) { 263 | buf[idx++] = byte; 264 | byte = 0; 265 | } 266 | } 267 | } 268 | buf.width = height; 269 | buf.height = width; 270 | return buf; 271 | } 272 | 273 | 274 | // Rotate 90 degrees clockwise 275 | function right(mono) { 276 | let width = mono.width; 277 | let height = mono.height; 278 | 279 | let buf = new Uint8Array(~~((height + 7) / 8) * width); 280 | let idx = 0; // index into buf 281 | let byte = 0; // current byte of image data 282 | for (let x = 0; x < width; x++) { 283 | let bitx = 0; // bit index 284 | for (let y = height - 1; y >= 0; y--) { 285 | byte |= mono[y * width + x] << (7 - (bitx++ & 7)); 286 | 287 | if (y == 0 || !(bitx & 7)) { 288 | buf[idx++] = byte; 289 | byte = 0; 290 | } 291 | } 292 | } 293 | buf.width = height; 294 | buf.height = width; 295 | return buf; 296 | } 297 | 298 | // Convert the RGBA to monochrome, 1-bit-per-byte. Crops 299 | // empty space around the edges of the image if !notrim. 300 | function monochrome(rgba, width, height, black, notrim) { 301 | // Convert black from percent to 0..255 value 302 | let y = 0, x = 0; 303 | black = 255 * black / 100; 304 | 305 | let minx, maxx, miny, maxy; 306 | if (notrim) { 307 | minx = miny = 0; 308 | maxx = width-1; 309 | maxy = height-1; 310 | } else { 311 | // Run through the image and determine bounding box 312 | maxx = maxy = 0; 313 | minx = width; 314 | miny = height; 315 | let x = 0, y = 0; 316 | for (let i = 0, n = width * height * 4; i < n; i += 4) { 317 | // Alpha blend with white. 318 | let a = rgba[i+3] / 255; 319 | let r = rgba[i] * .3 * a + 255 * (1 - a); 320 | let g = rgba[i+1] * .59 * a + 255 * (1 - a); 321 | let b = rgba[i+2] * .11 * a + 255 * (1 - a); 322 | let gray = r + g + b; 323 | 324 | if (gray <= black) { 325 | if (minx > x) minx = x; 326 | if (miny > y) miny = y; 327 | if (maxx < x) maxx = x; 328 | if (maxy < y) maxy = y; 329 | } 330 | if (++x == width) { 331 | x = 0; 332 | y++; 333 | } 334 | } 335 | } 336 | 337 | // One more time through the data, this time we create the cropped image. 338 | let cx = maxx - minx + 1; 339 | let cy = maxy - miny + 1; 340 | let buf = new Uint8Array(cx * cy); 341 | let idx = 0; 342 | for (y = miny; y <= maxy; y++) { 343 | let i = (y * width + minx) * 4; 344 | for (x = minx; x <= maxx; x++) { 345 | // Alpha blend with white. 346 | let a = rgba[i+3] / 255; 347 | let r = rgba[i] * .3 * a + 255 * (1 - a); 348 | let g = rgba[i+1] * .59 * a + 255 * (1 - a); 349 | let b = rgba[i+2] * .11 * a + 255 * (1 - a); 350 | let gray = r + g + b; 351 | 352 | buf[idx++] = gray <= black ? 1 : 0; 353 | i += 4; 354 | } 355 | } 356 | 357 | // Return the monochrome image 358 | buf.width = cx; 359 | buf.height = cy; 360 | return buf; 361 | } 362 | 363 | // Cannot use btoa() with Uint8Arrays. Used only by the browser. 364 | function u8tob64(a) { 365 | let s = ''; 366 | let i = 0; 367 | for (let l = a.length & 0xfffffff0; i < l; i += 16) { 368 | s += String.fromCharCode(a[i],a[i+1],a[i+2],a[i+3],a[i+4],a[i+5], 369 | a[i+6],a[i+7],a[i+8],a[i+9],a[i+10], 370 | a[i+11],a[i+12],a[i+13],a[i+14],a[i+15]); 371 | } 372 | while (i < a.length) { 373 | s += String.fromCharCode(a[i++]); 374 | } 375 | return btoa(s); 376 | } 377 | 378 | // CRC16 used by zebra 379 | const crcTable = [ 380 | 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 381 | 0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b, 382 | 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210, 383 | 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 384 | 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 385 | 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401, 386 | 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 387 | 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 388 | 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 389 | 0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738, 390 | 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5, 391 | 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 392 | 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 393 | 0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 394 | 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc, 395 | 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 396 | 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 397 | 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd, 398 | 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6, 399 | 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 400 | 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 401 | 0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 402 | 0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080, 0x00a1, 403 | 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 404 | 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 405 | 0xe37f, 0xf35e, 0x02b1, 0x1290, 0x22f3, 0x32d2, 406 | 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, 407 | 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 408 | 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 409 | 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8, 410 | 0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 411 | 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 412 | 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 413 | 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827, 414 | 0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c, 415 | 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 416 | 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 417 | 0x2ab3, 0x3a92, 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 418 | 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, 419 | 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 420 | 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 421 | 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74, 422 | 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 423 | ]; 424 | 425 | function crc16(s) { 426 | // This is not an accumlating crc routine. Normally, the acc is intialized to 427 | // 0xffff then inverted on each call. We just start with 0. 428 | let crc = 0; 429 | let j, i, c; 430 | 431 | for (i = 0; i < s.length; i++) { 432 | c = s.charCodeAt(i); 433 | if (c > 255) { 434 | throw new RangeError(); 435 | } 436 | j = (c ^ (crc >> 8)) & 0xFF; 437 | crc = crcTable[j] ^ (crc << 8); 438 | } 439 | 440 | crc = (crc & 0xffff).toString(16).toLowerCase(); 441 | return '0000'.substr(crc.length) + crc; 442 | } 443 | 444 | return zlib ? { rgbaToZ64, rgbaToACS } : { rgbaToZ64, rgbaToACS, imageToZ64, imageToACS }; 445 | })); 446 | --------------------------------------------------------------------------------