# JViewPort Ausschnitt skalieren



## Senda (10. Mai 2014)

Guten Tag,

ich in aktuell dabei ein Spiel zu Programmieren. Dazu habe ich ein JPanel erstellt in der Größe 5760x2160 in der befinden sich z.B. ein Hintergrundbild, Grafiken und dort sind auch Methoden, die das Spiel betreffen. Zum Beispiel kann eine Spielfigur (PNG Bild) über das Spielfeld bewegt werden. Das JPanel soll so gelassen werden, da sich Berechnung anhand der Größe errechnen.

Je nach "Spielgeschehen" soll ein bestimmter Auschnitt des JPanels gezeigt werden. Zum Beispiel wenn sich eine Spielfigur nach links bewegt soll der Ausschnitt auch nach links wandern. Die Höhe des Ausschnitts ist immer 1080 Pixel und die Breite richtet sich nach dem Bildschirmformat. Also z.B. bei 16:9 ist die Breite 1920 Pixel.

Dies habe ich aktuell mit einem JViewport realisiert, welches in einem JFrame angezeigt wird. Über setView habe ich das JPanel übergeben, mit setExtentSize die Größe des Ausschnitts gewählt und über setViewPosition die Position des Ausschnittes.


Nun habe ich jedoch ein Problem. Wenn das JFrame nun kleiner ist als 1920x1080, dann müsste das was im JViewPort angezeigt wird dementsprechend runterskaliert werden. Also nicht die Größe des Ausschnittes vom JPanel reduzieren, sondern das was "in JViewPort ist" muss auf die Größe des JFrames runterskaliert werden. 

Wie bekomme ich dies realisiert?
Würde dies mit JScrollPane gehen (wenn ja, wie?). Hatte mich für JViewPort entschieden, da der Benutzer ja nicht scrollen soll, sondern der Ausschnitt durch das Spielgeschehen bestimmt wird.

Schon mal vorab, vielen Dank für die Hilfe


----------



## kaoZ (11. Mai 2014)

schau dir doch mal die methode setViewPort() der JScrollPane an .


----------



## Senda (11. Mai 2014)

Also ich verstehe jetzt nicht genau wie mir das weiterhelfen soll.
Die Darstellung klappt ja bereits. JViewport nimmt sich den gewünschten 1920 x 1080 Ausschnit aus dem JPanel. Jedoch muss dieser ja noch auf die JFrame größe runter skaliert werden können. Wie soll das funktionieren  ?


----------



## kaoZ (11. Mai 2014)

Du solltest du in der ScrollPane den gewünschten bereich darstellen können ( x,y koordinaten ) wenn du die Grafik natürlich noch runterskalieren möchtest,

versuchs mal mit hiermit:

*BufferedImage.getScaledInstance();*


ansonsten hab ich auf die Schnelle das hier gefunden :

Image Scalling Java


```
public class ImageScaler {

    public BufferedImage scaleImage(BufferedImage img, Dimension d) {
        img = scaleByHalf(img, d);
        img = scaleExact(img, d);
        return img;
    }

    private BufferedImage scaleByHalf(BufferedImage img, Dimension d) {
        int w = img.getWidth();
        int h = img.getHeight();
        float factor = getBinFactor(w, h, d);

        // make new size
        w *= factor;
        h *= factor;
        BufferedImage scaled = new BufferedImage(w, h,
                BufferedImage.TYPE_INT_RGB);
        Graphics2D g = scaled.createGraphics();
        g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
        g.drawImage(img, 0, 0, w, h, null);
        g.dispose();
        return scaled;
    }

    private BufferedImage scaleExact(BufferedImage img, Dimension d) {
        float factor = getFactor(img.getWidth(), img.getHeight(), d);

        // create the image
        int w = (int) (img.getWidth() * factor);
        int h = (int) (img.getHeight() * factor);
        BufferedImage scaled = new BufferedImage(w, h,
                BufferedImage.TYPE_INT_RGB);

        Graphics2D g = scaled.createGraphics();
        g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g.drawImage(img, 0, 0, w, h, null);
        g.dispose();
        return scaled;
    }

    float getBinFactor(int width, int height, Dimension dim) {
        float factor = 1;
        float target = getFactor(width, height, dim);
        if (target <= 1) { while (factor / 2 > target) { factor /= 2; }
        } else { while (factor * 2 < target) { factor *= 2; }         }
        return factor;
    }

    float getFactor(int width, int height, Dimension dim) {
        float sx = dim.width / (float) width;
        float sy = dim.height / (float) height;
        return Math.min(sx, sy);
    }
}
```


