mcpField
W
web-files-image-handling

web-files-image-handling

github.com/agents-inc开发2666 次安装2 天前更新
代码仓库

关于 web-files-image-handling

web-files-image-handling 是一项关于浏览器图片预览与 Canvas 处理的指南技能,面向 Web 开发者。它分别提供显示和转换的做法,帮助避免方向、内存及画质问题。

主要功能

  1. 01创建并清理对象 URL 预览
  2. 02使用 Canvas 调整尺寸和压缩
  3. 03处理 EXIF 方向
  4. 04多次缩小图片
  5. 05裁剪与格式转换

适用场景

  1. 01在 Web 应用中预览所选图片
  2. 02上传前调整或压缩图片
  3. 03生成缩略图或裁剪图片
  4. 04规范化导出图片数据的方向

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/agents-inc/skills --skill web-files-image-handling

安装后在 Claude Code 中输入/web-files-image-handling确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

metadata.yaml
reference.md
examples/
canvas.md
core.md
preview.md

详细介绍

图像处理模式

快速指南: 两项不同的任务,需要使用两种不同的工具。显示图像时,使用 URL.createObjectURL(),之后再撤销对象 URL——浏览器已经会应用 EXIF 旋转,因此 手动旋转会导致旋转两次。处理图像时,使用 Canvas:将尺寸限制在 4096px,缩小幅度超过 50% 时分多次缩放,写入 JPEG 前先填充白色,并优先使用异步的 toBlob(),而不是 toDataURL()。

详细资源:

  • examples/core.md — 共享的 loadImage 辅助函数、预览 hook 和组件、尺寸提取与验证、EXIF 解析、图库状态
  • examples/preview.md — 并行生成缩略图组、图库网格
  • examples/canvas.md — 缩放流程、目标大小压缩、裁剪、水印、滤镜
  • reference.md — 格式与策略选择、常量、方法对比、EXIF 值、浏览器支持

适用路径

这两种路径对 EXIF 的处理方式不同,而大多数 bug 都出在这里。

  • 向用户显示图像 — 将对象 URL 直接传给 <img>,替换图像或卸载组件时撤销 URL。浏览器会自动旋转;无需处理方向。请从 examples/core.md 开始。
  • 处理图像后再发送到其他地方 — 使用 Canvas 缩放、压缩、转换或裁剪。这里不会自动旋转,因此要显式规范方向,并且显示结果时务必禁止浏览器自行旋转。请从 examples/canvas.md 开始。

<critical_requirements>

编写图像处理代码前

撤销每个对象 URL——卸载时,以及创建替代对象 URL 之前。 每个 createObjectURL 都会将其 blob 固定在内存中,直到被撤销;因此,如果用户改变主意,更换 选取的文件,每次尝试都会泄漏一张完整图像。

处理方向前,先确定你采用哪种路径。 自 2020 年起,浏览器默认使用 image-orientation: from-image,因此为显示而规范方向会导致图像旋转两次; 只对要离开浏览器的字节数据进行方向规范化。

将 canvas 尺寸限制在 4096px。 超过该尺寸时,canvas 可能静默失败或导致标签页崩溃, 而移动设备上的限制还要更低。

缩小幅度超过一半时,分多次缩放。 单次降采样会造成采样不足,产生模糊且有锯齿的结果; 通过两个中间步骤可以保持清晰。

</critical_requirements>


自动检测: URL.createObjectURL、URL.revokeObjectURL、createImageBitmap、OffscreenCanvas、 canvas.toBlob、canvas.toDataURL、convertToBlob、ctx.drawImage、imageSmoothingQuality、 image-orientation、EXIF orientation、0x0112、FileReader.readAsDataURL、naturalWidth、image/webp、 逐步缩小、缩略图生成、客户端调整大小、图像裁剪

适用于:

  • 显示用户选择的图像预览
  • 在浏览器中调整图像大小、压缩或转换图像
  • 读取并规范化 EXIF 方向
  • 生成缩略图、裁剪、添加水印、应用滤镜
  • 在进行其他操作前验证尺寸和宽高比

其他内容另行处理:

  • 选择和传输文件——此技能从你已持有的 File 或 Blob 开始
  • 在服务上调整大小或转码,以及基于 URL 的转换服务
  • 带有图层、选区和撤销功能的交互式编辑界面;这类场景需要使用 canvas 库,而不是直接调用 Canvas

## 理念

预览和处理的目标相反,将两者混为一谈是大多数图像错误的根源。

预览不应产生任何开销:URL.createObjectURL() 会将内存中已有字节的引用交给 <img>,无需解码到 JavaScript,也无需复制。代价是需要手动管理生命周期——如果不撤销引用,它就会一直存在,超出组件的生命周期。

处理则恰恰相反:必须将像素解码到画布中、进行转换,再重新编码。所有耗时操作都发生在这里,因此值得只在需要的尺寸上进行一次画布处理,而不是反复以全分辨率处理。

浏览器自身的 EXIF 处理横跨两者之间的界线。浏览器会在显示时旋转图像,但不会在画布中旋转,因此同一个文件经由不同路径读取时,方向会有所不同。


