/// <summary> /// Decodes a RGBE formatted image into a plain floating-point image /// </summary> /// <param name="_Source">The source RGBE formatted image</param> /// <param name="_bSourceIsXYZ">Tells if the source image is encoded as XYZE rather than RGBE</param> /// <param name="_bTargetNeedsXYZ">Tells if the target needs to be in CIE XYZ space (true) or RGB (false)</param> /// <param name="_ColorProfile">The color profile for the image</param> /// <returns>A HDR image as floats</returns> public static float4[,] DecodeRGBEImage( PF_RGBE[,] _Source, bool _bSourceIsXYZ, bool _bTargetNeedsXYZ, ColorProfile _ColorProfile ) { if ( _Source == null ) return null; float4[,] Result = new float4[_Source.GetLength( 0 ), _Source.GetLength( 1 )]; DecodeRGBEImage( _Source, _bSourceIsXYZ, Result, _bTargetNeedsXYZ, _ColorProfile ); return Result; }
/// <summary> /// Decodes a RGBE formatted image into a plain floating-point image /// </summary> /// <param name="_Source">The source RGBE formatted image</param> /// <param name="_bSourceIsXYZ">Tells if the source image is encoded as XYZE rather than RGBE</param> /// <param name="_Target">The target float4 image</param> /// <param name="_bTargetNeedsXYZ">Tells if the target needs to be in CIE XYZ space (true) or RGB (false)</param> /// <param name="_ColorProfile">The color profile for the image</param> public static void DecodeRGBEImage( PF_RGBE[,] _Source, bool _bSourceIsXYZ, float4[,] _Target, bool _bTargetNeedsXYZ, ColorProfile _ColorProfile ) { if ( _bSourceIsXYZ ^ _bTargetNeedsXYZ ) { // Requires conversion... if ( _bSourceIsXYZ ) { // Convert from XYZ to RGB for ( int Y=0; Y < _Source.GetLength( 1 ); Y++ ) for ( int X=0; X < _Source.GetLength( 0 ); X++ ) _Target[X,Y] = _ColorProfile.XYZ2RGB( new float4( _Source[X,Y].DecodedColor.x, _Source[X,Y].DecodedColor.y, _Source[X,Y].DecodedColor.z, 1.0f ) ); } else { // Convert from RGB to XYZ for ( int Y=0; Y < _Source.GetLength( 1 ); Y++ ) for ( int X=0; X < _Source.GetLength( 0 ); X++ ) _Target[X,Y] = _ColorProfile.RGB2XYZ( new float4( _Source[X,Y].DecodedColor.x, _Source[X,Y].DecodedColor.y, _Source[X,Y].DecodedColor.z, 1.0f ) ); } return; } // Simply decode vector and leave as-is for ( int Y=0; Y < _Source.GetLength( 1 ); Y++ ) for ( int X=0; X < _Source.GetLength( 0 ); X++ ) _Target[X,Y] = new float4( _Source[X,Y].DecodedColor.x, _Source[X,Y].DecodedColor.y, _Source[X,Y].DecodedColor.z, 1.0f ); }
/// <summary> /// Loads a bitmap in .HDR format into a RGBE array /// </summary> /// <param name="_HDRFormatBinary"></param> /// <param name="_bIsXYZ">Tells if the image is encoded as XYZE rather than RGBE</param> /// <param name="_ColorProfile">The color profile for the image</param> /// <returns></returns> public static unsafe PF_RGBE[,] LoadHDRFormat( byte[] _HDRFormatBinary, ColorProfile _ProfileOverride, out bool _bIsXYZ, out ColorProfile _ColorProfile ) { try { // The header of a .HDR image file consists of lines terminated by '\n' // It ends when there are 2 successive '\n' characters, then follows a single line containing the resolution of the image and only then, real scanlines begin... // // 1] We must isolate the header and find where it ends. // To do this, we seek and replace every '\n' characters by '\0' (easier to read) until we find a double '\n' List<string> HeaderLines = new List<string>(); int CharacterIndex = 0; int LineStartCharacterIndex = 0; while ( true ) { if ( _HDRFormatBinary[CharacterIndex] == '\n' || _HDRFormatBinary[CharacterIndex] == '\0' ) { // Found a new line! _HDRFormatBinary[CharacterIndex] = 0; fixed ( byte* pLineStart = &_HDRFormatBinary[LineStartCharacterIndex] ) HeaderLines.Add( new string( (sbyte*) pLineStart, 0, CharacterIndex-LineStartCharacterIndex, System.Text.Encoding.ASCII ) ); LineStartCharacterIndex = CharacterIndex + 1; // Check for header end if ( _HDRFormatBinary[CharacterIndex + 2] == '\n' ) { CharacterIndex += 3; break; } if ( _HDRFormatBinary[CharacterIndex + 1] == '\n' ) { CharacterIndex += 2; break; } } // Next character CharacterIndex++; } // 2] Read the last line containing the resolution of the image byte* pScanlines = null; string Resolution = null; LineStartCharacterIndex = CharacterIndex; while ( true ) { if ( _HDRFormatBinary[CharacterIndex] == '\n' || _HDRFormatBinary[CharacterIndex] == '\0' ) { _HDRFormatBinary[CharacterIndex] = 0; fixed ( byte* pLineStart = &_HDRFormatBinary[LineStartCharacterIndex] ) Resolution = new string( (sbyte*) pLineStart, 0, CharacterIndex-LineStartCharacterIndex, System.Text.Encoding.ASCII ); fixed ( byte* pScanlinesStart = &_HDRFormatBinary[CharacterIndex + 1] ) pScanlines = pScanlinesStart; break; } // Next character CharacterIndex++; } // 3] Check format and retrieve resolution // 3.1] Search lines for "#?RADIANCE" or "#?RGBE" if ( RadianceFileFindInHeader( HeaderLines, "#?RADIANCE" ) == null && RadianceFileFindInHeader( HeaderLines, "#?RGBE" ) == null ) throw new NotSupportedException( "Unknown HDR format!" ); // Unknown HDR file format! // 3.2] Search lines for format string FileFormat = RadianceFileFindInHeader( HeaderLines, "FORMAT=" ); if ( FileFormat == null ) throw new Exception( "No format description!" ); // Couldn't get FORMAT _bIsXYZ = false; if ( FileFormat.IndexOf( "32-bit_rle_rgbe" ) == -1 ) { // Check for XYZ encoding _bIsXYZ = true; if ( FileFormat.IndexOf( "32-bit_rle_xyze" ) == -1 ) throw new Exception( "Can't read format \"" + FileFormat + "\". Only 32-bit-rle-rgbe or 32-bit_rle_xyze is currently supported!" ); } // 3.3] Search lines for the exposure float fExposure = 0.0f; string ExposureText = RadianceFileFindInHeader( HeaderLines, "EXPOSURE=" ); if ( ExposureText != null ) float.TryParse( ExposureText, out fExposure ); // 3.4] Read the color primaries ColorProfile.Chromaticities Chromas = ColorProfile.Chromaticities.Radiance; // Default chromaticities string PrimariesText = RadianceFileFindInHeader( HeaderLines, "PRIMARIES=" ); if ( PrimariesText != null ) { string[] Primaries = PrimariesText.Split( ' ' ); if ( Primaries == null || Primaries.Length != 8 ) throw new Exception( "Failed to parse color profile chromaticities !" ); float.TryParse( Primaries[0], out Chromas.R.x ); float.TryParse( Primaries[1], out Chromas.R.y ); float.TryParse( Primaries[2], out Chromas.G.x ); float.TryParse( Primaries[3], out Chromas.G.y ); float.TryParse( Primaries[4], out Chromas.B.x ); float.TryParse( Primaries[5], out Chromas.B.y ); float.TryParse( Primaries[6], out Chromas.W.x ); float.TryParse( Primaries[7], out Chromas.W.y ); } // 3.5] Create the color profile if ( _ProfileOverride == null ) { _ColorProfile = new ColorProfile( Chromas, ColorProfile.GAMMA_CURVE.STANDARD, 1.0f ); _ColorProfile.Exposure = fExposure; } else _ColorProfile = _ProfileOverride; // 3.6] Read the resolution out of the last line int WayX = +1, WayY = +1; int Width = 0, Height = 0; int XIndex = Resolution.IndexOf( "+X" ); if ( XIndex == -1 ) { // Wrong way! WayX = -1; XIndex = Resolution.IndexOf( "-X" ); } if ( XIndex == -1 ) throw new Exception( "Couldn't find image width in resolution string \"" + Resolution + "\"!" ); int WidthEndCharacterIndex = Resolution.IndexOf( ' ', XIndex + 3 ); if ( WidthEndCharacterIndex == -1 ) WidthEndCharacterIndex = Resolution.Length; Width = int.Parse( Resolution.Substring( XIndex + 2, WidthEndCharacterIndex - XIndex - 2 ) ); int YIndex = Resolution.IndexOf( "+Y" ); if ( YIndex == -1 ) { // Flipped ! WayY = -1; YIndex = Resolution.IndexOf( "-Y" ); } if ( YIndex == -1 ) throw new Exception( "Couldn't find image height in resolution string \"" + Resolution + "\"!" ); int HeightEndCharacterIndex = Resolution.IndexOf( ' ', YIndex + 3 ); if ( HeightEndCharacterIndex == -1 ) HeightEndCharacterIndex = Resolution.Length; Height = int.Parse( Resolution.Substring( YIndex + 2, HeightEndCharacterIndex - YIndex - 2 ) ); // The encoding of the image data is quite simple: // // _ Each floating-point component is first encoded in Greg Ward's packed-pixel format which encodes 3 floats into a single DWORD organized this way: RrrrrrrrGgggggggBbbbbbbbEeeeeeee (E being the common exponent) // _ Each component of the packed-pixel is then encoded separately using a simple run-length encoding format // PF_RGBE[,] Dest = null; if ( ms_ReadContent ) { // 1] Allocate memory for the image and the temporary p_HDRFormatBinaryScanline Dest = new PF_RGBE[Width, Height]; // 2] Read the scanlines byte[,] TempScanline = new byte[Width,4]; int ImageY = WayY == +1 ? 0 : Height - 1; for ( int y=0; y < Height; y++, ImageY += WayY ) { if ( Width < 8 || Width > 0x7FFF || pScanlines[0] != 0x02 ) throw new Exception( "Unsupported old encoding format!" ); byte Temp; byte Green, Blue; // 2.1] Read an entire scanline pScanlines++; Green = *pScanlines++; Blue = *pScanlines++; Temp = *pScanlines++; if ( Green != 2 || (Blue & 0x80) != 0 ) throw new Exception( "Unsupported old encoding format!" ); if ( ((Blue << 8) | Temp) != Width ) throw new Exception( "Line and image widths mismatch!" ); for ( int ComponentIndex=0; ComponentIndex < 4; ComponentIndex++ ) { for ( int x=0; x < Width; ) { byte Code = *pScanlines++; if ( Code > 128 ) { // Run-Length encoding Code &= 0x7F; byte RLValue = *pScanlines++; while ( Code-- > 0 && x < Width ) TempScanline[x++,ComponentIndex] = RLValue; } else { // Normal encoding while ( Code-- > 0 && x < Width ) TempScanline[x++, ComponentIndex] = *pScanlines++; } } // For every pixels of the scanline } // For every color components (including exponent) // 2.2] Post-process the scanline and re-order it correctly int ImageX = WayX == +1 ? 0 : Width - 1; for ( int x=0; x < Width; x++, ImageX += WayX ) { Dest[x,y].R = TempScanline[ImageX, 0]; Dest[x,y].G = TempScanline[ImageX, 1]; Dest[x,y].B = TempScanline[ImageX, 2]; Dest[x,y].E = TempScanline[ImageX, 3]; } } } return Dest; } catch ( Exception _e ) { // Ouch! throw new Exception( "An exception occured while attempting to load an HDR file!", _e ); } }