//
// This unit is part of the GLScene Engine, http://glscene.org
//

unit GLS.RGBE;

(* GLScene RGBE utils *)

interface

uses
  System.Classes,
  System.SysUtils,
  System.Math,

  GLVectorTypes,
  GLVectorGeometry;

procedure Float2rgbe(var RGBE: TVector4b; const Red, Green, Blue: Single);
procedure Rgbe2float(var Red, Green, Blue: Single; const RGBE: TVector4b);
procedure LoadRLEpixels(Stream: TStream; Dst: PSingle;
  Scanline_width, Num_scanlines: Integer);
procedure LoadRGBEpixels(Stream: TStream; Dst: PSingle; Numpixels: Integer);

//====================================================================
implementation
//====================================================================

type
  ERGBEexception = class(Exception);

// Extract exponent and mantissa from X
procedure Frexp(X: Extended; var Mantissa: Extended; var Exponent: Integer);
begin
  Exponent := 0;
  if (X <> 0) then
    if (Abs(X) < 0.5) then
      repeat
        X := X * 2;
        Dec(Exponent);
      until (Abs(X) >= 0.5)
    else
      while (Abs(X) >= 1) do
      begin
        X := X / 2;
        Inc(Exponent);
      end;
  Mantissa := X;
end;


function Ldexp(X: Extended; const P: Integer): Extended;
begin
  Ldexp := X * PowerSingle(2.0, P); // Result := X * (2^P)
end;

// standard conversion from float pixels to rgbe pixels
procedure Float2rgbe(var RGBE: TVector4b; const Red, Green, Blue: Single);
var
  V, M: Extended;
  E: Integer;
begin
  V := Red;
  if (Green > V) then
    V := Green;
  if (Blue > V) then
    V := Blue;
  if (V < 1E-32) then
  begin
    RGBE.X := 0;
    RGBE.Y := 0;
    RGBE.Z := 0;
    RGBE.W := 0;
  end
  else
  begin
    FrExp(V, M, E);
    M := M * 256 / V;
    RGBE.X := Floor(Red * V);
    RGBE.Y := Floor(Green * V);
    RGBE.Z := Floor(Blue * V);
    RGBE.W := Floor(E + 128);
  end;
end;

// standard conversion from rgbe to float pixels
// note: Ward uses ldexp(col+0.5,exp-(128+8)).  However we wanted pixels
// in the range [0,1] to map back into the range [0,1].
procedure Rgbe2float(var Red, Green, Blue: Single; const RGBE: TVector4b);
var
  F: Single;
begin
  if RGBE.W <> 0 then // nonzero pixel
  begin
    F := Ldexp(1.0, RGBE.W - (128 + 8));
    Red := RGBE.X * F;
    Green := RGBE.Y * F;
    Blue := RGBE.Z * F;
  end
  else
  begin
    Red := 0;
    Green := 0;
    Blue := 0;
  end;
end;

procedure LoadRLEpixels(Stream: TStream; Dst: PSingle;
  Scanline_width, Num_scanlines: Integer);
var
  RgbeTemp: TVector4b;
  Buf: TVector2b;
  Rf, Gf, Bf: Single;
  Scanline_buffer: PByteArray;
  Ptr, Ptr_end: PByte;
  I: Integer;
  Count: Cardinal;
begin
  if (Scanline_width < 8) or (Scanline_width > $7FFF) then
  begin
    // run length encoding is not allowed so read flat
    LoadRGBEPixels(Stream, Dst, Scanline_width * Num_scanlines);
    Exit;
  end;

  Scanline_buffer := nil;
  while Num_scanlines > 0 do
  begin
    Stream.Read(RgbeTemp, SizeOf(TVector4b));
    if (RgbeTemp.X <> 2) or (RgbeTemp.Y <> 2) or
      (RgbeTemp.Z and $80 <> 0) then
    begin
      // this file is not run length encoded
      Rgbe2float(Rf, Gf, Bf, RgbeTemp);
      Dst^ := Rf;
      Inc(Dst);
      Dst^ := Gf;
      Inc(Dst);
      Dst^ := Bf;
      Inc(Dst);
      if Assigned(Scanline_buffer) then
        FreeMem(Scanline_buffer);
      LoadRGBEpixels(Stream, Dst, Scanline_width * Num_scanlines - 1);
      Exit;
    end;
    if ((Integer(RgbeTemp.Z) shl 8) or RgbeTemp.W) <> Scanline_width
    then
    begin
      if Assigned(Scanline_buffer) then
        FreeMem(Scanline_buffer);
      raise ERGBEexception.Create('Wrong scanline width.');
    end;

    if not Assigned(Scanline_buffer) then
      ReallocMem(Scanline_buffer, 4 * Scanline_width);

    Ptr := PByte(Scanline_buffer);
    // read each of the four channels for the scanline into the buffer
    for I := 0 to 3 do
    begin
      Ptr_end := @Scanline_buffer[(I + 1) * Scanline_width];
      while Cardinal(Ptr) < Cardinal(Ptr_end) do
      begin
        Stream.Read(Buf, SizeOf(TVector2b));
        if Buf.X > 128 then
        begin // a run of the same value
          Count := Buf.X - 128;
          if (Count = 0) or (Count > Cardinal(Ptr_end) - Cardinal(Ptr)) then
          begin
            FreeMem(Scanline_buffer);
            raise ERGBEexception.Create('Bad HDR scanline data.');
          end;
          while Count > 0 do
          begin
            Ptr^ := Buf.Y;
            Dec(Count);
            Inc(Ptr);
          end;
        end
        else
        begin // a non-run
          Count := Buf.X;
          if (Count = 0) or (Count > Cardinal(Ptr_end) - Cardinal(Ptr)) then
          begin
            FreeMem(Scanline_buffer);
            raise ERGBEexception.Create('Bad HDR scanline data.');
          end;
          Ptr^ := Buf.Y;
          Dec(Count);
          Inc(Ptr);
          if Count > 0 then
            Stream.Read(Ptr^, Count);
          Inc(Ptr, Count);
        end;
      end;
    end;

    // now convert data from buffer into floats
    for I := 0 to Scanline_width - 1 do
    begin
      RgbeTemp.X := Scanline_buffer[I];
      RgbeTemp.Y := Scanline_buffer[I + Scanline_width];
      RgbeTemp.Z := Scanline_buffer[I + 2 * Scanline_width];
      RgbeTemp.W := Scanline_buffer[I + 3 * Scanline_width];
      Rgbe2float(Rf, Gf, Bf, RgbeTemp);
      Dst^ := Rf;
      Inc(Dst);
      Dst^ := Gf;
      Inc(Dst);
      Dst^ := Bf;
      Inc(Dst);
    end;
    Dec(Num_scanlines);
  end;
  if Assigned(Scanline_buffer) then
    FreeMem(Scanline_buffer);
end;

procedure LoadRGBEpixels(Stream: TStream; Dst: PSingle; Numpixels: Integer);
var
  RgbeTemp: TVector4b;
  Rf, Gf, Bf: Single;
begin
  while Numpixels > 0 do
  begin
    Stream.Read(RgbeTemp, SizeOf(TVector4b));
    Rgbe2float(Rf, Gf, Bf, RgbeTemp);
    Dst^ := Rf;
    Inc(Dst);
    Dst^ := Gf;
    Inc(Dst);
    Dst^ := Bf;
    Inc(Dst);
    Dec(Numpixels);
  end;
end;

end.
