Exporting PNG

Member
Posts: 277
Joined: 2004.10
Post: #1
Hello, I have been loading font glyph's for my new font library.

In short I've been using TGA and now I want to make the switch to PNG. However, I can't find any resources for writing custom raw buffers with PNG. Only reading in PNG files and then re-exporting them out.

I find libpng old and badly designed (setjmp Annoyed)

And so far my code crashes. Do you have any examples for custom raw buffer output into PNG?

Here is my code if you think you can tell me what's wrong.
Code:
void write_png(char *file_name)
{
   FILE *fp;
   png_structp png_ptr;
   png_infop info_ptr;

   /* open the file */
   fp = fopen(file_name, "wb");
   if (fp == NULL) {
       cout << "error opening file" << endl;
       return;
   }

  
   png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
      NULL, NULL, NULL);

   if (png_ptr == NULL)
   {
      fclose(fp);
      cout << "png_create_write_struct failed" << endl;
      return;
   }

   /* Allocate/initialize the image information data.  REQUIRED */
   info_ptr = png_create_info_struct(png_ptr);
   if (info_ptr == NULL)
   {
      fclose(fp);
      png_destroy_write_struct(&png_ptr,  png_infopp_NULL);
      cout << "png_create_info_struct failed" << endl;
      return;
   }

   if (setjmp(png_jmpbuf(png_ptr)))
   {
      /* If we get here, we had a problem with the file */
      fclose(fp);
      png_destroy_write_struct(&png_ptr, &info_ptr);
      cout << "png_destroy_write_struct failed" << endl;
      return;
   }

/* set up the output control if you are using standard C streams */
   png_init_io(png_ptr, fp);

   // set the header
   png_set_IHDR(png_ptr, info_ptr, 4, 4, 8, PNG_COLOR_TYPE_GRAY, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);


   // CRASHES HERE
   png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, png_voidp_NULL);

   png_byte *row_pointers[4];
   int x,y;
   for (y = 0; y < 4; y++) {
       for (x = 0; x < 4; x++) {
           row_pointers[y][x] = 255;    
       }
   }

   png_write_image(png_ptr, row_pointers);

   /* clean up after the write, and free any memory allocated */
   png_destroy_write_struct(&png_ptr, &info_ptr);

   /* close the file */
   fclose(fp);

   /* that's it */
   return;
}

Thanks!
Nathan

Global warming is caused by hobos and mooses
Quote this message in a reply
Luminary
Posts: 5,143
Joined: 2002.04
Post: #2
setjmp in libpng is not poor design, it's the right design, given it's limited to C.

The rest of the API, however... yuk :'(

Anyway, I've never used libpng to write out images (and you don't provide any details on the crash either). The libpng documentation is pretty good, containing detailed examples of all the uses of the library.

Failing that, using ImageIO to write out images is pretty darn easy, as long as you don't mind being Mac-only.
Quote this message in a reply
Member
Posts: 277
Joined: 2004.10
Post: #3
OneSadCookie Wrote:(and you don't provide any details on the crash either).

// CRASHES HERE
png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, png_voidp_NULL);


OneSadCookie Wrote:The libpng documentation is pretty good, containing detailed examples of all the uses of the library.

MadI can't find any that uses raw buffers, they always read in an image then write it out. (meaning the info_ptr is already setup by the read functions, which I think is why mine is crashing)


OneSadCookie Wrote:Failing that, using ImageIO to write out images is pretty darn easy, as long as you don't mind being Mac-only.

Being that I actively develop on both mac and windows I can't do that LOL

Global warming is caused by hobos and mooses
Quote this message in a reply
Luminary
Posts: 5,143
Joined: 2002.04
Post: #4
"crashes here" is not "details on the crash"
Quote this message in a reply
Member
Posts: 446
Joined: 2002.09
Post: #5
BinarySpike Wrote:// CRASHES HERE
png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, png_voidp_NULL);

I think png_write_png expects row_pointers to be filled inside the info struct.

I usually just do things without png_write_png:
Code:
png_write_info(png_ptr, info_ptr);
// fill row_pointers
png_write_image(png_ptr, row_pointers);
png_write_end(png_ptr, info_ptr);
Quote this message in a reply
Member
Posts: 277
Joined: 2004.10
Post: #6
OneSadCookie Wrote:"crashes here" is not "details on the crash"

It's an Access Violation, Null pointer, whatever. Inside the png_write_png function.


Frank C. Wrote:I think png_write_png expects row_pointers to be filled inside the info struct.

I don't quite understand how to use row_pointers Annoyed

char *rowPointers[height] IIRC is how it is setup, does this mean I allocate each row and do rowPointers[Y] = PTR_To_Row_1
?

