void TestGraph( TEST_GRAPH_TYPE _type ) { ColorProfile sRGB = new ColorProfile( ColorProfile.STANDARD_PROFILE.sRGB ); m_imageFile.Init( 1024, 768, ImageFile.PIXEL_FORMAT.RGBA8, sRGB ); m_imageFile.Clear( new float4( 1, 1, 1, 1 ) ); // m_imageFile.Clear( new float4( 0, 0, 0, 1 ) ); if ( _type == TEST_GRAPH_TYPE.SIMPLE_FUNCTION ) { // Unit test simple graph float2 rangeY = new float2( -1.0f, 1.0f ); // m_imageFile.PlotGraph( black, new float2( -30.0f, 30.0f ), rangeY, ( float x ) => { return (float) Math.Sin( x ) / x; } ); m_imageFile.PlotGraphAutoRangeY( black, new float2( -30.0f, 30.0f ), ref rangeY, ( float x ) => { return (float) Math.Sin( x ) / x; } ); m_imageFile.PlotAxes( black, new float2( -30.0f, 30.0f ), rangeY, (float) (0.5 * Math.PI), 0.1f ); } else if ( _type == TEST_GRAPH_TYPE.SIMPLE_LOG_FUNCTIONS ) { m_imageFile.PlotLogGraph( red, new float2( 0.0f, 2.0f ), new float2( 0.0f, 100.0f ), ( float x ) => { return (float) Math.Pow( 10.0, x ); }, 1.0f, 1.0f ); // m_imageFile.PlotLogGraph( green, new float2( -2.0f, 2.0f ), new float2( 0.0f, 100.0f ), ( float x ) => { return (float) Math.Pow( 10.0, x ); }, 10.0f, 1.0f ); m_imageFile.PlotLogGraph( green, new float2( 0.0f, 2.0f ), new float2( 0.0f, 2.0f ), ( float x ) => { return (float) Math.Pow( 10.0, x ); }, 1.0f, 10.0f ); m_imageFile.PlotLogGraph( blue, new float2( -2.0f, 2.0f ), new float2( -2.0f, 2.0f ), ( float x ) => { return (float) Math.Pow( 10.0, x ); }, 10.0f, 10.0f ); // m_imageFile.PlotLogAxes( black, new float2( -1000.0f, 1000.0f ), new float2( -100.0f, 100.0f ), -100.0f, -10.0f ); // m_imageFile.PlotLogAxes( black, new float2( -100.0f, 1000.0f ), new float2( -2.0f, 2.0f ), -10.0f, 10.0f ); m_imageFile.PlotLogAxes( black, new float2( -2.0f, 2.0f ), new float2( -2.0f, 2.0f ), 10.0f, 2.0f ); } else if ( _type == TEST_GRAPH_TYPE.MANY_LINES ) { // Unit test a LOT of clipped lines! int W = (int) m_imageFile.Width; int H = (int) m_imageFile.Height; Random R = new Random( 1 ); float2 P0 = new float2(); float2 P1 = new float2(); for ( int i=0; i < 10000; i++ ) { P0.x = (float) (R.NextDouble() * 3*W) - W; P0.y = (float) (R.NextDouble() * 3*H) - H; P1.x = (float) (R.NextDouble() * 3*W) - W; P1.y = (float) (R.NextDouble() * 3*H) - H; m_imageFile.DrawLine( black, P0, P1 ); // m_imageFile.DrawLine( R.NextDouble() > 0.5 ? white : black, P0, P1 ); } } panelDrawing.Bitmap = m_imageFile.AsBitmap; }
void TestBlackBodyRadiation( TEST_COLOR_PROFILES _type ) { ColorProfile sRGB = new ColorProfile( ColorProfile.STANDARD_PROFILE.sRGB ); switch ( _type ) { // Load the color gamut and try and plot the locii of various white points // case TEST_COLOR_PROFILES.DRAW_WHITE_POINT_LOCI: { m_imageFile.Load( new System.IO.FileInfo( @"..\..\Images\In\xyGamut.png" ) ); float2 cornerZero = new float2( 114, 1336 ); // xy=(0.0, 0.0) float2 cornerPoint8Point9 = new float2( 1257, 49 ); // xy=(0.8, 0.9) // Check XYZ<->RGB and XYZ<->xyY converter code // float3 xyY = new float3(); // float3 XYZ = new float3(); // // float4 testRGB = new float4(); // float4 testXYZ = new float4(); // for ( int i=1; i <= 10; i++ ) { // float f = i / 10.0f; // testRGB.Set( 1*f, 1*f, 1*f, 1.0f ); // sRGB.RGB2XYZ( testRGB, ref testXYZ ); // // XYZ.Set( testXYZ.x, testXYZ.y, testXYZ.z ); // ColorProfile.XYZ2xyY( XYZ, ref xyY ); // ColorProfile.xyY2XYZ( xyY, ref XYZ ); // testXYZ.Set( XYZ, 1.0f ); // // sRGB.XYZ2RGB( testXYZ, ref testRGB ); // } float2 xy = new float2(); float4 color = new float4( 1, 0, 0, 1 ); float4 color2 = new float4( 0, 0.5f, 1, 1 ); for ( int locusIndex=0; locusIndex < 20; locusIndex++ ) { // float T = 1500.0f + (8000.0f - 1500.0f) * locusIndex / 20.0f; float T = 1500.0f + 500.0f * locusIndex; ColorProfile.ComputeWhitePointChromaticities( T, ref xy ); // Plot with the color of the white point // ColorProfile.xyY2XYZ( new float3( xy, 1.0f ), ref XYZ ); // sRGB.XYZ2RGB( new float4( XYZ, 1.0f ), ref color ); float2 fPos = cornerZero + (cornerPoint8Point9 - cornerZero) * new float2( xy.x / 0.8f, xy.y / 0.9f ); DrawPoint( (int) fPos.x, (int) fPos.y, 6, ref color ); ColorProfile.ComputeWhitePointChromaticitiesAnalytical( T, ref xy ); fPos = cornerZero + (cornerPoint8Point9 - cornerZero) * new float2( xy.x / 0.8f, xy.y / 0.9f ); DrawPoint( (int) fPos.x, (int) fPos.y, 3, ref color2 ); } } break; case TEST_COLOR_PROFILES.BUILD_WHITE_POINTS_GRADIENT_NO_BALANCE: case TEST_COLOR_PROFILES.BUILD_WHITE_POINTS_GRADIENT_BALANCE_D50_TO_D65: case TEST_COLOR_PROFILES.BUILD_WHITE_POINTS_GRADIENT_BALANCE_D65_TO_D50: { float3x3 whiteBalancingXYZ = float3x3.Identity; float whitePointCCT = 6500.0f; if ( _type == TEST_COLOR_PROFILES.BUILD_WHITE_POINTS_GRADIENT_BALANCE_D50_TO_D65 ) { // Compute white balancing from a D50 to a D65 illuminant whiteBalancingXYZ = ColorProfile.ComputeWhiteBalanceXYZMatrix( ColorProfile.Chromaticities.AdobeRGB_D50, ColorProfile.ILLUMINANT_D65 ); whitePointCCT = 5000.0f; } else if ( _type == TEST_COLOR_PROFILES.BUILD_WHITE_POINTS_GRADIENT_BALANCE_D65_TO_D50 ) { // Compute white balancing from a D65 to a D50 illuminant whiteBalancingXYZ = ColorProfile.ComputeWhiteBalanceXYZMatrix( ColorProfile.Chromaticities.sRGB, ColorProfile.ILLUMINANT_D50 ); whitePointCCT = 10000.0f; // ?? Actually we're already in D65 so assuming we're starting from a D50 illuminant instead actually pushes the white point far away... } // Build a gradient of white points from 1500K to 8000K m_imageFile.Init( 650, 32, ImageFile.PIXEL_FORMAT.RGBA8, sRGB ); float4 RGB = new float4( 0, 0, 0, 0 ); float3 XYZ = new float3( 0, 0, 0 ); float2 xy = new float2(); for ( uint X=0; X < 650; X++ ) { float T = 1500 + 10 * X; // From 1500K to 8000K ColorProfile.ComputeWhitePointChromaticities( T, ref xy ); ColorProfile.xyY2XYZ( new float3( xy, 1.0f ), ref XYZ ); // Apply white balancing XYZ *= whiteBalancingXYZ; sRGB.XYZ2RGB( new float4( XYZ, 1 ), ref RGB ); // "Normalize" //RGB /= Math.Max( Math.Max( RGB.x, RGB.y ), RGB.z ); // Isolate D65 if ( Math.Abs( T - whitePointCCT ) < 10.0f ) RGB.Set( 1, 0, 1, 1 ); for ( uint Y=0; Y < 32; Y++ ) { m_imageFile[X,Y] = RGB; } } // Check white balancing yields correct results // float3 XYZ_R_in = new float3(); // float3 XYZ_G_in = new float3(); // float3 XYZ_B_in = new float3(); // float3 XYZ_W_in = new float3(); // sRGB.RGB2XYZ( new float3( 1, 0, 0 ), ref XYZ_R_in ); // sRGB.RGB2XYZ( new float3( 0, 1, 0 ), ref XYZ_G_in ); // sRGB.RGB2XYZ( new float3( 0, 0, 1 ), ref XYZ_B_in ); // sRGB.RGB2XYZ( new float3( 1, 1, 1 ), ref XYZ_W_in ); // // float3 XYZ_R_out = XYZ_R_in * XYZ_D65_D50; // float3 XYZ_G_out = XYZ_G_in * XYZ_D65_D50; // float3 XYZ_B_out = XYZ_B_in * XYZ_D65_D50; // float3 XYZ_W_out = XYZ_W_in * XYZ_D65_D50; // // float3 xyY_R_out = new float3(); // float3 xyY_G_out = new float3(); // float3 xyY_B_out = new float3(); // float3 xyY_W_out = new float3(); // ColorProfile.XYZ2xyY( XYZ_R_out, ref xyY_R_out ); // ColorProfile.XYZ2xyY( XYZ_G_out, ref xyY_G_out ); // ColorProfile.XYZ2xyY( XYZ_B_out, ref xyY_B_out ); // ColorProfile.XYZ2xyY( XYZ_W_out, ref xyY_W_out ); } break; } panelColorProfile.Bitmap = m_imageFile.AsBitmap; }
public void Load( System.IO.Stream _ImageStream, FILE_TYPE _FileType, ColorProfile _ProfileOverride ) { // Read the file's content byte[] ImageContent = new byte[_ImageStream.Length]; _ImageStream.Read( ImageContent, 0, (int) _ImageStream.Length ); Load( ImageContent, _FileType, _ProfileOverride ); }
public void Load( byte[] _ImageFileContent, FILE_TYPE _FileType, ColorProfile _ProfileOverride ) { m_Type = _FileType; try { switch ( _FileType ) { case FILE_TYPE.JPEG: case FILE_TYPE.PNG: case FILE_TYPE.TIFF: case FILE_TYPE.GIF: case FILE_TYPE.BMP: using ( System.IO.MemoryStream Stream = new System.IO.MemoryStream( _ImageFileContent ) ) { // ===== 1] Load the bitmap source ===== BitmapDecoder Decoder = BitmapDecoder.Create( Stream, BitmapCreateOptions.IgnoreColorProfile | BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnDemand ); if ( Decoder.Frames.Count == 0 ) throw new Exception( "BitmapDecoder failed to read at least one bitmap frame!" ); BitmapFrame Frame = Decoder.Frames[0]; if ( Frame == null ) throw new Exception( "Invalid decoded bitmap!" ); // DEBUG // int StrideX = (Frame.Format.BitsPerPixel>>3)*Frame.PixelWidth; // byte[] DebugImageSource = new byte[StrideX*Frame.PixelHeight]; // Frame.CopyPixels( DebugImageSource, StrideX, 0 ); // DEBUG // pas de gamma sur les JPEG si non spécifié ! // Il y a bien une magouille faite lors de la conversion par le FormatConvertedBitmap! // ===== 2] Build the color profile ===== m_ColorProfile = _ProfileOverride != null ? _ProfileOverride : new ColorProfile( Frame.Metadata as BitmapMetadata, _FileType ); // ===== 3] Convert the frame to generic RGBA32F ===== ConvertFrame( Frame ); // ===== 4] Convert to CIE XYZ (our device-independent profile connection space) ===== if ( ms_ReadContent && ms_ConvertContent2XYZ ) m_ColorProfile.RGB2XYZ( m_Bitmap, m_Bitmap ); } break; case FILE_TYPE.TGA: { // Load as a System.Drawing.Bitmap and convert to float4 using ( System.IO.MemoryStream Stream = new System.IO.MemoryStream( _ImageFileContent ) ) using ( TargaImage TGA = new TargaImage( Stream, !ms_ReadContent ) ) { // Create a default sRGB linear color profile m_ColorProfile = _ProfileOverride != null ? _ProfileOverride : new ColorProfile( ColorProfile.Chromaticities.sRGB, // Use default sRGB color profile ColorProfile.GAMMA_CURVE.STANDARD, // But with a standard gamma curve... TGA.ExtensionArea.GammaRatio // ...whose gamma is retrieved from extension data ); if ( ms_ReadContent ) { // Convert byte[] ImageContent = LoadBitmap( TGA.Image, out m_Width, out m_Height ); m_Bitmap = new float4[m_Width,m_Height]; byte A; int i = 0; for ( int Y=0; Y < m_Height; Y++ ) for ( int X=0; X < m_Width; X++ ) { m_Bitmap[X,Y].x = BYTE_TO_FLOAT * ImageContent[i++]; m_Bitmap[X,Y].y = BYTE_TO_FLOAT * ImageContent[i++]; m_Bitmap[X,Y].z = BYTE_TO_FLOAT * ImageContent[i++]; A = ImageContent[i++]; m_bHasAlpha |= A != 0xFF; m_Bitmap[X,Y].w = BYTE_TO_FLOAT * A; } if ( ms_ConvertContent2XYZ ) { // Convert to CIEXYZ m_ColorProfile.RGB2XYZ( m_Bitmap, m_Bitmap ); } } else { // Only read dimensions m_Width = TGA.Header.Width; m_Height = TGA.Header.Height; } } return; } case FILE_TYPE.HDR: { // Load as XYZ m_Bitmap = LoadAndDecodeHDRFormat( _ImageFileContent, true, _ProfileOverride, out m_ColorProfile ); m_Width = m_Bitmap.GetLength( 0 ); m_Height = m_Bitmap.GetLength( 1 ); return; } #if USE_LIB_RAW case FILE_TYPE.CRW: case FILE_TYPE.CR2: case FILE_TYPE.DNG: { using ( System.IO.MemoryStream Stream = new System.IO.MemoryStream( _ImageFileContent ) ) using ( LibRawManaged.RawFile Raw = new LibRawManaged.RawFile() ) { Raw.UnpackRAW( Stream ); ColorProfile.Chromaticities Chroma = Raw.ColorProfile == LibRawManaged.RawFile.COLOR_PROFILE.ADOBE_RGB ? ColorProfile.Chromaticities.AdobeRGB_D65 // Use Adobe RGB : ColorProfile.Chromaticities.sRGB; // Use default sRGB color profile // Create a default sRGB linear color profile m_ColorProfile = _ProfileOverride != null ? _ProfileOverride : new ColorProfile( Chroma, ColorProfile.GAMMA_CURVE.STANDARD, // But with a standard gamma curve... 1.0f // Linear ); // Also get back valid camera shot info m_bHasValidShotInfo = true; m_ISOSpeed = Raw.ISOSpeed; m_ShutterSpeed = Raw.ShutterSpeed; m_Aperture = Raw.Aperture; m_FocalLength = Raw.FocalLength; // Convert m_Width = Raw.Width; m_Height = Raw.Height; // float ColorNormalizer = 1.0f / Raw.Maximum; float ColorNormalizer = 1.0f / 65535.0f; if ( ms_ReadContent ) { m_Bitmap = new float4[m_Width,m_Height]; UInt16[,][] ImageContent = Raw.Image; for ( int Y=0; Y < m_Height; Y++ ) for ( int X=0; X < m_Width; X++ ) { m_Bitmap[X,Y].x = ImageContent[X,Y][0] * ColorNormalizer; m_Bitmap[X,Y].y = ImageContent[X,Y][1] * ColorNormalizer; m_Bitmap[X,Y].z = ImageContent[X,Y][2] * ColorNormalizer; m_Bitmap[X,Y].w = ImageContent[X,Y][3] * ColorNormalizer; } if ( ms_ConvertContent2XYZ ) { // Convert to CIEXYZ m_ColorProfile.RGB2XYZ( m_Bitmap, m_Bitmap ); } } } #region My poor attempt at reading CRW files // using ( System.IO.MemoryStream Stream = new System.IO.MemoryStream( _ImageFileContent ) ) // using ( CanonRawLoader CRWLoader = new CanonRawLoader( Stream ) ) // { // ColorProfile.Chromaticities Chroma = CRWLoader.m_ColorProfile == CanonRawLoader.DataColorProfile.COLOR_PROFILE.ADOBE_RGB // ? ColorProfile.Chromaticities.AdobeRGB_D65 // Use Adobe RGB // : ColorProfile.Chromaticities.sRGB; // Use default sRGB color profile // // // Create a default sRGB linear color profile // m_ColorProfile = new ColorProfile( // Chroma, // ColorProfile.GAMMA_CURVE.STANDARD, // But with a standard gamma curve... // 1.0f // Linear // ); // // // Convert // m_Width = CRWLoader.m_RAWImage.m_Width; // m_Height = CRWLoader.m_RAWImage.m_Height; // // m_Bitmap = new float4[m_Width,m_Height]; // UInt16[] ImageContent = CRWLoader.m_RAWImage.m_DecodedImage; // int i = 0; // // for ( int Y=0; Y < m_Height; Y++ ) // // for ( int X=0; X < m_Width; X++ ) // // { // // m_Bitmap[X,Y].x = ImageContent[i++] / 4096.0f; // // m_Bitmap[X,Y].y = ImageContent[i++] / 4096.0f; // // m_Bitmap[X,Y].z = ImageContent[i++] / 4096.0f; // // i++; // // } // // i=0; // for ( int Y=0; Y < m_Height; Y++ ) // for ( int X=0; X < m_Width; X++ ) // m_Bitmap[X,Y].x = ImageContent[i++] / 4096.0f; // i=0; // for ( int Y=0; Y < m_Height; Y++ ) // for ( int X=0; X < m_Width; X++ ) // m_Bitmap[X,Y].y = ImageContent[i++] / 4096.0f; // i=0; // for ( int Y=0; Y < m_Height; Y++ ) // for ( int X=0; X < m_Width; X++ ) // m_Bitmap[X,Y].z = ImageContent[i++] / 4096.0f; // // // Convert to CIEXYZ // m_ColorProfile.RGB2XYZ( m_Bitmap ); // } #endregion return; } #endif default: throw new NotSupportedException( "The image file type \"" + _FileType + "\" is not supported by the Bitmap class!" ); } } catch ( Exception ) { throw; // Go on ! } }
/// <summary> /// Creates a bitmap from a System.Drawing.Bitmap and a color profile /// </summary> /// <param name="_Device"></param> /// <param name="_Name"></param> /// <param name="_Bitmap">The System.Drawing.Bitmap</param> /// <param name="_ColorProfile">The color profile to use to transform the bitmap</param> public Bitmap( System.Drawing.Bitmap _Bitmap, ColorProfile _ColorProfile ) { if ( _ColorProfile == null ) throw new Exception( "Invalid profile: can't convert to CIE XYZ!" ); m_ColorProfile = _ColorProfile; // Load the bitmap's content and copy it to a double entry array byte[] BitmapContent = LoadBitmap( _Bitmap, out m_Width, out m_Height ); if ( BitmapContent == null ) return; m_Bitmap = new float4[m_Width,m_Height]; int i=0; for ( int Y=0; Y < m_Height; Y++ ) for ( int X=0; X < m_Width; X++ ) { m_Bitmap[X,Y] = new float4( BYTE_TO_FLOAT * BitmapContent[i++], // R BYTE_TO_FLOAT * BitmapContent[i++], // G BYTE_TO_FLOAT * BitmapContent[i++], // B BYTE_TO_FLOAT * BitmapContent[i++] // A ); } if ( ms_ConvertContent2XYZ ) { // Convert to CIE XYZ m_ColorProfile.RGB2XYZ( m_Bitmap, m_Bitmap ); } }
public void Load( System.IO.FileInfo _ImageFileName, FILE_TYPE _FileType, ColorProfile _ProfileOverride ) { using ( System.IO.FileStream ImageStream = _ImageFileName.Open( System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.Read ) ) Load( ImageStream, _FileType, _ProfileOverride ); }
/// <summary> /// Creates a bitmap from memory /// </summary> /// <param name="_Device"></param> /// <param name="_Name"></param> /// <param name="_ImageFileContent">The memory buffer to load the bitmap from</param> /// <param name="_ImageFileNameName">The name of the image file the stream is coming from originally (used to identify image file type)</param> public Bitmap( byte[] _ImageFileContent, System.IO.FileInfo _ImageFileNameName, ColorProfile _ProfileOverride ) { Load( _ImageFileContent, GetFileType( _ImageFileNameName ), _ProfileOverride ); }
/// <summary> /// Creates a bitmap from memory /// </summary> /// <param name="_Device"></param> /// <param name="_Name"></param> /// <param name="_ImageFileContent">The memory buffer to load the bitmap from</param> /// <param name="_FileType">The image type</param> public Bitmap( byte[] _ImageFileContent, FILE_TYPE _FileType, ColorProfile _ProfileOverride ) { Load( _ImageFileContent, _FileType, _ProfileOverride ); }
/// <summary> /// Creates a bitmap from a stream /// </summary> /// <param name="_Device"></param> /// <param name="_Name"></param> /// <param name="_ImageStream">The image stream to load the bitmap from</param> /// <param name="_ImageFileNameName">The name of the image file the stream is coming from originally (used to identify image file type)</param> public Bitmap( System.IO.Stream _ImageStream, System.IO.FileInfo _ImageFileNameName, ColorProfile _ProfileOverride ) { Load( _ImageStream, GetFileType( _ImageFileNameName ), _ProfileOverride ); }
/// <summary> /// Creates a bitmap from a stream /// </summary> /// <param name="_Device"></param> /// <param name="_Name"></param> /// <param name="_ImageStream">The image stream to load the bitmap from</param> /// <param name="_FileType">The image type</param> public Bitmap( System.IO.Stream _ImageStream, FILE_TYPE _FileType, ColorProfile _ProfileOverride ) { Load( _ImageStream, _FileType, _ProfileOverride ); }
/// <summary> /// Manual creation /// </summary> /// <param name="_Width"></param> /// <param name="_Height"></param> /// <param name="_Profile">An optional color profile, you will need a valid profile if you wish to save the bitmap!</param> public Bitmap( int _Width, int _Height, ColorProfile _Profile ) { m_Width = _Width; m_Height = _Height; m_Bitmap = new float4[m_Width,m_Height]; for ( int Y=0; Y < m_Height; Y++ ) for ( int X=0; X < m_Width; X++ ) m_Bitmap[X,Y] = new float4( 0, 0, 0, 0 ); m_ColorProfile = _Profile; }
/// <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> /// 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> /// 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 ); } }
/// <summary> /// Loads a bitmap in .HDR format into a float4 array directly useable by the image constructor /// </summary> /// <param name="_HDRFormatBinary"></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></returns> public static float4[,] LoadAndDecodeHDRFormat( byte[] _HDRFormatBinary, bool _bTargetNeedsXYZ, ColorProfile _ProfileOverride, out ColorProfile _ColorProfile ) { bool bSourceIsXYZ; return DecodeRGBEImage( LoadHDRFormat( _HDRFormatBinary, _ProfileOverride, out bSourceIsXYZ, out _ColorProfile ), bSourceIsXYZ, _bTargetNeedsXYZ, _ColorProfile ); }