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:
- Inicjalizacja obiektu błędu (Error Handler).
- Tworzenie obiektu kompresji/dekompresji.
- Określenie źródła danych (np. plik
FILE*). - Odczyt nagłówka (
jpeg_read_header). - Uruchomienie procesu (
jpeg_start_decompress). - Odczyt obrazu linia po linii do bufora (skanlinii).
- 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.