package io.gitlab.jfronny.ImgJava.util;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

/**
 * Bildklasse fuer die Simulation von Processing-Befehlen
 * <p>
 * Diese Klasse stellt ein BufferedImage bereit, in das mit Processing-Befehlen gezeichnet
 * werden kann.
 * Zusaetzlich kann ein Bildanzeiger ueber jede Aenderung des Bildes informiert werden,
 * um "Zurueck"-Befehle zu ermoeglichen. Der Bildanzeiger ist entweder eine normale Java
 * ScrollPane oder ein Actor aus Greenfoot.
 * Die Dokumentation der einzelnen Zeichenmethoden ist der Processing-Reference
 * (https://processing.org/reference/ steht unter CC-Lizenz: https://creativecommons.org/)
 * entnommen und mit Deepl.com ins Deutsche uebersetzt.
 *
 * @author Thomas Schaller (ZPG Informatik Klasse 9)
 * @version 1.2 from 06.12.2019
 */
//TODO arrays to proper classes
public class Picture {

    // Einstellungmoeglichkeiten fuer das Zeichnen von Rechtecken und Ellipsen 
    // RADIUS = Mittelpunkt+Radius wird gegeben, CENTER = Mittelpunkt und Breite/Hoehe wird gegeben, 
    // CORNER = Linke obere Ecke + Breite/Hoehe,  CORNERS = Linke obere und rechte untere Ecke
    public static final int RADIUS = 1;
    public static final int CENTER = 2;
    public static final int CORNER = 3;
    public static final int CORNERS = 4;

    // gespeichertes Bild, 
    private BufferedImage image;
    private Graphics2D g;

    // aktuelle Farbeinstellungen
    private Color background;
    private Color pencolor;
    private Color fillcolor;

    // aktuelle Stiftdicke
    private double stroke;

    // aktueller Koordinatenmodus von Rechtecken und Ellipsen
    private int ellipseMode = CENTER;
    private int rectMode = CORNER;

    // aktueller Font
    private Font textfont = null;

    // muss ein Bildanzeiger benachrichtigt werden
    private ImageObserver observer = null;
    private boolean autorefresh = true;

    /**
     * Erzeugt ein Bild mit Standardgroesse 500x400
     */
    public Picture() {
        this(500, 400);
    }

    /**
     * Erzeugt ein Bild der angegeben Groesse
     *
     * @param width  Breite des Bildes
     * @param height Hoehe des Bildes
     */
    public Picture(int width, int height) {
        this(width, height, "D0D0D0");
    }

    /**
     * Erzeugt ein Bild aus einer Datei
     *
     * @param file Dateiname des Bildes
     */
    public Picture(Path file) throws IOException {
        load(file);
    }

    /**
     * Erzeugt ein Bild aus einer Ressource im images/ Ordner
     * 
     * @param resourceName Name der Ressource
     */
    public Picture(String resourceName) throws IOException {
        loadResource("images/" + resourceName);
    }

    /**
     * Erzeugt ein Bild der angegebenen Groesse mit festgelegtem Hintergrund
     *
     * @param width      Breite des Bildes
     * @param height     Hoehe des Bildes
     * @param background Farbe des Hintergrunds
     */
    public Picture(int width, int height, String background) {
        this.background = Color.decode("0x" + background);
        this.pencolor = new Color(0, 0, 0);
        this.stroke = 1;
        this.fillcolor = null;
        makeImage(width, height);
    }

    private void makeImage(int width, int height) {
        this.image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        g = createGraphics();
        g.setColor(this.background);
        g.fillRect(0, 0, width - 1, height - 1);
    }
    
    private Graphics2D createGraphics() {
        Graphics2D g = image.createGraphics();
        // Antialiasing
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        // Rendering
        g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        // Text
        g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        // Color
        g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
        return g;
    }

