Exemplo n.º 1
0
        void    Plot(int _roughnessIndex, int _row, int _column, ImageFile _image, PanelOutput _panel, Label _label)
        {
            _image.Clear(float4.One);

            // Determine range first
            int W = m_results.GetLength(0);

            float[] values = new float[W];
            float   min    = float.MaxValue;
            float   max    = -float.MaxValue;

            for (int X = 0; X < W; X++)
            {
                LTC    ltc  = m_results[_roughnessIndex, X];
                double term = ltc.invM[_row, _column];
                term     /= ltc.invM[1, 1];                             // Normalize by central term
                values[X] = (float)term;
                min       = Math.Min(min, values[X]);
                max       = Math.Max(max, values[X]);
            }
            float2 rangeY = float2.UnitY;

            rangeY.x = Math.Min(rangeY.x, min);
            rangeY.y = Math.Max(rangeY.y, max);

            // Plot graph and update UI
            float2 rangeX = new float2(0, 1);

            _image.PlotGraph(new float4(0, 0, 0, 1), rangeX, rangeY, ( float _X ) => {
                int X = Math.Min(W - 1, (int)(W * _X));
                return(values[X]);
            });
            _image.PlotAxes(float4.UnitW, rangeX, rangeY, 0.1f, 0.1f * (rangeY.y - rangeY.x));
            _label.Text     = "Coefficient m" + _row + "" + _column + " - Range [" + rangeY.x + ", " + rangeY.y + "]";
            _panel.m_bitmap = _image.AsBitmap;
            _panel.Refresh();
        }
Exemplo n.º 2
0
        void TestBuildImage( BUILD_TESTS _type )
        {
            try {
                switch ( _type ) {
                    case BUILD_TESTS.FILL_PIXEL:
                        // Write pixel per pixel
                        m_imageFile = new ImageFile( 378, 237, ImageFile.PIXEL_FORMAT.RGB16, new ColorProfile( ColorProfile.STANDARD_PROFILE.sRGB ) );
                        for ( uint Y=0; Y < m_imageFile.Height; Y++ ) {
                            for ( uint X=0; X < m_imageFile.Width; X++ ) {
                                float	R = (float) (1+X) / m_imageFile.Width;
                                float	G = (float) (1+Y) / m_imageFile.Height;
                                m_imageFile[X,Y] = new float4( R, G, 1.0f-0.5f*(R+G), 1 );
                            }
                        }
                        break;

                    case BUILD_TESTS.FILL_SCANLINE:
                        // Write scanline per scanline
                        m_imageFile = new ImageFile( 378, 237, ImageFile.PIXEL_FORMAT.RGB16, new ColorProfile( ColorProfile.STANDARD_PROFILE.sRGB ) );
                        float4[]	scanline = new float4[m_imageFile.Width];
                        for ( uint Y=0; Y < m_imageFile.Height; Y++ ) {
                            for ( uint X=0; X < m_imageFile.Width; X++ ) {
                                float	R = 1.0f - (float) (1+X) / m_imageFile.Width;
                                float	G = (float) (1+Y) / m_imageFile.Height;
                                scanline[X].Set( R, G, 1.0f-0.5f*(R+G), 1 );
                            }

                            m_imageFile.WriteScanline( Y, scanline );
                        }
                        break;

                    // Buddhabrot
                    case BUILD_TESTS.ADDITIVE_BUDDHABROT: {
                        m_imageFile = new ImageFile( 378, 237, ImageFile.PIXEL_FORMAT.RGB16, new ColorProfile( ColorProfile.STANDARD_PROFILE.sRGB ) );
                        uint	W = m_imageFile.Width;
                        uint	H = m_imageFile.Height;
                        float2	Z, Z0;
                        int		iterations = 50;
                        float4	inc = (1.0f / iterations) * float4.One;
                        float	zoom = 2.0f;
                        float	invZoom = 1.0f / zoom;

            #if DIRECT_WRITE		// Either directly accumulate to image
                        for ( uint Y=0; Y < H; Y++ ) {
                            Z0.y = zoom * (Y - 0.5f * H) / H;
                            for ( uint X=0; X < W; X++ ) {
                                Z0.x = zoom * (X - 0.5f * W) / H;
                                Z = Z0;
                                for ( int i=0; i < iterations; i++ ) {
                                    Z.Set( Z.x*Z.x - Z.y*Z.y + Z0.x, 2.0f * Z.x * Z.y + Z0.y );

                                    int	Nx = (int) (invZoom * Z.x * H + 0.5f * W);
                                    int	Ny = (int) (invZoom * Z.y * H + 0.5f * H);
                                    if ( Nx >= 0 && Nx < W && Ny >= 0 && Ny < H ) {
            // 										float4	tagada = (float4) m_imageFile[(uint)Nx,(uint)Ny];
            // 										tagada += inc;
            // 										m_imageFile[(uint)Nx,(uint)Ny] = tagada;
             										m_imageFile.Add( (uint)Nx, (uint)Ny, inc );
                                    }
                                }
                            }
                        }
            #else						// Or accumulate to a temp array and write result (this is obviously faster!)
                        float[,]	accumulators = new float[W,H];
                        for ( uint Y=0; Y < H; Y++ ) {
                            Z0.y = zoom * (Y - 0.5f * H) / H;
                            for ( uint X=0; X < W; X++ ) {
                                Z0.x = zoom * (X - 0.5f * W) / H;
                                Z = Z0;
                                for ( int i=0; i < iterations; i++ ) {
                                    Z.Set( Z.x*Z.x - Z.y*Z.y + Z0.x, 2.0f * Z.x * Z.y + Z0.y );

                                    int	Nx = (int) (invZoom * Z.x * H + 0.5f * W);
                                    int	Ny = (int) (invZoom * Z.y * H + 0.5f * H);
                                    if ( Nx >= 0 && Nx < W && Ny >= 0 && Ny < H )
                                        accumulators[Nx, Ny] += inc.x;
                                }
                            }
                        }
                        float4	temp = new float4();
                        for ( uint Y=0; Y < H; Y++ ) {
                            for ( uint X=0; X < W; X++ ) {
                                float	a = accumulators[X,Y];
                                temp.Set( a, a, a, 1 );
                                m_imageFile[X,Y] = temp;
                            }
                        }
            #endif
                    }
                    break;

                    case BUILD_TESTS.STRESS_BUILD:

                        ImageFile.PIXEL_FORMAT[]	formats = new ImageFile.PIXEL_FORMAT[] {
                            ImageFile.PIXEL_FORMAT.R8,
                            ImageFile.PIXEL_FORMAT.RG8,
                            ImageFile.PIXEL_FORMAT.RGB8,
                            ImageFile.PIXEL_FORMAT.RGBA8,
                            ImageFile.PIXEL_FORMAT.R16,
            //							ImageFile.PIXEL_FORMAT.RG16,
                            ImageFile.PIXEL_FORMAT.RGB16,
                            ImageFile.PIXEL_FORMAT.RGBA16,
                            ImageFile.PIXEL_FORMAT.R16F,
                            ImageFile.PIXEL_FORMAT.RG16F,
                            ImageFile.PIXEL_FORMAT.RGB16F,
                            ImageFile.PIXEL_FORMAT.RGBA16F,
                            ImageFile.PIXEL_FORMAT.R32F,
            //							ImageFile.PIXEL_FORMAT.RG32F,
                            ImageFile.PIXEL_FORMAT.RGB32F,
                            ImageFile.PIXEL_FORMAT.RGBA32F,
                        };
                        Random	RNG = new Random( 1 );
                        for ( int i=0; i < 1000; i++ ) {

                            uint	W = 100 + (uint) (1000 * RNG.NextDouble());
                            uint	H = 100 + (uint) (1000 * RNG.NextDouble());
                            ImageFile.PIXEL_FORMAT	format = formats[RNG.Next( formats.Length-1 )];
                            m_imageFile = new ImageFile( W, H,format, new ColorProfile( ColorProfile.STANDARD_PROFILE.sRGB ) );
                            m_imageFile.Dispose();
                        }

                        m_imageFile = new ImageFile( 1000, 1000, ImageFile.PIXEL_FORMAT.RGBA8, new ColorProfile( ColorProfile.STANDARD_PROFILE.sRGB ) );
                        m_imageFile.Clear( new float4( 0, 1, 0.2f, 1 ) );
                        break;

                }

                panelBuild.Bitmap = m_imageFile.AsBitmap;

            } catch ( Exception _e ) {
                MessageBox.Show( "Error: " + _e.Message );
            }
        }
