bluecore/corona/src/SavePNG.cpp

169 lines
4.5 KiB
C++

#include <memory>
#include <png.h>
#include "Debug.h"
#include "Save.h"
#include "Types.h"
namespace corona {
void PNG_write(png_structp png_ptr, png_bytep data, png_size_t length) {
File* file = (File*)png_get_io_ptr(png_ptr);
if (file->write(data, length) != int(length)) {
png_error(png_ptr, "Write error");
}
}
void PNG_flush(png_structp png_ptr) {
// assume that files always flush
}
bool SavePNG(File* file, Image* image) {
COR_GUARD("SavePNG");
if (!image) {
return false;
}
// If the image format isn't supported directly by this function,
// clone to a supported format and try to save with that.
switch (image->getFormat()) {
case PF_R8G8B8A8:
case PF_R8G8B8:
case PF_I8:
break;
default: {
COR_LOG("Unsupported pixel format... cloning");
std::auto_ptr<Image> cloned(CloneImage(image, PF_R8G8B8A8));
return SavePNG(file, cloned.get());
}
}
// create write struct
png_structp png_ptr = png_create_write_struct(
PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!png_ptr) {
return false;
}
// error handling!
if (setjmp(png_jmpbuf(png_ptr))) {
png_destroy_write_struct(&png_ptr, NULL);
return false;
}
// create info struct
png_infop info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr) {
png_destroy_write_struct(&png_ptr, NULL);
return false;
}
int width = image->getWidth();
int height = image->getHeight();
// set image characteristics
png_set_write_fn(png_ptr, file, PNG_write, PNG_flush);
int color_format = 0; // png output format
int color_format_bpp = 0; // png bytes per pixel
bool color_format_paletted = false; // png palette needed flag
// figure out output format
switch (image->getFormat()) {
case PF_R8G8B8A8:
color_format = PNG_COLOR_TYPE_RGB_ALPHA;
color_format_bpp = 4;
break;
case PF_R8G8B8:
color_format = PNG_COLOR_TYPE_RGB;
color_format_bpp = 3;
break;
case PF_I8:
color_format = PNG_COLOR_TYPE_PALETTE;
color_format_bpp = 1;
color_format_paletted = true;
break;
default:
// Unsupported format. This should already be taken care of
// by the test at the beginning of this function.
png_destroy_write_struct(&png_ptr, &info_ptr);
return false;
}
png_set_IHDR(
png_ptr, info_ptr,
width, height,
8,
color_format,
PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT,
PNG_FILTER_TYPE_DEFAULT);
png_color* png_palette = 0;
if (color_format_paletted) {
COR_LOG("Saving palettized image...");
int image_palette_format = image->getPaletteFormat(); // palette format
int image_palette_size = image->getPaletteSize(); // palette size
// allocate png palette and get pointer to image palette
png_palette = (png_color*)png_malloc(
png_ptr, sizeof(png_color) * image_palette_size);
byte* image_palette = (byte*)image->getPalette();
if (image_palette_format == PF_R8G8B8) {
// 24 bit source palette
for (int i = 0; i < image_palette_size; i++) {
// copy entry directly
png_palette[i].red = *image_palette++;
png_palette[i].green = *image_palette++;
png_palette[i].blue = *image_palette++;
}
} else if (image_palette_format == PF_R8G8B8A8) {
// 32 bit source palette
for (int i = 0; i < image_palette_size; i++) {
// copy entry, skip alpha
png_palette[i].red = *image_palette++;
png_palette[i].green = *image_palette++;
png_palette[i].blue = *image_palette++;
image_palette++;
}
}
// write palette
png_set_PLTE(png_ptr, info_ptr, png_palette, image_palette_size);
}
byte* pixels = (byte*)image->getPixels();
// build rows
void** rows = (void**)png_malloc(png_ptr, sizeof(void*) * height);
for (int i = 0; i < height; ++i) {
rows[i] = png_malloc(png_ptr, color_format_bpp * width);
memcpy(rows[i], pixels, color_format_bpp * width);
pixels += width * color_format_bpp;
}
png_set_rows(png_ptr, info_ptr, (png_bytepp)rows);
info_ptr->valid |= PNG_INFO_IDAT;
// actually write the image
png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);
// clean up memory
for (int i = 0; i < height; ++i) {
png_free(png_ptr, rows[i]);
}
png_free(png_ptr, rows);
if (png_palette) {
png_free(png_ptr, png_palette);
}
png_destroy_write_struct(&png_ptr, &info_ptr);
return true;
}
}