private unsafe void buttonConvertOld_Click( object sender, EventArgs args ) { if ( openFileDialog.ShowDialog( this ) != DialogResult.OK ) return; if ( saveFileDialog.ShowDialog( this ) != DialogResult.OK ) return; FileInfo SourceFileName = new FileInfo( openFileDialog.FileName ); FileInfo TargetFileName = new FileInfo( saveFileDialog.FileName ); int W, H; float3[,] Vectors; float x, y, z; using ( TargaImage TGA = new TargaImage( SourceFileName.FullName, false ) ) { // Convert byte[] ImageContent = Bitmap.LoadBitmap( TGA.Image, out W, out H ); Vectors = new float3[W,H]; int rha = 0; for ( int Y=0; Y < H; Y++ ) for ( int X=0; X < W; X++ ) { x = 2.0f * ImageContent[rha++] / 255 - 1.0f; y = 2.0f * ImageContent[rha++] / 255 - 1.0f; z = 2.0f * ImageContent[rha++] / 255 - 1.0f; rha++; // Skip alpha z = Math.Max( 0.0f, z ); float Norm = 1.0f / (float) Math.Sqrt( x*x + y*y + z*z ); Vectors[X,Y].x = x * Norm; Vectors[X,Y].y = y * Norm; Vectors[X,Y].z = z * Norm; } } // Convert to RG slightly less improved normal double Nx, Ny; double a, b, c = -1.0, d, t; ushort[,] PackedNormal = new ushort[W,H]; for ( int Y=0; Y < H; Y++ ) for ( int X=0; X < W; X++ ) { x = Vectors[X,Y].x; y = Vectors[X,Y].y; z = Vectors[X,Y].z; // Here I'm using the exact algorithm described by http://rgba32.blogspot.fr/2011/02/improved-normal-map-distributions.html a = (x * x) + (y * y); b = z; d = b*b - 4.0*a*c; t = (-b + Math.Sqrt( d )) / (2.0 * a); Nx = x * t; Ny = y * t; Vectors[X,Y].x = (float) (0.5 * (1.0 + Nx)); Vectors[X,Y].y = (float) (0.5 * (1.0 + Ny)); Vectors[X,Y].z = 0.0f; } // Save as target PNG using ( System.Drawing.Bitmap B = new System.Drawing.Bitmap( W, H, System.Drawing.Imaging.PixelFormat.Format32bppRgb ) ) { System.Drawing.Imaging.BitmapData LockedBitmap = B.LockBits( new System.Drawing.Rectangle( 0, 0, W, H ), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppRgb ); for ( int Y=0; Y < H; Y++ ) { byte* pScanline = (byte*) LockedBitmap.Scan0 + LockedBitmap.Stride * Y; for ( int X=0; X < W; X++ ) { *pScanline++ = 0; *pScanline++ = (byte) (255 * Vectors[X,Y].y); *pScanline++ = (byte) (255 * Vectors[X,Y].x); *pScanline++ = 0xFF; } } B.UnlockBits( LockedBitmap ); B.Save( TargetFileName.FullName ); } }
private unsafe void buttonConvertNew_Click( object sender, EventArgs _e ) { if ( openFileDialog.ShowDialog( this ) != DialogResult.OK ) return; if ( saveFileDialog.ShowDialog( this ) != DialogResult.OK ) return; FileInfo SourceFileName = new FileInfo( openFileDialog.FileName ); FileInfo TargetFileName = new FileInfo( saveFileDialog.FileName ); int W, H; float3[,] Vectors; float x, y, z; using ( TargaImage TGA = new TargaImage( SourceFileName.FullName, false ) ) { // Convert byte[] ImageContent = Bitmap.LoadBitmap( TGA.Image, out W, out H ); Vectors = new float3[W,H]; int rha = 0; for ( int Y=0; Y < H; Y++ ) for ( int X=0; X < W; X++ ) { x = 2.0f * ImageContent[rha++] / 255 - 1.0f; y = 2.0f * ImageContent[rha++] / 255 - 1.0f; z = 2.0f * ImageContent[rha++] / 255 - 1.0f; rha++; // Skip alpha z = Math.Max( 0.0f, z ); float Norm = 1.0f / (float) Math.Sqrt( x*x + y*y + z*z ); Vectors[X,Y].x = x * Norm; Vectors[X,Y].y = y * Norm; Vectors[X,Y].z = z * Norm; } } // Convert to RG improved normal double Nx, Ny; double CosPhi, SinPhi, CosTheta, SinTheta, Normalizer; double a = 1.0, b, c, d = 0.0, e, t; ushort[,] PackedNormal = new ushort[W,H]; for ( int Y=0; Y < H; Y++ ) for ( int X=0; X < W; X++ ) { x = Vectors[X,Y].x; y = Vectors[X,Y].y; z = Vectors[X,Y].z; CosTheta = z; SinTheta = Math.Sqrt( 1 - z*z ); Normalizer = 1.0 / Math.Max( 1e-10, SinTheta ); CosPhi = x * Normalizer; SinPhi = y * Normalizer; e = SinTheta*SinTheta*SinTheta*SinTheta * CosPhi*CosPhi * SinPhi*SinPhi; c = -SinTheta*SinTheta; b = -CosTheta; double[] roots = Polynomial.solvePolynomial( a, b, c, d, e ); t = Math.Sqrt( 2.0 ); for ( int i=0; i < roots.Length; i++ ) if ( !double.IsNaN( roots[i] ) && roots[i] >= 0.0 ) t = Math.Min( t, roots[i] ); // Nx = t * CosPhi * SinTheta; // Ny = t * SinPhi * SinTheta; Nx = t * x; Ny = t * y; Vectors[X,Y].x = (float) (0.5 * (1.0 + Nx)); Vectors[X,Y].y = (float) (0.5 * (1.0 + Ny)); Vectors[X,Y].z = 0.0f; } // Save as target PNG using ( System.Drawing.Bitmap B = new System.Drawing.Bitmap( W, H, System.Drawing.Imaging.PixelFormat.Format32bppRgb ) ) { System.Drawing.Imaging.BitmapData LockedBitmap = B.LockBits( new System.Drawing.Rectangle( 0, 0, W, H ), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppRgb ); for ( int Y=0; Y < H; Y++ ) { byte* pScanline = (byte*) LockedBitmap.Scan0 + LockedBitmap.Stride * Y; for ( int X=0; X < W; X++ ) { *pScanline++ = 0; *pScanline++ = (byte) (255 * Vectors[X,Y].y); *pScanline++ = (byte) (255 * Vectors[X,Y].x); *pScanline++ = 0xFF; } } B.UnlockBits( LockedBitmap ); B.Save( TargetFileName.FullName ); } }
public float4( float3 _xyz, float _w ) { x = _xyz.x; y = _xyz.y; z = _xyz.z; w = _w; }
public static float3 Parse( string v ) { string[] Components = v.Split( ';' ); if ( Components.Length < 3 ) throw new Exception( "Not enough vector components!" ); float3 Result = new float3(); if ( !float.TryParse( Components[0].Trim(), out Result.x ) ) throw new Exception( "Can't parse X field!" ); if ( !float.TryParse( Components[1].Trim(), out Result.y ) ) throw new Exception( "Can't parse Y field!" ); if ( !float.TryParse( Components[2].Trim(), out Result.z ) ) throw new Exception( "Can't parse Z field!" ); return Result; }
/// <summary> /// Builds the RGB<->XYZ transforms from chromaticities /// (refer to http://wiki.nuaj.net/index.php/Color_Transforms#XYZ_Matrices for explanations) /// </summary> protected void BuildTransformFromChroma( bool _bCheckGammaCurveOverride ) { float3 xyz_R = new float3( m_Chromaticities.R.x, m_Chromaticities.R.y, 1.0f - m_Chromaticities.R.x - m_Chromaticities.R.y ); float3 xyz_G = new float3( m_Chromaticities.G.x, m_Chromaticities.G.y, 1.0f - m_Chromaticities.G.x - m_Chromaticities.G.y ); float3 xyz_B = new float3( m_Chromaticities.B.x, m_Chromaticities.B.y, 1.0f - m_Chromaticities.B.x - m_Chromaticities.B.y ); float3 XYZ_W = xyY2XYZ( new float3( m_Chromaticities.W.x, m_Chromaticities.W.y, 1.0f ) ); float4x4 M_xyz = new float4x4() { row0 = new float4( xyz_R, 0.0f ), row1 = new float4( xyz_G, 0.0f ), row2 = new float4( xyz_B, 0.0f ), row3 = new float4( 0.0f, 0.0f, 0.0f, 1.0f ) }; M_xyz.Invert(); float4 Sum_RGB = new float4( XYZ_W, 1.0f ) * M_xyz; // Finally, we can retrieve the RGB->XYZ transform m_RGB2XYZ.row0 = new float4( Sum_RGB.x * xyz_R, 0.0f ); m_RGB2XYZ.row1 = new float4( Sum_RGB.y * xyz_G, 0.0f ); m_RGB2XYZ.row2 = new float4( Sum_RGB.z * xyz_B, 0.0f ); // And the XYZ->RGB transform m_XYZ2RGB = m_RGB2XYZ; m_XYZ2RGB.Invert(); // ============= Attempt to recognize a standard profile ============= STANDARD_PROFILE RecognizedChromaticity = m_Chromaticities.RecognizedChromaticity; if ( _bCheckGammaCurveOverride ) { // Also ensure the gamma ramp is correct before assigning a standard profile bool bIsGammaCorrect = true; switch ( RecognizedChromaticity ) { case STANDARD_PROFILE.sRGB: bIsGammaCorrect = EnsureGamma( GAMMA_CURVE.sRGB, GAMMA_EXPONENT_sRGB ); break; case STANDARD_PROFILE.ADOBE_RGB_D50: bIsGammaCorrect = EnsureGamma( GAMMA_CURVE.STANDARD, GAMMA_EXPONENT_ADOBE ); break; case STANDARD_PROFILE.ADOBE_RGB_D65: bIsGammaCorrect = EnsureGamma( GAMMA_CURVE.STANDARD, GAMMA_EXPONENT_ADOBE ); break; case STANDARD_PROFILE.PRO_PHOTO: bIsGammaCorrect = EnsureGamma( GAMMA_CURVE.PRO_PHOTO, GAMMA_EXPONENT_PRO_PHOTO ); break; case STANDARD_PROFILE.RADIANCE: bIsGammaCorrect = EnsureGamma( GAMMA_CURVE.STANDARD, 1.0f ); break; } if ( !bIsGammaCorrect ) RecognizedChromaticity = STANDARD_PROFILE.CUSTOM; // A non-standard gamma curves fails our pre-defined design... } // ============= Assign the internal converter depending on the profile ============= switch ( RecognizedChromaticity ) { case STANDARD_PROFILE.sRGB: m_GammaCurve = GAMMA_CURVE.sRGB; m_Gamma = GAMMA_EXPONENT_sRGB; m_InternalConverter = new InternalColorConverter_sRGB(); break; case STANDARD_PROFILE.ADOBE_RGB_D50: m_GammaCurve = GAMMA_CURVE.STANDARD; m_Gamma = GAMMA_EXPONENT_ADOBE; m_InternalConverter = new InternalColorConverter_AdobeRGB_D50(); break; case STANDARD_PROFILE.ADOBE_RGB_D65: m_GammaCurve = GAMMA_CURVE.STANDARD; m_Gamma = GAMMA_EXPONENT_ADOBE; m_InternalConverter = new InternalColorConverter_AdobeRGB_D65(); break; case STANDARD_PROFILE.PRO_PHOTO: m_GammaCurve = GAMMA_CURVE.PRO_PHOTO; m_Gamma = GAMMA_EXPONENT_PRO_PHOTO; m_InternalConverter = new InternalColorConverter_ProPhoto(); break; case STANDARD_PROFILE.RADIANCE: m_GammaCurve = GAMMA_CURVE.STANDARD; m_Gamma = 1.0f; m_InternalConverter = new InternalColorConverter_Radiance(); break; default: // Switch to one of our generic converters switch ( m_GammaCurve ) { case GAMMA_CURVE.sRGB: m_InternalConverter = new InternalColorConverter_Generic_sRGBGamma( m_RGB2XYZ, m_XYZ2RGB ); break; case GAMMA_CURVE.PRO_PHOTO: m_InternalConverter = new InternalColorConverter_Generic_ProPhoto( m_RGB2XYZ, m_XYZ2RGB ); break; case GAMMA_CURVE.STANDARD: if ( Math.Abs( m_Gamma - 1.0f ) < 1e-3f ) m_InternalConverter = new InternalColorConverter_Generic_NoGamma( m_RGB2XYZ, m_XYZ2RGB ); else m_InternalConverter = new InternalColorConverter_Generic_StandardGamma( m_RGB2XYZ, m_XYZ2RGB, m_Gamma ); break; } break; } }
/// <summary> /// Converts from XYZ to xyY /// </summary> /// <param name="_XYZ"></param> /// <returns></returns> public static float3 XYZ2xyY( float3 _XYZ ) { float InvSum = 1.0f / Math.Max( 1e-8f, _XYZ.x + _XYZ.y + _XYZ.z); return new float3( _XYZ.x * InvSum, _XYZ.y * InvSum, _XYZ.y ); }
/// <summary> /// Converts from xyY to XYZ /// </summary> /// <param name="_xyY"></param> /// <returns></returns> public static float3 xyY2XYZ( float3 _xyY ) { float Y_y = _xyY.y > 1e-8f ? _xyY.z / _xyY.y : 0.0f; return new float3( _xyY.x * Y_y, _xyY.z, (1.0f - _xyY.x - _xyY.y) * Y_y ); }