/// <summary> /// Very wasteful method that computes the spectrum of a source image /// </summary> /// <param name="_source"></param> /// <returns></returns> ImageFile ComputeSpectrum(ImageFile _source, float _scaleFactor) { uint size = _source.Width; uint offset = size >> 1; uint mask = size - 1; Complex[,] signal = new Complex[size, size]; _source.ReadPixels((uint _X, uint _Y, ref float4 _color) => { signal[_X, _Y].Set(_color.x, 0); }); SharpMath.FFT.FFT2D_GPU FFT = new SharpMath.FFT.FFT2D_GPU(m_device, size); Complex[,] spectrum = FFT.FFT_Forward(signal); FFT.Dispose(); ImageFile result = new ImageFile(size, size, _source.PixelFormat, _source.ColorProfile); result.WritePixels((uint _X, uint _Y, ref float4 _color) => { float V = _scaleFactor * (float)spectrum[(_X + offset) & mask, (_Y + offset) & mask].r; _color.Set(V, V, V, 1.0f); }); return(result); }
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"); } }
private void Convert(bool _centralValue, float _signX, float _signY) { // Initialize gradient filter Complex[,] dx = new Complex[m_size, m_size]; Complex[,] dy = new Complex[m_size, m_size]; Array.Clear(dx, 0, (int)(m_size * m_size)); Array.Clear(dy, 0, (int)(m_size * m_size)); if (_centralValue) { // Central-difference dx[m_size - 1, 0].Set(-_signX, 0); dx[1, 0].Set(_signX, 0); dy[0, m_size - 1].Set(_signY, 0); dy[0, 1].Set(-_signY, 0); } else { // One-sided difference dx[0, 0].Set(-_signX, 0); dx[1, 0].Set(_signX, 0); dy[0, 0].Set(_signY, 0); dy[0, 1].Set(-_signY, 0); } // Apply forward Fourier transform to obtain spectrum SharpMath.FFT.FFT2D_GPU FFT = new SharpMath.FFT.FFT2D_GPU(m_device, m_size); Complex[,] NX = FFT.FFT_Forward(nx); Complex[,] NY = FFT.FFT_Forward(ny); Complex[,] DX = FFT.FFT_Forward(dx); Complex[,] DY = FFT.FFT_Forward(dy); // Compute de-convolution float factor = m_imageNormal.Width * m_imageNormal.Height; Complex[,] H = new Complex[m_size, m_size]; Complex temp = new Complex(), sqrtTemp; for (uint Y = 0; Y < m_size; Y++) { for (uint X = 0; X < m_size; X++) { #if !F**K double sqNX = NX[X, Y].SquareMagnitude; double sqNY = NY[X, Y].SquareMagnitude; double num = sqNX + sqNY; double sqDX = DX[X, Y].SquareMagnitude; double sqDY = DY[X, Y].SquareMagnitude; double den = factor * (sqDX + sqDY); temp.Set(Math.Abs(den) > 0.0 ? num / den : 0.0, 0.0); sqrtTemp = temp.Sqrt(); H[X, Y] = sqrtTemp; #else Complex Nx = NX[X, Y]; Complex Ny = NY[X, Y]; Complex Dx = DX[X, Y]; Complex Dy = DY[X, Y]; double den = factor * (DX[X, Y].SquareMagnitude + DY[X, Y].SquareMagnitude); if (Math.Abs(den) > 0.0) { temp = -(Dx * Nx + Dy * Ny) / den; H[X, Y] = temp; } else { H[X, Y].Zero(); } #endif } } // Apply inverse transform Complex[,] h = FFT.FFT_Inverse(H); Complex heightMin = new Complex(double.MaxValue, double.MaxValue); Complex heightMax = new Complex(-double.MaxValue, -double.MaxValue); for (uint Y = 0; Y < m_size; Y++) { for (uint X = 0; X < m_size; X++) { Complex height = h[X, Y]; heightMin.Min(height); heightMax.Max(height); } } // Render result if (m_imageHeight != null) { m_imageHeight.Dispose(); m_imageHeight = null; } factor = 1.0f / (float)(heightMax.r - heightMin.r); // heightMin.r = -1.0f; // factor = 0.5f; m_imageHeight = new ImageFile(m_imageNormal.Width, m_imageNormal.Height, PIXEL_FORMAT.R16, new ColorProfile(ColorProfile.STANDARD_PROFILE.sRGB)); // m_imageHeight.WritePixels( ( uint X, uint Y, ref float4 _color ) => { _color.x = 1e3f * (float) H[X,Y].Magnitude; } ); // m_imageHeight.WritePixels( ( uint X, uint Y, ref float4 _color ) => { _color.x = 1e5f * (float) DX[X,Y].Magnitude; } ); m_imageHeight.WritePixels((uint X, uint Y, ref float4 _color) => { _color.x = factor * (float)(h[X, Y].r - heightMin.r); }); imagePanelHeight.Bitmap = m_imageHeight.AsBitmap; FFT.Dispose(); }