# BufferedImage Builder



## Bergtroll (11. Dez 2009)

Servus Leutz,

heute habe ich mal eine hoffentlich eher einfach zu beantwortende Frage. Ich habe schon gesucht, aber nicht so richtig was gefunden. Ich möchte Datensätze aus allen möglichen primitiven arrays unterschiedlicher Dimension oder DataBuffern in Bilddaten überführen, um daraus schließlich für Java3D (Aviatrix3D, JOGL, etc.) Texturen zu erzeugen. 

Nun drängt sich mir angesichts dessen, dass mir zumindest der erste Teil kein so seltenes Verfahren zu sein deucht, die Frage auf, ob es dafür nicht schon einen entsprechenden Builder, eine kleine Library oder wenigstens ein paar Best Practise Vorschläge gibt.

Vielen Dank vom Bergtroll


----------



## Marco13 (11. Dez 2009)

Was sollte diese Bibliothek anderes machen, als jeden einzelnen Eintrag des Eingabe-Arrays anzusehen, und dafür einen Pixel in einem BufferedImage zu setzen?


----------



## Bergtroll (11. Dez 2009)

Im Optimalfall? :-D

Also es geht darum, dass ich mein Projekt, was mittlerweile ein Equinox Projekt ist, für Erweiterungen offen halten möchte. Die Nutzer, die später das Kernprogramm erweitern, werden keine hauptamtlichen Programmierer sein, sondern vornehmlich Physiker, die irgendwie in kürzester Zeit (Bachelor dauert so sechs Monate?) ihre Unmengen an Arraydaten in adäquate Bilder umwandeln müssen und von denen viele vorher nie oder nur wenig programmiert haben. Viele der Anlagen bei uns spucken ihre Daten in sehr eigensinnigen Formaten aus. Das sollen sie tun, indem sie in ihrer Erweiterung ein paar wenige Interfaces implementieren, die ihren Datenstrukturen adaptern. Keinesfalls kann ich sie bitten, sich doch bitte selbst mit den Interna der Bilderzeugung aus Rohdaten mittels Java auseinanderzusetzen.

Also hätte ich gerne eine zentrale, möglichst einfache Fassade, die aber hinter den Kulissen sehr flexibel sein muss.

Sie sollte nach außen hin nur eine handvoll Methoden zur Übergabe der jeweiligen Rohdaten haben und dann möglichst intern und selbsständig herausfinden, in welchem Format (Primitivenarray, wieviel Dimensionen? DataBuffer? etc.) Das soll sie für jedes Array aus Primitiven in bis zu (fünf war Blödsinn, da habe ich nicht richtig mitgedacht, drei sinds. Und zwar dann, wenn die verschiedenen Farbkanäle jeweils als zweidimensionales Array zusammengepackt werden) dimensionen und DataBuffer sowie DataBuffer Arrays hinbekommen. Hinweise zur Interpretation können per Getter und Setter gegeben werden.

Sie sollte darüber informiert werden können, in welcher Weise die Arraydaten zu interpretieren sind (also von unten nach oben, von rechts nach links?)

Auf jeden Fall sollte die Interna der Bilderzeugung (welches Raster, welches ColorModel, welches Sample Model, etc.) vorm Programmierer verstecken.

Sie sollte auf einfache Art, z.B. durch das Setzen eines Zahlenwertes ein Bild in eben jener Farbtiefe ausspucken.

Sie sollte dazu jeweils die kompakteste Möglichkeit der Bilddatenspeicherung wählen. Per Flag z.B. auch mit indizierter Palette.

Sie sollte eine Methode bieten, die ein Konvertierung zwischen beliebigen Farbräumen ermöglicht. Leider weiß ich da selbst noch nicht so genau, welche da wichtig sein werden. Auf jeden Fall irgendetwas dass menschliche, physische Maßstäbe von "doppelt so hell" und "doppelt so gesättigt" auf den normalen RGB abbildet.

Sie sollte eine Methode bieten, um ein Bild beliebiger Farbtiefe mit einem Alphawert beliebiger... hmm Alphatiefe??? zu kombinieren. Per Flag auch in-place durch das Abschneiden der least significant bits

Sie sollte anhand eines Arrays von Grenzwerten aus einem Originalbild ein Bild erzeugen, dessen Pixel für Alphawerte stehen. Also bsp. USHORT nach BYTE_BINARY werte zwischen 0 und 9000 -> 0, alle anderen nach 1.

Sie sollte in die Trickkiste der Performanz greifen und so viel wie möglich blockweise kopieren, statt einzelne Pixel zu durchlaufen.

Erzeugen von Bildstatistiken, z.B. Histogramme wäre auch nett, aber vermutlich woanders besser aufgehoben.


----------



## Marco13 (11. Dez 2009)

A: "Kennt jemand ein Elektrogerät, mit dem man Licht machen kann?"
B: "Sollte das dann mehr können als eine Lampe?"
A: "Ja, es sollte auch buntes Licht machen können, und auch Musik spielen, und man sollte sich Videos angucken und im Internet surfen und Rechenaufgaben lösen können, und Texte bearbeiten und E-Mails verschicken... aber keinen Computer, der ist für meine Benutzer zu kompliziert"