Global warming is caused by hobos and mooses
Quote this message in a reply
Moderator
Posts: 1,560
Joined: 2003.10
Post: #7
Working libpng code from Fluid Paint. Hopefully will come in handy as a reference:
Code:
struct PNGImageAdaptorWriteCallbackInfo {
    void ** data;
    int * length;
    int index;
};

static void pngWriteFnMemoryBlock(png_structp pngWriteStruct, png_bytep data, png_size_t length) {
    struct PNGImageAdaptorWriteCallbackInfo * callbackInfo;
    
    callbackInfo = (struct PNGImageAdaptorWriteCallbackInfo *) png_get_io_ptr(pngWriteStruct);
    appendToBufferAndReallocIfNecessary(callbackInfo->data, callbackInfo->length, &callbackInfo->index, length, data);
}

static void pngFlushFnNoop(png_structp pngWriteStruct) {
}

+ (BOOL) writeDocument: (FPDocument *) inDocument toData: (void **) ioData length: (int *) ioLength typeHint: (NSString *) typeHint {
    png_structp pngWriteStruct;
    png_infop pngInfoStruct;
    png_byte ** rows;
    int rowIndex;
    struct PNGImageAdaptorWriteCallbackInfo callbackInfo;
    FPBitmapLayer * compositedImage;
    int numberOfLayers, layerIndex;
    FPColorComponent * pixels;
    
    pngWriteStruct = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    pngInfoStruct = png_create_info_struct(pngWriteStruct);
    
    if (setjmp(png_jmpbuf(pngWriteStruct))) {
        png_destroy_write_struct(&pngWriteStruct, &pngInfoStruct);
        return NO;
    }
    
    callbackInfo.data = ioData;
    callbackInfo.length = ioLength;
    callbackInfo.index = 0;
    png_set_write_fn(pngWriteStruct, &callbackInfo, pngWriteFnMemoryBlock, pngFlushFnNoop);
    
    png_set_IHDR(pngWriteStruct, pngInfoStruct, [inDocument width], [inDocument height], 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
    
    compositedImage = [[FPBitmapLayer alloc] initWithWidth: [inDocument width] height: [inDocument height]];
    numberOfLayers = [inDocument numberOfLayers];
    for (layerIndex = 0; layerIndex < numberOfLayers; layerIndex++) {
        [[inDocument layerAtIndex: layerIndex] copyOverLayer: compositedImage inRect: [[inDocument layerAtIndex: layerIndex] localBounds] operation: COMPOSITE_OPERATION_BLEND selection: nil negativeSource: nil];
    }
    
    pixels = [compositedImage pixels];
    rows = (png_byte **) malloc([compositedImage height] * sizeof(png_byte *));
    for (rowIndex = 0; rowIndex < [compositedImage height]; rowIndex++) {
        rows[rowIndex] = (pixels + (rowIndex * [compositedImage width] * 4));
    }
    
    png_set_rows(pngWriteStruct, pngInfoStruct, rows);
    png_write_png(pngWriteStruct, pngInfoStruct, PNG_TRANSFORM_IDENTITY, NULL);
    
    free(rows);
    [compositedImage release];
    png_destroy_write_struct(&pngWriteStruct, &pngInfoStruct);
    
    *ioLength = callbackInfo.index;
    
    return YES;
}
Quote this message in a reply
Member
Posts: 446
Joined: 2002.09
Post: #8
BinarySpike Wrote:I don't quite understand how to use row_pointers Annoyed

char *rowPointers[height] IIRC is how it is setup, does this mean I allocate each row and do rowPointers[Y] = PTR_To_Row_1
?
Yup - rowPointers are exactly what they sound like: pointers to the pixel data for each row of the image. Check out ThemsAllTook's example - if you want to use png_write_png you need to fill the row pointers and call png_set_rows first.
Quote this message in a reply
Member
Posts: 277
Joined: 2004.10
Post: #9
Thanks guys for your help!

Global warming is caused by hobos and mooses
Quote this message in a reply
Member
Posts: 567
Joined: 2004.07
Post: #10
Here's some code I wrote a while back; it's been twiddled with and it's in sdl/c++, but it should be a fairly straightforward 'port':
Code:
bool ImageWriter::writePNG(SDL_Surface *surface,
        const std::string &filename)
{
    // TODO Maybe someone can make this look nice?
    FILE *fp = fopen(filename.c_str(), "wb");
    if (!fp)
    {
        logger->log("could not open file %s for writing", filename.c_str());
        return false;
    }

    png_structp png_ptr;
    png_infop info_ptr;
    png_bytep *row_pointers;
    int colortype;

    if (SDL_MUSTLOCK(surface)) {
        SDL_LockSurface(surface);
    }

    png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
    if (!png_ptr)
    {
        logger->log("Had trouble creating png_structp");
        return false;
    }

    info_ptr = png_create_info_struct(png_ptr);
    if (!info_ptr)
    {
        png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
        logger->log("Could not create png_info");
        return false;
    }

    if (setjmp(png_jmpbuf(png_ptr)))
    {
        png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
        logger->log("problem writing to %s", filename.c_str());
        return false;
    }

    png_init_io(png_ptr, fp);

    colortype = (surface->format->BitsPerPixel == 24) ?
        PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA;

    png_set_IHDR(png_ptr, info_ptr, surface->w, surface->h, 8, colortype,
            PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);

    png_write_info(png_ptr, info_ptr);

    png_set_packing(png_ptr);

    row_pointers = new png_bytep[surface->h];
    if (!row_pointers)
    {
        logger->log("Had trouble converting surface to row pointers");
        return false;
    }

    for (int i = 0; i < surface->h; i++)
    {
        row_pointers[i] = (png_bytep)(Uint8 *)surface->pixels + i * surface->pitch;
    }

    png_write_image(png_ptr, row_pointers);
    png_write_end(png_ptr, info_ptr);

    fclose(fp);

    delete [] row_pointers;

    png_destroy_write_struct(&png_ptr, (png_infopp)NULL);

    if (SDL_MUSTLOCK(surface)) {
        SDL_UnlockSurface(surface);
    }

    return true;
}

It's not magic, it's Ruby.
Quote this message in a reply
Member
Posts: 277
Joined: 2004.10
Post: #11
Quick question, what is pitch?

Nayr Wrote:row_pointers[i] = (png_bytep)(Uint8 *)surface->pixels + i * surface->pitch;

Global warming is caused by hobos and mooses
Quote this message in a reply
Member
Posts: 567
Joined: 2004.07
Post: #12
something sdl-specific, I think. bytes-per-row, I think. That for loop is an sdl_surface-specific copy of memory.

It's not magic, it's Ruby.
Quote this message in a reply
Member
Posts: 277
Joined: 2004.10
Post: #13
Sorry to bother you guys again, but I can't figure out how to set row_pointers.

I have tried both png_write_image and png_write_png.
png_write_image executes fine, however no image is saved to file.
png_write_png crashes (access violation)

I'm not sure my row_pointers code is correct,
Here is my code:
Code:
#define width 4
#define height 4
#define depth 3
#define bits 8

void write_png(char *file_name)
{
    FILE *fp;
    png_structp png_ptr;
    png_infop info_ptr;

    fp = fopen(file_name, "wb");
    if (fp == NULL) {
        cout << "error initilizing file" << endl;
        return;
    }

    png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    if (png_ptr == NULL)
    {
        fclose(fp);
        cout << "png_create_write_struct failed" << endl;
        return;
    }

      info_ptr = png_create_info_struct(png_ptr);
    if (info_ptr == NULL)
    {
        fclose(fp);
         png_destroy_write_struct(&png_ptr,  png_infopp_NULL);
         cout << "png_create_info_struct failed" << endl;
         return;
    }

    if (setjmp(png_jmpbuf(png_ptr)))
    {
        fclose(fp);
        png_destroy_write_struct(&png_ptr, &info_ptr);
        cout << "png_destroy_write_struct failed" << endl;
        return;
    }

    png_init_io(png_ptr, fp);

    png_set_IHDR(png_ptr, info_ptr, width, height, bits,
        PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);

    png_set_packing(png_ptr);

    // setup row_pointers
    png_bytepp row_pointers; // (unsigned char**)

    row_pointers = new png_bytep[height];

    png_byte image[height*width*depth];

    // setup image
    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            for (int d = 0; d < depth; d++) {
                image[(y*width)+x+d] = 255;
            }
        }
    }

    // setup row_pointers
    for (int y = 0; y < height; y++) {
        row_pointers[y] = image+(y*(width*depth));
    }

    png_set_rows(png_ptr, info_ptr, row_pointers);

    // crashes
    png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);

    // executes fine, however creates no image
    png_write_image(png_ptr, row_pointers);

    png_destroy_write_struct(&png_ptr, &info_ptr);
    fclose(fp);
    return;
}

Thanks!

*edit*
Nayr: I looked it up, pitch is bytes per line. However, some video cards don't come out to the normal pitch that you would calculate.
Quote:http://www.gamedev.net/reference/program.../page2.asp
"The pitch contains the number of bytes per scan line for the surface. Video cards being what they are, this value is often not the same as the BytesPerPixel times the width."

Global warming is caused by hobos and mooses
Quote this message in a reply
Post Reply