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.

495 lines
14 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 image format loader/saver for Radiance HDR/RGBE images.}
  24. unit ImagingRadiance;
  25. {$I ImagingOptions.inc}
  26. interface
  27. uses
  28. SysUtils, Classes, Imaging, ImagingTypes, ImagingUtility;
  29. type
  30. { Radiance is a suite of tools for performing lighting simulation. It's
  31. development started in 1985 and it pioneered the concept of
  32. high dynamic range imaging. Radiance defined an image format for storing
  33. HDR images, now described as RGBE image format. Since it was the first
  34. HDR image format, this format is supported by many other software packages.
  35. Radiance image file consists of three sections: a header, resolution string,
  36. followed by the pixel data. Each pixel is stored as 4 bytes, one byte
  37. mantissa for each r, g, b and a shared one byte exponent.
  38. The pixel data may be stored uncompressed or using run length encoding.
  39. Imaging translates RGBE pixels to original float values and stores them
  40. in ifR32G32B32F data format. It can read both compressed and uncompressed
  41. files, and saves files as compressed.}
  42. THdrFileFormat = class(TImageFileFormat)
  43. protected
  44. procedure Define; override;
  45. function LoadData(Handle: TImagingHandle; var Images: TDynImageDataArray;
  46. OnlyFirstLevel: Boolean): Boolean; override;
  47. function SaveData(Handle: TImagingHandle; const Images: TDynImageDataArray;
  48. Index: LongInt): Boolean; override;
  49. procedure ConvertToSupported(var Image: TImageData;
  50. const Info: TImageFormatInfo); override;
  51. public
  52. function TestFormat(Handle: TImagingHandle): Boolean; override;
  53. end;
  54. implementation
  55. uses
  56. Math, ImagingIO;
  57. const
  58. SHdrFormatName = 'Radiance HDR/RGBE';
  59. SHdrMasks = '*.hdr';
  60. HdrSupportedFormats: TImageFormats = [ifR32G32B32F];
  61. type
  62. TSignature = array[0..9] of AnsiChar;
  63. THdrFormat = (hfRgb, hfXyz);
  64. THdrHeader = record
  65. Format: THdrFormat;
  66. Width: Integer;
  67. Height: Integer;
  68. end;
  69. TRgbe = packed record
  70. R, G, B, E: Byte;
  71. end;
  72. TDynRgbeArray = array of TRgbe;
  73. const
  74. RadianceSignature: TSignature = '#?RADIANCE';
  75. RgbeSignature: TSignature = '#?RGBE';
  76. SFmtRgbeRle = '32-bit_rle_rgbe';
  77. SFmtXyzeRle = '32-bit_rle_xyze';
  78. resourcestring
  79. SErrorBadHeader = 'Bad HDR/RGBE header format.';
  80. SWrongScanLineWidth = 'Wrong scanline width.';
  81. SXyzNotSupported = 'XYZ color space not supported.';
  82. { THdrFileFormat }
  83. procedure THdrFileFormat.Define;
  84. begin
  85. inherited;
  86. FName := SHdrFormatName;
  87. FFeatures := [ffLoad, ffSave];
  88. FSupportedFormats := HdrSupportedFormats;
  89. AddMasks(SHdrMasks);
  90. end;
  91. function THdrFileFormat.LoadData(Handle: TImagingHandle;
  92. var Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
  93. var
  94. Header: THdrHeader;
  95. IO: TIOFunctions;
  96. function ReadHeader: Boolean;
  97. const
  98. CommentIds: TAnsiCharSet = ['#', '!'];
  99. var
  100. Line: AnsiString;
  101. HasResolution: Boolean;
  102. Count, Idx: Integer;
  103. ValStr, NativeLine: string;
  104. ValFloat: Double;
  105. begin
  106. Result := False;
  107. HasResolution := False;
  108. Count := 0;
  109. repeat
  110. if not ReadLine(IO, Handle, Line) then
  111. Exit;
  112. Inc(Count);
  113. if Count > 16 then // Too long header for HDR
  114. Exit;
  115. if Length(Line) = 0 then
  116. Continue;
  117. if Line[1] in CommentIds then
  118. Continue;
  119. NativeLine := string(Line);
  120. if StrMaskMatch(NativeLine, 'Format=*') then
  121. begin
  122. // Data format parsing
  123. ValStr := Copy(NativeLine, 8, MaxInt);
  124. if ValStr = SFmtRgbeRle then
  125. Header.Format := hfRgb
  126. else if ValStr = SFmtXyzeRle then
  127. Header.Format := hfXyz
  128. else
  129. Exit;
  130. end;
  131. if StrMaskMatch(NativeLine, 'Gamma=*') then
  132. begin
  133. ValStr := Copy(NativeLine, 7, MaxInt);
  134. if TryStrToFloat(ValStr, ValFloat, GetFormatSettingsForFloats) then
  135. FMetadata.SetMetaItem(SMetaGamma, ValFloat);
  136. end;
  137. if StrMaskMatch(NativeLine, 'Exposure=*') then
  138. begin
  139. ValStr := Copy(NativeLine, 10, MaxInt);
  140. if TryStrToFloat(ValStr, ValFloat, GetFormatSettingsForFloats) then
  141. FMetadata.SetMetaItem(SMetaExposure, ValFloat);
  142. end;
  143. if StrMaskMatch(NativeLine, '?Y * ?X *') then
  144. begin
  145. Idx := Pos('X', NativeLine);
  146. ValStr := SubString(NativeLine, 4, Idx - 2);
  147. if not TryStrToInt(ValStr, Header.Height) then
  148. Exit;
  149. ValStr := Copy(NativeLine, Idx + 2, MaxInt);
  150. if not TryStrToInt(ValStr, Header.Width) then
  151. Exit;
  152. if (NativeLine[1] = '-') then
  153. Header.Height := -Header.Height;
  154. if (NativeLine[Idx - 1] = '-') then
  155. Header.Width := -Header.Width;
  156. HasResolution := True;
  157. end;
  158. until HasResolution;
  159. Result := True;
  160. end;
  161. procedure DecodeRgbe(const Src: TRgbe; Dest: PColor96FPRec); {$IFDEF USE_INLINE}inline;{$ENDIF}
  162. var
  163. Mult: Single;
  164. begin
  165. if Src.E > 0 then
  166. begin
  167. Mult := Math.Ldexp(1, Src.E - 128);
  168. Dest.R := Src.R / 255 * Mult;
  169. Dest.G := Src.G / 255 * Mult;
  170. Dest.B := Src.B / 255 * Mult;
  171. end
  172. else
  173. begin
  174. Dest.R := 0;
  175. Dest.G := 0;
  176. Dest.B := 0;
  177. end;
  178. end;
  179. procedure ReadCompressedLine(Width, Y: Integer; var DestBuffer: TDynRgbeArray);
  180. var
  181. Pos: Integer;
  182. I, X, Count: Integer;
  183. Code, Value: Byte;
  184. LineBuff: TDynByteArray;
  185. Rgbe: TRgbe;
  186. Ptr: PByte;
  187. begin
  188. SetLength(LineBuff, Width);
  189. IO.Read(Handle, @Rgbe, SizeOf(Rgbe));
  190. if ((Rgbe.B shl 8) or Rgbe.E) <> Width then
  191. RaiseImaging(SWrongScanLineWidth);
  192. for I := 0 to 3 do
  193. begin
  194. Pos := 0;
  195. while Pos < Width do
  196. begin
  197. IO.Read(Handle, @Code, SizeOf(Byte));
  198. if Code > 128 then
  199. begin
  200. Count := Code - 128;
  201. IO.Read(Handle, @Value, SizeOf(Byte));
  202. FillMemoryByte(@LineBuff[Pos], Count, Value);
  203. end
  204. else
  205. begin
  206. Count := Code;
  207. IO.Read(Handle, @LineBuff[Pos], Count * SizeOf(Byte));
  208. end;
  209. Inc(Pos, Count);
  210. end;
  211. Ptr := @PByteArray(@DestBuffer[0])[I];
  212. for X := 0 to Width - 1 do
  213. begin
  214. Ptr^ := LineBuff[X];
  215. Inc(Ptr, 4);
  216. end;
  217. end;
  218. end;
  219. procedure ReadPixels(var Image: TImageData);
  220. var
  221. Y, X, SrcLineLen: Integer;
  222. Dest: PColor96FPRec;
  223. Compressed: Boolean;
  224. Rgbe: TRgbe;
  225. Buffer: TDynRgbeArray;
  226. begin
  227. Dest := Image.Bits;
  228. Compressed := not ((Image.Width < 8) or (Image.Width > $7FFFF));
  229. SrcLineLen := Image.Width * SizeOf(TRgbe);
  230. IO.Read(Handle, @Rgbe, SizeOf(Rgbe));
  231. IO.Seek(Handle, -SizeOf(Rgbe), smFromCurrent);
  232. if (Rgbe.R <> 2) or (Rgbe.G <> 2) or ((Rgbe.B and 128) > 0) then
  233. Compressed := False;
  234. SetLength(Buffer, Image.Width);
  235. for Y := 0 to Image.Height - 1 do
  236. begin
  237. if Compressed then
  238. ReadCompressedLine(Image.Width, Y, Buffer)
  239. else
  240. IO.Read(Handle, @Buffer[0], SrcLineLen);
  241. for X := 0 to Image.Width - 1 do
  242. begin
  243. DecodeRgbe(Buffer[X], Dest);
  244. Inc(Dest);
  245. end;
  246. end;
  247. end;
  248. begin
  249. IO := GetIO;
  250. SetLength(Images, 1);
  251. // Read header, allocate new image and, then read and convert the pixels
  252. if not ReadHeader then
  253. RaiseImaging(SErrorBadHeader);
  254. if (Header.Format = hfXyz) then
  255. RaiseImaging(SXyzNotSupported);
  256. NewImage(Abs(Header.Width), Abs(Header.Height), ifR32G32B32F, Images[0]);
  257. ReadPixels(Images[0]);
  258. // Flip/mirror the image as needed (height < 0 is default top-down)
  259. if Header.Width < 0 then
  260. MirrorImage(Images[0]);
  261. if Header.Height > 0 then
  262. FlipImage(Images[0]);
  263. Result := True;
  264. end;
  265. function THdrFileFormat.SaveData(Handle: TImagingHandle;
  266. const Images: TDynImageDataArray; Index: Integer): Boolean;
  267. const
  268. LineEnd = #$0A;
  269. SPrgComment = '#Made with Vampyre Imaging Library';
  270. SSizeFmt = '-Y %d +X %d';
  271. var
  272. ImageToSave: TImageData;
  273. MustBeFreed: Boolean;
  274. IO: TIOFunctions;
  275. procedure SaveHeader;
  276. begin
  277. WriteLine(IO, Handle, RadianceSignature, LineEnd);
  278. WriteLine(IO, Handle, SPrgComment, LineEnd);
  279. WriteLine(IO, Handle, 'FORMAT=' + SFmtRgbeRle, LineEnd + LineEnd);
  280. WriteLine(IO, Handle, AnsiString(Format(SSizeFmt, [ImageToSave.Height, ImageToSave.Width])), LineEnd);
  281. end;
  282. procedure EncodeRgbe(const Src: TColor96FPRec; var DestR, DestG, DestB, DestE: Byte); {$IFDEF USE_INLINE}inline;{$ENDIF}
  283. var
  284. V, M: {$IFDEF FPC}Float{$ELSE}Extended{$ENDIF};
  285. E: Integer;
  286. begin
  287. V := Src.R;
  288. if (Src.G > V) then
  289. V := Src.G;
  290. if (Src.B > V) then
  291. V := Src.B;
  292. if V < 1e-32 then
  293. begin
  294. DestR := 0;
  295. DestG := 0;
  296. DestB := 0;
  297. DestE := 0;
  298. end
  299. else
  300. begin
  301. Frexp(V, M, E);
  302. V := M * 256.0 / V;
  303. DestR := ClampToByte(Round(Src.R * V));
  304. DestG := ClampToByte(Round(Src.G * V));
  305. DestB := ClampToByte(Round(Src.B * V));
  306. DestE := ClampToByte(E + 128);
  307. end;
  308. end;
  309. procedure WriteRleLine(const Line: array of Byte; Width: Integer);
  310. const
  311. MinRunLength = 4;
  312. var
  313. Cur, BeginRun, RunCount, OldRunCount, NonRunCount: Integer;
  314. Buf: array[0..1] of Byte;
  315. begin
  316. Cur := 0;
  317. while Cur < Width do
  318. begin
  319. BeginRun := Cur;
  320. RunCount := 0;
  321. OldRunCount := 0;
  322. while (RunCount < MinRunLength) and (BeginRun < Width) do
  323. begin
  324. Inc(BeginRun, RunCount);
  325. OldRunCount := RunCount;
  326. RunCount := 1;
  327. while (BeginRun + RunCount < Width) and (RunCount < 127) and (Line[BeginRun] = Line[BeginRun + RunCount]) do
  328. Inc(RunCount);
  329. end;
  330. if (OldRunCount > 1) and (OldRunCount = BeginRun - Cur) then
  331. begin
  332. Buf[0] := 128 + OldRunCount;
  333. Buf[1] := Line[Cur];
  334. IO.Write(Handle, @Buf, 2);
  335. Cur := BeginRun;
  336. end;
  337. while Cur < BeginRun do
  338. begin
  339. NonRunCount := Min(128, BeginRun - Cur);
  340. Buf[0] := NonRunCount;
  341. IO.Write(Handle, @Buf, 1);
  342. IO.Write(Handle, @Line[Cur], NonRunCount);
  343. Inc(Cur, NonRunCount);
  344. end;
  345. if RunCount >= MinRunLength then
  346. begin
  347. Buf[0] := 128 + RunCount;
  348. Buf[1] := Line[BeginRun];
  349. IO.Write(Handle, @Buf, 2);
  350. Inc(Cur, RunCount);
  351. end;
  352. end;
  353. end;
  354. procedure SavePixels;
  355. var
  356. Y, X, I, Width: Integer;
  357. SrcPtr: PColor96FPRecArray;
  358. Components: array of array of Byte;
  359. StartLine: array[0..3] of Byte;
  360. begin
  361. Width := ImageToSave.Width;
  362. // Save using RLE, each component is compressed separately
  363. SetLength(Components, 4, Width);
  364. for Y := 0 to ImageToSave.Height - 1 do
  365. begin
  366. SrcPtr := @PColor96FPRecArray(ImageToSave.Bits)[ImageToSave.Width * Y];
  367. // Identify line as using "new" RLE scheme (separate components)
  368. StartLine[0] := 2;
  369. StartLine[1] := 2;
  370. StartLine[2] := Width shr 8;
  371. StartLine[3] := Width and $FF;
  372. IO.Write(Handle, @StartLine, SizeOf(StartLine));
  373. for X := 0 to Width - 1 do
  374. begin
  375. EncodeRgbe(SrcPtr[X], Components[0, X], Components[1, X],
  376. Components[2, X], Components[3, X]);
  377. end;
  378. for I := 0 to 3 do
  379. WriteRleLine(Components[I], Width);
  380. end;
  381. end;
  382. begin
  383. Result := False;
  384. IO := GetIO;
  385. // Makes image to save compatible with Jpeg saving capabilities
  386. if MakeCompatible(Images[Index], ImageToSave, MustBeFreed) then
  387. with ImageToSave do
  388. try
  389. // Save header
  390. SaveHeader;
  391. // Save uncompressed pixels
  392. SavePixels;
  393. finally
  394. if MustBeFreed then
  395. FreeImage(ImageToSave);
  396. end;
  397. end;
  398. procedure THdrFileFormat.ConvertToSupported(var Image: TImageData;
  399. const Info: TImageFormatInfo);
  400. begin
  401. ConvertImage(Image, ifR32G32B32F);
  402. end;
  403. function THdrFileFormat.TestFormat(Handle: TImagingHandle): Boolean;
  404. var
  405. FileSig: TSignature;
  406. ReadCount: Integer;
  407. begin
  408. Result := False;
  409. if Handle <> nil then
  410. begin
  411. ReadCount := GetIO.Read(Handle, @FileSig, SizeOf(FileSig));
  412. GetIO.Seek(Handle, -ReadCount, smFromCurrent);
  413. Result := (ReadCount = SizeOf(FileSig)) and
  414. ((FileSig = RadianceSignature) or CompareMem(@FileSig, @RgbeSignature, 6));
  415. end;
  416. end;
  417. initialization
  418. RegisterImageFileFormat(THdrFileFormat);
  419. {
  420. File Notes:
  421. -- 0.77.1 ---------------------------------------------------
  422. - Added RLE compression to saving.
  423. - Added image saving.
  424. - Unit created with initial stuff (loading only).
  425. }
  426. end.