/// <summary> /// Prepares the interpolated calibration table to process the pixels in an image shot with the specified shot infos /// </summary> /// <param name="_Image"></param> public void PrepareCalibrationFor(ImageUtility.Bitmap _Image) { if (!_Image.HasValidShotInfo) { throw new Exception("Can't prepare calibration for specified image since it doesn't have valid shot infos!"); } PrepareCalibrationFor(_Image.ISOSpeed, _Image.ShutterSpeed, _Image.Aperture); }
private unsafe void UpdateProgress(ImageUtility.Bitmap _Image, int Y, bool _Bias) { const int REFRESH_EVERY_N_SCANLINES = 4; if (Y == 0 || (Y & (REFRESH_EVERY_N_SCANLINES - 1)) != 0) { return; } viewportPanelResult.Image = _Image; Application.DoEvents(); }
/// <summary> /// Builds a swatch bitmap /// </summary> /// <param name="_Width"></param> /// <param name="_Height"></param> /// <param name="_xyY"></param> /// <returns></returns> private ImageUtility.Bitmap BuildSwatch(int _Width, int _Height, ImageUtility.float3 _xyY) { ImageUtility.Bitmap Result = new ImageUtility.Bitmap(_Width, _Height, new ImageUtility.ColorProfile(ImageUtility.ColorProfile.STANDARD_PROFILE.sRGB)); ImageUtility.float4 XYZ = new ImageUtility.float4(ImageUtility.ColorProfile.xyY2XYZ(_xyY), 1.0f); for (int Y = 0; Y < _Height; Y++) { for (int X = 0; X < _Width; X++) { Result.ContentXYZ[X, Y] = XYZ; } } return(Result); }
private void buttonTestBilateral_Click(object sender, EventArgs e) { try { panelParameters.Enabled = false; ////////////////////////////////////////////////////////////////////////// // 1] Apply bilateral filtering to the input texture as a pre-process ApplyBilateralFiltering(m_TextureSource, m_TextureTarget0, floatTrackbarControlBilateralRadius.Value, floatTrackbarControlBilateralTolerance.Value, checkBoxWrap.Checked, 100); progressBar.Value = progressBar.Maximum; ////////////////////////////////////////////////////////////////////////// // 2] Copy target to staging for CPU readback and update the resulting bitmap m_TextureTarget_CPU.CopyFrom(m_TextureTarget0); if (m_BitmapResult != null) { m_BitmapResult.Dispose(); } m_BitmapResult = null; m_BitmapResult = new ImageUtility.Bitmap(W, H, m_ProfileLinear); m_BitmapResult.HasAlpha = true; RendererManaged.PixelsBuffer Pixels = m_TextureTarget_CPU.Map(0, 0); using (System.IO.BinaryReader R = Pixels.OpenStreamRead()) for (int Y = 0; Y < H; Y++) { R.BaseStream.Position = Y * Pixels.RowPitch; for (int X = 0; X < W; X++) { float AO = R.ReadSingle(); ImageUtility.float4 Color = new ImageUtility.float4(AO, AO, AO, AO); Color = m_ProfileLinear.RGB2XYZ(Color); m_BitmapResult.ContentXYZ[X, Y] = Color; } } Pixels.Dispose(); m_TextureTarget_CPU.UnMap(0, 0); // Assign result viewportPanelResult.Image = m_BitmapResult; } catch (Exception _e) { MessageBox("An error occurred during generation!\r\n\r\nDetails: ", _e); } finally { panelParameters.Enabled = true; } }
private void LoadNormalMap(System.IO.FileInfo _FileName) { try { // Dispose of existing resources if (m_BitmapSourceNormal != null) { m_BitmapSourceNormal.Dispose(); } m_BitmapSourceNormal = null; if (m_TextureSourceNormal != null) { m_TextureSourceNormal.Dispose(); } m_TextureSourceNormal = null; // Load the source image assuming it's in linear space (all normal maps should be in linear space, with the default value being (0.5, 0.5, 1)) m_BitmapSourceNormal = new ImageUtility.Bitmap(_FileName, m_ProfileLinear); // outputPanelInputHeightMap.Image = m_BitmapSourceNormal; int W = m_BitmapSourceNormal.Width; int H = m_BitmapSourceNormal.Height; // Build the source texture ImageUtility.float4[,] ContentRGB = m_BitmapSourceNormal.ConvertedContentRGB; RendererManaged.PixelsBuffer SourceNormalMap = new RendererManaged.PixelsBuffer(W * H * 4 * 4); using (System.IO.BinaryWriter Wr = SourceNormalMap.OpenStreamWrite()) for (int Y = 0; Y < H; Y++) { for (int X = 0; X < W; X++) { float Nx = 2.0f * ContentRGB[X, Y].x - 1.0f; float Ny = 1.0f - 2.0f * ContentRGB[X, Y].y; float Nz = 2.0f * ContentRGB[X, Y].z - 1.0f; Wr.Write(Nx); Wr.Write(Ny); Wr.Write(Nz); Wr.Write(1.0f); } } m_TextureSourceNormal = new RendererManaged.Texture2D(m_Device, W, H, 1, 1, RendererManaged.PIXEL_FORMAT.RGBA32_FLOAT, false, false, new RendererManaged.PixelsBuffer[] { SourceNormalMap }); } catch (Exception _e) { MessageBox("An error occurred while opening the image:\n\n", _e); } }
/// <summary> /// Creates an embeddable thumbnail of the reference image /// </summary> /// <param name="_Image"></param> public void CreateThumbnail(ImageUtility.Bitmap _Image) { int MaxDim = Math.Max(_Image.Width, _Image.Height); int ThumbnailSize = 256; int W = ThumbnailSize * _Image.Width / MaxDim; int H = ThumbnailSize * _Image.Height / MaxDim; // Build the thumbnail m_Thumbnail = new byte[W, H]; for (int Y = 0; Y < H; Y++) { for (int X = 0; X < W; X++) { ImageUtility.float4 XYZ = _Image.ContentXYZ[X * _Image.Width / W, Y *_Image.Height / H]; m_Thumbnail[X, Y] = (byte)Math.Min(255, Math.Max(0, 255.0f * XYZ.y)); } } }
static void SaveTestsRGB() { ImageUtility.ColorProfile Profile_sRGB = new ImageUtility.ColorProfile(ImageUtility.ColorProfile.STANDARD_PROFILE.sRGB); ImageUtility.ColorProfile Profile_Linear = new ImageUtility.ColorProfile(ImageUtility.ColorProfile.Chromaticities.sRGB, ImageUtility.ColorProfile.GAMMA_CURVE.STANDARD, 1.0f); ImageUtility.Bitmap Gray_sRGB = new ImageUtility.Bitmap(64, 64, Profile_sRGB); ImageUtility.Bitmap Gray_Linear = new ImageUtility.Bitmap(64, 64, Profile_Linear); ImageUtility.Bitmap Gradient_sRGB = new ImageUtility.Bitmap(128, 16, Profile_sRGB); ImageUtility.Bitmap Gradient_Linear = new ImageUtility.Bitmap(128, 16, Profile_Linear); for (int Y = 0; Y < Gray_sRGB.Height; Y++) { for (int X = 0; X < Gray_sRGB.Width; X++) { Gray_sRGB.ContentXYZ[X, Y] = Profile_Linear.RGB2XYZ(new ImageUtility.float4(0.5f, 0.5f, 0.5f, 1.0f)); Gray_Linear.ContentXYZ[X, Y] = Profile_Linear.RGB2XYZ(new ImageUtility.float4(0.5f, 0.5f, 0.5f, 1.0f)); } } int W = Gradient_sRGB.Width; for (int Y = 0; Y < Gradient_sRGB.Height; Y++) { for (int X = 0; X < Gradient_sRGB.Width; X++) { float C = (float)(X + 0.5f) / W; Gradient_sRGB.ContentXYZ[X, Y] = Profile_Linear.RGB2XYZ(new ImageUtility.float4(C, C, C, 1.0f)); Gradient_Linear.ContentXYZ[X, Y] = Profile_Linear.RGB2XYZ(new ImageUtility.float4(C, C, C, 1.0f)); } } Gray_sRGB.Save(new FileInfo("./Gray128_sRGB.png")); Gray_Linear.Save(new FileInfo("./Gray128_Linear.png")); Gradient_sRGB.Save(new FileInfo("./Gradient_sRGB.png")); Gradient_Linear.Save(new FileInfo("./Gradient_Linear.png")); }
/// <summary> /// Reads image size and other informations, doesn't read content /// </summary> public void ReadImageInfos() { m_usage = FindUsage(m_fileName); if (m_fileName.Extension.ToLower().StartsWith(".bimage")) { // Can't read! m_fileType = FILE_TYPE.BIMAGE; m_couldBeRead = false; return; } try { using (ImageUtility.Bitmap B = new ImageUtility.Bitmap(m_fileName)) { m_error = null; m_couldBeRead = true; m_width = B.Width; m_height = B.Height; switch (B.Type) { case ImageUtility.Bitmap.FILE_TYPE.PNG: m_fileType = FILE_TYPE.PNG; break; case ImageUtility.Bitmap.FILE_TYPE.TGA: m_fileType = FILE_TYPE.TGA; break; // case ImageUtility.Bitmap.FILE_TYPE.DDS: m_fileType = FILE_TYPE.DDS; break; // DDS not supported? case ImageUtility.Bitmap.FILE_TYPE.JPEG: m_fileType = FILE_TYPE.JPG; break; case ImageUtility.Bitmap.FILE_TYPE.TIFF: m_fileType = FILE_TYPE.TIFF; break; } } } catch (Exception _e) { m_couldBeRead = false; m_error = _e; throw _e; } }
/// <summary> /// Captures the calibrated texture /// </summary> /// <param name="_Source">The source image to capture</param> /// <param name="_Database">Database to perform proper calibration</param> /// <param name="_Parms">Parameters for the capture</param> public void Capture( ImageUtility.Bitmap _Source, CameraCalibrationDatabase _Database, CaptureParms _Parms ) { if ( _Source == null ) throw new Exception( "Invalid source bitmap to build texture from!" ); if ( _Database == null ) throw new Exception( "Invalid calibration database found in parameters!" ); if ( _Parms == null ) throw new Exception( "Invalid calibration parameters!" ); if ( m_SwatchWidth <= 0 || m_SwatchHeight <= 0 ) throw new Exception( "Invalid swatch size! Must be > 0!" ); // Save parameters as they're associated to this texture m_CaptureParameters = _Parms; m_WhiteReflectanceReference = _Database.WhiteReflectanceReference; m_WhiteReflectanceCorrectionFactor = _Database.WhiteReflectanceCorrectionFactor; m_SpatialCorrectionEnabled = _Database.WhiteReferenceImage != null; ////////////////////////////////////////////////////////////////////////// // Setup the database to find the most appropriate calibration data for our image infos _Database.PrepareCalibrationFor( _Parms.ISOSpeed, _Parms.ShutterSpeed, _Parms.Aperture ); ////////////////////////////////////////////////////////////////////////// // Build target texture ImageUtility.float4 AvgXYZ = new ImageUtility.float4( 0, 0, 0, 0 ); //DEBUG // float MinLuminance_Raw = float.MaxValue; // float MaxLuminance_Raw = -float.MaxValue; const int EXTREME_VALUES_COUNT = 100; ImageUtility.float3[] ArrayMin = new ImageUtility.float3[EXTREME_VALUES_COUNT]; ImageUtility.float3[] ArrayMax = new ImageUtility.float3[EXTREME_VALUES_COUNT]; for ( int i=0; i < EXTREME_VALUES_COUNT; i++ ) { ArrayMin[i] = new ImageUtility.float3( 0, 1, 0 ); ArrayMax[i] = new ImageUtility.float3( 0, 0, 0 ); } if ( _Parms.CropSource ) { float fImageWidth = 2.0f * _Parms.CropRectangleHalfSize.x * _Source.Height; float fImageHeight = 2.0f * _Parms.CropRectangleHalfSize.y * _Source.Height; int W = (int) Math.Floor( fImageWidth ); int H = (int) Math.Floor( fImageHeight ); ImageUtility.float2 AxisX = new ImageUtility.float2( (float) Math.Cos( _Parms.CropRectangleRotation ), -(float) Math.Sin( _Parms.CropRectangleRotation ) ); ImageUtility.float2 AxisY = new ImageUtility.float2( (float) Math.Sin( _Parms.CropRectangleRotation ), (float) Math.Cos( _Parms.CropRectangleRotation ) ); ImageUtility.float2 TopLeftCorner = new ImageUtility.float2( 0.5f * (_Source.Width - _Source.Height) + _Parms.CropRectangleCenter.x * _Source.Height, _Source.Height * _Parms.CropRectangleCenter.y ) + _Source.Height * (-_Parms.CropRectangleHalfSize.x * AxisX - _Parms.CropRectangleHalfSize.y * AxisY); m_Texture = new ImageUtility.Bitmap( W, H, new ImageUtility.ColorProfile( ImageUtility.ColorProfile.STANDARD_PROFILE.sRGB ) ); ImageUtility.float4 XYZ; ImageUtility.float3 ShortXYZ; ImageUtility.float3 xyY; ImageUtility.float2 CurrentScanlinePixel = TopLeftCorner + 0.5f * (fImageWidth - W) * AxisX + 0.5f * (fImageHeight - H) * AxisY; if ( Math.Abs( _Parms.CropRectangleRotation ) < 1e-6f ) { // Use integer pixels to avoid attenuated values due to bilinear filtering CurrentScanlinePixel.x = (float) Math.Floor( CurrentScanlinePixel.x ); CurrentScanlinePixel.y = (float) Math.Floor( CurrentScanlinePixel.y ); } for ( int Y=0; Y < H; Y++ ) { ImageUtility.float2 CurrentPixel = CurrentScanlinePixel; for ( int X=0; X < W; X++ ) { float U = CurrentPixel.x / _Source.Width; float V = CurrentPixel.y / _Source.Height; XYZ = _Source.BilinearSample( CurrentPixel.x, CurrentPixel.y ); //DEBUG // float L = XYZ.y * _Database.GetSpatialLuminanceCorrectionFactor( U, V ); // if ( L < MinLuminance_Raw ) // MinLuminance_Raw = L; // if ( L > MaxLuminance_Raw ) // MaxLuminance_Raw = L; //DEBUG xyY = ImageUtility.ColorProfile.XYZ2xyY( (ImageUtility.float3) XYZ ); xyY = _Database.CalibrateWithSpatialCorrection( U, V, xyY ); // Apply luminance calibration ShortXYZ = ImageUtility.ColorProfile.xyY2XYZ( xyY ); XYZ = new ImageUtility.float4( ShortXYZ, XYZ.w ); m_Texture.ContentXYZ[X,Y] = XYZ; // Update min/max/avg values InsertMinMax( ShortXYZ, ArrayMin, ArrayMax, EXTREME_VALUES_COUNT ); AvgXYZ += XYZ; CurrentPixel += AxisX; } CurrentScanlinePixel += AxisY; } } else { // Simple texture copy, with luminance calibration m_Texture = new ImageUtility.Bitmap( _Source.Width, _Source.Height, new ImageUtility.ColorProfile( ImageUtility.ColorProfile.STANDARD_PROFILE.sRGB ) ); ImageUtility.float4 XYZ; ImageUtility.float3 ShortXYZ; ImageUtility.float3 xyY; int W = m_Texture.Width; int H = m_Texture.Height; int X0 = 0; int X1 = W; int Y0 = 0; int Y1 = H; //DEBUG // X0 = 1088; Y0 = 764; // X1 = X0 + 1100; Y1 = Y0 + 632; for ( int Y=Y0; Y < Y1; Y++ ) { float V = (float) Y / H; for ( int X=X0; X < X1; X++ ) { float U = (float) X / W; XYZ = _Source.ContentXYZ[X,Y]; //DEBUG // float L = XYZ.y * _Database.GetSpatialLuminanceCorrectionFactor( U, V ); // if ( L < MinLuminance_Raw ) // MinLuminance_Raw = L; // if ( L > MaxLuminance_Raw ) // MaxLuminance_Raw = L; //DEBUG xyY = ImageUtility.ColorProfile.XYZ2xyY( (ImageUtility.float3) XYZ ); xyY = _Database.CalibrateWithSpatialCorrection( U, V, xyY ); // Apply luminance calibration ShortXYZ = ImageUtility.ColorProfile.xyY2XYZ( xyY ); XYZ = new ImageUtility.float4( ShortXYZ, XYZ.w ); m_Texture.ContentXYZ[X,Y] = XYZ; // Update min/max/avg values InsertMinMax( ShortXYZ, ArrayMin, ArrayMax, EXTREME_VALUES_COUNT ); AvgXYZ += XYZ; } } } // Normalize average swatch color float Normalizer = 1.0f / (m_Texture.Width*m_Texture.Height); ImageUtility.float3 avgxyY = ImageUtility.ColorProfile.XYZ2xyY( Normalizer * ((ImageUtility.float3) AvgXYZ) ); m_SwatchAvg.xyY = avgxyY; // Compute min & max using statistical norm ImageUtility.float3 BestXYZ_Min; ImageUtility.float3 BestXYZ_Max; if ( _Parms.UseModeInsteadOfMean ) { // Use mode BestXYZ_Min = ComputeMode( ArrayMin ); BestXYZ_Max = ComputeMode( ArrayMax ); } else { // Use mean BestXYZ_Min = ComputeMean( ArrayMin ); BestXYZ_Max = ComputeMean( ArrayMax ); } m_SwatchMin.xyY = ImageUtility.ColorProfile.XYZ2xyY( BestXYZ_Min ); m_SwatchMax.xyY = ImageUtility.ColorProfile.XYZ2xyY( BestXYZ_Max ); m_SwatchMin.Texture = BuildSwatch( m_SwatchWidth, m_SwatchHeight, m_SwatchMin.xyY ); m_SwatchMax.Texture = BuildSwatch( m_SwatchWidth, m_SwatchHeight, m_SwatchMax.xyY ); m_SwatchAvg.Texture = BuildSwatch( m_SwatchWidth, m_SwatchHeight, m_SwatchAvg.xyY ); // Rebuild custom swatches foreach ( CustomSwatch CS in m_CustomSwatches ) CS.Texture = BuildSwatch( m_SwatchWidth, m_SwatchHeight, CS.xyY ); ////////////////////////////////////////////////////////////////////////// // Feed some purely informational shot infos to the main texture, probably won't be saved anyway... m_Texture.HasValidShotInfo = true; m_Texture.ISOSpeed = _Parms.ISOSpeed; m_Texture.ShutterSpeed = _Parms.ShutterSpeed; m_Texture.Aperture = _Parms.Aperture; }
/// <summary> /// Builds a swatch bitmap /// </summary> /// <param name="_Width"></param> /// <param name="_Height"></param> /// <param name="_xyY"></param> /// <returns></returns> private ImageUtility.Bitmap BuildSwatch( int _Width, int _Height, ImageUtility.float3 _xyY ) { ImageUtility.Bitmap Result = new ImageUtility.Bitmap( _Width, _Height, new ImageUtility.ColorProfile( ImageUtility.ColorProfile.STANDARD_PROFILE.sRGB ) ); ImageUtility.float4 XYZ = new ImageUtility.float4( ImageUtility.ColorProfile.xyY2XYZ( _xyY ), 1.0f ); for ( int Y=0; Y < _Height; Y++ ) for ( int X=0; X < _Width; X++ ) Result.ContentXYZ[X,Y] = XYZ; return Result; }
/// <summary> /// Loads the white reference image file /// </summary> /// <param name="_FileName"></param> /// <param name="_AlertIfFailed"></param> private void LoadWhiteReferenceImage( System.IO.FileInfo _FileName, bool _AlertIfFailed ) { buttonSaveWhiteRefImage.Enabled = false; buttonResetWhiteRefImage.Enabled = false; try { ImageUtility.Bitmap RefImg = new ImageUtility.Bitmap( _FileName ); m_CalibrationDatabase.WhiteReferenceImage = RefImg; UpdateWhiteReferenceImageUI(); } catch ( Exception _e ) { if ( _AlertIfFailed ) MessageBox( "An error occurred while loading the white reference image \"" + _FileName.FullName + "\":\r\n\r\n", _e ); } }
private void buttonLoadImage_Click( object sender, EventArgs e ) { string OldFileName = GetRegKey( "LastImageFilename", m_ApplicationPath ); openFileDialogSourceImage.InitialDirectory = System.IO.Path.GetDirectoryName( OldFileName ); openFileDialogSourceImage.FileName = System.IO.Path.GetFileName( OldFileName ); if ( openFileDialogSourceImage.ShowDialog( this ) != DialogResult.OK ) return; SetRegKey( "LastImageFilename", openFileDialogSourceImage.FileName ); try { // Load System.IO.FileInfo ImageFileName = new System.IO.FileInfo( openFileDialogSourceImage.FileName ); ImageUtility.Bitmap NewBitmap = new ImageUtility.Bitmap( ImageFileName ); // Safely assign once loaded m_ImageFileName = ImageFileName; m_BitmapXYZ = NewBitmap; this.Text = APP_TITLE + " (" + ImageFileName.Name + ")"; // Setup camera shot info if it exists if ( m_BitmapXYZ.HasValidShotInfo ) { groupBoxCameraShotInfos.Enabled = false; floatTrackbarControlISOSpeed.Value = m_BitmapXYZ.ISOSpeed; floatTrackbarControlISOSpeed.SimulateValueChange(); // So we get notified even if value is the same as default slider value floatTrackbarControlShutterSpeed.Value = m_BitmapXYZ.ShutterSpeed; floatTrackbarControlShutterSpeed.SimulateValueChange(); // So we get notified even if value is the same as default slider value floatTrackbarControlAperture.Value = m_BitmapXYZ.Aperture; floatTrackbarControlAperture.SimulateValueChange(); // So we get notified even if value is the same as default slider value floatTrackbarControlFocalLength.Value = m_BitmapXYZ.FocalLength; floatTrackbarControlFocalLength.SimulateValueChange(); // So we get notified even if value is the same as default slider value } else groupBoxCameraShotInfos.Enabled = true; // Clear calibrated texture & UI if ( m_Texture != null ) m_Texture.Dispose(); m_Texture = null; resultTexturePanel.CalibratedTexture = null; buttonSaveCalibratedImage.Enabled = false; foreach ( CustomSwatch S in m_CustomSwatches ) { S.m_CheckBox.Checked = false; S.m_Panel.BackColor = Color.DimGray; } // Prepare database with new camera shot infos PrepareDatabase( false ); // Re-apply white reflectance reference if it's adapted to the if ( Math.Abs(m_CalibrationDatabase.PreparedForISOSpeed - m_WhiteReflectanceISOSpeed) < 1e-6f && Math.Abs(m_CalibrationDatabase.PreparedForShutterSpeed - m_WhiteReflectanceShutterSpeed) < 1e-6f && Math.Abs(m_CalibrationDatabase.PreparedForAperture - m_WhiteReflectanceAperture) < 1e-6f ) { m_CalibrationDatabase.WhiteReflectanceReference = m_WhiteReflectanceReference; } UpdateWhiteReflectanceUI(); // If the shot infos are different from the ones from which the white reflectance was picked then the values got reset so update UI RebuildImage(); // Finally, rebuild the image and show it in the output panel outputPanel.ResetCropRectangle(); // Previous crop rectangle is not valid anymore } catch ( Exception _e ) { MessageBox( "An error occurred while loading the image:\r\n\r\n", _e ); } }
////////////////////////////////////////////////////////////////////////// // White Reference Image // private void buttonPickWhiteRefImage_Click( object sender, EventArgs e ) { if ( m_BitmapXYZ == null ) { // No image loaded you moron! MessageBox( "Can't pick white reference as no image is currently loaded!", MessageBoxButtons.OK, MessageBoxIcon.Exclamation ); return; } try { int W, H; if ( m_BitmapXYZ.Width > m_BitmapXYZ.Height ) { W = DEFAULT_WHITE_REFERENCE_IMAGE_SIZE; H = W * m_BitmapXYZ.Height / m_BitmapXYZ.Width; } else { H = DEFAULT_WHITE_REFERENCE_IMAGE_SIZE; W = H * m_BitmapXYZ.Width / m_BitmapXYZ.Height; } // Find the maximum luminance in the image that we'll use as a normalizer ImageUtility.Bitmap WhiteRef = new ImageUtility.Bitmap( W, H, m_sRGBProfile ); for ( int Y=0; Y < H; Y++ ) for ( int X=0; X < W; X++ ) { float x0 = m_BitmapXYZ.Width * (float) X / W; float x1 = m_BitmapXYZ.Width * (float) (X+1) / W; float y0 = m_BitmapXYZ.Height * (float) Y / H; float y1 = m_BitmapXYZ.Height * (float) (Y+1) / H; ImageUtility.float4 SumXYZ = new ImageUtility.float4( 0, 0, 0, 0 ); int Count = 0; float y = y0; while ( y < y1 ) { float x = x0; while ( x < x1 ) { SumXYZ += m_BitmapXYZ.BilinearSample( x, y ); Count++; x++; } y++; } float Test = (float) (Math.Ceiling(x1-x0) * Math.Ceiling(y1-y0)); // Should equal Count SumXYZ = (1.0f / Math.Max( 1, Count)) * SumXYZ; ImageUtility.float3 xyY = ImageUtility.ColorProfile.XYZ2xyY( (ImageUtility.float3) SumXYZ ); xyY.x = m_sRGBProfile.Chromas.W.x; // B&W xyY.y = m_sRGBProfile.Chromas.W.y; ImageUtility.float4 XYZ = new ImageUtility.float4( ImageUtility.ColorProfile.xyY2XYZ( xyY ), SumXYZ.w ); WhiteRef.ContentXYZ[X,Y] = XYZ; } // Assign to the database m_CalibrationDatabase.WhiteReferenceImage = WhiteRef; UpdateWhiteReferenceImageUI(); } catch ( Exception _e ) { MessageBox( "An error occurred while creating the white reference image:\r\n\r\n", _e ); } }
private void buttonWhiteRefTest3_Click( object sender, EventArgs e ) { int W = DEFAULT_WHITE_REFERENCE_IMAGE_SIZE; int H = DEFAULT_WHITE_REFERENCE_IMAGE_SIZE; ImageUtility.Bitmap WhiteRef = new ImageUtility.Bitmap( W, H, m_sRGBProfile ); ImageUtility.float3 xyY = new ImageUtility.float3( m_sRGBProfile.Chromas.W.x, m_sRGBProfile.Chromas.W.y, 0.0f ); for ( int Y=0; Y < H; Y++ ) { float V = (float) Y / H; for ( int X=0; X < W; X++ ) { float U = (float) X / W; xyY.z = Math.Min( 1.0f, 1.0f - 0.5f * V ); ImageUtility.float4 XYZ = new ImageUtility.float4( ImageUtility.ColorProfile.xyY2XYZ( xyY ), 1.0f ); WhiteRef.ContentXYZ[X,Y] = XYZ; } } // Assign to the database m_CalibrationDatabase.WhiteReferenceImage = WhiteRef; UpdateWhiteReferenceImageUI(); }
private void Generate() { try { panelParameters.Enabled = false; ////////////////////////////////////////////////////////////////////////// // 1] Apply bilateral filtering to the input texture as a pre-process ApplyBilateralFiltering( m_textureSourceHeightMap, m_textureTarget0, floatTrackbarControlBilateralRadius.Value, floatTrackbarControlBilateralTolerance.Value, checkBoxWrap.Checked, BILATERAL_PROGRESS ); ////////////////////////////////////////////////////////////////////////// // 2] Compute directional occlusion m_textureTarget1.RemoveFromLastAssignedSlots(); // Prepare computation parameters m_textureTarget0.SetCS( 0 ); m_textureTarget1.SetCSUAV( 0 ); m_SB_Rays.SetInput( 1 ); m_TextureSourceNormal.SetCS( 2 ); m_CB_Input.m.RaysCount = (UInt32) Math.Min( MAX_THREADS, integerTrackbarControlRaysCount.Value ); m_CB_Input.m.MaxStepsCount = (UInt32) integerTrackbarControlMaxStepsCount.Value; m_CB_Input.m.Tile = (uint) (checkBoxWrap.Checked ? 1 : 0); m_CB_Input.m.TexelSize_mm = TextureSize_mm / Math.Max( W, H ); m_CB_Input.m.Displacement_mm = TextureHeight_mm; // Start if ( !m_CS_GenerateAOMap.Use() ) throw new Exception( "Can't generate self-shadowed bump map as compute shader failed to compile!" ); uint h = Math.Max( 1, MAX_LINES*1024 / W ); uint CallsCount = (uint) Math.Ceiling( (float) H / h ); for ( int i=0; i < CallsCount; i++ ) { m_CB_Input.m.Y0 = (UInt32) (i * h); m_CB_Input.UpdateData(); m_CS_GenerateAOMap.Dispatch( W, h, 1 ); m_device.Present( true ); progressBar.Value = (int) (0.01f * (BILATERAL_PROGRESS + (100-BILATERAL_PROGRESS) * (i+1) / (CallsCount)) * progressBar.Maximum); // for ( int a=0; a < 10; a++ ) Application.DoEvents(); } m_textureTarget1.RemoveFromLastAssignedSlotUAV(); // So we can use it as input for next stage progressBar.Value = progressBar.Maximum; // Compute in a single shot (this is madness!) // m_CB_Input.m.y = 0; // m_CB_Input.UpdateData(); // m_CS_GenerateSSBumpMap.Dispatch( W, H, 1 ); ////////////////////////////////////////////////////////////////////////// // 3] Copy target to staging for CPU readback and update the resulting bitmap m_textureTarget_CPU.CopyFrom( m_textureTarget1 ); // ImageUtility.ColorProfile profile = m_profilesRGB; // AO maps are sRGB! (although strange, that's certainly to have more range in dark values) ImageUtility.ColorProfile profile = m_profileLinear; float3 whitePoint_xyY = new float3( profile.Chromas.White, 0 ); float3 whitePoint_XYZ = new float3(); ImageUtility.Bitmap tempBitmap = new ImageUtility.Bitmap( W, H ); Renderer.PixelsBuffer Pixels = m_textureTarget_CPU.Map( 0, 0 ); using ( System.IO.BinaryReader R = Pixels.OpenStreamRead() ) for ( uint Y=0; Y < H; Y++ ) { R.BaseStream.Position = Y * Pixels.RowPitch; for ( uint X=0; X < W; X++ ) { whitePoint_xyY.z = R.ReadSingle(); // Linear value ImageUtility.ColorProfile.xyY2XYZ( whitePoint_xyY, ref whitePoint_XYZ ); tempBitmap[X,Y] = new float4( whitePoint_XYZ, 1 ); } } Pixels.Dispose(); m_textureTarget_CPU.UnMap( 0, 0 ); // Convert to RGB ImageUtility.ImageFile temmpImageRGBA32F = new ImageUtility.ImageFile(); tempBitmap.ToImageFile( temmpImageRGBA32F, profile ); if ( m_imageResult == null ) m_imageResult = new ImageUtility.ImageFile(); m_imageResult.ToneMapFrom( temmpImageRGBA32F, ( float3 _HDR, ref float3 _LDR ) => { _LDR = _HDR; // Return as-is.. } ); // Assign result viewportPanelResult.Bitmap = m_imageResult.AsBitmap; } catch ( Exception _e ) { MessageBox( "An error occurred during generation!\r\n\r\nDetails: ", _e ); } finally { panelParameters.Enabled = true; } }
/// <summary> /// Generates a (diffuse+gloss) texture from 2 distinct textures /// </summary> public void GenerateDiffuseGlossTexture() { if (m_diffuse == null) { throw new Exception("Invalid source diffuse image file!"); } if (!m_diffuse.m_fileName.Exists) { throw new Exception("Source diffuse image file \"" + m_diffuse.m_fileName.FullName + "\" does not exist on disk!"); } string targetFileNameString = TextureFileInfo.GetOptimizedDiffuseGlossNameFromDiffuseName(m_diffuse.m_fileName.FullName); FileInfo targetFileName = new FileInfo(targetFileNameString); using (ImageUtility.Bitmap diffuse = new ImageUtility.Bitmap(m_diffuse.m_fileName)) { int W = diffuse.Width; int H = diffuse.Height; ImageUtility.Bitmap gloss = null; if (m_gloss != null) { gloss = new ImageUtility.Bitmap(m_gloss.m_fileName); // int gW = gloss.Width; // int gH = gloss.Height; // for ( int Y=0; Y < gH; Y++ ) // for ( int X=0; X < gW; X++ ) { // gloss.ContentXYZ[X,Y].x = ImageUtility.ColorProfile.sRGB2Linear( gloss.ContentXYZ[X,Y].x ); // } } bool needsScale = false; if (gloss != null && (gloss.Width != W || gloss.Height != H)) { needsScale = true; } if (gloss != null) { if (needsScale) { // Set gloss as alpha with re-scaling float scaleX = (float)gloss.Width / W; float scaleY = (float)gloss.Height / H; for (int Y = 0; Y < H; Y++) { float Y2 = scaleY * Y; for (int X = 0; X < W; X++) { diffuse.ContentXYZ[X, Y].w = gloss.BilinearSample(scaleX * X, Y2).x; } } } else { // Set gloss as alpha without re-scaling for (int Y = 0; Y < H; Y++) { for (int X = 0; X < W; X++) { // diffuse.ContentXYZ[X,Y].w = gloss.ContentXYZ[X,Y].x; diffuse.ContentXYZ[X, Y].w = 0.3f * gloss.ContentXYZ[X, Y].x + 0.5f * gloss.ContentXYZ[X, Y].y + 0.2f * gloss.ContentXYZ[X, Y].z; } } } } else { for (int Y = 0; Y < H; Y++) { for (int X = 0; X < W; X++) { diffuse.ContentXYZ[X, Y].w = 1.0f; } } } // Save diffuse as target diffuse.HasAlpha = gloss != null; diffuse.Save(targetFileName); } // Save optimized filename + timestamps of source files m_diffuseFileName.Refresh(); if (m_glossFileName != null) { m_glossFileName.Refresh(); } m_optimizedDiffuseGlossFileName = targetFileName; m_diffuseTimeAtGeneration = m_diffuseFileName.LastWriteTime; m_glossTimeAtGeneration = m_glossFileName != null ? m_glossFileName.LastWriteTime : DateTime.MinValue; }
private void LoadHeightMap(System.IO.FileInfo _FileName) { try { tabControlGenerators.Enabled = false; // Dispose of existing resources if (m_BitmapSource != null) { m_BitmapSource.Dispose(); } m_BitmapSource = null; if (m_TextureTarget_CPU != null) { m_TextureTarget_CPU.Dispose(); } m_TextureTarget_CPU = null; if (m_TextureTarget0 != null) { m_TextureTarget0.Dispose(); } m_TextureTarget0 = null; if (m_TextureTarget1 != null) { m_TextureTarget1.Dispose(); } m_TextureTarget1 = null; if (m_TextureSource != null) { m_TextureSource.Dispose(); } m_TextureSource = null; // Load the source image assuming it's in linear space m_SourceFileName = _FileName; m_BitmapSource = new ImageUtility.Bitmap(_FileName, m_LinearProfile); outputPanelInputHeightMap.Image = m_BitmapSource; W = m_BitmapSource.Width; H = m_BitmapSource.Height; // Build the source texture RendererManaged.PixelsBuffer SourceHeightMap = new RendererManaged.PixelsBuffer(W * H * 4); using (System.IO.BinaryWriter Wr = SourceHeightMap.OpenStreamWrite()) for (int Y = 0; Y < H; Y++) { for (int X = 0; X < W; X++) { Wr.Write(m_BitmapSource.ContentXYZ[X, Y].y); } } m_TextureSource = new RendererManaged.Texture2D(m_Device, W, H, 1, 1, RendererManaged.PIXEL_FORMAT.R32_FLOAT, false, false, new RendererManaged.PixelsBuffer[] { SourceHeightMap }); // Build the target UAV & staging texture for readback m_TextureTarget0 = new RendererManaged.Texture2D(m_Device, W, H, 1, 1, RendererManaged.PIXEL_FORMAT.R32_FLOAT, false, true, null); m_TextureTarget1 = new RendererManaged.Texture2D(m_Device, W, H, 1, 1, RendererManaged.PIXEL_FORMAT.RGBA32_FLOAT, false, true, null); m_TextureTarget_CPU = new RendererManaged.Texture2D(m_Device, W, H, 1, 1, RendererManaged.PIXEL_FORMAT.RGBA32_FLOAT, true, false, null); tabControlGenerators.Enabled = true; buttonGenerate.Focus(); } catch (Exception _e) { MessageBox("An error occurred while opening the image:\n\n", _e); } }
/// <summary> /// Captures the calibrated texture /// </summary> /// <param name="_Source">The source image to capture</param> /// <param name="_Database">Database to perform proper calibration</param> /// <param name="_Parms">Parameters for the capture</param> public void Capture(ImageUtility.Bitmap _Source, CameraCalibrationDatabase _Database, CaptureParms _Parms) { if (_Source == null) { throw new Exception("Invalid source bitmap to build texture from!"); } if (_Database == null) { throw new Exception("Invalid calibration database found in parameters!"); } if (_Parms == null) { throw new Exception("Invalid calibration parameters!"); } if (m_SwatchWidth <= 0 || m_SwatchHeight <= 0) { throw new Exception("Invalid swatch size! Must be > 0!"); } // Save parameters as they're associated to this texture m_CaptureParameters = _Parms; m_WhiteReflectanceReference = _Database.WhiteReflectanceReference; m_WhiteReflectanceCorrectionFactor = _Database.WhiteReflectanceCorrectionFactor; m_SpatialCorrectionEnabled = _Database.WhiteReferenceImage != null; ////////////////////////////////////////////////////////////////////////// // Setup the database to find the most appropriate calibration data for our image infos _Database.PrepareCalibrationFor(_Parms.ISOSpeed, _Parms.ShutterSpeed, _Parms.Aperture); ////////////////////////////////////////////////////////////////////////// // Build target texture ImageUtility.float4 AvgXYZ = new ImageUtility.float4(0, 0, 0, 0); //DEBUG // float MinLuminance_Raw = float.MaxValue; // float MaxLuminance_Raw = -float.MaxValue; const int EXTREME_VALUES_COUNT = 100; ImageUtility.float3[] ArrayMin = new ImageUtility.float3[EXTREME_VALUES_COUNT]; ImageUtility.float3[] ArrayMax = new ImageUtility.float3[EXTREME_VALUES_COUNT]; for (int i = 0; i < EXTREME_VALUES_COUNT; i++) { ArrayMin[i] = new ImageUtility.float3(0, 1, 0); ArrayMax[i] = new ImageUtility.float3(0, 0, 0); } if (_Parms.CropSource) { float fImageWidth = 2.0f * _Parms.CropRectangleHalfSize.x * _Source.Height; float fImageHeight = 2.0f * _Parms.CropRectangleHalfSize.y * _Source.Height; int W = (int)Math.Floor(fImageWidth); int H = (int)Math.Floor(fImageHeight); ImageUtility.float2 AxisX = new ImageUtility.float2((float)Math.Cos(_Parms.CropRectangleRotation), -(float)Math.Sin(_Parms.CropRectangleRotation)); ImageUtility.float2 AxisY = new ImageUtility.float2((float)Math.Sin(_Parms.CropRectangleRotation), (float)Math.Cos(_Parms.CropRectangleRotation)); ImageUtility.float2 TopLeftCorner = new ImageUtility.float2(0.5f * (_Source.Width - _Source.Height) + _Parms.CropRectangleCenter.x * _Source.Height, _Source.Height * _Parms.CropRectangleCenter.y) + _Source.Height * (-_Parms.CropRectangleHalfSize.x * AxisX - _Parms.CropRectangleHalfSize.y * AxisY); m_Texture = new ImageUtility.Bitmap(W, H, new ImageUtility.ColorProfile(ImageUtility.ColorProfile.STANDARD_PROFILE.sRGB)); ImageUtility.float4 XYZ; ImageUtility.float3 ShortXYZ; ImageUtility.float3 xyY; ImageUtility.float2 CurrentScanlinePixel = TopLeftCorner + 0.5f * (fImageWidth - W) * AxisX + 0.5f * (fImageHeight - H) * AxisY; if (Math.Abs(_Parms.CropRectangleRotation) < 1e-6f) { // Use integer pixels to avoid attenuated values due to bilinear filtering CurrentScanlinePixel.x = (float)Math.Floor(CurrentScanlinePixel.x); CurrentScanlinePixel.y = (float)Math.Floor(CurrentScanlinePixel.y); } for (int Y = 0; Y < H; Y++) { ImageUtility.float2 CurrentPixel = CurrentScanlinePixel; for (int X = 0; X < W; X++) { float U = CurrentPixel.x / _Source.Width; float V = CurrentPixel.y / _Source.Height; XYZ = _Source.BilinearSample(CurrentPixel.x, CurrentPixel.y); //DEBUG // float L = XYZ.y * _Database.GetSpatialLuminanceCorrectionFactor( U, V ); // if ( L < MinLuminance_Raw ) // MinLuminance_Raw = L; // if ( L > MaxLuminance_Raw ) // MaxLuminance_Raw = L; //DEBUG xyY = ImageUtility.ColorProfile.XYZ2xyY((ImageUtility.float3)XYZ); xyY = _Database.CalibrateWithSpatialCorrection(U, V, xyY); // Apply luminance calibration ShortXYZ = ImageUtility.ColorProfile.xyY2XYZ(xyY); XYZ = new ImageUtility.float4(ShortXYZ, XYZ.w); m_Texture.ContentXYZ[X, Y] = XYZ; // Update min/max/avg values InsertMinMax(ShortXYZ, ArrayMin, ArrayMax, EXTREME_VALUES_COUNT); AvgXYZ += XYZ; CurrentPixel += AxisX; } CurrentScanlinePixel += AxisY; } } else { // Simple texture copy, with luminance calibration m_Texture = new ImageUtility.Bitmap(_Source.Width, _Source.Height, new ImageUtility.ColorProfile(ImageUtility.ColorProfile.STANDARD_PROFILE.sRGB)); ImageUtility.float4 XYZ; ImageUtility.float3 ShortXYZ; ImageUtility.float3 xyY; int W = m_Texture.Width; int H = m_Texture.Height; int X0 = 0; int X1 = W; int Y0 = 0; int Y1 = H; //DEBUG // X0 = 1088; Y0 = 764; // X1 = X0 + 1100; Y1 = Y0 + 632; for (int Y = Y0; Y < Y1; Y++) { float V = (float)Y / H; for (int X = X0; X < X1; X++) { float U = (float)X / W; XYZ = _Source.ContentXYZ[X, Y]; //DEBUG // float L = XYZ.y * _Database.GetSpatialLuminanceCorrectionFactor( U, V ); // if ( L < MinLuminance_Raw ) // MinLuminance_Raw = L; // if ( L > MaxLuminance_Raw ) // MaxLuminance_Raw = L; //DEBUG xyY = ImageUtility.ColorProfile.XYZ2xyY((ImageUtility.float3)XYZ); xyY = _Database.CalibrateWithSpatialCorrection(U, V, xyY); // Apply luminance calibration ShortXYZ = ImageUtility.ColorProfile.xyY2XYZ(xyY); XYZ = new ImageUtility.float4(ShortXYZ, XYZ.w); m_Texture.ContentXYZ[X, Y] = XYZ; // Update min/max/avg values InsertMinMax(ShortXYZ, ArrayMin, ArrayMax, EXTREME_VALUES_COUNT); AvgXYZ += XYZ; } } } // Normalize average swatch color float Normalizer = 1.0f / (m_Texture.Width * m_Texture.Height); ImageUtility.float3 avgxyY = ImageUtility.ColorProfile.XYZ2xyY(Normalizer * ((ImageUtility.float3)AvgXYZ)); m_SwatchAvg.xyY = avgxyY; // Compute min & max using statistical norm ImageUtility.float3 BestXYZ_Min; ImageUtility.float3 BestXYZ_Max; if (_Parms.UseModeInsteadOfMean) { // Use mode BestXYZ_Min = ComputeMode(ArrayMin); BestXYZ_Max = ComputeMode(ArrayMax); } else { // Use mean BestXYZ_Min = ComputeMean(ArrayMin); BestXYZ_Max = ComputeMean(ArrayMax); } m_SwatchMin.xyY = ImageUtility.ColorProfile.XYZ2xyY(BestXYZ_Min); m_SwatchMax.xyY = ImageUtility.ColorProfile.XYZ2xyY(BestXYZ_Max); m_SwatchMin.Texture = BuildSwatch(m_SwatchWidth, m_SwatchHeight, m_SwatchMin.xyY); m_SwatchMax.Texture = BuildSwatch(m_SwatchWidth, m_SwatchHeight, m_SwatchMax.xyY); m_SwatchAvg.Texture = BuildSwatch(m_SwatchWidth, m_SwatchHeight, m_SwatchAvg.xyY); // Rebuild custom swatches foreach (CustomSwatch CS in m_CustomSwatches) { CS.Texture = BuildSwatch(m_SwatchWidth, m_SwatchHeight, CS.xyY); } ////////////////////////////////////////////////////////////////////////// // Feed some purely informational shot infos to the main texture, probably won't be saved anyway... m_Texture.HasValidShotInfo = true; m_Texture.ISOSpeed = _Parms.ISOSpeed; m_Texture.ShutterSpeed = _Parms.ShutterSpeed; m_Texture.Aperture = _Parms.Aperture; }
/// <summary> /// Saves a texture to disk /// </summary> /// <param name="_Texture"></param> /// <param name="_FileName"></param> /// <param name="_FileType"></param> /// <param name="_Format"></param> private void SaveImage(ImageUtility.Bitmap _Texture, System.IO.FileInfo _FileName, ImageUtility.Bitmap.FILE_TYPE _FileType, ImageUtility.Bitmap.FORMAT_FLAGS _Format) { using (System.IO.FileStream S = _FileName.Create()) _Texture.Save(S, _FileType, _Format, null); }
/// <summary> /// Generates a (diffuse+gloss) texture from 2 distinct textures /// </summary> public void GenerateDiffuseGlossTexture() { if ( m_diffuse == null ) throw new Exception( "Invalid source diffuse image file!" ); if ( !m_diffuse.m_fileName.Exists ) throw new Exception( "Source diffuse image file \"" + m_diffuse.m_fileName.FullName + "\" does not exist on disk!" ); FileInfo targetFileName = GetDiffuseGlossTextureFileName(); using ( ImageUtility.Bitmap diffuse = new ImageUtility.Bitmap( m_diffuse.m_fileName ) ) { int W = diffuse.Width; int H = diffuse.Height; ImageUtility.Bitmap gloss = null; if ( m_gloss != null ) { gloss = new ImageUtility.Bitmap( m_gloss.m_fileName ); // int gW = gloss.Width; // int gH = gloss.Height; // for ( int Y=0; Y < gH; Y++ ) // for ( int X=0; X < gW; X++ ) { // gloss.ContentXYZ[X,Y].x = ImageUtility.ColorProfile.sRGB2Linear( gloss.ContentXYZ[X,Y].x ); // } } bool needsScale = false; if ( gloss != null && (gloss.Width != W || gloss.Height != H) ) { needsScale = true; } if ( gloss != null ) { if ( needsScale ) { // Set gloss as alpha with re-scaling int W2 = gloss.Width; int H2 = gloss.Height; float[,] source = new float[W2,H2]; for ( int Y=0; Y < H2; Y++ ) for ( int X=0; X < W2; X++ ) source[X,Y] = 0.3f * gloss.ContentXYZ[X,Y].x + 0.5f * gloss.ContentXYZ[X,Y].y + 0.2f * gloss.ContentXYZ[X,Y].z; // Downscale first while ( W2 > W ) { int halfW2 = W2 >> 1; float[,] temp = new float[halfW2,H2]; for ( int Y=0; Y < H2; Y++ ) for ( int X=0; X < halfW2; X++ ) temp[X,Y] = 0.5f * (source[2*X+0,Y] + source[2*X+1,Y]); source = temp; W2 = halfW2; } while ( H2 > H ) { int halfH2 = H2 >> 1; float[,] temp = new float[W2,halfH2]; for ( int Y=0; Y < halfH2; Y++ ) for ( int X=0; X < W2; X++ ) temp[X,Y] = 0.5f * (source[X,2*Y+0] + source[X,2*Y+1]); source = temp; H2 = halfH2; } // Upscale then while ( W2 < W ) { int doubleW2 = W2 << 1; float[,] temp = new float[doubleW2,H2]; for ( int Y=0; Y < H2; Y++ ) for ( int X=0; X < W2; X++ ) { temp[2*X+0,Y] = source[X,Y]; temp[2*X+1,Y] = 0.5f * (source[X,Y] + source[Math.Min( W2-1, X+1 ),Y]); } source = temp; W2 = doubleW2; } while ( H2 < H ) { int doubleH2 = H2 << 1; float[,] temp = new float[W2,doubleH2]; for ( int Y=0; Y < H2; Y++ ) for ( int X=0; X < W2; X++ ) { temp[X,2*Y+0] = source[X,Y]; temp[X,2*Y+1] = 0.5f * (source[X,Y] + source[X,Math.Min( H2-1, Y+1 )]); } source = temp; H2 = doubleH2; } for ( int Y=0; Y < H; Y++ ) for ( int X=0; X < W; X++ ) diffuse.ContentXYZ[X,Y].w = source[X,Y]; } else { // Set gloss as alpha without re-scaling for ( int Y=0; Y < H; Y++ ) for ( int X=0; X < W; X++ ) { // diffuse.ContentXYZ[X,Y].w = gloss.ContentXYZ[X,Y].x; diffuse.ContentXYZ[X,Y].w = 0.3f * gloss.ContentXYZ[X,Y].x + 0.5f * gloss.ContentXYZ[X,Y].y + 0.2f * gloss.ContentXYZ[X,Y].z; } } } else { for ( int Y=0; Y < H; Y++ ) for ( int X=0; X < W; X++ ) diffuse.ContentXYZ[X,Y].w = 1.0f; } // Save diffuse as target diffuse.HasAlpha = gloss != null; diffuse.Save( targetFileName ); } // Save optimized filename + timestamps of source files m_diffuseFileName.Refresh(); if ( m_glossFileName != null ) m_glossFileName.Refresh(); m_optimizedDiffuseGlossFileName = targetFileName; m_diffuseTimeAtGeneration = m_diffuseFileName.LastWriteTime; m_glossTimeAtGeneration = m_glossFileName != null ? m_glossFileName.LastWriteTime : DateTime.MinValue; }
/// <summary> /// Generates a (diffuse+gloss) texture from 2 distinct textures /// </summary> public void GenerateDiffuseGlossTexture() { if (m_diffuse == null) { throw new Exception("Invalid source diffuse image file!"); } if (!m_diffuse.m_fileName.Exists) { throw new Exception("Source diffuse image file \"" + m_diffuse.m_fileName.FullName + "\" does not exist on disk!"); } FileInfo targetFileName = GetDiffuseGlossTextureFileName(); using (ImageUtility.Bitmap diffuse = new ImageUtility.Bitmap(m_diffuse.m_fileName)) { int W = diffuse.Width; int H = diffuse.Height; ImageUtility.Bitmap gloss = null; if (m_gloss != null) { gloss = new ImageUtility.Bitmap(m_gloss.m_fileName); // int gW = gloss.Width; // int gH = gloss.Height; // for ( int Y=0; Y < gH; Y++ ) // for ( int X=0; X < gW; X++ ) { // gloss.ContentXYZ[X,Y].x = ImageUtility.ColorProfile.sRGB2Linear( gloss.ContentXYZ[X,Y].x ); // } } bool needsScale = false; if (gloss != null && (gloss.Width != W || gloss.Height != H)) { needsScale = true; } if (gloss != null) { if (needsScale) { // Set gloss as alpha with re-scaling int W2 = gloss.Width; int H2 = gloss.Height; float[,] source = new float[W2, H2]; for (int Y = 0; Y < H2; Y++) { for (int X = 0; X < W2; X++) { source[X, Y] = 0.3f * gloss.ContentXYZ[X, Y].x + 0.5f * gloss.ContentXYZ[X, Y].y + 0.2f * gloss.ContentXYZ[X, Y].z; } } // Downscale first while (W2 > W) { int halfW2 = W2 >> 1; float[,] temp = new float[halfW2, H2]; for (int Y = 0; Y < H2; Y++) { for (int X = 0; X < halfW2; X++) { temp[X, Y] = 0.5f * (source[2 * X + 0, Y] + source[2 * X + 1, Y]); } } source = temp; W2 = halfW2; } while (H2 > H) { int halfH2 = H2 >> 1; float[,] temp = new float[W2, halfH2]; for (int Y = 0; Y < halfH2; Y++) { for (int X = 0; X < W2; X++) { temp[X, Y] = 0.5f * (source[X, 2 * Y + 0] + source[X, 2 * Y + 1]); } } source = temp; H2 = halfH2; } // Upscale then while (W2 < W) { int doubleW2 = W2 << 1; float[,] temp = new float[doubleW2, H2]; for (int Y = 0; Y < H2; Y++) { for (int X = 0; X < W2; X++) { temp[2 * X + 0, Y] = source[X, Y]; temp[2 * X + 1, Y] = 0.5f * (source[X, Y] + source[Math.Min(W2 - 1, X + 1), Y]); } } source = temp; W2 = doubleW2; } while (H2 < H) { int doubleH2 = H2 << 1; float[,] temp = new float[W2, doubleH2]; for (int Y = 0; Y < H2; Y++) { for (int X = 0; X < W2; X++) { temp[X, 2 * Y + 0] = source[X, Y]; temp[X, 2 * Y + 1] = 0.5f * (source[X, Y] + source[X, Math.Min(H2 - 1, Y + 1)]); } } source = temp; H2 = doubleH2; } for (int Y = 0; Y < H; Y++) { for (int X = 0; X < W; X++) { diffuse.ContentXYZ[X, Y].w = source[X, Y]; } } } else { // Set gloss as alpha without re-scaling for (int Y = 0; Y < H; Y++) { for (int X = 0; X < W; X++) { // diffuse.ContentXYZ[X,Y].w = gloss.ContentXYZ[X,Y].x; diffuse.ContentXYZ[X, Y].w = 0.3f * gloss.ContentXYZ[X, Y].x + 0.5f * gloss.ContentXYZ[X, Y].y + 0.2f * gloss.ContentXYZ[X, Y].z; } } } } else { for (int Y = 0; Y < H; Y++) { for (int X = 0; X < W; X++) { diffuse.ContentXYZ[X, Y].w = 1.0f; } } } // Save diffuse as target diffuse.HasAlpha = gloss != null; diffuse.Save(targetFileName); } // Save optimized filename + timestamps of source files m_diffuseFileName.Refresh(); if (m_glossFileName != null) { m_glossFileName.Refresh(); } m_optimizedDiffuseGlossFileName = targetFileName; m_diffuseTimeAtGeneration = m_diffuseFileName.LastWriteTime; m_glossTimeAtGeneration = m_glossFileName != null ? m_glossFileName.LastWriteTime : DateTime.MinValue; }
private void Generate() { try { tabControlGenerators.Enabled = false; ////////////////////////////////////////////////////////////////////////// // 1] Apply bilateral filtering to the input texture as a pre-process ApplyBilateralFiltering(m_TextureSource, m_TextureTarget0, floatTrackbarControlBilateralRadius.Value, floatTrackbarControlBilateralTolerance.Value, checkBoxWrap.Checked); ////////////////////////////////////////////////////////////////////////// // 2] Compute directional occlusion m_TextureTarget1.RemoveFromLastAssignedSlots(); // Prepare computation parameters m_TextureTarget0.SetCS(0); m_TextureTarget1.SetCSUAV(0); m_SB_Rays.SetInput(1); m_CB_Input.m.RaysCount = (UInt32)Math.Min(MAX_THREADS, integerTrackbarControlRaysCount.Value); m_CB_Input.m.MaxStepsCount = (UInt32)integerTrackbarControlMaxStepsCount.Value; m_CB_Input.m.Tile = (uint)(checkBoxWrap.Checked ? 1 : 0); m_CB_Input.m.TexelSize_mm = TextureSize_mm / Math.Max(W, H); m_CB_Input.m.Displacement_mm = TextureHeight_mm; // Start if (!m_CS_GenerateSSBumpMap.Use()) { throw new Exception("Can't generate self-shadowed bump map as compute shader failed to compile!"); } int h = Math.Max(1, MAX_LINES * 1024 / W); int CallsCount = (int)Math.Ceiling((float)H / h); for (int i = 0; i < CallsCount; i++) { m_CB_Input.m.Y0 = (UInt32)(i * h); m_CB_Input.UpdateData(); m_CS_GenerateSSBumpMap.Dispatch(W, h, 1); m_Device.Present(true); progressBar.Value = (int)(0.01f * (BILATERAL_PROGRESS + (100 - BILATERAL_PROGRESS) * (i + 1) / (CallsCount)) * progressBar.Maximum); // for ( int a=0; a < 10; a++ ) Application.DoEvents(); } m_TextureTarget1.RemoveFromLastAssignedSlotUAV(); // So we can use it as input for next stage progressBar.Value = progressBar.Maximum; // Compute in a single shot (this is madness!) // m_CB_Input.m.y = 0; // m_CB_Input.UpdateData(); // m_CS_GenerateSSBumpMap.Dispatch( W, H, 1 ); ////////////////////////////////////////////////////////////////////////// // 3] Copy target to staging for CPU readback and update the resulting bitmap m_TextureTarget_CPU.CopyFrom(m_TextureTarget1); if (m_BitmapResult != null) { m_BitmapResult.Dispose(); } m_BitmapResult = null; m_BitmapResult = new ImageUtility.Bitmap(W, H, m_LinearProfile); m_BitmapResult.HasAlpha = true; RendererManaged.PixelsBuffer Pixels = m_TextureTarget_CPU.Map(0, 0); using (System.IO.BinaryReader R = Pixels.OpenStreamRead()) for (int Y = 0; Y < H; Y++) { R.BaseStream.Position = Y * Pixels.RowPitch; for (int X = 0; X < W; X++) { ImageUtility.float4 Color = new ImageUtility.float4(R.ReadSingle(), R.ReadSingle(), R.ReadSingle(), R.ReadSingle()); Color = m_LinearProfile.RGB2XYZ(Color); m_BitmapResult.ContentXYZ[X, Y] = Color; } } Pixels.Dispose(); m_TextureTarget_CPU.UnMap(0, 0); // Assign result viewportPanelResult.Image = m_BitmapResult; } catch (Exception _e) { MessageBox("An error occurred during generation!\r\n\r\nDetails: ", _e); } finally { tabControlGenerators.Enabled = true; } }
/// <summary> /// Reads image size and other informations, doesn't read content /// </summary> public void ReadImageInfos() { m_usage = FindUsage( m_fileName ); if ( m_fileName.Extension.ToLower().StartsWith( ".bimage" ) ) { // Can't read! m_fileType = FILE_TYPE.BIMAGE; m_couldBeRead = false; return; } try { using ( ImageUtility.Bitmap B = new ImageUtility.Bitmap( m_fileName ) ) { m_error = null; m_couldBeRead = true; m_width = B.Width; m_height = B.Height; switch ( B.Type ) { case ImageUtility.Bitmap.FILE_TYPE.PNG: m_fileType = FILE_TYPE.PNG; break; case ImageUtility.Bitmap.FILE_TYPE.TGA: m_fileType = FILE_TYPE.TGA; break; // case ImageUtility.Bitmap.FILE_TYPE.DDS: m_fileType = FILE_TYPE.DDS; break; // DDS not supported? case ImageUtility.Bitmap.FILE_TYPE.JPEG: m_fileType = FILE_TYPE.JPG; break; case ImageUtility.Bitmap.FILE_TYPE.TIFF: m_fileType = FILE_TYPE.TIFF; break; } } } catch ( Exception _e ) { m_couldBeRead = false; m_error = _e; throw _e; } }
static void SaveTestsRGB() { ImageUtility.ColorProfile Profile_sRGB = new ImageUtility.ColorProfile( ImageUtility.ColorProfile.STANDARD_PROFILE.sRGB ); ImageUtility.ColorProfile Profile_Linear = new ImageUtility.ColorProfile( ImageUtility.ColorProfile.Chromaticities.sRGB, ImageUtility.ColorProfile.GAMMA_CURVE.STANDARD, 1.0f ); ImageUtility.Bitmap Gray_sRGB = new ImageUtility.Bitmap( 64, 64, Profile_sRGB ); ImageUtility.Bitmap Gray_Linear = new ImageUtility.Bitmap( 64, 64, Profile_Linear ); ImageUtility.Bitmap Gradient_sRGB = new ImageUtility.Bitmap( 128, 16, Profile_sRGB ); ImageUtility.Bitmap Gradient_Linear = new ImageUtility.Bitmap( 128, 16, Profile_Linear ); for ( int Y=0; Y < Gray_sRGB.Height; Y++ ) for ( int X=0; X < Gray_sRGB.Width; X++ ) { Gray_sRGB.ContentXYZ[X,Y] = Profile_Linear.RGB2XYZ( new ImageUtility.float4( 0.5f, 0.5f, 0.5f, 1.0f ) ); Gray_Linear.ContentXYZ[X,Y] = Profile_Linear.RGB2XYZ( new ImageUtility.float4( 0.5f, 0.5f, 0.5f, 1.0f ) ); } int W = Gradient_sRGB.Width; for ( int Y=0; Y < Gradient_sRGB.Height; Y++ ) for ( int X=0; X < Gradient_sRGB.Width; X++ ) { float C = (float) (X+0.5f) / W; Gradient_sRGB.ContentXYZ[X,Y] = Profile_Linear.RGB2XYZ( new ImageUtility.float4( C, C, C, 1.0f ) ); Gradient_Linear.ContentXYZ[X,Y] = Profile_Linear.RGB2XYZ( new ImageUtility.float4( C, C, C, 1.0f ) ); } Gray_sRGB.Save( new FileInfo( "./Gray128_sRGB.png" ) ); Gray_Linear.Save( new FileInfo( "./Gray128_Linear.png" ) ); Gradient_sRGB.Save( new FileInfo( "./Gradient_sRGB.png" ) ); Gradient_Linear.Save( new FileInfo( "./Gradient_Linear.png" ) ); }
private void buttonTestBilateral_Click( object sender, EventArgs e ) { try { panelParameters.Enabled = false; ////////////////////////////////////////////////////////////////////////// // 1] Apply bilateral filtering to the input texture as a pre-process ApplyBilateralFiltering( m_textureSourceHeightMap, m_textureTarget0, floatTrackbarControlBilateralRadius.Value, floatTrackbarControlBilateralTolerance.Value, checkBoxWrap.Checked, 100 ); progressBar.Value = progressBar.Maximum; ////////////////////////////////////////////////////////////////////////// // 2] Copy target to staging for CPU readback and update the resulting bitmap m_textureTarget_CPU.CopyFrom( m_textureTarget0 ); ImageUtility.Bitmap tempBitmap = new ImageUtility.Bitmap( W, H ); Renderer.PixelsBuffer Pixels = m_textureTarget_CPU.Map( 0, 0 ); using ( System.IO.BinaryReader R = Pixels.OpenStreamRead() ) for ( uint Y=0; Y < H; Y++ ) { R.BaseStream.Position = Y * Pixels.RowPitch; for ( uint X=0; X < W; X++ ) { float AO = R.ReadSingle(); tempBitmap[X,Y] = new float4( AO, AO, AO, 1 ); } } Pixels.Dispose(); m_textureTarget_CPU.UnMap( 0, 0 ); // Convert to RGB // ImageUtility.ColorProfile Profile = m_ProfilesRGB; // AO maps are sRGB! (although strange, that's certainly to have more range in dark values) ImageUtility.ColorProfile Profile = m_profilesRGB; // AO maps are sRGB! (although strange, that's certainly to have more range in dark values) if ( m_imageResult != null ) m_imageResult.Dispose(); m_imageResult = new ImageUtility.ImageFile(); tempBitmap.ToImageFile( m_imageResult, Profile ); // Assign result viewportPanelResult.Bitmap = m_imageResult.AsCustomBitmap( ( ref float4 _color ) => {} ); } catch ( Exception _e ) { MessageBox( "An error occurred during generation!\r\n\r\nDetails: ", _e ); } finally { panelParameters.Enabled = true; } }