Exemplo n.º 3
0
        protected void TestConvertLDR2HDR( System.IO.FileInfo[] _LDRImageFileNames, bool _responseCurveOnly, bool _RAW )
        {
            try {

                // Load the LDR images
                List< ImageFile >	LDRImages = new List< ImageFile >();
                foreach ( System.IO.FileInfo LDRImageFileName in _LDRImageFileNames )
                    LDRImages.Add( new ImageFile( LDRImageFileName ) );

                // Retrieve the shutter speeds
                List< float >	shutterSpeeds = new List< float >();
                foreach ( ImageFile LDRImage in LDRImages ) {
                    shutterSpeeds.Add( LDRImage.Metadata.ExposureTime );
                }

                // Retrieve filter type
                Bitmap.FILTER_TYPE	filterType = Bitmap.FILTER_TYPE.NONE;
                if ( radioButtonFilterNone.Checked ) {
                     filterType = Bitmap.FILTER_TYPE.NONE;
                } else if ( radioButtonFilterGaussian.Checked ) {
                     filterType = Bitmap.FILTER_TYPE.SMOOTHING_GAUSSIAN;
                } else if ( radioButtonFilterGaussian2Pass.Checked ) {
                     filterType = Bitmap.FILTER_TYPE.SMOOTHING_GAUSSIAN_2_PASSES;
                } else if ( radioButtonFilterTent.Checked ) {
                     filterType = Bitmap.FILTER_TYPE.SMOOTHING_TENT;
                } else if ( radioButtonFilterCurveFitting.Checked ) {
                     filterType = Bitmap.FILTER_TYPE.GAUSSIAN_PLUS_CURVE_FITTING;
                }

            // Check EXR save is working!
            // ImageFile	pipo = new ImageFile();
            // 			pipo.ConvertFrom( LDRImages[0], ImageFile.PIXEL_FORMAT.RGB32F );
            // pipo.Save( new System.IO.FileInfo( @"..\..\Images\Out\LDR2HDR\FromJPG\Result.exr" ), ImageFile.FILE_FORMAT.EXR, ImageFile.SAVE_FLAGS.SF_EXR_DEFAULT );

            // Check bitmap->tone mapped image file is working
            // {
            // 	Bitmap	tempBitmap = new Bitmap();
            // 	List< float >	responseCurve = new List< float >( 256 );
            // 	for ( int i=0; i < 256; i++ )
            // 		responseCurve.Add( (float) (Math.Log( (1+i) / 256.0 ) / Math.Log(2)) );
            // 	tempBitmap.LDR2HDR( new ImageFile[] { LDRImages[4] }, new float[] { 1.0f }, responseCurve, 1.0f );
            //
            // 	ImageFile	tempHDR = new ImageFile();
            // 	tempBitmap.ToImageFile( tempHDR, new ColorProfile( ColorProfile.STANDARD_PROFILE.LINEAR ) );
            //
            // 	ImageFile	tempToneMappedHDR = new ImageFile();
            // 	tempToneMappedHDR.ToneMapFrom( tempHDR,( float3 _HDRColor, ref float3 _LDRColor ) => {
            // 		// Just do gamma un-correction, don't care about actual HDR range...
            // 		_LDRColor.x = (float) Math.Pow( Math.Max( 0.0f, _HDRColor.x ), 1.0f / 2.2f );	// Here we need to clamp negative values that we sometimes get in EXR format
            // 		_LDRColor.y = (float) Math.Pow( Math.Max( 0.0f, _HDRColor.y ), 1.0f / 2.2f );	//  (must be coming from the log encoding I suppose)
            // 		_LDRColor.z = (float) Math.Pow( Math.Max( 0.0f, _HDRColor.z ), 1.0f / 2.2f );
            // 	} );
            //
            // 	panel1.Bitmap = tempToneMappedHDR.AsBitmap;
            // 	return;
            // }

                //////////////////////////////////////////////////////////////////////////////////////////////
                // Build the HDR device-independent bitmap
            //				uint bitsPerPixel = _RAW ? 12U : 8U;
            uint	bitsPerPixel = _RAW ? 8U : 8U;
            float	quality = _RAW ? 3.0f : 3.0f;

                Bitmap.HDRParms	parms = new Bitmap.HDRParms() {
                    _inputBitsPerComponent = bitsPerPixel,
                    _luminanceFactor = 1.0f,
                    _curveSmoothnessConstraint = 1.0f,
                    _quality = quality,
                    _responseCurveFilterType = filterType
                };

                ImageUtility.Bitmap	HDRImage = new ImageUtility.Bitmap();

            //				HDRImage.LDR2HDR( LDRImages.ToArray(), shutterSpeeds.ToArray(), parms );

                // Compute response curve
                List< float >	responseCurve = new List< float >();
                Bitmap.ComputeCameraResponseCurve( LDRImages.ToArray(), shutterSpeeds.ToArray(), parms._inputBitsPerComponent, parms._curveSmoothnessConstraint, parms._quality, responseCurve );

                // Filter
                List< float >	responseCurve_filtered = new List< float >();
            //				Bitmap.FilterCameraResponseCurve( responseCurve, responseCurve_filtered, Bitmap.FILTER_TYPE.CURVE_FITTING );
                Bitmap.FilterCameraResponseCurve( responseCurve, responseCurve_filtered, filterType );

            // 				using ( System.IO.FileStream S = new System.IO.FileInfo( "../../responseCurve3.float" ).Create() )
            // 					using ( System.IO.BinaryWriter W = new System.IO.BinaryWriter( S ) ) {
            // 						for ( int i=0; i < 256; i++ )
            // 							W.Write( responseCurve[i] );
            // 					}

                // Write info
                string		info = "Exposures:\r\n";
                foreach ( float shutterSpeed in shutterSpeeds )
                    info += " " + shutterSpeed + "s + ";
                info += "\r\nLog2 exposures (EV):\r\n";
                foreach ( float shutterSpeed in shutterSpeeds )
                    info += " " + (float) (Math.Log( shutterSpeed ) / Math.Log(2)) + "EV + ";
                info += "\r\n\r\n";

                if ( _responseCurveOnly ) {
                    //////////////////////////////////////////////////////////////////////////////////////////////
                    // Render the response curve as a graph
             					ImageFile	tempCurveBitmap = new ImageFile( 1024, 768, ImageFile.PIXEL_FORMAT.RGB8, new ColorProfile( ColorProfile.STANDARD_PROFILE.sRGB ) );

                    int			responseCurveSizeMax = responseCurve.Count-1;

                    float2		rangeX = new float2( 0, responseCurveSizeMax+1 );
                    float2		rangeY = new float2( 0, 500 );
                    tempCurveBitmap.Clear( new float4( 1, 1, 1, 1 ) );
            //					tempCurveBitmap.PlotGraphAutoRangeY( red, rangeX, ref rangeY, ( float x ) => {
                    tempCurveBitmap.PlotGraph( red, rangeX, rangeY, ( float x ) => {
                        int		i0 = (int) Math.Min( responseCurveSizeMax, Math.Floor( x ) );
                        int		i1 = (int) Math.Min( responseCurveSizeMax, i0+1 );
                        float	g0 = responseCurve[i0];
                        float	g1 = responseCurve[i1];
                        float	t = x - i0;
            //						return g0 + (g1-g0) * t;
                        return (float) Math.Pow( 2.0f, g0 + (g1-g0) * t );
                    } );
                    tempCurveBitmap.PlotGraph( blue, rangeX, rangeY, ( float x ) => {
                        int		i0 = (int) Math.Min( responseCurveSizeMax, Math.Floor( x ) );
                        int		i1 = (int) Math.Min( responseCurveSizeMax, i0+1 );
                        float	g0 = responseCurve_filtered[i0];
                        float	g1 = responseCurve_filtered[i1];
                        float	t = x - i0;
            //						return g0 + (g1-g0) * t;
                        return (float) Math.Pow( 2.0f, g0 + (g1-g0) * t );
                    } );
            //					tempCurveBitmap.PlotAxes( black, rangeX, rangeY, 8, 2 );

                    info += "• Linear range Y = [" + rangeY.x + ", " + rangeY.y + "]\r\n";

                    rangeY = new float2( -4, 4 );
                    tempCurveBitmap.PlotLogGraphAutoRangeY( black, rangeX, ref rangeY, ( float x ) => {
            //					tempCurveBitmap.PlotLogGraph( black, rangeX, rangeY, ( float x ) => {
                        int		i0 = (int) Math.Min( responseCurveSizeMax, Math.Floor( x ) );
                        int		i1 = (int) Math.Min( responseCurveSizeMax, i0+1 );
                        float	g0 = responseCurve[i0];
                        float	g1 = responseCurve[i1];
                        float	t = x - i0;
            //						return g0 + (g1-g0) * t;
                        return (float) Math.Pow( 2.0f, g0 + (g1-g0) * t );
                    }, -1.0f, 2.0f );
                    tempCurveBitmap.PlotLogGraph( blue, rangeX, rangeY, ( float x ) => {
            //					tempCurveBitmap.PlotLogGraph( black, rangeX, rangeY, ( float x ) => {
                        int		i0 = (int) Math.Min( responseCurveSizeMax, Math.Floor( x ) );
                        int		i1 = (int) Math.Min( responseCurveSizeMax, i0+1 );
                        float	g0 = responseCurve_filtered[i0];
                        float	g1 = responseCurve_filtered[i1];
                        float	t = x - i0;
            //						return g0 + (g1-g0) * t;
                        return (float) Math.Pow( 2.0f, g0 + (g1-g0) * t );
                    }, -1.0f, 2.0f );
                    tempCurveBitmap.PlotLogAxes( black, rangeX, rangeY, -16, 2 );

                    info += "• Log2 range Y = [" + rangeY.x + ", " + rangeY.y + "]\r\n";

             					panelOutputHDR.Bitmap = tempCurveBitmap.AsBitmap;

                } else {
                    //////////////////////////////////////////////////////////////////////////////////////////////
                    // Recompose the HDR image
                    HDRImage.LDR2HDR( LDRImages.ToArray(), shutterSpeeds.ToArray(), responseCurve_filtered, 1.0f );

                    // Display as a tone-mapped bitmap
                    ImageFile	tempHDR = new ImageFile();
                    HDRImage.ToImageFile( tempHDR, new ColorProfile( ColorProfile.STANDARD_PROFILE.LINEAR ) );

                    if ( _RAW ) {
                        tempHDR.Save( new System.IO.FileInfo( @"..\..\Images\Out\LDR2HDR\FromRAW\Result.exr" ), ImageFile.FILE_FORMAT.EXR, ImageFile.SAVE_FLAGS.SF_EXR_DEFAULT );
                        tempHDR.Save( new System.IO.FileInfo( @"..\..\Images\Out\LDR2HDR\FromRAW\Result_B44LC.exr" ), ImageFile.FILE_FORMAT.EXR, ImageFile.SAVE_FLAGS.SF_EXR_B44 | ImageFile.SAVE_FLAGS.SF_EXR_LC );
                        tempHDR.Save( new System.IO.FileInfo( @"..\..\Images\Out\LDR2HDR\FromRAW\Result_noLZW.exr" ), ImageFile.FILE_FORMAT.EXR, ImageFile.SAVE_FLAGS.SF_EXR_NONE );
                    } else {
                        tempHDR.Save( new System.IO.FileInfo( @"..\..\Images\Out\LDR2HDR\FromJPG\Result.exr" ), ImageFile.FILE_FORMAT.EXR, ImageFile.SAVE_FLAGS.SF_EXR_DEFAULT );
                        tempHDR.Save( new System.IO.FileInfo( @"..\..\Images\Out\LDR2HDR\FromJPG\Result_B44LC.exr" ), ImageFile.FILE_FORMAT.EXR, ImageFile.SAVE_FLAGS.SF_EXR_B44 | ImageFile.SAVE_FLAGS.SF_EXR_LC );
                        tempHDR.Save( new System.IO.FileInfo( @"..\..\Images\Out\LDR2HDR\FromJPG\Result_noLZW.exr" ), ImageFile.FILE_FORMAT.EXR, ImageFile.SAVE_FLAGS.SF_EXR_NONE );
                    }

                    ImageFile	tempToneMappedHDR = new ImageFile();
                    tempToneMappedHDR.ToneMapFrom( tempHDR,( float3 _HDRColor, ref float3 _LDRColor ) => {
                        // Just do gamma un-correction, don't care about actual HDR range...
                        _LDRColor.x = (float) Math.Pow( Math.Max( 0.0f, _HDRColor.x ), 1.0f / 2.2f );	// Here we need to clamp negative values that we sometimes get in EXR format
                        _LDRColor.y = (float) Math.Pow( Math.Max( 0.0f, _HDRColor.y ), 1.0f / 2.2f );	//  (must be coming from the log encoding I suppose)
                        _LDRColor.z = (float) Math.Pow( Math.Max( 0.0f, _HDRColor.z ), 1.0f / 2.2f );
                    } );

                    panelOutputHDR.Bitmap = tempToneMappedHDR.AsBitmap;
                }

                textBoxHDR.Text = info;

            } catch ( Exception _e ) {
                MessageBox.Show( "Error: " + _e.Message );

            // Show debug image
            // panelLoad.Bitmap = Bitmap.DEBUG.AsBitmap;
            }
        }