    /**
     * Legt fest, wer das Bild anzeigt.
     * Diese ermoeglicht die Benachrichtung des Observers, wenn sich das Bild aendert.
     *
     * @param observer Anzeiger des Bildes
     */
    public Picture setObserver(ImageObserver observer) {
        this.observer = observer;
        return this;
    }

    /**
     * Direktes Abfragen des Bildes (fuer interne Zwecke)
     *
     * @return Bild, das gerade gespeichert ist.
     */
    public BufferedImage getImage() {
        return image;
    }

    /**
     * Direktes Setzen des Bildes (fuer interne Zwecke)
     *
     * @param b Bild, das gespeichert werden soll.
     */
    public Picture setImage(BufferedImage b) {
        image = b;
        return this;
    }

    /**
     * Definiert die Dimension der Breite und Hoehe des Anzeigefensters in Pixeleinheiten.
     * Die eingebauten Variablen Breite und Hoehe werden durch die an diese Funktion uebergebenen Parameter festgelegt. So weist beispielsweise
     * der Befehl size(640, 480) der Variablen Breite 640 und der Variablen Hoehe 480 zu.
     *
     * @param width  Breite des Bildes
     * @param height Hoehe des Bildes
     */
    public Picture size(int width, int height) {
        makeImage(width, height);

        g.setColor(background);
        g.fillRect(0, 0, width - 1, height - 1);
        if (observer != null) observer.resize();
        return repaint();
    }

    /**
     * Liefert die Breite des Bildes zurueck.
     *
     * @return Breite des Bildes
     */
    public int getWidth() {
        return image.getWidth();
    }

    /**
     * Liefert die Hoehe des Bildes zurueck.
     *
     * @return Hoehe des Bildes
     */
    public int getHeight() {
        return image.getHeight();
    }

    /**
     * Legt fest, ob nach jedem Zeichenbefehl automatisch das Bild auch in
     * der Oberflaeche aktualisiert wird. Die Einstellung "false" beschleunigt
     * das Zeichnen aufwaendiger Bilder und verhindert "Flackern".
     * Das Neuzeichnen kann durch die Methode "refresh" gezielt ausgeloest werden.
     *
     * @param autoRefresh true = nach jedem Zeichenbefehl die Anzeige aktualisieren, false= nur durch die Methode refresh neu zeichnen
     */
    public Picture setAutoRefresh(boolean autoRefresh) {
        this.autorefresh = autoRefresh;
        return this;
    }

    /**
     * Auch die anzeigenden Klasse wird zum Neuzeichnen aufgefordert.
     */
    public Picture repaint() {
        return repaint(false);
    }

    /**
     * Ein repaint() (das Neuzeichnen) kann manuell erzwungen werden.
     */
    public Picture repaint(boolean force) {
        if (observer != null && (force || autorefresh)) {
            observer.repaint(this);
        }
        return this;
    }

    // ----------------------------------------- Zeichenfunktionen -----------------------------------------------

    /**
     * Loescht den Inhalt des Bildes.
     * Der Hintergrund wird mit der Hintergrundfarbe neu gefuellt.
     */

    public Picture clear() {
        makeImage(image.getWidth(), image.getHeight());

        g.setColor(background);
        g.fillRect(0, 0, image.getWidth() - 1, image.getHeight() - 1);
        return repaint();
    }

    /**
     * Konvertiert die in einem bestimmten Modus gegebenen Koordinaten in die Java-uebliche Links_Oben_Breite_Hoehe Version
     * Die Aenderungen werden direkt im Array vorgenommen
     *
     * @param coord Array mit vier Koordinateneintraegen im gegebenen Modus
     * @param mode  Modus der Koordinaten (CORNER, CORNERS, RADIUS oder CENTER)
     */
    private Picture convert(int[] coord, int mode) {
        switch (mode) {
            case CORNER:
                break;
            case CORNERS:
                coord[2] -= coord[0];
                coord[3] -= coord[1];
                break;
            case RADIUS:
                coord[2] *= 2;
                coord[3] *= 2;
            case CENTER:
                coord[0] -= coord[2] / 2;
                coord[1] -= coord[3] / 2;
        }
        return this;
    }

