# Bildervorschau macht Probleme



## geneticZ (6. Dez 2009)

Hallo,
ich hab mir, in einer GUI, eine kleine Thumbnail Bildvorschau gebastelt. Diese Funktioniert auch, jedoch gibt es hier und da ein paar "kleine" Probleme und allgemein würde mich interessieren ob die Lösung die ich hier gewählt habe überhaupt brauchbar ist oder es wo möglich sogar viel bessere Alternativen gibt.

Aus Gründen der Übersicht werde ich jetzt nicht den ganzen Code reinstellen, sondern nur die für mich wichtig erscheinenden Schnipsel. Im Allgemeinen habe ich in der GUI einen FileChooser der nur Directories anzeigt und Observable fungiert und einen anderen Panel als Observer, welcher dann die Bilder in Form von ImageIcons auf JLabels anzeigt.

Hier lese ich das Verzeichnis aus und erstelle die JLabel:

```
pictures = dir.listFiles(new DateiFilter());
		labels = new JLabel[pictures.length];
		
		pane.setPreferredSize(new Dimension(100 * pictures.length, 100));
		
		if(pictures != null) {
			for(int i = 0; i < pictures.length; i++){
				labels[i] = new JLabel();
				labels[i].setIcon(imgPane.setImage(pictures[i]));
				pane.add(labels[i]);
			}
        }
```

Hier wird dann das ImagIcon gesetzt und wieder zurück gegeben:

```
public Icon setImage(File pictures){
		this.img = new ImageIcon(pictures.getAbsolutePath());
		this.tmpIcon = new ImageIcon(pictures.getAbsolutePath());
			
	    if (tmpIcon != null){
	            if (tmpIcon.getIconWidth() > 90){
	                img = new ImageIcon(tmpIcon.getImage().getScaledInstance(90, -1, Image.SCALE_DEFAULT));
	            }
	            else 
	                img = tmpIcon;   
	    }
	    return img;
	}
```

Nun zu meinen Problemen...
Wenn im Startverzeichnis mehr als 3 Bilder vorhanden sind, bekomme ich einen "Image Fetcher 1" java.lang.OutOfMemoryError: Java heap space Fehler. Die Anzeige funktioniert trotzdem! Wenn ich dann das Verzeichnis wechsle und exakt die selben Codezeilen mit dem Observer update abgearbeitet werden, tritt der "OutOfMemoryError" nicht bei über 200 Bildern auf?

Die nächste Frage wäre dann eben wie man eine große Anzahl von Bildern intelligent abarbeiten könnte, so dass einem lästige Wartezeiten erspart bleiben. Beispielsweise mit nem ViewPort oder Threads oder sowas?

Danke für die Hilfe
geneticZ


----------



## Spacerat (6. Dez 2009)

Also daran, das du erst in Zeile 6 deines ersten Codeschnipsels (und damit viel zu spät) den NullCheck auf [c]pictures[/c] machst, wird es wohl kaum liegen. In Zeile 2 des selben Codeschnipsels könnte nämlich schon genau die NPE fliegen, die man ja eigentlich mit dem NullCheck vermeiden wollte. Der NullCheck gehört jedenfalls schon mal in Zeile 2.
@Edit: Vllt. empfiehlt sich ja auch eine JComponent mit überschriebener [c]paint()[/c]-Methode mehr als so ein JLabel. Immerhin kann man das Bild in den Grafik-Kontext ja korrekt gescaled zeichnen ohne extra eine neue Instanz des Bildes zu generieren.


----------



## geneticZ (6. Dez 2009)

