jpg图片无损放入PDF的程序,PDF文件格式分析,图像表达方式

wxleasyland@139.com

用打印的方式,一般是将JPG解码后的数据发给打印机,而不是JPG原始数据,所以PDF打印机会重新压缩。

不对JPG图像进行解压,直接将JPG图像传送到虚拟打印机,也是可以的,但需要虚拟打印机支持、打印软件支持才行。

用直接文件转换的方法来转换成PDF,会比较好。

参见《图像转PDF的问题、方法及题外话》,很好很有深度的文章。

图片放WORD2007中,再打印dopdf,得到的pdf中的图片居然分成了2个。说明WORD不是直接将JPG传给dopdf的。

源JPG图像acdsee打印到dopdf,会根据情况:

如果显示到页面上比较大,则PDF中的图片是源图像分辨率,但PDF中的图像数据和源JPG图像数据不同,不像是acdsee直接传JPG给dopdf;

如果显示到页面上比较小,则PDF中的图片分辨率比源图像小,应是acdsee有缩小分辨率再打印给dopdf。

所以,还是自己直接将JPG源文件嵌到PDF中吧,达到无损插入不再重新压缩解压,应该不太难,研究下。

一、PDF格式简单分析

  1. PDF内容是区分大小写的

  1. PDF由一个个对象组成

一般是一个个“间接”对象,再加上文件头、xref、trailer、startxref、文件结束标识。

间接对象:就是比如像(对象号为12,版本号为0,obj表示是一个间接对象)

12 0 obj

对象内容

endobj

xref和trailer可以改成用交叉流对象,现在流行这样的方式,见后述。

对象还可以放在另一个对象(流对象)里,用索引号的方式引用。现在流行这样的方式。

  1. PDF按行进行存贮,行末有回车

行末有回车,回车可以是0D,或0A,或0D 0A。

PDF规范:The carriage return (CR) and line feed (LF) characters, also called newline characters, are treated as end-of-line (EOL) markers.   The combination of a carriage return followed immediately by a line feed is treated as one EOL marker. For the most part, EOL markers are treated the same as any other white-space characters.   However, sometimes an EOL marker is required or recommended—that is, the following token must appear at the beginning of a line.

CR、LF都当作是EOL,CR LF组合也当作是一个EOL。

Each line is terminated by an end-of-line (EOL) marker, which may be a carriage return (character code 13), a line feed (character code 10), or both.

实际发现,如果采用OD OA作为回车,ACROBAT READER关闭时会提示保存文件,如果采用0A作为回车,一切正常。哎,可能还是有区别的,只采用0A作为回车吧,即\n。

另外,如果stream流数据是二进制的,放入PDF时,需要在流数据后面加一个回车,这个回车不属于流数据,不算在流数据的/Length里,而是属于PDF的格式。

如果stream流数据是文本,如果本身最后带回车,则可以不再加回车了,本身的回车算在流数据的/Length里。

  1. PDF数据类型(对象)

实际是叫它对象,所有东西皆对象,这里还是叫它类型吧。

空类型:null。

布尔类型:True , false。

数字类型:以十进制表示。

串类型:包含在()内。

名称类型:以斜线/打头。

数组类型:包含在[]内。

“字典”Dictionary类型:是指一系列属性值的集合,包含在双尖括号 << >> 内,每两个元素为一对,第一个为key, 第二个为value。字典的存贮方式可以不采用回车,直接连着写。

流类型:包含在间接对象的标识符Stream和endstream内。

间接对象:见前述。  要使用一个间接对象,用这样12 0 R(对象编号、版本号及关键字R),R表示引用该对象。

  1. PDF文件分析示例:

ACDSEE打印到DOPDF,PDF是DOPDF生成的:即ACDSEE把解码后的图像数据传给DOPDF,DOPDF生成PDF。

得到的PDF文件,分析如下:

%PDF-1.5           文件头

%忏嫌              从%字符后面的所有的资料都是注解(除了流数据中的%字符外)

6 0 obj     间接对象,对象6存贮了图像(对象号=6   对象版本号=0    obj表示是一个间接对象)

<<                     字典开始

   /Type /XObject         对象类型是XObject

   /Subtype /Image          是一个图像

   /BitsPerComponent 8       每个颜色成份8bit

   /Width 1200                图像宽像素

   /Height 1600               图像高像素

   /ColorSpace /DeviceRGB     颜色空间

   /Filter /DCTDecode         解码器是采用JPEG图像格式

   /Length 224129            流数据的长度(已验证,就是纯流数据的长度)

>>                      字典结束

stream

。。。。。。。。。。。流数据,注意最后会加一个回车,这个回车不属于流数据,而是属于PDF的格式。这里是JPEG图像文件的内容,和.jpg文件内容完全一模一样,可以直接提取出来作为.jpg文件。

endstream

endobj

1 0 obj                 对象1存贮了目录

<<

   /Type /Catalog

   /Pages 2 0 R                    pages对象是对象号0,   R表示引用该对象

   /PageLayout /OneColumn

   /PageMode /UseNone

>>

endobj

2 0 obj                 对象2存贮了pages

<<

   /Type /Pages

   /Kids [3 0 R ]         kids对象说明它的子页对象为对象号3, R表示引用该对象

   /Count 1               页码数量为1

>>

endobj

3 0 obj                  对象3存贮了页page

<<

   /Type /Page

   /Parent 2 0 R          其父对象号为对象号2(即Pages 对象), R表示引用该对象

   /Resources <<          该页包含的资源

   /ProcSet [/PDF /Text ]

   /XObject <<

   /Img1 6 0 R

>>

