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

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

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() - 24y := base.Height() - pill.Height() - 24if 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())await sharp('input.jpg') .composite([ { input: Buffer.from( '<svg xmlns="http://www.w3.org/2000/svg" width="360" height="60"><rect width="360" height="60" rx="16" fill="#ffffff" fill-opacity="0.94"/></svg>', ), left: 1938 - 360 - 24, top: 1292 - 60 - 24, blend: 'over', }, { input: Buffer.from( '<svg xmlns="http://www.w3.org/2000/svg" width="360" height="60"><text x="26" y="40" font-family="DejaVu Sans" font-size="28" font-weight="700" fill="#12202b">© WebP Cloud</text></svg>', ), left: 1938 - 360 - 24, top: 1292 - 60 - 24, blend: 'over', }, ]) .flatten({ background: '#1d2d35' }) .webp({ quality: 82 }) .toFile('text-watermark.webp');base = pyvips.Image.new_from_file("input.jpg")text = pyvips.Image.text('<span foreground="#12202b" font-weight="700">© WebP Cloud</span>', font="DejaVu Sans", dpi=144, rgba=True)pad_x, pad_y = 26, 16pill = pyvips.Image.svgload_buffer( f'<svg xmlns="http://www.w3.org/2000/svg" width="{text.width + 2*pad_x}" ' f'height="{text.height + 2*pad_y}">' f'<rect width="{text.width + 2*pad_x}" height="{text.height + 2*pad_y}" rx="16" ' f'fill="#ffffff" fill-opacity="0.94"/></svg>'.encode())pill = pill.composite2(text.premultiply(), "over", x=pad_x, y=pad_y)x = base.width - pill.width - 24y = base.height - pill.height - 24result = base.composite2(pill, "over", x=max(x, 0), y=max(y, 0))result.write_to_file("text-watermark.webp", Q=82)vips text txt.png "<span foreground='#12202b' font-weight='700'>© WebP Cloud</span>" --font 'DejaVu Sans' --dpi 144 --rgba# 围绕文字加白色底块;定位:x = 宽度 - 底块宽度 - 24,y = 高度 - 底块高度 - 24vips svgload pill.svg pill.pngvips composite2 pill.png txt.png pilltext.png over --x 26 --y 16vips composite2 input.jpg pilltext.png 'text-watermark.webp[Q=82]' over --x "$X" --y "$Y"Logo 水印
Section titled “Logo 水印”在基底右下角叠加半透明 WebP Cloud 标识。由于 SVG 叠加层自带 Alpha 通道,合成前先进行预乘(premultiply),保证混合结果正确。

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() - 24y := base.Height() - logo.Height() - 24if 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())await sharp('input.jpg') .composite([{ input: 'watermark-logo.svg', gravity: 'southeast', blend: 'over' }]) .flatten({ background: '#1d2d35' }) .webp({ quality: 82 }) .toFile('logo-watermark.webp');base = pyvips.Image.new_from_file("input.jpg")logo = pyvips.Image.new_from_file("watermark-logo.svg")x = base.width - logo.width - 24y = base.height - logo.height - 24result = base.composite2(logo, "over", x=max(x, 0), y=max(y, 0))result.write_to_file("logo-watermark.webp", Q=82)vips composite2 input.jpg watermark-logo.svg 'logo-watermark.webp[Q=82]' over --x "$X" --y "$Y"能力边界:绘图在各 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 移植的操作。