Swing sehr großes Bild skalieren

Status
Nicht offen für weitere Antworten.

nuely

Mitglied
Moin zusammen.

Ich habe zwei Probleme mit einer sehr großen Grafik. Eine vergleichbare habe ich angehängt. Müsst ihr wieder vergrößern auf 6000x3000 Pixel. Das Original, was später eingesetzt werden soll, ist 230KB groß.
Über Pfeil Auf/Ab soll es möglich sein das Bild zu skalieren. Das habe ich auch soweit hinbekommen mit drawImage. Ich verwende VALUE_INTERPOLATION_NEAREST_NEIGHBOR, da dass die schnellste Methode sein soll. Nun zu meinen Problemen.

1. Das ganze ist ein Speicherfresser. Ohne mindestens mit -Xmx256M zu starten wird nicht mal das Bild geladen bzw. das Skalieren geht nicht. Manchmal funktioniert das Skalieren damit nicht einmal. Das Skalieren läuft nicht gerade flüssig. Ich hatte gehofft, wenn man fünf Mal verkleinert (fünf Mal sofort Pfeil Ab drücken) wird das relativ schnell ausgeführt. Es dauert aber etwa 5 Sekunden und auch nur wenn ich schon auf 512MB erhöhe. Wie kann ich das besser machen?

2. Wie kann ich den JFrame mit anpassen? Je kleiner das Bild wird, umso größer werden die grauen Bereiche, weil sich der Frame nicht mit anpasst. Mit setSize geht das zwar, aber ich möchte nicht, dass er größer wird als 1000x800. Das gleiche gilt für das verwendete JPanel und die JScrollPane.

Vielen Dank schon mal für eure Vorschläge.

Java:
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;

public class Visualisierung3 extends JFrame{

	private static final long serialVersionUID = 1L;
	private JScrollPane jScrollPane = null;
	private JPanel jPanel = null;
	private JLabel hintergrundLabel;
	private BufferedImage hintergrundBildNormal;
	private BufferedImage hintergrundBildScaled;
	private int widthHintergrund = 6000;
	private int heightHintergrund = 3000;
	private int zoom = 10;
	private final int MAXZOOM = 10;
	private final int MINZOOM = 1;
	
	public static void main(String[] args) {
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				Visualisierung3 thisClass = new Visualisierung3();
				thisClass.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
			}
		});
	}

	public Visualisierung3() {
		super();
		try {
			hintergrundBildNormal = ImageIO.read(new File("C:\\Bild.gif"));
			hintergrundBildScaled = hintergrundBildNormal;
		} catch (IOException e) {
			e.printStackTrace();
		}
		this.setSize(1000, 800);
		this.setMaximumSize(new Dimension(1000, 800));
		this.setTitle("Visualisierung");
		this.addKeyListener(new java.awt.event.KeyAdapter() {
			public void keyPressed(java.awt.event.KeyEvent e)
			{
				if(e.getKeyCode()==KeyEvent.VK_DOWN)
				{
					if (zoom != MINZOOM)
					{
						zoom = zoom - 1;
					}
					labelSkalieren();
				}
				if(e.getKeyCode()==KeyEvent.VK_UP)
				{
					if (zoom != MAXZOOM)
					{
						zoom = zoom + 1;
					}
					labelSkalieren();
				}
			}
		});
		
		jPanel = new JPanel();
		jPanel.setLayout(null);
		hintergrundLabel = new JLabel(new ImageIcon(hintergrundBildScaled));
		hintergrundLabel.setSize(widthHintergrund, heightHintergrund);
		jPanel.setPreferredSize(hintergrundLabel.getSize());
		jPanel.add(hintergrundLabel);
		jScrollPane = new JScrollPane();
		jScrollPane.setViewportView(jPanel);
		this.setContentPane(jScrollPane);
		this.setVisible(true);
	}

	private void labelSkalieren() {
		hintergrundBildScaled = scale(hintergrundBildNormal, (float) zoom/MAXZOOM);
		hintergrundLabel.setSize(hintergrundBildScaled.getWidth(), hintergrundBildScaled.getHeight());
		hintergrundLabel.setIcon(new ImageIcon(hintergrundBildScaled));
//		this.setBounds(0, 0, hintergrundLabel.getWidth(), hintergrundLabel.getHeight());
//		this.setSize(hintergrundLabel.getSize());
		this.repaint();
	}
	
	public BufferedImage scale(BufferedImage source, float factor) {
        int width = (int) (source.getWidth() * factor);
        int height = (int) (source.getHeight() * factor);
        BufferedImage img = new BufferedImage(width, height,
                                                    BufferedImage.TYPE_INT_RGB);
        Graphics2D g2d = (Graphics2D) img.getGraphics();
        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                             RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
        g2d.drawImage(source, 0, 0, width, height, null);
        return img;
    }
}
 