>>

   /MediaBox [0 0 595 842]    页面大小,左下角、右上角坐标,以1/72英寸为单位,即A4纸X210mm,Y297mm纵向

   /Contents 4 0 R            页面内容在对象号4,   R表示引用该对象

>>

endobj

4 0 obj            对象4存贮了页面内容Contents(页面内容就是在单页上有什么文字、图像)

<<

   /Filter /FlateDecode      流数据采用ZLIB flate压缩

   /Length 57                流数据长度

>>

stream

x^+?BC  这里是deflate压缩过的ZLIB格式流数据(注:不flate压缩也是可以的,流数据直接就是文本字符,前面/Filter要去掉)

             解压出来是:

q          --保存图像状态到堆栈

1 0 0 1 0 24.2399 cm   --移动单位矩形  到(0,24.2399) ,即Y轴24.2399*1/72*25.4=8.6厘米处

595.2 0 0 793.44 0 0 cm    --扩大单位矩形成大矩形,即宽210mm, 高280mm

/Img1 Do            --将图像绘制到大矩形内

Q          --从堆栈恢复图像状态

endstream

endobj

5 0 obj                对象5存贮了摘要资讯(可以省略)

<<

   /Producer <FEFF0064006F005000440046002000560065007200200037002E00330020004200750069006C00640020003300390031002000280075006E006B006E006F0077006E002000570069006E0064006F00770073002000760065007200730069006F006E0020002D002000560065007200730069006F006E003A002000310030002E0030002E003100390030003400320020002800780036003400290029>

   /CreationDate (D:20211014085321+08'00')

>>

endobj

xref                    交叉引用表

0 7                     0(开始的对象号,即从0开始),7(对象个数)

0000000000 65535 f   每个参照表项目固定20字节,第一行是对象号0,第二行是1。。。

0000224341 00000 n      对象的在文件中的位置;对象的生成号;对象的状态(f/n)

0000224445 00000 n        对象的在文件中的位置即十进制偏移字节地址

0000224513 00000 n        对象的生成号表示对象的修改次数

0000224694 00000 n        n表示对象使用中

0000224829 00000 n

0000000015 00000 n

trailer                文件尾

<<                       

   /Root 1 0 R         Catalog的对象号,   R表示引用该对象

   /Info 5 0 R         摘要资讯的对象号,   R表示引用该对象

   /Size 7             整个PDF文件的对象个数

>>

startxref

225222                xref之前有多少十进制字节(即偏移地址)

%%EOF                 文件结束标识

  1. 用交叉流对象来表示xref和trailer

xref交叉引用表和trailer文件尾的数据内容可以合并成一个交叉流间接对象Cross-Reference Stream,这时格式就完全不一样了,比较复杂。

这时没有xref、trailer标识符了。

仍有startxref标识符,指出了交叉流对象的偏移地址。

交叉流对象数据像这样(字典的存贮方式可以不采用回车):(另外一个文件的例子)

770 0 obj回车<</Type /XRef/W[1 4 2]/Index[0 771]/Size 771/Filter /FlateDecode/DecodeParms<</Columns 7/Predictor 12>>/Length 2112/Root 743 0 R /Info 744 0 R /ID[<3059271D5C7E2ACF3781F77575989593><6A52ACB6B7B6F9DCCAABBB69674BFB1A>]>>stream回车。。。endstream回车

我加上回车是这样:(格式参见PDF规范:Cross-Reference Stream Dictionary)

770 0 obj

<<

/Type /XRef             --表示是交叉流对象

/W[1 4 2]                --表示解码后的流数据,每个entry格式是1字节+4字节+2字节

/Index[0 771]          --表示解码后的流数据中,代表的对象是从对象号0开始,共771个

/Size 771              --表示 比最大对象号大1的数值

/Filter /FlateDecode       --表示需采用ZLIB flate压缩

/DecodeParms<</Columns 7/Predictor 12>>      -- Predictor 12表示需要用PNG filter2=filter up过滤器反向解码,Columns 7表示一行7个字节数据,参见PDF规范:Predictor values

/Length 2112          --表示流数据长2112字节(未解压的)

/Root 743 0 R

/Info 744 0 R

/ID[<3059271D5C7E2ACF3781F77575989593><6A52ACB6B7B6F9DCCAABBB69674BFB1A>]

>>

stream

2112字节流数据

endstream

这里,stream存的流数据→inflate解压→PNG filter up过滤器2反向解码→得到有效数据。

inflate解压得到的数据比如:

02 00 00 00 00 00 FF FF   02 02 00 00 03 01 01 01

02 00 00 00 00 00 00 01   02 FF 00 00 FD 10 00 FF

02 00 00 00 0A 71 00 00   02 00 00 00 00 3c 00 00

因为是PNG filter2=filter up,所以每行数据是02H开头,表示filter2号。因为Columns 7,所以每行是7字节有效数据。所以每组数据是1+7=8个字节。

再PNG filter up过滤器2反向解码得到数据,比如:(过滤器解码参见《char int unsinged型计算赋值时数据情况,PDF的XREF的PNG的filter up过滤器》)

00 00 00 00 00 FF FF 02   00 00 03 01 00 00 02 00

00 03 01 00 01 01 00 00   00 11 00 00 01 00 00 0A

82 00 00 01 00 00 0A BE   00 00 01 00 00 36 07 00

这里的数据就是一个个ENTRY。根据 /W[1 4 2] 和 PDF规范:Entries in a cross-reference stream,这里是每7个字节一个ENTRY,每个ENTRY固定有3个域,分别是1、4、2字节。

比如第1个ENTRY:

00       --ENTRY类型0,表示free objects的链表。这个对象在第1个ENTRY,所以是对象号0(因为前面/Index[0 771]代表了对象是从对象号0开始

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值