├── .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 | 
2 |
3 | 
4 |
5 | > JPEG-image decoding, encoding & EXIF reading library for a browser and node.js
6 |
7 | 
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 | 85 |
22 | 81 |
23 | 85 |
24 | 11* |
25 | 14 |
26 |
27 |
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 |
16 |
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 |
16 |
19 |
20 |
21 |
22 | Tag name |
23 | Tag value |
24 |
25 |
26 |
27 |
28 |
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 |
16 |
19 |
20 |
21 |
22 | Name |
23 | Value |
24 |
25 |
26 |
27 |
28 |
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 |
16 |
19 |
20 |
21 |
22 | MIME Type |
23 | Extension |
24 |
25 |
26 |
27 |
28 | |
29 | |
30 |
31 |
32 |
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 |
--------------------------------------------------------------------------------