----------



## Senda (11. Mai 2014)

Also irgendwie stehe ich glaube ich mal wieder auf dem Schlauch sry .
Also ich habe hier meine Klasse panView die erbt von JViewPort und bekommt die Klasse panGame (erbt von JPanel übergeben). panGame enthält mehrere Grafiken und hat eine größe von 5760x2180.
Es wird sich bei der oberen linken Pixel bei 1920.1080 das 1920x1080 große Feld genommen und in JViewPort genommen. Was muss ich nun genau machen um dieses auf einem JFrame darzustellen, dass nur 60% der Größe hat, denn panGame soll unverändert bleiben und wie kann ich dann nun in JViewPort mit BufferedImage arbeiten?


```
public class PanView extends JViewport {
    private PanGame panGame;

    public PanView(){
        panGame = new PanGame();


        setLayout(null);
        setView(panGame);
        setExtentSize(new Dimension(1920, 1080)); // Größe des Ausschnittes der gezeigt wird
        setViewPosition(new Point(1920, 1080));

    }

}
```


----------



## kaoZ (11. Mai 2014)

> panGame enthält mehrere Grafiken und hat eine größe von 5760x2180.
> Es wird sich bei der oberen linken Pixel bei 1920.1080 das 1920x1080 große Feld genommen und in JViewPort genommen. Was muss ich nun genau machen um dieses auf einem JFrame darzustellen, dass nur 60% der Größe hat,



die Grafiken Skalieren ?!

Wie schon gesagt wenn du Grafiken hast die Größer sind als der Bereich um diese Darzustellen, schriebst du entweder methoden oder eine UtilityKlasse welche die deine Grafiken skaliert in einer von dir bevorzugten Größe zurückgibt, oder du Verwendest für deine Grafiken ein BufferedImage welches dir es ermöglicht über die Methode .getScaledInstance(); eine Kopie des Bildes in einer von dir gewünschten Größe zu erhalten .

Hier zum Beispiel ist die eigentliche Grafik 1920x1080

runterskalliert zu 400 x 300 , sicher passen die Proportionen nichtmehr, und hier wird schnell skaliert , also leidet die auflösung....

Setz doch einfach mal einer deiner Grafiken hier ein , dann siehtst du was ich meine 



```
public class Foo extends JPanel{

	BufferedImage img;
	
	public Foo() throws IOException {
		
		/**
		 * Die Originalgrafik ist 1920 x 1080 und wird hier runterskaliert auf 400 x 300 px 
		 */
		
		img = ImageIO.read(new File("F:/Bilder/Dodge.png"));
		Image newImg = img.getScaledInstance(400, 300, BufferedImage.SCALE_FAST);
		
		this.add(new JLabel(new ImageIcon(newImg)));
	}
	
	public static void main(String[] args) throws IOException {
		JFrame f = new JFrame();
		
		f.setSize(600,600);
		f.setLocationRelativeTo(null);
		f.add(new Foo());
		
		f.setVisible(true);
	}
}
```


----------



## Senda (11. Mai 2014)

Das Problem was ich habe ist, dass die Grafiken in panGame nicht skaliert werden dürfen , da für Berechnung für das Spielgeschehen das Koordinatensystem, das durch die Hintergrundgrafik (deren Größe) bestimmt wird, entscheidend sind. Es muss also sozuagen ein Ausschnitt (feste Größe) der in panView gewählt wird runterskaliert werden damit er in das JFrame reinpasst aber dabei darf sich in panGame nicht die Größe der Grafiken ändern, da sonst die Spielberechnungen falsch sind. Hoffe du verstehst was ich meine.


----------



## kaoZ (11. Mai 2014)

Ich fasse schnell zusammen , 

-Du hast Grafiken für ein Game welche Größer sind als der ViewPort bzw. der Panel in welchem sie dargestellt werden.

-Du darfst diese Grafiken nicht skalieren da sie zur Berechnung der Koordinaten dienen

Ok, also hast du nur die möglichkeit den ViewPort entweder durch eine ScrollPane scrollbar zu machen , was hier für spiele denke ich die Falsche Lösung wäre , oder den Frame oder das Panel welches als Container für dein ViewPort dienen dementsprechend groß zu gestalten das der inhalt den du sichtbar machen möchstest auch sichtbar ist .

*Bzw. willst du nur einen bestimmten Bereich der eigentlichen Grafik anzeigen , also ein Ausschnitt  wie hier im bild :*








