├── .github └── workflows │ └── node.js.yml ├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── babel.config.json ├── build ├── index.js ├── lib │ ├── backend │ │ ├── exif-reader.js │ │ ├── jpg-encode.js │ │ └── jpg.js │ ├── decode-worker.js │ ├── decode.js │ ├── encode-worker.js │ ├── encode.js │ ├── exif-worker.js │ ├── exif.js │ ├── has-worker.js │ ├── info.js │ ├── magic-db.js │ ├── magic.js │ └── util │ │ ├── buffer.js │ │ └── color.js └── main.js ├── data ├── colors.css ├── inkjet-icon.png ├── inkjet-icon.xcf ├── inkjet-logo.png ├── inkjet-logo.xcf ├── inkjet-matrix.css ├── inkjet-matrix.html ├── inkjet-matrix.png └── reset.min.css ├── dist ├── inkjet.js └── inkjet.min.js ├── examples ├── decode │ ├── index.html │ └── index.js ├── encode │ ├── index.html │ └── index.js ├── exif │ ├── index.html │ └── index.js ├── info │ ├── index.html │ └── index.js └── magic │ ├── index.html │ └── index.js ├── gulpfile.babel.js ├── images ├── js_broken.jpg ├── js_logo-4-2-0.jpg ├── js_logo-4-2-2-horz.jpg ├── js_logo-4-2-2-vert.jpg ├── js_logo-4-4-4.jpg ├── js_logo-arithmetic-coding.jpg ├── js_logo-dct-float.jpg ├── js_logo-exif.jpg ├── js_logo-progressive.jpg ├── js_logo-sRGB-IEC61966-2-1.jpg └── js_logo.png ├── package-lock.json ├── package.json ├── src ├── index.js ├── lib │ ├── backend │ │ ├── LIBRARIES.md │ │ ├── exif-reader.js │ │ ├── jpg-encode.js │ │ └── jpg.js │ ├── decode-worker.js │ ├── decode.js │ ├── encode-worker.js │ ├── encode.js │ ├── exif-worker.js │ ├── exif.js │ ├── has-worker.js │ ├── info.js │ ├── magic-db.js │ ├── magic.js │ └── util │ │ ├── buffer.js │ │ └── color.js └── main.js ├── tasks ├── browserify-task.js ├── bundle-test-task.js └── config.js └── test ├── browser └── index.html ├── buffer-util.spec.js ├── build.spec.js ├── color-util.spec.js ├── decode.spec.js ├── encode.spec.js ├── exif.spec.js ├── info.spec.js ├── magic.spec.js ├── re-encode.spec.js └── util ├── constants.js ├── file-writer.js └── init.js /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | matrix: 16 | node-version: [14.x] 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v1 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | - run: npm ci 25 | - run: npm run build --if-present 26 | - run: npm run browser 27 | - run: npm test 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .nyc_output 3 | coverage 4 | node_modules 5 | test/out 6 | test/browser/inkjet-test-bundle.js 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .github 2 | .idea 3 | .nyc_output 4 | babel.config.json 5 | coverage 6 | data 7 | examples 8 | gulpfile.babel.js 9 | images 10 | node_modules 11 | src 12 | tasks 13 | test 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## 3.0.0 4 | - Should be a drop-in replacement for 2.x, no public API changes. 5 | - Updated encoding, decoding and exif reading libraries. 6 | - Add progressive JPEG decoding. 7 | - Add Uint8ClampedArray support. 8 | - Updated project dependencies. 9 | 10 | ## 2.1.2 11 | - Fixed import in node.js [#3](https://github.com/gchudnov/inkjet/issues/3) 12 | 13 | ## 2.1.1 14 | - Fixed entrypoint 15 | 16 | ## 2.1.0 17 | - Updated dependencies. 18 | - Migrated code to ES6 19 | 20 | ## 2.0.2 21 | - Updated dependencies. 22 | - Fixed missing /dist directory. 23 | 24 | ## 2.0.1 25 | - Updated dependencies. 26 | 27 | ## 2.0.0 28 | - Refined API: 29 | - `.info()` returns an Error for a broken image. 30 | - `.magic()` returns an Error for a broken image. 31 | - `.exif()` returns an empty object for an image without EXIF. 32 | - Updated dependencies. 33 | - Improved tests. 34 | - Updated examples. 35 | 36 | ## 1.2.0 37 | - Added method to return JPEG dimensions. 38 | 39 | ## 1.1.0 40 | - Added JPEG file-type detection. 41 | 42 | ## 1.0.0 43 | - Initial release. 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Grigorii Chudnov 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![inkjet-logo](data/inkjet-logo.png) 2 | 3 | ![Node.js CI](https://github.com/gchudnov/inkjet/workflows/Node.js%20CI/badge.svg) 4 | 5 | > JPEG-image decoding, encoding & EXIF reading library for a browser and node.js 6 | 7 | ![browser-matrix](data/inkjet-matrix.png) 8 | 9 | ## Installation 10 | 11 | installing with npm: 12 | 13 | ```bash 14 | npm install inkjet --save 15 | ``` 16 | 17 | ## In browser 18 | 19 | To use *inkjet* in a browser, use `inkjet.js` or `inkjet.min.js` in `/dist` directory, or build it manually: 20 | 21 | ```bash 22 | npm install 23 | npm run browser 24 | ``` 25 | 26 | ## Usage 27 | Decoding, encoding and EXIF extraction operations are offloaded to [WebWorkers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers) if the environment supports them. 28 | 29 | ### Decode JPEG 30 | 31 | ```javascript 32 | const inkjet = require('inkjet'); 33 | 34 | const filepath = './images/js_logo-4-2-0.jpg'; 35 | const buf = fs.readFileSync(filepath); 36 | 37 | inkjet.decode(buf, (err, decoded) => { 38 | // decoded: { width: number, height: number, data: Uint8Array } 39 | }); 40 | ``` 41 | 42 | ### Encode JPEG 43 | 44 | ```javascript 45 | const inkjet = require('inkjet'); 46 | 47 | const width = 320; 48 | const height = 180; 49 | const frameData = new Buffer(width * height * 4); 50 | const i = 0; 51 | 52 | while (i < frameData.length) { 53 | frameData[i++] = 0xFF; // R, red 54 | frameData[i++] = 0x00; // G, green 55 | frameData[i++] = 0x00; // B, blue 56 | frameData[i++] = 0xFF; // A, alpha - ignored in JPEGs 57 | } 58 | 59 | const buf = frameData; 60 | const options = { 61 | width: width, 62 | height: height, 63 | quality: 80 64 | }; 65 | 66 | inkjet.encode(buf, options, (err, encoded) => { 67 | // encoded: { width: number, height: number, data: Uint8Array } 68 | }); 69 | ``` 70 | 71 | ### Read EXIF 72 | 73 | ```javascript 74 | const inkjet = require('inkjet'); 75 | 76 | const filepath = './images/js_logo-exif.jpg'; 77 | const buf = fs.readFileSync(filepath); 78 | inkjet.exif(buf, (err, metadata) => { 79 | // metadata -- an object that maps EXIF tags to string values 80 | }); 81 | ``` 82 | 83 | ### Deduce image type 84 | 85 | ```javascript 86 | const inkjet = require('inkjet'); 87 | 88 | const filepath = './images/js_logo-4-2-0.jpg'; 89 | const buf = fs.readFileSync(filepath); 90 | inkjet.magic(buf, (err, data) => { 91 | // data -- an object that contains mime-type and extension 92 | }); 93 | ``` 94 | 95 | ### Image information 96 | 97 | ```javascript 98 | const inkjet = require('inkjet'); 99 | 100 | const filepath = './images/js_logo-4-2-0.jpg'; 101 | const buf = fs.readFileSync(filepath); 102 | inkjet.info(buf, (err, data) => { 103 | // data -- an object that contains width, height, mime type and extension data 104 | }); 105 | ``` 106 | 107 | ## API 108 | 109 | ### .decode(buf, [options], cb); 110 | 111 | Decodes a JPEG image. 112 | 113 | Arguments: 114 | * `buf` - source buffer, one of the following types: `Buffer|ArrayBuffer|Uint8Array|Uint8ClampedArray` 115 | * `[options]` - an optional object with settings to decode an image. Supported options: 116 | * `width` - override image width 117 | * `height` - override image height 118 | * `cb` - a callback that gets 2 arguments: 119 | * `err` - decoding `Error` 120 | * `decoded` - an object that describes the decoded image: `{ width: number, height: number, data: Uint8Array }` 121 | where data represents colors in RGBA format. 122 | 123 | ```javsscript 124 | inkjet.decode(buf, (err, decoded) => { 125 | // ... 126 | }); 127 | ``` 128 | 129 | ### .encode(buf, [options], cb); 130 | 131 | Encodes the provided buffer to a JPEG format. 132 | 133 | Arguments: 134 | * `buf` - source buffer, one of the following types: `Buffer|ArrayBuffer|Uint8Array|Uint8ClampedArray` 135 | * `[options]` - an optional object with settings to encode an image. Supported options: 136 | * `width` - width of the image in `buf` 137 | * `height`- height of the image in `buf` 138 | * `quality` - a numberic value [0-100], describes quality of encoding. 0 - low quality, 100 - high quality. 139 | * `cb` - a callback that gets 2 arguments: 140 | * `err` - encoding `Error` 141 | * `encoded` - an object that describes the encoded image: `{ width: number, height: number, data: Uint8Array }` 142 | 143 | ```javascript 144 | inkjet.encode(buf, (err, encoded) => { 145 | // ... 146 | }); 147 | ``` 148 | 149 | ### .exif(buf, [options], cb); 150 | 151 | Get EXIF metadata for the image. The metadata tags defined in the Exif standard cover date and time information, camera settings, descriptions, resolution and location information. 152 | 153 | Arguments: 154 | * `buf` - source buffer, one of the following types: `Buffer|ArrayBuffer|Uint8Array|Uint8ClampedArray` 155 | * `[options]` - an optional object with settings to encode an image. Supported options: 156 | * `hasMakerNote` - exclude *MakerNote* tag from metadata. Default value: `true`, *MakerNote* tag is excluded. 157 | * `cb` - a callback that gets 2 arguments: 158 | * `err` - exif extraction `Error` 159 | * `metadata` - metadata object, a set of tags and their values. 160 | 161 | ```javascript 162 | inkjet.exif(buf, (err, metadata) => { 163 | // ... 164 | }); 165 | ``` 166 | 167 | ### .magic(buf, cb); 168 | 169 | Deduce image type (mime type and extension) for the provided buffer. 170 | 171 | Arguments: 172 | * `buf` - source buffer, one of the following types: `Buffer|ArrayBuffer|Uint8Array|Uint8ClampedArray` 173 | * `cb` - a callback that gets 2 arguments: 174 | * `err` - `Error` object 175 | * `data` - data object { "mimeType": string, "extension": string } 176 | 177 | ```javascript 178 | inkjet.magic(buf, (err, data) => { 179 | // ... 180 | }); 181 | ``` 182 | 183 | ### .info(buf, cb); 184 | 185 | Get image information without reading and decoding an image. 186 | 187 | Arguments: 188 | * `buf` - source buffer, one of the following types: `Buffer|ArrayBuffer|Uint8Array|Uint8ClampedArray` 189 | * `cb` - a callback that gets 2 arguments: 190 | * `err` - `Error` object 191 | * `data` - data object { "type": string, "mimeType": string, "extension": string, "width": number, "height: number" } 192 | 193 | ```javascript 194 | inkjet.info(buf, (err, data) => { 195 | // data: { 196 | // type: "image" 197 | // mimeType: ... 198 | }); 199 | ``` 200 | 201 | ## Tests 202 | 203 | To run the tests for *inkjet* in Node.js: 204 | 205 | ```bash 206 | npm test 207 | ``` 208 | 209 | To run tests in a browser: 210 | 211 | ```bash 212 | npm run bundle:test 213 | ``` 214 | 215 | a bundle file `inkjet-test-bundle.js` with all tests will be generated in `inkjet/test/browser` directory. 216 | 217 | Open `inkjet/test/browser/index.html` in the target browser. Tests will run automatically. 218 | 219 | ## Contact 220 | 221 | [Grigorii Chudnov] (mailto:g.chudnov@gmail.com) 222 | 223 | 224 | ## License 225 | 226 | Distributed under the [The MIT License (MIT)](LICENSE). 227 | -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /build/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _main = _interopRequireDefault(require("./main")); 4 | 5 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 6 | 7 | module.exports = _main["default"]; -------------------------------------------------------------------------------- /build/lib/backend/jpg-encode.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /* 4 | Copyright (c) 2008, Adobe Systems Incorporated 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are 9 | met: 10 | 11 | * Redistributions of source code must retain the above copyright notice, 12 | this list of conditions and the following disclaimer. 13 | 14 | * Redistributions in binary form must reproduce the above copyright 15 | notice, this list of conditions and the following disclaimer in the 16 | documentation and/or other materials provided with the distribution. 17 | 18 | * Neither the name of Adobe Systems Incorporated nor the names of its 19 | contributors may be used to endorse or promote products derived from 20 | this software without specific prior written permission. 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 23 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 24 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 25 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 26 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 27 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 28 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 29 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 30 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 31 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 32 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | */ 34 | 35 | /* 36 | JPEG encoder ported to JavaScript and optimized by Andreas Ritter, www.bytestrom.eu, 11/2009 37 | 38 | Basic GUI blocking jpeg encoder 39 | */ 40 | function JPEGEncoder(quality) { 41 | var ffloor = Math.floor; 42 | var YTable = new Array(64); 43 | var UVTable = new Array(64); 44 | var fdtbl_Y = new Array(64); 45 | var fdtbl_UV = new Array(64); 46 | var YDC_HT; 47 | var UVDC_HT; 48 | var YAC_HT; 49 | var UVAC_HT; 50 | var bitcode = new Array(65535); 51 | var category = new Array(65535); 52 | var outputfDCTQuant = new Array(64); 53 | var DU = new Array(64); 54 | var byteout = []; 55 | var bytenew = 0; 56 | var bytepos = 7; 57 | var YDU = new Array(64); 58 | var UDU = new Array(64); 59 | var VDU = new Array(64); 60 | var clt = new Array(256); 61 | var RGB_YUV_TABLE = new Array(2048); 62 | var currentQuality; 63 | var ZigZag = [0, 1, 5, 6, 14, 15, 27, 28, 2, 4, 7, 13, 16, 26, 29, 42, 3, 8, 12, 17, 25, 30, 41, 43, 9, 11, 18, 24, 31, 40, 44, 53, 10, 19, 23, 32, 39, 45, 52, 54, 20, 22, 33, 38, 46, 51, 55, 60, 21, 34, 37, 47, 50, 56, 59, 61, 35, 36, 48, 49, 57, 58, 62, 63]; 64 | var std_dc_luminance_nrcodes = [0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0]; 65 | var std_dc_luminance_values = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]; 66 | var std_ac_luminance_nrcodes = [0, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 0x7d]; 67 | var std_ac_luminance_values = [0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa]; 68 | var std_dc_chrominance_nrcodes = [0, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0]; 69 | var std_dc_chrominance_values = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]; 70 | var std_ac_chrominance_nrcodes = [0, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 0x77]; 71 | var std_ac_chrominance_values = [0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa]; 72 | 73 | function initQuantTables(sf) { 74 | var YQT = [16, 11, 10, 16, 24, 40, 51, 61, 12, 12, 14, 19, 26, 58, 60, 55, 14, 13, 16, 24, 40, 57, 69, 56, 14, 17, 22, 29, 51, 87, 80, 62, 18, 22, 37, 56, 68, 109, 103, 77, 24, 35, 55, 64, 81, 104, 113, 92, 49, 64, 78, 87, 103, 121, 120, 101, 72, 92, 95, 98, 112, 100, 103, 99]; 75 | 76 | for (var i = 0; i < 64; i++) { 77 | var t = ffloor((YQT[i] * sf + 50) / 100); 78 | t = Math.min(Math.max(t, 1), 255); 79 | YTable[ZigZag[i]] = t; 80 | } 81 | 82 | var UVQT = [17, 18, 24, 47, 99, 99, 99, 99, 18, 21, 26, 66, 99, 99, 99, 99, 24, 26, 56, 99, 99, 99, 99, 99, 47, 66, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99]; 83 | 84 | for (var j = 0; j < 64; j++) { 85 | var u = ffloor((UVQT[j] * sf + 50) / 100); 86 | u = Math.min(Math.max(u, 1), 255); 87 | UVTable[ZigZag[j]] = u; 88 | } 89 | 90 | var aasf = [1.0, 1.387039845, 1.306562965, 1.175875602, 1.0, 0.785694958, 0.541196100, 0.275899379]; 91 | var k = 0; 92 | 93 | for (var row = 0; row < 8; row++) { 94 | for (var col = 0; col < 8; col++) { 95 | fdtbl_Y[k] = 1.0 / (YTable[ZigZag[k]] * aasf[row] * aasf[col] * 8.0); 96 | fdtbl_UV[k] = 1.0 / (UVTable[ZigZag[k]] * aasf[row] * aasf[col] * 8.0); 97 | k++; 98 | } 99 | } 100 | } 101 | 102 | function computeHuffmanTbl(nrcodes, std_table) { 103 | var codevalue = 0; 104 | var pos_in_table = 0; 105 | var HT = new Array(); 106 | 107 | for (var k = 1; k <= 16; k++) { 108 | for (var j = 1; j <= nrcodes[k]; j++) { 109 | HT[std_table[pos_in_table]] = []; 110 | HT[std_table[pos_in_table]][0] = codevalue; 111 | HT[std_table[pos_in_table]][1] = k; 112 | pos_in_table++; 113 | codevalue++; 114 | } 115 | 116 | codevalue *= 2; 117 | } 118 | 119 | return HT; 120 | } 121 | 122 | function initHuffmanTbl() { 123 | YDC_HT = computeHuffmanTbl(std_dc_luminance_nrcodes, std_dc_luminance_values); 124 | UVDC_HT = computeHuffmanTbl(std_dc_chrominance_nrcodes, std_dc_chrominance_values); 125 | YAC_HT = computeHuffmanTbl(std_ac_luminance_nrcodes, std_ac_luminance_values); 126 | UVAC_HT = computeHuffmanTbl(std_ac_chrominance_nrcodes, std_ac_chrominance_values); 127 | } 128 | 129 | function initCategoryNumber() { 130 | var nrlower = 1; 131 | var nrupper = 2; 132 | 133 | for (var cat = 1; cat <= 15; cat++) { 134 | //Positive numbers 135 | for (var nr = nrlower; nr < nrupper; nr++) { 136 | category[32767 + nr] = cat; 137 | bitcode[32767 + nr] = []; 138 | bitcode[32767 + nr][1] = cat; 139 | bitcode[32767 + nr][0] = nr; 140 | } //Negative numbers 141 | 142 | 143 | for (var nrneg = -(nrupper - 1); nrneg <= -nrlower; nrneg++) { 144 | category[32767 + nrneg] = cat; 145 | bitcode[32767 + nrneg] = []; 146 | bitcode[32767 + nrneg][1] = cat; 147 | bitcode[32767 + nrneg][0] = nrupper - 1 + nrneg; 148 | } 149 | 150 | nrlower <<= 1; 151 | nrupper <<= 1; 152 | } 153 | } 154 | 155 | function initRGBYUVTable() { 156 | for (var i = 0; i < 256; i++) { 157 | RGB_YUV_TABLE[i] = 19595 * i; 158 | RGB_YUV_TABLE[i + 256 >> 0] = 38470 * i; 159 | RGB_YUV_TABLE[i + 512 >> 0] = 7471 * i + 0x8000; 160 | RGB_YUV_TABLE[i + 768 >> 0] = -11059 * i; 161 | RGB_YUV_TABLE[i + 1024 >> 0] = -21709 * i; 162 | RGB_YUV_TABLE[i + 1280 >> 0] = 32768 * i + 0x807FFF; 163 | RGB_YUV_TABLE[i + 1536 >> 0] = -27439 * i; 164 | RGB_YUV_TABLE[i + 1792 >> 0] = -5329 * i; 165 | } 166 | } // IO functions 167 | 168 | 169 | function writeBits(bs) { 170 | var value = bs[0]; 171 | var posval = bs[1] - 1; 172 | 173 | while (posval >= 0) { 174 | if (value & 1 << posval) { 175 | bytenew |= 1 << bytepos; 176 | } 177 | 178 | posval--; 179 | bytepos--; 180 | 181 | if (bytepos < 0) { 182 | if (bytenew == 0xFF) { 183 | writeByte(0xFF); 184 | writeByte(0); 185 | } else { 186 | writeByte(bytenew); 187 | } 188 | 189 | bytepos = 7; 190 | bytenew = 0; 191 | } 192 | } 193 | } 194 | 195 | function writeByte(value) { 196 | //byteout.push(clt[value]); // write char directly instead of converting later 197 | byteout.push(value); 198 | } 199 | 200 | function writeWord(value) { 201 | writeByte(value >> 8 & 0xFF); 202 | writeByte(value & 0xFF); 203 | } // DCT & quantization core 204 | 205 | 206 | function fDCTQuant(data, fdtbl) { 207 | var d0, d1, d2, d3, d4, d5, d6, d7; 208 | /* Pass 1: process rows. */ 209 | 210 | var dataOff = 0; 211 | var i; 212 | var I8 = 8; 213 | var I64 = 64; 214 | 215 | for (i = 0; i < I8; ++i) { 216 | d0 = data[dataOff]; 217 | d1 = data[dataOff + 1]; 218 | d2 = data[dataOff + 2]; 219 | d3 = data[dataOff + 3]; 220 | d4 = data[dataOff + 4]; 221 | d5 = data[dataOff + 5]; 222 | d6 = data[dataOff + 6]; 223 | d7 = data[dataOff + 7]; 224 | var tmp0 = d0 + d7; 225 | var tmp7 = d0 - d7; 226 | var tmp1 = d1 + d6; 227 | var tmp6 = d1 - d6; 228 | var tmp2 = d2 + d5; 229 | var tmp5 = d2 - d5; 230 | var tmp3 = d3 + d4; 231 | var tmp4 = d3 - d4; 232 | /* Even part */ 233 | 234 | var tmp10 = tmp0 + tmp3; 235 | /* phase 2 */ 236 | 237 | var tmp13 = tmp0 - tmp3; 238 | var tmp11 = tmp1 + tmp2; 239 | var tmp12 = tmp1 - tmp2; 240 | data[dataOff] = tmp10 + tmp11; 241 | /* phase 3 */ 242 | 243 | data[dataOff + 4] = tmp10 - tmp11; 244 | var z1 = (tmp12 + tmp13) * 0.707106781; 245 | /* c4 */ 246 | 247 | data[dataOff + 2] = tmp13 + z1; 248 | /* phase 5 */ 249 | 250 | data[dataOff + 6] = tmp13 - z1; 251 | /* Odd part */ 252 | 253 | tmp10 = tmp4 + tmp5; 254 | /* phase 2 */ 255 | 256 | tmp11 = tmp5 + tmp6; 257 | tmp12 = tmp6 + tmp7; 258 | /* The rotator is modified from fig 4-8 to avoid extra negations. */ 259 | 260 | var z5 = (tmp10 - tmp12) * 0.382683433; 261 | /* c6 */ 262 | 263 | var z2 = 0.541196100 * tmp10 + z5; 264 | /* c2-c6 */ 265 | 266 | var z4 = 1.306562965 * tmp12 + z5; 267 | /* c2+c6 */ 268 | 269 | var z3 = tmp11 * 0.707106781; 270 | /* c4 */ 271 | 272 | var z11 = tmp7 + z3; 273 | /* phase 5 */ 274 | 275 | var z13 = tmp7 - z3; 276 | data[dataOff + 5] = z13 + z2; 277 | /* phase 6 */ 278 | 279 | data[dataOff + 3] = z13 - z2; 280 | data[dataOff + 1] = z11 + z4; 281 | data[dataOff + 7] = z11 - z4; 282 | dataOff += 8; 283 | /* advance pointer to next row */ 284 | } 285 | /* Pass 2: process columns. */ 286 | 287 | 288 | dataOff = 0; 289 | 290 | for (i = 0; i < I8; ++i) { 291 | d0 = data[dataOff]; 292 | d1 = data[dataOff + 8]; 293 | d2 = data[dataOff + 16]; 294 | d3 = data[dataOff + 24]; 295 | d4 = data[dataOff + 32]; 296 | d5 = data[dataOff + 40]; 297 | d6 = data[dataOff + 48]; 298 | d7 = data[dataOff + 56]; 299 | var tmp0p2 = d0 + d7; 300 | var tmp7p2 = d0 - d7; 301 | var tmp1p2 = d1 + d6; 302 | var tmp6p2 = d1 - d6; 303 | var tmp2p2 = d2 + d5; 304 | var tmp5p2 = d2 - d5; 305 | var tmp3p2 = d3 + d4; 306 | var tmp4p2 = d3 - d4; 307 | /* Even part */ 308 | 309 | var tmp10p2 = tmp0p2 + tmp3p2; 310 | /* phase 2 */ 311 | 312 | var tmp13p2 = tmp0p2 - tmp3p2; 313 | var tmp11p2 = tmp1p2 + tmp2p2; 314 | var tmp12p2 = tmp1p2 - tmp2p2; 315 | data[dataOff] = tmp10p2 + tmp11p2; 316 | /* phase 3 */ 317 | 318 | data[dataOff + 32] = tmp10p2 - tmp11p2; 319 | var z1p2 = (tmp12p2 + tmp13p2) * 0.707106781; 320 | /* c4 */ 321 | 322 | data[dataOff + 16] = tmp13p2 + z1p2; 323 | /* phase 5 */ 324 | 325 | data[dataOff + 48] = tmp13p2 - z1p2; 326 | /* Odd part */ 327 | 328 | tmp10p2 = tmp4p2 + tmp5p2; 329 | /* phase 2 */ 330 | 331 | tmp11p2 = tmp5p2 + tmp6p2; 332 | tmp12p2 = tmp6p2 + tmp7p2; 333 | /* The rotator is modified from fig 4-8 to avoid extra negations. */ 334 | 335 | var z5p2 = (tmp10p2 - tmp12p2) * 0.382683433; 336 | /* c6 */ 337 | 338 | var z2p2 = 0.541196100 * tmp10p2 + z5p2; 339 | /* c2-c6 */ 340 | 341 | var z4p2 = 1.306562965 * tmp12p2 + z5p2; 342 | /* c2+c6 */ 343 | 344 | var z3p2 = tmp11p2 * 0.707106781; 345 | /* c4 */ 346 | 347 | var z11p2 = tmp7p2 + z3p2; 348 | /* phase 5 */ 349 | 350 | var z13p2 = tmp7p2 - z3p2; 351 | data[dataOff + 40] = z13p2 + z2p2; 352 | /* phase 6 */ 353 | 354 | data[dataOff + 24] = z13p2 - z2p2; 355 | data[dataOff + 8] = z11p2 + z4p2; 356 | data[dataOff + 56] = z11p2 - z4p2; 357 | dataOff++; 358 | /* advance pointer to next column */ 359 | } // Quantize/descale the coefficients 360 | 361 | 362 | var fDCTQuant; 363 | 364 | for (i = 0; i < I64; ++i) { 365 | // Apply the quantization and scaling factor & Round to nearest integer 366 | fDCTQuant = data[i] * fdtbl[i]; 367 | outputfDCTQuant[i] = fDCTQuant > 0.0 ? fDCTQuant + 0.5 | 0 : fDCTQuant - 0.5 | 0; //outputfDCTQuant[i] = fround(fDCTQuant); 368 | } 369 | 370 | return outputfDCTQuant; 371 | } 372 | 373 | function writeAPP0() { 374 | writeWord(0xFFE0); // marker 375 | 376 | writeWord(16); // length 377 | 378 | writeByte(0x4A); // J 379 | 380 | writeByte(0x46); // F 381 | 382 | writeByte(0x49); // I 383 | 384 | writeByte(0x46); // F 385 | 386 | writeByte(0); // = "JFIF",'\0' 387 | 388 | writeByte(1); // versionhi 389 | 390 | writeByte(1); // versionlo 391 | 392 | writeByte(0); // xyunits 393 | 394 | writeWord(1); // xdensity 395 | 396 | writeWord(1); // ydensity 397 | 398 | writeByte(0); // thumbnwidth 399 | 400 | writeByte(0); // thumbnheight 401 | } 402 | 403 | function writeSOF0(width, height) { 404 | writeWord(0xFFC0); // marker 405 | 406 | writeWord(17); // length, truecolor YUV JPG 407 | 408 | writeByte(8); // precision 409 | 410 | writeWord(height); 411 | writeWord(width); 412 | writeByte(3); // nrofcomponents 413 | 414 | writeByte(1); // IdY 415 | 416 | writeByte(0x11); // HVY 417 | 418 | writeByte(0); // QTY 419 | 420 | writeByte(2); // IdU 421 | 422 | writeByte(0x11); // HVU 423 | 424 | writeByte(1); // QTU 425 | 426 | writeByte(3); // IdV 427 | 428 | writeByte(0x11); // HVV 429 | 430 | writeByte(1); // QTV 431 | } 432 | 433 | function writeDQT() { 434 | writeWord(0xFFDB); // marker 435 | 436 | writeWord(132); // length 437 | 438 | writeByte(0); 439 | 440 | for (var i = 0; i < 64; i++) { 441 | writeByte(YTable[i]); 442 | } 443 | 444 | writeByte(1); 445 | 446 | for (var j = 0; j < 64; j++) { 447 | writeByte(UVTable[j]); 448 | } 449 | } 450 | 451 | function writeDHT() { 452 | writeWord(0xFFC4); // marker 453 | 454 | writeWord(0x01A2); // length 455 | 456 | writeByte(0); // HTYDCinfo 457 | 458 | for (var i = 0; i < 16; i++) { 459 | writeByte(std_dc_luminance_nrcodes[i + 1]); 460 | } 461 | 462 | for (var j = 0; j <= 11; j++) { 463 | writeByte(std_dc_luminance_values[j]); 464 | } 465 | 466 | writeByte(0x10); // HTYACinfo 467 | 468 | for (var k = 0; k < 16; k++) { 469 | writeByte(std_ac_luminance_nrcodes[k + 1]); 470 | } 471 | 472 | for (var l = 0; l <= 161; l++) { 473 | writeByte(std_ac_luminance_values[l]); 474 | } 475 | 476 | writeByte(1); // HTUDCinfo 477 | 478 | for (var m = 0; m < 16; m++) { 479 | writeByte(std_dc_chrominance_nrcodes[m + 1]); 480 | } 481 | 482 | for (var n = 0; n <= 11; n++) { 483 | writeByte(std_dc_chrominance_values[n]); 484 | } 485 | 486 | writeByte(0x11); // HTUACinfo 487 | 488 | for (var o = 0; o < 16; o++) { 489 | writeByte(std_ac_chrominance_nrcodes[o + 1]); 490 | } 491 | 492 | for (var p = 0; p <= 161; p++) { 493 | writeByte(std_ac_chrominance_values[p]); 494 | } 495 | } 496 | 497 | function writeSOS() { 498 | writeWord(0xFFDA); // marker 499 | 500 | writeWord(12); // length 501 | 502 | writeByte(3); // nrofcomponents 503 | 504 | writeByte(1); // IdY 505 | 506 | writeByte(0); // HTY 507 | 508 | writeByte(2); // IdU 509 | 510 | writeByte(0x11); // HTU 511 | 512 | writeByte(3); // IdV 513 | 514 | writeByte(0x11); // HTV 515 | 516 | writeByte(0); // Ss 517 | 518 | writeByte(0x3f); // Se 519 | 520 | writeByte(0); // Bf 521 | } 522 | 523 | function processDU(CDU, fdtbl, DC, HTDC, HTAC) { 524 | var EOB = HTAC[0x00]; 525 | var M16zeroes = HTAC[0xF0]; 526 | var pos; 527 | var I16 = 16; 528 | var I63 = 63; 529 | var I64 = 64; 530 | var DU_DCT = fDCTQuant(CDU, fdtbl); //ZigZag reorder 531 | 532 | for (var j = 0; j < I64; ++j) { 533 | DU[ZigZag[j]] = DU_DCT[j]; 534 | } 535 | 536 | var Diff = DU[0] - DC; 537 | DC = DU[0]; //Encode DC 538 | 539 | if (Diff == 0) { 540 | writeBits(HTDC[0]); // Diff might be 0 541 | } else { 542 | pos = 32767 + Diff; 543 | writeBits(HTDC[category[pos]]); 544 | writeBits(bitcode[pos]); 545 | } //Encode ACs 546 | 547 | 548 | var end0pos = 63; // was const... which is crazy 549 | 550 | while (end0pos > 0 && DU[end0pos] == 0) { 551 | end0pos--; 552 | } //end0pos = first element in reverse order !=0 553 | 554 | 555 | if (end0pos == 0) { 556 | writeBits(EOB); 557 | return DC; 558 | } 559 | 560 | var i = 1; 561 | var lng; 562 | 563 | while (i <= end0pos) { 564 | var startpos = i; 565 | 566 | while (DU[i] == 0 && i <= end0pos) { 567 | ++i; 568 | } 569 | 570 | var nrzeroes = i - startpos; 571 | 572 | if (nrzeroes >= I16) { 573 | lng = nrzeroes >> 4; 574 | 575 | for (var nrmarker = 1; nrmarker <= lng; ++nrmarker) { 576 | writeBits(M16zeroes); 577 | } 578 | 579 | nrzeroes = nrzeroes & 0xF; 580 | } 581 | 582 | pos = 32767 + DU[i]; 583 | writeBits(HTAC[(nrzeroes << 4) + category[pos]]); 584 | writeBits(bitcode[pos]); 585 | i++; 586 | } 587 | 588 | if (end0pos != I63) { 589 | writeBits(EOB); 590 | } 591 | 592 | return DC; 593 | } 594 | 595 | function initCharLookupTable() { 596 | var sfcc = String.fromCharCode; 597 | 598 | for (var i = 0; i < 256; i++) { 599 | ///// ACHTUNG // 255 600 | clt[i] = sfcc(i); 601 | } 602 | } 603 | 604 | this.encode = function (image, quality) // image data object 605 | { 606 | if (quality) setQuality(quality); // Initialize bit writer 607 | 608 | byteout = new Array(); 609 | bytenew = 0; 610 | bytepos = 7; // Add JPEG headers 611 | 612 | writeWord(0xFFD8); // SOI 613 | 614 | writeAPP0(); 615 | writeDQT(); 616 | writeSOF0(image.width, image.height); 617 | writeDHT(); 618 | writeSOS(); // Encode 8x8 macroblocks 619 | 620 | var DCY = 0; 621 | var DCU = 0; 622 | var DCV = 0; 623 | bytenew = 0; 624 | bytepos = 7; 625 | this.encode.displayName = "_encode_"; 626 | var imageData = image.data; 627 | var width = image.width; 628 | var height = image.height; 629 | var quadWidth = width * 4; 630 | var x, 631 | y = 0; 632 | var r, g, b; 633 | var start, p, col, row, pos; 634 | 635 | while (y < height) { 636 | x = 0; 637 | 638 | while (x < quadWidth) { 639 | start = quadWidth * y + x; 640 | col = -1; 641 | row = 0; 642 | 643 | for (pos = 0; pos < 64; pos++) { 644 | row = pos >> 3; // /8 645 | 646 | col = (pos & 7) * 4; // %8 647 | 648 | p = start + row * quadWidth + col; 649 | 650 | if (y + row >= height) { 651 | // padding bottom 652 | p -= quadWidth * (y + 1 + row - height); 653 | } 654 | 655 | if (x + col >= quadWidth) { 656 | // padding right 657 | p -= x + col - quadWidth + 4; 658 | } 659 | 660 | r = imageData[p++]; 661 | g = imageData[p++]; 662 | b = imageData[p++]; 663 | /* // calculate YUV values dynamically 664 | YDU[pos]=((( 0.29900)*r+( 0.58700)*g+( 0.11400)*b))-128; //-0x80 665 | UDU[pos]=(((-0.16874)*r+(-0.33126)*g+( 0.50000)*b)); 666 | VDU[pos]=((( 0.50000)*r+(-0.41869)*g+(-0.08131)*b)); 667 | */ 668 | // use lookup table (slightly faster) 669 | 670 | YDU[pos] = (RGB_YUV_TABLE[r] + RGB_YUV_TABLE[g + 256 >> 0] + RGB_YUV_TABLE[b + 512 >> 0] >> 16) - 128; 671 | UDU[pos] = (RGB_YUV_TABLE[r + 768 >> 0] + RGB_YUV_TABLE[g + 1024 >> 0] + RGB_YUV_TABLE[b + 1280 >> 0] >> 16) - 128; 672 | VDU[pos] = (RGB_YUV_TABLE[r + 1280 >> 0] + RGB_YUV_TABLE[g + 1536 >> 0] + RGB_YUV_TABLE[b + 1792 >> 0] >> 16) - 128; 673 | } 674 | 675 | DCY = processDU(YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT); 676 | DCU = processDU(UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); 677 | DCV = processDU(VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); 678 | x += 32; 679 | } 680 | 681 | y += 8; 682 | } //////////////////////////////////////////////////////////////// 683 | // Do the bit alignment of the EOI marker 684 | 685 | 686 | if (bytepos >= 0) { 687 | var fillbits = []; 688 | fillbits[1] = bytepos + 1; 689 | fillbits[0] = (1 << bytepos + 1) - 1; 690 | writeBits(fillbits); 691 | } 692 | 693 | writeWord(0xFFD9); //EOI 694 | 695 | return new Uint8Array(byteout); 696 | }; 697 | 698 | function setQuality(quality) { 699 | quality = Math.min(Math.max(quality, 1), 100); 700 | if (currentQuality == quality) return; // don't recalc if unchanged 701 | 702 | var sf = quality < 50 ? Math.floor(5000 / quality) : Math.floor(200 - quality * 2); 703 | initQuantTables(sf); 704 | currentQuality = quality; //console.log('Quality set to: '+quality +'%'); 705 | } 706 | 707 | function init() { 708 | quality = quality || 50; // Create tables 709 | 710 | initCharLookupTable(); 711 | initHuffmanTbl(); 712 | initCategoryNumber(); 713 | initRGBYUVTable(); 714 | setQuality(quality); 715 | } 716 | 717 | init(); 718 | } // eslint-disable-next-line no-empty 719 | 720 | 721 | exports.JPEGEncoder = JPEGEncoder; -------------------------------------------------------------------------------- /build/lib/decode-worker.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports["default"] = _default; 7 | 8 | function _default(self) { 9 | var decode = require('./decode'); 10 | 11 | self.onmessage = function (_ref) { 12 | var msg = _ref.data; 13 | decode(msg.buf, msg.options, function (err, result) { 14 | if (err) { 15 | var errValue = err instanceof Error ? err.message : err; // Error is not clonable 16 | 17 | self.postMessage({ 18 | err: errValue 19 | }); 20 | } else { 21 | self.postMessage({ 22 | result: result 23 | }); 24 | } 25 | }); 26 | }; 27 | } 28 | 29 | ; -------------------------------------------------------------------------------- /build/lib/decode.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports["default"] = decode; 7 | 8 | var _jpg = require("./backend/jpg"); 9 | 10 | var _color = require("./util/color"); 11 | 12 | /** 13 | * Decode the JPEG data 14 | * 15 | * @param buf ArrayLike data structure 16 | * @param options Object { width: number, height: number } 17 | * @param cb Callback to invoke on completion 18 | * 19 | * @callback { width: number, height: number, data: Uint8Array } 20 | */ 21 | function decode(buf, options, cb) { 22 | // returns: Uint8ClampedArray(width * height * numComponents) 23 | function getData(j, width, height) { 24 | var opts = { 25 | width: width, 26 | height: height, 27 | forceRGB: true, 28 | isSourcePDF: false 29 | }; 30 | return j.getData(opts); 31 | } 32 | 33 | try { 34 | var j = new _jpg.JpegImage(); 35 | j.parse(buf); 36 | var width = options.width || j.width; 37 | var height = options.height || j.height; 38 | var rgbData = getData(j, width, height); // NOTE: each color is RGB without alpha-channel 39 | 40 | var rgbaData = (0, _color.arrayLikeRgbToRgba)(rgbData); // NOTE: convert to RGBA 41 | 42 | var result = { 43 | width: width, 44 | height: height, 45 | data: rgbaData 46 | }; 47 | cb(null, result); 48 | } catch (err) { 49 | cb(err); 50 | } 51 | } -------------------------------------------------------------------------------- /build/lib/encode-worker.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports["default"] = _default; 7 | 8 | function _default(self) { 9 | var encode = require('./encode'); 10 | 11 | self.onmessage = function (_ref) { 12 | var msg = _ref.data; 13 | encode(msg.buf, msg.options, function (err, result) { 14 | if (err) { 15 | var errValue = err instanceof Error ? err.message : err; // Error is not clonable 16 | 17 | self.postMessage({ 18 | err: errValue 19 | }); 20 | } else { 21 | self.postMessage({ 22 | result: result 23 | }); 24 | } 25 | }); 26 | }; 27 | } 28 | 29 | ; -------------------------------------------------------------------------------- /build/lib/encode.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports["default"] = encode; 7 | 8 | var _jpgEncode = require("./backend/jpg-encode"); 9 | 10 | /** 11 | * Encode the data to JPEG format 12 | * 13 | * @param buf Buffer|Uint8Array 14 | * @param options Object { width: number, height: number, quality: number } 15 | * @param cb Callback to invoke on completion 16 | * 17 | * @callback { width: number, height: number, data: Uint8Array } 18 | */ 19 | function encode(buf, options, cb) { 20 | try { 21 | var encoder = new _jpgEncode.JPEGEncoder(options.quality); 22 | var opts = { 23 | data: buf, 24 | width: options.width, 25 | height: options.height 26 | }; 27 | var encoded = encoder.encode(opts); 28 | var result = { 29 | data: encoded, 30 | width: options.width, 31 | height: options.height 32 | }; 33 | cb(null, result); 34 | } catch (err) { 35 | cb(err); 36 | } 37 | } -------------------------------------------------------------------------------- /build/lib/exif-worker.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports["default"] = _default; 7 | 8 | function _default(self) { 9 | var exif = require('./exif'); 10 | 11 | self.onmessage = function (_ref) { 12 | var msg = _ref.data; 13 | exif(msg.buf, {}, function (err, result) { 14 | if (err) { 15 | var errValue = err instanceof Error ? err.message : err; // Error is not clonable 16 | 17 | self.postMessage({ 18 | err: errValue 19 | }); 20 | } else { 21 | self.postMessage({ 22 | result: result 23 | }); 24 | } 25 | }); 26 | }; 27 | } 28 | 29 | ; -------------------------------------------------------------------------------- /build/lib/exif.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports["default"] = exif; 7 | 8 | var _exifReader = _interopRequireDefault(require("./backend/exif-reader")); 9 | 10 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 11 | 12 | /** 13 | * Read EXIF data from the provided buffer 14 | * 15 | * @param buf ArrayBuffer 16 | * @param options Object { hasMakerNote: true|false } 17 | * @param cb Callback to invoke on completion 18 | * 19 | * @callback Object { name: value, ... } 20 | */ 21 | function exif(buf, options, cb) { 22 | try { 23 | var tags = _exifReader["default"].load(buf); // The MakerNote tag can be really large. Remove it to lower memory usage. 24 | 25 | 26 | delete tags['MakerNote']; 27 | cb(null, tags); 28 | } catch (err) { 29 | if (err.message === 'No Exif data') { 30 | cb(null, {}); 31 | } else { 32 | cb(err); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /build/lib/has-worker.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.hasWorker = void 0; 7 | var hasWorker = typeof window !== 'undefined' && 'Worker' in window; 8 | exports.hasWorker = hasWorker; 9 | 10 | if (hasWorker) { 11 | try { 12 | var w = require('webworkify')(function () {}); 13 | 14 | w.terminate(); 15 | } catch (e) { 16 | exports.hasWorker = hasWorker = false; 17 | } 18 | } -------------------------------------------------------------------------------- /build/lib/info.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports["default"] = info; 7 | 8 | var _imageinfo = _interopRequireDefault(require("imageinfo")); 9 | 10 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 11 | 12 | /** 13 | * Get image information 14 | * @param {Buffer} buf Image or image part that contains image parameters 15 | * @param {function} cb Callback to invoke on completion 16 | */ 17 | function info(buf, cb) { 18 | setTimeout(function () { 19 | var info = (0, _imageinfo["default"])(buf); 20 | 21 | if (!info) { 22 | cb(new Error('Cannot get image info')); 23 | } else { 24 | cb(null, { 25 | type: info.type, 26 | mimeType: info.mimeType, 27 | extension: info.format.toLowerCase(), 28 | width: info.width, 29 | height: info.height 30 | }); 31 | } 32 | }, 0); 33 | } -------------------------------------------------------------------------------- /build/lib/magic-db.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports["default"] = void 0; 7 | var _default = { 8 | "474946383961": { 9 | "mimeType": "image/gif", 10 | "extension": "gif" 11 | }, 12 | "474946383761": { 13 | "mimeType": "image/gif", 14 | "extension": "gif" 15 | }, 16 | "89504e470d0a1a0a": { 17 | "mimeType": "image/png", 18 | "extension": "png" 19 | }, 20 | "ffd8ff": { 21 | "mimeType": "image/jpeg", 22 | "extension": "jpg" 23 | }, 24 | "57454250": { 25 | "mimeType": "image/webp", 26 | "extension": "webp" 27 | }, 28 | "49492a00": { 29 | "mimeType": "image/tiff", 30 | "extension": "tiff" 31 | }, 32 | "4d4d002a": { 33 | "mimeType": "image/tiff", 34 | "extension": "tiff" 35 | }, 36 | "424d": { 37 | "mimeType": "image/bmp", 38 | "extension": "bmp" 39 | }, 40 | "000000146674797069736f6d": { 41 | "mimeType": "video/mp4", 42 | "extension": "mp4" 43 | }, 44 | "000000186674797033677035": { 45 | "mimeType": "video/mp4", 46 | "extension": "mp4" 47 | }, 48 | "000000146674797071742020": { 49 | "mimeType": "video/quicktime", 50 | "extension": "mov" 51 | }, 52 | "1a45dfa3": { 53 | "mimeType": "video/webm", 54 | "extension": "webm" 55 | }, 56 | "25504446": { 57 | "mimeType": "application/pdf", 58 | "extension": "pdf" 59 | } 60 | }; 61 | exports["default"] = _default; -------------------------------------------------------------------------------- /build/lib/magic.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports["default"] = magic; 7 | 8 | var _magicDb = _interopRequireDefault(require("./magic-db")); 9 | 10 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 11 | 12 | /** 13 | * Lookup the magic number in magic-number DB 14 | * @param {Buffer} buf Data buffer 15 | * @param {function} cb Callback to invoke on completion 16 | */ 17 | function magic(buf, cb) { 18 | setTimeout(function () { 19 | var sampleLength = 24; 20 | var sample = buf.slice(0, sampleLength).toString('hex'); // lookup data 21 | 22 | var found = Object.keys(_magicDb["default"]).find(function (it) { 23 | return sample.indexOf(it) !== -1; 24 | }); 25 | 26 | if (found) { 27 | cb(null, _magicDb["default"][found]); 28 | } else { 29 | cb(new Error('Magic number not found')); 30 | } 31 | }, 0); 32 | } -------------------------------------------------------------------------------- /build/lib/util/buffer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.toBuffer = toBuffer; 7 | exports.toArrayBuffer = toArrayBuffer; 8 | exports.toArrayLike = toArrayLike; 9 | 10 | /** 11 | * Converts the buffer to Buffer 12 | * @param {Buffer|ArrayBuffer|Uint8Array|Uint8ClampedArray} buf Input buffer 13 | * @returns {Buffer} 14 | */ 15 | function toBuffer(buf) { 16 | if (buf instanceof ArrayBuffer) { 17 | return arrayBufferToBuffer(buf); 18 | } else if (Buffer.isBuffer(buf)) { 19 | return buf; 20 | } else if (buf instanceof Uint8Array || buf instanceof Uint8ClampedArray) { 21 | return Buffer.from(buf); 22 | } else { 23 | return buf; // type unknown, trust the user 24 | } 25 | } 26 | /** 27 | * Converts any buffer to ArrayBuffer 28 | * @param {Buffer|ArrayBuffer|Uint8Array|Uint8ClampedArray} buf Input buffer 29 | * @returns {ArrayBuffer} 30 | */ 31 | 32 | 33 | function toArrayBuffer(buf) { 34 | if (buf instanceof ArrayBuffer) { 35 | return buf; 36 | } else if (Buffer.isBuffer(buf)) { 37 | return arrayLikeToArrayBuffer(buf); 38 | } else if (buf instanceof Uint8Array || buf instanceof Uint8ClampedArray) { 39 | return arrayLikeToArrayBuffer(buf); 40 | } else { 41 | return buf; // type unknown, trust the user 42 | } 43 | } 44 | /** 45 | * Convert any buffer to array-like type: Uint8Array|Uint8ClampedArray|Buffer 46 | * @param {Buffer|ArrayBuffer|Uint8Array|Uint8ClampedArray} buf 47 | * @returns {Buffer|Uint8Array} 48 | */ 49 | 50 | 51 | function toArrayLike(buf) { 52 | if (buf instanceof Uint8Array || buf instanceof Uint8ClampedArray) { 53 | return buf; 54 | } else if (buf instanceof ArrayBuffer) { 55 | return new Uint8Array(buf); 56 | } else if (Buffer.isBuffer(buf)) { 57 | return buf; 58 | } else { 59 | return buf; // type unknown, trust the user 60 | } 61 | } 62 | /** 63 | * Converts Buffer to ArrayBuffer 64 | * 65 | * NOTE: we cannot convert Buffer to ArrayBuffer via `buf.buffer` since the size of the returned ArrayBuffer might be biger than the actual. 66 | * 67 | * @param {Buffer|Uint8Array|Uint8ClampedArray} buf 68 | * @returns {ArrayBuffer} 69 | */ 70 | 71 | 72 | function arrayLikeToArrayBuffer(buf) { 73 | var arrBuf = new ArrayBuffer(buf.length); 74 | var view = new Uint8Array(arrBuf); 75 | 76 | for (var i = 0; i < buf.length; ++i) { 77 | view[i] = buf[i]; 78 | } 79 | 80 | return arrBuf; 81 | } 82 | /** 83 | * Convert ArrayBuffer to Buffer 84 | * @param {ArrayBuffer} arrBuf 85 | * @returns {Buffer} 86 | */ 87 | 88 | 89 | function arrayBufferToBuffer(arrBuf) { 90 | return Buffer.from(new Uint8Array(arrBuf)); 91 | } -------------------------------------------------------------------------------- /build/lib/util/color.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.arrayLikeRgbToRgba = arrayLikeRgbToRgba; 7 | 8 | /** 9 | * Converts a buffer of RGB components to RGBA. 10 | * 11 | * @param buf {Buffer|Uint8Array|Uint8ClampedArray} array-like structure with RGB data 12 | */ 13 | function arrayLikeRgbToRgba(buf) { 14 | var filler = 0xFF; 15 | var result = new Uint8Array(buf.length / 3 * 4); 16 | 17 | for (var i = 0, p = 0; i < buf.length; i += 3) { 18 | result[p++] = buf[i]; 19 | result[p++] = buf[i + 1]; 20 | result[p++] = buf[i + 2]; 21 | result[p++] = filler; 22 | } 23 | 24 | return result; 25 | } -------------------------------------------------------------------------------- /build/main.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } 4 | 5 | Object.defineProperty(exports, "__esModule", { 6 | value: true 7 | }); 8 | exports["default"] = void 0; 9 | 10 | var _hasWorker = require("./lib/has-worker"); 11 | 12 | var bufferUtils = _interopRequireWildcard(require("./lib/util/buffer")); 13 | 14 | var _exif = _interopRequireDefault(require("./lib/exif")); 15 | 16 | var _decode = _interopRequireDefault(require("./lib/decode")); 17 | 18 | var _encode = _interopRequireDefault(require("./lib/encode")); 19 | 20 | var _magic = _interopRequireDefault(require("./lib/magic")); 21 | 22 | var _info = _interopRequireDefault(require("./lib/info")); 23 | 24 | var _exifWorker = _interopRequireDefault(require("./lib/exif-worker")); 25 | 26 | var _decodeWorker = _interopRequireDefault(require("./lib/decode-worker")); 27 | 28 | var _encodeWorker = _interopRequireDefault(require("./lib/encode-worker")); 29 | 30 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 31 | 32 | function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; } 33 | 34 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; } 35 | 36 | /** 37 | * Decode 38 | * 39 | * @param {Buffer|ArrayBuffer|Uint8Array} buf 40 | * @param {object} options Params: { width: number, height: number } 41 | * @param {function} cb Callback to invoke on completion 42 | * 43 | * @callback { width: number, height: number, data: Uint8Array } 44 | */ 45 | function decodeBuffer(buf, options, cb) { 46 | if (typeof options === 'function') { 47 | cb = options; 48 | options = {}; 49 | } 50 | 51 | try { 52 | buf = bufferUtils.toArrayLike(buf); 53 | 54 | if (_hasWorker.hasWorker) { 55 | var wr = require('webworkify')(_decodeWorker["default"]); 56 | 57 | wr.onmessage = function (_ref) { 58 | var msg = _ref.data; 59 | var err = msg.err ? new Error(msg.err) : undefined; 60 | cb(err, msg.result); 61 | }; 62 | 63 | var msg = { 64 | buf: buf, 65 | options: options 66 | }; 67 | 68 | if (options.transferable) { 69 | wr.postMessage(msg, [buf]); 70 | } else { 71 | wr.postMessage(msg); 72 | } 73 | } else { 74 | (0, _decode["default"])(buf, options, cb); 75 | } 76 | } catch (err) { 77 | cb(err); 78 | } 79 | } 80 | /** 81 | * Encode 82 | * 83 | * @param {Buffer|ArrayBuffer|Uint8Array} buf 84 | * @param {object} options Params { width: number, height: number, quality: number } 85 | * @param {function} cb Callback to invoke on completion 86 | * 87 | * @callback { width: number, height: number, data: Uint8Array } 88 | */ 89 | 90 | 91 | function encodeBuffer(buf, options, cb) { 92 | if (typeof options === 'function') { 93 | cb = options; 94 | options = {}; 95 | } 96 | 97 | try { 98 | buf = bufferUtils.toArrayLike(buf); 99 | 100 | if (!options.hasOwnProperty('width') || !options.hasOwnProperty('height')) { 101 | return cb(new Error('Width & height of the buffer is not provided.')); 102 | } 103 | 104 | if (_hasWorker.hasWorker) { 105 | var wr = require('webworkify')(_encodeWorker["default"]); 106 | 107 | wr.onmessage = function (_ref2) { 108 | var msg = _ref2.data; 109 | var err = msg.err ? new Error(msg.err) : undefined; 110 | cb(err, msg.result); 111 | }; 112 | 113 | var msg = { 114 | buf: buf, 115 | options: options 116 | }; 117 | 118 | if (options.transferable) { 119 | wr.postMessage(msg, [buf]); 120 | } else { 121 | wr.postMessage(msg); 122 | } 123 | } else { 124 | (0, _encode["default"])(buf, options, cb); 125 | } 126 | } catch (err) { 127 | cb(err); 128 | } 129 | } 130 | /** 131 | * Get EXIF 132 | * 133 | * @param {Buffer|ArrayBuffer|Uint8Array} buf 134 | * @param {object} options Params { hasMakerNote: true|false } 135 | * @param {function} cb Callback to invoke on completion 136 | * 137 | * @callback Object { name: value, ... } 138 | */ 139 | 140 | 141 | function exifBuffer(buf, options, cb) { 142 | if (typeof options === 'function') { 143 | cb = options; 144 | options = {}; 145 | } 146 | 147 | try { 148 | buf = bufferUtils.toArrayBuffer(buf); 149 | 150 | if (_hasWorker.hasWorker) { 151 | var wr = require('webworkify')(_exifWorker["default"]); 152 | 153 | wr.onmessage = function (_ref3) { 154 | var msg = _ref3.data; 155 | var err = msg.err ? new Error(msg.err) : undefined; 156 | cb(err, msg.result); 157 | }; 158 | 159 | var msg = { 160 | buf: buf 161 | }; 162 | 163 | if (options.transferable) { 164 | wr.postMessage(msg, [buf]); 165 | } else { 166 | wr.postMessage(msg); 167 | } 168 | } else { 169 | (0, _exif["default"])(buf, options, cb); 170 | } 171 | } catch (err) { 172 | cb(err); 173 | } 174 | } 175 | /** 176 | * Detect mime-type for the Buffer 177 | * @param {Buffer|ArrayBuffer|Uint8Array} buf Data buffer 178 | * @param {function} cb Callback to invoke on completion 179 | */ 180 | 181 | 182 | function magicBuffer(buf, cb) { 183 | try { 184 | buf = bufferUtils.toBuffer(buf); 185 | (0, _magic["default"])(buf, cb); 186 | } catch (err) { 187 | cb(err); 188 | } 189 | } 190 | /** 191 | * Get image information without reading and decoding a file 192 | * @param {Buffer|ArrayBuffer|Uint8Array} buf Data buffer 193 | * @param {function} cb Callback to invoke on completion 194 | */ 195 | 196 | 197 | function infoBuffer(buf, cb) { 198 | try { 199 | buf = bufferUtils.toBuffer(buf); 200 | (0, _info["default"])(buf, cb); 201 | } catch (err) { 202 | cb(err); 203 | } 204 | } 205 | 206 | var _default = { 207 | decode: decodeBuffer, 208 | encode: encodeBuffer, 209 | exif: exifBuffer, 210 | magic: magicBuffer, 211 | info: infoBuffer 212 | }; 213 | exports["default"] = _default; -------------------------------------------------------------------------------- /data/colors.css: -------------------------------------------------------------------------------- 1 | /* Backgrounds */ 2 | .bg-navy { background-color: #001F3F; } 3 | .bg-blue { background-color: #0074D9; } 4 | .bg-aqua { background-color: #7FDBFF; } 5 | .bg-teal { background-color: #39CCCC; } 6 | .bg-olive { background-color: #3D9970; } 7 | .bg-green { background-color: #2ECC40; } 8 | .bg-lime { background-color: #01FF70; } 9 | .bg-yellow { background-color: #FFDC00; } 10 | .bg-orange { background-color: #FF851B; } 11 | .bg-red { background-color: #FF4136; } 12 | .bg-fuchsia { background-color: #F012BE; } 13 | .bg-purple { background-color: #B10DC9; } 14 | .bg-maroon { background-color: #85144B; } 15 | .bg-white { background-color: #FFFFFF; } 16 | .bg-gray { background-color: #AAAAAA; } 17 | .bg-silver { background-color: #DDDDDD; } 18 | .bg-black { background-color: #111111; } 19 | 20 | /* Colors */ 21 | .navy { color: #001F3F; } 22 | .blue { color: #0074D9; } 23 | .aqua { color: #7FDBFF; } 24 | .teal { color: #39CCCC; } 25 | .olive { color: #3D9970; } 26 | .green { color: #2ECC40; } 27 | .lime { color: #01FF70; } 28 | .yellow { color: #FFDC00; } 29 | .orange { color: #FF851B; } 30 | .red { color: #FF4136; } 31 | .fuchsia { color: #F012BE; } 32 | .purple { color: #B10DC9; } 33 | .maroon { color: #85144B; } 34 | .white { color: #FFFFFF; } 35 | .silver { color: #DDDDDD; } 36 | .gray { color: #AAAAAA; } 37 | .black { color: #111111; } 38 | 39 | /* Border colors 40 | Use with another border utility that sets border-width and style 41 | i.e .border { border-width: 1px); border-style: solid); } 42 | */ 43 | .border--navy { border-color: #001F3F; } 44 | .border--blue { border-color: #0074D9; } 45 | .border--aqua { border-color: #7FDBFF; } 46 | .border--teal { border-color: #39CCCC; } 47 | .border--olive { border-color: #3D9970; } 48 | .border--green { border-color: #2ECC40; } 49 | .border--lime { border-color: #01FF70; } 50 | .border--yellow { border-color: #FFDC00; } 51 | .border--orange { border-color: #FF851B; } 52 | .border--red { border-color: #FF4136; } 53 | .border--fuchsia { border-color: #F012BE; } 54 | .border--purple { border-color: #B10DC9; } 55 | .border--maroon { border-color: #85144B; } 56 | .border--white { border-color: #FFFFFF; } 57 | .border--gray { border-color: #AAAAAA; } 58 | .border--silver { border-color: #DDDDDD; } 59 | .border--black { border-color: #111111; } 60 | 61 | /* Fills for SVG */ 62 | .fill-navy { fill: #001F3F; } 63 | .fill-blue { fill: #0074D9; } 64 | .fill-aqua { fill: #7FDBFF; } 65 | .fill-teal { fill: #39CCCC; } 66 | .fill-olive { fill: #3D9970; } 67 | .fill-green { fill: #2ECC40; } 68 | .fill-lime { fill: #01FF70; } 69 | .fill-yellow { fill: #FFDC00; } 70 | .fill-orange { fill: #FF851B; } 71 | .fill-red { fill: #FF4136; } 72 | .fill-fuchsia { fill: #F012BE; } 73 | .fill-purple { fill: #B10DC9; } 74 | .fill-maroon { fill: #85144B; } 75 | .fill-white { fill: #FFFFFF; } 76 | .fill-gray { fill: #AAAAAA; } 77 | .fill-silver { fill: #DDDDDD; } 78 | .fill-black { fill: #111111; } 79 | 80 | /* Strokes for SVG */ 81 | .stroke-navy { stroke: #001F3F; } 82 | .stroke-blue { stroke: #0074D9; } 83 | .stroke-aqua { stroke: #7FDBFF; } 84 | .stroke-teal { stroke: #39CCCC; } 85 | .stroke-olive { stroke: #3D9970; } 86 | .stroke-green { stroke: #2ECC40; } 87 | .stroke-lime { stroke: #01FF70; } 88 | .stroke-yellow { stroke: #FFDC00; } 89 | .stroke-orange { stroke: #FF851B; } 90 | .stroke-red { stroke: #FF4136; } 91 | .stroke-fuchsia { stroke: #F012BE; } 92 | .stroke-purple { stroke: #B10DC9; } 93 | .stroke-maroon { stroke: #85144B; } 94 | .stroke-white { stroke: #FFFFFF; } 95 | .stroke-gray { stroke: #AAAAAA; } 96 | .stroke-silver { stroke: #DDDDDD; } 97 | .stroke-black { stroke: #111111; } 98 | -------------------------------------------------------------------------------- /data/inkjet-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gchudnov/inkjet/6d90b61f9639377eefa6950dd58c086f17e70632/data/inkjet-icon.png -------------------------------------------------------------------------------- /data/inkjet-icon.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gchudnov/inkjet/6d90b61f9639377eefa6950dd58c086f17e70632/data/inkjet-icon.xcf -------------------------------------------------------------------------------- /data/inkjet-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gchudnov/inkjet/6d90b61f9639377eefa6950dd58c086f17e70632/data/inkjet-logo.png -------------------------------------------------------------------------------- /data/inkjet-logo.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gchudnov/inkjet/6d90b61f9639377eefa6950dd58c086f17e70632/data/inkjet-logo.xcf -------------------------------------------------------------------------------- /data/inkjet-matrix.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Open Sans", Helvetica, Arial, sans-serif; 3 | font-size: 14px; 4 | } 5 | 6 | table.matrix { 7 | margin: 32px; 8 | border-width: 1px; 9 | border-style: solid; 10 | } 11 | 12 | table.matrix td { 13 | width: 80px; 14 | text-align: center; 15 | } 16 | 17 | table.matrix td.header { 18 | border-bottom-width: 4px; 19 | border-bottom-style: solid; 20 | } 21 | 22 | .almost { 23 | background: repeating-linear-gradient( 24 | -45deg, 25 | #2ECC40, 26 | #2ECC40 10px, 27 | #deffe2 10px, 28 | #deffe2 20px 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /data/inkjet-matrix.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Inkjet Browser Matrix 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
ChromeFirefoxEdgeInternet ExplorerSafari
85818511*14
28 | 29 | 30 | -------------------------------------------------------------------------------- /data/inkjet-matrix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gchudnov/inkjet/6d90b61f9639377eefa6950dd58c086f17e70632/data/inkjet-matrix.png -------------------------------------------------------------------------------- /data/reset.min.css: -------------------------------------------------------------------------------- 1 | *,*::before,*::after{box-sizing:border-box}ul[class],ol[class]{padding:0}body,h1,h2,h3,h4,p,ul[class],ol[class],figure,blockquote,dl,dd{margin:0}html{scroll-behavior:smooth}body{min-height:100vh;text-rendering:optimizeSpeed;line-height:1.5}ul[class],ol[class]{list-style:none}a:not([class]){text-decoration-skip-ink:auto}img,picture{max-width:100%;display:block}article>*+*{margin-top:1em}input,button,textarea,select{font:inherit}img:not([alt]){filter:blur(10px)}@media(prefers-reduced-motion:reduce){*{animation-duration:.01ms !important;animation-iteration-count:1 !important;transition-duration:.01ms !important;scroll-behavior:auto !important}} 2 | -------------------------------------------------------------------------------- /examples/decode/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JPEG Decode 6 | 7 | 8 | 9 | 10 |
11 | 15 |
16 |
17 |
18 |
19 |
20 |

