Größe von BufferedImage ändern

  • Themenstarter Themenstarter fat32
  • Beginndatum Beginndatum
Status
Nicht offen für weitere Antworten.
F

fat32

Gast
Hi,

ich male ein BufferedImage auf ein JPanel:
Java:
protected void paintComponent( Graphics g )
  {
    if ( image != null )
      g.drawImage( image, 0, 0, image.getWidth(null), image.getHeight(null), this );
  }

Ich möchte das Bild so vergrößern bzw. verkleinern:
Java:
private void resizeImage(int x, int y) 
	{		
		if(x == 0 || y == 0)
		{
			System.err.println("x und y dürfen nicht 0 sein");
		}
		else
		{
			this.setImage(this.getImage().getScaledInstance(x, y, BufferedImage.SCALE_FAST));
			this.refresh();
		}
	}

Die setImage-Methode sieht so aus:
Java:
public void setImage(Image img)
  {
	  BufferedImage bi = new BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_INT_RGB);
	  bi.getGraphics().drawImage(img,0,0,bi.getWidth(),bi.getHeight(),null);
	  this.setImage(bi);
  }

Das Ändern der Größe funktioniert auch.
Was mich allerdings wundert, ist, dass das Bild zwar größer gezeichnet wird, wenn ich den Bildbereich größer ziehe, aber nicht kleiner, wenn ich ihn kleiner ziehe.
Die Größe wird intern zwar geändert, aber das Bild wird nicht richtig angezeigt, obwohl ich repaint() aufrufe.

An was kann das liegen?

MfG
Simon
 
Java:
private void resizeImage(int x, int y) 
	{		
		if(x == 0 || y == 0)
		{
			System.err.println("x und y dürfen nicht 0 sein");
		}
		else
		{
            this.setImage(this.getImage().getScaledInstance(x, y, BufferedImage.SCALE_FAST));
			this.refresh();
			this.parent.refreshWindow();
		}
	}
So hab ich es jetzt geschafft.

refreshWindow() macht folgendes:
Java:
public void refreshWindow()
	{
		this.window.pack();
		this.window.setExtendedState(JFrame.MAXIMIZED_BOTH);
		this.window.setLocationRelativeTo(null);	
	}
Wahrscheinlich war das pack() nötig...

Kann ich es irgenwie erreichen, dass nur das Bild gepackt wird?
So flackert nämlich das Bild

EDIT:
Das Flackern kommt von setExtendedState. Wenn ich das aber rausnehme, habe ich wieder da oben beschriebene Problem, dass das Bild zwar größer, aber nicht kleiner gemacht wird.

MfG
Simon
 
Zuletzt bearbeitet von einem Moderator:
Die Grösse eines BufferedImage können wir ändern, indem wir Breite und Höhe mit einem Zoomfaktor multiplizieren. Dann brauchen wir nur noch eine neues BufferedImage mit der neuen Grösse zu erzeugen und das alte draufzumalen. Aber warum machst du nicht einfach ein On-The-Fly Scaling (siehe auch The Perils of Image.getScaledInstance() | Java.net )? Falls du nämlich das Original BufferedImage tatsächlich kleiner machst und danach wieder grösser, wird die Qualität sehr schnell darunter leiden.
 
Hi,

ich hab mich wohl falsch ausgedrückt.

Ich möchte nicht das Bild skalieren, sondern die Bildfläche größer bzw. kleiner machen.
Das, was zu sehen ist, soll in der Größe unverändert bleiben, aber abgeschnitten werden.

Wird das Bild größer gezogen, soll der Bildbereich einfach weiß gemacht werden (das was dazugekommen ist)

Wie genau realisiere ich sowas?

Bei meiner Lösung wird es nämlich auch skaliert...
 
Code:
public void setImage(Image img)
  {
      BufferedImage bi = new BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_INT_RGB);
      bi.getGraphics().drawImage(img,0,0,bi.getWidth(),bi.getHeight(),null);
      this.setImage(bi);
  }
