image.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869
  1. /* Copyright 2012 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. import {
  16. assert,
  17. FeatureTest,
  18. FormatError,
  19. ImageKind,
  20. info,
  21. warn,
  22. } from "../shared/util.js";
  23. import { applyMaskImageData } from "../shared/image_utils.js";
  24. import { BaseStream } from "./base_stream.js";
  25. import { ColorSpace } from "./colorspace.js";
  26. import { DecodeStream } from "./decode_stream.js";
  27. import { JpegStream } from "./jpeg_stream.js";
  28. import { JpxImage } from "./jpx.js";
  29. import { Name } from "./primitives.js";
  30. /**
  31. * Decode and clamp a value. The formula is different from the spec because we
  32. * don't decode to float range [0,1], we decode it in the [0,max] range.
  33. */
  34. function decodeAndClamp(value, addend, coefficient, max) {
  35. value = addend + value * coefficient;
  36. // Clamp the value to the range
  37. if (value < 0) {
  38. value = 0;
  39. } else if (value > max) {
  40. value = max;
  41. }
  42. return value;
  43. }
  44. /**
  45. * Resizes an image mask with 1 component.
  46. * @param {TypedArray} src - The source buffer.
  47. * @param {number} bpc - Number of bits per component.
  48. * @param {number} w1 - Original width.
  49. * @param {number} h1 - Original height.
  50. * @param {number} w2 - New width.
  51. * @param {number} h2 - New height.
  52. * @returns {TypedArray} The resized image mask buffer.
  53. */
  54. function resizeImageMask(src, bpc, w1, h1, w2, h2) {
  55. const length = w2 * h2;
  56. let dest;
  57. if (bpc <= 8) {
  58. dest = new Uint8Array(length);
  59. } else if (bpc <= 16) {
  60. dest = new Uint16Array(length);
  61. } else {
  62. dest = new Uint32Array(length);
  63. }
  64. const xRatio = w1 / w2;
  65. const yRatio = h1 / h2;
  66. let i,
  67. j,
  68. py,
  69. newIndex = 0,
  70. oldIndex;
  71. const xScaled = new Uint16Array(w2);
  72. const w1Scanline = w1;
  73. for (i = 0; i < w2; i++) {
  74. xScaled[i] = Math.floor(i * xRatio);
  75. }
  76. for (i = 0; i < h2; i++) {
  77. py = Math.floor(i * yRatio) * w1Scanline;
  78. for (j = 0; j < w2; j++) {
  79. oldIndex = py + xScaled[j];
  80. dest[newIndex++] = src[oldIndex];
  81. }
  82. }
  83. return dest;
  84. }
  85. class PDFImage {
  86. constructor({
  87. xref,
  88. res,
  89. image,
  90. isInline = false,
  91. smask = null,
  92. mask = null,
  93. isMask = false,
  94. pdfFunctionFactory,
  95. localColorSpaceCache,
  96. }) {
  97. this.image = image;
  98. const dict = image.dict;
  99. const filter = dict.get("F", "Filter");
  100. let filterName;
  101. if (filter instanceof Name) {
  102. filterName = filter.name;
  103. } else if (Array.isArray(filter)) {
  104. const filterZero = xref.fetchIfRef(filter[0]);
  105. if (filterZero instanceof Name) {
  106. filterName = filterZero.name;
  107. }
  108. }
  109. switch (filterName) {
  110. case "JPXDecode":
  111. const jpxImage = new JpxImage();
  112. jpxImage.parseImageProperties(image.stream);
  113. image.stream.reset();
  114. image.width = jpxImage.width;
  115. image.height = jpxImage.height;
  116. image.bitsPerComponent = jpxImage.bitsPerComponent;
  117. image.numComps = jpxImage.componentsCount;
  118. break;
  119. case "JBIG2Decode":
  120. image.bitsPerComponent = 1;
  121. image.numComps = 1;
  122. break;
  123. }
  124. let width = dict.get("W", "Width");
  125. let height = dict.get("H", "Height");
  126. if (
  127. Number.isInteger(image.width) &&
  128. image.width > 0 &&
  129. Number.isInteger(image.height) &&
  130. image.height > 0 &&
  131. (image.width !== width || image.height !== height)
  132. ) {
  133. warn(
  134. "PDFImage - using the Width/Height of the image data, " +
  135. "rather than the image dictionary."
  136. );
  137. width = image.width;
  138. height = image.height;
  139. }
  140. if (width < 1 || height < 1) {
  141. throw new FormatError(
  142. `Invalid image width: ${width} or height: ${height}`
  143. );
  144. }
  145. this.width = width;
  146. this.height = height;
  147. this.interpolate = dict.get("I", "Interpolate");
  148. this.imageMask = dict.get("IM", "ImageMask") || false;
  149. this.matte = dict.get("Matte") || false;
  150. let bitsPerComponent = image.bitsPerComponent;
  151. if (!bitsPerComponent) {
  152. bitsPerComponent = dict.get("BPC", "BitsPerComponent");
  153. if (!bitsPerComponent) {
  154. if (this.imageMask) {
  155. bitsPerComponent = 1;
  156. } else {
  157. throw new FormatError(
  158. `Bits per component missing in image: ${this.imageMask}`
  159. );
  160. }
  161. }
  162. }
  163. this.bpc = bitsPerComponent;
  164. if (!this.imageMask) {
  165. let colorSpace = dict.getRaw("CS") || dict.getRaw("ColorSpace");
  166. if (!colorSpace) {
  167. info("JPX images (which do not require color spaces)");
  168. switch (image.numComps) {
  169. case 1:
  170. colorSpace = Name.get("DeviceGray");
  171. break;
  172. case 3:
  173. colorSpace = Name.get("DeviceRGB");
  174. break;
  175. case 4:
  176. colorSpace = Name.get("DeviceCMYK");
  177. break;
  178. default:
  179. throw new Error(
  180. `JPX images with ${image.numComps} color components not supported.`
  181. );
  182. }
  183. }
  184. this.colorSpace = ColorSpace.parse({
  185. cs: colorSpace,
  186. xref,
  187. resources: isInline ? res : null,
  188. pdfFunctionFactory,
  189. localColorSpaceCache,
  190. });
  191. this.numComps = this.colorSpace.numComps;
  192. }
  193. this.decode = dict.getArray("D", "Decode");
  194. this.needsDecode = false;
  195. if (
  196. this.decode &&
  197. ((this.colorSpace &&
  198. !this.colorSpace.isDefaultDecode(this.decode, bitsPerComponent)) ||
  199. (isMask &&
  200. !ColorSpace.isDefaultDecode(this.decode, /* numComps = */ 1)))
  201. ) {
  202. this.needsDecode = true;
  203. // Do some preprocessing to avoid more math.
  204. const max = (1 << bitsPerComponent) - 1;
  205. this.decodeCoefficients = [];
  206. this.decodeAddends = [];
  207. const isIndexed = this.colorSpace && this.colorSpace.name === "Indexed";
  208. for (let i = 0, j = 0; i < this.decode.length; i += 2, ++j) {
  209. const dmin = this.decode[i];
  210. const dmax = this.decode[i + 1];
  211. this.decodeCoefficients[j] = isIndexed
  212. ? (dmax - dmin) / max
  213. : dmax - dmin;
  214. this.decodeAddends[j] = isIndexed ? dmin : max * dmin;
  215. }
  216. }
  217. if (smask) {
  218. this.smask = new PDFImage({
  219. xref,
  220. res,
  221. image: smask,
  222. isInline,
  223. pdfFunctionFactory,
  224. localColorSpaceCache,
  225. });
  226. } else if (mask) {
  227. if (mask instanceof BaseStream) {
  228. const maskDict = mask.dict,
  229. imageMask = maskDict.get("IM", "ImageMask");
  230. if (!imageMask) {
  231. warn("Ignoring /Mask in image without /ImageMask.");
  232. } else {
  233. this.mask = new PDFImage({
  234. xref,
  235. res,
  236. image: mask,
  237. isInline,
  238. isMask: true,
  239. pdfFunctionFactory,
  240. localColorSpaceCache,
  241. });
  242. }
  243. } else {
  244. // Color key mask (just an array).
  245. this.mask = mask;
  246. }
  247. }
  248. }
  249. /**
  250. * Handles processing of image data and returns the Promise that is resolved
  251. * with a PDFImage when the image is ready to be used.
  252. */
  253. static async buildImage({
  254. xref,
  255. res,
  256. image,
  257. isInline = false,
  258. pdfFunctionFactory,
  259. localColorSpaceCache,
  260. }) {
  261. const imageData = image;
  262. let smaskData = null;
  263. let maskData = null;
  264. const smask = image.dict.get("SMask");
  265. const mask = image.dict.get("Mask");
  266. if (smask) {
  267. if (smask instanceof BaseStream) {
  268. smaskData = smask;
  269. } else {
  270. warn("Unsupported /SMask format.");
  271. }
  272. } else if (mask) {
  273. if (mask instanceof BaseStream || Array.isArray(mask)) {
  274. maskData = mask;
  275. } else {
  276. warn("Unsupported /Mask format.");
  277. }
  278. }
  279. return new PDFImage({
  280. xref,
  281. res,
  282. image: imageData,
  283. isInline,
  284. smask: smaskData,
  285. mask: maskData,
  286. pdfFunctionFactory,
  287. localColorSpaceCache,
  288. });
  289. }
  290. static createRawMask({
  291. imgArray,
  292. width,
  293. height,
  294. imageIsFromDecodeStream,
  295. inverseDecode,
  296. interpolate,
  297. }) {
  298. // |imgArray| might not contain full data for every pixel of the mask, so
  299. // we need to distinguish between |computedLength| and |actualLength|.
  300. // In particular, if inverseDecode is true, then the array we return must
  301. // have a length of |computedLength|.
  302. const computedLength = ((width + 7) >> 3) * height;
  303. const actualLength = imgArray.byteLength;
  304. const haveFullData = computedLength === actualLength;
  305. let data, i;
  306. if (imageIsFromDecodeStream && (!inverseDecode || haveFullData)) {
  307. // imgArray came from a DecodeStream and its data is in an appropriate
  308. // form, so we can just transfer it.
  309. data = imgArray;
  310. } else if (!inverseDecode) {
  311. data = new Uint8Array(imgArray);
  312. } else {
  313. data = new Uint8Array(computedLength);
  314. data.set(imgArray);
  315. data.fill(0xff, actualLength);
  316. }
  317. // If necessary, invert the original mask data (but not any extra we might
  318. // have added above). It's safe to modify the array -- whether it's the
  319. // original or a copy, we're about to transfer it anyway, so nothing else
  320. // in this thread can be relying on its contents.
  321. if (inverseDecode) {
  322. for (i = 0; i < actualLength; i++) {
  323. data[i] ^= 0xff;
  324. }
  325. }
  326. return { data, width, height, interpolate };
  327. }
  328. static createMask({
  329. imgArray,
  330. width,
  331. height,
  332. imageIsFromDecodeStream,
  333. inverseDecode,
  334. interpolate,
  335. isOffscreenCanvasSupported = true,
  336. }) {
  337. const isSingleOpaquePixel =
  338. width === 1 &&
  339. height === 1 &&
  340. inverseDecode === (imgArray.length === 0 || !!(imgArray[0] & 128));
  341. if (isSingleOpaquePixel) {
  342. return { isSingleOpaquePixel };
  343. }
  344. if (isOffscreenCanvasSupported && FeatureTest.isOffscreenCanvasSupported) {
  345. const canvas = new OffscreenCanvas(width, height);
  346. const ctx = canvas.getContext("2d");
  347. const imgData = ctx.createImageData(width, height);
  348. applyMaskImageData({
  349. src: imgArray,
  350. dest: imgData.data,
  351. width,
  352. height,
  353. inverseDecode,
  354. });
  355. ctx.putImageData(imgData, 0, 0);
  356. const bitmap = canvas.transferToImageBitmap();
  357. return {
  358. data: null,
  359. width,
  360. height,
  361. interpolate,
  362. bitmap,
  363. };
  364. }
  365. // Get the data almost as they're and they'll be decoded
  366. // just before being drawn.
  367. return this.createRawMask({
  368. imgArray,
  369. width,
  370. height,
  371. inverseDecode,
  372. imageIsFromDecodeStream,
  373. interpolate,
  374. });
  375. }
  376. get drawWidth() {
  377. return Math.max(
  378. this.width,
  379. (this.smask && this.smask.width) || 0,
  380. (this.mask && this.mask.width) || 0
  381. );
  382. }
  383. get drawHeight() {
  384. return Math.max(
  385. this.height,
  386. (this.smask && this.smask.height) || 0,
  387. (this.mask && this.mask.height) || 0
  388. );
  389. }
  390. decodeBuffer(buffer) {
  391. const bpc = this.bpc;
  392. const numComps = this.numComps;
  393. const decodeAddends = this.decodeAddends;
  394. const decodeCoefficients = this.decodeCoefficients;
  395. const max = (1 << bpc) - 1;
  396. let i, ii;
  397. if (bpc === 1) {
  398. // If the buffer needed decode that means it just needs to be inverted.
  399. for (i = 0, ii = buffer.length; i < ii; i++) {
  400. buffer[i] = +!buffer[i];
  401. }
  402. return;
  403. }
  404. let index = 0;
  405. for (i = 0, ii = this.width * this.height; i < ii; i++) {
  406. for (let j = 0; j < numComps; j++) {
  407. buffer[index] = decodeAndClamp(
  408. buffer[index],
  409. decodeAddends[j],
  410. decodeCoefficients[j],
  411. max
  412. );
  413. index++;
  414. }
  415. }
  416. }
  417. getComponents(buffer) {
  418. const bpc = this.bpc;
  419. // This image doesn't require any extra work.
  420. if (bpc === 8) {
  421. return buffer;
  422. }
  423. const width = this.width;
  424. const height = this.height;
  425. const numComps = this.numComps;
  426. const length = width * height * numComps;
  427. let bufferPos = 0;
  428. let output;
  429. if (bpc <= 8) {
  430. output = new Uint8Array(length);
  431. } else if (bpc <= 16) {
  432. output = new Uint16Array(length);
  433. } else {
  434. output = new Uint32Array(length);
  435. }
  436. const rowComps = width * numComps;
  437. const max = (1 << bpc) - 1;
  438. let i = 0,
  439. ii,
  440. buf;
  441. if (bpc === 1) {
  442. // Optimization for reading 1 bpc images.
  443. let mask, loop1End, loop2End;
  444. for (let j = 0; j < height; j++) {
  445. loop1End = i + (rowComps & ~7);
  446. loop2End = i + rowComps;
  447. // unroll loop for all full bytes
  448. while (i < loop1End) {
  449. buf = buffer[bufferPos++];
  450. output[i] = (buf >> 7) & 1;
  451. output[i + 1] = (buf >> 6) & 1;
  452. output[i + 2] = (buf >> 5) & 1;
  453. output[i + 3] = (buf >> 4) & 1;
  454. output[i + 4] = (buf >> 3) & 1;
  455. output[i + 5] = (buf >> 2) & 1;
  456. output[i + 6] = (buf >> 1) & 1;
  457. output[i + 7] = buf & 1;
  458. i += 8;
  459. }
  460. // handle remaining bits
  461. if (i < loop2End) {
  462. buf = buffer[bufferPos++];
  463. mask = 128;
  464. while (i < loop2End) {
  465. output[i++] = +!!(buf & mask);
  466. mask >>= 1;
  467. }
  468. }
  469. }
  470. } else {
  471. // The general case that handles all other bpc values.
  472. let bits = 0;
  473. buf = 0;
  474. for (i = 0, ii = length; i < ii; ++i) {
  475. if (i % rowComps === 0) {
  476. buf = 0;
  477. bits = 0;
  478. }
  479. while (bits < bpc) {
  480. buf = (buf << 8) | buffer[bufferPos++];
  481. bits += 8;
  482. }
  483. const remainingBits = bits - bpc;
  484. let value = buf >> remainingBits;
  485. if (value < 0) {
  486. value = 0;
  487. } else if (value > max) {
  488. value = max;
  489. }
  490. output[i] = value;
  491. buf &= (1 << remainingBits) - 1;
  492. bits = remainingBits;
  493. }
  494. }
  495. return output;
  496. }
  497. fillOpacity(rgbaBuf, width, height, actualHeight, image) {
  498. if (
  499. typeof PDFJSDev === "undefined" ||
  500. PDFJSDev.test("!PRODUCTION || TESTING")
  501. ) {
  502. assert(
  503. rgbaBuf instanceof Uint8ClampedArray,
  504. 'PDFImage.fillOpacity: Unsupported "rgbaBuf" type.'
  505. );
  506. }
  507. const smask = this.smask;
  508. const mask = this.mask;
  509. let alphaBuf, sw, sh, i, ii, j;
  510. if (smask) {
  511. sw = smask.width;
  512. sh = smask.height;
  513. alphaBuf = new Uint8ClampedArray(sw * sh);
  514. smask.fillGrayBuffer(alphaBuf);
  515. if (sw !== width || sh !== height) {
  516. alphaBuf = resizeImageMask(alphaBuf, smask.bpc, sw, sh, width, height);
  517. }
  518. } else if (mask) {
  519. if (mask instanceof PDFImage) {
  520. sw = mask.width;
  521. sh = mask.height;
  522. alphaBuf = new Uint8ClampedArray(sw * sh);
  523. mask.numComps = 1;
  524. mask.fillGrayBuffer(alphaBuf);
  525. // Need to invert values in rgbaBuf
  526. for (i = 0, ii = sw * sh; i < ii; ++i) {
  527. alphaBuf[i] = 255 - alphaBuf[i];
  528. }
  529. if (sw !== width || sh !== height) {
  530. alphaBuf = resizeImageMask(alphaBuf, mask.bpc, sw, sh, width, height);
  531. }
  532. } else if (Array.isArray(mask)) {
  533. // Color key mask: if any of the components are outside the range
  534. // then they should be painted.
  535. alphaBuf = new Uint8ClampedArray(width * height);
  536. const numComps = this.numComps;
  537. for (i = 0, ii = width * height; i < ii; ++i) {
  538. let opacity = 0;
  539. const imageOffset = i * numComps;
  540. for (j = 0; j < numComps; ++j) {
  541. const color = image[imageOffset + j];
  542. const maskOffset = j * 2;
  543. if (color < mask[maskOffset] || color > mask[maskOffset + 1]) {
  544. opacity = 255;
  545. break;
  546. }
  547. }
  548. alphaBuf[i] = opacity;
  549. }
  550. } else {
  551. throw new FormatError("Unknown mask format.");
  552. }
  553. }
  554. if (alphaBuf) {
  555. for (i = 0, j = 3, ii = width * actualHeight; i < ii; ++i, j += 4) {
  556. rgbaBuf[j] = alphaBuf[i];
  557. }
  558. } else {
  559. // No mask.
  560. for (i = 0, j = 3, ii = width * actualHeight; i < ii; ++i, j += 4) {
  561. rgbaBuf[j] = 255;
  562. }
  563. }
  564. }
  565. undoPreblend(buffer, width, height) {
  566. if (
  567. typeof PDFJSDev === "undefined" ||
  568. PDFJSDev.test("!PRODUCTION || TESTING")
  569. ) {
  570. assert(
  571. buffer instanceof Uint8ClampedArray,
  572. 'PDFImage.undoPreblend: Unsupported "buffer" type.'
  573. );
  574. }
  575. const matte = this.smask && this.smask.matte;
  576. if (!matte) {
  577. return;
  578. }
  579. const matteRgb = this.colorSpace.getRgb(matte, 0);
  580. const matteR = matteRgb[0];
  581. const matteG = matteRgb[1];
  582. const matteB = matteRgb[2];
  583. const length = width * height * 4;
  584. for (let i = 0; i < length; i += 4) {
  585. const alpha = buffer[i + 3];
  586. if (alpha === 0) {
  587. // according formula we have to get Infinity in all components
  588. // making it white (typical paper color) should be okay
  589. buffer[i] = 255;
  590. buffer[i + 1] = 255;
  591. buffer[i + 2] = 255;
  592. continue;
  593. }
  594. const k = 255 / alpha;
  595. buffer[i] = (buffer[i] - matteR) * k + matteR;
  596. buffer[i + 1] = (buffer[i + 1] - matteG) * k + matteG;
  597. buffer[i + 2] = (buffer[i + 2] - matteB) * k + matteB;
  598. }
  599. }
  600. createImageData(forceRGBA = false) {
  601. const drawWidth = this.drawWidth;
  602. const drawHeight = this.drawHeight;
  603. const imgData = {
  604. width: drawWidth,
  605. height: drawHeight,
  606. interpolate: this.interpolate,
  607. kind: 0,
  608. data: null,
  609. // Other fields are filled in below.
  610. };
  611. const numComps = this.numComps;
  612. const originalWidth = this.width;
  613. const originalHeight = this.height;
  614. const bpc = this.bpc;
  615. // Rows start at byte boundary.
  616. const rowBytes = (originalWidth * numComps * bpc + 7) >> 3;
  617. if (!forceRGBA) {
  618. // If it is a 1-bit-per-pixel grayscale (i.e. black-and-white) image
  619. // without any complications, we pass a same-sized copy to the main
  620. // thread rather than expanding by 32x to RGBA form. This saves *lots*
  621. // of memory for many scanned documents. It's also much faster.
  622. //
  623. // Similarly, if it is a 24-bit-per pixel RGB image without any
  624. // complications, we avoid expanding by 1.333x to RGBA form.
  625. let kind;
  626. if (this.colorSpace.name === "DeviceGray" && bpc === 1) {
  627. kind = ImageKind.GRAYSCALE_1BPP;
  628. } else if (
  629. this.colorSpace.name === "DeviceRGB" &&
  630. bpc === 8 &&
  631. !this.needsDecode
  632. ) {
  633. kind = ImageKind.RGB_24BPP;
  634. }
  635. if (
  636. kind &&
  637. !this.smask &&
  638. !this.mask &&
  639. drawWidth === originalWidth &&
  640. drawHeight === originalHeight
  641. ) {
  642. imgData.kind = kind;
  643. imgData.data = this.getImageBytes(originalHeight * rowBytes, {});
  644. if (this.needsDecode) {
  645. // Invert the buffer (which must be grayscale if we reached here).
  646. assert(
  647. kind === ImageKind.GRAYSCALE_1BPP,
  648. "PDFImage.createImageData: The image must be grayscale."
  649. );
  650. const buffer = imgData.data;
  651. for (let i = 0, ii = buffer.length; i < ii; i++) {
  652. buffer[i] ^= 0xff;
  653. }
  654. }
  655. return imgData;
  656. }
  657. if (this.image instanceof JpegStream && !this.smask && !this.mask) {
  658. let imageLength = originalHeight * rowBytes;
  659. switch (this.colorSpace.name) {
  660. case "DeviceGray":
  661. // Avoid truncating the image, since `JpegImage.getData`
  662. // will expand the image data when `forceRGB === true`.
  663. imageLength *= 3;
  664. /* falls through */
  665. case "DeviceRGB":
  666. case "DeviceCMYK":
  667. imgData.kind = ImageKind.RGB_24BPP;
  668. imgData.data = this.getImageBytes(imageLength, {
  669. drawWidth,
  670. drawHeight,
  671. forceRGB: true,
  672. });
  673. return imgData;
  674. }
  675. }
  676. }
  677. const imgArray = this.getImageBytes(originalHeight * rowBytes, {
  678. internal: true,
  679. });
  680. // imgArray can be incomplete (e.g. after CCITT fax encoding).
  681. const actualHeight =
  682. 0 | (((imgArray.length / rowBytes) * drawHeight) / originalHeight);
  683. const comps = this.getComponents(imgArray);
  684. // If opacity data is present, use RGBA_32BPP form. Otherwise, use the
  685. // more compact RGB_24BPP form if allowable.
  686. let alpha01, maybeUndoPreblend;
  687. if (!forceRGBA && !this.smask && !this.mask) {
  688. imgData.kind = ImageKind.RGB_24BPP;
  689. imgData.data = new Uint8ClampedArray(drawWidth * drawHeight * 3);
  690. alpha01 = 0;
  691. maybeUndoPreblend = false;
  692. } else {
  693. imgData.kind = ImageKind.RGBA_32BPP;
  694. imgData.data = new Uint8ClampedArray(drawWidth * drawHeight * 4);
  695. alpha01 = 1;
  696. maybeUndoPreblend = true;
  697. // Color key masking (opacity) must be performed before decoding.
  698. this.fillOpacity(
  699. imgData.data,
  700. drawWidth,
  701. drawHeight,
  702. actualHeight,
  703. comps
  704. );
  705. }
  706. if (this.needsDecode) {
  707. this.decodeBuffer(comps);
  708. }
  709. this.colorSpace.fillRgb(
  710. imgData.data,
  711. originalWidth,
  712. originalHeight,
  713. drawWidth,
  714. drawHeight,
  715. actualHeight,
  716. bpc,
  717. comps,
  718. alpha01
  719. );
  720. if (maybeUndoPreblend) {
  721. this.undoPreblend(imgData.data, drawWidth, actualHeight);
  722. }
  723. return imgData;
  724. }
  725. fillGrayBuffer(buffer) {
  726. if (
  727. typeof PDFJSDev === "undefined" ||
  728. PDFJSDev.test("!PRODUCTION || TESTING")
  729. ) {
  730. assert(
  731. buffer instanceof Uint8ClampedArray,
  732. 'PDFImage.fillGrayBuffer: Unsupported "buffer" type.'
  733. );
  734. }
  735. const numComps = this.numComps;
  736. if (numComps !== 1) {
  737. throw new FormatError(
  738. `Reading gray scale from a color image: ${numComps}`
  739. );
  740. }
  741. const width = this.width;
  742. const height = this.height;
  743. const bpc = this.bpc;
  744. // rows start at byte boundary
  745. const rowBytes = (width * numComps * bpc + 7) >> 3;
  746. const imgArray = this.getImageBytes(height * rowBytes, { internal: true });
  747. const comps = this.getComponents(imgArray);
  748. let i, length;
  749. if (bpc === 1) {
  750. // inline decoding (= inversion) for 1 bpc images
  751. length = width * height;
  752. if (this.needsDecode) {
  753. // invert and scale to {0, 255}
  754. for (i = 0; i < length; ++i) {
  755. buffer[i] = (comps[i] - 1) & 255;
  756. }
  757. } else {
  758. // scale to {0, 255}
  759. for (i = 0; i < length; ++i) {
  760. buffer[i] = -comps[i] & 255;
  761. }
  762. }
  763. return;
  764. }
  765. if (this.needsDecode) {
  766. this.decodeBuffer(comps);
  767. }
  768. length = width * height;
  769. // we aren't using a colorspace so we need to scale the value
  770. const scale = 255 / ((1 << bpc) - 1);
  771. for (i = 0; i < length; ++i) {
  772. buffer[i] = scale * comps[i];
  773. }
  774. }
  775. getImageBytes(
  776. length,
  777. { drawWidth, drawHeight, forceRGB = false, internal = false }
  778. ) {
  779. this.image.reset();
  780. this.image.drawWidth = drawWidth || this.width;
  781. this.image.drawHeight = drawHeight || this.height;
  782. this.image.forceRGB = !!forceRGB;
  783. const imageBytes = this.image.getBytes(length);
  784. // If imageBytes came from a DecodeStream, we're safe to transfer it
  785. // (and thus detach its underlying buffer) because it will constitute
  786. // the entire DecodeStream's data. But if it came from a Stream, we
  787. // need to copy it because it'll only be a portion of the Stream's
  788. // data, and the rest will be read later on.
  789. if (internal || this.image instanceof DecodeStream) {
  790. return imageBytes;
  791. }
  792. assert(
  793. imageBytes instanceof Uint8Array,
  794. 'PDFImage.getImageBytes: Unsupported "imageBytes" type.'
  795. );
  796. return new Uint8Array(imageBytes);
  797. }
  798. }
  799. export { PDFImage };