fastest image copying?

what is the fastest method of copying an Image/BufferedImage (is there a speed difference between using each one?) to a new Image object (so that they contain the same data, but are not the same reference, same thing as cloning an object).

I could make a new image object, and draw the desired image to the new image object’s graphics, but I have found that this is not as fast as I want. I also tried this (but it’s also slow):

	public BufferedImage copy(BufferedImage image) {
		boolean isAlphaPremultiplied = image.isAlphaPremultiplied();
		ColorModel colorModel = image.getColorModel();
		WritableRaster writableRaster = image.copyData(null);
		return new BufferedImage(colorModel, writableRaster, isAlphaPremultiplied, null);
	}

Help?

EDIT: Also, the reason I need to copy images is because in my application you can draw things. So after each drawing action is perform (like using the pen tool), it saves the image to an Array of images (the drawing history) so when you undo an action, it goes to the array to get the last image submitted to it. Is there a better solution?

Drawing is indeed extremely slow. I wouldn’t use this approach at all, though - for reasonably-sized images, you’ll run out of memory very quickly. As an example, a 1024x1024 image takes up 1024x1024x4 = 4MB.

I don’t know what the standard way of doing something like this would be, but at the very least you could just save the dirty area and not the whole image - for something like the pen tool, that would mean just saving what the area you drew on looked like beforehand.

Hmm… just thinking about it now, maybe this would work - what if you saved a “checkpoint” image and a current one, and then the sequence of operations it took to get from the checkpoint to current?

So the checkpoint is an old image, and you know that you did - fill(color, x, y), pen_draw(color, x, y), etc to get to current. Then if you need to step back, you would take the checkpoint image and apply all the operations to it again, up to (but not including) the operation you want to undo.

You could then set an undo buffer depth and, once it’s reached, apply the first operation in the sequence to the checkpoint image and then discard the operation. So when you draw, you’d be applying two operations - one to draw on the current image, and one to update the checkpoint to be operations away from current.

1024x1024x4 = 4MB (4 bytes per pixel)

I meant megabits, not megabytes ;D

Well, that’s what I ended up calculating, anyway. Thank you for spotting that.

Out of curiosity I did a test to see how long it would take to copy a BufferedImage to another BufferedImage by just drawing the first image onto the second. On average it took about 6.6 milliseconds (6603092 nanoseconds) to copy a 1024x1024pixel BufferedImage that is filled with random colored pixels. That seems fast enough to me, but I don’t know how your programs structured so I can’t really vouch for that time being acceptable. Just thought I’d share.

EDIT: Now I’m getting averages of around 5.4 milliseconds.

That’s slow for what should be just copying a bit of data. But to your point, that might be good enough for what the app needs to do, and what image sizes it works with.

The other problem here is it’s actually not going to give you the same image you started with. AFAIK the draw call does some anti-aliasing (not sure if you can turn that off, maybe you can), but as-is you’ll end up with a copy that’s subtly different from the source image. Not entirely sure if it always happens - might be it’ll only show up if you have transparency in the image, but it does happen.

Yah like I said, I have no idea if that timing is acceptable for the OP’s needs. I had no idea that the draw method did anti-aliasing though, thanks for the info on that. Of course I really wouldn’t notice it in my test program since it’s really just random colors.

AFAIK, Anti-Aliasing is not applied to images. Try turning on anti-aliasing and rotate an image: you’ll notice the image will have jagged edges :wink:

It’s definitely doing some interpolation between neighboring pixel values Edit#2 - or not, as it still happens in the case of a 1x1 image.

The case where I really noticed it was an image with transparent pixels that were (255,255,255,0). Ended up with very pronounced white areas around the edge of the sprite when it was re-drawn.

Then started looking at other sprites and realized they were also affected, just not to a degree that was immediately noticeable.

Edit:
Here’s some code to show what I mean.


import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;

public class ImageTest {
	public static void putPixel(WritableRaster data, int x, int y, Color c) {
		int[] i = new int[4];
		i[0] = c.getRed();
		i[1] = c.getGreen();
		i[2] = c.getBlue();
		i[3] = c.getAlpha();
		data.setPixel(x, y, i);
	}
	public static BufferedImage createBlankImage(int size) {
		BufferedImage b = new BufferedImage(size, size,BufferedImage.TYPE_4BYTE_ABGR);
		WritableRaster data = b.getRaster();
		for (int i = 0; i < b.getWidth(); i++) {
			for (int j = 0; j < b.getHeight(); j++) {
				putPixel(data, i, j, new Color(0,0,0,0));
			}
		}
		return b;
	}
	
	public static void main(String[] args) {
		BufferedImage src = createBlankImage(1);
		BufferedImage dest = createBlankImage(1);
		
		WritableRaster srcRaster = src.getRaster();
		putPixel(srcRaster, 0, 0, new Color(155,255,55,55));
		
		int r = srcRaster.getSample(0, 0, 0);
		int g = srcRaster.getSample(0, 0, 1);
		int b = srcRaster.getSample(0, 0, 2);
		int a = srcRaster.getSample(0, 0, 3);
		System.out.println(String.format("Before: %d,%d,%d,%d", r, g, b, a));
		
		Graphics2D graphics = dest.createGraphics();
		graphics.drawImage(src, 0, 0, null);
		
		WritableRaster destRaster = dest.getRaster();
		r = destRaster.getSample(0, 0, 0);
		g = destRaster.getSample(0, 0, 1);
		b = destRaster.getSample(0, 0, 2);
		a = destRaster.getSample(0, 0, 3);
		System.out.println(String.format("After: %d,%d,%d,%d", r, g, b, a));
	}
}

