ImagingRadiance.pas 13 KB

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