Farbwerte eines Bildes in Pixel-Array abspeichern

[QUOTE=DVDB]Sagt mir, als Java-Neuling nichts. Wie benutzt man es? Ist das sowas:

BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
int[] pixels = ((DataBufferInt)image.getRaster().getDataBuffer()).getData();
```[/QUOTE]Das nimm jetzt nicht persönlich, aber ganau an diesen Stellen erkennt man nicht blos Java-Neulinge, sondern auch die, die sich in diese Materie noch nicht eingelesen haben. Zeile 3 macht das BI nämlich ausgesprochen langsam und das nur, weil das (ein) DataArray davon in der JVM verfügbar ist und verändert werden kann, ohne das die Pixeldaten verifiziert wurden. Die Folge davon: Das Array muß bei jedem Paintzyklus umkopiert und dabei verifiziert werden. Sowas sollte man nur machen, wenn man genau weiss, dass man das Bild nicht mehr zeichnen will.
Was ich meinte, sieht ungefähr so aus:
```import java.awt.Point;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferInt;
import java.awt.image.Raster;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
import java.util.IdentityHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;

public class ObservableImage extends BufferedImage {
	private static final Map<Thread, ImageEvent> THREAD_2_EVENT = new IdentityHashMap<>();

	private static final ColorModel MODEL = new ColorModel(32) {
		@Override
		public int getRed(int pixel) {
			return (pixel >> 16) & 0xFF;
		}

		@Override
		public int getGreen(int pixel) {
			return (pixel >> 8) & 0xFF;
		}

		@Override
		public int getBlue(int pixel) {
			return pixel & 0xFF;
		}

		@Override
		public int getAlpha(int pixel) {
			return (pixel >> 24) & 0xFF;
		}

		@Override
		public boolean isCompatibleRaster(Raster raster) {
			return (raster.getSampleModel() instanceof ObservableSampleModel);
		}

		@Override
		public SampleModel createCompatibleSampleModel(int w, int h) {
			return new ObservableSampleModel(w, h);
		}

		@Override
		public WritableRaster createCompatibleWritableRaster(int w, int h) {
			return new WritableRaster(createCompatibleSampleModel(w, h), new Point()) {
			};
		}

		@Override
		public Object getDataElements(int rgb, Object pixel) {
			if(pixel == null) {
				pixel = new int[1];
			}
			((int[]) pixel)[0] = rgb;
			return pixel;
		}
	};

	private static class SampleObservable extends Observable {
		@Override
		protected synchronized void setChanged() {
			super.setChanged();
		}
	}

	public static interface ImageListener {
		void imageUpdated(ImageEvent ie);
	}

	public static final class ImageEvent {
		private ObservableImage source;
		private int x, y, newColor, oldColor;

		private ImageEvent() {
		}

		private void setData(ObservableImage source, int[] data) {
			this.source = source;
			x = data[0];
			y = data[1];
			newColor = data[2];
			oldColor = data[3];
		}

		public ObservableImage getSource() {
			return source;
		}

		public int getX() {
			return x;
		}

		public int getY() {
			return y;
		}

		public int getOldColor() {
			return oldColor;
		}

		public int getNewColor() {
			return newColor;
		}

		private static ImageEvent forThread(Thread t) {
			ImageEvent rc = THREAD_2_EVENT.get(t);
			if(rc == null) {
				rc = new ImageEvent();
				THREAD_2_EVENT.put(t, rc);
			}
			return rc;
		}
	}

	private static class ObservableSampleModel extends SampleModel {
		private final SampleObservable observable = new SampleObservable();
		int[] data = new int[4];

		public ObservableSampleModel(int w, int h) {
			super(DataBuffer.TYPE_INT, w, h, 1);
		}

		void addObserver(Observer obs) {
			observable.addObserver(obs);
		}

		void removeObserver(Observer obs) {
			observable.deleteObserver(obs);
		}

		@Override
		public int getNumDataElements() {
			return 1;
		}

		@Override
		public Object getDataElements(int x, int y, Object obj, DataBuffer data) {
			if(obj == null) {
				obj = new int[1];
			}
			((int[]) obj)[0] = getSample(x, y, 0, data);
			return obj;
		}

		@Override
		public void setDataElements(int x, int y, Object obj, DataBuffer data) {
			int[] in = (int[]) obj;
			if(in.length < 1) {
				throw new IllegalArgumentException("invalid data type");
			}
			setSample(x, y, 0, in[0], data);
		}

