说“DPI”
作者:马健
邮箱:stronghorse_mj@hotmail.com
发布:2007.03.08
更新:2007.04.02
目录
一、基本概念
二、图像文件中的DPI
三、PDG文件中的DPI
四、PDF文件中的DPI
五、DjVu文件中的DPI
一、基本概念
DPI是Dot Per Inch的缩写,字面意思就是“每英寸点数”,即在一英寸的长度上,设备能够显示、打印、扫描、拍摄……多少个点,其基本计算公式为:
DPI=象素点数÷英制长度(点/英寸)
习惯上,设备的象素点阵坐标系称为物理坐标系,其它坐标系称为逻辑坐标系。因此本文把点阵图像的象素尺寸称为物理尺寸,英制/公制尺寸称为逻辑尺寸;而DPI则可以看作是在物理尺寸和逻辑尺寸之间进行转换的桥梁:
- 在生成(扫描、拍摄)图像时,图像尺寸和设备DPI决定了最终图像的点阵尺寸。如一张宽度为5英寸的图片,在300 DPI的扫描仪上扫描,每英寸要扫描出300像素点,则最终点阵宽度=5×300=1500象素。
- 在输出(显示、打印)图像时,图像象素尺寸和设备DPI决定了最终图像的尺寸。如点阵宽度为1500象素的图像,在600 DPI的打印机上输出,每英寸上要打印600像素点,则最终图像宽度=1500÷600=2.5英寸。
从上面举的例子可以看出,当输入、输出设备的DPI不一致(这种情况很常见)时,可能会导致输出图像大小与原始输入图像大小不一致(上面例子中差了一倍)。为了解决这种不一致,让图像在所有输出设备上看起来都一样大小,通常作法是先按输入设备的DPI将点阵宽度换算回图像的原始逻辑宽度(英制、公制宽度),再按输出设备的DPI将逻辑宽度换算成输出点阵宽度。
还是以上面举的例子来说,5英寸宽的图像在300 DPI扫描仪上扫描后点阵宽度为1500象素,为了在600 DPI的打印机上打印这张图片时还能打印成5英寸宽,就需要先对这张图片进行放大,在将它放大一倍,宽度达到3000象素后,在600 DPI的设备上打印宽度=3000÷600=5英寸。
打印时的这种缩放,当然会对最终打印出来的图像造成影响。这就是为什么在用虚拟打印机将图像文件打印成PDF或DjVu时,最终文件大小、质量往往会与虚拟打印机DPI有关的原因。
当然除了使用英制单位外,DPI也可能使用公制单位,这时它的单位通常是“点/米”,即1米长度上有多少个象素点。从习惯上考虑,本文对用公制单位表示的DPI还是称为DPI,而不是DPM(Dot Per Meter)。
二、图像文件中的DPI
由于很多图像浏览、处理、印刷软件都会用输入设备的DPI计算原始图像逻辑尺寸,所以大多数常见图像格式都允许在文件结构中记录图像生成时的设备DPI:
- BMP格式:在BITMAPINFOHEADER结构体的biXPelsPerMeter、biYPelsPerMeter字段中定义, 从字面上看采用的是公制单位。
- PNG格式:在png_info结构体的x_pixels_per_unit、y_pixels_per_unit字段定义,单位是公制还是英制由phys_unit_type描述
- TIFF格式:通过TIFFTAG_XRESOLUTION、TIFFTAG_YRESOLUTION这两个tag定义,单位通过TIFFTAG_RESOLUTIONUNIT定义。
- JPG格式:JFIF格式的在APP0段(FF E0)的X_density、Y_density字段定义,单位由density_unit描述;EXIF格式的在Xresolution、Yresolution中定义,单位由ResolutionUnit描述。
从上面对图像格式的描述可以看出,大多数图像格式在存储DPI时都有共同点:
1、支持公制和英制单位。这大概就是所谓的“国际化”支持。
2、支持x、y方向各设置不同的DPI。这是由设备特性决定的:由于物理限制,某些扫描、印刷设备在两个方向上的运动精度可能不同,DPI当然也不一样,如我碰到过一台印刷设备的DPI就是600×300。在libtiff提供的测试图像中,有两张CCITT编码的Fax图像的x、y向DPI也不同,显示时如果不注意,就可能显示出变形
(园变成了扁椭圆)的图像。
三、PDG文件中的DPI
在目前发布的V1、V2版PDG文件结构中,并没有任何关于DPI的描述,那么超星浏览器所带的pdg2控件的GetDpi方法返回的DPI又是怎么回事?
其实说穿了很简单:GetDpi返回的DPI值是动态算出来的——如果图像宽度大于1200象素,则认为是300 DPI;否则是150 DPI。详见网上读书园地论坛(http://www.readfree.net/bbs/index.php)里cheming先生的相关分析文章。
对于通常的书籍页面来说,扫描图像宽度达到1200象素以上已经足够清晰,所以通常PDG收藏者(这么说是不是有点无聊?^_^)也用DPI值作为区别清晰版、快速版的标准:如果Pdg2Pic报告300 DPI,则认为是清晰版;否则认为是快速版。
大多数情况下,这种判别标准应该说是准确的,不过偶尔也有失手的时候:有时图像实在太大,即使已经按超星快速版的质量标准进行了压缩(长、宽各砍掉一半),但宽度还是超过1200象素,这时就会被误判为清晰版。不过这种情况毕竟少见,我也只在读书园地见过一页。
快速版PDG的数据流格式通常是DjVu,而DjVu格式要求在INFO段中填写DPI值。从实际解出来的数据流看,PDG中的DjVu有填150 DPI的,也有填100 DPI的。至于100 DPI是实际扫瞄时的DPI,还是超星程序员偷懒使用了djvulibre的缺省值,那就不知道了。
从目前了解的情况看,PDG文件的DPI对显示没有任何影响,超星浏览器显示时都是按照图像实际象素尺寸进行计算。不过Pdg2Pic在将PDG文件转换成图像文件时,会按照通常图像处理软件的习惯,将计算出来的PDG文件DPI(150/300)写入转换出来的图像文件。 从目前的情况看,这种做法似乎给某些原先习惯使用“打印大法”,现在转为使用FreePic2Pdf的人造成了困扰,详见下一节说明。
四、PDF文件中的DPI
PDF文件中同样没有任何地方存储DPI,但是所有PDF生成、处理软件都绕不开DPI:PDF文件格式规范规定,在描述页面及页面中的对象时,所有表示大小、位置的数值必须折算成“用户单位(User Unit)”。用户单位的缺省值为1/72英寸,即PDF文件的页面DPI缺省为72。当然这个值也可以在文件中加以更改,不过很少有人这么干。
以一幅宽度为1500象素,扫描DPI为300的图像为例,如果要在PDF中看起来的大小与被扫描的原始图像大小一样(5英寸),则在PDF页面描述中,必须定义这幅图像的宽度=1500÷300×72=360(用户单位)。
而如果要使图像上每个象素点用屏幕上一个象素点显示,则应该在PDF页面描述中,定义这幅图像的宽度=1500÷96×72=1125(用户单位)。其中96是屏幕的DPI值。
理论上说,对于图像对象,PDF允许将图像的物理表示与逻辑表示分开(参见我写的《图像转PDF的问题、方法及题外话》)。还是以上面这幅图像为例,PDF制作软件可以将原始图像按照1500象素宽度存储为图像对象,然后在页面中对这个图像对象进行引用,并按用户单位定义这个图像在页面上的位置、大小。在这种情况下,改变显示时的位置、大小,并不会对原始图像数据造成任何影响。FreePic2Pdf就是这么干的,所以在FreePic2Pdf中改变DPI,并不会改变最终PDF文件的大小,因为仅仅是图像的位置、大小被重新计算,图像数据流不会变。
但是对基于虚拟打印原理的转换软件来说,就是另外一回事了,参见前面“基本概念”部分。在这些软件中,改变目标DPI值(虚拟打印机的DPI),将对最终PDF的文件大小、清晰度产生影响,因为软件会按照虚拟DPI重新缩放图像本身。这也是我为什么一直对“打印大法”比较排斥的原因:原始图像可能会在打印过程中被重新采样。
另外对于PDF中的黑白图像来说,如果选择JBig2(从PDF 1.4,即Acrobat 5.0开始支持)有损压缩,则结果可能受DPI设置影响,也可能不受,由具体的符号匹配(Symbol Match)代码决定。《PDF Reference fifth edition》第3.3.6节对JBig2压缩的原理及作用描述如下:
The JBIG2 encoder builds a table of unique symbol bitmaps found in the image, and other symbols found later in the image are matched against the table. Matching symbols are replaced by an index into the table, and symbols that fail to match are added to the table. The table itself is compressed using other means. This method results in high compression ratios for documents in which the same symbol is repeated often, as is typical for images created by scanning text pages. It also results in high compression of white space in the image, which does not need to be encoded because it contains no symbols.
这段话对JBig2进行了专家级概括。而在判断符号是否匹配时,某些软件会结合DPI进行判断(参见后面“DjVu文件中的DPI”中对有损JB2的描述,JBig2和JB2不仅名字像,原理也一样)。但是在FreePic2Pdf中没有做这种结合,所以即使改变DPI,对FreePic2Pdf中的有损JBig2压缩结果也没有什么影响。
虽然如前所述,在FreePic2Pdf里改变DPI,不会改变最终图像数据流的长度,但是由于PDF显示软件本身的原因,在FreePic2Pdf里改变DPI后,还是可能对最终显示效果造成影响。例如某清晰版图像宽度为1634,按照缺省的96 DPI转换,在1024×768的屏幕上显示时选择“适合宽度”,在工具条上显示实际缩放比例为59%,即“缩小”;而如果按照300 DPI转换,显示比例将为185%,即“放大”。某些软件在显示PDF时,会先将图像解码,然后缩放到逻辑尺寸,再按照用户选择的缩放比例缩放成显示尺寸。对于这种软件,最终“缩小”的效果要好于最终“放大”的效果。当然最新的显示软件都没这么笨了,一般不经过中间环节,直接将图像从原始大小缩放到最终大小,这时DPI的作用就没这么明显。
正因为这个原因,FreePic2Pdf里缺省DPI是屏幕DPI——96 DPI,以免在某些软件上显示时造成质量下降。
WinDjView也有类似的毛病,所以在FreePic2Pdf之后开发的DjVuToy里,我本不想再让用户自己设置DPI的,但总有那么几个人认为不能自己设置就会浑身不舒服,所以最终还是把这个功能开放了。
五、DjVu文件中的DPI
在文件存储方面,按照Lizardtech公司出版的《Lizardtech DjVu Reference DjVu v3》第8.3.11节规定,DjVu文件在INFO段中定义DPI,单位为英制。与其他常见图像格式不同,DjVu中没有区别x、y方向DPI,即认为两个方向的DPI是一样的。在将x、y方向DPI不同的图像转换成DjVu时,必须对此加以注意,以免转完后变形。
在文件转换方面,如果按照djvulibre源代码实现,DPI仅对采用有损JB2压缩的前景蒙板层有影响,对无损JB2压缩的前景蒙板层,及前景层、背景层无影响(对DjVu各层的说明见我写的《说“层”》,JB2原理见前面“PDF文件中的DPI”):
- 在JB2有损压缩时,一般要在开始搜索符号(字符)前,先对版面进行去斑(clean),即将细小的孤立点当作噪声点去除。具体多大的点算“细小”,则由DPI决定。djvulibre中cjb2.cpp的相关计算代码为:
dpi = MAX(200, MIN(900, dpi));
largesize = MIN( 500, MAX(64, dpi));
smallsize = MAX(2, dpi/150);
tinysize = MAX(0, dpi*dpi/20000 - 1);
即在计算时,先对用户指定的DPI值有效性进行检查,小于200 DPI的一律算200 DPI,大于900 DPI的则算900 DPI;largesize是符号最大尺寸,长、宽大于等于此值的连通域均会被拆分,以避免符号粘连;smallsize是符号最小尺寸,长、宽小于等于此值的连通域均 将视需要合并,以避免一个字母被拆成两半(如字母i上面的点和下面的竖线通常是不连通的);tinysize是噪声点尺寸,长、宽小于等于此值的孤立连通域 可能会被当作噪声点加以清除。
简单来说,对于同一个原始图像,如果DPI小于等于200,只有长、宽为1个象素的孤立点会被clean掉;在DPI为300时,长、宽小于等于3个象素的孤立区域会被clean掉。 - 如果JB2选项选的是“去斑(clean)”,通常在完成上述去斑过程后,即按无损压缩处理;如果选择的是“有损(lossy)”,则在去斑后,再进行符号匹配,并对“相同”(其实是“相似”)的符号进行合并。至于什么样的符号算“相同”,则与DPI、有损级别(losslevel)有关:DPI和losslevel值越大,允许两个“相同”符号之间的差异越大,即越容易将两个有差异的符号判别为“相同”。在djvulibre中,losslevel的合法值是0~200:0即“无损”,1即“去斑”,通常“有损”时losslevel=100。
djvulibre的符号匹配算法是外国人写的,对字母、数字来说,有损JB2压缩能在较小的风险代价下,获得较高压缩比。但是对中文来说,故事就没这么美好:很多字本来就很像(如“己”和“已”、“人”和“入”、“面”和“而”等),扫瞄质量如果差点(笔画缺胳膊少腿),再被clean过程去掉点孤立的笔画或点,在有损压缩时就 可能将相似字判别成相同字。在读书园地BBS上已经有人贴过这样的实例:采用CCITT无损压缩的02H的PDG文件上没有错别字,但是同一个文件的05H版(DjVu)就出现错别字(将“面”和“而”混了)。
当然上面说的是djvulibre的实现,超星用它是确定无疑的,其他DjVu制作软件,尤其是各大商业DjVu制作软件是否也这样处理就不知道了。
在文件显示方面,在WinDjView中,按照不同的显示比例,决定是否按照DPI折算图像宽度:
- 如果选择“实际尺寸(Actual Size)”,则按照DjVu图像的实际象素尺寸显示,即图像上的一个象素点占用屏幕上的一个实际象素点。
- 选择“适合宽度(Fit Width)”、“适合高度(Fit Height)”、“适合页面(Fit Page)”、“拉伸(Stretch)”时,与选择“实际尺寸”一样,WinDjView根本就不管DjVu文件里DPI是怎么设置的,直接将象素点阵缩放到窗口宽度、高度显示。
- 如果选择“100%”,则按照图像DPI,将图像点阵尺寸按象素折算成英寸,然后按照屏幕DPI,折算成屏幕点阵尺寸显示出来。
- 选择其他百分比时,同样是先将图像点阵尺寸按DPI折算成英寸,然后按比例缩放,再按照屏幕DPI,折算成屏幕尺寸显示出来。所以有些高DPI的图像, 放大后看起来效果会有点差。这有点类似前面说的某些PDF显示软件。
正因为以上原因,我自己在看DjVu文件时,尽量不选按百分比显示,都是“Fit Width”、“Fit Page”或“Actual Size”。
在将PDG转换成DjVu时,DjVuToy直接对图像数据流进行操作,没有任何无谓的缩放,所以在选择无损JB2的情况下,就算改变DPI值,也只不过改变了最终DjVu文件的一个字段值而已,不会造成文件大小的变化。
对于其他基于虚拟打印原理实现的DjVu转换工具,与PDF虚拟打印一样,DPI是个绕不过去的坎:打印机需要按照DPI,计算最终图像大小。为了与这个最终尺寸相匹配,需要对图像进行缩放(通常是放大,因为打印机分辨率 通常比屏幕高),这也是为什么在使用这些软件时,设置不同的DPI不仅会改变文件大小,而且会改变最终图像质量的原因之一。
而在DjVuToy的“文件宽度”功能里,则通过更改每一页INFO段中的DPI值,保证在WinDjView中按照100%显示时,所有页面宽度一致。例如想将页面宽度统一为5英寸,则先找出各页的象素宽度,然后设置每一页的DPI=该页象素宽度÷5即可。这个也不需要更改原始图像数据。当然由于DjVu的DPI必须是整数,四舍五入后显示宽度可能会有一点微小的差异,影响不大。