首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >来自SVG的ImageBitmap -某些字体大小的锯齿状文本

来自SVG的ImageBitmap -某些字体大小的锯齿状文本
EN

Stack Overflow用户
提问于 2021-10-27 14:17:24
回答 1查看 414关注 0票数 1

我正在动态地创建包含透明背景上文本的SVG代码。SVG应该画在画布上,字体应该来自Google字体。

问题是:

虽然这种方法基本上是有效的,但一些字体大小显然会在createImageBitmap()中产生糟糕的alpha通道,从而导致可怕的锯齿状文本。

我在Windows 10和Ubuntu上都遇到了Chrome最新版本的问题。禁用Chrome的硬件加速不会改变任何事情。

输出图像:边缘锯齿状的文本

简而言之,代码就是这样做的:

  1. 生成SVG源代码,它在透明的背景上显示一些文本。
  2. 在SVG代码中,将指向外部内容(字体)的链接替换为相应的base64内容。
  3. 使用ImageBitmap从该SVG创建一个createImageBitmap()
  4. 在画布上画那个ImageBitmap。

代码语言:javascript
复制
function createSvg(bckgrColor1, bckgrColor2, w, h, fontSize) {
  return `
  <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
    xmlns:ev="http://www.w3.org/2001/xml-events" version="2" viewBox="0 0 ${w} ${h}" width="${w}" height="${h}">
    <style type="text/css">
      @font-face {
        font-family: 'Lobster';
        font-style: normal;
        font-weight: 400;
        font-display: swap;
        src: url(https://fonts.gstatic.com/s/lobster/v23/neILzCirqoswsqX9zoKmMw.woff2) format('woff2');
        unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
      }
    </style>
    <text x="0" y="50" font-family="Lobster" font-size="${fontSize}">
      Hello World!
    </text>
  </svg>`;
}

