我如何构建了一个实际上根本看不到你的文件的图像转换器
Source: Dev.to
我厌倦了把图片上传到随机的在线转换器
并不是因为它们慢——虽然确实慢——而是每次把文件拖进这些网站时,我根本不知道文件在另一端会发生什么。服务条款总是三页长,且在恰到好处的地方含糊其辞。
“我们可能会使用上传的内容来改进我们的服务。”
当然可以。
于是我自己动手做了一个。在此过程中,我发现浏览器的能力远超大多数开发者对它的评价。
工作原理(内部实现)
服务器端转换的问题
大多数图像转换工具遵循相同的架构:
- 您将文件上传到他们的服务器。
- 他们的后端运行 ImageMagick(或类似的工具)。
- 转换后的文件被写入他们的存储。
- 您下载它。
- 他们会删除它……大概。
“大概”是我无法跨越的那一步。即使把隐私问题抛在一边,这种做法也存在真实的性能瓶颈:你的瓶颈是 上传速度,而不是设备本身的处理能力。现代笔记本可以在毫秒级完成 JPEG 编码。仅仅为了转码而让文件往返服务器,毫无意义。
我想要的替代方案很简单:在浏览器中、在用户自己的机器上完成全部操作。 不上传。不需要服务器。不再猜测。
浏览器实际上能做什么
结果是,能做的相当多。
现代浏览器提供原生的 Canvas API,能够处理图像的编码与解码。一个基础的 JPG → PNG 转换流程如下:
async function convertImage(file, targetFormat) {
const bitmap = await createImageBitmap(file);
const canvas = new OffscreenCanvas(bitmap.width, bitmap.height);
const ctx = canvas.getContext('2d');
ctx.drawImage(bitmap, 0, 0);
const blob = await canvas.convertToBlob({
type: `image/${targetFormat}`,
quality: 0.92,
});
return blob;
}这就是全部过程。文件从用户磁盘进入浏览器内存,重新编码后作为下载返回——期间没有任何网络请求。
提示: 在转换文件时打开 DevTools 的 Network(网络)面板,你会看到没有任何上传请求。这不是政策,而是技术现实。
更有趣的地方:HEIC
JPG、PNG 和 WebP 相对简单,因为浏览器原生支持它们。HEIC(High Efficiency Image Container)则是另一番景象。
- HEIC 是自 iOS 11 起 Apple 的默认照片格式。
- 与 JPG 在相同质量下提供更好的压缩,并支持深度图、Live Photos 等。
- Windows 和大多数网络服务都不知道如何处理它——把 HEIC 上传到互联网上一半的网站都会报错。
浏览器并不原生解码 HEIC,因此我需要采用另一种方式:WebAssembly。
import { libheif } from 'libheif-js';
async function decodeHEIC(file) {
const decoder = new libheif.HeifDecoder();
const buffer = await file.arrayBuffer();
const data = new Uint8Array(buffer);
const images = decoder.decode(data);
const image = images[0];
const width = image.get_width();
const height = image.get_height();
const pixelData = await new Promise((resolve, reject) => {
image.display(
{ data: new Uint8ClampedArray(width * height * 4), width, height },
(result) => {
if (!result) reject(new Error('HEIC decode failed'));
else resolve(result);
}
);
});
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
const imageData = new ImageData(pixelData.data, width, height);
ctx.putImageData(imageData, 0, 0);
return new Promise((resolve) =>
canvas.toBlob(resolve, 'image/jpeg', 0.92)
);
}WASM 模块(libheif-js)在浏览器标签页中直接运行编译好的 C++ HEIC 解码器。这是真正的图像处理——解码 Apple 的专有格式——使用用户的 CPU,完全不涉及服务器。
第一次让它跑通时,我觉得有点荒唐。你基本上是在浏览器标签页里运行本地代码。但这正是 WebAssembly 的用途所在。
性能现实
有一点我没预料到:本地处理往往 更快,不仅仅是出于隐私考虑。
| 场景 | 典型耗时(现代机器) |
|---|---|
| 基于 Canvas 的 JPG → PNG(3–5 MB) |
| 100–300 ms | | HEIC → JPEG via WASM(大型 iPhone 照片) | 1–3 s | | Server‑side(上传 + 处理 + 下载) | > 3 s(取决于连接) |
当服务器端占优势的情况是批量处理大量大型文件时,此时服务器硬件的并行化能力起关键作用。对于一次性转换,本地处理更有优势。
我对浏览器能力的认识
构建这个功能改变了我对哪些工作应该放在服务器上的看法。很多工具会说“上传到我们的服务器,我们来处理”,但其实并不一定需要这么做。浏览器具备:
- 功能强大的 2‑D Canvas API,内置编解码支持。
- 用于计算密集型任务的 WebAssembly。
- 用于读取本地文件的 File API 和 FileReader。
- 用于在不将所有内容加载到内存的情况下处理大文件的 Streams API。
把文件发送到服务器的冲动往往是习惯,而非必要。只要用户已经在本地拥有文件并且只需要转换,完全可以认真考虑本地处理。
接下来的计划
我接下来要添加的格式是 AVIF。相较于 WebP,它的压缩提升是真实可见的——测试显示在相同质量下,摄影内容的文件大小可减少 20–30 %。挑战在于 AVIF 编码比 WebP 更加耗算,但采用相同的浏览器端方案(Canvas + WASM),仍然可以在本地完成,而无需离开用户的机器。
Overview
WASM 方法需要更仔细的优化,以避免让用户等待。
当前工作
- 批量转换 使用合适的队列。
- 更好地处理异常的颜色配置文件(例如,新款 iPhone 的宽色域图像,在某些情况下会表现异常)。
演示
如果您想看到实际效果,请访问我构建的实时工具 imageconvert.website。
该站点包括:
- 转换器
- HEIC 工具
- 压缩器
所有这些组件都实现了上述工作流程。
欢迎在评论中随时提问有关实现细节的问题!