Also, dass es langsam ist kann ich jetzt nicht nachvollziehen (keine merkliche Verzögerung, also weniger als 0.25 Sekunden oder so). Könnte am Rechner, an der JVM und den Startparametern liegen...

Zum Speicherverbrauch: Ein 6000x3000 Bild braucht prinzipbedingt schonmal 72 MB - da hilft alles nichts. Wenn dann für einen kurzen Moment das gleiche Bild nochmal um 10% kleiner vorliegt, kommt man auf ca. 150MB alleine für das Bild ... dann bleibt von 256 schon nicht mehr sooo viel übrig.

Das Anpassen der Größe... ja... och, da schreibt bestimmt noch jemand anderes was dazu...
 
Zu 2:

Versuche einfach immer pack() und revalidate() auf dem JFrame aufzurufen, dann geht er durchs Layout und setzt für Alle Komponenten die Größe neu, und schneiden nicht benötigten platz weg!

Zu 1:

Habe ich jetzt nicht so die Idee!Außer einfach ein Temp File anzulegen um es auf der Platte zwischen zu speichern!

Da habe ich auch noch ne frage, Jeder Kennt doch eine Miniatur Ansicht, wenn ich so was in eine JListe machen will und in die JListe Bilder Lade, dann muss ich die Miniatur Ansicht nach Laden, aber ich bekomme da doch ein Speicher Problem im Memory mit den ganzen Bildern ?

lg
 
Zum Speicherverbrauch: Ein 6000x3000 Bild braucht prinzipbedingt schonmal 72 MB - da hilft alles nichts. Wenn dann für einen kurzen Moment das gleiche Bild nochmal um 10% kleiner vorliegt, kommt man auf ca. 150MB alleine für das Bild ... dann bleibt von 256 schon nicht mehr sooo viel übrig.

Hab ich befürchtet.


Zu 2:
Versuche einfach immer pack() und revalidate() auf dem JFrame aufzurufen, dann geht er durchs Layout und setzt für Alle Komponenten die Größe neu, und schneiden nicht benötigten platz weg!

JFrame hat kein revalidate(). Hab jetzt mal ein this.setPreferredSize(new Dimension(1000, 800)) eingebaut und rufe nun beim Skalieren folgendes auf:

[Java]
jPanel.revalidate();
jScrollPane.revalidate();
this.pack();
this.repaint();
[/Java]

Ändert aber nichts.

Zu 1:
Habe ich jetzt nicht so die Idee!Außer einfach ein Temp File anzulegen um es auf der Platte zwischen zu speichern!

Der eigentliche Vorgang des Skalieren könnte durch vorgefertigte Bilder in jeder Größe in der Tat wegfallen. Der Speicherbedarf wäre trotzdem der selbe, da es kurzzeitig zwei eingelesene Bilder gibt.
 
Wenn man die Größen nicht alle selbst setzt, ist diese Anpassung nicht so schwierig
Java:
// From [url]http://www.java-forum.org/awt-swing-swt/92969-sehr-grosses-bild-skalieren.html[/url]

import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;

public class Visualisierung3 extends JFrame{