    /**
     * Aendert den Koordinaten-Modus beim Zeichnen von Rechtecken.
     * Aendert die Position, von der aus Rechtecke gezeichnet werden, indem es die Art und Weise aendert, wie Parameter, die an rect() uebergeben werden, interpretiert werden.
     * Der Standardmodus ist rectMode(Bild.CORNER), der die ersten beiden Parameter von rect() als die linke obere Ecke der Form interpretiert,
     * waehrend der dritte und vierte Parameter seine Breite und Hoehe sind.
     * rectMode(Bild.CORNERS) interpretiert die ersten beiden Parameter von rect() als die Position einer Ecke
     * und die dritten und vierten Parameter als die Position der gegenueberliegenden Ecke.
     * rectMode(Bild.CENTER) interpretiert die ersten beiden Parameter von rect() als Mittelpunkt der Form,
     * waehrend der dritte und vierte Parameter seine Breite und Hoehe sind.
     * rectMode(RADIUS) verwendet auch die ersten beiden Parameter von rect() als Mittelpunkt der Form,
     * verwendet aber den dritten und vierten Parameter, um die Haelfte der Breite und Hoehe der Formen festzulegen.
     *
     * @param mode Modus der Koordinateninterpretation (CORNER, CORNERS, CENTER oder RADIUS)
     */
    public Picture rectMode(int mode) {
        rectMode = mode;
        return this;
    }

    /**
     * Aendert den Koordinaten-Modus beim Zeichnen von Kreisen/Ellipsen.
     * Aendert die Position, von der aus Kreise/Ellipsen gezeichnet werden, indem es die Art und Weise aendert, wie Parameter, die an ellipse() uebergeben werden, interpretiert werden.
     * Der Standardmodus ist ellipseMode(Bild.CENTER), der die ersten beiden Parameter von ellipse() als Mittelpunkt der Form interpretiert,
     * waehrend der dritte und vierte Parameter seine Breite und Hoehe sind.
     * ellipseMode(Bild.CORNER) interpretiert die ersten beiden Parameter von ellipse() als die Position einer Ecke
     * und die dritten und vierten Parameter als Breite und Hoehe der Form.
     * ellipseMode(Bild.CORNERS) interpretiert die ersten beiden Parameter von ellipse() als die Position einer Ecke
     * und die dritten und vierten Parameter als die Position der gegenueberliegenden Ecke.
     * ellipseMode(RADIUS) verwendet auch die ersten beiden Parameter von ellipse() als Mittelpunkt der Form,
     * verwendet aber den dritten und vierten Parameter, um die Haelfte der Breite und Hoehe der Formen festzulegen.
     *
     * @param mode Modus der Koordinateninterpretation (CORNER, CORNERS, CENTER oder RADIUS)
     */
    public Picture ellipseMode(int mode) {
        ellipseMode = mode;
        return this;
    }

    /**
     * Zeichnet eine Linie (einen direkten Weg zwischen zwei Punkten) auf den Bildschirm.
     * Um eine Linie einzufaerben, verwenden Sie die {@link #stroke(int, int, int) stroke()} Funktion. Eine Zeile kann nicht gefuellt werden, daher hat die Funktion fill() keinen
     * Einfluss auf die Farbe einer Zeile. Linien werden standardmaessig mit einer Breite von einem Pixel gezeichnet, dies kann jedoch mit der Funktion
     * {@link #strokeWeight(double) strokeWeight()} geaendert werden.
     *
     * @param x1 x-Koordinate des 1. Punktes
     * @param y1 y-Koordinate des 1. Punktes
     * @param x2 x-Koordinate des 2. Punktes
     * @param y2 y-Koordinate des 2. Punktes
     */
    public Picture line(int x1, int y1, int x2, int y2) {
        if (stroke > 0) {
            g.setColor(pencolor);
            g.setStroke(new BasicStroke((float) stroke));
            // if(antialiasing) antialise(); 
            g.drawLine(x1, y1, x2, y2);
        }
        return repaint();
    }

