Use a BMP decoder when resizing an image
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.
This commit is contained in:
parent
07a1d30fad
commit
b649b6f8dd
@ -83,6 +83,7 @@ const DefaultPartialEvaluatorOptions = Object.freeze({
|
|||||||
ignoreErrors: false,
|
ignoreErrors: false,
|
||||||
isEvalSupported: true,
|
isEvalSupported: true,
|
||||||
isOffscreenCanvasSupported: false,
|
isOffscreenCanvasSupported: false,
|
||||||
|
isChrome: false,
|
||||||
canvasMaxAreaInBytes: -1,
|
canvasMaxAreaInBytes: -1,
|
||||||
fontExtraProperties: false,
|
fontExtraProperties: false,
|
||||||
useSystemFonts: true,
|
useSystemFonts: true,
|
||||||
@ -232,7 +233,14 @@ class PartialEvaluator {
|
|||||||
|
|
||||||
this._regionalImageCache = new RegionalImageCache();
|
this._regionalImageCache = new RegionalImageCache();
|
||||||
this._fetchBuiltInCMapBound = this.fetchBuiltInCMap.bind(this);
|
this._fetchBuiltInCMapBound = this.fetchBuiltInCMap.bind(this);
|
||||||
ImageResizer.setMaxArea(this.options.canvasMaxAreaInBytes);
|
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
|
||||||
|
ImageResizer.setMaxArea(this.options.canvasMaxAreaInBytes);
|
||||||
|
} else {
|
||||||
|
ImageResizer.setOptions({
|
||||||
|
isChrome: this.options.isChrome,
|
||||||
|
maxArea: this.options.canvasMaxAreaInBytes,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -13,7 +13,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { FeatureTest, ImageKind, shadow } from "../shared/util.js";
|
import { FeatureTest, ImageKind, shadow, warn } from "../shared/util.js";
|
||||||
|
|
||||||
const MIN_IMAGE_DIM = 2048;
|
const MIN_IMAGE_DIM = 2048;
|
||||||
|
|
||||||
@ -34,11 +34,28 @@ const MAX_ERROR = 128;
|
|||||||
class ImageResizer {
|
class ImageResizer {
|
||||||
static #goodSquareLength = MIN_IMAGE_DIM;
|
static #goodSquareLength = MIN_IMAGE_DIM;
|
||||||
|
|
||||||
|
static #isChrome = false;
|
||||||
|
|
||||||
constructor(imgData, isMask) {
|
constructor(imgData, isMask) {
|
||||||
this._imgData = imgData;
|
this._imgData = imgData;
|
||||||
this._isMask = isMask;
|
this._isMask = isMask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static get canUseImageDecoder() {
|
||||||
|
// TODO: remove the isChrome, once Chrome doesn't crash anymore with
|
||||||
|
// issue6741.pdf.
|
||||||
|
// https://issues.chromium.org/issues/374807001.
|
||||||
|
return shadow(
|
||||||
|
this,
|
||||||
|
"canUseImageDecoder",
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
this.#isChrome || typeof ImageDecoder === "undefined"
|
||||||
|
? Promise.resolve(false)
|
||||||
|
: // eslint-disable-next-line no-undef
|
||||||
|
ImageDecoder.isTypeSupported("image/bmp")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
static needsToBeResized(width, height) {
|
static needsToBeResized(width, height) {
|
||||||
if (width <= this.#goodSquareLength && height <= this.#goodSquareLength) {
|
if (width <= this.#goodSquareLength && height <= this.#goodSquareLength) {
|
||||||
return false;
|
return false;
|
||||||
@ -113,6 +130,14 @@ class ImageResizer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static setOptions(opts) {
|
||||||
|
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
|
||||||
|
throw new Error("Not implemented: setOptions");
|
||||||
|
}
|
||||||
|
this.setMaxArea(opts.maxArea ?? -1);
|
||||||
|
this.#isChrome = opts.isChrome ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
static _areGoodDims(width, height) {
|
static _areGoodDims(width, height) {
|
||||||
try {
|
try {
|
||||||
// This code is working in either Firefox or Chrome.
|
// This code is working in either Firefox or Chrome.
|
||||||
@ -157,10 +182,38 @@ class ImageResizer {
|
|||||||
|
|
||||||
async _createImage() {
|
async _createImage() {
|
||||||
const data = this._encodeBMP();
|
const data = this._encodeBMP();
|
||||||
const blob = new Blob([data.buffer], {
|
let decoder, imagePromise;
|
||||||
type: "image/bmp",
|
|
||||||
});
|
if (await ImageResizer.canUseImageDecoder) {
|
||||||
const bitmapPromise = createImageBitmap(blob);
|
// eslint-disable-next-line no-undef
|
||||||
|
decoder = new ImageDecoder({
|
||||||
|
data,
|
||||||
|
type: "image/bmp",
|
||||||
|
preferAnimation: false,
|
||||||
|
transfer: [data.buffer],
|
||||||
|
});
|
||||||
|
imagePromise = decoder
|
||||||
|
.decode()
|
||||||
|
.catch(reason => {
|
||||||
|
warn(`BMP image decoding failed: ${reason}`);
|
||||||
|
// It's a bit unfortunate to create the BMP twice but we shouldn't be
|
||||||
|
// here in the first place.
|
||||||
|
return createImageBitmap(
|
||||||
|
new Blob([this._encodeBMP().buffer], {
|
||||||
|
type: "image/bmp",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
decoder.close();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
imagePromise = createImageBitmap(
|
||||||
|
new Blob([data.buffer], {
|
||||||
|
type: "image/bmp",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const { MAX_AREA, MAX_DIM } = ImageResizer;
|
const { MAX_AREA, MAX_DIM } = ImageResizer;
|
||||||
const { _imgData: imgData } = this;
|
const { _imgData: imgData } = this;
|
||||||
@ -185,7 +238,8 @@ class ImageResizer {
|
|||||||
|
|
||||||
let newWidth = width;
|
let newWidth = width;
|
||||||
let newHeight = height;
|
let newHeight = height;
|
||||||
let bitmap = await bitmapPromise;
|
const result = await imagePromise;
|
||||||
|
let bitmap = result.image || result;
|
||||||
|
|
||||||
for (const step of steps) {
|
for (const step of steps) {
|
||||||
const prevWidth = newWidth;
|
const prevWidth = newWidth;
|
||||||
@ -210,6 +264,9 @@ class ImageResizer {
|
|||||||
newWidth,
|
newWidth,
|
||||||
newHeight
|
newHeight
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Release the resources associated with the bitmap.
|
||||||
|
bitmap.close();
|
||||||
bitmap = canvas.transferToImageBitmap();
|
bitmap = canvas.transferToImageBitmap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import {
|
|||||||
AbortException,
|
AbortException,
|
||||||
AnnotationMode,
|
AnnotationMode,
|
||||||
assert,
|
assert,
|
||||||
|
FeatureTest,
|
||||||
getVerbosityLevel,
|
getVerbosityLevel,
|
||||||
info,
|
info,
|
||||||
InvalidPDFException,
|
InvalidPDFException,
|
||||||
@ -177,6 +178,9 @@ const DefaultStandardFontDataFactory =
|
|||||||
* `OffscreenCanvas` in the worker. Primarily used to improve performance of
|
* `OffscreenCanvas` in the worker. Primarily used to improve performance of
|
||||||
* image conversion/rendering.
|
* image conversion/rendering.
|
||||||
* The default value is `true` in web environments and `false` in Node.js.
|
* The default value is `true` in web environments and `false` in Node.js.
|
||||||
|
* @property {boolean} [isChrome] - Determines if we can use bmp ImageDecoder.
|
||||||
|
* NOTE: Temporary option until [https://issues.chromium.org/issues/374807001]
|
||||||
|
* is fixed.
|
||||||
* @property {number} [canvasMaxAreaInBytes] - The integer value is used to
|
* @property {number} [canvasMaxAreaInBytes] - The integer value is used to
|
||||||
* know when an image must be resized (uses `OffscreenCanvas` in the worker).
|
* 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.
|
* If it's -1 then a possibly slow algorithm is used to guess the max value.
|
||||||
@ -281,6 +285,13 @@ function getDocument(src = {}) {
|
|||||||
typeof src.isOffscreenCanvasSupported === "boolean"
|
typeof src.isOffscreenCanvasSupported === "boolean"
|
||||||
? src.isOffscreenCanvasSupported
|
? src.isOffscreenCanvasSupported
|
||||||
: !isNodeJS;
|
: !isNodeJS;
|
||||||
|
const isChrome =
|
||||||
|
typeof src.isChrome === "boolean"
|
||||||
|
? src.isChrome
|
||||||
|
: (typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) &&
|
||||||
|
!FeatureTest.platform.isFirefox &&
|
||||||
|
typeof window !== "undefined" &&
|
||||||
|
!!window?.chrome;
|
||||||
const canvasMaxAreaInBytes = Number.isInteger(src.canvasMaxAreaInBytes)
|
const canvasMaxAreaInBytes = Number.isInteger(src.canvasMaxAreaInBytes)
|
||||||
? src.canvasMaxAreaInBytes
|
? src.canvasMaxAreaInBytes
|
||||||
: -1;
|
: -1;
|
||||||
@ -385,6 +396,7 @@ function getDocument(src = {}) {
|
|||||||
ignoreErrors,
|
ignoreErrors,
|
||||||
isEvalSupported,
|
isEvalSupported,
|
||||||
isOffscreenCanvasSupported,
|
isOffscreenCanvasSupported,
|
||||||
|
isChrome,
|
||||||
canvasMaxAreaInBytes,
|
canvasMaxAreaInBytes,
|
||||||
fontExtraProperties,
|
fontExtraProperties,
|
||||||
useSystemFonts,
|
useSystemFonts,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user