Exemplo n.º 4
0
        void    UpdateGraph1D()
        {
            double time = (DateTime.Now - m_startTime).TotalSeconds;

            if (checkBoxGPU.Checked)
            {
                UpdateGraph1D_GPU(time);
                return;
            }

            TestTransform1D(time);

            m_image.Clear(float4.One);

            float2 rangeX = new float2(0.0f, SIGNAL_SIZE);
            float2 rangeY = new float2(-1, 1);

            // Plot input signal
            if (checkBoxShowInput.Checked)
            {
//				m_image.PlotGraphAutoRangeY( m_black, rangeX, ref rangeY, ( float x ) => {
                m_image.PlotGraph(m_black, rangeX, rangeY, ( float x ) => {
                    int X = Math.Max(0, Math.Min(SIGNAL_SIZE - 1, (int)x));
                    return((float)m_signalSource[X].r);
                });
            }

            // Plot reconstructed signals (Real and Imaginary parts)
            if (checkBoxShowReconstructedSignal.Checked)
            {
                m_image.PlotGraph(m_red, rangeX, rangeY, ( float x ) => {
                    int X = Math.Max(0, Math.Min(SIGNAL_SIZE - 1, (int)x));
                    return((float)m_signalReconstructed[X].r);
                });
                m_image.PlotGraph(m_blue, rangeX, rangeY, ( float x ) => {
                    int X = Math.Max(0, Math.Min(SIGNAL_SIZE - 1, (int)x));
                    return((float)m_signalReconstructed[X].i);
                });
            }

            m_image.PlotAxes(m_black, rangeX, rangeY, 16.0f, 0.1f);

            //////////////////////////////////////////////////////////////////////////
            // Render spectrum as (Real=Red, Imaginary=Blue) vertical lines for each frequency
            float2 cornerMin = m_image.RangedCoordinates2ImageCoordinates(rangeX, rangeY, new float2(rangeX.x, -1.0f));
            float2 cornerMax = m_image.RangedCoordinates2ImageCoordinates(rangeX, rangeY, new float2(rangeX.y, +1.0f));
            float2 delta     = cornerMax - cornerMin;
            float  zeroY     = cornerMin.y + 0.5f * delta.y;

            float2 Xr0 = new float2(0, zeroY);
            float2 Xr1 = new float2(0, 0);
            float2 Xi0 = new float2(0, zeroY);
            float2 Xi1 = new float2(0, 0);

            float scale = 10.0f;

            float4 spectrumColorRe = new float4(1, 0.25f, 0, 1);
            float4 spectrumColorIm = new float4(0, 0.5f, 1, 1);
            int    size            = m_spectrum.Length;
            int    halfSize        = size >> 1;

            for (int i = 0; i < m_spectrum.Length; i++)
            {
                float X = cornerMin.x + i * delta.x / m_spectrum.Length;
//				int		frequencyIndex = i;							// Show spectrum as output by FFT
                int frequencyIndex = (i + halfSize) % size;                                     // Show offset spectrum with DC term in the middle
                Xr0.x = X;
                Xr1.x = X;
                Xr1.y = cornerMin.y + 0.5f * (scale * (float)m_spectrum[frequencyIndex].r + 1.0f) * delta.y;
                Xi0.x = X + 1;
                Xi1.x = X + 1;
                Xi1.y = cornerMin.y + 0.5f * (scale * (float)m_spectrum[frequencyIndex].i + 1.0f) * delta.y;

                m_image.DrawLine(spectrumColorRe, Xr0, Xr1);
                m_image.DrawLine(spectrumColorIm, Xi0, Xi1);
            }

            imagePanel.Bitmap = m_image.AsBitmap;
        }
