11

My task is to change the icon-color of an icon-image in Mapbox. The only way mapbox allow to do this is by using sdf-icons(https://docs.mapbox.com/mapbox-gl-js/style-spec/layers/#paint-symbol-icon-color).

By Hour's of searching i couldn't found the easiest way to achieve this. There is an npm module I found is https://www.npmjs.com/package/image-sdf but after using its command on a png to convert it into sdf and then rendering on a map is not giving me finest results.

The command I am using

image-sdf cycle-initial.png --spread 5 --downscale 1 --color black > cycle.png

cycle-initial.png(INPUT) is below:

enter image description here

cycle.png(OUTPUT) is below:

enter image description here

But while using the cycle.png as an Image src is not giving the finest results.

enter image description here

Code snippet:

const img = new Image();
img.addEventListener('load', () => {
            this.mapInstance.addImage('circle-icon', img, { sdf: true });
        }, false);
img.src = cycle;

I request if anyone, please help me if I am doing anything wrong here, or is there any correct way to create sdf-icon to render correctly.

Dolly
  • 2,213
  • 16
  • 38

3 Answers3

30

After days of research, I think i found the solution. Also, I want to sum-up creation steps of SDF icon's.

First, thing first what is SDF in simple words: SDF is a raster format that is optimised for displaying resized, recoloured, and rotated raster images. They are single color.

Usage: This is my first interaction with SDF in MAPBOX.

Reason is we cannot change the color of icon(if svg or normal raster png) in symbol layer, you have to provide SDF(specialised png) format in order to achieve it.(https://docs.mapbox.com/mapbox-gl-js/style-spec/layers/#paint-symbol-icon-color)

How to create?

  1. Npm module: https://www.npmjs.com/package/image-sdf Command:
image-sdf input.png --spread 10 --downscale 1 --color black > output.png
  1. Imagemagick: https://imagemagick.org/script/download.php . ImageMagick allows you to do this with one command:
convert in.png -filter Jinc -resize 400% -threshold 30% \( +clone -negate -morphology Distance Euclidean -level 50%,-50% \) -morphology Distance Euclidean -compose Plus -composite -level 45%,55% -resize 25% out.png

UPDATE

  1. There is also third way of achieving this by using older tag of Maki Library, Where there is only one JS file sdf-render.js can convert your source svg to sdf icon. But please note that this Tag(v0.5.0) is using Older version of JSDOM which is not supported now, To execute this file you have to convert the code of this file using svgdom and @svgdotjs/svg.js modules.

More Detail's: Box is appearing around Icons in Mapbox on sdf = true

What i was doing wrong?

Answer: Before executing the image-sdf command on a png(Please note: it has to be black & white raster path). Resize(Increase) it to around 40-50%.

Like in my case, I resized the normal raster png and the result's were amazing. Have a look. (Please note: I didn't change a word in code and used the same image-sdf command)

enter image description here

Dolly
  • 2,213
  • 16
  • 38
  • 5
    Thanks for spending the time to share what you learnt! – Steve Bennett Aug 09 '20 at 13:02
  • Super helpful question & answer, thanks. Faced with the same issue, after LOTS of fiddling with the image-sdf parameters and the mapbox style specification parameters, I wasn't able to find a good resolution-size compromise. I did find a little hack though, I made the size right, and then added a 1px halo in the same color as the icon. This outline helped keep the details of the icon. – Alex Feb 22 '21 at 20:52
2

I wanted to share my own experiences generating SDF symbol images for Mapbox.

First I tried the npm module image-sdf, but it seems buggy. The values in the generated SDF don't transition smoothly across edges in the input image. (See this example image, which shows the input on the left, and the output from image-sdf -s 3 on the right. In a correctly generated SDF, the outline of the cross shape should not be perceptible at all.)

Next I tried @Dolly's ImageMagick solution. However, the command Dolly provided didn't generate the precise format needed by Mapbox.

  • Mapbox seems to only read the alpha channel of the PNG, so we need to append extra commands to copy the data to the alpha channel.
  • Dolly's solution generates SDFs where the pixel value of 128 acts as a "zero point" or threshold – pixels outside the shape have a value less than 128, while pixels within the shape have a value greater than 128. However, Mapbox appears to use 192 as its threshold. I confirmed this by inspecting some of the font glyph SDFs stored inside Mapbox's internal state, as well as looking inside Mapbox's (delightfully uncommented) GLSL files.

The following ImageMagick command line takes these issues into account:

convert in.png -filter Jinc -resize 400% -threshold 30% \( +clone -negate -morphology Distance Euclidean -level 50%,-50% \) -morphology Distance Euclidean -compose Plus -composite -level 45%,55% -resize 25% -alpha copy -fill black -colorize 100 -channel alpha -fx "a+0.25" out.png

The output is delightful.

Note that the input image should be a pure bitmask - the symbol itself in white, all other pixels black, with no antialiasing (no grays).

I hope this is useful to somebody!

Alan Thomas
  • 23
  • 1
  • 3
1

Another thing to explore is giving your image a size. I wanted to use an svg for my marker image, and changing:

let img = new Image();
//to
let img = new Image(256, 256);

Gave much better results-

chrismarx
  • 11,488
  • 9
  • 84
  • 97