From 6a7126fdb4435eb119ec20dd6e0d3897eebfb895 Mon Sep 17 00:00:00 2001 From: Daniel Eke Date: Tue, 28 Mar 2023 09:04:06 +0300 Subject: [PATCH] Backport icon-image-cross-fade property and icon variant support (#505) * Add icon-image-cross-fade into style spec * Parse secondary image variant name in image expressions * Load and render secondary image variants * Update test configuration * Update examples * Name primary image variant explicitly --- build/generate-struct-arrays.js | 2 + debug/bucket-stats.html | 3 +- debug/measure-light.html | 53 ++++- src/data/array_types.js | 32 +++ src/data/bucket/symbol_attributes.js | 4 + src/data/bucket/symbol_bucket.js | 55 ++++-- src/render/draw_symbol.js | 8 +- src/render/program/symbol_program.js | 15 +- .../expression/definitions/image.js | 48 +++-- src/style-spec/expression/types/formatted.js | 4 +- .../expression/types/resolved_image.js | 27 ++- src/style-spec/reference/v8.json | 25 +++ src/style-spec/types.js | 1 + .../symbol_style_layer_properties.js | 2 + src/symbol/projection.js | 4 +- src/symbol/quads.js | 32 ++- src/symbol/shaping.js | 21 +- src/symbol/symbol_layout.js | 34 +++- test/ignores/all.js | 5 - .../format/image-sections/test.json | 4 +- .../expression-tests/image/basic/test.json | 4 +- .../expression-tests/image/coalesce/test.json | 4 +- .../expression-tests/image/compound/test.json | 2 +- .../expected.png | Bin 0 -> 3393 bytes .../style.json | 184 ++++++++++++++++++ .../style.json | 94 +++++---- .../style.json | 88 ++++----- .../style.json | 88 ++++----- test/unit/symbol/quads.test.js | 27 +-- test/unit/symbol/shaping.test.js | 76 +++++--- 30 files changed, 668 insertions(+), 278 deletions(-) create mode 100644 test/integration/render-tests/measure-light/global-brightness-icon-image-data-driven-mixed/expected.png create mode 100644 test/integration/render-tests/measure-light/global-brightness-icon-image-data-driven-mixed/style.json diff --git a/build/generate-struct-arrays.js b/build/generate-struct-arrays.js index 7379a35ef48..c168d03f7cd 100644 --- a/build/generate-struct-arrays.js +++ b/build/generate-struct-arrays.js @@ -158,6 +158,7 @@ import { symbolGlobeExtAttributes, dynamicLayoutAttributes, placementOpacityAttributes, + iconTransitioningAttributes, collisionBox, collisionBoxLayout, collisionCircleLayout, @@ -174,6 +175,7 @@ createStructArrayType(`symbol_layout`, symbolLayoutAttributes); createStructArrayType(`symbol_globe_ext`, symbolGlobeExtAttributes); createStructArrayType(`symbol_dynamic_layout`, dynamicLayoutAttributes); createStructArrayType(`symbol_opacity`, placementOpacityAttributes); +createStructArrayType(`symbol_icon_transitioning`, iconTransitioningAttributes); createStructArrayType('collision_box', collisionBox, true); createStructArrayType(`collision_box_layout`, collisionBoxLayout); createStructArrayType(`collision_circle_layout`, collisionCircleLayout); diff --git a/debug/bucket-stats.html b/debug/bucket-stats.html index 6b769505893..5bc439c44a1 100644 --- a/debug/bucket-stats.html +++ b/debug/bucket-stats.html @@ -80,7 +80,8 @@ bucket.dynamicLayoutVertexArray.arrayBuffer.byteLength + bucket.layoutVertexArray.arrayBuffer.byteLength + bucket.opacityVertexArray.arrayBuffer.byteLength + - bucket.placedSymbolArray.arrayBuffer.byteLength; + bucket.placedSymbolArray.arrayBuffer.byteLength + + bucket.iconTransitioningVertexArray.arrayBuffer.byteLength; } const statsEl = document.getElementById('stats'); diff --git a/debug/measure-light.html b/debug/measure-light.html index 108c6ab69ef..29ddba35d7b 100644 --- a/debug/measure-light.html +++ b/debug/measure-light.html @@ -92,27 +92,27 @@ updateLights(value); }); var directionalLightAltitude = lights.add(demo3d, 'directionalLightAltitude', 0.0, 90.0); - directionalLightAltitude.onFinishChange((value) => { + directionalLightAltitude.onChange((value) => { updateLights(demo3d.enable3dLights); }); var directionalLightAzimuth = lights.add(demo3d, 'directionalLightAzimuth', 0.0, 360.0); - directionalLightAzimuth.onFinishChange((value) => { + directionalLightAzimuth.onChange((value) => { updateLights(demo3d.enable3dLights); }); var directionalLightIntensity = lights.add(demo3d, 'directionalLightIntensity', 0.0, 1.0); - directionalLightIntensity.onFinishChange((value) => { + directionalLightIntensity.onChange((value) => { updateLights(demo3d.enable3dLights); }); var directionalLightColor = lights.addColor(demo3d, 'directionalLightColor'); - directionalLightColor.onFinishChange((value) => { + directionalLightColor.onChange((value) => { updateLights(demo3d.enable3dLights); }); var ambientLightIntensity = lights.add(demo3d, 'ambientLightIntensity', 0.0, 1.0); - ambientLightIntensity.onFinishChange((value) => { + ambientLightIntensity.onChange((value) => { updateLights(demo3d.enable3dLights); }); var ambientLightColor = lights.addColor(demo3d, 'ambientLightColor'); - ambientLightColor.onFinishChange((value) => { + ambientLightColor.onChange((value) => { updateLights(demo3d.enable3dLights); }); @@ -297,6 +297,47 @@ ] } }); + map.addLayer({ + "id": "icon-variant-label-demo", + "layout": { + "icon-image": [ + "case", + [">", ["get", "sizerank"], 14], + [ + "image", + "rectangle-yellow-2", + "rectangle-red-2" + ], + "rectangle-green-2" + ], + "text-field": [ + "get", + "sizerank" + ], + "text-size": 8 + }, + "paint": { + "icon-image-cross-fade": [ + "interpolate", + ["linear"], + ["measure-light", "brightness"], + 0.45, + 0.0, + 0.5, + 1.0 + ], + "text-color": [ + "case", + ["<", + ["measure-light", "brightness"], 0.5], + "black", + "white" + ] + }, + "source": "composite", + "source-layer": "poi_label", + "type": "symbol" + }); }); }); diff --git a/src/data/array_types.js b/src/data/array_types.js index 8a027ca72c7..64d894bbbd9 100644 --- a/src/data/array_types.js +++ b/src/data/array_types.js @@ -408,6 +408,36 @@ class StructArrayLayout1ul4 extends StructArray { StructArrayLayout1ul4.prototype.bytesPerElement = 4; register(StructArrayLayout1ul4, 'StructArrayLayout1ul4'); +/** + * Implementation of the StructArray layout: + * [0]: Uint8[2] + * + * @private + */ +class StructArrayLayout2ub2 extends StructArray { + uint8: Uint8Array; + + _refreshViews() { + this.uint8 = new Uint8Array(this.arrayBuffer); + } + + emplaceBack(v0: number, v1: number): number { + const i = this.length; + this.resize(i + 1); + return this.emplace(i, v0, v1); + } + + emplace(i: number, v0: number, v1: number): number { + const o1 = i * 2; + this.uint8[o1 + 0] = v0; + this.uint8[o1 + 1] = v1; + return i; + } +} + +StructArrayLayout2ub2.prototype.bytesPerElement = 2; +register(StructArrayLayout2ub2, 'StructArrayLayout2ub2'); + /** * Implementation of the StructArray layout: * [0]: Int16[5] @@ -1287,6 +1317,7 @@ export { StructArrayLayout4i4ui4i24, StructArrayLayout3i3f20, StructArrayLayout1ul4, + StructArrayLayout2ub2, StructArrayLayout5i4f1i1ul2ui40, StructArrayLayout3i2i2i16, StructArrayLayout2f1f2i16, @@ -1318,6 +1349,7 @@ export { StructArrayLayout3i3f20 as SymbolGlobeExtArray, StructArrayLayout4f16 as SymbolDynamicLayoutArray, StructArrayLayout1ul4 as SymbolOpacityArray, + StructArrayLayout2ub2 as SymbolIconTransitioningArray, StructArrayLayout3i2i2i16 as CollisionBoxLayoutArray, StructArrayLayout2f1f2i16 as CollisionCircleLayoutArray, StructArrayLayout2ub2f12 as CollisionVertexArray, diff --git a/src/data/bucket/symbol_attributes.js b/src/data/bucket/symbol_attributes.js index 6af63bb8fe5..20e7b43f497 100644 --- a/src/data/bucket/symbol_attributes.js +++ b/src/data/bucket/symbol_attributes.js @@ -22,6 +22,10 @@ export const placementOpacityAttributes: StructArrayLayout = createLayout([ {name: 'a_fade_opacity', components: 1, type: 'Uint32'} ], 4); +export const iconTransitioningAttributes: StructArrayLayout = createLayout([ + {name: 'a_texb', components: 2, type: 'Uint8'} +]); + export const collisionVertexAttributes: StructArrayLayout = createLayout([ {name: 'a_placed', components: 2, type: 'Uint8'}, {name: 'a_shift', components: 2, type: 'Float32'}, diff --git a/src/data/bucket/symbol_bucket.js b/src/data/bucket/symbol_bucket.js index 7cb4a3f199c..17def399ab9 100644 --- a/src/data/bucket/symbol_bucket.js +++ b/src/data/bucket/symbol_bucket.js @@ -5,7 +5,8 @@ import {symbolLayoutAttributes, collisionVertexAttributes, collisionVertexAttributesExt, collisionBoxLayout, - dynamicLayoutAttributes + dynamicLayoutAttributes, + iconTransitioningAttributes } from './symbol_attributes.js'; import {SymbolLayoutArray, @@ -18,7 +19,8 @@ import {SymbolLayoutArray, PlacedSymbolArray, SymbolInstanceArray, GlyphOffsetArray, - SymbolLineVertexArray + SymbolLineVertexArray, + SymbolIconTransitioningArray } from '../array_types.js'; import ONE_EM from '../../symbol/one_em.js'; @@ -155,6 +157,10 @@ function addVertex(array, tileAnchorX, tileAnchorY, ox, oy, tx, ty, sizeVertex, ); } +function addTransitioningVertex(array, tx, ty) { + array.emplaceBack(tx, ty); +} + function addGlobeVertex(array, projAnchorX, projAnchorY, projAnchorZ, normX, normY, normZ) { array.emplaceBack( // a_globe_anchor @@ -209,8 +215,11 @@ export class SymbolBuffers { opacityVertexArray: SymbolOpacityArray; opacityVertexBuffer: VertexBuffer; + iconTransitioningVertexArray: SymbolIconTransitioningArray; + iconTransitioningVertexBuffer: ?VertexBuffer; + globeExtVertexArray: SymbolGlobeExtArray; - globeExtVertexBuffer: VertexBuffer; + globeExtVertexBuffer: ?VertexBuffer; placedSymbolArray: PlacedSymbolArray; @@ -222,6 +231,7 @@ export class SymbolBuffers { this.dynamicLayoutVertexArray = new SymbolDynamicLayoutArray(); this.opacityVertexArray = new SymbolOpacityArray(); this.placedSymbolArray = new PlacedSymbolArray(); + this.iconTransitioningVertexArray = new SymbolIconTransitioningArray(); this.globeExtVertexArray = new SymbolGlobeExtArray(); } @@ -229,7 +239,8 @@ export class SymbolBuffers { return this.layoutVertexArray.length === 0 && this.indexArray.length === 0 && this.dynamicLayoutVertexArray.length === 0 && - this.opacityVertexArray.length === 0; + this.opacityVertexArray.length === 0 && + this.iconTransitioningVertexArray.length === 0; } upload(context: Context, dynamicIndexBuffer: boolean, upload?: boolean, update?: boolean) { @@ -242,6 +253,9 @@ export class SymbolBuffers { this.indexBuffer = context.createIndexBuffer(this.indexArray, dynamicIndexBuffer); this.dynamicLayoutVertexBuffer = context.createVertexBuffer(this.dynamicLayoutVertexArray, dynamicLayoutAttributes.members, true); this.opacityVertexBuffer = context.createVertexBuffer(this.opacityVertexArray, shaderOpacityAttributes, true); + if (this.iconTransitioningVertexArray.length > 0) { + this.iconTransitioningVertexBuffer = context.createVertexBuffer(this.iconTransitioningVertexArray, iconTransitioningAttributes.members, true); + } if (this.globeExtVertexArray.length > 0) { this.globeExtVertexBuffer = context.createVertexBuffer(this.globeExtVertexArray, symbolGlobeExtAttributes.members, true); } @@ -262,6 +276,9 @@ export class SymbolBuffers { this.segments.destroy(); this.dynamicLayoutVertexBuffer.destroy(); this.opacityVertexBuffer.destroy(); + if (this.iconTransitioningVertexBuffer) { + this.iconTransitioningVertexBuffer.destroy(); + } if (this.globeExtVertexBuffer) { this.globeExtVertexBuffer.destroy(); } @@ -584,7 +601,10 @@ class SymbolBucket implements Bucket { this.features.push(symbolFeature); if (icon) { - icons[icon.name] = true; + icons[icon.namePrimary] = true; + if (icon.nameSecondary) { + icons[icon.nameSecondary] = true; + } } if (text) { @@ -599,7 +619,7 @@ class SymbolBucket implements Bucket { this.calculateGlyphDependencies(section.text, sectionStack, textAlongLine, this.allowVerticalPlacement, doesAllowVerticalWritingMode); } else { // Add section image to the list of dependencies. - icons[section.image.name] = true; + icons[section.image.namePrimary] = true; } } } @@ -693,7 +713,8 @@ class SymbolBucket implements Bucket { associatedIconIndex: number, availableImages: Array, canonical: CanonicalTileID, - brightness: ?number) { + brightness: ?number, + hasAnySecondaryIcon: boolean) { const indexArray = arrays.indexArray; const layoutVertexArray = arrays.layoutVertexArray; const globeExtVertexArray = arrays.globeExtVertexArray; @@ -707,14 +728,14 @@ class SymbolBucket implements Bucket { const sections = feature.text && feature.text.sections; for (let i = 0; i < quads.length; i++) { - const {tl, tr, bl, br, tex, pixelOffsetTL, pixelOffsetBR, minFontScaleX, minFontScaleY, glyphOffset, isSDF, sectionIndex} = quads[i]; + const {tl, tr, bl, br, texPrimary, texSecondary, pixelOffsetTL, pixelOffsetBR, minFontScaleX, minFontScaleY, glyphOffset, isSDF, sectionIndex} = quads[i]; const index = segment.vertexLength; const y = glyphOffset[1]; - addVertex(layoutVertexArray, tileAnchor.x, tileAnchor.y, tl.x, y + tl.y, tex.x, tex.y, sizeVertex, isSDF, pixelOffsetTL.x, pixelOffsetTL.y, minFontScaleX, minFontScaleY); - addVertex(layoutVertexArray, tileAnchor.x, tileAnchor.y, tr.x, y + tr.y, tex.x + tex.w, tex.y, sizeVertex, isSDF, pixelOffsetBR.x, pixelOffsetTL.y, minFontScaleX, minFontScaleY); - addVertex(layoutVertexArray, tileAnchor.x, tileAnchor.y, bl.x, y + bl.y, tex.x, tex.y + tex.h, sizeVertex, isSDF, pixelOffsetTL.x, pixelOffsetBR.y, minFontScaleX, minFontScaleY); - addVertex(layoutVertexArray, tileAnchor.x, tileAnchor.y, br.x, y + br.y, tex.x + tex.w, tex.y + tex.h, sizeVertex, isSDF, pixelOffsetBR.x, pixelOffsetBR.y, minFontScaleX, minFontScaleY); + addVertex(layoutVertexArray, tileAnchor.x, tileAnchor.y, tl.x, y + tl.y, texPrimary.x, texPrimary.y, sizeVertex, isSDF, pixelOffsetTL.x, pixelOffsetTL.y, minFontScaleX, minFontScaleY); + addVertex(layoutVertexArray, tileAnchor.x, tileAnchor.y, tr.x, y + tr.y, texPrimary.x + texPrimary.w, texPrimary.y, sizeVertex, isSDF, pixelOffsetBR.x, pixelOffsetTL.y, minFontScaleX, minFontScaleY); + addVertex(layoutVertexArray, tileAnchor.x, tileAnchor.y, bl.x, y + bl.y, texPrimary.x, texPrimary.y + texPrimary.h, sizeVertex, isSDF, pixelOffsetTL.x, pixelOffsetBR.y, minFontScaleX, minFontScaleY); + addVertex(layoutVertexArray, tileAnchor.x, tileAnchor.y, br.x, y + br.y, texPrimary.x + texPrimary.w, texPrimary.y + texPrimary.h, sizeVertex, isSDF, pixelOffsetBR.x, pixelOffsetBR.y, minFontScaleX, minFontScaleY); if (globe) { const {x, y, z} = globe.anchor; @@ -729,6 +750,16 @@ class SymbolBucket implements Bucket { addDynamicAttributes(arrays.dynamicLayoutVertexArray, tileAnchor.x, tileAnchor.y, tileAnchor.z, angle); } + // For data-driven cases if at least of one the icon has a transitionable variant + // we have to load the main variant in cases where the secondary image is not specified + if (hasAnySecondaryIcon) { + const tex = texSecondary ? texSecondary : texPrimary; + addTransitioningVertex(arrays.iconTransitioningVertexArray, tex.x, tex.y); + addTransitioningVertex(arrays.iconTransitioningVertexArray, tex.x + tex.w, tex.y); + addTransitioningVertex(arrays.iconTransitioningVertexArray, tex.x, tex.y + tex.h); + addTransitioningVertex(arrays.iconTransitioningVertexArray, tex.x + tex.w, tex.y + tex.h); + } + indexArray.emplaceBack(index, index + 1, index + 2); indexArray.emplaceBack(index + 1, index + 2, index + 3); diff --git a/src/render/draw_symbol.js b/src/render/draw_symbol.js index ee123b04c7a..9fe85c57c8b 100644 --- a/src/render/draw_symbol.js +++ b/src/render/draw_symbol.js @@ -378,6 +378,7 @@ function drawLayerSymbols(painter, sourceCache, layer, coords, isText, translate const uLabelPlaneMatrix = projectedPosOnLabelSpace ? identityMat4 : labelPlaneMatrixRendering; const uglCoordMatrix = painter.translatePosMatrix(glCoordMatrix, tile, translate, translateAnchor, true); const invMatrix = bucket.getProjection().createInversionMatrix(tr, coord.canonical); + const transitionProgress = layer.paint.get('icon-image-cross-fade').constantOr(0.0); const baseDefines = ([]: any); if (painter.terrainRenderModeElevated() && pitchWithMap) { @@ -389,6 +390,9 @@ function drawLayerSymbols(painter, sourceCache, layer, coords, isText, translate if (projectedPosOnLabelSpace) { baseDefines.push('PROJECTED_POS_ON_VIEWPORT'); } + if (transitionProgress > 0.0) { + baseDefines.push('ICON_TRANSITION'); + } const hasHalo = isSDF && layer.paint.get(isText ? 'text-halo-width' : 'icon-halo-width').constantOr(1) !== 0; @@ -403,7 +407,7 @@ function drawLayerSymbols(painter, sourceCache, layer, coords, isText, translate } } else { uniformValues = symbolIconUniformValues(sizeData.kind, size, rotateInShader, pitchWithMap, painter, matrix, - uLabelPlaneMatrix, uglCoordMatrix, isText, texSize, coord, globeToMercator, mercatorCenter, invMatrix, cameraUpVector, bucket.getProjection()); + uLabelPlaneMatrix, uglCoordMatrix, isText, texSize, coord, globeToMercator, mercatorCenter, invMatrix, cameraUpVector, bucket.getProjection(), transitionProgress); } const program = painter.useProgram(getSymbolProgramName(isSDF, isText, bucket), programConfiguration, baseDefines); @@ -480,7 +484,7 @@ function drawLayerSymbols(painter, sourceCache, layer, coords, isText, translate function drawSymbolElements(buffers, segments, layer, painter, program, depthMode, stencilMode, colorMode, uniformValues) { const context = painter.context; const gl = context.gl; - const dynamicBuffers = [buffers.dynamicLayoutVertexBuffer, buffers.opacityVertexBuffer, buffers.globeExtVertexBuffer]; + const dynamicBuffers = [buffers.dynamicLayoutVertexBuffer, buffers.opacityVertexBuffer, buffers.iconTransitioningVertexBuffer, buffers.globeExtVertexBuffer]; program.draw(context, gl.TRIANGLES, depthMode, stencilMode, colorMode, CullFaceMode.disabled, uniformValues, layer.id, buffers.layoutVertexBuffer, buffers.indexBuffer, segments, layer.paint, diff --git a/src/render/program/symbol_program.js b/src/render/program/symbol_program.js index 49c32a79065..2c47b4157f8 100644 --- a/src/render/program/symbol_program.js +++ b/src/render/program/symbol_program.js @@ -42,7 +42,8 @@ export type SymbolIconUniformsType = {| 'u_tile_matrix': UniformMatrix4f, 'u_up_vector': Uniform3f, 'u_ecef_origin': Uniform3f, - 'u_texture': Uniform1i + 'u_texture': Uniform1i, + 'u_icon_transition': Uniform1f |}; export type SymbolSDFUniformsType = {| @@ -122,7 +123,8 @@ const symbolIconUniforms = (context: Context): SymbolIconUniformsType => ({ 'u_tile_matrix': new UniformMatrix4f(context), 'u_up_vector': new Uniform3f(context), 'u_ecef_origin': new Uniform3f(context), - 'u_texture': new Uniform1i(context) + 'u_texture': new Uniform1i(context), + 'u_icon_transition': new Uniform1f(context) }); const symbolSDFUniforms = (context: Context): SymbolSDFUniformsType => ({ @@ -195,7 +197,8 @@ const symbolIconUniformValues = ( mercatorCenter: [number, number], invMatrix: Float32Array, upVector: [number, number, number], - projection: Projection + projection: Projection, + transition: ?number ): UniformValues => { const transform = painter.transform; @@ -222,7 +225,8 @@ const symbolIconUniformValues = ( 'u_camera_forward': [0, 0, 0], 'u_ecef_origin': [0, 0, 0], 'u_tile_matrix': identityMatrix, - 'u_up_vector': [0, -1, 0] + 'u_up_vector': [0, -1, 0], + 'u_icon_transition': transition ? transition : 0.0 }; if (projection.name === 'globe') { @@ -263,7 +267,8 @@ const symbolSDFUniformValues = ( texSize, coord, zoomTransition, mercatorCenter, invMatrix, upVector, projection), { 'u_gamma_scale': pitchWithMap ? painter.transform.cameraToCenterDistance * Math.cos(painter.terrain ? 0 : painter.transform._pitch) : 1, 'u_device_pixel_ratio': browser.devicePixelRatio, - 'u_is_halo': +isHalo + 'u_is_halo': +isHalo, + undefined }); }; diff --git a/src/style-spec/expression/definitions/image.js b/src/style-spec/expression/definitions/image.js index b8095c3bbc6..72f4dd4cbe4 100644 --- a/src/style-spec/expression/definitions/image.js +++ b/src/style-spec/expression/definitions/image.js @@ -10,35 +10,51 @@ import type {Type} from '../types.js'; export default class ImageExpression implements Expression { type: Type; - input: Expression; + inputPrimary: Expression; + inputSecondary: ?Expression; - constructor(input: Expression) { + constructor(inputPrimary: Expression, inputSecondary: ?Expression) { this.type = ResolvedImageType; - this.input = input; + this.inputPrimary = inputPrimary; + this.inputSecondary = inputSecondary; } static parse(args: $ReadOnlyArray, context: ParsingContext): ?Expression { - if (args.length !== 2) { - return context.error(`Expected two arguments.`); + if (args.length < 2) { + return context.error(`Expected two or more arguments.`); } - const name = context.parse(args[1], 1, StringType); - if (!name) return context.error(`No image name provided.`); + const namePrimary = context.parse(args[1], 1, StringType); + if (!namePrimary) return context.error(`No image name provided.`); - return new ImageExpression(name); + if (args.length === 2) { + return new ImageExpression(namePrimary); + } + + const nameSecondary = context.parse(args[2], 1, StringType); + if (!nameSecondary) return context.error(`Secondary image variant is not a string.`); + + return new ImageExpression(namePrimary, nameSecondary); } evaluate(ctx: EvaluationContext): null | ResolvedImage { - const evaluatedImageName = this.input.evaluate(ctx); - - const value = ResolvedImage.fromString(evaluatedImageName); - if (value && ctx.availableImages) value.available = ctx.availableImages.indexOf(evaluatedImageName) > -1; + const value = ResolvedImage.fromString(this.inputPrimary.evaluate(ctx), this.inputSecondary ? this.inputSecondary.evaluate(ctx) : undefined); + if (value && ctx.availableImages) { + value.available = ctx.availableImages.indexOf(value.namePrimary) > -1; + // If there's a secondary variant, only mark it available if both are present + if (value.nameSecondary && value.available && ctx.availableImages) { + value.available = ctx.availableImages.indexOf(value.nameSecondary) > -1; + } + } return value; } eachChild(fn: (_: Expression) => void) { - fn(this.input); + fn(this.inputPrimary); + if (this.inputSecondary) { + fn(this.inputSecondary); + } } outputDefined(): boolean { @@ -47,6 +63,10 @@ export default class ImageExpression implements Expression { } serialize(): SerializedExpression { - return ["image", this.input.serialize()]; + if (this.inputSecondary) { + // $FlowIgnore + return ["image", this.inputPrimary.serialize(), this.inputSecondary.serialize()]; + } + return ["image", this.inputPrimary.serialize()]; } } diff --git a/src/style-spec/expression/types/formatted.js b/src/style-spec/expression/types/formatted.js index c3df0b0d443..075a281ccc2 100644 --- a/src/style-spec/expression/types/formatted.js +++ b/src/style-spec/expression/types/formatted.js @@ -33,7 +33,7 @@ export default class Formatted { isEmpty(): boolean { if (this.sections.length === 0) return true; return !this.sections.some(section => section.text.length !== 0 || - (section.image && section.image.name.length !== 0)); + (section.image && section.image.namePrimary.length !== 0)); } static factory(text: Formatted | string): Formatted { @@ -53,7 +53,7 @@ export default class Formatted { const serialized: Array = ["format"]; for (const section of this.sections) { if (section.image) { - serialized.push(["image", section.image.name]); + serialized.push(["image", section.image.namePrimary]); continue; } serialized.push(section.text); diff --git a/src/style-spec/expression/types/resolved_image.js b/src/style-spec/expression/types/resolved_image.js index a9e92f8fedb..40761c9978b 100644 --- a/src/style-spec/expression/types/resolved_image.js +++ b/src/style-spec/expression/types/resolved_image.js @@ -1,29 +1,40 @@ // @flow export type ResolvedImageOptions = { - name: string, + namePrimary: string, + nameSecondary: ?string, available: boolean }; export default class ResolvedImage { - name: string; + namePrimary: string; + nameSecondary: ?string; available: boolean; constructor(options: ResolvedImageOptions) { - this.name = options.name; + this.namePrimary = options.namePrimary; + if (options.nameSecondary) { + this.nameSecondary = options.nameSecondary; + } this.available = options.available; } toString(): string { - return this.name; + if (this.nameSecondary) { + return `[${this.namePrimary},${this.nameSecondary}]`; + } + return this.namePrimary; } - static fromString(name: string): ResolvedImage | null { - if (!name) return null; // treat empty values as no image - return new ResolvedImage({name, available: false}); + static fromString(namePrimary: string, nameSecondary: ?string): ResolvedImage | null { + if (!namePrimary) return null; // treat empty values as no image + return new ResolvedImage({namePrimary, nameSecondary, available: false}); } serialize(): Array { - return ["image", this.name]; + if (this.nameSecondary) { + return ["image", this.namePrimary, this.nameSecondary]; + } + return ["image", this.namePrimary]; } } diff --git a/src/style-spec/reference/v8.json b/src/style-spec/reference/v8.json index 33bc8e2220a..65366aa37d4 100644 --- a/src/style-spec/reference/v8.json +++ b/src/style-spec/reference/v8.json @@ -6259,6 +6259,31 @@ }, "property-type": "data-constant" }, + "icon-image-cross-fade": { + "type": "number", + "property-type": "data-constant", + "default": 0.0, + "minimum": 0, + "maximum": 1, + "expression": { + "interpolated": true, + "parameters": [ + "zoom", + "feature", + "feature-state", + "measure-light" + ] + }, + "sdk-support": { + "basic functionality": { + "js": "3.0.0", + "android": "11.0.0", + "ios": "11.0.0" + } + }, + "doc": "Controls the transition progress between the image variants of icon-image. Zero means the first variant is used, one is the second, and in between they are blended together.", + "transition": true + }, "text-opacity": { "type": "number", "doc": "The opacity at which the text will be drawn.", diff --git a/src/style-spec/types.js b/src/style-spec/types.js index f17a2ee2f4e..54f84a12b03 100644 --- a/src/style-spec/types.js +++ b/src/style-spec/types.js @@ -330,6 +330,7 @@ export type SymbolLayerSpecification = {| "icon-halo-blur"?: DataDrivenPropertyValueSpecification, "icon-translate"?: PropertyValueSpecification<[number, number]>, "icon-translate-anchor"?: PropertyValueSpecification<"map" | "viewport">, + "icon-image-cross-fade"?: PropertyValueSpecification, "text-opacity"?: DataDrivenPropertyValueSpecification, "text-color"?: DataDrivenPropertyValueSpecification, "text-halo-color"?: DataDrivenPropertyValueSpecification, diff --git a/src/style/style_layer/symbol_style_layer_properties.js b/src/style/style_layer/symbol_style_layer_properties.js index f6cb1678db7..6b2fe8014b2 100644 --- a/src/style/style_layer/symbol_style_layer_properties.js +++ b/src/style/style_layer/symbol_style_layer_properties.js @@ -117,6 +117,7 @@ export type PaintProps = {| "icon-halo-blur": DataDrivenProperty, "icon-translate": DataConstantProperty<[number, number]>, "icon-translate-anchor": DataConstantProperty<"map" | "viewport">, + "icon-image-cross-fade": DataDrivenProperty, "text-opacity": DataDrivenProperty, "text-color": DataDrivenProperty, "text-halo-color": DataDrivenProperty, @@ -134,6 +135,7 @@ const paint: Properties = new Properties({ "icon-halo-blur": new DataDrivenProperty(styleSpec["paint_symbol"]["icon-halo-blur"]), "icon-translate": new DataConstantProperty(styleSpec["paint_symbol"]["icon-translate"]), "icon-translate-anchor": new DataConstantProperty(styleSpec["paint_symbol"]["icon-translate-anchor"]), + "icon-image-cross-fade": new DataDrivenProperty(styleSpec["paint_symbol"]["icon-image-cross-fade"]), "text-opacity": new DataDrivenProperty(styleSpec["paint_symbol"]["text-opacity"]), "text-color": new DataDrivenProperty(styleSpec["paint_symbol"]["text-color"], { runtimeType: ColorType, getOverride: (o) => o.textColor, hasOverride: (o) => !!o.textColor }), "text-halo-color": new DataDrivenProperty(styleSpec["paint_symbol"]["text-halo-color"]), diff --git a/src/symbol/projection.js b/src/symbol/projection.js index ea3cce3fab9..b160c7f89cb 100644 --- a/src/symbol/projection.js +++ b/src/symbol/projection.js @@ -344,12 +344,12 @@ function updateLineLabels(bucket: SymbolBucket, if (isText) { bucket.text.dynamicLayoutVertexBuffer.updateData(dynamicLayoutVertexArray); - if (globeExtVertexArray) { + if (globeExtVertexArray && bucket.text.globeExtVertexBuffer) { bucket.text.globeExtVertexBuffer.updateData(globeExtVertexArray); } } else { bucket.icon.dynamicLayoutVertexBuffer.updateData(dynamicLayoutVertexArray); - if (globeExtVertexArray) { + if (globeExtVertexArray && bucket.icon.globeExtVertexBuffer) { bucket.icon.globeExtVertexBuffer.updateData(globeExtVertexArray); } } diff --git a/src/symbol/quads.js b/src/symbol/quads.js index 8a36f522d24..4ece1f1184a 100644 --- a/src/symbol/quads.js +++ b/src/symbol/quads.js @@ -15,6 +15,13 @@ import {isVerticalClosePunctuation, isVerticalOpenPunctuation} from '../util/ver import ONE_EM from './one_em.js'; import {warnOnce} from '../util/util.js'; +export type TextureCoordinate = { + x: number, + y: number, + w: number, + h: number +}; + /** * A textured quad for rendering a single icon or glyph. * @@ -24,7 +31,8 @@ import {warnOnce} from '../util/util.js'; * @param tr The offset of the top right corner from the anchor. * @param bl The offset of the bottom left corner from the anchor. * @param br The offset of the bottom right corner from the anchor. - * @param tex The texture coordinates. + * @param texPrimary The texture coordinates of the primary image. + * @param texSecondary The texture coordinates of an optional secondary image. * * @private */ @@ -33,12 +41,8 @@ export type SymbolQuad = { tr: Point, bl: Point, br: Point, - tex: { - x: number, - y: number, - w: number, - h: number - }, + texPrimary: TextureCoordinate, + texSecondary: ?TextureCoordinate, pixelOffsetTL: Point, pixelOffsetBR: Point, writingMode: any | void, @@ -65,7 +69,7 @@ export function getIconQuads( hasIconTextFit: boolean): Array { const quads = []; - const image = shapedIcon.image; + const image = shapedIcon.imagePrimary; const pixelRatio = image.pixelRatio; const imageWidth = image.paddedRect.w - 2 * border; const imageHeight = image.paddedRect.h - 2 * border; @@ -149,11 +153,19 @@ export function getIconQuads( h: y2 - y1 }; + const imageSecondary = shapedIcon.imageSecondary; + const subRectB = imageSecondary ? { + x: imageSecondary.paddedRect.x + border + x1, + y: imageSecondary.paddedRect.y + border + y1, + w: x2 - x1, + h: y2 - y1 + } : undefined; + const minFontScaleX = fixedContentWidth / pixelRatio / iconWidth; const minFontScaleY = fixedContentHeight / pixelRatio / iconHeight; // Icon quad is padded, so texture coordinates also need to be padded. - return {tl, tr, bl, br, tex: subRect, writingMode: undefined, glyphOffset: [0, 0], sectionIndex: 0, pixelOffsetTL, pixelOffsetBR, minFontScaleX, minFontScaleY, isSDF: isSDFIcon}; + return {tl, tr, bl, br, texPrimary: subRect, texSecondary: subRectB, writingMode: undefined, glyphOffset: [0, 0], sectionIndex: 0, pixelOffsetTL, pixelOffsetBR, minFontScaleX, minFontScaleY, isSDF: isSDFIcon}; }; if (!hasIconTextFit || (!image.stretchX && !image.stretchY)) { @@ -429,7 +441,7 @@ export function getGlyphQuads(anchor: Anchor, const pixelOffsetBR = new Point(0, 0); const minFontScaleX = 0; const minFontScaleY = 0; - quads.push({tl, tr, bl, br, tex: textureRect, writingMode: shaping.writingMode, glyphOffset, sectionIndex: positionedGlyph.sectionIndex, isSDF, pixelOffsetTL, pixelOffsetBR, minFontScaleX, minFontScaleY}); + quads.push({tl, tr, bl, br, texPrimary: textureRect, texSecondary: undefined, writingMode: shaping.writingMode, glyphOffset, sectionIndex: positionedGlyph.sectionIndex, isSDF, pixelOffsetTL, pixelOffsetBR, minFontScaleX, minFontScaleY}); } } diff --git a/src/symbol/shaping.js b/src/symbol/shaping.js index ae473198ad8..0e180017632 100644 --- a/src/symbol/shaping.js +++ b/src/symbol/shaping.js @@ -204,7 +204,7 @@ class TaggedString { } addImageSection(section: FormattedSection) { - const imageName = section.image ? section.image.name : ''; + const imageName = section.image ? section.image.namePrimary : ''; if (imageName.length === 0) { warnOnce(`Can't add FormattedSection with an empty image.`); return; @@ -789,7 +789,8 @@ function align(positionedLines: Array, } export type PositionedIcon = { - image: ImagePosition, + imagePrimary: ImagePosition, + imageSecondary: ?ImagePosition, top: number, bottom: number, left: number, @@ -797,15 +798,15 @@ export type PositionedIcon = { collisionPadding?: [number, number, number, number] }; -function shapeIcon(image: ImagePosition, iconOffset: [number, number], iconAnchor: SymbolAnchor): PositionedIcon { +function shapeIcon(imagePrimary: ImagePosition, imageSecondary: ?ImagePosition, iconOffset: [number, number], iconAnchor: SymbolAnchor): PositionedIcon { const {horizontalAlign, verticalAlign} = getAnchorAlignment(iconAnchor); const dx = iconOffset[0]; const dy = iconOffset[1]; - const x1 = dx - image.displaySize[0] * horizontalAlign; - const x2 = x1 + image.displaySize[0]; - const y1 = dy - image.displaySize[1] * verticalAlign; - const y2 = y1 + image.displaySize[1]; - return {image, top: y1, bottom: y2, left: x1, right: x2}; + const x1 = dx - imagePrimary.displaySize[0] * horizontalAlign; + const x2 = x1 + imagePrimary.displaySize[0]; + const y1 = dy - imagePrimary.displaySize[1] * verticalAlign; + const y2 = y1 + imagePrimary.displaySize[1]; + return {imagePrimary, imageSecondary, top: y1, bottom: y2, left: x1, right: x2}; } function fitIconToText(shapedIcon: PositionedIcon, shapedText: Shaping, @@ -816,7 +817,7 @@ function fitIconToText(shapedIcon: PositionedIcon, shapedText: Shaping, assert(Array.isArray(padding) && padding.length === 4); assert(Array.isArray(iconOffset) && iconOffset.length === 2); - const image = shapedIcon.image; + const image = shapedIcon.imagePrimary; let collisionPadding; if (image.content) { @@ -860,5 +861,5 @@ function fitIconToText(shapedIcon: PositionedIcon, shapedText: Shaping, bottom = top + image.displaySize[1]; } - return {image, top, right, bottom, left, collisionPadding}; + return {imagePrimary: image, imageSecondary: undefined, top, right, bottom, left, collisionPadding}; } diff --git a/src/symbol/symbol_layout.js b/src/symbol/symbol_layout.js index a57c78f4472..482ee7c933c 100644 --- a/src/symbol/symbol_layout.js +++ b/src/symbol/symbol_layout.js @@ -189,6 +189,14 @@ export function performSymbolLayout(bucket: SymbolBucket, const textAlongLine = layout.get('text-rotation-alignment') === 'map' && layout.get('symbol-placement') !== 'point'; const textSize = layout.get('text-size'); + let hasAnySecondaryIcon = false; + for (const feature of bucket.features) { + if (feature.icon && feature.icon.nameSecondary) { + hasAnySecondaryIcon = true; + break; + } + } + for (const feature of bucket.features) { const fontstack = layout.get('text-font').evaluate(feature, {}, canonical).join(','); const layoutTextSizeThisZoom = textSize.evaluate(feature, {}, canonical); @@ -287,11 +295,12 @@ export function performSymbolLayout(bucket: SymbolBucket, let shapedIcon; let isSDFIcon = false; - if (feature.icon && feature.icon.name) { - const image = imageMap[feature.icon.name]; + if (feature.icon && feature.icon.namePrimary) { + const image = imageMap[feature.icon.namePrimary]; if (image) { shapedIcon = shapeIcon( - imagePositions[feature.icon.name], + imagePositions[feature.icon.namePrimary], + feature.icon.nameSecondary ? imagePositions[feature.icon.nameSecondary] : undefined, layout.get('icon-offset').evaluate(feature, {}, canonical), layout.get('icon-anchor').evaluate(feature, {}, canonical)); isSDFIcon = image.sdf; @@ -313,7 +322,7 @@ export function performSymbolLayout(bucket: SymbolBucket, bucket.iconsInText = shapedText ? shapedText.iconsInText : false; } if (shapedText || shapedIcon) { - addFeature(bucket, feature, shapedTextOrientations, shapedIcon, imageMap, sizes, layoutTextSize, layoutIconSize, textOffset, isSDFIcon, availableImages, canonical, projection, brightness); + addFeature(bucket, feature, shapedTextOrientations, shapedIcon, imageMap, sizes, layoutTextSize, layoutIconSize, textOffset, isSDFIcon, availableImages, canonical, projection, brightness, hasAnySecondaryIcon); } } @@ -371,7 +380,8 @@ function addFeature(bucket: SymbolBucket, availableImages: Array, canonical: CanonicalTileID, projection: Projection, - brightness: ?number) { + brightness: ?number, + hasAnySecondaryIcon: boolean) { // To reduce the number of labels that jump around when zooming we need // to use a text-size value that is the same for all zoom levels. // bucket calculates text-size at a high zoom level so that all tiles can @@ -434,7 +444,7 @@ function addFeature(bucket: SymbolBucket, bucket.collisionBoxArray, feature.index, feature.sourceLayerIndex, bucket.index, textPadding, textAlongLine, textOffset, iconBoxScale, iconPadding, iconAlongLine, iconOffset, - feature, sizes, isSDFIcon, availableImages, canonical, brightness); + feature, sizes, isSDFIcon, availableImages, canonical, brightness, hasAnySecondaryIcon); }; if (symbolPlacement === 'line') { @@ -554,7 +564,8 @@ function addTextVertices(bucket: SymbolBucket, placedIconIndex, availableImages, canonical, - brightness); + brightness, + false); // The placedSymbolArray is used at render time in drawTileSymbols // These indices allow access to the array at collision detection time @@ -676,7 +687,8 @@ function addSymbol(bucket: SymbolBucket, isSDFIcon: boolean, availableImages: Array, canonical: CanonicalTileID, - brightness: ?number) { + brightness: ?number, + hasAnySecondaryIcon: boolean) { const lineArray = bucket.addToLineVertexArray(anchor, line); let textBoxIndex, iconBoxIndex, verticalTextBoxIndex, verticalIconBoxIndex; let textCircle, verticalTextCircle, verticalIconCircle; @@ -766,7 +778,8 @@ function addSymbol(bucket: SymbolBucket, -1, availableImages, canonical, - brightness); + brightness, + hasAnySecondaryIcon); placedIconSymbolIndex = bucket.icon.placedSymbolArray.length - 1; @@ -789,7 +802,8 @@ function addSymbol(bucket: SymbolBucket, -1, availableImages, canonical, - brightness); + brightness, + hasAnySecondaryIcon); verticalPlacedIconSymbolIndex = bucket.icon.placedSymbolArray.length - 1; } diff --git a/test/ignores/all.js b/test/ignores/all.js index 55bf80dfe13..2f08a45755e 100644 --- a/test/ignores/all.js +++ b/test/ignores/all.js @@ -159,11 +159,6 @@ const skip = [ // terrain model tests are flaky in CI "render-tests/model-layer/fill-extrusion--default-terrain-opacity", "render-tests/model-layer/fill-extrusion--default-terrain", - - // icon-image-cross-fade not supported in -js - "render-tests/measure-light/global-brightness-icon-image-fade", - "render-tests/measure-light/global-brightness-icon-image-switch-dark", - "render-tests/measure-light/global-brightness-icon-image-switch-light", ]; export default {todo, skip}; diff --git a/test/integration/expression-tests/format/image-sections/test.json b/test/integration/expression-tests/format/image-sections/test.json index 63f40ec477c..47377d9d64b 100644 --- a/test/integration/expression-tests/format/image-sections/test.json +++ b/test/integration/expression-tests/format/image-sections/test.json @@ -22,14 +22,14 @@ "sections": [ { "text": "", - "image": {"name": "monument-15", "available": true}, + "image": {"namePrimary": "monument-15", "available": true}, "scale": null, "fontStack": null, "textColor": null }, { "text": "", - "image": {"name": "beach-11", "available": false}, + "image": {"namePrimary": "beach-11", "available": false}, "scale": null, "fontStack": null, "textColor": null diff --git a/test/integration/expression-tests/image/basic/test.json b/test/integration/expression-tests/image/basic/test.json index 982e228187f..b797fe2720f 100644 --- a/test/integration/expression-tests/image/basic/test.json +++ b/test/integration/expression-tests/image/basic/test.json @@ -21,8 +21,8 @@ "type": "resolvedImage" }, "outputs": [ - {"name":"monument-15","available":true}, - {"name":"monument-15","available":false} + {"namePrimary":"monument-15","available":true}, + {"namePrimary":"monument-15","available":false} ], "serialized": [ "image", diff --git a/test/integration/expression-tests/image/coalesce/test.json b/test/integration/expression-tests/image/coalesce/test.json index e09283a0f9e..c9864e27bf0 100644 --- a/test/integration/expression-tests/image/coalesce/test.json +++ b/test/integration/expression-tests/image/coalesce/test.json @@ -21,8 +21,8 @@ "type": "resolvedImage" }, "outputs": [ - {"name":"monument-15","available":true}, - {"name": "foo", "available": false} + {"namePrimary":"monument-15","available":true}, + {"namePrimary": "foo", "available": false} ], "serialized": [ "coalesce", diff --git a/test/integration/expression-tests/image/compound/test.json b/test/integration/expression-tests/image/compound/test.json index 0b2b8bbcf28..2ea8d85afbf 100644 --- a/test/integration/expression-tests/image/compound/test.json +++ b/test/integration/expression-tests/image/compound/test.json @@ -19,7 +19,7 @@ "type": "resolvedImage" }, "outputs": [ - {"name":"monument-15","available":true} + {"namePrimary":"monument-15","available":true} ], "serialized": [ "image", diff --git a/test/integration/render-tests/measure-light/global-brightness-icon-image-data-driven-mixed/expected.png b/test/integration/render-tests/measure-light/global-brightness-icon-image-data-driven-mixed/expected.png new file mode 100644 index 0000000000000000000000000000000000000000..579982de871e17664162a31904c4598752708d78 GIT binary patch literal 3393 zcmdT{>08o?7RD^KO-HM7MoVXMEYr#w7qmp%)YJ+$Fqfos2v=MPQ4q;2%N(>B^|3Mc zTq;J$%rFr()0ny{?xcd6rYORX%mrkf=H7YkUvNL%IbY85={)aq&ikH|<$b|@`xc`u zAP{J~$JsN!AdtqFPy?i+^~HrGKDzp)uH$j$q=UsQb zXyG@QxJ($sm{i?!ZgON8i9h4s85w@}N=0+U@gZ2{!iDrd8$kSi({p*IjU{x1= zyzlL`FpNbx^CeEU->28yGDvNf6WdUsuTLbQ)>78q?q^?>_TujRKNz(R0MHswvot{4 z!a<`oLh&F32;siKx1|YGsU1oPOG(I(`8*N>2PIn#65M z>u_yzFx^l$&7=imt9ogIThdnJ}%+TfH;KL5ytF{T_15w(Sq3-m3 zvD+5sTAmsNTq@aYeq}hb`5}NVuV4o6@S>@lesI)_)uB8#lX9H%TO?RiG(D|eC17{G zS3KPpw?)qJ>!YdN?u=>Ne_r7S<|kPk<&T}S0Q`edpJ=2<$@JzO99-yf%GpE{%Y|)< zRXua@18F*zlXmzGRnV7D?#CX+yUv1k3IzjATzm7Y%4j$+@9!S)iIu=qjH{mXTIE<@ zam{n9$rsc_vZLOAJf~*=a==(Y604g&3hie_!YvAi%p17zwp4imKkRz<$jP>(@hA4X z;O?xK^VR~b@}oOPB+J41`Tf6l{;SiP7SO$7XJj9FE6l{oUokLdWis>9t~L60iRoOkd6<^Wno@KLT7bBiwFknngPQ@cFD#aT*fJ!!Z!g=cc zbm2(E%214m#6R80RFwkft$e1R$0%b0ACHkFt!qrBJ9tB&>d2b&{z&A*<0w(ODSK51 zn^^$YZ=Qy%O-P!$ju$x9Ui1o$8FFVqBV}WjWc-thOObW~PAZpYMgHEqM)}i;K41_O z9o64sCCGoO__ZHHw@A5KVNX|g{ac$ZBw7nDGD zRo$^!maKwJfSllCPE+^YdJg*buGu#-*Ql9{vZVUucS@f-C3M%Lz{3-NkWY=gfRpe0 z3`vU5@Uyn}{MBp3g3e<9bUMF-&^Gh6zhGqDxFEFuiC#`=-(zd`0{L{AMRnB$4``e> zw^F28YLkRyUM+0gnD%c_r&JwU=7%tg%#}(XpaGEZTgz)W6N4Y1P=T?eha`Q^SLy6_ zOz`|b*IQ&|d+mJiEOc|^Fp(|y2rUUZz842-8J}Q$7RYnJ&j(JUuo}&4ecElxn&6u=7Na@@neDkK3s)W})%syjmRFMH zhd6XmaRrm)P$|htt!Xf99a-{2t*BV6)rB(XBKKC@wNH6FWW3v~f|cZ)X&!%Og3o)F zT3BS33VQQ0d>&GIvkP`N#o>G{yELHVZj2XjAwqZf(nULq5rq{PlDqKxd6GOCJAhWsc>$J9| z+uudL^Xxizsakq#Tvm!hC1E=!H58qDPK%d18RMh+d2lHXBsmyD=?8k}2RKPh$IE?r zg+Wzd=Xe}QuU4WY+`!;2{DfE`1G-c0fy&-ngfjQ$T*vUTKm0hx;>CGBBBDf|iI`Lo zV$l;~^}Y9_%4T$;X`Wk;B|r%E6?n;9Ujcsu-EB$r`@B~|7CKL^&jm!hw>Dg-C$lc3 zRb;qBn>D1T#0(eati;*i2WaRVI-MSZ&#^(q#&)W8FOMxVw094l1t-2NAPmXI>2|_b zT}H~E>Zj)lJ1U$yiicUvi%;MP!e*ULVVLbZfMpD0+uv~6@!xczC64@Tb2q5=V9}5F% z`5xgr62B0V+@`({t9lz~jFrxBh9G#(?Qb&COnGg%Y_xJOxpX~sa`{a)+#+tg^lxNs zUtk3}wqOuD23~9FK#C0|*B3r25 z#3$~45~H&&rr^%iW9%&x@agcF3CO%%f=uYxgQs>7Z+1({cDUdgRMCOA{!6>MIA^3c;60XSWks!&W2Y>GOH; z2x;dg8K8;bE?N6`n^`IUQl6W&U`x(+*FM(Koh^*()kv&iZtR*Cxu`Yh^}cR-E$0?a zdkTW$Dt?484m}g!RFyv34Pw7uA1`pI^-n;9s9lM~*KKLQ((7`H|XfLR``mQIV5u;lL=LhO1(`f9{= zYfj%t#pb}-0HVosNtR=<67$VUw5jLBp8npNdjR`=;xK=2N>cs4*#Oy${>oyTeq|C! z0p`B7rVUC_+&WSB-Y(rRp%5EX{}{eUUlFV(;SY05PP~wJ6S9wwyz0$Baf`!d(T}s6 z^@Y|$qt!^`D20UhbonbV<*eF~c7}~6QzvDJ!%wu!1BlVLpCNu9o?*iU)aSnB^9KfU zzb=f3Qo$u;1aYZ0?WVdz9F;sdggqpBXF(Z>+hTdG_PBO?Mq&B>L zvaL5RL(=~U4?Vj8D9e7O!_;{}mBGHH9A2ccE%WIB`Es4nw&JZjH2V-uW#tI3J_p0% str$(vl)*pkg#0I-z5n~3g=!7K>-2AceOUIF*9hd{df^Q9)a4t00vi-&MgRZ+ literal 0 HcmV?d00001 diff --git a/test/integration/render-tests/measure-light/global-brightness-icon-image-data-driven-mixed/style.json b/test/integration/render-tests/measure-light/global-brightness-icon-image-data-driven-mixed/style.json new file mode 100644 index 00000000000..29f89d3d7b5 --- /dev/null +++ b/test/integration/render-tests/measure-light/global-brightness-icon-image-data-driven-mixed/style.json @@ -0,0 +1,184 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 256, + "width": 256, + "operations": [ + [ + "wait" + ], + [ + "setLights", + [ + { + "type": "ambient", + "id": "environment", + "properties": { + "intensity": 0.2 + } + }, + { + "type": "directional", + "id": "sun_light", + "properties": { + "color": "rgba(255.0, 255.0, 255.0, 1.0)", + "intensity": 0.4, + "direction": [ + 200.0, + 30.0 + ] + } + } + ] + ] + ] + } + }, + "center": [ + -113.2935, + 35.9529 + ], + "zoom": 11.2, + "pitch": 80, + "sources": { + "sample-point-a": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "sample": 2 + }, + "geometry": { + "coordinates": [ + -113.2935, + 35.9529 + ], + "type": "Point" + } + }, + { + "type": "Feature", + "properties": { + "sample": 1 + }, + "geometry": { + "coordinates": [ + -113.2735, + 35.9529 + ], + "type": "Point" + } + } + ] + } + }, + "sample-point-b": { + "type": "geojson", + "data": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "sample": 0 + }, + "geometry": { + "coordinates": [ + -113.3135, + 35.9529 + ], + "type": "Point" + } + } + ] + } + } + }, + "sprite": "local://sprites/sprite", + "glyphs": "local://glyphs/{fontstack}/{range}.pbf", + "lights": [ + { + "type": "ambient", + "id": "environment", + "properties": { + "intensity": 0.7 + } + }, + { + "type": "directional", + "id": "sun_light", + "properties": { + "color": "rgba(255.0, 255.0, 255.0, 1.0)", + "intensity": 0.8, + "direction": [ + 200.0, + 30.0 + ] + } + } + ], + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "white" + } + }, + { + "id": "sample-symbol-calculate-a", + "type": "symbol", + "source": "sample-point-a", + "symbol-placement": "point", + "layout": { + "icon-image": [ + "case", + ["<", ["get", "sample"], 2], + [ + "image", + "triangle-stroked-12", + "building-12" + ], + "circle-stroked-12" + ] + }, + "paint": { + "icon-image-cross-fade": [ + "+", + 0.3, + [ + "measure-light", + "brightness" + ] + ] + } + }, + { + "id": "sample-symbol-calculate-c", + "type": "symbol", + "source": "sample-point-b", + "symbol-placement": "point", + "layout": { + "icon-image": [ + "image", + "triangle-stroked-12", + "building-12" + ] + }, + "paint": { + "icon-image-cross-fade": [ + "+", + 0.6, + [ + "measure-light", + "brightness" + ] + ] + } + } + ] +} \ No newline at end of file diff --git a/test/integration/render-tests/measure-light/global-brightness-icon-image-fade/style.json b/test/integration/render-tests/measure-light/global-brightness-icon-image-fade/style.json index 7ec1c2ca0c1..c451a5b5e44 100644 --- a/test/integration/render-tests/measure-light/global-brightness-icon-image-fade/style.json +++ b/test/integration/render-tests/measure-light/global-brightness-icon-image-fade/style.json @@ -42,13 +42,6 @@ "zoom": 11.2, "pitch": 80, "sources": { - "mapbox": { - "type": "vector", - "maxzoom": 14, - "tiles": [ - "local://tiles/{z}-{x}-{y}.mvt" - ] - }, "sample-point-a": { "type": "geojson", "data": { @@ -138,67 +131,70 @@ "background-color": "white" } }, - { - "id": "land", - "type": "fill", - "source": "mapbox", - "source-layer": "water", - "paint": { - "fill-color": "#3399ff" - } - }, { "id": "sample-symbol-calculate-a", "type": "symbol", "source": "sample-point-a", "symbol-placement": "point", - "icon-image": [ - "image", - "triangle-stroked-12", - "building-12" - ], - "icon-image-cross-fade": [ - "measure-light", - "brightness" - ] + "layout": { + "icon-image": [ + "image", + "triangle-stroked-12", + "building-12" + ] + }, + "paint": { + "icon-image-cross-fade": [ + "measure-light", + "brightness" + ] + } }, { "id": "sample-symbol-calculate-b", "type": "symbol", "source": "sample-point-b", "symbol-placement": "point", - "icon-image": [ - "image", - "triangle-stroked-12", - "building-12" - ], - "icon-image-cross-fade": [ - "+", - 0.3, - [ - "measure-light", - "brightness" + "layout": { + "icon-image": [ + "image", + "triangle-stroked-12", + "building-12" ] - ] + }, + "paint": { + "icon-image-cross-fade": [ + "+", + 0.3, + [ + "measure-light", + "brightness" + ] + ] + } }, { "id": "sample-symbol-calculate-c", "type": "symbol", "source": "sample-point-c", "symbol-placement": "point", - "icon-image": [ - "image", - "triangle-stroked-12", - "building-12" - ], - "icon-image-cross-fade": [ - "+", - 0.6, - [ - "measure-light", - "brightness" + "layout": { + "icon-image": [ + "image", + "triangle-stroked-12", + "building-12" ] - ] + }, + "paint": { + "icon-image-cross-fade": [ + "+", + 0.6, + [ + "measure-light", + "brightness" + ] + ] + } } ] } \ No newline at end of file diff --git a/test/integration/render-tests/measure-light/global-brightness-icon-image-switch-dark/style.json b/test/integration/render-tests/measure-light/global-brightness-icon-image-switch-dark/style.json index 18670f08917..a2f15f086fa 100644 --- a/test/integration/render-tests/measure-light/global-brightness-icon-image-switch-dark/style.json +++ b/test/integration/render-tests/measure-light/global-brightness-icon-image-switch-dark/style.json @@ -42,13 +42,6 @@ "zoom": 11.2, "pitch": 80, "sources": { - "mapbox": { - "type": "vector", - "maxzoom": 14, - "tiles": [ - "local://tiles/{z}-{x}-{y}.mvt" - ] - }, "sample-point-a": { "type": "geojson", "data": { @@ -119,62 +112,61 @@ "background-color": "white" } }, - { - "id": "land", - "type": "fill", - "source": "mapbox", - "source-layer": "water", - "paint": { - "fill-color": "#3399ff" - } - }, { "id": "sample-symbol-calculate-a", "type": "symbol", "source": "sample-point-a", "symbol-placement": "point", - "icon-image": [ - "image", - "triangle-stroked-12", - "building-12" - ], - "icon-image-cross-fade": [ - "case", - [ - "<", + "layout": { + "icon-image": [ + "image", + "triangle-stroked-12", + "building-12" + ] + }, + "paint": { + "icon-image-cross-fade": [ + "case", [ - "measure-light", - "brightness" + "<", + [ + "measure-light", + "brightness" + ], + 0.5 ], - 0.5 - ], - 0.0, - 1.0 - ] + 0.0, + 1.0 + ] + } }, { "id": "sample-symbol-calculate-b", "type": "symbol", "source": "sample-point-b", "symbol-placement": "point", - "icon-image": [ - "image", - "triangle-stroked-12", - "building-12" - ], - "icon-image-cross-fade": [ - "case", - [ - ">", + "layout": { + "icon-image": [ + "image", + "triangle-stroked-12", + "building-12" + ] + }, + "paint": { + "icon-image-cross-fade": [ + "case", [ - "measure-light", - "brightness" + ">", + [ + "measure-light", + "brightness" + ], + 0.5 ], - 0.5 - ], - 0.0, - 1.0 - ] + 0.0, + 1.0 + ] + } } ] } \ No newline at end of file diff --git a/test/integration/render-tests/measure-light/global-brightness-icon-image-switch-light/style.json b/test/integration/render-tests/measure-light/global-brightness-icon-image-switch-light/style.json index 4039338577e..393a9eb409f 100644 --- a/test/integration/render-tests/measure-light/global-brightness-icon-image-switch-light/style.json +++ b/test/integration/render-tests/measure-light/global-brightness-icon-image-switch-light/style.json @@ -42,13 +42,6 @@ "zoom": 11.2, "pitch": 80, "sources": { - "mapbox": { - "type": "vector", - "maxzoom": 14, - "tiles": [ - "local://tiles/{z}-{x}-{y}.mvt" - ] - }, "sample-point-a": { "type": "geojson", "data": { @@ -119,62 +112,61 @@ "background-color": "white" } }, - { - "id": "land", - "type": "fill", - "source": "mapbox", - "source-layer": "water", - "paint": { - "fill-color": "#3399ff" - } - }, { "id": "sample-symbol-calculate-a", "type": "symbol", "source": "sample-point-a", "symbol-placement": "point", - "icon-image": [ - "image", - "triangle-stroked-12", - "building-12" - ], - "icon-image-cross-fade": [ - "case", - [ - "<", + "layout": { + "icon-image": [ + "image", + "triangle-stroked-12", + "building-12" + ] + }, + "paint": { + "icon-image-cross-fade": [ + "case", [ - "measure-light", - "brightness" + "<", + [ + "measure-light", + "brightness" + ], + 0.5 ], - 0.5 - ], - 0.0, - 1.0 - ] + 0.0, + 1.0 + ] + } }, { "id": "sample-symbol-calculate-b", "type": "symbol", "source": "sample-point-b", "symbol-placement": "point", - "icon-image": [ - "image", - "triangle-stroked-12", - "building-12" - ], - "icon-image-cross-fade": [ - "case", - [ - ">", + "layout": { + "icon-image": [ + "image", + "triangle-stroked-12", + "building-12" + ] + }, + "paint": { + "icon-image-cross-fade": [ + "case", [ - "measure-light", - "brightness" + ">", + [ + "measure-light", + "brightness" + ], + 0.5 ], - 0.5 - ], - 0.0, - 1.0 - ] + 0.0, + 1.0 + ] + } } ] } \ No newline at end of file diff --git a/test/unit/symbol/quads.test.js b/test/unit/symbol/quads.test.js index 4018287b823..72c6ba38dfe 100644 --- a/test/unit/symbol/quads.test.js +++ b/test/unit/symbol/quads.test.js @@ -2,7 +2,7 @@ import {test} from '../../util/test.js'; import {getIconQuads} from '../../../src/symbol/quads.js'; test('getIconQuads', (t) => { - const image = Object.freeze({ + const imagePrimary = Object.freeze({ pixelRatio: 1, displaySize: Object.freeze([ 15, 11 ]), paddedRect: Object.freeze({x: 0, y: 0, w: 17, h: 13}) @@ -14,13 +14,14 @@ test('getIconQuads', (t) => { right: 7.5, bottom: 5.5, left: -7.5, - image + imagePrimary }, 0, true), [{ tl: {x: -8.5, y: -6.5}, tr: {x: 8.5, y: -6.5}, bl: {x: -8.5, y: 6.5}, br: {x: 8.5, y: 6.5}, - tex: {x: 0, y: 0, w: 17, h: 13}, + texPrimary: {x: 0, y: 0, w: 17, h: 13}, + texSecondary: undefined, writingMode: null, glyphOffset: [0, 0], isSDF: true, @@ -42,13 +43,14 @@ test('getIconQuads', (t) => { right: 15, bottom: 11, left: -15, - image + imagePrimary }, 0, false), [{ tl: {x: -17, y: -13}, tr: {x: 17, y: -13}, bl: {x: -17, y: 13}, br: {x: 17, y: 13}, - tex: {x: 0, y: 0, w: 17, h: 13}, + texPrimary: {x: 0, y: 0, w: 17, h: 13}, + texSecondary: undefined, writingMode: null, glyphOffset: [0, 0], isSDF: false, @@ -70,13 +72,14 @@ test('getIconQuads', (t) => { right: 0, bottom: 11, left: -15, - image + imagePrimary }, 0, false), [{ tl: {x: -16, y: -1}, tr: {x: 1, y: -1}, bl: {x: -16, y: 12}, br: {x: 1, y: 12}, - tex: {x: 0, y: 0, w: 17, h: 13}, + texPrimary: {x: 0, y: 0, w: 17, h: 13}, + texSecondary: undefined, writingMode: null, glyphOffset: [0, 0], isSDF: false, @@ -98,13 +101,14 @@ test('getIconQuads', (t) => { right: 30, bottom: 5.5, left: -30, - image + imagePrimary }, 0, false), [{ tl: {x: -34, y: -6.5}, tr: {x: 34, y: -6.5}, bl: {x: -34, y: 6.5}, br: {x: 34, y: 6.5}, - tex: {x: 0, y: 0, w: 17, h: 13}, + texPrimary: {x: 0, y: 0, w: 17, h: 13}, + texSecondary: undefined, writingMode: null, glyphOffset: [0, 0], isSDF: false, @@ -130,13 +134,14 @@ test('getIconQuads', (t) => { right: 7.5, bottom: 5.5, left: -7.5, - image + imagePrimary }, 0, false), [{ tl: {x: -8.5, y: -6.5}, tr: {x: 8.5, y: -6.5}, bl: {x: -8.5, y: 6.5}, br: {x: 8.5, y: 6.5}, - tex: {x: 0, y: 0, w: 17, h: 13}, + texPrimary: {x: 0, y: 0, w: 17, h: 13}, + texSecondary: undefined, writingMode: null, glyphOffset: [0, 0], isSDF: false, diff --git a/test/unit/symbol/shaping.test.js b/test/unit/symbol/shaping.test.js index 61fda3a4d9e..5efd4e324ae 100644 --- a/test/unit/symbol/shaping.test.js +++ b/test/unit/symbol/shaping.test.js @@ -150,7 +150,7 @@ test('shaping', (t) => { test('shapeIcon', (t) => { const imagePosition = new ImagePosition({x: 0, y: 0, w: 22, h: 22}, {pixelRatio: 1, version: 1}); - const image = Object.freeze({ + const imagePrimary = Object.freeze({ content: null, stretchX: null, stretchY: null, @@ -160,58 +160,64 @@ test('shapeIcon', (t) => { }); t.test('text-anchor: center', (t) => { - t.deepEqual(shaping.shapeIcon(imagePosition, [ 0, 0 ], 'center'), { + t.deepEqual(shaping.shapeIcon(imagePosition, undefined, [ 0, 0 ], 'center'), { top: -10, bottom: 10, left: -10, right: 10, - image + imagePrimary, + imageSecondary: undefined }, 'no offset'); - t.deepEqual(shaping.shapeIcon(imagePosition, [ 4, 7 ], 'center'), { + t.deepEqual(shaping.shapeIcon(imagePosition, undefined, [ 4, 7 ], 'center'), { top: -3, bottom: 17, left: -6, right: 14, - image + imagePrimary, + imageSecondary: undefined }, 'with offset'); t.end(); }); t.test('text-anchor: left', (t) => { - t.deepEqual(shaping.shapeIcon(imagePosition, [ 0, 0 ], 'left'), { + t.deepEqual(shaping.shapeIcon(imagePosition, undefined, [ 0, 0 ], 'left'), { top: -10, bottom: 10, left: 0, right: 20, - image + imagePrimary, + imageSecondary: undefined }, 'no offset'); - t.deepEqual(shaping.shapeIcon(imagePosition, [ 4, 7 ], 'left'), { + t.deepEqual(shaping.shapeIcon(imagePosition, undefined, [ 4, 7 ], 'left'), { top: -3, bottom: 17, left: 4, right: 24, - image + imagePrimary, + imageSecondary: undefined }, 'with offset'); t.end(); }); t.test('text-anchor: bottom-right', (t) => { - t.deepEqual(shaping.shapeIcon(imagePosition, [ 0, 0 ], 'bottom-right'), { + t.deepEqual(shaping.shapeIcon(imagePosition, undefined, [ 0, 0 ], 'bottom-right'), { top: -20, bottom: 0, left: -20, right: 0, - image + imagePrimary, + imageSecondary: undefined }, 'no offset'); - t.deepEqual(shaping.shapeIcon(imagePosition, [ 4, 7 ], 'bottom-right'), { + t.deepEqual(shaping.shapeIcon(imagePosition, undefined, [ 4, 7 ], 'bottom-right'), { top: -13, bottom: 7, left: -16, right: 4, - image + imagePrimary, + imageSecondary: undefined }, 'with offset'); t.end(); }); @@ -227,11 +233,12 @@ test('fitIconToText', (t) => { left: -10, right: 10, collisionPadding: undefined, - image: Object.freeze({ + imagePrimary: Object.freeze({ pixelRatio: 1, displaySize: [ 20, 20 ], paddedRect: Object.freeze({x: 0, y: 0, w: 22, h: 22}) - }) + }), + imageSecondary: undefined }); const shapedText = Object.freeze({ @@ -243,7 +250,8 @@ test('fitIconToText', (t) => { t.test('icon-text-fit: width', (t) => { t.deepEqual(shaping.fitIconToText(shapedIcon, shapedText, 'width', [0, 0, 0, 0], [0, 0], 24 / glyphSize), { - image: shapedIcon.image, + imagePrimary: shapedIcon.imagePrimary, + imageSecondary: undefined, collisionPadding: undefined, top: 0, right: 20, @@ -252,7 +260,8 @@ test('fitIconToText', (t) => { }, 'preserves icon height, centers vertically'); t.deepEqual(shaping.fitIconToText(shapedIcon, shapedText, 'width', [0, 0, 0, 0], [3, 7], 24 / glyphSize), { - image: shapedIcon.image, + imagePrimary: shapedIcon.imagePrimary, + imageSecondary: undefined, collisionPadding: undefined, top: 7, right: 23, @@ -261,7 +270,8 @@ test('fitIconToText', (t) => { }, 'preserves icon height, centers vertically, applies offset'); t.deepEqual(shaping.fitIconToText(shapedIcon, shapedText, 'width', [0, 0, 0, 0], [0, 0], 12 / glyphSize), { - image: shapedIcon.image, + imagePrimary: shapedIcon.imagePrimary, + imageSecondary: undefined, collisionPadding: undefined, top: -5, right: 10, @@ -271,7 +281,8 @@ test('fitIconToText', (t) => { // Ignores padding for top/bottom, since the icon is only stretched to the text's width but not height t.deepEqual(shaping.fitIconToText(shapedIcon, shapedText, 'width', [ 5, 10, 5, 10 ], [0, 0], 12 / glyphSize), { - image: shapedIcon.image, + imagePrimary: shapedIcon.imagePrimary, + imageSecondary: undefined, collisionPadding: undefined, top: -5, right: 20, @@ -284,7 +295,8 @@ test('fitIconToText', (t) => { t.test('icon-text-fit: height', (t) => { t.deepEqual(shaping.fitIconToText(shapedIcon, shapedText, 'height', [0, 0, 0, 0], [0, 0], 24 / glyphSize), { - image: shapedIcon.image, + imagePrimary: shapedIcon.imagePrimary, + imageSecondary: undefined, collisionPadding: undefined, top: -10, right: -10, @@ -293,7 +305,8 @@ test('fitIconToText', (t) => { }, 'preserves icon width, centers horizontally'); t.deepEqual(shaping.fitIconToText(shapedIcon, shapedText, 'height', [0, 0, 0, 0], [3, 7], 24 / glyphSize), { - image: shapedIcon.image, + imagePrimary: shapedIcon.imagePrimary, + imageSecondary: undefined, collisionPadding: undefined, top: -3, right: -7, @@ -302,7 +315,8 @@ test('fitIconToText', (t) => { }, 'preserves icon width, centers horizontally, applies offset'); t.deepEqual(shaping.fitIconToText(shapedIcon, shapedText, 'height', [0, 0, 0, 0], [0, 0], 12 / glyphSize), { - image: shapedIcon.image, + imagePrimary: shapedIcon.imagePrimary, + imageSecondary: undefined, collisionPadding: undefined, top: -5, right: 0, @@ -312,7 +326,8 @@ test('fitIconToText', (t) => { // Ignores padding for left/right, since the icon is only stretched to the text's height but not width t.deepEqual(shaping.fitIconToText(shapedIcon, shapedText, 'height', [ 5, 10, 5, 10 ], [0, 0], 12 / glyphSize), { - image: shapedIcon.image, + imagePrimary: shapedIcon.imagePrimary, + imageSecondary: undefined, collisionPadding: undefined, top: -10, right: 0, @@ -325,7 +340,8 @@ test('fitIconToText', (t) => { t.test('icon-text-fit: both', (t) => { t.deepEqual(shaping.fitIconToText(shapedIcon, shapedText, 'both', [0, 0, 0, 0], [0, 0], 24 / glyphSize), { - image: shapedIcon.image, + imagePrimary: shapedIcon.imagePrimary, + imageSecondary: undefined, collisionPadding: undefined, top: -10, right: 20, @@ -334,7 +350,8 @@ test('fitIconToText', (t) => { }, 'stretches icon to text width and height'); t.deepEqual(shaping.fitIconToText(shapedIcon, shapedText, 'both', [0, 0, 0, 0], [3, 7], 24 / glyphSize), { - image: shapedIcon.image, + imagePrimary: shapedIcon.imagePrimary, + imageSecondary: undefined, collisionPadding: undefined, top: -3, right: 23, @@ -343,7 +360,8 @@ test('fitIconToText', (t) => { }, 'stretches icon to text width and height, applies offset'); t.deepEqual(shaping.fitIconToText(shapedIcon, shapedText, 'both', [0, 0, 0, 0], [0, 0], 12 / glyphSize), { - image: shapedIcon.image, + imagePrimary: shapedIcon.imagePrimary, + imageSecondary: undefined, collisionPadding: undefined, top: -5, right: 10, @@ -352,7 +370,8 @@ test('fitIconToText', (t) => { }, 'stretches icon to text width and height, adjusts for textSize'); t.deepEqual(shaping.fitIconToText(shapedIcon, shapedText, 'both', [ 5, 10, 5, 10 ], [0, 0], 12 / glyphSize), { - image: shapedIcon.image, + imagePrimary: shapedIcon.imagePrimary, + imageSecondary: undefined, collisionPadding: undefined, top: -10, right: 20, @@ -361,7 +380,8 @@ test('fitIconToText', (t) => { }, 'stretches icon to text width and height, adjusts for textSize, includes padding'); t.deepEqual(shaping.fitIconToText(shapedIcon, shapedText, 'both', [ 0, 5, 10, 15 ], [0, 0], 12 / glyphSize), { - image: shapedIcon.image, + imagePrimary: shapedIcon.imagePrimary, + imageSecondary: undefined, collisionPadding: undefined, top: -5, right: 15,