PDF文件开发详解 第三章 图像

1,808 阅读12分钟

图像(Images)

上一章我们学习了如何创建矢量图形——一系列没有预设分辨率的可以由多个颜色空间和属性组成的线条和路径(有时还有文本)。不过很多情况下只有矢量图形不能满足我们的要求,我们还需要使用光栅图像(也可以称为位图图像)。

光栅图像(Raster Images)

当大多数人想到光栅图像时,会想到标准光栅图像格式,例如JPEG、PNG、GIF或TIFF。虽然这些格式确实包含光栅图像数据,但它们也以“图像包”的形式包含很多其他的信息。但是在PDF中,我们不能使用这种完整的“图像包”(一种特殊情况除外——参见JPEG图像),我们需要“解开”这种“图像包”来得到能用在PDF中的“原始格式”。

这种“原始形式”是一系列像素,或者更专业地说,是这些像素的二维阵列(二维是图像的高度和图像的宽度)。图 3-1 中的高度为40像素,宽度为46像素。

图 3-1 大像素 (FatBits) 图像

这种图像中的每个像素就是一个数组 —— 保存颜色空间中的每种颜色(也称为颜色分量)。如果这是一个DeviceRGB图像,那么每个像素会有三个元素,如果是DeviceCMYK图像,则每个像素将有四个元素,如果是DeviceGray图像,那就只有一个。它与颜色空间运算符采用的操作数数量是完全一样的(请参见基本颜色章节)。

所以要了解如何保存这些图像的数据,需要知道每个像素的每个颜色分量需要多少位数据来表示。大多数情况下我们用8bits来表示,这就是为什么RGB图像有时被称为24位彩色图像 (8*3=24)。但是PDF中支持更丰富的选项,允许使用1、2、4、8或16位(尽管实际上只使用了 1、8 和 16 位)。

创建图像(Adding the Image)

想把一张光栅图像添加到PDF中并展示,需要三个步骤:

  1. 创建图像字典并添加图像数据(以流对象的方式)。
  2. 在资源字典中添加对图像字典的引用。
  3. 引用内容流中的资源。

图像字典(Image dictionaries)

和其他任何PDF的数据结构一样,我们需要一个字典来表示所有和图像数据相关的信息。可以参见 Stream Objects章节,图像数据本身存储在流对象中,所以我们要用的字典就是流对象的属性字典。

图像字典实际上是一种 XObject 字典(XObject是eXternal Object的缩写,一种位于内容流外部或外部的图形对象)。另一种类型将是您用于矢量图像的类型。因为对象是外部的,所以它们可以从多个内容流中引用而不会重复。

这对于出现在文档多页上的图形非常有用,例如公司徽标/信头或幻灯片背景。

以此作为字典的基础,结合前面讨论的所有属性,我们得到了一个类似于示例 3-1 中的字典。

示例 3-1 简单的图像字典

1 0 obj
<<
    /Type                /XObject
    /Subtype             /Image
    /Height              40
    /Width               46
    /ColorSpace          /DeviceRGB
    /BitsPerComponent    8
    /Length              5520    % 40*46*3
>>
stream
% pixel data goes here
endstream
endobj

在资源字典中使用该图像对象和上一章使用ExtGState资源的方式是一样的,类似于示例3-2。

示例 3-2 图片资源字典

% in the page dictionary
/Resources <<
    /XObject <<
        /Im1    1 0 R    % reference to our 1 0 obj in the previous example
    >>
>>

内容流中的图像(Images in content streams)

首先我们需要了解“Do”运算符,该运算符采用单个操作数:资源名称。不过如果只用Do运算符和资源名的话,像下面这样的话:

/Im1 Do

页面上是不会展示任何东西的。

问题在于绘制图像XObjects的时候需要对CTM进行特殊处理,可以参考ISO 32000-1:2008, 8.9.4了解更多背景知识。不过我们现在只需要知道图像XObject需要指定一个特性矩阵“w 0 0 h 0 0”(其中w是图像的宽度,h是其高度,以像素为单位,如图像字典中所定义)。因此为了让我们的图像在没有任何变换的情况下出现在页面的左下角,内容流需要看起来像示例3-3。

示例 3-3 FatBits 鱼

q            % you don't need the q/Q, but it's a good habit!
46 0 0 40 0 0 cm
/Im1 Do
    /XObject <<
        /Im1    1 0 R    % reference to our 1 0 obj in the previous example
    >>
>>

和上一章中使用路径的时候一样,可以对图像应用任何变换组合——缩放、旋转等。不过需要始终从图像的大小开始进行变换。

JPEG图像(JPEG Images)

