AffineTranformation Bilder verschwinden von Anzeige bei Pan, Zoom

simmerl74

Mitglied
Hallo,

versuche mittel eines Java-Programms Panorama-Bilder zu laden und anzuzeigen.
Bei der Anzeige soll ein Verschieben (Translation), ein Zoomen in beiden Achsen (Scale) und ein unabhängiges Stretchen der Bildbreite möglich sein.
Dies funktioniert auch soweit. Jedoch ab einer bestimmten Kombination aus Bildgröße und Zoomfakor verschwindet das Bild aus der Anzeige.
Verwendet man sehr sehr große Bilder werden die Bilddaten geladen aber kein Bild angezeigt.
Ich habe schon nach Beschränkungen bei Verwenden der AffinenTransformation gesucht aber denke da bewege ich mich noch im Rahmen.

Verwendetes Bild: Pano1.jpg
Zoom-Wert ab der das Bild verschindet 600% bei einem Stretch-Wert von 300%

Bei Bild Pano2.jpg wird gar kein Bild angezeigt.

Die Bilder:

Pano1.jpg

Pano2.jpg

Mein Eindruck ist dass es ein Problem gibt sobal einer der Werte >32768 ist klappt es nicht mehr wobei die Transformationsmatrix eigentlich als double deklariert ist.

[ sx 0 tx ]
[ 0 sy ty ]
[ 0 0 1 ]

Für eine Hilfe wäre ich sehr dankbar, da ich hier irgendwie nicht weiterkomme.
 

Anhänge

Dies funktioniert auch soweit. Jedoch ab einer bestimmten Kombination aus Bildgröße und Zoomfakor verschwindet das Bild aus der Anzeige.
Verwendet man sehr sehr große Bilder werden die Bilddaten geladen aber kein Bild angezeigt.
Ich habe schon nach Beschränkungen bei Verwenden der AffinenTransformation gesucht aber denke da bewege ich mich noch im Rahmen.
Bei mir funktioniert der Code. Ich denke dass es sich um ein Speicherproblem handelt.
Verwende ich eine 32 -Bit VM. Bekomme ich eine OutofMemory Exception beim 2ten Bild.
Ich habe deinen Code etwas überarbeitet, um ihn etwas übersichtlicher zu gestalten und lasse zusätzlich den freien Speicher vor dem Laden und nach dem Laden der Grafik anzuzeigen. Beim 1ten Bild geht sich bei mir der Speicher gerade noch aus. Das bedeutet ich kann maximal skalieren und zoomen.
[CODE lang="java" title="ImageZoomer"]import java.awt.BorderLayout;
import java.awt.Dimension;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class ImageZoomer {
public static boolean DEBUG = false;

final static String filename = "Pano2.jpg";

public static void main(String[] args) throws InterruptedException {
showMemory();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
final JFrame frame = new JFrame("PicturePanel");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
PicturePanel ucpanel = new PicturePanel(filename, 425);
frame.getContentPane().add(ucpanel, BorderLayout.CENTER);
frame.setSize(new Dimension(1920, 600));
frame.setVisible(true);
}
});
Thread.sleep(2000);
showMemory();
}

public static void showMemory() {
Runtime rt = Runtime.getRuntime();
long totalMem = rt.totalMemory();
long maxMem = rt.maxMemory();
long freeMem = rt.freeMemory();
double megs = 1048576.0;
System.out.println("------------------------------------------------");
System.out.println("Total Memory: " + totalMem + " (" + (totalMem / megs) + " MiB)");
System.out.println("Max Memory: " + maxMem + " (" + (maxMem / megs) + " MiB)");
System.out.println("Free Memory: " + freeMem + " (" + (freeMem / megs) + " MiB)");
}
}[/CODE]

[CODE lang="java" title="TransformParam Containerklasse mit Hilfsfunktionen"]import java.awt.Dimension;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;