Wie das NICHT zu einem StackOverflowError führen soll, ist mir im Moment nicht ganz klar.

Nach welchem Prinzip bist du denn vorgegangen, um das Problem zu lösen? Mal
[c]window.[/c]
eingetippt, und munter ein paar von den Methoden ausprobiert, die Eclipse da anbietet? :noe:
 
Ich möchte nicht das Bild skalieren, sondern die Bildfläche größer bzw. kleiner machen.
Dann malen wir einfach immer das Original auf das Panel: g.drawImage(image, x1, y1, this). Über die Bildkoordinaten und die Größe des Panels können wir den Bildausschnitt verändern. Zur Aktualisierung der Oberfläche machen wir ein repaint() vom Panel.
 
Zuletzt bearbeitet:
Code:
public void setImage(Image img)
  {
      BufferedImage bi = new BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_INT_RGB);
      bi.getGraphics().drawImage(img,0,0,bi.getWidth(),bi.getHeight(),null);
      this.setImage(bi);
  }
Wie das NICHT zu einem StackOverflowError führen soll, ist mir im Moment nicht ganz klar.

Nach welchem Prinzip bist du denn vorgegangen, um das Problem zu lösen? Mal
[c]window.[/c]
eingetippt, und munter ein paar von den Methoden ausprobiert, die Eclipse da anbietet? :noe:

Hi, warum sollte das zu einem Stack Overflow führen? Versteh ich nicht.

Nach welcme Prinzip ich vorgegangen bin?
Ganz einfach: Nach meinem Verstand

Im von dir zitierten Code kann ich kein window. erkennen

Oder meinst du das?
Java:
public void refreshWindow()
    {
        this.window.pack();
        this.window.setExtendedState(JFrame.MAXIMIZED_BOTH);
        this.window.setLocationRelativeTo(null);    
    }

Ok, hier hast du recht. das setLocationRelativeTo(null) ist unnötig.
Die anderen 2 Aufrufe finde ich aber gerechtfertigt.

Ich "packe" alle Elemente so auf die Oberfläche, dass sie an der richtigen Stelle und in der richtigen Größe sind. Danach maximiere ich das Fenster, weil ich in meiner Anwendung immer ein maximiertes Fenster haben will.

Was ist daran so falsch? Ich will aus meinen Fehlern lernen!

Dann malen wir einfach immer das Original auf das Panel: g.drawImage(image, x1, y1, this). Über die Bildkoordinaten und die Größe des Panels können wir den Bildausschnitt verändern. Zur Aktualisierung der Oberfläche machen wir ein repaint() vom Panel.
Danke, dann probieren wir das mal aus 🙂
 
Oh mann, ich bin so blöd.

Ich habe das Panel mit dem Bild in einem Scrollpane.
Wenn ich das Bild repainte, bringt es nichts, ich muss es mit dem Scrollpane machen.

Mein Ablauf sieht jetzt also so aus:
  • Bild verkleinern
  • Bild auf Panel "legen"
  • Panel repainten
  • Scrollpane repainten

Das Window muss nicht mehr gepackt werden. Sehr schön!

Ist das jetzt so von der Vorgehensweise her richtig?
 
Ich hab jeztt bestimmt eine Stunde lange gegoogelt.

Aber ich finde keine Lösung dafür, wie ich ein Bild vergrößern bzw. verkleinern kann, ohne es zu skalieren. (Abschneiden bzw. Hinzufügen von bereichen, so wie bei Paint)

Ich habe eine Lösung für das verkleinern gefunden: getSubimage()

Aber natürlich geht das nur, wenn ich vom Bild etwas abschneiden will. Möchte ich leere (weiße) Flächen hinzufügen, gibt es Exceptions, die besagen, dass die angegebenen Koordinaten nicht existieren. Ist auch irgendwie logisch.

