JPEG-Decoder

Hallo Community

Ich interessiere mich grad für das Dekodieren von JPEGs und habe dazu folgenden Prototyp gebastelt.

https://www.dropbox.com/s/s7toy4w9grflq50/JPEGDecoderKSKB.jar?dl=1
(zur Ausführung in der Konsole. Erwartet einen Pfad/Dateinamen zu einem JPEG)

Der ist zwar noch recht lahm (fast integerbased IDCT ist noch nicht implementiert) und noch lange nicht fertig, aber Baseline-Encodierte Bilder lädt er schon mal…

Bis auf eine winzige Kleinigkeit, die Bilder dürfen anscheinend keine RSTn-Tags haben, denn wenn sie welche haben, geschehen merkwürdige Dinge. Hier mal der mMm entscheidende Code (gekürzt - komplette Fassung in der Klasse HTable im Jar).

		if(currentByte[0] < 0) {
			/* handle RSTn
			 *
			 * Kein Plan was hier schief laeuft.
			 * JPEGs ohne RSTs werden jedenfalls
			 * einwandfrei gelesen
			 */
			currentByte[0] = fRead(f);
//			bitPos[0] = 0;
//			return 0;
		}
		int pn = 0;
		int ps = 0;
		int c = 0;
		int b = 0;
		while(true) {
			if(bitPos[0] > 7) {
				bitPos[0] = 0;
				currentByte[0] = fRead(f);
				if(currentByte[0] < 0) {
					/* handle RSTn
					 *
					 * Kein Plan was hier schief laeuft.
					 * JPEGs ohne RSTs werden jedenfalls
					 * einwandfrei gelesen
					 */
					currentByte[0] = fRead(f);
//					return values[ps + c - b];
//					return 0;
				}
			}
			c |= getBit(currentByte[0], bitPos[0]);
			bitPos[0]++;
			if(c - b < bits[pn]) {
				return values[ps + c - b] & 0xFF;
			}
			pn++;
			if(pn > 15) {
				throw new IOException("no such code");
			}
			b = (b + bits[pn - 1]) << 1;
			ps += bits[pn - 1];
			c <<= 1;
		}
	}```
Die auskommentierten Zeilen stellen meine bisherigen versuche dar, etwas gegen dieses "dämliche" Verhalten zu unternehmen. Im Netz finde ich (z.B. bei StackOverflow) nur Hinweise darauf, dass ich irgendwas resetten soll, nur was, daraus werde ich auch aus Unmengen gewälztem C-Code nicht schlau. In dem Beispielcode (C) der hierfür recht gut funktioniert hat, werden RSTs einfach übersprungen. Da weiß ich aber nicht, ob im Originalcode dann auch das selbe passiert, wie bei mir, weil ich keine Möglichkeit habe C-Code zu komplieren, geschweige denn zu debuggen. Ich hoffe mir kann da einer von euch weiterhelfen.

Edit:
Der ursprünliche C-Code oben stehender Java-Methode sieht btw. so aus:

byte decode() {
while(true) {
*ps=pn+16;

c=getBit(c);

if(c-b < *pn) return ps[c-b];

c*=2;
b = (b+*pn)*2;
ps += *pn;
pn++;

}
}

weiß nicht ob dir das was hilft, aber hab irgendwo folgende aussage gefunden,
verstehe inhaltlich auch nicht wirklich was…

First, you shouldn’t rely on the last byte before the RST marker being padded with ones, as the standard doesn’t seem to have such a restriction. But that’s a moot point anyway, because you can’t just skip RST markers »on the fly« whenever you find them (as it’s done with padding bytes) – you have to expect them. You need to have a macroblock counter and every time it reaches the value that was defined in the DRI marker segment, you have to stop the entropy decoder, discard the remaining bits of the current byte, skip (and check) the RST marker, and continue with the next macroblock. If you just check for RST markers after each macroblock, you might miss macroblocks that fit into a single byte.
Finally, you should take into account that the RST marker also resets DC prediction.

Danke für den Versuch, aber soweit war ich schon. Bis auf die Sache mit den DC-Predictions habe ich schon all dies getan (steht auch auskommentiert bereits im Code - z.B. “return 0” (EOB)). Ich wüsste auch nicht, was DC Prediction reset bedeuten könnte, denn das passiert nach einem EOB ohnehin automatisch (jeder neue Block liest den ersten Wert aus dem DC-Table).
Was ich mir noch vorstellen kann, ist die Tatsache, dass dieses Problem anscheinend damit zusammenhängt, dass da jeweils die Bytefolge “FF00” vor den RSTs auftaucht und sich deswegen 0xFF in “currentByte[0]” steht.
Ob ich aber den Block nun beende und resettiere oder ob ich den aktuellen Code noch zurückgebe und dann resettiere, es führt leider beides zu Fehlern, deswegen bin ich recht ratlos.
Wie dem auch sei. Mir fehlt funktionierender debugbarer Referenzcode um mir anzusehen, was eigentlich passieren müsste, um es mit dem zu vergleichen, was bei mir passiert.

Hmja, an einem schnellen (!) JPG-Reader wollte ich mich auch schon länger mal versuchen, glaube aber, dass der Aufwand zur Einarbeitung da nicht unereblich ist, und es tausende von Stolpersteinen gibt, wenn man es tatsächlich ausimplementiert (einer davon war wohl schon der Anlass für den Thread hier :D). Du hast von “ursprünglichem C-Code” geredet - wo stammt der her? (Durch reines Portieren eines existierenden Loaders kann man den Aufwand natürlich deutlich reduzieren…)

[QUOTE=Marco13]wo stammt der her?[/QUOTE]Wenn ich das noch wüsste, aber ich werd’ noch mal suchen. (gefunden! ;)) (Link tot :()

Ich weiß zumindest noch, dass bei dem gezeigten Abschitt oben noch dabei stand, dass das Handling von Markern Sache der getBit-Funktion sein würde und diese war auf der Seite aber nicht zu finden.
Auf all den anderen durchforsteten Seiten wurden tatsächlich bilderbuchgetreu stets Huffmantables (Binärbäume) implementiert und die sind logischerweise stets um einiges langsamer als der Algo von oben - Ein HT muss Bit für Bit von der Wurzel bis zum Leaf durchwandert werden, das kann nicht schnell sein. :wink:

Das Problem beim simplen Portieren eines Loaders ist doch, dass man dabei in den seltensten Fällen hinter die jewilige Materie steigt. Das habe ich bei MP3-Dateien für meine DT_Lib gemacht… Funktioniert zwar, aber frag’ mich nicht, wie. :smiley:

Allzu schnell ist mein JPG-Reader allerdings noch nicht. Er braucht für ein Bild mit 640*480 Pixeln immerhin noch knapp 5s und das langsamste dabei ist/sind die IDCTProcessoren (inverse diskrete Cosinustransformation).
Aber lass dich nicht entmutigen, deinen eigenen zu schreiben, kannst meinen ja als Inspiration hernehmen.

UPDATE: Gelöst! Juhu…

Entweder habe ich das ganze Restart-Geschreibsel falsch verstanden ober die haben sich in der lang und breit gewälzten Literatur nur unverständlich ausgedrückt. Zumindest hat das Tag-Handling in der getBit()-Methode rein gar nichts verloren. Wenn dort ein 0xFF gelesen wird, muss das nächste Byte gelesen werden und wenn das dann nich 0x00 oder 0xD9 ist, läuft etwas falsch. Wird 0xFFD9 gelesen, müssen die letzten beiden Bytes zurückgespult werden, damit das EOI-Tag dort gelesen kann, wo es erwartet wird.

Der Restart-Mechanismus funktioniert ein wenig anders. Aktiviert wird er mit dem DRI-Tag (Define Restartinterval). Aus diesem bekommt man die Anzahl der MCUs, die gelesen werden müssen, bevor ein Restart-Tag folgt. Nun zählt man die einzelnen MCUs und wenn mcuCount % rstInterval == 0 ist, muss ein Restart-Tag auftauchen. Ist dies nicht so, ist die Datei defekt. Andernfalls müssen BitCounter und sämtliche IDCTProzessoren neu initialisiert werden. Vorher kann man noch sicher stellen, dass auf RST0 RST1 usw. folgt.

Interessierte können sich den Code ja im Einzelnen ansehen, die aktuelle Reader-Version ist oben verlinkt. Ich denke mal, dass er inzwischen alle Baseline-JPEGs lesen und darstellen kann. Nach meinem Empfinden zeigt er aber alle durchweg dunkler dafür aber kontrastreicher an, was meint ihr?

Das Thema markiere ich mal als gelöst, aber wenn einer Fragen hat, scheut euch nicht sie zu stellen (wenn es nicht gerade um fehlende Code-Dokumentationen geht :D).

[ot]
Der Hauptgrund, weswegen ich hier nachgehakt hatte, ist gerade auch nochmal woanders aufgepoppt: java - can GPU computing power be used for image converstion(tiff to jpeg)? if yes how to acheive it - Stack Overflow Ich fände es cool, einen GPU-basierten JPG Encoder/Decoder zu schreiben. Aber da ich von JPG nicht mehr Ahnung habe, als das, was man von ein paar Vorlesungsfolien im 3./4. Semester so mitnimmt, ist das wohl nichts, was man “mal so nebenbei” macht.

Hältst du es für realistisch, den code auf die GPU zu bringen? In https://developer.nvidia.com/npp gibt’s ja ein paar “Unterfunktionen” für die JPG-Codierung, d.h. das könnte sich schon lohnen. Ich werde mir wohl deinen Code mal ansehen, ““wenn ich Zeit habe”” ( :rolleyes: )

[/ot]

[OT]lernt man sowas im infostudium? oder was hast du studiert?[/OT]

[OT]

Naja, Vorlesung „Graphische Datenverarbeitung“
[/OT]

Hab’ mal kurz über das KSKB gebrowst: Das ist doch nicht „selbstgeschrieben“?! (Und wenn doch, dann ist es zumindest besch… kommentiert ;))

@Marco13 :
Doch, das ist selbst programmiert und nein, es ist nicht besch… Dokumentiert, sondern gar nicht! :smiley:

Das Ding dabei ist, dass ich für mich selber nicht wirklich eine Dokumentation benötige und mir diese dann weiträumig spare. Die „Gefahr“, dass meinen Code irgendwann mal jemand ließt, ist zur Zeit noch äußerst gering.

Das betrifft vor allem auch immer noch meine DT_Lib. Aber JPEGs waren bei der eigentlich ein Meilenstein, den ich noch implementiert haben wollte, bevor ich sie veröffentliche. Wenn das durch ist, mach ich mich an die Dokumentation. Aber jeden einzelnen Datentyp dokumentiere ich jedenfalls nicht, da die Laderoutinen ohnehin für jeden davon anders und deren Mechanismen vor allem nicht von Belang sind. Nur wenn einzelne Datentypen spezielle Funktionen bieten, wie z.B. jener für Pro-/FastTracker-Dateien, ist eine Doku von Nöten, find ich.

Was einen solchen Decoder auf GPU-Basis angeht, so denke ich, dass man das evtl. mit JCuda also JOXL (JOCL) oder LWJGL realisierbar ist.

Ich meinte damit eigentlich, dass man sowas wie

				if(restartIntervals >= 0 && processedMCUs < numMCUs && processedMCUs % restartIntervals == 0) {
					int rst = reader.readChar();
					if(rst < 0xFFD0 || rst > 0xFFD7) {
						throw new IOException("error reading restart marker after mcu " + processedMCUs);
					}
					bitPos[0] = 8;
					rst &= 0x7;
					if(rst - nextRestart != 0) {
						throw new IOException("data missing at mcu " + processedMCUs);
					}
					nextRestart = ++rst % 8;
					reset = true;
				}

ja nicht einfach so hinschreibt, nachdem man mal die JPG-Spec gelesen hat (oder da schon mehr Zeit reingeflossen ist, als ich vermutet hätte).

So…
Ich habe den Reader mal geupdatet. Der Quellcode ist zwar, bis auf die Art, wie ich die IDCT beschleunigt habe, immer noch nicht dokumentiert, aber immerhin habe ich eine Beschleunigung um mindestens 1000% (also das 10-Fache :wink: - obwohl… selbst Bilder, die in der letzten Fassung noch 15s brauchten, sind nun auch in unter 1s fertig) herausgekitzelt und das in plain Java.

Die ganzen Start Of Frame Tags habe ich nun in einer Klasse vereint. Das scheint auf den ersten Blick evtl. zwar unübersichtlich, aber so muss ich bei meinen Experimenten zumindest nich N Klassen ändern, wenn sich z.B. Aufrufe in HTable ändern.

Progressive ist immer noch experimentell. Da komme ich auch ohne Hilfe wohl kaum weiter. Aber ich finde weder etwas darüber im Netz, noch in der Stadtbibliothek. Es geht im Prinzip eigentlich nur darum, dass ich erstens nicht so genau weiß, ob ich mit der Handhabung der Spectralselektion richtig liege und zweitens, was die Werte approxL und approxH machen sollen. Evtl. weiß hier ja jemand etwas genaueres. Wär schade, wenn ich den Reader mangels Info mal wieder auf Eis legen muss. :frowning:

UPDATE:
So, nun kann der Reader auch teilweise Prograssive JPEGs lesen und darstellen. Der einzige Haken ist, dass diese JPEGs möglichst keine (sogenannten) AC-Refine-Scans haben dürfen. In der Beschreibung, die ich dazu gefunden habe, steht auch, dass diese Scan-Art der Horror ist, erst recht dann, wenn man die Ergebnisse der einzelnen Scans zwischendurch zur Anzeige bringen will, was ich allerdings schon ausgespart habe. Ich denke mal, dass ich nun tatsächlich an einem Punkt angelangt bin, wo ich ohne Hilfe nicht weiter komme, also werde ich mal an der Dokumentation meines Codes arbeiten.

und gleich das nächste Update.
Es ist vollbracht! Der Reader liest nun problemlos Baseline und Progressive JPEGs. Das Problem war offensichtlich, dass für besagte AC-Refine-Scans völlig andere Vorschriften für die Reihenfolge, in welcher die Bits aus dem Stream gelesen werden, gelten, aber ganz sicher bin ich mir da nicht. Letztendlich habe ich den code der JPEG-Lib 6b nach “refine” durchforstet, bin dadurch auf die funktion “decode_mcu_AC_refine” gestoßen und habe diese Schritt für Schritt in Java übernommen.

Ich frage mich grad’ ob @Marco13 evtl. nicht der einzige ist, der mal einen schnellen JPEG-Decoder programmieren will (evtl. sogar schneller als meiner) und wieviele davon Deutsche sein mögen, die mit der englischen Sprache auf Kriegsfuß stehen. Ob das KSKB vllt. einen Platz in unserem Wiki verdient?

(Aktualisierst du das eigentlich immer in Dropbox? Ich hatte es mir nur am Anfang einmal runtergeladen, aber noch ohne die ganzen Fixes und Erweiterungen…)

Ja, dazu ist sie ja da. :wink:
Wenn ich mit der Doku soweit durch bin, lade ich es aber hier hoch.

Warum machst du das eigentlich? Gibt es das (so) noch nicht? Oder um den Aufbau von JPEGs richtig zu verstehen?

Ich habe da so einen Spleen, der sich in der Datatypes-Library des guten alten Amiga-OS 3.0 und höher begründet…

Besagte Library lieferte einen Mechanismus, mit welchem man jedweden Dateityp mit nur einer Anweisung laden konnte (bzw. immer noch kann, wenn man denn einen AmigaOne besizt :D), sofern man eine Ladeklasse dafür installiert hat. Da ist nichts, wie z.B. bei ImageIO, dass man etwa JPEGs mit einer geringfügig anderen Anweisung (“jpg” statt “png”) lädt. Man übergibt einen Dateinamen oder eine URL und erhält dafür ein Objekt, welches man auf eine Klasse prüfen und dann entsprechend casten kann. Die Amiga-Datatypes-Library erkennt solche Dateien auch nicht anhand irgend einer Dateiendung, wie dies gängige Betriebssysteme tun, nein, man kann auch feststellen, ob sich evtl. eine “exe” hinter einem “avi” befindet oder ein “jpg” fälschlicherweise die Endung “png” hat oder ähnliches. Datentyperkennung anhand des Inhalts statt anhand des Dateinamens, das ist der Trick. Kannst dich ja mal umsehen, ob du einen solchen Mechanismus irgendwo sonst findest, ausser beim Amiga.

Ich kenne das Amiga-System nicht, aber ich hatte schon Software, die mich darüber informiert hat, dass in einem gif, ein png (o.ä.) stecken würde und mir anbot, die Endung zu ändern. Das waren aber, wenn ich mich richtig erinnere, Bildverarbeitungsprogramme.

Wenn ich mich richtig erinnere, verlässt sich Linux auch nicht auf Dateiendungen, ich weiß aber nicht, ob sich das nur auf die ausführbaren Dateien (über chmod) bezog, oder auch auf andere. Ich meine mich dunkel zu erinnern, dass es einen Befehl gab, der untersuchte, was für eine Dateiart die gegebene Datei hätte. Aber meine aktiven Linuxzeiten liegen schon lange zurück.

Klingt auf jeden Fall nach einem interessanten Projekt!

Jo, das ist es schon seit Jahren. Ich bin nur zu faul, es zu Dokumentieren, damit es veröffentlicht werden kann. Interessanter wäre es allerdings in Sprachen wie C oder C++ und dann (wie beim Amiga) ins Betriebssystem integriert.

Für Unix/Linux könnte es wahrscheinlich etwas Ähnliches geben, zumal die Idee darauf beruht, dass das Betriebssystem eigentlich gar keine Dateiendungen benötigt. In all diesen Betriebssystemen müssen ausführbare Dateien nicht die Endung “.exe”, “.com” oder “.bat” haben, selbst “.sh” ist nur rein obligatorisch. Jedes Dateiformat hat einen bestimmten Aufbau maw. eine “Signatur” und nach dieser kann gesucht (gescannt) werden.

Versuch mal in Linux, Unix, Windows oder sonstwo ausser Amiga eine Datei ohne Endung per Doppelklick zu öffnen - sofort wirst du gefragt, womit du sie öffnen willst (davon ausgenommen sind ausführbare Dateien unter Linux und Unix). In Windows wirst du obendrein nochmal gefragt ob das Programm (tatsächlich) versuchen soll, die Datei zu öffnen bzw. widerzugeben (z.B. MP3 im MediaPlayer). Der Amiga hat anno dazumal nicht gefragt… Der hat die Signatur der Datei gescannt und entsprechend das damit assoziierte Programm (meistens Multiview) geöffnet.