ImagingPortableMaps.pas 33 KB

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