DCTDecode过滤器是可应用于流对象的各种过滤器之一。 使用了DCTDecode过滤器的流相当于JFIF文件,或者可以称为JPEG(或 .jpg/.jpeg)文件。JPEG文件是唯一一种无需任何修改即可放入PDF的标准图像格式——可以直接从文件中读取数据流,然后写入PDF中流对象的值里,如例 3-4 所示。当然还需要图像的大小、色彩空间这些信息。

示例 3-4 基于JPEG数据的PDF图像

1 0 obj
<<
    /Type                /XObject
    /Subtype             /Image
    /Height              246
    /Width               242
    /ColorSpace          /DeviceRGB
    /BitsPerComponent    8
    /Length              16423
    /Filter              /DCTDecode
>>
stream
% image data right from the JPEG goes here
endstream
endobj

透明属性(Transparency and Images)

虽然PDF成像模型中绘制图像一般来说会覆盖绘制范围内的像素,但我们还是有方式让图像部分或全部是完全透明或部分透明的。PDF一开始使用的方法是遮罩(masking),因为它们根据提供的标准完全“屏蔽”(关闭与打开)一组像素。现在使用和path一样的透明度模型(请参阅基本透明度),我们可以直接设置透明度。

软遮罩(Soft Masks)

在上一章我们了解到PDF中的图像处在固定色彩空间中,并有固定位数的颜色分量。在RGB色彩空间的情况下,颜色分量有三个:红、绿和蓝。这意味着没有透明度信息的空间。想解决这个问题可以像PNG和TIFF那样,定义一个具有四个分量(ARGB或RGBA)的新颜色空间,其中每个像素的第四个分量是其透明度值。但在PDF中引入透明度还需要保持100%向后兼容性,所以PDF中不能简单地使用新的色彩空间。

因此每个像素的透明度值都存储在单独的图像XObject中,而不是新的色彩空间里。原始图像XObject通过其字典中的SMask键引用这个“软遮罩”图像。软遮罩图像的字典是标准图像字典,受一些限制(参见 ISO 32000-1:2008,表 145)——最重要的是,图像的色彩空间必须是DeviceGray。每像素只需要一个透明度分量(8位)。这也是为什么大多数情况下父图像及其软遮罩的宽度和高度是相同的(尽管这不是必需的)。

给之前示例 3-3 中的图像使用软遮罩并使所有白色部分透明,我们会得到如图 3-2 所示的效果。

图 3-2 带软遮罩的FatBits鱼

% this is the soft mask
10 0 obj
<<
    /Type                /XObject
    /Subtype             /Image
    /BitsPerComponent    8
    /ColorSpace          /DeviceGray
    /Filter              /FlateDecode
    /Height              40
    /Width               46
    /Length              166    % smaller for compression
>>
stream
% masking data goes here
endstream
endobj

% this is the parent image
11 0 obj
<<
    /Type                /XObject
    /Subtype             /Image
    /BitsPerComponent    8
    /ColorSpace          /DeviceRGB
    /Filter              /FlateDecode
    /Height              40
    /Width               46
    /SMask               10 0 R
    /Length              166
>>
stream
% image data goes here
endstream
endobj

模版遮罩(Stencil Masks)

软遮罩是很强大的,因为它可以允许不同程度的透明度,但有时候我们只需要做到不去绘制某一些像素,这样就不会遮住后面的图像。这种只具有简单的开/关属性的遮罩被称为模板遮罩。它的工作方式几乎与软遮罩完全一样,除了每个像素只有一个颜色分量,而且该分量始终为1位,代表一个像素的开/关状态,0 表示开,1 表示关。如果有必要,可以通过模板遮罩的图像字典中值为[1 0]的解码(Decode)键来反转这些值。

示例 3-5为使用模板遮罩的FatBits鱼。

示例 3-5 带模板遮罩的FatBits鱼

% this is the stencil mask
10 0 obj
<<
    /Type                /XObject
    /Subtype             /Image
    /ImageMask           true
    /BitsPerComponent    1
    /Height              40
    /Width               46
    /Length              230    % (40*46)/8
>>
stream
% masking data goes here
endstream
endobj

% this is the parent image
11 0 obj
<<
    /Type                /XObject
    /Subtype             /Image
    /BitsPerComponent    8
    /ColorSpace          /DeviceRGB
    /Filter              /FlateDecode
    /Height              40
    /Width               46
    /Mask                10 0 R
    /Length              166
>>
stream
% image data goes here
endstream
endobj

色键遮罩(Color-Keyed Masks)

有些情况下,我们只需要通知PDF查看器忽略(屏蔽)特定颜色的像素。这种最简单的遮罩形式称为色键(或chroma-key)遮罩,工作原理类似于电影中的蓝屏,特定颜色的像素不会被绘制。

