Java: PNG Dateien validieren
In diesem Tutorial erkläre ich wie man PNG Dateien validieren kann und dabei eventuell auch die eigenen Kenntnisse beim Umgang mit Bytes auffrischt.
Im Rahmen meiner Masterarbeit stellte sich mir das Problem, dass mein Programm mit fehlerhaft oder unvollständig heruntergeladenen PNGs umgehen können musste. Die Bilder selbst zeigte ich mit den üblichen Methoden an (JLabel, ImageIcon), jedoch scheiterten diese Methoden bei unvollständig heruntergeladenen PNGs mit einem OutOfBounds Fehler.
Natürlich könnte man eine solche Exception einfach fangen, für mich war dies jedoch der Anlass das PNG Format etwas näher zu betrachten. Der Aufbau ist simpel, es gibt einen Header dem einige Chunks folgen. Ein Chunk ist die Abfolge von Bytes, die ersten 4 Bytes geben die Länge (=n) an und die folgenden 4 Bytes bescheiben die Art des Chunks. Dann folgen n Bytes Daten. Die Art der Daten hängt von der Art des Chunks ab. Letztlich kommen noch 4 Bytes für die CRC Prüfsumme.
Um zu prüfen ob die PNG richtig und vollständig heruntergeladen wurde validieren wir zuerst den Header. Dies ist ein einfacher Vergleich von Bytes und sollte keine Probleme bereiten. Um die Daten zu lesen wird ein FileInputStream gebraucht. Die Methode read(byte[] b) liest so viele Bytes nach b ein, wie b lang ist. Integer (nicht verwechseln mit int) haben eine Funktion um den Byte-Wert zu bekommen.
Integer[] head_verify = {137, 80, 78, 71, 13, 10, 26, 10}; FileInputStream fis = new FileInputStream(f); byte[] header = new byte[8]; fis.read(header); for (int i = 0; i < 8; i++) { if (header[i] != head_verify[i].byteValue()) { return false; } }
Nun validieren wir die Checksummen der Chunks.
byte[] length = new byte[4]; byte[] type = new byte[4]; byte[] data; byte[] crc = new byte[4]; long lcrc; while (fis.available() > 0) { fis.read(length); fis.read(type); data = new byte[b2i(length)]; fis.read(data); fis.read(crc); lcrc = b2i(crc) & 0x00000000ffffffffL; CRC32 crc32 = new CRC32(); crc32.update(type); crc32.update(data); if (lcrc != crc32.getValue()) { return false; } }
Hier bedarf es sicher einiger erklärender Worte. “b2i” ist eine von mir geschriebene Methode, die 4 Bytes zu einem Integer umwandelt. Die Klasse CRC32 liefert die Funktionalität die wir zum Validieren brauchen und steckt in java.util.zip.CRC32. Die darin enthaltene Funktion getValue() liefert einen long Wert zurück, weswegen wir unseren CRC Wert auch zu einem Long machen (in lcrc). Der Rest der Schleife dürfte selbsterklärend sein. Erst lesen wir jeweils 4 Bytes in die Byte-Arrays length und type. dann erstellen wir ein neues Byte-Array data mit der in length angegeben Länge. Sollten die angegebene Checksumme nicht mit der von uns errechneten Summer übereinstimmen geben wir false zurück.
In diesem Code fehlt selbstverständlich die Prüfung ob der erste Chunk vom Typen IHDR ist. Für das Problem von unvollständig heruntergeladenen PNGs ist es aber wichtiger zu prüfen ob der letzte Chunk tatsächlich ein “IEND” Chunk ist (und ob danach keine Bytes mehr übrig sind. Dass dieser Chunk valide ist stellt der Loop oben sicher).
String t = new String(type, "UTF-8");if (fis.available() != 0 && t.equals("IEND")){ return false; }
Update: Obiges if-Statement ist nicht ganz richtig. Man sollte vielmehr prüfen ob fis.available() > 0 ist ODER t NICHT “IEND” entspricht. So wird sicher gestellt, dass auf jeden Fall beide Dinge überprüft werden, wobei der zweite Teil eben der wichtigere ist. Die eigentliche Exception mit nicht vollständig heruntergeladenen PNGs wird aber durch das erfolgreiche durchlaufen der obigen Schleife vermieden.
Wichtig ist hierbei, dass die Chunk Types UTF-8 encodierte Zeichenketten sind. Letztlich reiche ich noch die b2i Funktion nach:
private static int b2i(byte[] b) { return b[0] << 24 | (b[1] & 0xff) << 16 | (b[2] & 0xff) << 8 | (b[3] & 0xff); }
Das << ist ein Bitshift und das | ist ein logisches Oder. Die Funktion sollte jedem nach Informatik I verständlich sein :)
Der gesamte Code befindet sich hier. Mehr zum Dateiformat weiß Wikipedia und die Spezifikation und diese Seite hier.

[...] Der erste Artikel ist ein Java Tutorial in dem es um die Struktur von PNG Dateien geht und wie man diese ohne großen Aufwand validieren kann. Die Prüfung der Dateistruktur erfolgt nur recht oberflächlich, sollte für den vorgesehenen Zweck aber allemal reichen. Eventuell führe ich das als kleine Serie fort und schreibe darüber wie man PNGs nun tatsächlich lesen und darstellen kann, ohne irgendwelche Bibliotheken, Byte für Byte. [...]
#20, geschrieben von Neue Tutorials « XLKSTYLE.com | BLOG 25.07.2010 @ 23:23:50