Files
igl/external/yimg/YImage.cpp
T

515 lines
14 KiB
C++

#include "YImage.hpp"
#include <iostream>
#include <cassert>
#include <cstdlib>
#include <cstring>
#include <png.h>
#include <zlib.h> // For Z_BEST_SPEED
// for: #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
// we use this to determine the pixel format on the fly
#include <cstddef>
YImage::YImage()
: m_width( 0 ), m_height( 0 ), m_data( NULL )
{
assert( sizeof( YPixel ) == 4 && "YPixel struct shouldn't be padded" ) ;
}
YImage::YImage( const YImage& rhs )
: m_width( 0 ), m_height( 0 ), m_data( NULL )
{
*this = rhs ;
}
YImage&
YImage::operator=( const YImage& rhs )
{
if( !rhs.m_data )
{
assert( 0 == rhs.m_width ) ;
assert( 0 == rhs.m_height ) ;
if( m_data ) free( m_data ) ;
m_data = NULL ;
m_width = 0 ;
m_height = 0 ;
return *this ;
}
if( m_width != rhs.m_width || m_height != rhs.m_height )
{
if( m_data ) free( m_data ) ;
m_width = rhs.m_width ;
m_height = rhs.m_height ;
m_data = (YPixel*) malloc( m_width * m_height * sizeof(YPixel) ) ;
}
assert( m_data ) ;
memcpy( m_data, rhs.m_data, m_width * m_height * sizeof(YPixel) ) ;
return *this ;
}
YImage::~YImage()
{
if( m_data ) free( m_data ) ;
m_data = NULL ;
m_width = m_height = 0 ;
}
YImage::YPixel*
YImage::data()
{
return m_data ;
}
const YImage::YPixel*
YImage::data()
const
{
return m_data ;
}
YImage::YPixel&
YImage::at( int i, int j )
{
assert( i >= 0 && i < m_width ) ;
assert( j >= 0 && j < m_height ) ;
return m_data[ i + j * m_width ] ;
}
const YImage::YPixel&
YImage::at( int i, int j )
const
{
assert( i >= 0 && i < m_width ) ;
assert( j >= 0 && j < m_height ) ;
return m_data[ i + j * m_width ] ;
}
int YImage::width() const
{
return m_width ;
}
int YImage::height() const
{
return m_height ;
}
// Creates a new image with the specified size, preserving as
// much as possible the old image (as in a window resize).
void YImage::resize( int widthh, int heightt )
{
// do nothing
if( m_width == widthh && m_height == heightt )
{
assert( m_data ) ;
return ;
}
YPixel* new_data = (YPixel*) malloc( widthh * heightt * sizeof(YPixel) ) ;
memset( new_data, 0, widthh * heightt * sizeof(YPixel) ) ;
if( m_data )
{
int min_width = std::min( m_width, widthh ) ;
int min_height = std::min( m_height, heightt ) ;
for( int j = 0 ; j < min_height ; ++j )
memcpy( new_data + j*widthh, m_data + j*m_width, min_width * sizeof(YPixel) ) ;
free( m_data ) ;
}
m_width = widthh ;
m_height = heightt ;
m_data = new_data ;
}
void YImage::greyscale()
{
for( int i = 0 ; i < m_width * m_height ; ++i )
{
YPixel* pix = m_data + i ;
int greyval = (int) pix->r + (int) pix->g + (int) pix->b ;
greyval /= 3 ;
pix->r = pix->g = pix->b = greyval ;
}
}
void YImage::flip()
{
for( int j = 0 ; j < m_height / 2 ; ++j )
for( int i = 0 ; i < m_width ; ++i )
{
YPixel* lhs = &at( i,j ) ;
YPixel* rhs = &at( i, m_height - j - 1 ) ;
YPixel temp = *lhs ;
*lhs = *rhs ;
*rhs = temp ;
}
}
void YImage::mirror()
{
for( int j = 0 ; j < m_height ; ++j )
for( int i = 0 ; i < m_width / 2 ; ++i )
{
YPixel* lhs = &at( i,j ) ;
YPixel* rhs = &at( m_width - i - 1, j ) ;
YPixel temp = *lhs ;
*lhs = *rhs ;
*rhs = temp ;
}
}
bool YImage::same( const YImage& rhs ) const
{
if( m_height != rhs.m_height || m_width != rhs.m_width ) return false;
// Check if this->m_data and rhs.m_data point to the same data.
// This also checks for if both are NULL.
if( m_data == rhs.m_data ) return true;
assert( m_data && rhs.m_data );
return 0 == memcmp( m_data, rhs.m_data, m_width * m_height * sizeof(YPixel) );
}
bool YImage::same_rgb( const YImage& rhs ) const
{
if( m_height != rhs.m_height || m_width != rhs.m_width ) return false;
// Check if this->m_data and rhs.m_data point to the same data.
// This also checks for if both are NULL.
if( m_data == rhs.m_data ) return true;
assert( m_data && rhs.m_data );
for( int i = 0; i < m_height*m_width; ++i )
{
if(
m_data[i].r != rhs.m_data[i].r ||
m_data[i].g != rhs.m_data[i].g ||
m_data[i].b != rhs.m_data[i].b )
return false;
}
return true;
}
// We use out own reading/writing functions because libpng may have been compiled using a
// different compiler & libc. That could make the FILE*'s incompatible.
static void user_write_data(png_structp png_ptr, png_bytep data, png_size_t length);
static void user_write_data(png_structp png_ptr, png_bytep data, png_size_t length)
{
png_voidp write_io_ptr = png_get_io_ptr(png_ptr) ;
fwrite( (unsigned char*) data, length, 1, (FILE*) write_io_ptr ) ;
}
static void user_flush_data(png_structp png_ptr);
static void user_flush_data(png_structp png_ptr)
{
png_voidp write_io_ptr = png_get_io_ptr(png_ptr) ;
fflush( (FILE*) write_io_ptr ) ;
}
static void user_read_data(png_structp png_ptr, png_bytep data, png_size_t length) ;
static void user_read_data(png_structp png_ptr, png_bytep data, png_size_t length)
{
png_voidp read_io_ptr = png_get_io_ptr(png_ptr) ;
(void) fread( (unsigned char*) data, length, 1, (FILE*) read_io_ptr ) ;
}
// Some error strings
#define ERROR_STRING_OPEN "Error opening %s\n"
#define ERROR_STRING_WRITING "libpng encountered a problem writing %s\n"
#define ERROR_STRING_INVALID_FILE "Invalid png file: %s\n"
#define ERROR_STRING_LIBERR "libpng encountered a problem (not related to file)\n"
#define ERROR_STRING_MAYBE_FILE "libpng encountered a problem (may be related to file: %s)\n"
bool YImage::save( const char* fname, const bool fast )
const
{
FILE* fp = NULL ;
bool rval = true ;
png_structp png_ptr = NULL ;
png_infop info_ptr = NULL ;
// Open the file for reading in binary mode.
fp = fopen( fname, "wb" ) ;
if( !fp )
{
fprintf( stderr, ERROR_STRING_OPEN, fname ) ;
rval = false ;
goto YImage_save_cleanup ;
}
// Allocate the png structs.
png_ptr = png_create_write_struct( PNG_LIBPNG_VER_STRING, NULL, NULL, NULL ) ;
if( !png_ptr )
{
fprintf( stderr, ERROR_STRING_WRITING, fname ) ;
rval = false ;
goto YImage_save_cleanup ;
}
info_ptr = png_create_info_struct( png_ptr ) ;
if( !info_ptr )
{
fprintf( stderr, ERROR_STRING_WRITING, fname ) ;
rval = false ;
goto YImage_save_cleanup ;
}
// Set up the png error routine.
if( setjmp(png_jmpbuf(png_ptr)) )
{
fprintf( stderr, ERROR_STRING_WRITING, fname ) ;
rval = false ;
goto YImage_save_cleanup ;
}
// Give libpng the FILE*.
// png_init_io( png_ptr, fp ) ;
// or
// use our own write callback
png_set_write_fn( png_ptr, fp, (png_rw_ptr) user_write_data, user_flush_data ) ;
// We'll use the low-level interface since the high-level interface won't handle
// png_set_filler() which we need to tell libpng to strip out the A from our
// 4-byte pixels.
// First we set and write the info struct.
png_set_IHDR(
png_ptr, info_ptr,
m_width, m_height,
8, PNG_COLOR_TYPE_RGB_ALPHA,
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT
) ;
png_write_info( png_ptr, info_ptr ) ;
// If we've been asked to write quickly, speed up the compression.
if( fast ) png_set_compression_level( png_ptr, Z_BEST_SPEED );
// Then we set up transforms.
/*
// 1. tell libpng to strip out the filler byte in our 4-byte pixels
// if YPixel::a comes after any other member (b,g,r), we strip AFTER
if( offsetof( YPixel, a ) > offsetof( YPixel, b ) ) {
png_set_filler( png_ptr, 0, PNG_FILLER_AFTER ) ;
// printf("alpha after\n");
} else {
png_set_filler( png_ptr, 0, PNG_FILLER_BEFORE ) ;
// printf("alpha before\n");
}
*/
if( offsetof( YPixel, a ) < offsetof( YPixel, b ) )
png_set_swap_alpha( png_ptr ) ;
// 2. tell libpng how our color triples are stored (b < r or vice versa)
if( offsetof( YPixel, b ) < offsetof( YPixel, r ) )
{
png_set_bgr( png_ptr ) ;
// printf("bgr\n") ;
}
// else { printf("rgb\n"); }
// printf( "offsetof r, b: %d %d\n", offsetof( YPixel, r ), offsetof( YPixel, b ) );
// Finally we create a row_pointers[] pointing into our data* and write the png out to the FILE*.
{
// 1. allocate row pointers array
png_bytep* row_pointers = (png_bytep*) png_malloc( png_ptr, m_height * sizeof(png_bytep) ) ;
// 2. point row pointers into m_data
for( int i = 0 ; i < m_height ; ++i ) {
row_pointers[i] = (png_bytep) (m_data + i*m_width) ;
}
// 3. write the image data
png_write_image( png_ptr, row_pointers ) ;
// 4. free row pointers array
png_free( png_ptr, row_pointers ) ;
}
// Write out end info. We're done. Fall through to cleanup.
png_write_end( png_ptr, NULL ) ;
YImage_save_cleanup:
png_destroy_write_struct( png_ptr ? &png_ptr : NULL, info_ptr ? &info_ptr : NULL ) ;
if( fp ) fclose( fp ) ;
return rval ;
}
bool YImage::load(const char* fname)
{
FILE* fp = NULL ;
bool rval = true;
png_structp png_ptr = NULL ;
png_infop info_ptr = NULL ;
// for checking the png header
const size_t PNG_BYTES_TO_CHECK = 4 ; // example.c uses 4
png_byte header[ PNG_BYTES_TO_CHECK ] ;
// Open the file for reading in binary mode.
fp = fopen( fname, "rb" ) ;
if( !fp )
{
fprintf( stderr, ERROR_STRING_OPEN, fname ) ;
rval = false;
goto YImage_load_cleanup ;
}
// Check some bytes at the beginning of the file to make sure it's a png.
if( PNG_BYTES_TO_CHECK != fread( header, 1, PNG_BYTES_TO_CHECK, fp ) )
{
fprintf( stderr, ERROR_STRING_INVALID_FILE, fname ) ;
rval = false;
goto YImage_load_cleanup ;
}
if( png_sig_cmp( header, 0, PNG_BYTES_TO_CHECK ) )
{
fprintf( stderr, ERROR_STRING_INVALID_FILE, fname ) ;
rval = false;
goto YImage_load_cleanup ;
}
// Since it looks like we have a good png file, allocate the png structs.
png_ptr = png_create_read_struct( PNG_LIBPNG_VER_STRING, NULL, NULL, NULL ) ;
if( !png_ptr )
{
fprintf( stderr, ERROR_STRING_LIBERR ) ;
rval = false;
goto YImage_load_cleanup ;
}
info_ptr = png_create_info_struct( png_ptr ) ;
if( !info_ptr )
{
fprintf( stderr, ERROR_STRING_LIBERR ) ;
rval = false;
goto YImage_load_cleanup ;
}
// Set up the png error routine.
if( setjmp(png_jmpbuf(png_ptr)) )
{
fprintf( stderr, ERROR_STRING_MAYBE_FILE, fname ) ;
rval = false;
goto YImage_load_cleanup ;
}
// Give libpng the FILE*, tell it how many bytes we read.
// png_init_io( png_ptr, fp ) ;
// or
// use our own read callback
png_set_read_fn( png_ptr, fp, (png_rw_ptr) user_read_data ) ;
png_set_sig_bytes( png_ptr, PNG_BYTES_TO_CHECK ) ;
// We'll use the low-level interface since the high-level interface won't handle
// png_set_filler() which we need to guarantee there'll be a filler "A" in our
// 4-byte ARGB pixels.
// Really the low-level interface isn't more complicated than the high-level interface.
// To choose transform flags we have to query the png_info struct.
// Instead of OR'ing in another transform flag (high-level interface), we call a set
// method (low-level interface).
// First we read the info struct.
png_read_info( png_ptr, info_ptr ) ;
// Now we set up transforms.
// 1. convert gray and paletted to rgb (this guarantees 8 or 16 bit depths (rgb must be 8 or 16)).
// also expand the alpha color to an alpha channel and convert 16 bit depths to 8.
// 2. if we don't have alpha, add an opaque channel. also swap RGB and BGR depending on YPixel.
// 3. ask libpng to deinterlace
{
png_byte color_type = png_get_color_type( png_ptr, info_ptr ) ;
png_byte depth = png_get_bit_depth( png_ptr, info_ptr ) ;
// 1
if( color_type == PNG_COLOR_TYPE_PALETTE )
png_set_expand( png_ptr ) ;
if( color_type == PNG_COLOR_TYPE_GRAY && depth < 8 )
png_set_expand( png_ptr ) ;
if( png_get_valid( png_ptr, info_ptr, PNG_INFO_tRNS ) )
png_set_expand( png_ptr ) ;
if( depth == 16 )
png_set_strip_16( png_ptr ) ;
if( color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA )
png_set_gray_to_rgb( png_ptr ) ;
// NOTE: This next step affects the layout of the channels in the pixel.
// We turn the pixel into the format in YPixel,
// with the restrication that alpha comes at the beginning or end
// and green is sandwiched between red and blue.
// The possibilities are: ARGB, ABGR, RGBA, BGRA.
// 2
if( color_type != PNG_COLOR_TYPE_GRAY_ALPHA && color_type != PNG_COLOR_TYPE_RGB_ALPHA && !png_get_valid( png_ptr, info_ptr, PNG_INFO_tRNS ) )
png_set_filler( png_ptr, 0xFF, offsetof( YPixel, a ) < offsetof( YPixel, b ) ? PNG_FILLER_BEFORE : PNG_FILLER_AFTER ) ;
else if( offsetof( YPixel, a ) < offsetof( YPixel, b ) )
png_set_swap_alpha( png_ptr ) ;
if( offsetof( YPixel, b ) < offsetof( YPixel, r ) )
png_set_bgr( png_ptr ) ;
// 3
png_set_interlace_handling( png_ptr ) ;
}
// We're almost ready to copy over the png data.
// First we must resize our data* and set our width & height.
{
png_uint_32 widthh = png_get_image_width( png_ptr, info_ptr ) ;
png_uint_32 heightt = png_get_image_height( png_ptr, info_ptr ) ;
// fprintf( stderr, "width: %d, height: %d\n", (int) widthh, (int) heightt ) ;
resize( widthh, heightt ) ;
}
// Now we can create a rows[] pointing into our data* and read the png into our buffer.
{
// fprintf( stderr, "width: %d, height: %d\n", (int) m_width, (int) m_height ) ;
/*
png_byte channels = png_get_channels( png_ptr, info_ptr ) ;
assert( 4 == channels ) ;
png_byte depth = png_get_bit_depth( png_ptr, info_ptr ) ;
assert( 8 == depth ) ;
png_uint_32 rowbytes = png_get_rowbytes( png_ptr, info_ptr ) ;
assert( sizeof(YPixel) * m_width == rowbytes ) ;
*/
// 1. allocate row pointers array
png_bytep* row_pointers = (png_bytep*) png_malloc( png_ptr, m_height * sizeof(png_bytep) ) ;
// 2. point row pointers into m_data
for( int i = 0 ; i < m_height ; ++i )
row_pointers[i] = (png_bytep) (m_data + i*m_width ) ;
// 3. read the image data
png_read_image( png_ptr, row_pointers ) ;
// 4. free row pointers array
png_free( png_ptr, row_pointers ) ;
}
// Read the end info. We're done. Fall through to cleanup.
png_read_end( png_ptr, NULL ) ;
YImage_load_cleanup:
// due to (what looks like) a bug in libpng-1.2.4, we can't pass NULL for the png_ptr arg
if( png_ptr ) png_destroy_read_struct( &png_ptr, info_ptr ? &info_ptr : NULL, NULL ) ;
if( fp ) fclose( fp ) ;
return rval ;
}