/* Copyright 2007-2008 Christian Henning, Andreas Pokorny, Lubomir Bourdev Use, modification and distribution are subject to the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt). */ /*************************************************************************************************/ #ifndef BOOST_GIL_EXTENSION_IO_DETAIL_PNG_IO_READ_HPP #define BOOST_GIL_EXTENSION_IO_DETAIL_PNG_IO_READ_HPP //////////////////////////////////////////////////////////////////////////////////////// /// \file /// \brief /// \author Christian Henning, Andreas Pokorny, Lubomir Bourdev \n /// /// \date 2007-2008 \n /// //////////////////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #include #include "base.hpp" #include "is_allowed.hpp" namespace boost { namespace gil { namespace detail { template< typename Device , typename ConversionPolicy > class reader< Device , png_tag , ConversionPolicy > : public png_io_base< Device > , public reader_base< png_tag , ConversionPolicy > { public: reader( Device& io_dev , const image_read_settings< png_tag >& settings ) : png_io_base< Device >( io_dev ) , reader_base< png_tag , ConversionPolicy >( settings ) , _png_ptr ( NULL ) , _info_ptr( NULL ) { this->check(); init_reader(); } reader( Device& io_dev , const typename ConversionPolicy::color_converter_type& cc , const image_read_settings< png_tag >& settings ) : png_io_base< Device >( io_dev ) , reader_base< png_tag , ConversionPolicy >( cc , settings ) , _png_ptr ( 0 ) , _info_ptr( 0 ) { this->check(); init_reader(); } ~reader() { png_destroy_read_struct( &_png_ptr , &_info_ptr , NULL ); } image_read_info get_info() const { image_read_info< png_tag > ret; // get PNG_IHDR chunk information from png_info structure png_get_IHDR( _png_ptr , _info_ptr , &ret._width , &ret._height , &ret._bit_depth , &ret._color_type , &ret._interlace_method , &ret._compression_method , &ret._filter_method ); // get number of color channels in image ret._num_channels = png_get_channels( _png_ptr , _info_ptr ); #ifdef BOOST_GIL_IO_PNG_FLOATING_POINT_SUPPORTED // Get CIE chromacities and referenced white point for given image if( this->_settings._read_cie_chromacities ) { ret._valid_cie_colors = png_get_cHRM( _png_ptr , _info_ptr , &ret._white_x, &ret._white_y , &ret._red_x, &ret._red_y , &ret._green_x, &ret._green_y , &ret._blue_x, &ret._blue_y ); } // get the gamma value for given image if( this->_settings._read_file_gamma ) { ret._valid_file_gamma = png_get_gAMA( _png_ptr , _info_ptr , &ret._file_gamma ); if( ret._valid_file_gamma == false ) { ret._file_gamma = 1.0; } } #else // Get CIE chromacities and referenced white point for given image if( this->_settings._read_cie_chromacities ) { ret._valid_cie_colors = png_get_cHRM_fixed( _png_ptr , _info_ptr , &ret._white_x, &ret._white_y , &ret._red_x, &ret._red_y , &ret._green_x, &ret._green_y , &ret._blue_x, &ret._blue_y ); } // get the gamma value for given image if( this->_settings._read_file_gamma ) { ret._valid_file_gamma = png_get_gAMA_fixed( _png_ptr , _info_ptr , &ret._file_gamma ); if( ret._valid_file_gamma == false ) { ret._file_gamma = 1; } } #endif // BOOST_GIL_IO_PNG_FLOATING_POINT_SUPPORTED // get the embedded ICC profile data for given image if( this->_settings._read_icc_profile ) { png_charp icc_name = png_charp( NULL ); png_charp profile = png_charp( NULL ); ret._valid_icc_profile = png_get_iCCP( _png_ptr , _info_ptr , &icc_name , &ret._iccp_compression_type , &profile , &ret._profile_length ); if( icc_name ) { ret._icc_name.append( icc_name , std::strlen( icc_name ) ); } if( ret._profile_length > 0 ) { ret._profile.append( profile , ret._profile_length ); } } // get the rendering intent for given image if( this->_settings._read_intent ) { ret._valid_intent = png_get_sRGB( _png_ptr , _info_ptr , &ret._intent ); } // get image palette information from png_info structure if( this->_settings._read_palette ) { png_colorp palette = png_colorp( NULL ); ret._valid_palette = png_get_PLTE( _png_ptr , _info_ptr , &palette , &ret._num_palette ); if( ret._num_palette > 0 ) { ret._palette.resize( ret._num_palette ); std::copy( palette , palette + ret._num_palette , &ret._palette.front() ); } } // get background color for given image if( this->_settings._read_background ) { png_color_16p background = png_color_16p( NULL ); ret._valid_background = png_get_bKGD( _png_ptr , _info_ptr , &background ); if( background ) { ret._background = *background; } } // get the histogram for given image if( this->_settings._read_histogram ) { png_uint_16p histogram = png_uint_16p( NULL ); ret._valid_histogram = png_get_hIST( _png_ptr , _info_ptr , &histogram ); if( histogram ) { // the number of values is set by the number of colors inside // the palette. if( this->_settings._read_palette == false ) { png_colorp palette = png_colorp( NULL ); png_get_PLTE( _png_ptr , _info_ptr , &palette , &ret._num_palette ); } std::copy( histogram , histogram + ret._num_palette , &ret._histogram.front() ); } } // get screen offsets for the given image if( this->_settings._read_screen_offsets ) { ret._valid_offset = png_get_oFFs( _png_ptr , _info_ptr , &ret._offset_x , &ret._offset_y , &ret._off_unit_type ); } // get pixel calibration settings if( this->_settings._read_pixel_calibration ) { png_charp purpose = png_charp ( NULL ); png_charp units = png_charp ( NULL ); png_charpp params = png_charpp( NULL ); ret._valid_pixel_calibration = png_get_pCAL( _png_ptr , _info_ptr , &purpose , &ret._X0 , &ret._X1 , &ret._cal_type , &ret._num_params , &units , ¶ms ); if( purpose ) { ret._purpose.append( purpose , std::strlen( purpose ) ); } if( units ) { ret._units.append( units , std::strlen( units ) ); } if( ret._num_params > 0 ) { ret._params.resize( ret._num_params ); for( png_CAL_nparam::type i = 0 ; i < ret._num_params ; ++i ) { ret._params[i].append( params[i] , std::strlen( params[i] ) ); } } } // get the physical resolution for given image if( this->_settings._read_physical_resolution ) { ret._valid_resolution = png_get_pHYs( _png_ptr , _info_ptr , &ret._res_x , &ret._res_y , &ret._phy_unit_type ); } // get number of significant bits for each color channel if( this->_settings._read_number_of_significant_bits ) { png_color_8p sig_bits = png_color_8p( NULL ); ret._valid_significant_bits = png_get_sBIT( _png_ptr , _info_ptr , &sig_bits ); // @todo Is there one or more colors? if( sig_bits ) { ret._sig_bits = *sig_bits; } } #ifdef BOOST_GIL_IO_PNG_FLOATING_POINT_SUPPORTED // get physical scale settings if( this->_settings._read_scale_factors ) { ret._valid_scale_factors = png_get_sCAL( _png_ptr , _info_ptr , &ret._scale_unit , &ret._scale_width , &ret._scale_height ); } #else #ifdef BOOST_GIL_IO_PNG_FIXED_POINT_SUPPORTED if( this->_settings._read_scale_factors ) { png_charp scale_width, scale_height; if( ret._valid_scale_factors = png_get_sCAL_s( _png_ptr , _info_ptr , &ret._scale_unit , &scale_width , &scale_height ) ) { if( scale_width ) { ret._scale_width.append( scale_width , std::strlen( scale_width ) ); } if( scale_height ) { ret._scale_height.append( scale_height , std::strlen( scale_height ) ); } } } #endif // BOOST_GIL_IO_PNG_FIXED_POINT_SUPPORTED #endif // BOOST_GIL_IO_PNG_FLOATING_POINT_SUPPORTED // get comments information from png_info structure if( this->_settings._read_comments ) { png_textp text = png_textp( NULL ); ret._valid_text = png_get_text( _png_ptr , _info_ptr , &text , &ret._num_text ); if( ret._num_text > 0 ) { ret._text.resize( ret._num_text ); for( png_num_text::type i = 0 ; i < ret._num_text ; ++i ) { ret._text[i]._compression = text[i].compression; ret._text[i]._key.append( text[i].key , std::strlen( text[i].key ) ); ret._text[i]._text.append( text[i].text , std::strlen( text[i].text ) ); } } } // get last modification time for the image if( this->_settings._read_last_modification_time ) { png_timep mod_time = png_timep( NULL ); ret._valid_modification_time = png_get_tIME( _png_ptr , _info_ptr , &mod_time ); ret._mod_time = *mod_time; } // get transparency data for images if( this->_settings._read_transparency_data ) { png_bytep trans = png_bytep ( NULL ); png_color_16p trans_values = png_color_16p( NULL ); ret._valid_transparency_factors = png_get_tRNS( _png_ptr , _info_ptr , &trans , &ret._num_trans , &trans_values ); if( trans ) { //@todo What to do, here? How do I know the length of the "trans" array? } if( ret._num_trans ) { ret._trans_values.resize( ret._num_trans ); std::copy( trans_values , trans_values + ret._num_trans , &ret._trans_values.front() ); } } // @todo One day! /* if( false ) { png_unknown_chunkp unknowns = png_unknown_chunkp( NULL ); int num_unknowns = static_cast< int >( png_get_unknown_chunks( _png_ptr , _info_ptr , &unknowns ) ); } */ return ret; } template< typename View > void apply( const View& view ) { // The info structures are filled at this point. // Now it's time for some transformations. if( little_endian() ) { if( this->_info._bit_depth == 16 ) { // Swap bytes of 16 bit files to least significant byte first. png_set_swap( _png_ptr ); } if( this->_info._bit_depth < 8 ) { // swap bits of 1, 2, 4 bit packed pixel formats png_set_packswap( _png_ptr ); } } if( this->_info._color_type == PNG_COLOR_TYPE_PALETTE ) { png_set_palette_to_rgb( _png_ptr ); } if( this->_info._num_trans > 0 ) { png_set_tRNS_to_alpha( _png_ptr ); } // Tell libpng to handle the gamma conversion for you. The final call // is a good guess for PC generated images, but it should be configurable // by the user at run time by the user. It is strongly suggested that // your application support gamma correction. if( this->_settings._apply_screen_gamma ) { // png_set_gamma will change the image data! #ifdef BOOST_GIL_IO_PNG_FLOATING_POINT_SUPPORTED png_set_gamma( _png_ptr , this->_settings._screen_gamma , this->_info._file_gamma ); #else png_set_gamma( _png_ptr , this->_settings._screen_gamma , this->_info._file_gamma ); #endif // BOOST_GIL_IO_PNG_FLOATING_POINT_SUPPORTED } // Turn on interlace handling. REQUIRED if you are not using // png_read_image(). To see how to handle interlacing passes, // see the png_read_row() method below: _number_passes = png_set_interlace_handling( _png_ptr ); // The above transformation might have changed the bit_depth and color type. png_read_update_info( _png_ptr , _info_ptr ); this->_info._bit_depth = png_get_bit_depth( _png_ptr , _info_ptr ); this->_info._num_channels = png_get_channels( _png_ptr , _info_ptr ); this->_info._color_type = png_get_color_type( _png_ptr , _info_ptr ); switch( this->_info._color_type ) { case PNG_COLOR_TYPE_GRAY: { switch( this->_info._bit_depth ) { case 1: read_rows< gray1_image_t::view_t::reference >( view ); break; case 2: read_rows< gray2_image_t::view_t::reference >( view ); break; case 4: read_rows< gray4_image_t::view_t::reference >( view ); break; case 8: read_rows< gray8_pixel_t >( view ); break; case 16: read_rows< gray16_pixel_t >( view ); break; default: io_error( "png_reader::read_data(): unknown combination of color type and bit depth" ); } break; } case PNG_COLOR_TYPE_GA: { #ifdef BOOST_GIL_IO_ENABLE_GRAY_ALPHA switch( this->_info._bit_depth ) { case 8: read_rows< gray_alpha8_pixel_t > ( view ); break; case 16: read_rows< gray_alpha16_pixel_t >( view ); break; default: io_error( "png_reader::read_data(): unknown combination of color type and bit depth" ); } #else io_error( "gray_alpha isn't enabled. Use ENABLE_GRAY_ALPHA when building application." ); #endif // BOOST_GIL_IO_ENABLE_GRAY_ALPHA break; } case PNG_COLOR_TYPE_RGB: { switch( this->_info._bit_depth ) { case 8: read_rows< rgb8_pixel_t > ( view ); break; case 16: read_rows< rgb16_pixel_t >( view ); break; default: io_error( "png_reader::read_data(): unknown combination of color type and bit depth" ); } break; } case PNG_COLOR_TYPE_RGBA: { switch( this->_info._bit_depth ) { case 8: read_rows< rgba8_pixel_t > ( view ); break; case 16: read_rows< rgba16_pixel_t >( view ); break; default: io_error( "png_reader_color_convert::read_data(): unknown combination of color type and bit depth" ); } break; } default: io_error( "png_reader_color_convert::read_data(): unknown color type" ); } // read rest of file, and get additional chunks in info_ptr png_read_end( _png_ptr , NULL ); } private: template< typename ImagePixel , typename View > void read_rows( const View& view ) { typedef row_buffer_helper_view< ImagePixel > row_buffer_helper_t; typedef typename row_buffer_helper_t::buffer_t buffer_t; typedef typename row_buffer_helper_t::iterator_t it_t; typedef typename is_same< ConversionPolicy , read_and_no_convert >::type is_read_and_convert_t; io_error_if( !is_allowed< View >( this->_info , is_read_and_convert_t() ) , "Image types aren't compatible." ); std::size_t rowbytes = png_get_rowbytes( _png_ptr , _info_ptr ); row_buffer_helper_t buffer( rowbytes , true ); png_bytep row_ptr = (png_bytep)( &( buffer.data()[0])); for( std::size_t pass = 0; pass < _number_passes; pass++ ) { if( pass == _number_passes - 1 ) { // skip lines if necessary for( std::ptrdiff_t y = 0; y < this->_settings._top_left.y; ++y ) { // Read the image using the "sparkle" effect. png_read_rows( _png_ptr , &row_ptr , NULL , 1 ); } for( std::ptrdiff_t y = 0 ; y < this->_settings._dim.y ; ++y ) { // Read the image using the "sparkle" effect. png_read_rows( _png_ptr , &row_ptr , NULL , 1 ); it_t first = buffer.begin() + this->_settings._top_left.x; it_t last = first + this->_settings._dim.x; // one after last element this->_cc_policy.read( first , last , view.row_begin( y )); } // Read the rest of the image. libpng needs that. std::ptrdiff_t remaining_rows = static_cast< std::ptrdiff_t >( this->_info._height ) - this->_settings._top_left.y - this->_settings._dim.y; for( std::ptrdiff_t y = 0 ; y < remaining_rows ; ++y ) { // Read the image using the "sparkle" effect. png_read_rows( _png_ptr , &row_ptr , NULL , 1 ); } } else { for( int y = 0; y < view.height(); ++y ) { // Read the image using the "sparkle" effect. png_read_rows( _png_ptr , &row_ptr , NULL , 1 ); } } } } void init_reader() { // Create and initialize the png_struct with the desired error handler // functions. If you want to use the default stderr and longjump method, // you can supply NULL for the last three parameters. We also supply the // the compiler header file version, so that we know if the application // was compiled with a compatible version of the library. REQUIRED _png_ptr = png_create_read_struct( PNG_LIBPNG_VER_STRING , NULL // user_error_ptr , NULL // user_error_fn , NULL // user_warning_fn ); io_error_if( _png_ptr == NULL , "png_reader: fail to call png_create_write_struct()" ); png_uint_32 user_chunk_data[4]; user_chunk_data[0] = 0; user_chunk_data[1] = 0; user_chunk_data[2] = 0; user_chunk_data[3] = 0; png_set_read_user_chunk_fn( _png_ptr , user_chunk_data , png_io_base< Device >::read_user_chunk_callback ); // Allocate/initialize the memory for image information. REQUIRED. _info_ptr = png_create_info_struct( _png_ptr ); if( _info_ptr == NULL ) { png_destroy_read_struct( &_png_ptr , NULL , NULL ); io_error( "png_reader: fail to call png_create_info_struct()" ); } // Set error handling if you are using the setjmp/longjmp method (this is // the normal method of doing things with libpng). REQUIRED unless you // set up your own error handlers in the png_create_read_struct() earlier. if( setjmp( png_jmpbuf( _png_ptr ))) { //free all of the memory associated with the png_ptr and info_ptr png_destroy_read_struct( &_png_ptr , &_info_ptr , NULL ); io_error( "png is invalid" ); } png_set_read_fn( _png_ptr , static_cast< png_voidp >( &this->_io_dev ) , png_io_base< Device >::read_data ); // Set up a callback function that will be // called after each row has been read, which you can use to control // a progress meter or the like. png_set_read_status_fn( _png_ptr , png_io_base< Device >::read_row_callback ); // Set up a callback which implements user defined transformation. // @todo png_set_read_user_transform_fn( _png_ptr , png_user_transform_ptr( NULL ) ); png_set_keep_unknown_chunks( _png_ptr , PNG_HANDLE_CHUNK_ALWAYS , NULL , 0 ); // Make sure we read the signature. // @todo make it an option png_set_sig_bytes( _png_ptr , PNG_BYTES_TO_CHECK ); // The call to png_read_info() gives us all of the information from the // PNG file before the first IDAT (image data chunk). REQUIRED png_read_info( _png_ptr , _info_ptr ); } private: png_structp _png_ptr; png_infop _info_ptr; std::size_t _number_passes; }; struct png_type_format_checker { png_type_format_checker( png_bitdepth::type bit_depth , png_color_type::type color_type ) : _bit_depth ( bit_depth ) , _color_type( color_type ) {} template< typename Image > bool apply() { typedef is_read_supported< typename get_pixel_type< typename Image::view_t >::type , png_tag > is_supported_t; return is_supported_t::_bit_depth == _bit_depth && is_supported_t::_color_type == _color_type; } private: png_bitdepth::type _bit_depth; png_color_type::type _color_type; }; struct png_read_is_supported { template< typename View > struct apply : public is_read_supported< typename get_pixel_type< View >::type , png_tag > {}; }; template< typename Device > class dynamic_image_reader< Device , png_tag > : public reader< Device , png_tag , detail::read_and_no_convert > { typedef reader< Device , png_tag , detail::read_and_no_convert > parent_t; public: dynamic_image_reader( Device& device , const image_read_settings< png_tag >& settings ) : parent_t( device , settings ) {} template< typename Images > void apply( any_image< Images >& images ) { png_type_format_checker format_checker( this->_info._bit_depth , this->_info._color_type ); if( !construct_matched( images , format_checker )) { io_error( "No matching image type between those of the given any_image and that of the file" ); } else { init_image( images , this->_info ); dynamic_io_fnobj< png_read_is_supported , parent_t > op( this ); apply_operation( view( images ) , op ); } } }; } // namespace detail } // namespace gil } // namespace boost #endif // BOOST_GIL_EXTENSION_IO_DETAIL_PNG_IO_READ_HPP