		@Override
		public int getSample(int x, int y, int b, DataBuffer data) {
			if(b != 0) {
				return 0;
			}
			return data.getElem(y * width + x);
		}

		@Override
		public void setSample(int x, int y, int b, int s, DataBuffer d) {
			if(b == 0) {
				int o = getSample(x, y, b, d);
				data[0] = x;
				data[1] = y;
				data[2] = s;
				data[3] = o;
				d.setElem(y * width + x, s);
				observable.setChanged();
				observable.notifyObservers(data);
				return;
			}
			throw new IllegalArgumentException("invalid band");
		}

		@Override
		public SampleModel createCompatibleSampleModel(int w, int h) {
			return new ObservableSampleModel(w, h);
		}

		@Override
		public SampleModel createSubsetSampleModel(int[] bands) {
			throw new UnsupportedOperationException("this samplemodel doesn't have any sub bands");
		}

		@Override
		public DataBuffer createDataBuffer() {
			return new DataBufferInt(width * height);
		}

		@Override
		public int[] getSampleSize() {
			return new int[] {32};
		}

		@Override
		public int getSampleSize(int band) {
			return (band == 0)? 32 : 0;
		}
	}

	private List<ImageListener> listeners;
	private Observer obs;

	public ObservableImage(int width, int height) {
		this(MODEL, MODEL.createCompatibleWritableRaster(width, height));
	}

	private ObservableImage(ColorModel model, WritableRaster raster) {
		super(model, raster, model.isAlphaPremultiplied(), null);
	}

	public synchronized void addImageListener(ImageListener il) {
		if(listeners == null) {
			listeners = new LinkedList<>();
			obs = new Observer() {
				@Override
				public void update(Observable o, Object arg) {
					ImageEvent ie = ImageEvent.forThread(Thread.currentThread());
					ie.setData(ObservableImage.this, (int[]) arg);
					for(ImageListener il : listeners) {
						il.imageUpdated(ie);
					}
				}
			};
			((ObservableSampleModel) getSampleModel()).addObserver(obs);
		}
		listeners.add(il);
	}

	public synchronized void removeObserver(ImageListener il) {
		if(listeners == null) {
			return;
		}
		listeners.remove(il);
		if(listeners.isEmpty()) {
			((ObservableSampleModel) getSampleModel()).removeObserver(obs);
			listeners = null;
			obs = null;
		}
	}
}```Dem Image kann ein Listener angehängt werden, wodurch man jede einzelne Veränderung eines Pixels nachvollziehen kann. Dieses "Verworrene" mit der Thread_2_Event-Map findet seine Begründung darin, dass Pixel meistens unheimlich schnell hintereinander verändert werden (müssen). Glücklicherweise kann pro Thread immer nur ein Pixel zu einer Zeit geschrieben werden, so dass man die Events im Thread immer wieder verwenden kann, das spart extrem viel an performancelastigen new-Anweisungen (und in diesem Fall sind new-Anweisungen extrem performancelastig und zwar so, dass es auffällt!).

Das Image möchte ich nun noch soweit erweitern, dass man jede einzelne Aktion die Pixel verändert, rückgängig machen kann, wenn im Listener eine Kollision festgestellt wurde, das ist aber ein anderes Thema.

Nichts was man jemandem empfehlen sollte, der gerade versucht, sein erstes Spiel zu schreiben…
BTW: Hast du da einen eigenen http://docs.oracle.com/javase/6/docs/api/java/lang/ThreadLocal.html gebaut?

[QUOTE=Marco13]Nichts was man jemandem empfehlen sollte, der gerade versucht, sein erstes Spiel zu schreiben…
BTW: Hast du da einen eigenen http://docs.oracle.com/javase/6/docs/api/java/lang/ThreadLocal.html gebaut?[/QUOTE]

  1. Ok, ist was dran, dabei braucht er die Klasse nur kopieren und kann sie ganz einfach verwenden.

  2. Kann sein, kenne ThreadLocal noch nicht… muß ich mir mal ansehen… sterben ThreadLocals eigentlich auch mit dem entsprechenden Thread? Dann müsste ich mich nämlich nicht mehr um die Entsorgung der Events von verstorbenen Threads kümmern, was hier noch fehlen würde.

  1. Nun, nicht direkt, aber über die verwendete ThreadLocalMap mit ihren WeakReferences dann eben doch.