Hugo 是一个用 Go 编写的开源静态网站生成器 (SSG, Static Site Generator)。它非常适合那些想要自己硬编码网站, 但又不想折腾复杂运行环境、依赖和数据库的人。
不过, 静态网站嘛, 很多朋友第一反应就是: 那还能像以前用 PHP 插件一样, 自动给图片加水印吗?
兄弟!有的兄弟, 有的! 😎
让我们了解一下基础知识
从 Hugo v0.80.0 开始, 官方加入了图片叠加 (overlay) 功能, 我们就能用它来实现水印效果。所以, 首先要确保你安装的是 v0.80 或更高版本。
你可以在终端或命令行输入 hugo version
, 来检查你本地的 Hugo 版本。
本文会带你实现一个简单的水印功能, 主要针对博客文章正文里的图片 (in-post images)。如果是主题里的封面图、标题图片等, 思路类似, 但代码需要稍微调整。
为了示范, 我们会把水印加在图片的右下角
关于 Page Bundles
要实现本文的方案, 我们需要使用 Page Bundles
Hugo 有一个叫 Page Resources 的概念, 它们只能在 Page Bundles 中使用。这一点非常关键, 因为我们想要给图片添加水印, 就必须先能获取到图片资源。
Page Resources 可以包含图片、文档等, 并带有页面相对路径和元数据。
因此, 要让 “Page Bundling” 生效, 你的 content
文件夹结构应该类似这样
* (site-root)
├── content
│ ├── post-one
│ │ ├── index.md
│ │ └── images
│ │ ├── image_one.jpeg
│ │ ├── image_two.jpeg
│ │ └── image_three.jpeg
│ └── post-two
│ ├── index.md
│ └── images
│ └── image_one.jpeg
└── themes
也就是说, post-one
目录下包含 index.md
以及 images
文件夹里的所有图片, 这些文件都属于该页面的资源 (Page Resources)。
因此我们可以借助 Hugo 的资源系统, 把这些资源按需获取并处理, 比如在图片上添加水印。
关于水印图片
目前 Hugo 只能将一张图片覆盖到另一张图片上, 还不支持直接在图像上添加文本。
所以我们需要先准备好一个作为水印的 logo 图片, 并把它放在 assets
目录下, 以便 Hugo 的资源处理器能够访问。
本文我们把 logo 放在 ./assets/images/logo.png
关于 Markdown 渲染钩子
Hugo 提供了 Markdown 渲染钩子 (Markdown Render Hooks), 这些钩子允许通过自定义模板来覆盖默认的 Markdown 渲染方式。
在这里, 我们将使用 render-image 钩子来处理文章中的图片。
挂钩 render-image
文件需要放在如下所示的位置
* (site root)
├── layouts
│ └── _default
│ └── _markup
│ └── render-image.html
└── themes
代码示例
在 render-image.html
中添加以下代码, 将 ./assets/images/logo.png
作为水印覆盖到原图右下角
<!-- contents of render-image.html -->
{{- $link := split .Destination "#" }}
{{- $image := (.Page.Resources.ByType "image").GetMatch (printf "*%s*" (index $link 0)) }}
{{- $logo := (resources.Get "images/logo.png") }}
{{- if and $image $logo }}
{{- $size := math.Round (mul $image.Height 0.25) }}
{{- $size := cond (ge $size 80) ($size) (80.0) }}
{{- $logo := $logo.Resize (printf "%.0fx jpg" $size) }}
{{- $image := $image.Filter (images.Overlay $logo (sub $image.Width $logo.Width) (sub $image.Height $logo.Height) ) }}
{{- $finalUrl := cond (isset $link 1) (printf "%s#%s" ($image.Permalink) (index $link 1)) ($image.Permalink) -}}
<img src="{{ $finalUrl | safeURL }}" alt="{{ .Text }}" {{ with .Title}} title="{{ . }}" {{ end }} />
{{- else }}
<img src="{{ .Destination | safeURL }}" alt="{{ .Text }}" {{ with .Title}} title="{{ . }}" {{ end }} />
{{- end }}
代码解释
render-image
markdown-hook 拥有 这些变量, 其中 .Destination
变量提供了资源的 路径 (在我们的例子中是图片)
<!-- render-image.html -->
{{/* 我们使用 # 作为分隔符, 把图片路径拆分成两部分, 这样可以确保资源路径中没有额外的参数 */}}
{{/* 然后获取实际的图片资源, 并存储在 $image 中 */}}
{{- $link := split .Destination "#" }}
{{- $image := (.Page.Resources.ByType "image").GetMatch (printf "*%s*" (index $link 0)) }}
{{/* 接着我们获取要作为水印的图片, 并存储在 $logo 中 */}}
{{- $logo := (resources.Get "images/logo.png") }}
{{/* 检查 $image 和 $logo 是否都存在, 如果都存在则继续水印处理 */}}
{{/* 否则, 就直接显示原始图片 */}}
{{- if and $image $logo }}
{{/* 接下来对 logo 的大小进行调整, 使其与原图尺寸相匹配, 既不太大也不太小 */}}
{{/* 计算水印 logo 的大小, 按原图高度的 30% 缩放, 但最小值限制为 80px */}}
{{- $size := math.Round (mul $image.Height 0.30) }}
{{- $size := cond (ge $size 80) ($size) (80.0) }}
{{/* 调整水印 logo 大小 */}}
{{- $logo := $logo.Resize (printf "%.0fx jpg" $size) }}
{{/* 下面这行代码告诉 HUGO 在图片上添加水印 ($logo) */}}
{{/* 使用 Filter + Overlay 把 logo 叠加到原图右下角 */}}
{{/* images.Overlay 需要 3 个参数: 水印图片、X 轴位置、Y 轴位置 */}}
{{/* 将水印放到右下角: */}}
{{/* X 坐标 (右边对齐) = 图片宽度 - 水印宽度 */}}
{{/* Y 坐标 (底部对齐) = 图片高度 - 水印高度 */}}
{{- $image := $image.Filter (
images.Overlay $logo (sub $image.Width $logo.Width) (sub $image.Height $logo.Height)
) }}
{{/* 最后, 我们把之前 URL 中的参数 (通过 # 分割的部分) 加回来, 生成最终的图片地址 */}}
{{/* 保存为 $finalUrl */}}
{{- $finalUrl := cond (isset $link 1)
(printf "%s#%s" ($image.Permalink) (index $link 1))
($image.Permalink) -}}
{{/* 输出最终带水印的图片 */}}
<img src="{{ $finalUrl | safeURL }}" alt="{{ .Text }}" {{ with .Title}} title="{{ . }}" {{ end }} />
{{- else }}
{{/* 如果 $image 或 $logo 为空, 则直接显示原始图片 */}}
<img src="{{ .Destination | safeURL }}" alt="{{ .Text }}" {{ with .Title}} title="{{ . }}" {{ end }} />
{{- end }}
关键语句是下面这一行代码, 告诉 Hugo 把水印 $logo
叠加到图片上
{{- $image := $image.Filter (images.Overlay $logo (sub $image.Width $logo.Width) (sub $image.Height $logo.Height) ) }}
images.Overlay 接收 3 个参数: 水印图片、X 轴位置、Y 轴位置
然后我们使用 Filter 函数, 把水印叠加到原始图片上。
实际效果
只要用普通的 Markdown 语法写图片

就会自动渲染成带水印的图片
原文