Forum: PC-Programmierung C - libpng unter Windows verwenden


von Max M. (maxmicr)


Lesenswert?

Ich entwickle gerade ein kleines Grafikprogramm in C mit CLion unter 
Windows. Dieses soll später auf einen Controller portiert werden.
Da der Hauptaufwand allerdings die Software selber und nicht den 
Controller betrifft, wollte ich sie vorab auf dem PC schreiben um 
komfortabel debuggen zu können.

Zur Bildausgabe soll später ein LCD-Display dienen, jetzt soll das Bild 
aber erstmal in ein übliches Format wie z.B. png oder jpeg geschrieben 
werden. Dafür sammle ich in einem Buffer
1
const int buffer[W * H * 3 + 1]

alle Farbinformationen (RGB) für jeden Pixel. Nachdem das mit C 
anscheinend alles andere als trivial ist, bin ich durch googlen auf 
libpng gestoßen, dessen Sourcecode ich mir hier heruntergeladen habe: 
https://sourceforge.net/projects/libpng/files/libpng16/1.6.34/ 
(lpng1634.zip)

.zip-Datei entpackt und den Ordner in CLion als "Project Source" 
markiert. Da die Verwendung auch alles andere als trivial (für mich) 
ist, bin ich durch erneutes Googlen auf diesen Beispielcode gestoßen:

https://dev.w3.org/Amaya/libpng/example.c

Ich bin nur an der letzten Funktion write_png interessiert. Die hab 
ich mir in mein Projekt kopiert, allerdings werden selbst nach
1
#include "libpng/png.h"
2
#include "libpng/pnglibconf.h"

noch nicht alle Strukturen erkannt. Die pnglibconf hab ich im 
Unterordner scripts/pnglibconf.h.prebuilt entdeckt und hab sie prompt in 
*pnglibconf.h* umbenannt, damit sind einige Structs mehr erkannt worden.

Trotzdem kommt z.B. bei
1
png_set_IHDR(png_ptr, info_ptr, W, H, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);

die Fehlermeldung, dass W und H (sind Integer) nicht mit png_uint_32 
kompatibel wären, aber woher bekomme ich diesen typedef? Das selbe bei
1
FILE *fp = fopen(file_name, "wb");
2
png_init_io(png_ptr, fp);

hier ist png_FILE_p nicht mit FILE* kompatibel. Aber woher bekomme ich 
png_FILE_p?

Jetzt frag ich mich - woran liegt das? In dem Beispiel wurde nichts 
anderes außer eben *png.h* importiert. Es wird sich ja wohl in den 
letzten Monaten bzw. Jahren nicht so viel grundlegendes geändert haben, 
immerhin existiert diese Lib bereits seit über 20 Jahren.

Hat jemand Erfahrung mit libpng? Für jeden Tipp wäre ich dankbar.

Grüße

: Bearbeitet durch User
von Nop (Gast)


Lesenswert?

Max M. schrieb:

> die Fehlermeldung, dass W und H (sind Integer) nicht mit png_uint_32
> kompatibel wären, aber woher bekomme ich diesen typedef?

Aus pngconf.h, Zeile 510?
1
#if UINT_MAX > 4294967294U
2
   typedef unsigned int png_uint_32;
3
#elif ULONG_MAX > 4294967294U
4
   typedef unsigned long int png_uint_32;
5
#else
6
#  error "libpng requires an unsigned 32-bit (or more) type"
7
#endif

> Aber woher bekomme ich png_FILE_p?

Aus pngconf.h, Zeile 595?
1
#ifdef PNG_STDIO_SUPPORTED
2
typedef FILE            * png_FILE_p;
3
#endif

Das erschließt sich einem übrigens, wenn man z.B. mit Notepad++ einfach 
mal alle *.h-Dateien nach den fraglichen Typen durchsucht. Linuxer 
bekämen das auch mit grep hin.

von Max M. (maxmicr)


Lesenswert?

Danke dir, das hat schon sehr geholfen. Leider hab ich immer noch ein 
paar Fragezeichen offen.