decoded source

21 | 22 |
23 | 24 | -------------------------------------------------------------------------------- /examples/decode/index.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | // Check that the browser supports the FileReader API. 4 | if (!window.FileReader) { 5 | document.write('Sorry, your web browser does not support the FileReader API.'); 6 | return; 7 | } 8 | 9 | function displayError(err) { 10 | var el = document.getElementById('errors'); 11 | el.innerHTML = err ? err.message : ''; 12 | } 13 | 14 | function clearError() { 15 | displayError() 16 | } 17 | 18 | function clearCanvas() { 19 | var canvas = document.getElementById('source-canvas'); 20 | var ctx = canvas.getContext("2d"); 21 | ctx.clearRect (0 , 0 , canvas.width, canvas.height); 22 | } 23 | 24 | function displayDecodedData(decoded) { 25 | var canvas = document.getElementById('source-canvas'); 26 | canvas.width = decoded.width; 27 | canvas.height = decoded.height; 28 | 29 | var ctx = canvas.getContext("2d"); 30 | 31 | var imageData = ctx.getImageData(0, 0, decoded.width, decoded.height); 32 | var imageBytes = imageData.data; 33 | for (var i = 0, j = 0, size = decoded.width * decoded.height * 4; i < size; ) { 34 | imageBytes[i++] = decoded.data[j++]; 35 | imageBytes[i++] = decoded.data[j++]; 36 | imageBytes[i++] = decoded.data[j++]; 37 | imageBytes[i++] = decoded.data[j++]; 38 | } 39 | 40 | ctx.putImageData(imageData, 0, 0); 41 | } 42 | 43 | var handleFile = function (event) { 44 | var files = event.target.files; 45 | 46 | var reader = new FileReader(); 47 | reader.onload = function (event) { 48 | var buf = event.target.result; 49 | try { 50 | inkjet.decode(buf, function(err, decoded) { 51 | if(err) { 52 | displayError(err); 53 | } else { 54 | displayDecodedData(decoded); 55 | } 56 | }); 57 | } catch (err) { 58 | displayError(err); 59 | } 60 | }; 61 | 62 | clearError(); 63 | clearCanvas(); 64 | 65 | // We only need the start of the file for the Exif info. 66 | reader.readAsArrayBuffer(files[0]); 67 | }; 68 | 69 | window.addEventListener('load', function () { 70 | document.getElementById('file').addEventListener('change', handleFile, false); 71 | }, false); 72 | 73 | }()); -------------------------------------------------------------------------------- /examples/encode/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JPEG Encode 6 | 7 | 8 | 9 | 10 |
11 |
12 |
13 |