Hallo und Danke für die Antwort,
du hast recht den Null-Check muss ich weiter hoch setzen!
Das ändert aber leider nichts an meinem Problem, wie du ja auch schon bemerkt hast...
Ich hab das ganze auch mit ner Paint Methode geschrieben und zur Verfügung, da bekomme ich aber leider genau den selben OutOfMemoryError! ;-(

Aufruf für Paint-Methode:

```
imgPaint = new ImagePaint();
		
		pictures = dir.listFiles(new DateiFilter());
		if(pictures != null) {
			pane.setPreferredSize(new Dimension(100 * pictures.length, 100));
			imgPaint.setImage(pictures);
        }
		pane.repaint();
```

Image Paint Methode:

```
public class ImagePaint extends JComponent{   
	private BufferedImage images[];
	
	public void setImage(File[] pictures){
		this.images = new BufferedImage[pictures.length];
		
		for(int i = 0; i < pictures.length; i++){
			try {
			    images[i] = ImageIO.read(pictures[i]);
			} catch (IOException e) {}
		}
	}
	
	protected void paintComponent( Graphics g ){		
		if(images != null){
			for(int i = 0; i < images.length; i++){
				g.drawImage(images[i], 0, 0, this);
			}
		}
	} 
}
```


----------



## Spacerat (6. Dez 2009)

2 Konzepte, selber Fehler... Das 2. Konzept erscheint mir auf den 1. Blick bis auf die Tatsache, dass du dort nicht gescaled ([c]drawImage(img, 0, 0, width, height, observer)[/c] zeichnest, fehlerlos. Ich würde den Fehler erstmal woanders suchen. Kannst du mal den kompletten Stacktrace posten?


----------



## geneticZ (6. Dez 2009)

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.awt.image.DataBufferByte.<init>(Unknown Source)
	at java.awt.image.ComponentSampleModel.createDataBuffer(Unknown Source)
	at java.awt.image.Raster.createWritableRaster(Unknown Source)
	at javax.imageio.ImageTypeSpecifier.createBufferedImage(Unknown Source)
	at javax.imageio.ImageReader.getDestination(Unknown Source)
	at com.sun.imageio.plugins.jpeg.JPEGImageReader.readInternal(Unknown Source)
	at com.sun.imageio.plugins.jpeg.JPEGImageReader.read(Unknown Source)
	at javax.imageio.ImageIO.read(Unknown Source)
	at javax.imageio.ImageIO.read(Unknown Source)
	at Paint.Preview.ImagePaint.setImage(ImagePaint.java:23)
	at Paint.Preview.PaneRight.<init>(PaneRight.java:34)
	at Paint.Preview.Frame.<init>(Frame.java:21)
	at Paint.Preview.Frame.main(Frame.java:36)

Hier ist mal die Exception, weiss nicht ob das was hilft, ansonsten muss ich erstmal schauen wie ich printStackTrace einbauen kann :-(


----------



## Spacerat (6. Dez 2009)

Nee... Hilft jetzt so auch nicht Wirklich weiter. Das einzige was mir noch einfällt ist, das beim 1. Konzept möglicherweise viel zu viele Bilder parallel geladen werden und beim 2. die Bilder in voller Grösse gespeichert werden (war also doch keine gute Idee). Was man noch mal testen könnte:
	
	
	
	





```
public class ImagePaint extends JComponent{   
    private static final int SIZE = 100;
    private ThumbNail images[];
    
    public void setImage(File[] pictures){
        this.images = new ThumbNail[pictures.length];
        
        for(int i = 0; i < pictures.length; i++){
            try {
                images[i] = new ThumbNail(ImageIO.read(pictures[i]));
            } catch (IOException e) {}
        }
    }
    
    protected void paintComponent( Graphics g ){        
        if(images != null){
            for(int i = 0; i < images.length; i++){
                g.drawImage(images[i], 0, i * SIZE, this);
            }
        }
    } 

    private static final class ThumbNail
    extends BufferedImage
    {
        private ThumbNail(BufferedImage src)
        {
            super(SIZE, SIZE, src.getType());
            Rectangle scale = scale(src);
            Graphics g = getGraphics();
            g.setColor(Color.WHITE);
            g.fillRect(0, 0, SIZE, SIZE);
            g.drawImage(src, scale.x, scale.y, scale.width, scale.height, null);
        }

        private Rectangle scale(BufferedImage src)
        {
            Rectangle rc = new Rectangle(0, 0, SIZE, SIZE);
            float w = src.getWidth();
            float h = src.getHeight();
            if(w <= h) {
                w = w / h * SIZE;
                h = SIZE;
            } else if(w > h) {
                h = h / w * SIZE;
                w = SIZE;
            }
            rc.x = (int) ((SIZE - w) / 2.0f);
            rc.y = (int) ((SIZE - h) / 2.0f);
            rc.width = (int) w;
            rc.height = (int) h;
            return rc;
        }
    }
}
```


----------



## geneticZ (6. Dez 2009)

Hallo,
hab dein Snippet mal ausprobiert, da bekomm ich folgende Exception

Exception in thread "main" java.lang.IllegalArgumentException: Unknown image type 0
	at java.awt.image.BufferedImage.<init>(Unknown Source)
	at Paint.Preview.ImagePaint$ThumbNail.<init>(ImagePaint.java:42)
	at Paint.Preview.ImagePaint$ThumbNail.<init>(ImagePaint.java:40)
	at Paint.Preview.ImagePaint.setImage(ImagePaint.java:24)
	at Paint.Preview.PaneRight.<init>(PaneRight.java:34)
	at Paint.Preview.Frame.<init>(Frame.java:21)
	at Paint.Preview.Frame.main(Frame.java:36)

Allerdings kein OutOfMemoryError!


----------



## Spacerat (6. Dez 2009)

OK, dann mal Zeile 28 wie folgt abändern: [c]super(SIZE, SIZE, Transparency.TRANSLUCENT);[/c]. Diese Fehlermeldung deutet im übrigen darauf hin, dass eine Datei als Bild geladen werden soll, dessen Typ nicht unterstützt wird (0 entspr. TYPE_CUSTOM maw. TYPE_UNKNOWN). Der Fehler in deinen anderen Versuchen könnte also auch daher kommen, dass dein Dateifilter auch Nicht-Image-Dateien einschliesst.


----------



## geneticZ (7. Dez 2009)

Hallo,
jetzt bekomme ich keine Fehlermeldung mehr aber leider wird auch kein Bild gezeichnet! 
Die Bilder werden aber richtig ausgelesen und auch übergeben. Das hab ich soweit überprüft.
Der Dateifilter dürfte eigentlich auch richtig sein:
[Java]
class DateiFilter implements FilenameFilter {
	     public String getDescription() {
	         return "Grafikdateien (*.gif, *.jpg, *.jpeg, *.png, *.tif, *.tiff)";
	     }

	     public boolean accept(File file, String name) {
	        if(name.toLowerCase().endsWith(".gif"))
	            return true;
	         else if(name.toLowerCase().endsWith(".jpg") || file.getName().toLowerCase().endsWith(".jpeg"))
	            return true;
	         else if(name.toLowerCase().endsWith(".tif") || file.getName().toLowerCase().endsWith(".tiff"))
		            return true;
	         else if(name.toLowerCase().endsWith(".png"))
	            return true;
	         else
	            return false;
	      }
	  }
[/code]


----------



## Ebenius (7. Dez 2009)

geneticZ hat gesagt.:


> Der Dateifilter dürfte eigentlich auch richtig sein:


Nö. Das erste Argument von [c]accept(File, name)[/c] heißt [c]dir[/c] und nicht [c]file[/c]. Das erklärt dann auch Deine falsche Verwendung, oder?

[edit: Ergänzung] Vielleicht hast Du nur .tiff und .jpeg im Verzeichnis. Die funktionieren mit dem Filter oben nämlich nicht. BTW: Kannst Du nicht einfach mal:

[c]name.toLowerCase[/c] in eine lokale Variable schreiben ([c]final String lowerName = name.toLowerCase();[/c])? Das macht den Code übersichtlicher und schneller.
[c]lowerName[/c] im Filter auf stdout ausgeben? [c]System.out.printf("Checking file name: %s (%s)%n", name, lowerName);[/c]

Ebenius


----------



## geneticZ (7. Dez 2009)

[Java]file = fc.getCurrentDirectory();[/Java]
Das mit dem File passt schon, die Namensgebung "file" ist hier vielleicht etwas ungünstig, aber dennoch ist der Aufruf richtig.
Deine Ergänzung werde ich mir zu Herzen nehmen. Danke!


----------



## Ebenius (7. Dez 2009)

geneticZ hat gesagt.:


> [Java]file = fc.getCurrentDirectory();[/Java]
> Das mit dem File passt schon, die Namensgebung "file" ist hier vielleicht etwas ungünstig, aber dennoch ist der Aufruf richtig.


Genau davon ging ich aus. Aber Dein FilenameFilter oben zeigt diesen Code (beachte Zeile 9 und 11 jeweils nach dem [c]||[/c]): [java=9]             else if(name.toLowerCase().endsWith(".jpg") || file.getName().toLowerCase().endsWith(".jpeg"))
                return true;
             else if(name.toLowerCase().endsWith(".tif") || file.getName().toLowerCase().endsWith(".tiff"))
                    return true;[/code]
Ich glaube nicht, dass Du prüfen möchtest, ob der Name Deines *Verzeichnisses* mit ".jpeg" oder ".tiff" aufhört. Ein Refactoring-Fehler Deinerseits, nehme ich an.

Ebenius


----------



## Spacerat (7. Dez 2009)

Wie Ebenius schon sagt: dein Dateifilter passt nicht. Allerdings stören da noch andere Dinge. Z.B. die Tatsache, dass die Standard-JRE das TIF-Format nicht unterstützt. Und wer solch eine Datei nicht Filtert und sie deswegen über ImageIO einliest, bekommt leere Bilder. Nehmen wir mal an, Ebenius hat damit Recht, dass im Fileparameter ausschliesslich Verzeichnisse vorkommen können, würde ein funktionierender DateiFilter wohl so aussehen:
	
	
	
	





```
public boolean accept(File file, String name) {
    	name = name.toLowerCase();
        if(name.endsWith(".gif")
        || name.endsWith(".jpg")
        || name.endsWith(".jpeg")
//      || name.endsWith(".tif")
//      || name.endsWith(".tiff")
        || name.endsWith(".png")
        		) {
        	return true;
        }
        return false;
      }
```


----------



## Ebenius (7. Dez 2009)

Spacerat hat gesagt.:


> Nehmen wir mal an, Ebenius hat damit Recht, dass im Fileparameter ausschliesslich Verzeichnisse vorkommen können [...]


Das nehmen wir doch nicht an. Das lesen wir nach :-D in der API-Doc:


			
				FilenameFilter.accept(File hat gesagt.:
			
		

> boolean *accept*(File dir, String name)
> Tests if a specified file should be included in a file list.​
> *Parameters:*
> dir - the directory in which the file was found.
> ...



Ebenius


----------



## Spacerat (7. Dez 2009)

Ebenius hat gesagt.:


> Das lesen wir nach :-D in der API-Doc:


Wieso? Können andere doch viel besser... :lol: Wenn's nicht grad' um meine Belange geht, muss ich doch keine API lesen. Das mach' ich erst kurz vor dem eröffnen eines Themas.


----------



## Ebenius (7. Dez 2009)

Spacerat hat gesagt.:


> Das mach' ich erst kurz vor dem eröffnen eines Themas.


Sehr lobenswert, die meisten tun das erst lange danach. ;-)

Ebenius


----------



## geneticZ (8. Dez 2009)

Spacerat hat gesagt.:


> Wie Ebenius schon sagt: dein Dateifilter passt nicht. Allerdings stören da noch andere Dinge. Z.B. die Tatsache, dass die Standard-JRE das TIF-Format nicht unterstützt. Und wer solch eine Datei nicht Filtert und sie deswegen über ImageIO einliest, bekommt leere Bilder...



Oh man stimmt, doofer Fehler mit dem Filter. Jetzt geht's auch, Danke!
2 Fragen hätte ich aber diesbezüglich leider noch:

- Der Nachteil den ich jetzt mit einer Paintmethode anstatt den ImageIcons habe ist doch, dass ich den einzelnen Thumbs jetzt kein Observable geben kann. Oder seh ich das falsch? Weil das war eigentlich mein Ziel 

- Wie könnte ich den Paintvorgang Userfreundlicher gestalten bei größerer Bildanzahl? Stichwort Threading oder ähnliches...


----------



## Spacerat (8. Dez 2009)

geneticZ hat gesagt.:


> - Der Nachteil den ich jetzt mit einer Paintmethode anstatt den ImageIcons habe ist doch, dass ich den einzelnen Thumbs jetzt kein Observable geben kann. Oder seh ich das falsch? Weil das war eigentlich mein Ziel


Das siehst du richtig. Meine ThumbNail-Klasse erweitert BufferedImage. Das bedeutet, das Bild (Icon bzw. ThumbNail) liegt schon fertig konstruiert im Speicher und benötigt deswegen auch keinen Observer (überwacht das Laden des Bildes) mehr. Wozu wolltest du dem ThumbNail also einen Observer adden wollen? Wenn ich besser wüsste, was du vorhast (ich kann nur raten... bei einem "MouseOverEvent" soll das Bild möglichst schnell in voller Grösse angezeigt werden), würde ich der der ThumbNail-Klasse die File-Instanz des Bildes mit übergeben und dieses bei Bedarf neu laden.





geneticZ hat gesagt.:


> - Wie könnte ich den Paintvorgang Userfreundlicher gestalten bei größerer Bildanzahl? Stichwort Threading oder ähnliches...


Dürfte mit meinen Ausführungen oben dann geklärt sein...


----------



## geneticZ (8. Dez 2009)

Hallo,
ich wollte ich im Prinzip einen Image Viewer schreiben und hab mich etwas von Adobe Bridge inspirieren lassen.
Was ich dabei eben ganz nett finde ist die Bildvorschau in einer Leiste unten die per Klick auf Vorschaubild das original Bild im Main-Panel anzeigt. Das wiederum wollte ich mit einem Observer Pattern verwirklichen.

Darum war mein erster Ansatz auch die Geschichte mit den ImageIcons, da ich da eben die jeweiligen JLabels Obervable hätte setzen können. Zumindest hatte ich mir das so gedacht. Aber vielleicht gibts ja viel bessere Lösungen!


----------



## Spacerat (8. Dez 2009)

geneticZ hat gesagt.:


> ... Aber vielleicht gibts ja viel bessere Lösungen!


Die gibt es ganz sicher. Ändere mal meine ThumbNail-Klasse wie folgt:
	
	
	
	





```
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

public class ThumbNail
extends Component
{
	private static final long serialVersionUID = -9148040613989442525L;
	private static final Dimension SIZE = new Dimension(100, 100);
	private final BufferedImage icon;
	private final File file;

	private ThumbNail(File file)
	{
		if(file == null || !file.isFile() || !file.exists()) {
			throw new IllegalArgumentException("invalid file");
		}
		BufferedImage icon;
		try {
			icon = ImageIO.read(file);
			if(icon.getType() == BufferedImage.TYPE_CUSTOM) {
				throw new IOException();
			}
		} catch(IOException e) {
			throw new IllegalArgumentException("invalid imagetype");
		}
		this.icon = new BufferedImage(SIZE.width, SIZE.height, icon.getType());
		this.file = file;
		Rectangle scale = scale(icon);
		Graphics g = this.icon.getGraphics();
		g.setColor(Color.WHITE);
		g.fillRect(0, 0, SIZE.width, SIZE.height);
		g.drawImage(icon, scale.x, scale.y, scale.width, scale.height, null);
		setPreferredSize(SIZE);
		setMinimumSize(SIZE);
		setMaximumSize(SIZE);
	}

	@Override
	public void paint(Graphics g)
	{
		g.drawImage(icon, 0, 0, this);
	}

	public File getFile()
	{
		return file;
	}

	private Rectangle scale(BufferedImage src)
	{
		Rectangle rc = new Rectangle(0, 0, SIZE.width, SIZE.height);
		float w = src.getWidth();
		float h = src.getHeight();
		if(w <= h) {
			w = w / h * SIZE.height;
			h = SIZE.width;
		} else if(w > h) {
			h = h / w * SIZE.width;
			w = SIZE.width;
		}
		rc.x = (int) ((SIZE.width - w) / 2.0f);
		rc.y = (int) ((SIZE.height - h) / 2.0f);
		rc.width = (int) w;
		rc.height = (int) h;
		return rc;
	}
}
```
Jedem dieser Thumbnail-Components wird ein MouseListener geadded:
	
	
	
	





```
public class LoadOnClick
extends MouseAdapter
{
	public void mouseClicked(MouseEvent e)
	{
		if(e.getSource() instanceof ThumbNail) {
			ThumbNail tn = (ThumbNail) e.getSource();
			try {
				BufferedImage toView = ImageIO.read(tn.getFile());
				//... open View usw...
			} catch(IOException e) {
				return;
			}
		}
	}
}
```


----------



## geneticZ (9. Dez 2009)

Ok das funktioniert soweit eigentlich ganz gut! Super schon mal vielen Dank!

2 Probleme hab ich momentan:

- PNG-Files sind nun "invalid imagetypes" und schmeißen die IOException. Sie werden wohl als BufferedImage.Type_Custom interpretiert. Warum ist das so und wie kann ich mit solchen Files umgehen?

- Wie kann ich mit langen Ladezeiten umgehen um sie Benutzerfreundlicher zu machen? Beispielsweise sowas wie invokeLater etc. verwenden?


----------



## Spacerat (9. Dez 2009)

geneticZ hat gesagt.:


> - PNG-Files sind nun "invalid imagetypes" und schmeißen die IOException. Sie werden wohl als BufferedImage.Type_Custom interpretiert. Warum ist das so und wie kann ich mit solchen Files umgehen?


Datischjamalnding... Wusste ich gar nicht. Ok, seis drum. In der Thumbnail-Klasse die Zeilen 28-30 entfernen und Zeile 34 (dürfte nach dem Löschen Zeile 31 sein) wie folgt ändern:[JAVA=31]        this.icon = new BufferedImage(SIZE.width, SIZE.height, Transparency.OPAQUE);[/code]


geneticZ hat gesagt.:


> - Wie kann ich mit langen Ladezeiten umgehen um sie Benutzerfreundlicher zu machen? Beispielsweise sowas wie invokeLater etc. verwenden?


Da wirst du wohl kaum was dran deichseln können. Der Ladevorgang dauert nun mal und ist in der Geschwindigkeit wohl kaum veränderbar. Den einzigen Vorteil den du durch invokeLater bekommst, ist wohl der, das deine GUI während dieser Zeit nicht blockiert und du so durchaus auch mehrere Bilder gleichzeitig laden Kannst. Ich hab' mit Swing und diesem Zeugs aber noch nicht viel gemacht und weis deswegen auch nicht wie es geht. Kann mir vorstellen, das in der [c]mouseClicked()[/c]-Methode ein Lade-Thread in einen dafür vorgesehenen Pool gelegt wird, welcher an anderer Stelle überwacht wird.


----------



## geneticZ (9. Dez 2009)

Ah super Danke!
Ja, ich werde das Laden der Vorschaubilder mal versuchen mit invokeLater benutzerfreundlicher zu machen.


----------