1. Ich verstehe diesen Codeabschnitt nicht:
1
    png_uint_32 k, height, width;
2
    png_byte image[height][width];
3
    png_bytep row_pointers[height];
4
    for (k = 0; k < height; k++)
5
        row_pointers[k] = image + k*width;

Was genau soll bei der Addition eines 2D-Arrays mit einem Integer-Wert 
herauskommen? Noch dazu sagt mir CLion:
1
Incompatible pointer types png_bytep and png_byte[height][width]

2. Auch im nächsten Abschnitt verstehe ich nicht, wie ich hier meinen 
"Bildbuffer" unterbringe, wo schreibe ich konkret meine R, G und B 
Werte?
1
    /* write out the image data by one or more scanlines */
2
    /* The number of passes is either 1 for non-interlaced images,
3
     * or 7 for interlaced images.
4
     */
5
    for (pass = 0; pass < number_passes; pass++)
6
    {
7
        /* Write a few rows at a time. */
8
        png_write_rows(png_ptr, &row_pointers[first_row], number_of_rows);
9
10
        /* If you are only writing one row at a time, this works */
11
        for (y = 0; y < height; y++)
12
        {
13
            png_write_rows(png_ptr, &row_pointers[y], 1);
14
        }
15
    }

: Bearbeitet durch User
von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Max M. schrieb:
> Was genau soll bei der Addition eines 2D-Arrays mit einem Integer-Wert
> herauskommen?

Das ist elementares C.
1
row_pointers[k] = image + k*width;

In diesem Kontext ist "image" gleichbedeutend mit einem Pointer auf das 
erste Arrayelement.

Addiert man einen Integer zu diesem Pointer, bekommt man einen Pointer 
auf das dem Integerwert entsprechende Arrayelement, bei 4 also das 
fünfte Arrayelement.

von Nop (Gast)


Lesenswert?

Max M. schrieb:

> Was genau soll bei der Addition eines 2D-Arrays mit einem Integer-Wert
> herauskommen?

Dazu müßtest Du Dich ein wenig mit Pointerarithmetik in C befassen, mit 
Arrays vs. Pointern und mit 2D-Arrays. Das ist durchaus tricky.

> 2. Auch im nächsten Abschnitt verstehe ich nicht, wie ich hier meinen
> "Bildbuffer" unterbringe

Dazu müßtest Du Dir die Beschreibung der API-Funktionen mal durchlesen. 
Das README-File ist da wohl der beste Einstieg.

von Max M. (maxmicr)


Lesenswert?

Ich versteh den Sinn nicht, warum man bereits vorhandene Datentypen 
nochmal neu anlegt, nur um ein png im Namen zu haben?

Das zwingt mich doch dazu, mein gesamtes Programm auf die Datentypen von 
libpng umzuschreiben, und die Lib verwende ich später auf dem Controller 
nicht.

Gibt es vielleicht eine einfachere Möglichkeit, ein Bild auf dem PC mit 
C zu erzeugen? Ehrlich gesagt übersteigt die Bibliothek meine 
C-Fähigkeiten und es scheint mir, als wäre ein großes Maß an Verständnis 
für die Zusammenhänge bzw. über die generelle Struktur einer PNG-Datei 
notwendig.

Vor 7 Stunden dachte ich naiver Weise, dass das Thema in einer halben 
Stunde gegessen ist...

: Bearbeitet durch User
von Nop (Gast)


Lesenswert?

Naja einfach ist das nicht. Da wirst Du Dir erstmal Tutorials durchlesen 
müssen, beispielsweise hier:

http://www.labbookpages.co.uk/software/imgProc/libPNG.html
http://zarb.org/~gc/html/libpng.html

Worüber ich mich wundere: wenn Du später auf einem Controller mit PNGs 
arbeiten willst, wie willst Du das eigentlich auf dem Controller 
erreichen, ohne libPNG dort auch zu haben? PNG ist mit den Optionen zur 
Komprimierung usw. nicht trivial.

