PGM-Bilddatei in Java laden

Hallo Leute,

ich habe das ganze weite Internet durchstöbert, um eine Antwort auf mein Problem zu finden, aber nichts gefunden. Ich hoffe hier etwas weiterkommen zu können.
Ich muss eine PGM-Bilddatei in Java laden. Dafür habe ich 3 Dateien gegeben (SimpleImage.java, ImageViewer.java und ImageReader.java) in der ImageReader.java Datei sind einige Stellen mit TODO gegeben und an diesen Stellen muss ich meinen Code so einfügen, dass am Ende das Bild angezeigt wird. Ich hatte absolut keine Ahnung wie ich das machen sollte und habe teilweise Sachen aus dem Internet übernommen, so dass jetzt leider irgendein Mischmasch rausgekommen ist, das ich nicht mal richtig selbst verstehe.

Ich hoffe es gibt da draußen jemand, der meinen Code versteht und mir Hilfe leisten kann.

Mein Quellcode zur Klasse ImageReader:

package PGMReader;
import java.io.*;

/**
 * Class for loading a PGM image file.
 * The constructor of the class receives the file name and reads the first four lines of the file.
 * Calling readImage() then reads the rest of the file line by line and returns a SimpleImage.
 * The SimpleImage can then be displayed.
*/
class ImageReader
{
	/// Reader to read the file from disk.
	BufferedReader reader;
	/// Width and height of the image to be opened.
	private int width, height;

	/**
	 * Initializes the attribute "reader" with an appropriate stream, which reads data from the file with the given name.
	 * The first four lines of the file are read by calling readHeader() and the attributes width and height are initialized from the read data.
	 */
	ImageReader(String fileName) throws IOException
	{
		/// Opens the file. A line of the file can be read with reader.readLine()
		reader = new BufferedReader(new FileReader(fileName));
		readHeader();
	}

	/**
	 * Reads the first four lines from the file.
	 * The numbers contained in the second and third row are stored in the attributes width and height respectively.
	 * The first and the fourth line are skipped.
	 * They contain the magic number and the highest possible pixel value.
	 */
	private void readHeader() throws IOException
	{
		/*
		 * um die ersten 4 Zeilen einzulesen habe ich readLine benutzt
		 */
		for(int i = 0; i<4; i++){
			reader.readLine();
		}
		/*
		 * die Methode readLine kann nur String einlesen deswegen deklariere ich eine Hilfsvariable
		 * und setze sie dann in Integer verwandelt der eigentlichen Variablen (width und height) gleich
		 */
		String widthStr = reader.readLine(); 
	    String heightStr = reader.readLine();
	    reader.readLine();
		
	    //liest die 2. Zeile, also die Breite
		for(int i = 1; i<2;){
			width = Integer.parseInt(widthStr);
		}
		//liest die 3. Zeile, also die Höhe
		for (int i = 2; i<3;){
			height = Integer.parseInt(heightStr);
		}
	    
		// reader.readLine() reads the next line from the file and returns it as a String.
		// TODO: Read/skip the first four lines of the file
		// TODO: Initialize attributes width and height from the values in the second and third row.
	}

	/**
	 * Reads one row of pixels from the file.
	 * One row of the file consists of numeric values which are separated by spaces.
	 * The read line is of type String.
	 * It can be split up into an array of strings, where each element is a number, by using String[] line.split(" ").
	 * Each element in the array is then converted to short.
	 * The resulting array of short values is returned. 
	 */
	private short[] readLine() throws IOException
	{
		/*
		 * Erzeugung des Arrays (result) aus den Zeilen
		 */
		short[] result = new short[width];
		//Methode split zum trennen der Zeilen, so dass sie untereinander sind; liest jede Zahl einzeln ein
	    String[]line = reader.readLine().split(" ");
	    /*
	     * verwandelt alle Zahlen der Zeile zu short und speichert sie im Array result
	     */
	    for(int i=0; i < width; i++){
	        result**=Short.parseShort(line**);
	    }
	    //gibt das Ergebnis-Array wieder zurück
	    return result;
	    
		// TODO: Create a result array.
		// TODO: Read one line from the file and store the values in the result array.
		//return null; // TODO: Return the array.
	}

