bool FPMWritePNGCustom(FloatPixMapRef srcPM, png_voidp ioPtr, png_rw_ptr writeDataFn, png_flush_ptr flushDataFn, FPMWritePNGFlags options, FPMGammaFactor sourceGamma, FPMGammaFactor fileGamma, FPMPNGErrorHandler errorHandler) { if (srcPM != NULL) { bool success = false; png_structp png = NULL; png_infop pngInfo = NULL; FloatPixMapRef pm = NULL; // Prepare data. FPMDimension width = FPMGetWidth(srcPM); FPMDimension height = FPMGetHeight(srcPM); if (width > UINT32_MAX || height > UINT32_MAX) { if (errorHandler != NULL) errorHandler("image is too large for PNG format.", true); return false; } pm = FPMCopy(srcPM); if (pm == NULL) return false; unsigned steps = (options & kFPMWritePNG16BPC) ? 0x10000 : 0x100; FPMApplyGamma(pm, sourceGamma, fileGamma, steps); FPMQuantize(pm, 0.0f, 1.0f, 0.0f, steps - 1, steps, (options & kFPMQuantizeDither & kFPMQuantizeJitter) | kFMPQuantizeClip | kFMPQuantizeAlpha); png = png_create_write_struct(PNG_LIBPNG_VER_STRING, errorHandler, PNGError, PNGWarning); if (png == NULL) goto FAIL; pngInfo = png_create_info_struct(png); if (pngInfo == NULL) goto FAIL; if (setjmp(png_jmpbuf(png))) { // libpng will jump here on error. goto FAIL; } png_set_write_fn(png, ioPtr, writeDataFn, flushDataFn); png_set_IHDR(png, pngInfo, width, height, (options & kFPMWritePNG16BPC) ? 16 : 8,PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); if (fileGamma == kFPMGammaSRGB) { png_set_sRGB_gAMA_and_cHRM(png, pngInfo, PNG_sRGB_INTENT_PERCEPTUAL); } else { png_set_gAMA(png, pngInfo, fileGamma); } /* Select function used to transform a row of data to PNG-friendly format. NOTE: these work in place,and overwrite the data in pm, which is OK since we copied it. */ RowTransformer transformer = NULL; if (options & kFPMWritePNG16BPC) { transformer = TransformRow16; } else { transformer = TransformRow8; } png_write_info(png, pngInfo); size_t i; size_t rowOffset = FPMGetRowByteCount(pm); png_bytep row = (png_bytep)FPMGetBufferPointer(pm); for (i = 0; i < height; i++) { transformer(row, width); png_write_row(png, row); row += rowOffset; } png_write_end(png, pngInfo); success = true; FAIL: if (png != NULL) png_destroy_write_struct(&png, &pngInfo); FPMRelease(&pm); return success; } else { return false; } }
void PNGImageFileWriter::write( const char* filename, const ICanvas& image, const ImageAttributes& image_attributes) { // Retrieve canvas properties. const CanvasProperties& props = image.properties(); // todo: lift these limitations. assert(props.m_channel_count == 3 || props.m_channel_count == 4); // Open the file in write mode. FILE* fp = fopen(filename, "wb"); if (fp == 0) throw ExceptionIOError(); // Allocate and initialize the png_struct structure. png_structp png_ptr = png_create_write_struct( PNG_LIBPNG_VER_STRING, fp, error_callback, warning_callback); if (png_ptr == 0) { fclose(fp); throw ExceptionMemoryError(); } // Allocate the png_info structure. png_infop info_ptr = png_create_info_struct(png_ptr); if (info_ptr == 0) { png_destroy_write_struct(&png_ptr, 0); fclose(fp); throw ExceptionMemoryError(); } // Set up the output control. png_init_io(png_ptr, fp); // Set image information. png_set_IHDR( png_ptr, info_ptr, static_cast<png_uint_32>(props.m_canvas_width), static_cast<png_uint_32>(props.m_canvas_height), 8, // bit depth -- todo: allow higher bit depths props.m_channel_count == 4 ? PNG_COLOR_TYPE_RGB_ALPHA : PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); // Mark the image as being sRGB (implying specific gamma and color matching functions). // See http://www.vias.org/pngguide/chapter10_07.html for details about intents. png_set_sRGB_gAMA_and_cHRM( png_ptr, info_ptr, PNG_sRGB_INTENT_PERCEPTUAL); // todo: allow the user to select different intents // Set the number of significant bits for each of the R, G, B and A channels. // todo: are we required to provide these information? png_color_8 sig_bit; sig_bit.red = 8; sig_bit.green = 8; sig_bit.blue = 8; sig_bit.alpha = 8; sig_bit.gray = 8; // for completeness png_set_sBIT(png_ptr, info_ptr, &sig_bit); // Add image attributes. vector<png_text_struct> text_chunks; add_attributes( png_ptr, info_ptr, text_chunks, image_attributes); if (!text_chunks.empty()) { png_set_text( png_ptr, info_ptr, &text_chunks[0], static_cast<int>(text_chunks.size())); } // Write the file header information. png_write_info(png_ptr, info_ptr); // Create the temporary buffer holding one row of tiles in target format. vector<uint8> buffer( props.m_canvas_width * props.m_tile_height * props.m_channel_count); // Construct pointers to each row of the temporary buffer. vector<uint8*> buffer_rows(props.m_tile_height); for (size_t y = 0; y < props.m_tile_height; ++y) buffer_rows[y] = &buffer[y * props.m_canvas_width * props.m_channel_count]; // Loop over the rows of tiles. for (size_t tile_y = 0; tile_y < props.m_tile_count_y; ++tile_y) { // Convert this row of tiles to target format. for (size_t tile_x = 0; tile_x < props.m_tile_count_x; ++tile_x) { const Tile& tile = image.tile(tile_x, tile_y); assert(tile.get_height() <= props.m_tile_height); for (size_t y = 0; y < tile.get_height(); ++y) { for (size_t x = 0; x < tile.get_width(); ++x) { // Horizontal coordinate of the pixel in the temporary buffer. const size_t buffer_x = tile_x * props.m_tile_width + x; // Index of the pixel in the temporary buffer. const size_t buffer_index = (y * props.m_canvas_width + buffer_x) * props.m_channel_count; // Fetch the pixel at coordinates (x, y) in the tile, // perform format conversion if necessary, and store // the converted pixel into the temporary buffer. if (tile.get_channel_count() == 3) { Color3i pixel; tile.get_pixel(x, y, pixel); buffer[buffer_index + 0] = pixel[0]; buffer[buffer_index + 1] = pixel[1]; buffer[buffer_index + 2] = pixel[2]; } else { assert(tile.get_channel_count() == 4); Color4i pixel; tile.get_pixel(x, y, pixel); buffer[buffer_index + 0] = pixel[0]; buffer[buffer_index + 1] = pixel[1]; buffer[buffer_index + 2] = pixel[2]; buffer[buffer_index + 3] = pixel[3]; } } } } // Write this row of tiles to the file. const size_t row_count = image.tile(0, tile_y).get_height(); png_write_rows( png_ptr, &buffer_rows[0], static_cast<png_uint_32>(row_count)); } // Finish writing the file. png_write_end(png_ptr, 0); // Deallocate the png_struct and png_info structures. png_destroy_write_struct(&png_ptr, &info_ptr); // Deallocate text chunks. destroy_text_chunks(text_chunks); // Close the file. fclose(fp); }
static gint export_png (GeglOperation *operation, GeglBuffer *input, const GeglRectangle *result, png_structp png, png_infop info, gint compression, gint bit_depth) { gint i, src_x, src_y; png_uint_32 width, height; guchar *pixels; png_color_16 white; int png_color_type; gchar format_string[16]; const Babl *format; src_x = result->x; src_y = result->y; width = result->width; height = result->height; { const Babl *babl = gegl_buffer_get_format (input); if (bit_depth != 16) bit_depth = 8; if (babl_format_has_alpha (babl)) if (babl_format_get_n_components (babl) != 2) { png_color_type = PNG_COLOR_TYPE_RGB_ALPHA; strcpy (format_string, "R'G'B'A "); } else { png_color_type = PNG_COLOR_TYPE_GRAY_ALPHA; strcpy (format_string, "Y'A "); } else if (babl_format_get_n_components (babl) != 1) { png_color_type = PNG_COLOR_TYPE_RGB; strcpy (format_string, "R'G'B' "); } else { png_color_type = PNG_COLOR_TYPE_GRAY; strcpy (format_string, "Y' "); } } if (bit_depth == 16) strcat (format_string, "u16"); else strcat (format_string, "u8"); if (setjmp (png_jmpbuf (png))) return -1; png_set_compression_level (png, compression); png_set_IHDR (png, info, width, height, bit_depth, png_color_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_DEFAULT); if (png_color_type == PNG_COLOR_TYPE_RGB || png_color_type == PNG_COLOR_TYPE_RGB_ALPHA) { white.red = 0xff; white.blue = 0xff; white.green = 0xff; png_set_sRGB_gAMA_and_cHRM (png, info, PNG_sRGB_INTENT_RELATIVE); } else white.gray = 0xff; png_set_bKGD (png, info, &white); png_write_info (png, info); #if BYTE_ORDER == LITTLE_ENDIAN if (bit_depth > 8) png_set_swap (png); #endif format = babl_format (format_string); pixels = g_malloc0 (width * babl_format_get_bytes_per_pixel (format)); for (i=0; i< height; i++) { GeglRectangle rect; rect.x = src_x; rect.y = src_y+i; rect.width = width; rect.height = 1; gegl_buffer_get (input, &rect, 1.0, babl_format (format_string), pixels, GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); png_write_rows (png, &pixels, 1); } png_write_end (png, info); g_free (pixels); return 0; }
PyObject * save_png_fast_progressive (char *filename, int w, int h, bool has_alpha, PyObject *data_generator, bool write_legacy_png) { png_structp png_ptr = NULL; png_infop info_ptr = NULL; PyObject * result = NULL; int bpc; FILE * fp = NULL; PyObject *iterator = NULL; /* TODO: try if this silliness helps #if defined(PNG_LIBPNG_VER) && (PNG_LIBPNG_VER >= 10200) png_uint_32 mask, flags; flags = png_get_asm_flags(png_ptr); mask = png_get_asm_flagmask(PNG_SELECT_READ | PNG_SELECT_WRITE); png_set_asm_flags(png_ptr, flags | mask); #endif */ bpc = 8; fp = fopen(filename, "wb"); if (!fp) { PyErr_SetFromErrno(PyExc_IOError); //PyErr_Format(PyExc_IOError, "Could not open PNG file for writing: %s", filename); goto cleanup; } png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, (png_voidp)NULL, png_write_error_callback, NULL); if (!png_ptr) { PyErr_SetString(PyExc_MemoryError, "png_create_write_struct() failed"); goto cleanup; } info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { PyErr_SetString(PyExc_MemoryError, "png_create_info_struct() failed"); goto cleanup; } if (setjmp(png_jmpbuf(png_ptr))) { goto cleanup; } png_init_io(png_ptr, fp); png_set_IHDR (png_ptr, info_ptr, w, h, bpc, has_alpha ? PNG_COLOR_TYPE_RGB_ALPHA : PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); if (! write_legacy_png) { // Internal data is sRGB by the time it gets here. // Explicitly save with the recommended chunks to advertise that fact. png_set_sRGB_gAMA_and_cHRM (png_ptr, info_ptr, PNG_sRGB_INTENT_PERCEPTUAL); } // default (all filters enabled): 1350ms, 3.4MB //png_set_filter(png_ptr, 0, PNG_FILTER_NONE); // 790ms, 3.8MB //png_set_filter(png_ptr, 0, PNG_FILTER_PAETH); // 980ms, 3.5MB png_set_filter(png_ptr, 0, PNG_FILTER_SUB); // 760ms, 3.4MB //png_set_compression_level(png_ptr, 0); // 0.49s, 32MB //png_set_compression_level(png_ptr, 1); // 0.98s, 9.6MB png_set_compression_level(png_ptr, 2); // 1.08s, 9.4MB //png_set_compression_level(png_ptr, 9); // 18.6s, 9.3MB png_write_info(png_ptr, info_ptr); if (!has_alpha) { // input array format format is rgbu png_set_filler(png_ptr, 0, PNG_FILLER_AFTER); } { iterator = PyObject_GetIter(data_generator); if (!iterator) goto cleanup; int y = 0; while (y < h) { int rows; PyObject * arr = PyIter_Next(iterator); if (PyErr_Occurred()) goto cleanup; assert(arr); // iterator should have data assert(PyArray_ISALIGNED(arr)); assert(PyArray_NDIM(arr) == 3); assert(PyArray_DIM(arr, 1) == w); assert(PyArray_DIM(arr, 2) == 4); // rgbu assert(PyArray_TYPE(arr) == NPY_UINT8); assert(PyArray_STRIDE(arr, 1) == 4); assert(PyArray_STRIDE(arr, 2) == 1); rows = PyArray_DIM(arr, 0); assert(rows > 0); y += rows; png_bytep p = (png_bytep)PyArray_DATA(arr); for (int row=0; row<rows; row++) { png_write_row (png_ptr, p); p += PyArray_STRIDE(arr, 0); } Py_DECREF(arr); } assert(y == h); PyObject * obj = PyIter_Next(iterator); assert(!obj); // iterator should be finished if (PyErr_Occurred()) goto cleanup; } png_write_end (png_ptr, NULL); result = Py_BuildValue("{}"); cleanup: if (iterator) Py_DECREF(iterator); if (info_ptr) png_destroy_write_struct(&png_ptr, &info_ptr); if (fp) fclose(fp); return result; }
/*--------------------------------------------------------------------------- Saves a PNG file. Image : Image to save. Path : Path to image file, relative to the current working directory. Compress : Specifies the compression level to use for encoding. ---------------------------------------------------------------------------*/ void PNG::Save(const Texture &Image, const std::string &Path, CompLevel Compress) { Destroy(); //Important - set class to writing mode Mode = PNG::ModeWrite; vector2u Res = Image.Resolution(); if (Res.U < 1 || Res.V < 1) {return;} #if defined (WINDOWS) if (fopen_s(&File, Path.c_str(), "wb") != 0) {throw dexception("Failed to open file: %s.", Path.c_str());} #else File = fopen(Path.c_str(), "wb"); if (File == nullptr) {throw dexception("Failed to open file: %s.", Path.c_str());} #endif PngPtr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); if (PngPtr == nullptr) {throw dexception("png_create_write_struct( ) failed.");} InfoPtr = png_create_info_struct(PngPtr); if (InfoPtr == nullptr) {throw dexception("png_create_info_struct( ) failed.");} if (setjmp(png_jmpbuf(PngPtr))) {throw dexception("setjmp( ) failed.");} png_init_io(PngPtr, File); png_set_compression_level(PngPtr, (int)Compress); int BitDepth, ColourType; switch (Image.DataType()) { case Texture::TypeAlpha : BitDepth = 8; ColourType = PNG_COLOR_TYPE_GRAY; break; case Texture::TypeLum : BitDepth = 8; ColourType = PNG_COLOR_TYPE_GRAY; break; case Texture::TypeDepth : BitDepth = 16; ColourType = PNG_COLOR_TYPE_GRAY; break; case Texture::TypeRGB : BitDepth = 8; ColourType = PNG_COLOR_TYPE_RGB; break; case Texture::TypeRGBA : BitDepth = 8; ColourType = PNG_COLOR_TYPE_RGB_ALPHA; break; default : throw dexception("Unsupported image type."); break; } png_set_IHDR(PngPtr, InfoPtr, (png_uint_32)Res.U, (png_uint_32)Res.V, BitDepth, ColourType, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); png_set_sRGB(PngPtr, InfoPtr, PNG_sRGB_INTENT_ABSOLUTE); png_set_sRGB_gAMA_and_cHRM(PngPtr, InfoPtr, PNG_sRGB_INTENT_ABSOLUTE); png_color_16 BackGnd; BackGnd.red = 0; BackGnd.green = 0; BackGnd.blue = 0; png_set_bKGD(PngPtr, InfoPtr, &BackGnd); png_text PngText[2]; memset(PngText, 0, sizeof(PngText)); PngText[0].compression = PNG_TEXT_COMPRESSION_NONE; PngText[0].key = const_cast<png_charp>("Software"); PngText[0].text = const_cast<png_charp>(AppName); PngText[0].text_length = strlen(PngText[0].text); PngText[1].compression = PNG_TEXT_COMPRESSION_NONE; PngText[1].key = const_cast<png_charp>("Author"); PngText[1].text = const_cast<png_charp>(AppAuthor); PngText[1].text_length = strlen(PngText[1].text); png_set_text(PngPtr, InfoPtr, PngText, 2); png_write_info(PngPtr, InfoPtr); //Populate row pointer array Rows.Create((usize)Res.V); for (uiter I = 0; I < (usize)Res.V; I++) { Rows[I] = Image.Address(0, I); } //Encode png_write_image(PngPtr, Rows.Pointer()); png_write_end(PngPtr, nullptr); //Clean up Destroy(); }