1 | import {RGBAImage} from '../util/image';
|
2 |
|
3 | import type Map from '../ui/map';
|
4 |
|
5 | export type StyleImageData = {
|
6 | data: RGBAImage;
|
7 | version: number;
|
8 | hasRenderCallback?: boolean;
|
9 | userImage?: StyleImageInterface;
|
10 | };
|
11 |
|
12 | export type StyleImageMetadata = {
|
13 | pixelRatio: number;
|
14 | sdf: boolean;
|
15 | stretchX?: Array<[number, number]>;
|
16 | stretchY?: Array<[number, number]>;
|
17 | content?: [number, number, number, number];
|
18 | };
|
19 |
|
20 | export type StyleImage = StyleImageData & StyleImageMetadata;
|
21 |
|
22 | /**
|
23 | * Interface for dynamically generated style images. This is a specification for
|
24 | * implementers to model: it is not an exported method or class.
|
25 | *
|
26 | * Images implementing this interface can be redrawn for every frame. They can be used to animate
|
27 | * icons and patterns or make them respond to user input. Style images can implement a
|
28 | * {@link StyleImageInterface#render} method. The method is called every frame and
|
29 | * can be used to update the image.
|
30 | *
|
31 | * @interface StyleImageInterface
|
32 | * @see [Add an animated icon to the map.](https://maplibre.org/maplibre-gl-js-docs/example/add-image-animated/)
|
33 | *
|
34 | * @example
|
35 | * var flashingSquare = {
|
36 | * width: 64,
|
37 | * height: 64,
|
38 | * data: new Uint8Array(64 * 64 * 4),
|
39 | *
|
40 | * onAdd: function(map) {
|
41 | * this.map = map;
|
42 | * },
|
43 | *
|
44 | * render: function() {
|
45 | * // keep repainting while the icon is on the map
|
46 | * this.map.triggerRepaint();
|
47 | *
|
48 | * // alternate between black and white based on the time
|
49 | * var value = Math.round(Date.now() / 1000) % 2 === 0 ? 255 : 0;
|
50 | *
|
51 | * // check if image needs to be changed
|
52 | * if (value !== this.previousValue) {
|
53 | * this.previousValue = value;
|
54 | *
|
55 | * var bytesPerPixel = 4;
|
56 | * for (var x = 0; x < this.width; x++) {
|
57 | * for (var y = 0; y < this.height; y++) {
|
58 | * var offset = (y * this.width + x) * bytesPerPixel;
|
59 | * this.data[offset + 0] = value;
|
60 | * this.data[offset + 1] = value;
|
61 | * this.data[offset + 2] = value;
|
62 | * this.data[offset + 3] = 255;
|
63 | * }
|
64 | * }
|
65 | *
|
66 | * // return true to indicate that the image changed
|
67 | * return true;
|
68 | * }
|
69 | * }
|
70 | * }
|
71 | *
|
72 | * map.addImage('flashing_square', flashingSquare);
|
73 | */
|
74 |
|
75 | export interface StyleImageInterface {
|
76 | /**
|
77 | * @property {number} width
|
78 | */
|
79 | width: number;
|
80 | /**
|
81 | * @property {number} height
|
82 | */
|
83 | height: number;
|
84 | /**
|
85 | * @property {Uint8Array | Uint8ClampedArray} data
|
86 | */
|
87 | data: Uint8Array | Uint8ClampedArray;
|
88 | /**
|
89 | * This method is called once before every frame where the icon will be used.
|
90 | * The method can optionally update the image's `data` member with a new image.
|
91 | *
|
92 | * If the method updates the image it must return `true` to commit the change.
|
93 | * If the method returns `false` or nothing the image is assumed to not have changed.
|
94 | *
|
95 | * If updates are infrequent it maybe easier to use {@link Map#updateImage} to update
|
96 | * the image instead of implementing this method.
|
97 | *
|
98 | * @function
|
99 | * @memberof StyleImageInterface
|
100 | * @instance
|
101 | * @name render
|
102 | * @return {boolean} `true` if this method updated the image. `false` if the image was not changed.
|
103 | */
|
104 | render?: () => boolean;
|
105 | /**
|
106 | * Optional method called when the layer has been added to the Map with {@link Map#addImage}.
|
107 | *
|
108 | * @function
|
109 | * @memberof StyleImageInterface
|
110 | * @instance
|
111 | * @name onAdd
|
112 | * @param {Map} map The Map this custom layer was just added to.
|
113 | */
|
114 | onAdd?: (map: Map, id: string) => void;
|
115 | /**
|
116 | * Optional method called when the icon is removed from the map with {@link Map#removeImage}.
|
117 | * This gives the image a chance to clean up resources and event listeners.
|
118 | *
|
119 | * @function
|
120 | * @memberof StyleImageInterface
|
121 | * @instance
|
122 | * @name onRemove
|
123 | */
|
124 | onRemove?: () => void;
|
125 | }
|
126 |
|
127 | export function renderStyleImage(image: StyleImage) {
|
128 | const {userImage} = image;
|
129 | if (userImage && userImage.render) {
|
130 | const updated = userImage.render();
|
131 | if (updated) {
|
132 | image.data.replace(new Uint8Array(userImage.data.buffer));
|
133 | return true;
|
134 | }
|
135 | }
|
136 | return false;
|
137 | }
|