给Meta2d 提交了一个 PR

问题引入

先附送上PR链接:github.com/le5le-com/m...

今日在开发公司项目需求的时候发现一个问题,在Mac m1下使用Meta2d内置的toPng方法绘制png图片,在高清屏下会导致图片模糊。具体糊成啥样,请看VCR:

毕竟项目是需要面向客户的,拿出这样的图,作为一个有强迫症的coder,肯定是不能容忍的!!!于是就带着问题去翻看了@meta2d/core的源码。

产生原因分析

Meta2d源码对于toPng源码实现如下(仅截取主函数):

ini 复制代码
toPng(padding: Padding = 2, callback?: BlobCallback, containBkImg = false) {
  const rect = getRect(this.store.data.pens);
  if (!isFinite(rect.width)) {
    throw new Error('can not to png, because width is not finite');
  }
  const oldRect = deepClone(rect);
  const storeData = this.store.data;
  // TODO: 目前背景颜色优先级更高
  const isDrawBkImg =
    containBkImg && !storeData.background && this.store.bkImg;
  // 主体在背景的右侧,下侧
  let isRight = false,
    isBottom = false;
  if (isDrawBkImg) {
    rect.x += storeData.x;
    rect.y += storeData.y;
    calcRightBottom(rect);
    if (rectInRect(rect, this.canvasRect, true)) {
      // 全部在区域内,那么 rect 就是 canvasRect
      Object.assign(rect, this.canvasRect);
    } else {
      // 合并区域
      const mergeArea = getRectOfPoints([
        ...rectToPoints(rect),
        ...rectToPoints(this.canvasRect),
      ]);
      Object.assign(rect, mergeArea);
    }
    isRight = rect.x === 0;
    isBottom = rect.y === 0;
  }

  // 有背景图,也添加 padding
  const p = formatPadding(padding);
  rect.x -= p[3];
  rect.y -= p[0];
  rect.width += p[3] + p[1];
  rect.height += p[0] + p[2];
  calcRightBottom(rect);

  const canvas = document.createElement('canvas');
  canvas.width = rect.width;
  canvas.height = rect.height;
  if (
    canvas.width > 32767 ||
    canvas.height > 32767 ||
    (!navigator.userAgent.includes('Firefox') &&
      canvas.height * canvas.width > 268435456) ||
    (navigator.userAgent.includes('Firefox') &&
      canvas.height * canvas.width > 472907776)
  ) {
    throw new Error(
      'can not to png, because the size exceeds the browser limit'
    );
  }
  const ctx = canvas.getContext('2d');

  ctx.textBaseline = 'middle'; // 默认垂直居中
  if (isDrawBkImg) {
    const x = rect.x < 0 ? -rect.x : 0;
    const y = rect.y < 0 ? -rect.y : 0;
    ctx.drawImage(
      this.store.bkImg,
      x,
      y,
      this.canvasRect.width,
      this.canvasRect.height
    );
  }
  const background =
    this.store.data.background || this.store.options.background;
  if (background) {
    // 绘制背景颜色
    ctx.save();
    ctx.fillStyle = background;
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    ctx.restore();
  }
  if (!isDrawBkImg) {
    ctx.translate(-rect.x, -rect.y);
  } else {
    // 平移画布,画笔的 worldRect 不变化
    ctx.translate(
      isRight ? storeData.x : -oldRect.x,
      isBottom ? storeData.y : -oldRect.y
    );
  }
  for (const pen of this.store.data.pens) {
    // 不使用 calculative.inView 的原因是,如果 pen 在 view 之外,那么它的 calculative.inView 为 false,但是它的绘制还是需要的
    if (!isShowChild(pen, this.store) || pen.visible == false) {
      continue;
    }
    // TODO: hover 待考虑,若出现再补上
    const { active } = pen.calculative;
    pen.calculative.active = false;
    if (pen.calculative.img) {
      renderPenRaw(ctx, pen);
    } else {
      renderPen(ctx, pen);
    }
    pen.calculative.active = active;
  }
  if (callback) {
    canvas.toBlob(callback);
    return;
  }
  return canvas.toDataURL();
}

通过阅读源码,发现toPng实现的核心是canvas,猛然间发现了问题出现的原因!canvas绘制图片,必然得跟dpi联系,就跟绿茶配青梅,天生是一对。

使用 ​​canvas​​ 绘制图片或者是文字在 Retina 屏中会非常模糊。究其原因​,canvas​​​ 不是矢量图,而是像图片一样是位图模式的。高 ​​dpi​​ 显示设备意味着每平方英寸有更多的像素。也就是说二倍屏,浏览器就会以 2 个像素点的宽度来渲染一个像素,该 ​​canvas​​ 在 Retina 屏幕下相当于占据了2倍的空间,相当于图片被放大了一倍,因此绘制出来的图片文字等会变模糊。

因此,要做 Retina 屏适配,关键是知道当前屏幕的设备像素比,然后将 ​​canvas​​​ 放大到该设备像素比来绘制,然后将 ​​canvas​​ 压缩到一倍来展示。

解决办法

既然知道了问题产生的原因,解决起来也就很好办了。在浏览器的 ​​window​​​ 对象中有一个 ​​devicePixelRatio​​ 的属性,该属性表示了屏幕的设备像素比,即用几个(通常是 2 个)像素点宽度来渲染 1 个像素。所以我们只需要根据实际渲染倍率来缩放canvas,即可解决如上问题。

注意基础知识点:

要设置 ​​canvas​​​ 的画布大小,使用的是 ​​canvas.width​​和 ​​canvas.height​​

要设置画布的实际渲染大小,使用的 ​​style​​​ 属性或 ​​CSS​​ 设置的 ​​width​​ 和 ​​height​​,只是简单的对画布进行缩放。

如上所诉,针对meta2d源码,笔者做了如下改动:

修改了toPng函数之后,在本地测试,效果如下:

肉眼可见的变清晰了不少!

改动完成之后,顺带给Meta2d官网提交了PR,期待官方的merge

相关推荐
可问春风_ren17 分钟前
前端文件上传详细解析
前端·ecmascript·reactjs·js
羊小猪~~1 小时前
【QT】--文件操作
前端·数据库·c++·后端·qt·qt6.3
晚风资源组2 小时前
CSS文字和图片在容器内垂直居中的简单方法
前端·css·css3
Miketutu2 小时前
Flutter学习 - 组件通信与网络请求Dio
开发语言·前端·javascript
光影少年4 小时前
前端如何调用gpu渲染,提升gpu渲染
前端·aigc·web·ai编程
Surplusx5 小时前
运用VS Code前端开发工具完成网页头部导航栏
前端·html
小宇的天下5 小时前
Calibre 3Dstack --每日一个命令day13【enclosure】(3-13)
服务器·前端·数据库
一只小bit6 小时前
Qt 文件:QFile 文件读写与管理教程
前端·c++·qt·gui
午安~婉6 小时前
整理知识点
前端·javascript·vue
军军君016 小时前
Three.js基础功能学习十二:常量与核心
前端·javascript·学习·3d·threejs·three·三维