von Nop (Gast)


Lesenswert?

Max M. schrieb:

> Gibt es vielleicht eine einfachere Möglichkeit, ein Bild auf dem PC mit
> C zu erzeugen?

Ja, natürlich, indem Du nicht PNG als Format verwendest, sondern z.B. 
BMP. Das hat keine Komprimierung und ist daher viel simpler zu 
handhaben. Logischerweise sind die Bilddateien dann bei selbem 
Bildinhalt viel größer.

von Nop (Gast)


Lesenswert?

Guck Dir mal das hier an:

http://ricardolovelace.com/creating-bitmap-images-with-c-on-windows.html

Das sieht recht simpel aus. Man sollte nur die Datentypen mal 
geradeziehen, also statt
1
unsigned int size_header;  // 4 bytes
lieber
1
uint32_t     size_header;  // 4 bytes
verwenden.

von Max M. (maxmicr)


Lesenswert?

Nop schrieb:
> Worüber ich mich wundere: wenn Du später auf einem Controller mit PNGs
> arbeiten willst, wie willst Du das eigentlich auf dem Controller
> erreichen, ohne libPNG dort auch zu haben? PNG ist mit den Optionen zur
> Komprimierung usw. nicht trivial.

Wie ich im ersten Beitrag geschrieben habe, erfolgt die Ausgabe auf dem 
Controller über ein LCD Display.

Nop schrieb:
> Ja, natürlich, indem Du nicht PNG als Format verwendest, sondern z.B.
> BMP. Das hat keine Komprimierung und ist daher viel simpler zu
> handhaben. Logischerweise sind die Bilddateien dann bei selbem
> Bildinhalt viel größer.

Danke, das werde ich mir mal ansehen.

Nop schrieb:
> Guck Dir mal das hier an:

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Nop schrieb:
> a, natürlich, indem Du nicht PNG als Format verwendest, sondern z.B.
> BMP.

BMP ist ein Containerformat und kann sowohl unkomprimierte als auch 
komprimierte Daten enthalten.

Einfacher zu handhaben ist GIF, da sind mittlerweile auch die letzten 
Patente ausgelaufen, und wenn man mit der 256-Farben-Beschränkung leben 
kann, ist das eine deutlich simplere Alternative zu PNG.

von Rolf M. (rmagnus)


Lesenswert?

Max M. schrieb:
> Nop schrieb:
>> Worüber ich mich wundere: wenn Du später auf einem Controller mit PNGs
>> arbeiten willst, wie willst Du das eigentlich auf dem Controller
>> erreichen, ohne libPNG dort auch zu haben? PNG ist mit den Optionen zur
>> Komprimierung usw. nicht trivial.
>
> Wie ich im ersten Beitrag geschrieben habe, erfolgt die Ausgabe auf dem
> Controller über ein LCD Display.

Und das Display kann direkt mit PNG-Dateien umgehen? Die Frage war doch, 
wie du auf dem µC die PNG-Dateien handhaben willst, wo du vermutlich 
keine libpng zur Verfügung hast.

> Nop schrieb:
>> Ja, natürlich, indem Du nicht PNG als Format verwendest, sondern z.B.
>> BMP. Das hat keine Komprimierung und ist daher viel simpler zu
>> handhaben. Logischerweise sind die Bilddateien dann bei selbem
>> Bildinhalt viel größer.
>
> Danke, das werde ich mir mal ansehen.

Alternativ könnte noch XPM interessant sein. Das besteht schon aus 
C-Code, den man direkt in sein Programm einbinden kann.
https://de.wikipedia.org/wiki/X_PixMap

von Max M. (maxmicr)


Lesenswert?

Rolf M. schrieb:
> Und das Display kann direkt mit PNG-Dateien umgehen? Die Frage war doch,
> wie du auf dem µC die PNG-Dateien handhaben willst, wo du vermutlich
> keine libpng zur Verfügung hast.

