WebP Cloud Services Blog

libvips 中不同的 VipsInteresting 是怎么决定图片裁剪位置的

· Nova Kwok

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.jpghttps://p2k7zwb.webp.ee/hetzner-arm64/c1-board.jpg?width=200
107.81 KB7.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_NONEdo nothing
VIPS_INTERESTING_CENTREjust take the centre
VIPS_INTERESTING_ENTROPYuse an entropy measure
VIPS_INTERESTING_ATTENTIONlook for features likely to draw human attention
VIPS_INTERESTING_LOWposition the crop towards the low coordinate
VIPS_INTERESTING_HIGHposition the crop towards the high coordinate
VIPS_INTERESTING_ALLeverything 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=400http://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=400http://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=400http://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=400http://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=400http://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=400http://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=400http://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 的情况下,如果同时传入了 heightwidth ,那么对于以下 Interesting 而言为了保证图片的长宽比例不变,只会保证一个维度符合传入的参数:

  • InterestingNone
  • InterestingAll

而其他的 Interesting 会对图片做一些裁剪保证输出的部分尺寸完全符合传入的两个维度的要求。

对于 InterestingEntropyInterestingAttention 而言更加适合用于生成缩略图,前者是根据图片熵大小来决定裁剪中心,后者是尝试找到画面中的重点来作为裁剪中心。

WebP Server Go 和 WebP Cloud 目前在使用 InterestingAttention 作为裁剪方式,方便生成合适的缩略图。

参考资料

  1. https://www.libvips.org/API/current/libvips-conversion.html#VipsInteresting
  2. https://pkg.go.dev/github.com/davidbyttow/govips/v2/vips#InterestingAll

WebP Cloud Services 团队是一个来自上海和赫尔辛堡的三人小团队,由于我们不融资,且没有盈利压力 ,所以我们会坚持做我们认为正确的事情,力求在我们的资源和能力允许范围内尽量把事情做到最好, 同时也会在不影响对外提供的服务的情况下整更多的活,并在我们产品上实践各种新奇的东西。

如果你觉得我们的这个服务有意思或者对我们服务感兴趣,欢迎登录 WebP Cloud Dashboard 来体验,如果你好奇它还有哪些神奇的功能,可以来看看我们的文档 WebP Cloud Services Docs,希望大家玩的开心~


Discuss on Hacker News