	/**
	 * Reads the entire image file and returns the image as an instance of SimpleImage.
	 * The file is read line by line (excluding the header, it contains exactly "height" rows).
	 * Each row is transferred pixel by pixel into the SimpleImage.
	 * Each row contains exactly "width" pixels.
	 */
	SimpleImage readImage() throws IOException
	{
		//erzeugung eines Objektes aus Klasse SimpleImage mit Parameter width und height
		SimpleImage image = new SimpleImage(width, height);
		// TODO: Read the file row by row and store the pixel values in the SimpleImage.
		
		//liest alle Zeilen ein
		for (int i = 0; i < width; i++){
			short[] r = readLine();
			
			//liest alle Spalten ein und setzt ein Pixel an diese Stelle
			for(int j = 0; j < height; j++){
				//Image image = new ImageIcon(this.getClass().getResource()).getImage();
				image.setPixelAt(j, i, r);
				
			}
		}
		//gibt Bild zurück
		return image;
	}

	/**
	 * Reads an image from disk and displays it.
	 */
	public static void main(String[] args)
	{
		try
		{
			ImageViewer viewer = new ImageViewer();
			ImageReader ir = new ImageReader("lenna.pgm");
			SimpleImage image = ir.readImage();
			viewer.showImage(image);
		}
		catch(IOException e)
		{
			System.out.println("Error while opening the file.");
		}
	}
}

Die anderen 2 Klassen:

/**
 * This class represents a simple grayscale image.
 * An image consists of a width, a height and an array of short values containing the gray values of the pixels.
 * The size of the array is exactly width*height.
 * Each element in the array has a value between 0 and 255 (both inclusive), which is the pixel's brightness.
 * The pixels are accessed through the getter and setter methods.
 */
public class SimpleImage
{
	private int width, height;
	private short[] pixels;

	/**
	 * Creates a new SimpleImage of the given size.
	 * @param width Width of the new image.
	 * @param height Height of the new image.
	 */
	public SimpleImage(int width, int height)
	{
		this.width = width >= 1 ? width : 1;
		this.height = height >= 1 ? height : 1;
		pixels = new short[width*height];
	}

	/**
	 * Returns the brightness value of the pixel at the given location.
	 * The brightness is a value between 0 and 255 (both inclusive).
	 * @param x x-position (column) of the pixel. The columns are counted from 0.
	 * @param y y-position (row) of the pixel. The rows are counted from 0.
	 * @return The brightness value of the pixel.
	 */
	public short getPixelAt(int x, int y)
	{
		return pixels[pixelIndex(x, y)];
	}

	/**
	 * Sets the brightness value of the pixel at the given location to the specified value.
	 * The brightness value must be a number between 0 and 255.
	 * @param x x-position (column) of the pixel. The columns are counted from 0.
       @param y y-position (row) of the pixel. The rows are counted from 0.
       @param r new brightness value of the pixel.
	 */
	public void setPixelAt(int x, int y, short r)
	{
		pixels[pixelIndex(x, y)] = r;
	}

	/**
	 * Returns the width of the image.
	 * @return the width of the image.
	 */
	public int getWidth()
	{
		return width;
	}

	/**
	 * Returns the height of the image.
	 * @return the height of the image.
	 */
	public int getHeight()
	{
		return height;
	}

	private int pixelIndex(int x, int y)
	{
		x = x>=0 ? x : 0;
		x = x<width ? x : width-1;
		y = y>=0 ? y : 0;
		y = y<height ? y : height-1;
		return y*width+x;
	}

}
import java.awt.*;
import javax.swing.*;

/**
 * Simple class for displaying a SimpleImage.
 * Use it by creating an instance of ImageViewer and then passing your image to the showImage() method.
*/
public class ImageViewer extends JFrame
{
	private static final long serialVersionUID = -8710968192678789420L;
	private SimpleImage image;

	/**
	 * Opens a window to display images.
	 * The window is empty at first.
	 */
	public ImageViewer()
	{
		super("Simple Image Viewer by Semicol");
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setMinimumSize(new Dimension(400, 300));
		setVisible(true);
	}

	/**
	 * Shows the given SimpleImage in the opened window.
	 * @param image the image to be displayed.
	 */
	public void showImage(SimpleImage image)
	{
		this.image = image;
		setMinimumSize(new Dimension(image.getWidth(), image.getHeight()));
		repaint();
	}

