Repo for the search and displace ingest module that takes odf, docx and pdf and transforms it into .md to be used with search and displace operations
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

801 lines
26 KiB

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