// // Copyright 2012 Christian Henning // // Distributed under 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_JPEG_DETAIL_READER_BACKEND_HPP #define BOOST_GIL_EXTENSION_IO_JPEG_DETAIL_READER_BACKEND_HPP #include #include #include #include namespace boost { namespace gil { #if BOOST_WORKAROUND(BOOST_MSVC, >= 1400) #pragma warning(push) #pragma warning(disable:4512) //assignment operator could not be generated #pragma warning(disable:4611) //interaction between '_setjmp' and C++ object destruction is non-portable #endif namespace detail { /// /// Wrapper for libjpeg's decompress object. Implements value semantics. /// struct jpeg_decompress_wrapper { protected: using jpeg_decompress_ptr_t = std::shared_ptr ; protected: /// /// Default Constructor /// jpeg_decompress_wrapper() : _jpeg_decompress_ptr( new jpeg_decompress_struct() , jpeg_decompress_deleter ) {} jpeg_decompress_struct* get() { return _jpeg_decompress_ptr.get(); } const jpeg_decompress_struct* get() const { return _jpeg_decompress_ptr.get(); } private: static void jpeg_decompress_deleter( jpeg_decompress_struct* jpeg_decompress_ptr ) { if( jpeg_decompress_ptr ) { jpeg_destroy_decompress( jpeg_decompress_ptr ); delete jpeg_decompress_ptr; jpeg_decompress_ptr = nullptr; } } private: jpeg_decompress_ptr_t _jpeg_decompress_ptr; }; } // namespace detail /// /// JPEG Backend /// template< typename Device > struct reader_backend< Device , jpeg_tag > : public jpeg_io_base , public detail::jpeg_decompress_wrapper { public: using format_tag_t = jpeg_tag; public: // // Constructor // reader_backend( const Device& io_dev , const image_read_settings< jpeg_tag >& settings ) : _io_dev( io_dev ) , _settings( settings ) , _info() , _scanline_length( 0 ) { get()->err = jpeg_std_error( &_jerr ); get()->client_data = this; // Error exit handler: does not return to caller. _jerr.error_exit = &reader_backend::error_exit; if( setjmp( _mark )) { raise_error(); } _src._jsrc.bytes_in_buffer = 0; _src._jsrc.next_input_byte = buffer_; _src._jsrc.init_source = reinterpret_cast< void(*) ( j_decompress_ptr )>( &reader_backend< Device, jpeg_tag >::init_device ); _src._jsrc.fill_input_buffer = reinterpret_cast< boolean(*)( j_decompress_ptr )>( &reader_backend< Device, jpeg_tag >::fill_buffer ); _src._jsrc.skip_input_data = reinterpret_cast< void(*) ( j_decompress_ptr , long num_bytes ) >( &reader_backend< Device, jpeg_tag >::skip_input_data ); _src._jsrc.term_source = reinterpret_cast< void(*) ( j_decompress_ptr ) >( &reader_backend< Device, jpeg_tag >::close_device ); _src._jsrc.resync_to_restart = jpeg_resync_to_restart; _src._this = this; jpeg_create_decompress( get() ); get()->src = &_src._jsrc; jpeg_read_header( get() , TRUE ); io_error_if( get()->data_precision != 8 , "Image file is not supported." ); // read_header(); // if( _settings._dim.x == 0 ) { _settings._dim.x = _info._width; } if( _settings._dim.y == 0 ) { _settings._dim.y = _info._height; } } /// Read image header. void read_header() { _info._width = get()->image_width; _info._height = get()->image_height; _info._num_components = get()->num_components; _info._color_space = get()->jpeg_color_space; _info._data_precision = get()->data_precision; _info._density_unit = get()->density_unit; _info._x_density = get()->X_density; _info._y_density = get()->Y_density; // obtain real world dimensions // taken from https://bitbucket.org/edd/jpegxx/src/ea2492a1a4a6/src/read.cpp#cl-62 jpeg_calc_output_dimensions( get() ); double units_conversion = 0; if (get()->density_unit == 1) // dots per inch { units_conversion = 25.4; // millimeters in an inch } else if (get()->density_unit == 2) // dots per cm { units_conversion = 10; // millimeters in a centimeter } _info._pixel_width_mm = get()->X_density ? (get()->output_width / double(get()->X_density)) * units_conversion : 0; _info._pixel_height_mm = get()->Y_density ? (get()->output_height / double(get()->Y_density)) * units_conversion : 0; } /// Return image read settings. const image_read_settings< jpeg_tag >& get_settings() { return _settings; } /// Return image header info. const image_read_info< jpeg_tag >& get_info() { return _info; } /// Check if image is large enough. void check_image_size( const point_t& img_dim ) { if( _settings._dim.x > 0 ) { if( img_dim.x < _settings._dim.x ) { io_error( "Supplied image is too small" ); } } else { if( (jpeg_image_width::type) img_dim.x < _info._width ) { io_error( "Supplied image is too small" ); } } if( _settings._dim.y > 0 ) { if( img_dim.y < _settings._dim.y ) { io_error( "Supplied image is too small" ); } } else { if( (jpeg_image_height::type) img_dim.y < _info._height ) { io_error( "Supplied image is too small" ); } } } protected: // Taken from jerror.c /* * Error exit handler: must not return to caller. * * Applications may override this if they want to get control back after * an error. Typically one would longjmp somewhere instead of exiting. * The setjmp buffer can be made a private field within an expanded error * handler object. Note that the info needed to generate an error message * is stored in the error object, so you can generate the message now or * later, at your convenience. * You should make sure that the JPEG object is cleaned up (with jpeg_abort * or jpeg_destroy) at some point. */ static void error_exit( j_common_ptr cinfo ) { reader_backend< Device, jpeg_tag >* mgr = reinterpret_cast< reader_backend< Device, jpeg_tag >* >( cinfo->client_data ); longjmp( mgr->_mark, 1 ); } void raise_error() { // we clean up in the destructor io_error( "jpeg is invalid." ); } private: // See jdatasrc.c for default implementation for the following static member functions. static void init_device( jpeg_decompress_struct* cinfo ) { gil_jpeg_source_mgr* src = reinterpret_cast< gil_jpeg_source_mgr* >( cinfo->src ); src->_jsrc.bytes_in_buffer = 0; src->_jsrc.next_input_byte = src->_this->buffer_; } static boolean fill_buffer( jpeg_decompress_struct* cinfo ) { gil_jpeg_source_mgr* src = reinterpret_cast< gil_jpeg_source_mgr* >( cinfo->src ); size_t count = src->_this->_io_dev.read(src->_this->buffer_, sizeof(src->_this->buffer_) ); if( count <= 0 ) { // libjpeg does that: adding an EOF marker src->_this->buffer_[0] = (JOCTET) 0xFF; src->_this->buffer_[1] = (JOCTET) JPEG_EOI; count = 2; } src->_jsrc.next_input_byte = src->_this->buffer_; src->_jsrc.bytes_in_buffer = count; return TRUE; } static void skip_input_data( jpeg_decompress_struct * cinfo, long num_bytes ) { gil_jpeg_source_mgr* src = reinterpret_cast< gil_jpeg_source_mgr* >( cinfo->src ); if( num_bytes > 0 ) { while( num_bytes > long( src->_jsrc.bytes_in_buffer )) { num_bytes -= (long) src->_jsrc.bytes_in_buffer; fill_buffer( cinfo ); } src->_jsrc.next_input_byte += num_bytes; src->_jsrc.bytes_in_buffer -= num_bytes; } } static void close_device( jpeg_decompress_struct* ) {} public: Device _io_dev; image_read_settings< jpeg_tag > _settings; image_read_info< jpeg_tag > _info; std::size_t _scanline_length; struct gil_jpeg_source_mgr { jpeg_source_mgr _jsrc; reader_backend* _this; }; gil_jpeg_source_mgr _src; // libjpeg default is 4096 - see jdatasrc.c JOCTET buffer_[4096]; }; #if BOOST_WORKAROUND(BOOST_MSVC, >= 1400) #pragma warning(pop) #endif } // namespace gil } // namespace boost #endif