diff --git a/src/Makefile b/src/Makefile index 1c185592..32bbf40a 100644 --- a/src/Makefile +++ b/src/Makefile @@ -809,6 +809,7 @@ OBJS = main.o init.o graph.o $(APIOBJ) misc.o special.o \ windows.o brush.o realpath.o mountlist.o input.o hotkeys.o \ transform.o pversion.o factory.o $(PLATFORMOBJ) \ loadsave.o loadsavefuncs.o \ + pngformat.o \ fileformats.o miscfileformats.o libraw2crtc.o \ brush_ops.o buttons_effects.o layers.o \ oldies.o tiles.o colorred.o unicode.o gfx2surface.o \ @@ -820,6 +821,7 @@ endif TESTSOBJS = $(patsubst %.c,%.o,$(wildcard tests/*.c)) \ miscfileformats.o fileformats.o oldies.o libraw2crtc.o \ loadsavefuncs.o packbits.o tifformat.o c64load.o 6502.o \ + pngformat.o \ op_c.o colorred.o \ unicode.o \ io.o realpath.o version.o pversion.o \ diff --git a/src/fileformats.c b/src/fileformats.c index c870c7ef..f6fb382e 100644 --- a/src/fileformats.c +++ b/src/fileformats.c @@ -32,32 +32,11 @@ #endif #include #ifndef __no_pnglib__ +// just for png_sig_cmp() #include -#if !defined(PNG_HAVE_PLTE) -#define PNG_HAVE_PLTE 0x02 #endif -#if (PNG_LIBPNG_VER_MAJOR <= 1) && (PNG_LIBPNG_VER_MINOR < 4) - // Compatibility layer to allow us to use libng 1.4 or any older one. - - // This function is renamed in 1.4 - #define png_set_expand_gray_1_2_4_to_8(x) png_set_gray_1_2_4_to_8(x) - - // Wrappers that are mandatory in 1.4. Older version allowed direct access. - #define png_get_rowbytes(png_ptr,info_ptr) ((info_ptr)->rowbytes) - #define png_get_image_width(png_ptr,info_ptr) ((info_ptr)->width) - #define png_get_image_height(png_ptr,info_ptr) ((info_ptr)->height) - #define png_get_bit_depth(png_ptr,info_ptr) ((info_ptr)->bit_depth) - #define png_get_color_type(png_ptr,info_ptr) ((info_ptr)->color_type) -#endif -#endif - -#ifndef png_jmpbuf -# define png_jmpbuf(png_ptr) ((png_ptr)->jmpbuf) -#endif - #include -#include #if defined(WIN32) #if defined(_MSC_VER) #include @@ -6360,746 +6339,3 @@ void Save_XPM(T_IO_Context* context) fclose(file); } - -//////////////////////////////////// PNG //////////////////////////////////// -/** - * @defgroup PNG PNG format - * @ingroup loadsaveformats - * Portable Network Graphics - * - * We make use of libpng : http://www.libpng.org/pub/png/libpng.html - * @{ - */ - -#ifndef __no_pnglib__ - -/// Test for PNG format -/// -/// The 8 byte signature at the start of file is tested -void Test_PNG(T_IO_Context * context, FILE * file) -{ - byte png_header[8]; - - (void)context; - File_error=1; - - // Lecture du header du fichier - if (Read_bytes(file,png_header,8)) - { - if ( !png_sig_cmp(png_header, 0, 8)) - File_error=0; - } -} - -/// Callback to handle our private chunks -/// -/// We have one private chunk at the moment : -/// - "crNg" which is similar to a CRNG chunk in an IFF file -static int PNG_read_unknown_chunk(png_structp ptr, png_unknown_chunkp chunk) -{ - T_IO_Context * context; - // png_unknown_chunkp members: - // png_byte name[5]; - // png_byte *data; - // png_size_t size; - - context = (T_IO_Context *)png_get_user_chunk_ptr(ptr); - - GFX2_Log(GFX2_DEBUG, "PNG private chunk '%s' :\n", chunk->name); - GFX2_LogHexDump(GFX2_DEBUG, "", chunk->data, 0, chunk->size); - - if (!strcmp((const char *)chunk->name, "crNg")) - { - unsigned int i; - const byte *chunk_ptr = chunk->data; - - // Should be a multiple of 6 - if (chunk->size % 6) - return (-1); - - - for(i=0;isize/6 && i<16; i++) - { - word rate; - word flags; - byte min_col; - byte max_col; - - // Rate (big-endian word) - rate = *(chunk_ptr++) << 8; - rate |= *(chunk_ptr++); - - // Flags (big-endian) - flags = *(chunk_ptr++) << 8; - flags |= *(chunk_ptr++); - - // Min color - min_col = *(chunk_ptr++); - // Max color - max_col = *(chunk_ptr++); - - // Check validity - if (min_col != max_col) - { - // Valid cycling range - if (max_colCycle_range[i].Start=min_col; - context->Cycle_range[i].End=max_col; - context->Cycle_range[i].Inverse=(flags&2)?1:0; - context->Cycle_range[i].Speed=(flags&1) ? rate/78 : 0; - - context->Color_cycles=i+1; - } - } - - return (1); // >0 = success - } - return (0); /* did not recognize */ - -} - - -/// Private structure used in PNG_memory_read() and PNG_memory_write() -struct PNG_memory_buffer { - char * buffer; - unsigned long offset; - unsigned long size; -}; - -/// read from memory buffer -static void PNG_memory_read(png_structp png_ptr, png_bytep p, png_size_t count) -{ - struct PNG_memory_buffer * buffer = (struct PNG_memory_buffer *)png_get_io_ptr(png_ptr); - GFX2_Log(GFX2_DEBUG, "PNG_memory_read(%p, %p, %u) (io_ptr=%p)\n", png_ptr, p, count, buffer); - if (buffer == NULL || p == NULL) - return; - if (buffer->offset + count <= buffer->size) - { - memcpy(p, buffer->buffer + buffer->offset, count); - buffer->offset += count; - } - else - { - unsigned long available_count = buffer->size - buffer->offset; - GFX2_Log(GFX2_DEBUG, "PNG_memory_read(): only %lu bytes available\n", available_count); - if (available_count > 0) - { - memcpy(p, buffer->buffer + buffer->offset, available_count); - buffer->offset += available_count; - } - } -} - - -/// Read PNG format file -void Load_PNG_Sub(T_IO_Context * context, FILE * file, const char * memory_buffer, unsigned long memory_buffer_size) -{ - png_structp png_ptr; - png_infop info_ptr = NULL; - - // Prepare internal PNG loader - png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); - if (png_ptr) - { - // Prepare internal PNG loader - info_ptr = png_create_info_struct(png_ptr); - if (info_ptr) - { - png_byte color_type; - png_byte bit_depth; - byte bpp; - struct PNG_memory_buffer buffer; - - // Setup a return point. If a pnglib loading error occurs - // in this if(), the else will be executed. - if (!setjmp(png_jmpbuf(png_ptr))) - { - // to read from memory, I need to use png_set_read_fn() instead of calling png_init_io() - if (file != NULL) - png_init_io(png_ptr, file); - else - { - buffer.buffer = (char *)memory_buffer; - buffer.offset = 8; // skip header - buffer.size = memory_buffer_size; - png_set_read_fn(png_ptr, &buffer, PNG_memory_read); - } - // Inform pnglib we already loaded the header. - png_set_sig_bytes(png_ptr, 8); - - // Hook the handler for unknown chunks - png_set_read_user_chunk_fn(png_ptr, (png_voidp)context, &PNG_read_unknown_chunk); - - // Load file information - png_read_info(png_ptr, info_ptr); - color_type = png_get_color_type(png_ptr,info_ptr); - bit_depth = png_get_bit_depth(png_ptr,info_ptr); - - switch (color_type) - { - case PNG_COLOR_TYPE_GRAY_ALPHA: - // no more than 8bpp or else we enable true color picture loading - bpp = MIN(8, bit_depth * 2); - break; - case PNG_COLOR_TYPE_RGB: - bpp = bit_depth * 3; - break; - case PNG_COLOR_TYPE_RGB_ALPHA: - bpp = bit_depth * 4; - break; - case PNG_COLOR_TYPE_PALETTE: - case PNG_COLOR_TYPE_GRAY: - default: - // no more than 8bpp or else we enable true color picture loading - bpp = MIN(8, bit_depth); - } - GFX2_Log(GFX2_DEBUG, "PNG type=%u bit_depth=%u : %ubpp\n", color_type, bit_depth, bpp); - - // If it's any supported file - // (Note: As of writing this, this test covers every possible - // image format of libpng) - if (color_type == PNG_COLOR_TYPE_PALETTE - || color_type == PNG_COLOR_TYPE_GRAY - || color_type == PNG_COLOR_TYPE_GRAY_ALPHA - || color_type == PNG_COLOR_TYPE_RGB - || color_type == PNG_COLOR_TYPE_RGB_ALPHA - ) - { - enum PIXEL_RATIO ratio = PIXEL_SIMPLE; - int num_text; - png_text *text_ptr; - - int unit_type; - png_uint_32 res_x; - png_uint_32 res_y; - - // Comment (tEXt) - context->Comment[0]='\0'; // Clear the previous comment - if ((num_text=png_get_text(png_ptr, info_ptr, &text_ptr, NULL))) - { - while (num_text--) - { - int size = COMMENT_SIZE; -#ifdef PNG_iTXt_SUPPORTED - size_t length = (text_ptr[num_text].compression >= 1) ? text_ptr[num_text].itxt_length : text_ptr[num_text].text_length; -#else - size_t length = text_ptr[num_text].text_length; - if (text_ptr[num_text].compression >= 1) - continue; // skip iTXt -#endif - if (length > 0 && length < COMMENT_SIZE) - size = (int)length; - GFX2_Log(GFX2_DEBUG, "PNG Text %d \"%s\" (%lu bytes): %.*s\n", - text_ptr[num_text].compression, text_ptr[num_text].key, - (unsigned long)length, - (int)MIN(length, 160), text_ptr[num_text].text); - if (strcmp(text_ptr[num_text].key,"Title") == 0) - { - strncpy(context->Comment, text_ptr[num_text].text, size); - context->Comment[size]='\0'; - break; // Skip all others tEXt chunks - } - else if(strcmp(text_ptr[num_text].key, "Comment") == 0) - { - strncpy(context->Comment, text_ptr[num_text].text, size); - context->Comment[size]='\0'; - break; // Skip all others tEXt chunks - } - } - } - // Pixel Ratio (pHYs) - if (png_get_pHYs(png_ptr, info_ptr, &res_x, &res_y, &unit_type)) - { - // Ignore unit, and use the X/Y ratio as a hint for - // WIDE or TALL pixels - if (res_x>0 && res_y>0) - { - GFX2_Log(GFX2_DEBUG, "PNG pHYs unit %d %dx%d\n", unit_type, res_x, res_y); - if (unit_type == 1) - GFX2_Log(GFX2_DEBUG, " %dx%d DPI\n", (res_x * 254 + 5000) / 10000, (res_y * 254 + 5000) / 10000); - if (res_y * 10 > res_x * 12) // X/Y < 1/1.2 - ratio = PIXEL_WIDE; - else if (res_x * 10 > res_y * 12) // X/Y > 1.2 - ratio = PIXEL_TALL; - } - } - Pre_load(context, - png_get_image_width(png_ptr, info_ptr), - png_get_image_height(png_ptr, info_ptr), - file != NULL ? File_length_file(file) : memory_buffer_size, - FORMAT_PNG, ratio, bpp); - - if (File_error==0) - { - int x,y; - png_colorp palette; - int num_palette; - png_bytep * Row_pointers = NULL; - byte row_pointers_allocated = 0; - int num_trans; - png_bytep trans; - png_color_16p trans_values; - - // 16-bit images - if (bit_depth == 16) - { - // Reduce to 8-bit - png_set_strip_16(png_ptr); - } - else if (bit_depth < 8) - { - // Inform libpng we want one byte per pixel, - // even though the file was less than 8bpp - png_set_packing(png_ptr); - } - - // Images with alpha channel - if (color_type & PNG_COLOR_MASK_ALPHA) - { - // Tell libpng to ignore it - png_set_strip_alpha(png_ptr); - } - - // Greyscale images : - if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) - { - // Map low bpp greyscales to full 8bit (0-255 range) - if (bit_depth < 8) - { -#if (PNG_LIBPNG_VER_MAJOR <= 1) && (PNG_LIBPNG_VER_MINOR < 4) - // Works well with png 1.2.8, but deprecated in 1.4 ... - png_set_gray_1_2_4_to_8(png_ptr); -#else - // ...where this seems to replace it: - png_set_expand_gray_1_2_4_to_8(png_ptr); -#endif - } - - // Create greyscale palette - for (x=0;x<256;x++) - { - context->Palette[x].R=x; - context->Palette[x].G=x; - context->Palette[x].B=x; - } - } - else if (color_type == PNG_COLOR_TYPE_PALETTE) // Palette images - { - if (bit_depth < 8) - { - // Clear unused colors - if (Config.Clear_palette) - memset(context->Palette,0,sizeof(T_Palette)); - } - // Get a pointer to the PNG palette - png_get_PLTE(png_ptr, info_ptr, &palette, - &num_palette); - // Copy all colors to the context - for (x=0;xPalette[x].R=palette[x].red; - context->Palette[x].G=palette[x].green; - context->Palette[x].B=palette[x].blue; - } - // The palette must not be freed: it is owned by libpng. - palette = NULL; - } - // Transparency (tRNS) - if (png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, &trans_values)) - { - GFX2_Log(GFX2_DEBUG, "PNG transparency %d %p %p\n", num_trans, trans, trans_values); - if (color_type == PNG_COLOR_TYPE_PALETTE && trans!=NULL) - { - int i; - for (i=0; iTransparent_color = i; - context->Background_transparent = 1; - break; - } - } - } - else if ((color_type == PNG_COLOR_TYPE_GRAY - || color_type == PNG_COLOR_TYPE_RGB) && trans_values!=NULL) - { - // In this case, num_trans is supposed to be "1", - // and trans_values[0] contains the reference color - // (RGB triplet) that counts as transparent. - - // Ideally, we should reserve this color in the palette, - // (so it's not merged and averaged with a neighbor one) - // and after creating the optimized palette, find its - // index and mark it transparent. - - // Current implementation: ignore. - } - } - - png_set_interlace_handling(png_ptr); // return number of image passes (7 for interlaced images) - png_read_update_info(png_ptr, info_ptr); - - // Allocate row pointers - Row_pointers = (png_bytep*) malloc(sizeof(png_bytep) * context->Height); - row_pointers_allocated = 0; - - /* read file */ - if (!setjmp(png_jmpbuf(png_ptr))) - { - if (color_type == PNG_COLOR_TYPE_GRAY - || color_type == PNG_COLOR_TYPE_GRAY_ALPHA - || color_type == PNG_COLOR_TYPE_PALETTE - ) - { - // 8bpp - - for (y=0; yHeight; y++) - Row_pointers[y] = (png_byte*) malloc(png_get_rowbytes(png_ptr,info_ptr)); - row_pointers_allocated = 1; - - png_read_image(png_ptr, Row_pointers); - - for (y=0; yHeight; y++) - for (x=0; xWidth; x++) - Set_pixel(context, x, y, Row_pointers[y][x]); - } - else - { - switch (context->Type) - { - case CONTEXT_PREVIEW: - // 24bpp - - // It's a preview - // Unfortunately we need to allocate loads of memory - for (y=0; yHeight; y++) - Row_pointers[y] = (png_byte*) malloc(png_get_rowbytes(png_ptr,info_ptr)); - row_pointers_allocated = 1; - - png_read_image(png_ptr, Row_pointers); - - for (y=0; yHeight; y++) - for (x=0; xWidth; x++) - Set_pixel_24b(context, x, y, Row_pointers[y][x*3],Row_pointers[y][x*3+1],Row_pointers[y][x*3+2]); - break; - case CONTEXT_MAIN_IMAGE: - case CONTEXT_BRUSH: - case CONTEXT_SURFACE: - // It's loading an actual image - // We'll save memory and time by writing directly into - // our pre-allocated 24bit buffer - for (y=0; yHeight; y++) - Row_pointers[y] = (png_byte*) (&(context->Buffer_image_24b[y * context->Width])); - png_read_image(png_ptr, Row_pointers); - break; - - case CONTEXT_PALETTE: - case CONTEXT_PREVIEW_PALETTE: - // No pixels to draw in a palette! - break; - } - } - } - else - File_error=2; - - /* cleanup heap allocation */ - if (row_pointers_allocated) - { - for (y=0; yHeight; y++) { - free(Row_pointers[y]); - Row_pointers[y] = NULL; - } - - } - free(Row_pointers); - Row_pointers = NULL; - } - else - File_error=2; - } - else - // Unsupported image type - File_error=1; - } - else - File_error=1; - } - else - File_error=1; - png_destroy_read_struct(&png_ptr, &info_ptr, NULL); - } -} - - -/// Read PNG format files -/// -/// just read/test the header and call Load_PNG_Sub() -void Load_PNG(T_IO_Context * context) -{ - FILE *file; - byte png_header[8]; - - File_error=0; - - if ((file=Open_file_read(context))) - { - // Load header (8 first bytes) - if (Read_bytes(file,png_header,8)) - { - // Do we recognize a png file signature ? - if ( !png_sig_cmp(png_header, 0, 8)) - Load_PNG_Sub(context, file, NULL, 0); - else - File_error=1; - } - else // Lecture header impossible: Error ne modifiant pas l'image - File_error=1; - - fclose(file); - } - else // Ouv. fichier impossible: Error ne modifiant pas l'image - File_error=1; -} - - -/// Write to memory buffer -static void PNG_memory_write(png_structp png_ptr, png_bytep p, png_size_t count) -{ - struct PNG_memory_buffer * buffer = (struct PNG_memory_buffer *)png_get_io_ptr(png_ptr); - GFX2_Log(GFX2_DEBUG, "PNG_memory_write(%p, %p, %u) (io_ptr=%p)\n", png_ptr, p, count, buffer); - if (buffer->size < buffer->offset + count) - { - char * tmp = realloc(buffer->buffer, buffer->offset + count + 1024); - if (tmp == NULL) - { - GFX2_Log(GFX2_ERROR, "PNG_memory_write() Failed to allocate %u bytes of memory\n", buffer->offset + count + 1024); - File_error = 1; - return; - } - buffer->buffer = tmp; - buffer->size = buffer->offset + count + 1024; - } - memcpy(buffer->buffer + buffer->offset, p, count); - buffer->offset += count; -} - -/// do nothing -static void PNG_memory_flush(png_structp png_ptr) -{ - struct PNG_memory_buffer * buffer = (struct PNG_memory_buffer *)png_get_io_ptr(png_ptr); - GFX2_Log(GFX2_DEBUG, "PNG_memory_flush(%p) (io_ptr=%p)\n", png_ptr, buffer); -} - - -/// Save a PNG to file or memory -/// @param context the IO context -/// @param file the FILE to write to or NULL to write to memory -/// @param buffer will receive a malloc'ed buffer if writting to memory -/// @param buffer_size will receive the PNG size in memory -void Save_PNG_Sub(T_IO_Context * context, FILE * file, char * * buffer, unsigned long * buffer_size) -{ - static png_bytep * Row_pointers = NULL; - int y; - byte * pixel_ptr; - png_structp png_ptr; - png_infop info_ptr; - png_unknown_chunk crng_chunk; - byte cycle_data[16*6]; // Storage for color-cycling data, referenced by crng_chunk - struct PNG_memory_buffer memory_buffer; - - assert((file != NULL) || ((buffer != NULL) && (buffer_size != NULL))); - memset(&memory_buffer, 0, sizeof(memory_buffer)); - /* initialisation */ - if ((png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL)) - && (info_ptr = png_create_info_struct(png_ptr))) - { - if (!setjmp(png_jmpbuf(png_ptr))) - { - if (file != NULL) - png_init_io(png_ptr, file); - else // to write to memory, use png_set_write_fn() instead of calling png_init_io() - png_set_write_fn(png_ptr, &memory_buffer, PNG_memory_write, PNG_memory_flush); - - /* read PNG header */ - if (!setjmp(png_jmpbuf(png_ptr))) - { - png_set_IHDR(png_ptr, info_ptr, context->Width, context->Height, - 8, PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE, - PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); - - png_set_PLTE(png_ptr, info_ptr, (png_colorp)context->Palette, 256); - { - // text chunks in PNG (optional) - png_text text_ptr[2] = { -#ifdef PNG_iTXt_SUPPORTED - {-1, "Software", "Grafx2", 6, 0, NULL, NULL}, - {-1, "Title", NULL, 0, 0, NULL, NULL} -#else - {-1, "Software", "Grafx2", 6}, - {-1, "Title", NULL, 0} -#endif - }; - int nb_text_chunks=1; - if (context->Comment[0]) - { - text_ptr[1].text=context->Comment; - text_ptr[1].text_length=strlen(context->Comment); - nb_text_chunks=2; - } - png_set_text(png_ptr, info_ptr, text_ptr, nb_text_chunks); - } - if (context->Background_transparent) - { - // Transparency - byte opacity[256]; - // Need to fill a segment with '255', up to the transparent color - // which will have a 0. This piece of data (1 to 256 bytes) - // will be stored in the file. - memset(opacity, 255,context->Transparent_color); - opacity[context->Transparent_color]=0; - png_set_tRNS(png_ptr, info_ptr, opacity, (int)1 + context->Transparent_color,0); - } - // if using PNG_RESOLUTION_METER, unit is in dot per meter. - // 72 DPI = 2835, 600 DPI = 23622 - // with PNG_RESOLUTION_UNKNOWN, it is arbitrary - switch(Pixel_ratio) - { - case PIXEL_WIDE: - case PIXEL_WIDE2: - png_set_pHYs(png_ptr, info_ptr, 1, 2, PNG_RESOLUTION_UNKNOWN); - break; - case PIXEL_TALL: - case PIXEL_TALL2: - png_set_pHYs(png_ptr, info_ptr, 2, 1, PNG_RESOLUTION_UNKNOWN); - break; - case PIXEL_TALL3: - png_set_pHYs(png_ptr, info_ptr, 4, 3, PNG_RESOLUTION_UNKNOWN); - break; - default: - break; - } - // Write cycling colors - if (context->Color_cycles) - { - // Save a chunk called 'crNg' - // The case is selected by the following rules from PNG standard: - // char 1: non-mandatory = lowercase - // char 2: private (not standard) = lowercase - // char 3: reserved = always uppercase - // char 4: can be copied by editors = lowercase - - // First, turn our nice structure into byte array - // (just to avoid padding in structures) - - byte *chunk_ptr = cycle_data; - int i; - - for (i=0; iColor_cycles; i++) - { - word flags=0; - flags|= context->Cycle_range[i].Speed?1:0; // Cycling or not - flags|= context->Cycle_range[i].Inverse?2:0; // Inverted - - // Big end of Rate - *(chunk_ptr++) = (context->Cycle_range[i].Speed*78) >> 8; - // Low end of Rate - *(chunk_ptr++) = (context->Cycle_range[i].Speed*78) & 0xFF; - - // Big end of Flags - *(chunk_ptr++) = (flags) >> 8; - // Low end of Flags - *(chunk_ptr++) = (flags) & 0xFF; - - // Min color - *(chunk_ptr++) = context->Cycle_range[i].Start; - // Max color - *(chunk_ptr++) = context->Cycle_range[i].End; - } - - // Build one unknown_chuck structure - memcpy(crng_chunk.name, "crNg",5); - crng_chunk.data=cycle_data; - crng_chunk.size=context->Color_cycles*6; - crng_chunk.location=PNG_HAVE_PLTE; - - // Give it to libpng - png_set_unknown_chunks(png_ptr, info_ptr, &crng_chunk, 1); - // libpng seems to ignore the location I provided earlier. - png_set_unknown_chunk_location(png_ptr, info_ptr, 0, PNG_HAVE_PLTE); - } - - - png_write_info(png_ptr, info_ptr); - - /* ecriture des pixels de l'image */ - Row_pointers = (png_bytep*) malloc(sizeof(png_bytep) * context->Height); - pixel_ptr = context->Target_address; - for (y=0; yHeight; y++) - Row_pointers[y] = (png_byte*)(pixel_ptr+y*context->Pitch); - - if (!setjmp(png_jmpbuf(png_ptr))) - { - png_write_image(png_ptr, Row_pointers); - - /* cloture png */ - if (!setjmp(png_jmpbuf(png_ptr))) - { - png_write_end(png_ptr, NULL); - } - else - File_error=1; - } - else - File_error=1; - } - else - File_error=1; - } - else - { - File_error=1; - } - png_destroy_write_struct(&png_ptr, &info_ptr); - } - else - File_error=1; - - if (Row_pointers) - free(Row_pointers); - if (File_error == 0 && buffer != NULL) - { - *buffer = memory_buffer.buffer; - if (buffer_size != NULL) - *buffer_size = memory_buffer.offset; - } - else - free(memory_buffer.buffer); -} - - -/// Save a PNG file -void Save_PNG(T_IO_Context * context) -{ - FILE *file; - - File_error = 0; - - file = Open_file_write(context); - if (file != NULL) - { - Save_PNG_Sub(context, file, NULL, NULL); - fclose(file); - // remove the file if there was an error - if (File_error) - Remove_file(context); - } - else - File_error = 1; -} -#endif // __no_pnglib__ -/** @} */ diff --git a/src/pngformat.c b/src/pngformat.c new file mode 100644 index 00000000..bd779f29 --- /dev/null +++ b/src/pngformat.c @@ -0,0 +1,806 @@ +/* vim:expandtab:ts=2 sw=2: +*/ +/* Grafx2 - The Ultimate 256-color bitmap paint program + + Copyright 2018 Thomas Bernard + Copyright 2011 Pawel Góralski + Copyright 2009 Petter Lindquist + Copyright 2008 Yves Rizoud + Copyright 2008 Franck Charlet + Copyright 2007 Adrien Destugues + Copyright 1996-2001 Sunset Design (Guillaume Dorme & Karl Maritaud) + + Grafx2 is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; version 2 + of the License. + + Grafx2 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Grafx2; if not, see +*/ + +///@file pngformat.c +/// Saving and loading of PNG file format + +#ifndef __no_pnglib__ +#include +#include +#include +#include +#if !defined(PNG_HAVE_PLTE) +#define PNG_HAVE_PLTE 0x02 +#endif +#if (PNG_LIBPNG_VER_MAJOR <= 1) && (PNG_LIBPNG_VER_MINOR < 4) + // Compatibility layer to allow us to use libng 1.4 or any older one. + + // This function is renamed in 1.4 + #define png_set_expand_gray_1_2_4_to_8(x) png_set_gray_1_2_4_to_8(x) + + // Wrappers that are mandatory in 1.4. Older version allowed direct access. + #define png_get_rowbytes(png_ptr,info_ptr) ((info_ptr)->rowbytes) + #define png_get_image_width(png_ptr,info_ptr) ((info_ptr)->width) + #define png_get_image_height(png_ptr,info_ptr) ((info_ptr)->height) + #define png_get_bit_depth(png_ptr,info_ptr) ((info_ptr)->bit_depth) + #define png_get_color_type(png_ptr,info_ptr) ((info_ptr)->color_type) +#endif + +#ifndef png_jmpbuf +# define png_jmpbuf(png_ptr) ((png_ptr)->jmpbuf) +#endif + +#include "loadsave.h" +#include "loadsavefuncs.h" +#include "io.h" +#include "misc.h" +#include "gfx2log.h" + +#ifndef MIN +#define MIN(a,b) ((a)<(b)?(a):(b)) +#endif + +//////////////////////////////////// PNG //////////////////////////////////// +/** + * @defgroup PNG PNG format + * @ingroup loadsaveformats + * Portable Network Graphics + * + * We make use of libpng : http://www.libpng.org/pub/png/libpng.html + * @{ + */ + +/// Test for PNG format +/// +/// The 8 byte signature at the start of file is tested +void Test_PNG(T_IO_Context * context, FILE * file) +{ + byte png_header[8]; + + (void)context; + File_error=1; + + // Lecture du header du fichier + if (Read_bytes(file,png_header,8)) + { + if ( !png_sig_cmp(png_header, 0, 8)) + File_error=0; + } +} + +/// Callback to handle our private chunks +/// +/// We have one private chunk at the moment : +/// - "crNg" which is similar to a CRNG chunk in an IFF file +static int PNG_read_unknown_chunk(png_structp ptr, png_unknown_chunkp chunk) +{ + T_IO_Context * context; + // png_unknown_chunkp members: + // png_byte name[5]; + // png_byte *data; + // png_size_t size; + + context = (T_IO_Context *)png_get_user_chunk_ptr(ptr); + + GFX2_Log(GFX2_DEBUG, "PNG private chunk '%s' :\n", chunk->name); + GFX2_LogHexDump(GFX2_DEBUG, "", chunk->data, 0, chunk->size); + + if (!strcmp((const char *)chunk->name, "crNg")) + { + unsigned int i; + const byte *chunk_ptr = chunk->data; + + // Should be a multiple of 6 + if (chunk->size % 6) + return (-1); + + + for(i=0;isize/6 && i<16; i++) + { + word rate; + word flags; + byte min_col; + byte max_col; + + // Rate (big-endian word) + rate = *(chunk_ptr++) << 8; + rate |= *(chunk_ptr++); + + // Flags (big-endian) + flags = *(chunk_ptr++) << 8; + flags |= *(chunk_ptr++); + + // Min color + min_col = *(chunk_ptr++); + // Max color + max_col = *(chunk_ptr++); + + // Check validity + if (min_col != max_col) + { + // Valid cycling range + if (max_colCycle_range[i].Start=min_col; + context->Cycle_range[i].End=max_col; + context->Cycle_range[i].Inverse=(flags&2)?1:0; + context->Cycle_range[i].Speed=(flags&1) ? rate/78 : 0; + + context->Color_cycles=i+1; + } + } + + return (1); // >0 = success + } + return (0); /* did not recognize */ + +} + + +/// Private structure used in PNG_memory_read() and PNG_memory_write() +struct PNG_memory_buffer { + char * buffer; + unsigned long offset; + unsigned long size; +}; + +/// read from memory buffer +static void PNG_memory_read(png_structp png_ptr, png_bytep p, png_size_t count) +{ + struct PNG_memory_buffer * buffer = (struct PNG_memory_buffer *)png_get_io_ptr(png_ptr); + GFX2_Log(GFX2_DEBUG, "PNG_memory_read(%p, %p, %u) (io_ptr=%p)\n", png_ptr, p, count, buffer); + if (buffer == NULL || p == NULL) + return; + if (buffer->offset + count <= buffer->size) + { + memcpy(p, buffer->buffer + buffer->offset, count); + buffer->offset += count; + } + else + { + unsigned long available_count = buffer->size - buffer->offset; + GFX2_Log(GFX2_DEBUG, "PNG_memory_read(): only %lu bytes available\n", available_count); + if (available_count > 0) + { + memcpy(p, buffer->buffer + buffer->offset, available_count); + buffer->offset += available_count; + } + } +} + + +/// Read PNG format file +void Load_PNG_Sub(T_IO_Context * context, FILE * file, const char * memory_buffer, unsigned long memory_buffer_size) +{ + png_structp png_ptr; + png_infop info_ptr = NULL; + + // Prepare internal PNG loader + png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (png_ptr) + { + // Prepare internal PNG loader + info_ptr = png_create_info_struct(png_ptr); + if (info_ptr) + { + png_byte color_type; + png_byte bit_depth; + byte bpp; + struct PNG_memory_buffer buffer; + + // Setup a return point. If a pnglib loading error occurs + // in this if(), the else will be executed. + if (!setjmp(png_jmpbuf(png_ptr))) + { + // to read from memory, I need to use png_set_read_fn() instead of calling png_init_io() + if (file != NULL) + png_init_io(png_ptr, file); + else + { + buffer.buffer = (char *)memory_buffer; + buffer.offset = 8; // skip header + buffer.size = memory_buffer_size; + png_set_read_fn(png_ptr, &buffer, PNG_memory_read); + } + // Inform pnglib we already loaded the header. + png_set_sig_bytes(png_ptr, 8); + + // Hook the handler for unknown chunks + png_set_read_user_chunk_fn(png_ptr, (png_voidp)context, &PNG_read_unknown_chunk); + + // Load file information + png_read_info(png_ptr, info_ptr); + color_type = png_get_color_type(png_ptr,info_ptr); + bit_depth = png_get_bit_depth(png_ptr,info_ptr); + + switch (color_type) + { + case PNG_COLOR_TYPE_GRAY_ALPHA: + // no more than 8bpp or else we enable true color picture loading + bpp = MIN(8, bit_depth * 2); + break; + case PNG_COLOR_TYPE_RGB: + bpp = bit_depth * 3; + break; + case PNG_COLOR_TYPE_RGB_ALPHA: + bpp = bit_depth * 4; + break; + case PNG_COLOR_TYPE_PALETTE: + case PNG_COLOR_TYPE_GRAY: + default: + // no more than 8bpp or else we enable true color picture loading + bpp = MIN(8, bit_depth); + } + GFX2_Log(GFX2_DEBUG, "PNG type=%u bit_depth=%u : %ubpp\n", color_type, bit_depth, bpp); + + // If it's any supported file + // (Note: As of writing this, this test covers every possible + // image format of libpng) + if (color_type == PNG_COLOR_TYPE_PALETTE + || color_type == PNG_COLOR_TYPE_GRAY + || color_type == PNG_COLOR_TYPE_GRAY_ALPHA + || color_type == PNG_COLOR_TYPE_RGB + || color_type == PNG_COLOR_TYPE_RGB_ALPHA + ) + { + enum PIXEL_RATIO ratio = PIXEL_SIMPLE; + int num_text; + png_text *text_ptr; + + int unit_type; + png_uint_32 res_x; + png_uint_32 res_y; + + // Comment (tEXt) + context->Comment[0]='\0'; // Clear the previous comment + if ((num_text=png_get_text(png_ptr, info_ptr, &text_ptr, NULL))) + { + while (num_text--) + { + int size = COMMENT_SIZE; +#ifdef PNG_iTXt_SUPPORTED + size_t length = (text_ptr[num_text].compression >= 1) ? text_ptr[num_text].itxt_length : text_ptr[num_text].text_length; +#else + size_t length = text_ptr[num_text].text_length; + if (text_ptr[num_text].compression >= 1) + continue; // skip iTXt +#endif + if (length > 0 && length < COMMENT_SIZE) + size = (int)length; + GFX2_Log(GFX2_DEBUG, "PNG Text %d \"%s\" (%lu bytes): %.*s\n", + text_ptr[num_text].compression, text_ptr[num_text].key, + (unsigned long)length, + (int)MIN(length, 160), text_ptr[num_text].text); + if (strcmp(text_ptr[num_text].key,"Title") == 0) + { + strncpy(context->Comment, text_ptr[num_text].text, size); + context->Comment[size]='\0'; + break; // Skip all others tEXt chunks + } + else if(strcmp(text_ptr[num_text].key, "Comment") == 0) + { + strncpy(context->Comment, text_ptr[num_text].text, size); + context->Comment[size]='\0'; + break; // Skip all others tEXt chunks + } + } + } + // Pixel Ratio (pHYs) + if (png_get_pHYs(png_ptr, info_ptr, &res_x, &res_y, &unit_type)) + { + // Ignore unit, and use the X/Y ratio as a hint for + // WIDE or TALL pixels + if (res_x>0 && res_y>0) + { + GFX2_Log(GFX2_DEBUG, "PNG pHYs unit %d %dx%d\n", unit_type, res_x, res_y); + if (unit_type == 1) + GFX2_Log(GFX2_DEBUG, " %dx%d DPI\n", (res_x * 254 + 5000) / 10000, (res_y * 254 + 5000) / 10000); + if (res_y * 10 > res_x * 12) // X/Y < 1/1.2 + ratio = PIXEL_WIDE; + else if (res_x * 10 > res_y * 12) // X/Y > 1.2 + ratio = PIXEL_TALL; + } + } + Pre_load(context, + png_get_image_width(png_ptr, info_ptr), + png_get_image_height(png_ptr, info_ptr), + file != NULL ? File_length_file(file) : memory_buffer_size, + FORMAT_PNG, ratio, bpp); + + if (File_error==0) + { + int x,y; + png_colorp palette; + int num_palette; + png_bytep * Row_pointers = NULL; + byte row_pointers_allocated = 0; + int num_trans; + png_bytep trans; + png_color_16p trans_values; + + // 16-bit images + if (bit_depth == 16) + { + // Reduce to 8-bit + png_set_strip_16(png_ptr); + } + else if (bit_depth < 8) + { + // Inform libpng we want one byte per pixel, + // even though the file was less than 8bpp + png_set_packing(png_ptr); + } + + // Images with alpha channel + if (color_type & PNG_COLOR_MASK_ALPHA) + { + // Tell libpng to ignore it + png_set_strip_alpha(png_ptr); + } + + // Greyscale images : + if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + { + // Map low bpp greyscales to full 8bit (0-255 range) + if (bit_depth < 8) + { +#if (PNG_LIBPNG_VER_MAJOR <= 1) && (PNG_LIBPNG_VER_MINOR < 4) + // Works well with png 1.2.8, but deprecated in 1.4 ... + png_set_gray_1_2_4_to_8(png_ptr); +#else + // ...where this seems to replace it: + png_set_expand_gray_1_2_4_to_8(png_ptr); +#endif + } + + // Create greyscale palette + for (x=0;x<256;x++) + { + context->Palette[x].R=x; + context->Palette[x].G=x; + context->Palette[x].B=x; + } + } + else if (color_type == PNG_COLOR_TYPE_PALETTE) // Palette images + { + if (bit_depth < 8) + { + // Clear unused colors + if (Config.Clear_palette) + memset(context->Palette,0,sizeof(T_Palette)); + } + // Get a pointer to the PNG palette + png_get_PLTE(png_ptr, info_ptr, &palette, + &num_palette); + // Copy all colors to the context + for (x=0;xPalette[x].R=palette[x].red; + context->Palette[x].G=palette[x].green; + context->Palette[x].B=palette[x].blue; + } + // The palette must not be freed: it is owned by libpng. + palette = NULL; + } + // Transparency (tRNS) + if (png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, &trans_values)) + { + GFX2_Log(GFX2_DEBUG, "PNG transparency %d %p %p\n", num_trans, trans, trans_values); + if (color_type == PNG_COLOR_TYPE_PALETTE && trans!=NULL) + { + int i; + for (i=0; iTransparent_color = i; + context->Background_transparent = 1; + break; + } + } + } + else if ((color_type == PNG_COLOR_TYPE_GRAY + || color_type == PNG_COLOR_TYPE_RGB) && trans_values!=NULL) + { + // In this case, num_trans is supposed to be "1", + // and trans_values[0] contains the reference color + // (RGB triplet) that counts as transparent. + + // Ideally, we should reserve this color in the palette, + // (so it's not merged and averaged with a neighbor one) + // and after creating the optimized palette, find its + // index and mark it transparent. + + // Current implementation: ignore. + } + } + + png_set_interlace_handling(png_ptr); // return number of image passes (7 for interlaced images) + png_read_update_info(png_ptr, info_ptr); + + // Allocate row pointers + Row_pointers = (png_bytep*) malloc(sizeof(png_bytep) * context->Height); + row_pointers_allocated = 0; + + /* read file */ + if (!setjmp(png_jmpbuf(png_ptr))) + { + if (color_type == PNG_COLOR_TYPE_GRAY + || color_type == PNG_COLOR_TYPE_GRAY_ALPHA + || color_type == PNG_COLOR_TYPE_PALETTE + ) + { + // 8bpp + + for (y=0; yHeight; y++) + Row_pointers[y] = (png_byte*) malloc(png_get_rowbytes(png_ptr,info_ptr)); + row_pointers_allocated = 1; + + png_read_image(png_ptr, Row_pointers); + + for (y=0; yHeight; y++) + for (x=0; xWidth; x++) + Set_pixel(context, x, y, Row_pointers[y][x]); + } + else + { + switch (context->Type) + { + case CONTEXT_PREVIEW: + // 24bpp + + // It's a preview + // Unfortunately we need to allocate loads of memory + for (y=0; yHeight; y++) + Row_pointers[y] = (png_byte*) malloc(png_get_rowbytes(png_ptr,info_ptr)); + row_pointers_allocated = 1; + + png_read_image(png_ptr, Row_pointers); + + for (y=0; yHeight; y++) + for (x=0; xWidth; x++) + Set_pixel_24b(context, x, y, Row_pointers[y][x*3],Row_pointers[y][x*3+1],Row_pointers[y][x*3+2]); + break; + case CONTEXT_MAIN_IMAGE: + case CONTEXT_BRUSH: + case CONTEXT_SURFACE: + // It's loading an actual image + // We'll save memory and time by writing directly into + // our pre-allocated 24bit buffer + for (y=0; yHeight; y++) + Row_pointers[y] = (png_byte*) (&(context->Buffer_image_24b[y * context->Width])); + png_read_image(png_ptr, Row_pointers); + break; + + case CONTEXT_PALETTE: + case CONTEXT_PREVIEW_PALETTE: + // No pixels to draw in a palette! + break; + } + } + } + else + File_error=2; + + /* cleanup heap allocation */ + if (row_pointers_allocated) + { + for (y=0; yHeight; y++) { + free(Row_pointers[y]); + Row_pointers[y] = NULL; + } + + } + free(Row_pointers); + Row_pointers = NULL; + } + else + File_error=2; + } + else + // Unsupported image type + File_error=1; + } + else + File_error=1; + } + else + File_error=1; + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + } +} + + +/// Read PNG format files +/// +/// just read/test the header and call Load_PNG_Sub() +void Load_PNG(T_IO_Context * context) +{ + FILE *file; + byte png_header[8]; + + File_error=0; + + if ((file=Open_file_read(context))) + { + // Load header (8 first bytes) + if (Read_bytes(file,png_header,8)) + { + // Do we recognize a png file signature ? + if ( !png_sig_cmp(png_header, 0, 8)) + Load_PNG_Sub(context, file, NULL, 0); + else + File_error=1; + } + else // Lecture header impossible: Error ne modifiant pas l'image + File_error=1; + + fclose(file); + } + else // Ouv. fichier impossible: Error ne modifiant pas l'image + File_error=1; +} + + +/// Write to memory buffer +static void PNG_memory_write(png_structp png_ptr, png_bytep p, png_size_t count) +{ + struct PNG_memory_buffer * buffer = (struct PNG_memory_buffer *)png_get_io_ptr(png_ptr); + GFX2_Log(GFX2_DEBUG, "PNG_memory_write(%p, %p, %u) (io_ptr=%p)\n", png_ptr, p, count, buffer); + if (buffer->size < buffer->offset + count) + { + char * tmp = realloc(buffer->buffer, buffer->offset + count + 1024); + if (tmp == NULL) + { + GFX2_Log(GFX2_ERROR, "PNG_memory_write() Failed to allocate %u bytes of memory\n", buffer->offset + count + 1024); + File_error = 1; + return; + } + buffer->buffer = tmp; + buffer->size = buffer->offset + count + 1024; + } + memcpy(buffer->buffer + buffer->offset, p, count); + buffer->offset += count; +} + +/// do nothing +static void PNG_memory_flush(png_structp png_ptr) +{ + struct PNG_memory_buffer * buffer = (struct PNG_memory_buffer *)png_get_io_ptr(png_ptr); + GFX2_Log(GFX2_DEBUG, "PNG_memory_flush(%p) (io_ptr=%p)\n", png_ptr, buffer); +} + + +/// Save a PNG to file or memory +/// @param context the IO context +/// @param file the FILE to write to or NULL to write to memory +/// @param buffer will receive a malloc'ed buffer if writting to memory +/// @param buffer_size will receive the PNG size in memory +void Save_PNG_Sub(T_IO_Context * context, FILE * file, char * * buffer, unsigned long * buffer_size) +{ + static png_bytep * Row_pointers = NULL; + int y; + byte * pixel_ptr; + png_structp png_ptr; + png_infop info_ptr; + png_unknown_chunk crng_chunk; + byte cycle_data[16*6]; // Storage for color-cycling data, referenced by crng_chunk + struct PNG_memory_buffer memory_buffer; + + assert((file != NULL) || ((buffer != NULL) && (buffer_size != NULL))); + memset(&memory_buffer, 0, sizeof(memory_buffer)); + /* initialisation */ + if ((png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL)) + && (info_ptr = png_create_info_struct(png_ptr))) + { + if (!setjmp(png_jmpbuf(png_ptr))) + { + if (file != NULL) + png_init_io(png_ptr, file); + else // to write to memory, use png_set_write_fn() instead of calling png_init_io() + png_set_write_fn(png_ptr, &memory_buffer, PNG_memory_write, PNG_memory_flush); + + /* read PNG header */ + if (!setjmp(png_jmpbuf(png_ptr))) + { + png_set_IHDR(png_ptr, info_ptr, context->Width, context->Height, + 8, PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + + png_set_PLTE(png_ptr, info_ptr, (png_colorp)context->Palette, 256); + { + // text chunks in PNG (optional) + png_text text_ptr[2] = { +#ifdef PNG_iTXt_SUPPORTED + {-1, "Software", "Grafx2", 6, 0, NULL, NULL}, + {-1, "Title", NULL, 0, 0, NULL, NULL} +#else + {-1, "Software", "Grafx2", 6}, + {-1, "Title", NULL, 0} +#endif + }; + int nb_text_chunks=1; + if (context->Comment[0]) + { + text_ptr[1].text=context->Comment; + text_ptr[1].text_length=strlen(context->Comment); + nb_text_chunks=2; + } + png_set_text(png_ptr, info_ptr, text_ptr, nb_text_chunks); + } + if (context->Background_transparent) + { + // Transparency + byte opacity[256]; + // Need to fill a segment with '255', up to the transparent color + // which will have a 0. This piece of data (1 to 256 bytes) + // will be stored in the file. + memset(opacity, 255,context->Transparent_color); + opacity[context->Transparent_color]=0; + png_set_tRNS(png_ptr, info_ptr, opacity, (int)1 + context->Transparent_color,0); + } + // if using PNG_RESOLUTION_METER, unit is in dot per meter. + // 72 DPI = 2835, 600 DPI = 23622 + // with PNG_RESOLUTION_UNKNOWN, it is arbitrary + switch(Pixel_ratio) + { + case PIXEL_WIDE: + case PIXEL_WIDE2: + png_set_pHYs(png_ptr, info_ptr, 1, 2, PNG_RESOLUTION_UNKNOWN); + break; + case PIXEL_TALL: + case PIXEL_TALL2: + png_set_pHYs(png_ptr, info_ptr, 2, 1, PNG_RESOLUTION_UNKNOWN); + break; + case PIXEL_TALL3: + png_set_pHYs(png_ptr, info_ptr, 4, 3, PNG_RESOLUTION_UNKNOWN); + break; + default: + break; + } + // Write cycling colors + if (context->Color_cycles) + { + // Save a chunk called 'crNg' + // The case is selected by the following rules from PNG standard: + // char 1: non-mandatory = lowercase + // char 2: private (not standard) = lowercase + // char 3: reserved = always uppercase + // char 4: can be copied by editors = lowercase + + // First, turn our nice structure into byte array + // (just to avoid padding in structures) + + byte *chunk_ptr = cycle_data; + int i; + + for (i=0; iColor_cycles; i++) + { + word flags=0; + flags|= context->Cycle_range[i].Speed?1:0; // Cycling or not + flags|= context->Cycle_range[i].Inverse?2:0; // Inverted + + // Big end of Rate + *(chunk_ptr++) = (context->Cycle_range[i].Speed*78) >> 8; + // Low end of Rate + *(chunk_ptr++) = (context->Cycle_range[i].Speed*78) & 0xFF; + + // Big end of Flags + *(chunk_ptr++) = (flags) >> 8; + // Low end of Flags + *(chunk_ptr++) = (flags) & 0xFF; + + // Min color + *(chunk_ptr++) = context->Cycle_range[i].Start; + // Max color + *(chunk_ptr++) = context->Cycle_range[i].End; + } + + // Build one unknown_chuck structure + memcpy(crng_chunk.name, "crNg",5); + crng_chunk.data=cycle_data; + crng_chunk.size=context->Color_cycles*6; + crng_chunk.location=PNG_HAVE_PLTE; + + // Give it to libpng + png_set_unknown_chunks(png_ptr, info_ptr, &crng_chunk, 1); + // libpng seems to ignore the location I provided earlier. + png_set_unknown_chunk_location(png_ptr, info_ptr, 0, PNG_HAVE_PLTE); + } + + + png_write_info(png_ptr, info_ptr); + + /* ecriture des pixels de l'image */ + Row_pointers = (png_bytep*) malloc(sizeof(png_bytep) * context->Height); + pixel_ptr = context->Target_address; + for (y=0; yHeight; y++) + Row_pointers[y] = (png_byte*)(pixel_ptr+y*context->Pitch); + + if (!setjmp(png_jmpbuf(png_ptr))) + { + png_write_image(png_ptr, Row_pointers); + + /* cloture png */ + if (!setjmp(png_jmpbuf(png_ptr))) + { + png_write_end(png_ptr, NULL); + } + else + File_error=1; + } + else + File_error=1; + } + else + File_error=1; + } + else + { + File_error=1; + } + png_destroy_write_struct(&png_ptr, &info_ptr); + } + else + File_error=1; + + if (Row_pointers) + free(Row_pointers); + if (File_error == 0 && buffer != NULL) + { + *buffer = memory_buffer.buffer; + if (buffer_size != NULL) + *buffer_size = memory_buffer.offset; + } + else + free(memory_buffer.buffer); +} + + +/// Save a PNG file +void Save_PNG(T_IO_Context * context) +{ + FILE *file; + + File_error = 0; + + file = Open_file_write(context); + if (file != NULL) + { + Save_PNG_Sub(context, file, NULL, NULL); + fclose(file); + // remove the file if there was an error + if (File_error) + Remove_file(context); + } + else + File_error = 1; +} +/** @} */ + +#endif // __no_pnglib__