/*
* Engine Alpha ist eine anfängerorientierte 2D-Gaming Engine.
*
* Copyright (c) 2011 - 2014 Michael Andonie and contributors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package ea.internal.gra;
import ea.BoundingRechteck;
import java.awt.*;
import java.awt.image.BufferedImage;
/**
* Ein PixelFeld ist eine Ansammlzung vieler Pixel, es kann gezeichnet werden.<br /> Es besteht aus
* mehreren Quadraten gleicher Groesse, die aneinandergereiht das Rechteck mit deren Groesse
* darstellen. <br /> <b>Achtung!</b> Pixelfelder leiten sich nicht aus der notwendigen Ueberklasse
* <code>Raum</code> ab, um direkt grafisch dargestellt werden zu koennen ein einzelnes Pixelfeld
* kann in einer unanimierten Figur dargestellt werden!
*
* @author Michael Andonie
*/
public class PixelFeld implements java.io.Serializable {
private static final long serialVersionUID = 78L;
/**
* Die Farbinformation der einzelnen Pixel.<br /> Ist einer dieser Werte <code>null</code>, so
* wird an dieser Position nicht gezeichnet.
*/
private final Color[][] farbe;
/**
* Speichert das Bild zwischen, damit das Rendern schneller geht.
*/
private transient BufferedImage cache;
/**
* Bei Änderungen wird dies falsch gesetzt.
*/
private transient boolean cacheOutdated;
/**
* Alternative Farbe fuer das einfarbige Zeichnen
*/
private Color alternativ = null;
/**
* Der Genauigkeitsfaktor.<br /> Die Groesse eines Unterquadrats ist das Multiplikationsergebnis
* des Faktors mit der Pixelgroesse.<br /> Daher <b>muss</b> Der Faktor groesser als 0 sein!
*/
private int faktor;
/**
* Gibt an, ob die Laenge sich seit dem letzten Wert geändert haben KÖNNTE.
*/
private boolean changed = true;
/**
* Die Pixelanzahl des letzten Kollisionstests.
*/
private int pixel = 0;
/**
* Konstruktor fuer Objekte der Klasse PixelFeld
*
* @param grX
* Die Breite der Figur in Quadraten
* @param grY
* Die Hoehe der Figur in Quadraten
* @param faktor
* Der Genauigkeitsfaktor der Figur. <b>MUSS</b> groesser als 0 zu sein !
*/
public PixelFeld (int grX, int grY, int faktor) {
farbe = new Color[grX][grY];
if (faktor <= 0) {
throw new IllegalArgumentException("Der Eingabefaktor muss größer als 0 sein. Deine Eingabe: " + faktor);
}
this.faktor = faktor;
this.cacheOutdated = true;
}
/**
* Setzt den Groessenfaktor des Feldes.
*
* @param faktor
* Der neue Groessenfaktor
*/
public void faktorSetzen (int faktor) {
if (faktor <= 0) {
throw new IllegalArgumentException("Zoomfaktor muss größer als 0 sein. Deine Eingabe: " + faktor);
}
this.faktor = faktor;
this.cacheOutdated = true;
}
/**
* @return Größenfaktor, der dieses Bild zeichnet.
*/
public int faktor () {
return faktor;
}
/**
* Gleicht dieses PixelFeld an ein anderes an, sodass beide genau dieselben Inhalte haben.
* <p/>
* <b>Achtung</b>: Hierfür müssen beide PixelFelder die selben Maße in Länge und Breite haben
* (hierbei zählt nicht der Größenfaktor, sondern die Anzahl an Unterquadraten in Richtung
* <code>x</code> und <code>y</code>.
*/
public void angleichen (PixelFeld f) {
if (f.hoeheN() == this.hoeheN() && f.breiteN() == this.breiteN()) {
for (int i = 0; i < farbe.length; i++) {
// Deutlich performanter als ein weiterer for-loop.
System.arraycopy(f.farbe[i], 0, this.farbe[i], 0, farbe[0].length);
}
} else {
throw new IllegalArgumentException("Achtung!\nDie beiden zum Angleich angeführten PixelFelder haben unterschiedliche Masse in Höhe und/oder Breite!");
}
this.cacheOutdated = true;
}
/**
* @return die Anzahl an Unterquadraten in Richtung X
*/
public int breiteN () {
return farbe.length;
}
/**
* @return die Anzahl an Unterquadraten in Richtung Y
*/
public int hoeheN () {
return farbe[0].length;
}
/**
* Ändert alle Farben des Feldes in ihr Negativ um.
*/
public void negativ () {
for (int i = 0; i < farbe.length; i++) {
for (int j = 0; j < farbe[0].length; j++) {
if (this.farbe[i][j] != null) {
this.farbe[i][j] = new Color(255 - farbe[i][j].getRed(), 255 - farbe[i][j].getGreen(), 255 - farbe[i][j].getBlue(), farbe[i][j].getAlpha());
}
}
}
this.cacheOutdated = true;
}
/**
* Hellt alle Farbwerte auf.
*/
public void heller () {
for (int i = 0; i < farbe.length; i++) {
for (int j = 0; j < farbe[0].length; j++) {
if (this.farbe[i][j] != null) {
this.farbe[i][j] = this.farbe[i][j].brighter();
}
}
}
this.cacheOutdated = true;
}
/**
* Dunkelt alle Farbwerte ab.
*/
public void dunkler () {
for (int i = 0; i < farbe.length; i++) {
for (int j = 0; j < farbe[0].length; j++) {
if (this.farbe[i][j] != null) {
this.farbe[i][j] = this.farbe[i][j].darker();
}
}
}
this.cacheOutdated = true;
}
/**
* Transformiert alle Farbwerte um einen entsprechenden Betrag.<br /> Bei Uebertreten des
* Definitionsbereiches bleibtwird bei den Grenzen (0 bzw. 255) gehalten.
*
* @param r
* Der Rot-Aenderungswert
* @param g
* Der Gruen-Aenderungswert
* @param b
* Der Blau-Aenderungswert
*/
public void transformieren (int r, int g, int b) {
for (int i = 0; i < farbe.length; i++) {
for (int j = 0; j < farbe[0].length; j++) {
if (this.farbe[i][j] != null) {
Color c = farbe[i][j];
farbe[i][j] = new Color(zahlenSumme(c.getRed(), r), zahlenSumme(c.getGreen(), g), zahlenSumme(c.getBlue(), b));
}
}
}
}
/**
* Errechnet aus zwei Zahlen die Summe und setzt das Ergebnis, sofern nicht im
* Definitionsbereich der Farbwerte auf den naeheren Grenzwert (0 bzw. 255)
*
* @param a
* Wert 1
* @param b
* Wert 2
*
* @return Die Summe, unter Garantie im Definitionsbereich
*/
private static int zahlenSumme (int a, int b) {
return Math.max(0, Math.min(255, a + b));
}
/**
* Sorgt fuer die einfarbige Darstellung des Feldes
*
* @param c
* Diese Farbe ist nun fuer alle farbeigen Quadrate die Farbe
*/
public void einfaerben (Color c) {
alternativ = c;
}
/**
* Sorgt fuer die normale Darstellung des Feldes
*/
public void zurueckFaerben () {
alternativ = null;
}
/**
* Zeichnet das Feld mit einem bestimmten Verzug.
*
* @param g
* Das zeichnende Graphics-Objekt
* @param x
* Der Verzug in Richtung X
* @param y
* Der Verzug in Richtung Y
* @param spiegelX
* Ob dieses Pixelfeld entlang der X-Achse gespiegelt werden soll
* @param spiegelY
* Ob dieses Pixelfeld entlang der Y-Achse gespiegelt werden soll
*/
public void zeichnen (Graphics2D g, int x, int y, boolean spiegelX, boolean spiegelY) {
if (cache == null || cacheOutdated) {
int width = farbe.length * faktor, height = farbe.length == 0 ? 0 : farbe[0].length * faktor;
cache = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D cacheGraphics = cache.createGraphics();
for (int i = 0; i < farbe.length; i++) {
for (int j = 0; j < farbe[i].length; j++) {
Color c = farbe[i][j];
if (c == null) {
continue;
}
if (alternativ == null) {
cacheGraphics.setColor(c);
} else {
cacheGraphics.setColor(alternativ);
}
cacheGraphics.fillRect(i * faktor, j * faktor, faktor, faktor);
}
}
cacheGraphics.dispose();
}
int w = breite(), h = hoehe();
if (spiegelX && spiegelY) {
g.drawImage(cache, x + w, y + h, x, y, 0, 0, w, h, null);
} else if (spiegelX) {
g.drawImage(cache, x + w, y, x, y + h, 0, 0, w, h, null);
} else if (spiegelY) {
g.drawImage(cache, x, y + h, x + w, y, 0, 0, w, h, null);
} else {
g.drawImage(cache, x, y, null);
}
}
/**
* @return die Breite des Feldes in der Zeichenebene.
*/
public int breite () {
return farbe.length * faktor;
}
/**
* @return die Hoehe des Feldes in der Zeichenebene.
*/
public int hoehe () {
return farbe[0].length * faktor;
}
/**
* In dieser Methode werden die einzelnen Quadrate von ihrer Informationsdichte her
* zurueckgegeben.
*
* @return Die Farbinformationen ueber dieses Pixelfeld.
*/
public Color[][] getPic () {
return farbe;
}
/**
* Erstellt ein neues PixelFeld mit exakt denselben Eigenschaften wie dieses.<br /> Diese
* Methode wird vor allem intern im FigurenEditor verwendet.
*
* @return Ein neues PixelFeld-Objekt mit genau demselben Zustand wie dieses.
*/
public PixelFeld erstelleKlon () {
PixelFeld ret = new PixelFeld(farbe.length, farbe[0].length, faktor);
for (int i = 0; i < farbe.length; i++) {
for (int j = 0; j < farbe[0].length; j++) {
ret.farbeSetzen(i, j, farbe[i][j]);
}
}
return ret;
}
/**
* Setzt an einer bestimmten Position eine Farbe.
*
* @param x
* Die Relative X-Position des zu aendernden Quadrats
* @param y
* Die Relative Y-Position des zu aendernden Quadrats
* @param c
* Die neu zu setzende Farbe. Ist dieser Wert null, so wird dieses Unterquadrat nicht
* mitgezeichnet.
*/
public void farbeSetzen (int x, int y, Color c) {
farbe[x][y] = c;
this.cacheOutdated = true;
}
/**
* Berechnet <b>EXAKT</b> die Flaechen aus denen dieses Pixel-Feld besteht.
*
* @param x
* Die X-Startkoordinate der linken oberen Ecke
* @param y
* Die Y-Startkoordinate der linken oberen Ecke
*
* @return alle Flächen dieses Pixel-Feldes als Array aus Bounding-Rechtecken
*/
public BoundingRechteck[] flaechen (float x, float y) {
BoundingRechteck[] ret = new BoundingRechteck[anzahlPixel()];
int cnt = 0;
for (int i = 0; i < farbe.length; i++) {
for (int j = 0; j < farbe[0].length; j++) {
if (farbe[i][j] != null) {
ret[cnt] = new BoundingRechteck(x + i * faktor, y + j * faktor, faktor, faktor);
cnt++;
}
}
}
return ret;
}
/**
* Berechnet die Anzahl an Pixeln, die auf diesem PixelFeld liegen.
*
* @return Die Anzahl an tatsaechlichen Pixeln.
*/
public int anzahlPixel () {
if (changed) {
int neu = 0;
for (int i = 0; i < farbe.length; i++) {
for (int j = 0; j < farbe[0].length; j++) {
if (farbe[i][j] != null) {
neu++;
}
}
}
pixel = neu;
changed = false;
}
return pixel;
}
}