content: /**
   * Copyright 2020 Alexander Herzog
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   * http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package ui.modeleditor.elements;
  
  import java.awt.BasicStroke;
  import java.awt.Color;
  import java.awt.Component;
  import java.awt.Dimension;
  import java.awt.Graphics;
  import java.awt.Graphics2D;
  import java.awt.Image;
  import java.awt.Point;
  import java.awt.Rectangle;
  import java.awt.Stroke;
  import java.awt.image.BufferedImage;
  import java.awt.image.ColorModel;
  import java.awt.image.WritableRaster;
  import java.io.ByteArrayInputStream;
  import java.io.ByteArrayOutputStream;
  import java.io.File;
  import java.io.IOException;
  import java.net.URL;
  import java.util.Base64;
  import java.util.Objects;
  import java.util.concurrent.Callable;
  import java.util.concurrent.ExecutionException;
  import java.util.concurrent.ExecutorService;
  import java.util.concurrent.Future;
  import java.util.concurrent.LinkedBlockingQueue;
  import java.util.concurrent.ThreadFactory;
  import java.util.concurrent.ThreadPoolExecutor;
  import java.util.concurrent.TimeUnit;
  
  import javax.imageio.ImageIO;
  import javax.swing.Icon;
  
  import org.w3c.dom.Document;
  import org.w3c.dom.Element;
  
  import language.Language;
  import simulator.editmodel.EditModel;
  import ui.images.Images;
  import ui.modeleditor.ModelClientData;
  import ui.modeleditor.ModelElementBaseDialog;
  import ui.modeleditor.ModelSequences;
  import ui.modeleditor.ModelSurface;
  import ui.modeleditor.ScaledImageCache;
  import ui.modeleditor.coreelements.ModelElement;
  import ui.modeleditor.coreelements.ModelElementDecoration;
  import ui.modeleditor.outputbuilder.HTMLOutputBuilder;
  import ui.modeleditor.outputbuilder.SpecialOutputBuilder;
  
  /**
   * Zeigt auf der Zeichenflche ein statisches Bild als Dekoration an.
   * @author Alexander Herzog
   */
  public class ModelElementImage extends ModelElementDecoration {
  	/**
  	 * Anzuzeigendes Bild
  	 * @see #getImage()
  	 * @see #setImage(BufferedImage)
  	 */
  	private BufferedImage image; /* Wird Immutable behandelt, kann daher als Referenz an Clone von ModelElementImage weitergegeben werden. Bei nderungen wird hier ein neuer Wert gesetzt. */
  
  	/**
  	 * Wird in {@link #drawImage(Graphics, double)} verwendet,
  	 * um mglichst wenig Rechenarbeit in
  	 * {@link ScaledImageCache#getScaledImage(String, Image, int, int)}
  	 * zu generieren.
  	 * @see #drawImage(Graphics, double)
  	 */
  	private String imageHash;
  
  	/**
  	 * Konstruktor der Klasse <code>ModelElementPosition</code>
  	 * @param model	Modell zu dem dieses Element gehren soll (kann spter nicht mehr gendert werden)
  	 * @param surface	Zeichenflche zu dem dieses Element gehren soll (kann spter nicht mehr gendert werden)
  	 * @param loadDummy	Soll initial das Dummy-Bild geladen werden? (Darf nur auf <code>false</code> gesetzt werden, wenn als nchstes direkt {@link #copyDataFrom(ModelElement)} aufgerufen wird)
  	 */
  	private ModelElementImage(final EditModel model, final ModelSurface surface, final boolean loadDummy) {
  		super(model,surface,new Dimension(50,50));
  		lineWidth=1;
  		if (loadDummy) loadDummyImage();
  	}
  
  	/**
  	 * Konstruktor der Klasse <code>ModelElementPosition</code>
  	 * @param model	Modell zu dem dieses Element gehren soll (kann spter nicht mehr gendert werden)
  	 * @param surface	Zeichenflche zu dem dieses Element gehren soll (kann spter nicht mehr gendert werden)
  	 */
  	public ModelElementImage(final EditModel model, final ModelSurface surface) {
  		this(model,surface,true);
  	}
  
  	/**
  	 * Icon, welches im "Element hinzufgen"-Dropdown-Men angezeigt werden soll.
  	 * @return	Icon fr das Dropdown-Men
  	 */
  	@Override
  	public Icon getAddElementIcon() {
  		return Images.MODELEDITOR_ELEMENT_IMAGE.getIcon();
  	}
  
  	/**
  	 * Tooltip fr den "Element hinzufgen"-Dropdown-Men-Eintrag.
  	 * @return Tooltip fr den "Element hinzufgen"-Dropdown-Meneintrag
  	 */
  	@Override
  	public String getToolTip() {
  		return Language.tr("Surface.Image.Tooltip");
  	}
  
  	/**
  	 * Name des Elementtyps fr die Anzeige im Kontextmen
  	 * @return	Name des Elementtyps
  	 */
  	@Override
  	public String getContextMenuElementName() {
  		return Language.tr("Surface.Image.Name");
  	}
  
  	/**
  	 * Liefert das aktuelle Bild zurck
  	 * @return	Aktuelles Bild
  	 */
  	public BufferedImage getImage() {
  		requireImageLoaded();
  		return image;
  	}
  
  	/**
  	 * Stellt ein neues Bild ein
  	 * @param image	Neues Bild
  	 */
  	public void setImage(final BufferedImage image) {
  		requireImageLoaded();
  		if (image!=null) this.image=copyImage(image);
  		imageHash=null;
  		fireChanged();
  	}
  
  	/**
  	 * Minimal einstellbare Linienbreite (hier: 0)
  	 * @return	Minimale Breite der Linie
  	 */
  	@Override
  	protected int getMinLineWidth() {
  		return 0;
  	}
  
  	/**
  	 * berprft, ob das Element mit dem angegebenen Element inhaltlich identisch ist.
  	 * @param element	Element mit dem dieses Element verglichen werden soll.
  	 * @return	Gibt <code>true</code> zurck, wenn die beiden Elemente identisch sind.
  	 */
  	@Override
  	public boolean equalsModelElement(ModelElement element) {
  		if (!super.equalsModelElement(element)) return false;
  
  		if (!(element instanceof ModelElementImage)) return false;
  
  		((ModelElementImage)element).requireImageLoaded();
  		requireImageLoaded();
  
  		if (!(((ModelElementImage)element).image==null && image==null)) {
  			if (((ModelElementImage)element).image==null || image==null) return false;
  			if (!Objects.equals(((ModelElementImage)element).imageHash,imageHash)) return false;
  			if (!ScaledImageCache.compare(((ModelElementImage)element).image,image)) return false;
  		}
  
  		return true;
  	}
  
  	/**
  	 * Erstellt eine Kopie eines Bildobjektes
  	 * @param source	Ausgangs-Bildobjekt
  	 * @return	Kopie des Bildobjektes
  	 */
  	private static BufferedImage copyImage(final BufferedImage source) {
  		final ColorModel cm=source.getColorModel();
  		final boolean isAlphaPremultiplied=cm.isAlphaPremultiplied();
  		final WritableRaster raster=source.copyData(null);
  		return new BufferedImage(cm,raster,isAlphaPremultiplied,null);
  	}
  
  	/**
  	 * bertrgt die Einstellungen von dem angegebenen Element auf dieses.
  	 * @param element	Element, von dem alle Einstellungen bernommen werden sollen
  	 */
  	@Override
  	public void copyDataFrom(ModelElement element) {
  		super.copyDataFrom(element);
  		if (element instanceof ModelElementImage) {
  			((ModelElementImage)element).requireImageLoaded(); /* Die Quelle muss mit dem Laden fertig sein. */
  			final BufferedImage source=((ModelElementImage)element).image;
  			/* dauert lange: image=copyImage(source); */
  			image=source; /* Drfen wir, da in der Quelle bei nderungen ein neues Objekt im image-Feld hinterlegt wird. Das bestehende Objekt selbst wird inhaltlich nie gendert. */
  			imageHash=null;
  		}
  	}
  
  	/**
  	 * Erstellt eine Kopie des Elements
  	 * @param model	Modell zu dem das kopierte Element gehren soll.
  	 * @param surface	Zeichenflche zu der das kopierte Element gehren soll.
  	 * @return	Kopiertes Element
  	 */
  	@Override
  	public ModelElementImage clone(final EditModel model, final ModelSurface surface) {
  		final ModelElementImage element=new ModelElementImage(model,surface,false); /* false=Dummy-Bild nicht laden, da gleich copyDataFrom aufgerufen wird. */
  		requireImageLoaded();
  		element.copyDataFrom(this);
  		return element;
  	}
  
  	/**
  	 * Stellt das Standard-Vorgabe-Bild ein.
  	 */
  	public void loadDummyImage() {
  		requireImageLoaded();
  		final URL[] urls=Images.MODELEDITOR_ELEMENT_IMAGE_EXAMPLE.getURLs();
  		final URL url=(urls==null || urls.length==0)?null:urls[0];
  		if (url==null) {
  			image=new BufferedImage(100,100,BufferedImage.TYPE_4BYTE_ABGR);
  		} else {
  			try {
  				image=ImageIO.read(url);
  			} catch (IOException e) {
  				image=new BufferedImage(100,100,BufferedImage.TYPE_4BYTE_ABGR);
  			}
  		}
  		imageHash=null;
  		fireChanged();
  	}
  
  	/**
  	 * Ldt ein Bild aus einer Datei
  	 * @param imageFile	Zu ladende Bilddatei
  	 * @return	Gibt <code>true</code> zurck, wenn das Bild erfolgreich geladen werden konnte
  	 */
  	public boolean loadImageFromFile(final File imageFile) {
  		requireImageLoaded();
  		try {
  			image=ImageIO.read(imageFile);
  			imageHash=null;
  		} catch (IOException e) {
  			return false;
  		}
  		fireChanged();
  		return true;
  	}
  
  	/**
  	 * Prft, ob sich ein Punkt innerhalb des Elements befindet
  	 * @param point	Punkt, bei dem geprft werden soll, ob er sich innerhalb des Elements befindet
  	 * @param zoom	Zoomfaktor
  	 * @return	Gibt <code>true</code> zurck, wenn sich der Punkt innerhalb des Elements befindet
  	 */
  	@Override
  	public boolean containsPoint(final Point point, final double zoom) {
  		if (super.containsPoint(point,zoom)) return true;
  
  		final Point p=getPosition(true);
  		final Dimension s=getSize();
  
  		if (isNearLine(new Point(p.x,p.y),new Point(p.x+s.width,p.y),point,zoom,lineWidth)) return true;
  		if (isNearLine(new Point(p.x+s.width,p.y),new Point(p.x+s.width,p.y+s.height),point,zoom,lineWidth)) return true;
  		if (isNearLine(new Point(p.x+s.width,p.y+s.height),new Point(p.x,p.y+s.height),point,zoom,lineWidth)) return true;
  		if (isNearLine(new Point(p.x,p.y+s.height),new Point(p.x,p.y),point,zoom,lineWidth)) return true;
  
  		return false;
  	}
  
  	/**
  	 * Liefert die Position eines bestimmten Randpunktes
  	 * @param index	0-basierender Index des Randpunktes
  	 * @return	Position des Randpunktes oder <code>null</code>, wenn der Index auerhalb des gltigen Bereichs liegt
  	 */
  	@Override
  	public Point getBorderPointPosition(final int index) {
  		final Point p=getPosition(true);
  		final Dimension s=getSize();
  
  		switch (index) {
  		case 0: return new Point(p);
  		case 1: return new Point(p.x+s.width,p.y);
  		case 2: return new Point(p.x+s.width,p.y+s.height);
  		case 3: return new Point(p.x,p.y+s.height);
  		default: return null;
  		}
  	}
  
  	/**
  	 * Setzt die Position eines Randpunktes
  	 * @param index	0-basierender Index des Randpunktes
  	 * @param point	Neue Position des Randpunktes
  	 */
  	@Override
  	public void setBorderPointPosition(final int index, final Point point) {
  		final Point p1=getPosition(true);
  		final Point p2=getLowerRightPosition();
  
  		switch (index) {
  		case 0:
  			setPosition(point);
  			setSize(new Dimension(p2.x-point.x,p2.y-point.y));
  			break;
  		case 1:
  			setPosition(new Point(p1.x,point.y));
  			setSize(new Dimension(point.x-p1.x,p2.y-point.y));
  			break;
  		case 2:
  			setSize(new Dimension(point.x-p1.x,point.y-p1.y));
  			break;
  		case 3:
  			setPosition(new Point(point.x,p1.y));
  			setSize(new Dimension(p2.x-point.x,point.y-p1.y));
  			break;
  		}
  	}
  
  	/**
  	 * Gibt das Bild aus.
  	 * @param graphics	Ausgabe-Grafikobjekt
  	 * @param zoom	Zoomfaktor
  	 */
  	private void drawImage(final Graphics graphics, final double zoom) {
  		requireImageLoaded();
  		if (image==null) return;
  
  		final Point p1=getPosition(true);
  		final Dimension s1=getSize();
  
  		final Point p2=new Point(p1.x+Math.min(0,s1.width),p1.y+Math.min(0,s1.height));
  		final Dimension s2=new Dimension(Math.abs(s1.width),Math.abs(s1.height));
  
  		final Point p3=new Point((int)Math.round(p2.x*zoom),(int)Math.round(p2.y*zoom));
  		final Dimension s3=new Dimension((int)Math.round(s2.width*zoom),(int)Math.round(s2.height*zoom));
  
  		final Object[] data=ScaledImageCache.getScaledImageCache().getScaledImage(imageHash,image,s3.width,s3.height);
  		final BufferedImage scaledImage=(BufferedImage)data[0];
  		imageHash=(String)data[1];
  
  		graphics.drawImage(scaledImage,p3.x,p3.y,null);
  	}
  
  	/**
  	 * Zeichnet den Rahmen um die Grafik
  	 * @param graphics	Ausgabe-Grafikobjekt
  	 * @param zoom	Zoomfaktor
  	 * @param showSelectionFrames	Normaler Rahmen (<code>false</code>) oder Rahmen fr ein selektiertes Element (<code>true</code>)
  	 */
  	private void drawFrame(final Graphics graphics, final double zoom, final boolean showSelectionFrames) {
  		if (lineWidth==0 && !isSelected() && !isSelectedArea()) return;
  
  		final Point p=getPosition(true);
  		final Dimension s=getSize();
  
  		final Graphics2D g2=(Graphics2D)graphics;
  		final Stroke saveStroke=g2.getStroke();
  
  		Color lineColor=color;
  		g2.setStroke(new BasicStroke((float)(lineWidth*zoom)));
  
  		if (isSelected() && showSelectionFrames) {
  			lineColor=Color.GREEN;
  			g2.setStroke(new BasicStroke((float)(Math.max(lineWidth,3)*zoom)));
  		} else {
  			if (isSelectedArea() && showSelectionFrames) {
  				lineColor=Color.BLUE;
  				g2.setStroke(new BasicStroke((float)(Math.max(lineWidth,3)*zoom)));
  			}
  		}
  
  		Rectangle rectangle=new Rectangle((int)Math.round(Math.min(p.x,p.x+s.width)*zoom),(int)Math.round(Math.min(p.y,p.y+s.height)*zoom),(int)Math.round(Math.abs(s.width)*zoom),(int)Math.round(Math.abs(s.height)*zoom));
  		g2.setColor(lineColor);
  		g2.draw(rectangle);
  
  		if (isSelected() && showSelectionFrames) {
  			drawBorderBox(g2,p,zoom);
  			drawBorderBox(g2,new Point(p.x+s.width,p.y),zoom);
  			drawBorderBox(g2,new Point(p.x,p.y+s.height),zoom);
  			drawBorderBox(g2,new Point(p.x+s.width,p.y+s.height),zoom);
  		}
  		g2.setStroke(saveStroke);
  	}
  
  	/**
  	 * Zeichnet das Element in ein <code>Graphics</code>-Objekt
  	 * @param graphics	<code>Graphics</code>-Objekt in das das Element eingezeichnet werden soll
  	 * @param drawRect	Tatschlich sichtbarer Ausschnitt
  	 * @param zoom	Zoomfaktor
  	 * @param showSelectionFrames	Rahmen anzeigen, wenn etwas ausgewhlt ist
  	 */
  	@Override
  	public void drawToGraphics(final Graphics graphics, final Rectangle drawRect, final double zoom, final boolean showSelectionFrames) {
  		setClip(graphics,drawRect,null);
  
  		/* Bild zeichnen */
  		drawImage(graphics,zoom);
  
  		/* Rahmen zeichnen */
  		drawFrame(graphics,zoom,showSelectionFrames);
  	}
  
  	/**
  	 * Liefert ein <code>Runnable</code>-Objekt zurck, welches aufgerufen werden kann, wenn die Eigenschaften des Elements verndert werden sollen.
  	 * @param owner	bergeordnetes Fenster
  	 * @param readOnly	Wird dieser Parameter auf <code>true</code> gesetzt, so wird die "Ok"-Schaltflche deaktiviert
  	 * @param clientData	Kundendaten-Objekt
  	 * @param sequences	Fertigungsplne-Liste
  	 * @return	<code>Runnable</code>-Objekt zur Einstellung der Eigenschaften oder <code>null</code>, wenn das Element keine Eigenschaften besitzt
  	 */
  	@Override
  	public Runnable getProperties(final Component owner, final boolean readOnly, final ModelClientData clientData, final ModelSequences sequences) {
  		return ()->{
  			requireImageLoaded();
  			new ModelElementImageDialog(owner,ModelElementImage.this,readOnly?ModelElementBaseDialog.ReadOnlyMode.FULL_READ_ONLY:ModelElementBaseDialog.ReadOnlyMode.ALLOW_ALL);
  		};
  	}
  
  	@Override
  	public Runnable getPropertiesSemiEditable(final Component owner, final ModelClientData clientData, final ModelSequences sequences) {
  		return ()->{
  			new ModelElementImageDialog(owner,ModelElementImage.this,ModelElementBaseDialog.ReadOnlyMode.ALLOW_CONTENT_DATA_EDIT);
  		};
  	}
  
  	/**
  	 * Liefert den jeweiligen xml-Element-Namen fr das Modell-Element
  	 * @return	xml-Element-Namen, der diesem Modell-Element zugeordnet werden soll
  	 */
  	@Override
  	public String[] getXMLNodeNames() {
  		return Language.trAll("Surface.Image.XML.Root");
  	}
  
  	/**
  	 * Speichert die Eigenschaften des Modell-Elements als Untereintrge eines xml-Knotens
  	 * @param doc	bergeordnetes xml-Dokument
  	 * @param node	bergeordneter xml-Knoten, in dessen Kindelementen die Daten des Objekts gespeichert werden sollen
  	 */
  	@Override
  	protected void addPropertiesDataToXML(final Document doc, final Element node) {
  		super.addPropertiesDataToXML(doc,node);
  
  		requireImageLoaded();
  
  		if (image!=null) {
  			Element sub=doc.createElement(Language.trPrimary("Surface.Image.XML.Data"));
  			node.appendChild(sub);
  			try {
  				final ByteArrayOutputStream stream=new ByteArrayOutputStream();
  				ImageIO.write(image,"png",stream);
  				sub.setTextContent(new String(Base64.getEncoder().encode(stream.toByteArray())));
  			} catch (IOException e) {}
  		}
  	}
  
  	/**
  	 * Bilder in eigenem Thread im Hintergrund laden?
  	 */
  	private static final boolean USE_BACKGROUND_LOAD=true;
  
  	/**
  	 * Thread-Pool fr den Hintergrund-Lader
  	 * @see #loadFuture
  	 * @see #USE_BACKGROUND_LOAD
  	 */
  	private static final ExecutorService loadService;
  
  	static {
  		final int coreCount=Runtime.getRuntime().availableProcessors();
  		loadService=new ThreadPoolExecutor(coreCount,coreCount,2,TimeUnit.SECONDS,new LinkedBlockingQueue<>(),(ThreadFactory)r->new Thread(r,"Model Element Image Loader"));
  		((ThreadPoolExecutor)loadService).allowCoreThreadTimeOut(true);
  	}
  
  	/**
  	 * Aufgabe fr den Thread-Pool zum Laden des Bildes
  	 * @see #loadService
  	 * @see #USE_BACKGROUND_LOAD
  	 */
  	private Future<Integer> loadFuture=null;
  
  	/**
  	 * Beendet explizit die Threads, die zum Hintergrundladen von Bildern verwendet werden.
  	 * (Ist nur fr den Kommandozeilenbetrieb notwendig.)
  	 */
  	public static void shutdownBackgroundLoadService() {
  		if (USE_BACKGROUND_LOAD && loadService!=null) loadService.shutdown();
  	}
  
  	/**
  	 * Wird aufgerufen, bevor auf das Bild zugegriffen wird, um
  	 * sicher zu stellen, dass ein mglicher Hintergrund-Ladeprozess
  	 * mittlerweile abgeschlossen wurde.
  	 * @see #loadFuture
  	 */
  	private void requireImageLoaded() {
  		if (loadFuture==null) return;
  		try {
  			loadFuture.get();
  		} catch (InterruptedException | ExecutionException e) {
  		}
  		loadFuture=null;
  	}
  
  	/**
  	 * Ldt eine einzelne Einstellung des Modell-Elements aus einem einzelnen xml-Element.
  	 * @param name	Name des xml-Elements
  	 * @param content	Inhalt des xml-Elements als Text
  	 * @param node	xml-Element, aus dem das Datum geladen werden soll
  	 * @return	Tritt ein Fehler auf, so wird die Fehlermeldung als String zurckgegeben. Im Erfolgsfall wird <code>null</code> zurckgegeben.
  	 */
  	@Override
  	protected String loadProperty(final String name, final String content, final Element node) {
  		String error=super.loadProperty(name,content,node);
  		if (error!=null) return error;
  
  		if (Language.trAll("Surface.Image.XML.Data",name)) {
  
  			final Callable<Integer> loader=()-> {
  				try {
  					final ByteArrayInputStream stream=new ByteArrayInputStream(Base64.getDecoder().decode(content));
  					final boolean useCache=ImageIO.getUseCache();
  					try {
  						ImageIO.setUseCache(false); /* Wird bentigt, wenn im Stream nicht gesprungen werden kann, was bei einem ByteArrayInputStream nun definitiv mglich ist.  */
  						final BufferedImage image=ImageIO.read(stream);
  						if (image!=null) ModelElementImage.this.image=image;
  					} finally {
  						ImageIO.setUseCache(useCache);
  					}
  				} catch (IOException | IllegalArgumentException e) {}
  				return null;
  			};
  
  			if (USE_BACKGROUND_LOAD) {
  				loadFuture=loadService.submit(loader);
  			} else {
  				try {
  					loader.call();
  				} catch (Exception e) {}
  			}
  
  			return null;
  		}
  
  		return null;
  	}
  
  	@Override
  	public String getHelpPageName() {
  		return "ModelElementImage";
  	}
  
  	/**
  	 * Liefert die Javascript-Daten fr die Station zur Ausgabe des Modells als HTML-Datei
  	 * @param outputBuilder	Builder, der die Gesamtdaten aufnehmen soll
  	 * @return	Javascript-Daten fr die Station
  	 */
  	private String getHTMLDrawImage(final HTMLOutputBuilder outputBuilder) {
  		final StringBuilder sb=new StringBuilder();
  
  		sb.append("function drawPlainImage(rect,borderColor,borderWidth,image) {\n");
  
  		sb.append("  if (borderWidth>0) {\n");
  		sb.append("    context.strokeStyle=borderColor;\n");
  		sb.append("    context.lineWidth=borderWidth;\n");
  		sb.append("    context.beginPath();\n");
  		sb.append("    context.moveTo(rect.x,rect.y);\n");
  		sb.append("    context.lineTo(rect.x+rect.w,rect.y);\n");
  		sb.append("    context.lineTo(rect.x+rect.w,rect.y+rect.h);\n");
  		sb.append("    context.lineTo(rect.x,rect.y+rect.h);\n");
  		sb.append("    context.closePath();\n");
  		sb.append("    context.stroke();\n");
  		sb.append("  }\n");
  
  		sb.append("  var img=new Image();\n");
  		sb.append("  img.src=image;\n");
  		sb.append("  img.onload=function(){context.drawImage(img,rect.x,rect.y,rect.w,rect.h);};\n");
  
  		sb.append("}\n");
  
  		return sb.toString();
  	}
  
  	/**
  	 * Zeichnet das Element in einem {@link HTMLOutputBuilder}
  	 * @param outputBuilder	Builder, der die Daten aufnehmen soll
  	 */
  	private void specialOutputHTML(final HTMLOutputBuilder outputBuilder) {
  		outputBuilder.addJSUserFunction("drawPlainImage",builder->getHTMLDrawImage(builder));
  
  		final Point p=getPosition(true);
  		final Dimension s=getSize();
  
  		String image;
  		try {
  			requireImageLoaded();
  			final ByteArrayOutputStream out=new ByteArrayOutputStream();
  			ImageIO.write(this.image,"png",out);
  			final byte[] bytes=out.toByteArray();
  			final String base64bytes=Base64.getEncoder().encodeToString(bytes);
  			image="data:image/png;base64,"+base64bytes;
  		} catch (IOException e) {
  			image="";
  		}
  
  		outputBuilder.outputBody.append("drawPlainImage({x: "+p.x+",y: "+p.y+", w:"+s.width+",h: "+s.height+"},\""+HTMLOutputBuilder.colorToHTML(color)+"\","+lineWidth+",\""+image+"\");\n");
  	}
  
  	/**
  	 * Zeichnet das Element in einem {@link SpecialOutputBuilder}
  	 * @param outputBuilder	Builder, der die Daten aufnehmen soll
  	 */
  	@Override
  	public void specialOutput(final SpecialOutputBuilder outputBuilder) {
  		if (outputBuilder instanceof HTMLOutputBuilder) specialOutputHTML((HTMLOutputBuilder)outputBuilder);
  	}
  }