libvips 中不同的 VipsInteresting 是怎么决定图片裁剪位置的
This article is also available in English, at How does different VipsInteresting values in libvips determine the cropping position of an image?.
在使用 WebP Cloud 或者 WebP Server Go 的时候我们会看到一个选项允许用户传入 width
和/或 height
来对图片进行动态的 resize,对于需要快速生成缩略图的场景非常有用,在 WebP Cloud 上效果如下:
https://blog.webp.se/hetzner-arm64/c1-board.jpg | https://p2k7zwb.webp.ee/hetzner-arm64/c1-board.jpg?width=200 |
107.81 KB | 7.27 KB |
类似我们的开源组件 WebP Server Go 的 SaaS 版本,用户只需要用 GitHub 登录,然后填写源站地址,即可获得一个新的带 WebP 转换的,带 CDN 和缓存的新地址,比如 100KB 的图片
https://blog.webp.se/hetzner-arm64/c1-board.png
地址变成 WebP 版本的只有 60KB 的https://p2k7zwb.webp.ee/hetzner-arm64/c1-board.png
地址(且画质几乎不会衰减)。
在 WebP Server Go 上对应的代码类似如下:
func resizeImage(img *vips.ImageRef, extraParams config.ExtraParams) error {
imgHeightWidthRatio := float32(img.Metadata().Height) / float32(img.Metadata().Width)
if extraParams.Width > 0 && extraParams.Height > 0 {
err := img.Thumbnail(extraParams.Width, extraParams.Height, vips.InterestingAll)
if err != nil {
return err
}
} else if extraParams.Width > 0 && extraParams.Height == 0 {
err := img.Thumbnail(extraParams.Width, int(float32(extraParams.Width)*imgHeightWidthRatio), vips.InterestingAll)
if err != nil {
return err
}
} else if extraParams.Height > 0 && extraParams.Width == 0 {
err := img.Thumbnail(int(float32(extraParams.Height)/imgHeightWidthRatio), extraParams.Height, vips.InterestingAll)
if err != nil {
return err
}
}
return nil
}
可以看出这里的逻辑是:
- 如果用户只传入了 Width 或者 Height,那么图片对应尺度会进行改变,同时另一个尺度会跟着比例进行调整(防止图片被压缩)
- 如果用户同时传入了 Width 和 Height,那么我们就会对图片做
Thumbnail
的操作并同时传入用户的 Width 和 Height,但图片不会因此失真,这是为什么呢?
通过观察
err := img.Thumbnail(extraParams.Width, extraParams.Height, vips.InterestingAll)
我们可以发现最后有一个 vips.InterestingAll
,根据 davidbyttow/govips 的文档我们可以知道,这里有如下选项:
const (
InterestingNone Interesting = C.VIPS_INTERESTING_NONE
InterestingCentre Interesting = C.VIPS_INTERESTING_CENTRE
InterestingEntropy Interesting = C.VIPS_INTERESTING_ENTROPY
InterestingAttention Interesting = C.VIPS_INTERESTING_ATTENTION
InterestingLow Interesting = C.VIPS_INTERESTING_LOW
InterestingHigh Interesting = C.VIPS_INTERESTING_HIGH
InterestingAll Interesting = C.VIPS_INTERESTING_ALL
InterestingLast Interesting = C.VIPS_INTERESTING_LAST
)
由于 libvips 是 vips 的 Go Wrapper,所以我们查阅 libvips 的文档 可以知道如下表格:
VIPS_INTERESTING_NONE | do nothing |
---|---|
VIPS_INTERESTING_CENTRE | just take the centre |
VIPS_INTERESTING_ENTROPY | use an entropy measure |
VIPS_INTERESTING_ATTENTION | look for features likely to draw human attention |
VIPS_INTERESTING_LOW | position the crop towards the low coordinate |
VIPS_INTERESTING_HIGH | position the crop towards the high coordinate |
VIPS_INTERESTING_ALL | everything is interesting |
VIPS_INTERESTING_LAST |
为了更加直观的知道不同的 Interesting
到底对于图片有什么样的效果,我们做了以下对比,供大家参考:
原图
“origin.png (PNG Image, 384 × 512 pixels)”
可以看出原图是个 384 × 512 pixels 图片。
VIPS Crop
我们使用 WebP Server Go 的修改版进行测试,请求地址分别为:
http://localhost:3333/origin.png?width=400&height=400
http://localhost:3333/origin.png?width=400&height=800
InterestingNone
“origin.png (WEBP Image, 300 × 400 pixels)” | “origin.png (WEBP Image, 400 × 533 pixels)” |
http://localhost:3333/origin.png?width=400&height=400 | http://localhost:3333/origin.png?width=400&height=800 |
InterestingCentre
“origin.png (WEBP Image, 400 × 400 pixels)” | “origin.png (WEBP Image, 400 × 800 pixels)” |
http://localhost:3333/origin.png?width=400&height=400 | http://localhost:3333/origin.png?width=400&height=800 |
InterestingEntropy
“origin.png (WEBP Image, 400 × 400 pixels)” | “origin.png (WEBP Image, 400 × 800 pixels)” |
http://localhost:3333/origin.png?width=400&height=400 | http://localhost:3333/origin.png?width=400&height=800 |
InterestingAttention
“origin.png (WEBP Image, 400 × 400 pixels)” | “origin.png (WEBP Image, 400 × 800 pixels)” |
http://localhost:3333/origin.png?width=400&height=400 | http://localhost:3333/origin.png?width=400&height=800 |
InterestingLow
“origin.png (WEBP Image, 400 × 400 pixels)” | “origin.png (WEBP Image, 400 × 800 pixels)” |
http://localhost:3333/origin.png?width=400&height=400 | http://localhost:3333/origin.png?width=400&height=800 |
InterestingHigh
“origin.png (WEBP Image, 400 × 400 pixels)” | “origin.png (WEBP Image, 400 × 800 pixels)” |
http://localhost:3333/origin.png?width=400&height=400 | http://localhost:3333/origin.png?width=400&height=800 |
InterestingAll
“origin.png (WEBP Image, 400 × 533 pixels)” | “origin.png (WEBP Image, 600 × 800 pixels)” |
http://localhost:3333/origin.png?width=400&height=400 | http://localhost:3333/origin.png?width=400&height=800 |
InterestingLast
这个模式很有意思,直接报错了:
2023/07/09 12:51:15 [VIPS.info] reduceh: 11 point mask
2023/07/09 12:51:15 [VIPS.info] cropping to 400x400
fatal error: unexpected signal during runtime execution
[signal SIGSEGV: segmentation violation code=0x2 addr=0x7f32ef4fcb0a pc=0x7f32ef4fcb0d]
runtime stack:
runtime.throw({0x8b43e4?, 0x7f32b801f8c0?})
/usr/local/go/src/runtime/panic.go:1047 +0x5d fp=0x7f32c3604cd0 sp=0x7f32c3604ca0 pc=0x43d19d
runtime.sigpanic()
/usr/local/go/src/runtime/signal_unix.go:825 +0x3e9 fp=0x7f32c3604d30 sp=0x7f32c3604cd0 pc=0x4537e9
goroutine 19 [syscall]:
runtime.cgocall(0x7e4c20, 0xc000080b18)
/usr/local/go/src/runtime/cgocall.go:157 +0x5c fp=0xc000080af0 sp=0xc000080ab8 pc=0x40c57c
github.com/davidbyttow/govips/v2/vips._Cfunc_thumbnail_image(0x7f32b8006020, 0xc000114020, 0x190, 0x190, 0x7, 0x0)
_cgo_gotypes.go:2232 +0x4c fp=0xc000080b18 sp=0xc000080af0 pc=0x79eccc
github.com/davidbyttow/govips/v2/vips.vipsThumbnail.func1(0x7f32c2dc72c8?, 0x7f32eb94e5b8?, 0x190, 0x190, 0x7, 0x0)
/home/Nova/go/pkg/mod/github.com/davidbyttow/govips/v2@v2.13.0/vips/resample.go:57 +0xa7 fp=0xc000080b60 sp=0xc000080b18 pc=0x7a3647
github.com/davidbyttow/govips/v2/vips.vipsThumbnail(0x1?, 0x1?, 0x16?, 0x2b4?, 0x26d?)
/home/Nova/go/pkg/mod/github.com/davidbyttow/govips/v2@v2.13.0/vips/resample.go:57 +0x91 fp=0xc000080be0 sp=0xc000080b60 pc=0x7a3511
github.com/davidbyttow/govips/v2/vips.(*ImageRef).Thumbnail(0xc000112050, 0x1?, 0x33668?, 0x33669?)
/home/Nova/go/pkg/mod/github.com/davidbyttow/govips/v2@v2.13.0/vips/image.go:1677 +0x27 fp=0xc000080c18 sp=0xc000080be0 pc=0x7a2cc7
webp_server_go/encoder.resizeImage(0xc0001160a8?, {0x14?, 0x4ac456?})
/home/Nova/workspace/webp-server/webp_server_go/encoder/encoder.go:35 +0xb5 fp=0xc000080c68 sp=0xc000080c18 pc=0x7af7f5
webp_server_go/encoder.webpEncoder({0xc0001160a8, 0x14}, {0xc000146140, 0x3c}, {0x0?, 0x942f30?})
/home/Nova/workspace/webp-server/webp_server_go/encoder/encoder.go:217 +0x191 fp=0xc000080e48 sp=0xc000080c68 pc=0x7b0871
webp_server_go/encoder.convertImage({0xc0001160a8, 0x14}, {0xc000146140, 0x3c}, {0x89bdda, 0x4}, {0x0?, 0xc0000687d0?})
/home/Nova/workspace/webp-server/webp_server_go/encoder/encoder.go:115 +0x2d0 fp=0xc000080f58 sp=0xc000080e48 pc=0x7b00d0
webp_server_go/encoder.ConvertFilter.func2()
/home/Nova/workspace/webp-server/webp_server_go/encoder/encoder.go:72 +0x5c fp=0xc000080fe0 sp=0xc000080f58 pc=0x7afb9c
runtime.goexit()
/usr/local/go/src/runtime/asm_amd64.s:1598 +0x1 fp=0xc000080fe8 sp=0xc000080fe0 pc=0x46f281
created by webp_server_go/encoder.ConvertFilter
/home/Nova/workspace/webp-server/webp_server_go/encoder/encoder.go:71 +0x215
一点结论
从上面的例子中我们可以发现,在原图尺寸是 384 × 512 px 的情况下,如果同时传入了 height
和 width
,那么对于以下 Interesting
而言为了保证图片的长宽比例不变,只会保证一个维度符合传入的参数:
- InterestingNone
- InterestingAll
而其他的 Interesting
会对图片做一些裁剪保证输出的部分尺寸完全符合传入的两个维度的要求。
对于 InterestingEntropy
和 InterestingAttention
而言更加适合用于生成缩略图,前者是根据图片熵大小来决定裁剪中心,后者是尝试找到画面中的重点来作为裁剪中心。
WebP Server Go 和 WebP Cloud 目前在使用 InterestingAttention
作为裁剪方式,方便生成合适的缩略图。
参考资料
WebP Cloud Services 团队是一个来自上海和赫尔辛堡的三人小团队,由于我们不融资,且没有盈利压力 ,所以我们会坚持做我们认为正确的事情,力求在我们的资源和能力允许范围内尽量把事情做到最好, 同时也会在不影响对外提供的服务的情况下整更多的活,并在我们产品上实践各种新奇的东西。
如果你觉得我们的这个服务有意思或者对我们服务感兴趣,欢迎登录 WebP Cloud Dashboard 来体验,如果你好奇它还有哪些神奇的功能,可以来看看我们的文档 WebP Cloud Services Docs,希望大家玩的开心~
Discuss on Hacker News