Define the color of a SF symbols in drawcontext

I already know that I can define the color of a sfsymbles when add image to a stack by using tintcolor.
What I want to know is that how can I do the same in a drawcontext?

1 Like

I have looked for this as well but couldn’t find anything. I guess there is no way currently to do that. As an alternative I am using Unicode emojis.

So I haven’t needed this yet, but I took on the challenge and found a workaround. I’ve used the canvas element from the browser to decode the image from the SFSymbol and then just set the color values manually. After that I again used the canvas to convert it back to a PNG image. I’ve also annotated the function with JSDoc, so you get the correct types if you use an IDE. Just copy the function into your code to use it. I hope it is useful!

/**
 * source: https://talk.automators.fm/t/define-the-color-of-a-sf-symbols-in-drawcontext/9897/3
 * @param {Image} image The image from the SFSymbol
 * @param {Color} color The color it should be tinted with
 */
async function tintSFSymbol(image, color) {
  let html = `
  <img id="image" src="data:image/png;base64,${Data.fromPNG(image).toBase64String()}" />
  <canvas id="canvas"></canvas>
  `;
  
  let js = `
    let img = document.getElementById("image");
    let canvas = document.getElementById("canvas");
    let color = 0x${color.hex};

    canvas.width = img.width;
    canvas.height = img.height;
    let ctx = canvas.getContext("2d");
    ctx.drawImage(img, 0, 0);
    let imgData = ctx.getImageData(0, 0, img.width, img.height);
    // ordered in RGBA format
    let data = imgData.data;
    for (let i = 0; i < data.length; i++) {
      // skip alpha channel
      if (i % 4 === 3) continue;
      // bit shift the color value to get the correct channel
      data[i] = (color >> (2 - i % 4) * 8) & 0xFF
    }
    ctx.putImageData(imgData, 0, 0);
    canvas.toDataURL("image/png").replace(/^data:image\\/png;base64,/, "");
  `;
  
  let wv = new WebView();
  await wv.loadHTML(html);
  let base64 = await wv.evaluateJavaScript(js);
  return Image.fromData(Data.fromBase64String(base64));
}

// example
// you don't need to copy this
let sym = SFSymbol.named("icloud").image;
let col = Color.orange();
let res = await tintSFSymbol(sym, col);
await QuickLook.present(sym)
QuickLook.present(res)
2 Likes

Thank you! This works fine. Except that I faced an issue with the size of the image. The image size in Scriptable is defined in “Points” and when I pass it to WebView it is converted to “Pixels” (i.e. Points x Device.screenScale()) so my image is expanded 3 times (iPhone X).
Note that I am working with small image size (45px x 35px).
So, I tried sending the original size to tintSFSymbol function and put width and height attributes in the img tag as -

<img id="image" src="data:image/png;base64,${Data.fromPNG(image).toBase64String()}" width="${width}" height="{height}" />

but for some reason it didn’t work as expected. The image size returned is very small, almost a dot.

Finally, I resized the image returned from tintSFSymbol function as-

          const oW = symbolImage.size.width;
          const oH = symbolImage.size.height;
          const tintedSymbol = await tintSFSymbol(symbolImage, color));
          symbolCanvas=new DrawContext();
          symbolCanvas.opaque = false;
          symbolCanvas.size = new Size(oW,oH);
          const symbolRect = new Rect(0,0,oW,oH);
          symbolCanvas.drawImageInRect(tintedSymbol, symbolRect);
          const newSymbolImage = symbolCanvas.getImage();

I am not sure if this the ideal solution, but this works for me.

Yes, I’ve noticed that too. The initial width of the Symbol was for me something.5, which seemed very odd. Since the returned image was just scaled by the screen scale, I thought that it’s not a big deal, especially if you want to use it in a draw context where you can resize it while inserting as you did.

@Pih & @ajatkj, on this topic, there are two iOS shortcuts available on RoutineHub that you might find helpful:

How do these relate to the topic of setting up the symbols in Scriptable? Aren’t these only for Shortcuts use?

Directly, there’s a loose connection. SF Emoji Menu Builder includes some code that I thought might be helpful (it uses Scriptable). The other shortcut may be of no use, but not knowing the detailed requirements, I took a chance thinking it might help.