Best Practice BufferedImage in JPane darstellen - aber wie?

pkm

Bekanntes Mitglied
Ich habe mich erneut daran gemacht, mich mit Computerprogrammierung zu beschäftigen und mache als Übung gerade ein einfachstes Bildleseprogramm. Dieses soll durch einen FileChooser Bilddateien einlesen und diese auf einem JPanel darstellen.

Dazu erschaffe ich in der Mainmethode eine Gui:

Java:
package core;

import gui.Gui;

public class Main {

public static void main(String[] args) {

Gui gui = Gui.getInstance();
gui.createAndShowGUI();

  }

}

... ich wollte nun zunächst durch die Methode setPicPane() eine leere JPane erzeugen. An den Button des FileChooser, mit dem ich das Bild lade, habe ich einen Actionlistener gehängt, wenn dieser Button betätigt wird, habe ich die Methode setPicPane(pic) mit dem BufferedImage namens pic überladen.

Java:
package gui;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.swing.*;
import loader.Loader;

public class Gui implements ActionListener{

private myJFrame frame;
private JButton choose;
private JButton save;
private JButton cancel;
private JLabel picPane;
private static final Gui gui = new Gui();

private Gui() {
   System.out.println("Gui built...");
}

public static Gui getInstance() {
    return gui;
}

public void createAndShowGUI() {


frame = new myJFrame();

setPicPane();

JPanel buttonPane = new JPanel();

buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS));
buttonPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));

choose = new JButton("choose pic");
choose.addActionListener(this);

buttonPane.add(choose);
buttonPane.add(Box.createRigidArea(new Dimension(10, 0)));

save = new JButton("save");

save.addActionListener(this);

buttonPane.add(save);
buttonPane.add(Box.createRigidArea(new Dimension(10, 0)));

cancel = new JButton("cancel");

cancel.addActionListener(this);

buttonPane.add(cancel);

frame.addToFrame(buttonPane,BorderLayout.PAGE_END);

frame.show();

}

@Override

public void actionPerformed(ActionEvent ae) {

if(ae.getSource() == this.cancel){

   System.exit(0);

}

else if(ae.getSource() == this.choose){

Loader loader = Loader.getInstance();

BufferedImage pic = loader.getFileFromLoader();

setPicPane(pic);

}

else if(ae.getSource() == this.save){

// ...

     }

}



private void setPicPane() {

picPane = new JLabel();

picPane.setLayout(new BoxLayout(picPane, BoxLayout.PAGE_AXIS));
picPane.add(Box.createRigidArea(new Dimension(5,5)));
picPane.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));

frame.addToFrame(picPane,BorderLayout.CENTER);

}

private void setPicPane(BufferedImage pic) {

picPane = new JLabel();

picPane.setLayout(new BoxLayout(picPane, BoxLayout.PAGE_AXIS));
picPane.add(Box.createRigidArea(new Dimension(5,5)));
picPane.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
picPane.paintComponents(pic.getGraphics());

frame.addToFrame(picPane,BorderLayout.CENTER);

   }
}

Nun meine Frage: Leider funktioniert das so nicht. Liegt das daran, dass sie Idee mit der überladenen setPicPane()-Methode nicht geht, oder lade ich das Bild falsch in das BufferedImage-Objekt?

Hier noch die Loader-Klasse, wo das Bild eben geladen werden soll:

Code:
package loader;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.JFileChooser;
import javax.swing.filechooser.FileNameExtensionFilter;


public class Loader {
  
  
  
    private static final Loader loader = new Loader();
    
      private Loader() {
          System.out.println("Loader built...");
      }
          
      public static Loader getInstance() {
        return loader;
      }
    