Exemplo n.º 5
0
        void    CreateNoiseSpectrum(Complex[,] _spectrum)
        {
            uint size     = m_handMadeBlueNoise.Width;
            uint halfSize = size >> 1;

            double noiseScale        = floatTrackbarControlScale.Value;
            double noiseBias         = floatTrackbarControlOffset.Value;
            double radialOffset      = floatTrackbarControlRadialOffset.Value;
            double radialScale       = floatTrackbarControlRadialScale.Value;
            double distributionPower = floatTrackbarControlDistributionPower.Value;
            double sigma             = floatTrackbarControlSigma.Value;

            Complex Cnoise = new Complex();

            for (uint Y = 0; Y < m_handMadeBlueNoise.Height; Y++)
            {
                uint Yoff = (Y + halfSize) & (size - 1);
                int  Yrel = (int)Y - (int)halfSize;
                for (uint X = 0; X < m_handMadeBlueNoise.Width; X++)
                {
                    uint Xoff = (X + halfSize) & (size - 1);
                    int  Xrel = (int)X - (int)halfSize;

                    // Fetch "center noise" from blue noise
//					Cnoise = output[(halfSize>>1) + (X & (halfSize-1)), (halfSize>>1) + (Y & (halfSize-1))];
//					Cnoise /= maxAverage;	// Center noise is already factored by the maximum average so we "renormalize it"

                    // Apply simple uniform noise
                    Cnoise.r = noiseScale * (Math.Pow(SimpleRNG.GetUniform(), distributionPower) - noiseBias);
                    Cnoise.i = noiseScale * (Math.Pow(SimpleRNG.GetUniform(), distributionPower) - noiseBias);

//                  Cnoise.r = noiseScale * (SimpleRNG.GetNormal() - noiseBias);
//                  Cnoise.i = noiseScale * (SimpleRNG.GetNormal() - noiseBias);

//                  Cnoise.r = noiseScale * (SimpleRNG.GetLaplace( 0, sigma ) - noiseBias);
//                  Cnoise.i = noiseScale * (SimpleRNG.GetLaplace( 0, sigma ) - noiseBias);

//                  Cnoise.r = 2.0 * SimpleRNG.GetNormal( 0, 1 ) - 1.0;
//                  Cnoise.i = 2.0 * SimpleRNG.GetNormal( 0, 1 ) - 1.0;

// Cnoise.r = 1.0;
// Cnoise.i = 0.0;

                    // Apply weighting by radial profile
                    int sqRadius = Xrel * Xrel + Yrel * Yrel;

                    // Use averaged radial profile extracted from the noise texture
//					int		radius = (int) Math.Max( 1, Math.Min( halfSize-1, Math.Sqrt( sqRadius ) ) );
//					double	profileFactor = m_radialSliceAverage_Smoothed[radius].r;
//profileFactor *= 2.0;

                    // Use the Mathematica hand-fitted curve
                    double profileFactor = RadialProfile(radialOffset + radialScale * Math.Sqrt(sqRadius) / halfSize);
//profileFactor *= 0.75 / 1;
//profileFactor *= 3.0;

//profileFactor *= Math.Sqrt( 2.0 );
//profileFactor *= 1.1;

                    Cnoise *= profileFactor;

//Cnoise = output[Xoff,Yoff];
//Cnoise *= Math.Exp( 0.01 * Math.Max( 0.0, radius - 128 ) );

                    _spectrum[Xoff, Yoff] = Cnoise;
                }
            }
            _spectrum[0, 0].Set(floatTrackbarControlDC.Value, 0.0);                     // Central value for constant term


                        #if COMPUTE_RADIAL_SLICE
            const int BUCKETS_COUNT = 1000;
            uint[]    noiseDistributionHistogram = new uint[BUCKETS_COUNT];
            for (uint i = 0; i < m_handMadeBlueNoise.Width * m_handMadeBlueNoise.Height; i++)
            {
                double noiseValue  = noiseScale * (Math.Pow(SimpleRNG.GetUniform(), distributionPower) - noiseBias);
                double noiseValue2 = noiseScale * (Math.Pow(SimpleRNG.GetUniform(), distributionPower) - noiseBias);
                noiseValue = Math.Abs(noiseValue + noiseValue2);

                noiseValue = Math.Pow(SimpleRNG.GetUniform(), 0.5);
                noiseValue = SimpleRNG.GetUniform() < 0.5 ? 0.5 * Math.Sqrt(1.0 - noiseValue) : 1.0 - 0.5 * Math.Sqrt(1.0 - noiseValue);

                int bucketIndex = Math.Min(BUCKETS_COUNT - 1, (int)Math.Floor(noiseValue * BUCKETS_COUNT));
                noiseDistributionHistogram[bucketIndex]++;
            }

//              for ( uint Y=0; Y < m_handMadeBlueNoise.Height; Y++ ) {
//                  uint	Yoff = (Y + halfSize) & (size-1);
//                  int		Yrel = (int) Y - (int) halfSize;
//                  for ( uint X=0; X < m_handMadeBlueNoise.Width; X++ ) {
//                      uint	Xoff = (X + halfSize) & (size-1);
//                      int		Xrel = (int) X - (int) halfSize;
//                      int		sqRadius = Xrel*Xrel + Yrel*Yrel;
//                      double	radius = Math.Sqrt( sqRadius ) / halfSize;
//                      if ( radius < 0.01f )
//                          continue;	// Avoid central values because of too much imprecision
//
//                      double	random = Math.Abs( _spectrum[Xoff,Yoff].r );
//                      double	profileAmplitude = RadialProfile( radius );
//                      double	normalizedRandom = random / profileAmplitude;
//
//                      int		bucketIndex = Math.Min( BUCKETS_COUNT-1, (int) Math.Floor( normalizedRandom * BUCKETS_COUNT ) );
//                      noiseDistributionHistogram[bucketIndex]++;
//                  }
//              }

            m_spectrumNoiseDistribution_Custom.Clear(float4.One);
            float2 rangeX = new float2(0, 1);
            float2 rangeY = new float2(0, 4.0f / BUCKETS_COUNT);
            m_spectrumNoiseDistribution_Custom.PlotGraph(float4.UnitW, rangeX, rangeY, ( float _x ) => {
                int bucketIndex = Math.Min(BUCKETS_COUNT - 1, (int)Math.Floor(_x * BUCKETS_COUNT));
                return((float)noiseDistributionHistogram[bucketIndex] / (m_blueNoise.Width * m_blueNoise.Height));
            });
                        #endif
        }
