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.