    /**
     * Zeichnet ein Rechteck auf das Bild.
     * Standardmaessig legen die ersten beiden Parameter die Position der linken oberen Ecke fest, der dritte die Breite und der vierte die Hoehe.
     * Die Art und Weise, wie diese Parameter interpretiert werden, kann jedoch mit der Funktion {@link #rectMode(int) rectMode()} geaendert werden.
     * Durch den Befehl {@link #fill(int, int, int) fill()} /{@link #noFill() noFill()}  kann die Fuellfarbe des Rechtecks gewaehlt werden, durch {@link #stroke(int, int, int) stroke()}/{@link #noStroke() noStroke()}  die Rahmenfarbe.
     *
     * @param a meist die x-Koordinate der linken oberen Ecke (kann durch rectMode() geaendert werden).
     * @param b meist die y-Koordinate der linken oberen Ecke (kann durch rectMode() geaendert werden).
     * @param c meist die Breite des Rechtecks (kann durch rectMode() geaendert werden).
     * @param d meist die Hoehe des Rechtecks (kann durch rectMode() geaendert werden).
     */
    public Picture rect(int a, int b, int c, int d) {
        int[] coord = {a, b, c, d};
        convert(coord, rectMode);
        if (fillcolor != null) {
            g.setColor(fillcolor);
            g.fillRect(coord[0], coord[1], coord[2], coord[3]);
        }
        if (pencolor != null) {
            g.setColor(pencolor);
            g.setStroke(new BasicStroke((float) stroke));
            g.drawRect(coord[0], coord[1], coord[2], coord[3]);
        }
        return repaint();
    }

    /**
     * Zeichnet eine Ellipse/Kreis auf das Bild.
     * Standardmaessig legen die ersten beiden Parameter die Position des Mittelpunkts fest, der dritte die Breite und der vierte die Hoehe.
     * Die Art und Weise, wie diese Parameter interpretiert werden, kann jedoch mit der Funktion {@link #ellipseMode(int) ellipseMode()} geaendert werden.
     * Durch den Befehl {@link #fill(int, int, int) fill()} /{@link #noFill() noFill()} kann die Fuellfarbe des Rechtecks gewaehlt werden, durch {@link #stroke(int, int, int) stroke()}/{@link #noStroke() noStroke()}  die Rahmenfarbe.
     *
     * @param a meist die x-Koordinate des Mittelpunkts (kann durch ellipseMode() geaendert werden).
     * @param b meist die y-Koordinate des Mittelpunkts (kann durch ellipseMode() geaendert werden).
     * @param c meist die Breite des Rechtecks (kann durch ellipseMode() geaendert werden).
     * @param d meist die Hoehe des Rechtecks (kann durch ellipseMode() geaendert werden).
     */
    public Picture ellipse(int a, int b, int c, int d) {
        int[] coord = {a, b, c, d};
        convert(coord, ellipseMode);
        if (fillcolor != null) {
            g.setColor(fillcolor);
            g.fillOval(coord[0], coord[1], coord[2], coord[3]);
        }
        if (pencolor != null) {
            g.setColor(pencolor);
            g.setStroke(new BasicStroke((float) stroke));
            // if(antialiasing) antialise();
            g.drawOval(coord[0], coord[1], coord[2], coord[3]);
        }
        return repaint();
    }

