ImagingPortableMaps.pas 32 KB

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