Ganz einfach, ich habe keine PNG-Dateien auf dem µC. Das ist ein 
SPI-Display, also schreibe ich die Pixeldaten über den SPI Bus in den 
Puffer des Displays. Da war ich wohl etwas ungenau in der Beschreibung.

Das hier funktioniert nun:
1
#include <stdio.h>
2
#include <mem.h>
3
#include "image.h"
4
5
void save_bitmap(const char *file_name, const unsigned int width, const unsigned int height, const unsigned int dpi, const int *buffer,
6
                 int samples) {
7
// create a file object that we will use to write our image
8
    FILE *image;
9
// we want to know how many pixels to reserve
10
    unsigned int image_size = width * height;
11
// a byte is 4 bits but we are creating a 24 bit image so we can represent a pixel with 3
12
// our final file size of our image is the width * height * 4 + size of bitmap header
13
    unsigned int file_size = 54 + 4 * image_size;
14
// pixels per meter https://www.wikiwand.com/en/Dots_per_inch
15
    unsigned int ppm = (unsigned int)(dpi * 39.375);
16
17
    struct bitmap_file_header {
18
        unsigned char   bitmap_type[2];     // 2 bytes
19
        int             file_size;          // 4 bytes
20
        short           reserved1;          // 2 bytes
21
        short           reserved2;          // 2 bytes
22
        unsigned int    offset_bits;        // 4 bytes
23
    } bfh;
24
25
// bitmap image header (40 bytes)
26
    struct bitmap_image_header {
27
        unsigned int    size_header;        // 4 bytes
28
        unsigned int    width;              // 4 bytes
29
        unsigned int    height;             // 4 bytes
30
        short int       planes;             // 2 bytes
31
        short int       bit_count;          // 2 bytes
32
        unsigned int    compression;        // 4 bytes
33
        unsigned int    image_size;         // 4 bytes
34
        unsigned int    ppm_x;              // 4 bytes
35
        unsigned int    ppm_y;              // 4 bytes
36
        unsigned int    clr_used;           // 4 bytes
37
        unsigned int    clr_important;      // 4 bytes
38
    } bih;
39
40
// if you are on Windows you can include <windows.h>
41
// and make use of the BITMAPFILEHEADER and BITMAPINFOHEADER structs
42
43
    memcpy(&bfh.bitmap_type, "BM", 2);
44
    bfh.file_size       = file_size;
45
    bfh.reserved1       = 0;
46
    bfh.reserved2       = 0;
47
    bfh.offset_bits     = 0;
48
49
    bih.size_header     = sizeof(bih);
50
    bih.width           = width;
51
    bih.height          = height;
52
    bih.planes          = 1;
53
    bih.bit_count       = 24;
54
    bih.compression     = 0;
55
    bih.image_size      = file_size;
56
    bih.ppm_x           = ppm;
57
    bih.ppm_y           = ppm;
58
    bih.clr_used        = 0;
59
    bih.clr_important   = 0;
60
61
    image = fopen(file_name, "wb");
62
63
// compiler woes so we will just use the constant 14 we know we have
64
    fwrite(&bfh, 1, 14, image);
65
    fwrite(&bih, 1, sizeof(bih), image);
66
67
// write out pixel data, one last important this to know is the ordering is backwards
68
// we have to go BGR as opposed to RGB
69
    int i = 0;
70
    for(unsigned y = 0; y < height; y++)
71
        for(unsigned x = 0; x < width; x++) {
72
            char r = (char)(buffer[i * 3 + 0] / samples);
73
            char g = (char)(buffer[i * 3 + 1] / samples);
74
            char b = (char)(buffer[i * 3 + 2] / samples);
75
76
            char color[] = {b,g,r};
77
78
            fwrite(color, 1, sizeof(color), image);
79
            i++;
80
        }
81
82
    fclose(image);
83
}

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