Nicht böse gemeint, so kam das eben irgendwie rüber 

Was du suchst klingt so speziell, dass es da wohl kaum eine Out-Of-The-Box-Lösung gibt - wenn man so feingranualre Kontrolle (Farbmodelle, irgendwelche Farbtiefen etc.) will, dann braucht man eine Low-Level-API, wie BufferedImage, ColorModel, ColorSpace & Co, und wenn man Histogramme und andere Operationen will, braucht man ImageOP oder sogar JAI ... und wenn man eine Fassade will, die bestimmte Aspekte dieser feingranularen APIs auf ganz bestimmte (grobgranulare) Art anbietet, kann es sein, dass man sich das selbst schreiben muss. (Das Hauptproblem dabei könnte sein, dass man dazu ja erstmal genau wissen müßte, was man eigentlich will... außer wenn man diese neue Klasse aus Java 7, die http://java.sun.com/javase/7/docs/api/com/sun/secret/special/WildcardClass.html verwendet, die macht automatisch das, was der Benutzer will)

An sich klingt das Thema ja ganz spannend - so allgemeine Datentransformationen für Visualisierungen :reflect: Aber eine Fertiglösung wüßte ich da jetzt nicht. (Mich würde mal interessieren, wie viele Leute auf den Link klicken  )


----------



## Bergtroll (12. Dez 2009)

Haha, Ich will die Wildcard Klasse haben, sie iss ja quasi so die eierlegende Wollmilchsau schlechthin unter den Utility Klasen.

Also sagen wir mal so, du bist herzlich eingeladen, dich an der Fassade und sonstigem Zeugs zu beteiligen um eine schöne und Java basierte, erweiterbare OpenSource Lösung zur Datenvisualisierung zu erstellen. Momentan werkel ich nämlich so ein bisschen allein auf weiter Flur an entsprechenden Erweiterungen für den zwar tollen, aber atm gänzlich undokumentierten J3DWorkbench (Eclectic3D | home of J3DWorkbench). Zur Belohnung gäbe es Luft und Liebe und wütende Einträge der hoffentlich schnell wachsenden internationalen Userschar im Supportforum, wenn wieder was nicht funktioniert.

Ich habe aber auch nochmal nachgedacht, also was ich mir von der Fassade wünsche ist, dass der User sein Eingabeformat und das gewünschte Ausgabeformat beschreibt, und diese das dann bestmöglichst umsetzt. Alleine das übergeben der Rohdaten soll durch Default Werte direkt ein sinnvolles Ergebnis liefern können. Ferner sollte in der Fassade sichergestellt sein, dass zu jedem Zeitpunkt ein Bild erzeugbar ist. 

Einen allerersten, präalphatischen Erstentwurf hänge ich hier auch dran, der hoffentlich ein wenig verdeutlicht, was ich meine. Das ganze könnte man noch mit DataBuffern und Streams erweitern, bla. Mit der Bitte um Rückmeldung, falls mein Vorhaben aus irgendwelchen Gründen, die ich gerade nicht erkenne, Blödsinn oder unumsetzbar sein sollte.


```
package de.jscivision.utils;

import java.awt.image.BufferedImage;
import java.util.EnumMap;

public class BufferedImageFacade {

	private int width;
	private int height;
	
	private final EnumMap<ColorChannel, Object> channels = new EnumMap<ColorChannel, Object>(ColorChannel.class);
	private final EnumMap<ColorChannel, Object> destColorDepths = new EnumMap<ColorChannel, Object>(ColorChannel.class);
	
	/*
	 * Was brauchen wir noch?
	 * Zieltyp fürs Bild und entsprechende Übersetzer. z.B. aus nem RGB Bild die Helligkeit berechnen und daraus grey machen.
            * ...
	 */
	
	public enum ColorChannel {
		RED, GREEN, BLUE, ALPHA, INTENSITY;
	}
	
	/**
	 * <p>To call this method, rawData must be an array of {@link Number} with a a dimension of 3
	 * and the first dimension (index 0) must have a length between 1-4 otherwise an 
	 * {@link IllegalArgumentException} exception is thrown.</p> 
	 *
	 * <p>We have to distinguish between four possible lengths of the first dimension</p>
	 * <ol>
	 * 	<li>The data is interpreted as greyscale image.</li>
	 * 	<li>The data is interpreted as greyscale image with Alpha channel.</li>
	 * 	<li>the data is interpreted as RGB image. The mapping of the 1st dimension to RGB is 
	 * [0]=R, [1]=G, [2]=B,</li>
	 * 	<li>the data is interpreted as RGBA image, hasAlpha is ignored. The mapping of the 1st dimension to RGB is
	 * [0]=R, [1]=G, [2]=B, [3]=A</li>
	 * </ol>
	 * 
	 * <p>By default, the second dimensions is interpreted as row index and its length defines the height of the image.
	 * In consequence the third dimension is interpreted as column index of a pixel and its length defines the width of the image.
	 * You may change this behavior by calling 
	 * </p>
	 * 
	 *  
	 * @param rawData
	 * @param bitLength
	 */
	public BufferedImageFacade(Object rawData) {}
	
	/**
	 * <p>To call this method, rawData must be an array of {@link Number} with a a dimension of 2 
	 * and the first dimension (index 0) must have a length between 1-4 otherwise an 
	 * {@link IllegalArgumentException} exception is thrown.</p> 
	 *
	 * <p>We have to distinguish between four possible lengths of the first dimension</p>
	 * <ol>
	 * 	<li>The data is interpreted as greyscale image.</li>
	 * 	<li>The data is interpreted as greyscale image with Alpha channel.</li>
	 * 	<li>the data is interpreted as RGB image. The mapping of the 1st dimension to RGB is 
	 * [0]=R, [1]=G, [2]=B,</li>
	 * 	<li>the data is interpreted as RGBA image. The mapping of the 1st dimension to RGB is
	 * [0]=R, [1]=G, [2]=B, [3]=A</li>
	 * </ol>
	 * 
	 *  
	 * @param rawData
	 * @param bitLength
	 * @param hasAlpha
	 */
	public BufferedImageFacade(Object rawData, int width, int height) {}
	
	
	/**
	 * <p>this constructor is used with rawData, that represents image data in a packed format. That means
	 * all the color values for one pixel are given in one array value. To know which bits of that value
	 * represent which color, you also have to provide a bit mask, specifying this information. The rawData
	 * must be an array of primitives with a a dimension of 1.</p>
	 * 
	 * <p>The length of the bitmask MUST be between 0-3 thus dividing an array value into, at maximum, four areas. An bit 
	 * mask value determines the number of bits to read, initially starting from the highest significant bit left. The
	 * bit mask keys in ascending order are interpreted as significant bit information in the order RGB(A) 
	 * respectively luminance, alpha. The length of the bit mask information is interpreted as follows</p>
	 * <ol start="0">
	 * 	<li>The data is interpreted as greyscale image.</li>
	 * 	<li>The data is interpreted as greyscale image with Alpha channel.</li>
	 * 	<li>the data is interpreted as RGB image.</li> 
	 * 	<li>the data is interpreted as RGBA image</li>
	 * </ol>
	 * 
	 * @param rawData
	 * @param bitLength length must be between 1 and four.
	 * @param width
	 * @param height
	 */
	public BufferedImageFacade(Object rawData, int[] bitMask, int width, int height) {}
	
	/**
	 * <p>this constructor is used with rawData, that represents image data in a packed format. That means
	 * all the color values for one pixel are given in one array value. To know which bits of that value
	 * represent which color, you also have to provide a bit mask, specifying this information. The rawData
	 * must be an array of primitives with a a dimension of 2.</p>
	 * 
	 * <p>By default, the first dimension of rawData is interpreted as row index and its length defines the height of the image.
	 * In consequence the second dimension is interpreted as column index of a pixel and its length defines the width of the image.
	 * You may change this behavior by calling</p>
	 * 
	 * <p>The length of the bitmask MUST be between 0-3 thus dividing an array value into, at maximum, four areas. An bit 
	 * mask value determines the number of bits to read, initially starting from the highest significant bit left. The
	 * bit mask keys in ascending order are interpreted as significant bit information in the order RGB(A) 
	 * respectively luminance, alpha. The length of the bit mask information is interpreted as follows</p>
	 * <ol start="0">
	 * 	<li>The data is interpreted as greyscale image.</li>
	 * 	<li>The data is interpreted as greyscale image with Alpha channel.</li>
	 * 	<li>the data is interpreted as RGB image.</li> 
	 * 	<li>the data is interpreted as RGBA image</li>
	 * </ol>
	 * 
	 * @param rawData
	 * @param bitLength length must be between 1 and four.
	 * @param width
	 * @param height
	 */
	public BufferedImageFacade(Object rawData, int[] bitMask) {}
	
	/**
	 * This method returns a copy of the input array with replaced values. For the replacement 
	 * a lookup definition defined by ranges is used.
	 *
	 * @param ranges for example {"5,6","10,7"} means
	 * @param inputArray 
	 * @return
	 */
	public Object createAlphaChannel(String[] ranges, Object inputArray) {return null;}
	
	/**
	 * this method unpacks the rawData object into its channels and puts them to the channels map 
	 * @param rawData
	 */
	private void unpackRawArrayData(Object rawData, Object... args) {}
	
	/**
	 * builds the image from the current state information
	 * @return
	 */
	public BufferedImage createImage() {return null;}
	
	/*
	 * 
	 * 
	 * GETTERS AND SETTERS
	 * 
	 * 
	 */
	public Object getChannel(ColorChannel channel) {
		return channels.get(channel);
	}
	
	public void setChannel(ColorChannel channel, Object rawData) {
		channels.put(channel,rawData);
	}
	
	public Object getDestColorDepth(ColorChannel channel) {
		return destColorDepths.get(channel);
	}
	
	public void setDestColorDepth(ColorChannel channel, int depth) {
		destColorDepths.put(channel,depth);
	}
}
```

Mangels aktiver Anwendung mag mein Englisch manchmal ein wenig hölzern wirken, Verbesserungen sind durchaus erwünscht

Mfg, Troll


----------



## Marco13 (12. Dez 2009)

Ein bißchem her Luft (im zeitlichen Sinne) könnte ich gebrauchen. 

Wenn ich das richtig sehe, ist es bisher drauf runterkondensiert, aus einem Array von Numbers ein BufferedImage zu machen?! (Für den Entwurf würde es übrigens EIN Konstruktor tun - nämlich der mächtigste).


----------



## Bergtroll (13. Dez 2009)

Sodalla, ich habe mir gestern und heute mal überlegt, was genau ich mir eigentlich vorstelle und wie man es evtl. umsetzen könnte. Der UML Entwurf Klassenentwurf sieht so aus





(Was benutzt ihr eigentlich zum modellieren, Code generieren und reverse engineeren?)

Den aktuellen SourceCode dazu könnt ihr hier finden: Source

Meine Idee um die Sachen voneinander zu entkoppeln und trotzdem zusammenarbeiten zu lassen ist folgende: 

RawImageData packt ein beliebiges Datenobjekt zusammen und liefert ein paar optionale Zusatzwerte, die gesetzt werden können, aber nicht zwingend müssen. Ein weiterer Wert, der bei Erstellung übergeben wird, ist String id. Das Ding soll später als Eclipse RCP Erweiterung laufen und auf die Weise ggf. sicherzustellen, einen bestimmten Adapter zu wählen, falls mehrere möglich wären.

RawImageDataAdapter interpretiert so ein verpacktes Datenobjekt auf bestimmte Art und Weise, es stellt quasi einen "Viewer" dar.

Der DataAdapterValidator prüft, ob RawImageData zu einem bestimmten Adapter passt, ich wollte die Interfaces trennen, um die Möglichkeit zu haben, eine leichtgewichtige Prüfklasse zu haben, da ja das erstellen des richtigen Adapters schon kosten könnte. Vielleicht gibt es da einen besseren Ansatz? Kann man definieren, dass zwei interfaces  nicht zusammen implementiert werden dürfen? Später könnte es damit z.B. möglich sein, dass der Builder selbstständig nach passenden Adaptern sucht.

Das AlphaCreationAlgorithm Interface definiert Funktionen die für einen beliebigen RawImageDataAdapter einen passenden Alpha Channel zurückgeben müssen, auch, wenn der Adapter selbst keinen Alpha Kanal zurückgeben würde.

Der Builder selbst wird einfach nur konfiguriert, ein bestimmtes Zielbildformat zu erzeugen. Dabei soll sichergestellt sein, dass zu jedem Zeitpunkt mit einem validen Adapter auch tatsächlich ein Bild erzeugt werden kann. Jeder build() Aufruf bekommt jeweils immer einen RawImageDataAdapter übergeben, aus dem er das Bild erzeugt. Außerdem ist im Builder immer ein AlphaCreationAlgorithm hinterlegt, der im Falle, dass ein RGB oder Greyscale Bild mit Alpha Kanal versehen werden soll, diesen erzeugt. Ferner kann dem build Aufruf auch ein bestimmter AlphaCreationAlgorithm mitgegeben werden, der für diesen einen build den AlphaKanal des Original bzw. die im Builder hinterlegten AlgoRegeln überschreibt.

Die drei Enumerations stellen schlicht die Aufzählung eindeutiger Bezeichner dar.

Erweiterungsmöglichkeiten:
1.) RawImageData: enthält eine HashMap "userData" in der wahlfreie weitere Informationen mit String Key hinterlegt werden können.

