questions about modifying image (raster)

Due to lack of tutorials and books that are, like, readable I started experimanting with manipulating image data (raster). This is the thread where I’ll ask questions until I get the image manipulation :slight_smile:
I have some questions… am I doing it right? I load BufferedImage, take it’s raster, create a compatibile writable raster, change it, create new BuffferedImage using changed raster.
The thing is, in compilable code below (you need “images/player.png” to make it work) it’s all fine if I create new image with original raster (a duplicate image for testing if it works), but if I createCompatibleWritableRaster() and use that raster, new image dosen’t appear. What’s wrong? Also I use BufferedImage constructor that takes width, height and type… and for some reason original_image.getType() return custom type (int 0) … I need to manually enter BufferedImage.TYPE_INT_ARGB (int 12) to make it work, otherwise constructor throws exception and complain it can’t use 0.
Thank you all.

Copied image dosen’t show here:


import java.awt.*;
import java.awt.image.*;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.*;

public class Test extends JFrame {
	private BufferedImage original_image;
	private BufferedImage copy_image;
	
	public static void main(String[] args) {
		Test frame = new Test();
	}
	
	public Test() {
		super("Image processing Test");

		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		this.setBounds(0, 0, 400, 300);
		this.setLocation(300,250);
		
		original_image = loadImage("images/player.png"); // uses ImageIO.read()
		int imgHeight = original_image.getHeight();
		int imgWidth = original_image.getWidth();
		
		Raster org_raster = original_image.getData();
		copy_image = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_ARGB);
		WritableRaster mod_raster = org_raster.createCompatibleWritableRaster();
//		for (int i=0; i<40; i++) {
//			mod_raster.setPixel(i,0, new int[] {255,0,0,0});
//		}
		copy_image.setData(mod_raster);
		
		this.setVisible(true);
	}

	public BufferedImage loadImage(String path) {
		URL url = null;
		try {
	        	url = Test.class.getClassLoader().getResource(path);
	                return ImageIO.read(url);
	        } catch (Exception e) {
	    	        e.printStackTrace();
	    	        System.out.println("Error loading image: " + path + " " + url);
	    	        System.exit(0);
	    	        return null;
	        }
	}

	public void paint(Graphics g) {
		super.paint(g);
		Graphics2D g2d = (Graphics2D)g;
		g2d.clearRect(0, 0, getWidth(), getHeight());
		g2d.drawImage(original_image, 50, 50, null);
		g2d.drawImage(copy_image, 50, 100, null);
		g2d.dispose();
	}

}

Hi! It’s possible I’m misunderstanding what it is you’re trying to do, but here are some comments for what they’re worth.

If you want to modify the pixels of a BufferedImage, then I believe that you can just do

BufferedImage im = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
WritableRaster raster = im.getRaster();

There’s no need to create a separate raster.

Alternatively, of course, you could call getRGB()/setRGB() on the BufferedImage directly to change individual pixels, but I’ve found that to be slow if you’re modifying a lot of the image.

Once you have a WritableRaster, you can use

int pixels[] = ((DataBufferInt)(raster.getDataBuffer())).getData();

to get very direct access to the pixels. You have to be pretty sure of the type (e.g, INT_ARGB) of the BufferedImage if you’re going to do this.

I’m not entirely sure about this, but I remember hearing something about images loaded from external sources often having slightly odd formats (image type 0?). The recommendation is to create a fresh BufferedImage (of your preferred type), and to draw the loaded image onto that. Then discard the original loaded image. Maybe someone else can clarify this?

I’ve never used createCompatibleWritableRaster() (I bet there aren’t many people who have!) but from the documentation I don’t see anything about it copying the content of the original raster. It only copies the “type” of the raster. So that may be why your copied image is blank.

Finally, if you do manage to manipulate the raster of an image, then the received wisdom (received specifically from this thread: http://www.java-gaming.org/forums/index.php?topic=14064.0) is that that image will no longer be accelerated. If you want the image to be accelerated, then you need to either copy it onto a fresh BufferedImage, or use an alternative approach for doing your image manipulation. (Java APIs include a lot of image manipulation functionality, although I must admit I’ve never used much of it.)

Hope that’s some help.
Simon

Wow you really helped me, tnx a lot man, I owe you one!

What I’m trying here is to load an image, display it, create a new, modified one and then display it also for comparison… this is just for me learning how to modify images.

yeah my png returned 0 on getType().

oh damn, you’re right, this is my fault for not reading carefully… it says: “and a new initialized DataBuffer”, so that’s all zeroes in array I guess

yeah I’m aware of that… I’ll create a fresh instance of BufferedImage with modified data to get it accelerated.

Awsome help, thank you very much.

EDIT: note that WritableRaster.setPixel(x,y, int[4]) I’m using in this example uses int[4] as RGBA information, not ARGB as you might (and I) thought.

[b]I just remembered I could post the solution…

here’s the method for making image (argument 1) look more like color (argument 2)
… since it works by adding rgb values to all pixels it dosen’t work with black color (ads all zeroes)… didn’t test for gray. I know this is primitive but it works for me so don’t have reason to develop it further.[/b]


	public BufferedImage modifyImageColor(BufferedImage image, Color color) {
		int[] pixel_value = new int[4];
		int[] color_value = new int[] {color.getRed() / 2, color.getGreen() / 2, color.getBlue() / 2, 0};	// Kova: store pixel values of color in darker form 
		int image_width = image.getWidth();
		int image_height = image.getHeight();
		
		WritableRaster raster = (WritableRaster)image.getData();
		for (int i=0; i<image_width; i++) {
			for (int j=0; j<image_height; j++) {
				raster.getPixel(i, j, pixel_value);									// Kova: get pixel value from picture
				for (int k=0; k<4; k++) {
					pixel_value[k] += color_value[k];								// ... and than add desired color values so it would look like more like desired color
					pixel_value[k] = (pixel_value[k] > 255) ? 255 : pixel_value[k];	// Kova: if pixel value larger than it shuold be, max it to largest possible
				}
				raster.setPixel(i, j, pixel_value);
			}
		}
		BufferedImage modified_image = new BufferedImage(image_width, image_height, BufferedImage.TYPE_INT_ARGB);
		modified_image.setData(raster);
		return modified_image;
	}

need color picker also? … here’s how to modify JComboBox to use colors:
as items, add Colors and use custom renderer (setRenderer() ) like this:


	private class CellColorRenderer extends JLabel implements ListCellRenderer {
	     public CellColorRenderer() {
	         setOpaque(true);
	     }
	     
	     public void setBackground(Color col) {}

	     public void setMyBackground(Color col) {
	        super.setBackground(col);
	     }	     
	     
	     public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
	    	 setText(" ");
	    	 setMyBackground((Color)value);
	    	 setForeground((Color)value);
	         return this;
	     }
	 }

