Skip to content

Incorrect rendering when SDF and non-SDF images are together on the same layer #13477

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
airbreather opened this issue May 19, 2025 · 3 comments

Comments

@airbreather
Copy link

mapbox-gl-js version: Found in 3.3.0, confirmed present in 3.12.0

browser: Tested in Firefox and Chrome

Steps to Trigger Behavior

  1. Host the included index.html, dot1.png, and dot2.png static files in a "real" HTTP server (python -m http.server is my tool of choice).
  2. Navigate to index.html
  3. Pass in your access token
  4. Observe: two dots, each with a different color.
  5. Click the zoom-out button in the bottom-right.
  6. Observe: now one of the dots is white and fuzzy, even though it's nowhere near the other.
  7. Click the zoom-in button in the bottom-right.
  8. Observe: we're back to the original colors.

Link to Demonstration

dot1.png
dot2.png

index.html (click to expand)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Repro</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
<link href="https://api.mapbox.com/mapbox-gl-js/v3.12.0/mapbox-gl.css" rel="stylesheet">
<script src="https://api.mapbox.com/mapbox-gl-js/v3.12.0/mapbox-gl.js"></script>
<style>
body { margin: 0; padding: 0; }
#map { position: absolute; top: 0; bottom: 0; width: 100%; }
</style>
</head>
<body>
<div id="map"></div>
<img id="dot1" src="dot1.png" style="display:none;" />
<img id="dot2" src="dot2.png" style="display:none;" />
<script>
    mapboxgl.accessToken = prompt('access token');
    const map = new mapboxgl.Map({
        container: 'map',
        style: 'mapbox://styles/mapbox/streets-v12',
        zoom: 15.1,
        center: [-83.04421, 42.33034],
    });
    map.addControl(new mapboxgl.NavigationControl(), 'bottom-right');
    map.on('style.load', () => {
        map.addImage('dot1', document.getElementById('dot1'));
        map.addImage('dot2', document.getElementById('dot2'), { sdf: true });
        map.addSource('dots', {
            type: 'geojson',
            data: {
                type: 'FeatureCollection',
                features: [
                    { type: 'Feature', geometry: { type: 'Point', coordinates: [-83.040210, 42.330340] }, properties: { icon: 'dot1' } },
                    { type: 'Feature', geometry: { type: 'Point', coordinates: [-83.047038, 42.332267] }, properties: { icon: 'dot2' } },
                ],
            },
        });
        map.addLayer({
            id: 'dots',
            source: 'dots',
            type: 'symbol',
            layout: { 'icon-image': ['get', 'icon'] },
            paint: { 'icon-color': '#FF0000' },
        });
    });
</script>
</body>
</html>

Expected Behavior

The dots should both be drawable at the same time. Of course, in the actual use case, there are much more dots, and I want to be able to overlap them in any order, independent of which image is used.

Actual Behavior

One of the dots gets fuzzy sometimes.

@ibesora
Copy link
Contributor

ibesora commented May 20, 2025

Running your example and zooming out, you can see in the console that there's a warning about mixing sdf and non-sdf icons on the same buffer.
When you are zoomed in, each GeoJSON feature is on it's own tile and this is not a problem because we have different buffers for every tile. When you zoom out though, both features are on the same tile and thus sdf and non-sdf icons are mixed in the same buffer.

A workaround for this would be to split the layer in two, one for sdf icons and the other for non-sdf icons.
This code below does fix the issue.

const map = new mapboxgl.Map({
        container: 'map',
        style: 'mapbox://styles/mapbox/streets-v12',
        zoom: 15.1,
        center: [-83.04421, 42.33034],
    });
    map.addControl(new mapboxgl.NavigationControl(), 'bottom-right');
    map.on('style.load', () => {
        map.addImage('dot1', document.getElementById('dot1'));
        map.addImage('dot2', document.getElementById('dot2'), { sdf: true });
        map.addSource('dots1', {
            type: 'geojson',
            data: {
                type: 'FeatureCollection',
                features: [
                    { type: 'Feature', geometry: { type: 'Point', coordinates: [-83.040210, 42.330340] }, properties: { icon: 'dot1' } },
                ],
            },
        });
        map.addSource('dots2', {
            type: 'geojson',
            data: {
                type: 'FeatureCollection',
                features: [
                    { type: 'Feature', geometry: { type: 'Point', coordinates: [-83.047038, 42.332267] }, properties: { icon: 'dot2' } },
                ],
            },
        });
        map.addLayer({
            id: 'dots1',
            source: 'dots1',
            type: 'symbol',
            layout: { 'icon-image': ['get', 'icon'] },
        });
        map.addLayer({
            id: 'dots2',
            source: 'dots2',
            type: 'symbol',
            layout: { 'icon-image': ['get', 'icon'] },
            paint: { 'icon-color': '#FF0000' },
        });
    });

Image

@airbreather
Copy link
Author

airbreather commented May 20, 2025

A workaround for this would be to split the layer in two, one for sdf icons and the other for non-sdf icons.

Yes, but it's not quite enough — my use case sets 'icon-allow-overlap': true and wants to be able to choose the order of some locations relative to others independently of their underlying images. If there are ever two icons A (sdf) and B (non-sdf) where I want to have some A draw on top of B and some B draw on top of A, then I would have to create more layers.

At this moment, our application does recoloring by creating copies of the relevant images instead of SDF (though that was mostly because the previous folks never could figure out how to make acceptable SDF files from our SVGs), and smooth resizing is handled by oversizing the PNG files to the maximum allowed size and then scaling them down, so the main impact for now is just that I need to carry over all the overhead and baggage from that into a refactor that I'm working on.

@ibesora
Copy link
Contributor

ibesora commented May 26, 2025

I've opened an internal ticket to tackle this but it will require some work and it hasn't yet been prioritized. I'm afraid there's no workaround I can suggest other than using the same style of icon everywhere.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants