ImagingPsd.pas 26 KB

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