Exemplo n.º 6
0
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            try {
                                #if COMPUTE_RADIAL_SLICE
                m_blueNoise = new ImageFile(new System.IO.FileInfo(@"Images\Data\512_512\LDR_LLL1_0.png"));
//					m_blueNoise = new ImageFile( new System.IO.FileInfo( @"Images\BlueNoiseSpectrumCorners256.png" ) );
//					m_blueNoise = new ImageFile( new System.IO.FileInfo( @"BlueNoise512x512_VoidAndCluster.png" ) );
//					m_blueNoise = new ImageFile( new System.IO.FileInfo( @"BlueNoise256x256_VoidAndCluster.png" ) );

                uint size     = m_blueNoise.Width;
                uint halfSize = size >> 1;

                try {
                    m_device.Init(panelImageSpectrum.Handle, false, false);
                    m_FFT = new SharpMath.FFT.FFT2D_GPU(m_device, size);
                } catch (Exception) {
                }

                if (m_FFT == null)
                {
                    return;
                }

                m_blueNoiseSpectrum = new ImageFile(m_blueNoise.Width, m_blueNoise.Height, ImageFile.PIXEL_FORMAT.RGBA8, new ColorProfile(ColorProfile.STANDARD_PROFILE.sRGB));
                m_handMadeBlueNoise = new ImageFile(m_blueNoise.Width, m_blueNoise.Height, ImageFile.PIXEL_FORMAT.RGBA8, new ColorProfile(ColorProfile.STANDARD_PROFILE.sRGB));
                m_handMadeSpectrum  = new ImageFile(m_blueNoise.Width, m_blueNoise.Height, ImageFile.PIXEL_FORMAT.RGBA8, new ColorProfile(ColorProfile.STANDARD_PROFILE.sRGB));
                m_scanline          = new float4[m_blueNoise.Width];

                //////////////////////////////////////////////////////////////////////////
                // Apply FFT to blue noise
                Complex[,]      input  = new Complex[m_blueNoise.Width, m_blueNoise.Height];
                Complex[,]      output = new Complex[m_blueNoise.Width, m_blueNoise.Height];
                m_blueNoise.ReadPixels((uint X, uint Y, ref float4 _color) => { input[X, Y].Set(_color.x, 0); });
//                  for ( uint Y=0; Y < m_blueNoise.Height; Y++ ) {
//                      m_blueNoise.ReadScanline( Y, m_scanline );
//                      for ( uint X=0; X < m_blueNoise.Width; X++ )
//                          input[X,Y].Set( m_scanline[X].x, 0 );
//                  }
                m_FFT.FFT_Forward(input, output);

                //////////////////////////////////////////////////////////////////////////
                // Build the radial slice average
                Complex[] radialSliceAverage         = new Complex[halfSize];
                uint[]    radialSliceAverageCounters = new uint[halfSize];
                for (uint Y = 0; Y < m_blueNoise.Height; Y++)
                {
                    uint Yoff = (Y + halfSize) & (size - 1);
                    int  Yrel = (int)Y - (int)halfSize;
                    for (uint X = 0; X < m_blueNoise.Width; X++)
                    {
                        uint Xoff     = (X + halfSize) & (size - 1);
                        int  Xrel     = (int)X - (int)halfSize;
                        int  sqRadius = Xrel * Xrel + Yrel * Yrel;
                        if (sqRadius > (halfSize - 1) * (halfSize - 1))
                        {
                            continue;
                        }

                        int radius = (int)Math.Floor(Math.Sqrt(sqRadius));
//							radialSliceAverage[radius] += output[Xoff,Yoff];
                        radialSliceAverage[radius].r += Math.Abs(output[Xoff, Yoff].r);
                        radialSliceAverage[radius].i += Math.Abs(output[Xoff, Yoff].i);
                        radialSliceAverageCounters[radius]++;
                    }
                }
                for (int i = 0; i < halfSize; i++)
                {
//						radialSliceAverage[i].r = Math.Abs( radialSliceAverage[i].r );
                    radialSliceAverage[i] *= radialSliceAverageCounters[i] > 0 ? 1.0f / radialSliceAverageCounters[i] : 1.0f;
                }
                double minAverage = double.MaxValue;
                double maxAverage = double.MinValue;
                for (int i = 0; i < halfSize; i++)
                {
                    if (radialSliceAverage[i].r < 1e-12)
                    {
                        radialSliceAverage[i].r = 1e-12;
                    }
                    if (radialSliceAverage[i].i < 1e-12)
                    {
                        radialSliceAverage[i].i = 1e-12;
                    }

                    if (i > 1)
                    {
                        minAverage = Math.Min(minAverage, radialSliceAverage[i].r);
                        maxAverage = Math.Max(maxAverage, radialSliceAverage[i].r);
                    }
                }

                // Write it to disk
                using (System.IO.FileStream S = new System.IO.FileInfo("BlueNoiseRadialProfile255.complex").Create())
                    using (System.IO.BinaryWriter W = new System.IO.BinaryWriter(S)) {
                        for (int i = 1; i < radialSliceAverage.Length; i++)
                        {
                            W.Write(radialSliceAverage[i].r);
                            W.Write(radialSliceAverage[i].i);
                        }
                    }

                // Smooth it out
                m_radialSliceAverage_Smoothed = new Complex[halfSize];
                Complex average = new Complex();
                for (int i = 1; i < halfSize; i++)
                {
                    average.Zero();
                    for (int j = -4; j <= 4; j++)
                    {
                        average += radialSliceAverage[Math.Max(1, Math.Min(halfSize - 1, i + j))];
                    }
                    m_radialSliceAverage_Smoothed[i] = average / 9.0;
                }


                //////////////////////////////////////////////////////////////////////////
                // Build spectrum noise distribution histogram
                const int BUCKETS_COUNT = 1000;
                uint[]    noiseDistributionHistogram = new uint[BUCKETS_COUNT];
                for (uint Y = 0; Y < m_blueNoise.Height; Y++)
                {
                    uint Yoff = (Y + halfSize) & (size - 1);
                    int  Yrel = (int)Y - (int)halfSize;
                    for (uint X = 0; X < m_blueNoise.Width; X++)
                    {
                        uint   Xoff     = (X + halfSize) & (size - 1);
                        int    Xrel     = (int)X - (int)halfSize;
                        int    sqRadius = Xrel * Xrel + Yrel * Yrel;
                        double radius   = Math.Sqrt(sqRadius) / halfSize;
                        if (radius < 0.01f)
                        {
                            continue;                                           // Avoid central values because of too much imprecision
                        }
                        double random           = Math.Abs(output[Xoff, Yoff].r);
                        double profileAmplitude = RadialProfile(radius);
                        double normalizedRandom = random / profileAmplitude;

                        int bucketIndex = Math.Min(BUCKETS_COUNT - 1, (int)Math.Floor(normalizedRandom * BUCKETS_COUNT));
                        noiseDistributionHistogram[bucketIndex]++;
                    }
                }

                // Write histogram to disk
                using (System.IO.FileStream S = new System.IO.FileInfo("noiseDistribution.float").Create())
                    using (System.IO.BinaryWriter W = new System.IO.BinaryWriter(S)) {
                        for (int i = 0; i < BUCKETS_COUNT; i++)
                        {
                            W.Write((float)noiseDistributionHistogram[i] / BUCKETS_COUNT);
                        }
                    }


                //////////////////////////////////////////////////////////////////////////
                // Build the images

                // Initial Blue Noise Spectrum
                for (uint Y = 0; Y < m_blueNoiseSpectrum.Height; Y++)
                {
                    uint Yoff = (Y + halfSize) & (size - 1);

                    // Initial blue noise spectrum
                    for (uint X = 0; X < m_blueNoiseSpectrum.Width; X++)
                    {
                        uint Xoff = (X + halfSize) & (size - 1);

                        float R = (float)output[Xoff, Yoff].r;
                        float I = (float)output[Xoff, Yoff].i;

                        R *= 500.0f;
                        I *= 500.0f;
                        R  = Math.Abs(R);

                        m_scanline[X].Set(R, R, R, 1.0f);
                    }
                    m_blueNoiseSpectrum.WriteScanline(Y, m_scanline);
                }

                // =====================================================
                // Average radial slice
                m_spectrumRadialSlice = new ImageFile(m_blueNoise.Width, m_blueNoise.Height, ImageFile.PIXEL_FORMAT.RGBA8, new ColorProfile(ColorProfile.STANDARD_PROFILE.sRGB));
                m_spectrumRadialSlice.Clear(float4.One);
                float2 rangeX = new float2(0, halfSize);
//					float2	rangeY = new float2( -8, 0 );
//						m_spectrumRadialSlice.PlotLogGraphAutoRangeY( float4.UnitW, rangeX, ref rangeY, ( float _x ) => {
//                  m_spectrumRadialSlice.PlotLogGraph( new float4( 0, 0, 0, 1 ), rangeX, rangeY, ( float _x ) => {
//                          return (float) radialSliceAverage[(int) Math.Floor( _x )].r;
//                      }, -1.0f, 10.0f );
//
//                  m_spectrumRadialSlice.PlotLogGraph( new float4( 0, 0.5f, 0, 1 ), rangeX, rangeY, ( float _x ) => {
//                          return (float) RadialProfile( _x / halfSize );
//                      }, -1.0f, 10.0f );
//                  m_spectrumRadialSlice.PlotLogGraph( new float4( 0.25f, 0.25f, 0.25f, 1 ), rangeX, rangeY, ( float _x ) => { return (float) radialSliceAverage[(int) Math.Floor( _x )].i; }, -1.0f, 10.0f );
//                  m_spectrumRadialSlice.PlotLogGraph( new float4( 1, 0, 0, 1 ), rangeX, rangeY, ( float _x ) => { return (float) m_radialSliceAverage_Smoothed[(int) Math.Floor( _x )].r; }, -1.0f, 10.0f );
//                  m_spectrumRadialSlice.PlotLogGraph( new float4( 0, 0, 1, 1 ), rangeX, rangeY, ( float _x ) => { return (float) m_radialSliceAverage_Smoothed[(int) Math.Floor( _x )].i; }, -1.0f, 10.0f );
//                  m_spectrumRadialSlice.PlotLogAxes( float4.UnitW, rangeX, rangeY, -16.0f, 10.0f );

                float2 rangeY = new float2(0, 0.0005f);
                m_spectrumRadialSlice.PlotGraph(new float4(0, 0, 0, 1), rangeX, rangeY, ( float _x ) => {
                    return((float)radialSliceAverage[(int)Math.Floor(_x)].r);
                });

                m_spectrumRadialSlice.PlotGraph(new float4(0, 0.5f, 0, 1), rangeX, rangeY, ( float _x ) => {
                    return((float)RadialProfile(_x / halfSize));
                });

                m_spectrumRadialSlice.PlotGraph(new float4(0.25f, 0.25f, 0.25f, 1), rangeX, rangeY, ( float _x ) => { return((float)radialSliceAverage[(int)Math.Floor(_x)].i); });
                m_spectrumRadialSlice.PlotGraph(new float4(1, 0, 0, 1), rangeX, rangeY, ( float _x ) => { return((float)m_radialSliceAverage_Smoothed[(int)Math.Floor(_x)].r); });
                m_spectrumRadialSlice.PlotGraph(new float4(0, 0, 1, 1), rangeX, rangeY, ( float _x ) => { return((float)m_radialSliceAverage_Smoothed[(int)Math.Floor(_x)].i); });

                m_spectrumRadialSlice.PlotAxes(float4.UnitW, rangeX, rangeY, 16.0f, 1e-4f);

                // =====================================================
                // Noise distribution
                m_spectrumNoiseDistribution        = new ImageFile(m_blueNoise.Width, m_blueNoise.Height, ImageFile.PIXEL_FORMAT.RGBA8, new ColorProfile(ColorProfile.STANDARD_PROFILE.sRGB));
                m_spectrumNoiseDistribution_Custom = new ImageFile(m_blueNoise.Width, m_blueNoise.Height, ImageFile.PIXEL_FORMAT.RGBA8, new ColorProfile(ColorProfile.STANDARD_PROFILE.sRGB));
                m_spectrumNoiseDistribution.Clear(float4.One);
                rangeX = new float2(0, 1);
                rangeY = new float2(0, 1.0f / BUCKETS_COUNT);
                m_spectrumNoiseDistribution.PlotGraph(float4.UnitW, rangeX, rangeY, ( float _x ) => {
                    int bucketIndex = Math.Min(BUCKETS_COUNT - 1, (int)Math.Floor(_x * BUCKETS_COUNT));
                    return((float)noiseDistributionHistogram[bucketIndex] / (m_blueNoise.Width * m_blueNoise.Height));
                });
                                #else
                // Create our hand made textures
                const uint NOISE_SIZE = 512;

                try {
                    m_device.Init(panelImageSpectrum.Handle, false, false);
                    m_FFT = new SharpMath.FFT.FFT2D_GPU(m_device, NOISE_SIZE);
                } catch (Exception) {
                }

                m_handMadeBlueNoise = new ImageFile(NOISE_SIZE, NOISE_SIZE, PIXEL_FORMAT.RGBA8, new ColorProfile(ColorProfile.STANDARD_PROFILE.sRGB));
                m_handMadeSpectrum  = new ImageFile(m_handMadeBlueNoise.Width, m_handMadeBlueNoise.Height, PIXEL_FORMAT.RGBA8, new ColorProfile(ColorProfile.STANDARD_PROFILE.sRGB));

                // Read radial profile from disk
                Complex[] radialSliceAverage = new Complex[256];
                using (System.IO.FileStream S = new System.IO.FileInfo("BlueNoiseRadialProfile255.complex").OpenRead())
                    using (System.IO.BinaryReader R = new System.IO.BinaryReader(S)) {
                        for (int i = 1; i < radialSliceAverage.Length; i++)
                        {
                            radialSliceAverage[i].r = R.ReadSingle();
                            radialSliceAverage[i].i = R.ReadSingle();
                        }
                    }
                                #endif

                //////////////////////////////////////////////////////////////////////////
                // Build initial blue-noise
                RebuildNoise();
            } catch (Exception _e) {
                MessageBox.Show(this, "Error during Load() => " + _e.Message, "BlueNoiseGenerator");
            }
        }