核心模式

模式 1:使用对象 URL 预览并清理

创建下一个 URL 之前,先撤销上一个 URL,并在卸载时撤销 URL。在渲染期间创建 URL,会导致每次渲染都泄漏一个 URL。

useEffect(() => {
  const url = URL.createObjectURL(file);
  setPreviewUrl(url);
  return () => URL.revokeObjectURL(url);
}, [file]);

完整代码:examples/core.md

模式 2:画布缩放

将尺寸限制在浏览器上限内,保持宽高比,启用高质量平滑;输出格式为 JPEG 时,绘制前先填充白色——JPEG 没有 alpha 通道,因此透明区域会编码为黑色。

const MAX_CANVAS_DIMENSION = 4096;

const ratio = Math.min(maxWidth / img.width, maxHeight / img.height);
const width = Math.round(img.width * Math.min(ratio, 1)); // never upscale
const height = Math.round(img.height * Math.min(ratio, 1));

ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = "high";
if (mimeType === "image/jpeg") {
  ctx.fillStyle = "#ffffff";
  ctx.fillRect(0, 0, width, height);
}
ctx.drawImage(img, 0, 0, width, height);

完整代码:examples/canvas.md

模式 3:逐步缩小

drawImage 会对固定邻域进行采样,因此一次大幅缩小会丢弃大部分源图像信息。反复减半可以让每个像素都参与其中。

const STEP_DOWN_THRESHOLD = 0.5;

if (targetWidth / img.width < STEP_DOWN_THRESHOLD) {
  // 4000 → 400 → 100 rather than 4000 → 100
  const factor = Math.pow(targetWidth / img.width, 1 / steps);
  for (let i = 0; i < steps; i++) {
    /* draw into a canvas scaled by factor, then use it as the next source */
  }
}

完整代码:examples/canvas.md

模式 4:EXIF 方向
// Display: the browser has already rotated it
<img src={URL.createObjectURL(file)} />

// Outbound bytes: normalise, because the next consumer may not rotate
const orientation = await getExifOrientation(file);
const normalized = orientation === 1 ? file : await normalizeOrientation(file);

// Displaying an already-normalised image: suppress the second rotation
<img src={url} style={{ imageOrientation: "none" }} />

完整代码:examples/core.md

模式 5:格式转换

不同格式对质量参数的含义不同,而 PNG 会完全忽略该参数。

const FORMAT_QUALITY_DEFAULTS: Record<string, number> = {
  "image/jpeg": 0.85,
  "image/webp": 0.82,
  "image/png": 1, // lossless — the quality argument is ignored
};

现代浏览器都支持 WebP 编码,包括 Safari 14 及更高版本。若要满足字节数预算,而非指定质量设置,可以对质量参数进行二分搜索。

完整代码:examples/canvas.md

模式 6:裁剪

drawImage 使用九个参数时,会同时接受源矩形和目标矩形,因此一次调用即可完成裁剪和缩放。先将源矩形限制在图像范围内——超出边界的区域不会绘制任何内容,而不会报错。

// drawImage(source, sx, sy, sw, sh, dx, dy, dw, dh)
ctx.drawImage(
  img,
  cropX,
  cropY,
  cropWidth,
  cropHeight,
  0,
  0,
  outputWidth,
  outputHeight,
);

完整代码:examples/canvas.md


<red_flags>

风险提示

运行时会出问题:

  • 没有 URL.revokeObjectURL() — 页面存续期间,每次选择都会让另一张图片占用内存 — 在 effect 清理时以及替换 URL 之前撤销 URL
  • 在渲染期间调用 URL.createObjectURL() — 每次渲染都会创建一个新 URL,且没有一个会被撤销 — 将其移入 effect
  • canvas 尺寸超过 4096px — 标签页会崩溃,或 canvas 会静默地什么也不输出 — 赋值给 canvas.width 之前先限制尺寸
  • 先规范化方向,再在 <img> 中渲染结果 — 浏览器会再次旋转图片 — 在该元素上设置 image-orientation: none,或显示时不要规范化方向
  • 将透明 PNG 转换为 JPEG 时没有填充背景 — 透明区域会变成黑色 — 先用 fillRect 填充白色

容易令人意外的行为:

  • 单次缩小超过 50% 会显得模糊且有锯齿,而两次缩小则显得清晰
  • toBlob() 是异步的,而 toDataURL() 不是;同步的那个开销更大
  • data URL 大约比它编码的文件大三分之一,因此 10MB 的图片会变成主线程中的 13MB 字符串
  • Node 不支持 image-orientation,因此服务端 canvas 操作仍需手动处理 EXIF
  • createImageBitmap 会跳过布局和解码,其结果会一直占用内存,直到调用 close()
  • 图片的每个维度都在限制范围内,仍可能超出 canvas 的总面积限制
  • 各浏览器并不都支持通过 toBlob() 编码 AVIF;解码则支持
  • 重新编码已经优化过的图片通常会让文件变大,而不是变小

评论

支持 Markdown