JPEG

JPEG

Joint Photographic Experts Group — verlustbehaftete Kompression mit Markern

JPEG ist kein Dateiformat, sondern ein Kompressionsverfahren — die übliche Datei nennt man JFIF. Der Datenstrom besteht aus „Markern“: jeder beginnt mit dem Byte 0xFF, gefolgt von einem Markercode.

Die Datei startet mit dem SOI-Marker (FF D8) und endet mit EOI (FF D9). Dazwischen liegen Segmente mit Tabellen (Quantisierung, Huffman), Rahmenparametern (Bildgröße) und den eigentlichen komprimierten Bilddaten.

JPEG ist verlustbehaftet: Das Bild wird in 8×8-Blöcke zerlegt, per DCT in Frequenzen transformiert, quantisiert (hier geht Information verloren) und anschließend entropiekodiert.

Magic Number: FF D8 FF  →  SOI-Marker + Beginn des nächsten Markers

🧩 Aufbau der Datei

So liegen die Bestandteile einer JPEG-Datei nacheinander im Speicher.

SignaturMetadatenKopf/StrukturBilddatenEnde
SOI — Start of Image
2 Byte
FF D8
Markiert den Beginn. Hat keine Nutzdaten/Länge.
APP0 (JFIF) / APP1 (Exif)
variabel
FF E0 … 'JFIF\0'
Anwendungssegment: JFIF-Version, Auflösung, Thumbnail — oder Exif-Metadaten.
DQT — Quantisierungstabellen
variabel
FF DB …
Bestimmen, wie stark welche Frequenzen gerundet werden → steuert Qualität/Verlust.
SOF0 — Start of Frame
variabel
FF C0 …
Rahmenkopf: Bildbreite/-höhe, Anzahl Komponenten, Subsampling.
DHT — Huffman-Tabellen
variabel
FF C4 …
Codebäume für die Entropiekodierung der DCT-Koeffizienten.
SOS + komprimierte Daten
variabel
FF DA … <entropy>
Start of Scan: ab hier folgen die eigentlichen, Huffman-kodierten Bilddaten.
EOI — End of Image
2 Byte
FF D9
Markiert das Dateiende.

📐 Ablaufdiagramm (ASCII)

Schematischer Gesamtaufbau auf einen Blick.

┌──────────────────────────────────────────────────────────────┐
│ FF D8                                  ← SOI (Start of Image)  │
├──────────────────────────────────────────────────────────────┤
│ Marker-Schema (Segmente mit Länge):                           │
│   FF <code> <len_hi> <len_lo> <daten …>                       │
│   (len zählt 2 Längen-Bytes mit, aber NICHT den Marker)       │
├──────────────────────────────────────────────────────────────┤
│ FF E0  APP0  → "JFIF", Version, DPI, Thumbnail                 │
│ FF DB  DQT   → Quantisierungstabelle(n)                        │
│ FF C0  SOF0  → Breite, Höhe, Komponenten (Baseline DCT)        │
│ FF C4  DHT   → Huffman-Tabelle(n)                              │
│ FF DA  SOS   → Scan-Kopf  ┐                                    │
│        <entropie-codierte 8×8-Blöcke …>  (FF00 = escaped FF)   │
│ FF D9  EOI   → Ende                                            │
└──────────────────────────────────────────────────────────────┘

📊 Tabellen & Referenz

Wichtige JPEG-Marker

CodeNameBedeutung
FF D8SOIStart of Image (keine Länge)
FF E0APP0JFIF-Header (Version, DPI)
FF E1APP1Exif-/XMP-Metadaten
FF DBDQTQuantisierungstabelle
FF C0SOF0Baseline-DCT-Rahmenkopf
FF C2SOF2Progressive-DCT-Rahmenkopf
FF C4DHTHuffman-Tabelle
FF DASOSStart of Scan (Bilddaten folgen)
FF D9EOIEnd of Image (keine Länge)

Pipeline der Kompression

SchrittOperation
1Farbraum RGB → YCbCr
2Chroma-Subsampling (z. B. 4:2:0)
3Aufteilung in 8×8-Blöcke
4Diskrete Cosinus-Transformation (DCT)
5Quantisierung (verlustbehaftet!)
6Zickzack + Huffman-Entropiekodierung

