假设我们有许多由现有软件包(PSTricks、TikZ、Asymptote 等)生成的图表。我们知道这些软件包在不断发展,我们需要确保现有代码仍然正确,而不必逐一进行目视检查。
当然,包作者可能会用自己的测试代码进行检查。由于代码输出是图像,作者的测试代码可能不足以测试所有可能的情况,所以我们需要自己进行单元测试。怎么做呢?使用图像处理?
答案1
您可以采用多种方法来对图形进行回归测试。
- 输出文件的像素比较
- 日志文件比较
- PDF 文件的文本比较
1. 输出文件的像素比较
这种方法基本上可以归结为“了解你的工具”。在这里我将使用 ImageMagick 工具compare
比较两张图片的像素。让我们从 MWE 开始
\documentclass{standalone}
\usepackage{tikz}
\begin{document}
\begin{tikzpicture}
\draw (0,0) -- (1,1);
\end{tikzpicture}
\end{document}
并将其排版为
pdflatex test.tex
mv test.pdf test.ref.pdf
这是我们的参考文件。稍后我们可以使用以下方法将新版本与参考文件进行比较
compare test.pdf test.ref.pdf -metric MAE diff.png
echo $?
这可能会打印一些有关颜色配置文件的警告,但只有退出状态($?
)才是真正重要的,其中0
表示成功,1
表示失败,其他任何内容都表示您输入的命令有错误。如果我们现在将行的终点从 更改为 ,则(1,1)
终端(1.1,1.1)
上的输出将是
1203.18 (0.0183594)1
尾随的1
是退出状态(失败)并且diff.png
现在将包含突出显示差异的图像。
如您所见,在默认设置下,这个分辨率非常小。如何获得更高的分辨率留给读者练习吧(暗示:咨询手动的)。
优点
- 适用于任何图像,并不特定于 TeX。
- 跨引擎工作,例如,您可以将 pdfTeX 的参考图片与 LuaTeX 的输出进行比较,以确保两者生成相同的图片。
缺点
- 需要大量存储空间。您必须存储每个输入的完整输出文件。
- 尽管它原则上可以在各个引擎上运行,但字体指标的差异使得它非常脆弱且普遍不可用。
- ImageMagick 在安全性方面确实没有很好的记录(https://imagetragick.com/)。这可能不是一个太大的问题,因为毕竟我们可以信任输入文件,因为它是我们生成的,但如果用它来推出一个其他人可以提交输入的单元测试系统,我会更加小心。
2. 日志文件的比较
这种方法也适用于
l3build
系统,我确信它提供了更好的界面,但却局限于“经典 LaTeX 包”的打包工作流程。
这利用了图形是根据设备相关\special
指令(或\pdfliteral
等等)实现的这一事实。当处于活动状态时,这些指令将显示在日志文件中\tracingoutput
。它只需要对图片本身进行少量操作。
\documentclass{article}
\usepackage{tikz}
\begin{document}
\showboxbreadth=\maxdimen
\showboxdepth=\maxdimen
\tracingoutput=1
\tracingonline=1
\typeout{START}
\shipout\vbox{
\begin{tikzpicture}
\draw (0,0) -- (1,1);
\end{tikzpicture}
}
\typeout{END}
\tracingoutput=0
\tracingonline=0
\end{document}
日志文件中最有趣的部分是包含括号内容的部分START...END
。我们并不关心其他任何事情,因为测试应该与软件包版本和其他“不纯” 诸如一天中的时间之类的事情。在这个例子中,我使用
pdflatex test.tex
你可以用任何处理和报告语言提取相关位,我使用了
awk '/^START$/,/^END$/ { print }' test.log > test.ref.log
只需将其保存在图像源中并使用,例如标准 POSIX 工具diff
比较输出。
START
Completed box being shipped out [1]
\vbox(28.85274+0.0)x345.0
.\hbox(0.0+0.0)x0.0
.\hbox(28.85274+0.0)x345.0, glue set 301.14726fil
..\hbox(0.0+0.0)x15.0
..\hbox(28.85274+0.0)x28.85274
...\glue 0.2
...\hbox(0.0+0.0)x0.0, shifted -0.2
....\pdfliteral{q }
....\pdfliteral{0 G }
....\pdfliteral{0 g }
....\pdfliteral{0.3985 w }
....\hbox(0.0+0.0)x0.0
.....\pdfliteral{q }
.....\glue 0.0
.....\pdfliteral{0.0 0.0 m }
.....\pdfliteral{28.3468 28.3468 l }
.....\pdfliteral{S }
.....\glue 0.0
.....\glue 0.0
.....\pdfliteral{Q }
.....\glue 0.0 plus 1.0fil minus 1.0fil
....\pdfliteral{n }
....\pdfliteral{Q }
....\glue 0.0 plus 1.0fil minus 1.0fil
..\penalty 10000
..\glue(\parfillskip) 0.0 plus 1.0fil
..\glue(\rightskip) 0.0
END
然后你可以稍后使用以下方法比较此输出
pdflatex test.tex
awk '/^START$/,/^END$/ { print }' test.log | diff -u - test.ref.log
如果我改变线的终点,例如从(1,1)
到(1.1,1.1)
diff 将显示
--- - 2020-06-18 14:12:20.880605535 +1200
+++ test.ref.log 2020-06-18 14:10:56.947754945 +1200
@@ -1,11 +1,11 @@
START
Completed box being shipped out [1]
-\vbox(31.69818+0.0)x345.0
+\vbox(28.85274+0.0)x345.0
.\hbox(0.0+0.0)x0.0
-.\hbox(31.69818+0.0)x345.0, glue set 298.30182fil
+.\hbox(28.85274+0.0)x345.0, glue set 301.14726fil
..\hbox(0.0+0.0)x15.0
-..\hbox(31.69818+0.0)x31.69818
+..\hbox(28.85274+0.0)x28.85274
...\glue 0.2
...\hbox(0.0+0.0)x0.0, shifted -0.2
....\pdfliteral{q }
@@ -16,7 +16,7 @@
.....\pdfliteral{q }
.....\glue 0.0
.....\pdfliteral{0.0 0.0 m }
-.....\pdfliteral{31.18166 31.18166 l }
+.....\pdfliteral{28.3468 28.3468 l }
.....\pdfliteral{S }
.....\glue 0.0
.....\glue 0.0
优点
- 只需要很少的存储空间,并且参考数据是纯文本(非常适合版本控制)。
- 生成并比较参考数据非常简单,只需要很少的工具。
- 追踪发生了什么变化可能会更容易一些。
缺点
- 在 TeX 之外不起作用。
- 无法跨引擎工作
- 容易受到 TeX 引擎本身变化的影响,但实际上不会改变可见的输出。
- 这种简单的方法没有考虑到模式等全局 PDF 对象。模式只会显示为对模式的引用
\pdfliteral{/pgfprgb cs 0 0 0 /pgfpat3 scn }
,而不会显示模式代码本身。
3. PDF 文件的文本比较
您可以采取的另一种方法是对整个 PDF 进行文本比较。为此,您必须删除一些状态TeX 插入 PDF 的信息,例如引擎的名称和版本或一天中的时间。此外,我关闭了 PDF 压缩,这样文本比较实际上就能产生可用的输出。
\pdfcompresslevel=0
\pdfobjcompresslevel=0
\pdfinfo{/Producer (pdfTeX)}
\pdfinfoomitdate=1
\pdfsuppressptexinfo=1
\pdftrailerid{}
\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{patterns}
\begin{document}
\begin{tikzpicture}
\draw[pattern=north east lines] (0,0) rectangle (1,1);
\end{tikzpicture}
\end{document}
这一切都是针对 pdfTeX 的。对于其他引擎,原语非常不同,对于 XeTeX,您可能需要比较 XDV 文件,具体取决于您的目标。要生成参考数据,只需排版文档
pdflatex test.tex
mv test.pdf test.ref.pdf
现在我们在示例中更改north east lines
为。之前的方法也可以捕捉到这种变化,因为两个模式有不同的 id,但如果模式本身发生变化而不改变其 id,则不会注意到。排版更改后的示例,我们可以运行north west lines
diff
diff -au test.pdf test.ref.pdf
该-a
标志告诉的 GNU 实现diff
将输入视为文本,即使检测到二进制数据也是如此。根据您运行的系统,该标志可能具有不同的名称。由此产生的差异有点难以处理,但我们可以看到模式本身已经改变(以 和 开头的行+/Length
)+q
以及一些我们并不真正关心的外部引用内容。
--- test.pdf 2020-06-18 16:34:54.345842013 +1200
+++ test.ref.pdf 2020-06-18 16:24:34.586572786 +1200
@@ -3,10 +3,10 @@
4 0 obj
<<
/Type /Pattern /PatternType 1 /PaintType 2 /TilingType 1 /BBox [-0.99628 -0.99628 3.9851 3.9851] /XStep 2.98883 /YStep 2.98883 /Matrix [1.0 0.0 0.0 1.0 0.0 0.0] /Resources << >>
-/Length 48
+/Length 43
>>
stream
-q 0.3985 w 0.0 2.98883 m 3.08846 -0.09962 l S Q
+q 0.3985 w 0.0 0.0 m 3.08846 3.08846 l S Q
endstream
endobj
7 0 obj
@@ -24,7 +24,7 @@
0.3985 w
q
q
-/pgfprgb cs 0 0 0 /pgfpat4 scn
+/pgfprgb cs 0 0 0 /pgfpat3 scn
0.0 0.0 m
0.0 0.0 m
0.0 28.3468 l
@@ -66,7 +66,7 @@
<<>>
endobj
2 0 obj
-<< /pgfpat4 4 0 R>>
+<< /pgfpat3 4 0 R>>
endobj
3 0 obj
<< /pgfprgb [/Pattern /DeviceRGB] >>
@@ -200,25 +200,25 @@
xref
0 15
0000000000 65535 f
-0000000893 00000 n
-0000000913 00000 n
-0000000948 00000 n
+0000000888 00000 n
+0000000908 00000 n
+0000000943 00000 n
0000000015 00000 n
-0000000774 00000 n
-0000000670 00000 n
-0000000301 00000 n
-0000008648 00000 n
-0000008786 00000 n
-0000001001 00000 n
-0000001023 00000 n
-0000008429 00000 n
-0000008843 00000 n
-0000008893 00000 n
+0000000769 00000 n
+0000000665 00000 n
+0000000296 00000 n
+0000008643 00000 n
+0000008781 00000 n
+0000000996 00000 n
+0000001018 00000 n
+0000008424 00000 n
+0000008838 00000 n
+0000008888 00000 n
trailer
<< /Size 15
/Root 13 0 R
/Info 14 0 R
>>
startxref
-8965
+8960
%%EOF
由于我们之前关闭了 PDF 压缩,因此这种文本比较很有用。原则上,我们也可以保留压缩启用状态并删除标志,-a
在diff
这种情况下,输出将只是
Binary files test.pdf and test.ref.pdf differ
这或许是或者不是你想要的。
优点
- 适用于任何 PDF 文件,并且不特定于 TeX,因为您可以从 PDF 中删除所有状态信息。
缺点
- 不能跨引擎工作。
- 需要大量存储空间。您必须存储每个输入的完整输出文件,甚至以未压缩的形式存储,这将占用更多空间。
- 没有关于差异的视觉反馈,但您可以使用 ImageMagick 进行视觉比较来补充这种方法。
- 非常容易受到 PDF 结构细微变化的影响。