    private static final long serialVersionUID = 1L;
    private JScrollPane jScrollPane = null;
    private JPanel jPanel = null;
    private JLabel hintergrundLabel;
    private BufferedImage hintergrundBildNormal;
    private BufferedImage hintergrundBildScaled;
    private int zoom = 10;
    private final int MAXZOOM = 10;
    private final int MINZOOM = 1;

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                Visualisierung3 thisClass = new Visualisierung3();
                thisClass.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            }
        });
    }

    public Visualisierung3() {
        super();
        try {
            hintergrundBildNormal = ImageIO.read(new File("Bild.gif"));
            hintergrundBildScaled = hintergrundBildNormal;
        } catch (IOException e) {
            e.printStackTrace();
        }
        this.setSize(1000, 800);
        this.setMaximumSize(new Dimension(1000, 800));
        this.setTitle("Visualisierung");
        this.addKeyListener(new java.awt.event.KeyAdapter() {
            public void keyPressed(java.awt.event.KeyEvent e)
            {
                if(e.getKeyCode()==KeyEvent.VK_DOWN)
                {
                    if (zoom != MINZOOM)
                    {
                        zoom = zoom - 1;
                    }
                    labelSkalieren();
                }
                if(e.getKeyCode()==KeyEvent.VK_UP)
                {
                    if (zoom != MAXZOOM)
                    {
                        zoom = zoom + 1;
                    }
                    labelSkalieren();
                }
            }
        });

        jPanel = new JPanel();
        jPanel.setLayout(new GridLayout(1,1));
        hintergrundLabel = new JLabel(new ImageIcon(hintergrundBildScaled));
        jPanel.add(hintergrundLabel);
        jScrollPane = new JScrollPane();
        jScrollPane.setViewportView(jPanel);
        this.setContentPane(jScrollPane);
        this.setVisible(true);
    }



    private void labelSkalieren() {
        hintergrundBildScaled = scale(hintergrundBildNormal, (float) zoom/MAXZOOM);
        hintergrundLabel.setIcon(new ImageIcon(hintergrundBildScaled));
//      this.setBounds(0, 0, hintergrundLabel.getWidth(), hintergrundLabel.getHeight());
//      this.setSize(hintergrundLabel.getSize());
        adjustSize();
        this.repaint();
    }

    private void adjustSize()
    {
        Dimension size = getPreferredSize();
        size.width = Math.min(size.width, 1000);
        size.height = Math.min(size.height, 800);
        setSize(size.width, size.height);
    }


    public BufferedImage scale(BufferedImage source, float factor) {
        int width = (int) (source.getWidth() * factor);
        int height = (int) (source.getHeight() * factor);
        BufferedImage img = new BufferedImage(width, height,
                                                    BufferedImage.TYPE_INT_RGB);
        Graphics2D g2d = (Graphics2D) img.getGraphics();
        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                             RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
        g2d.drawImage(source, 0, 0, width, height, this);
        return img;
    }
}
 
Ich hab jetzt einfach mal folgendes eingebaut:

Java:
System.out.println("Frame : "+this.getSize());
System.out.println("Scroll: "+jScrollPane.getSize());
System.out.println("Panel : "+jPanel.getSize());
System.out.println("Label : "+hintergrundLabel.getSize());

Ich hab gemerkt, dass sich die Größe des Panel nie verändert. Sie ist immer 6000x3000. Weil ich weiter oben jPanel.setPreferredSize(hintergrundLabel.getSize()) aufrufe. Ich habe den Aufruf jetzt beim Skalieren auch mit eingebaut und jetzt verkleinert sich das Panel auch mit. Lediglich der Frame bleibt immer bei 1000x800.
 
Wenn man die Größen nicht alle selbst setzt, ist diese Anpassung nicht so schwierig

Hatte deinen Post gerade noch gar nicht gesehen. Das funktioniert in der Tat in diesem Beispiel. Aber du hast aus dem Layout ein GridLayout gemacht. Ich muss mal gucken, ob das Originalprogramm dann noch funktioniert.

MATH.min kannte ich noch gar nicht.
 
Wenn man auf Layoutmanager verzichtet und glaubt sich mit einem Null-Layout das Leben einfacher machen zu können, muss eben auch mit den Konsequenzen leben ;-)

Das stimmt natürlich 🙂. In diesem Fall werd ich auf das große Bild aber andere Label pixelgenau setzen und auch hin und herspringen lassen. Und da weiß ich nichts besseres als ein Null-Layout.

Wenn ich das setPreferredSize() wieder rausnehme kann ich jedenfalls Marcos Variante nutzen und das obwohl ich noch öfters setSize() auf die anderen Komponenten verwende.
 
Status
Nicht offen für weitere Antworten.

Zurück
Oben