Skip to content

Commit

Permalink
Use a BMP decoder when resizing an image
Browse files Browse the repository at this point in the history
The image decoding won't block the main thread any more.
For now, it isn't enabled for Chrome because issue6741.pdf leads to a crash.
  • Loading branch information
calixteman committed Oct 18, 2024
1 parent 689ffda commit 4a69d96
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 10 deletions.
6 changes: 5 additions & 1 deletion src/core/evaluator.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ const DefaultPartialEvaluatorOptions = Object.freeze({
ignoreErrors: false,
isEvalSupported: true,
isOffscreenCanvasSupported: false,
isChrome: false,
canvasMaxAreaInBytes: -1,
fontExtraProperties: false,
useSystemFonts: true,
Expand Down Expand Up @@ -233,7 +234,10 @@ class PartialEvaluator {

this._regionalImageCache = new RegionalImageCache();
this._fetchBuiltInCMapBound = this.fetchBuiltInCMap.bind(this);
ImageResizer.setMaxArea(this.options.canvasMaxAreaInBytes);
ImageResizer.setOptions({
isChrome: this.options.isChrome,
maxArea: this.options.canvasMaxAreaInBytes,
});
}

/**
Expand Down
49 changes: 44 additions & 5 deletions src/core/image_resizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ const MAX_ERROR = 128;
// should be a way faster to create the bitmap.

class ImageResizer {
static #isChrome = false;

constructor(imgData, isMask) {
this._imgData = imgData;
this._isMask = isMask;
Expand Down Expand Up @@ -116,6 +118,11 @@ class ImageResizer {
}
}

static setOptions({ maxArea = -1, isChrome = false }) {
this.setMaxArea(maxArea);
this.#isChrome = isChrome;
}

static _areGoodDims(width, height) {
try {
// This code is working in either Firefox or Chrome.
Expand Down Expand Up @@ -160,10 +167,33 @@ class ImageResizer {

async _createImage() {
const data = this._encodeBMP();
const blob = new Blob([data.buffer], {
type: "image/bmp",
});
const bitmapPromise = createImageBitmap(blob);

let decoder, imagePromise;

// TODO: remove the isChrome, once Chrome isn't crashing anymore with
// isssue6741.pdf.
if (
(typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) ||
(!ImageResizer.#isChrome &&
// eslint-disable-next-line no-undef
typeof ImageDecoder !== "undefined" &&
// eslint-disable-next-line no-undef
(await ImageDecoder.isTypeSupported("image/bmp")))
) {
// eslint-disable-next-line no-undef
decoder = new ImageDecoder({
data,
type: "image/bmp",
preferAnimation: false,
transfer: [data.buffer],
});
imagePromise = decoder.decode();
} else {
const blob = new Blob([data.buffer], {
type: "image/bmp",
});
imagePromise = createImageBitmap(blob);
}

const { MAX_AREA, MAX_DIM } = ImageResizer;
const { _imgData: imgData } = this;
Expand All @@ -188,7 +218,16 @@ class ImageResizer {

let newWidth = width;
let newHeight = height;
let bitmap = await bitmapPromise;
let bitmap;
if (
(typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) ||
decoder
) {
({ image: bitmap } = await imagePromise);
decoder.close();
} else {
bitmap = await imagePromise;
}

for (const step of steps) {
const prevWidth = newWidth;
Expand Down
3 changes: 3 additions & 0 deletions src/display/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ const DefaultStandardFontDataFactory =
* `OffscreenCanvas` in the worker. Primarily used to improve performance of
* image conversion/rendering.
* The default value is `true` in web environments and `false` in Node.js.
* @property {boolean} [isChrome] - Determines if we can use bmp ImageDecoder.
* @property {number} [canvasMaxAreaInBytes] - The integer value is used to
* know when an image must be resized (uses `OffscreenCanvas` in the worker).
* If it's -1 then a possibly slow algorithm is used to guess the max value.
Expand Down Expand Up @@ -281,6 +282,7 @@ function getDocument(src = {}) {
typeof src.isOffscreenCanvasSupported === "boolean"
? src.isOffscreenCanvasSupported
: !isNodeJS;
const isChrome = src.isChrome === true;
const canvasMaxAreaInBytes = Number.isInteger(src.canvasMaxAreaInBytes)
? src.canvasMaxAreaInBytes
: -1;
Expand Down Expand Up @@ -385,6 +387,7 @@ function getDocument(src = {}) {
ignoreErrors,
isEvalSupported,
isOffscreenCanvasSupported,
isChrome,
canvasMaxAreaInBytes,
fontExtraProperties,
useSystemFonts,
Expand Down
11 changes: 7 additions & 4 deletions src/shared/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -634,19 +634,22 @@ class FeatureTest {
(typeof navigator !== "undefined" &&
typeof navigator?.platform === "string")
) {
const isFirefox =
(typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) ||
(typeof navigator?.userAgent === "string" &&
navigator.userAgent.includes("Firefox"));
return shadow(this, "platform", {
isChrome: !isFirefox && !!window.chrome,
isMac: navigator.platform.includes("Mac"),
isWindows: navigator.platform.includes("Win"),
isFirefox:
(typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) ||
(typeof navigator?.userAgent === "string" &&
navigator.userAgent.includes("Firefox")),
isFirefox,
});
}
return shadow(this, "platform", {
isMac: false,
isWindows: false,
isFirefox: false,
isChrome: !!window.chrome,
});
}

Expand Down
1 change: 1 addition & 0 deletions web/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -1045,6 +1045,7 @@ const PDFViewerApplication = {
const loadingTask = getDocument({
...apiParams,
...args,
isChrome: FeatureTest.platform.isChrome,
});
this.pdfLoadingTask = loadingTask;

Expand Down

0 comments on commit 4a69d96

Please sign in to comment.