canvas source

14 | 15 |
16 | 17 | -------------------------------------------------------------------------------- /examples/encode/index.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | function displayInfo(message) { 4 | var el = document.getElementById('info'); 5 | el.innerHTML = message; 6 | } 7 | 8 | function displayError(err) { 9 | var el = document.getElementById('errors'); 10 | el.innerHTML = err ? err.message : ''; 11 | } 12 | 13 | function clearError() { 14 | displayError() 15 | } 16 | 17 | function clearCanvas() { 18 | var canvas = document.getElementById('source-canvas'); 19 | var ctx = canvas.getContext("2d"); 20 | ctx.clearRect (0 , 0 , canvas.width, canvas.height); 21 | } 22 | 23 | function makeSourceData() { 24 | var width = 512; 25 | var height = 512; 26 | 27 | var canvas = document.getElementById('source-canvas'); 28 | canvas.width = width; 29 | canvas.height = height; 30 | 31 | var ctx = canvas.getContext("2d"); 32 | 33 | var imageData = ctx.getImageData(0, 0, width, height); 34 | var imageBytes = imageData.data; 35 | for (var i = 0, j = 0, size = width * height * 4; i < size; ) { 36 | imageBytes[i++] = 0; // R 37 | imageBytes[i++] = 0; // G 38 | imageBytes[i++] = 0xFF; // B 39 | imageBytes[i++] = 0xFF; // A 40 | } 41 | 42 | ctx.putImageData(imageData, 0, 0); 43 | } 44 | 45 | function getImageData() { 46 | var canvas = document.getElementById('source-canvas'); 47 | var ctx = canvas.getContext("2d"); 48 | 49 | // { width, height, data } 50 | return ctx.getImageData(0, 0, canvas.width, canvas.height); 51 | } 52 | 53 | function encodeCanvasData() { 54 | try { 55 | var imageData = getImageData(); 56 | 57 | var buf = imageData.data; 58 | var options = { 59 | width: imageData.width, 60 | height: imageData.height, 61 | quality: 80 62 | }; 63 | 64 | inkjet.encode(buf, options, function(err, encoded) { 65 | if(err) { 66 | displayError(err); 67 | } else { 68 | displayInfo('Image encoded successfully'); 69 | } 70 | }); 71 | } catch(err) { 72 | displayError(err); 73 | } 74 | } 75 | 76 | document.addEventListener('DOMContentLoaded', function(){ 77 | clearError(); 78 | makeSourceData(); 79 | encodeCanvasData(); 80 | }); 81 | 82 | 83 | }()); -------------------------------------------------------------------------------- /examples/exif/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | EXIF Reader 6 | 7 | 8 | 9 | 10 |
11 | 15 |
16 |
17 |
18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
Tag nameTag value
29 | 30 | -------------------------------------------------------------------------------- /examples/exif/index.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | // Check that the browser supports the FileReader API. 4 | if (!window.FileReader) { 5 | document.write('Sorry, your web browser does not support the FileReader API.'); 6 | return; 7 | } 8 | 9 | function displayError(err) { 10 | var el = document.getElementById('errors'); 11 | el.innerHTML = err ? err.message : ''; 12 | } 13 | 14 | function clearError() { 15 | displayError() 16 | } 17 | 18 | function displayMetadata(tags) { 19 | var tableBody = document.getElementById('exif-table-body'); 20 | var row; 21 | for (name in tags) { 22 | if (tags.hasOwnProperty(name)) { 23 | row = document.createElement('tr'); 24 | row.innerHTML = '' + name + '' + tags[name].description + ''; 25 | tableBody.appendChild(row); 26 | } 27 | } 28 | } 29 | 30 | function clearMetadata() { 31 | var tableBody = document.getElementById('exif-table-body'); 32 | while (tableBody.firstChild) { 33 | tableBody.removeChild(tableBody.firstChild); 34 | } 35 | } 36 | 37 | var handleFile = function (event) { 38 | var files, reader; 39 | 40 | files = event.target.files; 41 | reader = new FileReader(); 42 | reader.onload = function (event) { 43 | try { 44 | inkjet.exif(event.target.result, function(err, tags) { 45 | if(err) { 46 | displayError(err); 47 | } else { 48 | displayMetadata(tags); 49 | } 50 | }); 51 | 52 | } catch (err) { 53 | displayError(err); 54 | } 55 | }; 56 | 57 | clearMetadata(); 58 | clearError(); 59 | 60 | // We only need the start of the file for the Exif info. 61 | var bufferSize = 128 * 1024; 62 | reader.readAsArrayBuffer(files[0].slice(0, bufferSize)); 63 | }; 64 | 65 | window.addEventListener('load', function () { 66 | document.getElementById('file').addEventListener('change', handleFile, false); 67 | }, false); 68 | 69 | }()); -------------------------------------------------------------------------------- /examples/info/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Image information 6 | 7 | 8 | 9 | 10 |
11 | 15 |
16 |
17 |
18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
NameValue
29 | 30 | -------------------------------------------------------------------------------- /examples/info/index.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | // Check that the browser supports the FileReader API. 4 | if (!window.FileReader) { 5 | document.write('Sorry, your web browser does not support the FileReader API.'); 6 | return; 7 | } 8 | 9 | function displayError(err) { 10 | var el = document.getElementById('errors'); 11 | el.innerHTML = err ? err.message : ''; 12 | } 13 | 14 | function clearError() { 15 | displayError() 16 | } 17 | 18 | function displayData(data) { 19 | var tableBody = document.getElementById('info-table-body'); 20 | 21 | var row; 22 | for (name in data) { 23 | if (data.hasOwnProperty(name)) { 24 | row = document.createElement('tr'); 25 | row.innerHTML = '' + name + '' + data[name] + ''; 26 | tableBody.appendChild(row); 27 | } 28 | } 29 | } 30 | 31 | function clearData() { 32 | var tableBody = document.getElementById('info-table-body'); 33 | while (tableBody.firstChild) { 34 | tableBody.removeChild(tableBody.firstChild); 35 | } 36 | } 37 | 38 | var handleFile = function (event) { 39 | var files, reader; 40 | 41 | files = event.target.files; 42 | reader = new FileReader(); 43 | reader.onload = function (event) { 44 | try { 45 | inkjet.info(event.target.result, function(err, data) { 46 | if(err) { 47 | displayError(err); 48 | } else { 49 | displayData(data); 50 | } 51 | }); 52 | 53 | } catch (err) { 54 | displayData(err); 55 | } 56 | }; 57 | 58 | clearError(); 59 | clearData(); 60 | 61 | // We only need the start of the file to get the info. 62 | var bufferSize = 1024 * 1024; 63 | reader.readAsArrayBuffer(files[0].slice(0, bufferSize)); 64 | }; 65 | 66 | window.addEventListener('load', function () { 67 | document.getElementById('file').addEventListener('change', handleFile, false); 68 | }, false); 69 | 70 | }()); -------------------------------------------------------------------------------- /examples/magic/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Image Type Deduction 6 | 7 | 8 | 9 | 10 |
11 | 15 |
16 |
17 |
18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
MIME TypeExtension
33 | 34 | -------------------------------------------------------------------------------- /examples/magic/index.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | // Check that the browser supports the FileReader API. 4 | if (!window.FileReader) { 5 | document.write('Sorry, your web browser does not support the FileReader API.'); 6 | return; 7 | } 8 | 9 | function displayError(err) { 10 | var el = document.getElementById('errors'); 11 | el.innerHTML = err ? err.message : ''; 12 | } 13 | 14 | function clearError() { 15 | displayError() 16 | } 17 | 18 | function displayData(data) { 19 | var mimeType = (data && data.mimeType) || ''; 20 | var extension = (data && data.extension) || ''; 21 | 22 | var mimeEl = document.getElementById('file_mime'); 23 | mimeEl.innerHTML = mimeType; 24 | 25 | var extEl = document.getElementById('file_extension'); 26 | extEl.innerHTML = extension; 27 | } 28 | 29 | function clearData() { 30 | var mimeEl = document.getElementById('file_mime'); 31 | mimeEl.innerHTML = ''; 32 | 33 | var extEl = document.getElementById('file_extension'); 34 | extEl.innerHTML = ''; 35 | } 36 | 37 | var handleFile = function (event) { 38 | var files, reader; 39 | 40 | files = event.target.files; 41 | reader = new FileReader(); 42 | reader.onload = function (event) { 43 | try { 44 | inkjet.magic(event.target.result, function(err, data) { 45 | if(err) { 46 | displayError(err); 47 | } else { 48 | displayData(data); 49 | } 50 | }); 51 | 52 | } catch (err) { 53 | displayError(err); 54 | } 55 | }; 56 | 57 | clearError(); 58 | clearData(); 59 | 60 | reader.readAsArrayBuffer(files[0]); 61 | }; 62 | 63 | window.addEventListener('load', function () { 64 | document.getElementById('file').addEventListener('change', handleFile, false); 65 | }, false); 66 | 67 | }()); -------------------------------------------------------------------------------- /gulpfile.babel.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require("@babel/register")({}); 4 | 5 | const { browserifyTask } = require('./tasks/browserify-task'); 6 | const { bundleTestTask } = require('./tasks/bundle-test-task'); 7 | 8 | exports.default = browserifyTask; 9 | exports.bundleTest = bundleTestTask; 10 | -------------------------------------------------------------------------------- /images/js_broken.jpg: -------------------------------------------------------------------------------- 1 | BROKEN_FILE 2 | -------------------------------------------------------------------------------- /images/js_logo-4-2-0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gchudnov/inkjet/6d90b61f9639377eefa6950dd58c086f17e70632/images/js_logo-4-2-0.jpg -------------------------------------------------------------------------------- /images/js_logo-4-2-2-horz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gchudnov/inkjet/6d90b61f9639377eefa6950dd58c086f17e70632/images/js_logo-4-2-2-horz.jpg -------------------------------------------------------------------------------- /images/js_logo-4-2-2-vert.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gchudnov/inkjet/6d90b61f9639377eefa6950dd58c086f17e70632/images/js_logo-4-2-2-vert.jpg -------------------------------------------------------------------------------- /images/js_logo-4-4-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gchudnov/inkjet/6d90b61f9639377eefa6950dd58c086f17e70632/images/js_logo-4-4-4.jpg -------------------------------------------------------------------------------- /images/js_logo-arithmetic-coding.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gchudnov/inkjet/6d90b61f9639377eefa6950dd58c086f17e70632/images/js_logo-arithmetic-coding.jpg -------------------------------------------------------------------------------- /images/js_logo-dct-float.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gchudnov/inkjet/6d90b61f9639377eefa6950dd58c086f17e70632/images/js_logo-dct-float.jpg -------------------------------------------------------------------------------- /images/js_logo-exif.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gchudnov/inkjet/6d90b61f9639377eefa6950dd58c086f17e70632/images/js_logo-exif.jpg -------------------------------------------------------------------------------- /images/js_logo-progressive.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gchudnov/inkjet/6d90b61f9639377eefa6950dd58c086f17e70632/images/js_logo-progressive.jpg -------------------------------------------------------------------------------- /images/js_logo-sRGB-IEC61966-2-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gchudnov/inkjet/6d90b61f9639377eefa6950dd58c086f17e70632/images/js_logo-sRGB-IEC61966-2-1.jpg -------------------------------------------------------------------------------- /images/js_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gchudnov/inkjet/6d90b61f9639377eefa6950dd58c086f17e70632/images/js_logo.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "inkjet", 3 | "version": "3.0.0", 4 | "description": "JPEG-image decoding, encoding & EXIF reading library for browser and node.js", 5 | "main": "build/index.js", 6 | "scripts": { 7 | "test": "mocha test --require @babel/register", 8 | "bundle:test": "gulp bundleTest", 9 | "build": "./node_modules/.bin/babel src --out-dir build", 10 | "browser": "gulp", 11 | "browser:release": "NODE_ENV=production gulp", 12 | "coverage": "nyc --reporter=lcov --reporter=html npm run test" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/gchudnov/inkjet.git" 17 | }, 18 | "keywords": [ 19 | "jpeg", 20 | "exif", 21 | "image", 22 | "photo", 23 | "decode", 24 | "encode", 25 | "metadata" 26 | ], 27 | "author": "Grigorii Chudnov (https://github.com/gchudnov)", 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/gchudnov/inkjet/issues" 31 | }, 32 | "homepage": "https://github.com/gchudnov/inkjet", 33 | "dependencies": { 34 | "imageinfo": "^1.0.4", 35 | "webworkify": "^1.5.0" 36 | }, 37 | "devDependencies": { 38 | "@babel/cli": "^7.11.6", 39 | "@babel/core": "^7.11.6", 40 | "@babel/preset-env": "^7.11.5", 41 | "@babel/register": "^7.11.5", 42 | "ansi-colors": "^4.1.1", 43 | "async": "^3.2.0", 44 | "babel-plugin-add-module-exports": "^1.0.4", 45 | "babelify": "^10.0.0", 46 | "browserify": "^16.5.2", 47 | "fancy-log": "^1.3.3", 48 | "gulp": "^4.0.2", 49 | "gulp-header": "^2.0.9", 50 | "gulp-if": "^3.0.0", 51 | "gulp-replace": "^1.0.0", 52 | "gulp-uglify": "^3.0.2", 53 | "mocha": "^8.1.3", 54 | "nyc": "^15.1.0", 55 | "once": "^1.4.0", 56 | "should": "^13.2.3", 57 | "vinyl-buffer": "^1.0.1", 58 | "vinyl-source-stream": "^2.0.0" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import lib from "./main"; 2 | module.exports = lib; 3 | -------------------------------------------------------------------------------- /src/lib/backend/LIBRARIES.md: -------------------------------------------------------------------------------- 1 | Used external libraries: 2 | 3 | - Read EXIF: [ExifReader v3.12.2](https://github.com/mattiasw/ExifReader) 4 | - Decode JPEG: [pdf.js v2.5.207](https://github.com/mozilla/pdf.js) 5 | - Encode JPEG: JPEGEncoder 6 | 7 | ## Upgrading libraries 8 | 9 | ### Read EXIF 10 | 11 | ```bash 12 | git clone https://github.com/mattiasw/ExifReader.git 13 | cd ExifReader 14 | cp ./dist/exif-reader.js ./../inkjet/src/lib/backend/ 15 | ``` 16 | 17 | ### Decode JPEG 18 | 19 | ```bash 20 | git clone git@github.com:mozilla/pdf.js.git 21 | cd pdf.js/src/core 22 | # embed imports 23 | cp ./jpg.js ./../inkjet/src/lib/backend/ 24 | ``` 25 | -------------------------------------------------------------------------------- /src/lib/backend/jpg-encode.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008, Adobe Systems Incorporated 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the distribution. 15 | 16 | * Neither the name of Adobe Systems Incorporated nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 21 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 22 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | /* 33 | JPEG encoder ported to JavaScript and optimized by Andreas Ritter, www.bytestrom.eu, 11/2009 34 | 35 | Basic GUI blocking jpeg encoder 36 | */ 37 | 38 | function JPEGEncoder(quality) { 39 | var ffloor = Math.floor; 40 | var YTable = new Array(64); 41 | var UVTable = new Array(64); 42 | var fdtbl_Y = new Array(64); 43 | var fdtbl_UV = new Array(64); 44 | var YDC_HT; 45 | var UVDC_HT; 46 | var YAC_HT; 47 | var UVAC_HT; 48 | 49 | var bitcode = new Array(65535); 50 | var category = new Array(65535); 51 | var outputfDCTQuant = new Array(64); 52 | var DU = new Array(64); 53 | var byteout = []; 54 | var bytenew = 0; 55 | var bytepos = 7; 56 | 57 | var YDU = new Array(64); 58 | var UDU = new Array(64); 59 | var VDU = new Array(64); 60 | var clt = new Array(256); 61 | var RGB_YUV_TABLE = new Array(2048); 62 | var currentQuality; 63 | 64 | var ZigZag = [ 65 | 0, 1, 5, 6, 14, 15, 27, 28, 66 | 2, 4, 7, 13, 16, 26, 29, 42, 67 | 3, 8, 12, 17, 25, 30, 41, 43, 68 | 9, 11, 18, 24, 31, 40, 44, 53, 69 | 10, 19, 23, 32, 39, 45, 52, 54, 70 | 20, 22, 33, 38, 46, 51, 55, 60, 71 | 21, 34, 37, 47, 50, 56, 59, 61, 72 | 35, 36, 48, 49, 57, 58, 62, 63 73 | ]; 74 | 75 | var std_dc_luminance_nrcodes = [0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0]; 76 | var std_dc_luminance_values = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]; 77 | var std_ac_luminance_nrcodes = [0, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 0x7d]; 78 | var std_ac_luminance_values = [ 79 | 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 80 | 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 81 | 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 82 | 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, 83 | 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 84 | 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, 85 | 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 86 | 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 87 | 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 88 | 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 89 | 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 90 | 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 91 | 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 92 | 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 93 | 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 94 | 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 95 | 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 96 | 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, 97 | 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 98 | 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 99 | 0xf9, 0xfa 100 | ]; 101 | 102 | var std_dc_chrominance_nrcodes = [0, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0]; 103 | var std_dc_chrominance_values = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]; 104 | var std_ac_chrominance_nrcodes = [0, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 0x77]; 105 | var std_ac_chrominance_values = [ 106 | 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 107 | 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 108 | 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 109 | 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 110 | 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 111 | 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, 112 | 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 113 | 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 114 | 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 115 | 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 116 | 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 117 | 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 118 | 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 119 | 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 120 | 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 121 | 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 122 | 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 123 | 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 124 | 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 125 | 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 126 | 0xf9, 0xfa 127 | ]; 128 | 129 | function initQuantTables(sf) { 130 | var YQT = [ 131 | 16, 11, 10, 16, 24, 40, 51, 61, 132 | 12, 12, 14, 19, 26, 58, 60, 55, 133 | 14, 13, 16, 24, 40, 57, 69, 56, 134 | 14, 17, 22, 29, 51, 87, 80, 62, 135 | 18, 22, 37, 56, 68, 109, 103, 77, 136 | 24, 35, 55, 64, 81, 104, 113, 92, 137 | 49, 64, 78, 87, 103, 121, 120, 101, 138 | 72, 92, 95, 98, 112, 100, 103, 99 139 | ]; 140 | 141 | for (var i = 0; i < 64; i++) { 142 | var t = ffloor((YQT[i] * sf + 50) / 100); 143 | t = Math.min(Math.max(t, 1), 255); 144 | YTable[ZigZag[i]] = t; 145 | } 146 | var UVQT = [ 147 | 17, 18, 24, 47, 99, 99, 99, 99, 148 | 18, 21, 26, 66, 99, 99, 99, 99, 149 | 24, 26, 56, 99, 99, 99, 99, 99, 150 | 47, 66, 99, 99, 99, 99, 99, 99, 151 | 99, 99, 99, 99, 99, 99, 99, 99, 152 | 99, 99, 99, 99, 99, 99, 99, 99, 153 | 99, 99, 99, 99, 99, 99, 99, 99, 154 | 99, 99, 99, 99, 99, 99, 99, 99 155 | ]; 156 | for (var j = 0; j < 64; j++) { 157 | var u = ffloor((UVQT[j] * sf + 50) / 100); 158 | u = Math.min(Math.max(u, 1), 255); 159 | UVTable[ZigZag[j]] = u; 160 | } 161 | var aasf = [ 162 | 1.0, 1.387039845, 1.306562965, 1.175875602, 163 | 1.0, 0.785694958, 0.541196100, 0.275899379 164 | ]; 165 | var k = 0; 166 | for (var row = 0; row < 8; row++) { 167 | for (var col = 0; col < 8; col++) { 168 | fdtbl_Y[k] = (1.0 / (YTable[ZigZag[k]] * aasf[row] * aasf[col] * 8.0)); 169 | fdtbl_UV[k] = (1.0 / (UVTable[ZigZag[k]] * aasf[row] * aasf[col] * 8.0)); 170 | k++; 171 | } 172 | } 173 | } 174 | 175 | function computeHuffmanTbl(nrcodes, std_table) { 176 | var codevalue = 0; 177 | var pos_in_table = 0; 178 | var HT = new Array(); 179 | for (var k = 1; k <= 16; k++) { 180 | for (var j = 1; j <= nrcodes[k]; j++) { 181 | HT[std_table[pos_in_table]] = []; 182 | HT[std_table[pos_in_table]][0] = codevalue; 183 | HT[std_table[pos_in_table]][1] = k; 184 | pos_in_table++; 185 | codevalue++; 186 | } 187 | codevalue *= 2; 188 | } 189 | return HT; 190 | } 191 | 192 | function initHuffmanTbl() { 193 | YDC_HT = computeHuffmanTbl(std_dc_luminance_nrcodes, std_dc_luminance_values); 194 | UVDC_HT = computeHuffmanTbl(std_dc_chrominance_nrcodes, std_dc_chrominance_values); 195 | YAC_HT = computeHuffmanTbl(std_ac_luminance_nrcodes, std_ac_luminance_values); 196 | UVAC_HT = computeHuffmanTbl(std_ac_chrominance_nrcodes, std_ac_chrominance_values); 197 | } 198 | 199 | function initCategoryNumber() { 200 | var nrlower = 1; 201 | var nrupper = 2; 202 | for (var cat = 1; cat <= 15; cat++) { 203 | //Positive numbers 204 | for (var nr = nrlower; nr < nrupper; nr++) { 205 | category[32767 + nr] = cat; 206 | bitcode[32767 + nr] = []; 207 | bitcode[32767 + nr][1] = cat; 208 | bitcode[32767 + nr][0] = nr; 209 | } 210 | //Negative numbers 211 | for (var nrneg = -(nrupper - 1); nrneg <= -nrlower; nrneg++) { 212 | category[32767 + nrneg] = cat; 213 | bitcode[32767 + nrneg] = []; 214 | bitcode[32767 + nrneg][1] = cat; 215 | bitcode[32767 + nrneg][0] = nrupper - 1 + nrneg; 216 | } 217 | nrlower <<= 1; 218 | nrupper <<= 1; 219 | } 220 | } 221 | 222 | function initRGBYUVTable() { 223 | for (var i = 0; i < 256; i++) { 224 | RGB_YUV_TABLE[i] = 19595 * i; 225 | RGB_YUV_TABLE[(i + 256) >> 0] = 38470 * i; 226 | RGB_YUV_TABLE[(i + 512) >> 0] = 7471 * i + 0x8000; 227 | RGB_YUV_TABLE[(i + 768) >> 0] = -11059 * i; 228 | RGB_YUV_TABLE[(i + 1024) >> 0] = -21709 * i; 229 | RGB_YUV_TABLE[(i + 1280) >> 0] = 32768 * i + 0x807FFF; 230 | RGB_YUV_TABLE[(i + 1536) >> 0] = -27439 * i; 231 | RGB_YUV_TABLE[(i + 1792) >> 0] = - 5329 * i; 232 | } 233 | } 234 | 235 | // IO functions 236 | function writeBits(bs) { 237 | var value = bs[0]; 238 | var posval = bs[1] - 1; 239 | while (posval >= 0) { 240 | if (value & (1 << posval)) { 241 | bytenew |= (1 << bytepos); 242 | } 243 | posval--; 244 | bytepos--; 245 | if (bytepos < 0) { 246 | if (bytenew == 0xFF) { 247 | writeByte(0xFF); 248 | writeByte(0); 249 | } 250 | else { 251 | writeByte(bytenew); 252 | } 253 | bytepos = 7; 254 | bytenew = 0; 255 | } 256 | } 257 | } 258 | 259 | function writeByte(value) { 260 | //byteout.push(clt[value]); // write char directly instead of converting later 261 | byteout.push(value); 262 | } 263 | 264 | function writeWord(value) { 265 | writeByte((value >> 8) & 0xFF); 266 | writeByte((value) & 0xFF); 267 | } 268 | 269 | // DCT & quantization core 270 | function fDCTQuant(data, fdtbl) { 271 | var d0, d1, d2, d3, d4, d5, d6, d7; 272 | /* Pass 1: process rows. */ 273 | var dataOff = 0; 274 | var i; 275 | var I8 = 8; 276 | var I64 = 64; 277 | for (i = 0; i < I8; ++i) { 278 | d0 = data[dataOff]; 279 | d1 = data[dataOff + 1]; 280 | d2 = data[dataOff + 2]; 281 | d3 = data[dataOff + 3]; 282 | d4 = data[dataOff + 4]; 283 | d5 = data[dataOff + 5]; 284 | d6 = data[dataOff + 6]; 285 | d7 = data[dataOff + 7]; 286 | 287 | var tmp0 = d0 + d7; 288 | var tmp7 = d0 - d7; 289 | var tmp1 = d1 + d6; 290 | var tmp6 = d1 - d6; 291 | var tmp2 = d2 + d5; 292 | var tmp5 = d2 - d5; 293 | var tmp3 = d3 + d4; 294 | var tmp4 = d3 - d4; 295 | 296 | /* Even part */ 297 | var tmp10 = tmp0 + tmp3; /* phase 2 */ 298 | var tmp13 = tmp0 - tmp3; 299 | var tmp11 = tmp1 + tmp2; 300 | var tmp12 = tmp1 - tmp2; 301 | 302 | data[dataOff] = tmp10 + tmp11; /* phase 3 */ 303 | data[dataOff + 4] = tmp10 - tmp11; 304 | 305 | var z1 = (tmp12 + tmp13) * 0.707106781; /* c4 */ 306 | data[dataOff + 2] = tmp13 + z1; /* phase 5 */ 307 | data[dataOff + 6] = tmp13 - z1; 308 | 309 | /* Odd part */ 310 | tmp10 = tmp4 + tmp5; /* phase 2 */ 311 | tmp11 = tmp5 + tmp6; 312 | tmp12 = tmp6 + tmp7; 313 | 314 | /* The rotator is modified from fig 4-8 to avoid extra negations. */ 315 | var z5 = (tmp10 - tmp12) * 0.382683433; /* c6 */ 316 | var z2 = 0.541196100 * tmp10 + z5; /* c2-c6 */ 317 | var z4 = 1.306562965 * tmp12 + z5; /* c2+c6 */ 318 | var z3 = tmp11 * 0.707106781; /* c4 */ 319 | 320 | var z11 = tmp7 + z3; /* phase 5 */ 321 | var z13 = tmp7 - z3; 322 | 323 | data[dataOff + 5] = z13 + z2; /* phase 6 */ 324 | data[dataOff + 3] = z13 - z2; 325 | data[dataOff + 1] = z11 + z4; 326 | data[dataOff + 7] = z11 - z4; 327 | 328 | dataOff += 8; /* advance pointer to next row */ 329 | } 330 | 331 | /* Pass 2: process columns. */ 332 | dataOff = 0; 333 | for (i = 0; i < I8; ++i) { 334 | d0 = data[dataOff]; 335 | d1 = data[dataOff + 8]; 336 | d2 = data[dataOff + 16]; 337 | d3 = data[dataOff + 24]; 338 | d4 = data[dataOff + 32]; 339 | d5 = data[dataOff + 40]; 340 | d6 = data[dataOff + 48]; 341 | d7 = data[dataOff + 56]; 342 | 343 | var tmp0p2 = d0 + d7; 344 | var tmp7p2 = d0 - d7; 345 | var tmp1p2 = d1 + d6; 346 | var tmp6p2 = d1 - d6; 347 | var tmp2p2 = d2 + d5; 348 | var tmp5p2 = d2 - d5; 349 | var tmp3p2 = d3 + d4; 350 | var tmp4p2 = d3 - d4; 351 | 352 | /* Even part */ 353 | var tmp10p2 = tmp0p2 + tmp3p2; /* phase 2 */ 354 | var tmp13p2 = tmp0p2 - tmp3p2; 355 | var tmp11p2 = tmp1p2 + tmp2p2; 356 | var tmp12p2 = tmp1p2 - tmp2p2; 357 | 358 | data[dataOff] = tmp10p2 + tmp11p2; /* phase 3 */ 359 | data[dataOff + 32] = tmp10p2 - tmp11p2; 360 | 361 | var z1p2 = (tmp12p2 + tmp13p2) * 0.707106781; /* c4 */ 362 | data[dataOff + 16] = tmp13p2 + z1p2; /* phase 5 */ 363 | data[dataOff + 48] = tmp13p2 - z1p2; 364 | 365 | /* Odd part */ 366 | tmp10p2 = tmp4p2 + tmp5p2; /* phase 2 */ 367 | tmp11p2 = tmp5p2 + tmp6p2; 368 | tmp12p2 = tmp6p2 + tmp7p2; 369 | 370 | /* The rotator is modified from fig 4-8 to avoid extra negations. */ 371 | var z5p2 = (tmp10p2 - tmp12p2) * 0.382683433; /* c6 */ 372 | var z2p2 = 0.541196100 * tmp10p2 + z5p2; /* c2-c6 */ 373 | var z4p2 = 1.306562965 * tmp12p2 + z5p2; /* c2+c6 */ 374 | var z3p2 = tmp11p2 * 0.707106781; /* c4 */ 375 | 376 | var z11p2 = tmp7p2 + z3p2; /* phase 5 */ 377 | var z13p2 = tmp7p2 - z3p2; 378 | 379 | data[dataOff + 40] = z13p2 + z2p2; /* phase 6 */ 380 | data[dataOff + 24] = z13p2 - z2p2; 381 | data[dataOff + 8] = z11p2 + z4p2; 382 | data[dataOff + 56] = z11p2 - z4p2; 383 | 384 | dataOff++; /* advance pointer to next column */ 385 | } 386 | 387 | // Quantize/descale the coefficients 388 | var fDCTQuant; 389 | for (i = 0; i < I64; ++i) { 390 | // Apply the quantization and scaling factor & Round to nearest integer 391 | fDCTQuant = data[i] * fdtbl[i]; 392 | outputfDCTQuant[i] = (fDCTQuant > 0.0) ? ((fDCTQuant + 0.5) | 0) : ((fDCTQuant - 0.5) | 0); 393 | //outputfDCTQuant[i] = fround(fDCTQuant); 394 | 395 | } 396 | return outputfDCTQuant; 397 | } 398 | 399 | function writeAPP0() { 400 | writeWord(0xFFE0); // marker 401 | writeWord(16); // length 402 | writeByte(0x4A); // J 403 | writeByte(0x46); // F 404 | writeByte(0x49); // I 405 | writeByte(0x46); // F 406 | writeByte(0); // = "JFIF",'\0' 407 | writeByte(1); // versionhi 408 | writeByte(1); // versionlo 409 | writeByte(0); // xyunits 410 | writeWord(1); // xdensity 411 | writeWord(1); // ydensity 412 | writeByte(0); // thumbnwidth 413 | writeByte(0); // thumbnheight 414 | } 415 | 416 | function writeSOF0(width, height) { 417 | writeWord(0xFFC0); // marker 418 | writeWord(17); // length, truecolor YUV JPG 419 | writeByte(8); // precision 420 | writeWord(height); 421 | writeWord(width); 422 | writeByte(3); // nrofcomponents 423 | writeByte(1); // IdY 424 | writeByte(0x11); // HVY 425 | writeByte(0); // QTY 426 | writeByte(2); // IdU 427 | writeByte(0x11); // HVU 428 | writeByte(1); // QTU 429 | writeByte(3); // IdV 430 | writeByte(0x11); // HVV 431 | writeByte(1); // QTV 432 | } 433 | 434 | function writeDQT() { 435 | writeWord(0xFFDB); // marker 436 | writeWord(132); // length 437 | writeByte(0); 438 | for (var i = 0; i < 64; i++) { 439 | writeByte(YTable[i]); 440 | } 441 | writeByte(1); 442 | for (var j = 0; j < 64; j++) { 443 | writeByte(UVTable[j]); 444 | } 445 | } 446 | 447 | function writeDHT() { 448 | writeWord(0xFFC4); // marker 449 | writeWord(0x01A2); // length 450 | 451 | writeByte(0); // HTYDCinfo 452 | for (var i = 0; i < 16; i++) { 453 | writeByte(std_dc_luminance_nrcodes[i + 1]); 454 | } 455 | for (var j = 0; j <= 11; j++) { 456 | writeByte(std_dc_luminance_values[j]); 457 | } 458 | 459 | writeByte(0x10); // HTYACinfo 460 | for (var k = 0; k < 16; k++) { 461 | writeByte(std_ac_luminance_nrcodes[k + 1]); 462 | } 463 | for (var l = 0; l <= 161; l++) { 464 | writeByte(std_ac_luminance_values[l]); 465 | } 466 | 467 | writeByte(1); // HTUDCinfo 468 | for (var m = 0; m < 16; m++) { 469 | writeByte(std_dc_chrominance_nrcodes[m + 1]); 470 | } 471 | for (var n = 0; n <= 11; n++) { 472 | writeByte(std_dc_chrominance_values[n]); 473 | } 474 | 475 | writeByte(0x11); // HTUACinfo 476 | for (var o = 0; o < 16; o++) { 477 | writeByte(std_ac_chrominance_nrcodes[o + 1]); 478 | } 479 | for (var p = 0; p <= 161; p++) { 480 | writeByte(std_ac_chrominance_values[p]); 481 | } 482 | } 483 | 484 | function writeSOS() { 485 | writeWord(0xFFDA); // marker 486 | writeWord(12); // length 487 | writeByte(3); // nrofcomponents 488 | writeByte(1); // IdY 489 | writeByte(0); // HTY 490 | writeByte(2); // IdU 491 | writeByte(0x11); // HTU 492 | writeByte(3); // IdV 493 | writeByte(0x11); // HTV 494 | writeByte(0); // Ss 495 | writeByte(0x3f); // Se 496 | writeByte(0); // Bf 497 | } 498 | 499 | function processDU(CDU, fdtbl, DC, HTDC, HTAC) { 500 | var EOB = HTAC[0x00]; 501 | var M16zeroes = HTAC[0xF0]; 502 | var pos; 503 | var I16 = 16; 504 | var I63 = 63; 505 | var I64 = 64; 506 | var DU_DCT = fDCTQuant(CDU, fdtbl); 507 | //ZigZag reorder 508 | for (var j = 0; j < I64; ++j) { 509 | DU[ZigZag[j]] = DU_DCT[j]; 510 | } 511 | var Diff = DU[0] - DC; DC = DU[0]; 512 | //Encode DC 513 | if (Diff == 0) { 514 | writeBits(HTDC[0]); // Diff might be 0 515 | } else { 516 | pos = 32767 + Diff; 517 | writeBits(HTDC[category[pos]]); 518 | writeBits(bitcode[pos]); 519 | } 520 | //Encode ACs 521 | var end0pos = 63; // was const... which is crazy 522 | while ((end0pos > 0) && (DU[end0pos] == 0)) { 523 | end0pos--; 524 | } 525 | //end0pos = first element in reverse order !=0 526 | if (end0pos == 0) { 527 | writeBits(EOB); 528 | return DC; 529 | } 530 | var i = 1; 531 | var lng; 532 | while (i <= end0pos) { 533 | var startpos = i; 534 | while ((DU[i] == 0) && (i <= end0pos)) { 535 | ++i; 536 | } 537 | var nrzeroes = i - startpos; 538 | if (nrzeroes >= I16) { 539 | lng = nrzeroes >> 4; 540 | for (var nrmarker = 1; nrmarker <= lng; ++nrmarker) 541 | writeBits(M16zeroes); 542 | nrzeroes = nrzeroes & 0xF; 543 | } 544 | pos = 32767 + DU[i]; 545 | writeBits(HTAC[(nrzeroes << 4) + category[pos]]); 546 | writeBits(bitcode[pos]); 547 | i++; 548 | } 549 | if (end0pos != I63) { 550 | writeBits(EOB); 551 | } 552 | return DC; 553 | } 554 | 555 | function initCharLookupTable() { 556 | var sfcc = String.fromCharCode; 557 | for (var i = 0; i < 256; i++) { ///// ACHTUNG // 255 558 | clt[i] = sfcc(i); 559 | } 560 | } 561 | 562 | this.encode = function (image, quality) // image data object 563 | { 564 | if (quality) setQuality(quality); 565 | 566 | // Initialize bit writer 567 | byteout = new Array(); 568 | bytenew = 0; 569 | bytepos = 7; 570 | 571 | // Add JPEG headers 572 | writeWord(0xFFD8); // SOI 573 | writeAPP0(); 574 | writeDQT(); 575 | writeSOF0(image.width, image.height); 576 | writeDHT(); 577 | writeSOS(); 578 | 579 | 580 | // Encode 8x8 macroblocks 581 | var DCY = 0; 582 | var DCU = 0; 583 | var DCV = 0; 584 | 585 | bytenew = 0; 586 | bytepos = 7; 587 | 588 | 589 | this.encode.displayName = "_encode_"; 590 | 591 | var imageData = image.data; 592 | var width = image.width; 593 | var height = image.height; 594 | 595 | var quadWidth = width * 4; 596 | 597 | var x, y = 0; 598 | var r, g, b; 599 | var start, p, col, row, pos; 600 | while (y < height) { 601 | x = 0; 602 | while (x < quadWidth) { 603 | start = quadWidth * y + x; 604 | col = -1; 605 | row = 0; 606 | 607 | for (pos = 0; pos < 64; pos++) { 608 | row = pos >> 3;// /8 609 | col = (pos & 7) * 4; // %8 610 | p = start + (row * quadWidth) + col; 611 | 612 | if (y + row >= height) { // padding bottom 613 | p -= (quadWidth * (y + 1 + row - height)); 614 | } 615 | 616 | if (x + col >= quadWidth) { // padding right 617 | p -= ((x + col) - quadWidth + 4); 618 | } 619 | 620 | r = imageData[p++]; 621 | g = imageData[p++]; 622 | b = imageData[p++]; 623 | 624 | 625 | /* // calculate YUV values dynamically 626 | YDU[pos]=((( 0.29900)*r+( 0.58700)*g+( 0.11400)*b))-128; //-0x80 627 | UDU[pos]=(((-0.16874)*r+(-0.33126)*g+( 0.50000)*b)); 628 | VDU[pos]=((( 0.50000)*r+(-0.41869)*g+(-0.08131)*b)); 629 | */ 630 | 631 | // use lookup table (slightly faster) 632 | YDU[pos] = ((RGB_YUV_TABLE[r] + RGB_YUV_TABLE[(g + 256) >> 0] + RGB_YUV_TABLE[(b + 512) >> 0]) >> 16) - 128; 633 | UDU[pos] = ((RGB_YUV_TABLE[(r + 768) >> 0] + RGB_YUV_TABLE[(g + 1024) >> 0] + RGB_YUV_TABLE[(b + 1280) >> 0]) >> 16) - 128; 634 | VDU[pos] = ((RGB_YUV_TABLE[(r + 1280) >> 0] + RGB_YUV_TABLE[(g + 1536) >> 0] + RGB_YUV_TABLE[(b + 1792) >> 0]) >> 16) - 128; 635 | 636 | } 637 | 638 | DCY = processDU(YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT); 639 | DCU = processDU(UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); 640 | DCV = processDU(VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); 641 | x += 32; 642 | } 643 | y += 8; 644 | } 645 | 646 | 647 | //////////////////////////////////////////////////////////////// 648 | 649 | // Do the bit alignment of the EOI marker 650 | if (bytepos >= 0) { 651 | var fillbits = []; 652 | fillbits[1] = bytepos + 1; 653 | fillbits[0] = (1 << (bytepos + 1)) - 1; 654 | writeBits(fillbits); 655 | } 656 | 657 | writeWord(0xFFD9); //EOI 658 | 659 | return new Uint8Array(byteout); 660 | }; 661 | 662 | function setQuality(quality) { 663 | quality = Math.min(Math.max(quality, 1), 100); 664 | 665 | if (currentQuality == quality) return // don't recalc if unchanged 666 | 667 | var sf = (quality < 50) ? Math.floor(5000 / quality) : Math.floor(200 - quality * 2); 668 | 669 | initQuantTables(sf); 670 | currentQuality = quality; 671 | //console.log('Quality set to: '+quality +'%'); 672 | } 673 | 674 | function init() { 675 | quality = quality || 50; 676 | // Create tables 677 | initCharLookupTable() 678 | initHuffmanTbl(); 679 | initCategoryNumber(); 680 | initRGBYUVTable(); 681 | 682 | setQuality(quality); 683 | } 684 | init(); 685 | } 686 | 687 | // eslint-disable-next-line no-empty 688 | exports.JPEGEncoder = JPEGEncoder; -------------------------------------------------------------------------------- /src/lib/backend/jpg.js: -------------------------------------------------------------------------------- 1 | /* Copyright 2014 Mozilla Foundation 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the 'License'); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an 'AS IS' BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | const BaseException = (function BaseExceptionClosure() { 17 | // eslint-disable-next-line no-shadow 18 | function BaseException(message) { 19 | if (this.constructor === BaseException) { 20 | unreachable("Cannot initialize BaseException."); 21 | } 22 | this.message = message; 23 | this.name = this.constructor.name; 24 | } 25 | BaseException.prototype = new Error(); 26 | BaseException.constructor = BaseException; 27 | 28 | return BaseException; 29 | })(); 30 | 31 | const VerbosityLevel = { 32 | ERRORS: 0, 33 | WARNINGS: 1, 34 | INFOS: 5, 35 | }; 36 | 37 | 38 | // Non-fatal warnings. 39 | function warn(msg) { 40 | if (verbosity >= VerbosityLevel.WARNINGS) { 41 | console.log(`Warning: ${msg}`); 42 | } 43 | } 44 | 45 | function unreachable(msg) { 46 | throw new Error(msg); 47 | } 48 | 49 | function assert(cond, msg) { 50 | if (!cond) { 51 | unreachable(msg); 52 | } 53 | } 54 | 55 | function readUint16(data, offset) { 56 | return (data[offset] << 8) | data[offset + 1]; 57 | } 58 | 59 | class JpegError extends BaseException { 60 | constructor(msg) { 61 | super(`JPEG error: ${msg}`); 62 | } 63 | } 64 | 65 | class DNLMarkerError extends BaseException { 66 | constructor(message, scanLines) { 67 | super(message); 68 | this.scanLines = scanLines; 69 | } 70 | } 71 | 72 | class EOIMarkerError extends BaseException {} 73 | 74 | /** 75 | * This code was forked from https://github.com/notmasteryet/jpgjs. 76 | * The original version was created by GitHub user notmasteryet. 77 | * 78 | * - The JPEG specification can be found in the ITU CCITT Recommendation T.81 79 | * (www.w3.org/Graphics/JPEG/itu-t81.pdf) 80 | * - The JFIF specification can be found in the JPEG File Interchange Format 81 | * (www.w3.org/Graphics/JPEG/jfif3.pdf) 82 | * - The Adobe Application-Specific JPEG markers in the 83 | * Supporting the DCT Filters in PostScript Level 2, Technical Note #5116 84 | * (partners.adobe.com/public/developer/en/ps/sdk/5116.DCT_Filter.pdf) 85 | */ 86 | 87 | var JpegImage = (function JpegImageClosure() { 88 | // prettier-ignore 89 | var dctZigZag = new Uint8Array([ 90 | 0, 91 | 1, 8, 92 | 16, 9, 2, 93 | 3, 10, 17, 24, 94 | 32, 25, 18, 11, 4, 95 | 5, 12, 19, 26, 33, 40, 96 | 48, 41, 34, 27, 20, 13, 6, 97 | 7, 14, 21, 28, 35, 42, 49, 56, 98 | 57, 50, 43, 36, 29, 22, 15, 99 | 23, 30, 37, 44, 51, 58, 100 | 59, 52, 45, 38, 31, 101 | 39, 46, 53, 60, 102 | 61, 54, 47, 103 | 55, 62, 104 | 63 105 | ]); 106 | 107 | var dctCos1 = 4017; // cos(pi/16) 108 | var dctSin1 = 799; // sin(pi/16) 109 | var dctCos3 = 3406; // cos(3*pi/16) 110 | var dctSin3 = 2276; // sin(3*pi/16) 111 | var dctCos6 = 1567; // cos(6*pi/16) 112 | var dctSin6 = 3784; // sin(6*pi/16) 113 | var dctSqrt2 = 5793; // sqrt(2) 114 | var dctSqrt1d2 = 2896; // sqrt(2) / 2 115 | 116 | // eslint-disable-next-line no-shadow 117 | function JpegImage({ decodeTransform = null, colorTransform = -1 } = {}) { 118 | this._decodeTransform = decodeTransform; 119 | this._colorTransform = colorTransform; 120 | } 121 | 122 | function buildHuffmanTable(codeLengths, values) { 123 | var k = 0, 124 | code = [], 125 | i, 126 | j, 127 | length = 16; 128 | while (length > 0 && !codeLengths[length - 1]) { 129 | length--; 130 | } 131 | code.push({ children: [], index: 0 }); 132 | var p = code[0], 133 | q; 134 | for (i = 0; i < length; i++) { 135 | for (j = 0; j < codeLengths[i]; j++) { 136 | p = code.pop(); 137 | p.children[p.index] = values[k]; 138 | while (p.index > 0) { 139 | p = code.pop(); 140 | } 141 | p.index++; 142 | code.push(p); 143 | while (code.length <= i) { 144 | code.push((q = { children: [], index: 0 })); 145 | p.children[p.index] = q.children; 146 | p = q; 147 | } 148 | k++; 149 | } 150 | if (i + 1 < length) { 151 | // p here points to last code 152 | code.push((q = { children: [], index: 0 })); 153 | p.children[p.index] = q.children; 154 | p = q; 155 | } 156 | } 157 | return code[0].children; 158 | } 159 | 160 | function getBlockBufferOffset(component, row, col) { 161 | return 64 * ((component.blocksPerLine + 1) * row + col); 162 | } 163 | 164 | function decodeScan( 165 | data, 166 | offset, 167 | frame, 168 | components, 169 | resetInterval, 170 | spectralStart, 171 | spectralEnd, 172 | successivePrev, 173 | successive, 174 | parseDNLMarker = false 175 | ) { 176 | var mcusPerLine = frame.mcusPerLine; 177 | var progressive = frame.progressive; 178 | 179 | const startOffset = offset; 180 | let bitsData = 0, 181 | bitsCount = 0; 182 | 183 | function readBit() { 184 | if (bitsCount > 0) { 185 | bitsCount--; 186 | return (bitsData >> bitsCount) & 1; 187 | } 188 | bitsData = data[offset++]; 189 | if (bitsData === 0xff) { 190 | var nextByte = data[offset++]; 191 | if (nextByte) { 192 | if (nextByte === /* DNL = */ 0xdc && parseDNLMarker) { 193 | offset += 2; // Skip marker length. 194 | 195 | const scanLines = readUint16(data, offset); 196 | offset += 2; 197 | if (scanLines > 0 && scanLines !== frame.scanLines) { 198 | throw new DNLMarkerError( 199 | "Found DNL marker (0xFFDC) while parsing scan data", 200 | scanLines 201 | ); 202 | } 203 | } else if (nextByte === /* EOI = */ 0xd9) { 204 | if (parseDNLMarker) { 205 | // NOTE: only 8-bit JPEG images are supported in this decoder. 206 | const maybeScanLines = blockRow * (frame.precision === 8 ? 8 : 0); 207 | // Heuristic to attempt to handle corrupt JPEG images with too 208 | // large `scanLines` parameter, by falling back to the currently 209 | // parsed number of scanLines when it's at least (approximately) 210 | // one order of magnitude smaller than expected (fixes 211 | // issue10880.pdf and issue10989.pdf). 212 | if ( 213 | maybeScanLines > 0 && 214 | Math.round(frame.scanLines / maybeScanLines) >= 10 215 | ) { 216 | throw new DNLMarkerError( 217 | "Found EOI marker (0xFFD9) while parsing scan data, " + 218 | "possibly caused by incorrect `scanLines` parameter", 219 | maybeScanLines 220 | ); 221 | } 222 | } 223 | throw new EOIMarkerError( 224 | "Found EOI marker (0xFFD9) while parsing scan data" 225 | ); 226 | } 227 | throw new JpegError( 228 | `unexpected marker ${((bitsData << 8) | nextByte).toString(16)}` 229 | ); 230 | } 231 | // unstuff 0 232 | } 233 | bitsCount = 7; 234 | return bitsData >>> 7; 235 | } 236 | 237 | function decodeHuffman(tree) { 238 | var node = tree; 239 | while (true) { 240 | node = node[readBit()]; 241 | switch (typeof node) { 242 | case "number": 243 | return node; 244 | case "object": 245 | continue; 246 | } 247 | throw new JpegError("invalid huffman sequence"); 248 | } 249 | } 250 | 251 | function receive(length) { 252 | var n = 0; 253 | while (length > 0) { 254 | n = (n << 1) | readBit(); 255 | length--; 256 | } 257 | return n; 258 | } 259 | 260 | function receiveAndExtend(length) { 261 | if (length === 1) { 262 | return readBit() === 1 ? 1 : -1; 263 | } 264 | var n = receive(length); 265 | if (n >= 1 << (length - 1)) { 266 | return n; 267 | } 268 | return n + (-1 << length) + 1; 269 | } 270 | 271 | function decodeBaseline(component, blockOffset) { 272 | var t = decodeHuffman(component.huffmanTableDC); 273 | var diff = t === 0 ? 0 : receiveAndExtend(t); 274 | component.blockData[blockOffset] = component.pred += diff; 275 | var k = 1; 276 | while (k < 64) { 277 | var rs = decodeHuffman(component.huffmanTableAC); 278 | var s = rs & 15, 279 | r = rs >> 4; 280 | if (s === 0) { 281 | if (r < 15) { 282 | break; 283 | } 284 | k += 16; 285 | continue; 286 | } 287 | k += r; 288 | var z = dctZigZag[k]; 289 | component.blockData[blockOffset + z] = receiveAndExtend(s); 290 | k++; 291 | } 292 | } 293 | 294 | function decodeDCFirst(component, blockOffset) { 295 | var t = decodeHuffman(component.huffmanTableDC); 296 | var diff = t === 0 ? 0 : receiveAndExtend(t) << successive; 297 | component.blockData[blockOffset] = component.pred += diff; 298 | } 299 | 300 | function decodeDCSuccessive(component, blockOffset) { 301 | component.blockData[blockOffset] |= readBit() << successive; 302 | } 303 | 304 | var eobrun = 0; 305 | function decodeACFirst(component, blockOffset) { 306 | if (eobrun > 0) { 307 | eobrun--; 308 | return; 309 | } 310 | var k = spectralStart, 311 | e = spectralEnd; 312 | while (k <= e) { 313 | var rs = decodeHuffman(component.huffmanTableAC); 314 | var s = rs & 15, 315 | r = rs >> 4; 316 | if (s === 0) { 317 | if (r < 15) { 318 | eobrun = receive(r) + (1 << r) - 1; 319 | break; 320 | } 321 | k += 16; 322 | continue; 323 | } 324 | k += r; 325 | var z = dctZigZag[k]; 326 | component.blockData[blockOffset + z] = 327 | receiveAndExtend(s) * (1 << successive); 328 | k++; 329 | } 330 | } 331 | 332 | var successiveACState = 0, 333 | successiveACNextValue; 334 | function decodeACSuccessive(component, blockOffset) { 335 | var k = spectralStart; 336 | var e = spectralEnd; 337 | var r = 0; 338 | var s; 339 | var rs; 340 | while (k <= e) { 341 | const offsetZ = blockOffset + dctZigZag[k]; 342 | const sign = component.blockData[offsetZ] < 0 ? -1 : 1; 343 | switch (successiveACState) { 344 | case 0: // initial state 345 | rs = decodeHuffman(component.huffmanTableAC); 346 | s = rs & 15; 347 | r = rs >> 4; 348 | if (s === 0) { 349 | if (r < 15) { 350 | eobrun = receive(r) + (1 << r); 351 | successiveACState = 4; 352 | } else { 353 | r = 16; 354 | successiveACState = 1; 355 | } 356 | } else { 357 | if (s !== 1) { 358 | throw new JpegError("invalid ACn encoding"); 359 | } 360 | successiveACNextValue = receiveAndExtend(s); 361 | successiveACState = r ? 2 : 3; 362 | } 363 | continue; 364 | case 1: // skipping r zero items 365 | case 2: 366 | if (component.blockData[offsetZ]) { 367 | component.blockData[offsetZ] += sign * (readBit() << successive); 368 | } else { 369 | r--; 370 | if (r === 0) { 371 | successiveACState = successiveACState === 2 ? 3 : 0; 372 | } 373 | } 374 | break; 375 | case 3: // set value for a zero item 376 | if (component.blockData[offsetZ]) { 377 | component.blockData[offsetZ] += sign * (readBit() << successive); 378 | } else { 379 | component.blockData[offsetZ] = 380 | successiveACNextValue << successive; 381 | successiveACState = 0; 382 | } 383 | break; 384 | case 4: // eob 385 | if (component.blockData[offsetZ]) { 386 | component.blockData[offsetZ] += sign * (readBit() << successive); 387 | } 388 | break; 389 | } 390 | k++; 391 | } 392 | if (successiveACState === 4) { 393 | eobrun--; 394 | if (eobrun === 0) { 395 | successiveACState = 0; 396 | } 397 | } 398 | } 399 | 400 | let blockRow = 0; 401 | function decodeMcu(component, decode, mcu, row, col) { 402 | var mcuRow = (mcu / mcusPerLine) | 0; 403 | var mcuCol = mcu % mcusPerLine; 404 | blockRow = mcuRow * component.v + row; 405 | var blockCol = mcuCol * component.h + col; 406 | const blockOffset = getBlockBufferOffset(component, blockRow, blockCol); 407 | decode(component, blockOffset); 408 | } 409 | 410 | function decodeBlock(component, decode, mcu) { 411 | blockRow = (mcu / component.blocksPerLine) | 0; 412 | var blockCol = mcu % component.blocksPerLine; 413 | const blockOffset = getBlockBufferOffset(component, blockRow, blockCol); 414 | decode(component, blockOffset); 415 | } 416 | 417 | var componentsLength = components.length; 418 | var component, i, j, k, n; 419 | var decodeFn; 420 | if (progressive) { 421 | if (spectralStart === 0) { 422 | decodeFn = successivePrev === 0 ? decodeDCFirst : decodeDCSuccessive; 423 | } else { 424 | decodeFn = successivePrev === 0 ? decodeACFirst : decodeACSuccessive; 425 | } 426 | } else { 427 | decodeFn = decodeBaseline; 428 | } 429 | 430 | var mcu = 0, 431 | fileMarker; 432 | var mcuExpected; 433 | if (componentsLength === 1) { 434 | mcuExpected = components[0].blocksPerLine * components[0].blocksPerColumn; 435 | } else { 436 | mcuExpected = mcusPerLine * frame.mcusPerColumn; 437 | } 438 | 439 | var h, v; 440 | while (mcu <= mcuExpected) { 441 | // reset interval stuff 442 | var mcuToRead = resetInterval 443 | ? Math.min(mcuExpected - mcu, resetInterval) 444 | : mcuExpected; 445 | 446 | // The `mcuToRead === 0` case should only occur when all of the expected 447 | // MCU data has been already parsed, i.e. when `mcu === mcuExpected`, but 448 | // some corrupt JPEG images contain more data than intended and we thus 449 | // want to skip over any extra RSTx markers below (fixes issue11794.pdf). 450 | if (mcuToRead > 0) { 451 | for (i = 0; i < componentsLength; i++) { 452 | components[i].pred = 0; 453 | } 454 | eobrun = 0; 455 | 456 | if (componentsLength === 1) { 457 | component = components[0]; 458 | for (n = 0; n < mcuToRead; n++) { 459 | decodeBlock(component, decodeFn, mcu); 460 | mcu++; 461 | } 462 | } else { 463 | for (n = 0; n < mcuToRead; n++) { 464 | for (i = 0; i < componentsLength; i++) { 465 | component = components[i]; 466 | h = component.h; 467 | v = component.v; 468 | for (j = 0; j < v; j++) { 469 | for (k = 0; k < h; k++) { 470 | decodeMcu(component, decodeFn, mcu, j, k); 471 | } 472 | } 473 | } 474 | mcu++; 475 | } 476 | } 477 | } 478 | 479 | // find marker 480 | bitsCount = 0; 481 | fileMarker = findNextFileMarker(data, offset); 482 | if (!fileMarker) { 483 | break; // Reached the end of the image data without finding any marker. 484 | } 485 | if (fileMarker.invalid) { 486 | // Some bad images seem to pad Scan blocks with e.g. zero bytes, skip 487 | // past those to attempt to find a valid marker (fixes issue4090.pdf). 488 | const partialMsg = mcuToRead > 0 ? "unexpected" : "excessive"; 489 | warn( 490 | `decodeScan - ${partialMsg} MCU data, current marker is: ${fileMarker.invalid}` 491 | ); 492 | offset = fileMarker.offset; 493 | } 494 | if (fileMarker.marker >= 0xffd0 && fileMarker.marker <= 0xffd7) { 495 | // RSTx 496 | offset += 2; 497 | } else { 498 | break; 499 | } 500 | } 501 | 502 | return offset - startOffset; 503 | } 504 | 505 | // A port of poppler's IDCT method which in turn is taken from: 506 | // Christoph Loeffler, Adriaan Ligtenberg, George S. Moschytz, 507 | // 'Practical Fast 1-D DCT Algorithms with 11 Multiplications', 508 | // IEEE Intl. Conf. on Acoustics, Speech & Signal Processing, 1989, 509 | // 988-991. 510 | function quantizeAndInverse(component, blockBufferOffset, p) { 511 | var qt = component.quantizationTable, 512 | blockData = component.blockData; 513 | var v0, v1, v2, v3, v4, v5, v6, v7; 514 | var p0, p1, p2, p3, p4, p5, p6, p7; 515 | var t; 516 | 517 | if (!qt) { 518 | throw new JpegError("missing required Quantization Table."); 519 | } 520 | 521 | // inverse DCT on rows 522 | for (var row = 0; row < 64; row += 8) { 523 | // gather block data 524 | p0 = blockData[blockBufferOffset + row]; 525 | p1 = blockData[blockBufferOffset + row + 1]; 526 | p2 = blockData[blockBufferOffset + row + 2]; 527 | p3 = blockData[blockBufferOffset + row + 3]; 528 | p4 = blockData[blockBufferOffset + row + 4]; 529 | p5 = blockData[blockBufferOffset + row + 5]; 530 | p6 = blockData[blockBufferOffset + row + 6]; 531 | p7 = blockData[blockBufferOffset + row + 7]; 532 | 533 | // dequant p0 534 | p0 *= qt[row]; 535 | 536 | // check for all-zero AC coefficients 537 | if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) === 0) { 538 | t = (dctSqrt2 * p0 + 512) >> 10; 539 | p[row] = t; 540 | p[row + 1] = t; 541 | p[row + 2] = t; 542 | p[row + 3] = t; 543 | p[row + 4] = t; 544 | p[row + 5] = t; 545 | p[row + 6] = t; 546 | p[row + 7] = t; 547 | continue; 548 | } 549 | // dequant p1 ... p7 550 | p1 *= qt[row + 1]; 551 | p2 *= qt[row + 2]; 552 | p3 *= qt[row + 3]; 553 | p4 *= qt[row + 4]; 554 | p5 *= qt[row + 5]; 555 | p6 *= qt[row + 6]; 556 | p7 *= qt[row + 7]; 557 | 558 | // stage 4 559 | v0 = (dctSqrt2 * p0 + 128) >> 8; 560 | v1 = (dctSqrt2 * p4 + 128) >> 8; 561 | v2 = p2; 562 | v3 = p6; 563 | v4 = (dctSqrt1d2 * (p1 - p7) + 128) >> 8; 564 | v7 = (dctSqrt1d2 * (p1 + p7) + 128) >> 8; 565 | v5 = p3 << 4; 566 | v6 = p5 << 4; 567 | 568 | // stage 3 569 | v0 = (v0 + v1 + 1) >> 1; 570 | v1 = v0 - v1; 571 | t = (v2 * dctSin6 + v3 * dctCos6 + 128) >> 8; 572 | v2 = (v2 * dctCos6 - v3 * dctSin6 + 128) >> 8; 573 | v3 = t; 574 | v4 = (v4 + v6 + 1) >> 1; 575 | v6 = v4 - v6; 576 | v7 = (v7 + v5 + 1) >> 1; 577 | v5 = v7 - v5; 578 | 579 | // stage 2 580 | v0 = (v0 + v3 + 1) >> 1; 581 | v3 = v0 - v3; 582 | v1 = (v1 + v2 + 1) >> 1; 583 | v2 = v1 - v2; 584 | t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12; 585 | v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12; 586 | v7 = t; 587 | t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12; 588 | v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12; 589 | v6 = t; 590 | 591 | // stage 1 592 | p[row] = v0 + v7; 593 | p[row + 7] = v0 - v7; 594 | p[row + 1] = v1 + v6; 595 | p[row + 6] = v1 - v6; 596 | p[row + 2] = v2 + v5; 597 | p[row + 5] = v2 - v5; 598 | p[row + 3] = v3 + v4; 599 | p[row + 4] = v3 - v4; 600 | } 601 | 602 | // inverse DCT on columns 603 | for (var col = 0; col < 8; ++col) { 604 | p0 = p[col]; 605 | p1 = p[col + 8]; 606 | p2 = p[col + 16]; 607 | p3 = p[col + 24]; 608 | p4 = p[col + 32]; 609 | p5 = p[col + 40]; 610 | p6 = p[col + 48]; 611 | p7 = p[col + 56]; 612 | 613 | // check for all-zero AC coefficients 614 | if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) === 0) { 615 | t = (dctSqrt2 * p0 + 8192) >> 14; 616 | // Convert to 8-bit. 617 | if (t < -2040) { 618 | t = 0; 619 | } else if (t >= 2024) { 620 | t = 255; 621 | } else { 622 | t = (t + 2056) >> 4; 623 | } 624 | blockData[blockBufferOffset + col] = t; 625 | blockData[blockBufferOffset + col + 8] = t; 626 | blockData[blockBufferOffset + col + 16] = t; 627 | blockData[blockBufferOffset + col + 24] = t; 628 | blockData[blockBufferOffset + col + 32] = t; 629 | blockData[blockBufferOffset + col + 40] = t; 630 | blockData[blockBufferOffset + col + 48] = t; 631 | blockData[blockBufferOffset + col + 56] = t; 632 | continue; 633 | } 634 | 635 | // stage 4 636 | v0 = (dctSqrt2 * p0 + 2048) >> 12; 637 | v1 = (dctSqrt2 * p4 + 2048) >> 12; 638 | v2 = p2; 639 | v3 = p6; 640 | v4 = (dctSqrt1d2 * (p1 - p7) + 2048) >> 12; 641 | v7 = (dctSqrt1d2 * (p1 + p7) + 2048) >> 12; 642 | v5 = p3; 643 | v6 = p5; 644 | 645 | // stage 3 646 | // Shift v0 by 128.5 << 5 here, so we don't need to shift p0...p7 when 647 | // converting to UInt8 range later. 648 | v0 = ((v0 + v1 + 1) >> 1) + 4112; 649 | v1 = v0 - v1; 650 | t = (v2 * dctSin6 + v3 * dctCos6 + 2048) >> 12; 651 | v2 = (v2 * dctCos6 - v3 * dctSin6 + 2048) >> 12; 652 | v3 = t; 653 | v4 = (v4 + v6 + 1) >> 1; 654 | v6 = v4 - v6; 655 | v7 = (v7 + v5 + 1) >> 1; 656 | v5 = v7 - v5; 657 | 658 | // stage 2 659 | v0 = (v0 + v3 + 1) >> 1; 660 | v3 = v0 - v3; 661 | v1 = (v1 + v2 + 1) >> 1; 662 | v2 = v1 - v2; 663 | t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12; 664 | v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12; 665 | v7 = t; 666 | t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12; 667 | v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12; 668 | v6 = t; 669 | 670 | // stage 1 671 | p0 = v0 + v7; 672 | p7 = v0 - v7; 673 | p1 = v1 + v6; 674 | p6 = v1 - v6; 675 | p2 = v2 + v5; 676 | p5 = v2 - v5; 677 | p3 = v3 + v4; 678 | p4 = v3 - v4; 679 | 680 | // Convert to 8-bit integers. 681 | if (p0 < 16) { 682 | p0 = 0; 683 | } else if (p0 >= 4080) { 684 | p0 = 255; 685 | } else { 686 | p0 >>= 4; 687 | } 688 | if (p1 < 16) { 689 | p1 = 0; 690 | } else if (p1 >= 4080) { 691 | p1 = 255; 692 | } else { 693 | p1 >>= 4; 694 | } 695 | if (p2 < 16) { 696 | p2 = 0; 697 | } else if (p2 >= 4080) { 698 | p2 = 255; 699 | } else { 700 | p2 >>= 4; 701 | } 702 | if (p3 < 16) { 703 | p3 = 0; 704 | } else if (p3 >= 4080) { 705 | p3 = 255; 706 | } else { 707 | p3 >>= 4; 708 | } 709 | if (p4 < 16) { 710 | p4 = 0; 711 | } else if (p4 >= 4080) { 712 | p4 = 255; 713 | } else { 714 | p4 >>= 4; 715 | } 716 | if (p5 < 16) { 717 | p5 = 0; 718 | } else if (p5 >= 4080) { 719 | p5 = 255; 720 | } else { 721 | p5 >>= 4; 722 | } 723 | if (p6 < 16) { 724 | p6 = 0; 725 | } else if (p6 >= 4080) { 726 | p6 = 255; 727 | } else { 728 | p6 >>= 4; 729 | } 730 | if (p7 < 16) { 731 | p7 = 0; 732 | } else if (p7 >= 4080) { 733 | p7 = 255; 734 | } else { 735 | p7 >>= 4; 736 | } 737 | 738 | // store block data 739 | blockData[blockBufferOffset + col] = p0; 740 | blockData[blockBufferOffset + col + 8] = p1; 741 | blockData[blockBufferOffset + col + 16] = p2; 742 | blockData[blockBufferOffset + col + 24] = p3; 743 | blockData[blockBufferOffset + col + 32] = p4; 744 | blockData[blockBufferOffset + col + 40] = p5; 745 | blockData[blockBufferOffset + col + 48] = p6; 746 | blockData[blockBufferOffset + col + 56] = p7; 747 | } 748 | } 749 | 750 | function buildComponentData(frame, component) { 751 | var blocksPerLine = component.blocksPerLine; 752 | var blocksPerColumn = component.blocksPerColumn; 753 | var computationBuffer = new Int16Array(64); 754 | 755 | for (var blockRow = 0; blockRow < blocksPerColumn; blockRow++) { 756 | for (var blockCol = 0; blockCol < blocksPerLine; blockCol++) { 757 | var offset = getBlockBufferOffset(component, blockRow, blockCol); 758 | quantizeAndInverse(component, offset, computationBuffer); 759 | } 760 | } 761 | return component.blockData; 762 | } 763 | 764 | function findNextFileMarker(data, currentPos, startPos = currentPos) { 765 | const maxPos = data.length - 1; 766 | var newPos = startPos < currentPos ? startPos : currentPos; 767 | 768 | if (currentPos >= maxPos) { 769 | return null; // Don't attempt to read non-existent data and just return. 770 | } 771 | var currentMarker = readUint16(data, currentPos); 772 | if (currentMarker >= 0xffc0 && currentMarker <= 0xfffe) { 773 | return { 774 | invalid: null, 775 | marker: currentMarker, 776 | offset: currentPos, 777 | }; 778 | } 779 | var newMarker = readUint16(data, newPos); 780 | while (!(newMarker >= 0xffc0 && newMarker <= 0xfffe)) { 781 | if (++newPos >= maxPos) { 782 | return null; // Don't attempt to read non-existent data and just return. 783 | } 784 | newMarker = readUint16(data, newPos); 785 | } 786 | return { 787 | invalid: currentMarker.toString(16), 788 | marker: newMarker, 789 | offset: newPos, 790 | }; 791 | } 792 | 793 | JpegImage.prototype = { 794 | parse(data, { dnlScanLines = null } = {}) { 795 | function readDataBlock() { 796 | const length = readUint16(data, offset); 797 | offset += 2; 798 | let endOffset = offset + length - 2; 799 | 800 | var fileMarker = findNextFileMarker(data, endOffset, offset); 801 | if (fileMarker && fileMarker.invalid) { 802 | warn( 803 | "readDataBlock - incorrect length, current marker is: " + 804 | fileMarker.invalid 805 | ); 806 | endOffset = fileMarker.offset; 807 | } 808 | 809 | var array = data.subarray(offset, endOffset); 810 | offset += array.length; 811 | return array; 812 | } 813 | 814 | function prepareComponents(frame) { 815 | var mcusPerLine = Math.ceil(frame.samplesPerLine / 8 / frame.maxH); 816 | var mcusPerColumn = Math.ceil(frame.scanLines / 8 / frame.maxV); 817 | for (var i = 0; i < frame.components.length; i++) { 818 | component = frame.components[i]; 819 | var blocksPerLine = Math.ceil( 820 | (Math.ceil(frame.samplesPerLine / 8) * component.h) / frame.maxH 821 | ); 822 | var blocksPerColumn = Math.ceil( 823 | (Math.ceil(frame.scanLines / 8) * component.v) / frame.maxV 824 | ); 825 | var blocksPerLineForMcu = mcusPerLine * component.h; 826 | var blocksPerColumnForMcu = mcusPerColumn * component.v; 827 | 828 | var blocksBufferSize = 829 | 64 * blocksPerColumnForMcu * (blocksPerLineForMcu + 1); 830 | component.blockData = new Int16Array(blocksBufferSize); 831 | component.blocksPerLine = blocksPerLine; 832 | component.blocksPerColumn = blocksPerColumn; 833 | } 834 | frame.mcusPerLine = mcusPerLine; 835 | frame.mcusPerColumn = mcusPerColumn; 836 | } 837 | 838 | var offset = 0; 839 | var jfif = null; 840 | var adobe = null; 841 | var frame, resetInterval; 842 | let numSOSMarkers = 0; 843 | var quantizationTables = []; 844 | var huffmanTablesAC = [], 845 | huffmanTablesDC = []; 846 | 847 | let fileMarker = readUint16(data, offset); 848 | offset += 2; 849 | if (fileMarker !== /* SOI (Start of Image) = */ 0xffd8) { 850 | throw new JpegError("SOI not found"); 851 | } 852 | fileMarker = readUint16(data, offset); 853 | offset += 2; 854 | 855 | markerLoop: while (fileMarker !== /* EOI (End of Image) = */ 0xffd9) { 856 | var i, j, l; 857 | switch (fileMarker) { 858 | case 0xffe0: // APP0 (Application Specific) 859 | case 0xffe1: // APP1 860 | case 0xffe2: // APP2 861 | case 0xffe3: // APP3 862 | case 0xffe4: // APP4 863 | case 0xffe5: // APP5 864 | case 0xffe6: // APP6 865 | case 0xffe7: // APP7 866 | case 0xffe8: // APP8 867 | case 0xffe9: // APP9 868 | case 0xffea: // APP10 869 | case 0xffeb: // APP11 870 | case 0xffec: // APP12 871 | case 0xffed: // APP13 872 | case 0xffee: // APP14 873 | case 0xffef: // APP15 874 | case 0xfffe: // COM (Comment) 875 | var appData = readDataBlock(); 876 | 877 | if (fileMarker === 0xffe0) { 878 | // 'JFIF\x00' 879 | if ( 880 | appData[0] === 0x4a && 881 | appData[1] === 0x46 && 882 | appData[2] === 0x49 && 883 | appData[3] === 0x46 && 884 | appData[4] === 0 885 | ) { 886 | jfif = { 887 | version: { major: appData[5], minor: appData[6] }, 888 | densityUnits: appData[7], 889 | xDensity: (appData[8] << 8) | appData[9], 890 | yDensity: (appData[10] << 8) | appData[11], 891 | thumbWidth: appData[12], 892 | thumbHeight: appData[13], 893 | thumbData: appData.subarray( 894 | 14, 895 | 14 + 3 * appData[12] * appData[13] 896 | ), 897 | }; 898 | } 899 | } 900 | // TODO APP1 - Exif 901 | if (fileMarker === 0xffee) { 902 | // 'Adobe' 903 | if ( 904 | appData[0] === 0x41 && 905 | appData[1] === 0x64 && 906 | appData[2] === 0x6f && 907 | appData[3] === 0x62 && 908 | appData[4] === 0x65 909 | ) { 910 | adobe = { 911 | version: (appData[5] << 8) | appData[6], 912 | flags0: (appData[7] << 8) | appData[8], 913 | flags1: (appData[9] << 8) | appData[10], 914 | transformCode: appData[11], 915 | }; 916 | } 917 | } 918 | break; 919 | 920 | case 0xffdb: // DQT (Define Quantization Tables) 921 | const quantizationTablesLength = readUint16(data, offset); 922 | offset += 2; 923 | var quantizationTablesEnd = quantizationTablesLength + offset - 2; 924 | var z; 925 | while (offset < quantizationTablesEnd) { 926 | var quantizationTableSpec = data[offset++]; 927 | var tableData = new Uint16Array(64); 928 | if (quantizationTableSpec >> 4 === 0) { 929 | // 8 bit values 930 | for (j = 0; j < 64; j++) { 931 | z = dctZigZag[j]; 932 | tableData[z] = data[offset++]; 933 | } 934 | } else if (quantizationTableSpec >> 4 === 1) { 935 | // 16 bit values 936 | for (j = 0; j < 64; j++) { 937 | z = dctZigZag[j]; 938 | tableData[z] = readUint16(data, offset); 939 | offset += 2; 940 | } 941 | } else { 942 | throw new JpegError("DQT - invalid table spec"); 943 | } 944 | quantizationTables[quantizationTableSpec & 15] = tableData; 945 | } 946 | break; 947 | 948 | case 0xffc0: // SOF0 (Start of Frame, Baseline DCT) 949 | case 0xffc1: // SOF1 (Start of Frame, Extended DCT) 950 | case 0xffc2: // SOF2 (Start of Frame, Progressive DCT) 951 | if (frame) { 952 | throw new JpegError("Only single frame JPEGs supported"); 953 | } 954 | offset += 2; // Skip marker length. 955 | 956 | frame = {}; 957 | frame.extended = fileMarker === 0xffc1; 958 | frame.progressive = fileMarker === 0xffc2; 959 | frame.precision = data[offset++]; 960 | const sofScanLines = readUint16(data, offset); 961 | offset += 2; 962 | frame.scanLines = dnlScanLines || sofScanLines; 963 | frame.samplesPerLine = readUint16(data, offset); 964 | offset += 2; 965 | frame.components = []; 966 | frame.componentIds = {}; 967 | var componentsCount = data[offset++], 968 | componentId; 969 | var maxH = 0, 970 | maxV = 0; 971 | for (i = 0; i < componentsCount; i++) { 972 | componentId = data[offset]; 973 | var h = data[offset + 1] >> 4; 974 | var v = data[offset + 1] & 15; 975 | if (maxH < h) { 976 | maxH = h; 977 | } 978 | if (maxV < v) { 979 | maxV = v; 980 | } 981 | var qId = data[offset + 2]; 982 | l = frame.components.push({ 983 | h, 984 | v, 985 | quantizationId: qId, 986 | quantizationTable: null, // See comment below. 987 | }); 988 | frame.componentIds[componentId] = l - 1; 989 | offset += 3; 990 | } 991 | frame.maxH = maxH; 992 | frame.maxV = maxV; 993 | prepareComponents(frame); 994 | break; 995 | 996 | case 0xffc4: // DHT (Define Huffman Tables) 997 | const huffmanLength = readUint16(data, offset); 998 | offset += 2; 999 | for (i = 2; i < huffmanLength; ) { 1000 | var huffmanTableSpec = data[offset++]; 1001 | var codeLengths = new Uint8Array(16); 1002 | var codeLengthSum = 0; 1003 | for (j = 0; j < 16; j++, offset++) { 1004 | codeLengthSum += codeLengths[j] = data[offset]; 1005 | } 1006 | var huffmanValues = new Uint8Array(codeLengthSum); 1007 | for (j = 0; j < codeLengthSum; j++, offset++) { 1008 | huffmanValues[j] = data[offset]; 1009 | } 1010 | i += 17 + codeLengthSum; 1011 | 1012 | (huffmanTableSpec >> 4 === 0 ? huffmanTablesDC : huffmanTablesAC)[ 1013 | huffmanTableSpec & 15 1014 | ] = buildHuffmanTable(codeLengths, huffmanValues); 1015 | } 1016 | break; 1017 | 1018 | case 0xffdd: // DRI (Define Restart Interval) 1019 | offset += 2; // Skip marker length. 1020 | 1021 | resetInterval = readUint16(data, offset); 1022 | offset += 2; 1023 | break; 1024 | 1025 | case 0xffda: // SOS (Start of Scan) 1026 | // A DNL marker (0xFFDC), if it exists, is only allowed at the end 1027 | // of the first scan segment and may only occur once in an image. 1028 | // Furthermore, to prevent an infinite loop, do *not* attempt to 1029 | // parse DNL markers during re-parsing of the JPEG scan data. 1030 | const parseDNLMarker = ++numSOSMarkers === 1 && !dnlScanLines; 1031 | 1032 | offset += 2; // Skip marker length. 1033 | 1034 | var selectorsCount = data[offset++]; 1035 | var components = [], 1036 | component; 1037 | for (i = 0; i < selectorsCount; i++) { 1038 | const index = data[offset++]; 1039 | var componentIndex = frame.componentIds[index]; 1040 | component = frame.components[componentIndex]; 1041 | component.index = index; 1042 | var tableSpec = data[offset++]; 1043 | component.huffmanTableDC = huffmanTablesDC[tableSpec >> 4]; 1044 | component.huffmanTableAC = huffmanTablesAC[tableSpec & 15]; 1045 | components.push(component); 1046 | } 1047 | var spectralStart = data[offset++]; 1048 | var spectralEnd = data[offset++]; 1049 | var successiveApproximation = data[offset++]; 1050 | try { 1051 | var processed = decodeScan( 1052 | data, 1053 | offset, 1054 | frame, 1055 | components, 1056 | resetInterval, 1057 | spectralStart, 1058 | spectralEnd, 1059 | successiveApproximation >> 4, 1060 | successiveApproximation & 15, 1061 | parseDNLMarker 1062 | ); 1063 | offset += processed; 1064 | } catch (ex) { 1065 | if (ex instanceof DNLMarkerError) { 1066 | warn(`${ex.message} -- attempting to re-parse the JPEG image.`); 1067 | return this.parse(data, { dnlScanLines: ex.scanLines }); 1068 | } else if (ex instanceof EOIMarkerError) { 1069 | warn(`${ex.message} -- ignoring the rest of the image data.`); 1070 | break markerLoop; 1071 | } 1072 | throw ex; 1073 | } 1074 | break; 1075 | 1076 | case 0xffdc: // DNL (Define Number of Lines) 1077 | // Ignore the marker, since it's being handled in `decodeScan`. 1078 | offset += 4; 1079 | break; 1080 | 1081 | case 0xffff: // Fill bytes 1082 | if (data[offset] !== 0xff) { 1083 | // Avoid skipping a valid marker. 1084 | offset--; 1085 | } 1086 | break; 1087 | 1088 | default: 1089 | // Could be incorrect encoding -- the last 0xFF byte of the previous 1090 | // block could have been eaten by the encoder, hence we fallback to 1091 | // `startPos = offset - 3` when looking for the next valid marker. 1092 | const nextFileMarker = findNextFileMarker( 1093 | data, 1094 | /* currentPos = */ offset - 2, 1095 | /* startPos = */ offset - 3 1096 | ); 1097 | if (nextFileMarker && nextFileMarker.invalid) { 1098 | warn( 1099 | "JpegImage.parse - unexpected data, current marker is: " + 1100 | nextFileMarker.invalid 1101 | ); 1102 | offset = nextFileMarker.offset; 1103 | break; 1104 | } 1105 | if (offset >= data.length - 1) { 1106 | warn( 1107 | "JpegImage.parse - reached the end of the image data " + 1108 | "without finding an EOI marker (0xFFD9)." 1109 | ); 1110 | break markerLoop; 1111 | } 1112 | throw new JpegError( 1113 | "JpegImage.parse - unknown marker: " + fileMarker.toString(16) 1114 | ); 1115 | } 1116 | fileMarker = readUint16(data, offset); 1117 | offset += 2; 1118 | } 1119 | 1120 | this.width = frame.samplesPerLine; 1121 | this.height = frame.scanLines; 1122 | this.jfif = jfif; 1123 | this.adobe = adobe; 1124 | this.components = []; 1125 | for (i = 0; i < frame.components.length; i++) { 1126 | component = frame.components[i]; 1127 | 1128 | // Prevent errors when DQT markers are placed after SOF{n} markers, 1129 | // by assigning the `quantizationTable` entry after the entire image 1130 | // has been parsed (fixes issue7406.pdf). 1131 | var quantizationTable = quantizationTables[component.quantizationId]; 1132 | if (quantizationTable) { 1133 | component.quantizationTable = quantizationTable; 1134 | } 1135 | 1136 | this.components.push({ 1137 | index: component.index, 1138 | output: buildComponentData(frame, component), 1139 | scaleX: component.h / frame.maxH, 1140 | scaleY: component.v / frame.maxV, 1141 | blocksPerLine: component.blocksPerLine, 1142 | blocksPerColumn: component.blocksPerColumn, 1143 | }); 1144 | } 1145 | this.numComponents = this.components.length; 1146 | return undefined; 1147 | }, 1148 | 1149 | _getLinearizedBlockData(width, height, isSourcePDF = false) { 1150 | var scaleX = this.width / width, 1151 | scaleY = this.height / height; 1152 | 1153 | var component, componentScaleX, componentScaleY, blocksPerScanline; 1154 | var x, y, i, j, k; 1155 | var index; 1156 | var offset = 0; 1157 | var output; 1158 | var numComponents = this.components.length; 1159 | var dataLength = width * height * numComponents; 1160 | var data = new Uint8ClampedArray(dataLength); 1161 | var xScaleBlockOffset = new Uint32Array(width); 1162 | var mask3LSB = 0xfffffff8; // used to clear the 3 LSBs 1163 | let lastComponentScaleX; 1164 | 1165 | for (i = 0; i < numComponents; i++) { 1166 | component = this.components[i]; 1167 | componentScaleX = component.scaleX * scaleX; 1168 | componentScaleY = component.scaleY * scaleY; 1169 | offset = i; 1170 | output = component.output; 1171 | blocksPerScanline = (component.blocksPerLine + 1) << 3; 1172 | // Precalculate the `xScaleBlockOffset`. Since it doesn't depend on the 1173 | // component data, that's only necessary when `componentScaleX` changes. 1174 | if (componentScaleX !== lastComponentScaleX) { 1175 | for (x = 0; x < width; x++) { 1176 | j = 0 | (x * componentScaleX); 1177 | xScaleBlockOffset[x] = ((j & mask3LSB) << 3) | (j & 7); 1178 | } 1179 | lastComponentScaleX = componentScaleX; 1180 | } 1181 | // linearize the blocks of the component 1182 | for (y = 0; y < height; y++) { 1183 | j = 0 | (y * componentScaleY); 1184 | index = (blocksPerScanline * (j & mask3LSB)) | ((j & 7) << 3); 1185 | for (x = 0; x < width; x++) { 1186 | data[offset] = output[index + xScaleBlockOffset[x]]; 1187 | offset += numComponents; 1188 | } 1189 | } 1190 | } 1191 | 1192 | // decodeTransform contains pairs of multiplier (-256..256) and additive 1193 | let transform = this._decodeTransform; 1194 | 1195 | // In PDF files, JPEG images with CMYK colour spaces are usually inverted 1196 | // (this can be observed by extracting the raw image data). 1197 | // Since the conversion algorithms (see below) were written primarily for 1198 | // the PDF use-cases, attempting to use `JpegImage` to parse standalone 1199 | // JPEG (CMYK) images may thus result in inverted images (see issue 9513). 1200 | // 1201 | // Unfortunately it's not (always) possible to tell, from the image data 1202 | // alone, if it needs to be inverted. Thus in an attempt to provide better 1203 | // out-of-box behaviour when `JpegImage` is used standalone, default to 1204 | // inverting JPEG (CMYK) images if and only if the image data does *not* 1205 | // come from a PDF file and no `decodeTransform` was passed by the user. 1206 | if (!isSourcePDF && numComponents === 4 && !transform) { 1207 | // prettier-ignore 1208 | transform = new Int32Array([ 1209 | -256, 255, -256, 255, -256, 255, -256, 255]); 1210 | } 1211 | 1212 | if (transform) { 1213 | for (i = 0; i < dataLength; ) { 1214 | for (j = 0, k = 0; j < numComponents; j++, i++, k += 2) { 1215 | data[i] = ((data[i] * transform[k]) >> 8) + transform[k + 1]; 1216 | } 1217 | } 1218 | } 1219 | return data; 1220 | }, 1221 | 1222 | get _isColorConversionNeeded() { 1223 | if (this.adobe) { 1224 | // The adobe transform marker overrides any previous setting. 1225 | return !!this.adobe.transformCode; 1226 | } 1227 | if (this.numComponents === 3) { 1228 | if (this._colorTransform === 0) { 1229 | // If the Adobe transform marker is not present and the image 1230 | // dictionary has a 'ColorTransform' entry, explicitly set to `0`, 1231 | // then the colours should *not* be transformed. 1232 | return false; 1233 | } else if ( 1234 | this.components[0].index === /* "R" = */ 0x52 && 1235 | this.components[1].index === /* "G" = */ 0x47 && 1236 | this.components[2].index === /* "B" = */ 0x42 1237 | ) { 1238 | // If the three components are indexed as RGB in ASCII 1239 | // then the colours should *not* be transformed. 1240 | return false; 1241 | } 1242 | return true; 1243 | } 1244 | // `this.numComponents !== 3` 1245 | if (this._colorTransform === 1) { 1246 | // If the Adobe transform marker is not present and the image 1247 | // dictionary has a 'ColorTransform' entry, explicitly set to `1`, 1248 | // then the colours should be transformed. 1249 | return true; 1250 | } 1251 | return false; 1252 | }, 1253 | 1254 | _convertYccToRgb: function convertYccToRgb(data) { 1255 | var Y, Cb, Cr; 1256 | for (var i = 0, length = data.length; i < length; i += 3) { 1257 | Y = data[i]; 1258 | Cb = data[i + 1]; 1259 | Cr = data[i + 2]; 1260 | data[i] = Y - 179.456 + 1.402 * Cr; 1261 | data[i + 1] = Y + 135.459 - 0.344 * Cb - 0.714 * Cr; 1262 | data[i + 2] = Y - 226.816 + 1.772 * Cb; 1263 | } 1264 | return data; 1265 | }, 1266 | 1267 | _convertYcckToRgb: function convertYcckToRgb(data) { 1268 | var Y, Cb, Cr, k; 1269 | var offset = 0; 1270 | for (var i = 0, length = data.length; i < length; i += 4) { 1271 | Y = data[i]; 1272 | Cb = data[i + 1]; 1273 | Cr = data[i + 2]; 1274 | k = data[i + 3]; 1275 | 1276 | data[offset++] = 1277 | -122.67195406894 + 1278 | Cb * 1279 | (-6.60635669420364e-5 * Cb + 1280 | 0.000437130475926232 * Cr - 1281 | 5.4080610064599e-5 * Y + 1282 | 0.00048449797120281 * k - 1283 | 0.154362151871126) + 1284 | Cr * 1285 | (-0.000957964378445773 * Cr + 1286 | 0.000817076911346625 * Y - 1287 | 0.00477271405408747 * k + 1288 | 1.53380253221734) + 1289 | Y * 1290 | (0.000961250184130688 * Y - 1291 | 0.00266257332283933 * k + 1292 | 0.48357088451265) + 1293 | k * (-0.000336197177618394 * k + 0.484791561490776); 1294 | 1295 | data[offset++] = 1296 | 107.268039397724 + 1297 | Cb * 1298 | (2.19927104525741e-5 * Cb - 1299 | 0.000640992018297945 * Cr + 1300 | 0.000659397001245577 * Y + 1301 | 0.000426105652938837 * k - 1302 | 0.176491792462875) + 1303 | Cr * 1304 | (-0.000778269941513683 * Cr + 1305 | 0.00130872261408275 * Y + 1306 | 0.000770482631801132 * k - 1307 | 0.151051492775562) + 1308 | Y * 1309 | (0.00126935368114843 * Y - 1310 | 0.00265090189010898 * k + 1311 | 0.25802910206845) + 1312 | k * (-0.000318913117588328 * k - 0.213742400323665); 1313 | 1314 | data[offset++] = 1315 | -20.810012546947 + 1316 | Cb * 1317 | (-0.000570115196973677 * Cb - 1318 | 2.63409051004589e-5 * Cr + 1319 | 0.0020741088115012 * Y - 1320 | 0.00288260236853442 * k + 1321 | 0.814272968359295) + 1322 | Cr * 1323 | (-1.53496057440975e-5 * Cr - 1324 | 0.000132689043961446 * Y + 1325 | 0.000560833691242812 * k - 1326 | 0.195152027534049) + 1327 | Y * 1328 | (0.00174418132927582 * Y - 1329 | 0.00255243321439347 * k + 1330 | 0.116935020465145) + 1331 | k * (-0.000343531996510555 * k + 0.24165260232407); 1332 | } 1333 | // Ensure that only the converted RGB data is returned. 1334 | return data.subarray(0, offset); 1335 | }, 1336 | 1337 | _convertYcckToCmyk: function convertYcckToCmyk(data) { 1338 | var Y, Cb, Cr; 1339 | for (var i = 0, length = data.length; i < length; i += 4) { 1340 | Y = data[i]; 1341 | Cb = data[i + 1]; 1342 | Cr = data[i + 2]; 1343 | data[i] = 434.456 - Y - 1.402 * Cr; 1344 | data[i + 1] = 119.541 - Y + 0.344 * Cb + 0.714 * Cr; 1345 | data[i + 2] = 481.816 - Y - 1.772 * Cb; 1346 | // K in data[i + 3] is unchanged 1347 | } 1348 | return data; 1349 | }, 1350 | 1351 | _convertCmykToRgb: function convertCmykToRgb(data) { 1352 | var c, m, y, k; 1353 | var offset = 0; 1354 | for (var i = 0, length = data.length; i < length; i += 4) { 1355 | c = data[i]; 1356 | m = data[i + 1]; 1357 | y = data[i + 2]; 1358 | k = data[i + 3]; 1359 | 1360 | data[offset++] = 1361 | 255 + 1362 | c * 1363 | (-0.00006747147073602441 * c + 1364 | 0.0008379262121013727 * m + 1365 | 0.0002894718188643294 * y + 1366 | 0.003264231057537806 * k - 1367 | 1.1185611867203937) + 1368 | m * 1369 | (0.000026374107616089405 * m - 1370 | 0.00008626949158638572 * y - 1371 | 0.0002748769067499491 * k - 1372 | 0.02155688794978967) + 1373 | y * 1374 | (-0.00003878099212869363 * y - 1375 | 0.0003267808279485286 * k + 1376 | 0.0686742238595345) - 1377 | k * (0.0003361971776183937 * k + 0.7430659151342254); 1378 | 1379 | data[offset++] = 1380 | 255 + 1381 | c * 1382 | (0.00013596372813588848 * c + 1383 | 0.000924537132573585 * m + 1384 | 0.00010567359618683593 * y + 1385 | 0.0004791864687436512 * k - 1386 | 0.3109689587515875) + 1387 | m * 1388 | (-0.00023545346108370344 * m + 1389 | 0.0002702845253534714 * y + 1390 | 0.0020200308977307156 * k - 1391 | 0.7488052167015494) + 1392 | y * 1393 | (0.00006834815998235662 * y + 1394 | 0.00015168452363460973 * k - 1395 | 0.09751927774728933) - 1396 | k * (0.00031891311758832814 * k + 0.7364883807733168); 1397 | 1398 | data[offset++] = 1399 | 255 + 1400 | c * 1401 | (0.000013598650411385307 * c + 1402 | 0.00012423956175490851 * m + 1403 | 0.0004751985097583589 * y - 1404 | 0.0000036729317476630422 * k - 1405 | 0.05562186980264034) + 1406 | m * 1407 | (0.00016141380598724676 * m + 1408 | 0.0009692239130725186 * y + 1409 | 0.0007782692450036253 * k - 1410 | 0.44015232367526463) + 1411 | y * 1412 | (5.068882914068769e-7 * y + 1413 | 0.0017778369011375071 * k - 1414 | 0.7591454649749609) - 1415 | k * (0.0003435319965105553 * k + 0.7063770186160144); 1416 | } 1417 | // Ensure that only the converted RGB data is returned. 1418 | return data.subarray(0, offset); 1419 | }, 1420 | 1421 | getData({ width, height, forceRGB = false, isSourcePDF = false }) { 1422 | if (this.numComponents > 4) { 1423 | throw new JpegError("Unsupported color mode"); 1424 | } 1425 | // Type of data: Uint8ClampedArray(width * height * numComponents) 1426 | var data = this._getLinearizedBlockData(width, height, isSourcePDF); 1427 | 1428 | if (this.numComponents === 1 && forceRGB) { 1429 | var dataLength = data.length; 1430 | var rgbData = new Uint8ClampedArray(dataLength * 3); 1431 | var offset = 0; 1432 | for (var i = 0; i < dataLength; i++) { 1433 | var grayColor = data[i]; 1434 | rgbData[offset++] = grayColor; 1435 | rgbData[offset++] = grayColor; 1436 | rgbData[offset++] = grayColor; 1437 | } 1438 | return rgbData; 1439 | } else if (this.numComponents === 3 && this._isColorConversionNeeded) { 1440 | return this._convertYccToRgb(data); 1441 | } else if (this.numComponents === 4) { 1442 | if (this._isColorConversionNeeded) { 1443 | if (forceRGB) { 1444 | return this._convertYcckToRgb(data); 1445 | } 1446 | return this._convertYcckToCmyk(data); 1447 | } else if (forceRGB) { 1448 | return this._convertCmykToRgb(data); 1449 | } 1450 | } 1451 | return data; 1452 | }, 1453 | }; 1454 | 1455 | return JpegImage; 1456 | })(); 1457 | 1458 | export { JpegImage }; 1459 | -------------------------------------------------------------------------------- /src/lib/decode-worker.js: -------------------------------------------------------------------------------- 1 | 2 | export default function(self) { 3 | const decode = require('./decode'); 4 | self.onmessage = ({ data: msg }) => { 5 | decode(msg.buf, msg.options, (err, result) => { 6 | if (err) { 7 | const errValue = err instanceof Error ? err.message : err; // Error is not clonable 8 | self.postMessage({ err: errValue }); 9 | } else { 10 | self.postMessage({ result: result }); 11 | } 12 | }); 13 | }; 14 | }; 15 | -------------------------------------------------------------------------------- /src/lib/decode.js: -------------------------------------------------------------------------------- 1 | import { JpegImage } from './backend/jpg'; 2 | import { arrayLikeRgbToRgba } from './util/color'; 3 | 4 | /** 5 | * Decode the JPEG data 6 | * 7 | * @param buf ArrayLike data structure 8 | * @param options Object { width: number, height: number } 9 | * @param cb Callback to invoke on completion 10 | * 11 | * @callback { width: number, height: number, data: Uint8Array } 12 | */ 13 | export default function decode(buf, options, cb) { 14 | 15 | // returns: Uint8ClampedArray(width * height * numComponents) 16 | function getData(j, width, height) { 17 | const opts = { 18 | width: width, 19 | height: height, 20 | forceRGB: true, 21 | isSourcePDF: false 22 | }; 23 | 24 | return j.getData(opts); 25 | } 26 | 27 | try { 28 | const j = new JpegImage(); 29 | j.parse(buf); 30 | 31 | const width = options.width || j.width; 32 | const height = options.height || j.height; 33 | const rgbData = getData(j, width, height); // NOTE: each color is RGB without alpha-channel 34 | const rgbaData = arrayLikeRgbToRgba(rgbData); // NOTE: convert to RGBA 35 | 36 | const result = { 37 | width: width, 38 | height: height, 39 | data: rgbaData 40 | }; 41 | 42 | cb(null, result); 43 | } catch(err) { 44 | cb(err); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/lib/encode-worker.js: -------------------------------------------------------------------------------- 1 | 2 | export default function(self) { 3 | const encode = require('./encode'); 4 | self.onmessage = function ({ data: msg }) { 5 | encode(msg.buf, msg.options, (err, result) => { 6 | if (err) { 7 | const errValue = err instanceof Error ? err.message : err; // Error is not clonable 8 | self.postMessage({ err: errValue }); 9 | } else { 10 | self.postMessage({ result: result }); 11 | } 12 | }); 13 | }; 14 | }; 15 | -------------------------------------------------------------------------------- /src/lib/encode.js: -------------------------------------------------------------------------------- 1 | import { JPEGEncoder } from './backend/jpg-encode'; 2 | 3 | /** 4 | * Encode the data to JPEG format 5 | * 6 | * @param buf Buffer|Uint8Array 7 | * @param options Object { width: number, height: number, quality: number } 8 | * @param cb Callback to invoke on completion 9 | * 10 | * @callback { width: number, height: number, data: Uint8Array } 11 | */ 12 | export default function encode(buf, options, cb) { 13 | try { 14 | const encoder = new JPEGEncoder(options.quality); 15 | const opts = { 16 | data: buf, 17 | width: options.width, 18 | height: options.height 19 | } 20 | 21 | const encoded = encoder.encode(opts); 22 | 23 | const result = { 24 | data: encoded, 25 | width: options.width, 26 | height: options.height 27 | }; 28 | 29 | cb(null, result); 30 | } catch(err) { 31 | cb(err); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/lib/exif-worker.js: -------------------------------------------------------------------------------- 1 | 2 | export default function(self) { 3 | const exif = require('./exif'); 4 | self.onmessage = ({ data: msg }) => { 5 | exif(msg.buf, {}, (err, result) => { 6 | if (err) { 7 | const errValue = err instanceof Error ? err.message : err; // Error is not clonable 8 | self.postMessage({ err: errValue }); 9 | } else { 10 | self.postMessage({ result: result }); 11 | } 12 | }); 13 | }; 14 | }; 15 | -------------------------------------------------------------------------------- /src/lib/exif.js: -------------------------------------------------------------------------------- 1 | import ExifReader from './backend/exif-reader'; 2 | 3 | /** 4 | * Read EXIF data from the provided buffer 5 | * 6 | * @param buf ArrayBuffer 7 | * @param options Object { hasMakerNote: true|false } 8 | * @param cb Callback to invoke on completion 9 | * 10 | * @callback Object { name: value, ... } 11 | */ 12 | export default function exif(buf, options, cb) { 13 | try { 14 | const tags = ExifReader.load(buf); 15 | 16 | // The MakerNote tag can be really large. Remove it to lower memory usage. 17 | delete tags['MakerNote']; 18 | 19 | cb(null, tags); 20 | } catch(err) { 21 | if(err.message === 'No Exif data') { 22 | cb(null, {}); 23 | } else { 24 | cb(err); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/lib/has-worker.js: -------------------------------------------------------------------------------- 1 | let hasWorker = (typeof window !== 'undefined') && ('Worker' in window); 2 | 3 | if (hasWorker) { 4 | try { 5 | const w = require('webworkify')(() => {}); 6 | w.terminate(); 7 | } catch (e) { 8 | hasWorker = false; 9 | } 10 | } 11 | 12 | export { 13 | hasWorker 14 | } 15 | -------------------------------------------------------------------------------- /src/lib/info.js: -------------------------------------------------------------------------------- 1 | import imageinfo from 'imageinfo'; 2 | 3 | /** 4 | * Get image information 5 | * @param {Buffer} buf Image or image part that contains image parameters 6 | * @param {function} cb Callback to invoke on completion 7 | */ 8 | export default function info(buf, cb) { 9 | setTimeout(() => { 10 | const info = imageinfo(buf); 11 | if(!info) { 12 | cb(new Error('Cannot get image info')); 13 | } else { 14 | cb(null, { 15 | type: info.type, 16 | mimeType: info.mimeType, 17 | extension: info.format.toLowerCase(), 18 | width: info.width, 19 | height: info.height 20 | }); 21 | } 22 | }, 0); 23 | } 24 | -------------------------------------------------------------------------------- /src/lib/magic-db.js: -------------------------------------------------------------------------------- 1 | export default { 2 | "474946383961": { 3 | "mimeType": "image/gif", 4 | "extension": "gif" 5 | }, 6 | "474946383761": { 7 | "mimeType": "image/gif", 8 | "extension": "gif" 9 | }, 10 | "89504e470d0a1a0a": { 11 | "mimeType": "image/png", 12 | "extension": "png" 13 | }, 14 | "ffd8ff": { 15 | "mimeType": "image/jpeg", 16 | "extension": "jpg" 17 | }, 18 | "57454250": { 19 | "mimeType": "image/webp", 20 | "extension": "webp" 21 | }, 22 | "49492a00": { 23 | "mimeType": "image/tiff", 24 | "extension": "tiff" 25 | }, 26 | "4d4d002a": { 27 | "mimeType": "image/tiff", 28 | "extension": "tiff" 29 | }, 30 | "424d": { 31 | "mimeType": "image/bmp", 32 | "extension": "bmp" 33 | }, 34 | "000000146674797069736f6d": { 35 | "mimeType": "video/mp4", 36 | "extension": "mp4" 37 | }, 38 | "000000186674797033677035": { 39 | "mimeType": "video/mp4", 40 | "extension": "mp4" 41 | }, 42 | "000000146674797071742020": { 43 | "mimeType": "video/quicktime", 44 | "extension": "mov" 45 | }, 46 | "1a45dfa3": { 47 | "mimeType": "video/webm", 48 | "extension": "webm" 49 | }, 50 | "25504446": { 51 | "mimeType": "application/pdf", 52 | "extension": "pdf" 53 | } 54 | }; 55 | -------------------------------------------------------------------------------- /src/lib/magic.js: -------------------------------------------------------------------------------- 1 | import db from './magic-db'; 2 | 3 | /** 4 | * Lookup the magic number in magic-number DB 5 | * @param {Buffer} buf Data buffer 6 | * @param {function} cb Callback to invoke on completion 7 | */ 8 | export default function magic(buf, cb) { 9 | setTimeout(() => { 10 | const sampleLength = 24; 11 | const sample = buf.slice(0, sampleLength).toString('hex'); // lookup data 12 | 13 | const found = Object.keys(db).find((it) => { return (sample.indexOf(it) !== -1); }); 14 | 15 | if(found) { 16 | cb(null, db[found]); 17 | } else { 18 | cb(new Error('Magic number not found')); 19 | } 20 | }, 0); 21 | } 22 | -------------------------------------------------------------------------------- /src/lib/util/buffer.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Converts the buffer to Buffer 4 | * @param {Buffer|ArrayBuffer|Uint8Array|Uint8ClampedArray} buf Input buffer 5 | * @returns {Buffer} 6 | */ 7 | export function toBuffer(buf) { 8 | if(buf instanceof ArrayBuffer) { 9 | return arrayBufferToBuffer(buf); 10 | } else if(Buffer.isBuffer(buf)) { 11 | return buf; 12 | } else if(buf instanceof Uint8Array || buf instanceof Uint8ClampedArray) { 13 | return Buffer.from(buf); 14 | } else { 15 | return buf; // type unknown, trust the user 16 | } 17 | } 18 | 19 | /** 20 | * Converts any buffer to ArrayBuffer 21 | * @param {Buffer|ArrayBuffer|Uint8Array|Uint8ClampedArray} buf Input buffer 22 | * @returns {ArrayBuffer} 23 | */ 24 | export function toArrayBuffer(buf) { 25 | if(buf instanceof ArrayBuffer) { 26 | return buf; 27 | } else if(Buffer.isBuffer(buf)) { 28 | return arrayLikeToArrayBuffer(buf); 29 | } else if(buf instanceof Uint8Array || buf instanceof Uint8ClampedArray) { 30 | return arrayLikeToArrayBuffer(buf); 31 | } else { 32 | return buf; // type unknown, trust the user 33 | } 34 | } 35 | 36 | /** 37 | * Convert any buffer to array-like type: Uint8Array|Uint8ClampedArray|Buffer 38 | * @param {Buffer|ArrayBuffer|Uint8Array|Uint8ClampedArray} buf 39 | * @returns {Buffer|Uint8Array} 40 | */ 41 | export function toArrayLike(buf) { 42 | if(buf instanceof Uint8Array || buf instanceof Uint8ClampedArray) { 43 | return buf; 44 | } else if(buf instanceof ArrayBuffer) { 45 | return new Uint8Array(buf); 46 | } else if(Buffer.isBuffer(buf)) { 47 | return buf; 48 | } else { 49 | return buf; // type unknown, trust the user 50 | } 51 | } 52 | 53 | /** 54 | * Converts Buffer to ArrayBuffer 55 | * 56 | * NOTE: we cannot convert Buffer to ArrayBuffer via `buf.buffer` since the size of the returned ArrayBuffer might be biger than the actual. 57 | * 58 | * @param {Buffer|Uint8Array|Uint8ClampedArray} buf 59 | * @returns {ArrayBuffer} 60 | */ 61 | function arrayLikeToArrayBuffer(buf) { 62 | const arrBuf = new ArrayBuffer(buf.length); 63 | const view = new Uint8Array(arrBuf); 64 | for (let i = 0; i < buf.length; ++i) { 65 | view[i] = buf[i]; 66 | } 67 | return arrBuf; 68 | } 69 | 70 | /** 71 | * Convert ArrayBuffer to Buffer 72 | * @param {ArrayBuffer} arrBuf 73 | * @returns {Buffer} 74 | */ 75 | function arrayBufferToBuffer(arrBuf) { 76 | return Buffer.from(new Uint8Array(arrBuf)); 77 | } 78 | -------------------------------------------------------------------------------- /src/lib/util/color.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | /** 4 | * Converts a buffer of RGB components to RGBA. 5 | * 6 | * @param buf {Buffer|Uint8Array|Uint8ClampedArray} array-like structure with RGB data 7 | */ 8 | export function arrayLikeRgbToRgba(buf) { 9 | const filler = 0xFF; 10 | const result = new Uint8Array((buf.length / 3) * 4); 11 | 12 | for(let i = 0, p = 0; i < buf.length; i += 3) { 13 | result[p++] = buf[i]; 14 | result[p++] = buf[i + 1]; 15 | result[p++] = buf[i + 2]; 16 | result[p++] = filler; 17 | } 18 | 19 | return result; 20 | } 21 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import { hasWorker } from './lib/has-worker'; 2 | 3 | import * as bufferUtils from './lib/util/buffer'; 4 | 5 | import exif from './lib/exif'; 6 | import decode from './lib/decode'; 7 | import encode from './lib/encode'; 8 | import magic from './lib/magic'; 9 | import info from './lib/info'; 10 | 11 | import exifWorker from './lib/exif-worker'; 12 | import decodeWorker from './lib/decode-worker'; 13 | import encodeWorker from './lib/encode-worker'; 14 | 15 | /** 16 | * Decode 17 | * 18 | * @param {Buffer|ArrayBuffer|Uint8Array} buf 19 | * @param {object} options Params: { width: number, height: number } 20 | * @param {function} cb Callback to invoke on completion 21 | * 22 | * @callback { width: number, height: number, data: Uint8Array } 23 | */ 24 | function decodeBuffer(buf, options, cb) { 25 | if(typeof options === 'function') { 26 | cb = options; 27 | options = {}; 28 | } 29 | 30 | try { 31 | buf = bufferUtils.toArrayLike(buf); 32 | 33 | if(hasWorker) { 34 | const wr = require('webworkify')(decodeWorker); 35 | 36 | wr.onmessage = ({ data: msg }) => { 37 | const err = msg.err ? new Error(msg.err) : undefined; 38 | cb(err, msg.result); 39 | }; 40 | 41 | const msg = { 42 | buf: buf, 43 | options: options 44 | }; 45 | 46 | if (options.transferable) { 47 | wr.postMessage(msg, [ buf ]); 48 | } else { 49 | wr.postMessage(msg); 50 | } 51 | } else { 52 | decode(buf, options, cb); 53 | } 54 | } catch(err) { 55 | cb(err); 56 | } 57 | } 58 | 59 | /** 60 | * Encode 61 | * 62 | * @param {Buffer|ArrayBuffer|Uint8Array} buf 63 | * @param {object} options Params { width: number, height: number, quality: number } 64 | * @param {function} cb Callback to invoke on completion 65 | * 66 | * @callback { width: number, height: number, data: Uint8Array } 67 | */ 68 | function encodeBuffer(buf, options, cb) { 69 | if(typeof options === 'function') { 70 | cb = options; 71 | options = {}; 72 | } 73 | 74 | try { 75 | buf = bufferUtils.toArrayLike(buf); 76 | 77 | if(!options.hasOwnProperty('width') || !options.hasOwnProperty('height')) { 78 | return cb(new Error('Width & height of the buffer is not provided.')); 79 | } 80 | 81 | if(hasWorker) { 82 | const wr = require('webworkify')(encodeWorker); 83 | 84 | wr.onmessage = ({ data: msg }) => { 85 | const err = msg.err ? new Error(msg.err) : undefined; 86 | cb(err, msg.result); 87 | }; 88 | 89 | const msg = { 90 | buf: buf, 91 | options: options 92 | }; 93 | 94 | if (options.transferable) { 95 | wr.postMessage(msg, [ buf ]); 96 | } else { 97 | wr.postMessage(msg); 98 | } 99 | } else { 100 | encode(buf, options, cb); 101 | } 102 | } catch(err) { 103 | cb(err); 104 | } 105 | } 106 | 107 | /** 108 | * Get EXIF 109 | * 110 | * @param {Buffer|ArrayBuffer|Uint8Array} buf 111 | * @param {object} options Params { hasMakerNote: true|false } 112 | * @param {function} cb Callback to invoke on completion 113 | * 114 | * @callback Object { name: value, ... } 115 | */ 116 | function exifBuffer(buf, options, cb) { 117 | if(typeof options === 'function') { 118 | cb = options; 119 | options = {}; 120 | } 121 | 122 | try { 123 | buf = bufferUtils.toArrayBuffer(buf); 124 | 125 | if(hasWorker) { 126 | const wr = require('webworkify')(exifWorker); 127 | 128 | wr.onmessage = ({ data: msg }) => { 129 | const err = msg.err ? new Error(msg.err) : undefined; 130 | cb(err, msg.result); 131 | }; 132 | 133 | const msg = { 134 | buf: buf 135 | }; 136 | 137 | if (options.transferable) { 138 | wr.postMessage(msg, [ buf ]); 139 | } else { 140 | wr.postMessage(msg); 141 | } 142 | } else { 143 | exif(buf, options, cb); 144 | } 145 | } catch(err) { 146 | cb(err); 147 | } 148 | } 149 | 150 | /** 151 | * Detect mime-type for the Buffer 152 | * @param {Buffer|ArrayBuffer|Uint8Array} buf Data buffer 153 | * @param {function} cb Callback to invoke on completion 154 | */ 155 | function magicBuffer(buf, cb) { 156 | try { 157 | buf = bufferUtils.toBuffer(buf); 158 | magic(buf, cb); 159 | } catch(err) { 160 | cb(err); 161 | } 162 | } 163 | 164 | /** 165 | * Get image information without reading and decoding a file 166 | * @param {Buffer|ArrayBuffer|Uint8Array} buf Data buffer 167 | * @param {function} cb Callback to invoke on completion 168 | */ 169 | function infoBuffer(buf, cb) { 170 | try { 171 | buf = bufferUtils.toBuffer(buf); 172 | info(buf, cb); 173 | } catch(err) { 174 | cb(err); 175 | } 176 | } 177 | 178 | 179 | export default { 180 | decode: decodeBuffer, 181 | encode: encodeBuffer, 182 | exif: exifBuffer, 183 | magic: magicBuffer, 184 | info: infoBuffer, 185 | } 186 | -------------------------------------------------------------------------------- /tasks/browserify-task.js: -------------------------------------------------------------------------------- 1 | import gulp from 'gulp'; 2 | import async from 'async'; 3 | import once from 'once'; 4 | import log from 'fancy-log'; 5 | import colors from 'ansi-colors'; 6 | import header from 'gulp-header'; 7 | import uglify from 'gulp-uglify'; 8 | import gulpIf from 'gulp-if'; 9 | import browserify from 'browserify'; 10 | import source from 'vinyl-source-stream'; 11 | import buffer from 'vinyl-buffer'; 12 | import pkg from '../package.json'; 13 | import { isProduction, browserifyConfig } from './config'; 14 | 15 | const banner = [ 16 | '/*', 17 | ' * <%= pkg.name %> - <%= pkg.description %>', 18 | ' * @version v<%= pkg.version %>', 19 | ' * @author <%= pkg.author %>', 20 | ' * @link <%= pkg.homepage %>', 21 | ' * @license <%= pkg.license %>', 22 | ' */', 23 | ''].join('\n'); 24 | 25 | function handleErrors(...args) { 26 | console.error(args); 27 | this.emit('end'); // Keep gulp from hanging on this task 28 | } 29 | 30 | function browserifyTask(next) { 31 | log('NODE_ENV:', colors.yellow(process.env.NODE_ENV)); 32 | log('IS_PRODUCTION:', colors.yellow(isProduction.toString())); 33 | 34 | async.each(browserifyConfig.bundleConfigs, (bundleConfig, cb) => { 35 | cb = once(cb); 36 | 37 | const bundler = browserify({ 38 | entries: bundleConfig.entries, 39 | insertGlobals: false, 40 | detectGlobals: true, 41 | standalone: bundleConfig.name, 42 | debug: browserifyConfig.debug 43 | }); 44 | 45 | let handleEnd = () => { 46 | log('Bundled', colors.green(bundleConfig.outputName)); 47 | cb(); 48 | }; 49 | 50 | const bundle = () => { 51 | log('Bundling', colors.green(bundleConfig.outputName)); 52 | return bundler 53 | .transform("babelify", browserifyConfig.babelConfig) 54 | .bundle() 55 | .on('error', handleErrors) 56 | .pipe(source(bundleConfig.outputName)) 57 | .pipe(buffer()) 58 | .pipe(header(banner, { pkg: pkg })) 59 | .pipe(gulpIf(bundleConfig.isUglify, uglify())) 60 | .pipe(gulp.dest(bundleConfig.dest)) 61 | .on('end', handleEnd); 62 | }; 63 | 64 | bundle(); 65 | 66 | }, next); 67 | } 68 | 69 | export { browserifyTask }; 70 | -------------------------------------------------------------------------------- /tasks/bundle-test-task.js: -------------------------------------------------------------------------------- 1 | import gulp from 'gulp'; 2 | import async from 'async'; 3 | import once from 'once'; 4 | import fs from 'fs'; 5 | import log from 'fancy-log'; 6 | import colors from 'ansi-colors'; 7 | import replace from 'gulp-replace'; 8 | import browserify from 'browserify'; 9 | import source from 'vinyl-source-stream'; 10 | import buffer from 'vinyl-buffer'; 11 | import constants from '../test/util/constants'; 12 | 13 | const srcDir = './test'; 14 | const dstDir = './test/browser'; 15 | 16 | const ignoreFiles = [ 17 | `${srcDir}/util/file-writer.js` 18 | ]; 19 | 20 | const paths = ['pathPng', 'path420', 'path422h', 'path422v', 'path444', 'pathAC', 'pathP', 'pathDCTF', 'pathCP', 'pathExif', 'pathBroken']; 21 | const replacements = paths.map((p) => { 22 | return { 23 | name: p, 24 | search: `_fs["default"].readFileSync(${p})`, 25 | replacement: "Buffer.from('" + fs.readFileSync(constants[p], { encoding: 'base64' }) + "', 'base64')" 26 | }; 27 | }).reduce((acc, it) => { 28 | return Object.assign({ [it.name] : it }, acc); 29 | }, {}); 30 | 31 | const bundleConfig = { 32 | name: 'inkjet-test', 33 | entries: [ 34 | `${srcDir}/buffer-util.spec.js`, 35 | `${srcDir}/color-util.spec.js`, 36 | `${srcDir}/decode.spec.js`, 37 | `${srcDir}/encode.spec.js`, 38 | `${srcDir}/exif.spec.js`, 39 | `${srcDir}/info.spec.js`, 40 | `${srcDir}/magic.spec.js`, 41 | `${srcDir}/re-encode.spec.js`, 42 | ], 43 | dest: dstDir, 44 | outputName: `inkjet-test-bundle.js` 45 | }; 46 | 47 | const babelConfig = { 48 | presets: [ 49 | "@babel/preset-env" 50 | ], 51 | plugins: [ 52 | "add-module-exports", 53 | ] 54 | }; 55 | 56 | export const browserifyConfig = { 57 | debug: true, 58 | bundleConfigs: [ bundleConfig ], 59 | babelConfig: babelConfig 60 | }; 61 | 62 | function handleErrors(...args) { 63 | console.error(args); 64 | this.emit('end'); // Keep gulp from hanging on this task 65 | } 66 | 67 | 68 | function bundleTestTask(next) { 69 | async.each(browserifyConfig.bundleConfigs, (bundleConfig, cb) => { 70 | cb = once(cb); 71 | 72 | const bundler = browserify({ 73 | entries: bundleConfig.entries, 74 | insertGlobals: true, 75 | detectGlobals: true, 76 | standalone: bundleConfig.name, 77 | debug: browserifyConfig.debug 78 | }); 79 | 80 | let handleEnd = () => { 81 | log('Bundled', colors.green(bundleConfig.outputName)); 82 | cb(); 83 | }; 84 | 85 | const bundle = () => { 86 | log('Bundling', colors.green(bundleConfig.outputName)); 87 | return bundler 88 | .ignore(ignoreFiles) 89 | .transform("babelify", browserifyConfig.babelConfig) 90 | .bundle() 91 | .on('error', handleErrors) 92 | .pipe(source(bundleConfig.outputName)) 93 | .pipe(buffer()) 94 | .pipe(replace(replacements.pathPng.search, replacements.pathPng.replacement)) 95 | .pipe(replace(replacements.path420.search, replacements.path420.replacement)) 96 | .pipe(replace(replacements.path422h.search, replacements.path422h.replacement)) 97 | .pipe(replace(replacements.path422v.search, replacements.path422v.replacement)) 98 | .pipe(replace(replacements.path444.search, replacements.path444.replacement)) 99 | .pipe(replace(replacements.pathAC.search, replacements.pathAC.replacement)) 100 | .pipe(replace(replacements.pathP.search, replacements.pathP.replacement)) 101 | .pipe(replace(replacements.pathDCTF.search, replacements.pathDCTF.replacement)) 102 | .pipe(replace(replacements.pathCP.search, replacements.pathCP.replacement)) 103 | .pipe(replace(replacements.pathExif.search, replacements.pathExif.replacement)) 104 | .pipe(replace(replacements.pathBroken.search, replacements.pathBroken.replacement)) 105 | .pipe(gulp.dest(bundleConfig.dest)) 106 | .on('end', handleEnd); 107 | }; 108 | 109 | bundle(); 110 | 111 | }, next); 112 | } 113 | 114 | export { bundleTestTask }; 115 | -------------------------------------------------------------------------------- /tasks/config.js: -------------------------------------------------------------------------------- 1 | if(!process.env.NODE_ENV) { 2 | process.env.NODE_ENV = 'development'; 3 | } 4 | 5 | export const isProduction = (process.env.NODE_ENV === 'production'); 6 | 7 | const srcDir = './src'; 8 | const dstDir = './dist'; 9 | 10 | const bundleConfig = { 11 | name: 'inkjet', 12 | entries: [ `${srcDir}/index.js` ], 13 | dest: dstDir, 14 | outputName: `inkjet${isProduction ? '.min' : ''}.js`, 15 | isUglify: isProduction, 16 | }; 17 | 18 | const babelConfig = { 19 | presets: [ 20 | "@babel/preset-env" 21 | ], 22 | plugins: [ 23 | "add-module-exports", 24 | ] 25 | }; 26 | 27 | export const browserifyConfig = { 28 | debug: !isProduction, 29 | bundleConfigs: [ bundleConfig ], 30 | babelConfig: babelConfig 31 | }; 32 | -------------------------------------------------------------------------------- /test/browser/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Mocha Tests 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /test/buffer-util.spec.js: -------------------------------------------------------------------------------- 1 | import { toBuffer, toArrayBuffer, toArrayLike } from '../src/lib/util/buffer'; 2 | 3 | const arrData = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F]; 4 | 5 | function copyData(from, to) { 6 | let i = 0; 7 | while (i < from.length) { 8 | to[i] = from[i]; 9 | i++ 10 | } 11 | } 12 | 13 | describe('buffer-utils', () => { 14 | 15 | it('can convert Buffer to Buffer', (done) => { 16 | const input = Buffer.from(arrData) 17 | 18 | const actual = toBuffer(input); 19 | const actualArr = Array.prototype.slice.call(actual, 0); 20 | 21 | (actual.length).should.be.eql(arrData.length); 22 | (actualArr).should.be.eql(arrData); 23 | 24 | done(); 25 | }); 26 | 27 | it('can convert Uint8Array to Buffer', (done) => { 28 | const input = new Uint8Array(arrData) 29 | 30 | const actual = toBuffer(input); 31 | const actualArr = Array.prototype.slice.call(actual, 0); 32 | 33 | (actual.length).should.be.eql(arrData.length); 34 | (actualArr).should.be.eql(arrData); 35 | 36 | done(); 37 | }); 38 | 39 | it('can convert Uint8ClampedArray to Buffer', (done) => { 40 | const input = new Uint8ClampedArray(arrData) 41 | 42 | const actual = toBuffer(input); 43 | const actualArr = Array.prototype.slice.call(actual, 0); 44 | 45 | (actual.length).should.be.eql(arrData.length); 46 | (actualArr).should.be.eql(arrData); 47 | 48 | done(); 49 | }); 50 | 51 | it('can convert ArrayBuffer to Buffer', (done) => { 52 | const buffer = new ArrayBuffer(arrData.length); 53 | const view = new Uint8Array(buffer); 54 | copyData(arrData, view) 55 | 56 | const actual = toBuffer(buffer); 57 | 58 | const actualArr = Array.prototype.slice.call(actual, 0); 59 | 60 | (actual.length).should.be.eql(arrData.length); 61 | (actualArr).should.be.eql(arrData); 62 | 63 | done(); 64 | }); 65 | 66 | it('can convert ArrayBuffer to ArrayBuffer', (done) => { 67 | const input = new ArrayBuffer(arrData.length); 68 | const view = new Uint8Array(input); 69 | copyData(arrData, view) 70 | 71 | const actual = toArrayBuffer(input); 72 | const actualView = new Uint8Array(actual); 73 | const actualArr = Array.prototype.slice.call(actualView, 0); 74 | 75 | (actual.byteLength).should.be.eql(arrData.length); 76 | (actualArr).should.be.eql(arrData); 77 | 78 | done(); 79 | }); 80 | 81 | it('can convert Buffer to ArrayBuffer', (done) => { 82 | const input = Buffer.from(arrData) 83 | 84 | const actual = toArrayBuffer(input); 85 | const actualView = new Uint8Array(actual); 86 | const actualArr = Array.prototype.slice.call(actualView, 0); 87 | 88 | (actual.byteLength).should.be.eql(arrData.length); 89 | (actualArr).should.be.eql(arrData); 90 | 91 | done(); 92 | }); 93 | 94 | it('can convert Uint8Array to ArrayBuffer', (done) => { 95 | const input = new Uint8Array(arrData) 96 | 97 | const actual = toArrayBuffer(input); 98 | const actualView = new Uint8Array(actual); 99 | const actualArr = Array.prototype.slice.call(actualView, 0); 100 | 101 | (actual.byteLength).should.be.eql(arrData.length); 102 | (actualArr).should.be.eql(arrData); 103 | 104 | done(); 105 | }); 106 | 107 | it('can convert Uint8ClampedArray to ArrayBuffer', (done) => { 108 | const input = new Uint8ClampedArray(arrData) 109 | 110 | const actual = toArrayBuffer(input); 111 | const actualView = new Uint8Array(actual); 112 | const actualArr = Array.prototype.slice.call(actualView, 0); 113 | 114 | (actual.byteLength).should.be.eql(arrData.length); 115 | (actualArr).should.be.eql(arrData); 116 | 117 | done(); 118 | }); 119 | 120 | it('can convert Uint8Array to ArrayLike data structure', (done) => { 121 | const input = new Uint8Array(arrData) 122 | 123 | const actual = toArrayLike(input); 124 | const actualArr = Array.prototype.slice.call(actual, 0); 125 | 126 | (actual.length).should.be.eql(arrData.length); 127 | (actualArr).should.be.eql(arrData); 128 | 129 | done(); 130 | }); 131 | 132 | it('can convert Uint8ClampedArray to ArrayLike data structure', (done) => { 133 | const input = new Uint8ClampedArray(arrData) 134 | 135 | const actual = toArrayLike(input); 136 | const actualArr = Array.prototype.slice.call(actual, 0); 137 | 138 | (actual.length).should.be.eql(arrData.length); 139 | (actualArr).should.be.eql(arrData); 140 | 141 | done(); 142 | }); 143 | 144 | it('can convert ArrayBuffer to ArrayLike data structure', (done) => { 145 | const input = new ArrayBuffer(arrData.length); 146 | const view = new Uint8Array(input); 147 | copyData(arrData, view) 148 | 149 | const actual = toArrayLike(input); 150 | const actualArr = Array.prototype.slice.call(actual, 0); 151 | 152 | (actual.length).should.be.eql(arrData.length); 153 | (actualArr).should.be.eql(arrData); 154 | 155 | done(); 156 | }); 157 | 158 | it('can convert Buffer to ArrayLike data structure', (done) => { 159 | const input = Buffer.from(arrData) 160 | 161 | const actual = toArrayLike(input); 162 | const actualArr = Array.prototype.slice.call(actual, 0); 163 | 164 | (actual.length).should.be.eql(arrData.length); 165 | (actualArr).should.be.eql(arrData); 166 | 167 | done(); 168 | }); 169 | 170 | }); 171 | -------------------------------------------------------------------------------- /test/build.spec.js: -------------------------------------------------------------------------------- 1 | import should from 'should'; 2 | 3 | import lib from '../build/index'; 4 | import constants from './util/constants'; 5 | import init from './util/init'; 6 | 7 | const frameWidth = init.frameWidth; 8 | const frameHeight = init.frameHeight; 9 | 10 | describe('Build', () => { 11 | 12 | it('an image can be decoded ' + constants.name420, (done) => { 13 | const jpegData = constants.buf420; 14 | lib.decode(jpegData, (err, decoded) => { 15 | should.not.exist(err); 16 | should.exist(decoded); 17 | 18 | (decoded.width).should.be.eql(1052); 19 | (decoded.height).should.be.eql(1052); 20 | (decoded.data.buffer).should.be.instanceOf(ArrayBuffer); 21 | done(err); 22 | }); 23 | }); 24 | 25 | it('an image can be encoded', (done) => { 26 | const { width, height, data } = init.makeRgbBuffer(0xFF, 0, 0); 27 | 28 | const options = { 29 | width: width, 30 | height: height, 31 | quality: 80 32 | }; 33 | 34 | lib.encode(data, options, (err, encoded) => { 35 | should.not.exist(err); 36 | should.exist(encoded); 37 | (encoded.width).should.be.eql(frameWidth); 38 | (encoded.height).should.be.eql(frameHeight); 39 | (encoded.data).should.be.instanceOf(Uint8Array); 40 | 41 | done(err); 42 | }); 43 | }); 44 | 45 | }); 46 | -------------------------------------------------------------------------------- /test/color-util.spec.js: -------------------------------------------------------------------------------- 1 | import { arrayLikeRgbToRgba } from '../src/lib/util/color'; 2 | 3 | describe('Color', () => { 4 | 5 | it('can convert array from RGB to RGBA', (done) => { 6 | // NOTE: array encodes the image: 3x2 7 | const inputArr = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12]; 8 | const input = new Uint8Array(inputArr) 9 | 10 | const actual = arrayLikeRgbToRgba(input) 11 | 12 | const expectedArr = [0x01, 0x02, 0x03, 0xFF, 0x04, 0x05, 0x06, 0xFF, 0x07, 0x08, 0x09, 0xFF, 0x0A, 0x0B, 0x0C, 0xFF, 0x0D, 0x0E, 0x0F, 0xFF, 0x10, 0x11, 0x12, 0xFF]; 13 | const expected = new Uint8Array(expectedArr); 14 | 15 | (actual.length).should.be.eql(expected.length); 16 | (actual).should.be.eql(expected); 17 | 18 | done(); 19 | }); 20 | 21 | }).timeout(60000); 22 | -------------------------------------------------------------------------------- /test/decode.spec.js: -------------------------------------------------------------------------------- 1 | import should from 'should'; 2 | import lib from '../src/index'; 3 | import constants from './util/constants'; 4 | 5 | describe('Decode', () => { 6 | 7 | it('can be used to process ' + constants.name420, (done) => { 8 | const jpegData = constants.buf420; 9 | lib.decode(jpegData, (err, decoded) => { 10 | should.not.exist(err); 11 | should.exist(decoded); 12 | 13 | (decoded.width).should.be.eql(1052); 14 | (decoded.height).should.be.eql(1052); 15 | (decoded.data.buffer).should.be.instanceOf(ArrayBuffer); 16 | done(err); 17 | }); 18 | }); 19 | 20 | it('can be used to process ' + constants.name422h, (done) => { 21 | const jpegData = constants.buf422h; 22 | lib.decode(jpegData, (err, decoded) => { 23 | should.not.exist(err); 24 | should.exist(decoded); 25 | (decoded.width).should.be.eql(1052); 26 | (decoded.height).should.be.eql(1052); 27 | (decoded.data.buffer).should.be.instanceOf(ArrayBuffer); 28 | done(err); 29 | }); 30 | }); 31 | 32 | it('can be used to process ' + constants.name422v, (done) => { 33 | const jpegData = constants.buf422v; 34 | lib.decode(jpegData, (err, decoded) => { 35 | should.not.exist(err); 36 | should.exist(decoded); 37 | (decoded.width).should.be.eql(1052); 38 | (decoded.height).should.be.eql(1052); 39 | (decoded.data.buffer).should.be.instanceOf(ArrayBuffer); 40 | done(err); 41 | }) 42 | }); 43 | 44 | it('can be used to process ' + constants.name444, (done) => { 45 | const jpegData = constants.buf444; 46 | lib.decode(jpegData, (err, decoded) => { 47 | should.not.exist(err); 48 | should.exist(decoded); 49 | (decoded.width).should.be.eql(1052); 50 | (decoded.height).should.be.eql(1052); 51 | (decoded.data.buffer).should.be.instanceOf(ArrayBuffer); 52 | done(err); 53 | }); 54 | }); 55 | 56 | it('fail to process ' + constants.nameAC, (done) => { 57 | const jpegData = constants.bufAC; 58 | lib.decode(jpegData, (err, decoded) => { 59 | should.exist(err); 60 | should.not.exist(decoded); 61 | err.should.be.an.instanceOf(Error); 62 | done(decoded); 63 | }); 64 | }); 65 | 66 | it('can be used to process ' + constants.nameP, (done) => { 67 | const jpegData = constants.bufP; 68 | lib.decode(jpegData, (err, decoded) => { 69 | should.not.exist(err); 70 | should.exist(decoded); 71 | (decoded.width).should.be.eql(1052); 72 | (decoded.height).should.be.eql(1052); 73 | (decoded.data.buffer).should.be.instanceOf(ArrayBuffer); 74 | done(err); 75 | }); 76 | }); 77 | 78 | it('can be used to process ' + constants.nameDCTF, (done) => { 79 | const jpegData = constants.bufDCTF; 80 | lib.decode(jpegData, (err, decoded) => { 81 | should.not.exist(err); 82 | should.exist(decoded); 83 | (decoded.width).should.be.eql(1052); 84 | (decoded.height).should.be.eql(1052); 85 | (decoded.data.buffer).should.be.instanceOf(ArrayBuffer); 86 | done(err); 87 | }); 88 | }); 89 | 90 | it('can be used to process ' + constants.nameCP, (done) => { 91 | const jpegData = constants.bufCP; 92 | lib.decode(jpegData, (err, decoded) => { 93 | should.not.exist(err); 94 | should.exist(decoded); 95 | (decoded.width).should.be.eql(1052); 96 | (decoded.height).should.be.eql(1052); 97 | (decoded.data.buffer).should.be.instanceOf(ArrayBuffer); 98 | done(err); 99 | }); 100 | }); 101 | 102 | it('can be used to process ' + constants.nameExif, (done) => { 103 | const jpegData = constants.bufExif; 104 | lib.decode(jpegData, (err, decoded) => { 105 | should.not.exist(err); 106 | should.exist(decoded); 107 | (decoded.width).should.be.eql(1052); 108 | (decoded.height).should.be.eql(1052); 109 | (decoded.data.buffer).should.be.instanceOf(ArrayBuffer); 110 | done(err); 111 | }); 112 | }); 113 | 114 | it('fails to decode a broken JPEG image', (done) => { 115 | const jpegData = constants.bufBroken; 116 | lib.decode(jpegData, (err, decoded) => { 117 | should.exist(err); 118 | should.not.exist(decoded); 119 | err.should.be.an.instanceOf(Error); 120 | done(decoded); 121 | }); 122 | }); 123 | 124 | }).timeout(60000); 125 | -------------------------------------------------------------------------------- /test/encode.spec.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import should from 'should'; 3 | import lib from '../src/index'; 4 | import writer from './util/file-writer'; 5 | import init from './util/init'; 6 | 7 | const frameWidth = init.frameWidth; 8 | const frameHeight = init.frameHeight; 9 | 10 | describe('Encode', () => { 11 | 12 | it('can be used to create a JPEG image (Buffer)', (done) => { 13 | const { width, height, data } = init.makeRgbBuffer(0xFF, 0, 0); 14 | 15 | const options = { 16 | width: width, 17 | height: height, 18 | quality: 80 19 | }; 20 | 21 | lib.encode(data, options, (err, encoded) => { 22 | should.not.exist(err); 23 | should.exist(encoded); 24 | (encoded.width).should.be.eql(frameWidth); 25 | (encoded.height).should.be.eql(frameHeight); 26 | (encoded.data).should.be.instanceOf(Uint8Array); 27 | 28 | if('writeFileSync' in writer) { 29 | writer.writeFileSync(path.join(__dirname, './out/' + 'encoded-red.jpg'), encoded.data); 30 | } 31 | 32 | done(err); 33 | }); 34 | }); 35 | 36 | it('can be used to create a JPEG image (ArrayBuffer)', (done) => { 37 | const { width, height, data } = init.makeRgbArrayBuffer( 0, 0xFF, 0); 38 | 39 | const options = { 40 | width: width, 41 | height: height, 42 | quality: 80 43 | }; 44 | 45 | lib.encode(data, options, (err, encoded) => { 46 | should.not.exist(err); 47 | should.exist(encoded); 48 | (encoded.width).should.be.eql(frameWidth); 49 | (encoded.height).should.be.eql(frameHeight); 50 | (encoded.data).should.be.instanceOf(Uint8Array); 51 | 52 | if('writeFileSync' in writer) { 53 | writer.writeFileSync(path.join(__dirname, './out/' + 'encoded-green.jpg'), encoded.data); 54 | } 55 | 56 | done(err); 57 | }); 58 | }); 59 | 60 | it('can be used to create a JPEG image (Uint8Array)', (done) => { 61 | const { width, height, data } = init.makeRgbUint8Array(0, 0, 0xFF); 62 | 63 | const options = { 64 | width: width, 65 | height: height, 66 | quality: 80 67 | }; 68 | 69 | lib.encode(data, options, (err, encoded) => { 70 | should.not.exist(err); 71 | should.exist(encoded); 72 | (encoded.width).should.be.eql(frameWidth); 73 | (encoded.height).should.be.eql(frameHeight); 74 | (encoded.data).should.be.instanceOf(Uint8Array); 75 | 76 | if('writeFileSync' in writer) { 77 | writer.writeFileSync(path.join(__dirname, './out/' + 'encoded-blue.jpg'), encoded.data); 78 | } 79 | 80 | done(err); 81 | }); 82 | }); 83 | 84 | it('can be used to create a JPEG image (Uint8ClampedArray)', (done) => { 85 | const { width, height, data } = init.makeRgbUint8ClampedArray(0, 0xFF, 0xFF); 86 | 87 | const options = { 88 | width: width, 89 | height: height, 90 | quality: 80 91 | }; 92 | 93 | lib.encode(data, options, (err, encoded) => { 94 | should.not.exist(err); 95 | should.exist(encoded); 96 | (encoded.width).should.be.eql(frameWidth); 97 | (encoded.height).should.be.eql(frameHeight); 98 | (encoded.data).should.be.instanceOf(Uint8Array); 99 | 100 | if('writeFileSync' in writer) { 101 | writer.writeFileSync(path.join(__dirname, './out/' + 'encoded-cyan.jpg'), encoded.data); 102 | } 103 | 104 | done(err); 105 | }); 106 | }); 107 | 108 | }).timeout(60000); 109 | -------------------------------------------------------------------------------- /test/exif.spec.js: -------------------------------------------------------------------------------- 1 | import should from 'should'; 2 | import lib from '../src/index'; 3 | import constants from './util/constants'; 4 | 5 | describe('Exif', () => { 6 | 7 | it('should be detected for ' + constants.nameExif, (done) => { 8 | const jpegData = constants.bufExif; 9 | lib.exif(jpegData, (err, data) => { 10 | should.not.exist(err); 11 | should.exist(data); 12 | data.should.have.properties('ImageDescription'); 13 | done(err); 14 | }); 15 | }); 16 | 17 | it('should be detected for ' + constants.nameExif + ' (take 128Kb)', (done) => { 18 | let jpegData = constants.bufExif; 19 | jpegData = jpegData.slice(0, 128 * 1024); 20 | lib.exif(jpegData, (err, data) => { 21 | should.not.exist(err); 22 | should.exist(data); 23 | data.should.have.properties('ImageDescription'); 24 | done(err); 25 | }); 26 | }); 27 | 28 | it('should NOT be detected for ' + constants.name420, (done) => { 29 | const jpegData = constants.buf420; 30 | lib.exif(jpegData, (err, data) => { 31 | should.not.exist(err); 32 | should.exist(data); 33 | done(err); 34 | }); 35 | }); 36 | 37 | it('should return an error for ' + constants.nameBroken, (done) => { 38 | const jpegData = constants.bufBroken; 39 | lib.exif(jpegData, (err, data) => { 40 | should.exist(err); 41 | should.not.exist(data); 42 | done(data); 43 | }); 44 | }); 45 | 46 | }).timeout(60000); 47 | -------------------------------------------------------------------------------- /test/info.spec.js: -------------------------------------------------------------------------------- 1 | import should from 'should'; 2 | import lib from '../src/index'; 3 | import constants from './util/constants'; 4 | 5 | describe('Info', () => { 6 | 7 | it('can be fetched for a JPEG file', (done) => { 8 | const buf = constants.buf420; 9 | lib.info(buf, (err, data) => { 10 | should.not.exist(err); 11 | should.exist(data); 12 | 13 | data.should.have.a.property('type').equal('image'); 14 | data.should.have.a.property('mimeType').equal('image/jpeg'); 15 | data.should.have.a.property('extension').equal('jpg'); 16 | data.should.have.a.property('width').equal(1052); 17 | data.should.have.a.property('height').equal(1052); 18 | 19 | done(err); 20 | }); 21 | }); 22 | 23 | it('can be fetched for a PNG file', (done) => { 24 | const buf = constants.bufPng; 25 | lib.info(buf, (err, data) => { 26 | should.not.exist(err); 27 | should.exist(data); 28 | 29 | data.should.have.a.property('type').equal('image'); 30 | data.should.have.a.property('mimeType').equal('image/png'); 31 | data.should.have.a.property('extension').equal('png'); 32 | data.should.have.a.property('width').equal(1052); 33 | data.should.have.a.property('height').equal(1052); 34 | 35 | done(err); 36 | }); 37 | }); 38 | 39 | it('cannot be fetched for a broken JPEG file', (done) => { 40 | const buf = constants.bufBroken; 41 | lib.info(buf, (err, data) => { 42 | should.exist(err); 43 | should.not.exist(data); 44 | err.should.be.an.instanceOf(Error); 45 | done(data); 46 | }); 47 | }); 48 | 49 | }); -------------------------------------------------------------------------------- /test/magic.spec.js: -------------------------------------------------------------------------------- 1 | import should from 'should'; 2 | import lib from '../src/index'; 3 | import constants from './util/constants'; 4 | 5 | describe('Magic number', () => { 6 | 7 | it('can be detected for a JPEG Buffer', (done) => { 8 | const buf = Buffer.from([0xFF, 0xD8, 0xFF]); 9 | 10 | lib.magic(buf, (err, result) => { 11 | should.not.exist(err); 12 | should.exist(result); 13 | 14 | result.mimeType.should.be.equal('image/jpeg'); 15 | result.extension.should.be.equal('jpg'); 16 | 17 | done(err); 18 | }); 19 | }); 20 | 21 | it('can be detected for a JPEG ArrayBuffer', (done) => { 22 | const buf = new ArrayBuffer(3); 23 | const view = new Uint8Array(buf); 24 | view[0] = 0xFF; 25 | view[1] = 0xD8; 26 | view[2] = 0xFF; 27 | 28 | lib.magic(buf, (err, result) => { 29 | should.not.exist(err); 30 | should.exist(result); 31 | 32 | result.mimeType.should.be.equal('image/jpeg'); 33 | result.extension.should.be.equal('jpg'); 34 | 35 | done(err); 36 | }); 37 | }); 38 | 39 | it('can be detected for a JPEG Uint8Array', (done) => { 40 | const buf = new ArrayBuffer(3); 41 | const view = new Uint8Array(buf); 42 | view[0] = 0xFF; 43 | view[1] = 0xD8; 44 | view[2] = 0xFF; 45 | 46 | lib.magic(view, (err, result) => { 47 | should.not.exist(err); 48 | should.exist(result); 49 | 50 | result.mimeType.should.be.equal('image/jpeg'); 51 | result.extension.should.be.equal('jpg'); 52 | 53 | done(err); 54 | }); 55 | }); 56 | 57 | it('can be detected for a JPEG Uint8ClampedArray', (done) => { 58 | const buf = new ArrayBuffer(3); 59 | const view = new Uint8ClampedArray(buf); 60 | view[0] = 0xFF; 61 | view[1] = 0xD8; 62 | view[2] = 0xFF; 63 | 64 | lib.magic(view, (err, result) => { 65 | should.not.exist(err); 66 | should.exist(result); 67 | 68 | result.mimeType.should.be.equal('image/jpeg'); 69 | result.extension.should.be.equal('jpg'); 70 | 71 | done(err); 72 | }); 73 | }); 74 | 75 | it('can be detected for a JPEG file', (done) => { 76 | const buf = constants.buf420; 77 | 78 | lib.magic(buf, (err, result) => { 79 | should.not.exist(err); 80 | should.exist(result); 81 | 82 | result.mimeType.should.be.equal('image/jpeg'); 83 | result.extension.should.be.equal('jpg'); 84 | 85 | done(err); 86 | }); 87 | }); 88 | 89 | it('can be detected for a PNG file', (done) => { 90 | const buf = constants.bufPng; 91 | 92 | lib.magic(buf, (err, result) => { 93 | should.not.exist(err); 94 | should.exist(result); 95 | 96 | result.mimeType.should.be.equal('image/png'); 97 | result.extension.should.be.equal('png'); 98 | 99 | done(err); 100 | }); 101 | }); 102 | 103 | it('cannot be detected for a broken JPEG file', (done) => { 104 | const buf = constants.bufBroken; 105 | 106 | lib.magic(buf, (err, result) => { 107 | should.exist(err); 108 | should.not.exist(result); 109 | err.should.be.an.instanceOf(Error); 110 | done(result); 111 | }); 112 | }); 113 | 114 | }); 115 | -------------------------------------------------------------------------------- /test/re-encode.spec.js: -------------------------------------------------------------------------------- 1 | import should from 'should'; 2 | import path from 'path'; 3 | import lib from '../src/index'; 4 | import constants from './util/constants'; 5 | import writer from './util/file-writer'; 6 | 7 | describe('Re-Encode', () => { 8 | 9 | it('can be used to process ' + constants.name420, (done) => { 10 | const jpegData = constants.buf420; 11 | lib.decode(jpegData, (err, decoded) => { 12 | should.not.exist(err); 13 | should.exist(decoded); 14 | (decoded.width).should.be.eql(1052); 15 | (decoded.height).should.be.eql(1052); 16 | (decoded.data.buffer).should.be.instanceOf(ArrayBuffer); 17 | 18 | const buf = decoded.data; 19 | const options = { 20 | width: decoded.width, 21 | height: decoded.height, 22 | quality: 80 23 | }; 24 | 25 | lib.encode(buf, options, (err, encoded) => { 26 | should.not.exist(err); 27 | should.exist(encoded); 28 | (encoded.width).should.be.eql(1052); 29 | (encoded.height).should.be.eql(1052); 30 | (encoded.data.buffer).should.be.instanceOf(ArrayBuffer); 31 | 32 | if('writeFileSync' in writer) { 33 | writer.writeFileSync(path.join(__dirname, './out/' + constants.name420), encoded.data); 34 | } 35 | 36 | done(err); 37 | }); 38 | }); 39 | }); 40 | 41 | it('can be used to process ' + constants.name422h, (done) => { 42 | const jpegData = constants.buf422h; 43 | lib.decode(jpegData, { width: 1052, height: 1052 }, (err, decoded) => { 44 | should.not.exist(err); 45 | should.exist(decoded); 46 | (decoded.width).should.be.eql(1052); 47 | (decoded.height).should.be.eql(1052); 48 | (decoded.data.buffer).should.be.instanceOf(ArrayBuffer); 49 | 50 | const buf = decoded.data; 51 | const options = { 52 | width: decoded.width, 53 | height: decoded.height, 54 | quality: 80 55 | }; 56 | 57 | lib.encode(buf, options, (err, encoded) => { 58 | should.not.exist(err); 59 | should.exist(encoded); 60 | (encoded.width).should.be.eql(1052); 61 | (encoded.height).should.be.eql(1052); 62 | (encoded.data.buffer).should.be.instanceOf(ArrayBuffer); 63 | 64 | if('writeFileSync' in writer) { 65 | writer.writeFileSync(path.join(__dirname, './out/' + constants.name422h), encoded.data); 66 | } 67 | 68 | done(err); 69 | }); 70 | }); 71 | }); 72 | 73 | it('can be used to process ' + constants.name422v, (done) => { 74 | const jpegData = constants.buf422v; 75 | lib.decode(jpegData, (err, decoded) => { 76 | should.not.exist(err); 77 | should.exist(decoded); 78 | (decoded.width).should.be.eql(1052); 79 | (decoded.height).should.be.eql(1052); 80 | (decoded.data.buffer).should.be.instanceOf(ArrayBuffer); 81 | 82 | const buf = decoded.data; 83 | const options = { 84 | width: decoded.width, 85 | height: decoded.height, 86 | quality: 80 87 | }; 88 | 89 | lib.encode(buf, options, (err, encoded) => { 90 | should.not.exist(err); 91 | should.exist(encoded); 92 | (encoded.width).should.be.eql(1052); 93 | (encoded.height).should.be.eql(1052); 94 | (encoded.data.buffer).should.be.instanceOf(ArrayBuffer); 95 | 96 | if('writeFileSync' in writer) { 97 | writer.writeFileSync(path.join(__dirname, './out/' + constants.name422v), encoded.data); 98 | } 99 | 100 | done(err); 101 | }); 102 | }); 103 | }); 104 | 105 | it('can be used to process ' + constants.nameExif, (done) => { 106 | const jpegData = constants.bufExif; 107 | lib.decode(jpegData, (err, decoded) => { 108 | should.not.exist(err); 109 | should.exist(decoded); 110 | (decoded.width).should.be.eql(1052); 111 | (decoded.height).should.be.eql(1052); 112 | (decoded.data.buffer).should.be.instanceOf(ArrayBuffer); 113 | 114 | const buf = decoded.data; 115 | const options = { 116 | width: decoded.width, 117 | height: decoded.height, 118 | quality: 80 119 | }; 120 | 121 | lib.encode(buf, options, (err, encoded) => { 122 | should.not.exist(err); 123 | should.exist(encoded); 124 | (encoded.width).should.be.eql(1052); 125 | (encoded.height).should.be.eql(1052); 126 | (encoded.data.buffer).should.be.instanceOf(ArrayBuffer); 127 | 128 | if('writeFileSync' in writer) { 129 | writer.writeFileSync(path.join(__dirname, './out/' + constants.nameExif), encoded.data); 130 | } 131 | 132 | done(err); 133 | }); 134 | }); 135 | }); 136 | 137 | }).timeout(60000); 138 | -------------------------------------------------------------------------------- /test/util/constants.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | 4 | const namePng = 'js_logo.png'; 5 | const name420 = 'js_logo-4-2-0.jpg'; 6 | const name422h = 'js_logo-4-2-2-horz.jpg'; 7 | const name422v = 'js_logo-4-2-2-vert.jpg'; 8 | const name444 = 'js_logo-4-4-4.jpg'; 9 | const nameAC = 'js_logo-arithmetic-coding.jpg'; 10 | const nameP = 'js_logo-progressive.jpg'; 11 | const nameDCTF = 'js_logo-dct-float.jpg'; 12 | const nameCP = 'js_logo-sRGB-IEC61966-2-1.jpg'; 13 | const nameExif = 'js_logo-exif.jpg'; 14 | const nameBroken = 'js_broken.jpg'; 15 | 16 | const pathPng = path.join(__dirname, '../../images/', namePng); 17 | const path420 = path.join(__dirname, '../../images/', name420); 18 | const path422h = path.join(__dirname, '../../images/', name422h); 19 | const path422v = path.join(__dirname, '../../images/', name422v); 20 | const path444 = path.join(__dirname, '../../images/', name444); 21 | const pathAC = path.join(__dirname, '../../images/', nameAC); 22 | const pathP = path.join(__dirname, '../../images/', nameP); 23 | const pathDCTF = path.join(__dirname, '../../images/', nameDCTF); 24 | const pathCP = path.join(__dirname, '../../images/', nameCP); 25 | const pathExif = path.join(__dirname, '../../images/', nameExif); 26 | const pathBroken = path.join(__dirname, '../../images/', nameBroken); 27 | 28 | export default { 29 | namePng: namePng, 30 | name420: name420, 31 | name422h: name422h, 32 | name422v: name422v, 33 | name444: name444, 34 | nameAC: nameAC, 35 | nameP: nameP, 36 | nameDCTF: nameDCTF, 37 | nameCP: nameCP, 38 | nameExif: nameExif, 39 | nameBroken: nameBroken, 40 | 41 | pathPng: pathPng, 42 | path420: path420, 43 | path422h: path422h, 44 | path422v: path422v, 45 | path444: path444, 46 | pathAC: pathAC, 47 | pathP: pathP, 48 | pathDCTF: pathDCTF, 49 | pathCP: pathCP, 50 | pathExif: pathExif, 51 | pathBroken: pathBroken, 52 | 53 | bufPng: fs.readFileSync(pathPng), 54 | buf420: fs.readFileSync(path420), 55 | buf422h: fs.readFileSync(path422h), 56 | buf422v: fs.readFileSync(path422v), 57 | buf444: fs.readFileSync(path444), 58 | bufAC: fs.readFileSync(pathAC), 59 | bufP: fs.readFileSync(pathP), 60 | bufDCTF: fs.readFileSync(pathDCTF), 61 | bufCP: fs.readFileSync(pathCP), 62 | bufExif: fs.readFileSync(pathExif), 63 | bufBroken: fs.readFileSync(pathBroken), 64 | } 65 | -------------------------------------------------------------------------------- /test/util/file-writer.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | module.exports.writeFileSync = writeFileSync; 5 | 6 | /** 7 | * Write file to the `out` directory 8 | * NOTE: this method is used for test in node.js and disabled in browserify 9 | * @param filepath {string} 10 | * @param buf {Buffer} 11 | */ 12 | function writeFileSync(filepath, buf) { 13 | const outDir = path.join(__dirname, '../out'); 14 | 15 | if(!fs.existsSync(outDir)) { 16 | fs.mkdirSync(outDir); 17 | } 18 | 19 | fs.writeFileSync(filepath, buf); 20 | } 21 | -------------------------------------------------------------------------------- /test/util/init.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const frameComponents = 4; 4 | const frameWidth = 320; 5 | const frameHeight = 240; 6 | const frameLength = frameWidth * frameHeight * frameComponents; 7 | 8 | function initData(frameData, r, g, b) { 9 | const length = frameData.length || frameData.byteLength; 10 | const view = (frameData instanceof ArrayBuffer) ? new Uint8Array(frameData) : frameData; 11 | 12 | let i = 0; 13 | while (i < length) { 14 | view[i++] = r; // red 15 | view[i++] = g; // green 16 | view[i++] = b; // blue 17 | view[i++] = 0xFF; // alpha - ignored in JPEGs 18 | } 19 | 20 | return { 21 | width: frameWidth, 22 | height: frameHeight, 23 | data: frameData 24 | } 25 | } 26 | 27 | function makeRgbBuffer(r, g, b) { 28 | const frameData = Buffer.alloc(frameLength); 29 | return initData(frameData, r, g, b) 30 | } 31 | 32 | function makeRgbArrayBuffer(r, g, b) { 33 | const frameData = new ArrayBuffer(frameLength); 34 | return initData(frameData, r, g, b) 35 | } 36 | 37 | function makeRgbUint8Array(r, g, b) { 38 | const frameData = new Uint8Array(frameLength); 39 | return initData(frameData, r, g, b) 40 | } 41 | 42 | function makeRgbUint8ClampedArray(r, g, b) { 43 | const frameData = new Uint8ClampedArray(frameLength); 44 | return initData(frameData, r, g, b) 45 | } 46 | 47 | export default { 48 | makeRgbBuffer: makeRgbBuffer, 49 | makeRgbArrayBuffer: makeRgbArrayBuffer, 50 | makeRgbUint8Array: makeRgbUint8Array, 51 | makeRgbUint8ClampedArray: makeRgbUint8ClampedArray, 52 | 53 | frameWidth: frameWidth, 54 | frameHeight: frameHeight, 55 | 56 | 57 | }; 58 | --------------------------------------------------------------------------------