Пример #1
0
static FIBITMAP * DLL_CALLCONV
Load(FreeImageIO *io, fi_handle handle, int page, int flags, void *data) {
	png_structp png_ptr = NULL;
	png_infop info_ptr = NULL;
	png_uint_32 width, height;
	int color_type;
	int bit_depth;
	int pixel_depth = 0;	// pixel_depth = bit_depth * channels

	FIBITMAP *dib = NULL;
	png_bytepp row_pointers = NULL;

    fi_ioStructure fio;
    fio.s_handle = handle;
	fio.s_io = io;
    
	if (handle) {
		BOOL header_only = (flags & FIF_LOAD_NOPIXELS) == FIF_LOAD_NOPIXELS;

		try {		
			// check to see if the file is in fact a PNG file

			BYTE png_check[PNG_BYTES_TO_CHECK];

			io->read_proc(png_check, PNG_BYTES_TO_CHECK, 1, handle);

			if (png_sig_cmp(png_check, (png_size_t)0, PNG_BYTES_TO_CHECK) != 0) {
				return NULL;	// Bad signature
			}
			
			// create the chunk manage structure

			png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, (png_voidp)NULL, error_handler, warning_handler);

			if (!png_ptr) {
				return NULL;			
			}

			// create the info structure

		    info_ptr = png_create_info_struct(png_ptr);

			if (!info_ptr) {
				png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL);
				return NULL;
			}

			// init the IO

			png_set_read_fn(png_ptr, &fio, _ReadProc);

            if (setjmp(png_jmpbuf(png_ptr))) {
				png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
				return NULL;
			}

			// because we have already read the signature...

			png_set_sig_bytes(png_ptr, PNG_BYTES_TO_CHECK);

			// read the IHDR chunk

			png_read_info(png_ptr, info_ptr);
			png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, NULL, NULL, NULL);

			// configure the decoder

			FREE_IMAGE_TYPE image_type = FIT_BITMAP;

			if(!ConfigureDecoder(png_ptr, info_ptr, flags, &image_type)) {
				throw FI_MSG_ERROR_UNSUPPORTED_FORMAT;
			}

			// update image info

			color_type = png_get_color_type(png_ptr, info_ptr);
			bit_depth = png_get_bit_depth(png_ptr, info_ptr);
			pixel_depth = bit_depth * png_get_channels(png_ptr, info_ptr);

			// create a dib and write the bitmap header
			// set up the dib palette, if needed

			switch (color_type) {
				case PNG_COLOR_TYPE_RGB:
				case PNG_COLOR_TYPE_RGB_ALPHA:
					dib = FreeImage_AllocateHeaderT(header_only, image_type, width, height, pixel_depth, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK);
					break;

				case PNG_COLOR_TYPE_PALETTE:
					dib = FreeImage_AllocateHeaderT(header_only, image_type, width, height, pixel_depth, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK);
					if(dib) {
						png_colorp png_palette = NULL;
						int palette_entries = 0;

						png_get_PLTE(png_ptr,info_ptr, &png_palette, &palette_entries);

						palette_entries = MIN((unsigned)palette_entries, FreeImage_GetColorsUsed(dib));

						// store the palette

						RGBQUAD *palette = FreeImage_GetPalette(dib);
						for(int i = 0; i < palette_entries; i++) {
							palette[i].rgbRed   = png_palette[i].red;
							palette[i].rgbGreen = png_palette[i].green;
							palette[i].rgbBlue  = png_palette[i].blue;
						}
					}
					break;

				case PNG_COLOR_TYPE_GRAY:
					dib = FreeImage_AllocateHeaderT(header_only, image_type, width, height, pixel_depth, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK);

					if(dib && (pixel_depth <= 8)) {
						RGBQUAD *palette = FreeImage_GetPalette(dib);
						const int palette_entries = 1 << pixel_depth;

						for(int i = 0; i < palette_entries; i++) {
							palette[i].rgbRed   =
							palette[i].rgbGreen =
							palette[i].rgbBlue  = (BYTE)((i * 255) / (palette_entries - 1));
						}
					}
					break;

				default:
					throw FI_MSG_ERROR_UNSUPPORTED_FORMAT;
			}

			if(!dib) {
				throw FI_MSG_ERROR_DIB_MEMORY;
			}

			// store the transparency table

			if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
				// array of alpha (transparency) entries for palette
				png_bytep trans_alpha = NULL;
				// number of transparent entries
				int num_trans = 0;						
				// graylevel or color sample values of the single transparent color for non-paletted images
				png_color_16p trans_color = NULL;

				png_get_tRNS(png_ptr, info_ptr, &trans_alpha, &num_trans, &trans_color);

				if((color_type == PNG_COLOR_TYPE_GRAY) && trans_color) {
					// single transparent color
					if (trans_color->gray < 256) { 
						BYTE table[256]; 
						memset(table, 0xFF, 256); 
						table[trans_color->gray] = 0; 
						FreeImage_SetTransparencyTable(dib, table, 256); 
					}
					// check for a full transparency table, too
					else if ((trans_alpha) && (pixel_depth <= 8)) {
						FreeImage_SetTransparencyTable(dib, (BYTE *)trans_alpha, num_trans);
					}

				} else if((color_type == PNG_COLOR_TYPE_PALETTE) && trans_alpha) {
					// transparency table
					FreeImage_SetTransparencyTable(dib, (BYTE *)trans_alpha, num_trans);
				}
			}

			// store the background color (only supported for FIT_BITMAP types)

			if ((image_type == FIT_BITMAP) && png_get_valid(png_ptr, info_ptr, PNG_INFO_bKGD)) {
				// Get the background color to draw transparent and alpha images over.
				// Note that even if the PNG file supplies a background, you are not required to
				// use it - you should use the (solid) application background if it has one.

				png_color_16p image_background = NULL;
				RGBQUAD rgbBkColor;

				if (png_get_bKGD(png_ptr, info_ptr, &image_background)) {
					rgbBkColor.rgbRed      = (BYTE)image_background->red;
					rgbBkColor.rgbGreen    = (BYTE)image_background->green;
					rgbBkColor.rgbBlue     = (BYTE)image_background->blue;
					rgbBkColor.rgbReserved = 0;

					FreeImage_SetBackgroundColor(dib, &rgbBkColor);
				}
			}

			// get physical resolution

			if (png_get_valid(png_ptr, info_ptr, PNG_INFO_pHYs)) {
				png_uint_32 res_x, res_y;
				
				// we'll overload this var and use 0 to mean no phys data,
				// since if it's not in meters we can't use it anyway

				int res_unit_type = PNG_RESOLUTION_UNKNOWN;

				png_get_pHYs(png_ptr,info_ptr, &res_x, &res_y, &res_unit_type);

				if (res_unit_type == PNG_RESOLUTION_METER) {
					FreeImage_SetDotsPerMeterX(dib, res_x);
					FreeImage_SetDotsPerMeterY(dib, res_y);
				}
			}

			// get possible ICC profile

			if (png_get_valid(png_ptr, info_ptr, PNG_INFO_iCCP)) {
				png_charp profile_name = NULL;
				png_bytep profile_data = NULL;
				png_uint_32 profile_length = 0;
				int  compression_type;

				png_get_iCCP(png_ptr, info_ptr, &profile_name, &compression_type, &profile_data, &profile_length);

				// copy ICC profile data (must be done after FreeImage_AllocateHeader)

				FreeImage_CreateICCProfile(dib, profile_data, profile_length);
			}

			// --- header only mode => clean-up and return

			if (header_only) {
				// get possible metadata (it can be located both before and after the image data)
				ReadMetadata(png_ptr, info_ptr, dib);
				if (png_ptr) {
					// clean up after the read, and free any memory allocated - REQUIRED
					png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
				}
				return dib;
			}

			// set the individual row_pointers to point at the correct offsets

			row_pointers = (png_bytepp)malloc(height * sizeof(png_bytep));

			if (!row_pointers) {
				png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
				FreeImage_Unload(dib);
				return NULL;
			}

			// read in the bitmap bits via the pointer table
			// allow loading of PNG with minor errors (such as images with several IDAT chunks)

			for (png_uint_32 k = 0; k < height; k++) {
				row_pointers[height - 1 - k] = FreeImage_GetScanLine(dib, k);
			}

			png_set_benign_errors(png_ptr, 1);
			png_read_image(png_ptr, row_pointers);

			// check if the bitmap contains transparency, if so enable it in the header

			if (FreeImage_GetBPP(dib) == 32) {
				if (FreeImage_GetColorType(dib) == FIC_RGBALPHA) {
					FreeImage_SetTransparent(dib, TRUE);
				} else {
					FreeImage_SetTransparent(dib, FALSE);
				}
			}
				
			// cleanup

			if (row_pointers) {
				free(row_pointers);
				row_pointers = NULL;
			}

			// read the rest of the file, getting any additional chunks in info_ptr

			png_read_end(png_ptr, info_ptr);

			// get possible metadata (it can be located both before and after the image data)

			ReadMetadata(png_ptr, info_ptr, dib);

			if (png_ptr) {
				// clean up after the read, and free any memory allocated - REQUIRED
				png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
			}

			return dib;

		} catch (const char *text) {
			if (png_ptr) {
				png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
			}
			if (row_pointers) {
				free(row_pointers);
			}
			if (dib) {
				FreeImage_Unload(dib);
			}
			FreeImage_OutputMessageProc(s_format_id, text);
			
			return NULL;
		}
	}			

	return NULL;
}
Пример #2
0
static FIBITMAP * DLL_CALLCONV
Load(FreeImageIO *io, fi_handle handle, int page, int flags, void *data) {
	png_structp png_ptr = NULL;
	png_infop info_ptr;
	png_uint_32 width, height;
	png_colorp png_palette = NULL;
	int color_type, palette_entries = 0;
	int bit_depth, pixel_depth;		// pixel_depth = bit_depth * channels

	FIBITMAP *dib = NULL;
	RGBQUAD *palette = NULL;		// pointer to dib palette
	png_bytepp  row_pointers = NULL;
	int i;

    fi_ioStructure fio;
    fio.s_handle = handle;
	fio.s_io = io;
    
	if (handle) {
		BOOL header_only = (flags & FIF_LOAD_NOPIXELS) == FIF_LOAD_NOPIXELS;

		try {		
			// check to see if the file is in fact a PNG file

			BYTE png_check[PNG_BYTES_TO_CHECK];

			io->read_proc(png_check, PNG_BYTES_TO_CHECK, 1, handle);

			if (png_sig_cmp(png_check, (png_size_t)0, PNG_BYTES_TO_CHECK) != 0) {
				return NULL;	// Bad signature
			}
			
			// create the chunk manage structure

			png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, (png_voidp)NULL, error_handler, warning_handler);

			if (!png_ptr) {
				return NULL;			
			}

			// create the info structure

		    info_ptr = png_create_info_struct(png_ptr);

			if (!info_ptr) {
				png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL);
				return NULL;
			}

			// init the IO

			png_set_read_fn(png_ptr, &fio, _ReadProc);

            if (setjmp(png_jmpbuf(png_ptr))) {
				png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
				return NULL;
			}

			// because we have already read the signature...

			png_set_sig_bytes(png_ptr, PNG_BYTES_TO_CHECK);

			// read the IHDR chunk

			png_read_info(png_ptr, info_ptr);
			png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, NULL, NULL, NULL);

			pixel_depth = png_get_bit_depth(png_ptr, info_ptr) * png_get_channels(png_ptr, info_ptr);

			// get image data type (assume standard image type)

			FREE_IMAGE_TYPE image_type = FIT_BITMAP;
			if (bit_depth == 16) {
				if ((pixel_depth == 16) && (color_type == PNG_COLOR_TYPE_GRAY)) {
					image_type = FIT_UINT16;
				} 
				else if ((pixel_depth == 48) && (color_type == PNG_COLOR_TYPE_RGB)) {
					image_type = FIT_RGB16;
				} 
				else if ((pixel_depth == 64) && (color_type == PNG_COLOR_TYPE_RGB_ALPHA)) {
					image_type = FIT_RGBA16;
				} else {
					// tell libpng to strip 16 bit/color files down to 8 bits/color
					png_set_strip_16(png_ptr);
					bit_depth = 8;
				}
			}

