Perlin Noise – Source Code – (3 posts)

  • Profile picture of Eggsworth Eggsworth6p said 1 year, 6 months ago:

    I did not make this!

    I just wanted to share it with the community. I spent a decent amount of time searching, and this was exactly what I was searching for. I hope other people can find use with it as well. (If I can find the page where I found it in my history I will put it up, otherwise, I am sorry I dont know who made it)

    It is a single class which generates a procedurally generated perlin noise .png image

    Enjoy!

    import java.awt.Color;
    import java.awt.Graphics2D;
    import java.awt.RenderingHints;
    import java.awt.image.BufferedImage;
    import java.io.File;
    import java.io.IOException;
    import java.util.Random;
    
    import javax.imageio.ImageIO;
    
    public class Perlin
    {
       private BufferedImage   image;
    
       private int            section;
       private BufferedImage[]   imageSections;
       private char[]         remap;
    
       public static void main(String[] args)
       {
          Perlin perlin;
          perlin = new Perlin(1024, 1024, 4, 2.5f, 0x70, 0.9875f, 6);
          System.out.println("writing image file");
          long start = System.currentTimeMillis();
          try
          {
             ImageIO.write(perlin.image, "png", new File("test.png"));
          }
          catch (IOException e)
          {
             // TODO Auto-generated catch block
             e.printStackTrace();
          }
          System.out.println(System.currentTimeMillis() - start);
       }
    
       /**
        * Builds a Cloud texture map with Perlin noise
        *
        * @param width
        * @param height
        * @param initFreq
        * @param persistency
        * @param decay
        * @param detail
        */
       public Perlin(int width, int height, int initFreq, float persistency,
             int density, float cloudSharpness, int detail)
       {
          long start = System.currentTimeMillis();
          System.out.println("Generating lookup table");
          // generate a re-mapping lookup table
          if (density < 0)
          {
             density = 0;
          }
          else if (density > 0xFF)
          {
             density = 0xFF;
          }
          if (cloudSharpness < 0.0f)
          {
             cloudSharpness = 0.0f;
          }
          else if (cloudSharpness > 1.0f)
          {
             cloudSharpness = 1.0f;
          }
          remap = new char[0xFF];
          for (int i = 0; i < 0xFF; i++)
          {
             remap[i] = (char) (density - i);
             if (remap[i] < 0 || remap[i] > 0xFF)
             {
                remap[i] = (char) 0;
             }
             remap[i] = (char) (0xFF - (Math.pow(cloudSharpness, remap[i]) * 0xFF));
          }
          System.out.println(System.currentTimeMillis() - start);
          System.out.println("Generating noise maps and combining...");
          start = System.currentTimeMillis();
          // time to generate the 2D noise functions
          Noise2D[] noiseMaps = new Noise2D[detail];
          float amplitude = 1.0f;
          for (int i = 0; i < detail; i++)
          {
             noiseMaps[i] = new Noise2D(width, height, initFreq, initFreq,
                   amplitude);
             noiseMaps[i].start();
             amplitude /= persistency;
             initFreq *= 2;
          }
          // initialize our main image
          image = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
          Graphics2D g = image.createGraphics();
          g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION,
                RenderingHints.VALUE_ALPHA_INTERPOLATION_DEFAULT);
          // as thread finish, blend them in
          boolean keepRunning = true;
          while (keepRunning)
          {
             keepRunning = false;
             for (int i = 0; i < noiseMaps.length; i++)
             {
                if (noiseMaps[i] != null)
                {
                   // check to see if we can extract data
                   if (!noiseMaps[i].isAlive())
                   {
                      // we can get the data
                      g.drawImage(noiseMaps[i].image, null, 0, 0);
                      // allow Java to garbage collect the thread
                      noiseMaps[i] = null;
                   }
                   else
                   {
                      keepRunning = true;
                   }
                }
             }
             try
             {
                Thread.sleep(15);
             }
             catch (InterruptedException e)
             {
                // TODO Auto-generated catch block
                e.printStackTrace();
             }
          }
          g.dispose();
          System.out.println(System.currentTimeMillis() - start);
          System.out.println("Adjusting image");
          start = System.currentTimeMillis();
          // split the image up into sections and re-map the image
          adjustImage(Runtime.getRuntime().availableProcessors(), image
                .getWidth()
                * image.getHeight() / 0x40000);
          System.out.println(System.currentTimeMillis() - start);
       }
    
       /**
        * Adjusts the image using the LUT
        *
        * @param threadCount
        * @param sectionsCount
        */
       public void adjustImage(int threadCount, int sectionsCount)
       {
          if (sectionsCount == 0)
          {
             // need at least one section
             sectionsCount = 1;
          }
          if (sectionsCount < threadCount)
          {
             // split up into more sections
             sectionsCount = threadCount;
          }
          ImageAdjuster[] threads = new ImageAdjuster[threadCount];
          imageSections = new BufferedImage[sectionsCount];
          for (int i = 0; i < sectionsCount; i++)
          {
             int sectionStart = i * image.getHeight() / sectionsCount;
             if (i + 1 == sectionsCount)
             {
                // last section
                imageSections[i] = image.getSubimage(0, sectionStart, image
                      .getWidth(), image.getHeight() - sectionStart);
             }
             else
             {
                imageSections[i] = image.getSubimage(0, sectionStart, image
                      .getWidth(), image.getHeight() / sectionsCount);
             }
          }
          for (int i = 0; i < threads.length; i++)
          {
             threads[i] = new ImageAdjuster(this);
             threads[i].start();
          }
          // sleep this thread until done re-mapping image
          boolean keepRunning = true;
          while (keepRunning)
          {
             keepRunning = false;
             for (int i = 0; i < threads.length; i++)
             {
                if (threads[i].isAlive())
                {
                   keepRunning = true;
                }
             }
             try
             {
                Thread.sleep(30);
             }
             catch (InterruptedException e)
             {
                // TODO Auto-generated catch block
                e.printStackTrace();
             }
          }
       }
    
       /**
        * helper method to give ImageAdjuster threads new image sections to adjust
        *
        * @param thread
        * @return
        */
       private synchronized BufferedImage requestNextSection(ImageAdjuster thread)
       {
          // divide the image up into 64 scan-lines
          if (section == imageSections.length)
          {
             return null;
          }
          BufferedImage toReturn = imageSections[section];
          imageSections[section] = null;
          section++;
          return toReturn;
       }
    
       /**
        * Internal class used by Perlin to adjust the image using the remap LUT
        *
        * @author Andrew
        */
       private class ImageAdjuster extends Thread
       {
          public BufferedImage   image;
          private Perlin         owner;
    
          public ImageAdjuster(Perlin owner)
          {
             this.owner = owner;
          }
    
          @Override
          public void run()
          {
             // get an initial image
             image = owner.requestNextSection(this);
             while (image != null)
             {
                // work
                for (int i = 0; i < image.getWidth(); i++)
                {
                   for (int j = 0; j < image.getHeight(); j++)
                   {
                      char val = (char) (image.getRGB(i, j) & 0xFF);
                      val = owner.remap[val];
                      image.setRGB(i, j, (val << 16) + (val << 8) + val);
                   }
                }
                image = owner.requestNextSection(this);
             }
          }
       }
    
       private static class Noise2D extends Thread
       {
          BufferedImage   image;
          private int      width;
          private int      height;
          private int      freqX;
          private float   alpha;
          private int      freqY;
    
          public Noise2D(int width, int height, int freqX, int freqY, float alpha)
          {
             this.width = width;
             this.height = height;
             this.freqX = freqX;
             this.freqY = freqY;
             this.alpha = alpha;
          }
    
          @Override
          public void run()
          {
             BufferedImage temp = new BufferedImage(freqX, freqY,
                   BufferedImage.TYPE_4BYTE_ABGR);
             Graphics2D g = temp.createGraphics();
             // generate a low-res random image
             for (int i = 0; i < freqX; i++)
             {
                for (int j = 0; j < freqY; j++)
                {
                   int val = new Random().nextInt(255);
                   g.setColor(new Color(val, val, val, (int) (alpha * 0xFF)));
                   g.fillRect(i, j, 1, 1);
                }
             }
             g.dispose();
             // re-scale the image up using interpolation (in this case, linear)
             image = new BufferedImage(width, height,
                   BufferedImage.TYPE_4BYTE_ABGR);
             g = image.createGraphics();
             g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                   RenderingHints.VALUE_INTERPOLATION_BILINEAR);
    
             g.drawImage(temp, 0, 0, width, height, 0, 0, freqX, freqY, null);
             g.dispose();
          }
       }
    }

  • Profile picture of sbook sbook189p said 1 year, 6 months ago:

    Nice find!

    I had a lecture by Ken Perlin some time back.. he had a lot of other cool stuff besides the noise function going on.. and he's a java guy ;)

    http://cs.nyu.edu/~perlin/

  • Profile picture of normen normen1022p said 1 year, 6 months ago:

    Guess this could somehow fit into the jMP image editor, huh? Any ideas for integration? Just creating noise images? Or maybe some image+noise to bumpmap or smth?