Skip to content

Commit

Permalink
Decompress when it's possible images in using DecompressStream
Browse files Browse the repository at this point in the history
Getting images is already asynchronous, so we can use this opportunity
to use DecompressStream (which is async too) to decompress images.
  • Loading branch information
calixteman committed May 26, 2024
1 parent 18a7bd6 commit b153192
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 29 deletions.
16 changes: 16 additions & 0 deletions src/core/base_stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,22 @@ class BaseStream {
unreachable("Abstract method `getBytes` called");
}

async getImageData(length, ignoreColorSpace) {
return this.getBytes(length, ignoreColorSpace);
}

async asyncGetBytes() {
unreachable("Abstract method `asyncGetBytes` called");
}

get isAsync() {
return false;
}

get canAsyncDecodeImageFromBuffer() {
return false;
}

peekByte() {
const peekedByte = this.getByte();
if (peekedByte !== -1) {
Expand Down
8 changes: 8 additions & 0 deletions src/core/decode_stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,14 @@ class DecodeStream extends BaseStream {
return this.buffer.subarray(pos, end);
}

async getImageData(length, ignoreColorSpace = false) {
if (!this.canAsyncDecodeImageFromBuffer) {
return this.getBytes(length, ignoreColorSpace);
}
const data = await this.stream.asyncGetBytes();
return this.decodeImage(data, ignoreColorSpace);
}

reset() {
this.pos = 0;
}
Expand Down
45 changes: 45 additions & 0 deletions src/core/flate_stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import { FormatError, info } from "../shared/util.js";
import { DecodeStream } from "./decode_stream.js";
import { Stream } from "./stream.js";

const codeLenCodeMap = new Int32Array([
16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15,
Expand Down Expand Up @@ -148,6 +149,50 @@ class FlateStream extends DecodeStream {
this.codeBuf = 0;
}

get isAsync() {
return true;
}

async getImageData(length, _ignoreColorSpace) {
const data = await this.asyncGetBytes();
if (!data) {
return this.getBytes(length);
}
return data.subarray(0, length);
}

async asyncGetBytes() {
this.str.reset();
const bytes = this.str.getBytes();

try {
const { readable, writable } = new DecompressionStream("deflate");
const writer = writable.getWriter();
writer.write(bytes);
writer.close();

const chunks = [];
let totalLength = 0;

for await (const chunk of readable) {
chunks.push(chunk);
totalLength += chunk.byteLength;
}
const data = new Uint8Array(totalLength);
let offset = 0;
for (const chunk of chunks) {
data.set(chunk, offset);
offset += chunk.byteLength;
}

return data;
} catch {
this.str = new Stream(bytes, 2, bytes.length, this.str.dict);
this.reset();
return null;
}
}

getBits(bits) {
const str = this.str;
let codeSize = this.codeSize;
Expand Down
29 changes: 17 additions & 12 deletions src/core/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,7 @@ class PDFImage {
return output;
}

fillOpacity(rgbaBuf, width, height, actualHeight, image) {
async fillOpacity(rgbaBuf, width, height, actualHeight, image) {
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
assert(
rgbaBuf instanceof Uint8ClampedArray,
Expand All @@ -580,7 +580,7 @@ class PDFImage {
sw = smask.width;
sh = smask.height;
alphaBuf = new Uint8ClampedArray(sw * sh);
smask.fillGrayBuffer(alphaBuf);
await smask.fillGrayBuffer(alphaBuf);
if (sw !== width || sh !== height) {
alphaBuf = resizeImageMask(alphaBuf, smask.bpc, sw, sh, width, height);
}
Expand All @@ -590,7 +590,7 @@ class PDFImage {
sh = mask.height;
alphaBuf = new Uint8ClampedArray(sw * sh);
mask.numComps = 1;
mask.fillGrayBuffer(alphaBuf);
await mask.fillGrayBuffer(alphaBuf);

// Need to invert values in rgbaBuf
for (i = 0, ii = sw * sh; i < ii; ++i) {
Expand Down Expand Up @@ -716,7 +716,7 @@ class PDFImage {
drawWidth === originalWidth &&
drawHeight === originalHeight
) {
const data = this.getImageBytes(originalHeight * rowBytes, {});
const data = await this.getImageBytes(originalHeight * rowBytes, {});
if (isOffscreenCanvasSupported) {
if (mustBeResized) {
return ImageResizer.createImage(
Expand Down Expand Up @@ -774,7 +774,7 @@ class PDFImage {
}

if (isHandled) {
const rgba = this.getImageBytes(imageLength, {
const rgba = await this.getImageBytes(imageLength, {
drawWidth,
drawHeight,
forceRGBA: true,
Expand All @@ -794,7 +794,7 @@ class PDFImage {
case "DeviceRGB":
case "DeviceCMYK":
imgData.kind = ImageKind.RGB_24BPP;
imgData.data = this.getImageBytes(imageLength, {
imgData.data = await this.getImageBytes(imageLength, {
drawWidth,
drawHeight,
forceRGB: true,
Expand All @@ -809,7 +809,7 @@ class PDFImage {
}
}

const imgArray = this.getImageBytes(originalHeight * rowBytes, {
const imgArray = await this.getImageBytes(originalHeight * rowBytes, {
internal: true,
});
// imgArray can be incomplete (e.g. after CCITT fax encoding).
Expand Down Expand Up @@ -852,7 +852,7 @@ class PDFImage {
maybeUndoPreblend = true;

// Color key masking (opacity) must be performed before decoding.
this.fillOpacity(data, drawWidth, drawHeight, actualHeight, comps);
await this.fillOpacity(data, drawWidth, drawHeight, actualHeight, comps);
}

if (this.needsDecode) {
Expand Down Expand Up @@ -893,7 +893,7 @@ class PDFImage {
return imgData;
}

fillGrayBuffer(buffer) {
async fillGrayBuffer(buffer) {
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
assert(
buffer instanceof Uint8ClampedArray,
Expand All @@ -913,7 +913,9 @@ class PDFImage {

// rows start at byte boundary
const rowBytes = (width * numComps * bpc + 7) >> 3;
const imgArray = this.getImageBytes(height * rowBytes, { internal: true });
const imgArray = await this.getImageBytes(height * rowBytes, {
internal: true,
});

const comps = this.getComponents(imgArray);
let i, length;
Expand Down Expand Up @@ -975,7 +977,7 @@ class PDFImage {
};
}

getImageBytes(
async getImageBytes(
length,
{
drawWidth,
Expand All @@ -990,7 +992,10 @@ class PDFImage {
this.image.drawHeight = drawHeight || this.height;
this.image.forceRGBA = !!forceRGBA;
this.image.forceRGB = !!forceRGB;
const imageBytes = this.image.getBytes(length, this.ignoreColorSpace);
const imageBytes = await this.image.getImageData(
length,
this.ignoreColorSpace
);

// If imageBytes came from a DecodeStream, we're safe to transfer it
// (and thus detach its underlying buffer) because it will constitute
Expand Down
17 changes: 15 additions & 2 deletions src/core/jbig2_stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,15 @@ class Jbig2Stream extends DecodeStream {
}

readBlock() {
return this.decodeImage();
}

decodeImage(bytes) {
if (this.eof) {
return;
return this.buffer;
}
if (!bytes) {
bytes = this.bytes;
}
const jbig2Image = new Jbig2Image();

Expand All @@ -57,7 +64,7 @@ class Jbig2Stream extends DecodeStream {
chunks.push({ data: globals, start: 0, end: globals.length });
}
}
chunks.push({ data: this.bytes, start: 0, end: this.bytes.length });
chunks.push({ data: bytes, start: 0, end: bytes.length });
const data = jbig2Image.parseChunks(chunks);
const dataLength = data.length;

Expand All @@ -68,6 +75,12 @@ class Jbig2Stream extends DecodeStream {
this.buffer = data;
this.bufferLength = dataLength;
this.eof = true;

return this.buffer;
}

get canAsyncDecodeImageFromBuffer() {
return this.stream.isAsync;
}
}

Expand Down
36 changes: 24 additions & 12 deletions src/core/jpeg_stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,6 @@ import { shadow } from "../shared/util.js";
*/
class JpegStream extends DecodeStream {
constructor(stream, maybeLength, params) {
// Some images may contain 'junk' before the SOI (start-of-image) marker.
// Note: this seems to mainly affect inline images.
let ch;
while ((ch = stream.getByte()) !== -1) {
// Find the first byte of the SOI marker (0xFFD8).
if (ch === 0xff) {
stream.skip(-1); // Reset the stream position to the SOI.
break;
}
}
super(maybeLength);

this.stream = stream;
Expand All @@ -53,8 +43,24 @@ class JpegStream extends DecodeStream {
}

readBlock() {
return this.decodeImage();
}

decodeImage(bytes) {
if (this.eof) {
return;
return this.buffer;
}
if (!bytes) {
bytes = this.bytes;
}

// Some images may contain 'junk' before the SOI (start-of-image) marker.
// Note: this seems to mainly affect inline images.
for (let i = 0, ii = bytes.length - 1; i < ii; i++) {
if (bytes[i] === 0xff && bytes[i + 1] === 0xd8) {
bytes = bytes.subarray(i);
break;
}
}
const jpegOptions = {
decodeTransform: undefined,
Expand Down Expand Up @@ -89,7 +95,7 @@ class JpegStream extends DecodeStream {
}
const jpegImage = new JpegImage(jpegOptions);

jpegImage.parse(this.bytes);
jpegImage.parse(bytes);
const data = jpegImage.getData({
width: this.drawWidth,
height: this.drawHeight,
Expand All @@ -100,6 +106,12 @@ class JpegStream extends DecodeStream {
this.buffer = data;
this.bufferLength = data.length;
this.eof = true;

return this.buffer;
}

get canAsyncDecodeImageFromBuffer() {
return this.stream.isAsync;
}
}

Expand Down
18 changes: 15 additions & 3 deletions src/core/jpx_stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,25 @@ class JpxStream extends DecodeStream {
}

readBlock(ignoreColorSpace) {
return this.decodeImage(null, ignoreColorSpace);
}

decodeImage(bytes, ignoreColorSpace) {
if (this.eof) {
return;
return this.buffer;
}

this.buffer = JpxImage.decode(this.bytes, ignoreColorSpace);
if (!bytes) {
bytes = this.bytes;
}
this.buffer = JpxImage.decode(bytes, ignoreColorSpace);
this.bufferLength = this.buffer.length;
this.eof = true;

return this.buffer;
}

get canAsyncDecodeImageFromBuffer() {
return this.stream.isAsync;
}
}

Expand Down

0 comments on commit b153192

Please sign in to comment.