跳转到内容

图片与文字水印

复制本页

水印用于标注图片来源与归属:在照片上放置半透明文字、品牌标识或 Logo。本质上它是两层合成的过程——基础图与带 Alpha 通道的叠加层——在固定的边距上定位。本 Recipe 演示文字水印与 Logo 水印两种变体,均锚定在右下角。

两种变体使用同一张输入照片,以及程序生成的 WebP Cloud 标识叠加层。

作为水印基底使用的苏州东方之门夜景照片
基底:JPEG1938 × 1292,3 通道
作为 Logo 水印使用的半透明 WebP Cloud 标识
WebP Cloud 叠加层:SVG220 × 72,透明背景

用较大的深色文字渲染 © WebP Cloud,并放在白色圆角底色块上,再把该色块在右下角以 24 像素边距叠加到基底上。给文字配上浅色底块,可让标签在任何照片亮度下都保持清晰。结果压平为纯 RGB 后导出为 WebP。

右下角带半透明文字水印的苏州东方之门夜景照片
文字水印 · WEBP1938 × 1292, 399,498 bytes, 3 bands

Generated with govips v2.19.0 / libvips 8.17.3.

textImg, err := vips.Text(&vips.TextParams{
Text: "<span foreground='#12202b' font-weight='700'>© WebP Cloud</span>",
Font: "DejaVu Sans",
DPI: 144,
RGBA: true,
Wrap: vips.TextWrapWord,
})
if err != nil {
return err
}
// 白色圆角底块,尺寸根据渲染出的文字计算。
pill, err := vips.NewImageFromBuffer([]byte(fmt.Sprintf(
`<svg xmlns="http://www.w3.org/2000/svg" width="%d" height="%d"><rect width="%d" height="%d" rx="16" fill="#ffffff" fill-opacity="0.94"/></svg>`,
textImg.Width()+52, textImg.Height()+32, textImg.Width()+52, textImg.Height()+32)))
if err != nil {
return err
}
if err := textImg.PremultiplyAlpha(); err != nil {
return err
}
if err := pill.Composite(textImg, vips.BlendModeOver, 26, 16); err != nil {
return err
}
base, err := vips.NewImageFromFile(inputPath)
if err != nil {
return err
}
x := base.Width() - pill.Width() - 24
y := base.Height() - pill.Height() - 24
if err := base.Composite(pill, vips.BlendModeOver, x, y); err != nil {
return err
}
if err := base.Flatten(&vips.Color{R: 29, G: 45, B: 53}); err != nil {
return err
}
data, _, err := base.ExportWebp(vips.NewWebpExportParams())

在基底右下角叠加半透明 WebP Cloud 标识。由于 SVG 叠加层自带 Alpha 通道,合成前先进行预乘(premultiply),保证混合结果正确。

右下角带 WebP Cloud 标识水印的苏州东方之门夜景照片
WebP Cloud Logo 水印 · WEBP1938 × 1292, 399,632 bytes, 3 bands

Generated with govips v2.19.0 / libvips 8.17.3.

logo, err := vips.NewImageFromFile(overlayPath)
if err != nil {
return err
}
// 预乘带 Alpha 的叠加层,保证混合正确。
if err := logo.PremultiplyAlpha(); err != nil {
return err
}
base, err := vips.NewImageFromFile(inputPath)
if err != nil {
return err
}
x := base.Width() - logo.Width() - 24
y := base.Height() - logo.Height() - 24
if err := base.Composite(logo, vips.BlendModeOver, x, y); err != nil {
return err
}
if err := base.Flatten(&vips.Color{R: 29, G: 45, B: 53}); err != nil {
return err
}
data, _, err := base.ExportWebp(vips.NewWebpExportParams())

能力边界:绘图在各 binding 间并不统一

Section titled “能力边界:绘图在各 binding 间并不统一”

此处的水印建立在“文本渲染 + 两层合成”之上,四个 binding 都支持。但“直接绘图”则是另一回事:

  • pyvips 与 vips CLI 提供完整的绘图套件:draw_line、draw_circle、draw_rect、draw_flood、draw_image。
  • govips 只暴露 DrawRect (ink, left, top, width, height, fill);govips v2.19.0 中没有手写或生成的 DrawLine、DrawCircle。
  • sharp 本身没有任何绘图原语。要画线或图形,只能先栅格化 SVG 或文本叠加层,再进行合成。

因此“画一个红色圆形”这类产品意图,在 govips 或 sharp 中无法直接表达,必须借助 SVG 或文本。本 Recipe 有意让每个 binding 都走它们共有支持的合成路径,并把“直接绘图”当作能力边界而不是可跨 binding 移植的操作。