Ich kann so zwar das Bild vergrößern bzw. verkleinern, aber das Ergebnis ist ein komplett schwartes Bild:
Java:
private void resizeImage(int x, int y) 
	{		
		if(x == 0 || y == 0)
		{
			System.err.println("x und y dürfen nicht 0 sein");
		}
		else
		{
			BufferedImage newImage = new BufferedImage(x,y,BufferedImage.TYPE_INT_RGB);
			newImage.getGraphics().drawImage(this.getImage(),x,y, null) ;
            this.setImage(newImage);
			this.refresh();
		}
	}

Was genau mache ich falsch?
 
Im Konstruktor von BufferedImage musst du sagen wie groß es werden soll (Breite x Höhe). Innerhalb dieser Grenzen kannst du drauf malen.
 
Ich mich fragen... geht's um so was?
Java:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.IOException;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;

import datatypes.DT_Image;
import datatypes.DataType;

public final class ImageViewer
extends JPanel
{
	private static final long serialVersionUID = 1L;
	private final BufferedImage toDraw;

	private ImageViewer(BufferedImage img)
	{
		if(img == null) {
			throw new IllegalArgumentException("null image");
		}
		toDraw = img;
		setPreferredSize(new Dimension(toDraw.getWidth(), toDraw.getHeight()));
		setMaximumSize(getPreferredSize());
		setMinimumSize(new Dimension(1, 1));
	}

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

	public static void main(String[] args)
	{
		if(args == null || args.length == 0) {
			args = new String[] {"sunrise.jpg"};
		}
		try {
			ImageViewer iv = new ImageViewer((DT_Image) DataType.getFile(args[0]));
			final JFrame f = new JFrame("ImageViewer: " + args[0]);
			f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
			f.add(new JScrollPane(iv));
			f.pack();
			f.setVisible(true);
		} catch(IOException e) {
			e.printStackTrace();
			System.exit(-1);
		}
		
	}
}
Bis auf die Art, wie ich die Bilder (oder Dateien überhaupt 😉) lade, dürften wohl kaum Fragen offen bleiben.
@Edit: Und für den Fall, dass unbedingt "resized" werden soll:
Java:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.IOException;

import javax.swing.JFrame;
import javax.swing.JPanel;

import datatypes.DT_Image;
import datatypes.DataType;

public final class ImageViewer2
extends JPanel
{
	private static final long serialVersionUID = 1L;
	private final BufferedImage toDraw;

	private ImageViewer2(BufferedImage img)
	{
		if(img == null) {
			throw new IllegalArgumentException("null image");
		}
		toDraw = img;
		setPreferredSize(new Dimension(toDraw.getWidth(), toDraw.getHeight()));
	}

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

	public static void main(String[] args)
	{
		if(args == null || args.length == 0) {
			args = new String[] {"sunrise.jpg"};
		}
		try {
			ImageViewer2 iv = new ImageViewer2((DT_Image) DataType.getFile(args[0]));
			final JFrame f = new JFrame("ImageViewer: " + args[0]);
			f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
			f.add(iv);
			f.pack();
			f.setVisible(true);
		} catch(IOException e) {
			e.printStackTrace();
			System.exit(-1);
		}
		
	}
}
 
Zuletzt bearbeitet von einem Moderator:
Im Konstruktor von BufferedImage musst du sagen wie groß es werden soll (Breite x Höhe). Innerhalb dieser Grenzen kannst du drauf malen.

Hab ich das nicht hiermit erledigt?
Java:
 BufferedImage newImage = new BufferedImage(x,y,BufferedImage.TYPE_INT_RGB);

@Spacerat:
Danke für die 2 Codeausschnitte.
Leider kann ich auch nach langer Suche nicht die Stelle finden, an der resized werden soll.

Und selbst beokmme ich es immer noch nicht hin, dass ich ein BufferedImage in ein anderes laden kann.
 