	/**
	 * This method is inherited from java.awt.Container and should not be called directly.
	 * It is only used internally.
	 */
	public void paint(Graphics g)
	{
		if (image == null)
			return;

		for (int y=0; y<image.getHeight(); ++y)
			for (int x=0; x<image.getWidth(); ++x)
			{
				int value = image.getPixelAt(x, y) & 0xFF;
				g.setColor(new Color(value, value, value));
				g.drawRect(x, y, 0, 0);
			}
	}
}

Danke im Voraus :slight_smile:
lg ElifÖzt

Ich kenn zwar das PGM-Format nicht, kann mir aber anhand der JavaDoc zusammenreimen dass du zumindest readHeader() falsch implementiert hast.
Auch ist der Viewer-Code kompletter Schrott und sollte so nicht genutzt werden.

Sehe ich auch so.

  • Nicht von JFrame erben!
  • Gezeichnet wird normalerweise in einen abgeleiteten JPanel-Typ, dort die paintComponent()-Methode überschreiben.

Für deine PGM-Datei wirst du dir dann einen entsprechenden Algorithmus einfallen lassen müssen, wenn du keine Bibliothek dazu findest.

Ich muss den Vorpostern leider beistimmen, dass hier noch sehr vieles im Argen liegt.

Es fehlen die grundlegendsten Dinge, wie zum Beispiel das korrekte verwenden einer Schleife.

for(int i = 1; i<2;){
  width = Integer.parseInt(widthStr);
}
//liest die 3. Zeile, also die Höhe
for (int i = 2; i<3;){
  height = Integer.parseInt(heightStr);
}```

Das kann und wird so nicht funktionieren, da dies eine Endlosschleife ist.
Und zudem sind die Schleifen an dieser Stelle völlig unnötig.

```/**
     * Reads the first four lines from the file.
     * The numbers contained in the second and third row are stored in the attributes width and height respectively.
     * The first and the fourth line are skipped.
     * They contain the magic number and the highest possible pixel value.
     */
    private void readHeader() throws IOException
    {
        // reader.readLine() reads the next line from the file and returns it as a String.
        // TODO: Read/skip the first four lines of the file
        // TODO: Initialize attributes width and height from the values in the second and third row.
       String firstLine = reader.readLine();

       String secondLine = reader.readLine();
       this.width = Integer.parseInt(secondLine);

       String thirdLine = reader.readLine();
       this.height = Integer.parseInt(thirdLine);

       String fourthLine = reader.readLine();
    }```

Nur mal als Beispiel dazu, was da tatsächlich in readHeader() stehen sollte.

readLine() sieht auf den ersten Blick gut aus.

Das gesamte readImage() benötigt noch den ein oder anderen Kniff.

Die Kommentare sollte man eben doch lesen und verstehen, was damit gemeint ist.
Variablen i und j, zu bezeichnen ist langfristig nicht hilfreich, wenn row oder column angebrachter ist.
Das macht es dann auch viel einfacher zu entscheiden ob nun über höhe oder breite iteriert werden soll.

/**
* Reads the entire image file and returns the image as an instance of SimpleImage.
* The file is read line by line (excluding the header, it contains exactly “height” rows).
* Each row is transferred pixel by pixel into the SimpleImage.
* Each row contains exactly “width” pixels.
*/
SimpleImage readImage() throws IOException
{
//erzeugung eines Objektes aus Klasse SimpleImage mit Parameter width und height
SimpleImage image = new SimpleImage(width, height);
// TODO: Read the file row by row and store the pixel values in the SimpleImage.

    //Um alle Zeilen (rows) zu lesen muss über die Höhe iteriert werden
    for (int row = 0; row < height; row++){
        short[] rowValues = readLine();
       //liest alle Spalten ein und setzt ein Pixel an diese Stelle
       // hier dann über die columns, kurz col iterieren
        for(int col = 0;col < width; col++){
            image.setPixelAt(col, row, rowValues[col]);
        }
    }
    //gibt Bild zurück
    return image;
}

Ich hab mich mal schnell ins PGM-Format eingelesen - wo auch immer du deinen Beispielcode herhast - der Autor hat es sich sehr einfach gemacht. Denn nicht nur Line 2 und 3 sind wichtig, sondern für die korrekte Überführung in den von Java genutzten ARGB-Farbraum auch Line 1 und 4 - denn diese enthalten einmal dass Format (ASCII oder binär) und den möglichen Höchstwert und damit eine Information für die Helligkeit. Ohne diese kannst du ein PGM-Bild nicht korrekt in ein RGB-Bild umrechnen.