"Images" in Java (part 3)

Outline

Using a Resource Folder

A resource folder is where we are putting information that our program uses. This program is normally a large package with a number of Java classes. It might need a bunch of images, sound files, and maybe some text files too (for high score or saved games).
If we have a lot of material to put into our resource folder, we can make subfolders: /res/images, /res/audio, /res/savedGames
One problem is that Java treats Jar files and resource folders in the same way. Jar files are read-only, consequently Java makes resource folders hard to write to.
I haven't figured out how to do this yet :(

If you are using Eclipse, when you do any reading or writing from files it puts the files in the Java project folder, not in the folder where your source code (java file) is.

I don’t know what other IDEs do.

If you just use a text editor (Notepad++ or vim) and use “javac MyFile.java” to compile the program and “java MyFile” to run it, the files will be put in the same folder that you’re in.

Use this simple program to test it: SaveToFile.java

← the files are in the Java Project

This is becomes a problem
(i) if we have a lot of files that we need for our program (e.g. images)
(ii) we want to package our program as a Jar file and give it to someone so that they can run it.

First make a new source folder in the Java package that you want

Right click on the package ("Cool Programs")
then choose "New" and "Source Folder", then name the folder. I'm calling in "res" for resource.

    

You can see it here in the image below


If you have a normal folder (not src, bin, or anything special), you can right click on it, and use it as a source folder like this:

We don't need to do this, since we did the "New / Source Folder" step above.

If you choose "Configure Build Path" from the same menu, you'll see that your "res" folder is a source folder.

Getting Java to use the Resource folder

First of all, we're going to be talking about images here.
I tried to figure out how to read/write text files from a resource folder and it is even more complicated. Here is some info if you really want it.

Our old way of loading images does not read them from a resource folder: Image im = new ImageIcon(fn).getImage();

First step: get the URL or InputStream

Both of these ways work, but the syntax is different.

These methods work for reading any file, not just images

URLInputStream
Requires a / at the beginning of filename (path)No / required
Works with ImageIcon as well as ImageIO.readOnly works with ImageIO.read
URL url = this.getClass().getResource("/" + filename);
InputStream inputStr = DrawAndScaleImage.class.getClassLoader().getResourceAsStream(filename);

Second step: read in the image

