Swing Zoom-Vorgang optimieren [BufferedImage]

Status
Nicht offen für weitere Antworten.

Dragonfire

Bekanntes Mitglied
Ich hab mich heute mal an die Arbeit gemacht und
eine etwas ältere Komponente von mir überarbeitet.

Es geht um ein JPanel
auf dem man ein Hintergrundbild setzt und ein weiteres Bild,
welches man zoomen kann.

Hatte mir damals sehr viele Probleme bereitet:

http://www.java-forum.org/awt-swing-swt/81823-zoom-jpanel-repaint.html
http://www.java-forum.org/awt-swing-swt/80224-zoomimage-flackert-fehler-repaint-aufruf-geloest.html


Heute habe ich mich mal ein wenig durch das Internet geforstet und den Zoom-Vorgang optimiert,
hauptsächliche Neuerung ist die überarbeitete paintComponent-Methode ("On-The-Fly Scaling):

The Perils of Image.getScaledInstance() | Java.net


Gibt es vielleicht noch andere Ansätze die mein Vorhaben eleganter (wollte nicht "schneller", "besser" schreiben ^^) lösen?
Würde mich über Anregungen freuen :)

Hier mal meine Klasse:

Java:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;

import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class ZoomImage5 extends JComponent {
	private static final long serialVersionUID = 1L;

	private int startX;
	private int startY;
	private BufferedImage zoomImg;
	private BufferedImage bakImg;
	private int quality;
	private int pixel;
	private Image currentImage;

	// for painting
	private boolean zoom;
	private int tmpWidth;
	private int tmpHeight;

	// zoomMax
	private int maxWidth;
	private int maxHeight;

	public ZoomImage5() {

		this.startX = 0;
		this.startY = 0;
		this.quality = Image.SCALE_AREA_AVERAGING;
		this.pixel = 2;
		this.zoom = false;
		initCurrentImage();
	}

	public void setBakImg(BufferedImage bakImg) {
		this.bakImg = bakImg;
		if (this.bakImg != null) {
			this.setPreferredSize(new Dimension(this.bakImg.getWidth(),
					this.bakImg.getHeight()));
		}
	}

	public void setImg(BufferedImage zoomImage, int startX, int startY,
			int startWidth, int startHeight) {
		this.zoomImg = zoomImage;
		this.startX = startX;
		this.startY = startY;
		this.tmpWidth = startWidth;
		this.tmpHeight = startHeight;
		initCurrentImage();
	}

	/**
	 * Zommt auf dem Panel.
	 * 
	 * @param modus
	 *            0 = nach rechts, unten; 1 = nach links, unten 2 = nach rechts,
	 *            oben; 3 = nach links, oben
	 */
	public void zoomImage(int modus) {
		if (this.zoomImg != null) {

			this.maxWidth = this.zoomImg.getWidth();
			this.maxHeight = this.zoomImg.getHeight();

			// wenn zu breit
			if (maxWidth > this.bakImg.getWidth()) {
				this.maxHeight = (this.maxHeight * this.bakImg.getWidth())
						/ this.maxWidth;
				this.maxWidth = this.bakImg.getWidth();
			}
			// wenn zu hoch
			if (maxHeight > this.bakImg.getHeight()) {
				this.maxWidth = (this.maxWidth * this.bakImg.getHeight())
						/ this.maxHeight;
				this.maxHeight = this.bakImg.getHeight();
			}
			myZoom(modus);
		}
	}

	/**
	 * modifizierte "paint"-Methode, zeichnet das jeweilige Hintergrundbild und
	 * das aktuelle Bild.
	 */

	@Override
	public void paintComponent(Graphics g) {
		super.paintComponent(g);
		if (zoom) {
			Graphics2D g2 = (Graphics2D) g;
			g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
					RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
			g2.setRenderingHint(RenderingHints.KEY_RENDERING,
					RenderingHints.VALUE_RENDER_SPEED);

			g2.drawImage(this.bakImg, 0, 0, null);
			g2.drawImage(this.currentImage, startX, startY, tmpWidth,
					tmpHeight, null);
		} else {
			g.drawImage(this.bakImg, 0, 0, null);
			g.drawImage(this.currentImage, startX, startY, null);
		}
	}

	/**
	 * interne zoom-Methode.
	 */
	private void myZoom(int modus) {
		new zoomThread(modus).start();
	}

	private void initCurrentImage() {
		if (this.zoomImg != null) {
			this.currentImage = this.zoomImg.getScaledInstance(this.tmpWidth,
					this.tmpHeight, this.quality);
		}
	}

	private void startXYfix(int modus) {
		switch (modus) {
		case 1: {
			this.startX -= tmpWidth;
			break;
		}
		case 2: {
			this.startY -= tmpHeight;
			break;
		}
		case 3: {
			this.startX -= tmpWidth;
			this.startY -= tmpHeight;
			break;
		}
		}
	}

	private void changeXY(int modus, int diffH, int diffW) {
		if (modus <= 0) {
			// zoome nach rechts unten
			// nichts machen
		} else if (modus <= 1) {
			// zoome nach links unten
			this.startX -= pixel;
		} else if (modus <= 2) {
			// zoome nach rechts oben
			this.startY -= diffH;
		} else {
			// zoome nach links oben
			this.startX -= diffW;
			this.startY -= diffH;
		}
	}

	// Thread für den ZoomVorgang
	private class zoomThread extends Thread {
		private int modus;

		public zoomThread(int modus) {
			this.modus = modus;
		}

		@Override
		public void run() {
			zoom = true;

			try {
				SwingUtilities.invokeAndWait(new Runnable() {

					@Override
					public void run() {
						repaint();
					}
				});
			} catch (Exception e) {
				e.printStackTrace();
			}
			startXYfix(modus);

			int clearH = zoomImg.getHeight();
			int clearW = zoomImg.getWidth();
			int lastH = 0;
			int lastW = 0;
			int iterations = (maxWidth - tmpWidth) / pixel;

			// zeichnen sämtlicher Bilder (Zoomvorgang)
			for (int i = 0; i < iterations; i++) {
				lastH = tmpHeight;
				lastW = tmpWidth;
				if (maxHeight > maxWidth) {
					tmpHeight += pixel;
					tmpWidth = (tmpHeight * clearW) / clearH;

				} else {
					tmpWidth += pixel;
					tmpHeight = (tmpWidth * clearH) / clearW;
				}

				try {
					Thread.sleep(0, 1);
				} catch (InterruptedException e1) {
					e1.printStackTrace();
				}

				changeXY(modus, tmpHeight - lastH, tmpWidth - lastW);

				try {
					SwingUtilities.invokeLater(new Runnable() {

						@Override
						public void run() {
							repaint(startX, startY, tmpWidth, tmpHeight);
						}
					});
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			currentImage = zoomImg.getScaledInstance(tmpWidth, tmpHeight,
					quality);
			try {
				SwingUtilities.invokeAndWait(new Runnable() {

					@Override
					public void run() {
						repaint();
					}
				});
			} catch (Exception e) {
				e.printStackTrace();
			}
			zoom = false;
		}
	}
}

Nachtrag:

Hab jetzt mal vom JComponent erben lassen,
da ich Funktionen vom JPanel nicht mehr brauche ...
 
Zuletzt bearbeitet:

André Uhres

Top Contributor
Daß "getScaledInstance" suboptimal ist, müsstest du ja nach dem Lesen des angegebenen Artikels wissen. Die beiden repaint() (ohne Argumente) sind imho überflüssig. Ebenso der sleep: der bewirkt nur, dass der RepaintManager nicht optimieren kann (es kann aber auch sein, daß ich nicht richtig verstanden habe, was du machen willst)! Ausserdem brauchen wir beim Malen vom "bakImg" nur den Teil des Bildes neu zu malen, der sich mit dem Cliprechteck überschneidet:
Java:
...
public class ZoomImage5 extends JPanel {
...
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        Rectangle r = g.getClipBounds();
        if (zoom) {
            Graphics2D g2 = (Graphics2D) g;
            g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                    RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
            g2.setRenderingHint(RenderingHints.KEY_RENDERING,
                    RenderingHints.VALUE_RENDER_SPEED);
            g2.drawImage(bakImg, r.x, r.y, r.width + r.x, r.height + r.y, r.x,
                    r.y, r.width + r.x, r.height + r.y, null);
            g2.drawImage(this.currentImage, startX, startY, tmpWidth,
                    tmpHeight, null);
        } else {
            g.drawImage(bakImg, r.x, r.y, r.width + r.x, r.height + r.y, r.x, 
                    r.y, r.width + r.x, r.height + r.y, null);
            g.drawImage(this.currentImage, startX, startY, null);
        }
    }
...
    private class zoomThread extends Thread {
        private int modus;
        public zoomThread(int modus) {
            this.modus = modus;
        }
        @Override
        public void run() {
            zoom = true;
            startXYfix(modus);
            int clearH = zoomImg.getHeight();
            int clearW = zoomImg.getWidth();
            int lastH = 0;
            int lastW = 0;
            int iterations = (maxWidth - tmpWidth) / pixel;
            // zeichnen sämtlicher Bilder (Zoomvorgang)
            for (int i = 0; i < iterations; i++) {
                lastH = tmpHeight;
                lastW = tmpWidth;
                if (maxHeight > maxWidth) {
                    tmpHeight += pixel;
                    tmpWidth = (tmpHeight * clearW) / clearH;
                } else {
                    tmpWidth += pixel;
                    tmpHeight = (tmpWidth * clearH) / clearW;
                }
                changeXY(modus, tmpHeight - lastH, tmpWidth - lastW);
                try {
                    SwingUtilities.invokeLater(new Runnable() {

                        @Override
                        public void run() {
                            repaint(startX, startY, tmpWidth, tmpHeight);
                        }
                    });
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            currentImage = zoomImg.getScaledInstance(tmpWidth, tmpHeight, quality);
            zoom = false;
        }
    }
}
 

Dragonfire

Bekanntes Mitglied
Auf der Optimierung hätte ich selbst kommen können ^^
"getScaledInstance" habe ich genommen um das letzte Bild in der besten Qualität anzuzeigen,
deswegen auch der repaint, aber jetzt mit Parameter :)
Den sleep habe ich eingebaut
damit der User auch etwas von dem zoom-Vorgang sieht ...

Java:
@Override
	public void paintComponent(Graphics g) {
		super.paintComponent(g);
		Rectangle r = g.getClipBounds();
		if (zoom) {
			Graphics2D g2 = (Graphics2D) g;
			g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
					RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
			g2.setRenderingHint(RenderingHints.KEY_RENDERING,
					RenderingHints.VALUE_RENDER_SPEED);
			g2.drawImage(this.bakImg, r.x, r.y, r.width + r.x, r.height + r.y,
					r.x, r.y, r.width + r.x, r.height + r.y, null);
			g2.drawImage(this.currentImage, startX, startY, tmpWidth,
					tmpHeight, null);
		} else {
			g.drawImage(this.bakImg, r.x, r.y, r.width + r.x, r.height + r.y,
					r.x, r.y, r.width + r.x, r.height + r.y, null);
			g.drawImage(this.currentImage, startX, startY, null);
		}
	}

...

// Thread für den ZoomVorgang
	private class zoomThread extends Thread {
		private int modus;

		public zoomThread(int modus) {
			this.modus = modus;
		}

		@Override
		public void run() {
			zoom = true;
			startXYfix(modus);

			int clearH = zoomImg.getHeight();
			int clearW = zoomImg.getWidth();
			int lastH = 0;
			int lastW = 0;
			int iterations = (maxWidth - tmpWidth) / pixel;

			// zeichnen sämtlicher Bilder (Zoomvorgang)
			for (int i = 0; i < iterations; i++) {
				lastH = tmpHeight;
				lastW = tmpWidth;
				if (maxHeight > maxWidth) {
					tmpHeight += pixel;
					tmpWidth = (tmpHeight * clearW) / clearH;

				} else {
					tmpWidth += pixel;
					tmpHeight = (tmpWidth * clearH) / clearW;
				}

				try {
					Thread.sleep(0, 1);
				} catch (InterruptedException e1) {
					e1.printStackTrace();
				}

				changeXY(modus, tmpHeight - lastH, tmpWidth - lastW);

				try {
					SwingUtilities.invokeLater(new Runnable() {

						@Override
						public void run() {
							repaint(startX, startY, tmpWidth, tmpHeight);
						}
					});
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			currentImage = zoomImg.getScaledInstance(tmpWidth, tmpHeight,
					quality);
			zoom = false;
			try {
				SwingUtilities.invokeAndWait(new Runnable() {

					@Override
					public void run() {
						repaint(startX, startY, tmpWidth, tmpHeight);
					}
				});
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
 
Status
Nicht offen für weitere Antworten.

Ähnliche Java Themen

Neue Themen


Oben