PNG-Dateien in C++

Teil 1 von 3 der Serie Grafik

Dieses kurze Beispiel führt den Umgang mit der libpng vor. Es werden mehrere Dinge gezeigt:

  • Eine PNG-Datei wird geladen und gleichzeitig in das Format RGBA/8Bit umgewandet. Damit wird der Transformations-Code einfacher.
  • Jedes Pixel wird in einen Grauwert konvertiert (per gewichtetem Mittel).
  • Es wird ein Histogram über die Grauwerte erstellt (und auf die Konsole ausgegeben)
  • Wenn der Grauwert eines Pixels größer als ein Schwellenwert ist (den ich aus dem Histogram abgelesen habe) wird das Pixel in fast weiß umgewandelt, anderenfalls in fast schwarz
  • Abschliessend wird das Bild als RGBA/8Bit gespeichert
#include <stdlib.h>
#include <stdio.h>
#include <png.h>

void read_image(
        const char *filename,
        int& width,
        int& height,
        png_bytep*& image)
{
    FILE *fp = fopen(filename, "rb");

    png_byte color_type;
    png_byte bit_depth;

    png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    png_infop info = png_create_info_struct(png);
    (void)setjmp(png_jmpbuf(png));
    png_init_io(png, fp);
    png_read_info(png, info);

    width      = png_get_image_width(png, info);
    height     = png_get_image_height(png, info);
    color_type = png_get_color_type(png, info);
    bit_depth  = png_get_bit_depth(png, info);

    // Lese alle Color-Typen einheitlich im 8Bit-Tiefe, RGBA-Format
    // Siehe http://www.libpng.org/pub/png/libpng-manual.txt
    // BEGIN-TRANSFORM
    if(bit_depth == 16)
    {
        png_set_strip_16(png);
    }
    if(color_type == PNG_COLOR_TYPE_PALETTE)
    {
        png_set_palette_to_rgb(png);
    }

    // PNG_COLOR_TYPE_GRAY_ALPHA is always 8 or 16bit depth.
    if(color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
    {
        png_set_expand_gray_1_2_4_to_8(png);
    }
    if(png_get_valid(png, info, PNG_INFO_tRNS))
    {
        png_set_tRNS_to_alpha(png);
    }

    // Manche color-types haben keinen Alpha-Chanel => mit 0xff auffuellen
    if( color_type == PNG_COLOR_TYPE_RGB ||
        color_type == PNG_COLOR_TYPE_GRAY ||
        color_type == PNG_COLOR_TYPE_PALETTE)
    {
        png_set_filler(png, 0xFF, PNG_FILLER_AFTER);
    }

    if(color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
    {
        png_set_gray_to_rgb(png);
    }

    png_read_update_info(png, info);
    // END-TRANSFORM-TRANSFORM

    //image = (png_bytep*)malloc(sizeof(png_bytep) * height);
    image = new png_bytep[height]; // (png_bytep*)malloc(sizeof(png_bytep) * height);

    for(int y = 0; y < height; y++)
    {
        //image[y] = (png_byte*)malloc(png_get_rowbytes(png, info));
        image[y] = new png_byte[png_get_rowbytes(png, info)];
    }
    png_read_image(png, image);
    fclose(fp);
    png_destroy_read_struct(&png, &info, NULL);
}

void write_png_file(const char *filename, int width, int height, png_bytep*& image)
{
    int y;

    FILE *fp = fopen(filename, "wb");
    if(!fp)
    {
        fprintf(stderr, "kann %s nicht erzeugen [%s:%d]\n", filename, __FILE__, __LINE__);
        exit(1);
    }

    png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    png_infop info = png_create_info_struct(png);
    setjmp(png_jmpbuf(png));
    png_init_io(png, fp);

    // Output is 8bit depth, RGBA format.
    png_set_IHDR(
        png,
        info,
        width, height,
        8,
        PNG_COLOR_TYPE_RGBA,
        PNG_INTERLACE_NONE,
        PNG_COMPRESSION_TYPE_DEFAULT,
        PNG_FILTER_TYPE_DEFAULT
    );
    png_write_info(png, info);

    png_write_image(png, image);
    png_write_end(png, NULL);

    fclose(fp);
    png_destroy_write_struct(&png, &info);
}

void build_histogram(int width, int height, png_bytep* image, int histogram[])
{
    for(int y = 0; y < height; y++)
    {
        png_bytep row = image[y];
        for(int x = 0; x < width; x++)
        {
            png_bytep px = &(row[x * 4]);
            int r = px[0]; int g = px[1]; int b = px[2];
            int grey = b*0.0722+g*0.7152+r*0.2126;
            histogram[grey]++;
        }
    }
}

void transform_image(int width, int height, png_bytep* image, int treshold)
{
    for(int y = 0; y < height; y++)
    {
        png_bytep row = image[y];
        for(int x = 0; x < width; x++)
        {
            png_bytep px = &(row[x * 4]);
            int grey = px[2]*0.0722+px[1]*0.7152+px[0]*0.2126;
            px[0] = px[1] = px[2] = (grey > treshold ? 200 : 50);
        }
    }
}

void aufraeumen(int width, int height, png_bytep*& image)
{
    for(int i = 0; i< height; i++)
    {
        delete [] image[i];
    }
    delete [] image;
}

int main(int, char**)
{
    int width;
    int height;
    png_bytep* image;

    read_image("image.png", width, height, image);

    int histogram[256] = {0};
    build_histogram(width, height, image, histogram);
    transform_image(width, height, image, 34);
    write_png_file("sw.png", width, height, image);
    aufraeumen(width, height, image);

    // Histogrammdatei speichern und in Libreoffice oder Excel anschauen
    FILE* fp = fopen("histogramm.csv", "w");
    for(int i = 0; i < 256; i++)
    {
        fprintf(fp, "%d\n", histogram[i]);
    }
    fclose(fp);

    return 0;
}

Das Beispiel kann mit einem Standard-Makefile übersetzt werden.

Im Histogramm kann man sehen, dass der größte Teil der Punkte schwarz bzw. fast schwarz ist. Alle Graustuffen ab 1/3-Helligkeit kommen seltener als 200x vor.

Grauwerte-Histogram, log-Skalierung (Basis 10)

Selbstverständlich kann man ein beliebiges Bild laden. Ich habe ein selbstgerechnetes Mandelbrot-Bildchen verwendet.

Ein Blick schneller Blick mit den Augen kommt tatsächlich ohne viel Rechnerei auf das selbe Resultat: Fast schwarz. Aber was das Auge nicht sogut kann ist zu transformieren:

Series NavigationGaußscher Glättungs Kernel >>