1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
|
import { detect2dCanvasBlockOnCanvas } from './detect-canvas-block';
export const shuffleInPlace = <T extends any[]>(a: T): T => {
a.sort(() => Math.random() - 0.5);
return a;
};
export const canvascopy = async (
image: HTMLImageElement,
artifacts: 'fix' | 'keep' = 'fix',
getBlobURL: () => string | undefined,
setBlobURL: (url: string | undefined) => void,
tick: () => Promise<void>,
newBlob: (blob: string) => void,
getAndRemoveRandomBlob: () => string,
betweenSegments: undefined | ((ourPromise: Promise<string>) => Promise<void> | void)
) => {
console.debug('[canvascopy] Entering canvascopy');
const canvas = document.createElement('canvas');
const canvasOpts = {
desynchronized: true,
alpha: true,
willReadFrequently: true,
// change to unorm8 to use 8-bit colours (will break HDR images)
colorType: 'float16' as const,
// change to srgb if colours look off
colorSpace: 'display-p3' as const,
} as const;
const e = detect2dCanvasBlockOnCanvas(canvas, false, {
...canvasOpts,
willReadFrequently: false,
});
if (e) throw e;
console.debug('[canvascopy] Canvas OK');
try {
// See https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/naturalHeight
const canvasWidth = (canvas.width = image.naturalWidth);
const canvasHeight = (canvas.height = image.naturalHeight);
const ctx = canvas.getContext('2d', canvasOpts)!;
const doIntermittentBlob = true;
if (doIntermittentBlob) {
ctx.drawImage(image, 0, 0, canvasWidth, canvasHeight);
// First, convert to blob, make sure that goes well
setBlobURL(URL.createObjectURL(
await new Promise<Blob>((rs, rj) =>
canvas.toBlob((value) =>
value ? rs(value) : rj(new Error('No blob')),
),
),
));
await tick();
}
// TODO: For GNOME Web, we need to check if drawImage is implemented
const oldBlob = doIntermittentBlob ? getBlobURL() : void 0;
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
const segmentPromises = [] as Promise<void>[];
const xSegments = 8, ySegments = 4;
const xSegmentWidth = canvasWidth / xSegments, ySegmentHeight = canvasHeight / ySegments;
const pad = artifacts === 'fix' ? 4 : 0;
for (let x = 0; x < xSegments; x++)
for (let y = 0; y < ySegments; y++) {
const padLeft = x === 0 ? 0 : pad;
const padRight = x === xSegments - 1 ? 0 : pad;
const padTop = y === 0 ? 0 : pad;
const padBottom = y === ySegments - 1 ? 0 : pad;
const xStart = x * xSegmentWidth - padLeft,
xWidth = xSegmentWidth + padLeft + padRight,
yStart = y * ySegmentHeight - padTop,
yHeight = ySegmentHeight + padTop + padBottom;
ctx.drawImage(
image,
xStart,
yStart,
xWidth,
yHeight,
xStart,
yStart,
xWidth,
yHeight,
);
const segmentPromise = new Promise<string>((rs, rj) =>
canvas.toBlob((value) =>
value
? rs(URL.createObjectURL(value))
: rj(new Error('No blob')),
),
);
segmentPromises.push(segmentPromise.then(v => newBlob(v)));
console.debug('[canvascopy] Push overlayed blob');
if (betweenSegments) await betweenSegments(segmentPromise)
ctx.clearRect(xStart, yStart, xWidth, yHeight);
}
await Promise.all(segmentPromises)
if (doIntermittentBlob) await tick();
setBlobURL(getAndRemoveRandomBlob());
if (doIntermittentBlob) URL.revokeObjectURL(oldBlob!);
console.debug('[canvascopy] Done!');
} catch (error) {
console.error('[canvascopy] Failed to get canvas data:', error);
}
};
|