Max M. schrieb:
> Rolf M. schrieb:
>> Und das Display kann direkt mit PNG-Dateien umgehen? Die Frage war doch,
>> wie du auf dem µC die PNG-Dateien handhaben willst, wo du vermutlich
>> keine libpng zur Verfügung hast.
>
> Ganz einfach, ich habe keine PNG-Dateien auf dem µC. Das ist ein
> SPI-Display, also schreibe ich die Pixeldaten über den SPI Bus in den
> Puffer des Displays. Da war ich wohl etwas ungenau in der Beschreibung.

Das geht aber immer noch an der Frage vorbei, denn wo kommen die 
Pixeldaten denn her?

Max M. schrieb:
> struct bitmap_file_header {
>         unsigned char   bitmap_type[2];     // 2 bytes
>         int             file_size;          // 4 bytes
>         short           reserved1;          // 2 bytes
>         short           reserved2;          // 2 bytes
>         unsigned int    offset_bits;        // 4 bytes
>     } bfh;

Ich würde hier die Standard-Typedef aus stdint.h empfehlen. Außerdem 
kann es zu Alignment-Problemen kommen, da offset_bits nicht an einer 
4-Byte-Grenze ausgerichtet ist. Dazu kommt dann noch ggf. das Thema 
Endianness.

von Nop (Gast)


Lesenswert?

Rufus Τ. F. schrieb:

> BMP ist ein Containerformat und kann sowohl unkomprimierte als auch
> komprimierte Daten enthalten.

Im simpelsten Fall hat man unkomprimierte Daten mit je 8 bit R, G und B 
pro Pixel. Das dürfte einfacher sein, als sich mit Paletten und 
Paletenindizes herumzuschlagen.

Besonders, wo der OP später mit einem Display wohl auch einfach nur 
Bildbufferdaten übertragen wird, da ist unkomprimiertes BMP schon sehr 
nahe dran. Es ging schließlich für den PC jetzt nur darum, möglichst 
einfach einen Buffer in C manipulieren zu können und den als Bild 
wegzuschreiben.


Rolf M. schrieb:
> Ich würde hier die Standard-Typedef aus stdint.h empfehlen.

Ja, das hatte ich auch schon angemerkt, als ich das Codebeispiel 
berlinkt hatte.

> Außerdem kann es zu Alignment-Problemen kommen, da offset_bits nicht
> an einer 4-Byte-Grenze ausgerichtet ist.

Guter Punkt. Zumindest sollte das struct auf dem PC besser "packed" 
sein, sonst wird da u.U. Unsinn weggeschrieben auch bei der Dateigröße.

> Dazu kommt dann noch ggf. das Thema Endianness.

Auf dem PC sollte das kein Problem sein, und das wird ja als Dateiformat 
offenbar auch nur auf dem PC gebraucht.

von Max M. (maxmicr)


Lesenswert?

Nop schrieb:
> sonst wird da u.U. Unsinn weggeschrieben

Ich hab das Beispiel ausprobiert: 
https://stackoverflow.com/questions/2654480/writing-bmp-image-in-pure-c-c-without-other-libraries 
und bei mir sieht es genauso aus, heißt das nun, dass alles 
funktioniert?

Nop schrieb:
> Es ging schließlich für den PC jetzt nur darum

Exakt.

Rolf M. schrieb:
> Das geht aber immer noch an der Frage vorbei, denn wo kommen die
> Pixeldaten denn her?

Die erzeugt das Programm.

von Rolf M. (rmagnus)


Lesenswert?

Max M. schrieb:
> olf M. schrieb:
>> Das geht aber immer noch an der Frage vorbei, denn wo kommen die
>> Pixeldaten denn her?
>
> Die erzeugt das Programm.

Ja und dann!?
Das erinnert mich irgendwie an die Formel zum Profit von den "underpants 
gnomes" aus South Park:

1. Unterhosen einsammeln
2. ?
3. Profit

Also, wir haben:

1. Ein PC-Programm erzeugt eine Bilddatei
2. ?
3. Die Pixel der Bilddatei gehen per SPI ans Display

Also die Frage nochmal anders gestellt: Wie kommt die Datei zum µC, so 
dass er sie per SPI senden kann?