const _embedAssets = async function(svgSrc) {
  const _imageExtensions = ["png", "gif", "jpg", "jpeg", "svg", "bmp"];
  const _fontExtensions = ["woff2"];
  const _assetExtensions = [..._imageExtensions, ..._fontExtensions];
  /**
   * @uses https://stackoverflow.com/a/8943487/1273551
   * @license CC BY-SA 4.0.
   */
  const urlRegex = /(\bhttps?:\/\/[-A-Z0-9+&@#/%?=~_|!:,.;]*[-A-Z0-9+&@#/%=~_|])/gi;
  const allUrls = svgSrc.match(urlRegex);
  const assetUrls = allUrls.filter((url) =>
    _assetExtensions.some((extension) =>
      url.toLowerCase().endsWith(`.${extension}`)
    )
  );
  const assetBase64Fetcher = assetUrls.map(_fetchBase64AssetUrl);
  const assetFetcherResults = await Promise.all(assetBase64Fetcher);
  return assetFetcherResults.reduce(
    (svgSrc, x) => svgSrc.replace(x.url, x.base64),
    svgSrc
  );
};

// Fetch asset (image or font) and convert it to base64 string representation.
const _fetchBase64AssetUrl = async function(assetUrl) {
  return new Promise(async(resolve, reject) => {
    const resp = await fetch(assetUrl);
    const blob = await resp.blob();
    const reader = new FileReader();
    reader.onloadend = (event) => {
      const target = event.target;
      if (!target) {
        return reject(`Asset with URL "${assetUrl}" could not be loaded.`);
      }
      const result = target.result;
      if (!result) {
        return reject(`Asset with URL "${assetUrl}" returned an empty result.`);
      }
      resolve({
        url: assetUrl,
        base64: result.toString()
      });
    };
    reader.readAsDataURL(blob);
  });
};

const createImageBitmapFromSvg = async function(svgSrc) {
  return new Promise(async(resolve) => {
    const svgWithAssetsEmbedded = await _embedAssets(svgSrc);
    const svgBlob = new Blob([svgWithAssetsEmbedded], {
      type: "image/svg+xml;charset=utf-8"
    });
    const svgBase64 = URL.createObjectURL(svgBlob);
    let img = new Image();
    img.onload = async() => {
      const imgBitmap = await createImageBitmap(img);
      resolve(imgBitmap);
    };
    img.src = svgBase64;
  });
};

const renderCanvas = async function(canvas, svgSource, width, height, color) {
  canvas.width = width;
  canvas.height = height;
  let svgEmbedded = await _embedAssets(svgSource);
  let svgImageBitmap = await createImageBitmapFromSvg(svgEmbedded);
  let ctx = canvas.getContext("2d");
  if (ctx) {
    ctx.fillStyle = color;
    ctx.strokeStyle = "#000000";
    ctx.lineWidth = 2;
    ctx.fillRect(0, 0, canvas.width, canvas.height); //
    ctx.strokeRect(0, 0, canvas.width, canvas.height); //for white background
    ctx.drawImage(svgImageBitmap, 0, 0, canvas.width, canvas.height);
  }
};

const renderCanvasAlternative = async function(canvas, svgSource, width, height, color) {

  // create imagebitmap from raw svg code  
  let svgImageBitmap = await createImageBitmapFromSvg(svgSource);

  // temporary intermediate step as suggested on StackOverflow 
  const osc = await new OffscreenCanvas(width, height)
  let oscx = osc.getContext("bitmaprenderer")
  oscx.transferFromImageBitmap(svgImageBitmap);

  const svgImageBitmapFromOffscreenCanvas = osc.transferToImageBitmap();
  // const svgImageBitmapFromOffscreenCanvas2 = await createImageBitmap(osc); // results in empty bitmap

  // draw image bitmap on canvas
  canvas.width = width;
  canvas.height = height;
  let ctx = canvas.getContext("bitmaprenderer");
  if (!ctx) throw new Error("Could not get context from canvas.");
  ctx.transferFromImageBitmap(svgImageBitmapFromOffscreenCanvas);
}

const bootstrap = async() => {
  // width and height for svg and canvases
  const w = "300";
  const h = "80";

  // create two svg sources, only difference is fontsize of embedded font
  const svgSourceGood = createSvg("", "", w, h, 49);
  const svgSourceBad = createSvg("#990000", "", w, h, 48);

  // draw GOOD svg in canvas
  renderCanvasAlternative(
    document.getElementById("myCanvas01"),
    svgSourceGood,
    w,
    h,
    "green"
  );

  // draw BAD svg in canvas
  renderCanvasAlternative(
    document.getElementById("myCanvas02"),
    svgSourceBad,
    w,
    h,
    "red"
  );
};

bootstrap();
代码语言:javascript
复制
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Document</title>
</head>

<body>
  <div>SVG drawn in Canvas, Fontsize 49</div>
  <canvas id="myCanvas01"></canvas>
  <div>SVG drawn in Canvas, Fontsize 48</div>
  <canvas id="myCanvas02"></canvas>
  <script src="index.js"></script>
</body>

</html>

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-11-04 09:57:02

因为画布上下文也接受HTMLImageElement作为输入,所以在这里使用createImageBitmap()是多余的。相反,我们返回DOM加载的<img>本身,从而绕过createImageBitmap(),这显然会导致锯齿状的边缘。多亏了凯伊多。

代码语言:javascript
复制
const createImageFromSvg = async function(svgSrc: string): Promise < HTMLImageElement > {
  return new Promise(async resolve => {
    // replace assets with their base64 versions in svg source code 
    const svgWithAssetsEmbedded = await _embedAssets(svgSrc);

    // create blob from that
    const svgBlob = new Blob([svgWithAssetsEmbedded], {
      type: 'image/svg+xml;charset=utf-8'
    });

    // create URL that can be used in HTML (?)
    const svgBase64 = URL.createObjectURL(svgBlob);

    let img = new Image();
    img.onload = async() => {
      resolve(img);
    };
    img.src = svgBase64;
  });
};
票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/69740333

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档