====== Bildverarbeitung mit Lyx ======
''std.image'' ist eine **reine Decoder-Bibliothek**: Sie lädt Bilder in ein einheitliches RGBA-Format, bietet Pixel-Lese- und Schreibzugriff, aber **keine Encoder**. Es gibt kein "Bild als PNG speichern" — nur lesen, analysieren und programmatisch erzeugen.
Alle sieben Formate (BMP, PNG, JPEG, GIF, TIFF, WebP, AVIF) werden auf dasselbe Zielformat normalisiert: **4 Kanäle, 8 Bit/Kanal, RGBA, zeilenweise top-down**.
→ [[lyx_-_programmiersprache:guides|Guides]] · [[lyx_-_programmiersprache:units:image|std.image Referenz]]
import std.image.image; // Unified Facade: reicht für 90 % der Fälle
import std.image.core; // Struct-Direktzugriff, GrfImageAlloc, Pixel-Ops
----
===== Architektur =====
std/image/
├── core.lyx ← GrfImage-Struct, Allokation, Pixel-Ops, Format-Erkennung
├── image.lyx ← Unified Facade: ImageDecode, ImageDecodeFromMem, Accessoren
├── bmp.lyx ← BmpDecode
├── png.lyx ← PngDecode
├── jpeg.lyx ← JpegDecode
├── gif.lyx ← GifDecode
├── tiff.lyx ← TiffDecode
├── webp.lyx ← WebpDecode
└── avif.lyx ← AvifDecode
''std.image.image'' importiert alle Format-Units automatisch. Wer nur JPEG braucht, kann direkt ''std.image.jpeg'' importieren — spart Link-Zeit und vermeidet unnötige Abhängigkeiten.
----
===== Minimales Beispiel =====
import std.image.image;
import std.image.core;
import std.alloc;
import std.io;
fn main(): int64 {
var img: int64 := GrfImageAlloc(0, 0); // leeres Struct anlegen
if (img == 0) { return 1; }
if (ImageDecode("foto.jpg"c as int64, 0, img) == 0) {
PrintLn("Laden fehlgeschlagen");
GrfImageFree(img);
return 1;
}
var w: int64 := ImageGetWidth(img);
var h: int64 := ImageGetHeight(img);
PrintLn(IntToStr(w) + "x" + IntToStr(h));
// Mittelpixel lesen — Format: 0xRRGGBBAA
var px: int64 := ImagePixelAt(img, w / 2, h / 2);
var r: int64 := (px >> 24) & 255;
var g: int64 := (px >> 16) & 255;
var b: int64 := (px >> 8) & 255;
PrintLn("R=" + IntToStr(r) + " G=" + IntToStr(g) + " B=" + IntToStr(b));
ImageFree(img);
return 0;
}
----
===== Workflow 1: Bild aus Datei laden =====
import std.image.image;
import std.image.core;
import std.alloc;
fn LoadImage(path: int64): int64 {
var img: int64 := GrfImageAlloc(0, 0);
if (img == 0) { return 0; }
// plen=0: Dateigröße wird nicht zurückgegeben (nicht benötigt)
if (ImageDecode(path, 0, img) == 0) {
GrfImageFree(img);
return 0;
}
return img; // Aufrufer muss ImageFree(img) aufrufen
}
''ImageDecode'' liest die Datei intern in einen Puffer, erkennt das Format via Magic-Bytes und ruft den passenden Decoder auf. Kein manuelles Puffer-Management nötig.
----
===== Workflow 2: Bild aus Speicher-Puffer laden =====
Nützlich wenn das Bild bereits im RAM liegt — z.B. aus einem HTTP-Response, einem ZIP-Archiv oder einer eingebetteten Ressource:
import std.image.image;
import std.image.core;
fn DecodeFromBuffer(buf: int64, len: int64): int64 {
var img: int64 := GrfImageAlloc(0, 0);
if (img == 0) { return 0; }
if (ImageDecodeFromMem(buf, len, img) == 0) {
GrfImageFree(img);
return 0;
}
return img;
}
''ImageDecodeFromMem'' benötigt mindestens 4 Bytes für die Format-Erkennung (WebP/AVIF brauchen 12 Bytes).
----
===== Workflow 3: Format-spezifischer Decoder =====
Wenn das Format bereits bekannt ist (z.B. eine Datei mit garantiert `.png`-Extension), spart der direkte Aufruf die Format-Erkennung:
import std.image.core;
import std.image.png;
import std.image.jpeg;
fn DecodeKnownFormat(buf: int64, len: int64, isPng: int64): int64 {
var img: int64 := GrfImageAlloc(0, 0);
if (img == 0) { return 0; }
var ok: int64 := 0;
if (isPng != 0) {
ok := PngDecode(buf, len, img);
} else {
ok := JpegDecode(buf, len, img);
}
if (ok == 0) { GrfImageFree(img); return 0; }
return img;
}
Oder mit ''GrfDetectFormat'' manuell prüfen:
import std.image.core;
import std.image.jpeg;
import std.image.png;
import std.image.bmp;
fn DecodeWithDetect(buf: int64, len: int64): int64 {
var fmt: int64 := GrfDetectFormat(buf, len);
var img: int64 := GrfImageAlloc(0, 0);
var ok: int64 := 0;
if (fmt == GRF_FMT_JPEG) { ok := JpegDecode(buf, len, img); }
if (fmt == GRF_FMT_PNG) { ok := PngDecode(buf, len, img); }
if (fmt == GRF_FMT_BMP) { ok := BmpDecode(buf, len, img); }
// Unbekannte Formate: ok bleibt 0
if (ok == 0) { GrfImageFree(img); return 0; }
return img;
}
----
===== Workflow 4: Durchschnittsfarbe berechnen =====
Ein typisches Analyse-Beispiel — alle Pixel iterieren und Mittelwert von R/G/B berechnen:
import std.image.image;
import std.image.core;
import std.alloc;
import std.io;
fn AverageColor(img: int64): void {
var w: int64 := ImageGetWidth(img);
var h: int64 := ImageGetHeight(img);
var sumR: int64 := 0;
var sumG: int64 := 0;
var sumB: int64 := 0;
var y: int64 := 0;
while (y < h) {
var x: int64 := 0;
while (x < w) {
var px: int64 := ImagePixelAt(img, x, y);
sumR := sumR + ((px >> 24) & 255);
sumG := sumG + ((px >> 16) & 255);
sumB := sumB + ((px >> 8) & 255);
x := x + 1;
}
y := y + 1;
}
var total: int64 := w * h;
PrintLn("Avg R=" + IntToStr(sumR / total) +
" G=" + IntToStr(sumG / total) +
" B=" + IntToStr(sumB / total));
}
Für Performance: bei großen Bildern jedes 4. Pixel samplen (''x := x + 4'') — Ergebnis bleibt repräsentativ, Laufzeit sinkt um Faktor 16.
----
===== Workflow 5: Bild programmatisch erzeugen =====
''GrfImageAlloc'' mit echten Dimensionen anlegen und Pixel schreiben:
import std.image.core;
import std.alloc;
fn CreateCheckerboard(w: int64, h: int64, size: int64): int64 {
var img: int64 := GrfImageAlloc(w, h);
if (img == 0) { return 0; }
var y: int64 := 0;
while (y < h) {
var x: int64 := 0;
while (x < w) {
var check: int64 := ((x / size) + (y / size)) & 1;
var rgba: int64 := 0;
if (check == 0) {
rgba := 0xFFFFFFFF; // weiß, volle Deckkraft
} else {
rgba := 0x000000FF; // schwarz, volle Deckkraft
}
GrfImageSetPixel(img, x, y, rgba);
x := x + 1;
}
y := y + 1;
}
return img;
}
Zum Füllen mit einer Farbe die schnellere ''GrfImageClear''-Funktion verwenden:
var img: int64 := GrfImageAlloc(800, 600);
GrfImageClear(img, 0xFF1A1A2EFF); // alle Pixel navy-blau, volle Deckkraft
----
===== Workflow 6: MJPEG-Frame aus AVI extrahieren =====
''std.video.avi'' und ''std.image'' arbeiten direkt zusammen — ''AviExtractMjpegFrame'' dekodiert ein MJPEG-Frame aus dem AVI-Datenstrom in ein ''GrfImage'':
import std.video.core;
import std.video.avi;
import std.image.core;
import std.image.jpeg;
import std.alloc;
import std.fs;
import std.io;
fn ExtractAviFrame(aviPath: int64, frameIdx: int64): int64 {
// AVI in Puffer laden
var fsize: int64 := FileSize(aviPath);
if (fsize <= 0) { return 0; }
var buf: int64 := alloc(fsize);
ReadFile(aviPath, buf, fsize);
// Metadaten prüfen — nur bei MJPEG sinnvoll
var vid: int64 := alloc(VID_SIZE);
if (AviMeta(buf, fsize, vid) == 0) {
free(buf, fsize); free(vid, VID_SIZE); return 0;
}
if (peek64(vid + VID_OFF_CODEC) != VID_CODEC_MJPEG) {
PrintLn("Kein MJPEG-Codec");
free(buf, fsize); free(vid, VID_SIZE); return 0;
}
// Frame dekodieren
var img: int64 := GrfImageAlloc(0, 0);
var ok: int64 := AviExtractMjpegFrame(buf, fsize, frameIdx, img);
free(buf, fsize);
free(vid, VID_SIZE);
if (ok == 0) { GrfImageFree(img); return 0; }
return img; // GrfImage mit RGBA-Pixeln; Aufrufer gibt frei
}
----
===== Workflow 7: RGBA → BGRA für Vega =====
Der Vega-Compositor (''bsys_vega.lyx'') arbeitet mit 32-Bit **BGRA** (Byte-Reihenfolge im Speicher: B, G, R, A). Beim Kopieren eines geladenen Bilds in einen Vega-Framebuffer müssen Kanal R und B getauscht werden:
import std.image.core;
// Kopiert img-Pixel in einen BGRA-Puffer (z.B. Vega-Fenster-FB)
fn CopyRgbaToBgra(img: int64, dst: int64): void {
var w: int64 := peek64(img + GRF_OFF_WIDTH);
var h: int64 := peek64(img + GRF_OFF_HEIGHT);
var pixels: int64 := peek64(img + GRF_OFF_PIXELS);
var pixLen: int64 := w * h * 4;
var i: int64 := 0;
while (i < pixLen) {
var r: int64 := peek8(pixels + i); // RGBA: Byte 0 = R
var g: int64 := peek8(pixels + i + 1);
var b: int64 := peek8(pixels + i + 2);
var a: int64 := peek8(pixels + i + 3);
// BGRA: Byte 0 = B, Byte 1 = G, Byte 2 = R, Byte 3 = A
poke8(dst + i, b);
poke8(dst + i + 1, g);
poke8(dst + i + 2, r);
poke8(dst + i + 3, a);
i := i + 4;
}
}
In Vega dann ''DisplayBlit'' oder ''BlitWindow'' mit dem konvertierten Puffer aufrufen.
----
===== Direktzugriff auf den Pixel-Buffer =====
Für hochperformante Operationen kann der interne Pixel-Buffer direkt über ''GRF_OFF_PIXELS'' angesprochen werden:
import std.image.core;
fn FastScan(img: int64): void {
var w: int64 := peek64(img + GRF_OFF_WIDTH);
var h: int64 := peek64(img + GRF_OFF_HEIGHT);
var pixels: int64 := peek64(img + GRF_OFF_PIXELS);
var pixLen: int64 := peek64(img + GRF_OFF_PIXLEN); // = w*h*4
// Zeiger-Arithmetik direkt auf den Puffer — kein Funktions-Overhead
var i: int64 := 0;
while (i < pixLen) {
// Byte-Reihenfolge im Puffer: R(i), G(i+1), B(i+2), A(i+3)
var r: int64 := peek8(pixels + i);
i := i + 4;
}
}
Der Puffer liegt zusammenhängend im Heap (alloziert von ''GrfImageAlloc''). Stride = w × 4 Bytes, kein Padding.
----
===== Speicherverwaltung =====
''GrfImageAlloc'' alloziert zwei Blöcke:
- Das Kontroll-Struct (56 Bytes, ''GRF_SIZE'')
- Den Pixel-Buffer (w × h × 4 Bytes)
**Beide werden von ''GrfImageFree'' / ''ImageFree'' freigegeben.** Nie nur den Struct-Pointer freigeben — das leakt den Pixel-Buffer.
var img: int64 := GrfImageAlloc(800, 600);
// ... arbeiten ...
GrfImageFree(img); // gibt Struct UND Pixel-Buffer frei
// alternativ: ImageFree(img) aus std.image.image
Beim Fehlerfall in einem Decoder: Der Decoder gibt **kein** partiell alloziertes ''GrfImage'' zurück. Wenn ''ImageDecode'' 0 zurückgibt, ist der ''img''-Puffer noch in dem Zustand wie vor dem Aufruf — bei ''GrfImageAlloc(0,0)'' hat ''GRF_OFF_PIXELS'' den Wert 0, ''GrfImageFree'' ist trotzdem sicher.
----
===== Einschränkungen =====
^ Was ^ Einschränkung ^
| **Encoder** | Nicht vorhanden — kein PNG-/JPEG-Schreiben |
| **Resize / Transform** | Nicht vorhanden — Pixel-Loop selbst implementieren |
| **Farbräume** | Alle Decoder liefern immer RGBA; kein YCbCr, kein CMYK als Ausgabe |
| **GIF-Animationen** | Nur der erste Frame wird dekodiert |
| **Alpha-Kanal** | Immer vorhanden (Kanal 4); bei JPEG und BMP ist ''a=255'' |
| **Maximale Bildgröße** | Durch verfügbaren Heap begrenzt; 4K (3840×2160) = ~31 MB Pixel-Buffer |
| **Thread-Safety** | Kein Locking — ein ''GrfImage'' pro Thread oder extern synchronisieren |
----
===== Entscheidungsguide =====
^ Ich will ... ^ Import ^
| Bild aus Datei laden (beliebiges Format) | ''std.image.image'' |
| Bild aus Speicher laden (HTTP-Antwort, ZIP) | ''std.image.image'' |
| Nur JPEG verarbeiten (weniger Abhängigkeiten) | ''std.image.jpeg'' + ''std.image.core'' |
| Nur PNG verarbeiten | ''std.image.png'' + ''std.image.core'' |
| Format per Magic-Bytes prüfen | ''std.image.core'' (''GrfDetectFormat'') |
| Bild programmatisch erzeugen | ''std.image.core'' (''GrfImageAlloc'' + ''GrfImageSetPixel'') |
| MJPEG-Frame aus AVI holen | ''std.video.avi'' + ''std.image.core'' + ''std.image.jpeg'' |
| Bild für Vega-Compositor vorbereiten | RGBA→BGRA-Konvertierung (Workflow 7) |
Letzte Aktualisierung: 2026-06-15