Markdown × 静态博客——图片智能压缩与高效写作发布流

Markdown × 静态博客——图片智能压缩与高效写作发布流

Obsidian to Hugo工作流 #

Hugo允许我们使用Markdown格式进行写作,并自动转换为静态网站,Markdown编辑器可以自由地选择。我使用的是Obsidian编辑器,将.md文件同步至HomeServer后,在Linux环境中构建并预览网站,最终Push至Github并由Cloudflare解析。这样的工作流并无可说道之处,但是其中的附件(图片、视频)的处理是比较麻烦的,常规的处理办法有:

  • 使用Markdown风格链接附件,并随项目上传至Github仓库;
  • 写作时使用图床(图床服务、OSS等)托管图片并在.md文件中以Markdown风格插入链接。

不管哪种方法,在写作时都是不够流畅的,在前期、后期还可能要处理图像的压缩问题,当然我们可以引入第三方的工具比如WebP Cloud Services,但有悖于我用Cloudflare一把梭哈的理念1,且无法解决CF的R2免费存储空间(10GB)可能不足的问题。

为了让Obsidian - Hugo的写作、发布流更加顺畅,我之前使用脚本在本地(HomeServer)转换Obsidian的WiKi链接(双方括号)并压缩图像自动上传至CloudFlare R2。有24小时在线的HomeServer辅助,这套流程允许我在全平台同步写作、插入附件,足够流畅。但使用过程中发现,固定的压缩设置使得一些照片质量有明显的下降,而对于一些画面简单的图片,体积似乎还有压缩的空间。

因此,Obsidian - Hugo自动化发布的核心在于解决多媒体附件(主要是图片)问题,即自动转换附件链接、自动优化图片体积与质量、自动上传OSS。

图像优化既是一门艺术,也是一门科学:说它是一门艺术,是因为单个图像的压缩并不存在明确的最佳方案,说它是一门科学,则是因为有许多发展成熟的方法和算法都能够显著缩减图像的大小。找到图像的最佳设置需要在许多方面进行认真分析:格式能力、编码数据的内容、质量、像素尺寸等。2

图像尺寸的确定 #

首先要确定目标尺寸宽高,高清原图超大的尺寸对于Web浏览来说是无意义的3,浏览器会根据css样式和设备屏幕自动缩放图像,与其让浏览器对图像降采样,不如在服务端完成,如此可以有效减小存储体积并提高网站加载速度。

应对多设备的最佳画质图片问题,可以通过优化单张图片,或者准备数张不同尺寸的图片并根据设备自动选择。为了在有限容量的R2存储桶中尽可能多地存储文件,我们还是采用优化单张图片的路线。因此首先要确定图片的宽高尺寸应该设置为多大。

图像元素 #

根据hugo-book的样式源码:

// /themes/hugo-book/assets/_defaults.scss
$font-size-base: 16px !default;
$font-size-16: 1rem !default;
$body-min-width: 20rem !default;
$container-max-width: 80rem !default;
$menu-width: 16rem !default;
$toc-width: 16rem !default;

1rem对应16px,因此有效画布的最大尺寸为80*16px=1280px,其中菜单menu和标签toc的组件宽度固定为16rem,对于横向分辨率大于1280px的浏览器,两边自动留空。在网站的正文区域,最大横向显示尺寸为(80-16-16)*16px=768px,由此我们初步确定正文插图的最大的横向尺寸为768px,其它容器中的图像尺寸也可以在css样式中查到,或者直接通过浏览器开发者模式(ctrl+shift+c)并用样式拾取器查看。

开发者模式拾取的图片显示宽度 开发者模式拾取的图片显示宽度

对于移动端,我们在浏览器开发者模式中模拟移动设备(Device Toolbar),可以看到一些预设的设备尺寸,比如Samsung Galxy S20 Ultra的尺寸为412*915px。然而,如果我们将图像降采样为412px像素,会感到模糊不清,412px也远低于真实的屏幕分辨率。这和浏览器渲染网页使用的是“CSS 像素(viewport)”而非物理像素有关,例如Galaxy S20 Ultra的CSS 像素为412px*915px,物理像素为1080px*2400px(FHD+模式),比值Device Pixel Ratio (DPR)为2.62。

设备DPR #