@Spacerat:
Danke für die 2 Codeausschnitte.
Leider kann ich auch nach langer Suche nicht die Stelle finden, an der resized werden soll.

Und selbst beokmme ich es immer noch nicht hin, dass ich ein BufferedImage in ein anderes laden kann.
Bei meinen Codeschnipseln suchst du da auch vergeblich, weil es, selbst bei AWT, welches kein DoubleBuffering unterstützt, unnötig ist. Geht es hier darum, ein Image zu resizen, oder den Inhalt resized anzuzeigen? Wie auch immer. Für beide Belange stehen bereits Lösungen im Thread, wobei man sagen muss, das [c]getScaledInstance()[/c]die unperformantere Lösung ist. Diese Methode ist so nützlich wie ein Buckel, aber da ist 'ne andere Geschichte. Hier nochmal ein wenig Code, wie man performanter an seine ScaledInstance kommt.
Java:
// als Ergänzung zu meinen Codeschnipseln
public BufferedImage getScaledInstance(int width, int height)
{
  BufferedImage rc = new BufferedImage(width, height, toDraw.getType());
  rc.getGraphics().drawImage(toDraw, 0, 0, width, height, null);
  return rc;
}
Ach so... Ich seh' das gerade...
Also mein Ablauf sieht so aus:

-Bild in der Grösse der ViewComponent in den GraphicsContext zeichnen.
...
Damit steht es 4 : 1 für dich. Beim Fussball z.B. hättest du gewonnen.😀
 
Zuletzt bearbeitet von einem Moderator:
Hab ich das nicht hiermit erledigt?
Java:
 BufferedImage newImage = new BufferedImage(x,y,BufferedImage.TYPE_INT_RGB);
Naja, für meine Begriffe hat (x,y) nix mit der Grösse zu tun, sondern stellen die Koordinaten dar 🙂
Du sagst damit: die Koordinaten sind die Grösse, was für mich total Quatsch ist 😀
 
Wie ich die Variablen nenne, ist doch egal.
Ich kann die Breite auch pi nennen, wenn mir danach ist.

@Spacerat:
Dein Code funktioniert zwar bestens, aber er tut genau das, was ich nicht will. Er skaliert das Bild. Ich will aber, dass das Bild abgeschnitten wird, wenn es nicht mehr aufs neue Bild passen würde bzw. dass ein weißer Rand dazukommt, wenn es kleiner als das neue Bild ist.

Ich will also genau das Verhalten von Paint haben, wenn man die Bildgröße verändert (NICHT die Skalierung)

Und hierfür habe ich IMMER noch keine Lösung gefunden. Kann langsam nichtmehr wahr sein...

Geht es hier darum, ein Image zu resizen, oder den Inhalt resized anzuzeigen?
Ich will dem Bild neue Maße geben. Angezeigt wird es dann schon richtig
 
Also... eines ist klar. Du hast die ganze Zeit die falsche Methode angewendet. Es muss also [c]drawImage(img, x, y, width, height, null)[/c] statt [c]drawImage(img, x, y, null)[/c] verwendet werden. Fehlt also nur noch die Unterscheidung, welche "width" und / oder welche "height" verwendet werden soll... hmmm
Java:
int w = Math.min(toDraw.getWidth(), getWidth());
int h = Math.min(toDraw.getHeight(), getHeight());
g.drawImage(toDraw, 0, 0, w, h, null);
... versuchs mal...😉
@Edit: ...bei erneutem überlegen dann doch eher:
Java:
g.drawImage(toDraw, 0, 0, toDraw.getWidth(), toDraw.getHeight(), null);
ode mit anderen Worten:
Java:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.IOException;

import javax.swing.JFrame;
import javax.swing.JPanel;

import datatypes.DT_Image;
import datatypes.DataType;

