旋转图片在线

按画布包围盒公式重算尺寸后旋转,任意角度自动扩边,90 度整数倍零插值。

上面 5 个是零插值吸附角;其他任意角度都会重采样。快捷键:在角度框按回车直接重算。

原图尺寸800 × 600 px
旋转后画布—
弧度值—
面积变化—
扩边空白—
重采样—
角度(度)弧度(6 位小数)新宽(px)新高(px)面积(px)相对原图新尺寸

表格按当前原图尺寸实时重算,默认显示 12 行,点「加载更多」每次再加 12 行。上表 12 行对应 800×600 演示图。

使用方法

  1. 点「载入示例」用内置的 800×600 网格测试图,或选一张本地 PNG/JPEG/WebP。原图只在本页解码,不经过网络。
  2. 在「旋转角度」填度数,示例 30;也可以直接点 0°/90°/180°/270°/-90° 这 5 个吸附按钮。填完按回车立即重算。
  3. 选扩边填充色。示例用白色 #ffffff;原图带透明通道时选「透明」才能把 Alpha 一起带出去。
  4. 选导出格式,示例 PNG。点「旋转生成」后画布按新尺寸重算,再点「下载图片」存盘。
  5. 下方的角度对照表列出每个角度的新宽、新高与面积倍率,验证 0°/90°/180° 那几行为什么不变或只换宽高。需要拿数据做二次分析就点「导出对照表 CSV」。

旋转图片在线能解决什么问题

手机拍歪的证件照要摆正、扫描件歪了 2 度要校正、电商图需要统一倾斜角做视觉节奏——这三类活儿的共同点是旋转后画布尺寸会变,而多数人一转就发现图被裁掉一角或者多出一大片空白。旋转图片在线的核心价值就是把这个尺寸重算做对:它按矩形的轴对齐包围盒公式给出新画布,而不是简单交换宽高。

第二个场景是方向纠正。手机横拍出来的照片,EXIF 里存着 Orientation=6(顺时针 90°),在文件管理器里看是正的,一旦用某些程序重新编码,Orientation 字段被丢掉,图就躺下了。旋转图片在线读的是浏览器已按 EXIF 摆正的位图,你填 0° 得到的就是正的,再导出就把方向固化进像素了。

第三个场景是批量出图的角度对照。一组 18 张图要统一转到同一个角度,做完一次就知道每张扩边后多出多少像素,可以提前估下载体积和 CDN 流量。本页不替你改尺寸比例,也不做色彩和压缩率的调整——那两件事分别交给图片尺寸调整和图片压缩工具两个专门页面;本页只做几何旋转这一件事。

旋转原理:弧度换算、画布包围盒与重采样

第一步是单位换算。canvas.getContext('2d').rotate() 收的是弧度,不是角度,公式是 θ = deg × π / 180。这是本页最常见的翻车点:直接把 30 传给 rotate,浏览器会当成 30 弧度(约 1718.9 度),图会转成一大圈肉眼看不出朝向的形态。30 度换算是 0.523599 弧度,45 度是 0.785398,90 度是 1.570796——这三个值可以拿去对照任何一份旋转代码。

第二步是画布尺寸重算。图片本身只有 W×H,旋转 θ 后它在平面上的投影是一个外接矩形,新宽新高是:

newW = ceil(|W × cos θ| + |H × sin θ|)
newH = ceil(|W × sin θ| + |H × cos θ|)

取绝对值是因为要的是包围盒而不是有向投影,θ 转负数时结果必须一样。800×600 的图转 30 度:cos 30 = 0.866025、sin 30 = 0.5,newW = 800×0.866025 + 600×0.5 = 992.82,向上取整 993;newH = 800×0.5 + 600×0.866025 = 919.62,取整 920。

第三步是浮点防护。Math.sin(Math.PI) 返回的是 1.2246467991473532e-16 而不是 0,于是 800×1.22e-16 ≈ 9.8e-14,ceil 之后凭空多出 1 像素:800×600 转 180 度会得到 801×601 这种荒谬结果。解决办法是先把两个投影值四舍五入到 4 位小数再 ceil,本页的 r4() 就是干这个的。加上防护后 180 度、270 度、360 度都能拿回精确的 800×600 与 600×800。