Hab ich das soweit nun richtig verstanden ?

[EDIT]also sowas in der Art ? Hier sehr sehr stark vereinfacht xD [/EDIT]


```
public class Foo extends JViewport{
	
	public Foo() throws IOException {
		
		JPanel panel = new JPanel(new BorderLayout());
		panel.setPreferredSize(new Dimension(400,200));
		panel.add(new JLabel(new ImageIcon(ImageIO.read(new File("F:/Bilder/Dodge.png")))));
		
		setSize(100,200);
		setView(panel);
		setViewPosition(new Point(20,10));
	}
	
	public static void main(String[] args) throws IOException {
		JFrame f = new JFrame();
		
		f.setBounds(200,400,600,600);
		f.setLayout(null);
		f.setLocationRelativeTo(null);
		f.add(new Foo());
		
		f.setVisible(true);
	}
}
```

Der eigentliche Panel ist hier 400x200 px , der Viewport stellt aber nur 100x200px zur verfügung, die Grafik ist unverändert in ihrer Größe


----------



## Senda (11. Mai 2014)

Jop, hast du richtig verstanden.

Die Option, dass ich mein JFrame dementsprechend groß mache klappt aber leider auch nicht. Weil wenn ich zum Beispiel nen Bildschirm habe der nur 1280x720 groß ist, dann könnte der 1920x1080 große Spielbereich ja niemals dargestellt werden.

Habe hier mal ne Zeichnung mit Pain gemacht. Ich hoffe es ist verständlich


----------



## Senda (11. Mai 2014)

Kurze Erklärung zum oberen Bild.

Das JPanael ist mein Spielfeld, davon wird je nach Spielgeschehen ein bestimmter Ausschnitt mit JViewPort gezeigt. Dieser Ausschnitt hat eine feste Größe, hier jetzt 1920x1080.

Dieser Ausschnitt in JViewPort muss nur für kleinere Bildschirme runterskaliert werden können, damit er auch vollständig im JFrame angezeigt werden kann


----------



## kaoZ (11. Mai 2014)

Das Problem ist das du nicht den JViewPort skalieren musst sondern das was der JViewPort darstellt, und das ist in deinem Fall nun mal die Grafik.

Klar kannst du den ViewPort dazu bringen nur einen Bestimmten ausschnitt anzuzeigen, dennoch müsstest du wenn du verschiedene Auflösungen verwenden möchtest die genutzte Grafik dementsprechend runterskalieren, 

ich bin mir nicht ganz sicher aber ich denke mal du wirst nicht drum herum kommen die Koordinaten dementsprechend der skalierten Grafik anzupassen bzw. umzurechnen, ich wüsste zumindest jetzt auf die schnelle keine Möglichkeit anhand einer skalierten Grafik, die Originalgröße der Grafik zum berechnen der Koordinaten zu verwenden.

Grafik 5760 x 2160   - Spielwelt

der Nutzer sieht 1920 x 1080 - Vollbild

Du meinst, was passiert nun wenn der Nutzer eine geringe Auflösung nutzt richtig ?

Und diese soll dann automatisch dein ViewPort und die dahinter liegende Grafik dementsprechend anpassen , wie in deinem Beispiel gezeigt , richtig ?


----------



## Senda (11. Mai 2014)

Genau, weil aktuell wird dann einfach der obere linke Teilblock (1280x720) von meinem 1920x1080 großen Spielfeld angezeigt.


Hm also Spielfeld zu ändern wäre echt die schlechteste Möglichkeit, weil dadurch müssten alle Berechnungen für das setzten von Figuren, Bewegungen die Physik abhängig sind und so weiter abgeändert werden.

Gibt es nicht irgendeine Alternative. z.B. das aus der Spielewelt Bild erzeugt wird, dass alles darstellt, und dieses könnte ich dann runterskalieren und den entsprechenden Ausschnitt anzeigen oder  so.


----------



## kaoZ (11. Mai 2014)

Du darfst nicht vergessen du musst auch die Figur selbst etc. runterskallieren , sonst hast du nachher Godzilla der durch ne Miniaturstadt rennt 

du musst die Berechnungen nicht ändern sondern lediglich Methoden schreiben die je nach Auflösung die Koordinaten umrechnen, und der Skallierten Grafik anpassen .


----------



## Senda (11. Mai 2014)

Hm dann werd ich mich die Woche mal da dran setzten, hatte eigentlich gehofft es gibt ne schönere Methode aber bleibt wohl keine andere Wahl.

Vielen Dank für deine Hilfe


----------

