1 |
|
2 |
|
3 |
|
4 | 'use strict';
|
5 |
|
6 | const color = require('color');
|
7 | const is = require('./is');
|
8 | const sharp = require('./sharp');
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 | const align = {
|
16 | left: 'low',
|
17 | center: 'centre',
|
18 | centre: 'centre',
|
19 | right: 'high'
|
20 | };
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 | function _inputOptionsFromObject (obj) {
|
27 | const { raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd } = obj;
|
28 | return [raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd].some(is.defined)
|
29 | ? { raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd }
|
30 | : undefined;
|
31 | }
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 | function _createInputDescriptor (input, inputOptions, containerOptions) {
|
38 | const inputDescriptor = {
|
39 | failOn: 'warning',
|
40 | limitInputPixels: Math.pow(0x3FFF, 2),
|
41 | ignoreIcc: false,
|
42 | unlimited: false,
|
43 | sequentialRead: true
|
44 | };
|
45 | if (is.string(input)) {
|
46 |
|
47 | inputDescriptor.file = input;
|
48 | } else if (is.buffer(input)) {
|
49 |
|
50 | if (input.length === 0) {
|
51 | throw Error('Input Buffer is empty');
|
52 | }
|
53 | inputDescriptor.buffer = input;
|
54 | } else if (is.arrayBuffer(input)) {
|
55 | if (input.byteLength === 0) {
|
56 | throw Error('Input bit Array is empty');
|
57 | }
|
58 | inputDescriptor.buffer = Buffer.from(input, 0, input.byteLength);
|
59 | } else if (is.typedArray(input)) {
|
60 | if (input.length === 0) {
|
61 | throw Error('Input Bit Array is empty');
|
62 | }
|
63 | inputDescriptor.buffer = Buffer.from(input.buffer, input.byteOffset, input.byteLength);
|
64 | } else if (is.plainObject(input) && !is.defined(inputOptions)) {
|
65 |
|
66 | inputOptions = input;
|
67 | if (_inputOptionsFromObject(inputOptions)) {
|
68 |
|
69 | inputDescriptor.buffer = [];
|
70 | }
|
71 | } else if (!is.defined(input) && !is.defined(inputOptions) && is.object(containerOptions) && containerOptions.allowStream) {
|
72 |
|
73 | inputDescriptor.buffer = [];
|
74 | } else {
|
75 | throw new Error(`Unsupported input '${input}' of type ${typeof input}${
|
76 | is.defined(inputOptions) ? ` when also providing options of type ${typeof inputOptions}` : ''
|
77 | }`);
|
78 | }
|
79 | if (is.object(inputOptions)) {
|
80 |
|
81 | if (is.defined(inputOptions.failOnError)) {
|
82 | if (is.bool(inputOptions.failOnError)) {
|
83 | inputDescriptor.failOn = inputOptions.failOnError ? 'warning' : 'none';
|
84 | } else {
|
85 | throw is.invalidParameterError('failOnError', 'boolean', inputOptions.failOnError);
|
86 | }
|
87 | }
|
88 |
|
89 | if (is.defined(inputOptions.failOn)) {
|
90 | if (is.string(inputOptions.failOn) && is.inArray(inputOptions.failOn, ['none', 'truncated', 'error', 'warning'])) {
|
91 | inputDescriptor.failOn = inputOptions.failOn;
|
92 | } else {
|
93 | throw is.invalidParameterError('failOn', 'one of: none, truncated, error, warning', inputOptions.failOn);
|
94 | }
|
95 | }
|
96 |
|
97 | if (is.defined(inputOptions.density)) {
|
98 | if (is.inRange(inputOptions.density, 1, 100000)) {
|
99 | inputDescriptor.density = inputOptions.density;
|
100 | } else {
|
101 | throw is.invalidParameterError('density', 'number between 1 and 100000', inputOptions.density);
|
102 | }
|
103 | }
|
104 |
|
105 | if (is.defined(inputOptions.ignoreIcc)) {
|
106 | if (is.bool(inputOptions.ignoreIcc)) {
|
107 | inputDescriptor.ignoreIcc = inputOptions.ignoreIcc;
|
108 | } else {
|
109 | throw is.invalidParameterError('ignoreIcc', 'boolean', inputOptions.ignoreIcc);
|
110 | }
|
111 | }
|
112 |
|
113 | if (is.defined(inputOptions.limitInputPixels)) {
|
114 | if (is.bool(inputOptions.limitInputPixels)) {
|
115 | inputDescriptor.limitInputPixels = inputOptions.limitInputPixels
|
116 | ? Math.pow(0x3FFF, 2)
|
117 | : 0;
|
118 | } else if (is.integer(inputOptions.limitInputPixels) && is.inRange(inputOptions.limitInputPixels, 0, Number.MAX_SAFE_INTEGER)) {
|
119 | inputDescriptor.limitInputPixels = inputOptions.limitInputPixels;
|
120 | } else {
|
121 | throw is.invalidParameterError('limitInputPixels', 'positive integer', inputOptions.limitInputPixels);
|
122 | }
|
123 | }
|
124 |
|
125 | if (is.defined(inputOptions.unlimited)) {
|
126 | if (is.bool(inputOptions.unlimited)) {
|
127 | inputDescriptor.unlimited = inputOptions.unlimited;
|
128 | } else {
|
129 | throw is.invalidParameterError('unlimited', 'boolean', inputOptions.unlimited);
|
130 | }
|
131 | }
|
132 |
|
133 | if (is.defined(inputOptions.sequentialRead)) {
|
134 | if (is.bool(inputOptions.sequentialRead)) {
|
135 | inputDescriptor.sequentialRead = inputOptions.sequentialRead;
|
136 | } else {
|
137 | throw is.invalidParameterError('sequentialRead', 'boolean', inputOptions.sequentialRead);
|
138 | }
|
139 | }
|
140 |
|
141 | if (is.defined(inputOptions.raw)) {
|
142 | if (
|
143 | is.object(inputOptions.raw) &&
|
144 | is.integer(inputOptions.raw.width) && inputOptions.raw.width > 0 &&
|
145 | is.integer(inputOptions.raw.height) && inputOptions.raw.height > 0 &&
|
146 | is.integer(inputOptions.raw.channels) && is.inRange(inputOptions.raw.channels, 1, 4)
|
147 | ) {
|
148 | inputDescriptor.rawWidth = inputOptions.raw.width;
|
149 | inputDescriptor.rawHeight = inputOptions.raw.height;
|
150 | inputDescriptor.rawChannels = inputOptions.raw.channels;
|
151 | inputDescriptor.rawPremultiplied = !!inputOptions.raw.premultiplied;
|
152 |
|
153 | switch (input.constructor) {
|
154 | case Uint8Array:
|
155 | case Uint8ClampedArray:
|
156 | inputDescriptor.rawDepth = 'uchar';
|
157 | break;
|
158 | case Int8Array:
|
159 | inputDescriptor.rawDepth = 'char';
|
160 | break;
|
161 | case Uint16Array:
|
162 | inputDescriptor.rawDepth = 'ushort';
|
163 | break;
|
164 | case Int16Array:
|
165 | inputDescriptor.rawDepth = 'short';
|
166 | break;
|
167 | case Uint32Array:
|
168 | inputDescriptor.rawDepth = 'uint';
|
169 | break;
|
170 | case Int32Array:
|
171 | inputDescriptor.rawDepth = 'int';
|
172 | break;
|
173 | case Float32Array:
|
174 | inputDescriptor.rawDepth = 'float';
|
175 | break;
|
176 | case Float64Array:
|
177 | inputDescriptor.rawDepth = 'double';
|
178 | break;
|
179 | default:
|
180 | inputDescriptor.rawDepth = 'uchar';
|
181 | break;
|
182 | }
|
183 | } else {
|
184 | throw new Error('Expected width, height and channels for raw pixel input');
|
185 | }
|
186 | }
|
187 |
|
188 | if (is.defined(inputOptions.animated)) {
|
189 | if (is.bool(inputOptions.animated)) {
|
190 | inputDescriptor.pages = inputOptions.animated ? -1 : 1;
|
191 | } else {
|
192 | throw is.invalidParameterError('animated', 'boolean', inputOptions.animated);
|
193 | }
|
194 | }
|
195 | if (is.defined(inputOptions.pages)) {
|
196 | if (is.integer(inputOptions.pages) && is.inRange(inputOptions.pages, -1, 100000)) {
|
197 | inputDescriptor.pages = inputOptions.pages;
|
198 | } else {
|
199 | throw is.invalidParameterError('pages', 'integer between -1 and 100000', inputOptions.pages);
|
200 | }
|
201 | }
|
202 | if (is.defined(inputOptions.page)) {
|
203 | if (is.integer(inputOptions.page) && is.inRange(inputOptions.page, 0, 100000)) {
|
204 | inputDescriptor.page = inputOptions.page;
|
205 | } else {
|
206 | throw is.invalidParameterError('page', 'integer between 0 and 100000', inputOptions.page);
|
207 | }
|
208 | }
|
209 |
|
210 | if (is.defined(inputOptions.level)) {
|
211 | if (is.integer(inputOptions.level) && is.inRange(inputOptions.level, 0, 256)) {
|
212 | inputDescriptor.level = inputOptions.level;
|
213 | } else {
|
214 | throw is.invalidParameterError('level', 'integer between 0 and 256', inputOptions.level);
|
215 | }
|
216 | }
|
217 |
|
218 | if (is.defined(inputOptions.subifd)) {
|
219 | if (is.integer(inputOptions.subifd) && is.inRange(inputOptions.subifd, -1, 100000)) {
|
220 | inputDescriptor.subifd = inputOptions.subifd;
|
221 | } else {
|
222 | throw is.invalidParameterError('subifd', 'integer between -1 and 100000', inputOptions.subifd);
|
223 | }
|
224 | }
|
225 |
|
226 | if (is.defined(inputOptions.create)) {
|
227 | if (
|
228 | is.object(inputOptions.create) &&
|
229 | is.integer(inputOptions.create.width) && inputOptions.create.width > 0 &&
|
230 | is.integer(inputOptions.create.height) && inputOptions.create.height > 0 &&
|
231 | is.integer(inputOptions.create.channels)
|
232 | ) {
|
233 | inputDescriptor.createWidth = inputOptions.create.width;
|
234 | inputDescriptor.createHeight = inputOptions.create.height;
|
235 | inputDescriptor.createChannels = inputOptions.create.channels;
|
236 |
|
237 | if (is.defined(inputOptions.create.noise)) {
|
238 | if (!is.object(inputOptions.create.noise)) {
|
239 | throw new Error('Expected noise to be an object');
|
240 | }
|
241 | if (!is.inArray(inputOptions.create.noise.type, ['gaussian'])) {
|
242 | throw new Error('Only gaussian noise is supported at the moment');
|
243 | }
|
244 | if (!is.inRange(inputOptions.create.channels, 1, 4)) {
|
245 | throw is.invalidParameterError('create.channels', 'number between 1 and 4', inputOptions.create.channels);
|
246 | }
|
247 | inputDescriptor.createNoiseType = inputOptions.create.noise.type;
|
248 | if (is.number(inputOptions.create.noise.mean) && is.inRange(inputOptions.create.noise.mean, 0, 10000)) {
|
249 | inputDescriptor.createNoiseMean = inputOptions.create.noise.mean;
|
250 | } else {
|
251 | throw is.invalidParameterError('create.noise.mean', 'number between 0 and 10000', inputOptions.create.noise.mean);
|
252 | }
|
253 | if (is.number(inputOptions.create.noise.sigma) && is.inRange(inputOptions.create.noise.sigma, 0, 10000)) {
|
254 | inputDescriptor.createNoiseSigma = inputOptions.create.noise.sigma;
|
255 | } else {
|
256 | throw is.invalidParameterError('create.noise.sigma', 'number between 0 and 10000', inputOptions.create.noise.sigma);
|
257 | }
|
258 | } else if (is.defined(inputOptions.create.background)) {
|
259 | if (!is.inRange(inputOptions.create.channels, 3, 4)) {
|
260 | throw is.invalidParameterError('create.channels', 'number between 3 and 4', inputOptions.create.channels);
|
261 | }
|
262 | const background = color(inputOptions.create.background);
|
263 | inputDescriptor.createBackground = [
|
264 | background.red(),
|
265 | background.green(),
|
266 | background.blue(),
|
267 | Math.round(background.alpha() * 255)
|
268 | ];
|
269 | } else {
|
270 | throw new Error('Expected valid noise or background to create a new input image');
|
271 | }
|
272 | delete inputDescriptor.buffer;
|
273 | } else {
|
274 | throw new Error('Expected valid width, height and channels to create a new input image');
|
275 | }
|
276 | }
|
277 |
|
278 | if (is.defined(inputOptions.text)) {
|
279 | if (is.object(inputOptions.text) && is.string(inputOptions.text.text)) {
|
280 | inputDescriptor.textValue = inputOptions.text.text;
|
281 | if (is.defined(inputOptions.text.height) && is.defined(inputOptions.text.dpi)) {
|
282 | throw new Error('Expected only one of dpi or height');
|
283 | }
|
284 | if (is.defined(inputOptions.text.font)) {
|
285 | if (is.string(inputOptions.text.font)) {
|
286 | inputDescriptor.textFont = inputOptions.text.font;
|
287 | } else {
|
288 | throw is.invalidParameterError('text.font', 'string', inputOptions.text.font);
|
289 | }
|
290 | }
|
291 | if (is.defined(inputOptions.text.fontfile)) {
|
292 | if (is.string(inputOptions.text.fontfile)) {
|
293 | inputDescriptor.textFontfile = inputOptions.text.fontfile;
|
294 | } else {
|
295 | throw is.invalidParameterError('text.fontfile', 'string', inputOptions.text.fontfile);
|
296 | }
|
297 | }
|
298 | if (is.defined(inputOptions.text.width)) {
|
299 | if (is.integer(inputOptions.text.width) && inputOptions.text.width > 0) {
|
300 | inputDescriptor.textWidth = inputOptions.text.width;
|
301 | } else {
|
302 | throw is.invalidParameterError('text.width', 'positive integer', inputOptions.text.width);
|
303 | }
|
304 | }
|
305 | if (is.defined(inputOptions.text.height)) {
|
306 | if (is.integer(inputOptions.text.height) && inputOptions.text.height > 0) {
|
307 | inputDescriptor.textHeight = inputOptions.text.height;
|
308 | } else {
|
309 | throw is.invalidParameterError('text.height', 'positive integer', inputOptions.text.height);
|
310 | }
|
311 | }
|
312 | if (is.defined(inputOptions.text.align)) {
|
313 | if (is.string(inputOptions.text.align) && is.string(this.constructor.align[inputOptions.text.align])) {
|
314 | inputDescriptor.textAlign = this.constructor.align[inputOptions.text.align];
|
315 | } else {
|
316 | throw is.invalidParameterError('text.align', 'valid alignment', inputOptions.text.align);
|
317 | }
|
318 | }
|
319 | if (is.defined(inputOptions.text.justify)) {
|
320 | if (is.bool(inputOptions.text.justify)) {
|
321 | inputDescriptor.textJustify = inputOptions.text.justify;
|
322 | } else {
|
323 | throw is.invalidParameterError('text.justify', 'boolean', inputOptions.text.justify);
|
324 | }
|
325 | }
|
326 | if (is.defined(inputOptions.text.dpi)) {
|
327 | if (is.integer(inputOptions.text.dpi) && is.inRange(inputOptions.text.dpi, 1, 1000000)) {
|
328 | inputDescriptor.textDpi = inputOptions.text.dpi;
|
329 | } else {
|
330 | throw is.invalidParameterError('text.dpi', 'integer between 1 and 1000000', inputOptions.text.dpi);
|
331 | }
|
332 | }
|
333 | if (is.defined(inputOptions.text.rgba)) {
|
334 | if (is.bool(inputOptions.text.rgba)) {
|
335 | inputDescriptor.textRgba = inputOptions.text.rgba;
|
336 | } else {
|
337 | throw is.invalidParameterError('text.rgba', 'bool', inputOptions.text.rgba);
|
338 | }
|
339 | }
|
340 | if (is.defined(inputOptions.text.spacing)) {
|
341 | if (is.integer(inputOptions.text.spacing) && is.inRange(inputOptions.text.spacing, -1000000, 1000000)) {
|
342 | inputDescriptor.textSpacing = inputOptions.text.spacing;
|
343 | } else {
|
344 | throw is.invalidParameterError('text.spacing', 'integer between -1000000 and 1000000', inputOptions.text.spacing);
|
345 | }
|
346 | }
|
347 | if (is.defined(inputOptions.text.wrap)) {
|
348 | if (is.string(inputOptions.text.wrap) && is.inArray(inputOptions.text.wrap, ['word', 'char', 'word-char', 'none'])) {
|
349 | inputDescriptor.textWrap = inputOptions.text.wrap;
|
350 | } else {
|
351 | throw is.invalidParameterError('text.wrap', 'one of: word, char, word-char, none', inputOptions.text.wrap);
|
352 | }
|
353 | }
|
354 | delete inputDescriptor.buffer;
|
355 | } else {
|
356 | throw new Error('Expected a valid string to create an image with text.');
|
357 | }
|
358 | }
|
359 | } else if (is.defined(inputOptions)) {
|
360 | throw new Error('Invalid input options ' + inputOptions);
|
361 | }
|
362 | return inputDescriptor;
|
363 | }
|
364 |
|
365 |
|
366 |
|
367 |
|
368 |
|
369 |
|
370 |
|
371 |
|
372 | function _write (chunk, encoding, callback) {
|
373 |
|
374 | if (Array.isArray(this.options.input.buffer)) {
|
375 |
|
376 | if (is.buffer(chunk)) {
|
377 | if (this.options.input.buffer.length === 0) {
|
378 | this.on('finish', () => {
|
379 | this.streamInFinished = true;
|
380 | });
|
381 | }
|
382 | this.options.input.buffer.push(chunk);
|
383 | callback();
|
384 | } else {
|
385 | callback(new Error('Non-Buffer data on Writable Stream'));
|
386 | }
|
387 | } else {
|
388 | callback(new Error('Unexpected data on Writable Stream'));
|
389 | }
|
390 | }
|
391 |
|
392 |
|
393 |
|
394 |
|
395 |
|
396 | function _flattenBufferIn () {
|
397 | if (this._isStreamInput()) {
|
398 | this.options.input.buffer = Buffer.concat(this.options.input.buffer);
|
399 | }
|
400 | }
|
401 |
|
402 |
|
403 |
|
404 |
|
405 |
|
406 |
|
407 | function _isStreamInput () {
|
408 | return Array.isArray(this.options.input.buffer);
|
409 | }
|
410 |
|
411 |
|
412 |
|
413 |
|
414 |
|
415 |
|
416 |
|
417 |
|
418 |
|
419 |
|
420 |
|
421 |
|
422 |
|
423 |
|
424 |
|
425 |
|
426 |
|
427 |
|
428 |
|
429 |
|
430 |
|
431 |
|
432 |
|
433 |
|
434 |
|
435 |
|
436 |
|
437 |
|
438 |
|
439 |
|
440 |
|
441 |
|
442 |
|
443 |
|
444 |
|
445 |
|
446 |
|
447 |
|
448 |
|
449 |
|
450 |
|
451 |
|
452 |
|
453 |
|
454 |
|
455 |
|
456 |
|
457 |
|
458 |
|
459 |
|
460 |
|
461 |
|
462 |
|
463 |
|
464 |
|
465 |
|
466 |
|
467 |
|
468 |
|
469 |
|
470 |
|
471 |
|
472 |
|
473 |
|
474 |
|
475 |
|
476 |
|
477 |
|
478 |
|
479 |
|
480 |
|
481 |
|
482 |
|
483 |
|
484 |
|
485 |
|
486 | function metadata (callback) {
|
487 | const stack = Error();
|
488 | if (is.fn(callback)) {
|
489 | if (this._isStreamInput()) {
|
490 | this.on('finish', () => {
|
491 | this._flattenBufferIn();
|
492 | sharp.metadata(this.options, (err, metadata) => {
|
493 | if (err) {
|
494 | callback(is.nativeError(err, stack));
|
495 | } else {
|
496 | callback(null, metadata);
|
497 | }
|
498 | });
|
499 | });
|
500 | } else {
|
501 | sharp.metadata(this.options, (err, metadata) => {
|
502 | if (err) {
|
503 | callback(is.nativeError(err, stack));
|
504 | } else {
|
505 | callback(null, metadata);
|
506 | }
|
507 | });
|
508 | }
|
509 | return this;
|
510 | } else {
|
511 | if (this._isStreamInput()) {
|
512 | return new Promise((resolve, reject) => {
|
513 | const finished = () => {
|
514 | this._flattenBufferIn();
|
515 | sharp.metadata(this.options, (err, metadata) => {
|
516 | if (err) {
|
517 | reject(is.nativeError(err, stack));
|
518 | } else {
|
519 | resolve(metadata);
|
520 | }
|
521 | });
|
522 | };
|
523 | if (this.writableFinished) {
|
524 | finished();
|
525 | } else {
|
526 | this.once('finish', finished);
|
527 | }
|
528 | });
|
529 | } else {
|
530 | return new Promise((resolve, reject) => {
|
531 | sharp.metadata(this.options, (err, metadata) => {
|
532 | if (err) {
|
533 | reject(is.nativeError(err, stack));
|
534 | } else {
|
535 | resolve(metadata);
|
536 | }
|
537 | });
|
538 | });
|
539 | }
|
540 | }
|
541 | }
|
542 |
|
543 |
|
544 |
|
545 |
|
546 |
|
547 |
|
548 |
|
549 |
|
550 |
|
551 |
|
552 |
|
553 |
|
554 |
|
555 |
|
556 |
|
557 |
|
558 |
|
559 |
|
560 |
|
561 |
|
562 |
|
563 |
|
564 |
|
565 |
|
566 |
|
567 |
|
568 |
|
569 |
|
570 |
|
571 |
|
572 |
|
573 |
|
574 |
|
575 |
|
576 |
|
577 |
|
578 |
|
579 |
|
580 |
|
581 |
|
582 |
|
583 |
|
584 |
|
585 |
|
586 |
|
587 |
|
588 | function stats (callback) {
|
589 | const stack = Error();
|
590 | if (is.fn(callback)) {
|
591 | if (this._isStreamInput()) {
|
592 | this.on('finish', () => {
|
593 | this._flattenBufferIn();
|
594 | sharp.stats(this.options, (err, stats) => {
|
595 | if (err) {
|
596 | callback(is.nativeError(err, stack));
|
597 | } else {
|
598 | callback(null, stats);
|
599 | }
|
600 | });
|
601 | });
|
602 | } else {
|
603 | sharp.stats(this.options, (err, stats) => {
|
604 | if (err) {
|
605 | callback(is.nativeError(err, stack));
|
606 | } else {
|
607 | callback(null, stats);
|
608 | }
|
609 | });
|
610 | }
|
611 | return this;
|
612 | } else {
|
613 | if (this._isStreamInput()) {
|
614 | return new Promise((resolve, reject) => {
|
615 | this.on('finish', function () {
|
616 | this._flattenBufferIn();
|
617 | sharp.stats(this.options, (err, stats) => {
|
618 | if (err) {
|
619 | reject(is.nativeError(err, stack));
|
620 | } else {
|
621 | resolve(stats);
|
622 | }
|
623 | });
|
624 | });
|
625 | });
|
626 | } else {
|
627 | return new Promise((resolve, reject) => {
|
628 | sharp.stats(this.options, (err, stats) => {
|
629 | if (err) {
|
630 | reject(is.nativeError(err, stack));
|
631 | } else {
|
632 | resolve(stats);
|
633 | }
|
634 | });
|
635 | });
|
636 | }
|
637 | }
|
638 | }
|
639 |
|
640 |
|
641 |
|
642 |
|
643 |
|
644 | module.exports = function (Sharp) {
|
645 | Object.assign(Sharp.prototype, {
|
646 |
|
647 | _inputOptionsFromObject,
|
648 | _createInputDescriptor,
|
649 | _write,
|
650 | _flattenBufferIn,
|
651 | _isStreamInput,
|
652 |
|
653 | metadata,
|
654 | stats
|
655 | });
|
656 |
|
657 | Sharp.align = align;
|
658 | };
|