简体   繁体   中英

Generate action icon from svg image?

I'm trying make my action icon dynamic by manipulating it with offscreenCanvas() :

service_worker.js

const iconFile = `icon.png`;
const canvas = new OffscreenCanvas(24, 24),
      ctx = canvas.getContext("2d");

fetch(iconFile)
.then(r => r.blob())
.then(createImageBitmap)
.then(img => ctx.drawImage(img, 0, 0))
.then(() =>
{
  ctx.fillStyle = 'lightgreen';
  ctx.fillRect(0, canvas.height-9, canvas.width, 9);
  chrome.action.setIcon({imageData: ctx.getImageData(0, 0, 24, 24)});
});

This works fine, however when I attempt use a SVG image instead of PNG , the createImageBitmap() returns error:

DOMException: The source image could not be decoded

manifest.json

{
  "manifest_version": 3,
  "name": "Test extension",
  "author": "test",
  "description": "Test",
  "version": "0.0.1",

  "permissions":[],
  "action": {},
  "background": {
    "service_worker": "service_worker.js"
  }
}

icon.svg

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#00864b" d="M8.1 18.8.7 11.4l2.1-2.2 5.3 5.3L21.4 1.2l2.2 2.1z"/></svg>

Any suggestions how to use SVG image in offscreenCanvas in service workers?

If your the SVG images in your Blobs have absolute width and height , they should be supported by createImageBitmap natively. However, currently no browser supports this feature, and while I made a polyfill for the main thread, it won't work in a Worker where implementers are very reluctant to add an SVG parser.

So one could have hoped that injecting some scripts into the current tab would have made it.

async function execScript() {
  const [tab] = await chrome.tabs.query({active: true, currentWindow: true});
  const bmp = await chrome.scripting.executeScript({
    target: {tabId: tab.id},
    func: grabImageBitmap,
    args: [the_url]
  });
  const canvas = new OffscreenCanvas(bmp.width, bmp.height);
  // ... do whatever with the bitmap
}

function grabImageBitmap(url) {
  const img = new Image();
  img.src = url;
  // we can't use img.decode() here, doesn't work with SVG...
  return new Promise((res, rej) => {
    img.onload = async (evt) => {
      const bmp = await createImageBitmap(img);
      res(bmp);
    };
    img.onerror = rej;
  });
}

But Chrome extensions still use JSON serialization for their various messaging APIs ( BUG 248548 ) and there is thus no way (that I found) of transferring the ImageBitmap from one context to the other in MV3.


So you will have to parse the SVG image yourself and convert it to Canvas2D commands.
For this task, a library like canvg may come handy. They're in the area for a long time, seem to do a pretty good job, and do work in Workers... after some tweaks.
They only provide an UMD version of their library, so you'd have to build it to ESM yourself, and also bring a DOMParser implementation (canvg uses xmldom, you can make your build export it too).
Then, you have to declare your background script as a module and you can import it all in there:

import { Canvg, presets, DOMParser } from "your_custom_build.js";
const preset = presets.offscreen({ DOMParser });

async function execScript() {
  const req = await fetch(the_url);
  const markup = req.ok && await req.text();
  const canvas = new OffscreenCanvas(width, height);
  const ctx = canvas.getContext("2d");

  const renderer = await Canvg.from(ctx, markup, preset);
  renderer.render();
  // The SVG image has been drawn on your context
}

But if you don't want to do all these builds, and add the 300KB of lib necessary for all this, AND if you have only icons as simple as the one in your example, which consist of only <path> elements, you can certainly come up with your own parser, or even avoid the SVG format entirely and instead store the data as JSON. The Path2D constructor can take an SVG path declaration string as input, which allows us to generate simple assets in JSON format:

 (async () => { // You'd use the OffscreenCanvas() constructor in background.js const ctx = document.querySelector("canvas").getContext("2d"); // You'd use an actual URL to a JSON file const JSONURL = `data:application/json,${ encodeURIComponent(JSON.stringify( [ { "d": "M8.1 18.8.7 11.4l2.1-2.2 5.3 5.3L21.4 1.2l2.2 2.1z", "fill": "#00864b", "transform": "matrix(2, 0, 0, 2, 20, 20)", "stroke": "red", "strokeWidth": 3 }, // Add more if needed ] )) }`; const resp = await fetch(JSONURL); const pathes = resp.ok && await resp.json(); for (const path of pathes) { const pathObject = new Path2D(path.d); if (path.transform) { // as a CSSMatrixFunction ctx.setTransform(new DOMMatrix(path.transform)); } if (path.stroke) { ctx.lineWidth = path.strokeWidth?? 1; ctx.strokeStyle = path.stroke; ctx.stroke(pathObject); } if (path.fill) { ctx.fillStyle = path.fill; ctx.fill(pathObject); } // Add more features if wanted } })().catch(console.error);
 <canvas></canvas>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM