给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

相关推荐
y先森5 小时前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3
前端Hardy5 小时前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu10830189115 小时前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
IT女孩儿6 小时前
CSS查缺补漏(补充上一条)
前端·css
吃杠碰小鸡7 小时前
commitlint校验git提交信息
前端
虾球xz7 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇8 小时前
HTML常用表格与标签
前端·html
疯狂的沙粒8 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员8 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
野槐8 小时前
前端图像处理(一)
前端