#ifndef FREEIMAGE_BIGENDIAN
			if((image_type == FIT_UINT16) || (image_type == FIT_RGB16) || (image_type == FIT_RGBA16)) {
				// turn on 16 bit byte swapping
				png_set_swap(png_ptr);
			}
#endif						

			// set some additional flags

			switch(color_type) {
				case PNG_COLOR_TYPE_RGB:
				case PNG_COLOR_TYPE_RGB_ALPHA:
#if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_BGR
					// flip the RGB pixels to BGR (or RGBA to BGRA)

					if(image_type == FIT_BITMAP) {
						png_set_bgr(png_ptr);
					}
#endif
					break;

				case PNG_COLOR_TYPE_PALETTE:
					// expand palette images to the full 8 bits from 2 bits/pixel

					if (pixel_depth == 2) {
						png_set_packing(png_ptr);
						pixel_depth = 8;
					}					

					break;

				case PNG_COLOR_TYPE_GRAY:
					// expand grayscale images to the full 8 bits from 2 bits/pixel
					// but *do not* expand fully transparent palette entries to a full alpha channel

					if (pixel_depth == 2) {
						png_set_expand_gray_1_2_4_to_8(png_ptr);
						pixel_depth = 8;
					}

					break;

				case PNG_COLOR_TYPE_GRAY_ALPHA:
					// expand 8-bit greyscale + 8-bit alpha to 32-bit

					png_set_gray_to_rgb(png_ptr);
#if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_BGR
					// flip the RGBA pixels to BGRA

					png_set_bgr(png_ptr);
#endif
					pixel_depth = 32;

					break;

				default:
					throw FI_MSG_ERROR_UNSUPPORTED_FORMAT;
			}

			// unlike the example in the libpng documentation, we have *no* idea where
			// this file may have come from--so if it doesn't have a file gamma, don't
			// do any correction ("do no harm")

			if (png_get_valid(png_ptr, info_ptr, PNG_INFO_gAMA)) {
				double gamma = 0;
				double screen_gamma = 2.2;

				if (png_get_gAMA(png_ptr, info_ptr, &gamma) && ( flags & PNG_IGNOREGAMMA ) != PNG_IGNOREGAMMA) {
					png_set_gamma(png_ptr, screen_gamma, gamma);
				}
			}

			// all transformations have been registered; now update info_ptr data

			png_read_update_info(png_ptr, info_ptr);

			// color type may have changed, due to our transformations

			color_type = png_get_color_type(png_ptr,info_ptr);

			// create a DIB and write the bitmap header
			// set up the DIB palette, if needed

			switch (color_type) {
				case PNG_COLOR_TYPE_RGB:
					png_set_invert_alpha(png_ptr);

					if(image_type == FIT_BITMAP) {
						dib = FreeImage_AllocateHeader(header_only, width, height, 24, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK);
					} else {
						dib = FreeImage_AllocateHeaderT(header_only, image_type, width, height, pixel_depth);
					}
					break;

				case PNG_COLOR_TYPE_RGB_ALPHA:
					if(image_type == FIT_BITMAP) {
						dib = FreeImage_AllocateHeader(header_only, width, height, 32, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK);
					} else {
						dib = FreeImage_AllocateHeaderT(header_only, image_type, width, height, pixel_depth);
					}
					break;

				case PNG_COLOR_TYPE_PALETTE:
					dib = FreeImage_AllocateHeader(header_only, width, height, pixel_depth);

					png_get_PLTE(png_ptr,info_ptr, &png_palette, &palette_entries);

					palette_entries = MIN((unsigned)palette_entries, FreeImage_GetColorsUsed(dib));
					palette = FreeImage_GetPalette(dib);

					// store the palette

					for (i = 0; i < palette_entries; i++) {
						palette[i].rgbRed   = png_palette[i].red;
						palette[i].rgbGreen = png_palette[i].green;
						palette[i].rgbBlue  = png_palette[i].blue;
					}
					break;

				case PNG_COLOR_TYPE_GRAY:
					dib = FreeImage_AllocateHeaderT(header_only, image_type, width, height, pixel_depth);

					if(pixel_depth <= 8) {
						palette = FreeImage_GetPalette(dib);
						palette_entries = 1 << pixel_depth;

						for (i = 0; i < palette_entries; i++) {
							palette[i].rgbRed   =
							palette[i].rgbGreen =
							palette[i].rgbBlue  = (BYTE)((i * 255) / (palette_entries - 1));
						}
					}
					break;

				default:
					throw FI_MSG_ERROR_UNSUPPORTED_FORMAT;
			}

			// store the transparency table

			if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
				// array of alpha (transparency) entries for palette
				png_bytep trans_alpha = NULL;
				// number of transparent entries
				int num_trans = 0;						
				// graylevel or color sample values of the single transparent color for non-paletted images
				png_color_16p trans_color = NULL;

				png_get_tRNS(png_ptr, info_ptr, &trans_alpha, &num_trans, &trans_color);

				if((color_type == PNG_COLOR_TYPE_GRAY) && trans_color) {
					// single transparent color
					if (trans_color->gray < palette_entries) { 
						BYTE table[256]; 
						memset(table, 0xFF, palette_entries); 
						table[trans_color->gray] = 0; 
						FreeImage_SetTransparencyTable(dib, table, palette_entries); 
					}
				} else if((color_type == PNG_COLOR_TYPE_PALETTE) && trans_alpha) {
					// transparency table
					FreeImage_SetTransparencyTable(dib, (BYTE *)trans_alpha, num_trans);
				}
			}

			// store the background color 

			if (png_get_valid(png_ptr, info_ptr, PNG_INFO_bKGD)) {
				// Get the background color to draw transparent and alpha images over.
				// Note that even if the PNG file supplies a background, you are not required to
				// use it - you should use the (solid) application background if it has one.

				png_color_16p image_background = NULL;
				RGBQUAD rgbBkColor;

				if (png_get_bKGD(png_ptr, info_ptr, &image_background)) {
					rgbBkColor.rgbRed      = (BYTE)image_background->red;
					rgbBkColor.rgbGreen    = (BYTE)image_background->green;
					rgbBkColor.rgbBlue     = (BYTE)image_background->blue;
					rgbBkColor.rgbReserved = 0;

					FreeImage_SetBackgroundColor(dib, &rgbBkColor);
				}
			}

			// get physical resolution

			if (png_get_valid(png_ptr, info_ptr, PNG_INFO_pHYs)) {
				png_uint_32 res_x, res_y;
				
				// we'll overload this var and use 0 to mean no phys data,
				// since if it's not in meters we can't use it anyway

				int res_unit_type = PNG_RESOLUTION_UNKNOWN;

				png_get_pHYs(png_ptr,info_ptr, &res_x, &res_y, &res_unit_type);

				if (res_unit_type == PNG_RESOLUTION_METER) {
					FreeImage_SetDotsPerMeterX(dib, res_x);
					FreeImage_SetDotsPerMeterY(dib, res_y);
				}
			}

			// get possible ICC profile

			if (png_get_valid(png_ptr, info_ptr, PNG_INFO_iCCP)) {
				png_charp profile_name = NULL;
				png_bytep profile_data = NULL;
				png_uint_32 profile_length = 0;
				int  compression_type;

				png_get_iCCP(png_ptr, info_ptr, &profile_name, &compression_type, &profile_data, &profile_length);

				// copy ICC profile data (must be done after FreeImage_AllocateHeader)

				FreeImage_CreateICCProfile(dib, profile_data, profile_length);
			}

			// --- header only mode => clean-up and return

			if (header_only) {
				// get possible metadata (it can be located both before and after the image data)
				ReadMetadata(png_ptr, info_ptr, dib);
				if (png_ptr) {
					// clean up after the read, and free any memory allocated - REQUIRED
					png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
				}
				return dib;
			}

			// set the individual row_pointers to point at the correct offsets

			row_pointers = (png_bytepp)malloc(height * sizeof(png_bytep));

			if (!row_pointers) {
				png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
				FreeImage_Unload(dib);
				return NULL;
			}

			// read in the bitmap bits via the pointer table
			// allow loading of PNG with minor errors (such as images with several IDAT chunks)

			for (png_uint_32 k = 0; k < height; k++) {
				row_pointers[height - 1 - k] = FreeImage_GetScanLine(dib, k);			
			}

			png_set_benign_errors(png_ptr, 1);
			png_read_image(png_ptr, row_pointers);

			// check if the bitmap contains transparency, if so enable it in the header

			if (FreeImage_GetBPP(dib) == 32) {
				if (FreeImage_GetColorType(dib) == FIC_RGBALPHA) {
					FreeImage_SetTransparent(dib, TRUE);
				} else {
					FreeImage_SetTransparent(dib, FALSE);
				}
			}
				
			// cleanup

			if (row_pointers) {
				free(row_pointers);
				row_pointers = NULL;
			}

			// read the rest of the file, getting any additional chunks in info_ptr

			png_read_end(png_ptr, info_ptr);

			// get possible metadata (it can be located both before and after the image data)

			ReadMetadata(png_ptr, info_ptr, dib);

			if (png_ptr) {
				// clean up after the read, and free any memory allocated - REQUIRED
				png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
			}

			return dib;

		} catch (const char *text) {
			if (png_ptr) {
				png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
			}
			if (row_pointers) {
				free(row_pointers);
			}
			if (dib) {
				FreeImage_Unload(dib);			
			}
			FreeImage_OutputMessageProc(s_format_id, text);
			
			return NULL;
		}
	}			

	return NULL;
}
Пример #3
0
static int /* 0 on success, else an error code */
write_png(const char **name, FILE *fp, int color_type, int bit_depth,
   volatile png_fixed_point gamma,
   unsigned int filters, unsigned int *colors)
{
	png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, name, makepng_error, makepng_warning);
	volatile png_infop info_ptr = NULL;
	volatile png_bytep row = NULL;

	if (png_ptr == NULL) {
		fprintf(stderr, "makepng: OOM allocating write structure\n");
		return 1;
	}

	if (setjmp(png_jmpbuf(png_ptr))) {
		png_structp nv_ptr = png_ptr;
		png_infop nv_info = info_ptr;

		png_ptr = NULL;
		info_ptr = NULL;
		png_destroy_write_struct(&nv_ptr, &nv_info);
		if (row != NULL) free(row);
		return 1;
	}

	/* Allow benign errors so that we can write PNGs with errors */
	png_set_benign_errors(png_ptr, 1/*allowed*/);
	png_init_io(png_ptr, fp);

	info_ptr = png_create_info_struct(png_ptr);
	if (info_ptr == NULL) png_error(png_ptr, "OOM allocating info structure");

	unsigned int size = 500;
	unsigned int i;

	png_set_IHDR(png_ptr, info_ptr, size, size, bit_depth, color_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);

	if (gamma == PNG_DEFAULT_sRGB) png_set_sRGB(png_ptr, info_ptr, PNG_sRGB_INTENT_ABSOLUTE);

	/* Write the file header. */
	png_write_info(png_ptr, info_ptr);

	/* Restrict the filters */
	png_set_filter(png_ptr, PNG_FILTER_TYPE_BASE, filters);

	int passes = png_set_interlace_handling(png_ptr);
	int pass;
	png_size_t rowbytes = png_get_rowbytes(png_ptr, info_ptr);

	row = malloc(rowbytes);

	if (row == NULL) png_error(png_ptr, "OOM allocating row buffer");

	for (pass = 0; pass < passes; ++pass) {
		unsigned int y;
		for (y=0; y<size; ++y) {
			generate_row(row, rowbytes, y, color_type, bit_depth);
			png_write_row(png_ptr, row);
		}
	}

	/* Finish writing the file. */
	png_write_end(png_ptr, info_ptr);


	png_structp nv_ptr = png_ptr;
	png_infop nv_info = info_ptr;

	png_ptr = NULL;
	info_ptr = NULL;
	png_destroy_write_struct(&nv_ptr, &nv_info);

	free(row);
	return 0;
}