273 lines
8.8 KiB
C++
273 lines
8.8 KiB
C++
/** @file
|
|
* @author Edouard DUPIN
|
|
* @copyright 2011, Edouard DUPIN, all right reserved
|
|
* @license APACHE v2.0 (see license file)
|
|
*/
|
|
|
|
#include <etk/types.hpp>
|
|
#include <egami/debug.hpp>
|
|
#include <egami/Image.hpp>
|
|
#include <egami/wrapperPNG.hpp>
|
|
#include <etk/os/FSNode.hpp>
|
|
#include <png/png.h>
|
|
|
|
// we must change the access of the IO of the png lib :
|
|
static void local_ReadData(png_structp png_ptr, png_bytep data, png_size_t length) {
|
|
etk::FSNode* fileNode = static_cast<etk::FSNode*>(png_get_io_ptr(png_ptr));
|
|
if (fileNode != nullptr) {
|
|
fileNode->fileRead(data, 1, length);
|
|
}
|
|
}
|
|
/*
|
|
static void LocalWriteData(png_structp png_ptr, png_bytep data, png_size_t length)
|
|
{
|
|
etk::FSNode* fileNode = static_cast<etk::FSNode*>(png_get_io_ptr(png_ptr));
|
|
if (NULL!=fileNode) {
|
|
fileNode->FileWrite(data, 1, length);
|
|
}
|
|
}
|
|
|
|
static void localFlushData(png_structp png_ptr)
|
|
{
|
|
etk::FSNode* fileNode = static_cast<etk::FSNode*>(png_get_io_ptr(png_ptr));
|
|
if (NULL!=fileNode) {
|
|
fileNode->FileFlush();
|
|
}
|
|
}
|
|
*/
|
|
|
|
void user_error_fn(png_structp _pngPtr, png_const_charp _errorMsg) {
|
|
EGAMI_ERROR("libpng error: '" << _errorMsg << "'");
|
|
}
|
|
|
|
void user_warning_fn(png_structp _pngPtr, png_const_charp _warningMsg) {
|
|
EGAMI_WARNING("libpng warning: '" << _warningMsg << "'");
|
|
}
|
|
|
|
egami::Image egami::loadPNG(const std::string& _inputFile) {
|
|
egami::Image out;
|
|
etk::FSNode fileName(_inputFile);
|
|
if (fileName.exist() == false) {
|
|
EGAMI_ERROR("File does not existed='" << fileName << "'");
|
|
return out;
|
|
}
|
|
if(fileName.fileOpenRead() == false) {
|
|
EGAMI_ERROR("Can not find the file name='" << fileName << "'");
|
|
return out;
|
|
}
|
|
unsigned char header[8];
|
|
png_infop info_ptr;
|
|
png_structp png_ptr;
|
|
|
|
if (fileName.fileRead(header,1,8) != 8) {
|
|
EGAMI_ERROR("error loading file header");
|
|
fileName.fileClose();
|
|
return out;
|
|
}
|
|
if (png_sig_cmp(header, 0, 8)) {
|
|
EGAMI_ERROR("Invalid file :" << fileName);
|
|
return out;
|
|
}
|
|
|
|
// PNG read setup
|
|
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, user_error_fn, user_warning_fn);
|
|
if (png_ptr == nullptr) {
|
|
EGAMI_ERROR("Can not Allocate PNG structure");
|
|
fileName.fileClose();
|
|
return out;
|
|
}
|
|
info_ptr = png_create_info_struct(png_ptr);
|
|
if (info_ptr == nullptr) {
|
|
EGAMI_ERROR("Can not Allocate PNG info structure");
|
|
png_destroy_read_struct(&png_ptr, nullptr, nullptr);
|
|
fileName.fileClose();
|
|
return out;
|
|
}
|
|
/*
|
|
if (setjmp(png_jmpbuf(png_ptr))) {
|
|
EGAMI_ERROR(" Can not set the JUMP buffer adresses");
|
|
// Free all of the memory associated with the png_ptr and info_ptr
|
|
png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
|
|
fileName.fileClose();
|
|
return false;
|
|
}
|
|
*/
|
|
// overwrite the read and write function :
|
|
png_set_read_fn(png_ptr,
|
|
&fileName,
|
|
&local_ReadData);
|
|
/*
|
|
png_set_write_fn(png_ptr,
|
|
&fileName,
|
|
&LocalWriteData,
|
|
&localFlushData);
|
|
*/
|
|
// If we have already read some of the signature
|
|
png_set_sig_bytes(png_ptr, 8);
|
|
|
|
png_read_info(png_ptr, info_ptr);
|
|
png_uint_32 width = 0;
|
|
png_uint_32 height = 0;
|
|
int bit_depth = 0;
|
|
int colorType = 0;
|
|
int interlace_type = 0;
|
|
png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &colorType, &interlace_type, nullptr, nullptr);
|
|
// reallocate the image
|
|
EGAMI_VERBOSE("Load PNG image : (" << width << "," << height << ")" );
|
|
switch (colorType) {
|
|
case PNG_COLOR_TYPE_RGBA:
|
|
out.configure(ivec2(width,height), egami::colorType::RGBA8);
|
|
break;
|
|
case PNG_COLOR_TYPE_RGB:
|
|
out.configure(ivec2(width,height), egami::colorType::RGB8);
|
|
break;
|
|
case PNG_COLOR_TYPE_GRAY:
|
|
out.configure(ivec2(width,height), egami::colorType::RGB8);
|
|
break;
|
|
case PNG_COLOR_TYPE_GRAY_ALPHA:
|
|
out.configure(ivec2(width,height), egami::colorType::RGBA8);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Tell libpng to strip 16 bits/color files down to 8 bits/color. Use accurate scaling if it's available, otherwise just chop off the low byte.
|
|
#ifdef PNG_READ_SCALE_16_TO_8_SUPPORTED
|
|
png_set_scale_16(png_ptr);
|
|
#else
|
|
png_set_strip_16(png_ptr);
|
|
#endif
|
|
|
|
// Strip alpha bytes from the input data without combining with the background (not recommended).
|
|
//png_set_strip_alpha(png_ptr);
|
|
|
|
// Extract multiple pixels with bit depths of 1, 2, and 4 from a single byte into separate bytes (useful for paletted and grayscale images).
|
|
png_set_packing(png_ptr);
|
|
|
|
// Change the order of packed pixels to least significant bit first (not useful if you are using png_set_packing).
|
|
png_set_packswap(png_ptr);
|
|
|
|
/* Expand paletted colors into true RGB triplets */
|
|
if (colorType == PNG_COLOR_TYPE_PALETTE) {
|
|
png_set_palette_to_rgb(png_ptr);
|
|
}
|
|
|
|
// Expand grayscale images to the full 8 bits from 1, 2, or 4 bits/pixel
|
|
if ( colorType == PNG_COLOR_TYPE_GRAY
|
|
&& bit_depth < 8) {
|
|
png_set_expand_gray_1_2_4_to_8(png_ptr);
|
|
}
|
|
|
|
// Expand paletted or RGB images with transparency to full alpha channels so the data will be available as RGBA quartets.
|
|
if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS) != 0) {
|
|
png_set_tRNS_to_alpha(png_ptr);
|
|
}
|
|
|
|
/* Set the background color to draw transparent and alpha images over.
|
|
* It is possible to set the red, green, and blue components directly
|
|
* for paletted images instead of supplying a palette index. 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::16 my_background, *image_background;
|
|
if (png_get_bKGD(png_ptr, info_ptr, &image_background) != 0) {
|
|
png_set_background(png_ptr, image_background, PNG_BACKGROUND_GAMMA_FILE, 1, 1.0);
|
|
} else {
|
|
png_set_background(png_ptr, &my_background, PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0);
|
|
}
|
|
*/
|
|
/* Optional call to gamma correct and add the background to the palette
|
|
* and update info structure. REQUIRED if you are expecting libpng to
|
|
* update the palette for you (ie you selected such a transform above).
|
|
*/
|
|
png_read_update_info(png_ptr, info_ptr);
|
|
|
|
// Allocate the memory to hold the image using the fields of info_ptr.
|
|
// The easiest way to read the image:
|
|
png_bytep row_pointers[height];
|
|
/* Clear the pointer array */
|
|
for (png_uint_32 row = 0; row < height; row++) {
|
|
row_pointers[row] = nullptr;
|
|
}
|
|
for (png_uint_32 row = 0; row < height; row++) {
|
|
row_pointers[row] = (png_bytep)png_malloc(png_ptr, png_get_rowbytes(png_ptr, info_ptr));
|
|
}
|
|
EGAMI_DEBUG("Load image: " << _inputFile);
|
|
png_read_image(png_ptr, row_pointers);
|
|
EGAMI_DEBUG("Load image: " << _inputFile << " DONE");
|
|
// Read rest of file, and get additional chunks in info_ptr - REQUIRED
|
|
png_read_end(png_ptr, info_ptr);
|
|
|
|
//png_set_expand(png_ptr);
|
|
|
|
etk::Color<> tmpColor(0,0,0,0);
|
|
switch (colorType) {
|
|
case PNG_COLOR_TYPE_RGBA:
|
|
EGAMI_DEBUG("colorset: PNG_COLOR_TYPE_RGBA");
|
|
// Conversion to OpenGL texture
|
|
for (png_uint_32 yyy = 0; yyy < height; ++yyy) {
|
|
png_byte* row = row_pointers[yyy];
|
|
for (png_uint_32 xxx = 0; xxx < width; ++xxx) {
|
|
png_byte* ptr = &(row[xxx*4]);
|
|
tmpColor.set(ptr[0], ptr[1], ptr[2], ptr[3]);
|
|
out.set(ivec2(xxx,yyy), tmpColor);
|
|
}
|
|
}
|
|
break;
|
|
case PNG_COLOR_TYPE_RGB:
|
|
EGAMI_DEBUG("colorset: PNG_COLOR_TYPE_RGB");
|
|
// Conversion to OpenGL texture
|
|
for (png_uint_32 yyy = 0; yyy < height; ++yyy) {
|
|
png_byte* row = row_pointers[yyy];
|
|
for (png_uint_32 xxx = 0; xxx < width; ++xxx) {
|
|
png_byte* ptr = &(row[xxx*3]);
|
|
tmpColor.set(ptr[0], ptr[1], ptr[2]);
|
|
out.set(ivec2(xxx,yyy), tmpColor);
|
|
}
|
|
}
|
|
break;
|
|
case PNG_COLOR_TYPE_GRAY:
|
|
EGAMI_DEBUG("colorset: PNG_COLOR_TYPE_GRAY");
|
|
// Conversion to OpenGL texture
|
|
for (png_uint_32 yyy = 0; yyy < height; ++yyy) {
|
|
png_byte* row = row_pointers[yyy];
|
|
for (png_uint_32 xxx = 0; xxx < width; ++xxx) {
|
|
png_byte* ptr = &(row[xxx]);
|
|
tmpColor.set(ptr[0], ptr[0], ptr[0]);
|
|
out.set(ivec2(xxx,yyy), tmpColor);
|
|
}
|
|
}
|
|
break;
|
|
case PNG_COLOR_TYPE_GRAY_ALPHA:
|
|
EGAMI_DEBUG("colorset: PNG_COLOR_TYPE_GRAY_ALPHA");
|
|
// Conversion to OpenGL texture
|
|
for (png_uint_32 yyy = 0; yyy < height; ++yyy) {
|
|
png_byte* row = row_pointers[yyy];
|
|
for (png_uint_32 xxx = 0; xxx < width; ++xxx) {
|
|
png_byte* ptr = &(row[xxx*2]);
|
|
tmpColor.set(ptr[0], ptr[0], ptr[0], ptr[1]);
|
|
out.set(ivec2(xxx,yyy), tmpColor);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
EGAMI_ERROR("Must be RGB+alpha?/GRAY+alpha? not supported : " << (int64_t)png_get_color_type(png_ptr, info_ptr));
|
|
if ((png_get_color_type(png_ptr, info_ptr) & PNG_COLOR_MASK_PALETTE) != 0) {
|
|
EGAMI_ERROR(" palette");
|
|
}
|
|
if ((png_get_color_type(png_ptr, info_ptr) & PNG_COLOR_MASK_COLOR) != 0) {
|
|
EGAMI_ERROR(" color");
|
|
}
|
|
if ((png_get_color_type(png_ptr, info_ptr) & PNG_COLOR_MASK_ALPHA) != 0) {
|
|
EGAMI_ERROR(" Alpha");
|
|
}
|
|
return egami::Image();
|
|
}
|
|
fileName.fileClose();
|
|
// Clean up after the read, and free any memory allocated - REQUIRED
|
|
png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
|
|
return out;
|
|
}
|
|
|