public class TransformParam {
double translateX = 0;
double translateY = 0;
double scaleX = 1;
double scaleY = 1;
double stretchX = 1;
double prescaler = 1;

public void fitInRangeTranslateX(double min, double max) {
if (translateX < min)
translateX = min;
else if (translateX > max)
translateX = max;
}

public void fitInRangeTranslateY(double min, double max) {
if (translateY < min)
translateY = min;
else if (translateY > max)
translateY = max;
}

public AffineTransform getAffineTransFormParam(BufferedImage image, Dimension dim) {
AffineTransform at = new AffineTransform();
double t1x = -(double) image.getWidth(null) / 2 * scaleX * prescaler;
double t1y = -(double) image.getHeight(null) / 2 * scaleY * prescaler;
double sx = scaleX * prescaler;
double sy = scaleY * prescaler;
double t2x = (double) dim.width / 2 / scaleX / prescaler;
double t2y = (double) dim.height / 2 / scaleY / prescaler;

if (ImageZoomer.DEBUG)
System.out.println("T1X: " + t1x + "\tT1Y: " + t1y + "\tSX: " + sx + "\tSY: " + sy + "\tT2X: " + t2x
+ "\tT2Y: " + t2y);

// // the prescale, stretch_x and zoom transformation
at.setToIdentity();
at.translate(t1x, t1y);
at.scale(sx, sy);
at.translate(t2x, t2y);
// The panning transformation
at.translate(translateX, translateY);
return at;
}

public void reset() {
translateX = translateY = 0;
scaleX = scaleY = stretchX = prescaler = 1;
}

@Override
public String toString() {
return "translateX: " + translateX + "\ttranslateY: " + translateY + "\tstretchX: " + stretchX + "\tscaleX: "
+ scaleX + "\tscaleY: " + scaleY + "\tprescaler: " + prescaler;
}
}[/CODE]
[CODE lang="java" title="PicturePanel"]import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.JSlider;
import javax.swing.SwingConstants;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class PicturePanel extends JPanel {
private static final long serialVersionUID = 1L;
private static final int MIN_ZOOM = 100;
private static final int MAX_ZOOM = 1000;

private PanAndZoomCanvas canvas;
private JSlider zoomSlider;
private JSlider horizontalScaleSlider;
private int zoomPercent = 100;
private TransformParam param = new TransformParam();

public PicturePanel(String imagename, int height) {
setPreferredSize(new Dimension(1840, height));
setBackground(Color.DARK_GRAY);
createComponents(imagename, height);

JLabel zoomSliderLabel = new JLabel("Zoom", JLabel.CENTER);
zoomSliderLabel.setForeground(Color.white);
JLabel horizontalScaleSliderLabel = new JLabel("Stretch", JLabel.CENTER);
horizontalScaleSliderLabel.setForeground(Color.white);

JSeparator separator = new JSeparator(SwingConstants.VERTICAL);
Dimension sDimension = new Dimension(100, 0);
separator.setPreferredSize(sDimension);
separator.setMaximumSize(sDimension);

add(separator);
add(zoomSliderLabel);
add(zoomSlider, BorderLayout.NORTH);
add(horizontalScaleSliderLabel);
add(horizontalScaleSlider, BorderLayout.NORTH);
add(canvas, BorderLayout.SOUTH);
}

public void createComponents(String imagename, int height) {
PanningHandler panner = new PanningHandler();
canvas = new PanAndZoomCanvas(imagename, height);
canvas.addMouseListener(panner);
canvas.addMouseMotionListener(panner);
canvas.setBorder(BorderFactory.createLineBorder(Color.black));
canvas.addMouseWheelListener(new MouseWheelListener() {
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
int notches = e.getWheelRotation();
int zoomStep = 50;
// Don't scale below 100% and above 1000%
if (notches > 0 && zoomPercent == MIN_ZOOM || notches < 0 && zoomPercent == MAX_ZOOM)
return;
zoomStep = (zoomPercent >= 300 && notches != 0) ? 50 : 20;
zoomPercent += notches < 0 ? zoomStep : -zoomStep;
zoomSlider.setValue(zoomPercent);
}
});

// code for handling zooming
zoomSlider = new JSlider(JSlider.HORIZONTAL, MIN_ZOOM, MAX_ZOOM, MIN_ZOOM);
zoomSlider.setMajorTickSpacing(200);
zoomSlider.setMinorTickSpacing(100);
zoomSlider.setPaintTicks(true);
zoomSlider.setPaintLabels(true);
zoomSlider.setToolTipText("Zoom");
zoomSlider.addChangeListener(new ScaleHandler());
zoomSlider.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) {
zoomSlider.setValue(100);
param.translateX = 0;
param.translateY = 0;
param.scaleX = 1;
param.scaleY = 1;
repaint();
}
}
});