Exemplo n.º 7
0
        void    UpdateLevels()
        {
//          const uint	BUCKETS_PER_LAYER = LEVEL_BUCKETS_COUNT / LAYERS_COUNT;
//
//          float	normalizer = 1.0f / (LAYERS_COUNT-1);
//          uint	bucketIndex = 0;
//          for ( uint layerIndex=0; layerIndex < LAYERS_COUNT; layerIndex++ ) {
//              for ( uint i=0; i < BUCKETS_PER_LAYER; i++ ) {
//                  float	t = (float) i / BUCKETS_PER_LAYER;
//                  m_levels[bucketIndex++] = normalizer * (layerIndex + t);
//              }
//          }

                        #if CUBIC_SPLINES
            float F      = 4.0f;
            float T0     = F * (0 * 1.0f + floatTrackbarControlTangent0.Value);
            float T1_in  = F * (0 * 1.0f + floatTrackbarControlTangent1.Value);
            float T1_out = checkBoxSplit1.Checked ? F * (0 * 1.0f + floatTrackbarControlTangent1_Out.Value) : T1_in;
            float T2_in  = F * (0 * 1.0f + floatTrackbarControlTangent2.Value);
            float T2_out = checkBoxSplit2.Checked ? F * (0 * 1.0f + floatTrackbarControlTangent2_Out.Value) : T2_in;
            float T3     = F * (0 * 1.0f + floatTrackbarControlTangent3.Value);

            float4[] hermites = new float4[6];
//              hermites[0].Set( 0.0f, T0, 1.0f, T1 );
//              hermites[1].Set( 0.0f, T1, 1.0f, T2 );
//              hermites[2].Set( 0.0f, T2, 1.0f, T3 );

            hermites[0].Set(0.0f, T0, 0.5f, -0.5f * (T0 - T1_in));
            hermites[1].Set(0.5f, -0.5f * (T0 - T1_in), 1.0f, T1_in);
            hermites[2].Set(0.0f, T1_out, 0.5f, 0.5f * (T1_out + T2_in));
            hermites[3].Set(0.5f, 0.5f * (T1_out + T2_in), 1.0f, T2_in);
            hermites[4].Set(0.0f, T2_out, 0.5f, 0.5f * (T2_out + T3));
            hermites[5].Set(0.5f, 0.5f * (T2_out + T3), 1.0f, T3);
                        #elif POWERS
//              float[]		powers = new float[LAYERS_COUNT-1];
//              powers[0] = floatTrackbarControlTangent0.Value;
//              powers[1] = floatTrackbarControlTangent1.Value;
//              powers[2] = floatTrackbarControlTangent2.Value;
//              powers[0] = powers[0] < 0.0f ? 1.0f / (1.0f - 9.0f * powers[0]) : 1.0f + 9.0f * powers[0];
//              powers[1] = powers[1] < 0.0f ? 1.0f / (1.0f - 9.0f * powers[1]) : 1.0f + 9.0f * powers[1];
//              powers[2] = powers[2] < 0.0f ? 1.0f / (1.0f - 9.0f * powers[2]) : 1.0f + 9.0f * powers[2];

            float[] powers  = new float[LAYERS_COUNT - 1];
            float[] scalesX = new float[LAYERS_COUNT - 1];
            float[] scalesY = new float[LAYERS_COUNT - 1];
            powers[0] = floatTrackbarControlTangent0.Value;
            powers[1] = floatTrackbarControlTangent1.Value;
            powers[2] = floatTrackbarControlTangent2.Value;
//              powers[0] = powers[0] < 0.0f ? 1.0f / (1.0f - 9.0f * powers[0]) : 1.0f + 9.0f * powers[0];
//              powers[1] = powers[1] < 0.0f ? 1.0f / (1.0f - 9.0f * powers[1]) : 1.0f + 9.0f * powers[1];
//              powers[2] = powers[2] < 0.0f ? 1.0f / (1.0f - 9.0f * powers[2]) : 1.0f + 9.0f * powers[2];
            powers[0]  = (float)Math.Pow(10.0, 3.0 * powers[0]);
            powers[1]  = (float)Math.Pow(10.0, 3.0 * powers[1]);
            powers[2]  = (float)Math.Pow(10.0, 3.0 * powers[2]);
            scalesX[0] = 1.0f;                    // + 0.5f * Math.Max( 0.0f,  floatTrackbarControlTangent0.Value );
            scalesY[0] = 1.0f;                    // + 0.5f * Math.Max( 0.0f, -floatTrackbarControlTangent0.Value );
            scalesX[1] = 1.0f;                    // + 0.5f * Math.Max( 0.0f,  floatTrackbarControlTangent1.Value );
            scalesY[1] = 1.0f;                    // + 0.5f * Math.Max( 0.0f, -floatTrackbarControlTangent1.Value );
            scalesX[2] = 1.0f;                    // + 0.5f * Math.Max( 0.0f,  floatTrackbarControlTangent2.Value );
            scalesY[2] = 1.0f;                    // + 0.5f * Math.Max( 0.0f, -floatTrackbarControlTangent2.Value );
                        #endif

            for (uint bucketIndex = 0; bucketIndex < LEVEL_BUCKETS_COUNT; bucketIndex++)
            {
                float maskIn = (float)bucketIndex / (LEVEL_BUCKETS_COUNT - 1);

                float scaledMask = (LAYERS_COUNT - 1) * maskIn;
                uint  layerStart = Math.Min(LAYERS_COUNT - 2, (uint)Math.Floor(scaledMask));
                float t          = scaledMask - layerStart;

                                #if CUBIC_SPLINES
                // Compute Hermite curves
//                  float4	hermite = hermites[layerStart];
//                  float	maskOut = hermite.x * (1+2*t)*(1-t)*(1-t)
//                                  + hermite.y * t*(1-t)*(1-t)
//                                  + hermite.z * t*t*(3-2*t)
//                                  + hermite.w * t*t*(t-1);

                t = 2.0f * t;
                uint hermiteIndex = 2 * layerStart;
                if (t > 1.0f)
                {
                    hermiteIndex++;
                    t -= 1.0f;
                }
                float4 hermite = hermites[hermiteIndex];
                float  maskOut = hermite.x * (1 + 2 * t) * (1 - t) * (1 - t)
                                 + hermite.y * t * (1 - t) * (1 - t)
                                 + hermite.z * t * t * (3 - 2 * t)
                                 + hermite.w * t * t * (t - 1);
                                #elif POWERS
//                  // Apply curves
//                  float	tIn = 2.0f * t - 1.0f;
//                  float	tOut = Math.Sign( tIn ) * (float) Math.Pow( Math.Abs( tIn ), powers[layerStart] );
//                  float	maskOut = (layerStart + 0.5f * (1.0f + tOut)) / (LAYERS_COUNT-1);

                float maskOut = scalesY[layerStart] * (float)Math.Pow(t / scalesX[layerStart], powers[layerStart]);
                                #endif

                maskOut = Math.Max(0, Math.Min(1, maskOut));
                maskOut = (layerStart + maskOut) / (LAYERS_COUNT - 1);
                m_levels[bucketIndex] = maskOut;
            }

            // Redraw levels
            float2 rangeX = new float2(0, 1);
            float2 rangeY = new float2(0, 1);
            m_imageLevels.Clear(float4.One);
            m_imageLevels.PlotAxes(float4.UnitW, rangeX, rangeY, 1.0f / (LAYERS_COUNT - 1), 1.0f / (LAYERS_COUNT - 1));
            m_imageLevels.PlotGraph(float4.UnitW, rangeX, rangeY, ( float x ) => { return(m_levels[Math.Min(LEVEL_BUCKETS_COUNT - 1, (uint)((LEVEL_BUCKETS_COUNT - 1) * x))]); });
            for (int layerIndex = 1; layerIndex < LAYERS_COUNT; layerIndex++)
            {
                m_imageLevels.DrawLine(new float4(0.2f, 0.5f, 0.75f, 1), m_imageLevels.RangedCoordinates2ImageCoordinates(rangeX, rangeY, new float2((float)layerIndex / (LAYERS_COUNT - 1), 0)), m_imageLevels.RangedCoordinates2ImageCoordinates(rangeX, rangeY, new float2((float)layerIndex / (LAYERS_COUNT - 1), 1)));
                m_imageLevels.DrawLine(new float4(0.2f, 0.5f, 0.75f, 1), m_imageLevels.RangedCoordinates2ImageCoordinates(rangeX, rangeY, new float2(0, (float)layerIndex / (LAYERS_COUNT - 1))), m_imageLevels.RangedCoordinates2ImageCoordinates(rangeX, rangeY, new float2(1, (float)layerIndex / (LAYERS_COUNT - 1))));
            }
            panelOutputLevels.m_bitmap = m_imageLevels.AsBitmap;
            panelOutputLevels.Refresh();
        }
        /// <summary>
        /// Computes the radius of the sphere tangent to a vertex given a set of neighbor vertices
        /// </summary>
        /// <param name="_P"></param>
        /// <param name="_N"></param>
        /// <param name="_neighbors"></param>
        /// <returns></returns>
        float   ComputeTangentSphereRadius_SUPER_SLOW(float3 _P, float3 _N, float3[] _neighbors)
        {
            ImageFile graph = new ImageFile((uint)panelOutputGraph.Width, (uint)panelOutputGraph.Height, PIXEL_FORMAT.BGRA8, new ColorProfile(ColorProfile.STANDARD_PROFILE.sRGB));

            graph.Clear(float4.One);

            Func <float3, float, float3[], float> SquareDistance = (float3 _C, float _R, float3[] _Pns) => { float result = 0.0f; foreach (float3 Pn in _Pns)
                                                                                                             {
                                                                                                                 result += (Pn - _C).LengthSquared - _R * _R;
                                                                                                             }
                                                                                                             return(Math.Abs(result)); };
//			Func<float3,float3[],float>		SquareDistance = ( float3 _C, float3[] _Pns ) => { float result = 0.0f; foreach ( float3 Pn in _Pns ) result += (Pn - _C).Length; return result / _Pns.Length; };

            float2 rangeX = new float2(0, 4), rangeY = new float2(-50, 50);

            graph.PlotAxes(float4.UnitW, rangeX, rangeY, 0.5f, 5.0f);
            graph.PlotGraph(float4.UnitW, rangeX, rangeY, ( float x ) => { return(SquareDistance(_P - x * _N, x, _neighbors)); });

            const float eps = 0.01f;
            const float tol = 1e-3f;

            float  previousR           = -float.MaxValue;
            float  R                   = 0.0f;
            float  step                = 0.1f;
            float3 C                   = _P;
            int    iterationsCount     = 0;
            float  previousSqDistance  = float.MaxValue;
            float  bestSqDistance      = float.MaxValue;
            float  bestR               = 0.0f;
            int    bestIterationsCount = 0;

            while (step > tol && R < 10000.0f && iterationsCount < 1000)
            {
                // Compute gradient
                float sqDistance  = SquareDistance(C, R, _neighbors);
                float sqDistance2 = SquareDistance(C - eps * _N, R + eps, _neighbors);
                float grad        = (sqDistance2 - sqDistance) / eps;

                if (previousSqDistance < sqDistance)
                {
                    step *= 0.95f;                      // Climbing up again, roll down slower...
                }

                // Follow opposite gradient direction toward minimum
                previousR = R;
                R        -= step * grad;
                C         = _P - R * _N;
                iterationsCount++;

                previousSqDistance = sqDistance;
                if (sqDistance < bestSqDistance)
                {
                    bestSqDistance      = sqDistance;
                    bestR               = previousR;
                    bestIterationsCount = iterationsCount;
                }

                graph.DrawLine(new float4(1, 0, 0, 1), graph.RangedCoordinates2ImageCoordinates(rangeX, rangeY, new float2(previousR, sqDistance)), graph.RangedCoordinates2ImageCoordinates(rangeX, rangeY, new float2(R, SquareDistance(_P - R * _N, R, _neighbors))));

                float k = 0.1f;
                graph.DrawLine(new float4(0, 0.5f, 0, 1), graph.RangedCoordinates2ImageCoordinates(rangeX, rangeY, new float2(previousR, k * (iterationsCount - 1))), graph.RangedCoordinates2ImageCoordinates(rangeX, rangeY, new float2(R, k * iterationsCount)));
            }

            // Since we crossed the minimum, take the average for a better result
            R = 0.5f * (R + previousR);

            panelOutputGraph.m_bitmap = graph.AsBitmap;
            panelOutputGraph.Refresh();
            labelResult.Text = "R = " + R + " (" + previousSqDistance + ") in " + iterationsCount + " iterations...\r\nBest = " + bestR + " (" + bestSqDistance + ") in " + bestIterationsCount + " iterations...";

            return(R);
        }
        /// <summary>
        /// Computes the radius of the sphere tangent to a vertex given a set of neighbor vertices
        /// </summary>
        /// <param name="_P"></param>
        /// <param name="_N"></param>
        /// <param name="_neighbors"></param>
        /// <returns></returns>
        float   ComputeTangentSphereRadius(float3 _P, float3 _N, float3[] _neighbors, bool _debugGraph)
        {
            Func <float3, double, float3[], double> SquareDistance = (float3 _C, double _R, float3[] _Pns) => {
                double result = 0.0f;
                foreach (float3 Pn in _Pns)
                {
                    result += (Pn - _C).LengthSquared - _R * _R;
                }
                return(result);
            };

            float2 rangeX = new float2(-4, 4), rangeY = new float2(-50, 50);

            if (_debugGraph)
            {
                graph.Clear(float4.One);
                graph.PlotAxes(float4.UnitW, rangeX, rangeY, 0.5f, 5.0f);
                graph.PlotGraph(float4.UnitW, rangeX, rangeY, ( float x ) => { return((float)SquareDistance(_P - x * _N, x, _neighbors)); });
            }

            const float  eps = 0.01f;
            const double tol = 1e-3;

            double previousR           = -double.MaxValue;
            double R                   = 0.0f;
            float3 C                   = _P;
            int    iterationsCount     = 0;
            double previousSqDistance  = double.MaxValue;
            double bestSqDistance      = double.MaxValue;
            double bestR               = 0.0f;
            int    bestIterationsCount = 0;

            while (Math.Abs(R) < 10000.0f && iterationsCount < 1000)
            {
                // Compute gradient
                double sqDistance  = SquareDistance(C, R, _neighbors);
                double sqDistance2 = SquareDistance(C - eps * _N, R + eps, _neighbors);
                double grad        = (sqDistance2 - sqDistance) / eps;

                // Compute intersection with secant Y=0
                double t = -sqDistance * (Math.Abs(grad) > 1e-6f ? 1.0f / grad : (Math.Sign(grad) * 1e6));
                if (Math.Abs(t) < tol)
                {
                    break;
                }

                previousR = R;
                R        += t;
                C         = _P - (float)R * _N;
                iterationsCount++;

                previousSqDistance = sqDistance;
                if (sqDistance < bestSqDistance)
                {
                    bestSqDistance      = sqDistance;
                    bestR               = previousR;
                    bestIterationsCount = iterationsCount;
                }

                if (_debugGraph)
                {
                    graph.DrawLine(new float4(1, 0, 0, 1), graph.RangedCoordinates2ImageCoordinates(rangeX, rangeY, new float2((float)previousR, (float)sqDistance)), graph.RangedCoordinates2ImageCoordinates(rangeX, rangeY, new float2((float)R, (float)SquareDistance(_P - (float)R * _N, R, _neighbors))));
                    float k = 0.1f;
                    graph.DrawLine(new float4(0, 0.5f, 0, 1), graph.RangedCoordinates2ImageCoordinates(rangeX, rangeY, new float2((float)previousR, k * (iterationsCount - 1))), graph.RangedCoordinates2ImageCoordinates(rangeX, rangeY, new float2((float)R, k * iterationsCount)));
                }
            }

            if (_debugGraph)
            {
                panelOutputGraph.m_bitmap = graph.AsBitmap;
                panelOutputGraph.Refresh();
                labelResult.Text = "R = " + R + " (" + previousSqDistance + ") in " + iterationsCount + " iterations...\r\nBest = " + bestR + " (" + bestSqDistance + ") in " + bestIterationsCount + " iterations...";
            }

//          if ( R < 1000.0 )
//              throw new Exception( "Maybe R should be Math.Abs()'ed? (neighbors are all lying on a flat plane or in a ?" );
            R = Math.Max(-10000.0, Math.Min(10000.0, R));

            return((float)R);
        }