写下 \includegraphics{plot} 时,你交给 LaTeX 的并不是一个文件名,而是一个名字的主干。graphicx 宏包会按固定顺序在后面接上 .pdf、.png、.jpg 等扩展名逐一寻找,取第一个存在的文件。正是这个设计,让同一份源文件在 pdfLaTeX、LuaLaTeX、dvipdfmx 下都能编译;也正是这个设计,造成了「明明重新生成了 PNG,图却没变」这种常见事故。本页讲 \includegraphics 的选项、用 \graphicspath 设定搜索路径、各引擎实际能读的图像格式,以及 LaTeX 说找不到文件时该怎么读那条错误。
graphicx 与 \includegraphics:为什么不写扩展名
插图由标准的 graphicx 宏包负责:先声明 \usepackage{graphicx},再写 \includegraphics[选项]{名字}。惯例是省略扩展名,看一眼内部实现就明白为什么。pdfTeX 的驱动文件 pdftex.def 明确列出了尝试的扩展名顺序:.pdf, .png, .jpg, .mps, .jpeg, .jbig2, .jb2。也就是说,如果文件夹里同时有 plot.pdf 和 plot.png,永远是 PDF 胜出。当你把图表重新导出为 PNG 而插图纹丝不动时,多半是旧的 plot.pdf 还躺在旁边。反过来说,正因为省略了扩展名,日后把 PNG 换成 PDF 才能一个字都不改原稿。graphicx 是更早的 graphics 宏包的扩展版,区别在于选项以 key=value 的形式成组传入;两者都属于 LaTeX 标准的 latex-graphics 套件,无需另行安装。
\documentclass{article}
\usepackage{graphicx}
\begin{document}
% no extension: graphicx tries .pdf, .png, .jpg ... in that order
\includegraphics[width=0.6\textwidth]{plot}
\end{document}写出扩展名不只是关掉自动选择,连错误信息的措辞都会变。\includegraphics{plot} 找不到东西时,负责搜索的是 LaTeX 内核,它会说 ! LaTeX Error: File 'plot' not found.。而 \includegraphics{plot.png} 找不到时,回答的是驱动:! Package pdftex.def Error: File 'plot.png' not found: using draft setting.。前者意为「所有扩展名都没匹配上」,后者意为「就缺这一个文件」——出现哪一条,决定了你该去哪里找。另外,TeX Live 里的 mwe 宏包会把 example-image.pdf 装进 TeX 树本身,所以在任何目录下 \includegraphics{example-image} 都能用。问答网站的最小示例里总出现这个名字,原因就在这里。
指定尺寸:width、height、scale、keepaspectratio
实际写作中几乎总要指定尺寸,而且惯例是相对于版面书写,例如 width=0.8\textwidth。写成 8cm 这类绝对值,一旦改成双栏或换了纸张尺寸,就只有插图被落在原地。\textwidth 是正文块的宽度,\linewidth 是「当前正在排的这一行的宽度」,在 minipage 或 subfigure 内部会缩成盒子的宽度。所以在盒子里写 width=\linewidth,日后改动盒子宽度时图会自动跟随。scale= 是相对于图像自身原始尺寸的倍率,适合原始尺寸本身有意义的场合,但它对版面宽度一无所知,很容易溢出;通常 width= 更稳妥。
同时写 width= 和 height=,graphicx 会老老实实照办,于是宽高比被破坏,图像变形。keepaspectratio 正是为此而设:加上它之后,两个值定义一个框,图像在框内按比例放到最大。把照片灌进固定尺寸的位置时,几乎总要带上它。另一个值得记住的检查用选项是 draft:它根本不读取图像,只排出尺寸正确的空框并在里面印出文件名,让上百张插图的文档试排快得多。写成 \usepackage[draft]{graphicx} 就对整篇文档生效。
旋转与裁剪:angle、trim、clip、viewport
angle= 接收以度为单位的逆时针旋转角。angle=90 把图向左转四分之一圈,是把横向图表放进纵向页面的常规手法。值得注意的是 angle= 与 width= 的书写顺序,因为 graphicx 按写入顺序依次应用各键。[angle=90,width=6cm] 意为「先旋转,再把结果缩放到 6cm 宽」;[width=6cm,angle=90] 意为「先做成 6cm 宽,再旋转」——最终尺寸并不相同。旋转默认围绕图像左下角进行,origin=c 把支点移到中心,origin=tr 移到右上角。
要裁掉图像边缘,就把 trim= 与 clip 配对使用。trim 按 左、下、右、上 的顺序取四个值——这个顺序继承自原点位于左下角的 PostScript 坐标系,所以和直觉对不上。默认单位是 bp(big point,一英寸的七十二分之一)。而且一定要加 clip:忘了它,本该裁掉的部分不会消失,反而会溢出并盖住周围的正文。给负值则相反,会增加边距,是在图周围留白的巧法。如果你想用绝对坐标的矩形而不是「从四边各削去多少」来指定裁剪范围,就用 viewport=,同样要配合 clip。
% half the text width
\includegraphics[width=0.5\textwidth]{photo}
% fit inside 8cm x 5cm without distortion
\includegraphics[width=8cm,height=5cm,keepaspectratio]{photo}
% rotate a quarter turn to the left, then scale the result
\includegraphics[angle=90,width=6cm]{diagram}
% shave 20bp left, 20bp bottom, 30bp right, 10bp top -- clip is mandatory
\includegraphics[trim=20 20 30 10,clip,width=6cm]{scan}
% page 3 of a multi-page PDF
\includegraphics[page=3,width=\linewidth]{report}| 选项 | 作用 |
|---|---|
width= | 输出宽度;惯例是 0.8\textwidth 这样的相对值 |
height= | 输出高度;与 width= 并用时请加 keepaspectratio |
scale= | 相对于图像原始尺寸的倍率(scale=0.5 为一半) |
angle= | 逆时针旋转角度(度);与 width= 的书写先后会改变结果 |
origin= | 旋转支点;默认左下角,c 为中心,tr 为右上角 |
keepaspectratio | 在宽高构成的框内按比例尽量放大 |
trim= ... clip | 削去左、下、右、上;单位默认 bp,clip 必不可少 |
viewport= ... clip | 以绝对坐标的矩形指定裁剪窗口;与 clip 并用 |
page= | 选择插入多页 PDF 中的第几页(默认第 1 页) |
draft | 不读取图像,只排出尺寸正确的空框并写上文件名;供试排使用 |
用 \graphicspath 指向图片文件夹
如果图片集中放在另一个文件夹,就用 \graphicspath 追加搜索路径。每个目录名各自用一对花括号包住(只有一个也要包),末尾必须加正斜杠 /(Windows 上同样用 /)。 搜索顺序可以通过实验确认:当前目录下的同名图片绝对优先;找不到时,才按书写顺序依次尝试所列目录,取先命中者。因此写成 \graphicspath{{figures/}{old-figures/}} 会形成一种好用的安排:figures/ 里的新版本自动遮住旧版本,只有尚未重画的图才会从 old-figures/ 取。含空格的路径要用引号包住,例如 {"my figures/"}。若要改动尝试的扩展名序列本身,用 \DeclareGraphicsExtensions。
\usepackage{graphicx}
% braces around each directory, trailing slash on each, searched in this order
\graphicspath{ {figures/} {../shared-figures/} }
% narrow or reorder the list of extensions graphicx will try
\DeclareGraphicsExtensions{.pdf,.png,.jpg}PDF 还是 PNG:矢量与栅格的取舍
判断标准其实只有一条:由线条和文字构成的图用矢量,带连续色调的图像用栅格。 矢量图(PDF、EPS、SVG)把点、线、曲线记为坐标,因此无论放大到什么程度轮廓都会重新计算,与打印分辨率毫无关系。曲线图、电路图、含公式的图纸属于这一类。栅格图(PNG、JPEG)是彩色像素组成的网格,放大只是让像素变大而已。照片和截图属于这一类,要送印就准备 300 dpi 以上的原始数据。把照片矢量化毫无收益,只会让文件变大。若是在 LaTeX 内部生成图形(TikZ、pgfplots),输出从一开始就是矢量。
在栅格图内部,口诀很短:照片用 JPEG,其余一律用 PNG。 JPEG 是有损压缩,善于处理照片的层次,但会在线条和文字边缘留下被称为蚊式噪声的模糊——把截图存成 JPEG,文字周围显得脏兮兮的正是这个原因。PNG 无损、支持透明度,适合大面积纯色的图和截图。至于 SVG,没有任何引擎能直接读取。svg 宏包让你可以写 \includesvg{file},但它在幕后调用 Inkscape 的命令行转成 PDF(DVI 输出时转成 EPS),并把 SVG 内的文字导出到单独文件由 LaTeX 重新排版。这需要装好 Inkscape 并以 --shell-escape 编译。若求稳妥,先用 Inkscape 之类的工具导出成 PDF 最可靠。反方向,若想把 TeX 的输出变成 SVG,可用 dvisvgm。
各引擎能读哪些格式:EPS 真的读不了吗
决定可接受格式的不是引擎,而是它背后的驱动文件。启动时 graphics.cfg 判断正在运行哪个引擎,pdfTeX 就加载 pdftex.def,XeTeX 就加载 xetex.def,输出 DVI 则加载 dvips.def;该文件里声明的扩展名列表,就是所支持格式的全部。由此要对流行说法作两处订正。其一,XeLaTeX 与 dvipdfmx 可以直接处理 EPS 和 PS:xetex.def 与 dvipdfmx.def 的扩展名列表里有 .eps 和 .ps,还一路排到 .ai、.bmp、.jp2。其二,pdfLaTeX 与 LuaLaTeX 实际上也能读 EPS。
| 编译路线 | 驱动文件 | 尝试的扩展名(按此顺序) |
|---|---|---|
pdflatex | pdftex.def | .pdf .png .jpg .mps .jpeg .jbig2 .jb2;启用 shell escape 时会补上 .eps |
lualatex | luatex.def | 与 pdftex.def 完全相同的顺序,.eps 的处理也一样 |
xelatex | xetex.def | .pdf .ai .png .jpg .jpeg .jp2 .jpf .bmp .ps .eps .mps——EPS 与 PS 都能直接处理 |
platex + dvipdfmx | dvipdfmx.def | 与 xetex.def 相同的顺序;尺寸有时取自 .xbb 辅助文件 |
latex + dvips | dvips.def | 只有 .eps .ps .eps.gz .ps.gz .eps.Z .mps——不含 PDF、PNG、JPEG |
pdfLaTeX 让 EPS 通过的机制就写在 pdftex.def 里。到 \begin{document} 时,如果 shell escape 已启用,该文件会自行把 .eps 追加进扩展名列表并载入 epstopdf-base。而且 TeX Live 默认启用的受限 shell escape 就够了,不需要 --shell-escape。受限模式下调用的是安全版 repstopdf;只有加上 --shell-escape 才会换成 epstopdf 本体。转换结果会以 myfig-eps-converted-to.pdf 的名字留在源文件旁边。不过 pdftex.def 自己也附了警告:当原本是 PDF 而非 EPS 时,这种自动转换可能给出错误的结果。要关掉它,就在 \documentclass 之前写 \newcommand{\DoNotLoadEpstopdf}{}。
% EPS under pdflatex already works; this line only makes it explicit
\usepackage{epstopdf}
% ... and this, placed BEFORE \documentclass, switches the conversion off
% \newcommand{\DoNotLoadEpstopdf}{}
% pLaTeX / upLaTeX: the driver cannot be detected, so name it
% \usepackage[dvipdfmx]{graphicx}也有理由不要过分依赖这种自动转换。用 pdflatex -no-shell-escape 运行时,.eps 根本不会加入扩展名列表,于是 myfig.eps 明明就在眼前,仍会得到 ! LaTeX Error: File 'myfig' not found.。更麻烦的是写了扩展名的情形:\includegraphics{myfig.eps} 会既不报错也不警告,安安静静地排出一个印着文件名的方框来代替图像。凡是构建服务器或合作者的工具链使用 -no-shell-escape 的场合,稳妥的做法是事先把 EPS 转成 PDF 并纳入版本管理。日文常用的 pLaTeX / upLaTeX 无法自动判别驱动,请明写 \usepackage[dvipdfmx]{graphicx}。需要 PNG、JPEG、PDF 的尺寸信息时,用 extractbb 生成 .xbb 辅助文件。
当 LaTeX 说 File not found
与图像有关的错误,措辞本身就指出了原因。记住四条,绝大多数事故当场就能收拾。! LaTeX Error: File 'plot' not found. 意为「扩展名列表里没有一个能对上」——拼写错误、缺少 \graphicspath,或者只有 EPS 而 shell escape 被关掉了。! Package pdftex.def Error: File 'plot.png' not found: using draft setting. 意为「就缺这一个文件」。! LaTeX Error: Unknown graphics extension: .tiff. 意为「文件在,但这条路线不认识这个格式」——把 TIFF 或 WebP 直接递过去就会这样。请转成 PNG 或 PDF。
第四条性质不同。! LaTeX Error: Cannot determine size of graphic in plot.png (no BoundingBox). 意为「文件读到了,但不知道尺寸」。缺少 BoundingBox 行的 EPS 会触发它,但日常工作中遇到它的压倒性原因是本想用 pdflatex 却启动了 latex。latex 走的是 DVI 路线,因此加载的是 dvips.def,它无法把 PNG 或 JPEG 的字节流读作尺寸。如果所有插图突然一起坏掉,先怀疑编译命令。
- EPS 的 BoundingBox:缺行或数值错误会导致尺寸无法确定。用
ps2eps或epstool重写。 - 多页 PDF:默认只取第 1 页。用
page=选择页面;要整份文档插入,请用pdfpages宏包。 - PDF 的留白:图形 PDF 边距过大会让图显得很小。先用
pdfcrop裁掉再插入。 - 文件名里的空格与句点:
my plot.tar.gz这类名字难以干净地切出扩展名。请只用字母、数字、连字符和下划线。 - 写死扩展名:指定扩展名会妨碍以后更换格式。一般只写不带扩展名的名称。
最后要确认一点:\includegraphics 只是把图放在原地,不加编号也不加说明文字。论文里要写「参见图 3」,就得把图放进 figure 环境并配上 \caption 和 \label。此后的事——[htbp] 究竟把图落在哪里、说明文字怎么修饰、如何拆成 (a)(b)——由本页的两个姊妹页负责。