前言
在使用 Fabric.js 开发图像编辑应用时,我们经常需要将画布内容导出为图片。然而,很多开发者会遇到一个常见问题:导出的图片看起来模糊不清,特别是在高分辨率屏幕上。
这个问题主要有两个原因:
- 设备像素比(DPR)问题:在高分辨率屏幕(如Retina显示器)上,物理像素密度更高,导致图像看起来模糊
- 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 的实际像素尺寸相同,而不是其显示尺寸。这就是为什么在高分辨率屏幕上,如果不做特殊处理,导出的图片会看起来模糊。
为什么导出的图片会模糊?
-
设备像素比问题:在高分辨率屏幕上,一个 CSS 像素对应多个物理像素。例如,在 Retina 屏幕上,设备像素比(DPR)为 2,意味着 1 个 CSS 像素对应 4 个物理像素(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 导出图片时,为了解决模糊问题并实现自定义尺寸,我们可以采用以下三种方法:
- 使用 multiplier 参数:简单易用,适合一般场景
- 临时调整 Canvas 尺寸:可以精确控制输出尺寸,但代码复杂
- 使用离屏 Canvas:适合导出非常大的图像,性能较好
选择哪种方法取决于你的具体需求:
- 如果只是解决高分辨率屏幕上的模糊问题,使用方法一就足够了
- 如果需要导出特定尺寸的图像,方法二或方法三更合适
- 如果需要导出非常大的图像,方法三是最佳选择
无论使用哪种方法,都要注意浏览器的内存限制。对于非常大的图像,可能需要考虑在服务器端处理或分段处理。
通过正确使用这些方法,你可以确保从 Fabric.js 导出的图像既清晰又符合你的尺寸要求。
参考资料: