对于PSD文件的读取,国外已经有很多源代码可以下载,而且效率还不错,但对于PSD图层的读取,在下颇为少见,所有决心自己写一个,希望能为广大吃苦耐劳的写程序的人民出点棉力....
首先必须说明一下,PSD文件的图像储存方式和一般的Pic图或者Bitmap图极为不同,它不是以Pixel (象素点) 作为储存标准,而是以Channel (通道)作为储存的标准,例如PSD图为RGB格式时,其Channel 为 Mask Red Green Blue 4个,因为在下的源代码只是涉及到RGB的PSD图片,所以本文主要讲述如何读取RGB格式的PSD图片的各个图层.
宰牛必先磨刀.说到文件格式这样东西,就必须去下载个说明文档回来看看..
大家可以到http://www.moon-soft.com/program/FORMAT/ 找找,里面有很多不同文件格式的说明,当然也包括<<Photoshop 4.0文件.PSD文件格式的英文说明书>>.
好了,现在有说明书了,但有个遗憾是En文版的,看是能看,但是如果有个源代码对比会不会好点.嗯….去 codeproject 搜一下..
http://www.codeproject.com/bitmap/MyPSD.asp ihaml先生的读取PSD文件图片的源代码,在下个人认为ihaml写得已经很明确,而且MyPSD::CPSD类也已经比较完善,遗憾得是有些图还是会出错,但已经没关系了,可以着手动工了.
打开我们刚才下载回来得说明文档,找下第二章Document file formats ,去到
Photoshop3.0 file format 你会看到一下图表 (图1)..
(图1)
这说明了PSD文件的总结构.这里在下简单说明一下.
File Header:
为定长结构,24bytes,陈述了图片的标记,Channel数目,Rows图片高度(Pixel为单位),Columns图片宽度 (Pixel为单位),Depth图片深度,则每个Channel有多少bits,还有的就是Mode..注意,Mode为3的时候是RGB图,如果阁下的图片不是Mode=3,那么本说明文的源代码就处理不了阁下的图片了.
我们再看看其他的结构,都是变长结构,但幸运的是,每个结构的前4个byte都说明了该结构的长度,这让我们很容易的跳过其中一个结构,了解下一个结构.
图2
现在我们再回看图1.在下发现, Color Mode Data结构和Image Resources结构对读取RGB模式的图层是没有什么实际用途的.说明一下, Color Mode Data结构只有当图像模式为Indexed和Duotone时(Mode=2或者Mode=8)才有内容,否则只是为4Bytes的零值.我认为这个结构应该是给图片提供一个索引的颜色值,类似于调色板的用途,由于没有认真考证过,希望这个猜测是对的.而Image Resources结构是储存文件相关的信息,例如Photoshop版本号,文件路径,颜色的Function等等..
好了,到目前为止,我们认为File Header和Layer and Mask Information是对我们有用的,至于Image Data结构,储存的是生成后的图像信息,也就是说通过各种效果,各种处理后的图像,我们在这里可以看到.这一部分的图像读取, ihaml先生已经做得很好,建议去http://www.codeproject.com/bitmap/MyPSD.asp下载他得源代码分析.
好了,我们最后的,也是最艰巨的任务就是分析Layer and Mask Information结构.
现在我们必须从说明文档中的Table2-10 (Layer and Mask Information) 着手.
下图 (图3) 为在下分解后得到的部分结构层次分析.
图3
当然并不是所有的信息都是有用的,我们只是用到部分结构里面的部分信息,为了统一起见,我把该用到的信息列一下:
(需要说明的是,原说明文档有一处地方是错的,Table 2-13里面的变长结构Layer应该指向的是Table2-14,不是Table2-18,请大家注意)
1.Table2-13里面的Count,让我们知道总共有多少个Layer,如果取到的值小于零,用该值的绝对值.然后通过这个数值循环下面的Table2-14(Layer Records)的读取,有多少个Layer就读取多少遍该结构.
2.Table2-14里面,Layer的Top,Bottom,Left,Right属性.
3. Table2-14里面,Layer的NumberOfChannels,找到我们的Layer里面包含的Channel数.
4.Table2-14里面,Channel Length Info (详见Table2-15),找到该Channel里面含数据的长度.
Ok,暂停一下,我们发现以上4个条款说明了一个道理,先循环Layer Count来找到每个Layer的信息,然后循环Layer信息里面的Channel数来找到每个Channel的长度信息.如图(图4)
图4
接下来继续..
5.Table2-14里面,Blend Mode Signature 和 Blend Mode Key.通过这两个属性来对应一下,判断我们读取到的结构到底对不对, Blend Mode Signature必须为"8BIM",而Blend Mode Key为Table2-14里面列表内属性的一种.
6.Table2-14里面,Extra Data Size,表明了这个结构以下4个结构的总长度.跳过这个长度,我们就可以获得下个Layer Record结构的信息了,当然,如果把全部的Layer Record信息都读完了,那以下的便是我们所十分需要的Channel Image Data结构了,那便是我们储存Channel图像的地方了....如图5
图5
实行以上6个条款,是我们要取得Channel Image Data的必要条件.
现在再用VB的调试窗口看看我们我们取得的成果.如图6
图6
好了,现在我们只是完成需要做的事情的一小半,Now,我们应该开始读取Channel Image Data结构了.
在这之前,我们必须说明一下Channel Image Data结构(Table2-18).每个Channel Image Data都是以Compression(压缩)属性开始,该属性指明以下Image Data信息的数据是否被压缩,0为不压缩,1为压缩.接着就是具体的象素数据.那么我们需要读取的Image Data长度是多少,还有就是要读取多少个Channel Image Data结构呢!呵呵,我们原来
通过第3条款和第4条款得到的NumberOfChannels和Channel Length Info就有用了.
做个例子吧,一个标准的RGB模式PSD图,里面有3个Layer,由于每个Layer拥有4个Channel(Mask Red Green Blue),则我们要读3*4=12个Channel Image Data结构,如图7
图7
我们再一次用VB的调试窗口说明下问题吧.
图8
好了,我们已经很成功的得到Layer Image Data的数据了,包括每个Channel的Compression属性和象素具体的Buffer.那我们接着就是要把每个Layer 的Channel数据投放 (转换) 到Layer 的 RGB象素形式…
因为在VB里面没有实际的指针概念,所以我们还必须借用以下的自定义Type.
Private Type COLOR
R As Integer '0~255
G As Integer
B As Integer
End Type
Private Type IMAGE_DATA
nWidth As Long
nHeight As Long
pixlData() As COLOR
End Type
如果要转换,我们就必须了解Table2-18里面的Image Data是怎样储存的.
首先是Compression=0,象素数据没有压缩的情况.
在这种情况下, Image Data储存区是以Layer的Top-Left 到 Bottom-Right的顺序,储存每个象素点的当前Channel值,每点值占1个byte.我们要做的是把Layer里面R G B的channle信息分别读取到 IMAGE_DATA结构的RGB值内..
'转换图层Channel Red的数据
If Layer_image_data(图层号).cData(1).Compression=0 Tthen
For 象素号=0 Tto 图片象素
IMAGE_DATA.pixlData(象素号).R = Layer_image_data(图层号).cData(1).Data(象素号)
Next 象素号
End If
'转换图层Channel Green的数据
If Layer_image_data(图层号).cData(2).Compression=0 Tthen
For 象素号=0 Tto 图片象素
IMAGE_DATA.pixlData(象素号).G = Layer_image_data(图层号).cData(2).Data(象素号)
Next 象素号
End If
………
……
我们再用图片说明一下把,如图9
图9
Ok,接下来我们要处理最麻烦的, Compression=1,象素数据压缩的情况.
在这种情况下, Image Data首先是 以(Layer.Bottom-Layer-Top)*2个Byte为开头,则(Layer.Height*2)Bytes,保存的是图片每一行(Columns)占用的数据长度大小.接下来的就是以2个Byte块为单位,表示Channel值在图片里有效长度(Pixel)和当前Channel值,这样以块为单位一直顺序循环下来,直到,Channel Image Data结构结束.
例如,现在有个4pixel*2 pixel的图层,图层的Channel Image Data结构如下.
我们在程序里面定义了nLen(Layer.nHeight - 1) as Long 变量表示每一行占用的字节长度,根据这个长度量顺序读取Channel值的有效象素长度和Channel值,然后转换到相对的RGB Image Data里面.
'cData(1)表示我们现在要读的是Channel R的数据
If m_layer_image_data(countLayer).cData(1).Compression = 1 Then
ReDim nLen(m_image_data.nHeight - 1)
nPos = 0 '当前Buffer的地址
'取得每行图像占用的字节长度
For i = 0 To m_image_data.nHeight - 1
nLen(i) = m_layer_image_data(countLayer).cData(1).Data(0 + nPos) * 256
+ m_layer_image_data(countLayer).cData(1).Data(1 + nPos)
nPos = nPos + 2
Next i
For nLineCount = 0 To UBound(nLen)
nBeginPos = nPos
'读取RLE Data,并分解到每个R Buffer里面
While nPos < nBeginPos + nLen(nLineCount)
'读取第一个字节,看看Channel的有效象素长度
nLenEx = m_layer_image_data(countLayer).cData(1).Data(nPos)
nPos = nPos + 1
If nLenEx = 128 Then
'当这个有效长度值为128时,不做任何事
ElseIf nLenEx < 128 Then
'当这个有效长度值<128时,实际值为nLenEx+1
'这时,Image Data结构后nLenEx+1个Byte表示的都是Channel值,他们分别对应
'图像后nLenEx+1个象素的R值
nLenEx = nLenEx + 1
For i = 0 To nLenEx - 1
m_image_data.pixlData(nPixlCount).R = m_layer_image_data(countLayer).cData(1).Data(nPos)
nPixlCount = nPixlCount + 1
nPos = nPos + 1
Next i
ElseIf nLenEx > 128 Then
'当这个有效长度值>128时,实际值为255 - nLenEx + 2
'这时,把下一个Byte表示的Channel值,赋值到图像后255 - nLenEx + 2个象素的
'R值里面
nLenEx = 255 - nLenEx + 2
For i = 0 To nLenEx - 1
m_image_data.pixlData(nPixlCount).R = m_layer_image_data(countLayer).cData(1).Data(nPos)
nPixlCount = nPixlCount + 1
Next i
nPos = nPos + 1
End If
Wend
Next nLineCount
End If
我们按照这个步骤,分别把 RGB的值都得到, Type IMAGE_DATA里面,最后用SetPixel API画到PictureBox里面.
需要记得的是,nLenEx要分成3个不同逻辑来处理(>128, <128, =128),至于为什么这样,我也不是很清楚,在下也是按照ihaml先生给出的源代码分析得到的,如果阁下想了解详细的资料,可以参考下TIFF和Macintosh ROM的资料.
在下的源码里面还有很多应该判断的的逻辑都没有写上去,包括Mask Channel的应用,Channel Image Data的预定义值为白色等…..
好了,大家可以到 http://www.ubekar.com/downloads/PSD_LayerReader.rar 下载在下的Demo源代码.
补充一点,在下之所以用VB来写Demo,是因为VB可以快速的做Buffer调试,虽然VB里面没有纯属的指针,对于Buffer的拷贝处理会比较慢,但调试和编译都比VC要快,所以大家可以尝试在做Demo时候使用比较简单的构造方式,例如VB….
如果大家对PSD的读取分析有其他见解,或者对本代码做了有效的修改或加强,又或者通过本文的某些信息编写出了更好的Demo,请告知在下,如果能附上源代码,在下无甚感激,在下养了只猫Xeden3@Hotmail.Com.
又或者阁下在编写的时候有什么疑问,也可以发Email给在下.
谢谢各位
Xeden 2005-9