      public static BufferedImage getFileFromLoader(){
    
      
      JFileChooser chooser = new JFileChooser();
      FileNameExtensionFilter filter = new FileNameExtensionFilter(
          "jpg", "jpeg", "gif", "png", "JPG", "JPEG", "GIF", "PNG");
      chooser.setFileFilter(filter);
    
      int returnVal = chooser.showOpenDialog(null);
      if(returnVal == JFileChooser.APPROVE_OPTION) {
         System.out.println("You chose to open this file: " +
              chooser.getSelectedFile().getName());
      }
    
      BufferedImage buffImg = null;
    
      if (chooser.showOpenDialog(null) == 0) {
      
      try {
        
          buffImg = ImageIO.read(chooser.getSelectedFile());
    
      } catch (IOException e) {
          System.out.println("Image could not be loaded.");
       }
      }
    
      return buffImg;
    
     }
}
 
Zuletzt bearbeitet:
Leider funktioniert das so nicht. Liegt das daran, dass sie Idee mit der überladenen setPicPane()-Methode nicht geht, oder lade ich das Bild falsch in das BufferedImage-Objekt
Ersteres. Du zeichnest den Inhalt des JLabels auf den Graphics2D Context des Images. Was du aber eigentlich möchtest, ist es, das Bild auf den Grapics2D des JLabel zu zeichnen. Dazu kannst du z. B. eine ImageIcon erstellen und das als Icon auf dem JLabel setzen.
 
Vielen Danke für die Antworten. Ich habe das nun erstmal wie folgt gelöst:

Java:
    BufferedImage pic = loader.getFileFromLoader();
           
               if(pic!=null){
               
               //Der aussere Rahmen muss sich der Bildgroesse anpassen.
               frame.setFrameSize(pic.getWidth()+180,pic.getHeight()+180);
               
               ////...das Bildpanel auch.
               picPanel.setPanelSize(pic.getWidth(),pic.getHeight());
               picPanel.prepareImage(pic);
               
               //Das Bild wird ins Panel gesetzt.
               Graphics g = pic.getGraphics();
               picPanel.paintComponent(g);
                   }else{
                       System.out.println("Image could not be loaded.");
                   }
               }



Java:
@Override
   protected void paintComponent(Graphics g) {
       
       try{
       Graphics2D g2 = getBuffer().createGraphics();
        g2.dispose();
       g.drawImage(getBuffer(), (frameWIDTH-imgWIDTH)/4, 0, imgWIDTH, imgHEIGHT, null);
       
       }catch(NullPointerException e){
       
           System.out.println("A myPanel has been created and is awaiting a BufferedImage or there is an image-buffer which is null.");   
       
       }
   }
 
Ganz ehrlich: nach "gelöst" sieht mir das nicht aus.
Du zeichnest unnötigerweise den Inhalt des picPanel auf den Graphics2D des BufferedImages. In der paintComponent() Methode wird der Graphics2D von einem "buffer" beschafft und sofort wieder disposed. Erst dann zeichnest du richtigerweise den buffer auf den Graphics context.
 
Hmmm, danke. Was ich nicht ganz verstehe sind sowieso Folgendes:

Es scheint so, dass das Bild erst dann auf Anhieb gezeichnet wird, wenn ich den Frame am Anfang ganz weit auseinanderziehe. Wenn ich dies nicht tue, erscheint das Bild erst, wenn ich den Rahmen was auseinanderziehe, aber es wird jeweils ein und dasselbe Bild überöblendet dargestellt :\ Dabei wird drawing nur einmal aufgerufen, ich verstehe nicht, woran dies liegen könnte.

Hier die ganze GUI-Klasse und die myJImgPanel Klasse:

Java:
package gui;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import javax.swing.*;


import loader.Loader;

public class Gui implements ActionListener{
  
   private myJFrame frame;
   private JButton choose;
   private JButton save;
   private JButton cancel;
   private myJImgPanel picPanel;
  
   BufferedImage pic;
  
    private static final Gui gui = new Gui();
    
      private Gui() {
          System.out.println("Gui built...");
      }
          