// Slider for horizontal scaling
horizontalScaleSlider = new JSlider(JSlider.HORIZONTAL, 0, 500, 100);
horizontalScaleSlider.setMajorTickSpacing(100);
horizontalScaleSlider.setMinorTickSpacing(50);
horizontalScaleSlider.setPaintTicks(true);
horizontalScaleSlider.setPaintLabels(true);
horizontalScaleSlider.setToolTipText("Horizontal Stretch");
horizontalScaleSlider.addChangeListener(new HorizontalScaleHandler());
horizontalScaleSlider.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) {
horizontalScaleSlider.setValue(100);
}
}
});
}

public double getStretch_X() {
return param.stretchX;
}

public double getTranslateX() {
return param.translateX;
}

public double getTranslateY() {
return param.translateY;
}

public int getZoomPercent() {
return zoomPercent;
}

public void setStretch_X(double value) {
param.scaleX = value;
horizontalScaleSlider.setValue((int) (param.stretchX * 100));
repaint();
}

public void setTranslateX(double value) {
param.translateX = value;
this.repaint();
}

public void setTranslateY(double value) {
param.translateY = value;
this.repaint();
}

public void setZoomPercent(int value) {
zoomPercent = value;
zoomSlider.setValue(zoomPercent);
this.repaint();
}

class PanAndZoomCanvas extends JComponent {
public final int MAX_IMG_HEIGHT;
private static final long serialVersionUID = 1L;
private String ucImageURL;
private BufferedImage image = null;

PanAndZoomCanvas(String ucimagename, int height) {
MAX_IMG_HEIGHT = height;
param.reset();
ucImageURL = ucimagename;
}

@Override
public Dimension getPreferredSize() {
return getParent().getPreferredSize();
}

public boolean loadImage() {
if (ucImageURL == null || ucImageURL.length() < 2) // invalid URL
return false;
try {
image = ImageIO.read(new File(ucImageURL));
if (ImageZoomer.DEBUG && image != null)
System.out.println("Loaded new image: " + ucImageURL + "\tWidth: " + image.getWidth() + "\tHeight: "
+ image.getHeight());
} catch (IOException e) {
System.out.println("Unable to load file: " + ucImageURL.toString());
return false;
}
return true;
}

public void paintComponent(Graphics g) {
super.paintComponent(g);
if (image == null && !loadImage())
return;
Graphics2D g2D = (Graphics2D) g;
g2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2D.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);

double translate_x_max = image.getWidth(null) / 2;
double translate_x_min = -image.getWidth(null) / 2;
double translate_y_max = image.getHeight(null) / 2;
double translate_y_min = -image.getHeight(null) / 2;
param.fitInRangeTranslateX(translate_x_min, translate_x_max);
param.fitInRangeTranslateY(translate_y_min, translate_y_max);
param.prescaler = (double) MAX_IMG_HEIGHT / (double) image.getHeight(null);

if (ImageZoomer.DEBUG)
System.out.println("Panel width: " + getWidth() + "\tHeight: " + getHeight());

// // the prescale, stretch_x and zoom transformation
AffineTransform at = param.getAffineTransFormParam(image,
new Dimension((int) getPreferredSize().getWidth(), MAX_IMG_HEIGHT));
if (ImageZoomer.DEBUG) {
System.out.println(param);
System.out.println("Transformation: " + at.toString());
}
g2D.drawImage(image, at, null);
g2D.dispose();
}
}

class PanningHandler extends MouseAdapter {
private int x;
private int y;

@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) {
param.reset();
zoomSlider.setValue(100);
horizontalScaleSlider.setValue(100);
canvas.repaint();
}
}