    /**
     * Zeichnet ein Dreieck auf das Bild.
     * Ein Dreieck ist eine Ebene, die durch die Verbindung von drei Punkten entsteht. Die ersten beiden Argumente spezifizieren den
     * ersten Punkt, die mittleren beiden Argumente spezifizieren den zweiten Punkt und die letzten beiden Argumente spezifizieren den dritten Punkt.
     * Durch den Befehl {@link #fill(int, int, int) fill()} /{@link #noFill() noFill()} kann die Fuellfarbe des Rechtecks gewaehlt werden, durch {@link #stroke(int, int, int) stroke()}/{@link #noStroke() noStroke()}  die Rahmenfarbe.
     *
     * @param x1 meist die x-Koordinate des 1. Punkts.
     * @param y1 meist die y-Koordinate des 1. Punkts.
     * @param x2 meist die x-Koordinate des 2. Punkts.
     * @param y2 meist die y-Koordinate des 2. Punkts.
     * @param x3 meist die x-Koordinate des 3. Punkts.
     * @param y3 meist die y-Koordinate des 3. Punkts.
     */
    public Picture triangle(int x1, int y1, int x2, int y2, int x3, int y3) {
        int[] px = {x1, x2, x3};
        int[] py = {y1, y2, y3};
        return polygon(px, py);
    }

    /**
     * Zeichnet ein Viereck auf das Bild.
     * Ein Viereck ist ein vierseitiges Polygon. Es ist aehnlich wie ein Rechteck, aber die Winkel zwischen seinen Kanten
     * sind nicht auf neunzig Grad beschraenkt. Das erste Paar von Parametern (x1,y1) setzt den ersten Scheitelpunkt und die nachfolgenden
     * Paare sollten im Uhrzeigersinn oder gegen den Uhrzeigersinn um die definierte Form herum verlaufen.
     * Durch den Befehl {@link #fill(int, int, int) fill()} /{@link #noFill() noFill()} kann die Fuellfarbe des Rechtecks gewaehlt werden, durch {@link #stroke(int, int, int) stroke()}/{@link #noStroke() noStroke()}  die Rahmenfarbe.
     *
     * @param x1 meist die x-Koordinate des 1. Punkts.
     * @param y1 meist die y-Koordinate des 1. Punkts.
     * @param x2 meist die x-Koordinate des 2. Punkts.
     * @param y2 meist die y-Koordinate des 2. Punkts.
     * @param x3 meist die x-Koordinate des 3. Punkts.
     * @param y3 meist die y-Koordinate des 3. Punkts.
     * @param x4 meist die x-Koordinate des 3. Punkts.
     * @param y4 meist die y-Koordinate des 3. Punkts.
     */
    public Picture quad(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4) {
        int[] px = {x1, x2, x3, x4};
        int[] py = {y1, y2, y3, y4};
        polygon(px, py);
        return this;
    }

    /**
     * Zeichnet ein Polygon auf das Bild.
     * Gleich lange Listen von x und y-Koordinaten bestimmen die Eckpunkte des Polygons.
     * Durch den Befehl {@link #fill(int, int, int) fill()} /{@link #noFill() noFill()} kann die Fuellfarbe des Rechtecks gewaehlt werden, durch {@link #stroke(int, int, int) stroke()}/{@link #noStroke() noStroke()} die Rahmenfarbe.
     *
     * @param x Liste der x-Koordinaten der Punkte.
     * @param y Liste der y-Koordinaten der Punkte.
     */

    public Picture polygon(int[] x, int[] y) {
        if (fillcolor != null) {
            g.setColor(fillcolor);
            g.fillPolygon(x, y, y.length);
        }
        if (pencolor != null) {
            g.setColor(pencolor);
            g.setStroke(new BasicStroke((float) stroke));
            // if(antialiasing) antialise();
            g.drawPolygon(x, y, x.length);
        }
        return repaint();
    }

    /**
     * Zeichnet einen Punkt, d.h. einen Kreis in der Dimension eines Pixels.
     * Der erste Parameter ist der x-Wert fuer den Punkt, der zweite Wert ist der y-Wert fuer den Punkt.
     *
     * @param x x-Koordinate des Punktes
     * @param y y-Koordinate des Punktes
     */
    public Picture point(int x, int y) {
        ellipse(x, y, 1, 1);
        return this;
    }