NOTE: if either the URL or InputStream is null (if it can't find the file), then your program WILL crash with a null pointer error.
This did not happen when we read in an image using a String or File object.

//Method 1. URL using ImageIcon
if (imageURL != null) {
	ImageIcon icon = new ImageIcon(imageURL);				
	image = icon.getImage();
} else {
	JOptionPane.showMessageDialog(null, "An image failed to load: " + filename , "ERROR", JOptionPane.ERROR_MESSAGE);
}

//2. URL using ImageIO.read which requires a try-catch. 
//Sadly the try-catch won't prevent the program from crashing with IllegalArgumentException:
if (imageURL != null) {
	try {
		image = ImageIO.read(imageURL);
	} catch (IOException e) {
		e.printStackTrace();
	}
} else {
	JOptionPane.showMessageDialog(null, "An image failed to load: " + filename , "ERROR", JOptionPane.ERROR_MESSAGE);
}

//Method 3. InputStream 
if (imageURL != null) {
	try {
		image = ImageIO.read(inputStr);
	} catch (IOException e) {
		e.printStackTrace();
	}
} else {
	JOptionPane.showMessageDialog(null, "An image failed to load: " + filename , "ERROR", JOptionPane.ERROR_MESSAGE);
}

HTML formatting of code created using ToHtml.com on 2022-01-05 00:00:20 UTC

A complete program

Grab the fish.jpeg image

/* This program demonstrates different ways of reading in an image from a resource folder in Java.
 * It also shows how to manipulate images (flipping and resizing them)
 * M. Harwood. Jan 2022.
 */

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;


public class DrawAndScaleImage extends JFrame{

	public static void main(String[] args) {
		new DrawAndScaleImage();
	}

	//This could be a BufferedImage
	Image imgFish;

	DrawAndScaleImage(){	

		this.setTitle("Drawing images");
		this.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );	
		this.setSize(600,400);
		this.setLocationRelativeTo(null);
		JPanel grpanel = new GrPanel();
		grpanel.setBackground(new Color(20,50,100));

		String filename =  "fish.jpeg"; //this is in a resource folder		
		imgFish = loadImage(filename);

		this.add(grpanel,BorderLayout.CENTER);
		this.setVisible(true);
	}


	private class GrPanel extends JPanel {
		
		public void paintComponent(Graphics g) {
			super.paintComponent(g);
			g.setColor(Color.WHITE);
			if (imgFish == null) return;

			//it turns out that the fish image is 204x204
			int fishW = imgFish.getWidth(null);	
			int fishH = imgFish.getHeight(null);							

			//g.drawImage(imgFish, 0,0, null); //this would draw the fish at the top, left corner

			//Draw the fish at the right middle of the screen, normal size:
			g.drawImage(imgFish, 380, 50, fishW, fishH, null);
			g.drawString("Original image", 400, 45);
			g.drawString(String.format("Size is %sx%s",fishW,fishH), 400,70+fishH);

			//Flip the fish sideways:
			//if (image != null) g.drawImage(image, 100, 0, 0, 100,this); //does not work to flip the image

			//Only use the left half of the fish for the image
			//and flip it horizontally, and space it 15px from the top corner 	
			int spc = 15;
			g.drawImage(imgFish, 100+spc, 0+spc, 0+spc, 100+spc, //destination: (100,0), to (0,100). ie. x goes from 100 to 0 (so it's flipped right to left)
					0, 0, fishW/2,fishH, null); 

			//Middle two fish. ABout half normal size. 
			//(1) upright				
			g.drawImage(imgFish, 200, 100, 300, 200,    0, 0, fishW, fishH, null); //y goes from 100 to 200 (half the size of the original)
			//(2) flipped vertically
			g.drawImage(imgFish, 200, 300, 300, 200,    0, 0, fishW, fishH, null); //y goes from 300 to 200 (half size and upside down)

		}
	}

	Image loadImage(String filename) {

		Image image = null;
		
		/* ********************************************************************************
		 *  If it's not in the resource folder use one of our other methods like this one:
		 
		image = new ImageIcon(filename).getImage();
		//System.out.println(image.getClass());
		if (image.getWidth(null) == -1) {
			image = null;
			JOptionPane.showMessageDialog(null, "An image failed to load: " + fn , "ERROR", JOptionPane.ERROR_MESSAGE);
		}	
		 
		//or this one
		try {
			image = ImageIO.read(new File(filename));
		} catch (IOException e) {
			System.out.println(e.toString());
			JOptionPane.showMessageDialog(null, "An image failed to load: " + fn , "ERROR", JOptionPane.ERROR_MESSAGE);
		}
		******************************************************************************** */
		
		// Requires a / at the beginning of the filename
		URL imageURL = this.getClass().getResource("/" + filename); 

		// Filename must be relative (no / at beginning) 
		InputStream inputStr = DrawAndScaleImage.class.getClassLoader().getResourceAsStream(filename);

		//Method 1. URL using ImageIcon
		if (imageURL != null) {
			ImageIcon icon = new ImageIcon(imageURL);				
			image = icon.getImage();
		} else {
			JOptionPane.showMessageDialog(null, "An image failed to load: " + filename , "ERROR", JOptionPane.ERROR_MESSAGE);
		}

/*
		//2. URL using ImageIO.read 
		//Sadly the try-catch won't prevent the program from crashing with IllegalArgumentException:
		if (imageURL != null) {
			try {
				image = ImageIO.read(imageURL);
			} catch (IOException e) {
				e.printStackTrace();
			}
		} else {
			JOptionPane.showMessageDialog(null, "An image failed to load: " + filename , "ERROR", JOptionPane.ERROR_MESSAGE);
		}

		//Method 3. InputStream using ImageIO.read
		if (imageURL != null) {
			try {
				image = ImageIO.read(inputStr);
			} catch (IOException e) {
				e.printStackTrace();
			}
		} else {
			JOptionPane.showMessageDialog(null, "An image failed to load: " + filename , "ERROR", JOptionPane.ERROR_MESSAGE);
		}

*/
		return image;
	}

}