2.) RawImageDataAdapter: Will man ein neues Datenformat als Bild interpretieren, muss man einfach dieses Interface implementieren. Damit sollte es möglich sein, wahlfreie Eingaben in Bilder umzuwandeln, sogar String Listen, etc. Zu jedem neuen RawImageDataAdapter sollte auch ein Validator erzeugt werden, am Besten als nested static klasse. Kann man das irgendwie erzwingen?

3.) AlphaCreationAlgorithm: Man kann beliebige Regeln in seiner Implementierung erstellen, wie aus den Angaben eines bestimmten RawImageAdapters ein Alpha Channel erzeugt wird. Die zwei Standard Implementierungen AllTransparent und AllOpaque liefern einen komplett transparenten bzw. komplett
undurchsichtigen Kanal zurück. (Obwohl ich mir gerade überlege, dass ein Uniform mehr Sinn macht  

So far 

P.S. Macht es Sinn, diese Object Geschichte für Arrays irgendwie generisch zu erledigen? Ich habe das versucht, bin aber irgendwie zu blöd dafür, ich raff einfach nicht, wie man dem compiler beibringt, dass er ein beliebiges primitiven Array zu übernehmen bzw.  zurückzugeben hat.


----------



## Marco13 (14. Dez 2009)

Ja, in sowas kann man beliebig viel Aufwand stecken. 

Hab's mal überflogen. (BTW: Ein Bild (UML Diagramm) sagt zwar mehr als 1000 Worte, aber ein Interface sagt mehr als 1000 Bilder - z.B. auch, welche Rückgabewerte die Methoden haben).

Bin da ja nicht so "drin", aber nur aus Neugier ... wenn ich es richtig sehe, ist die intendierte Verwendung jetzt etwa sowas

```
Integer array[][] = new Integer[800*600][3]; // 800x600, RGB
RawImageData data = new RawImageData(array, ...);
RawImageDataAdapter adapter = new RawImageDataAdapter() 
{
    getType() { return RGB; }
    getChannel(Channel c) { return arrayContaining(array[0..800*600][indexFor(c)]); }
    ...
};
BufferedImage result = BufferedImageBuilder.setWidth(800).setBla.......().build(adapter,...);
```

Wie gesagt, so ganz sehe ich da noch nicht, inwiefern das viel einfacher oder mächtiger ist, als ein BufferedImage direkt mit den Daten aus dem Array zu füllen... 
Wie das Alpha behandelt wird (das scheint ja eine wichtige Rolle zu spielen) müßte ich mir nochmal genauer ansehen...

BTW: Statt des Objects könnte man auch Generics verwenden, etwa im Sinne von 

```
public final class RawImageData<T> {

	private final T rawData = null;
}
...

RawImageData<int[]> data = new RawImageData(new int[10]);
```
aber da müßte man sich überlegen, ob man das wirklich ausnutzen kann... manchmal ist es so, dass einem diese Typinformation im Endeffekt "verlorengeht" und nichts mehr bringt...


----------



## Bergtroll (14. Dez 2009)

Also die grundsätzliche Idee ist, die Erzeugung des BufferedImage von einem programmatischen zu einem deklarativen Ansatz zu verschieben.

Ich gehe davon aus, dass ein Benutzer weiß, in welchem Format seine Daten vorliegen und das er weiß, welches Format das Zielbild haben soll. IMHO sollte es reichen, wenn er sein Ausgangsformat und das gewünschte Zielformat (also Abmessung, Kanäle und Farbtiefe) beschreibt. Ersteres durch Auswahl oder Implementierung eines geeigneten RawImageDataAdapters. 

Ein Beispiel hierfür könnte sein, dass er als RawFormat eine HashMap hat, die einem Wahlkreis ihre Partei zuweist. Je nach nach Partei wird der Wahlkreis auf einer Karte eingefärbt. Nach außen hin wird dann vom entsprechenden Adapter einfach die eingefärbte Karte als Pixelarray, dessen aktuelle Farbtiefe und Bildtyp gegeben, die der Builder wiederum als Eingabe nutzt.

Im Builder ist wie gesagt das Zielformat eingestellt, also ebenfalls Channels und Farbtiefe. Hinter den Kulissen sollte der Bilder das passendste Datenmodell fürs Zielformat und einen Algorithmus zur Überführung wählen. Voraussetzung für das Funktionieren ist, dass es für jede mögliche Kombination einen algorithmischen Weg zur Umwandlung gibt.

Ergo, wenn das möglich ist, würde die Fassade es dem Nutzer ersparen, sich mit Rastern, ColorModels, SampleModels, etc. auseinanderzusetzen. Stattdessen könnte er sich auf sein Kerngeschäft, nämlich das korrekte Beschreiben, Auswerten und Visualisieren seiner Daten konzentrieren. Eigentlich wundert es mich, dass ich nirgendwo ein offizielles Interface gefunden habe, dass in etwa die Aufgabe des RawImageAdapters hat.

Übrigens vielen Dank für die kontinuierlichen Rückmeldungen 

Es folgen die Interfaces:

```
/**
  * <p>This interface is implemented by classes to interpret {@link RawImageData} in a
  * certain way. Thus meaning, it is representing a certain view to the underlying raw
  * data that may differ from the view another RawImageDataAdapter would present.</p> 
  */
abstract public interface RawImageDataAdapter {

	/**
	 * Returns the requested channel as array of integers, the return value MUST NEVER be null, return
	 * an empty array instead. For any channel that claims to be available regarding the ImageType 
	 * returned by {@link RawImageDataAdapter#getImageType()}, the return array MUST have a length of
	 * width*length.
	 *     
	 * @param channel the color channel to get
	 * @return the channel as int[] array
	 */
	public int[] getChannelAsInt(ColorChannel channel);
	
	/**
	 * Returns the requested channel as array of floats, the return value MUST NEVER be null, return
	 * an empty array instead. For any channel that claims to be available regarding the ImageType 
	 * returned by {@link RawImageDataAdapter#getImageType()}, the return array MUST have a length of
	 * width*length.
	 *     
	 * @param channel the color channel to get
	 * @return the channel as int[] array
	 */
	public int[] getChannelAsFloat(ColorChannel channel);
	
	/**
	 * Returns the requested channel as array of doubles, the return value MUST NEVER be null, return
	 * an empty array instead. For any channel that claims to be available regarding the ImageType 
	 * returned by {@link RawImageDataAdapter#getImageType()}, the return array MUST have a length of
	 * width*length.
	 *     
	 * @param channel the color channel to get
	 * @return the channel as int[] array
	 */
	public int[] getChannelAsDouble(ColorChannel channel);

	/**
	 * This method is used to enquire, as which image type the underlying RawData is interpreted by this adapter
	 * @return The {@link ImageType} of the underlying raw data
	 */
	public ImageType getImageType();

	/**
	 * This method returns the color depth of a certain channel of the underlying raw image data
	 * @param channel
	 * @return a byte value >= 0
	 */
	public byte getColorDepth(ColorChannel channel);

	/**
	 * This method returns the origin (the pixel of the image at the location x=y=0) of a
	 * certain image channel
	 * @param channel The {@link ColorChannel} to get the origin for
	 * @return The {@link ImageOrigin} of this channel
	 */
	public ImageOrigin getImageOrigin(ColorChannel channel);

	/**
	 * @return the height of the underlying {@link RawImageData}
	 */
	public int getHeight();


	/**
	 * @return the width of the underlying {@link RawImageData}
	 */
	public int getWidth();

}
```


```
/**
 * This interface describes method to validate and get adapters for a certain
 * instance of {@link RawImageData} 
 */
public interface DataAdapterValidator {
	
	/**
	 * @param rawImageData
	 * @return true, if destination adapter is valid for rawImageData
	 */
	public boolean isAdapterFor(RawImageData rawImageData);

	/**
	 * this method returns an instance of the {@link RawImageDataAdapter} this
	 * interface is a validator for.
	 * @param rawImageData the {@link RawImageDataAdapter} instance
	 * @return
	 */
	public RawImageDataAdapter getInstance(RawImageData rawImageData);

}
```


```
import de.jscivision.utils.imageFacade.RawImageDataAdapter;

/**
 * Classes implementing this interface represent an algorithm, to
 * construct an alpha channel in a certain depth from an 
 * {@link RawImageDataAdapter} instance. 
 */
public interface AlphaCreationAlgorithm {
	
	/**
	 * This method returns an alpha channel according from the rules of
	 * the implementing class and returns it as int[].
	 * 
	 * @return
	 */
	public int[] getAlphaAsInt(RawImageDataAdapter rawData, short depth);

	/**
	 * This method returns an alpha channel according from the rules of
	 * the implementing class and returns it as float[].
	 * 
	 * @return
	 */
	public float[] getAlphaAsFloat(RawImageDataAdapter rawData, short depth);
	
	/**
	 * This method returns an alpha channel according from the rules of
	 * the implementing class and returns it as double[]
	 * 
	 * @return
	 */
	public double[] getAlphaAsDouble(RawImageDataAdapter rawData, short depth);
	
}
```


----------



## Marco13 (14. Dez 2009)

Naja... wirklich hilfreich waren meine bisherigen Rückmeldungen ja nicht ... einerseits ist es zwar interessant, andererseits habe ich kaum Anlass und vor allem Zeit mir da wirklich tiefergehende Gedanken zu machen 

Die eigentliche Konvertierung erfolgt, wie du sagtest, durch "Auswahl oder Implementierung eines geeigneten RawImageDataAdapters". Du hast ja schon einige Default-Implementierungen dafür anskizziert, und vermutlich schon konkretere Ideen, welche Möglichen Rohformate es geben kann. Für alles andere würde sich der Benutzer dann eine minimale Menge von Methoden bauen müssen - also in erster Linie die getChannelAs*-Methode. Man könnte sich überlegen, ob man das noch ein bißchen ... abstrakter oder einfacher erweiterbar macht - aber vermutlich hast du schon sowas wie einen vollständig mit Default-Werten vor-Implementierten AbstractRawImageDataAdapter nachgedacht. 

Was ich persönlich jetzt (nach kurzem Drüberschauen - deswegen nicht unbedingt so ernst nehmen) nicht so hübsch finde, sind die redundant wirkenden Methoden
AlphaCreationAlgorithm#getAlphaAsInt
AlphaCreationAlgorithm#getAlphaAsFloat
AlphaCreationAlgorithm#getAlphaAsDouble
und
RawImageDataAdapter#getChannelAsInt
RawImageDataAdapter#getChannelAsFloat
RawImageDataAdapter#getChannelAsDouble
(bei letzteren stimmen im zuletzt geposteten auch die Rückabetypen noch nicht).

Erstens müßte man ja noch
RawImageDataAdapter#getChannelAsChar?
RawImageDataAdapter#getChannelAsBoolean?
RawImageDataAdapter#getChannelAsLong
RawImageDataAdapter#getChannelAsShort
RawImageDataAdapter#getChannelAsByte !!!
einfügen - insbesondere das letzte ist ja "klassisch" das, was benötigt wird, um ein RGB-Bild zu erstellen. Aber natürlich wäre es dadurch noch weniger schön. Man könnte schauen, ob man das schön und sinnvoll mit Generics lösen könnte, oder vielleicht mit einem ByteBuffer (Java Platform SE 6) den man dann als Long- Float- oder sonstigen Buffer ansehen kann. Wie man das dann schön, typsicher und einfach zu verwenden hinkriegen könnte, müßte man sich überlegen. 

Abgesehen davon ist Alpha ja eigentlich auch nur ein Channel. Man könnte sich überlegen, ob man das irgendwie ausnutzen könnte - in dem Sinne, dass ... der "AlphaCreationAlgorithm" sozusagen(!) durch einen speziellen RawImageDataAdapter ersetzt wird, oder dass der RawImageDataAdapter aus mehreren "ChannelCreationAlgorithms" besteht (also der Alpha-Channel irgendwie genauso behandelt wird wie die anderen). Da jetzt nicht so draufhüpfen, das ist wirklich nur ganz grob rumgesponnen... aber vielleicht weißt du ja, was ich meine


----------



## Bergtroll (14. Dez 2009)

Hello again,

Am einfachsten wäre die Implementierung des Builders wohl, wenn ich jedem Channel die größtmögliche Freiheit für Farbtiefe einräume, z.B. als int[]. 

Andererseits befürchte ich, dass das Ding dadurch sehr langsam wird und mehr Speicher verbraucht, als nötig. Deswegen wäre es wiederum schön, wenn man eine Möglichkeit hätte, zur Laufzeit das kleinstmögliche und effizienteste Rückgabeformat zu wählen. Deswegen dachte ich darüber nach, ob Generics Sinn machen. Da ich die aber nicht so ganz raffe (zumindest was arrays angeht), habe ich erstmal Methoden für unterschiedliche Arraytypen angelegt. Nun aber, beim zweiten Hinschauen, finde ich das auch nicht schön und deine Anmerkung bestätigt das . Aber die Idee mit dem ByteBuffer lasse ich mir jetzt erstmal durch den Kopf gehen.

Auch die Sache mit der unnötigen Extrabehandlung des AlphaChannels ist richtig. Ich denke, ich werde die Sache irgendwie so aufdröseln, dass ich einen ChannelCreationAlgorithm als Interface definiere, der alle fünf möglichen Channels als Eingabe akzeptieren muss und daraus einen Ausgabechannel erzeugen soll. Evtl. irgendwie als Dekorator, so dass man channelbasierte Änderungen verketten kann. 

Der RawImageDataAdapter bekommt dann die zwingende Aufgabe, dass er für jeden Channel eine Rückgabe in der nötigen Pixelzahl machen können muss. Dabei kann er aber bei Bedarf intern einen oder mehrere der tollen Algorithmen speichern, sofern er Lust drauf hat!


----------



## Marco13 (14. Dez 2009)

Wenn du irgendwann eine aktualisierte Version irgendwo hochlädtst (Vielleicht irgendein SVN? http://www.jscivision.de/ gibt's ja nicht  ) und FALLS ich die Zeit finde, würde ich da ggf. auch mal ein bißchen rumprobieren, aber ob du das willst, weiß ich nicht, und versprechen könnte ich auch nichts...


----------



## Bergtroll (14. Dez 2009)

Danke für den Hinweis, jscivision registrieren stand auf einem von diesen kleinen gelben Zetteln, die ich mit Sicherheit weggeworfen habe, iss aber nun endlich erledigt :-D. Ich richte gerade ein Subversion Repo ein, und du darfst natürlich soviel herumprobieren wie du magst und es würde mich sogar freuen. Schließlich ist ein Ansatz, an dem man alleine rumdoktort nur halb so viel Wert!


----------



## Bergtroll (18. Dez 2009)

Sodalla, hier noch ein überarbeiteter Entwurf, bei Denkfehler bitte melden:

Ich habe die ChannelCreationAlgorithm gestrichen, weil mir beim genauer drüber nachdenken klargeworden ist, dass eine solche Klasse die einem Adapter Kanäle hinzufügt, um sie zu einem anderen ImageType zu machen ja auch nur wieder eine Interpretation eines RawImage Formats (in diesem Fall eben vorliegend als IImageAdapter) ist. Demzufolge wäre sie auch nur wieder ein besonderer IImageAdapter, deren Construktor als Eingabe nach einem IImageAdapter verlangt. 

Ein solches Einpacken eines Adapters durch einen anderen lässt sich durch Decorator realisieren. Als Basisklasse für die Dekorator habe ich in Anlehnung an die Java Klasse "FilterInputStream" die Klasse "FilterImageData" eingeführt. Erweiterungen dieser Klasse können einen IImageAdapter, der einen bestimmten ImageType zur Verfügung stellt so einpacken, dass ein anderer ImageType nach Außen hin zur Verfügung steht. Will man nun z.B. aus einem Farb-RGB ein RGB in Graustufen machen, könnte man dem IImageAdapter zuerst in einen GreyscaleDecorator und diesen dann wiederum in einen RGBDecorator einpacken (beide Klassen existieren noch nicht, aber soweit der Plan).

Den Rückgabewert des IImageAdapters.getChannel() habe ich auf java.nio.Buffer geändert, da man auf diese Weise gleich verschiedene Arten von Buffern Typesafe zurückgeben kann, und mit der asReadOnly Methode hässliche Probleme mit mutable Arraywerten umgehen kann. Wenn jemand einen besseren Rückgabewert weiß, her damit. (Ich hatte direkt DataBuffer nachgedacht, was spricht dafür, was spricht dagegen?)

Für den BufferedImageBuilder habe ich den Ziel ImageType gestrichen, weil man so oder so an einer Stelle einen entsprechenden Adapter auswählen müsste. Die Bilder, die der Builder erzeugt werden vom ImageType sein, den der Adapter (gewrappt oder nicht), nach außen hin darstellt. Die Zielfarbtiefe stellt man allerdings nach wie vor im Builder ein.

Was jetzt auf magischen Wegem im Builder geschehen soll ist, dass für einen ImageType in den eingestellten Zielfarbtiefen das Optimale ColorModel und SampleModel gewählt werden soll. Unter optimal definiere ich momentan geringstmöglichen Speicherverbrauch bei für die Zielfarbtiefe maximaler Information. Sofern die gewählten Farbtiefen das zulassen, sollte das Zielbild, wenn man BufferedImage.getType() darauf anwendet, einen anderen Typ als Custom zurückgeben.

Den aktuellen Codeentwurf findet ihr unter nachfolgendem Link und könnt ihn anonym downloaden. Lasst euch von den anderen Paketen nicht stören, die sind roher Wildwuchs *gg*:
jscivision - Project Hosting on Google Code

Leider arbeite ich die nächsten 14 Tage erstmal an einem anderen Projekt, aber ich freue mich trotzdem schon auf eure Rückmeldungen und Anregungen.

Mfg und frohe Weihnachten
Bergtroll


----------



## Marco13 (19. Dez 2009)

_ 
Willkommen, Marco13.
...
Ihre Benachrichtigungen: 2
_

Das sind "kleine gelbe Haftetiketten"  Hab's nicht vergessen, und werde jetzt am Wochenende vielleicht mal drüberschauen und meinen Senf abgeben können


----------



## Marco13 (20. Dez 2009)

OK... Wildwuchs... Da ist es jetzt schwer, den relevanten Teil rauszufischen, aber ich gehe davon aus, dass es nur um die schon angesprochenen Packages geht.

So ganz hab' ich's wohl immernoch nicht verstanden. Ich dachte mir jetzt "Ich bin ein Ingenieur, und habe Messwerte" - sowas wie

```
class DataItem 
		{
			float a; // Zwischen 0 und 1
			float b; // Zwischen 0 und 1
		}
```
Davon hab' ich 10000 Stück in einer Liste
[c]List<DataItem> values = new ArrayList<DataItem>();[/c]

Und jetzt will ich ein 100x100 großes Bild, bei dem "a" auf den Rotkanal und "b" auf den Grünkanal abgebildet wird... Also los

```
public class BufferedImageBuilderExample 
{
	public static void main(String args[])
	{
		class DataItem 
		{
			float a;
			float b;
		}
		List<DataItem> values = new ArrayList<DataItem>();

		BufferedImageBuilder b = new BufferedImageBuilder();
		b.setDestColorDepth(ColorChannel.RED, (byte)8);
		b.setDestColorDepth(ColorChannel.GREEN, (byte)8);
		b.setDestColorDepth(ColorChannel.BLUE, (byte)8);
		b.setDestWidth(100);
		b.setDestHeight(100);
		
		b.build(new IImageDataAdapter()
		{
			@Override
			public Buffer getChannel(ColorChannel channel) 
			{
				// TODO Auto-generated method stub
				return null;
			}

			@Override
			public byte getColorDepth(ColorChannel channel) {
				// TODO Auto-generated method stub
				return 0;
			}

			@Override
			public int getHeight() {
				// TODO Auto-generated method stub
				return 0;
			}

			@Override
			public ImageOrigin getImageOrigin(ColorChannel channel) {
				// TODO Auto-generated method stub
				return null;
			}

			@Override
			public ImageType getImageType() {
				// TODO Auto-generated method stub
				return null;
			}

			@Override
			public int getWidth() {
				// TODO Auto-generated method stub
				return 0;
			}
			
		});
	}
}
```
sooo, das muss jetzt noch implementiert werden.

Also, wie auch immer das genau geht, und wie auch immer man das genau implementieren muss - mit

```
BufferedImage b = new BufferedImage(100,100, BufferedImage.TYPE_INT_RGB);
		for (int i=0; i<100; i++)
		{
			for (int j=0; j<100; i++)
			{
				DataItem d = values.get(i*100+j);
				int r = (int)(255 * d.a);
				int g = (int)(255 * d.b);
				b.setRGB(i,j,new Color(r,g,0).getRGB());
			}
		}
```
wäre das auch erledigt :bahnhof: 

(Sorry, ich bin vielleicht manchmal ein bißchen schwer von Begriff...  )

BTW: Die Werte vom MAX_VALUE_FOR_DEPTH_ im BufferedImageBuilder sind genau (1<<(i+1))-1._


----------

