ImagingPsd.pas 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807
  1. {
  2. $Id$
  3. Vampyre Imaging Library
  4. by Marek Mauder
  5. http://imaginglib.sourceforge.net
  6. The contents of this file are used with permission, subject to the Mozilla
  7. Public License Version 1.1 (the "License"); you may not use this file except
  8. in compliance with the License. You may obtain a copy of the License at
  9. http://www.mozilla.org/MPL/MPL-1.1.html
  10. Software distributed under the License is distributed on an "AS IS" basis,
  11. WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
  12. the specific language governing rights and limitations under the License.
  13. Alternatively, the contents of this file may be used under the terms of the
  14. GNU Lesser General Public License (the "LGPL License"), in which case the
  15. provisions of the LGPL License are applicable instead of those above.
  16. If you wish to allow use of your version of this file only under the terms
  17. of the LGPL License and not to allow others to use your version of this file
  18. under the MPL, indicate your decision by deleting the provisions above and
  19. replace them with the notice and other provisions required by the LGPL
  20. License. If you do not delete the provisions above, a recipient may use
  21. your version of this file under either the MPL or the LGPL License.
  22. For more information about the LGPL: http://www.gnu.org/copyleft/lesser.html
  23. }
  24. { This unit contains image format loader/saver for Photoshop PSD image format.}
  25. unit ImagingPsd;
  26. {$I ImagingOptions.inc}
  27. interface
  28. uses
  29. SysUtils, ImagingTypes, Imaging, ImagingColors, ImagingUtility;
  30. type
  31. { Class for loading and saving Adobe Photoshop PSD images.
  32. Loading and saving of indexed, grayscale, RGB(A), HDR (FP32), and CMYK
  33. (auto converted to RGB) images is supported. Non-HDR gray, RGB,
  34. and CMYK images can have 8bit or 16bit color channels.
  35. There is no support for loading mono images, duotone images are treated
  36. like grayscale images, and multichannel and CIE Lab images are loaded as
  37. RGB images but without actual conversion to RGB color space.
  38. Also no layer information is loaded.}
  39. TPSDFileFormat = class(TImageFileFormat)
  40. protected
  41. FSaveAsLayer: LongBool;
  42. function LoadData(Handle: TImagingHandle; var Images: TDynImageDataArray;
  43. OnlyFirstLevel: Boolean): Boolean; override;
  44. function SaveData(Handle: TImagingHandle; const Images: TDynImageDataArray;
  45. Index: LongInt): Boolean; override;
  46. procedure ConvertToSupported(var Image: TImageData;
  47. const Info: TImageFormatInfo); override;
  48. public
  49. constructor Create; override;
  50. function TestFormat(Handle: TImagingHandle): Boolean; override;
  51. published
  52. property SaveAsLayer: LongBool read FSaveAsLayer write FSaveAsLayer;
  53. end;
  54. implementation
  55. uses
  56. ImagingExtras;
  57. const
  58. SPSDFormatName = 'Photoshop Image';
  59. SPSDMasks = '*.psd,*.pdd';
  60. PSDSupportedFormats: TImageFormats = [ifIndex8, ifGray8, ifA8Gray8,
  61. ifR8G8B8, ifA8R8G8B8, ifGray16, ifA16Gray16, ifR16G16B16, ifA16R16G16B16,
  62. ifR32F, ifA32R32G32B32F];
  63. PSDDefaultSaveAsLayer = True;
  64. const
  65. SPSDMagic = '8BPS';
  66. CompressionNone: Word = 0;
  67. CompressionRLE: Word = 1;
  68. type
  69. {$MINENUMSIZE 2}
  70. { PSD Image color mode.}
  71. TPSDColorMode = (
  72. cmMono = 0,
  73. cmGrayscale = 1,
  74. cmIndexed = 2,
  75. cmRGB = 3,
  76. cmCMYK = 4,
  77. cmMultiChannel = 7,
  78. cmDuoTone = 8,
  79. cmLab = 9
  80. );
  81. { PSD image main header.}
  82. TPSDHeader = packed record
  83. Signature: TChar4; // Format ID '8BPS'
  84. Version: Word; // Always 1
  85. Reserved: array[0..5] of Byte; // Reserved, all zero
  86. Channels: Word; // Number of color channels (1-24) including alpha channels
  87. Rows : LongWord; // Height of image in pixels (1-30000)
  88. Columns: LongWord; // Width of image in pixels (1-30000)
  89. Depth: Word; // Number of bits per channel (1, 8, and 16)
  90. Mode: TPSDColorMode; // Color mode
  91. end;
  92. TPSDChannelInfo = packed record
  93. ChannelID: Word; // 0 = Red, 1 = Green, 2 = Blue etc., -1 = Transparency mask, -2 = User mask
  94. Size: LongWord; // Size of channel data.
  95. end;
  96. procedure SwapHeader(var Header: TPSDHeader);
  97. begin
  98. Header.Version := SwapEndianWord(Header.Version);
  99. Header.Channels := SwapEndianWord(Header.Channels);
  100. Header.Depth := SwapEndianWord(Header.Depth);
  101. Header.Rows := SwapEndianLongWord(Header.Rows);
  102. Header.Columns := SwapEndianLongWord(Header.Columns);
  103. Header.Mode := TPSDColorMode(SwapEndianWord(Word(Header.Mode)));
  104. end;
  105. {
  106. TPSDFileFormat class implementation
  107. }
  108. constructor TPSDFileFormat.Create;
  109. begin
  110. inherited Create;
  111. FName := SPSDFormatName;
  112. FCanLoad := True;
  113. FCanSave := True;
  114. FIsMultiImageFormat := False;
  115. FSupportedFormats := PSDSupportedFormats;
  116. AddMasks(SPSDMasks);
  117. FSaveAsLayer := PSDDefaultSaveAsLayer;
  118. RegisterOption(ImagingPSDSaveAsLayer, @FSaveAsLayer);
  119. end;
  120. function TPSDFileFormat.LoadData(Handle: TImagingHandle;
  121. var Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
  122. var
  123. Header: TPSDHeader;
  124. ByteCount: LongWord;
  125. RawPal: array[0..767] of Byte;
  126. Compression, PackedSize: Word;
  127. LineSize, ChannelPixelSize, WidthBytes,
  128. CurrChannel, MaxRLESize, I, Y, X: LongInt;
  129. Info: TImageFormatInfo;
  130. PackedLine, LineBuffer: PByte;
  131. RLELineSizes: array of Word;
  132. Col32: TColor32Rec;
  133. Col64: TColor64Rec;
  134. PCol32: PColor32Rec;
  135. PCol64: PColor64Rec;
  136. PColF: PColorFPRec;
  137. { PackBits RLE decode code from Mike Lischke's GraphicEx library.}
  138. procedure DecodeRLE(Source, Dest: PByte; PackedSize, UnpackedSize: LongInt);
  139. var
  140. Count: LongInt;
  141. begin
  142. while (UnpackedSize > 0) and (PackedSize > 0) do
  143. begin
  144. Count := ShortInt(Source^);
  145. Inc(Source);
  146. Dec(PackedSize);
  147. if Count < 0 then
  148. begin
  149. // Replicate next byte -Count + 1 times
  150. if Count = -128 then
  151. Continue;
  152. Count := -Count + 1;
  153. if Count > UnpackedSize then
  154. Count := UnpackedSize;
  155. FillChar(Dest^, Count, Source^);
  156. Inc(Source);
  157. Dec(PackedSize);
  158. Inc(Dest, Count);
  159. Dec(UnpackedSize, Count);
  160. end
  161. else
  162. begin
  163. // Copy next Count + 1 bytes from input
  164. Inc(Count);
  165. if Count > UnpackedSize then
  166. Count := UnpackedSize;
  167. if Count > PackedSize then
  168. Count := PackedSize;
  169. Move(Source^, Dest^, Count);
  170. Inc(Dest, Count);
  171. Inc(Source, Count);
  172. Dec(PackedSize, Count);
  173. Dec(UnpackedSize, Count);
  174. end;
  175. end;
  176. end;
  177. begin
  178. Result := False;
  179. SetLength(Images, 1);
  180. with GetIO, Images[0] do
  181. begin
  182. // Read PSD header
  183. Read(Handle, @Header, SizeOf(Header));
  184. SwapHeader(Header);
  185. // Determine image data format
  186. Format := ifUnknown;
  187. case Header.Mode of
  188. cmGrayscale, cmDuoTone:
  189. begin
  190. if Header.Depth in [8, 16] then
  191. begin
  192. if Header.Channels = 1 then
  193. Format := IffFormat(Header.Depth = 8, ifGray8, ifGray16)
  194. else if Header.Channels >= 2 then
  195. Format := IffFormat(Header.Depth = 8, ifA8Gray8, ifA16Gray16);
  196. end
  197. else if (Header.Depth = 32) and (Header.Channels = 1) then
  198. Format := ifR32F;
  199. end;
  200. cmIndexed:
  201. begin
  202. if Header.Depth = 8 then
  203. Format := ifIndex8;
  204. end;
  205. cmRGB, cmMultiChannel, cmCMYK, cmLab:
  206. begin
  207. if Header.Depth in [8, 16] then
  208. begin
  209. if Header.Channels = 3 then
  210. Format := IffFormat(Header.Depth = 8, ifR8G8B8, ifR16G16B16)
  211. else if Header.Channels >= 4 then
  212. Format := IffFormat(Header.Depth = 8, ifA8R8G8B8, ifA16R16G16B16);
  213. end
  214. else if Header.Depth = 32 then
  215. Format := ifA32R32G32B32F;
  216. end;
  217. cmMono:; // Not supported
  218. end;
  219. // Exit if no compatible format was found
  220. if Format = ifUnknown then
  221. Exit;
  222. NewImage(Header.Columns, Header.Rows, Format, Images[0]);
  223. Info := GetFormatInfo(Format);
  224. // Read or skip Color Mode Data Block (palette)
  225. Read(Handle, @ByteCount, SizeOf(ByteCount));
  226. ByteCount := SwapEndianLongWord(ByteCount);
  227. if Format = ifIndex8 then
  228. begin
  229. // Read palette only for indexed images
  230. Read(Handle, @RawPal, SizeOf(RawPal));
  231. for I := 0 to 255 do
  232. begin
  233. Palette[I].A := $FF;
  234. Palette[I].R := RawPal[I + 0];
  235. Palette[I].G := RawPal[I + 256];
  236. Palette[I].B := RawPal[I + 512];
  237. end;
  238. end
  239. else
  240. Seek(Handle, ByteCount, smFromCurrent);
  241. // Skip Image Resources Block
  242. Read(Handle, @ByteCount, SizeOf(ByteCount));
  243. ByteCount := SwapEndianLongWord(ByteCount);
  244. Seek(Handle, ByteCount, smFromCurrent);
  245. // Now there is Layer and Mask Information Block
  246. Read(Handle, @ByteCount, SizeOf(ByteCount));
  247. ByteCount := SwapEndianLongWord(ByteCount);
  248. // Skip Layer and Mask Information Block
  249. Seek(Handle, ByteCount, smFromCurrent);
  250. // Read compression flag
  251. Read(Handle, @Compression, SizeOf(Compression));
  252. Compression := SwapEndianWord(Compression);
  253. if Compression = CompressionRLE then
  254. begin
  255. // RLE compressed PSDs (most) have first lengths of compressed scanlines
  256. // for each channel stored
  257. SetLength(RLELineSizes, Height * Header.Channels);
  258. Read(Handle, @RLELineSizes[0], Length(RLELineSizes) * SizeOf(Word));
  259. SwapEndianWord(@RLELineSizes[0], Height * Header.Channels);
  260. MaxRLESize := RLELineSizes[0];
  261. for I := 1 to High(RLELineSizes) do
  262. begin
  263. if MaxRLESize < RLELineSizes[I] then
  264. MaxRLESize := RLELineSizes[I];
  265. end;
  266. end
  267. else
  268. MaxRLESize := 0;
  269. ChannelPixelSize := Info.BytesPerPixel div Info.ChannelCount;
  270. LineSize := Width * ChannelPixelSize;
  271. WidthBytes := Width * Info.BytesPerPixel;
  272. GetMem(LineBuffer, LineSize);
  273. GetMem(PackedLine, MaxRLESize);
  274. try
  275. // Image color chanels are stored separately in PSDs so we will load
  276. // one by one and copy their data to appropriate addresses of dest image.
  277. for I := 0 to Header.Channels - 1 do
  278. begin
  279. // Now determine to which color channel of destination image we are going
  280. // to write pixels.
  281. if I <= 4 then
  282. begin
  283. // If PSD has alpha channel we need to switch current channel order -
  284. // PSDs have alpha stored after blue channel but Imaging has alpha
  285. // before red.
  286. if Info.HasAlphaChannel and (Header.Mode <> cmCMYK) then
  287. begin
  288. if I = Info.ChannelCount - 1 then
  289. CurrChannel := I
  290. else
  291. CurrChannel := Info.ChannelCount - 2 - I;
  292. end
  293. else
  294. CurrChannel := Info.ChannelCount - 1 - I;
  295. end
  296. else
  297. begin
  298. // No valid channel remains
  299. CurrChannel := -1;
  300. end;
  301. if CurrChannel >= 0 then
  302. begin
  303. for Y := 0 to Height - 1 do
  304. begin
  305. if Compression = CompressionRLE then
  306. begin
  307. // Read RLE line and decompress it
  308. PackedSize := RLELineSizes[I * Height + Y];
  309. Read(Handle, PackedLine, PackedSize);
  310. DecodeRLE(PackedLine, LineBuffer, PackedSize, LineSize);
  311. end
  312. else
  313. begin
  314. // Just read uncompressed line
  315. Read(Handle, LineBuffer, LineSize);
  316. end;
  317. // Swap endian if needed
  318. if ChannelPixelSize = 4 then
  319. SwapEndianLongWord(PLongWord(LineBuffer), Width)
  320. else if ChannelPixelSize = 2 then
  321. SwapEndianWord(PWordArray(LineBuffer), Width);
  322. if Info.ChannelCount > 1 then
  323. begin
  324. // Copy each pixel fragment to its right place in destination image
  325. for X := 0 to Width - 1 do
  326. begin
  327. Move(PByteArray(LineBuffer)[X * ChannelPixelSize],
  328. PByteArray(Bits)[Y * WidthBytes + X * Info.BytesPerPixel + CurrChannel * ChannelPixelSize],
  329. ChannelPixelSize);
  330. end;
  331. end
  332. else
  333. begin
  334. // Just copy the line
  335. Move(LineBuffer^, PByteArray(Bits)[Y * LineSize], LineSize);
  336. end;
  337. end;
  338. end
  339. else
  340. begin
  341. // Skip current color channel, not needed for image loading - just to
  342. // get stream's position to the end of PSD
  343. if Compression = CompressionRLE then
  344. begin
  345. for Y := 0 to Height - 1 do
  346. Seek(Handle, RLELineSizes[I * Height + Y], smFromCurrent);
  347. end
  348. else
  349. Seek(Handle, LineSize * Height, smFromCurrent);
  350. end;
  351. end;
  352. if Header.Mode = cmCMYK then
  353. begin
  354. // Convert CMYK images to RGB (alpha is ignored here). PSD stores CMYK
  355. // channels in the way that first requires substraction from max channel value
  356. if ChannelPixelSize = 1 then
  357. begin
  358. PCol32 := Bits;
  359. for X := 0 to Width * Height - 1 do
  360. begin
  361. Col32.A := 255 - PCol32.A;
  362. Col32.R := 255 - PCol32.R;
  363. Col32.G := 255 - PCol32.G;
  364. Col32.B := 255 - PCol32.B;
  365. CMYKToRGB(Col32.A, Col32.R, Col32.G, Col32.B, PCol32.R, PCol32.G, PCol32.B);
  366. PCol32.A := 255;
  367. Inc(PCol32);
  368. end;
  369. end
  370. else
  371. begin
  372. PCol64 := Bits;
  373. for X := 0 to Width * Height - 1 do
  374. begin
  375. Col64.A := 65535 - PCol64.A;
  376. Col64.R := 65535 - PCol64.R;
  377. Col64.G := 65535 - PCol64.G;
  378. Col64.B := 65535 - PCol64.B;
  379. CMYKToRGB16(Col64.A, Col64.R, Col64.G, Col64.B, PCol64.R, PCol64.G, PCol64.B);
  380. PCol64.A := 65535;
  381. Inc(PCol64);
  382. end;
  383. end;
  384. end;
  385. if Header.Depth = 32 then
  386. begin
  387. if (Header.Channels = 3) and (Header.Mode = cmRGB) then
  388. begin
  389. // RGB images were loaded as ARGB so we must wet alpha manually to 1.0
  390. PColF := Bits;
  391. for X := 0 to Width * Height - 1 do
  392. begin
  393. PColF.A := 1.0;
  394. Inc(PColF);
  395. end;
  396. end;
  397. end;
  398. Result := True;
  399. finally
  400. FreeMem(LineBuffer);
  401. FreeMem(PackedLine);
  402. end;
  403. end;
  404. end;
  405. function TPSDFileFormat.SaveData(Handle: TImagingHandle;
  406. const Images: TDynImageDataArray; Index: LongInt): Boolean;
  407. type
  408. TURect = packed record
  409. Top, Left, Bottom, Right: LongWord;
  410. end;
  411. const
  412. BlendMode: TChar8 = '8BIMnorm';
  413. LayerOptions: array[0..3] of Byte = (255, 0, 0, 0);
  414. LayerName: array[0..7] of AnsiChar = #7'Layer 0';
  415. var
  416. MustBeFreed: Boolean;
  417. ImageToSave: TImageData;
  418. Info: TImageFormatInfo;
  419. Header: TPSDHeader;
  420. I, CurrChannel, ChannelPixelSize: LongInt;
  421. LayerBlockOffset, SaveOffset, ChannelInfoOffset: Integer;
  422. ChannelInfo: TPSDChannelInfo;
  423. R: TURect;
  424. LongVal: LongWord;
  425. WordVal, LayerCount: Word;
  426. RawPal: array[0..767] of Byte;
  427. ChannelDataSizes: array of Integer;
  428. function PackLine(Src, Dest: PByteArray; Length: Integer): Integer;
  429. var
  430. I, Remaining: Integer;
  431. begin
  432. Remaining := Length;
  433. Result := 0;
  434. while Remaining > 0 do
  435. begin
  436. I := 0;
  437. // Look for characters same as the first
  438. while (I < 128) and (Remaining - I > 0) and (Src[0] = Src[I]) do
  439. Inc(I);
  440. if I > 2 then
  441. begin
  442. Dest[0] := Byte(-(I - 1));
  443. Dest[1] := Src[0];
  444. Dest := PByteArray(@Dest[2]);
  445. Src := PByteArray(@Src[I]);
  446. Dec(Remaining, I);
  447. Inc(Result, 2);
  448. end
  449. else
  450. begin
  451. // Look for different characters
  452. I := 0;
  453. while (I < 128) and (Remaining - (I + 1) > 0) and
  454. ((Src[I] <> Src[I + 1]) or (Remaining - (I + 2) <= 0) or
  455. (Src[I] <> Src[I + 2])) do
  456. begin
  457. Inc(I);
  458. end;
  459. // If there's only 1 remaining, the previous WHILE doesn't catch it
  460. if Remaining = 1 then
  461. I := 1;
  462. if I > 0 then
  463. begin
  464. // Some distinct ones found
  465. Dest[0] := I - 1;
  466. Move(Src[0], Dest[1], I);
  467. Dest := PByteArray(@Dest[1 + I]);
  468. Src := PByteArray(@Src[I]);
  469. Dec(Remaining, I);
  470. Inc(Result, I + 1);
  471. end;
  472. end;
  473. end;
  474. end;
  475. procedure WriteChannelData(SeparateChannelStorage: Boolean);
  476. var
  477. I, X, Y, LineSize, WidthBytes, RLETableOffset, CurrentOffset, WrittenLineSize: Integer;
  478. LineBuffer, RLEBuffer: PByteArray;
  479. RLELengths: array of Word;
  480. Compression: Word;
  481. begin
  482. LineSize := ImageToSave.Width * ChannelPixelSize;
  483. WidthBytes := ImageToSave.Width * Info.BytesPerPixel;
  484. GetMem(LineBuffer, LineSize);
  485. GetMem(RLEBuffer, LineSize * 3);
  486. SetLength(RLELengths, ImageToSave.Height * Info.ChannelCount);
  487. RLETableOffset := 0;
  488. // No compression for FP32, Photoshop won't open them
  489. Compression := Iff(Info.IsFloatingPoint, CompressionNone, CompressionRLE);
  490. if not SeparateChannelStorage then
  491. begin
  492. // This is for storing background merged image. There's only one
  493. // complession flag and one RLE lenghts table for all channels
  494. WordVal := Swap(Compression);
  495. GetIO.Write(Handle, @WordVal, SizeOf(WordVal));
  496. if Compression = CompressionRLE then
  497. begin
  498. RLETableOffset := GetIO.Tell(Handle);
  499. GetIO.Write(Handle, @RLELengths[0], SizeOf(Word) * ImageToSave.Height * Info.ChannelCount);
  500. end;
  501. end;
  502. for I := 0 to Info.ChannelCount - 1 do
  503. begin
  504. if SeparateChannelStorage then
  505. begin
  506. // Layer image data has compression flag and RLE lenghts table
  507. // independent for each channel
  508. WordVal := Swap(CompressionRLE);
  509. GetIO.Write(Handle, @WordVal, SizeOf(WordVal));
  510. if Compression = CompressionRLE then
  511. begin
  512. RLETableOffset := GetIO.Tell(Handle);
  513. GetIO.Write(Handle, @RLELengths[0], SizeOf(Word) * ImageToSave.Height);
  514. ChannelDataSizes[I] := 0;
  515. end;
  516. end;
  517. // Now determine which color channel we are going to write to file.
  518. if Info.HasAlphaChannel then
  519. begin
  520. if I = Info.ChannelCount - 1 then
  521. CurrChannel := I
  522. else
  523. CurrChannel := Info.ChannelCount - 2 - I;
  524. end
  525. else
  526. CurrChannel := Info.ChannelCount - 1 - I;
  527. for Y := 0 to ImageToSave.Height - 1 do
  528. begin
  529. if Info.ChannelCount > 1 then
  530. begin
  531. // Copy each pixel fragment to its right place in destination image
  532. for X := 0 to ImageToSave.Width - 1 do
  533. begin
  534. Move(PByteArray(ImageToSave.Bits)[Y * WidthBytes + X * Info.BytesPerPixel + CurrChannel * ChannelPixelSize],
  535. PByteArray(LineBuffer)[X * ChannelPixelSize], ChannelPixelSize);
  536. end;
  537. end
  538. else
  539. Move(PByteArray(ImageToSave.Bits)[Y * LineSize], LineBuffer^, LineSize);
  540. // Write current channel line to file (swap endian if needed first)
  541. if ChannelPixelSize = 4 then
  542. SwapEndianLongWord(PLongWord(LineBuffer), ImageToSave.Width)
  543. else if ChannelPixelSize = 2 then
  544. SwapEndianWord(PWordArray(LineBuffer), ImageToSave.Width);
  545. if Compression = CompressionRLE then
  546. begin
  547. // Compress and write line
  548. WrittenLineSize := PackLine(LineBuffer, RLEBuffer, LineSize);
  549. {RLELineSize := 7;
  550. RLEBuffer[0] := 129; RLEBuffer[1] := 255; RLEBuffer[2] := 131; RLEBuffer[3] := 100;
  551. RLEBuffer[4] := 1; RLEBuffer[5] := 0; RLEBuffer[6] := 255;}
  552. RLELengths[ImageToSave.Height * I + Y] := SwapEndianWord(WrittenLineSize);
  553. GetIO.Write(Handle, RLEBuffer, WrittenLineSize);
  554. end
  555. else
  556. begin
  557. WrittenLineSize := LineSize;
  558. GetIO.Write(Handle, LineBuffer, WrittenLineSize);
  559. end;
  560. if SeparateChannelStorage then
  561. Inc(ChannelDataSizes[I], WrittenLineSize);
  562. end;
  563. if SeparateChannelStorage and (Compression = CompressionRLE) then
  564. begin
  565. // Update channel RLE lengths
  566. CurrentOffset := GetIO.Tell(Handle);
  567. GetIO.Seek(Handle, RLETableOffset, smFromBeginning);
  568. GetIO.Write(Handle, @RLELengths[ImageToSave.Height * I], SizeOf(Word) * ImageToSave.Height);
  569. GetIO.Seek(Handle, CurrentOffset, smFromBeginning);
  570. Inc(ChannelDataSizes[I], SizeOf(Word) * ImageToSave.Height);
  571. end;
  572. end;
  573. if not SeparateChannelStorage and (Compression = CompressionRLE) then
  574. begin
  575. // Update channel RLE lengths
  576. CurrentOffset := GetIO.Tell(Handle);
  577. GetIO.Seek(Handle, RLETableOffset, smFromBeginning);
  578. GetIO.Write(Handle, @RLELengths[0], SizeOf(Word) * ImageToSave.Height * Info.ChannelCount);
  579. GetIO.Seek(Handle, CurrentOffset, smFromBeginning);
  580. end;
  581. FreeMem(LineBuffer);
  582. FreeMem(RLEBuffer);
  583. end;
  584. begin
  585. Result := False;
  586. if MakeCompatible(Images[Index], ImageToSave, MustBeFreed) then
  587. with GetIO, ImageToSave do
  588. try
  589. Info := GetFormatInfo(Format);
  590. ChannelPixelSize := Info.BytesPerPixel div Info.ChannelCount;
  591. // Fill header with proper info and save it
  592. FillChar(Header, SizeOf(Header), 0);
  593. Header.Signature := SPSDMagic;
  594. Header.Version := 1;
  595. Header.Channels := Info.ChannelCount;
  596. Header.Rows := Height;
  597. Header.Columns := Width;
  598. Header.Depth := Info.BytesPerPixel div Info.ChannelCount * 8;
  599. if Info.IsIndexed then
  600. Header.Mode := cmIndexed
  601. else if Info.HasGrayChannel or (Info.ChannelCount = 1) then
  602. Header.Mode := cmGrayscale
  603. else
  604. Header.Mode := cmRGB;
  605. SwapHeader(Header);
  606. Write(Handle, @Header, SizeOf(Header));
  607. // Write palette size and data
  608. LongVal := SwapEndianLongWord(IffUnsigned(Info.IsIndexed, SizeOf(RawPal), 0));
  609. Write(Handle, @LongVal, SizeOf(LongVal));
  610. if Info.IsIndexed then
  611. begin
  612. for I := 0 to Info.PaletteEntries - 1 do
  613. begin
  614. RawPal[I] := Palette[I].R;
  615. RawPal[I + 256] := Palette[I].G;
  616. RawPal[I + 512] := Palette[I].B;
  617. end;
  618. Write(Handle, @RawPal, SizeOf(RawPal));
  619. end;
  620. // Write empty resource and layer block sizes
  621. LongVal := 0;
  622. Write(Handle, @LongVal, SizeOf(LongVal));
  623. LayerBlockOffset := Tell(Handle);
  624. Write(Handle, @LongVal, SizeOf(LongVal));
  625. if FSaveAsLayer and (ChannelPixelSize < 4) then // No Layers for FP32 images
  626. begin
  627. LayerCount := SwapEndianWord(Iff(Info.HasAlphaChannel, Word(-1), 1)); // Must be -1 to get transparency in Photoshop
  628. R.Top := 0;
  629. R.Left := 0;
  630. R.Bottom := SwapEndianLongWord(Height);
  631. R.Right := SwapEndianLongWord(Width);
  632. WordVal := SwapEndianWord(Info.ChannelCount);
  633. Write(Handle, @LongVal, SizeOf(LongVal)); // Layer section size, empty now
  634. Write(Handle, @LayerCount, SizeOf(LayerCount)); // Layer count
  635. Write(Handle, @R, SizeOf(R)); // Bounds rect
  636. Write(Handle, @WordVal, SizeOf(WordVal)); // Channeel count
  637. ChannelInfoOffset := Tell(Handle);
  638. SetLength(ChannelDataSizes, Info.ChannelCount); // Empty channel infos
  639. FillChar(ChannelInfo, SizeOf(ChannelInfo), 0);
  640. for I := 0 to Info.ChannelCount - 1 do
  641. Write(Handle, @ChannelInfo, SizeOf(ChannelInfo));
  642. Write(Handle, @BlendMode, SizeOf(BlendMode)); // Blend mode = normal
  643. Write(Handle, @LayerOptions, SizeOf(LayerOptions)); // Predefined options
  644. LongVal := SwapEndianLongWord(16); // Extra data size (4 (mask size) + 4 (ranges size) + 8 (name))
  645. Write(Handle, @LongVal, SizeOf(LongVal));
  646. LongVal := 0;
  647. Write(Handle, @LongVal, SizeOf(LongVal)); // Mask size = 0
  648. LongVal := 0;
  649. Write(Handle, @LongVal, SizeOf(LongVal)); // Blend ranges size
  650. Write(Handle, @LayerName, SizeOf(LayerName)); // Layer name
  651. WriteChannelData(True); // Write Layer image data
  652. Write(Handle, @LongVal, SizeOf(LongVal)); // Global mask info size = 0
  653. SaveOffset := Tell(Handle);
  654. Seek(Handle, LayerBlockOffset, smFromBeginning);
  655. // Update layer and mask section sizes
  656. LongVal := SwapEndianLongWord(SaveOffset - LayerBlockOffset - 4);
  657. Write(Handle, @LongVal, SizeOf(LongVal));
  658. LongVal := SwapEndianLongWord(SaveOffset - LayerBlockOffset - 8);
  659. Write(Handle, @LongVal, SizeOf(LongVal));
  660. // Update layer channel info
  661. Seek(Handle, ChannelInfoOffset, smFromBeginning);
  662. for I := 0 to Info.ChannelCount - 1 do
  663. begin
  664. ChannelInfo.ChannelID := SwapEndianWord(I);
  665. if (I = Info.ChannelCount - 1) and Info.HasAlphaChannel then
  666. ChannelInfo.ChannelID := Swap(Word(-1));
  667. ChannelInfo.Size := SwapEndianLongWord(ChannelDataSizes[I] + 2); // datasize (incl RLE table) + comp. flag
  668. Write(Handle, @ChannelInfo, SizeOf(ChannelInfo));
  669. end;
  670. Seek(Handle, SaveOffset, smFromBeginning);
  671. end;
  672. // Write background merged image
  673. WriteChannelData(False);
  674. Result := True;
  675. finally
  676. if MustBeFreed then
  677. FreeImage(ImageToSave);
  678. end;
  679. end;
  680. procedure TPSDFileFormat.ConvertToSupported(var Image: TImageData;
  681. const Info: TImageFormatInfo);
  682. var
  683. ConvFormat: TImageFormat;
  684. begin
  685. if Info.IsFloatingPoint then
  686. ConvFormat := IffFormat(Info.ChannelCount = 1, ifR32F, ifA32R32G32B32F)
  687. else if Info.HasGrayChannel then
  688. ConvFormat := IffFormat(Info.HasAlphaChannel, ifA16Gray16, ifGray16)
  689. else if Info.RBSwapFormat in GetSupportedFormats then
  690. ConvFormat := Info.RBSwapFormat
  691. else
  692. ConvFormat := IffFormat(Info.HasAlphaChannel, ifA8R8G8B8, ifR8G8B8);
  693. ConvertImage(Image, ConvFormat);
  694. end;
  695. function TPSDFileFormat.TestFormat(Handle: TImagingHandle): Boolean;
  696. var
  697. Header: TPSDHeader;
  698. ReadCount: LongInt;
  699. begin
  700. Result := False;
  701. if Handle <> nil then
  702. begin
  703. ReadCount := GetIO.Read(Handle, @Header, SizeOf(Header));
  704. SwapHeader(Header);
  705. GetIO.Seek(Handle, -ReadCount, smFromCurrent);
  706. Result := (ReadCount >= SizeOf(Header)) and
  707. (Header.Signature = SPSDMagic) and
  708. (Header.Version = 1);
  709. end;
  710. end;
  711. initialization
  712. RegisterImageFileFormat(TPSDFileFormat);
  713. {
  714. File Notes:
  715. -- TODOS ----------------------------------------------------
  716. - nothing now
  717. -- 0.26.1 Changes/Bug Fixes ---------------------------------
  718. - PSDs are now saved with RLE compression.
  719. - Mask layer saving added to SaveData for images with alpha
  720. (shows proper transparency when opened in Photoshop). Can be
  721. enabled/disabled using option
  722. - Fixed memory leak in SaveData.
  723. -- 0.23 Changes/Bug Fixes -----------------------------------
  724. - Saving implemented.
  725. - Loading implemented.
  726. - Unit created with initial stuff!
  727. }
  728. end.