Fabric.js 导出图片模糊解决方案

0 阅读4分钟

前言

在使用 Fabric.js 开发图像编辑应用时,我们经常需要将画布内容导出为图片。然而,很多开发者会遇到一个常见问题:导出的图片看起来模糊不清,特别是在高分辨率屏幕上。

这个问题主要有两个原因:

  1. 设备像素比(DPR)问题:在高分辨率屏幕(如Retina显示器)上,物理像素密度更高,导致图像看起来模糊
  2. Canvas尺寸问题:Canvas的实际像素尺寸与显示尺寸不匹配

本文将详细介绍如何解决这些问题,并提供三种不同的方法来导出高质量、自定义尺寸的图片。

导出图片的方法

Fabric.js 提供了 toDataURL() 方法来将画布内容导出为图片。这个方法的基本用法如下:

// 基本用法
const dataURL = canvas.toDataURL();

// 带参数的用法
const dataURL = canvas.toDataURL({
  format: 'png',  // 'png' 或 'jpeg'
  quality: 1,     // 0-1 之间,仅对JPEG有效
  multiplier: 1   // 输出尺寸的倍数
});

理解 toDataURL 的工作原理

toDataURL() 方法实际上是调用了原生 Canvas 的 HTMLCanvasElement.toDataURL() 方法。这个方法会将当前 Canvas 的内容转换为 base64 编码的数据 URL。

默认情况下,输出图片的尺寸与 Canvas 的实际像素尺寸相同,而不是其显示尺寸。这就是为什么在高分辨率屏幕上,如果不做特殊处理,导出的图片会看起来模糊。

为什么导出的图片会模糊?

  1. 设备像素比问题:在高分辨率屏幕上,一个 CSS 像素对应多个物理像素。例如,在 Retina 屏幕上,设备像素比(DPR)为 2,意味着 1 个 CSS 像素对应 4 个物理像素(2×2)。

  2. Canvas 尺寸问题:Canvas 有两种尺寸:

    • CSS 尺寸:通过 CSS 样式设置的显示尺寸
    • 画布尺寸:通过 JavaScript 设置的实际像素尺寸

    如果这两者不匹配,就会导致模糊问题。

如何调整导出图片的像素

下面介绍三种方法来解决导出图片模糊的问题,并实现自定义输出尺寸。

方法一:使用 multiplier 参数

最简单的方法是使用 toDataURL() 方法的 multiplier 参数,它可以按比例放大输出图像。

// 获取设备像素比
const dpr = window.devicePixelRatio || 1;

// 计算所需的倍数
const targetWidth = 2000;
const multiplier = targetWidth / canvas.getWidth();

// 导出大图
const dataURL = canvas.toDataURL({
  format: 'png',
  quality: 1,
  multiplier: multiplier
});

优点

  • 实现简单,代码量少
  • 不需要修改原始 Canvas

缺点

  • 对于非常大的尺寸,可能会受到浏览器内存限制
  • 不能精确控制输出图像的宽高比

方法二:临时调整 Canvas 尺寸

这种方法通过临时调整 Canvas 的实际尺寸,导出后再恢复原始尺寸:

function exportLargeImage(fabricCanvas, targetWidth, targetHeight) {
  // 保存原始尺寸和内容
  const originalWidth = fabricCanvas.getWidth();
  const originalHeight = fabricCanvas.getHeight();
  const originalZoom = fabricCanvas.getZoom();
  const json = fabricCanvas.toJSON();
  
  // 计算缩放比例
  const scaleX = targetWidth / originalWidth;
  const scaleY = targetHeight / originalHeight;
  const scale = Math.max(scaleX, scaleY);
  
  // 临时调整canvas尺寸
  fabricCanvas.setWidth(targetWidth);
  fabricCanvas.setHeight(targetHeight);
  fabricCanvas.setZoom(originalZoom * scale);
  
  // 重新加载内容,但尺寸更大
  fabricCanvas.loadFromJSON(json, function() {
    // 缩放所有对象
    fabricCanvas.getObjects().forEach(obj => {
      obj.set({
        scaleX: obj.scaleX * scale,
        scaleY: obj.scaleY * scale,
        left: obj.left * scale,
        top: obj.top * scale
      });
    });
    
    fabricCanvas.renderAll();
    
    // 导出大图
    const dataURL = fabricCanvas.toDataURL({format: 'png'});
    
    // 恢复原始尺寸和内容
    fabricCanvas.setWidth(originalWidth);
    fabricCanvas.setHeight(originalHeight);
    fabricCanvas.setZoom(originalZoom);
    fabricCanvas.loadFromJSON(json, function() {
      fabricCanvas.renderAll();
    });
    
    // 使用dataURL...
  });
}

优点

  • 可以精确控制输出尺寸
  • 适合需要特定尺寸的场景

缺点

  • 代码复杂度高
  • 需要临时修改 Canvas 状态
  • 对于非常大的尺寸,可能会有性能问题

方法三:使用离屏 Canvas

对于非常大的图像,使用离屏 Canvas 可能更高效:

function exportVeryLargeImage(fabricCanvas, targetWidth, targetHeight) {
  // 创建离屏canvas
  const multiplier = Math.max(targetWidth / fabricCanvas.getWidth(), targetHeight / fabricCanvas.getHeight());
  
  // 使用toCanvasElement获取高分辨率的canvas元素
  return fabricCanvas.toCanvasElement(multiplier).then(canvasEl => {
    // 创建目标尺寸的canvas
    const finalCanvas = document.createElement('canvas');
    finalCanvas.width = targetWidth;
    finalCanvas.height = targetHeight;
    const ctx = finalCanvas.getContext('2d');
    
    // 计算居中位置
    const scaledWidth = fabricCanvas.getWidth() * multiplier;
    const scaledHeight = fabricCanvas.getHeight() * multiplier;
    const x = (targetWidth - scaledWidth) / 2;
    const y = (targetHeight - scaledHeight) / 2;
    
    // 在目标canvas上绘制
    ctx.fillStyle = fabricCanvas.backgroundColor || '#ffffff';
    ctx.fillRect(0, 0, targetWidth, targetHeight);
    ctx.drawImage(
      canvasEl, 
      0, 0, canvasEl.width, canvasEl.height,
      x, y, scaledWidth, scaledHeight
    );
    
    // 导出最终图像
    return finalCanvas.toDataURL('image/png');
  });
}

优点

  • 可以处理非常大的图像
  • 可以精确控制输出尺寸和位置
  • 性能较好,不会阻塞主线程太久

缺点

  • 代码相对复杂
  • 需要理解 Canvas API

总结

在使用 Fabric.js 导出图片时,为了解决模糊问题并实现自定义尺寸,我们可以采用以下三种方法:

  1. 使用 multiplier 参数:简单易用,适合一般场景
  2. 临时调整 Canvas 尺寸:可以精确控制输出尺寸,但代码复杂
  3. 使用离屏 Canvas:适合导出非常大的图像,性能较好

选择哪种方法取决于你的具体需求:

  • 如果只是解决高分辨率屏幕上的模糊问题,使用方法一就足够了
  • 如果需要导出特定尺寸的图像,方法二或方法三更合适
  • 如果需要导出非常大的图像,方法三是最佳选择

无论使用哪种方法,都要注意浏览器的内存限制。对于非常大的图像,可能需要考虑在服务器端处理或分段处理。

通过正确使用这些方法,你可以确保从 Fabric.js 导出的图像既清晰又符合你的尺寸要求。


参考资料