Ein- und Ausgabebibliotheken für Grafikformate?



  • Hi(gh)!

    Bislang konnte mein kommandozeilenorientiertes Grafikbearbeitungsprogrämmchen ("yip", "Yadgar's Image Processor") nur unkomprimierte 24-bit-TGA-Files einlesen und erzeugen, was auf die Dauer etwas umständlich ist. Daher will ich jetzt endlich mal die entsprechenden Funktionalitäten für GIF, PNG, JPG und WEBP implementieren... aber auf cppreference.com finde ich nirgendwo einen Link, der auf so etwas wie Grafikbibliotheken hindeutet! Wo müsste ich da suchen?

    Mir ist schon klar, das so etwas häufig compilerspezifisch ist, mein Compiler ist der g++ unter Linux... wahrscheinlich sind die Bibliotheken, nach denen ich suche, längst auf meinem System (Debian "Bullseye" 11) installiert - nur, wie heißen sie und wo finde ich sie?

    Bis bald im Khyberspace!

    Yadgar



  • "Compilerspezifisch" ist das mitnichten, gerade solche Bibliotheken lassen sich meist mit jedem Compiler und für jedes System bauen. Generell verwenden Programme, die mit Grafikformaten umgehen wollen die entsprechenden Referenzbibliotheken oder alternative Implementierungen für das jeweilige Bildformat. Das wären z.B. für die von dir aufgelisteten Formate GIFLIB, libpng, libjpeg oder (glaube mittlerweile populärer) libjpeg-turbo und libwebp.

    Die sind ziemlich sicher bereits auf einem Desktop-Linux installiert. Eventuell benötigst du noch die -dev-Pakete dieser Bibliotheken für Header und andere Dinge, die man braucht, wenn man die in eigenen Programmen verwenden will.

    Diese Bibliotheken direkt zu verwenden ist wahrscheinlich das, was die meisten Bildverarbeitungsprogramme ebenfalls machen. Damit hast du Zugriff auf alle (auch die exotischeren) Features der Formate. Das bedeutet aber auch, dass du das Laden und Speichern für jedes Format individuell implementieren musst, da jede dieser Bibliotheken eine eigene API verwendet.

    Es gibt aber auch Bibliotheken, die das vereinheitlichen. Auf Anhieb fällt mir da nur SDL_image ein, da sollte es aber auch noch etliche andere geben. Die verwenden dann ebenfalls diese Referenzbibliotheken, stellen aber eine einheitliche API zur Verfügung. Eventuell muss man aber ein paar Abstriche bei den unterstützten Funktionalitäten machen. Es ist möglich, dass weniger verbreitete Features wie 12-bit JPEGs oder PNGs mit 16-bit pro Kanal eventuell nicht unterstützt werden.

    Sehr populär scheint auch stb zu sein (in Debian als libstb0). Das sieht mir wie eine Header-Only-Bibliothek zu sein die eine Reihe von Bildformaten laden und speichern kann. Offenbar bringt diese Bibliothek eigene Implementierungen der Bildformate mit, so dass es keine Abhängigkeiten zu den Referenzbibliotheken gibt. Die könnte am simpelsten zu verwenden sein.

    Ich muss noch anmerken, dass ich selbst nur wenig Erfahrung mit all diesen Bibliotheken habe. Das sind aber die, die ich mir selbst auch ansehen würde, wenn ich eine solche Funktionalität benötige. Ich würde wahrscheinlich die Bibliotheken für die jeweiligen Formate direkt verwenden, da ich Standard/Referenzimplementierungen generell bevorzuge und versuche, Abhängigkeiten gering zu halten (stb hätte zwar die wenigsten, ist aber eine eigene Implementierung der Formate).



  • Was für ein Framework nutzt Du für die Softwareentwicklung? Je nachdem was Du verwendest, gibt es schon Libraries im Framework, um Pixel Bilder bzw. Vektorbilder zu laden.



  • Hi(gh)!

    Sehr populär scheint auch stb zu sein (in Debian als libstb0). Das sieht mir wie eine Header-Only-Bibliothek zu sein die eine Reihe von Bildformaten laden und speichern kann. Offenbar bringt diese Bibliothek eigene Implementierungen der Bildformate mit, so dass es keine Abhängigkeiten zu den Referenzbibliotheken gibt. Die könnte am simpelsten zu verwenden sein.

    Ich habe mir jetzt libstb0 und die zugehörige Header-Datei libstb-dev installiert... ein erstes kleines Testprogramm:

    // yaigat - Yadgar's Artificial Intelligence Graphics Analysis Tool
    
    #include <stdlib.h>
    #include <iostream>
    #include <string.h>
    #include <stb_image.h>
    
    using namespace std;
    
    int main()
    {
      cout << "Test!" << endl;
      
      return 0;
    }
    

    wurde leider nicht kompiliert, es gab eine Fehlermeldung:

    yaigat.cc:6:10: fatal error: stb_image.h: Datei oder Verzeichnis nicht gefunden
    6 | #include <stb_image.h>
    | ^~~~~~~~~~~~~

    Der Header ist laut https://packages.debian.org/bullseye/libstb-dev korrekt - aber in welchem Verzeichnis liegt er lokal?

    Bis bald im Khyberspace!

    Yadgar


  • Mod

    Laut deinem eigenem Link sollte das aber <stb/stb_image.h> sein. Ansonsten: Deine Dateien auf deinem Computer danach durchsuchen?



  • @SeppJ sagte in Ein- und Ausgabebibliotheken für Grafikformate?:

    Laut deinem eigenem Link sollte das aber <stb/stb_image.h> sein. Ansonsten: Deine Dateien auf deinem Computer danach durchsuchen?

    Ja, ich habe es jetzt gefunden, die Zeile 6 ersetzt durch

    #include <stb/stb_image.h>
    

    ...und weiß seither, dass sämtliche C-Header standardmäßig in /usr/include liegen, die C++-Bibliotheken in /usr/include/C++/10/! Sehr instruktiv, danke für den Tipp!

    Bis bald im Khyberspace!

    Yadgar

    Now playing: Karlheinz Stockhausen: Kontra-Punkte (Ensemble Recherche)



  • @Yadgar sagte in Ein- und Ausgabebibliotheken für Grafikformate?:

    Ja, ich habe es jetzt gefunden, die Zeile 6 ersetzt durch

    #include <stb/stb_image.h>
    

    ...und weiß seither, dass sämtliche C-Header standardmäßig in /usr/include liegen, die C++-Bibliotheken in /usr/include/C++/10/! Sehr instruktiv, danke für den Tipp!

    Das Wichtige ist nicht /usr/include, da liegen fast alle systemweiten Header, sondern dass das Paket die Header nach /usr/include/stb/ installiert. Compiler auf Linux sind typischerweise so konfiguriert, dass sie standardmäßig /usr/include als eines der Default-Includeverzeichnisse verwenden. Um von diesem aus /usr/include/stb zu erreichen, muss man natürlich im Quellcode einen relativen Pfad stb/stb_image.h angeben. Mit g++ -v ... bekommst du das auch angezeigt. Da gibt GCC dann am Ende z.B. so was aus:

    #include "..." search starts here:
    #include <...> search starts here:
     /usr/lib/gcc/x86_64-pc-linux-gnu/16/../../../../include/c++/16
     /usr/lib/gcc/x86_64-pc-linux-gnu/16/../../../../include/c++/16/x86_64-pc-linux-gnu
     /usr/lib/gcc/x86_64-pc-linux-gnu/16/../../../../include/c++/16/backward
     /usr/lib/gcc/x86_64-pc-linux-gnu/16/include
     /usr/local/include
     /usr/include
    End of search list.
    

    Wenn der Compiler also ein relatives #include <stb/stb_image.h> sieht, dann hängt er das an all diese Verzeichnisse an und nimmt das erste, wo er die Datei findet:

     /usr/lib/gcc/x86_64-pc-linux-gnu/16/../../../../include/c++/16/stb/stb_image.h
     /usr/lib/gcc/x86_64-pc-linux-gnu/16/../../../../include/c++/16/x86_64-pc-linux-gnu/stb/stb_image.h
     /usr/lib/gcc/x86_64-pc-linux-gnu/16/../../../../include/c++/16/backward/stb/stb_image.h
     /usr/lib/gcc/x86_64-pc-linux-gnu/16/include/stb/stb_image.h
     /usr/local/include/stb/stb_image.h
     /usr/include/stb/stb_image.h
    

    Und das letzte Verzeichnis ist genau das, wo das libstb-dev-Paket den Header installiert hat.

    So am Rande: /usr/include/C++/10 (ich sehe du verwendest GCC 10 😉 ) ist übrigens nicht unbedingt nicht immer das Include-Verzeichnis für die C++-Header. Die werden beim Build von GCC konfiguriert und können z.B. auch relativ zum g++-Program sein, wie bei einer anderen portablen GCC-Toolchain die ich verwende:

    /toolchains/dlo-toolchain-x86_64-linux-musl/bin/../lib/gcc/i386-dlo-elf/16.2.0/include/c++

    Das passt sich dann immer an das Verzeichnis an, in dem GCC installiert wurde und ermöglicht, den Compiler auch neben dem System-Compiler in einem beliebigen Verzeichnis zu installieren. System-Compiler (also die, die mit der Distribution kommen) sind aber typischerweise für feste, absolute Include-Verzeichnisse wie /usr/include konfiguriert.

    Das nur am Rande, falls du in Zukunft noch andere nicht gefundene Header debuggen willst. Schau die am besten an, was gcc -v ... oder g++ -v ... als Include-Verzeichnisse angeben und ob er den Header damit auch finden kann.

    Natürlich kannst du auch eigene Suchverzeichnisse hinzufügen. Wenn du z.B. mit

    g++ -I/usr/include/stb

    kompilierst, dann funktioniert auch #include <stb_image.h>. Lass es aber besser bei #include <stb/stb_image.h>. Das ist portabler, weil es nicht den expliziten Include-Pfade erfordert.



  • Hi(gh)!

    Danach suchte ich natürlich die Ladefunktion(en) für die einzelnen Dateiformate... und wurde vom Quellcode in stb_image.h einfach nur erschlagen! Google war dann ausnahmsweise doch mein Freund (obwohl mir Ecosia ja sympathischer ist...), ich suchte nach "stb_image.h tutorial" und fand dann diesen netten kleinen Codeschnipsel hier: https://psgraphics.blogspot.com/2015/06/a-small-image-io-library-stbimageh.html

    Das war ja wirklich einfacher, als ich es mir hätte träumen lassen... sofort implementiert und mit einer 1-Pixel-Datei getestet:

    // yaigat - Yadgar's Artificial Intelligence Graphics Analysis Tool
    
    #define STBI_NO_BMP
    #define STBI_NO_PSD
    #define STBI_NO_HDR
    #define STBI_NO_PIC
    #define STBI_NO_PNM
    
    
    #include <stdlib.h>
    #include <iostream>
    #include <string.h>
    #define STB_IMAGE_IMPLEMENTATION
    #include <stb/stb_image.h>
    #define STB_IMAGE_WRITE_IMPLEMENTATION
    #include <stb/stb_image_write.h>
    
    
    using namespace std;
    
    int main()
    {
      const string auml = "ä";
      const string Auml = "Ä";
      const string ouml = "ö";
      const string Ouml = "Ö";
      const string uuml = "ü";
      const string Uuml = "Ü";
      const string szlig = "ß";
    
      
      
      int x, y, n;
      unsigned char *data = stbi_load("0000.gif", &x, &y, &n, 0);
      cout << "Breite: " << x << endl;
      cout << "H" << ouml << "he: " << y << endl;
      unsigned int s = sizeof(data);
      unsigned i;
      
      for (i=0; i<s; i++)
        cout << (unsigned int)data[i] << endl;
      for (i=0; i<=2; i++)
        data[i]/=2;
            
      stbi_write_png("0000.png", x, y, 3, data, 0);
      
      return 0;
    }
    

    Ausgabe:

    Breite: 1
    Höhe: 1
    106
    119
    109
    255
    254
    127
    0
    0

    Die ersten drei Bytes des char-Arrays sind die RGB-Werte des Pixels, wie ich mich mich umgehend per GIMP überzeugen konnte... und was die übrigen fünf Bytes bedeutet sollte aus der Dokumentation von stb_image.h hervorgehen! Wenn ich die erläuternden Kommentare im Quelltext richtig verstanden habe, erzeugen alle Importfunktionen dieselbe Array-Struktur: Pixel mit Kanälen pro Pixel entsprechend des beim Laden ausgelesenen Wertes n (1 bis 4, also Graustufen, Graustufen plus Alpha, RGB oderRGB plus Alpha) und nach den Pixeln diese ominösen fünf Bytes - interessanterweise schwankt Byte Nr. 4 von Programmlauf zu Programmlauf zwischen 252 und 255!

    Das Schreiben ergibt (hier) wiederum ein einwandfreies PNG-File, die Farbwerte sind ebenfalls stabil - bzw. werdenin dieser Version korrekt halbiert! Was will man mehr?

    Bis bald im Khyberspace!

    Yadgar



  • @Yadgar Ist ja schön, dass das so simpel ist. Das wünscht man sich oft, wenn man einfach nur mit Pixeldaten hantieren will.

    Die "ominösen 5 Bytes" kann ich dir erklären: die 255 ist der Alpha-Wert des einen Pixel. 255 bedeutet "völlig deckend". Die anderen 4 Bytes kommen von dem

    s = sizeof(data);

    Damit bestimmst du nämlich die Größe des unsigned char* (Pointer) und der ist auf einem 64-Bit-System üblicherweise immer 8 😛

    Versuch es mal mit: s = x * y * n; ... das sollte dem was du vermutlich vorhast näher kommen.


Anmelden zum Antworten