maplibre-gl 3.3.1 → 3.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/maplibre-gl-csp-worker.js +1 -1
- package/dist/maplibre-gl-csp-worker.js.map +1 -1
- package/dist/maplibre-gl-csp.js +1 -1
- package/dist/maplibre-gl-csp.js.map +1 -1
- package/dist/maplibre-gl-dev.js +430 -128
- package/dist/maplibre-gl-dev.js.map +1 -1
- package/dist/maplibre-gl.d.ts +18 -9
- package/dist/maplibre-gl.js +4 -4
- package/dist/maplibre-gl.js.map +1 -1
- package/package.json +41 -41
- package/src/data/dem_data.test.ts +120 -165
- package/src/data/dem_data.ts +38 -18
- package/src/render/glyph_manager.test.ts +10 -9
- package/src/render/glyph_manager.ts +17 -10
- package/src/source/image_source.test.ts +17 -24
- package/src/source/raster_dem_tile_source.ts +36 -11
- package/src/source/raster_dem_tile_worker_source.ts +9 -26
- package/src/source/raster_tile_source.test.ts +1 -1
- package/src/source/raster_tile_source.ts +1 -1
- package/src/source/terrain_source_cache.test.ts +1 -1
- package/src/source/vector_tile_source.test.ts +1 -1
- package/src/source/vector_tile_worker_source.test.ts +45 -1
- package/src/source/vector_tile_worker_source.ts +19 -6
- package/src/source/worker_source.ts +6 -2
- package/src/style/load_glyph_range.test.ts +6 -8
- package/src/style/load_sprite.test.ts +48 -71
- package/src/style/style.test.ts +19 -49
- package/src/style/style.ts +3 -0
- package/src/style/style_glyph.ts +4 -3
- package/src/style/style_layer/line_style_layer.test.ts +50 -0
- package/src/style/style_layer/line_style_layer.ts +8 -4
- package/src/symbol/quads.ts +4 -2
- package/src/ui/handler/scroll_zoom.ts +6 -0
- package/src/ui/handler_manager.ts +2 -1
- package/src/ui/map.test.ts +17 -0
- package/src/ui/map.ts +1 -0
- package/src/ui/marker.test.ts +25 -0
- package/src/ui/marker.ts +8 -1
- package/src/util/ajax.test.ts +1 -1
- package/src/util/image_request.test.ts +1 -1
- package/src/util/offscreen_canvas_distorted.test.ts +13 -0
- package/src/util/offscreen_canvas_distorted.ts +39 -0
- package/src/util/test/util.ts +12 -0
- package/src/util/util.test.ts +171 -1
- package/src/util/util.ts +150 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {offscreenCanvasSupported} from './offscreen_canvas_supported';
|
|
2
|
+
|
|
3
|
+
let offscreenCanvasDistorted: boolean;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Some browsers don't return the exact pixels from a canvas to prevent user fingerprinting (see #3185).
|
|
7
|
+
* This function writes pixels to an OffscreenCanvas and reads them back using getImageData, returning false
|
|
8
|
+
* if they don't match.
|
|
9
|
+
*
|
|
10
|
+
* @returns true if the browser supports OffscreenCanvas but it distorts getImageData results, false otherwise.
|
|
11
|
+
*/
|
|
12
|
+
export function isOffscreenCanvasDistorted(): boolean {
|
|
13
|
+
if (offscreenCanvasDistorted == null) {
|
|
14
|
+
offscreenCanvasDistorted = false;
|
|
15
|
+
if (offscreenCanvasSupported()) {
|
|
16
|
+
const size = 5;
|
|
17
|
+
const canvas = new OffscreenCanvas(size, size);
|
|
18
|
+
const context = canvas.getContext('2d', {willReadFrequently: true});
|
|
19
|
+
if (context) {
|
|
20
|
+
// fill each pixel with an RGB value that should make the byte at index i equal to i (except alpha channel):
|
|
21
|
+
// [0, 1, 2, 255, 4, 5, 6, 255, 8, 9, 10, 255, ...]
|
|
22
|
+
for (let i = 0; i < size * size; i++) {
|
|
23
|
+
const base = i * 4;
|
|
24
|
+
context.fillStyle = `rgb(${base},${base + 1},${base + 2})`;
|
|
25
|
+
context.fillRect(i % size, Math.floor(i / size), 1, 1);
|
|
26
|
+
}
|
|
27
|
+
const data = context.getImageData(0, 0, size, size).data;
|
|
28
|
+
for (let i = 0; i < size * size * 4; i++) {
|
|
29
|
+
if (i % 4 !== 3 && data[i] !== i) {
|
|
30
|
+
offscreenCanvasDistorted = true;
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return offscreenCanvasDistorted || false;
|
|
39
|
+
}
|
package/src/util/test/util.ts
CHANGED
|
@@ -132,3 +132,15 @@ export function stubAjaxGetImage(createImageBitmap) {
|
|
|
132
132
|
}
|
|
133
133
|
});
|
|
134
134
|
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* This should be used in test that use nise since the internal buffer returned from a file is not an instance of ArrayBuffer for some reason.
|
|
138
|
+
* @param data - the data read from a file, for example by `fs.readFileSync(...)`
|
|
139
|
+
* @returns a copy of the data in the file in `ArrayBuffer` format
|
|
140
|
+
*/
|
|
141
|
+
export function bufferToArrayBuffer(data: Buffer): ArrayBuffer {
|
|
142
|
+
const newBuffer = new ArrayBuffer(data.buffer.byteLength);
|
|
143
|
+
const view = new Uint8Array(newBuffer);
|
|
144
|
+
data.copy(view);
|
|
145
|
+
return view.buffer;
|
|
146
|
+
}
|
package/src/util/util.test.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Point from '@mapbox/point-geometry';
|
|
2
|
-
import {arraysIntersect, asyncAll, bezier, clamp, clone, deepEqual, easeCubicInOut, extend, filterObject, findLineIntersection, isClosedPolygon, isCounterClockwise, isPowerOfTwo, keysDifference, mapObject, nextPowerOfTwo, parseCacheControl, pick, uniqueId, wrap} from './util';
|
|
2
|
+
import {arraysIntersect, asyncAll, bezier, clamp, clone, deepEqual, easeCubicInOut, extend, filterObject, findLineIntersection, isClosedPolygon, isCounterClockwise, isPowerOfTwo, keysDifference, mapObject, nextPowerOfTwo, parseCacheControl, pick, readImageDataUsingOffscreenCanvas, readImageUsingVideoFrame, uniqueId, wrap} from './util';
|
|
3
|
+
import {Canvas} from 'canvas';
|
|
3
4
|
|
|
4
5
|
describe('util', () => {
|
|
5
6
|
expect(easeCubicInOut(0)).toBe(0);
|
|
@@ -346,3 +347,172 @@ describe('util findLineIntersection', () => {
|
|
|
346
347
|
expect(intersection).toBeNull();
|
|
347
348
|
});
|
|
348
349
|
});
|
|
350
|
+
|
|
351
|
+
describe('util readImageUsingVideoFrame', () => {
|
|
352
|
+
let format = 'RGBA';
|
|
353
|
+
const frame = {
|
|
354
|
+
get format() {
|
|
355
|
+
return format;
|
|
356
|
+
},
|
|
357
|
+
copyTo: jest.fn(buf => {
|
|
358
|
+
buf.set(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]).subarray(0, buf.length));
|
|
359
|
+
return Promise.resolve();
|
|
360
|
+
}),
|
|
361
|
+
close: jest.fn(),
|
|
362
|
+
};
|
|
363
|
+
(window as any).VideoFrame = jest.fn(() => frame);
|
|
364
|
+
const canvas = document.createElement('canvas');
|
|
365
|
+
canvas.width = canvas.height = 2;
|
|
366
|
+
|
|
367
|
+
beforeEach(() => {
|
|
368
|
+
format = 'RGBA';
|
|
369
|
+
frame.copyTo.mockClear();
|
|
370
|
+
frame.close.mockReset();
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
test('copy RGB', async () => {
|
|
374
|
+
format = 'RGBA';
|
|
375
|
+
const result = await readImageUsingVideoFrame(canvas, 0, 0, 2, 2);
|
|
376
|
+
expect(result).toHaveLength(4 * 4);
|
|
377
|
+
expect(frame.copyTo).toHaveBeenCalledWith(expect.anything(), {
|
|
378
|
+
layout: [{offset: 0, stride: 8}],
|
|
379
|
+
rect: {x: 0, y: 0, width: 2, height: 2}
|
|
380
|
+
});
|
|
381
|
+
expect(result).toEqual(new Uint8ClampedArray([
|
|
382
|
+
1, 2, 3, 4, 5, 6, 7, 8,
|
|
383
|
+
9, 10, 11, 12, 13, 14, 15, 16
|
|
384
|
+
]));
|
|
385
|
+
expect(frame.close).toHaveBeenCalledTimes(1);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
test('flip BRG', async () => {
|
|
389
|
+
format = 'BGRX';
|
|
390
|
+
const result = await readImageUsingVideoFrame(canvas, 0, 0, 2, 2);
|
|
391
|
+
expect(result).toEqual(new Uint8ClampedArray([
|
|
392
|
+
3, 2, 1, 4, 7, 6, 5, 8,
|
|
393
|
+
11, 10, 9, 12, 15, 14, 13, 16
|
|
394
|
+
]));
|
|
395
|
+
expect(frame.close).toHaveBeenCalledTimes(1);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
test('ignore bad format', async () => {
|
|
399
|
+
format = 'OTHER';
|
|
400
|
+
await expect(readImageUsingVideoFrame(canvas, 0, 0, 2, 2)).rejects.toThrow();
|
|
401
|
+
expect(frame.close).toHaveBeenCalledTimes(1);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
describe('layout/rect', () => {
|
|
405
|
+
beforeEach(() => {
|
|
406
|
+
(window as any).VideoFrame = jest.fn(() => frame);
|
|
407
|
+
canvas.width = canvas.height = 3;
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
test('full rectangle', async () => {
|
|
411
|
+
await readImageUsingVideoFrame(canvas, 0, 0, 3, 3);
|
|
412
|
+
expect(frame.copyTo).toHaveBeenCalledWith(expect.anything(), {
|
|
413
|
+
layout: [{offset: 0, stride: 12}],
|
|
414
|
+
rect: {x: 0, y: 0, width: 3, height: 3}
|
|
415
|
+
});
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
test('top left', async () => {
|
|
419
|
+
await readImageUsingVideoFrame(canvas, 0, 0, 2, 2);
|
|
420
|
+
expect(frame.copyTo).toHaveBeenCalledWith(expect.anything(), {
|
|
421
|
+
layout: [{offset: 0, stride: 8}],
|
|
422
|
+
rect: {x: 0, y: 0, width: 2, height: 2}
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
test('top right', async () => {
|
|
427
|
+
await readImageUsingVideoFrame(canvas, 1, 0, 2, 2);
|
|
428
|
+
expect(frame.copyTo).toHaveBeenCalledWith(expect.anything(), {
|
|
429
|
+
layout: [{offset: 0, stride: 8}],
|
|
430
|
+
rect: {x: 1, y: 0, width: 2, height: 2}
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
test('bottom left', async () => {
|
|
435
|
+
await readImageUsingVideoFrame(canvas, 0, 1, 2, 2);
|
|
436
|
+
expect(frame.copyTo).toHaveBeenCalledWith(expect.anything(), {
|
|
437
|
+
layout: [{offset: 0, stride: 8}],
|
|
438
|
+
rect: {x: 0, y: 1, width: 2, height: 2}
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
test('bottom right', async () => {
|
|
443
|
+
await readImageUsingVideoFrame(canvas, 1, 1, 2, 2);
|
|
444
|
+
expect(frame.copyTo).toHaveBeenCalledWith(expect.anything(), {
|
|
445
|
+
layout: [{offset: 0, stride: 8}],
|
|
446
|
+
rect: {x: 1, y: 1, width: 2, height: 2}
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
test('middle', async () => {
|
|
451
|
+
await readImageUsingVideoFrame(canvas, 1, 1, 1, 1);
|
|
452
|
+
expect(frame.copyTo).toHaveBeenCalledWith(expect.anything(), {
|
|
453
|
+
layout: [{offset: 0, stride: 4}],
|
|
454
|
+
rect: {x: 1, y: 1, width: 1, height: 1}
|
|
455
|
+
});
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
test('extend past on all sides', async () => {
|
|
459
|
+
await readImageUsingVideoFrame(canvas, -1, -1, 5, 5);
|
|
460
|
+
expect(frame.copyTo).toHaveBeenCalledWith(expect.anything(), {
|
|
461
|
+
layout: [{offset: 4 * 5 + 4, stride: 4 * 5}],
|
|
462
|
+
rect: {x: 0, y: 0, width: 3, height: 3}
|
|
463
|
+
});
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
test('overhang top left', async () => {
|
|
467
|
+
await readImageUsingVideoFrame(canvas, -1, -1, 2, 2);
|
|
468
|
+
expect(frame.copyTo).toHaveBeenCalledWith(expect.anything(), {
|
|
469
|
+
layout: [{offset: 4 * 2 + 4, stride: 4 * 2}],
|
|
470
|
+
rect: {x: 0, y: 0, width: 1, height: 1}
|
|
471
|
+
});
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
test('overhang top right', async () => {
|
|
475
|
+
await readImageUsingVideoFrame(canvas, 2, -1, 2, 2);
|
|
476
|
+
expect(frame.copyTo).toHaveBeenCalledWith(expect.anything(), {
|
|
477
|
+
layout: [{offset: 4 * 2, stride: 4 * 2}],
|
|
478
|
+
rect: {x: 2, y: 0, width: 1, height: 1}
|
|
479
|
+
});
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
test('overhang bottom left', async () => {
|
|
483
|
+
await readImageUsingVideoFrame(canvas, -1, 2, 2, 2);
|
|
484
|
+
expect(frame.copyTo).toHaveBeenCalledWith(expect.anything(), {
|
|
485
|
+
layout: [{offset: 4, stride: 4 * 2}],
|
|
486
|
+
rect: {x: 0, y: 2, width: 1, height: 1}
|
|
487
|
+
});
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
test('overhang bottom right', async () => {
|
|
491
|
+
await readImageUsingVideoFrame(canvas, 2, 2, 2, 2);
|
|
492
|
+
expect(frame.copyTo).toHaveBeenCalledWith(expect.anything(), {
|
|
493
|
+
layout: [{offset: 0, stride: 4 * 2}],
|
|
494
|
+
rect: {x: 2, y: 2, width: 1, height: 1}
|
|
495
|
+
});
|
|
496
|
+
});
|
|
497
|
+
});
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
describe('util readImageDataUsingOffscreenCanvas', () => {
|
|
501
|
+
test('reads pixels from image', async () => {
|
|
502
|
+
(window as any).OffscreenCanvas = Canvas;
|
|
503
|
+
const image = new Canvas(2, 2);
|
|
504
|
+
const context = image.getContext('2d');
|
|
505
|
+
context.fillStyle = 'rgb(10,0,0)';
|
|
506
|
+
context.fillRect(0, 0, 1, 1);
|
|
507
|
+
context.fillStyle = 'rgb(0,20,0)';
|
|
508
|
+
context.fillRect(1, 0, 1, 1);
|
|
509
|
+
context.fillStyle = 'rgb(0,0,30)';
|
|
510
|
+
context.fillRect(0, 1, 1, 1);
|
|
511
|
+
context.fillStyle = 'rgb(40,40,40)';
|
|
512
|
+
context.fillRect(1, 1, 1, 1);
|
|
513
|
+
expect([...await readImageDataUsingOffscreenCanvas(image as any, 0, 0, 2, 2)]).toEqual([
|
|
514
|
+
10, 0, 0, 255, 0, 20, 0, 255,
|
|
515
|
+
0, 0, 30, 255, 40, 40, 40, 255,
|
|
516
|
+
]);
|
|
517
|
+
});
|
|
518
|
+
});
|
package/src/util/util.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import Point from '@mapbox/point-geometry';
|
|
2
2
|
import UnitBezier from '@mapbox/unitbezier';
|
|
3
3
|
import type {Callback} from '../types/callback';
|
|
4
|
+
import {isOffscreenCanvasDistorted} from './offscreen_canvas_distorted';
|
|
5
|
+
import type {Size} from './image';
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* Given a value `t` that varies between 0 and 1, return
|
|
@@ -517,3 +519,151 @@ export function arrayBufferToImage(data: ArrayBuffer, callback: (err?: Error | n
|
|
|
517
519
|
const blob: Blob = new Blob([new Uint8Array(data)], {type: 'image/png'});
|
|
518
520
|
img.src = data.byteLength ? URL.createObjectURL(blob) : transparentPngUrl;
|
|
519
521
|
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* Computes the webcodecs VideoFrame API options to select a rectangle out of
|
|
525
|
+
* an image and write it into the destination rectangle.
|
|
526
|
+
*
|
|
527
|
+
* Rect (x/y/width/height) select the overlapping rectangle from the source image
|
|
528
|
+
* and layout (offset/stride) write that overlapping rectangle to the correct place
|
|
529
|
+
* in the destination image.
|
|
530
|
+
*
|
|
531
|
+
* Offset is the byte offset in the dest image that the first pixel appears at
|
|
532
|
+
* and stride is the number of bytes to the start of the next row:
|
|
533
|
+
* ┌───────────┐
|
|
534
|
+
* │ dest │
|
|
535
|
+
* │ ┌───┼───────┐
|
|
536
|
+
* │offset→│▓▓▓│ source│
|
|
537
|
+
* │ │▓▓▓│ │
|
|
538
|
+
* │ └───┼───────┘
|
|
539
|
+
* │stride ⇠╌╌╌│
|
|
540
|
+
* │╌╌╌╌╌╌→ │
|
|
541
|
+
* └───────────┘
|
|
542
|
+
*
|
|
543
|
+
* @param image - source image containing a width and height attribute
|
|
544
|
+
* @param x - top-left x coordinate to read from the image
|
|
545
|
+
* @param y - top-left y coordinate to read from the image
|
|
546
|
+
* @param width - width of the rectangle to read from the image
|
|
547
|
+
* @param height - height of the rectangle to read from the image
|
|
548
|
+
* @returns the layout and rect options to pass into VideoFrame API
|
|
549
|
+
*/
|
|
550
|
+
function computeVideoFrameParameters(image: Size, x: number, y: number, width: number, height: number): VideoFrameCopyToOptions {
|
|
551
|
+
const destRowOffset = Math.max(-x, 0) * 4;
|
|
552
|
+
const firstSourceRow = Math.max(0, y);
|
|
553
|
+
const firstDestRow = firstSourceRow - y;
|
|
554
|
+
const offset = firstDestRow * width * 4 + destRowOffset;
|
|
555
|
+
const stride = width * 4;
|
|
556
|
+
|
|
557
|
+
const sourceLeft = Math.max(0, x);
|
|
558
|
+
const sourceTop = Math.max(0, y);
|
|
559
|
+
const sourceRight = Math.min(image.width, x + width);
|
|
560
|
+
const sourceBottom = Math.min(image.height, y + height);
|
|
561
|
+
return {
|
|
562
|
+
rect: {
|
|
563
|
+
x: sourceLeft,
|
|
564
|
+
y: sourceTop,
|
|
565
|
+
width: sourceRight - sourceLeft,
|
|
566
|
+
height: sourceBottom - sourceTop
|
|
567
|
+
},
|
|
568
|
+
layout: [{offset, stride}]
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Reads pixels from an ImageBitmap/Image/canvas using webcodec VideoFrame API.
|
|
574
|
+
*
|
|
575
|
+
* @param data - image, imagebitmap, or canvas to parse
|
|
576
|
+
* @param x - top-left x coordinate to read from the image
|
|
577
|
+
* @param y - top-left y coordinate to read from the image
|
|
578
|
+
* @param width - width of the rectangle to read from the image
|
|
579
|
+
* @param height - height of the rectangle to read from the image
|
|
580
|
+
* @returns a promise containing the parsed RGBA pixel values of the image, or the error if an error occurred
|
|
581
|
+
*/
|
|
582
|
+
export async function readImageUsingVideoFrame(
|
|
583
|
+
image: HTMLImageElement | HTMLCanvasElement | ImageBitmap | OffscreenCanvas,
|
|
584
|
+
x: number, y: number, width: number, height: number
|
|
585
|
+
): Promise<Uint8ClampedArray> {
|
|
586
|
+
if (typeof VideoFrame === 'undefined') {
|
|
587
|
+
throw new Error('VideoFrame not supported');
|
|
588
|
+
}
|
|
589
|
+
const frame = new VideoFrame(image, {timestamp: 0});
|
|
590
|
+
try {
|
|
591
|
+
const format = frame?.format;
|
|
592
|
+
if (!format || !(format.startsWith('BGR') || format.startsWith('RGB'))) {
|
|
593
|
+
throw new Error(`Unrecognized format ${format}`);
|
|
594
|
+
}
|
|
595
|
+
const swapBR = format.startsWith('BGR');
|
|
596
|
+
const result = new Uint8ClampedArray(width * height * 4);
|
|
597
|
+
await frame.copyTo(result, computeVideoFrameParameters(image, x, y, width, height));
|
|
598
|
+
if (swapBR) {
|
|
599
|
+
for (let i = 0; i < result.length; i += 4) {
|
|
600
|
+
const tmp = result[i];
|
|
601
|
+
result[i] = result[i + 2];
|
|
602
|
+
result[i + 2] = tmp;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
return result;
|
|
606
|
+
} finally {
|
|
607
|
+
frame.close();
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
let offscreenCanvas: OffscreenCanvas;
|
|
612
|
+
let offscreenCanvasContext: OffscreenCanvasRenderingContext2D;
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* Reads pixels from an ImageBitmap/Image/canvas using OffscreenCanvas
|
|
616
|
+
*
|
|
617
|
+
* @param data - image, imagebitmap, or canvas to parse
|
|
618
|
+
* @param x - top-left x coordinate to read from the image
|
|
619
|
+
* @param y - top-left y coordinate to read from the image
|
|
620
|
+
* @param width - width of the rectangle to read from the image
|
|
621
|
+
* @param height - height of the rectangle to read from the image
|
|
622
|
+
* @returns a promise containing the parsed RGBA pixel values of the image, or the error if an error occurred
|
|
623
|
+
*/
|
|
624
|
+
export function readImageDataUsingOffscreenCanvas(
|
|
625
|
+
imgBitmap: HTMLImageElement | HTMLCanvasElement | ImageBitmap | OffscreenCanvas,
|
|
626
|
+
x: number, y: number, width: number, height: number
|
|
627
|
+
): Uint8ClampedArray {
|
|
628
|
+
const origWidth = imgBitmap.width;
|
|
629
|
+
const origHeight = imgBitmap.height;
|
|
630
|
+
// Lazily initialize OffscreenCanvas
|
|
631
|
+
if (!offscreenCanvas || !offscreenCanvasContext) {
|
|
632
|
+
// Dem tiles are typically 256x256
|
|
633
|
+
offscreenCanvas = new OffscreenCanvas(origWidth, origHeight);
|
|
634
|
+
offscreenCanvasContext = offscreenCanvas.getContext('2d', {willReadFrequently: true});
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
offscreenCanvas.width = origWidth;
|
|
638
|
+
offscreenCanvas.height = origHeight;
|
|
639
|
+
|
|
640
|
+
offscreenCanvasContext.drawImage(imgBitmap, 0, 0, origWidth, origHeight);
|
|
641
|
+
const imgData = offscreenCanvasContext.getImageData(x, y, width, height);
|
|
642
|
+
offscreenCanvasContext.clearRect(0, 0, origWidth, origHeight);
|
|
643
|
+
return imgData.data;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* Reads RGBA pixels from an preferring OffscreenCanvas, but falling back to VideoFrame if supported and
|
|
648
|
+
* the browser is mangling OffscreenCanvas getImageData results.
|
|
649
|
+
*
|
|
650
|
+
* @param data - image, imagebitmap, or canvas to parse
|
|
651
|
+
* @param x - top-left x coordinate to read from the image
|
|
652
|
+
* @param y - top-left y coordinate to read from the image
|
|
653
|
+
* @param width - width of the rectangle to read from the image
|
|
654
|
+
* @param height - height of the rectangle to read from the image
|
|
655
|
+
* @returns a promise containing the parsed RGBA pixel values of the image
|
|
656
|
+
*/
|
|
657
|
+
export async function getImageData(
|
|
658
|
+
image: HTMLImageElement | HTMLCanvasElement | ImageBitmap | OffscreenCanvas,
|
|
659
|
+
x: number, y: number, width: number, height: number
|
|
660
|
+
): Promise<Uint8ClampedArray> {
|
|
661
|
+
if (isOffscreenCanvasDistorted()) {
|
|
662
|
+
try {
|
|
663
|
+
return await readImageUsingVideoFrame(image, x, y, width, height);
|
|
664
|
+
} catch (e) {
|
|
665
|
+
// fall back to OffscreenCanvas
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
return readImageDataUsingOffscreenCanvas(image, x, y, width, height);
|
|
669
|
+
}
|