要在PDF中使用色键遮罩,不需要辅助图像,只需要知道想忽略什么颜色。图像字典中的Mask键将包含一个2*n个条目的数组作为其值(其中n是图像色彩空间中的颜色分量的数量):每一对指定要被屏蔽的组件的最小值和最大值。因此对于RGB 图像,我们需要六个值。因为我们只想屏蔽白色,所以最小值和最大值都是一样的——255(白色的值)。使用这种方法,完整的图像字典如图 3-3 所示。

图 3-3 带色键遮罩的FatBits鱼

11 0 obj
<<
    /Type                /XObject
    /Subtype             /Image
    /BitsPerComponent    8
    /ColorSpace          /DeviceRGB
    /Filter              /FlateDecode
    /Height              40
    /Width               46
    /Mask                [255 255 255 255 255 255]
    /Length              166
>>
stream
% image data goes here
endstream
endobj

矢量图像(Vector Images)

PDF中并没有真正的矢量图像的概念,比如EPS或EMF文件。PDF可以将内容流封装到可重用对象XObject中。与光栅图像一样,这种被称为表单(form)XObject。

这里的“表单”名称来自PostScript语法,它与交互式表单无关,交互式表单会在第 7 章中提到。

创建表单XObjects(Adding the Form XObject)

就像图像XObjects一样,创建表单XObjects需要三步:

  1. 创建表单字典并添加数据。
  2. 在资源字典中添加对表单字典的引用。
  3. 引用内容流中的资源。

表单字典(The Form Dictionary)

表单XObject是一个内容流(在内容流中讨论),与之关联的表单字典负责给该内容流提供一些额外的信息支持。可以放入页面内容流中的任何内容,都可以放入表单XObject中。这很有用,因为它支持在其他位置中重用整个PDF页面。后面会介绍如何实现。

表单字典中有四个很重要的特殊键:

BBox

最重要的,也是唯一必须的键。BBox的值是一个数组,表示内容的边界框。推荐使用大于实际内容大小的矩形,较小的矩形会导致内容被裁剪。

Matrix

矩阵是一个标准的变换矩阵,XObject被绘制时会被用到。几乎总是单位矩阵,因为具体的转换会在调用内容流进行。既然默认就是单位矩阵,那如果没有其他变换需要的时候就可以不用写。

Resources

和页面一样,如果需要额外的资源(ExtGStates、字体等),可以在Resources中引用它们。

FormType

一个没有实际用途键,因为它只有一个值,1。所以一般不要写它,只会浪费空间。

示例 3-6 展示了一个简单的表单XObject。

示例 3-6 简单的表单XObject

1 0 obj
<<
    /Type              /XObject
    /Subtype           /Form
    /BBox              [0 0 100 100]
    /FormType          1  % optional, only here for example
    /Matrix            [1 0 0 1 0 0] % optional, only here for example
    /Length            180
>>
stream
     0 0 1 rg          % set the color to blue in RGB
     0 0 100 100 re    % draw a rectangle 100x100 with the bottom left at 0,0
     F                 % fill it
endstream
endobj

定义好表单XObject之后将它添加到页面的资源字典中,就可以在内容流中引用它(参见示例 3-7)。和图像的情况不同,对于表单XObject,可以使用标准当前转换矩阵(CTM)并且无需担心大小(因为它不是以像素为单位)。

示例 3-7 引用页面字典中的XObject

% in the page dictionary
/Resources <<
    /XObject <<
        /Im1    1 0 R    % reference to our 1 0 obj in the previous example
    >>
>>

                          % in the page's content stream
q
        1 0 0 1 0 0 cm    % as with normal content, this means 100% at 0,0
    /Im1 Do
Q

当与表单XObject一起使用时,Do运算符使用我们之前讨论过的键作为其渲染或绘制的一部分。操作如下:

  • 保存当前图形状态,就像q操作符一样
  • 将表单字典的Matrix中的矩阵与当前转换矩阵(CTM)连接起来
  • 根据表单字典的BBox的值裁剪
  • 绘制在表单内容流中指定的图形对象
  • 恢复保存的图形状态,就像Q操作符一样

由于表单XObject的绘制会自动调用q/Q对,因此无需在表单XObject内容流的开始和结束再次调用。

页面转换(Copying a Page to a Form XObject)

如果我们需要开发一个工具来拼接或标记PDF文档,一个常见的操作是将PDF页面转换为表单XObject,这样就能轻松地将其合并到任何其他页面中。通过页面生成表单XObject也分三步:

  1. 把页面的所有内容流合并为一个。
  2. 根据页面的MediaBox和Rotate键计算BBox。
  3. 将资源字典从页面复制到表单XObject(如果在同一个PDF中执行此操作,可以使用浅拷贝而不是深拷贝)。

由于ProcSet键已被 ISO 32000-1:2008 弃用,可以删除ProcSet键(如果有的话)。

下一章的内容(What’s Next)

本章我们了解了PDF如何通过XObjects处理光栅和矢量图像,下一章我们研究如何绘制文本。