2 $Id: ImagingPsd.pas 154 2008-12-27 15:41:09Z galfar $
3 Vampyre Imaging Library
5 http://imaginglib.sourceforge.net
7 The contents of this file are used with permission, subject to the Mozilla
8 Public License Version 1.1 (the "License"); you may not use this file except
9 in compliance with the License. You may obtain a copy of the License at
10 http://www.mozilla.org/MPL/MPL-1.1.html
12 Software distributed under the License is distributed on an "AS IS" basis,
13 WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
14 the specific language governing rights and limitations under the License.
16 Alternatively, the contents of this file may be used under the terms of the
17 GNU Lesser General Public License (the "LGPL License"), in which case the
18 provisions of the LGPL License are applicable instead of those above.
19 If you wish to allow use of your version of this file only under the terms
20 of the LGPL License and not to allow others to use your version of this file
21 under the MPL, indicate your decision by deleting the provisions above and
22 replace them with the notice and other provisions required by the LGPL
23 License. If you do not delete the provisions above, a recipient may use
24 your version of this file under either the MPL or the LGPL License.
26 For more information about the LGPL: http://www.gnu.org/copyleft/lesser.html
29 { This unit contains image format loader/saver for Photoshop PSD image format.}
32 {$I ImagingOptions.inc}
37 SysUtils
, ImagingTypes
, Imaging
, ImagingColors
, ImagingUtility
;
40 { Class for loading and saving Adobe Photoshop PSD images.
41 Loading and saving of indexed, grayscale, RGB(A), HDR (FP32), and CMYK
42 (auto converted to RGB) images is supported. Non-HDR gray, RGB,
43 and CMYK images can have 8bit or 16bit color channels.
44 There is no support for loading mono images, duotone images are treated
45 like grayscale images, and multichannel and CIE Lab images are loaded as
46 RGB images but without actual conversion to RGB color space.
47 Also no layer information is loaded.}
48 TPSDFileFormat
= class(TImageFileFormat
)
50 FSaveAsLayer
: LongBool;
51 function LoadData(Handle
: TImagingHandle
; var Images
: TDynImageDataArray
;
52 OnlyFirstLevel
: Boolean): Boolean; override;
53 function SaveData(Handle
: TImagingHandle
; const Images
: TDynImageDataArray
;
54 Index
: LongInt): Boolean; override;
55 procedure ConvertToSupported(var Image
: TImageData
;
56 const Info
: TImageFormatInfo
); override;
58 constructor Create
; override;
59 function TestFormat(Handle
: TImagingHandle
): Boolean; override;
61 property SaveAsLayer
: LongBool read FSaveAsLayer write FSaveAsLayer
;
70 SPSDFormatName
= 'Photoshop Image';
71 SPSDMasks
= '*.psd,*.pdd';
72 PSDSupportedFormats
: TImageFormats
= [ifIndex8
, ifGray8
, ifA8Gray8
,
73 ifR8G8B8
, ifA8R8G8B8
, ifGray16
, ifA16Gray16
, ifR16G16B16
, ifA16R16G16B16
,
74 ifR32F
, ifA32R32G32B32F
];
75 PSDDefaultSaveAsLayer
= True;
79 CompressionNone
: Word = 0;
80 CompressionRLE
: Word = 1;
84 { PSD Image color mode.}
96 { PSD image main header.}
97 TPSDHeader
= packed record
98 Signature
: TChar4
; // Format ID '8BPS'
99 Version
: Word; // Always 1
100 Reserved
: array[0..5] of Byte; // Reserved, all zero
101 Channels
: Word; // Number of color channels (1-24) including alpha channels
102 Rows
: LongWord; // Height of image in pixels (1-30000)
103 Columns
: LongWord; // Width of image in pixels (1-30000)
104 Depth
: Word; // Number of bits per channel (1, 8, and 16)
105 Mode
: TPSDColorMode
; // Color mode
108 TPSDChannelInfo
= packed record
109 ChannelID
: Word; // 0 = Red, 1 = Green, 2 = Blue etc., -1 = Transparency mask, -2 = User mask
110 Size
: LongWord; // Size of channel data.
113 procedure SwapHeader(var Header
: TPSDHeader
);
115 Header
.Version
:= SwapEndianWord(Header
.Version
);
116 Header
.Channels
:= SwapEndianWord(Header
.Channels
);
117 Header
.Depth
:= SwapEndianWord(Header
.Depth
);
118 Header
.Rows
:= SwapEndianLongWord(Header
.Rows
);
119 Header
.Columns
:= SwapEndianLongWord(Header
.Columns
);
120 Header
.Mode
:= TPSDColorMode(SwapEndianWord(Word(Header
.Mode
)));
124 TPSDFileFormat class implementation
127 constructor TPSDFileFormat
.Create
;
130 FName
:= SPSDFormatName
;
133 FIsMultiImageFormat
:= False;
134 FSupportedFormats
:= PSDSupportedFormats
;
137 FSaveAsLayer
:= PSDDefaultSaveAsLayer
;
138 RegisterOption(ImagingPSDSaveAsLayer
, @FSaveAsLayer
);
141 function TPSDFileFormat
.LoadData(Handle
: TImagingHandle
;
142 var Images
: TDynImageDataArray
; OnlyFirstLevel
: Boolean): Boolean;
146 RawPal
: array[0..767] of Byte;
147 Compression
, PackedSize
: Word;
148 LineSize
, ChannelPixelSize
, WidthBytes
,
149 CurrChannel
, MaxRLESize
, I
, Y
, X
: LongInt;
150 Info
: TImageFormatInfo
;
151 PackedLine
, LineBuffer
: PByte;
152 RLELineSizes
: array of Word;
159 { PackBits RLE decode code from Mike Lischke's GraphicEx library.}
160 procedure DecodeRLE(Source
, Dest
: PByte; PackedSize
, UnpackedSize
: LongInt);
164 while (UnpackedSize
> 0) and (PackedSize
> 0) do
166 Count
:= ShortInt(Source
^);
171 // Replicate next byte -Count + 1 times
175 if Count
> UnpackedSize
then
176 Count
:= UnpackedSize
;
177 FillChar(Dest
^, Count
, Source
^);
181 Dec(UnpackedSize
, Count
);
185 // Copy next Count + 1 bytes from input
187 if Count
> UnpackedSize
then
188 Count
:= UnpackedSize
;
189 if Count
> PackedSize
then
191 Move(Source
^, Dest
^, Count
);
194 Dec(PackedSize
, Count
);
195 Dec(UnpackedSize
, Count
);
202 SetLength(Images
, 1);
203 with GetIO
, Images
[0] do
206 Read(Handle
, @Header
, SizeOf(Header
));
208 // Determine image data format
211 cmGrayscale
, cmDuoTone
:
213 if Header
.Depth
in [8, 16] then
215 if Header
.Channels
= 1 then
216 Format
:= IffFormat(Header
.Depth
= 8, ifGray8
, ifGray16
)
217 else if Header
.Channels
>= 2 then
218 Format
:= IffFormat(Header
.Depth
= 8, ifA8Gray8
, ifA16Gray16
);
220 else if (Header
.Depth
= 32) and (Header
.Channels
= 1) then
225 if Header
.Depth
= 8 then
228 cmRGB
, cmMultiChannel
, cmCMYK
, cmLab
:
230 if Header
.Depth
in [8, 16] then
232 if Header
.Channels
= 3 then
233 Format
:= IffFormat(Header
.Depth
= 8, ifR8G8B8
, ifR16G16B16
)
234 else if Header
.Channels
>= 4 then
235 Format
:= IffFormat(Header
.Depth
= 8, ifA8R8G8B8
, ifA16R16G16B16
);
237 else if Header
.Depth
= 32 then
238 Format
:= ifA32R32G32B32F
;
240 cmMono
:; // Not supported
243 // Exit if no compatible format was found
244 if Format
= ifUnknown
then
247 NewImage(Header
.Columns
, Header
.Rows
, Format
, Images
[0]);
248 Info
:= GetFormatInfo(Format
);
250 // Read or skip Color Mode Data Block (palette)
251 Read(Handle
, @ByteCount
, SizeOf(ByteCount
));
252 ByteCount
:= SwapEndianLongWord(ByteCount
);
253 if Format
= ifIndex8
then
255 // Read palette only for indexed images
256 Read(Handle
, @RawPal
, SizeOf(RawPal
));
260 Palette
[I
].R
:= RawPal
[I
+ 0];
261 Palette
[I
].G
:= RawPal
[I
+ 256];
262 Palette
[I
].B
:= RawPal
[I
+ 512];
266 Seek(Handle
, ByteCount
, smFromCurrent
);
268 // Skip Image Resources Block
269 Read(Handle
, @ByteCount
, SizeOf(ByteCount
));
270 ByteCount
:= SwapEndianLongWord(ByteCount
);
271 Seek(Handle
, ByteCount
, smFromCurrent
);
272 // Now there is Layer and Mask Information Block
273 Read(Handle
, @ByteCount
, SizeOf(ByteCount
));
274 ByteCount
:= SwapEndianLongWord(ByteCount
);
275 // Skip Layer and Mask Information Block
276 Seek(Handle
, ByteCount
, smFromCurrent
);
278 // Read compression flag
279 Read(Handle
, @Compression
, SizeOf(Compression
));
280 Compression
:= SwapEndianWord(Compression
);
282 if Compression
= CompressionRLE
then
284 // RLE compressed PSDs (most) have first lengths of compressed scanlines
285 // for each channel stored
286 SetLength(RLELineSizes
, Height
* Header
.Channels
);
287 Read(Handle
, @RLELineSizes
[0], Length(RLELineSizes
) * SizeOf(Word));
288 SwapEndianWord(@RLELineSizes
[0], Height
* Header
.Channels
);
289 MaxRLESize
:= RLELineSizes
[0];
290 for I
:= 1 to High(RLELineSizes
) do
292 if MaxRLESize
< RLELineSizes
[I
] then
293 MaxRLESize
:= RLELineSizes
[I
];
299 ChannelPixelSize
:= Info
.BytesPerPixel
div Info
.ChannelCount
;
300 LineSize
:= Width
* ChannelPixelSize
;
301 WidthBytes
:= Width
* Info
.BytesPerPixel
;
302 GetMem(LineBuffer
, LineSize
);
303 GetMem(PackedLine
, MaxRLESize
);
306 // Image color chanels are stored separately in PSDs so we will load
307 // one by one and copy their data to appropriate addresses of dest image.
308 for I
:= 0 to Header
.Channels
- 1 do
310 // Now determine to which color channel of destination image we are going
314 // If PSD has alpha channel we need to switch current channel order -
315 // PSDs have alpha stored after blue channel but Imaging has alpha
317 if Info
.HasAlphaChannel
and (Header
.Mode
<> cmCMYK
) then
319 if I
= Info
.ChannelCount
- 1 then
322 CurrChannel
:= Info
.ChannelCount
- 2 - I
;
325 CurrChannel
:= Info
.ChannelCount
- 1 - I
;
329 // No valid channel remains
333 if CurrChannel
>= 0 then
335 for Y
:= 0 to Height
- 1 do
337 if Compression
= CompressionRLE
then
339 // Read RLE line and decompress it
340 PackedSize
:= RLELineSizes
[I
* Height
+ Y
];
341 Read(Handle
, PackedLine
, PackedSize
);
342 DecodeRLE(PackedLine
, LineBuffer
, PackedSize
, LineSize
);
346 // Just read uncompressed line
347 Read(Handle
, LineBuffer
, LineSize
);
350 // Swap endian if needed
351 if ChannelPixelSize
= 4 then
352 SwapEndianLongWord(PLongWord(LineBuffer
), Width
)
353 else if ChannelPixelSize
= 2 then
354 SwapEndianWord(PWordArray(LineBuffer
), Width
);
356 if Info
.ChannelCount
> 1 then
358 // Copy each pixel fragment to its right place in destination image
359 for X
:= 0 to Width
- 1 do
361 Move(PByteArray(LineBuffer
)[X
* ChannelPixelSize
],
362 PByteArray(Bits
)[Y
* WidthBytes
+ X
* Info
.BytesPerPixel
+ CurrChannel
* ChannelPixelSize
],
368 // Just copy the line
369 Move(LineBuffer
^, PByteArray(Bits
)[Y
* LineSize
], LineSize
);
375 // Skip current color channel, not needed for image loading - just to
376 // get stream's position to the end of PSD
377 if Compression
= CompressionRLE
then
379 for Y
:= 0 to Height
- 1 do
380 Seek(Handle
, RLELineSizes
[I
* Height
+ Y
], smFromCurrent
);
383 Seek(Handle
, LineSize
* Height
, smFromCurrent
);
387 if Header
.Mode
= cmCMYK
then
389 // Convert CMYK images to RGB (alpha is ignored here). PSD stores CMYK
390 // channels in the way that first requires substraction from max channel value
391 if ChannelPixelSize
= 1 then
394 for X
:= 0 to Width
* Height
- 1 do
396 Col32
.A
:= 255 - PCol32
.A
;
397 Col32
.R
:= 255 - PCol32
.R
;
398 Col32
.G
:= 255 - PCol32
.G
;
399 Col32
.B
:= 255 - PCol32
.B
;
400 CMYKToRGB(Col32
.A
, Col32
.R
, Col32
.G
, Col32
.B
, PCol32
.R
, PCol32
.G
, PCol32
.B
);
408 for X
:= 0 to Width
* Height
- 1 do
410 Col64
.A
:= 65535 - PCol64
.A
;
411 Col64
.R
:= 65535 - PCol64
.R
;
412 Col64
.G
:= 65535 - PCol64
.G
;
413 Col64
.B
:= 65535 - PCol64
.B
;
414 CMYKToRGB16(Col64
.A
, Col64
.R
, Col64
.G
, Col64
.B
, PCol64
.R
, PCol64
.G
, PCol64
.B
);
421 if Header
.Depth
= 32 then
423 if (Header
.Channels
= 3) and (Header
.Mode
= cmRGB
) then
425 // RGB images were loaded as ARGB so we must wet alpha manually to 1.0
427 for X
:= 0 to Width
* Height
- 1 do
443 function TPSDFileFormat
.SaveData(Handle
: TImagingHandle
;
444 const Images
: TDynImageDataArray
; Index
: LongInt): Boolean;
446 TURect
= packed record
447 Top
, Left
, Bottom
, Right
: LongWord;
450 BlendMode
: TChar8
= '8BIMnorm';
451 LayerOptions
: array[0..3] of Byte = (255, 0, 0, 0);
452 LayerName
: array[0..7] of AnsiChar = #7'Layer 0';
454 MustBeFreed
: Boolean;
455 ImageToSave
: TImageData
;
456 Info
: TImageFormatInfo
;
458 I
, CurrChannel
, ChannelPixelSize
: LongInt;
459 LayerBlockOffset
, SaveOffset
, ChannelInfoOffset
: Integer;
460 ChannelInfo
: TPSDChannelInfo
;
463 WordVal
, LayerCount
: Word;
464 RawPal
: array[0..767] of Byte;
465 ChannelDataSizes
: array of Integer;
467 function PackLine(Src
, Dest
: PByteArray
; Length
: Integer): Integer;
469 I
, Remaining
: Integer;
473 while Remaining
> 0 do
476 // Look for characters same as the first
477 while (I
< 128) and (Remaining
- I
> 0) and (Src
[0] = Src
[I
]) do
482 Dest
[0] := Byte(-(I
- 1));
484 Dest
:= PByteArray(@Dest
[2]);
486 Src
:= PByteArray(@Src
[I
]);
492 // Look for different characters
494 while (I
< 128) and (Remaining
- (I
+ 1) > 0) and
495 ((Src
[I
] <> Src
[I
+ 1]) or (Remaining
- (I
+ 2) <= 0) or
496 (Src
[I
] <> Src
[I
+ 2])) do
500 // If there's only 1 remaining, the previous WHILE doesn't catch it
501 if Remaining
= 1 then
506 // Some distinct ones found
508 Move(Src
[0], Dest
[1], I
);
509 Dest
:= PByteArray(@Dest
[1 + I
]);
510 Src
:= PByteArray(@Src
[I
]);
518 procedure WriteChannelData(SeparateChannelStorage
: Boolean);
520 I
, X
, Y
, LineSize
, WidthBytes
, RLETableOffset
, CurrentOffset
, WrittenLineSize
: Integer;
521 LineBuffer
, RLEBuffer
: PByteArray
;
522 RLELengths
: array of Word;
525 LineSize
:= ImageToSave
.Width
* ChannelPixelSize
;
526 WidthBytes
:= ImageToSave
.Width
* Info
.BytesPerPixel
;
527 GetMem(LineBuffer
, LineSize
);
528 GetMem(RLEBuffer
, LineSize
* 3);
529 SetLength(RLELengths
, ImageToSave
.Height
* Info
.ChannelCount
);
531 // No compression for FP32, Photoshop won't open them
532 Compression
:= Iff(Info
.IsFloatingPoint
, CompressionNone
, CompressionRLE
);
534 if not SeparateChannelStorage
then
536 // This is for storing background merged image. There's only one
537 // complession flag and one RLE lenghts table for all channels
538 WordVal
:= Swap(Compression
);
539 GetIO
.Write(Handle
, @WordVal
, SizeOf(WordVal
));
540 if Compression
= CompressionRLE
then
542 RLETableOffset
:= GetIO
.Tell(Handle
);
543 GetIO
.Write(Handle
, @RLELengths
[0], SizeOf(Word) * ImageToSave
.Height
* Info
.ChannelCount
);
547 for I
:= 0 to Info
.ChannelCount
- 1 do
549 if SeparateChannelStorage
then
551 // Layer image data has compression flag and RLE lenghts table
552 // independent for each channel
553 WordVal
:= Swap(CompressionRLE
);
554 GetIO
.Write(Handle
, @WordVal
, SizeOf(WordVal
));
555 if Compression
= CompressionRLE
then
557 RLETableOffset
:= GetIO
.Tell(Handle
);
558 GetIO
.Write(Handle
, @RLELengths
[0], SizeOf(Word) * ImageToSave
.Height
);
559 ChannelDataSizes
[I
] := 0;
563 // Now determine which color channel we are going to write to file.
564 if Info
.HasAlphaChannel
then
566 if I
= Info
.ChannelCount
- 1 then
569 CurrChannel
:= Info
.ChannelCount
- 2 - I
;
572 CurrChannel
:= Info
.ChannelCount
- 1 - I
;
574 for Y
:= 0 to ImageToSave
.Height
- 1 do
576 if Info
.ChannelCount
> 1 then
578 // Copy each pixel fragment to its right place in destination image
579 for X
:= 0 to ImageToSave
.Width
- 1 do
581 Move(PByteArray(ImageToSave
.Bits
)[Y
* WidthBytes
+ X
* Info
.BytesPerPixel
+ CurrChannel
* ChannelPixelSize
],
582 PByteArray(LineBuffer
)[X
* ChannelPixelSize
], ChannelPixelSize
);
586 Move(PByteArray(ImageToSave
.Bits
)[Y
* LineSize
], LineBuffer
^, LineSize
);
588 // Write current channel line to file (swap endian if needed first)
589 if ChannelPixelSize
= 4 then
590 SwapEndianLongWord(PLongWord(LineBuffer
), ImageToSave
.Width
)
591 else if ChannelPixelSize
= 2 then
592 SwapEndianWord(PWordArray(LineBuffer
), ImageToSave
.Width
);
594 if Compression
= CompressionRLE
then
596 // Compress and write line
597 WrittenLineSize
:= PackLine(LineBuffer
, RLEBuffer
, LineSize
);
599 RLEBuffer[0] := 129; RLEBuffer[1] := 255; RLEBuffer[2] := 131; RLEBuffer[3] := 100;
600 RLEBuffer[4] := 1; RLEBuffer[5] := 0; RLEBuffer[6] := 255;}
601 RLELengths
[ImageToSave
.Height
* I
+ Y
] := SwapEndianWord(WrittenLineSize
);
602 GetIO
.Write(Handle
, RLEBuffer
, WrittenLineSize
);
606 WrittenLineSize
:= LineSize
;
607 GetIO
.Write(Handle
, LineBuffer
, WrittenLineSize
);
610 if SeparateChannelStorage
then
611 Inc(ChannelDataSizes
[I
], WrittenLineSize
);
614 if SeparateChannelStorage
and (Compression
= CompressionRLE
) then
616 // Update channel RLE lengths
617 CurrentOffset
:= GetIO
.Tell(Handle
);
618 GetIO
.Seek(Handle
, RLETableOffset
, smFromBeginning
);
619 GetIO
.Write(Handle
, @RLELengths
[ImageToSave
.Height
* I
], SizeOf(Word) * ImageToSave
.Height
);
620 GetIO
.Seek(Handle
, CurrentOffset
, smFromBeginning
);
621 Inc(ChannelDataSizes
[I
], SizeOf(Word) * ImageToSave
.Height
);
625 if not SeparateChannelStorage
and (Compression
= CompressionRLE
) then
627 // Update channel RLE lengths
628 CurrentOffset
:= GetIO
.Tell(Handle
);
629 GetIO
.Seek(Handle
, RLETableOffset
, smFromBeginning
);
630 GetIO
.Write(Handle
, @RLELengths
[0], SizeOf(Word) * ImageToSave
.Height
* Info
.ChannelCount
);
631 GetIO
.Seek(Handle
, CurrentOffset
, smFromBeginning
);
640 if MakeCompatible(Images
[Index
], ImageToSave
, MustBeFreed
) then
641 with GetIO
, ImageToSave
do
643 Info
:= GetFormatInfo(Format
);
644 ChannelPixelSize
:= Info
.BytesPerPixel
div Info
.ChannelCount
;
646 // Fill header with proper info and save it
647 FillChar(Header
, SizeOf(Header
), 0);
648 Header
.Signature
:= SPSDMagic
;
650 Header
.Channels
:= Info
.ChannelCount
;
651 Header
.Rows
:= Height
;
652 Header
.Columns
:= Width
;
653 Header
.Depth
:= Info
.BytesPerPixel
div Info
.ChannelCount
* 8;
654 if Info
.IsIndexed
then
655 Header
.Mode
:= cmIndexed
656 else if Info
.HasGrayChannel
or (Info
.ChannelCount
= 1) then
657 Header
.Mode
:= cmGrayscale
659 Header
.Mode
:= cmRGB
;
662 Write(Handle
, @Header
, SizeOf(Header
));
664 // Write palette size and data
665 LongVal
:= SwapEndianLongWord(IffUnsigned(Info
.IsIndexed
, SizeOf(RawPal
), 0));
666 Write(Handle
, @LongVal
, SizeOf(LongVal
));
667 if Info
.IsIndexed
then
669 for I
:= 0 to Info
.PaletteEntries
- 1 do
671 RawPal
[I
] := Palette
[I
].R
;
672 RawPal
[I
+ 256] := Palette
[I
].G
;
673 RawPal
[I
+ 512] := Palette
[I
].B
;
675 Write(Handle
, @RawPal
, SizeOf(RawPal
));
678 // Write empty resource and layer block sizes
680 Write(Handle
, @LongVal
, SizeOf(LongVal
));
681 LayerBlockOffset
:= Tell(Handle
);
682 Write(Handle
, @LongVal
, SizeOf(LongVal
));
684 if FSaveAsLayer
and (ChannelPixelSize
< 4) then // No Layers for FP32 images
686 LayerCount
:= SwapEndianWord(Iff(Info
.HasAlphaChannel
, Word(-1), 1)); // Must be -1 to get transparency in Photoshop
689 R
.Bottom
:= SwapEndianLongWord(Height
);
690 R
.Right
:= SwapEndianLongWord(Width
);
691 WordVal
:= SwapEndianWord(Info
.ChannelCount
);
692 Write(Handle
, @LongVal
, SizeOf(LongVal
)); // Layer section size, empty now
693 Write(Handle
, @LayerCount
, SizeOf(LayerCount
)); // Layer count
694 Write(Handle
, @R
, SizeOf(R
)); // Bounds rect
695 Write(Handle
, @WordVal
, SizeOf(WordVal
)); // Channeel count
697 ChannelInfoOffset
:= Tell(Handle
);
698 SetLength(ChannelDataSizes
, Info
.ChannelCount
); // Empty channel infos
699 FillChar(ChannelInfo
, SizeOf(ChannelInfo
), 0);
700 for I
:= 0 to Info
.ChannelCount
- 1 do
701 Write(Handle
, @ChannelInfo
, SizeOf(ChannelInfo
));
703 Write(Handle
, @BlendMode
, SizeOf(BlendMode
)); // Blend mode = normal
704 Write(Handle
, @LayerOptions
, SizeOf(LayerOptions
)); // Predefined options
705 LongVal
:= SwapEndianLongWord(16); // Extra data size (4 (mask size) + 4 (ranges size) + 8 (name))
706 Write(Handle
, @LongVal
, SizeOf(LongVal
));
708 Write(Handle
, @LongVal
, SizeOf(LongVal
)); // Mask size = 0
710 Write(Handle
, @LongVal
, SizeOf(LongVal
)); // Blend ranges size
711 Write(Handle
, @LayerName
, SizeOf(LayerName
)); // Layer name
713 WriteChannelData(True); // Write Layer image data
715 Write(Handle
, @LongVal
, SizeOf(LongVal
)); // Global mask info size = 0
717 SaveOffset
:= Tell(Handle
);
718 Seek(Handle
, LayerBlockOffset
, smFromBeginning
);
720 // Update layer and mask section sizes
721 LongVal
:= SwapEndianLongWord(SaveOffset
- LayerBlockOffset
- 4);
722 Write(Handle
, @LongVal
, SizeOf(LongVal
));
723 LongVal
:= SwapEndianLongWord(SaveOffset
- LayerBlockOffset
- 8);
724 Write(Handle
, @LongVal
, SizeOf(LongVal
));
726 // Update layer channel info
727 Seek(Handle
, ChannelInfoOffset
, smFromBeginning
);
728 for I
:= 0 to Info
.ChannelCount
- 1 do
730 ChannelInfo
.ChannelID
:= SwapEndianWord(I
);
731 if (I
= Info
.ChannelCount
- 1) and Info
.HasAlphaChannel
then
732 ChannelInfo
.ChannelID
:= Swap(Word(-1));
733 ChannelInfo
.Size
:= SwapEndianLongWord(ChannelDataSizes
[I
] + 2); // datasize (incl RLE table) + comp. flag
734 Write(Handle
, @ChannelInfo
, SizeOf(ChannelInfo
));
737 Seek(Handle
, SaveOffset
, smFromBeginning
);
740 // Write background merged image
741 WriteChannelData(False);
746 FreeImage(ImageToSave
);
750 procedure TPSDFileFormat
.ConvertToSupported(var Image
: TImageData
;
751 const Info
: TImageFormatInfo
);
753 ConvFormat
: TImageFormat
;
755 if Info
.IsFloatingPoint
then
756 ConvFormat
:= IffFormat(Info
.ChannelCount
= 1, ifR32F
, ifA32R32G32B32F
)
757 else if Info
.HasGrayChannel
then
758 ConvFormat
:= IffFormat(Info
.HasAlphaChannel
, ifA16Gray16
, ifGray16
)
759 else if Info
.RBSwapFormat
in GetSupportedFormats
then
760 ConvFormat
:= Info
.RBSwapFormat
762 ConvFormat
:= IffFormat(Info
.HasAlphaChannel
, ifA8R8G8B8
, ifR8G8B8
);
764 ConvertImage(Image
, ConvFormat
);
767 function TPSDFileFormat
.TestFormat(Handle
: TImagingHandle
): Boolean;
773 if Handle
<> nil then
775 ReadCount
:= GetIO
.Read(Handle
, @Header
, SizeOf(Header
));
777 GetIO
.Seek(Handle
, -ReadCount
, smFromCurrent
);
778 Result
:= (ReadCount
>= SizeOf(Header
)) and
779 (Header
.Signature
= SPSDMagic
) and
780 (Header
.Version
= 1);
785 RegisterImageFileFormat(TPSDFileFormat
);
790 -- TODOS ----------------------------------------------------
793 -- 0.26.1 Changes/Bug Fixes ---------------------------------
794 - PSDs are now saved with RLE compression.
795 - Mask layer saving added to SaveData for images with alpha
796 (shows proper transparency when opened in Photoshop). Can be
797 enabled/disabled using option
798 - Fixed memory leak in SaveData.
800 -- 0.23 Changes/Bug Fixes -----------------------------------
801 - Saving implemented.
802 - Loading implemented.
803 - Unit created with initial stuff!