====== 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