UNPKG

19.9 kBJavaScriptView Raw
1/**
2 * @licstart The following is the entire license notice for the
3 * JavaScript code in this page
4 *
5 * Copyright 2022 Mozilla Foundation
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 *
19 * @licend The above is the entire license notice for the
20 * JavaScript code in this page
21 */
22"use strict";
23
24Object.defineProperty(exports, "__esModule", {
25 value: true
26});
27exports.PDFImage = void 0;
28
29var _util = require("../shared/util.js");
30
31var _image_utils = require("../shared/image_utils.js");
32
33var _base_stream = require("./base_stream.js");
34
35var _colorspace = require("./colorspace.js");
36
37var _decode_stream = require("./decode_stream.js");
38
39var _jpeg_stream = require("./jpeg_stream.js");
40
41var _jpx = require("./jpx.js");
42
43var _primitives = require("./primitives.js");
44
45function decodeAndClamp(value, addend, coefficient, max) {
46 value = addend + value * coefficient;
47
48 if (value < 0) {
49 value = 0;
50 } else if (value > max) {
51 value = max;
52 }
53
54 return value;
55}
56
57function resizeImageMask(src, bpc, w1, h1, w2, h2) {
58 const length = w2 * h2;
59 let dest;
60
61 if (bpc <= 8) {
62 dest = new Uint8Array(length);
63 } else if (bpc <= 16) {
64 dest = new Uint16Array(length);
65 } else {
66 dest = new Uint32Array(length);
67 }
68
69 const xRatio = w1 / w2;
70 const yRatio = h1 / h2;
71 let i,
72 j,
73 py,
74 newIndex = 0,
75 oldIndex;
76 const xScaled = new Uint16Array(w2);
77 const w1Scanline = w1;
78
79 for (i = 0; i < w2; i++) {
80 xScaled[i] = Math.floor(i * xRatio);
81 }
82
83 for (i = 0; i < h2; i++) {
84 py = Math.floor(i * yRatio) * w1Scanline;
85
86 for (j = 0; j < w2; j++) {
87 oldIndex = py + xScaled[j];
88 dest[newIndex++] = src[oldIndex];
89 }
90 }
91
92 return dest;
93}
94
95class PDFImage {
96 constructor({
97 xref,
98 res,
99 image,
100 isInline = false,
101 smask = null,
102 mask = null,
103 isMask = false,
104 pdfFunctionFactory,
105 localColorSpaceCache
106 }) {
107 this.image = image;
108 const dict = image.dict;
109 const filter = dict.get("F", "Filter");
110 let filterName;
111
112 if (filter instanceof _primitives.Name) {
113 filterName = filter.name;
114 } else if (Array.isArray(filter)) {
115 const filterZero = xref.fetchIfRef(filter[0]);
116
117 if (filterZero instanceof _primitives.Name) {
118 filterName = filterZero.name;
119 }
120 }
121
122 switch (filterName) {
123 case "JPXDecode":
124 const jpxImage = new _jpx.JpxImage();
125 jpxImage.parseImageProperties(image.stream);
126 image.stream.reset();
127 image.width = jpxImage.width;
128 image.height = jpxImage.height;
129 image.bitsPerComponent = jpxImage.bitsPerComponent;
130 image.numComps = jpxImage.componentsCount;
131 break;
132
133 case "JBIG2Decode":
134 image.bitsPerComponent = 1;
135 image.numComps = 1;
136 break;
137 }
138
139 let width = dict.get("W", "Width");
140 let height = dict.get("H", "Height");
141
142 if (Number.isInteger(image.width) && image.width > 0 && Number.isInteger(image.height) && image.height > 0 && (image.width !== width || image.height !== height)) {
143 (0, _util.warn)("PDFImage - using the Width/Height of the image data, " + "rather than the image dictionary.");
144 width = image.width;
145 height = image.height;
146 }
147
148 if (width < 1 || height < 1) {
149 throw new _util.FormatError(`Invalid image width: ${width} or height: ${height}`);
150 }
151
152 this.width = width;
153 this.height = height;
154 this.interpolate = dict.get("I", "Interpolate");
155 this.imageMask = dict.get("IM", "ImageMask") || false;
156 this.matte = dict.get("Matte") || false;
157 let bitsPerComponent = image.bitsPerComponent;
158
159 if (!bitsPerComponent) {
160 bitsPerComponent = dict.get("BPC", "BitsPerComponent");
161
162 if (!bitsPerComponent) {
163 if (this.imageMask) {
164 bitsPerComponent = 1;
165 } else {
166 throw new _util.FormatError(`Bits per component missing in image: ${this.imageMask}`);
167 }
168 }
169 }
170
171 this.bpc = bitsPerComponent;
172
173 if (!this.imageMask) {
174 let colorSpace = dict.getRaw("CS") || dict.getRaw("ColorSpace");
175
176 if (!colorSpace) {
177 (0, _util.info)("JPX images (which do not require color spaces)");
178
179 switch (image.numComps) {
180 case 1:
181 colorSpace = _primitives.Name.get("DeviceGray");
182 break;
183
184 case 3:
185 colorSpace = _primitives.Name.get("DeviceRGB");
186 break;
187
188 case 4:
189 colorSpace = _primitives.Name.get("DeviceCMYK");
190 break;
191
192 default:
193 throw new Error(`JPX images with ${image.numComps} color components not supported.`);
194 }
195 }
196
197 this.colorSpace = _colorspace.ColorSpace.parse({
198 cs: colorSpace,
199 xref,
200 resources: isInline ? res : null,
201 pdfFunctionFactory,
202 localColorSpaceCache
203 });
204 this.numComps = this.colorSpace.numComps;
205 }
206
207 this.decode = dict.getArray("D", "Decode");
208 this.needsDecode = false;
209
210 if (this.decode && (this.colorSpace && !this.colorSpace.isDefaultDecode(this.decode, bitsPerComponent) || isMask && !_colorspace.ColorSpace.isDefaultDecode(this.decode, 1))) {
211 this.needsDecode = true;
212 const max = (1 << bitsPerComponent) - 1;
213 this.decodeCoefficients = [];
214 this.decodeAddends = [];
215 const isIndexed = this.colorSpace && this.colorSpace.name === "Indexed";
216
217 for (let i = 0, j = 0; i < this.decode.length; i += 2, ++j) {
218 const dmin = this.decode[i];
219 const dmax = this.decode[i + 1];
220 this.decodeCoefficients[j] = isIndexed ? (dmax - dmin) / max : dmax - dmin;
221 this.decodeAddends[j] = isIndexed ? dmin : max * dmin;
222 }
223 }
224
225 if (smask) {
226 this.smask = new PDFImage({
227 xref,
228 res,
229 image: smask,
230 isInline,
231 pdfFunctionFactory,
232 localColorSpaceCache
233 });
234 } else if (mask) {
235 if (mask instanceof _base_stream.BaseStream) {
236 const maskDict = mask.dict,
237 imageMask = maskDict.get("IM", "ImageMask");
238
239 if (!imageMask) {
240 (0, _util.warn)("Ignoring /Mask in image without /ImageMask.");
241 } else {
242 this.mask = new PDFImage({
243 xref,
244 res,
245 image: mask,
246 isInline,
247 isMask: true,
248 pdfFunctionFactory,
249 localColorSpaceCache
250 });
251 }
252 } else {
253 this.mask = mask;
254 }
255 }
256 }
257
258 static async buildImage({
259 xref,
260 res,
261 image,
262 isInline = false,
263 pdfFunctionFactory,
264 localColorSpaceCache
265 }) {
266 const imageData = image;
267 let smaskData = null;
268 let maskData = null;
269 const smask = image.dict.get("SMask");
270 const mask = image.dict.get("Mask");
271
272 if (smask) {
273 if (smask instanceof _base_stream.BaseStream) {
274 smaskData = smask;
275 } else {
276 (0, _util.warn)("Unsupported /SMask format.");
277 }
278 } else if (mask) {
279 if (mask instanceof _base_stream.BaseStream || Array.isArray(mask)) {
280 maskData = mask;
281 } else {
282 (0, _util.warn)("Unsupported /Mask format.");
283 }
284 }
285
286 return new PDFImage({
287 xref,
288 res,
289 image: imageData,
290 isInline,
291 smask: smaskData,
292 mask: maskData,
293 pdfFunctionFactory,
294 localColorSpaceCache
295 });
296 }
297
298 static createRawMask({
299 imgArray,
300 width,
301 height,
302 imageIsFromDecodeStream,
303 inverseDecode,
304 interpolate
305 }) {
306 const computedLength = (width + 7 >> 3) * height;
307 const actualLength = imgArray.byteLength;
308 const haveFullData = computedLength === actualLength;
309 let data, i;
310
311 if (imageIsFromDecodeStream && (!inverseDecode || haveFullData)) {
312 data = imgArray;
313 } else if (!inverseDecode) {
314 data = new Uint8Array(imgArray);
315 } else {
316 data = new Uint8Array(computedLength);
317 data.set(imgArray);
318 data.fill(0xff, actualLength);
319 }
320
321 if (inverseDecode) {
322 for (i = 0; i < actualLength; i++) {
323 data[i] ^= 0xff;
324 }
325 }
326
327 return {
328 data,
329 width,
330 height,
331 interpolate
332 };
333 }
334
335 static createMask({
336 imgArray,
337 width,
338 height,
339 imageIsFromDecodeStream,
340 inverseDecode,
341 interpolate
342 }) {
343 const isSingleOpaquePixel = width === 1 && height === 1 && inverseDecode === (imgArray.length === 0 || !!(imgArray[0] & 128));
344
345 if (isSingleOpaquePixel) {
346 return {
347 isSingleOpaquePixel
348 };
349 }
350
351 if (_util.FeatureTest.isOffscreenCanvasSupported) {
352 const canvas = new OffscreenCanvas(width, height);
353 const ctx = canvas.getContext("2d");
354 const imgData = ctx.createImageData(width, height);
355 (0, _image_utils.applyMaskImageData)({
356 src: imgArray,
357 dest: imgData.data,
358 width,
359 height,
360 inverseDecode
361 });
362 ctx.putImageData(imgData, 0, 0);
363 const bitmap = canvas.transferToImageBitmap();
364 return {
365 data: null,
366 width,
367 height,
368 interpolate,
369 bitmap
370 };
371 }
372
373 return this.createRawMask({
374 imgArray,
375 width,
376 height,
377 inverseDecode,
378 imageIsFromDecodeStream,
379 interpolate
380 });
381 }
382
383 get drawWidth() {
384 return Math.max(this.width, this.smask && this.smask.width || 0, this.mask && this.mask.width || 0);
385 }
386
387 get drawHeight() {
388 return Math.max(this.height, this.smask && this.smask.height || 0, this.mask && this.mask.height || 0);
389 }
390
391 decodeBuffer(buffer) {
392 const bpc = this.bpc;
393 const numComps = this.numComps;
394 const decodeAddends = this.decodeAddends;
395 const decodeCoefficients = this.decodeCoefficients;
396 const max = (1 << bpc) - 1;
397 let i, ii;
398
399 if (bpc === 1) {
400 for (i = 0, ii = buffer.length; i < ii; i++) {
401 buffer[i] = +!buffer[i];
402 }
403
404 return;
405 }
406
407 let index = 0;
408
409 for (i = 0, ii = this.width * this.height; i < ii; i++) {
410 for (let j = 0; j < numComps; j++) {
411 buffer[index] = decodeAndClamp(buffer[index], decodeAddends[j], decodeCoefficients[j], max);
412 index++;
413 }
414 }
415 }
416
417 getComponents(buffer) {
418 const bpc = this.bpc;
419
420 if (bpc === 8) {
421 return buffer;
422 }
423
424 const width = this.width;
425 const height = this.height;
426 const numComps = this.numComps;
427 const length = width * height * numComps;
428 let bufferPos = 0;
429 let output;
430
431 if (bpc <= 8) {
432 output = new Uint8Array(length);
433 } else if (bpc <= 16) {
434 output = new Uint16Array(length);
435 } else {
436 output = new Uint32Array(length);
437 }
438
439 const rowComps = width * numComps;
440 const max = (1 << bpc) - 1;
441 let i = 0,
442 ii,
443 buf;
444
445 if (bpc === 1) {
446 let mask, loop1End, loop2End;
447
448 for (let j = 0; j < height; j++) {
449 loop1End = i + (rowComps & ~7);
450 loop2End = i + rowComps;
451
452 while (i < loop1End) {
453 buf = buffer[bufferPos++];
454 output[i] = buf >> 7 & 1;
455 output[i + 1] = buf >> 6 & 1;
456 output[i + 2] = buf >> 5 & 1;
457 output[i + 3] = buf >> 4 & 1;
458 output[i + 4] = buf >> 3 & 1;
459 output[i + 5] = buf >> 2 & 1;
460 output[i + 6] = buf >> 1 & 1;
461 output[i + 7] = buf & 1;
462 i += 8;
463 }
464
465 if (i < loop2End) {
466 buf = buffer[bufferPos++];
467 mask = 128;
468
469 while (i < loop2End) {
470 output[i++] = +!!(buf & mask);
471 mask >>= 1;
472 }
473 }
474 }
475 } else {
476 let bits = 0;
477 buf = 0;
478
479 for (i = 0, ii = length; i < ii; ++i) {
480 if (i % rowComps === 0) {
481 buf = 0;
482 bits = 0;
483 }
484
485 while (bits < bpc) {
486 buf = buf << 8 | buffer[bufferPos++];
487 bits += 8;
488 }
489
490 const remainingBits = bits - bpc;
491 let value = buf >> remainingBits;
492
493 if (value < 0) {
494 value = 0;
495 } else if (value > max) {
496 value = max;
497 }
498
499 output[i] = value;
500 buf &= (1 << remainingBits) - 1;
501 bits = remainingBits;
502 }
503 }
504
505 return output;
506 }
507
508 fillOpacity(rgbaBuf, width, height, actualHeight, image) {
509 const smask = this.smask;
510 const mask = this.mask;
511 let alphaBuf, sw, sh, i, ii, j;
512
513 if (smask) {
514 sw = smask.width;
515 sh = smask.height;
516 alphaBuf = new Uint8ClampedArray(sw * sh);
517 smask.fillGrayBuffer(alphaBuf);
518
519 if (sw !== width || sh !== height) {
520 alphaBuf = resizeImageMask(alphaBuf, smask.bpc, sw, sh, width, height);
521 }
522 } else if (mask) {
523 if (mask instanceof PDFImage) {
524 sw = mask.width;
525 sh = mask.height;
526 alphaBuf = new Uint8ClampedArray(sw * sh);
527 mask.numComps = 1;
528 mask.fillGrayBuffer(alphaBuf);
529
530 for (i = 0, ii = sw * sh; i < ii; ++i) {
531 alphaBuf[i] = 255 - alphaBuf[i];
532 }
533
534 if (sw !== width || sh !== height) {
535 alphaBuf = resizeImageMask(alphaBuf, mask.bpc, sw, sh, width, height);
536 }
537 } else if (Array.isArray(mask)) {
538 alphaBuf = new Uint8ClampedArray(width * height);
539 const numComps = this.numComps;
540
541 for (i = 0, ii = width * height; i < ii; ++i) {
542 let opacity = 0;
543 const imageOffset = i * numComps;
544
545 for (j = 0; j < numComps; ++j) {
546 const color = image[imageOffset + j];
547 const maskOffset = j * 2;
548
549 if (color < mask[maskOffset] || color > mask[maskOffset + 1]) {
550 opacity = 255;
551 break;
552 }
553 }
554
555 alphaBuf[i] = opacity;
556 }
557 } else {
558 throw new _util.FormatError("Unknown mask format.");
559 }
560 }
561
562 if (alphaBuf) {
563 for (i = 0, j = 3, ii = width * actualHeight; i < ii; ++i, j += 4) {
564 rgbaBuf[j] = alphaBuf[i];
565 }
566 } else {
567 for (i = 0, j = 3, ii = width * actualHeight; i < ii; ++i, j += 4) {
568 rgbaBuf[j] = 255;
569 }
570 }
571 }
572
573 undoPreblend(buffer, width, height) {
574 const matte = this.smask && this.smask.matte;
575
576 if (!matte) {
577 return;
578 }
579
580 const matteRgb = this.colorSpace.getRgb(matte, 0);
581 const matteR = matteRgb[0];
582 const matteG = matteRgb[1];
583 const matteB = matteRgb[2];
584 const length = width * height * 4;
585
586 for (let i = 0; i < length; i += 4) {
587 const alpha = buffer[i + 3];
588
589 if (alpha === 0) {
590 buffer[i] = 255;
591 buffer[i + 1] = 255;
592 buffer[i + 2] = 255;
593 continue;
594 }
595
596 const k = 255 / alpha;
597 buffer[i] = (buffer[i] - matteR) * k + matteR;
598 buffer[i + 1] = (buffer[i + 1] - matteG) * k + matteG;
599 buffer[i + 2] = (buffer[i + 2] - matteB) * k + matteB;
600 }
601 }
602
603 createImageData(forceRGBA = false) {
604 const drawWidth = this.drawWidth;
605 const drawHeight = this.drawHeight;
606 const imgData = {
607 width: drawWidth,
608 height: drawHeight,
609 interpolate: this.interpolate,
610 kind: 0,
611 data: null
612 };
613 const numComps = this.numComps;
614 const originalWidth = this.width;
615 const originalHeight = this.height;
616 const bpc = this.bpc;
617 const rowBytes = originalWidth * numComps * bpc + 7 >> 3;
618
619 if (!forceRGBA) {
620 let kind;
621
622 if (this.colorSpace.name === "DeviceGray" && bpc === 1) {
623 kind = _util.ImageKind.GRAYSCALE_1BPP;
624 } else if (this.colorSpace.name === "DeviceRGB" && bpc === 8 && !this.needsDecode) {
625 kind = _util.ImageKind.RGB_24BPP;
626 }
627
628 if (kind && !this.smask && !this.mask && drawWidth === originalWidth && drawHeight === originalHeight) {
629 imgData.kind = kind;
630 imgData.data = this.getImageBytes(originalHeight * rowBytes, {});
631
632 if (this.needsDecode) {
633 (0, _util.assert)(kind === _util.ImageKind.GRAYSCALE_1BPP, "PDFImage.createImageData: The image must be grayscale.");
634 const buffer = imgData.data;
635
636 for (let i = 0, ii = buffer.length; i < ii; i++) {
637 buffer[i] ^= 0xff;
638 }
639 }
640
641 return imgData;
642 }
643
644 if (this.image instanceof _jpeg_stream.JpegStream && !this.smask && !this.mask) {
645 let imageLength = originalHeight * rowBytes;
646
647 switch (this.colorSpace.name) {
648 case "DeviceGray":
649 imageLength *= 3;
650
651 case "DeviceRGB":
652 case "DeviceCMYK":
653 imgData.kind = _util.ImageKind.RGB_24BPP;
654 imgData.data = this.getImageBytes(imageLength, {
655 drawWidth,
656 drawHeight,
657 forceRGB: true
658 });
659 return imgData;
660 }
661 }
662 }
663
664 const imgArray = this.getImageBytes(originalHeight * rowBytes, {
665 internal: true
666 });
667 const actualHeight = 0 | imgArray.length / rowBytes * drawHeight / originalHeight;
668 const comps = this.getComponents(imgArray);
669 let alpha01, maybeUndoPreblend;
670
671 if (!forceRGBA && !this.smask && !this.mask) {
672 imgData.kind = _util.ImageKind.RGB_24BPP;
673 imgData.data = new Uint8ClampedArray(drawWidth * drawHeight * 3);
674 alpha01 = 0;
675 maybeUndoPreblend = false;
676 } else {
677 imgData.kind = _util.ImageKind.RGBA_32BPP;
678 imgData.data = new Uint8ClampedArray(drawWidth * drawHeight * 4);
679 alpha01 = 1;
680 maybeUndoPreblend = true;
681 this.fillOpacity(imgData.data, drawWidth, drawHeight, actualHeight, comps);
682 }
683
684 if (this.needsDecode) {
685 this.decodeBuffer(comps);
686 }
687
688 this.colorSpace.fillRgb(imgData.data, originalWidth, originalHeight, drawWidth, drawHeight, actualHeight, bpc, comps, alpha01);
689
690 if (maybeUndoPreblend) {
691 this.undoPreblend(imgData.data, drawWidth, actualHeight);
692 }
693
694 return imgData;
695 }
696
697 fillGrayBuffer(buffer) {
698 const numComps = this.numComps;
699
700 if (numComps !== 1) {
701 throw new _util.FormatError(`Reading gray scale from a color image: ${numComps}`);
702 }
703
704 const width = this.width;
705 const height = this.height;
706 const bpc = this.bpc;
707 const rowBytes = width * numComps * bpc + 7 >> 3;
708 const imgArray = this.getImageBytes(height * rowBytes, {
709 internal: true
710 });
711 const comps = this.getComponents(imgArray);
712 let i, length;
713
714 if (bpc === 1) {
715 length = width * height;
716
717 if (this.needsDecode) {
718 for (i = 0; i < length; ++i) {
719 buffer[i] = comps[i] - 1 & 255;
720 }
721 } else {
722 for (i = 0; i < length; ++i) {
723 buffer[i] = -comps[i] & 255;
724 }
725 }
726
727 return;
728 }
729
730 if (this.needsDecode) {
731 this.decodeBuffer(comps);
732 }
733
734 length = width * height;
735 const scale = 255 / ((1 << bpc) - 1);
736
737 for (i = 0; i < length; ++i) {
738 buffer[i] = scale * comps[i];
739 }
740 }
741
742 getImageBytes(length, {
743 drawWidth,
744 drawHeight,
745 forceRGB = false,
746 internal = false
747 }) {
748 this.image.reset();
749 this.image.drawWidth = drawWidth || this.width;
750 this.image.drawHeight = drawHeight || this.height;
751 this.image.forceRGB = !!forceRGB;
752 const imageBytes = this.image.getBytes(length);
753
754 if (internal || this.image instanceof _decode_stream.DecodeStream) {
755 return imageBytes;
756 }
757
758 (0, _util.assert)(imageBytes instanceof Uint8Array, 'PDFImage.getImageBytes: Unsupported "imageBytes" type.');
759 return new Uint8Array(imageBytes);
760 }
761
762}
763
764exports.PDFImage = PDFImage;
\No newline at end of file