      public static Gui getInstance() {
        return gui;
      }

      
        @SuppressWarnings("deprecation")
       public void createAndShowGUI() {
          
               //Create and set up the window.
            frame = new myJFrame();
          
            //Hier wird das Bild ins das Frame gesetzt.
            picPanel = new myJImgPanel();
            picPanel.setLayout(new BoxLayout(picPanel, BoxLayout.X_AXIS));
            //picPanel.add(Box.createRigidArea(new Dimension(5,5)));
            //picPanel.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));          
          
          
            frame.addToFrame(picPanel,BorderLayout.CENTER);
                      
           //Hier werden die Buttons ins das Frame gesetzt.
            JPanel buttonPane = new JPanel();
            buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.X_AXIS));
            buttonPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
            choose = new JButton("choose pic");
            choose.addActionListener(this);
            buttonPane.add(choose);  
            buttonPane.add(Box.createRigidArea(new Dimension(10, 0)));
            save = new JButton("save");
            save.addActionListener(this);
            buttonPane.add(save);
            buttonPane.add(Box.createRigidArea(new Dimension(10, 0)));
            cancel = new JButton("cancel");
            cancel.addActionListener(this);
            buttonPane.add(cancel);
        
             frame.addToFrame(buttonPane,BorderLayout.PAGE_END);
            
            frame.show();
           }

      

       @SuppressWarnings("static-access")
       @Override
       public void actionPerformed(ActionEvent ae) {
          
            if(ae.getSource() == this.cancel){
                System.exit(0);
               }
          
               //Wenn das Bild ausgewaehlt wird...
               else if(ae.getSource() == this.choose){
              
               Loader loader = Loader.getInstance();
              
               pic = loader.getFileFromLoader();
          
               if(pic!=null){
              
               //Der aussere Rahmen muss sich der Bildgroesse anpassen.
               frame.setFrameSize(pic.getWidth(),pic.getHeight());
              
               ////...das Bildpanel auch.
               picPanel.setPanelSize(pic.getWidth(),pic.getHeight());
               picPanel.prepareImage(pic);
              
               //Das Bild wird ins Panel gesetzt.
               System.out.println("Drawing...");//--> Es scheint so, dass das Bild erst dann auf Anhieb
                                        //gezeichnet wird, wenn ich den Frame am anfang ganz weit auseinanderziehe.
                                        //Wenn nicht, dann erscheint das Bild, aber es wird jeweils ein und dasselbe Bild überöblendet dargestellt.
               Graphics g = pic.getGraphics();
               picPanel.paintComponent(g);
                
               }
              
               else{
                       System.out.println("Problem with displaying image.");
                   }
               }
          
               else if(ae.getSource() == this.save){
                   // ...
                   }
          
       }

  
      
      
   }

Java:
package gui;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;

import javax.swing.JPanel;


public class myJImgPanel extends JPanel{

    /**
    *
    */
   private static final long serialVersionUID = 1L;

    Imagedata imgdata;
  
    int PANELHEIGHT, PANELWIDTH;
  
  
    myJImgPanel() {
      
        imgdata = new Imagedata();
      
    }
  

  
   void prepareImage(BufferedImage pic){
          
       imgdata.setBuffer(pic);
       imgdata.setWidth(pic.getWidth());
       imgdata.setHeight(pic.getHeight());
      
}
  
  
   private void setPanelHeight(int panelHeight) {
       PANELHEIGHT=panelHeight;
      
   }
  
   private void setPanelWIDTH(int panelWidth) {
       PANELWIDTH=panelWidth;
      
   }

   @Override
   protected void paintComponent(Graphics g) {
      
   try{
          
   g.drawImage(imgdata.getBuffer(), (getWidth()-imgdata.getWidth())/2, (getHeight()-imgdata.getHeight())/2, imgdata.getWidth(), imgdata.getHeight(), null);
      
   }catch(NullPointerException e){
      
     System.out.println("A myPanel has been created and is awaiting a BufferedImage.");  
      
       }
   }

   public void setPanelSize(int panelWidth, int panelHeight) {
       setPanelWIDTH(panelWidth);
       setPanelHeight(panelHeight);
      
   }
}
 
Moin,
Code:
frame.setFrameSize(pic.getWidth(),pic.getHeight()); 
picPanel.setPanelSize(pic.getWidth(),pic.getHeight());
Bist Du sicher, dass Frame und picPanel genau gleich groß sein sollen ??

VG Klaus
 
