Merge pull request #20016 from ryzokuken/move-getcontext
[api-minor] Move getContext call to InternalRenderTask
This commit is contained in:
commit
1b427a3af5
@ -501,7 +501,8 @@ function getDocument(src = {}) {
|
||||
task,
|
||||
networkStream,
|
||||
transportParams,
|
||||
transportFactory
|
||||
transportFactory,
|
||||
enableHWA
|
||||
);
|
||||
task._transport = transport;
|
||||
messageHandler.send("Ready", null);
|
||||
@ -1181,8 +1182,12 @@ class PDFDocumentProxy {
|
||||
* Page render parameters.
|
||||
*
|
||||
* @typedef {Object} RenderParameters
|
||||
* @property {CanvasRenderingContext2D} canvasContext - A 2D context of a DOM
|
||||
* Canvas object.
|
||||
* @property {CanvasRenderingContext2D} canvasContext - Deprecated 2D context of
|
||||
* a DOM Canvas object for backwards compatibility; it is recommended to use
|
||||
* the `canvas` parameter instead.
|
||||
* @property {HTMLCanvasElement} canvas - A DOM Canvas object. The default value
|
||||
* is the canvas associated with the `canvasContext` parameter if no value is
|
||||
* provided explicitly.
|
||||
* @property {PageViewport} viewport - Rendering viewport obtained by calling
|
||||
* the `PDFPageProxy.getViewport` method.
|
||||
* @property {string} [intent] - Rendering intent, can be 'display', 'print',
|
||||
@ -1405,6 +1410,7 @@ class PDFPageProxy {
|
||||
*/
|
||||
render({
|
||||
canvasContext,
|
||||
canvas = canvasContext.canvas,
|
||||
viewport,
|
||||
intent = "display",
|
||||
annotationMode = AnnotationMode.ENABLE,
|
||||
@ -1496,7 +1502,7 @@ class PDFPageProxy {
|
||||
callback: complete,
|
||||
// Only include the required properties, and *not* the entire object.
|
||||
params: {
|
||||
canvasContext,
|
||||
canvas,
|
||||
viewport,
|
||||
transform,
|
||||
background,
|
||||
@ -1511,6 +1517,7 @@ class PDFPageProxy {
|
||||
useRequestAnimationFrame: !intentPrint,
|
||||
pdfBug: this._pdfBug,
|
||||
pageColors,
|
||||
enableHWA: this._transport.enableHWA,
|
||||
});
|
||||
|
||||
(intentState.renderTasks ||= new Set()).add(internalRenderTask);
|
||||
@ -2305,7 +2312,14 @@ class WorkerTransport {
|
||||
|
||||
#passwordCapability = null;
|
||||
|
||||
constructor(messageHandler, loadingTask, networkStream, params, factory) {
|
||||
constructor(
|
||||
messageHandler,
|
||||
loadingTask,
|
||||
networkStream,
|
||||
params,
|
||||
factory,
|
||||
enableHWA
|
||||
) {
|
||||
this.messageHandler = messageHandler;
|
||||
this.loadingTask = loadingTask;
|
||||
this.commonObjs = new PDFObjects();
|
||||
@ -2329,6 +2343,7 @@ class WorkerTransport {
|
||||
this._fullReader = null;
|
||||
this._lastProgress = null;
|
||||
this.downloadInfoCapability = Promise.withResolvers();
|
||||
this.enableHWA = enableHWA;
|
||||
|
||||
this.setupMessageHandler();
|
||||
|
||||
@ -3104,6 +3119,7 @@ class InternalRenderTask {
|
||||
useRequestAnimationFrame = false,
|
||||
pdfBug = false,
|
||||
pageColors = null,
|
||||
enableHWA = false,
|
||||
}) {
|
||||
this.callback = callback;
|
||||
this.params = params;
|
||||
@ -3131,7 +3147,8 @@ class InternalRenderTask {
|
||||
this._continueBound = this._continue.bind(this);
|
||||
this._scheduleNextBound = this._scheduleNext.bind(this);
|
||||
this._nextBound = this._next.bind(this);
|
||||
this._canvas = params.canvasContext.canvas;
|
||||
this._canvas = params.canvas;
|
||||
this._enableHWA = enableHWA;
|
||||
}
|
||||
|
||||
get completed() {
|
||||
@ -3161,7 +3178,12 @@ class InternalRenderTask {
|
||||
this.stepper.init(this.operatorList);
|
||||
this.stepper.nextBreakPoint = this.stepper.getNextBreakPoint();
|
||||
}
|
||||
const { canvasContext, viewport, transform, background } = this.params;
|
||||
const { viewport, transform, background } = this.params;
|
||||
|
||||
const canvasContext = this._canvas.getContext("2d", {
|
||||
alpha: false,
|
||||
willReadFrequently: !this._enableHWA,
|
||||
});
|
||||
|
||||
this.gfx = new CanvasGraphics(
|
||||
canvasContext,
|
||||
|
||||
@ -1016,7 +1016,7 @@ class Driver {
|
||||
}
|
||||
}
|
||||
const renderContext = {
|
||||
canvasContext: ctx,
|
||||
canvas: this.canvas,
|
||||
viewport,
|
||||
optionalContentConfigPromise: task.optionalContentConfigPromise,
|
||||
annotationCanvasMap,
|
||||
|
||||
@ -41,6 +41,7 @@ async function runTests(results) {
|
||||
"stamp_editor_spec.mjs",
|
||||
"text_field_spec.mjs",
|
||||
"text_layer_spec.mjs",
|
||||
"thumbnail_view_spec.mjs",
|
||||
"viewer_spec.mjs",
|
||||
],
|
||||
});
|
||||
|
||||
35
test/integration/thumbnail_view_spec.mjs
Normal file
35
test/integration/thumbnail_view_spec.mjs
Normal file
@ -0,0 +1,35 @@
|
||||
import { closePages, loadAndWait } from "./test_utils.mjs";
|
||||
|
||||
describe("PDF Thumbnail View", () => {
|
||||
describe("Works without errors", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait("tracemonkey.pdf", "#sidebarToggleButton");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("should render thumbnails without errors", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await page.click("#sidebarToggleButton");
|
||||
|
||||
const thumbSelector = "#thumbnailView .thumbnailImage";
|
||||
await page.waitForSelector(thumbSelector, { visible: true });
|
||||
|
||||
await page.waitForSelector(
|
||||
'#thumbnailView .thumbnail[data-loaded="true"]'
|
||||
);
|
||||
|
||||
const src = await page.$eval(thumbSelector, el => el.src);
|
||||
expect(src)
|
||||
.withContext(`In ${browserName}`)
|
||||
.toMatch(/^data:image\//);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -4410,7 +4410,7 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`)
|
||||
viewport.height
|
||||
);
|
||||
const renderTask = pdfPage.render({
|
||||
canvasContext: canvasAndCtx.context,
|
||||
canvas: canvasAndCtx.canvas,
|
||||
viewport,
|
||||
});
|
||||
expect(renderTask instanceof RenderTask).toEqual(true);
|
||||
@ -4446,7 +4446,7 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`)
|
||||
viewport.height
|
||||
);
|
||||
const renderTask = page.render({
|
||||
canvasContext: canvasAndCtx.context,
|
||||
canvas: canvasAndCtx.canvas,
|
||||
viewport,
|
||||
});
|
||||
expect(renderTask instanceof RenderTask).toEqual(true);
|
||||
@ -4477,7 +4477,7 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`)
|
||||
viewport.height
|
||||
);
|
||||
const renderTask = page.render({
|
||||
canvasContext: canvasAndCtx.context,
|
||||
canvas: canvasAndCtx.canvas,
|
||||
viewport,
|
||||
});
|
||||
expect(renderTask instanceof RenderTask).toEqual(true);
|
||||
@ -4494,7 +4494,7 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`)
|
||||
}
|
||||
|
||||
const reRenderTask = page.render({
|
||||
canvasContext: canvasAndCtx.context,
|
||||
canvas: canvasAndCtx.canvas,
|
||||
viewport,
|
||||
});
|
||||
expect(reRenderTask instanceof RenderTask).toEqual(true);
|
||||
@ -4518,14 +4518,14 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`)
|
||||
viewport.height
|
||||
);
|
||||
const renderTask1 = page.render({
|
||||
canvasContext: canvasAndCtx.context,
|
||||
canvas: canvasAndCtx.canvas,
|
||||
viewport,
|
||||
optionalContentConfigPromise,
|
||||
});
|
||||
expect(renderTask1 instanceof RenderTask).toEqual(true);
|
||||
|
||||
const renderTask2 = page.render({
|
||||
canvasContext: canvasAndCtx.context,
|
||||
canvas: canvasAndCtx.canvas,
|
||||
viewport,
|
||||
optionalContentConfigPromise,
|
||||
});
|
||||
@ -4562,7 +4562,7 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`)
|
||||
viewport.height
|
||||
);
|
||||
const renderTask = pdfPage.render({
|
||||
canvasContext: canvasAndCtx.context,
|
||||
canvas: canvasAndCtx.canvas,
|
||||
viewport,
|
||||
});
|
||||
expect(renderTask instanceof RenderTask).toEqual(true);
|
||||
@ -4591,7 +4591,7 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`)
|
||||
viewport.height
|
||||
);
|
||||
const renderTask = pdfPage.render({
|
||||
canvasContext: canvasAndCtx.context,
|
||||
canvas: canvasAndCtx.canvas,
|
||||
viewport,
|
||||
background: "#FF0000", // See comment below.
|
||||
});
|
||||
@ -4651,7 +4651,7 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`)
|
||||
viewport.height
|
||||
);
|
||||
const renderTask = pdfPage.render({
|
||||
canvasContext: canvasAndCtx.context,
|
||||
canvas: canvasAndCtx.canvas,
|
||||
viewport,
|
||||
});
|
||||
|
||||
@ -4755,7 +4755,7 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`)
|
||||
viewport.height
|
||||
);
|
||||
const renderTask = pdfPage.render({
|
||||
canvasContext: canvasAndCtx.context,
|
||||
canvas: canvasAndCtx.canvas,
|
||||
viewport,
|
||||
});
|
||||
|
||||
@ -4802,7 +4802,7 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`)
|
||||
viewport.height
|
||||
);
|
||||
const renderTask = pdfPage.render({
|
||||
canvasContext: canvasAndCtx.context,
|
||||
canvas: canvasAndCtx.canvas,
|
||||
viewport,
|
||||
});
|
||||
|
||||
@ -4852,7 +4852,7 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`)
|
||||
viewport.height
|
||||
);
|
||||
const renderTask = pdfPage.render({
|
||||
canvasContext: canvasAndCtx.context,
|
||||
canvas: canvasAndCtx.canvas,
|
||||
viewport,
|
||||
intent: "print",
|
||||
annotationMode: AnnotationMode.ENABLE_STORAGE,
|
||||
@ -4911,6 +4911,34 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`)
|
||||
|
||||
await loadingTask.destroy();
|
||||
});
|
||||
|
||||
it("should work with the legacy canvasContext parameter", async function () {
|
||||
const loadingTask = getDocument(tracemonkeyGetDocumentParams);
|
||||
const pdfDoc = await loadingTask.promise;
|
||||
const pdfPage = await pdfDoc.getPage(1);
|
||||
const viewport = pdfPage.getViewport({ scale: 1 });
|
||||
|
||||
const { canvasFactory } = pdfDoc;
|
||||
const canvasAndCtx = canvasFactory.create(
|
||||
viewport.width,
|
||||
viewport.height
|
||||
);
|
||||
const renderTask = pdfPage.render({
|
||||
canvasContext: canvasAndCtx.context,
|
||||
viewport,
|
||||
});
|
||||
expect(renderTask instanceof RenderTask).toEqual(true);
|
||||
|
||||
await renderTask.promise;
|
||||
expect(
|
||||
canvasAndCtx.context
|
||||
.getImageData(0, 0, viewport.width, viewport.height)
|
||||
.data.some(channel => channel !== 0)
|
||||
).toEqual(true);
|
||||
|
||||
canvasFactory.destroy(canvasAndCtx);
|
||||
await loadingTask.destroy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Multiple `getDocument` instances", function () {
|
||||
@ -4939,7 +4967,7 @@ Caron Broadcasting, Inc., an Ohio corporation (“Lessee”).`)
|
||||
viewport.height
|
||||
);
|
||||
const renderTask = page.render({
|
||||
canvasContext: canvasAndCtx.context,
|
||||
canvas: canvasAndCtx.canvas,
|
||||
viewport,
|
||||
});
|
||||
await renderTask.promise;
|
||||
|
||||
@ -50,7 +50,7 @@ describe("custom canvas rendering", function () {
|
||||
const canvasAndCtx = canvasFactory.create(viewport.width, viewport.height);
|
||||
|
||||
const renderTask = page.render({
|
||||
canvasContext: canvasAndCtx.context,
|
||||
canvas: canvasAndCtx.canvas,
|
||||
viewport,
|
||||
});
|
||||
await renderTask.promise;
|
||||
@ -70,7 +70,7 @@ describe("custom canvas rendering", function () {
|
||||
const canvasAndCtx = canvasFactory.create(viewport.width, viewport.height);
|
||||
|
||||
const renderTask = page.render({
|
||||
canvasContext: canvasAndCtx.context,
|
||||
canvas: canvasAndCtx.canvas,
|
||||
viewport,
|
||||
background: "rgba(255,0,0,1.0)",
|
||||
});
|
||||
@ -160,7 +160,7 @@ describe("custom ownerDocument", function () {
|
||||
const canvasAndCtx = canvasFactory.create(viewport.width, viewport.height);
|
||||
|
||||
await page.render({
|
||||
canvasContext: canvasAndCtx.context,
|
||||
canvas: canvasAndCtx.canvas,
|
||||
viewport,
|
||||
}).promise;
|
||||
|
||||
@ -194,7 +194,7 @@ describe("custom ownerDocument", function () {
|
||||
const canvasAndCtx = canvasFactory.create(viewport.width, viewport.height);
|
||||
|
||||
await page.render({
|
||||
canvasContext: canvasAndCtx.context,
|
||||
canvas: canvasAndCtx.canvas,
|
||||
viewport,
|
||||
}).promise;
|
||||
|
||||
|
||||
@ -17,8 +17,6 @@ import { RenderingCancelledException } from "pdfjs-lib";
|
||||
import { RenderingStates } from "./ui_utils.js";
|
||||
|
||||
class BasePDFPageView {
|
||||
#enableHWA = false;
|
||||
|
||||
#loadingId = null;
|
||||
|
||||
#minDurationToUpdateCanvas = 0;
|
||||
@ -51,8 +49,6 @@ class BasePDFPageView {
|
||||
resume = null;
|
||||
|
||||
constructor(options) {
|
||||
this.#enableHWA =
|
||||
#enableHWA in options ? options.#enableHWA : options.enableHWA || false;
|
||||
this.eventBus = options.eventBus;
|
||||
this.id = options.id;
|
||||
this.pageColors = options.pageColors || null;
|
||||
@ -166,12 +162,7 @@ class BasePDFPageView {
|
||||
}
|
||||
};
|
||||
|
||||
const ctx = canvas.getContext("2d", {
|
||||
alpha: false,
|
||||
willReadFrequently: !this.#enableHWA,
|
||||
});
|
||||
|
||||
return { canvas, prevCanvas, ctx };
|
||||
return { canvas, prevCanvas };
|
||||
}
|
||||
|
||||
#renderContinueCallback = cont => {
|
||||
|
||||
@ -72,7 +72,7 @@ function composePage(
|
||||
currentRenderTask = null;
|
||||
}
|
||||
const renderContext = {
|
||||
canvasContext: ctx,
|
||||
canvas: ctx.canvas,
|
||||
transform: [PRINT_UNITS, 0, 0, PRINT_UNITS, 0, 0],
|
||||
viewport: pdfPage.getViewport({ scale: 1, rotation: size.rotation }),
|
||||
intent: "print",
|
||||
|
||||
@ -214,7 +214,7 @@ class PDFPageDetailView extends BasePDFPageView {
|
||||
|
||||
const canvasWrapper = this.pageView._ensureCanvasWrapper();
|
||||
|
||||
const { canvas, prevCanvas, ctx } = this._createCanvas(newCanvas => {
|
||||
const { canvas, prevCanvas } = this._createCanvas(newCanvas => {
|
||||
// If there is already the background canvas, inject this new canvas
|
||||
// after it. We cannot simply use .append because all canvases must
|
||||
// be before the SVG elements used for drawings.
|
||||
@ -249,7 +249,7 @@ class PDFPageDetailView extends BasePDFPageView {
|
||||
style.left = `${(area.minX * 100) / width}%`;
|
||||
|
||||
const renderingPromise = this._drawCanvas(
|
||||
this.pageView._getRenderingContext(ctx, transform),
|
||||
this.pageView._getRenderingContext(canvas, transform),
|
||||
() => {
|
||||
// If the rendering is cancelled, keep the old canvas visible.
|
||||
this.canvas?.remove();
|
||||
|
||||
@ -95,8 +95,6 @@ import { XfaLayerBuilder } from "./xfa_layer_builder.js";
|
||||
* @property {IL10n} [l10n] - Localization service.
|
||||
* @property {Object} [layerProperties] - The object that is used to lookup
|
||||
* the necessary layer-properties.
|
||||
* @property {boolean} [enableHWA] - Enables hardware acceleration for
|
||||
* rendering. The default value is `false`.
|
||||
* @property {boolean} [enableAutoLinking] - Enable creation of hyperlinks from
|
||||
* text that look like URLs. The default value is `true`.
|
||||
*/
|
||||
@ -912,9 +910,9 @@ class PDFPageView extends BasePDFPageView {
|
||||
return canvasWrapper;
|
||||
}
|
||||
|
||||
_getRenderingContext(canvasContext, transform) {
|
||||
_getRenderingContext(canvas, transform) {
|
||||
return {
|
||||
canvasContext,
|
||||
canvas,
|
||||
transform,
|
||||
viewport: this.viewport,
|
||||
annotationMode: this.#annotationMode,
|
||||
@ -1000,7 +998,7 @@ class PDFPageView extends BasePDFPageView {
|
||||
const { width, height } = viewport;
|
||||
this.#originalViewport = viewport;
|
||||
|
||||
const { canvas, prevCanvas, ctx } = this._createCanvas(newCanvas => {
|
||||
const { canvas, prevCanvas } = this._createCanvas(newCanvas => {
|
||||
// Always inject the canvas as the first element in the wrapper.
|
||||
canvasWrapper.prepend(newCanvas);
|
||||
});
|
||||
@ -1042,7 +1040,7 @@ class PDFPageView extends BasePDFPageView {
|
||||
? [outputScale.sx, 0, 0, outputScale.sy, 0, 0]
|
||||
: null;
|
||||
const resultPromise = this._drawCanvas(
|
||||
this._getRenderingContext(ctx, transform),
|
||||
this._getRenderingContext(canvas, transform),
|
||||
() => {
|
||||
prevCanvas?.remove();
|
||||
this._resetCanvas();
|
||||
|
||||
@ -58,7 +58,7 @@ function renderPage(
|
||||
printAnnotationStoragePromise,
|
||||
]).then(function ([pdfPage, printAnnotationStorage]) {
|
||||
const renderContext = {
|
||||
canvasContext: ctx,
|
||||
canvas: scratchCanvas,
|
||||
transform: [PRINT_UNITS, 0, 0, PRINT_UNITS, 0, 0],
|
||||
viewport: pdfPage.getViewport({ scale: 1, rotation: size.rotation }),
|
||||
intent: "print",
|
||||
|
||||
@ -58,8 +58,6 @@ function zeroCanvas(c) {
|
||||
* @property {Object} [pageColors] - Overwrites background and foreground colors
|
||||
* with user defined ones in order to improve readability in high contrast
|
||||
* mode.
|
||||
* @property {boolean} [enableHWA] - Enables hardware acceleration for
|
||||
* rendering. The default value is `false`.
|
||||
*/
|
||||
|
||||
class TempImageFactory {
|
||||
@ -106,7 +104,6 @@ class PDFThumbnailView {
|
||||
maxCanvasPixels,
|
||||
maxCanvasDim,
|
||||
pageColors,
|
||||
enableHWA,
|
||||
}) {
|
||||
this.id = id;
|
||||
this.renderingId = "thumbnail" + id;
|
||||
@ -120,7 +117,6 @@ class PDFThumbnailView {
|
||||
this.maxCanvasPixels = maxCanvasPixels ?? AppOptions.get("maxCanvasPixels");
|
||||
this.maxCanvasDim = maxCanvasDim || AppOptions.get("maxCanvasDim");
|
||||
this.pageColors = pageColors || null;
|
||||
this.enableHWA = enableHWA || false;
|
||||
|
||||
this.eventBus = eventBus;
|
||||
this.linkService = linkService;
|
||||
@ -214,14 +210,10 @@ class PDFThumbnailView {
|
||||
this.resume = null;
|
||||
}
|
||||
|
||||
#getPageDrawContext(upscaleFactor = 1, enableHWA = this.enableHWA) {
|
||||
#getPageDrawContext(upscaleFactor = 1) {
|
||||
// Keep the no-thumbnail outline visible, i.e. `data-loaded === false`,
|
||||
// until rendering/image conversion is complete, to avoid display issues.
|
||||
const canvas = document.createElement("canvas");
|
||||
const ctx = canvas.getContext("2d", {
|
||||
alpha: false,
|
||||
willReadFrequently: !enableHWA,
|
||||
});
|
||||
const outputScale = new OutputScale();
|
||||
const width = upscaleFactor * this.canvasWidth,
|
||||
height = upscaleFactor * this.canvasHeight;
|
||||
@ -239,7 +231,7 @@ class PDFThumbnailView {
|
||||
? [outputScale.sx, 0, 0, outputScale.sy, 0, 0]
|
||||
: null;
|
||||
|
||||
return { ctx, canvas, transform };
|
||||
return { canvas, transform };
|
||||
}
|
||||
|
||||
#convertCanvasToImage(canvas) {
|
||||
@ -280,8 +272,7 @@ class PDFThumbnailView {
|
||||
// the `draw` and `setImage` methods (fixes issue 8233).
|
||||
// NOTE: To primarily avoid increasing memory usage too much, but also to
|
||||
// reduce downsizing overhead, we purposely limit the up-scaling factor.
|
||||
const { ctx, canvas, transform } =
|
||||
this.#getPageDrawContext(DRAW_UPSCALE_FACTOR);
|
||||
const { canvas, transform } = this.#getPageDrawContext(DRAW_UPSCALE_FACTOR);
|
||||
const drawViewport = this.viewport.clone({
|
||||
scale: DRAW_UPSCALE_FACTOR * this.scale,
|
||||
});
|
||||
@ -298,7 +289,7 @@ class PDFThumbnailView {
|
||||
};
|
||||
|
||||
const renderContext = {
|
||||
canvasContext: ctx,
|
||||
canvas,
|
||||
transform,
|
||||
viewport: drawViewport,
|
||||
optionalContentConfigPromise: this._optionalContentConfigPromise,
|
||||
@ -378,7 +369,11 @@ class PDFThumbnailView {
|
||||
}
|
||||
|
||||
#reduceImage(img) {
|
||||
const { ctx, canvas } = this.#getPageDrawContext(1, true);
|
||||
const { canvas } = this.#getPageDrawContext(1);
|
||||
const ctx = canvas.getContext("2d", {
|
||||
alpha: false,
|
||||
willReadFrequently: false,
|
||||
});
|
||||
|
||||
if (img.width <= 2 * canvas.width) {
|
||||
ctx.drawImage(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user