    // ----------------------------------------- Schriftdarstellung -----------------------------------------------

    /**
     * Gibt einen Text an den gegebenen Koordinaten aus
     * Zur Ausgabe des Textes wird der ausgewaehlte Font verwendet. Dieser muss vorher mit {@link #textFont(Font) textFont() } festgelegt.
     *
     * @param s Text, der angezeigt werden soll
     * @param x x-Koordinate des Textanfangs
     * @param y y-Koordinate der Grundlinie des Textes.
     */
    public Picture text(String s, int x, int y) {
        if (pencolor != null) {
            if (fillcolor == null)
                g.setColor(Color.black);
            else
                g.setColor(fillcolor);
            g.setStroke(new BasicStroke((float) stroke));
            g.setFont(textfont);
            // if(antialiasing)g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
            g.drawString(s, x, y);
        }
        return repaint();
    }

    /**
     * Legt die Schriftart fuer Textausgaben fest.
     * Jeder uebliche Java-Font kann verwendet werden. Er kann mit z.B. Font f = new Font( "Arial", Font.PLAIN, 14 ); definiert werden.
     *
     * @param font ein Font-Objekt
     */
    public Picture textFont(Font font) {
        this.textfont = font;
        return this;
    }

    // ----------------------------------------- Farbfestlegungen -----------------------------------------------


    /**
     * Legt die Farbe fest, mit der Linien und Raender um Formen gezeichnet werden.
     * Diese Farbe wird hexadezimal in Form der RGB angegeben: z.B.  "CCFFAA" oder "004E23". Die Syntax verwendet sechs Ziffern - je zwei fuer die roten, gruenen und blauen Komponenten,
     * um eine Farbe anzugeben (genau wie Farben typischerweise in HTML und CSS angegeben werden).
     *
     * @param pencolor Stiftfarbe in Hexadezimaldarstellung
     */
    public Picture stroke(String pencolor) {
        this.pencolor = Color.decode("0x" + pencolor);
        return this;
    }

    /**
     * Legt die Farbe fest, mit der Linien und Raender um Formen gezeichnet werden.
     * Diese Farbe wird entweder als Graustufe (0-255) oder als 3-Byte RGB-Wert angegeben
     *
     * @param pencolor Stiftfarbe (0-255: Graustufe zwischen 0 schwarz und 255 weiss, sonst: c wird als 3-Byte RGB-Wert interpretiert)
     */
    public Picture stroke(int pencolor) {
        this.pencolor = new Color(pencolor);
        return this;
    }

    /**
     * Legt die Farbe fest, mit der Linien und Raender um Formen gezeichnet werden.
     * Diese Farbe wird komponentenweise als RGB-Wert angegeben
     *
     * @param r Rotanteil (0-255) der Stiftfarbe
     * @param g Gruenanteil (0-255) der Stiftfarbe
     * @param b Blauanteil (0-255) der Stiftfarbe
     */
    public Picture stroke(int r, int g, int b) {
        this.pencolor = new Color(r, g, b);
        return this;
    }

    /**
     * Legt fest, dass keine Linien oder Raender um Formen gezeichnet werden soll.
     */
    public Picture noStroke() {
        this.pencolor = null;
        return this;
    }

    /**
     * Legt die Breite des Strichs fuer Linien, Punkte und den Rand um Formen fest.
     * Alle Breiten werden in Pixeleinheiten angegeben.
     *
     * @param width Breite in Pixel
     */
    public Picture strokeWeight(double width) {
        this.stroke = width;
        return this;
    }

