ImagingPortableMaps.pas 33 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015
  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. GetIO.Write(Handle, @S[1], Length(S));
  540. Inc(LineLength, Length(S));
  541. end;
  542. procedure WriteHeader;
  543. var
  544. OldSeparator: Char;
  545. begin
  546. WriteString('P' + MapInfo.FormatId);
  547. if not MapInfo.HasPAMHeader then
  548. begin
  549. // Write header of PGM, PPM, and PFM files
  550. WriteString(IntToStr(ImageToSave.Width));
  551. WriteString(IntToStr(ImageToSave.Height));
  552. case MapInfo.TupleType of
  553. ttGrayScale, ttRGB: WriteString(IntToStr(Pow2Int(MapInfo.BitCount) - 1));
  554. ttGrayScaleFP, ttRGBFP:
  555. begin
  556. OldSeparator := DecimalSeparator;
  557. DecimalSeparator := '.';
  558. // Negative value indicates that raster data is saved in little endian
  559. WriteString(FloatToStr(-1.0));
  560. DecimalSeparator := OldSeparator;
  561. end;
  562. end;
  563. end
  564. else
  565. begin
  566. // Write PAM file header
  567. WriteString(Format('%s %d', [SPAMWidth, ImageToSave.Width]));
  568. WriteString(Format('%s %d', [SPAMHeight, ImageToSave.Height]));
  569. WriteString(Format('%s %d', [SPAMDepth, MapInfo.Depth]));
  570. WriteString(Format('%s %d', [SPAMMaxVal, Pow2Int(MapInfo.BitCount) - 1]));
  571. WriteString(Format('%s %s', [SPAMTupleType, TupleTypeNames[MapInfo.TupleType]]));
  572. WriteString(SPAMEndHdr);
  573. end;
  574. end;
  575. begin
  576. Result := False;
  577. if MakeCompatible(Images[Index], ImageToSave, MustBeFreed) then
  578. with GetIO, ImageToSave do
  579. try
  580. Info := GetFormatInfo(Format);
  581. // Fill values of MapInfo record that were not filled by
  582. // descendants in their SaveData methods
  583. MapInfo.BitCount := (Info.BytesPerPixel div Info.ChannelCount) * 8;
  584. MapInfo.Depth := Info.ChannelCount;
  585. if MapInfo.TupleType = ttInvalid then
  586. begin
  587. if Info.HasGrayChannel then
  588. begin
  589. if Info.HasAlphaChannel then
  590. MapInfo.TupleType := ttGrayScaleAlpha
  591. else
  592. MapInfo.TupleType := ttGrayScale;
  593. end
  594. else
  595. begin
  596. if Info.HasAlphaChannel then
  597. MapInfo.TupleType := ttRGBAlpha
  598. else
  599. MapInfo.TupleType := ttRGB;
  600. end;
  601. end;
  602. // Write file header
  603. WriteHeader;
  604. if not MapInfo.Binary then
  605. begin
  606. Src := Bits;
  607. LineLength := 0;
  608. // For each pixel find its text representation and write it to file
  609. for I := 0 to Width * Height - 1 do
  610. begin
  611. case Format of
  612. ifGray8: WriteString(IntToStr(Src^), PixelDelimiter);
  613. ifGray16: WriteString(IntToStr(PWord(Src)^), PixelDelimiter);
  614. ifR8G8B8:
  615. with PColor24Rec(Src)^ do
  616. WriteString(SysUtils.Format('%d %d %d', [R, G, B]), PixelDelimiter);
  617. ifR16G16B16:
  618. with PColor48Rec(Src)^ do
  619. WriteString(SysUtils.Format('%d %d %d', [R, G, B]), PixelDelimiter);
  620. end;
  621. // Lines in text PNM images should have length <70
  622. if LineLength > 65 then
  623. begin
  624. LineLength := 0;
  625. WriteString('', LineDelimiter);
  626. end;
  627. Inc(Src, Info.BytesPerPixel);
  628. end;
  629. end
  630. else
  631. begin
  632. // Write binary images
  633. if not (MapInfo.TupleType in [ttGrayScaleFP, ttRGBFP]) then
  634. begin
  635. // Save integer binary images
  636. if MapInfo.BitCount = 8 then
  637. begin
  638. if MapInfo.TupleType in [ttGrayScale, ttGrayScaleAlpha] then
  639. begin
  640. // 8bit grayscale images can be written in one Write call
  641. Write(Handle, Bits, Size);
  642. end
  643. else
  644. begin
  645. // 8bit RGB/ARGB images: read and blue must be swapped and
  646. // 3 or 4 bytes must be written
  647. Src := Bits;
  648. for I := 0 to Width * Height - 1 do
  649. with PColor32Rec(Src)^ do
  650. begin
  651. if MapInfo.TupleType = ttRGBAlpha then
  652. Pixel32.A := A;
  653. Pixel32.R := B;
  654. Pixel32.G := G;
  655. Pixel32.B := R;
  656. Write(Handle, @Pixel32, Info.BytesPerPixel);
  657. Inc(Src, Info.BytesPerPixel);
  658. end;
  659. end;
  660. end
  661. else
  662. begin
  663. // Images with 16bit channels: make sure that channel values are saved in big endian
  664. Src := Bits;
  665. if MapInfo.TupleType in [ttGrayScale, ttGrayScaleAlpha] then
  666. begin
  667. // 16bit grayscale image
  668. for I := 0 to Width * Height * Info.BytesPerPixel div SizeOf(Word) - 1 do
  669. begin
  670. W := SwapEndianWord(PWord(Src)^);
  671. Write(Handle, @W, SizeOf(Word));
  672. Inc(Src, SizeOf(Word));
  673. end;
  674. end
  675. else
  676. begin
  677. // RGB images with 16bit channels: swap RB and endian too
  678. for I := 0 to Width * Height - 1 do
  679. with PColor64Rec(Src)^ do
  680. begin
  681. if MapInfo.TupleType = ttRGBAlpha then
  682. Pixel64.A := SwapEndianWord(A);
  683. Pixel64.R := SwapEndianWord(B);
  684. Pixel64.G := SwapEndianWord(G);
  685. Pixel64.B := SwapEndianWord(R);
  686. Write(Handle, @Pixel64, Info.BytesPerPixel);
  687. Inc(Src, Info.BytesPerPixel);
  688. end;
  689. end;
  690. end;
  691. end
  692. else
  693. begin
  694. // Floating point images (no need to swap endian here - little
  695. // endian is specified in file header)
  696. if MapInfo.TupleType = ttGrayScaleFP then
  697. begin
  698. // Grayscale images can be written in one Write call
  699. Write(Handle, Bits, Size);
  700. end
  701. else
  702. begin
  703. // Expected data format of PFM RGB file is B32G32R32F which is not
  704. // supported by Imaging. We must write pixels one by one and
  705. // write only RGB part of A32B32G32B32 image.
  706. Src := Bits;
  707. for I := 0 to Width * Height - 1 do
  708. begin
  709. Write(Handle, Src, SizeOf(Single) * 3);
  710. Inc(Src, Info.BytesPerPixel);
  711. end;
  712. end;
  713. end;
  714. end;
  715. Result := True;
  716. finally
  717. if MustBeFreed then
  718. FreeImage(ImageToSave);
  719. end;
  720. end;
  721. function TPortableMapFileFormat.TestFormat(Handle: TImagingHandle): Boolean;
  722. var
  723. Id: TChar4;
  724. ReadCount: LongInt;
  725. begin
  726. Result := False;
  727. if Handle <> nil then
  728. with GetIO do
  729. begin
  730. ReadCount := Read(Handle, @Id, SizeOf(Id));
  731. Seek(Handle, -ReadCount, smFromCurrent);
  732. Result := (Id[0] = 'P') and (Id[1] in [FIdNumbers[0], FIdNumbers[1]]) and
  733. (Id[2] in WhiteSpaces);
  734. end;
  735. end;
  736. { TPBMFileFormat }
  737. constructor TPBMFileFormat.Create;
  738. begin
  739. inherited Create;
  740. FName := SPBMFormatName;
  741. FCanSave := False;
  742. AddMasks(SPBMMasks);
  743. FIdNumbers := '14';
  744. end;
  745. { TPGMFileFormat }
  746. constructor TPGMFileFormat.Create;
  747. begin
  748. inherited Create;
  749. FName := SPGMFormatName;
  750. FSupportedFormats := PGMSupportedFormats;
  751. AddMasks(SPGMMasks);
  752. RegisterOption(ImagingPGMSaveBinary, @FSaveBinary);
  753. FIdNumbers := '25';
  754. end;
  755. function TPGMFileFormat.SaveData(Handle: TImagingHandle;
  756. const Images: TDynImageDataArray; Index: Integer): Boolean;
  757. var
  758. MapInfo: TPortableMapInfo;
  759. begin
  760. FillChar(MapInfo, SizeOf(MapInfo), 0);
  761. if FSaveBinary then
  762. MapInfo.FormatId := FIdNumbers[1]
  763. else
  764. MapInfo.FormatId := FIdNumbers[0];
  765. MapInfo.Binary := FSaveBinary;
  766. Result := SaveDataInternal(Handle, Images, Index, MapInfo);
  767. end;
  768. procedure TPGMFileFormat.ConvertToSupported(var Image: TImageData;
  769. const Info: TImageFormatInfo);
  770. var
  771. ConvFormat: TImageFormat;
  772. begin
  773. if Info.IsFloatingPoint then
  774. // All FP images go to 16bit
  775. ConvFormat := ifGray16
  776. else if Info.HasGrayChannel then
  777. // Grayscale will be 8 or 16 bit - depends on input's bitcount
  778. ConvFormat := IffFormat(Info.BytesPerPixel div Info.ChannelCount > 1,
  779. ifGray16, ifGray8)
  780. else if Info.BytesPerPixel > 4 then
  781. // Large bitcounts -> 16bit
  782. ConvFormat := ifGray16
  783. else
  784. // Rest of the formats -> 8bit
  785. ConvFormat := ifGray8;
  786. ConvertImage(Image, ConvFormat);
  787. end;
  788. { TPPMFileFormat }
  789. constructor TPPMFileFormat.Create;
  790. begin
  791. inherited Create;
  792. FName := SPPMFormatName;
  793. FSupportedFormats := PPMSupportedFormats;
  794. AddMasks(SPPMMasks);
  795. RegisterOption(ImagingPPMSaveBinary, @FSaveBinary);
  796. FIdNumbers := '36';
  797. end;
  798. function TPPMFileFormat.SaveData(Handle: TImagingHandle;
  799. const Images: TDynImageDataArray; Index: Integer): Boolean;
  800. var
  801. MapInfo: TPortableMapInfo;
  802. begin
  803. FillChar(MapInfo, SizeOf(MapInfo), 0);
  804. if FSaveBinary then
  805. MapInfo.FormatId := FIdNumbers[1]
  806. else
  807. MapInfo.FormatId := FIdNumbers[0];
  808. MapInfo.Binary := FSaveBinary;
  809. Result := SaveDataInternal(Handle, Images, Index, MapInfo);
  810. end;
  811. procedure TPPMFileFormat.ConvertToSupported(var Image: TImageData;
  812. const Info: TImageFormatInfo);
  813. var
  814. ConvFormat: TImageFormat;
  815. begin
  816. if Info.IsFloatingPoint then
  817. // All FP images go to 48bit RGB
  818. ConvFormat := ifR16G16B16
  819. else if Info.HasGrayChannel then
  820. // Grayscale will be 24 or 48 bit RGB - depends on input's bitcount
  821. ConvFormat := IffFormat(Info.BytesPerPixel div Info.ChannelCount > 1,
  822. ifR16G16B16, ifR8G8B8)
  823. else if Info.BytesPerPixel > 4 then
  824. // Large bitcounts -> 48bit RGB
  825. ConvFormat := ifR16G16B16
  826. else
  827. // Rest of the formats -> 24bit RGB
  828. ConvFormat := ifR8G8B8;
  829. ConvertImage(Image, ConvFormat);
  830. end;
  831. { TPAMFileFormat }
  832. constructor TPAMFileFormat.Create;
  833. begin
  834. inherited Create;
  835. FName := SPAMFormatName;
  836. FSupportedFormats := PAMSupportedFormats;
  837. AddMasks(SPAMMasks);
  838. FIdNumbers := '77';
  839. end;
  840. function TPAMFileFormat.SaveData(Handle: TImagingHandle;
  841. const Images: TDynImageDataArray; Index: Integer): Boolean;
  842. var
  843. MapInfo: TPortableMapInfo;
  844. begin
  845. FillChar(MapInfo, SizeOf(MapInfo), 0);
  846. MapInfo.FormatId := FIdNumbers[0];
  847. MapInfo.Binary := True;
  848. MapInfo.HasPAMHeader := True;
  849. Result := SaveDataInternal(Handle, Images, Index, MapInfo);
  850. end;
  851. procedure TPAMFileFormat.ConvertToSupported(var Image: TImageData;
  852. const Info: TImageFormatInfo);
  853. var
  854. ConvFormat: TImageFormat;
  855. begin
  856. if Info.IsFloatingPoint then
  857. ConvFormat := IffFormat(Info.HasAlphaChannel, ifA16R16G16B16, ifR16G16B16)
  858. else if Info.HasGrayChannel then
  859. ConvFormat := IffFormat(Info.HasAlphaChannel, ifA16Gray16, ifGray16)
  860. else
  861. begin
  862. if Info.BytesPerPixel <= 4 then
  863. ConvFormat := IffFormat(Info.HasAlphaChannel, ifA8R8G8B8, ifR8G8B8)
  864. else
  865. ConvFormat := IffFormat(Info.HasAlphaChannel, ifA16R16G16B16, ifR16G16B16);
  866. end;
  867. ConvertImage(Image, ConvFormat);
  868. end;
  869. { TPFMFileFormat }
  870. constructor TPFMFileFormat.Create;
  871. begin
  872. inherited Create;
  873. FName := SPFMFormatName;
  874. AddMasks(SPFMMasks);
  875. FIdNumbers := 'Ff';
  876. FSupportedFormats := PFMSupportedFormats;
  877. end;
  878. function TPFMFileFormat.SaveData(Handle: TImagingHandle;
  879. const Images: TDynImageDataArray; Index: Integer): Boolean;
  880. var
  881. Info: TImageFormatInfo;
  882. MapInfo: TPortableMapInfo;
  883. begin
  884. FillChar(MapInfo, SizeOf(MapInfo), 0);
  885. Info := GetFormatInfo(Images[Index].Format);
  886. if (Info.ChannelCount > 1) or Info.IsIndexed then
  887. MapInfo.TupleType := ttRGBFP
  888. else
  889. MapInfo.TupleType := ttGrayScaleFP;
  890. if MapInfo.TupleType = ttGrayScaleFP then
  891. MapInfo.FormatId := FIdNumbers[1]
  892. else
  893. MapInfo.FormatId := FIdNumbers[0];
  894. MapInfo.Binary := True;
  895. Result := SaveDataInternal(Handle, Images, Index, MapInfo);
  896. end;
  897. procedure TPFMFileFormat.ConvertToSupported(var Image: TImageData;
  898. const Info: TImageFormatInfo);
  899. begin
  900. if (Info.ChannelCount > 1) or Info.IsIndexed then
  901. ConvertImage(Image, ifA32B32G32R32F)
  902. else
  903. ConvertImage(Image, ifR32F);
  904. end;
  905. initialization
  906. RegisterImageFileFormat(TPBMFileFormat);
  907. RegisterImageFileFormat(TPGMFileFormat);
  908. RegisterImageFileFormat(TPPMFileFormat);
  909. RegisterImageFileFormat(TPAMFileFormat);
  910. RegisterImageFileFormat(TPFMFileFormat);
  911. {
  912. File Notes:
  913. -- TODOS ----------------------------------------------------
  914. - nothing now
  915. -- 0.24.3 Changes/Bug Fixes -----------------------------------
  916. - Improved compatibility of 16bit/component image loading.
  917. - Changes for better thread safety.
  918. -- 0.21 Changes/Bug Fixes -----------------------------------
  919. - Made modifications to ASCII PNM loading to be more "stream-safe".
  920. - Fixed bug: indexed images saved as grayscale in PFM.
  921. - Changed converting to supported formats little bit.
  922. - Added scaling of channel values (non-FP and non-mono images) according
  923. to MaxVal.
  924. - Added buffering to loading of PNM files. More than 10x faster now
  925. for text files.
  926. - Added saving support to PGM, PPM, PAM, and PFM format.
  927. - Added PFM file format.
  928. - Initial version created.
  929. }
  930. end.