2
0

GR32.ImageFormats.PSD.Writer.pas 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790
  1. unit GR32.ImageFormats.PSD.Writer;
  2. (* ***** BEGIN LICENSE BLOCK *****
  3. * Version: MPL 1.1 or LGPL 2.1 with linking exception
  4. *
  5. * The contents of this file are subject to the Mozilla Public License Version
  6. * 1.1 (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. * http://www.mozilla.org/MPL/
  9. *
  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
  12. * for the specific language governing rights and limitations under the
  13. * License.
  14. *
  15. * Alternatively, the contents of this file may be used under the terms of the
  16. * Free Pascal modified version of the GNU Lesser General Public License
  17. * Version 2.1 (the "FPC modified LGPL License"), in which case the provisions
  18. * of this license are applicable instead of those above.
  19. * Please see the file LICENSE.txt for additional information concerning this
  20. * license.
  21. *
  22. * The Original Code is PSD Image Format support for Graphics32
  23. *
  24. * The Initial Developer of the Original Code is
  25. * Lamdalili
  26. *
  27. * Portions created by the Initial Developer are Copyright (C) 2023
  28. * the Initial Developer. All Rights Reserved.
  29. *
  30. * Contributor(s):
  31. * Anders Melander <[email protected]>
  32. *
  33. * ***** END LICENSE BLOCK ***** *)
  34. // WEAKPACKAGEUNIT so we can include the unit in the GR32 design time
  35. // package in order to have the design time editor support the various formats.
  36. {$WEAKPACKAGEUNIT ON}
  37. interface
  38. {$include GR32.inc}
  39. uses
  40. Classes,
  41. GR32.ImageFormats.PSD;
  42. //------------------------------------------------------------------------------
  43. //
  44. // TPhotoshopDocumentWriter
  45. //
  46. //------------------------------------------------------------------------------
  47. // Writes a PSD document to a stream
  48. //------------------------------------------------------------------------------
  49. type
  50. TPhotoshopDocumentWriter = class abstract
  51. public
  52. class procedure SaveToStream(ADocument: TPhotoshopDocument; AStream: TStream);
  53. end;
  54. //------------------------------------------------------------------------------
  55. //------------------------------------------------------------------------------
  56. //------------------------------------------------------------------------------
  57. implementation
  58. uses
  59. Generics.Collections,
  60. {$ifndef FPC}
  61. ZLib,
  62. {$else FPC}
  63. zstream,
  64. {$endif FPC}
  65. Math,
  66. SysUtils,
  67. GR32,
  68. GR32_LowLevel,
  69. GR32.BigEndian,
  70. GR32.ImageFormats.PSD.Types;
  71. type
  72. TBytesArray = array of byte;
  73. TPhotoshopLayerCracker = class(TCustomPhotoshopLayer);
  74. //------------------------------------------------------------------------------
  75. //
  76. // Scanline compression
  77. //
  78. //------------------------------------------------------------------------------
  79. type
  80. // Write all channels in one go
  81. // Used for background bitmap
  82. TPSDBitmapWriterDelegate = procedure(AStream: TStream; ALayer: TCustomPhotoshopLayer);
  83. // Write a single channels
  84. // Used for layer bitmaps
  85. TPSDChannelWriterDelegate = procedure(AStream: TStream; AChannel: TColor32Component; ALayer: TCustomPhotoshopLayer; var ABuffer);
  86. const
  87. // Number of channels
  88. PSD_CHANNELS = Ord(High(TColor32Component))-Ord(Low(TColor32Component))+1;
  89. {$IFNDEF RGBA_FORMAT}
  90. PSD_CHANNELS_IDS: array[TColor32Component] of SmallInt = (PSD_MASK_BLUE, PSD_MASK_GREEN, PSD_MASK_RED, PSD_MASK_ALPHA);
  91. {$ELSE}
  92. PSD_CHANNELS_IDS: array[TColor32Component] of SmallInt = (PSD_MASK_RED, PSD_MASK_GREEN, PSD_MASK_BLUE, PSD_MASK_ALPHA);
  93. {$ENDIF}
  94. const
  95. // The PSD channels in "planar" order
  96. PSDPlanarOrder: array[0..PSD_CHANNELS-1] of TColor32Component = (ccRed, ccGreen, ccBlue, ccAlpha);
  97. //------------------------------------------------------------------------------
  98. // RAW compression (i.e. no compression)
  99. //------------------------------------------------------------------------------
  100. type
  101. CompressionRAW = record
  102. class function WriteScanline(AStream: TStream; const ABuffer; Width: integer): Cardinal; static;
  103. class procedure WriteChannel(AStream: TStream; AChannel: TColor32Component; ALayer: TCustomPhotoshopLayer; var ABuffer); static;
  104. class procedure WriteBitmap(AStream: TStream; ALayer: TCustomPhotoshopLayer); static;
  105. end;
  106. class function CompressionRAW.WriteScanline(AStream: TStream; const ABuffer; Width: integer): Cardinal;
  107. begin
  108. Result := AStream.Write(ABuffer, Width);
  109. end;
  110. class procedure CompressionRAW.WriteChannel(AStream: TStream; AChannel: TColor32Component; ALayer: TCustomPhotoshopLayer; var ABuffer);
  111. var
  112. i: integer;
  113. begin
  114. for i := 0 to ALayer.Height - 1 do
  115. begin
  116. TPhotoshopLayerCracker(ALayer).GetChannelScanLine(AChannel, i, ABuffer);
  117. WriteScanline(AStream, ABuffer, ALayer.Width);
  118. end;
  119. end;
  120. class procedure CompressionRAW.WriteBitmap(AStream: TStream; ALayer: TCustomPhotoshopLayer);
  121. var
  122. ScanLineBuffer: TBytesArray;
  123. Channel: TColor32Component;
  124. i: integer;
  125. begin
  126. SetLength(ScanLineBuffer, ALayer.Width);
  127. for Channel in PSDPlanarOrder do
  128. for i := 0 to ALayer.Height - 1 do
  129. begin
  130. TPhotoshopLayerCracker(ALayer).GetChannelScanLine(Channel, i, ScanLineBuffer[0]);
  131. WriteScanline(AStream, ScanLineBuffer[0], ALayer.Width);
  132. end;
  133. end;
  134. //------------------------------------------------------------------------------
  135. // RLE compression (PackBit)
  136. //------------------------------------------------------------------------------
  137. type
  138. TPackBitsStream = class(TStream)
  139. private
  140. FStream: TStream;
  141. public
  142. constructor Create(AStream: TStream);
  143. function Read(var Buffer; Count: Longint): Longint; override;
  144. function Write(const Buffer; Count: Longint): Longint; override;
  145. end;
  146. constructor TPackBitsStream.Create(AStream: TStream);
  147. begin
  148. inherited Create;
  149. FStream := AStream;
  150. end;
  151. function TPackBitsStream.Read(var Buffer; Count: Longint): Longint;
  152. begin
  153. Assert(False, 'Not implemented');
  154. Result := 0;
  155. end;
  156. function TPackBitsStream.Write(const Buffer; Count: Longint): Longint;
  157. const
  158. MaxRun = 128;
  159. // These values are for classic PackBits encoding.
  160. // Other variants use other values (e.g. PDB uses PackBase=127, PackSign=1)
  161. PackBase = 257;
  162. PackSign = -1;
  163. var
  164. Index: Int64;
  165. RunCount: Byte;
  166. RunValue: Byte;
  167. StartIndex: integer;
  168. begin
  169. Index := 0;
  170. Result := 0;
  171. while (Index < Count) do
  172. begin
  173. (*
  174. ** Always encode 3-byte repeat sequences.
  175. ** Encode 2-byte repeat sequences only when they are at the start of the block.
  176. *)
  177. RunValue := TByteArray(Buffer)[Index];
  178. if (Index < Count - 1) and (TByteArray(Buffer)[Index] = TByteArray(Buffer)[Index + 1]) then
  179. begin
  180. // Do a repeat run
  181. RunCount := 2; // We already know that we have at least a run of two because of the test above
  182. Inc(Index, 2);
  183. while (Index < Count) and (RunValue = TByteArray(Buffer)[Index]) and (RunCount < MaxRun) do
  184. begin
  185. Inc(Index);
  186. Inc(RunCount);
  187. end;
  188. // Encode run count
  189. // RunCount := Byte(PackBase + PackSign * RunCount);
  190. RunCount := Byte(257 - RunCount);
  191. FStream.Write(RunCount, 1);
  192. FStream.Write(RunValue, 1);
  193. Inc(Result, 2);
  194. end else
  195. begin
  196. // Do a non-repeat run
  197. RunCount := 0;
  198. StartIndex := Index;
  199. while
  200. // We're at the end; No room for repeat runs
  201. ((Index + 2 >= Count) and (Index < Count)) or
  202. // There's at least 3 bytes left and...
  203. ((Index + 2 < Count) and (
  204. // Next 2 differ
  205. (RunValue <> TByteArray(Buffer)[Index + 1]) or
  206. // Next 2 same, but differs from the third
  207. (RunValue <> TByteArray(Buffer)[Index + 2]))) do
  208. begin
  209. Inc(Index);
  210. Inc(RunCount);
  211. if (RunCount = MaxRun) then
  212. Break;
  213. RunValue := TByteArray(Buffer)[Index];
  214. end;
  215. BigEndian.WriteByte(FStream, RunCount-1);
  216. FStream.Write(TByteArray(Buffer)[StartIndex], RunCount);
  217. Inc(Result, RunCount+1);
  218. end;
  219. end;
  220. end;
  221. type
  222. CompressionRLE = record
  223. class procedure WriteChannel(AStream: TStream; AChannel: TColor32Component; ALayer: TCustomPhotoshopLayer; var ABuffer); static;
  224. class procedure WriteBitmap(AStream: TStream; ALayer: TCustomPhotoshopLayer); static;
  225. end;
  226. class procedure CompressionRLE.WriteChannel(AStream: TStream; AChannel: TColor32Component; ALayer: TCustomPhotoshopLayer; var ABuffer);
  227. var
  228. i: integer;
  229. RowTablePos: Int64;
  230. RowSize: Word;
  231. RowTable: array of Word;
  232. SavePos: Int64;
  233. RLEStream: TStream;
  234. begin
  235. RowTablePos := AStream.Position;
  236. // Make room for row table
  237. AStream.Seek(ALayer.Height * SizeOf(Smallint), soFromCurrent);
  238. SetLength(RowTable, ALayer.Height);
  239. RLEStream := TPackBitsStream.Create(AStream);
  240. try
  241. for i := 0 to ALayer.Height - 1 do
  242. begin
  243. TPhotoshopLayerCracker(ALayer).GetChannelScanLine(AChannel, i, ABuffer);
  244. RowSize := RLEStream.Write(ABuffer, ALayer.Width);
  245. RowTable[i] := Swap16(RowSize);
  246. end;
  247. finally
  248. RLEStream.Free;
  249. end;
  250. // Rewind and update row table
  251. SavePos := AStream.Position;
  252. AStream.Position := RowTablePos;
  253. AStream.Write(RowTable[0], ALayer.Height * SizeOf(Word));
  254. AStream.Position := SavePos;
  255. end;
  256. class procedure CompressionRLE.WriteBitmap(AStream: TStream; ALayer: TCustomPhotoshopLayer);
  257. var
  258. ScanLineBuffer: TBytesArray;
  259. Channel: TColor32Component;
  260. i: integer;
  261. RowTablePos: Int64;
  262. RowSize: Word;
  263. RowTable: array of Word;
  264. SavePos: Int64;
  265. RLEStream: TStream;
  266. begin
  267. SetLength(ScanLineBuffer, ALayer.Width);
  268. SetLength(RowTable, ALayer.Height);
  269. RowTablePos := AStream.Position;
  270. // Make room for row table (for all channels)
  271. AStream.Seek(ALayer.Height * SizeOf(Word) * PSD_CHANNELS, soFromCurrent);
  272. RLEStream := TPackBitsStream.Create(AStream);
  273. try
  274. for Channel in PSDPlanarOrder do
  275. begin
  276. for i := 0 to ALayer.Height - 1 do
  277. begin
  278. TPhotoshopLayerCracker(ALayer).GetChannelScanLine(Channel, i, ScanLineBuffer[0]);
  279. RowSize := RLEStream.Write(ScanLineBuffer[0], ALayer.Width);
  280. RowTable[i] := Swap16(RowSize);
  281. end;
  282. // Rewind and update row table for the channel
  283. SavePos := AStream.Position;
  284. AStream.Position := RowTablePos;
  285. AStream.Write(RowTable[0], ALayer.Height * SizeOf(Word));
  286. // Move table pos forward to next channel
  287. Inc(RowTablePos, ALayer.Height * SizeOf(Word));
  288. AStream.Position := SavePos;
  289. end;
  290. finally
  291. RLEStream.Free;
  292. end;
  293. end;
  294. //------------------------------------------------------------------------------
  295. // ZIP compression
  296. //------------------------------------------------------------------------------
  297. type
  298. CompressionZIP = record
  299. class procedure WriteChannel(AStream: TStream; AChannel: TColor32Component; ALayer: TCustomPhotoshopLayer; var ABuffer); static;
  300. class procedure WriteBitmap(AStream: TStream; ALayer: TCustomPhotoshopLayer); static;
  301. end;
  302. class procedure CompressionZIP.WriteChannel(AStream: TStream; AChannel: TColor32Component; ALayer: TCustomPhotoshopLayer; var ABuffer);
  303. var
  304. i: integer;
  305. Stream: TStream;
  306. begin
  307. Stream := TCompressionStream.Create(clDefault, AStream);
  308. try
  309. for i := 0 to ALayer.Height - 1 do
  310. begin
  311. TPhotoshopLayerCracker(ALayer).GetChannelScanLine(AChannel, i, ABuffer);
  312. Stream.Write(ABuffer, ALayer.Width);
  313. end;
  314. finally
  315. Stream.Free;
  316. end;
  317. end;
  318. class procedure CompressionZIP.WriteBitmap(AStream: TStream; ALayer: TCustomPhotoshopLayer);
  319. var
  320. ScanLineBuffer: TBytesArray;
  321. Stream: TStream;
  322. Channel: TColor32Component;
  323. i: integer;
  324. begin
  325. SetLength(ScanLineBuffer, ALayer.Width);
  326. Stream := TCompressionStream.Create(clDefault, AStream);
  327. try
  328. for Channel in PSDPlanarOrder do
  329. for i := 0 to ALayer.Height - 1 do
  330. begin
  331. TPhotoshopLayerCracker(ALayer).GetChannelScanLine(Channel, i, ScanLineBuffer[0]);
  332. Stream.Write(ScanLineBuffer[0], ALayer.Width);
  333. end;
  334. finally
  335. Stream.Free;
  336. end;
  337. end;
  338. //------------------------------------------------------------------------------
  339. function GetLayerWriter(ALayer: TCustomPhotoshopLayer): TPSDChannelWriterDelegate;
  340. begin
  341. case ALayer.Compression of
  342. lcRLE:
  343. Result := CompressionRLE.WriteChannel;
  344. lcZIP:
  345. Result := CompressionZIP.WriteChannel;
  346. lcRAW:
  347. Result := CompressionRAW.WriteChannel;
  348. else
  349. raise EPhotoshopDocument.CreateFmt('Unsupported compression method: %d', [Ord(ALayer.Compression)]);
  350. end;
  351. end;
  352. function GetBitmapWriter(ALayer: TCustomPhotoshopLayer): TPSDBitmapWriterDelegate;
  353. begin
  354. case ALayer.Compression of
  355. lcRLE:
  356. Result := CompressionRLE.WriteBitmap;
  357. lcZIP:
  358. Result := CompressionZIP.WriteBitmap;
  359. lcRAW:
  360. Result := CompressionRAW.WriteBitmap;
  361. else
  362. raise EPhotoshopDocument.CreateFmt('Unsupported compression method: %d', [Ord(ALayer.Compression)]);
  363. end;
  364. end;
  365. //------------------------------------------------------------------------------
  366. //
  367. // TPhotoshopDocumentWriter
  368. //
  369. //------------------------------------------------------------------------------
  370. class procedure TPhotoshopDocumentWriter.SaveToStream(ADocument: TPhotoshopDocument; AStream: TStream);
  371. var
  372. SectionsCaptures: TStack<Int64>;
  373. function Pad(Value: Cardinal; Alignment: Cardinal = 4): integer;
  374. begin
  375. Result := (Alignment - (Value and (Alignment - 1))) and (Alignment - 1);
  376. end;
  377. function WritePadding(ASize: Cardinal): Cardinal;
  378. const
  379. Zero: byte = 0;
  380. begin
  381. Result := AStream.Position;
  382. while (ASize > 0) do
  383. begin
  384. AStream.Write(Zero, 1);
  385. Dec(ASize);
  386. end;
  387. end;
  388. procedure WritePadToAlignment(Value: Cardinal; Alignment: Cardinal = 4);
  389. begin
  390. WritePadding(Pad(Value, Alignment));
  391. end;
  392. function WriteRawAnsiString(const s: AnsiString): Cardinal;
  393. begin
  394. Result := Length(s);
  395. AStream.Write(PAnsiChar(s)^, Result);
  396. end;
  397. function WriteAnsiText(const AText: AnsiString): Cardinal;
  398. begin
  399. BigEndian.WriteByte(AStream, Length(AText));
  400. Result := WriteRawAnsiString(AText) + 1;
  401. end;
  402. function WriteUnicodeText(const AText: string): Cardinal;
  403. var
  404. c: Char;
  405. begin
  406. BigEndian.WriteCardinal(AStream, Length(AText));
  407. for c in AText do
  408. BigEndian.WriteWord(AStream, Ord(c));
  409. c := #0;
  410. AStream.Write(c, SizeOf(Char));
  411. Result := (Length(AText)+1) * SizeOf(Char) + SizeOf(Cardinal);
  412. end;
  413. procedure WriteBeginSection;
  414. begin
  415. BigEndian.WriteCardinal(AStream, 0); // field slot
  416. SectionsCaptures.Push(AStream.Position);
  417. end;
  418. procedure WriteEndSection(Align: Cardinal = 4);
  419. var
  420. Size: Cardinal;
  421. SectionStartPos: Int64;
  422. SavePos: Int64;
  423. begin
  424. SectionStartPos := SectionsCaptures.Pop;
  425. Size := AStream.Position - SectionStartPos;
  426. WritePadToAlignment(Size, Align);
  427. Size := Swap32(AStream.Position - SectionStartPos);
  428. SavePos := AStream.Position;
  429. AStream.Position := SectionStartPos - SizeOf(Cardinal); // field slot
  430. AStream.Write(Size, SizeOf(Size));
  431. AStream.Position := SavePos;
  432. end;
  433. procedure WriteEmptyImage;
  434. procedure WriteEmptyImageRLE;
  435. var
  436. RepeatsCount: integer;
  437. RemainCount: integer;
  438. i: integer;
  439. RowBuffer: array of Word;
  440. begin
  441. BigEndian.WriteWord(AStream, PSD_COMPRESSION_RLE);
  442. // Everything is repeats.
  443. // How many whole 128 byte repeats do we have?
  444. RepeatsCount := (ADocument.Width + 127) div 128; // round up
  445. // How many bytes remaining?
  446. RemainCount := ADocument.Width mod 128;
  447. SetLength(RowBuffer, RepeatsCount);
  448. // Write row table (all 4 channels)
  449. for i := 0 to ADocument.Height * PSD_CHANNELS - 1 do
  450. BigEndian.WriteWord(AStream, RepeatsCount * SizeOf(Word));
  451. (*
  452. ** Write RGB channels = $xxFFFFFF
  453. *)
  454. for i := 0 to RepeatsCount - 1 do
  455. RowBuffer[i] := Swap16($81FF); // Fill with whole 128 byte repeat runs
  456. if (RemainCount <> 0) then
  457. // Replace last entry with the remaining repeat run
  458. RowBuffer[RepeatsCount - 1] := Swap16(byte(-RemainCount + 1) shl 8 or $FF);
  459. for i := 0 to ADocument.Height * (PSD_CHANNELS-1) - 1 do
  460. AStream.Write(RowBuffer[0], RepeatsCount * SizeOf(Word));
  461. (*
  462. ** Write A channel = $00xxxxxx
  463. *)
  464. for i := 0 to RepeatsCount - 1 do
  465. RowBuffer[i] := Swap16($8100); // Fill with whole 128 byte repeat runs
  466. if (RemainCount <> 0) then
  467. // Replace last entry with the remaining repeat run
  468. RowBuffer[RepeatsCount - 1] := Swap16(byte(-RemainCount + 1) shl 8 or $00);
  469. for i := 0 to ADocument.Height - 1 do
  470. AStream.Write(RowBuffer[0], RepeatsCount * SizeOf(Word));
  471. end;
  472. procedure WriteEmptyImageRAW;
  473. var
  474. RowBuffer: array of byte;
  475. i: integer;
  476. begin
  477. BigEndian.WriteWord(AStream, PSD_COMPRESSION_NONE); // No compression
  478. SetLength(RowBuffer, ADocument.Width);
  479. // Write RGB channels = $xxFFFFFF
  480. FillChar(RowBuffer[0], ADocument.Width, $FF);
  481. for i := 0 to (ADocument.Height * (PSD_CHANNELS-1)) - 1 do
  482. AStream.Write(RowBuffer[0], ADocument.Width);
  483. // Write A channel = $00xxxxxx
  484. FillChar(RowBuffer[0], ADocument.Width, $00);
  485. for i := 0 to ADocument.Height - 1 do
  486. AStream.Write(RowBuffer[0], ADocument.Width);
  487. end;
  488. begin
  489. // Write an "empty" image containing ARGB=$00FFFFFF
  490. if (ADocument.Compression = lcRAW) then
  491. WriteEmptyImageRAW
  492. else
  493. WriteEmptyImageRLE;
  494. end;
  495. procedure WriteLayerImage(ALayer: TCustomPhotoshopLayer; AChannelsInfoPos: Int64);
  496. var
  497. LayerWriter: TPSDChannelWriterDelegate;
  498. Size: Cardinal;
  499. Channel: TColor32Component;
  500. ChannelsInfo: array[TColor32Component] of TPSDChannelInfo;
  501. ScanLineBuffer: TBytesArray;
  502. SavePos: Int64;
  503. begin
  504. SetLength(ScanLineBuffer, ALayer.Width);
  505. LayerWriter := GetLayerWriter(ALayer);
  506. ALayer.BeginScan;
  507. begin
  508. for Channel := Low(TColor32Component) to High(TColor32Component) do
  509. begin
  510. SavePos := AStream.Position;
  511. BigEndian.WriteWord(AStream, Ord(ALayer.Compression));
  512. LayerWriter(AStream, Channel, ALayer, ScanLineBuffer[0]);
  513. Size := AStream.Position - SavePos;
  514. ChannelsInfo[Channel].ChannelID := Swap16(Word(PSD_CHANNELS_IDS[Channel]));
  515. ChannelsInfo[Channel].ChannelSize := Swap32(Size);
  516. end;
  517. end;
  518. ALayer.EndScan;
  519. // Rewind and update channel table
  520. SavePos := AStream.Position;
  521. AStream.Position := AChannelsInfoPos;
  522. AStream.Write(ChannelsInfo, SizeOf(ChannelsInfo));
  523. AStream.Position := SavePos;
  524. end;
  525. procedure WriteLayerName(const AName: AnsiString; Align: Cardinal = 4);
  526. var
  527. Size: integer;
  528. begin
  529. Size := WriteAnsiText(AName);
  530. WritePadToAlignment(Size, Align);
  531. end;
  532. procedure WriteLayerBeginExtraInfo(const AKey: AnsiString);
  533. begin
  534. if Length(AKey) <> 4 then
  535. raise EPhotoshopDocument.CreateFmt('Invalid layer info key: "%s"',[string(AKey)]);
  536. WriteRawAnsiString('8BIM'); // Signature
  537. WriteRawAnsiString(AKey); // Key
  538. WriteBeginSection; // Size field
  539. end;
  540. procedure WriteLayerEndExtraInfo();
  541. begin
  542. // Specs state section size should be aligned to 2 bytes for most sub section types:
  543. //
  544. // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_71546
  545. //
  546. // In reality the one section we write ('luni') is aligned to 4 bytes and some readers
  547. // complain if it isn't.
  548. WriteEndSection(4);
  549. end;
  550. procedure WriteLayerRecord(ALayer: TCustomPhotoshopLayer; var AChannelsInfoPos: Int64);
  551. begin
  552. BigEndian.WriteCardinal(AStream, ALayer.Top); // Top
  553. BigEndian.WriteCardinal(AStream, ALayer.Left); // Left
  554. BigEndian.WriteCardinal(AStream, ALayer.Top + ALayer.Height); // Bottom
  555. BigEndian.WriteCardinal(AStream, ALayer.Left + ALayer.Width); // Right
  556. BigEndian.WriteWord(AStream, PSD_CHANNELS);
  557. // Make room for channel info list. Later updated in WriteLayerImage
  558. AChannelsInfoPos := AStream.Position;
  559. AStream.Seek(PSD_CHANNELS * SizeOf(TPSDChannelInfo), soFromCurrent);
  560. WriteRawAnsiString('8BIM'); // Signature
  561. WriteRawAnsiString(PSDBlendModeMapping[ALayer.BlendMode]); // Blend mode
  562. BigEndian.WriteByte(AStream, ALayer.Opacity); // Opacity
  563. BigEndian.WriteByte(AStream, Ord(ALayer.Clipping)); // Clipping
  564. BigEndian.WriteByte(AStream, byte(ALayer.Options)); // Options
  565. BigEndian.WriteByte(AStream, 0); // Filler, always 0
  566. // Variable section
  567. WriteBeginSection; // Extra data field
  568. begin
  569. BigEndian.WriteCardinal(AStream, 0); // Layer mask
  570. BigEndian.WriteCardinal(AStream, 0); // Blending ranges
  571. // Name of layer - ANSI
  572. WriteLayerName(AnsiString(ALayer.Name), 4);
  573. // *Layer extra info '8BIM' sequences
  574. WriteLayerBeginExtraInfo('luni');
  575. begin
  576. WriteUnicodeText(ALayer.Name); // unicode layer name sequence
  577. end;
  578. WriteLayerEndExtraInfo;
  579. end;
  580. WriteEndSection(4);
  581. end;
  582. procedure WriteLayerInfo;
  583. var
  584. i: integer;
  585. ChannelsInfoPos: array of Int64;
  586. begin
  587. WriteBeginSection(); // Layer info size field
  588. begin
  589. BigEndian.WriteWord(AStream, ADocument.Layers.Count); // Layers count
  590. SetLength(ChannelsInfoPos, ADocument.Layers.Count);
  591. for i := 0 to ADocument.Layers.Count - 1 do
  592. WriteLayerRecord(TCustomPhotoshopLayer(ADocument.Layers[i]), ChannelsInfoPos[i]);
  593. for i := 0 to ADocument.Layers.Count - 1 do
  594. WriteLayerImage(TCustomPhotoshopLayer(ADocument.Layers[i]), ChannelsInfoPos[i]);
  595. end;
  596. // Specs state section size should be aligned to 2 bytes:
  597. //
  598. // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_16000
  599. //
  600. // In reality it is aligned to 4 bytes and some readers complain if it isn't.
  601. WriteEndSection(4);
  602. end;
  603. procedure WriteLayer;
  604. begin
  605. if ADocument.Layers.Count = 0 then
  606. begin
  607. BigEndian.WriteCardinal(AStream, 0);
  608. exit;
  609. end;
  610. WriteBeginSection; // layer's total size field
  611. begin
  612. WriteLayerInfo;
  613. BigEndian.WriteCardinal(AStream, 0); // Global Mask .. optional
  614. // * global extra layer info '8BIM'
  615. end;
  616. WriteEndSection(4);
  617. end;
  618. procedure WriteImage;
  619. var
  620. BitmapWriter: TPSDBitmapWriterDelegate;
  621. begin
  622. BitmapWriter := GetBitmapWriter(ADocument.Background);
  623. ADocument.Background.BeginScan;
  624. begin
  625. BigEndian.WriteWord(AStream, Ord(ADocument.Background.Compression));
  626. BitmapWriter(AStream, ADocument.Background);
  627. end;
  628. ADocument.Background.EndScan;
  629. end;
  630. begin
  631. if (ADocument.Width = 0) or (ADocument.Height = 0) then
  632. raise EPhotoshopDocument.Create('Invalid PSD document size');
  633. // Header
  634. WriteRawAnsiString('8BPS');
  635. BigEndian.WriteWord(AStream, PSD_VERSION_PSD);
  636. WritePadding(6); // unused
  637. BigEndian.WriteWord(AStream, PSD_CHANNELS);// PSD_CHANNELS
  638. BigEndian.WriteCardinal(AStream, ADocument.Height); // height
  639. BigEndian.WriteCardinal(AStream, ADocument.Width); // width
  640. BigEndian.WriteWord(AStream, 8);// bit depth
  641. BigEndian.WriteWord(AStream, PSD_RGB);// color mode RGB = 3
  642. // color mode Table
  643. BigEndian.WriteCardinal(AStream, 0);
  644. // resources
  645. BigEndian.WriteCardinal(AStream, 0);
  646. SectionsCaptures := TStack<Int64>.Create;
  647. try
  648. // layer
  649. WriteLayer;
  650. finally
  651. SectionsCaptures.Free;
  652. end;
  653. //Image
  654. if (ADocument.Background = nil) then
  655. WriteEmptyImage
  656. else
  657. WriteImage;
  658. end;
  659. end.