Welcome to the Java Programming Forums


The professional, friendly Java community. 21,500 members and growing!


The Java Programming Forums are a community of Java programmers from all around the World. Our members have a wide range of skills and they all have one thing in common: A passion to learn and code Java. We invite beginner Java programmers right through to Java professionals to post here and share your knowledge. Become a part of the community, help others, expand your knowledge of Java and enjoy talking with like minded people. Registration is quick and best of all free. We look forward to meeting you.


>> REGISTER NOW TO START POSTING


Members have full access to the forums. Advertisements are removed for registered users.

Results 1 to 4 of 4

Thread: Perlin Noise (and other Java 2d graphics questions)

  1. #1
    Super Moderator helloworld922's Avatar
    Join Date
    Jun 2009
    Posts
    2,896
    Thanks
    23
    Thanked 619 Times in 561 Posts
    Blog Entries
    18

    Default Perlin Noise (and other Java 2d graphics questions)

    I've been working on a Perlin noise-map cloud generator, but I've run into a few performance issues.

    I've managed to generate a working Perlin noise-map using simple random values put into very low-res images then re-scaling them up with interpolation before re-combining them, but I'm having some problems adjusting the density. Currently, I have to manually re-calculate the density, which is really slow. Basically, what i'm doing is getting the RGB values as a float from [0.0-1.0] then raising it to an arbitrary power. The higher the power, the lower the density of the cloud map.

    I was wondering is there a way that I could use the Graphics packages built into Java to do this (preferably a package with hardware acceleration)?

    Also, I was wondering is the re-scaling of images using Graphics.drawImage() hardware accelerated? It seems like it should be since it's faster than me calculating it manually in Java, but it feels slower than using hardware.

    Here's my code:

    package perlin;
     
    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 Noise2D
    {
    	BufferedImage	image;
     
    	public static void main(String[] args)
    	{
    		// generate a perlin noise map
    		Noise2D[] noise = new Noise2D[5];
    		float amplitude = 1.0f;
    		double persistence = 2.5;
    		int width = 1024;
    		int height = 1024;
    		noise[0] = new Noise2D(width, height, 2, 2, amplitude);
    		amplitude /= persistence;
    		noise[1] = new Noise2D(width, height, 4, 4, amplitude);
    		amplitude /= persistence;
    		noise[2] = new Noise2D(width, height, 8, 8, amplitude);
    		amplitude /= persistence;
    		noise[3] = new Noise2D(width, height, 16, 16, amplitude);
    		amplitude /= persistence;
    		noise[4] = new Noise2D(width, height, 32, 32, amplitude);
    		BufferedImage perlin = new BufferedImage(width, height,
    				BufferedImage.TYPE_3BYTE_BGR);
    		Graphics2D g = perlin.createGraphics();
    		g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION,
    				RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
    		for (int i = 0; i < noise.length; i++)
    		{
    			g.drawImage(noise[i].image, null, 0, 0);
    		}
    		// Manually re-calculate the RGB values
    		// this is the section of code I would ideally like to replace
    		for (int i = 0; i < perlin.getWidth(); i++)
    		{
    			for (int j = 0; j < perlin.getHeight(); j++)
    			{
    				int color = perlin.getRGB(i, j);
    				float val = (float) Math.pow((color & 0xFF) / 255.0f, 5.0);
    				assert (val <= 1.0f);
    				color = (((int) (val * 0xFF)) << 16)
    						+ (((int) (val * 0xFF)) << 8) + ((int) (val * 0xFF));
    				perlin.setRGB(i, j, color);
    			}
    		}
    		// write the image out to test.png so I can view it easily
    		try
    		{
    			ImageIO.write(perlin, "png", new File("test.png"));
    		}
    		catch (IOException e)
    		{
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    	}
     
    	public Noise2D(int width, int height, int freqX, int freqY, float alpha)
    	{
    		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, cubic)
    		image = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
    		g = image.createGraphics();
    		g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
    				RenderingHints.VALUE_INTERPOLATION_BICUBIC);
     
    		g.drawImage(temp, 0, 0, width, height, 0, 0, freqX, freqY, null);
    		g.dispose();
    	}
    }


  2. #2
    Administrator copeg's Avatar
    Join Date
    Oct 2009
    Location
    US
    Posts
    5,320
    Thanks
    181
    Thanked 833 Times in 772 Posts
    Blog Entries
    5

    Default Re: Perlin Noise (and other Java 2d graphics questions)

    Not sure about the acceleration issue (I've had issues with speed when manually setting a BufferedImage values), but you might be able to speed up those calculations in the loops a bit. Might give a slight performance bump. For example
    float val = (float) Math.pow((color & 0xFF) / 255.0f, 5.0);
    can be reduced to a lookup table
    float val = lookup[color & 0xff];//where lookup is a pre-made array of your Math.pow values

  3. #3
    Super Moderator helloworld922's Avatar
    Join Date
    Jun 2009
    Posts
    2,896
    Thanks
    23
    Thanked 619 Times in 561 Posts
    Blog Entries
    18

    Default Re: Perlin Noise (and other Java 2d graphics questions)

    For some reason I always forget to use lookup tables Thanks, that sped up the calculations.

    I decided to put some code in to time how much it's spending in each section, and ran it on a really large map (4096x4096).

    With using lookup tables:
    package perlin;
     
    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 Noise2D
    {
    	BufferedImage			image;
    	public static byte[]	lookup;
     
    	public static void main(String[] args)
    	{
    		// create the lookup table
    		long start;
    		System.out.println("generating lookup table... ");
    		start = System.currentTimeMillis();
    		lookup = new byte[255];
    		for (int i = 0; i < 255; i++)
    		{
    			lookup[i] = (byte) (Math.pow((i & 0xFF) / 255.0f, 5) * 0xFF);
    		}
    		// generate a perlin noise map
    		System.out.println((System.currentTimeMillis() - start));
    		System.out.println("generating noise maps...");
    		start = System.currentTimeMillis();
    		Noise2D[] noise = new Noise2D[5];
    		float amplitude = 1.0f;
    		double persistence = 2.5;
    		int width = 4096;
    		int height = 4096;
    		noise[0] = new Noise2D(width, height, 3, 3, amplitude);
    		amplitude /= persistence;
    		noise[1] = new Noise2D(width, height, 6, 6, amplitude);
    		amplitude /= persistence;
    		noise[2] = new Noise2D(width, height, 12, 12, amplitude);
    		amplitude /= persistence;
    		noise[3] = new Noise2D(width, height, 24, 24, amplitude);
    		amplitude /= persistence;
    		noise[4] = new Noise2D(width, height, 48, 48, amplitude);
    		BufferedImage perlin = new BufferedImage(width, height,
    				BufferedImage.TYPE_3BYTE_BGR);
    		System.out.println((System.currentTimeMillis() - start));
    		System.out.println("combining noise maps... ");
    		start = System.currentTimeMillis();
    		Graphics2D g = perlin.createGraphics();
    		g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION,
    				RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
    		for (int i = 0; i < noise.length; i++)
    		{
    			g.drawImage(noise[i].image, null, 0, 0);
    		}
    		System.out.println((System.currentTimeMillis() - start));
    		System.out.println("calculating density values... ");
    		start = System.currentTimeMillis();
    		// Manually re-calculate the RGB values
    		// this is the section of code I would ideally like to replace
    		for (int i = 0; i < perlin.getWidth(); i++)
    		{
    			for (int j = 0; j < perlin.getHeight(); j++)
    			{
    				int color = perlin.getRGB(i, j);
    				byte val = lookup[color & 0xFF];
    				// float val = (float) Math.pow((color & 0xFF) / 255.0f, 5.0);
    				color = (((val)) << 16) + (((val)) << 8) + ((val));
    				// color = (((int) (val * 0xFF)) << 16)
    				// + ((int) ((val * 0xFF)) << 8) + ((int) (val * 0xFF));
    				perlin.setRGB(i, j, color);
    			}
    		}
    		System.out.println((System.currentTimeMillis() - start));
    		System.out.println("writing file... ");
    		start = System.currentTimeMillis();
    		// write the image out to test.png so I can view it easily
    		try
    		{
    			ImageIO.write(perlin, "png", new File("test.png"));
    		}
    		catch (IOException e)
    		{
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    		System.out.println((System.currentTimeMillis() - start));
    		System.out.println("done! ");
    	}
     
    	public Noise2D(int width, int height, int freqX, int freqY, float alpha)
    	{
    		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();
    	}
    }

    output (times in milliseconds):
    Quote Originally Posted by with lookup tables
    generating lookup table...
    0
    generating noise maps...
    4016
    combining noise maps...
    790
    calculating density values...
    5614
    writing file...
    6746
    done!
    This leads me to believe that there's some hardware acceleration involved since it can combine the maps much quicker than me manually calculating them (I also checked task manager to make sure it wasn't just using a multi-threaded solution to calculate image painting).

    For kicks and giggles, I tried the code without using lookup tables and boy was it slow

    Quote Originally Posted by no lookup tables
    generating noise maps...
    4003
    combining noise maps...
    806
    calculating density values...
    11165
    writing file...
    5957
    done!
    Last edited by helloworld922; June 9th, 2010 at 07:29 PM.

  4. #4
    Super Moderator helloworld922's Avatar
    Join Date
    Jun 2009
    Posts
    2,896
    Thanks
    23
    Thanked 619 Times in 561 Posts
    Blog Entries
    18

    Default Re: Perlin Noise (and other Java 2d graphics questions)

    Here's the final version of the code, if anyone's interested. If anyone still has suggestions on how to speed this up (preferably with hardware, I don't think the software is going to get much faster), I'd gladly take them.

    I re-implemented large sections of the code inside of worker threads since the BufferedImage can be partitioned into sub-sections and manipulated from multiple threads at the same time.

    The code is provided as-is with limited support from me (just ask here and your question might be answered). You're free to use the code any way you want.

    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(4096, 4096, 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();
    		}
    	}
    }

    And the performance statistics on my computer (again, for a 4096x4096 map):
    Note that the statistics for generating noise maps and combining them has been combined since the threading code makes it impossible to separate the statistics for them.

    Generating lookup table
    0
    Generating noise maps and combining...
    3650
    Adjusting image
    1828
    writing image file
    3090
    edit:

    hmm.. not really sure why, but writing the image file ended up being faster, too. That portion in the code wasn't changed, though :p
    Last edited by helloworld922; June 11th, 2010 at 07:48 PM.

Similar Threads

  1. graphics in job?
    By SweetyStacey in forum The Cafe
    Replies: 10
    Last Post: May 3rd, 2010, 03:29 PM
  2. Noobie to Java questions!! :D
    By chansey123 in forum What's Wrong With My Code?
    Replies: 4
    Last Post: March 17th, 2010, 11:53 AM
  3. Java Questions! :)
    By xs4rdx in forum Java Theory & Questions
    Replies: 0
    Last Post: February 21st, 2010, 08:40 AM
  4. Few very basic Java questions.
    By 01001010 in forum Java Theory & Questions
    Replies: 2
    Last Post: February 13th, 2010, 01:14 PM
  5. How do i use graphics with Java?
    By DarrenReeder in forum Java Theory & Questions
    Replies: 4
    Last Post: December 27th, 2009, 05:16 PM