Das habe ich zwar geändert wie folgt:
Java:
 //Der aussere Rahmen muss sich der Bildgroesse anpassen.
               frame.setFrameSize(pic.getWidth()+500,pic.getHeight()+500);

...das Resultat ist nun, dass das Bild zwar auf Anhieb geöffnet wird... allerdings wird es nach wie vor überblendet dargestellt.
 
Nicht ganz.

Ich habe es nun wie folgt hinbekommen:

Ich habe das @Override der paintComponents()-Methode auskommentiert sowie die repaint()-Methode an frame aufgerufen:

Java:
picPanel.paintComponents(g);
frame.repaint();
 
Der direkte Aufruf von paintComponent(Graphics g) ist nur in Ausnahmefällen notwendig, z. B. wenn der Inhalt der JComponent als Bild gespeichert werden soll. Dazu muss z. B. der Graphcis2D eines Images übergeben werden. Im vorliegen den Fall sieht der Aufruf unnötig bis falsch aus.
@Override der paintComponents()-Methode auskommentiert
paintComponent oder wirklich paintComponents ?
 
Ja, ich meinte das Override; es klappt jetzt sehr gut.

Allerdings habe ich nun versucht, besonders große Bilder zu laden, und um dies zu bewerkstelligen, habe ich eine maximale Höhe und Breite für den Rahmen festgelegt: scaledWidth und scaledHeight. Die Methode zum Skalieren und darstellen des Bildes sieht wie folgt aus:

Java:
    private void displayLarge() {
           frame.setFrameSize(scaledWidth+20,scaledHeight+20);
                       
           picPanel.setImgdata(imgdata);

           Graphics2D g = pic.createGraphics();
                  g.drawImage(pic, 0, 0, scaleWidth, scaleHeight, null);//Hier will ich das Bild skalieren...
           
             picPanel.paintComponents(g); //und dann darstellen.
       
       g.dispose();
           
       frame.repaint();
           
  }

Leider wird anscheinend nichts skaliert.
 
Es ist nicht wirklich hilfreich, eine vollkommen aus dem Klassenkontext gerissene Methode zu posten (höchstens wenn es in der Methode einen Syntaxfehler gibt, den du nicht alleine lösen kannst).
Wie ich schon mehrfach angemerkt habe, zeichnest du das picPanel auf dem Graphics2D des Images. Es muss aber andersrum sein! Das Image soll auf dem Graphics2D des JPanel gezeichnet werden. Ausserdem zeichnest du jetzt auch noch auf dem Bild dasselbe Bild in der skalierten Version. Ich vermute mal, dass das Ergebnis ziemlich merkwürdig aussehen würde.
Ich bin erstmal raus aus der Diskussion.
 
Ich habe nun versucht, das Bild auf eine andere Weise zu laden, es funktioniert nicht, und ich komme leider wieder nicht weiter - das Bild wird nicht angezeigt, mein Versuch sieht wie folgt aus (die Mainmethode ruft createAndShowGUI() auf):

Java:
package gui;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;

import javax.swing.*;

import loader.Loader;

public class Gui implements ActionListener{
   
   private MyJFrame frame;
   private JButton choose;
   private JButton save;
   private JButton cancel;
   
   BufferedImage pic;
   
   int scaleHeight = 600;
   int scaleWidth = 600;
   
    private static final Gui gui = new Gui();
           
      public static Gui getInstance() {
        return gui;
      }

   
        @SuppressWarnings("deprecation")
       public void createAndShowGUI() {
           
            //Create and set up the frame.
            frame = new MyJFrame();
                               
            //Hier soll das Bild ins das Frame gesetzt werden.
            JPanel imagePane = new MyImageJPanel(); //Siehe unten.
            imagePane.setLayout(new BoxLayout(imagePane, BoxLayout.X_AXIS));
           
           
           //Hier werden die Buttons ins das Frame gesetzt.////////////////////////////////////////////////////////////////////////////////
            JPanel buttonPane = new JPanel();
            buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.X_AXIS));
            buttonPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
           
            choose = new JButton("choose pic");
            choose.addActionListener(this);
            buttonPane.add(choose);   
            buttonPane.add(Box.createRigidArea(new Dimension(10, 0)));
           
            save = new JButton("save");
            save.addActionListener(this);
            buttonPane.add(save);
            buttonPane.add(Box.createRigidArea(new Dimension(10, 0)));
           
            cancel = new JButton("cancel");
            cancel.addActionListener(this);
            buttonPane.add(cancel);
         
            frame.addToFrame(imagePane, BorderLayout.NORTH);
            frame.addToFrame(buttonPane,BorderLayout.PAGE_END);