    /**
     * Legt die Farbe fest, mit der Formen gefuellt werden.
     * Diese Farbe wird hexadezimal in Form der RGB angegeben: z.B.  "CCFFAA" oder "004E23". Die Syntax verwendet sechs Ziffern - je zwei fuer die roten, gruenen und blauen Komponenten,
     * um eine Farbe anzugeben (genau wie Farben typischerweise in HTML und CSS angegeben werden).
     *
     * @param fillcolor Fuellfarbe in Hexadezimaldarstellung
     */
    public Picture fill(String fillcolor) {
        this.fillcolor = Color.decode("0x" + fillcolor);
        return this;
    }

    /**
     * Legt die Farbe fest, mit der Formen gefuellt werden.
     * Diese Farbe wird entweder als Graustufe (0-255) oder als 3-Byte RGB-Wert angegeben.
     *
     * @param fillcolor Fuellfarbe (0-255: Graustufe zwischen 0 schwarz und 255 weiss, sonst: c wird als 3-Byte RGB-Wert interpretiert)
     */
    public Picture fill(int fillcolor) {
        this.fillcolor = new Color(fillcolor);
        return this;
    }

    /**
     * Legt die Farbe fest, mit der Formen gefuellt werden.
     * Diese Farbe wird komponentenweise als RGB-Wert angegeben.
     *
     * @param r Rotanteil (0-255) der Fuellfarbe
     * @param g Gruenanteil (0-255) der Fuellfarbe
     * @param b Blauanteil (0-255) der Fuellfarbe
     */
    public Picture fill(int r, int g, int b) {
        this.fillcolor = new Color(r, g, b);
        return this;
    }

    /**
     * Legt fest, dass die Formen nicht gefuellt werden sollen.
     */
    public Picture noFill() {
        this.fillcolor = null;
        return this;
    }

    /**
     * Die Funktion background() setzt die Farbe, die fuer den Hintergrund des Bildes verwendet wird. Der Standardhintergrund ist hellgrau.
     * Es ist nicht moeglich, den Alpha-Parameter Transparenz mit Hintergrundfarben auf der Hauptzeichnungsoberflaeche zu verwenden.
     *
     * @param c Farbe fuer den Hintergrund (0-255: Graustufe zwischen 0 schwarz und 255 weiss, sonst: c wird als 3-Byte RGB-Wert interpretiert)
     */
    public Picture background(int c) {
        if (c < 256) {
            this.background = new Color(c, c, c);
        } else {
            this.background = new Color(c);
        }
        return this.clear();
    }

    /**
     * Die Funktion background() setzt die Farbe, die fuer den Hintergrund des Bildes verwendet wird. Der Standardhintergrund ist hellgrau.
     * Es ist nicht moeglich, den Alpha-Parameter Transparenz mit Hintergrundfarben auf der Hauptzeichnungsoberflaeche zu verwenden.
     *
     * @param r Rotanteil (0-255) der Hintergrundfarbe
     * @param g Gruenanteil (0-255) der Hintergrundfarbe
     * @param b Blauanteil (0-255) der Hintergrundfarbe
     */
    public Picture background(int r, int g, int b) {
        this.background = new Color(r, g, b);
        return this.clear();
    }

    /**
     * Die Funktion background() setzt die Farbe, die fuer den Hintergrund des Bildes verwendet wird. Der Standardhintergrund ist hellgrau.
     * Es ist nicht moeglich, den Alpha-Parameter Transparenz mit Hintergrundfarben auf der Hauptzeichnungsoberflaeche zu verwenden.
     *
     * @param hex String    Farbe in Hexadezimalangabe
     */
    public Picture background(String hex) {
        this.background = Color.decode("0x" + hex);
        return this.clear();
    }

    // ----------------------------------------- Dateioperationen -----------------------------------------------

    /**
     * Laedt ein Bild aus dem Dateisystem.
     * Laedt ein Bild von einem Datentraeger und setzt Stiftfarbe und Fuellfarbe auf Standardwerte zurueck.
     *
     * @param file Dateipfad
     */
    public Picture load(String file) throws IOException {
        return load(Files.newInputStream(Paths.get(".").toAbsolutePath().normalize().resolve(file)));
    }