⌨️ C-Strukturen & Auslese-Code

Einfache C-Strukturen, die exakt dem Byte-Layout entsprechen, plus Beispielcode zum Einlesen.

Marker-Grundgerüst · c
#include <stdint.h>

#define JPEG_SOI  0xD8   /* Start of Image */
#define JPEG_EOI  0xD9   /* End of Image   */
#define JPEG_SOF0 0xC0   /* Baseline DCT   */
#define JPEG_DHT  0xC4   /* Huffman-Tabelle*/
#define JPEG_DQT  0xDB   /* Quant.-Tabelle */
#define JPEG_SOS  0xDA   /* Start of Scan  */
#define JPEG_APP0 0xE0   /* JFIF           */

/* Ein Marker mit Länge: FF <code> <length(2)> <payload...> */
typedef struct {
    uint8_t  prefix;   /* immer 0xFF                          */
    uint8_t  code;     /* Markercode, z. B. 0xC0              */
    uint16_t length;   /* Big-Endian, inkl. dieser 2 Bytes    */
    /* uint8_t payload[length - 2]; */
} jpeg_marker_t;

ℹ️ Alle Längen in JPEG sind Big-Endian. SOI, EOI und RSTn haben KEINE Längen-/Datenfelder.

SOF0 — Rahmenkopf (Bildgröße & Komponenten) · c
#pragma pack(push, 1)
typedef struct {
    uint8_t  precision;       /* meist 8 Bit pro Sample        */
    uint16_t height;          /* Bildhöhe   (Big-Endian)       */
    uint16_t width;           /* Bildbreite (Big-Endian)       */
    uint8_t  num_components;  /* 1 = Graustufen, 3 = YCbCr     */
} jpeg_sof0_t;

typedef struct {
    uint8_t id;               /* Komponenten-ID (1=Y,2=Cb,3=Cr)*/
    uint8_t sampling;         /* hi nibble = H, lo nibble = V  */
    uint8_t quant_table_id;   /* Index der DQT-Tabelle         */
} jpeg_component_t;
#pragma pack(pop)
Datei-Marker durchlaufen (Skelett) · c
#include <stdio.h>
#include <arpa/inet.h>  /* ntohs */

int jpeg_scan_markers(FILE *f) {
    int b;
    if (fgetc(f) != 0xFF || fgetc(f) != JPEG_SOI) return -1; /* SOI? */

    while ((b = fgetc(f)) != EOF) {
        if (b != 0xFF) continue;          /* auf 0xFF synchronisieren */
        int code = fgetc(f);
        if (code == JPEG_EOI) break;      /* fertig */
        if (code == JPEG_SOI || code == 0xFF) continue;

        uint16_t len;
        fread(&len, 2, 1, f);
        len = ntohs(len) - 2;             /* Längenbytes herausrechnen */
        printf("Marker FF%02X, %u Byte Nutzdaten\n", code, len);
        fseek(f, len, SEEK_CUR);          /* Segment überspringen */
        if (code == JPEG_SOS) break;      /* danach kommen Bilddaten */
    }
    return 0;
}

ℹ️ Innerhalb der Scan-Daten wird 0xFF als 0xFF 0x00 „gestuffed“, um echte Marker abzugrenzen.

🗜️ Kompression

JPEG transformiert 8×8-Pixelblöcke mit der diskreten Cosinus-Transformation (DCT) in Frequenzanteile. Die hohen Frequenzen (feine Details) werden durch Quantisierung grob gerundet — hier entsteht der Qualitätsverlust. Die gerundeten Koeffizienten werden im Zickzack gelesen und Huffman-kodiert. Stärkere Quantisierung = kleinere Datei, aber mehr sichtbare Artefakte.

💡 Wissenswert

Das eigentliche „JPEG“ ist nur der Algorithmus (ISO 10918) — die Datei ist meist JFIF oder Exif.
Progressive JPEGs (SOF2) bauen das Bild schrittweise schärfer auf, statt zeilenweise.
Jedes erneute Speichern eines JPEGs quantisiert neu und verschlechtert die Qualität („Generationsverlust“).
← Zurück zur Übersicht