maplibre-gl 3.4.0 → 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/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
+ }