Hi.

The fastest (and imho the best) way to do what you want is to do what dishmoth said. You’ll want to do something like this:



public BufferedImage getModifiedImage(BufferedImage origImage)
{
       BufferedImage resultImage = myGraphicsConfig.createCompatibleImage(origImage.getWidth(),origImage.getHeight(),origImage.getTransparency());
       Graphics2D g = resultImage.createGraphics();
       g.drawImage(origImage,0,0,null);
       g.dispose();

       WritableRaster raster = resultImage.getRaster();
       // normally GraphicsConfiguration returns 32 bit images, but you can apply JVM flags
       // to force for instance 16bit, - then you have to use DataBufferShort and a short[] array.
       DataBufferInt dataBuffer = (DataBufferInt)raster.getDataBuffer();
       int[] data = dataBuffer.getData();

       // work directly on the data array, - dont touch anything else.

      return resultImage;
}


I don’t guarantee that the code will work. I just wrote it directly in here. But any errors should be typos, or small stuff. The concept is valid.

thank you, I will modify the method

Not something I’ve ever tried myself, but you may be able to perform similar manipulations of pixel colours in a BufferedImage by using one of the classes that implements java.awt.image.BufferedImageOp (for example, colorConvertOp sounds promising).

On the downside, these classes look quite fiddly to use, they may not be as fast as you’d like, and they’re not nearly as much fun as manipulating pixels by hand. :wink:

Simon

I’ve been messing around with quick modification of BufferedImages and I’ve found that using int pixels[] = ((DataBufferInt)(raster.getDataBuffer())).getData(); is incredibly slow to perform, and so is very unrealistic to use for any game with a rapidly modified BufferedImage.

I am logging on average around 100 milliseconds to call this method on an 800x600 RGBA BufferedImage. Each one of my timesteps is 35 milliseconds, and so this obviously creates an incredibly noticeable lag. Bad news. Don’t use this method more than once, at the very initial loading of an image (and even then it will slow you down).

While getSample doesn’t seem capable of returning all the information within the image, it is much much faster and returns whether or not a pixel is transparent, which is all I need to know for my uses. I recommend trying that (alternatively getPixel works just as well, but has that int[] as a parameter which just annoys me)

the int[] array returned is not a copy, you only need to call getDataBuffer() etc once.

Yeah but then don’t I need to pass the int[] array back into the BufferedImage once I modify it, thereby making this method worse for WritableRaster’s and only really useful for regular Raster’s? Or are they linked by reference?

Also, if you have an RBGA image or RBG, then getPixel will return very simply the Red, Blue, Green, and Alpha values very simply and quickly, and I think is therefore much more useful when you are looking at localized pieces of large BufferedImage’s or small BufferedImage’s.

short answer:
the int[] array is the final underlying array the bufferedimage ultimately uses.

long answer:

in BufferedImage

in the constructor of BufferedImage:
for RGB or RGBA images


colorModel = new DirectColorModel(...
raster = colorModel.createCompatibleWritableRaster(width, height);

for color bit depth > 16, in DirectColorModel.createCompatibleWritableRaster(width,height)


if (pixel_bits > 16) {
   return Raster.createPackedRaster(DataBuffer.TYPE_INT,
                                             w,h,bandmasks,null); 
}

in Raster.createPackedRaster(DataBuffer.TYPE_INT

notice the DataBufferInt which is created here


...
case DataBuffer.TYPE_INT:
            d = new DataBufferInt(w*h);
...
SunWritableRaster raster = (SunWritableRaster)
            createPackedRaster(d, w, h, w, bandMasks, location);
....

in Raster.createPackedRaster(DataBuffer buffer, …


case DataBuffer.TYPE_INT:
            return new IntegerInterleavedRaster(sppsm, dataBuffer, location);
....

finally, an IntegerInterleavedRaster is created, with the integer databuffer as basis…

now, in BufferedImage we can retrieve a reference to this raster.


private WritableRaster raster;
public WritableRaster getRaster() {
      return raster;
}

since IntegerInterleavedRaster ultimately extends WritableRaster, we can look at the method there


public DataBuffer getDataBuffer() {
     return dataBuffer;
}

and finally, in DataBufferInt


private int[] data; 
public int[] getData() {
      return data;
}

so the method outlined will get you a reference to the underlying int array, for INT_ RGB or ARGB images.