ImagingPortableMaps.pas 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961
  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 loader/saver for Portable Maps file format family (or PNM).
  12. That includes PBM, PGM, PPM, PAM, and PFM formats.}
  13. unit ImagingPortableMaps;
  14. {$I ImagingOptions.inc}
  15. interface
  16. uses
  17. SysUtils, ImagingTypes, Imaging, ImagingFormats, ImagingUtility;
  18. type
  19. { Types of pixels of PNM images.}
  20. TTupleType = (ttInvalid, ttBlackAndWhite, ttGrayScale, ttRGB, ttBlackAndWhiteAlpha,
  21. ttGrayScaleAlpha, ttRGBAlpha, ttGrayScaleFP, ttRGBFP);
  22. { Record with info about PNM image used in both loading and saving functions.}
  23. TPortableMapInfo = record
  24. Width: LongInt;
  25. Height: LongInt;
  26. FormatId: AnsiChar;
  27. MaxVal: LongInt;
  28. BitCount: LongInt;
  29. Depth: LongInt;
  30. TupleType: TTupleType;
  31. Binary: Boolean;
  32. HasPAMHeader: Boolean;
  33. IsBigEndian: Boolean;
  34. end;
  35. { Base class for Portable Map file formats (or Portable AnyMaps or PNM).
  36. There are several types of PNM file formats that share common
  37. (simple) structure. This class can actually load all supported PNM formats.
  38. Saving is also done by this class but descendants (each for different PNM
  39. format) control it.}
  40. TPortableMapFileFormat = class(TImageFileFormat)
  41. protected
  42. FIdNumbers: TChar2;
  43. FSaveBinary: LongBool;
  44. FUSFormat: TFormatSettings;
  45. procedure Define; override;
  46. function LoadData(Handle: TImagingHandle; var Images: TDynImageDataArray;
  47. OnlyFirstLevel: Boolean): Boolean; override;
  48. function SaveDataInternal(Handle: TImagingHandle; const Images: TDynImageDataArray;
  49. Index: LongInt; var MapInfo: TPortableMapInfo): Boolean;
  50. public
  51. function TestFormat(Handle: TImagingHandle): Boolean; override;
  52. published
  53. { If set to True images will be saved in binary format. If it is False
  54. they will be saved in text format (which could result in 5-10x bigger file).
  55. Default is value True. Note that PAM and PFM files are always saved in binary.}
  56. property SaveBinary: LongBool read FSaveBinary write FSaveBinary;
  57. end;
  58. { Portable Bit Map is used to store monochrome 1bit images. Raster data
  59. can be saved as text or binary data. Either way value of 0 represents white
  60. and 1 is black. As Imaging does not have support for 1bit data formats
  61. PBM images can be loaded but not saved. Loaded images are returned in
  62. ifGray8 format (witch pixel values scaled from 1bit to 8bit).}
  63. TPBMFileFormat = class(TPortableMapFileFormat)
  64. protected
  65. procedure Define; override;
  66. end;
  67. { Portable Gray Map is used to store grayscale 8bit or 16bit images.
  68. Raster data can be saved as text or binary data.}
  69. TPGMFileFormat = class(TPortableMapFileFormat)
  70. protected
  71. procedure Define; override;
  72. function SaveData(Handle: TImagingHandle; const Images: TDynImageDataArray;
  73. Index: LongInt): Boolean; override;
  74. procedure ConvertToSupported(var Image: TImageData;
  75. const Info: TImageFormatInfo); override;
  76. end;
  77. { Portable Pixel Map is used to store RGB images with 8bit or 16bit channels.
  78. Raster data can be saved as text or binary data.}
  79. TPPMFileFormat = class(TPortableMapFileFormat)
  80. protected
  81. procedure Define; override;
  82. function SaveData(Handle: TImagingHandle; const Images: TDynImageDataArray;
  83. Index: LongInt): Boolean; override;
  84. procedure ConvertToSupported(var Image: TImageData;
  85. const Info: TImageFormatInfo); override;
  86. end;
  87. { Portable Arbitrary Map is format that can store image data formats
  88. of PBM, PGM, and PPM formats with optional alpha channel. Raster data
  89. can be stored only in binary format. All data formats supported
  90. by this format are ifGray8, ifGray16, ifA8Gray8, ifA16Gray16,
  91. ifR8G8B8, ifR16G16R16, ifA8R8G8B8, and ifA16R16G16B16.}
  92. TPAMFileFormat = class(TPortableMapFileFormat)
  93. protected
  94. procedure Define; override;
  95. function SaveData(Handle: TImagingHandle; const Images: TDynImageDataArray;
  96. Index: LongInt): Boolean; override;
  97. procedure ConvertToSupported(var Image: TImageData;
  98. const Info: TImageFormatInfo); override;
  99. end;
  100. { Portable Float Map is unofficial extension of PNM format family which
  101. can store images with floating point pixels. Raster data is saved in
  102. binary format as array of IEEE 32 bit floating point numbers. One channel
  103. or RGB images are supported by PFM format (so no alpha).}
  104. TPFMFileFormat = class(TPortableMapFileFormat)
  105. protected
  106. procedure Define; override;
  107. function SaveData(Handle: TImagingHandle; const Images: TDynImageDataArray;
  108. Index: LongInt): Boolean; override;
  109. procedure ConvertToSupported(var Image: TImageData;
  110. const Info: TImageFormatInfo); override;
  111. end;
  112. implementation
  113. const
  114. PortableMapDefaultBinary = True;
  115. SPBMFormatName = 'Portable Bit Map';
  116. SPBMMasks = '*.pbm';
  117. SPGMFormatName = 'Portable Gray Map';
  118. SPGMMasks = '*.pgm';
  119. PGMSupportedFormats = [ifGray8, ifGray16];
  120. SPPMFormatName = 'Portable Pixel Map';
  121. SPPMMasks = '*.ppm';
  122. PPMSupportedFormats = [ifR8G8B8, ifR16G16B16];
  123. SPAMFormatName = 'Portable Arbitrary Map';
  124. SPAMMasks = '*.pam';
  125. PAMSupportedFormats = [ifGray8, ifGray16, ifA8Gray8, ifA16Gray16,
  126. ifR8G8B8, ifR16G16B16, ifA8R8G8B8, ifA16R16G16B16];
  127. SPFMFormatName = 'Portable Float Map';
  128. SPFMMasks = '*.pfm';
  129. PFMSupportedFormats = [ifR32F, ifB32G32R32F];
  130. const
  131. { TAB, CR, LF, and Space are used as seperators in Portable map headers and data.}
  132. WhiteSpaces = [#9, #10, #13, #32];
  133. SPAMWidth = 'WIDTH';
  134. SPAMHeight = 'HEIGHT';
  135. SPAMDepth = 'DEPTH';
  136. SPAMMaxVal = 'MAXVAL';
  137. SPAMTupleType = 'TUPLTYPE';
  138. SPAMEndHdr = 'ENDHDR';
  139. { Size of buffer used to speed up text PNM loading/saving.}
  140. LineBufferCapacity = 16 * 1024;
  141. TupleTypeNames: array[TTupleType] of string = (
  142. 'INVALID', 'BLACKANDWHITE', 'GRAYSCALE', 'RGB',
  143. 'BLACKANDWHITE_ALPHA', 'GRAYSCALE_ALPHA', 'RGB_ALPHA', 'GRAYSCALEFP',
  144. 'RGBFP');
  145. { TPortableMapFileFormat }
  146. procedure TPortableMapFileFormat.Define;
  147. begin
  148. inherited;
  149. FFeatures := [ffLoad, ffSave];
  150. FSaveBinary := PortableMapDefaultBinary;
  151. FUSFormat := GetFormatSettingsForFloats;
  152. end;
  153. function TPortableMapFileFormat.LoadData(Handle: TImagingHandle;
  154. var Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
  155. var
  156. I, ScanLineSize, MonoSize: LongInt;
  157. Dest: PByte;
  158. MonoData: Pointer;
  159. Info: TImageFormatInfo;
  160. LineBuffer: array[0..LineBufferCapacity - 1] of AnsiChar;
  161. LineEnd, LinePos: LongInt;
  162. MapInfo: TPortableMapInfo;
  163. LineBreak: string;
  164. procedure CheckBuffer;
  165. begin
  166. if (LineEnd = 0) or (LinePos = LineEnd) then
  167. begin
  168. // Reload buffer if its is empty or its end was reached
  169. LineEnd := GetIO.Read(Handle, @LineBuffer[0], LineBufferCapacity);
  170. LinePos := 0;
  171. end;
  172. end;
  173. procedure FixInputPos;
  174. begin
  175. // Sets input's position to its real pos as it would be without buffering
  176. if LineEnd > 0 then
  177. begin
  178. GetIO.Seek(Handle, -LineEnd + LinePos, smFromCurrent);
  179. LineEnd := 0;
  180. end;
  181. end;
  182. function ReadString: string;
  183. var
  184. S: AnsiString;
  185. C: AnsiChar;
  186. begin
  187. // First skip all whitespace chars
  188. SetLength(S, 1);
  189. repeat
  190. CheckBuffer;
  191. S[1] := LineBuffer[LinePos];
  192. Inc(LinePos);
  193. if S[1] = '#' then
  194. repeat
  195. // Comment detected, skip everything until next line is reached
  196. CheckBuffer;
  197. S[1] := LineBuffer[LinePos];
  198. Inc(LinePos);
  199. until S[1] = #10;
  200. until not(S[1] in WhiteSpaces);
  201. // Now we have reached some chars other than white space, read them until
  202. // there is whitespace again
  203. repeat
  204. SetLength(S, Length(S) + 1);
  205. CheckBuffer;
  206. S[Length(S)] := LineBuffer[LinePos];
  207. Inc(LinePos);
  208. // Repeat until current char is whitespace or end of file is reached
  209. // (Line buffer has 0 bytes which happens only on EOF)
  210. until (S[Length(S)] in WhiteSpaces) or (LineEnd = 0);
  211. // Get rid of last char - whitespace or null
  212. SetLength(S, Length(S) - 1);
  213. // Move position to the beginning of next string (skip white space - needed
  214. // to make the loader stop at the right input position)
  215. repeat
  216. CheckBuffer;
  217. C := LineBuffer[LinePos];
  218. Inc(LinePos);
  219. until not (C in WhiteSpaces) or (LineEnd = 0);
  220. // Dec pos, current is the beginning of the the string
  221. Dec(LinePos);
  222. Result := string(S);
  223. end;
  224. function ReadIntValue: LongInt; {$IFDEF USE_INLINE}inline;{$ENDIF}
  225. begin
  226. Result := StrToInt(ReadString);
  227. end;
  228. procedure FindLineBreak;
  229. var
  230. C: AnsiChar;
  231. begin
  232. LineBreak := #10;
  233. repeat
  234. CheckBuffer;
  235. C := LineBuffer[LinePos];
  236. Inc(LinePos);
  237. if C = #13 then
  238. LineBreak := #13#10;
  239. until C = #10;
  240. end;
  241. function ParseHeader: Boolean;
  242. var
  243. Id: TChar2;
  244. I: TTupleType;
  245. TupleTypeName: string;
  246. Scale: Single;
  247. begin
  248. Result := False;
  249. with GetIO do
  250. begin
  251. FillChar(MapInfo, SizeOf(MapInfo), 0);
  252. Read(Handle, @Id, SizeOf(Id));
  253. FindLineBreak;
  254. if Id[1] in ['1'..'6'] then
  255. begin
  256. // Read header for PBM, PGM, and PPM files
  257. MapInfo.Width := ReadIntValue;
  258. MapInfo.Height := ReadIntValue;
  259. if Id[1] in ['1', '4'] then
  260. begin
  261. MapInfo.MaxVal := 1;
  262. MapInfo.BitCount := 1
  263. end
  264. else
  265. begin
  266. // Read channel max value, <=255 for 8bit images, >255 for 16bit images
  267. // but some programs think its max colors so put <=256 here
  268. MapInfo.MaxVal := ReadIntValue;
  269. MapInfo.BitCount := Iff(MapInfo.MaxVal <= 256, 8, 16);
  270. end;
  271. MapInfo.Depth := 1;
  272. case Id[1] of
  273. '1', '4': MapInfo.TupleType := ttBlackAndWhite;
  274. '2', '5': MapInfo.TupleType := ttGrayScale;
  275. '3', '6':
  276. begin
  277. MapInfo.TupleType := ttRGB;
  278. MapInfo.Depth := 3;
  279. end;
  280. end;
  281. end
  282. else if Id[1] = '7' then
  283. begin
  284. // Read values from PAM header
  285. // WIDTH
  286. if (ReadString <> SPAMWidth) then Exit;
  287. MapInfo.Width := ReadIntValue;
  288. // HEIGHT
  289. if (ReadString <> SPAMheight) then Exit;
  290. MapInfo.Height := ReadIntValue;
  291. // DEPTH
  292. if (ReadString <> SPAMDepth) then Exit;
  293. MapInfo.Depth := ReadIntValue;
  294. // MAXVAL
  295. if (ReadString <> SPAMMaxVal) then Exit;
  296. MapInfo.MaxVal := ReadIntValue;
  297. MapInfo.BitCount := Iff(MapInfo.MaxVal <= 256, 8, 16);
  298. // TUPLETYPE
  299. if (ReadString <> SPAMTupleType) then Exit;
  300. TupleTypeName := ReadString;
  301. for I := Low(TTupleType) to High(TTupleType) do
  302. if SameText(TupleTypeName, TupleTypeNames[I]) then
  303. begin
  304. MapInfo.TupleType := I;
  305. Break;
  306. end;
  307. // ENDHDR
  308. if (ReadString <> SPAMEndHdr) then Exit;
  309. end
  310. else if Id[1] in ['F', 'f'] then
  311. begin
  312. // Read header of PFM file
  313. MapInfo.Width := ReadIntValue;
  314. MapInfo.Height := ReadIntValue;
  315. Scale := StrToFloatDef(ReadString, 0, FUSFormat);
  316. MapInfo.IsBigEndian := Scale > 0.0;
  317. if Id[1] = 'F' then
  318. MapInfo.TupleType := ttRGBFP
  319. else
  320. MapInfo.TupleType := ttGrayScaleFP;
  321. MapInfo.Depth := Iff(MapInfo.TupleType = ttRGBFP, 3, 1);
  322. MapInfo.BitCount := Iff(MapInfo.TupleType = ttRGBFP, 96, 32);
  323. end;
  324. FixInputPos;
  325. MapInfo.Binary := (Id[1] in ['4', '5', '6', '7', 'F', 'f']);
  326. if MapInfo.Binary and not (Id[1] in ['F', 'f']) then
  327. begin
  328. // Mimic the behaviour of Photoshop and other editors/viewers:
  329. // If linereaks in file are DOS CR/LF 16bit binary values are
  330. // little endian, Unix LF only linebreak indicates big endian.
  331. MapInfo.IsBigEndian := LineBreak = #10;
  332. end;
  333. // Check if values found in header are valid
  334. Result := (MapInfo.Width > 0) and (MapInfo.Height > 0) and
  335. (MapInfo.BitCount in [1, 8, 16, 32, 96]) and (MapInfo.TupleType <> ttInvalid);
  336. // Now check if image has proper number of channels (PAM)
  337. if Result then
  338. case MapInfo.TupleType of
  339. ttBlackAndWhite, ttGrayScale: Result := MapInfo.Depth = 1;
  340. ttBlackAndWhiteAlpha, ttGrayScaleAlpha: Result := MapInfo.Depth = 2;
  341. ttRGB: Result := MapInfo.Depth = 3;
  342. ttRGBAlpha: Result := MapInfo.Depth = 4;
  343. end;
  344. end;
  345. end;
  346. begin
  347. Result := False;
  348. LineEnd := 0;
  349. LinePos := 0;
  350. SetLength(Images, 1);
  351. with GetIO, Images[0] do
  352. begin
  353. Format := ifUnknown;
  354. // Try to parse file header
  355. if not ParseHeader then Exit;
  356. // Select appropriate data format based on values read from file header
  357. case MapInfo.TupleType of
  358. ttBlackAndWhite: Format := ifGray8;
  359. ttBlackAndWhiteAlpha: Format := ifA8Gray8;
  360. ttGrayScale: Format := IffFormat(MapInfo.BitCount = 8, ifGray8, ifGray16);
  361. ttGrayScaleAlpha: Format := IffFormat(MapInfo.BitCount = 8, ifA8Gray8, ifA16Gray16);
  362. ttRGB: Format := IffFormat(MapInfo.BitCount = 8, ifR8G8B8, ifR16G16B16);
  363. ttRGBAlpha: Format := IffFormat(MapInfo.BitCount = 8, ifA8R8G8B8, ifA16R16G16B16);
  364. ttGrayScaleFP: Format := ifR32F;
  365. ttRGBFP: Format := ifB32G32R32F;
  366. end;
  367. // Exit if no matching data format was found
  368. if Format = ifUnknown then Exit;
  369. NewImage(MapInfo.Width, MapInfo.Height, Format, Images[0]);
  370. Info := GetFormatInfo(Format);
  371. // Now read pixels from file to dest image
  372. if not MapInfo.Binary then
  373. begin
  374. Dest := Bits;
  375. for I := 0 to Width * Height - 1 do
  376. begin
  377. case Format of
  378. ifGray8:
  379. begin
  380. Dest^ := ReadIntValue;
  381. if MapInfo.BitCount = 1 then
  382. // If source is 1bit mono image (where 0=white, 1=black)
  383. // we must scale it to 8bits
  384. Dest^ := 255 - Dest^ * 255;
  385. end;
  386. ifGray16: PWord(Dest)^ := ReadIntValue;
  387. ifR8G8B8:
  388. with PColor24Rec(Dest)^ do
  389. begin
  390. R := ReadIntValue;
  391. G := ReadIntValue;
  392. B := ReadIntValue;
  393. end;
  394. ifR16G16B16:
  395. with PColor48Rec(Dest)^ do
  396. begin
  397. R := ReadIntValue;
  398. G := ReadIntValue;
  399. B := ReadIntValue;
  400. end;
  401. end;
  402. Inc(Dest, Info.BytesPerPixel);
  403. end;
  404. end
  405. else
  406. begin
  407. if MapInfo.BitCount > 1 then
  408. begin
  409. if not (MapInfo.TupleType in [ttGrayScaleFP, ttRGBFP]) then
  410. begin
  411. // Just copy bytes from binary Portable Maps (non 1bit, non FP)
  412. Read(Handle, Bits, Size);
  413. end
  414. else
  415. begin
  416. Dest := Bits;
  417. // FP images are in BGR order and endian swap maybe needed.
  418. // Some programs store scanlines in bottom-up order but
  419. // I will stick with Photoshops behaviour here
  420. Read(Handle, Bits, Size);
  421. if MapInfo.IsBigEndian then
  422. SwapEndianUInt32(PUInt32(Dest), Size div SizeOf(UInt32));
  423. end;
  424. if MapInfo.TupleType in [ttBlackAndWhite, ttBlackAndWhiteAlpha] then
  425. begin
  426. // Black and white PAM files must be scaled to 8bits. Note that
  427. // in PAM files 1=white, 0=black (reverse of PBM)
  428. for I := 0 to Width * Height * Iff(MapInfo.TupleType = ttBlackAndWhiteAlpha, 2, 1) - 1 do
  429. PByteArray(Bits)[I] := PByteArray(Bits)[I] * 255;
  430. end
  431. else if MapInfo.TupleType in [ttRGB, ttRGBAlpha] then
  432. begin
  433. // Swap channels of RGB/ARGB images. Binary RGB image files use BGR order.
  434. SwapChannels(Images[0], ChannelBlue, ChannelRed);
  435. end;
  436. // Swap byte order if needed
  437. if (MapInfo.BitCount = 16) and MapInfo.IsBigEndian then
  438. SwapEndianWord(Bits, Width * Height * Info.BytesPerPixel div SizeOf(Word));
  439. end
  440. else
  441. begin
  442. // Handle binary PBM files (ttBlackAndWhite 1bit)
  443. ScanLineSize := (Width + 7) div 8;
  444. // Get total binary data size, read it from file to temp
  445. // buffer and convert the data to Gray8
  446. MonoSize := ScanLineSize * Height;
  447. GetMem(MonoData, MonoSize);
  448. try
  449. Read(Handle, MonoData, MonoSize);
  450. Convert1To8(MonoData, Bits, Width, Height, ScanLineSize, False);
  451. // 1bit mono images must be scaled to 8bit, but inverted (where 0=white, 1=black)
  452. for I := 0 to Width * Height - 1 do
  453. PByteArray(Bits)[I] := 255 - PByteArray(Bits)[I] * 255;
  454. finally
  455. FreeMem(MonoData);
  456. end;
  457. end;
  458. end;
  459. FixInputPos;
  460. if (MapInfo.MaxVal <> Pow2Int(MapInfo.BitCount) - 1) and
  461. (MapInfo.TupleType in [ttGrayScale, ttGrayScaleAlpha, ttRGB, ttRGBAlpha]) then
  462. begin
  463. Dest := Bits;
  464. // Scale color values according to MaxVal we got from header
  465. // if necessary.
  466. for I := 0 to Width * Height * Info.BytesPerPixel div (MapInfo.BitCount shr 3) - 1 do
  467. begin
  468. if MapInfo.BitCount = 8 then
  469. Dest^ := Dest^ * 255 div MapInfo.MaxVal
  470. else
  471. PWord(Dest)^ := PWord(Dest)^ * 65535 div MapInfo.MaxVal;
  472. Inc(Dest, MapInfo.BitCount shr 3);
  473. end;
  474. end;
  475. Result := True;
  476. end;
  477. end;
  478. function TPortableMapFileFormat.SaveDataInternal(Handle: TImagingHandle;
  479. const Images: TDynImageDataArray; Index: LongInt; var MapInfo: TPortableMapInfo): Boolean;
  480. const
  481. // Use Unix linebreak, for many viewers/editors it means that
  482. // 16bit samples are stored as big endian - so we need to swap byte order
  483. // before saving
  484. LineDelimiter = #10;
  485. PixelDelimiter = #32;
  486. var
  487. ImageToSave: TImageData;
  488. MustBeFreed: Boolean;
  489. Info: TImageFormatInfo;
  490. I, LineLength: LongInt;
  491. Src: PByte;
  492. Pixel32: TColor32Rec;
  493. Pixel64: TColor64Rec;
  494. W: Word;
  495. procedure WriteString(S: string; Delimiter: Char = LineDelimiter);
  496. begin
  497. SetLength(S, Length(S) + 1);
  498. S[Length(S)] := Delimiter;
  499. {$IF Defined(DCC) and Defined(UNICODE)}
  500. GetIO.Write(Handle, @AnsiString(S)[1], Length(S));
  501. {$ELSE}
  502. GetIO.Write(Handle, @S[1], Length(S));
  503. {$IFEND}
  504. Inc(LineLength, Length(S));
  505. end;
  506. procedure WriteHeader;
  507. begin
  508. WriteString('P' + MapInfo.FormatId);
  509. if not MapInfo.HasPAMHeader then
  510. begin
  511. // Write header of PGM, PPM, and PFM files
  512. WriteString(IntToStr(ImageToSave.Width));
  513. WriteString(IntToStr(ImageToSave.Height));
  514. case MapInfo.TupleType of
  515. ttGrayScale, ttRGB: WriteString(IntToStr(Pow2Int(MapInfo.BitCount) - 1));
  516. ttGrayScaleFP, ttRGBFP:
  517. begin
  518. // Negative value indicates that raster data is saved in little endian
  519. WriteString(FloatToStr(-1.0, FUSFormat));
  520. end;
  521. end;
  522. end
  523. else
  524. begin
  525. // Write PAM file header
  526. WriteString(Format('%s %d', [SPAMWidth, ImageToSave.Width]));
  527. WriteString(Format('%s %d', [SPAMHeight, ImageToSave.Height]));
  528. WriteString(Format('%s %d', [SPAMDepth, MapInfo.Depth]));
  529. WriteString(Format('%s %d', [SPAMMaxVal, Pow2Int(MapInfo.BitCount) - 1]));
  530. WriteString(Format('%s %s', [SPAMTupleType, TupleTypeNames[MapInfo.TupleType]]));
  531. WriteString(SPAMEndHdr);
  532. end;
  533. end;
  534. begin
  535. Result := False;
  536. if MakeCompatible(Images[Index], ImageToSave, MustBeFreed) then
  537. with GetIO, ImageToSave do
  538. try
  539. Info := GetFormatInfo(Format);
  540. // Fill values of MapInfo record that were not filled by
  541. // descendants in their SaveData methods
  542. MapInfo.BitCount := (Info.BytesPerPixel div Info.ChannelCount) * 8;
  543. MapInfo.Depth := Info.ChannelCount;
  544. if MapInfo.TupleType = ttInvalid then
  545. begin
  546. if Info.HasGrayChannel then
  547. begin
  548. if Info.HasAlphaChannel then
  549. MapInfo.TupleType := ttGrayScaleAlpha
  550. else
  551. MapInfo.TupleType := ttGrayScale;
  552. end
  553. else
  554. begin
  555. if Info.HasAlphaChannel then
  556. MapInfo.TupleType := ttRGBAlpha
  557. else
  558. MapInfo.TupleType := ttRGB;
  559. end;
  560. end;
  561. // Write file header
  562. WriteHeader;
  563. if not MapInfo.Binary then
  564. begin
  565. Src := Bits;
  566. LineLength := 0;
  567. // For each pixel find its text representation and write it to file
  568. for I := 0 to Width * Height - 1 do
  569. begin
  570. case Format of
  571. ifGray8: WriteString(IntToStr(Src^), PixelDelimiter);
  572. ifGray16: WriteString(IntToStr(PWord(Src)^), PixelDelimiter);
  573. ifR8G8B8:
  574. with PColor24Rec(Src)^ do
  575. WriteString(SysUtils.Format('%d %d %d', [R, G, B]), PixelDelimiter);
  576. ifR16G16B16:
  577. with PColor48Rec(Src)^ do
  578. WriteString(SysUtils.Format('%d %d %d', [R, G, B]), PixelDelimiter);
  579. end;
  580. // Lines in text PNM images should have length <70
  581. if LineLength > 65 then
  582. begin
  583. LineLength := 0;
  584. WriteString('', LineDelimiter);
  585. end;
  586. Inc(Src, Info.BytesPerPixel);
  587. end;
  588. end
  589. else
  590. begin
  591. // Write binary images
  592. if not (MapInfo.TupleType in [ttGrayScaleFP, ttRGBFP]) then
  593. begin
  594. // Save integer binary images
  595. if MapInfo.BitCount = 8 then
  596. begin
  597. if MapInfo.TupleType in [ttGrayScale, ttGrayScaleAlpha] then
  598. begin
  599. // 8bit grayscale images can be written in one Write call
  600. Write(Handle, Bits, Size);
  601. end
  602. else
  603. begin
  604. // 8bit RGB/ARGB images: red and blue must be swapped and
  605. // 3 or 4 bytes must be written
  606. Src := Bits;
  607. for I := 0 to Width * Height - 1 do
  608. with PColor32Rec(Src)^ do
  609. begin
  610. if MapInfo.TupleType = ttRGBAlpha then
  611. Pixel32.A := A;
  612. Pixel32.R := B;
  613. Pixel32.G := G;
  614. Pixel32.B := R;
  615. Write(Handle, @Pixel32, Info.BytesPerPixel);
  616. Inc(Src, Info.BytesPerPixel);
  617. end;
  618. end;
  619. end
  620. else
  621. begin
  622. // Images with 16bit channels: make sure that channel values are saved in big endian
  623. Src := Bits;
  624. if MapInfo.TupleType in [ttGrayScale, ttGrayScaleAlpha] then
  625. begin
  626. // 16bit grayscale image
  627. for I := 0 to Width * Height * Info.BytesPerPixel div SizeOf(Word) - 1 do
  628. begin
  629. W := SwapEndianWord(PWord(Src)^);
  630. Write(Handle, @W, SizeOf(Word));
  631. Inc(Src, SizeOf(Word));
  632. end;
  633. end
  634. else
  635. begin
  636. // RGB images with 16bit channels: swap RB and endian too
  637. for I := 0 to Width * Height - 1 do
  638. with PColor64Rec(Src)^ do
  639. begin
  640. if MapInfo.TupleType = ttRGBAlpha then
  641. Pixel64.A := SwapEndianWord(A);
  642. Pixel64.R := SwapEndianWord(B);
  643. Pixel64.G := SwapEndianWord(G);
  644. Pixel64.B := SwapEndianWord(R);
  645. Write(Handle, @Pixel64, Info.BytesPerPixel);
  646. Inc(Src, Info.BytesPerPixel);
  647. end;
  648. end;
  649. end;
  650. end
  651. else
  652. begin
  653. // Floating point images (no need to swap endian here - little
  654. // endian is specified in file header)
  655. Write(Handle, Bits, Size);
  656. end;
  657. end;
  658. Result := True;
  659. finally
  660. if MustBeFreed then
  661. FreeImage(ImageToSave);
  662. end;
  663. end;
  664. function TPortableMapFileFormat.TestFormat(Handle: TImagingHandle): Boolean;
  665. var
  666. Id: TChar4;
  667. ReadCount: LongInt;
  668. begin
  669. Result := False;
  670. if Handle <> nil then
  671. with GetIO do
  672. begin
  673. ReadCount := Read(Handle, @Id, SizeOf(Id));
  674. Seek(Handle, -ReadCount, smFromCurrent);
  675. Result := (Id[0] = 'P') and (Id[1] in [FIdNumbers[0], FIdNumbers[1]]) and
  676. (Id[2] in WhiteSpaces);
  677. end;
  678. end;
  679. { TPBMFileFormat }
  680. procedure TPBMFileFormat.Define;
  681. begin
  682. inherited;
  683. FName := SPBMFormatName;
  684. FFeatures := [ffLoad];
  685. AddMasks(SPBMMasks);
  686. FIdNumbers := '14';
  687. end;
  688. { TPGMFileFormat }
  689. procedure TPGMFileFormat.Define;
  690. begin
  691. inherited;
  692. FName := SPGMFormatName;
  693. FSupportedFormats := PGMSupportedFormats;
  694. AddMasks(SPGMMasks);
  695. RegisterOption(ImagingPGMSaveBinary, @FSaveBinary);
  696. FIdNumbers := '25';
  697. end;
  698. function TPGMFileFormat.SaveData(Handle: TImagingHandle;
  699. const Images: TDynImageDataArray; Index: LongInt): Boolean;
  700. var
  701. MapInfo: TPortableMapInfo;
  702. begin
  703. FillChar(MapInfo, SizeOf(MapInfo), 0);
  704. if FSaveBinary then
  705. MapInfo.FormatId := FIdNumbers[1]
  706. else
  707. MapInfo.FormatId := FIdNumbers[0];
  708. MapInfo.Binary := FSaveBinary;
  709. Result := SaveDataInternal(Handle, Images, Index, MapInfo);
  710. end;
  711. procedure TPGMFileFormat.ConvertToSupported(var Image: TImageData;
  712. const Info: TImageFormatInfo);
  713. var
  714. ConvFormat: TImageFormat;
  715. begin
  716. if Info.IsFloatingPoint then
  717. // All FP images go to 16bit
  718. ConvFormat := ifGray16
  719. else if Info.HasGrayChannel then
  720. // Grayscale will be 8 or 16 bit - depends on input's bitcount
  721. ConvFormat := IffFormat(Info.BytesPerPixel div Info.ChannelCount > 1,
  722. ifGray16, ifGray8)
  723. else if Info.BytesPerPixel > 4 then
  724. // Large bitcounts -> 16bit
  725. ConvFormat := ifGray16
  726. else
  727. // Rest of the formats -> 8bit
  728. ConvFormat := ifGray8;
  729. ConvertImage(Image, ConvFormat);
  730. end;
  731. { TPPMFileFormat }
  732. procedure TPPMFileFormat.Define;
  733. begin
  734. inherited;
  735. FName := SPPMFormatName;
  736. FSupportedFormats := PPMSupportedFormats;
  737. AddMasks(SPPMMasks);
  738. RegisterOption(ImagingPPMSaveBinary, @FSaveBinary);
  739. FIdNumbers := '36';
  740. end;
  741. function TPPMFileFormat.SaveData(Handle: TImagingHandle;
  742. const Images: TDynImageDataArray; Index: LongInt): Boolean;
  743. var
  744. MapInfo: TPortableMapInfo;
  745. begin
  746. FillChar(MapInfo, SizeOf(MapInfo), 0);
  747. if FSaveBinary then
  748. MapInfo.FormatId := FIdNumbers[1]
  749. else
  750. MapInfo.FormatId := FIdNumbers[0];
  751. MapInfo.Binary := FSaveBinary;
  752. Result := SaveDataInternal(Handle, Images, Index, MapInfo);
  753. end;
  754. procedure TPPMFileFormat.ConvertToSupported(var Image: TImageData;
  755. const Info: TImageFormatInfo);
  756. var
  757. ConvFormat: TImageFormat;
  758. begin
  759. if Info.IsFloatingPoint then
  760. // All FP images go to 48bit RGB
  761. ConvFormat := ifR16G16B16
  762. else if Info.HasGrayChannel then
  763. // Grayscale will be 24 or 48 bit RGB - depends on input's bitcount
  764. ConvFormat := IffFormat(Info.BytesPerPixel div Info.ChannelCount > 1,
  765. ifR16G16B16, ifR8G8B8)
  766. else if Info.BytesPerPixel > 4 then
  767. // Large bitcounts -> 48bit RGB
  768. ConvFormat := ifR16G16B16
  769. else
  770. // Rest of the formats -> 24bit RGB
  771. ConvFormat := ifR8G8B8;
  772. ConvertImage(Image, ConvFormat);
  773. end;
  774. { TPAMFileFormat }
  775. procedure TPAMFileFormat.Define;
  776. begin
  777. inherited;
  778. FName := SPAMFormatName;
  779. FSupportedFormats := PAMSupportedFormats;
  780. AddMasks(SPAMMasks);
  781. FIdNumbers := '77';
  782. end;
  783. function TPAMFileFormat.SaveData(Handle: TImagingHandle;
  784. const Images: TDynImageDataArray; Index: LongInt): Boolean;
  785. var
  786. MapInfo: TPortableMapInfo;
  787. begin
  788. FillChar(MapInfo, SizeOf(MapInfo), 0);
  789. MapInfo.FormatId := FIdNumbers[0];
  790. MapInfo.Binary := True;
  791. MapInfo.HasPAMHeader := True;
  792. Result := SaveDataInternal(Handle, Images, Index, MapInfo);
  793. end;
  794. procedure TPAMFileFormat.ConvertToSupported(var Image: TImageData;
  795. const Info: TImageFormatInfo);
  796. var
  797. ConvFormat: TImageFormat;
  798. begin
  799. if Info.IsFloatingPoint then
  800. ConvFormat := IffFormat(Info.HasAlphaChannel, ifA16R16G16B16, ifR16G16B16)
  801. else if Info.HasGrayChannel then
  802. ConvFormat := IffFormat(Info.HasAlphaChannel, ifA16Gray16, ifGray16)
  803. else
  804. begin
  805. if Info.BytesPerPixel <= 4 then
  806. ConvFormat := IffFormat(Info.HasAlphaChannel, ifA8R8G8B8, ifR8G8B8)
  807. else
  808. ConvFormat := IffFormat(Info.HasAlphaChannel, ifA16R16G16B16, ifR16G16B16);
  809. end;
  810. ConvertImage(Image, ConvFormat);
  811. end;
  812. { TPFMFileFormat }
  813. procedure TPFMFileFormat.Define;
  814. begin
  815. inherited;
  816. FName := SPFMFormatName;
  817. AddMasks(SPFMMasks);
  818. FIdNumbers := 'Ff';
  819. FSupportedFormats := PFMSupportedFormats;
  820. end;
  821. function TPFMFileFormat.SaveData(Handle: TImagingHandle;
  822. const Images: TDynImageDataArray; Index: LongInt): Boolean;
  823. var
  824. Info: TImageFormatInfo;
  825. MapInfo: TPortableMapInfo;
  826. begin
  827. FillChar(MapInfo, SizeOf(MapInfo), 0);
  828. Info := GetFormatInfo(Images[Index].Format);
  829. if (Info.ChannelCount > 1) or Info.IsIndexed then
  830. MapInfo.TupleType := ttRGBFP
  831. else
  832. MapInfo.TupleType := ttGrayScaleFP;
  833. if MapInfo.TupleType = ttGrayScaleFP then
  834. MapInfo.FormatId := FIdNumbers[1]
  835. else
  836. MapInfo.FormatId := FIdNumbers[0];
  837. MapInfo.Binary := True;
  838. Result := SaveDataInternal(Handle, Images, Index, MapInfo);
  839. end;
  840. procedure TPFMFileFormat.ConvertToSupported(var Image: TImageData;
  841. const Info: TImageFormatInfo);
  842. begin
  843. if (Info.ChannelCount > 1) or Info.IsIndexed then
  844. ConvertImage(Image, ifB32G32R32F)
  845. else
  846. ConvertImage(Image, ifR32F);
  847. end;
  848. initialization
  849. RegisterImageFileFormat(TPBMFileFormat);
  850. RegisterImageFileFormat(TPGMFileFormat);
  851. RegisterImageFileFormat(TPPMFileFormat);
  852. RegisterImageFileFormat(TPAMFileFormat);
  853. RegisterImageFileFormat(TPFMFileFormat);
  854. {
  855. File Notes:
  856. -- TODOS ----------------------------------------------------
  857. - nothing now
  858. -- 0.77.1 Changes/Bug Fixes -----------------------------------
  859. - Native RGB floating point format of PFM is now supported by Imaging
  860. so we use it now for saving instead of A32B32G32B32.
  861. - String to float formatting changes (don't change global settings).
  862. -- 0.26.3 Changes/Bug Fixes -----------------------------------
  863. - Fixed D2009 Unicode related bug in PNM saving.
  864. -- 0.24.3 Changes/Bug Fixes -----------------------------------
  865. - Improved compatibility of 16bit/component image loading.
  866. - Changes for better thread safety.
  867. -- 0.21 Changes/Bug Fixes -----------------------------------
  868. - Made modifications to ASCII PNM loading to be more "stream-safe".
  869. - Fixed bug: indexed images saved as grayscale in PFM.
  870. - Changed converting to supported formats little bit.
  871. - Added scaling of channel values (non-FP and non-mono images) according
  872. to MaxVal.
  873. - Added buffering to loading of PNM files. More than 10x faster now
  874. for text files.
  875. - Added saving support to PGM, PPM, PAM, and PFM format.
  876. - Added PFM file format.
  877. - Initial version created.
  878. }
  879. end.