第四步是重采样与画布变换顺序。正确写法是 translate(w/2, h/2) → rotate(θ) → drawImage(img, -W/2, -H/2),先平移到中心、旋转、再按左上角为原点贴回。少一步 translate,图就会绕左上角甩出去。本页把 imageSmoothingQuality 设成 high,因为双线性插值在非 90 倍数角度上会明显糊;这是拿一点速度换边缘质量,90 倍数角度本来就不用插值,不受影响。

完整示例(内置 800×600 演示图,数值仅作演示)

打开本页时画布上已经有一张 800×600 的网格测试图(页面自带,不是你的文件),角度框默认 30 度。点「旋转生成」得到:

同一张图把角度换成 0 度,新画布是 800×600,扩边空白 0;换成 90 度是 600×800,扩边空白同样是 0,因为 800×sin 90 = 800、600×cos 90 = 0 在数学上正好抵消。换成 45 度最夸张:1400 × cos 45 = 989.9495,取整 990,新画布 990×990,面积 980,100 px,是原图的 204.2%,多出的 500,100 个像素按 RGBA 算约 1953.5 KB 全是空白。45 度因此成为最亏的角度。

角度、格式与扩边对照表

角度这一列的规律很整齐:0° 与 90° 面积比都是 100.0%,180° 与 360° 加上浮点防护后也是 100.0%;15° 和 75° 面积比都是 152.3%,因为 90−15 = 75 时两个投影项只是互换位置;30° 与 60° 同为 190.3%,45° 与 135° 同为 204.2%。也就是说对同一张非正方形图片,面积倍率关于 45° 对称。

角度性质800×600 结果面积比像素处理
0 / 360恒等800×600100.0%不重采样
90 / 270转置600×800100.0%不重采样,宽高互换
180中心对称800×600100.0%不重采样
15 / 75锐角929×787152.3%双线性插值
30 / 60常用倾斜993×920190.3%双线性插值
45最扩边990×990204.2%双线性插值,模糊最明显
7.5微调872×700127.2%插值,接近原图

扩边填充色这一列也有讲究。PNG 和 WebP 支持 alpha 通道,选「透明」时扩出来的区域是真透明,可以叠到任何底图上;JPEG 没有 alpha 通道,同一份画布导成 JPEG 时透明区会被填成黑色(也有实现填白,取决于浏览器)。所以原图是透明 PNG 又要出 JPEG 时,必须把填充色显式改成白色。选深墨 #172033 则适合深色主题的展示位,边缘不会显得脏。

导出格式另一个取舍是体积。PNG 是无损的,旋转后像素没变,体积和原 PNG 基本持平;JPEG 在浏览器里 toBlob 的质量参数默认 0.92,相当于在原图有损压缩之上再叠一层有损,细节会二次流失。已经压过一遍的图想再换角度,尽量走 PNG。纯粹是容器格式的问题(跟几何朝向无关),交给下方的图片格式转换更直接。

边界与精度:浮点余数、画布上限与二次压缩

浮点余数是本页最实在的精度陷阱。IEEE 754 双精度下 sin(π) 不为 0,1.2246467991473532e-16 乘上 800 也有 9.8e-14,ceil 之后就是多一整行/一整列像素。r4() 把投影值先 round 到小数点后 4 位再取整,把误差压到 1e-4 像素以下,远小于一个物理像素。本页 12 行对照表里 180°、270°、360° 三行能显示成 800×600 而不是 801×601,靠的就是这一层。

画布上限取决于浏览器:Chrome 与 Edge 的单边上限是 16384 像素,总面积上限 268,435,456 像素;Firefox 的总面积上限约 124,994,000 像素;Safari 保守得多,单边 4096 像素、总面积 16,777,216 像素。本页设了 8000×8000 像素(即 64,000,000 像素面积)的硬阈值,低于 Chrome 上限、高于 Safari 上限——也就是说在 Safari 上转大图仍可能拿到空白画布,这是浏览器的限制而不是本页的 bug。预算是按 8000×8000 算的 64,000,000 像素面积,比 Chrome 的 268,435,456 低得多,比 Safari 的 16,777,216 高得多,正好卡在中间偏保守的位置。

