缩放、裁剪、旋转与翻转
几何处理首先要明确输出画布:目标宽高是多少、是否允许裁剪、超出原图时如何填充、方向是否已按 EXIF Orientation 归一化。下面按变体分节,每个几何操作单独展示其输出和对应代码。
所有变体使用同一张输入图:

参考输出由 govips v2.19.0 / libvips 8.17.3 实际生成。
缩放(fit inside)
Section titled “缩放(fit inside)”将输入缩小到 400 × 400 边界内,不放大小图。下列代码动态计算缩放因子,并把最小边钳制在 1.0 以避免放大。

func scale(image *vips.ImageRef) float64 { s := 400.0 / float64(image.Width()) if hs := 400.0 / float64(image.Height()); hs < s { s = hs } if s > 1.0 { s = 1.0 } return s}# 在 run() 内:vips.Startup(nil) 并 defer vips.Shutdown();image, err := vips.NewImageFromFile("input.jpg")if err != nil { return err }defer image.Close()s := scale(image)if err := image.Resize(s, vips.KernelLanczos3); err != nil { return err }data, _, err := image.ExportWebp(vips.NewWebpExportParams())if err != nil { return err }return os.WriteFile("resize-fit.webp", data, 0o644)await sharp('input.jpg') .resize(400, 400, { fit: 'inside', withoutEnlargement: true }) .webp({ quality: 82 }) .toFile('resize-fit.webp');image = pyvips.Image.new_from_file("input.jpg")s = min(400 / image.width, 400 / image.height, 1.0)fit = image.resize(s, kernel="lanczos3")fit.write_to_file("resize-fit.webp", Q=82)W=$(vipsheader --all input.jpg | grep '^width:' | awk '{print $2}')H=$(vipsheader --all input.jpg | grep '^height:' | awk '{print $2}')SCALE=$(python3 -c "print(min(400/${W},400/${H},1.0))")vips resize input.jpg 'resize-fit.webp[Q=82]' "$SCALE" --vscale "$SCALE" --kernel lanczos3从左上角坐标 (520, 250) 取出 500 × 500 的正方形区域。裁剪前应验证矩形不越界。

image, err := vips.NewImageFromFile("input.jpg")if err != nil { return err }defer image.Close()if err := image.ExtractArea(520, 250, 500, 500); err != nil { return err }data, _, err := image.ExportWebp(vips.NewWebpExportParams())if err != nil { return err }return os.WriteFile("crop.webp", data, 0o644)await sharp('input.jpg') .extract({ left: 520, top: 250, width: 500, height: 500 }) .webp({ quality: 82 }) .toFile('crop.webp');image = pyvips.Image.new_from_file("input.jpg")crop = image.extract_area(520, 250, 500, 500)crop.write_to_file("crop.webp", Q=82)vips extract_area input.jpg 'crop.webp[Q=82]' 520 250 500 500将图像顺时针旋转 30 度。任意角度旋转需要插值,可能扩大画布并引入背景区域,这里用深色 #1d2d35 填充扩展部分。

image, err := vips.NewImageFromFile("input.jpg")if err != nil { return err }defer image.Close()if err := image.Similarity(1, 30, &vips.ColorRGBA{R: 29, G: 45, B: 53, A: 255}, 0, 0, 0, 0); err != nil { return err }data, _, err := image.ExportWebp(vips.NewWebpExportParams())if err != nil { return err }return os.WriteFile("rotate-30.webp", data, 0o644)await sharp('input.jpg') .rotate(30, { background: { r: 29, g: 45, b: 53 } }) .webp({ quality: 82 }) .toFile('rotate-30.webp');image = pyvips.Image.new_from_file("input.jpg")rotated = image.similarity(scale=1, angle=30, background=[29, 45, 53])rotated.write_to_file("rotate-30.webp", Q=82)vips similarity input.jpg 'rotate-30.webp[Q=82]' --angle 30 --background 29,45,53沿水平轴翻转图像,输出画布尺寸不变。

image, err := vips.NewImageFromFile("input.jpg")if err != nil { return err }defer image.Close()if err := image.Flip(vips.DirectionHorizontal); err != nil { return err }data, _, err := image.ExportWebp(vips.NewWebpExportParams())if err != nil { return err }return os.WriteFile("flip-horizontal.webp", data, 0o644)await sharp('input.jpg') .flop() .webp({ quality: 82 }) .toFile('flip-horizontal.webp');image = pyvips.Image.new_from_file("input.jpg")flipped = image.fliphor()flipped.write_to_file("flip-horizontal.webp", Q=82)vips flip input.jpg 'flip-horizontal.webp[Q=82]' horizontal选择投影背景
Section titled “选择投影背景”不同变体对画布大小和背景的选择不同:fit 保持原比例缩小,crop 固定取区域,旋转可能需要扩展画布并填色。生产环境应针对输出画布、背景策略和兴趣区域显式决策,而不是依赖 binding 默认值。
- 未验证裁剪边界:越界
extract_area会返回错误。 - 依赖默认缩放:只传两个数字而不指定 fit/cover/inside,不同 binding 默认不同。
- 忽略背景颜色:透明输出与不支持 Alpha 的格式需要不同的旋转背景处理。