////////////////////////////////////////////////////////////////////////////////
           
            frame.show();
           
}
   

       @SuppressWarnings("static-access")
       @Override
       public void actionPerformed(ActionEvent ae) {
           
               //Wenn das Bild ausgewaehlt wird...
               else if(ae.getSource() == this.choose){
               
               Loader loader = Loader.getInstance();
               
               pic = loader.getFileFromLoader();
           
               if(pic!=null){
              
              //Wenn ein Bild ausgewählt ist, will ich es darstellen, indem ich die paint-Methode am MyImagePanel-Objekt aufrufe (siehe unten), und das geht nicht.
     
                     Graphics g = pic.getGraphics();
                     ((MyImageJPanel) frame.getContentPane().getComponent(0)).setBuffer(pic);
                     ((MyImageJPanel) frame.getContentPane().getComponent(0)).paint(g);
                         
                 }      
               
               else{
                       System.out.println("Problem with displaying image.");
                   }
               }               
       }

       
   
       class ImageLoadStatus {

           public boolean widthDone = false;

           public boolean heightDone = false;

       }
       
   }

Java:
package gui;

import java.awt.Graphics;
import java.awt.image.BufferedImage;

import javax.swing.JPanel;

public class MyImageJPanel extends JPanel {
   
   /**
    *
    */
   private static final long serialVersionUID = 1L;

   BufferedImage picture;
   
   void setBuffer(BufferedImage pic){
       
       picture=pic;
       
   }
   
   @Override
   public void paint(Graphics g) {
       
       
       try{
       
        int x = (int) getBounds().getX();
        int y = (int) getBounds().getY();
   
        g.drawImage(picture, x, y, this);
         
          }catch(NullPointerException e){
         
         System.out.println("A myPanel has been created and is awaiting a BufferedImage or there is an image-buffer which is null.");  
         
          }
   
   g.dispose();       
           
             
   }
   
   
}
 
Der direkte Aufruf von paintComponent(Graphics g) ist nur in Ausnahmefällen notwendig
Wie ich schon mehrfach angemerkt habe, zeichnest du das picPanel auf dem Graphics2D des Images. Es muss aber andersrum sein! Das Image soll auf dem Graphics2D des JPanel gezeichnet werden.
Da die Erklärungen offenbar nicht wirklich weiterhelfen, und er Code nach wie vor unnötig kompliziert/chaotisch aussieht, hier ein Beispiel:
Java:
import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.*;

public class ImagePanel extends JComponent {

    private Image buffer;

    public static void main(String[] args) {
        JFrame frame = new JFrame();
        ImagePanel ip = new ImagePanel();
        frame.getContentPane().add(new JScrollPane(ip));
        JButton loader = new JButton("Laden");
        loader.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                JFileChooser jfc = new JFileChooser();
                int result = jfc.showOpenDialog(loader);
                if (result == JFileChooser.APPROVE_OPTION) {
                    File input = jfc.getSelectedFile();
                    try {
                        Image img = ImageIO.read(input);
                        if(img != null) ip.setBuffer(img);
                    } catch (IOException ioe) {
                         ioe.printStackTrace();
                    }
                }
            }
        });
        frame.getContentPane().add(loader, BorderLayout.NORTH);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setVisible(true);
    }

    public void setBuffer(Image buffer) {
        this.buffer = buffer;
        setPreferredSize(new Dimension(buffer.getWidth(null), buffer.getHeight(null)));
        repaint();

    }

    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;
        g2.drawImage(buffer, 0, 0, null);
    }
}
 

Zurück
Oben