Search code examples
c#filefile-manipulation

C# two pass image creation


So, I'm building a console application to generate 2D perlin(ish) noise that is split among multiple image files. The algorithm I'm using requires that I do the generation in two passes, one to create the seed for the noise, then to generate the noise. I'm saving the generated noise to the same file that the seed is generated to in order to cut down on disk-space, and to make it clean in the end. I'm running into an issue though, during the second pass, I can't actually access the file, as it's already being used by another procces. Here's the full code to see if there is anything that I'm missing:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace MapGenerator
{
class Program
{
    public static Random rng = new Random();

    public static void Seed(int px, int py)
    {
        Bitmap seed = new Bitmap(256, 256, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
        for (int x = 0; x < seed.Width; x++)
        {
            for (int y = 0; y < seed.Height; y++)
            {
                var val = rng.Next(0, 255);
                seed.SetPixel(x, y, Color.FromArgb(255, val, val, val));
            }
        }

        if (File.Exists("chunk," + (px - 1) + "," + (py) + ".jpeg"))
        {
            Bitmap source = new Bitmap("chunk," + (px - 1) + "," + (py) + ".jpeg");
            for (int y = 0; y < 256; y++)
            {
                seed.SetPixel(0, y, Color.FromArgb(255, source.GetPixel(255, y).R, source.GetPixel(255, y).G, source.GetPixel(255, y).B));
            }
        }
        if (File.Exists("chunk," + (px + 1) + "," + (py) + ".jpeg"))
        {
            Bitmap source = new Bitmap("chunk," + (px + 1) + "," + (py) + ".jpeg");
            for (int y = 0; y < 256; y++)
            {
                seed.SetPixel(255, y, Color.FromArgb(255, source.GetPixel(0, y).R, source.GetPixel(0, y).G, source.GetPixel(0, y).B));
            }
        }
        if (File.Exists("chunk," + (px) + "," + (py - 1) + ".jpeg"))
        {
            Bitmap source = new Bitmap("chunk," + (px) + "," + (py - 1) + ".jpeg");
            for (int x = 0; x < 256; x++)
            {
                seed.SetPixel(x, 0, Color.FromArgb(255, source.GetPixel(x, 255).R, source.GetPixel(x, 255).G, source.GetPixel(x, 255).B));
            }
        }
        if (File.Exists("chunk," + (px) + "," + (py + 1) + ".jpeg"))
        {
            Bitmap source = new Bitmap("chunk," + (px) + "," + (py + 1) + ".jpeg");
            for (int x = 0; x < 256; x++)
            {
                seed.SetPixel(x, 255, Color.FromArgb(255, source.GetPixel(x, 0).R, source.GetPixel(x, 0).G, source.GetPixel(x, 0).B));
            }
        }
        seed.Save("chunk," + px + "," + py + ".jpeg", System.Drawing.Imaging.ImageFormat.Jpeg);
        seed.Dispose();
    }

    public static void Perlin(int px, int py)
    {
        Bitmap seed = new Bitmap("chunk," + px + "," + py + ".jpeg");

        Bitmap output = new Bitmap(256, 256, System.Drawing.Imaging.PixelFormat.Format24bppRgb);


        for (int y = 0; y < 256; y++)
        {
            for (int x = 0; x < 256; x++)
            {
                double noise = 0.0;
                double scale = 1.0;
                double acc = 0.0;

                for (int o = 0; o < 8; o++)
                {
                    int pitch = 256 >> o;
                    int sampleX1 = (x / pitch) * pitch;
                    int sampleY1 = (y / pitch) * pitch;


                    int sampleX2 = (sampleX1 + pitch);
                    int sampleY2 = (sampleY1 + pitch);

                    sampleX2 = (sampleX2 == 256) ? 255 : sampleX2;
                    sampleY2 = (sampleY2 == 256) ? 255 : sampleY2;

                    double Xblend = (double)(x - sampleX1) / (double)pitch;
                    double Yblend = (double)(y - sampleY1) / (double)pitch;

                    // interpolate between the two points
                    double Tsample = ((1 - Xblend) * ((double)seed.GetPixel(sampleX1, sampleY1).R / 255.0)) + (Xblend * ((double)seed.GetPixel(sampleX2, sampleY1).R / 255.0));
                    double Bsample = ((1 - Xblend) * ((double)seed.GetPixel(sampleX1, sampleY2).R / 255.0)) + (Xblend * ((double)seed.GetPixel(sampleX2, sampleY2).R / 255.0));

                    noise += (((1 - Yblend) * Tsample) + (Yblend * Bsample)) * scale;
                    acc += scale;
                    scale = scale * 0.6;
                }

                noise = noise / acc;

                noise = noise * 255.0;

                output.SetPixel(x, y, Color.FromArgb(255, (int)(noise), (int)(noise), (int)(noise)));
            }
        }
        seed.Dispose();
        File.Delete("chunk," + px + "," + py + ".jpeg");
        output.Save("chunk," + px + "," + py + ".jpeg", System.Drawing.Imaging.ImageFormat.Jpeg);
        output.Dispose();
    }

    static void Main(string[] args)
    {
        int depth = 1;

        for(int x = -depth; x <= depth; x++)
        {
            for(int y = -depth; y <= depth; y++)
            {
                Seed(x, y);
            }
        }

        for (int x = -depth; x <= depth; x++)
        {
            for (int y = -depth; y <= depth; y++)
            {
                Perlin(x, y);
            }
        }
    }
}
}

As you can see, I've tried a lot of disposing of the variables, but to no avail. Any ideas what I'm doing wrong? The odd thing is, when it was only a single function running, I could freely overwrite the base image file, but now that I split it up into two different functions, it's all downhill, and it won't let me manipulate the files.


Solution

  • You need to dispose your bitmaps, as below

    Also i would use Lockbits instead of GetPixel and SetPixel and you will get factors better performance

    public static void Seed(int px, int py)
    {
        using (var seed = new Bitmap(256, 256, PixelFormat.Format24bppRgb))
        {
            for (var x = 0; x < seed.Width; x++)
                for (var y = 0; y < seed.Height; y++)
                {
                    var val = rng.Next(0, 255);
                    seed.SetPixel(x, y, Color.FromArgb(255, val, val, val));
                }       
    
            if (File.Exists("chunk," + (px - 1) + "," + py + ".jpeg"))
                using (var source = new Bitmap("chunk," + (px - 1) + "," + py + ".jpeg"))
                    for (var y = 0; y < 256; y++)
                        seed.SetPixel(0, y, Color.FromArgb(255, source.GetPixel(255, y).R, source.GetPixel(255, y).G, source.GetPixel(255, y).B));           
    
            if (File.Exists("chunk," + (px + 1) + "," + py + ".jpeg"))
                using (var source = new Bitmap("chunk," + (px + 1) + "," + py + ".jpeg"))
                    for (var y = 0; y < 256; y++)
                        seed.SetPixel(255, y, Color.FromArgb(255, source.GetPixel(0, y).R, source.GetPixel(0, y).G, source.GetPixel(0, y).B));      
    
            if (File.Exists("chunk," + px + "," + (py - 1) + ".jpeg"))
                using (var source = new Bitmap("chunk," + px + "," + (py - 1) + ".jpeg"))
                    for (var x = 0; x < 256; x++)
                        seed.SetPixel(x, 0, Color.FromArgb(255, source.GetPixel(x, 255).R, source.GetPixel(x, 255).G, source.GetPixel(x, 255).B));
    
            if (File.Exists("chunk," + px + "," + (py + 1) + ".jpeg"))
                using (var source = new Bitmap("chunk," + px + "," + (py + 1) + ".jpeg"))
                    for (var x = 0; x < 256; x++)
                        seed.SetPixel(x, 255, Color.FromArgb(255, source.GetPixel(x, 0).R, source.GetPixel(x, 0).G, source.GetPixel(x, 0).B));
    
            seed.Save("chunk," + px + "," + py + ".jpeg", ImageFormat.Jpeg);
        }
    }
    

    Example how to use LockBits and pointers using the unsafe keyword. Basically you are accessing your picture by scan lines in a pinned array using pointers directly accessing the 32 bit ARBG values in memory

    using (var seed = new Bitmap(256, 256, PixelFormat.Format24bppRgb))
    {
       // lock the array for direct access
       var bitmapData = seed.LockBits(new Rectangle(0,0,seed.Width,seed.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppPArgb);
       // get the pointer
       var scan0Ptr = (int*)bitmapData.Scan0;
       // get the stride
       var stride = bitmapData.Stride / 4;
    
       for (var x = 0; x < seed.Width; x++)
          for (var y = 0; y < seed.Height; y++)
          {
             var val = rng.Next(0, 255);
             *(scan0Ptr + x + y * stride) = Color.FromArgb(255, val, val, val).ToArgb();
          }
    
       seed.UnlockBits(bitmapData);
       ...