| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480 |
- {
- Vampyre Imaging Library
- by Marek Mauder
- https://github.com/galfar/imaginglib
- https://imaginglib.sourceforge.io
- - - - - -
- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at https://mozilla.org/MPL/2.0.
- }
- { This unit contains image format loader/saver for Radiance HDR/RGBE images.}
- unit ImagingRadiance;
- {$I ImagingOptions.inc}
- interface
- uses
- SysUtils, Classes, Imaging, ImagingTypes, ImagingUtility;
- type
- { Radiance is a suite of tools for performing lighting simulation. It's
- development started in 1985 and it pioneered the concept of
- high dynamic range imaging. Radiance defined an image format for storing
- HDR images, now described as RGBE image format. Since it was the first
- HDR image format, this format is supported by many other software packages.
- Radiance image file consists of three sections: a header, resolution string,
- followed by the pixel data. Each pixel is stored as 4 bytes, one byte
- mantissa for each r, g, b and a shared one byte exponent.
- The pixel data may be stored uncompressed or using run length encoding.
- Imaging translates RGBE pixels to original float values and stores them
- in ifR32G32B32F data format. It can read both compressed and uncompressed
- files, and saves files as compressed.}
- THdrFileFormat = class(TImageFileFormat)
- protected
- procedure Define; override;
- function LoadData(Handle: TImagingHandle; var Images: TDynImageDataArray;
- OnlyFirstLevel: Boolean): Boolean; override;
- function SaveData(Handle: TImagingHandle; const Images: TDynImageDataArray;
- Index: LongInt): Boolean; override;
- procedure ConvertToSupported(var Image: TImageData;
- const Info: TImageFormatInfo); override;
- public
- function TestFormat(Handle: TImagingHandle): Boolean; override;
- end;
- implementation
- uses
- Math, ImagingIO;
- const
- SHdrFormatName = 'Radiance HDR/RGBE';
- SHdrMasks = '*.hdr';
- HdrSupportedFormats: TImageFormats = [ifR32G32B32F];
- type
- TSignature = array[0..9] of AnsiChar;
- THdrFormat = (hfRgb, hfXyz);
- THdrHeader = record
- Format: THdrFormat;
- Width: Integer;
- Height: Integer;
- end;
- TRgbe = packed record
- R, G, B, E: Byte;
- end;
- TDynRgbeArray = array of TRgbe;
- const
- RadianceSignature: TSignature = '#?RADIANCE';
- RgbeSignature: TSignature = '#?RGBE';
- SFmtRgbeRle = '32-bit_rle_rgbe';
- SFmtXyzeRle = '32-bit_rle_xyze';
- resourcestring
- SErrorBadHeader = 'Bad HDR/RGBE header format.';
- SWrongScanLineWidth = 'Wrong scanline width.';
- SXyzNotSupported = 'XYZ color space not supported.';
- { THdrFileFormat }
- procedure THdrFileFormat.Define;
- begin
- inherited;
- FName := SHdrFormatName;
- FFeatures := [ffLoad, ffSave];
- FSupportedFormats := HdrSupportedFormats;
- AddMasks(SHdrMasks);
- end;
- function THdrFileFormat.LoadData(Handle: TImagingHandle;
- var Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
- var
- Header: THdrHeader;
- IO: TIOFunctions;
- function ReadHeader: Boolean;
- const
- CommentIds: TAnsiCharSet = ['#', '!'];
- var
- Line: AnsiString;
- HasResolution: Boolean;
- Count, Idx: Integer;
- ValStr, NativeLine: string;
- ValFloat: Double;
- begin
- Result := False;
- HasResolution := False;
- Count := 0;
- repeat
- if not ReadLine(IO, Handle, Line) then
- Exit;
- Inc(Count);
- if Count > 16 then // Too long header for HDR
- Exit;
- if Length(Line) = 0 then
- Continue;
- if Line[1] in CommentIds then
- Continue;
- NativeLine := string(Line);
- if StrMaskMatch(NativeLine, 'Format=*') then
- begin
- // Data format parsing
- ValStr := Copy(NativeLine, 8, MaxInt);
- if ValStr = SFmtRgbeRle then
- Header.Format := hfRgb
- else if ValStr = SFmtXyzeRle then
- Header.Format := hfXyz
- else
- Exit;
- end;
- if StrMaskMatch(NativeLine, 'Gamma=*') then
- begin
- ValStr := Copy(NativeLine, 7, MaxInt);
- if TryStrToFloat(ValStr, ValFloat, GetFormatSettingsForFloats) then
- FMetadata.SetMetaItem(SMetaGamma, ValFloat);
- end;
- if StrMaskMatch(NativeLine, 'Exposure=*') then
- begin
- ValStr := Copy(NativeLine, 10, MaxInt);
- if TryStrToFloat(ValStr, ValFloat, GetFormatSettingsForFloats) then
- FMetadata.SetMetaItem(SMetaExposure, ValFloat);
- end;
- if StrMaskMatch(NativeLine, '?Y * ?X *') then
- begin
- Idx := Pos('X', NativeLine);
- ValStr := SubString(NativeLine, 4, Idx - 2);
- if not TryStrToInt(ValStr, Header.Height) then
- Exit;
- ValStr := Copy(NativeLine, Idx + 2, MaxInt);
- if not TryStrToInt(ValStr, Header.Width) then
- Exit;
- if (NativeLine[1] = '-') then
- Header.Height := -Header.Height;
- if (NativeLine[Idx - 1] = '-') then
- Header.Width := -Header.Width;
- HasResolution := True;
- end;
- until HasResolution;
- Result := True;
- end;
- procedure DecodeRgbe(const Src: TRgbe; Dest: PColor96FPRec); {$IFDEF USE_INLINE}inline;{$ENDIF}
- var
- Mult: Single;
- begin
- if Src.E > 0 then
- begin
- Mult := Math.Ldexp(1, Src.E - 128);
- Dest.R := Src.R / 255 * Mult;
- Dest.G := Src.G / 255 * Mult;
- Dest.B := Src.B / 255 * Mult;
- end
- else
- begin
- Dest.R := 0;
- Dest.G := 0;
- Dest.B := 0;
- end;
- end;
- procedure ReadCompressedLine(Width, Y: Integer; var DestBuffer: TDynRgbeArray);
- var
- Pos: Integer;
- I, X, Count: Integer;
- Code, Value: Byte;
- LineBuff: TDynByteArray;
- Rgbe: TRgbe;
- Ptr: PByte;
- begin
- SetLength(LineBuff, Width);
- IO.Read(Handle, @Rgbe, SizeOf(Rgbe));
- if ((Rgbe.B shl 8) or Rgbe.E) <> Width then
- RaiseImaging(SWrongScanLineWidth);
- for I := 0 to 3 do
- begin
- Pos := 0;
- while Pos < Width do
- begin
- IO.Read(Handle, @Code, SizeOf(Byte));
- if Code > 128 then
- begin
- Count := Code - 128;
- IO.Read(Handle, @Value, SizeOf(Byte));
- FillMemoryByte(@LineBuff[Pos], Count, Value);
- end
- else
- begin
- Count := Code;
- IO.Read(Handle, @LineBuff[Pos], Count * SizeOf(Byte));
- end;
- Inc(Pos, Count);
- end;
- Ptr := @PByteArray(@DestBuffer[0])[I];
- for X := 0 to Width - 1 do
- begin
- Ptr^ := LineBuff[X];
- Inc(Ptr, 4);
- end;
- end;
- end;
- procedure ReadPixels(var Image: TImageData);
- var
- Y, X, SrcLineLen: Integer;
- Dest: PColor96FPRec;
- Compressed: Boolean;
- Rgbe: TRgbe;
- Buffer: TDynRgbeArray;
- begin
- Dest := Image.Bits;
- Compressed := not ((Image.Width < 8) or (Image.Width > $7FFFF));
- SrcLineLen := Image.Width * SizeOf(TRgbe);
- IO.Read(Handle, @Rgbe, SizeOf(Rgbe));
- IO.Seek(Handle, -SizeOf(Rgbe), smFromCurrent);
- if (Rgbe.R <> 2) or (Rgbe.G <> 2) or ((Rgbe.B and 128) > 0) then
- Compressed := False;
- SetLength(Buffer, Image.Width);
- for Y := 0 to Image.Height - 1 do
- begin
- if Compressed then
- ReadCompressedLine(Image.Width, Y, Buffer)
- else
- IO.Read(Handle, @Buffer[0], SrcLineLen);
- for X := 0 to Image.Width - 1 do
- begin
- DecodeRgbe(Buffer[X], Dest);
- Inc(Dest);
- end;
- end;
- end;
- begin
- IO := GetIO;
- SetLength(Images, 1);
- // Read header, allocate new image and, then read and convert the pixels
- if not ReadHeader then
- RaiseImaging(SErrorBadHeader);
- if (Header.Format = hfXyz) then
- RaiseImaging(SXyzNotSupported);
- NewImage(Abs(Header.Width), Abs(Header.Height), ifR32G32B32F, Images[0]);
- ReadPixels(Images[0]);
- // Flip/mirror the image as needed (height < 0 is default top-down)
- if Header.Width < 0 then
- MirrorImage(Images[0]);
- if Header.Height > 0 then
- FlipImage(Images[0]);
- Result := True;
- end;
- function THdrFileFormat.SaveData(Handle: TImagingHandle;
- const Images: TDynImageDataArray; Index: LongInt): Boolean;
- const
- LineEnd = #$0A;
- SPrgComment = '#Made with Vampyre Imaging Library';
- SSizeFmt = '-Y %d +X %d';
- var
- ImageToSave: TImageData;
- MustBeFreed: Boolean;
- IO: TIOFunctions;
- procedure SaveHeader;
- begin
- WriteLine(IO, Handle, RadianceSignature, LineEnd);
- WriteLine(IO, Handle, SPrgComment, LineEnd);
- WriteLine(IO, Handle, 'FORMAT=' + SFmtRgbeRle, LineEnd + LineEnd);
- WriteLine(IO, Handle, AnsiString(Format(SSizeFmt, [ImageToSave.Height, ImageToSave.Width])), LineEnd);
- end;
- procedure EncodeRgbe(const Src: TColor96FPRec; var DestR, DestG, DestB, DestE: Byte); {$IFDEF USE_INLINE}inline;{$ENDIF}
- var
- V, M: {$IFDEF FPC}Float{$ELSE}Extended{$ENDIF};
- E: Integer;
- begin
- V := Src.R;
- if (Src.G > V) then
- V := Src.G;
- if (Src.B > V) then
- V := Src.B;
- if V < 1e-32 then
- begin
- DestR := 0;
- DestG := 0;
- DestB := 0;
- DestE := 0;
- end
- else
- begin
- Frexp(V, M, E);
- V := M * 256.0 / V;
- DestR := ClampToByte(Round(Src.R * V));
- DestG := ClampToByte(Round(Src.G * V));
- DestB := ClampToByte(Round(Src.B * V));
- DestE := ClampToByte(E + 128);
- end;
- end;
- procedure WriteRleLine(const Line: array of Byte; Width: Integer);
- const
- MinRunLength = 4;
- var
- Cur, BeginRun, RunCount, OldRunCount, NonRunCount: Integer;
- Buf: array[0..1] of Byte;
- begin
- Cur := 0;
- while Cur < Width do
- begin
- BeginRun := Cur;
- RunCount := 0;
- OldRunCount := 0;
- while (RunCount < MinRunLength) and (BeginRun < Width) do
- begin
- Inc(BeginRun, RunCount);
- OldRunCount := RunCount;
- RunCount := 1;
- while (BeginRun + RunCount < Width) and (RunCount < 127) and (Line[BeginRun] = Line[BeginRun + RunCount]) do
- Inc(RunCount);
- end;
- if (OldRunCount > 1) and (OldRunCount = BeginRun - Cur) then
- begin
- Buf[0] := 128 + OldRunCount;
- Buf[1] := Line[Cur];
- IO.Write(Handle, @Buf, 2);
- Cur := BeginRun;
- end;
- while Cur < BeginRun do
- begin
- NonRunCount := Min(128, BeginRun - Cur);
- Buf[0] := NonRunCount;
- IO.Write(Handle, @Buf, 1);
- IO.Write(Handle, @Line[Cur], NonRunCount);
- Inc(Cur, NonRunCount);
- end;
- if RunCount >= MinRunLength then
- begin
- Buf[0] := 128 + RunCount;
- Buf[1] := Line[BeginRun];
- IO.Write(Handle, @Buf, 2);
- Inc(Cur, RunCount);
- end;
- end;
- end;
- procedure SavePixels;
- var
- Y, X, I, Width: Integer;
- SrcPtr: PColor96FPRecArray;
- Components: array of array of Byte;
- StartLine: array[0..3] of Byte;
- begin
- Width := ImageToSave.Width;
- // Save using RLE, each component is compressed separately
- SetLength(Components, 4, Width);
- for Y := 0 to ImageToSave.Height - 1 do
- begin
- SrcPtr := @PColor96FPRecArray(ImageToSave.Bits)[ImageToSave.Width * Y];
- // Identify line as using "new" RLE scheme (separate components)
- StartLine[0] := 2;
- StartLine[1] := 2;
- StartLine[2] := Width shr 8;
- StartLine[3] := Width and $FF;
- IO.Write(Handle, @StartLine, SizeOf(StartLine));
- for X := 0 to Width - 1 do
- begin
- EncodeRgbe(SrcPtr[X], Components[0, X], Components[1, X],
- Components[2, X], Components[3, X]);
- end;
- for I := 0 to 3 do
- WriteRleLine(Components[I], Width);
- end;
- end;
- begin
- Result := False;
- IO := GetIO;
- // Makes image to save compatible with Jpeg saving capabilities
- if MakeCompatible(Images[Index], ImageToSave, MustBeFreed) then
- with ImageToSave do
- try
- // Save header
- SaveHeader;
- // Save uncompressed pixels
- SavePixels;
- Result := True;
- finally
- if MustBeFreed then
- FreeImage(ImageToSave);
- end;
- end;
- procedure THdrFileFormat.ConvertToSupported(var Image: TImageData;
- const Info: TImageFormatInfo);
- begin
- ConvertImage(Image, ifR32G32B32F);
- end;
- function THdrFileFormat.TestFormat(Handle: TImagingHandle): Boolean;
- var
- FileSig: TSignature;
- ReadCount: Integer;
- begin
- Result := False;
- if Handle <> nil then
- begin
- ReadCount := GetIO.Read(Handle, @FileSig, SizeOf(FileSig));
- GetIO.Seek(Handle, -ReadCount, smFromCurrent);
- Result := (ReadCount = SizeOf(FileSig)) and
- ((FileSig = RadianceSignature) or CompareMem(@FileSig, @RgbeSignature, 6));
- end;
- end;
- initialization
- RegisterImageFileFormat(THdrFileFormat);
- {
- File Notes:
- -- 0.77.1 ---------------------------------------------------
- - Added RLE compression to saving.
- - Added image saving.
- - Unit created with initial stuff (loading only).
- }
- end.
|