libjpeg – biblioteka współdzielona i serce obsługi formatu JPEG w systemie Linux

Jeśli kiedykolwiek otwierałeś zdjęcie na Linuxie, na telefonie mobilnym, przeglądałeś internet lub renderowałeś grafikę w terminalu, prawdopodobnie korzystałeś z libjpeg. To jedna z tych „niewidzialnych” bibliotek współdzielonych (.so), które stanowią fundament nowoczesnego przetwarzania obrazu. W tym wpisie przyjrzymy się jej historii, architekturze oraz nauczymy się, jak wykorzystać ją we własnych projektach w języku ANSI C.

Krótka historia standardu i biblioteki

Wszystko zaczęło się w 1986 roku, kiedy powstała grupa Joint Photographic Experts Group (JPEG). Ich celem było stworzenie standardu kompresji obrazów fotograficznych, który balansowałby między jakością a rozmiarem pliku.

Sama biblioteka libjpeg została opracowana przez Independent JPEG Group (IJG). Pierwsze publiczne wydanie (wersja 1) pojawiło się w 1991 roku. Od tego czasu stała się ona de facto standardem implementacji JPEG dla systemów uniksowych.

W historii biblioteki nastąpił jednak ciekawy zwrot akcji. W okolicach 2010 roku powstał fork o nazwie libjpeg-turbo. Wykorzystuje on instrukcje SIMD (Single Instruction, Multiple Data) procesorów x86, ARM i PowerPC, aby drastycznie przyspieszyć kompresję i dekompresję. Obecnie większość dystrybucji Linuxa (jak Ubuntu, Fedora czy Arch) domyślnie dostarcza libjpeg-turbo, która jest w pełni kompatybilna wstecz z oryginalnym API od IJG, ale działa od 2 do 10 razy szybciej.

Zastosowanie: Gdzie spotkasz libjpeg?

Biblioteka ta jest wszechobecna. Jej bezpośrednie wywołania znajdziesz w:

  • Przeglądarkach internetowych (Firefox, Chromium).
  • Środowiskach graficznych (GNOME, KDE – do wyświetlania tapet i ikon).
  • Narzedziach CLI (ImageMagick, fbv do wyświetlania zdjęć w framebufferze).
  • Serwerach druku (CUPS).

Architektura i API

libjpeg opiera się na strukturze „obiektowej” zaimplementowanej w czystym C. Głównym elementem jest struktura jpeg_decompress_struct (dla odczytu) lub jpeg_compress_struct (dla zapisu).

Proces pracy z biblioteką zawsze podąża tym samym schematem:

  1. Inicjalizacja obiektu błędu (Error Handler).
  2. Tworzenie obiektu kompresji/dekompresji.
  3. Określenie źródła danych (np. plik FILE*).
  4. Odczyt nagłówka (jpeg_read_header).
  5. Uruchomienie procesu (jpeg_start_decompress).
  6. Odczyt obrazu linia po linii do bufora (skanlinii).
  7. Sprzątanie pamięci.

Praktyka: Dekompresja JPEG w ANSI C

Aby skompilować poniższy kod, upewnij się, że masz zainstalowane nagłówki deweloperskie.

Instalacja bibliteki w Debian / Ubuntu Linux:

$ sudo apt install libjpeg-dev

Plik źródłowy z przykładem użycia biblioteki libjpeg – example_jpeg.c:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <jpeglib.h>

int main() {
    int width = 256;
    int height = 256;
    int numPixels = width * height;
    int quality = 90; // zmień do testów

    float data[256 * 256];

    // generowanie danych
    for (int i = 0; i < numPixels; i++) {
        data[i] = (float)i / 10.0f;
    }

    // konwersja float → uint8 (0–255)
    uint8_t* image = (uint8_t*)malloc(numPixels);
    if (!image) {
        printf("Memory allocation failed\n");
        return 1;
    }

    // znajdź min/max
    float min = data[0], max = data[0];
    for (int i = 1; i < numPixels; i++) {
        if (data[i] < min) min = data[i];
        if (data[i] > max) max = data[i];
    }

    // normalizacja
    for (int i = 0; i < numPixels; i++) {
        image[i] = (uint8_t)(255.0f * (data[i] - min) / (max - min));
    }

    // rozmiar raw (po konwersji do 8-bit)
    unsigned long rawSize = numPixels * sizeof(uint8_t);

    // JPEG struktury
    struct jpeg_compress_struct cinfo;
    struct jpeg_error_mgr jerr;

    unsigned char* jpegBuf = NULL;
    unsigned long jpegSize = 0;

    cinfo.err = jpeg_std_error(&jerr);
    jpeg_create_compress(&cinfo);

    jpeg_mem_dest(&cinfo, &jpegBuf, &jpegSize);

    cinfo.image_width = width;
    cinfo.image_height = height;
    cinfo.input_components = 1; // grayscale
    cinfo.in_color_space = JCS_GRAYSCALE;

    jpeg_set_defaults(&cinfo);

    jpeg_set_quality(&cinfo, quality, TRUE);

    jpeg_start_compress(&cinfo, TRUE);

    JSAMPROW row_pointer[1];

    while (cinfo.next_scanline < cinfo.image_height) {
        row_pointer[0] = &image[cinfo.next_scanline * width];
        jpeg_write_scanlines(&cinfo, row_pointer, 1);
    }

    jpeg_finish_compress(&cinfo);

    // ratio
    double ratio = (double)rawSize / (double)jpegSize;

    printf("JPEG quality: %d\n", quality);
    printf("Raw size (8-bit): %lu bytes\n", rawSize);
    printf("Compressed size: %lu bytes\n", jpegSize);
    printf("Compression ratio: %.2fx\n", ratio);

    jpeg_destroy_compress(&cinfo);

    free(jpegBuf);
    free(image);

    return 0;
}

Kompilacja

Plik kompilujemy, linkując bibliotekę flagą -ljpeg:

$ gcc example_jpeg.c -o example_jpeg -ljpeg

$ ./example_jpeg

JPEG quality: 90
Raw size (8-bit): 65536 bytes
Compressed size: 3821 bytes
Compression ratio: 17.15x

Dlaczego warto znać biblitekę libjpeg?

Mimo pojawiania się nowych formatów (WebP, AVIF), JPEG pozostaje królem kompatybilności. Znajomość libjpeg pozwala na niskopoziomową manipulację obrazem bez narzutu ciężkich bibliotek typu OpenCV czy Qt. To idealne narzędzie do systemów embedded, własnych silników gier czy lekkich narzędzi systemowych.

Na Linuxie libjpeg to klasyka gatunku – stabilna, szybka (dzięki wersji turbo) i niezawodna.

About the author

Autor "BIELI" to zapalony entuzjasta otwartego oprogramowania, który dzieli się swoją pasją na blogu poznajlinuxa.pl. Jego wpisy są skarbnicą wiedzy na temat Linuxa, programowania oraz najnowszych trendów w świecie technologii. Autor "BIELI" wierzy w siłę społeczności Open Source i zawsze stara się inspirować swoich czytelników do eksplorowania i eksperymentowania z kodem.