The output I get from it is this:


Before: 155,255,55,55
After: 153,255,56,55

Which seems to indicate there’s more than a straight copy of the data going on… unless I’m doing something silly here. I’m not an expert on Java2D by any means.

It would be fine if the copying speed did not jump up to 300+ ms sometimes for one image (usually it’s only around 2 ms for the size of the images I’m copying) and I’m using the method posted in first post.

@avm1979
Off-topic: you know there is a System.out.printf(String,Object…) method? :wink:

Thanks, did not know that! Had “sop” set up as a template in Eclipse for println (so useful…), now also added “sof” to expand into the printf.

I think I’ve been conditioned not to expect the standard Java APIs to provide anything conveniently short :wink:

Whoa…you can set shorcuts to methods in Eclipse?? O_O
Medal for you too sir!

Off-Topic: If only Eclipse did it like NetBeans, I seem to remeber being able to type sout, hit tab, and have it expand to System.out.println(""); with the cursor between the quotes. There are a few features I miss from NetBeans… not enough to use it though ;D

@counterp If you care I did testing with your copy method, it’s about 3 milliseconds for a 1024x1024 image.

syso CTRL+SPACE

:o Thank you Riven

There’s a whole slew of templates like this, and you can also define your own.

Window -> Preferences -> Java -> Editor -> Templates

in my drawing application, I use a software renderer that is able to save it’s old state.

so for example, I divide the whole canvas up into 32x32 tiles and store them in a hashmap.


	class UndoData {
		int [] pixelData = new int[32 * 32];
	}

the above class stores the undo information for a single tile. in each level of undo, I store a map of these objects, in the form Map<Point, UndoData>

now, whenever the user uses the pen, it calls a method with the following signature:


public void drawBrush(int x, int y, int size, int alpha, int color, int [] pixels, int width, int height, Map<Point, UndoData> undoData)

and then when a pixel is modified, it is placed into the undoData map (creating a UndoData object if one doesnt already exist for the 32x32 area im currently drawing in)

as for the undo queue, I have a structure like the following:


Deque<Map<Point,UndoData>> data = new ArrayDeque() (or LinkedList, whatever)

append the current map to the queue when the user releases the mouse:


public void onPointerRelease(PointerEvent pEvt) {
 ...
 data.append(currentUndoState);
}

and when the user presses CTRL+Z, all I do is


Map<Point, UndoData> shit = data.pop();
for (Entry<Point,UndoData> entry:shit.entrySet()) {
 Point x = entry.getKey();
 recoverTile(x.x<<5,x.y<<5,entry.getValue());
}

the recoverTile method would look something like the following:


void recoverTile(int x, int y, UndoData shit) {
 Tile t = getTile(x,y);
 copyPixels(shit.pixelData, t.pixelData); // replace
}

HTH

ps: if you’re interested, check out my drawing application: http://www.java-gaming.org/index.php/topic,24478.0.html (inb4 shameless advertising)

if you have any more questions, pls ask

EDIT: Here is my full class, feel free to use it.


package com.uxpaint.internal;

import java.awt.Point;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.util.Deque;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;

/**
 *
 * @author David
 */
public class UndoManager {
	static class UndoData {
		int [] pixelData = new int[32 * 32];
	}

	private Deque<UndoState> stateQueue = new LinkedList<UndoState>();
	private UndoState currentState = new UndoState();

	public UndoState popState() {
		return stateQueue.pollLast();
	}

	public void pushState() {
		stateQueue.addLast(currentState);
		currentState = new UndoState();
	}

	public static class UndoState extends HashMap<Point, UndoData> {
		public UndoState() {

		}

		public void undo(BufferedImage image) {
			final int
					width = image.getWidth(),
					height = image.getHeight();
			final int [] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
			for (Iterator<Map.Entry<Point, UndoData> > i = this.entrySet().iterator(); i.hasNext();) {
				Map.Entry<Point, UndoData> e = i.next();
				Point p = e.getKey();
				final int startOffset = ((p.y<<5) * width) + (p.x<<5);
				UndoData undoData = e.getValue();
				for (int x = 0; x < 32; x++) {
					for (int y = 0; y < 32; y++) {
						final int pos = (x<<5) + y;
						if (undoData.pixelData[pos] != 0) {
							pixels[startOffset + width * y + x] = undoData.pixelData[pos];
						}
					}
				}
			}
			clear();
		}

		public void putPixel(int pixel, int x, int y) {
			Point pt = new Point(x >> 5, y >> 5);
			UndoData data = get(pt);
			if (data == null) {
				data = new UndoData();
				put(pt, data);
			}
			int xpos = x-(pt.x<<5);
			int ypos = y-(pt.y<<5);
			data.pixelData[xpos+(ypos<<5)] = pixel;
		}

		public int calculateMemorySize() {
			return size() * 32 * 32 * 4;
		}
	}
}


when this above class is used compared to recovering the whole image, it uses roughly ~20% of how much memory would be required if you saved the whole canvas every step.

it is also about ~1000% faster when you need to undo a step.

Hey, a post that’s on-topic :smiley:

Thank you very much, I will check out your application and test out this class when I have enough free time!