JFrame - Größe falsch , führt zu Render Fehlern?

Hallo Community,

Ich arbeite jetzt seit einiger Zeit an einem Projekt mit dem Ziel, meine Java - Kenntnisse zu verbessern.
Ich habe schon erfolgreich eine .jar Datei zum laufen gebracht, allerdings ist bei meinem Rendercode irgendetwas anscheinend “falsch”…

Erstens ist der eigentliche Bildschirm insgesamt 1290 x 730 Pixel groß anstatt die gewohnten 1280 x 720 , und man kann in regelmäßigen Abständen “doppelte” pixel erkennen, das bedeutet zwei Reihen von Pixeln, die den gleichen Wert haben.

Ich habe die .jar datei im Anhang hochgeladen, schaut es euch selber an. Den kompletten SourceCode habe ich auch gepackt im Anhang (WARNUNG: Leute mit Epilepsie sollten die .jar nicht ausführen!), die eigentlichen java Klassen um die es sich handeln sollte sind hier:

Main.java
[spoiler]```package com.jayanwarden.kannnix2;

import java.awt.Canvas;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;

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

import com.jayanwarden.kannnix2.Debug.PerformanceMonitor;
import com.jayanwarden.kannnix2.Graphics.Graphic;

public class Main extends Canvas implements Runnable{
private static final long serialVersionUID = 1L;

private static final String TITLE = "KannNix2";
private Thread thread;
private PerformanceMonitor perfmon;
private Graphic graphic;

private BufferedImage img;

private boolean running;

private int fps;
private int[] pixels;
public static int width;
public static int height;

public Main() {
    running = false;
    
    perfmon = new PerformanceMonitor();
    
    fps = 0;
    width = 1280;
    height = 720;
    
    init();
}

private void start() {
    if (running) {
        return;
    }
    running = true;
    thread = new Thread(this);
    thread.start();
}

private void stop() {
    if (!running)
        return;
    running = false;
    try {
        thread.join();
    } catch (Exception e) {
        e.printStackTrace();
        System.exit(0);
    }
}

public void run() {
    int frames = 0;
    double unprocessedSeconds = 0;
    long previousTime = System.nanoTime();
    double secondsPerTick = 1 / 60.0;
    int tickCount = 0;
    boolean ticked = false;
    
    while (running) {
        long currentTime = System.nanoTime();
        long passedTime = currentTime - previousTime;
        previousTime = currentTime;
        unprocessedSeconds += passedTime / 1000000000.0;
        requestFocus();
        
        while (unprocessedSeconds > secondsPerTick) {
            tick();
            unprocessedSeconds -= secondsPerTick;
            ticked = true;
            tickCount++;
            if (tickCount % 60 == 0) {
                System.out.println(frames + "fps | " + perfmon.usedMem() + "|" + perfmon.freeMem() + "|" + perfmon.allocMem() + "|" + perfmon.maxMem());
                fps = frames;
                previousTime += 1000;
                frames = 0;
            }
        }
        if (ticked) {
            render();
            frames++;
        }
        //render();
        //frames++;
    }
}

private void tick() {
    
}

private void render() {
    BufferStrategy bs = this.getBufferStrategy();
    if (bs == null) {
        createBufferStrategy(3);
        return;
    }
    
    graphic.render(bs);
    
    for (int i = 0; i < width * height; i++) {
        pixels** = Graphic.pixels**;
    }
    
    Graphics g = bs.getDrawGraphics();
    g.drawImage(img, 0, 0, width + 10, height + 10, null);
    g.setFont(new Font("Verdana", 0, 12));
    g.setColor(Color.YELLOW);
    g.drawString((fps + " fps | " + perfmon.usedMem() + "MB|"), 0, 12);
    g.dispose();
    bs.show();
    
}

public static void main(String[] args) {
    BufferedImage cursor = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
    Cursor blank = Toolkit.getDefaultToolkit().createCustomCursor(cursor, new Point(0,0), "blank");
    Main game = new Main();
    JFrame frame = new JFrame();
    frame.add(game);
    //frame.setPreferredSize(new Dimension(width, height));
    frame.pack();
    frame.getContentPane().setCursor(blank);
    frame.setTitle(TITLE);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setLocationRelativeTo(null);
    frame.setResizable(false);
    frame.setVisible(true);
    
    game.start();
}

private void init() {
    Dimension size = new Dimension(width, height);
    setPreferredSize(size);
    setMinimumSize(size);
    setMaximumSize(size);
    
    graphic = new Graphic(width, height);
    img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
    pixels = ((DataBufferInt)img.getRaster().getDataBuffer()).getData();
}

}```[/spoiler]

Graphic.java
[spoiler]```package com.jayanwarden.kannnix2.Graphics;

import java.awt.image.BufferStrategy;
import java.util.Random;

import com.jayanwarden.kannnix2.Graphics.HUD.Button;
import com.jayanwarden.kannnix2.Graphics.Primitives.Grayscale;

public class Graphic{

public static int[] pixels;
public static int width;
public static int height;

private Button button;
private Grayscale grayscale;

public Graphic(int width, int height) {
    Graphic.width = width;
    Graphic.height = height;
    
    button = new Button();
    grayscale = new Grayscale();
    
    pixels = new int[width * height];
}

public void render(BufferStrategy bs) {
    for (int i = 0; i < width * height; i++) {
        pixels** = 0;
    }
    test();
    button.drawButton(bs, 100, 100, 400, 400, 50, 0, "Notice the render glitches?");
}

public void test() {
    Random random = new Random();
    for (int y = 0; y < height; y++) {
        for(int x = 0; x < width; x++) {
            int alpha = grayscale.getGrayscale(random.nextInt());
            if (alpha > 0) {
                pixels[x + y * width] = alpha;
            }
        }
    }
}

}```[/spoiler]

