Repo for the search and displace ingest module that takes odf, docx and pdf and transforms it into .md to be used with search and displace operations
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

977 lines
31 KiB

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