public final class ImageViewer3
extends JPanel
{
	private static final long serialVersionUID = 1L;
	private final BufferedImage toDraw;

	private ImageViewer3(BufferedImage img)
	{
		if(img == null) {
			throw new IllegalArgumentException("null image");
		}
		toDraw = img;
		setPreferredSize(new Dimension(toDraw.getWidth(), toDraw.getHeight()));
	}

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

	public static void main(String[] args)
	{
		if(args == null || args.length == 0) {
			args = new String[] {"GW236H236.jpg"};
		}
		try {
			ImageViewer3 iv = new ImageViewer3((DT_Image) DataType.getFile(args[0]));
			final JFrame f = new JFrame("ImageViewer: " + args[0]);
			f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
			f.add(iv);
			f.pack();
			f.setVisible(true);
		} catch(IOException e) {
			e.printStackTrace();
			System.exit(-1);
		}
		
	}
}
 
Zuletzt bearbeitet von einem Moderator:
Wenn ich es so mache, wie du vorgeschlagen hast, wird wieder skaliert.

So geht es aber:
Java:
private void resizeImage(int width, int height) 
	{		
		if(width == 0 || height == 0)
		{
			System.err.println("x und y dürfen nicht 0 sein");
		}
		else
		{
			BufferedImage newimage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
			newimage.getGraphics().fillRect(0, 0, width, height);
			newimage.getGraphics().drawImage(this.getImage(), 0, 0, null);
            this.setImage(newimage);
			this.refresh();
		}
	}

Aber ich frage mich jetzt, warum mein BufferedImage ohne diese Zeile komplett schwarz und auch nicht bemalbar ist:
Java:
newimage.getGraphics().fillRect(0, 0, width, height);

Auch hier konnte ich beim Googlen niemanden finden, der das gleiche Problem hat.

EDIT: Ich hätte schwören können, dass das Bild komplett schwarz und nicht bemalbar ist, wenn ich den oben geposteten Code benutze.
Jetzt habe ich es gerade nochmal getestet und das Resultat ist folgendes:



Der erweiterte Bereich ist schwarz. Warum ist das so?
 
Zuletzt bearbeitet von einem Moderator:
Du kannst die Hintergrundfarbe setzen, etwa so:
Java:
Graphics2D g = newimage.createGraphics();
g.setBackground(Color.WHITE);
g.clearRect(0, 0, width, height);
...
g.dispose();
 
So bleibt der Bereich schwarz.

Mein Code zum Erstellen des Bilds ist jetzt so:

Java:
this.setImage(new BufferedImage(800, 600,BufferedImage.TYPE_INT_RGB));
	  this.getGraphics2D().setBackground(Color.WHITE);
	  this.getGraphics2D().fillRect(0, 0, this.image.getWidth(), this.image.getHeight());

Ohne die letzte Zeile erhalte ich wieder ein schwarzes Bild.
 
Ich versteh' beim besten willen gar nicht mehr was du möchtest... hast du mal eine meiner Klassen getestet (Achtung: das Laden der Bilder musst du wohl in eine für dich gewohnte Weise umschreiben) bzw. meinen Edit gelesen? ImageViewer3 macht nämlich genau das was du haben möchtest. Er skaliert da gar nichts, sondern verwendet lt. Edit die Breite und Höhe des anzuzeigenden Bildes. Ausserdem:
1. Wenn ein Bild nicht skaliert werden soll, ist es auch nicht nötig es neu zu instanzieren.
2. Du verwendest nach wie vor die falsche [c]drawImage()[/c]-Methode. Wenn du erst da anfängst zu zeichnen, wo das Bild aufhört, ist es kein wunder, wenn du nichts siehst.
Also nochmal:
die richtige [c]drawImage[/c]-Methode ist:
Java:
g.drawImage(image, xPos, yPos, width, height, observer);
 