    /**
     * Laedt ein Bild aus dem Dateisystem.
     * Laedt ein Bild von einem Datentraeger und setzt Stiftfarbe und Fuellfarbe auf Standardwerte zurueck.
     *
     * @param file Dateipfad
     */
    public Picture load(Path file) throws IOException {
        return load(Files.newInputStream(file));
    }

    /**
     * Laedt ein Bild aus dem Dateisystem.
     * Laedt ein Bild von einem Datentraeger und setzt Stiftfarbe und Fuellfarbe auf Standardwerte zurueck.
     *
     * @param stream Bildquelle
     */
    public Picture load(InputStream stream) throws IOException {
        this.image = ImageIO.read(stream);
        this.g = createGraphics();
        this.background = Color.decode("0xD0D0D0");
        this.pencolor = new Color(0, 0, 0);
        this.fillcolor = null;
        this.stroke = 1;
        if (observer != null) observer.resize();
        return this.repaint();
    }

    /**
     * Laedt ein Bild aus einer Ressource.
     *
     * @param resource Ressourcenpfad
     */
    public Picture loadResource(String resource) throws IOException {
        return load(Thread.currentThread().getContextClassLoader().getResourceAsStream(resource));
    }

    /**
     * Speichert ein Bild.
     * Speichert ein Bild auf einem Datentraeger. Zulaessig sind die Dateiformate PNG und GIF. Die Dateiendung legt den Typ fest.
     * Standardmaessig wird die Dateiendung .png ergaenzt, wenn keine angegeben ist.
     *
     * @param file Neuer Dateipfad
     */
    public Picture save(Path file) throws IOException {
        String[] fn = file.getFileName().toString().split("\\.");
        if (fn.length == 0) {
            return save(file.resolve("unnamed.png"));
        }
        else if (fn.length < 2) {
            return save(file.getParent().resolve(fn[0] + ".png"));
        }
        else {
            ImageIO.write(image, fn[fn.length - 1].toUpperCase(), Files.newOutputStream(file));
            return this;
        }
    }

    /**
     * Speichert ein Bild.
     * Speichert ein Bild auf einem Datentraeger. Der Pfad ist relativ zum aktuellen.
     * Standardmaessig wird die Dateiendung .png ergaenzt, wenn keine angegeben ist.
     *
     * @param file Neuer Dateipfad
     */
    public Picture save(String file) throws IOException {
        return save(Paths.get(".").toAbsolutePath().normalize().resolve(file));
    }

    // ----------------------------------------- Sonstiges -----------------------------------------------  

    /**
     * Liefert das Bild als zweidimensionales Pixel-Array.
     *
     * @return zweidimensionales Array von Color-Objekten, die den Pixeln des Bildes entsprechen.
     */

    public Color[][] getPixelArray() {
        Color[][] pixel = new Color[image.getWidth()][image.getHeight()];
        for (int x = 0; x < image.getWidth(); x++) {
            for (int y = 0; y < image.getHeight(); y++) {
                pixel[x][y] = new java.awt.Color(image.getRGB(x, y));
            }
        }
        return pixel;
    }

    /**
     * Setzt das Bild neu auf Basis des Pixel-Arrays.
     * Die Groesse des Bildes wird nicht automatisch an das Array angepasst.
     *
     * @param pixel zweidimensionales Array von Color-Objekten
     */
    public Picture setPixelArray(Color[][] pixel) {
        size(pixel.length, pixel[0].length);

        for (int x = 0; x < image.getWidth(); x++) {
            for (int y = 0; y < image.getHeight(); y++) {
                g.setColor(pixel[x][y]);
                g.fillRect(x, y, 1, 1);
            }
        }
        repaint();
        return this;
    }
}
