diff --git a/src/render/image_atlas.ts b/src/render/image_atlas.ts index a4dc7892824..df2c29e0d94 100644 --- a/src/render/image_atlas.ts +++ b/src/render/image_atlas.ts @@ -1,8 +1,8 @@ import assert from 'assert'; import {RGBAImage} from '../util/image'; import {register} from '../util/web_worker_transfer'; -import ResolvedImage from '../style-spec/expression/types/resolved_image'; import potpack from 'potpack'; +import {ImageIdWithOptions} from '../style-spec/expression/types/image_id_with_options'; import type {StyleImage} from '../style/style_image'; import type ImageManager from './image_manager'; @@ -153,7 +153,7 @@ export default class ImageAtlas { positions[id] = new ImagePosition(bin, src, padding); if (src.hasRenderCallback) { - this.haveRenderCallbacks.push(id); + this.haveRenderCallbacks.push(ImageIdWithOptions.deserializeId(id)); } } } @@ -161,10 +161,19 @@ export default class ImageAtlas { patchUpdatedImages(imageManager: ImageManager, texture: Texture, scope: string) { this.haveRenderCallbacks = this.haveRenderCallbacks.filter(id => imageManager.hasImage(id, scope)); imageManager.dispatchRenderCallbacks(this.haveRenderCallbacks, scope); + for (const name in imageManager.getUpdatedImages(scope)) { - const imageKey = ResolvedImage.build(name).getSerializedPrimary(); - this.patchUpdatedImage(this.iconPositions[imageKey], imageManager.getImage(name, scope), texture); - this.patchUpdatedImage(this.patternPositions[imageKey], imageManager.getImage(name, scope), texture); + for (const id of Object.keys(this.iconPositions)) { + if (ImageIdWithOptions.deserializeId(id) === name) { + this.patchUpdatedImage(this.iconPositions[id], imageManager.getImage(name, scope), texture); + } + } + + for (const id of Object.keys(this.patternPositions)) { + if (ImageIdWithOptions.deserializeId(id) === name) { + this.patchUpdatedImage(this.patternPositions[id], imageManager.getImage(name, scope), texture); + } + } } } diff --git a/src/style-spec/expression/types/image_id_with_options.ts b/src/style-spec/expression/types/image_id_with_options.ts index 8f1bd9f6848..610194cd6bf 100644 --- a/src/style-spec/expression/types/image_id_with_options.ts +++ b/src/style-spec/expression/types/image_id_with_options.ts @@ -16,6 +16,10 @@ export class ImageIdWithOptions { } } + static deserializeId(serialized: string): string { + return JSON.parse(serialized).id; + } + static deserializeFromString(serialized: string): ImageIdWithOptions { const deserializedObject = JSON.parse(serialized); const options: RasterizationOptions = {params: deserializedObject.options.params}; diff --git a/src/symbol/symbol_layout.ts b/src/symbol/symbol_layout.ts index ad7ba970fed..e928f1032c2 100644 --- a/src/symbol/symbol_layout.ts +++ b/src/symbol/symbol_layout.ts @@ -344,7 +344,7 @@ export function performSymbolLayout(bucket: SymbolBucket, let isUSVGIcon = false; if (feature.icon && feature.icon.namePrimary) { const iconSizeFactor = getRasterizedIconSize(bucket.iconSizeData, unevaluatedLayoutValues['icon-size'], canonical, bucket.zoom, feature); - const scaleFactor = iconSizeFactor * sizes.iconScaleFactor * pixelRatio; + const scaleFactor = iconSizeFactor * sizes.iconScaleFactor * pixelRatio; const primaryImageSerialized = feature.icon.getPrimary().scaleSelf(scaleFactor).serialize(); const image = imageMap[primaryImageSerialized]; if (image) { diff --git a/test/integration/image/dot.js b/test/integration/image/dot.js new file mode 100644 index 00000000000..c703a612422 --- /dev/null +++ b/test/integration/image/dot.js @@ -0,0 +1,59 @@ +const size = 200; + +export const image = { + width: size, + height: size, + data: new Uint8Array(size * size * 4), + + onAdd: function () { + const canvas = document.createElement('canvas'); + canvas.width = this.width; + canvas.height = this.height; + this.context = canvas.getContext('2d'); + }, + + render: function () { + const radius = (size / 2) * 0.3; + const outerRadius = (size / 2) * 0.7 + radius; + const context = this.context; + + // Draw the outer circle. + context.clearRect(0, 0, this.width, this.height); + context.beginPath(); + context.arc( + this.width / 2, + this.height / 2, + outerRadius, + 0, + Math.PI * 2 + ); + context.fillStyle = `rgba(255, 200, 200, 1)`; + context.fill(); + + // Draw the inner circle. + context.beginPath(); + context.arc( + this.width / 2, + this.height / 2, + radius, + 0, + Math.PI * 2 + ); + context.fillStyle = 'rgba(255, 100, 100, 1)'; + context.strokeStyle = 'white'; + context.lineWidth = 2 + 4; + context.fill(); + context.stroke(); + + // Update this image's data with data from the canvas. + this.data = context.getImageData( + 0, + 0, + this.width, + this.height + ).data; + + // Return `true` to let the map know that the image was updated. + return true; + } +} diff --git a/test/integration/lib/operation-handlers.js b/test/integration/lib/operation-handlers.js index 38e7e359929..09afa851f2a 100644 --- a/test/integration/lib/operation-handlers.js +++ b/test/integration/lib/operation-handlers.js @@ -51,6 +51,14 @@ export const operationHandlers = { setTimeout(doneCb, params[0]); }, addImage(map, params, doneCb) { + if (params[1].endsWith('.js')) { + import(params[1].replace('./', '../')).then(({image}) => { + map.addImage(params[0], image, params[2] || {}); + doneCb(); + }); + return; + } + const image = new Image(); image.onload = () => { map.addImage(params[0], image, params[2] || {}); diff --git a/test/integration/render-tests/image/render-callback/expected.png b/test/integration/render-tests/image/render-callback/expected.png new file mode 100644 index 00000000000..658e7c79873 Binary files /dev/null and b/test/integration/render-tests/image/render-callback/expected.png differ diff --git a/test/integration/render-tests/image/render-callback/style.json b/test/integration/render-tests/image/render-callback/style.json new file mode 100644 index 00000000000..d0e094ff21e --- /dev/null +++ b/test/integration/render-tests/image/render-callback/style.json @@ -0,0 +1,49 @@ +{ + "version": 8, + "metadata": { + "test": { + "width": 200, + "height": 200, + "operations": [ + [ + "addImage", + "dot", + "./image/dot.js" + ], + [ + "wait" + ] + ] + } + }, + "sources": { + "dot-point": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [ + 0, + 0 + ] + } + } + ] + } + } + }, + "layers": [ + { + "id": "layer-with-dot", + "type": "symbol", + "source": "dot-point", + "layout": { + "icon-image": "dot" + } + } + ] +}