So bleibt der Bereich schwarz.
Mein Code funktioniert bei mir:
Java:
class MyTestPanel extends JPanel {
    private int width = 100, height = 100;
    private BufferedImage newimage = new BufferedImage(width, height,
            BufferedImage.TYPE_INT_RGB);
    public MyTestPanel() {
        Graphics2D g = newimage.createGraphics();
        g.setBackground(Color.WHITE);
        g.clearRect(0, 0, width, height);
        g.setColor(Color.BLACK);
        g.drawOval(10, 10, 10, 10);
        g.dispose();
    }
    @Override
    protected void paintComponent(Graphics g1) {
        super.paintComponent(g1);
        g1.drawImage(newimage, 0, 0, this);//nicht skalieren
//        g1.drawImage(newimage, 0, 0, 200, 200, this);//skalieren
    }
}
 
@Spacerat: Die von dir genannte Methode skaliert das Bild. Habe ich getestet und das steht auch so in den JavaDocs:
The image is drawn inside the specified rectangle of this graphics context's coordinate space, and is scaled if necessary.

Mit der Methode ohne width und height, also so:
Java:
BufferedImage newimage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
			newimage.getGraphics().fillRect(0, 0, width, height);
			newimage.getGraphics().drawImage(this.getImage(), 0, 0, null);
            this.setImage(newimage);
funktioniert es genau so, wie ich es haben will.

Das mit clearRect funktioniert jetzt übrigens auch.
Aus irgendeinem Grund muss ich die Methode createGraphics statt getGraphics benutzen. Diese Methode liefert mir dann ein Grapics2D-Objekt, mit dem es geht. Auf die Idee bin ich übrigens dank André gekommen. Vielen Dank!
Mein Code sieht jetzt so aus:

Java:
BufferedImage newimage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = newimage.createGraphics();
g2d.setBackground(Color.WHITE);
g2d.clearRect(0, 0, width, height);
g2d.drawImage(this.getImage(), 0, 0, null);
this.setImage(newimage);
g2d.dispose();

Und - unglaublich, aber wahr - es klappt!
Ist das jetzt so richtig gelöst oder mache ich noch irgendwas falsch?
 
Java:
BufferedImage newimage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = newimage.createGraphics();
g2d.setBackground(Color.WHITE);
g2d.clearRect(0, 0, width, height);
g2d.drawImage(this.getImage(), 0, 0, null);
this.setImage(newimage);
g2d.dispose();
Ist das jetzt so richtig gelöst ...?
Für sich betrachtet ist das Fragment jedenfalls korrekt. Wenn es seinen Zweck erfüllt, ist es imho auch richtig gelöst.
 
Sehr schön!
Es funktioniert und ist deiner Meinung nach OK. Dann kann ich ja jetzt endlich mit dem "eigentlichen" Zeichenprogramm anfangen... Hoffentlich gibts da nicht solche Probleme 🙂
 
Wenn du willst, kannst du dir auch noch dieses Beispiel anschauen: Malen in Swing Teil 2: ein einfaches Malprogramm - Byte-Welt Wiki. Das Grundproblem ist dort etwas anders gelöst: wenn beim Vergrössern des Frames ein "leerer" Raum entsteht, wird in der Methode "resetImage()" die Methode Component#createImage(getWidth(), getHeight()) benutzt, um ein passendes Bild zu erzeugen. Wenn dagegen das Bild grösser ist als der Frame, erscheinen Scrollbars.
 
Thx für den Link.
Sieht interessant aus. Jetzt muss ich allerdings erstmal Mathe lernen 😉

Falls ich Probleme bei meinem Malprogramm haben sollte, melde ich mich wieder.

MfG
Simon
 
Ok... von mir aus... meine Methode skaliert... wenn's so in den Docs steht, wird's wohl stimmen.
Aber... Aber... Aber... mit der Breite und der Höhe des Bildes skaliert es 1:1 (also eben doch nicht!). Egal... viel Spass mit deiner "Code-Wurst" 😉.
 
Status
Nicht offen für weitere Antworten.

Neue Themen


Zurück
Oben