von Max M. (maxmicr)


Lesenswert?

Rolf M. schrieb:
> Wie kommt die Datei zum µC, so
> dass er sie per SPI senden kann?

Die Pixeldaten werden im Controller erzeugt und auf einem Display, dass 
per SPI angesteuert wird, angezeigt. Es gibt keine Datei im µC, die 
Datei will ich lediglich auf dem PC haben, um zu testen, ob mein 
Programm korrekt funktioniert.
PC-Programm und Controller sind in keiner Weise verbunden oder gehören 
zusammen. Am PC teste ich die Software und später läuft sie auf dem 
Controller (deswegen tue ich mir C auf dem PC auch an).

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

Max M. schrieb:
> Rolf M. schrieb:
>> Wie kommt die Datei zum µC, so
>> dass er sie per SPI senden kann?
>
> Die Pixeldaten werden im Controller erzeugt und auf einem Display, dass
> per SPI angesteuert wird, angezeigt. Es gibt keine Datei im µC, die
> Datei will ich lediglich auf dem PC haben, um zu testen, ob mein
> Programm korrekt funktioniert.

Ach so. Ok, ich dachte, das PC-Programm soll die Datei erzeugen, und der 
µC soll sie dann ans Display schicken.

> PC-Programm und Controller sind in keiner Weise verbunden oder gehören
> zusammen. Am PC teste ich die Software und später läuft sie auf dem
> Controller (deswegen tue ich mir C auf dem PC auch an).

Ah, jetzt verstehe ich. Dann dürfte ein RGB-BMP tatsächlich das 
einfachste für dich sein.

von Nop (Gast)


Lesenswert?

Max M. schrieb:

> Ich hab das Beispiel ausprobiert:
> https://stackoverflow.com/questions/2654480/writin...
> und bei mir sieht es genauso aus, heißt das nun, dass alles
> funktioniert?

Wenn Du eines der Beispiele gewählt hast, wo diese Header vom Typ 
unsigned char sind, und wo z.B. Filesize dann geshiftet und stückweise 
reingepackt wird, dann sollte das gut sein.

Ein wichtiger Tip war da noch, daß die Zeilenlänge in Bytes durch vier 
teilbar sein muß. Das ist in den Antworten, wo mit der Variable 
"extrabytes" gearbeitet wird, aber auch schon bedacht.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Max M. schrieb:
> Die Pixeldaten werden im Controller erzeugt und auf einem Display, dass
> per SPI angesteuert wird, angezeigt.

(das, nicht "dass")

Warum aber müssen die Pixeldaten dann im PNG-Format vorliegen? Das musst 
Du ja erst mal mit libpng erzeugen ... und dann gleich wieder 
decodieren, um es ans Display senden zu können.

von Nop (Gast)


Lesenswert?

Rufus Τ. F. schrieb:

> Warum aber müssen die Pixeldaten dann im PNG-Format vorliegen?

Gar nicht. Der OP wollte einfach nur auf dem PC einen Bildbuffer im RAM 
haben, den mit ein paar C-Routinen manipulieren (was er auch auf dem 
Controller dann tun wird) und auf dem PC in irgendeinem Bildformat 
ausgeben, mit dem er sich das Ergebnis der Buffer-Manipulationen auf dem 
PC schonmal angucken kann.

PNG nur deswegen, weil es einfach ein verbreitetes Format ist und dem OP 
daher als Möglichkeit eingefallen ist, und weil es libpng zum 
Wegschreiben gibt - nur daß das dann wesentlich komplizierter war als 
gedacht.

von Max M. (maxmicr)


Lesenswert?

Danke Nop, anscheinend hast du das als Einziger verstanden :)

Ich dachte, ich hätte es verständlich erklärt.

von Rolf M. (rmagnus)


Lesenswert?

Max M. schrieb:
> Danke Nop, anscheinend hast du das als Einziger verstanden :)

Einspruch: Ich hab's inzwischen auch verstanden. ;-)

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.