插值精度方面,非 90 倍数角度必然重采样。800×600 转 30 度后画布 913,560 像素,插值是双线性的一次性混合,边缘斜线会有一到两个像素的过渡带。要判断一张图能不能承受任意角度,看它的边缘是不是硬边(文字、线稿、图标)——硬边图对插值最敏感,45 度下文字边缘会明显发虚。证件照、扫描文字这类内容如果只需要摆正,用 90 倍数角度一步到位,零损失比任何插值质量设置都管用。

角度范围是 −180 到 180,开区间两端等价:180 和 −180 是同一个朝向。step 取 1 度时这段范围有 361 个可选值,取 0.1 度有 3601 个。步长越细不代表越准,0.1 度以下的角度变化在 800 像素画布上不足一个像素的位移,纯粹是给插值制造抖动。

常见问题

转完 180 度为什么画布变成 801×601,多出一圈白边?

这是浮点余数,不是算错。Math.sin(Math.PI) 的返回值是 1.2246467991473532e-16 而非 0,800 乘上它得到 9.8e-14,向上取整就凭空多 1 像素。本页对投影值做了四位小数防护,180 度能精确回到 800×600;自己写代码时也要加这层,否则换任何尺寸都可能随机多一列。

导出的 PNG 边缘有黑边,不是设置的黑边颜色。

填充色选的是「透明」时画布 alpha 为 0,PNG 会如实保留透明。当这个 PNG 被某些不支持 alpha 的程序(比如部分 PDF 渲染器或图片查看器)打开时,透明区会显示成黑色。把填充色改成白色再导一次即可,代价是失去透明背景。

为什么 90 度不糊,30 度糊?

90 度是轴对齐变换,新坐标的取整是整数到整数的映射,每个输出像素恰好来自一个输入像素,没有插值。30 度会让像素落在非整数位置,必须由邻近像素加权合成,高频边缘就会被平滑。差距在高对比硬边上最明显,纯色或渐变内容几乎看不出区别。

手机拍的图在电脑上是躺着的,填 0 度也没用?

现代浏览器在解码 JPEG 时已经按 EXIF 的 Orientation 字段(TIFF 标签 0x0111,共 8 种取值)把像素摆正了,本页拿到的是摆正后的位图。问题多半出在原图被某个程序重新编码时把 Orientation 字段丢了,浏览器无从判断。旋转图片在线的用法是:先试 0 度,如果躺着的角度是 90 度就填 90 或 −90 把它转回来,导出后方向就固化进像素,不再依赖这个字段。

转完的文件比原图大很多,是压缩失效了吗?

不是压缩失效,是画布变大了。800×600 转 30 度后像素从 480,000 涨到 913,560,涨了 90.3%,其中 433,560 个像素是扩出来的空白(占新画布 47.5%)。这部分像素在 PNG 里压得动(大片纯色),但在 JPEG 里属于高频噪点边缘,压不动。想要小文件就把角度改到 90 的整数倍,或者先用尺寸调整把长边压到 1000 像素以内。

大图转完画布是空白的,一片白。

超出了浏览器的画布上限。Safari 单边只有 4096 像素,Chrome 是 16384 像素,超过后 canvas.width 赋值会失败或静默丢弃绘制。本页对超过 8000×8000 的结果直接给出中文提示而不是画一张空白图。解决办法是先缩小原图再转,或者改用 90 倍数角度避免扩边——90 度只换宽高不扩面积,是大图最稳的选择。

能只旋转 90 的倍数、避开插值吗?

能,这也是扫描件和证件照场景的首选。本页的 0°/90°/180°/270°/−90° 五个按钮就是为此准备的,它们的面积比都是 100.0%,完全不扩边也不插值。代价是只能做垂直水平四个朝向,2 度那种微歪救不回来——要修正 2 度歪斜就必须接受插值,这是几何上的取舍,没有第三条路。

镜像对调和旋转有什么区别?

翻转是镜像,旋转是刚体变换。镜像把像素左右或上下对调,画布尺寸永远不变、不需要插值;旋转改变朝向,任意角度都要按包围盒重算画布并重采样。两者偶尔会连用(先转 90 度再翻转),但处理路径完全不同,所以拆成了两个独立工具(见下方相关工具)。想要的是「把图摆正」用本页,要的是「把图照过来」用翻转页。

相关工具