如何读取PSD文件(photoshop)的图层......

对于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

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值