在像素密度较高的设备(如 Retina 显示器)上,DPR 大于 1(例如 2、3 甚至更高)。 这意味着单个 CSS 像素将由多个物理像素表示,从而产生更清晰的图像和文本。从本质上讲,DPR允许 Web 开发人员使内容适应不同的屏幕分辨率,确保图像和文本在各种设备上显示清晰且大小合适。例如在hugo-book主题中,通过布局断点$mobile-breakpoint来进行移动端适配,$mobile-breakpoint的默认值为56rem896px

$mobile-breakpoint: $menu-width + $body-min-width * 1.2 + $toc-width !default;
@media screen and (max-width: $mobile-breakpoint) {
  .book-menu {
    visibility: hidden;
    margin-inline-start: -$menu-width;
    z-index: 1;
  }
...

但是,我们仍应准备等于或略高于CSS像素 * DPR的图像,这样才能在高清屏(高 DPR)下显示清晰。在移动端,我们也可以简单地直接取物理像素为参考值(即不考虑边距)。

目前,移动端的高分屏已达1440px,我们就取这个作为移动端的尺寸上限。在桌面端,由于高分屏的普及,DPR也随移动端一样水涨船高,比如4k屏和iMac Retina屏幕,这类屏幕的DPR一般可达 2。因此,综合现有硬件和hugo-book模板的设置,我们应该准备的插图横向尺寸不应小于1440px768*2=1536px。我们将博客中不同种类的图的最大尺寸计算出来,以最终的综合尺寸为目标像素值。

图像类型 CSS像素/px 移动端最大尺寸/px DPR(desktop) 综合尺寸/px
banner 768 1440 2.0 1536
插图 614 1440 2.0 1440
双栏插图 332 720 2.0 720
卡片插图 120 360 2.0 360

正确地截图 #

相机/手机拍摄的图像分辨远远超过参考像素值,我们对其进行降采样处理。但如果网站中有大量的屏幕截图,其原始尺寸可能就比参考像素低了,当然我们可以对其进行超采样,但这件事放在前期做效果会更好。

在浏览器中,直接通过ctrl+鼠标滚轮对窗口放大(chrome最大可以至500%),再对目标进行截图就可以得到分辨率更高的图了。桌面环境(win11),可以通过系统 - 屏幕 - 缩放和布局 增加缩放比例,让小元素放大显示。

以截本站侧边栏状态图为例,该组件的CSS像素宽度为224px,2k/25寸屏的缩放率一般为150%,若直接屏幕截图得到的图片宽度为336px,放在正文中(768px宽度)显然是不够的,我们直接将浏览器放大500%,就可以截到宽度近1700px的图,对比如下。

重采样、格式、质量 #

python的Pillow库可以快速实现图像缩放、压缩和保存。其重采样默认算法为BICUBIC,我们对时间不敏感,因此直接显式指定质量最高的LANCZOS插值4

img = img.resize((w, h), resample=Image.Resampling.LANCZOS)

在保存时,可以进一步选择格式、压缩率和质量。

img.save(output_path, format="WEBP", method=6, quality=quality)

显式指定速度最慢但压缩率最高的method=6,尽可能地降低文件大小。

显式指定格式format="WEBP"。WebP支持有损压缩与无损压缩,且支持动图和Alpha透明通道。平均而言,WebP 的文件大小比同类 JPEG 图像减少了 30%。时至今日,Webp格式已经得到绝大多数浏览器支持(仅IE不支持)5。博客网站完全可以统一使用Webp格式图像。

quality极大地影响文件体积和质量,一般来说取值60-80就能平衡好质量-体积这对矛盾。大多文章也就介绍到这了,以一个较为普适但固定的参数来应对所有场景。但我在实践中发现,固定的quality取值经常造成图像压缩过度或不足,而用肉眼去评估图像质量显然是低效的。

点击跳转至PNG原图 点击跳转至PNG原图

图像压缩与质量评价 #

评价指标 #

为了在保证质量的同时,根据每张图片的具体情况最大化地压缩图像体积,要引入评价标准,主观地来说,可以通过以下几点,但无法量化差异:

  • 是否模糊、失真(如文字边缘锯齿、细节丢失);
  • 色彩是否偏差、对比度是否异常;
  • 压缩伪影(如马赛克、块效应、色带);

尝试使用NeRF常用的评价指标来协助量化差异。

指标 全称 原理 含义 优点 缺点
PSNR Peak Signal-to-Noise Ratio 峰值信噪比 基于 MSE(均方误差)计算信号强度与噪声强度的比值 误差越小,PSNR 越高,代表压缩图像越接近原图 简单快速,历史最悠久 无法反映结构、感知差异;对视觉不敏感
SSIM Structural Similarity Index 结构相似性 模拟人眼对亮度、对比度、结构的感知 更符合人眼感知的结构对比 考虑结构相似性,视觉友好 对多尺度、大范围失真不敏感
MS-SSIM Multi-Scale SSIM 多尺度结构相似性 多分辨率下计算 SSIM,增强全局感知 比 SSIM 更稳定地反映感知质量 比 SSIM 更接近人类主观评分 计算复杂度比 SSIM 稍高
LPIPS Learned Perceptual Image Patch Similarity 感知图像差异 基于深度学习,使用特征空间距离计算感知相似度 越小越接近,越大越不相似 最贴近人眼感知,处理模糊、结构变形效果好 依赖模型,速度慢

实际操作,同时使用MS-SSIM和LPIPS来评价图像质量,分别设置阈值为0.980.02,通过搜索quality的值来获取最佳设置。我测试了博客中的20张图片,前6张为手机拍摄的照片,后14张则包含了电子书插图、屏幕截图、线稿图、PPT合成图等。以quality=80作为参考值,由结果可以看到,自适应的quality选择范围很大,10-86都有;20张图的平均结果,自适应方法节省空间84.81%,固定质量(80)则为81.69%。虽然总的来看空间节省差距不大,但可以初步看出,“照片”所需的quality要比截图、线稿要大一些,且分辨率越大的图,quality也会偏大;而画面干净的图quality可以很小,比如7.png9.png10.png19.jpg

文件名 原始 自适应 Q=80 质量Q MS-A LPIPS-A MS-Q80 LPIPS-Q80
1.jpg 425.7KB 150.5KB 185.7KB 71 0.9924 0.0191 0.9944 0.0104
2.jpg 2175.4KB 163.7KB 235.5KB 63 0.9898 0.0199 0.9926 0.0120
3.jpg 559.1KB 206.1KB 159.9KB 85 0.9913 0.0191 0.9885 0.0325
4.jpg 537.9KB 177.9KB 146.7KB 84 0.9907 0.0193 0.9889 0.0248
5.jpg 660.3KB 336.2KB 291.1KB 83 0.9912 0.0177 0.9884 0.0276
6.png 1183.5KB 67.5KB 50.9KB 86 0.9915 0.0197 0.9882 0.0309
7.png 288.1KB 61.5KB 146.5KB 12 0.9859 0.0186 0.9962 0.0041
8.jpg 232.1KB 37.9KB 55.2KB 56 0.9892 0.0200 0.9945 0.0074
9.png 101.8KB 12.0KB 26.0KB 13 0.9888 0.0185 0.9976 0.0030
10.png 200.6KB 16.7KB 41.8KB 12 0.9893 0.0189 0.9987 0.0020
11.png 159.8KB 14.8KB 26.1KB 33 0.9881 0.0197 0.9958 0.0053
12.png 402.3KB 163.4KB 147.2KB 83 0.9803 0.0072 0.9792 0.0097
13.png 128.2KB 8.9KB 16.4KB 25 0.9852 0.0179 0.9901 0.0072
14.png 925.1KB 83.1KB 155.3KB 31 0.9844 0.0197 0.9926 0.0086
15.png 642.8KB 61.8KB 114.4KB 40 0.9854 0.0198 0.9924 0.0085
16.png 749.8KB 37.4KB 38.9KB 79 0.9955 0.0188 0.9957 0.0166
17.png 880.0KB 25.1KB 38.9KB 52 0.9905 0.0193 0.9940 0.0076
18.png 523.0KB 35.1KB 41.9KB 76 0.9916 0.0182 0.9933 0.0145
19.jpg 444.8KB 53.8KB 126.7KB 10 0.9911 0.0157 0.9995 0.0009
20.jpg 331.1KB 41.4KB 69.6KB 16 0.9933 0.0189 0.9983 0.0037
总和 11551.3KB 1754.8KB 2114.6KB

与在线工具的对比 #

有很多在线图像压缩工具或CDN提供了图像优化服务,来试试我们的压缩效果与之相比如何6WebP Cloud ServicesTinyPNG的压缩率选择有较大的差别,WebP Cloud Services的质量较好,TinyPNG的压缩率较大。我们无法精细地调试这些在线工具的压缩参数,本地的灵活性则较高,通过参数的调试可以达到同样的效果。最终,根据我们设定的MS-SSIM和LPIPS阈值,使用quality=85获得了42.5KB大小的最终文件,而原文件大小599.0KB。

quality=93 WebP Cloud quality=75 TinyPNG quality=85
SIZE /KB 75.8 75.7 28.4 28.6 42.5
PSNR /dB 42.82 41.83 37.89 35.27 40.10
SSIM 0.9846 0.9782 0.9598 0.9595 0.9716
MS-SSIM 0.9969 0.9954 0.9903 0.9890 0.9937
VIF 0.8806 0.8513 0.7720 0.7661 0.8279
LPIPS 0.0055 0.0074 0.0263 0.0260 0.0198

链接转换与文件上传 #

把图片的压缩问题解决后,剩下的就很简单了:

  • 使用正则表达式匹配markdown文件中的链接(WiKI和Markdown风格);
  • 分类,判断类型:静态/动态图片、视频、本地/外链/站内链接;
  • 根据分类进行降采样、压缩,移除元数据;
  • 生成唯一文件名,保存至本地,将markdown文件中的链接替换为标准图像引用格式;
  • 将压缩参数、文件名映射保存至数据库。

以下是几种正则匹配的Markdown语法和转换后的样式:

![](image_filename | url)
[](image_filename | url)
![[image_filename | url]]
[[image_filename | url]]
>>>>>Transfer to>>>>>
![](https://img.osnsyc.top/XXXXXX.webp)
banner: [[image_filename | url]]
>>>>>Transfer to>>>>>
banner: https://img.osnsyc.top/XXXXXX.webp
{{< dbcard
	...
    cover=[[Pasted image 2025.png]]
    ...
>}}
>>>>>Transfer to>>>>>
{{< dbcard
	...
    cover="https://img.osnsyc.top/XXXXXX.webp"
    ...
>}}
![](video_filename | url)
[](video_filename | url)
![[video_filename | url]]
[[video_filename | url]]
>>>>>Transfer to>>>>>
{{< video src="https://img.osnsyc.top/XXXXXX.mp4" >}}

链接替换完成后生成一个新的markdown文件,可以由hugo正确地编译。生成的附件也可以用脚本一键上传至Cloudfalre R2存储桶。最终,一个完整的Obsidian至Hugo的工作流只需要两行命令即可完成。相关代码可以移步GitHub - osnsyc/obsidian-to-hugo-toolbox查看。

# 格式化Markdown文件
python md_publisher.py -f test_post.md
# 上传至R2
python r2_uploader.py -f <path_to_file_or_folder>

左: Obsidian,右: 静态博客 左: Obsidian,右: 静态博客

我在去年建立博客之初Hugo博客快速搭建小记写道:

我写了9篇博文,平均篇幅4570字,附件不算少,R2空间用了160MB,算下来,免费空间够写562篇长文了。

现在看来,博客共有文章24篇,各类图像都有7,优化后的附件总大小为113.9MB,所以现在算下来,CloudFlare R2免费空间够写2107篇长文了。

数量 体积/MB 平均体积
静态图片 258 23.1 0.09MB
动态图片 29 87.9 3.03MB
视频 1 3.9 3.9MB
总和 288 113.9 0.40MB

相关阅读 #


  1. Hugo博客快速搭建小记 ↩︎

  2. Google Web Fundamentals – Choose the correct level of compression ↩︎

  3. 除非是摄影网站,且需要放大展示原图的。 ↩︎

  4. 大多情况下是成立的,但在实践中,在某些情况下Resampling.BOX可能更好,比如纯线条图,边缘呈阶跃特征,Lanczos容易产生振铃。 ↩︎

  5. WebP image format | Can I use… Support tables for HTML5, CSS3, etc ↩︎

  6. 原图 https://blog.webp.se/libvips-cgo/build.png ↩︎

  7. 目前动图没有特别优化,依赖原始编码设置,从文件体积方面看,还是推荐优先使用视频展现动画。 ↩︎

前一篇 hugo博客装修小记... 随机阅读