1. 输出文件的像素比较

1. 输出文件的像素比较

假设我们有许多由现有软件包(PSTricks、TikZ、Asymptote 等)生成的图表。我们知道这些软件包在不断发展,我们需要确保现有代码仍然正确,而不必逐一进行目视检查。

当然,包作者可能会用自己的测试代码进行检查。由于代码输出是图像,作者的测试代码可能不足以测试所有可能的情况,所以我们需要自己进行单元测试。怎么做呢?使用图像处理?

答案1

您可以采用多种方法来对图形进行回归测试。

  1. 输出文件的像素比较
  2. 日志文件比较
  3. 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 linesdiff

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 压缩,因此这种文本比较很有用。原则上,我们也可以保留压缩启用状态并删除标志,-adiff这种情况下,输出将只是

Binary files test.pdf and test.ref.pdf differ

这或许是或者不是你想要的。

优点

  • 适用于任何 PDF 文件,并且不特定于 TeX,因为您可以从 PDF 中删除所有状态信息。

缺点

  • 不能跨引擎工作。
  • 需要大量存储空间。您必须存储每个输入的完整输出文件,甚至以未压缩的形式存储,这将占用更多空间。
  • 没有关于差异的视觉反馈,但您可以使用 ImageMagick 进行视觉比较来补充这种方法。
  • 非常容易受到 PDF 结构细微变化的影响。

相关内容