Grayscale.java
[spoiler]```package com.jayanwarden.kannnix2.Graphics.Primitives;

public class Grayscale {

private int r, g, b, d;

public Grayscale() {
    r = 0;
    g = 0;
    b = 0;
    d = 0;
}

public int getGrayscale(int rgb) {
    r = (rgb >> 16) & 0xff;
    g = (rgb >> 8) & 0xff;
    b = (rgb) & 0xff;
    
    d = (int) (r + g + b) / 3;
    
    return (d << 16 | d << 8 | d);
}

}```[/spoiler]

Es wäre wunderbar wenn sich das mal jemand anschauen könnte der ein wenig mehr Ahnung hat als ich…

Und nicht wundern wenn die FPS bei der App sehr wenig sind. Ist normal. Mehr oder weniger.

Vielen Dank!

Ohne alles gelesen zu haben, und ohne ganz verstanden zu haben, was das Programm machen soll, ist mir beim Drüberschauen das aufgefallen:
g.drawImage(img, 0, 0, width + 10, height + 10, null);
Dort wird ein Bild der Größe 1280x720 gemalt - aber so, dass es einen Bereich der Größe 1290x730 ausfüllt. Da “müssen” Pixel doppelt vorkommen. (War das schon die Frage?)

Die Antwort liegt in “setResizable(false)”. Ohne dies wird dein Frame die korrekten Ausmaße haben. Ich weis nicht, ob das Problem nur in Windows auftritt oder auch noch woanders, es hängt dort auch nur davon ab, welches Window-Theme man wählt. Das Classic95-Theme verfäscht die Größe eines Frames zumindest nur um einen Pixel. Ein Workaround ist ein Frame, in welchem man einen Panel einfügt, welchem man die gewünschten Dimensionen gibt. Ansonsten kommt dadurch natürlich auch der von Marco angesprochene Effekt (falsche Skalierung) zum tragen. Aber wenn du nicht skalierst, entstehen unschöne Ränder richtig?

[QUOTE=Marco13]Ohne alles gelesen zu haben, und ohne ganz verstanden zu haben, was das Programm machen soll, ist mir beim Drüberschauen das aufgefallen:
g.drawImage(img, 0, 0, width + 10, height + 10, null);
Dort wird ein Bild der Größe 1280x720 gemalt - aber so, dass es einen Bereich der Größe 1290x730 ausfüllt. Da „müssen“ Pixel doppelt vorkommen. (War das schon die Frage?)[/QUOTE]

Ach ja. Das war ein „hotfix“ , weil mein Bild rumgeflackert hat. Am unteren und rechten Rand fehlten irgendwie ein paar Pixel, ich konnte mir nicht erklären, warum das so war und hab die Rendergröße verändert…
Wenn ich das zurückstelle passiert was komisches… (siehe Anhang)

Ich habs ausprobiert. Ohne setResizable ist die Richtige Größe drin!
Aber dann hab ich das problem dass man die Größe beliebig verändern kann… dann hab ich rechts und unten schwarze Ränder…
Ich könnt ja dann theoretisch in der tick() funktion immer setPreferredSize pollen, wenn jemand mit der Größe rumspielt, oder? ich meine, das wird ja nur maximal 60 mal pro Sekunde ausgeführt…

Hilft dir http://userpage.fu-berlin.de/~ram/pub/pub_jf47ht81Ht/java_swing_zeichenflaeche_ermitteln vielleicht weiter?

Sag’ ich doch…
Die Problematik ist folgendes (und bei Java 8 hat sich das noch nicht geändert!):

import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Frame;

public class ResizableTest {
	public static void main(String[] args) throws Exception {
		final Frame c = new Frame("Fullscreen test");
		c.pack();
		final Insets i = c.getInsets();
		final Frame f = new Frame() {
			private static final long serialVersionUID = 1L;

			String inside, outside, insets, shouldBe;

			@Override
			public void paint(Graphics g) {
				Insets i2 = getInsets();
				int x = i.left;
				int y = i.top;
				int w = getWidth() - i.left - i.right;
				int h = getHeight() - i.top - i.bottom;
				inside  = "inside  width: " + getWidth() + "; height: " + getHeight();
				outside = "outside width: " + c.getWidth() + "; height: " + c.getHeight();
				insets  = "insets left: " + i2.left + "; right : " + i2.right + "; ";
				insets += "top :" + i2.top + "; bottom: " + i2.bottom;
				shouldBe  = "shouldBe left: " + i.left + "; right : " + i.right + "; ";
				shouldBe += "top :" + i.top + "; bottom: " + i.bottom;
				g.setColor(Color.YELLOW);
				g.fillRect(x, y, w, h);
				g.setColor(Color.BLACK);
				g.drawString(inside, 10, i.top + 15);
				g.drawString(outside, 10, i.top + 30);
				g.drawString(insets, 10, i.top + 45);
				g.drawString(shouldBe, 10, i.top + 60);
			}
		};
		f.setVisible(true);
		c.setVisible(true);
		f.setBounds(0, 0, 320, 240);
		c.setBounds(320, 0, 320, 240);
		Thread.sleep(5000);
		f.setResizable(false);
		Thread.sleep(5000);
		f.setResizable(true);
		Thread.sleep(5000);
		f.dispose();
		c.dispose();
	}
}```
Deswegen: Einen Panel mit der gewünschten Größe erstellen (min-, max- und prefferedSize) und sozusagen einrahmen. Dann erst setResizable und pack aufrufen.