@Override
public void mouseDragged(MouseEvent e) {
// the size of the pan translations
// are defined by the current mouse location subtracted
// from the reference location
int deltaX = e.getX() - x;
int deltaY = e.getY() - y;

// make the reference point be the new mouse point.
param.translateX += deltaX / Math.max(0.00001, (param.scaleX * param.prescaler));
param.translateY += deltaY / Math.max(0.00001, (param.scaleY * param.prescaler));
x = e.getX();
y = e.getY();
canvas.repaint();
}

@Override
public void mousePressed(MouseEvent e) {
// capture the starting point
x = e.getX();
y = e.getY();
}
}

class ScaleHandler implements ChangeListener {
public void stateChanged(ChangeEvent e) {
JSlider slider = (JSlider) e.getSource();
zoomPercent = slider.getValue();

// make sure zoom never gets to actual 0, or else the objects will
// disappear and the matrix will be non-invertible.
param.scaleY = Math.max(0.00001, zoomPercent) / 100.0;
param.scaleX = param.scaleY * param.stretchX;
canvas.repaint();
}
}

class HorizontalScaleHandler implements ChangeListener {
public void stateChanged(ChangeEvent e) {
JSlider slider = (JSlider) e.getSource();
double horizontalScalePercent = slider.getValue();
// make sure zoom never gets to actual 0, or else the objects will
// disappear and the matrix will be non-invertible.
param.stretchX = Math.max(0.15, horizontalScalePercent / 100.0);
param.scaleY = Math.max(0.00001, zoomPercent) / 100.0;
param.scaleX = param.scaleY * param.stretchX;
canvas.repaint();
}
}
}[/CODE]
 
Hallo Blender3D , vielen Dank für deine Antwort.

Ich habe nun auf verschiedenen Rechnern mit unterscheidlichen Betriebssystemen und Java VMs nochmals getestet und siehe da unter 32bit (java-1.7.0-openjdk-1.7.0) funktioniert alles einwandfrei. Versuche ich den gleichen Code unter (java-11-openjdk-amd64) laufen zu lassen treten dann massive Probleme auf: "Pano2.jpg" wird gar nicht angezeigt und bei "Pano1.jpg" gibt es Probleme beim Zoomen.

Dass dir der Speicher zu wenig wir hängt wohl mit den Einstellungen der VM zusammen. Hier je nach verfügbarem physischen Speicher einfach mal mittels VW arguments etwas mehr Speicher zuweisen:

-Xms1g -Xmx2g

Habe jetzt auch auf dem 32bit Rechner festegestellt unter Java 7 läuft es unter Java 8 und Java 11 gibt es Fehlfunktionen.
Welche Java Runtime verwendest du?
 
Dass dir der Speicher zu wenig wir hängt wohl mit den Einstellungen der VM zusammen. Hier je nach verfügbarem physischen Speicher einfach mal mittels VW arguments etwas mehr Speicher zuweisen:

-Xms1g -Xmx2g

Habe jetzt auch auf dem 32bit Rechner festegestellt unter Java 7 läuft es unter Java 8 und Java 11 gibt es Fehlfunktionen.
Welche Java Runtime verwendest du?
Es handelt sich wahrscheinlich um ein Problem der benutzten VM. Trotz genug Heap wirft es beim Laden OutOfMemory bei Pano2.jpg
Wahrscheinlich ist die 39485 Pixel Weite des Bildes das Problem. Pano1.jpg hat nur 15794 Pixel und benötigt insgesamt mehr Speicher als Pano2.jpg. Mit mehr Heapspeicher hätte ich genug Speicher für das Bild zur Verfügung es liegt meines Erachtens an der zu Großen Pixelzahl der Weite bei der jeweiligen benutzten VM.
Ich verwende
1627039794057.png
Ich hoffe der überarbeitete Code von mir hilft dir weiter.
 
Interessant ist ja dass dies bei Verwendung von java-1.7.0-openjdk ohne Probleme läuft sowohl das Zoomproblem bei Pano1 ist da verschwunden und auch das Pano2 wird angezeigt und lässt sich zoomen.

Denke das ist ein Java BUG
 
Vielen Dank. Es scheint von der VM abzuhängen ob es funktioniert oder nicht.
Ich habe mich dazu entschlossen das ganze umzubauen und mit Subimages zu arbeiten (was auch die Performance noch steigert)
 

Zurück
Oben