1 //
2 // Copyright 2012 Christian Henning
3 //
4 // Distributed under the Boost Software License, Version 1.0
5 // See accompanying file LICENSE_1_0.txt or copy at
6 // http://www.boost.org/LICENSE_1_0.txt
7 //
8 #ifndef BOOST_GIL_EXTENSION_IO_JPEG_DETAIL_READER_BACKEND_HPP
9 #define BOOST_GIL_EXTENSION_IO_JPEG_DETAIL_READER_BACKEND_HPP
10 
11 #include <boost/gil/extension/io/jpeg/tags.hpp>
12 #include <boost/gil/extension/io/jpeg/detail/base.hpp>
13 
14 #include <csetjmp>
15 #include <memory>
16 
17 namespace boost { namespace gil {
18 
19 #if BOOST_WORKAROUND(BOOST_MSVC, >= 1400)
20 #pragma warning(push)
21 #pragma warning(disable:4512) //assignment operator could not be generated
22 #pragma warning(disable:4611) //interaction between '_setjmp' and C++ object destruction is non-portable
23 #endif
24 
25 namespace detail {
26 
27 ///
28 /// Wrapper for libjpeg's decompress object. Implements value semantics.
29 ///
30 struct jpeg_decompress_wrapper
31 {
32 protected:
33 
34     using jpeg_decompress_ptr_t = std::shared_ptr<jpeg_decompress_struct> ;
35 
36 protected:
37 
38     ///
39     /// Default Constructor
40     ///
jpeg_decompress_wrapperboost::gil::detail::jpeg_decompress_wrapper41     jpeg_decompress_wrapper()
42     : _jpeg_decompress_ptr( new jpeg_decompress_struct()
43                           , jpeg_decompress_deleter
44                           )
45     {}
46 
getboost::gil::detail::jpeg_decompress_wrapper47     jpeg_decompress_struct*       get()       { return _jpeg_decompress_ptr.get(); }
getboost::gil::detail::jpeg_decompress_wrapper48     const jpeg_decompress_struct* get() const { return _jpeg_decompress_ptr.get(); }
49 
50 private:
51 
jpeg_decompress_deleterboost::gil::detail::jpeg_decompress_wrapper52     static void jpeg_decompress_deleter( jpeg_decompress_struct* jpeg_decompress_ptr )
53     {
54         if( jpeg_decompress_ptr )
55         {
56             jpeg_destroy_decompress( jpeg_decompress_ptr );
57 
58             delete jpeg_decompress_ptr;
59             jpeg_decompress_ptr = nullptr;
60         }
61     }
62 
63 private:
64 
65    jpeg_decompress_ptr_t _jpeg_decompress_ptr;
66 
67 };
68 
69 } // namespace detail
70 
71 ///
72 /// JPEG Backend
73 ///
74 template< typename Device >
75 struct reader_backend< Device
76                      , jpeg_tag
77                      >
78     : public jpeg_io_base
79     , public detail::jpeg_decompress_wrapper
80 {
81 public:
82 
83     using format_tag_t = jpeg_tag;
84 
85 public:
86     //
87     // Constructor
88     //
reader_backendboost::gil::reader_backend89     reader_backend( const Device&                          io_dev
90                   , const image_read_settings< jpeg_tag >& settings
91                   )
92     : _io_dev( io_dev )
93     , _settings( settings )
94     , _info()
95 
96     , _scanline_length( 0 )
97     {
98         get()->err         = jpeg_std_error( &_jerr );
99         get()->client_data = this;
100 
101         // Error exit handler: does not return to caller.
102         _jerr.error_exit = &reader_backend::error_exit;
103 
104         if( setjmp( _mark ))
105         {
106             raise_error();
107         }
108 
109         _src._jsrc.bytes_in_buffer   = 0;
110         _src._jsrc.next_input_byte   = buffer_;
111         _src._jsrc.init_source       = reinterpret_cast< void(*)   ( j_decompress_ptr )>( &reader_backend< Device, jpeg_tag >::init_device );
112         _src._jsrc.fill_input_buffer = reinterpret_cast< boolean(*)( j_decompress_ptr )>( &reader_backend< Device, jpeg_tag >::fill_buffer );
113         _src._jsrc.skip_input_data   = reinterpret_cast< void(*)   ( j_decompress_ptr
114                                                                    , long num_bytes
115                                                                    ) >( &reader_backend< Device, jpeg_tag >::skip_input_data );
116         _src._jsrc.term_source       = reinterpret_cast< void(*)   ( j_decompress_ptr ) >( &reader_backend< Device, jpeg_tag >::close_device );
117         _src._jsrc.resync_to_restart = jpeg_resync_to_restart;
118         _src._this = this;
119 
120         jpeg_create_decompress( get() );
121 
122         get()->src = &_src._jsrc;
123 
124         jpeg_read_header( get()
125                         , TRUE
126                         );
127 
128         io_error_if( get()->data_precision != 8
129                    , "Image file is not supported."
130                    );
131 
132         //
133         read_header();
134 
135         //
136         if( _settings._dim.x == 0 )
137         {
138             _settings._dim.x = _info._width;
139         }
140 
141         if( _settings._dim.y == 0 )
142         {
143             _settings._dim.y = _info._height;
144         }
145     }
146 
147     /// Read image header.
read_headerboost::gil::reader_backend148     void read_header()
149     {
150         _info._width          = get()->image_width;
151         _info._height         = get()->image_height;
152         _info._num_components = get()->num_components;
153         _info._color_space    = get()->jpeg_color_space;
154         _info._data_precision = get()->data_precision;
155 
156         _info._density_unit = get()->density_unit;
157         _info._x_density    = get()->X_density;
158         _info._y_density    = get()->Y_density;
159 
160         // obtain real world dimensions
161         // taken from https://bitbucket.org/edd/jpegxx/src/ea2492a1a4a6/src/read.cpp#cl-62
162 
163         jpeg_calc_output_dimensions( get() );
164 
165         double units_conversion = 0;
166         if (get()->density_unit == 1) // dots per inch
167         {
168             units_conversion = 25.4; // millimeters in an inch
169         }
170         else if (get()->density_unit == 2) // dots per cm
171         {
172             units_conversion = 10; // millimeters in a centimeter
173         }
174 
175         _info._pixel_width_mm  = get()->X_density ? (get()->output_width  / double(get()->X_density)) * units_conversion : 0;
176         _info._pixel_height_mm = get()->Y_density ? (get()->output_height / double(get()->Y_density)) * units_conversion : 0;
177     }
178 
179     /// Return image read settings.
get_settingsboost::gil::reader_backend180     const image_read_settings< jpeg_tag >& get_settings()
181     {
182         return _settings;
183     }
184 
185     /// Return image header info.
get_infoboost::gil::reader_backend186     const image_read_info< jpeg_tag >& get_info()
187     {
188         return _info;
189     }
190 
191     /// Check if image is large enough.
check_image_sizeboost::gil::reader_backend192     void check_image_size( const point_t& img_dim )
193     {
194         if( _settings._dim.x > 0 )
195         {
196             if( img_dim.x < _settings._dim.x ) { io_error( "Supplied image is too small" ); }
197         }
198         else
199         {
200             if( (jpeg_image_width::type) img_dim.x < _info._width ) { io_error( "Supplied image is too small" ); }
201         }
202 
203 
204         if( _settings._dim.y > 0 )
205         {
206             if( img_dim.y < _settings._dim.y ) { io_error( "Supplied image is too small" ); }
207         }
208         else
209         {
210             if( (jpeg_image_height::type) img_dim.y < _info._height ) { io_error( "Supplied image is too small" ); }
211         }
212     }
213 
214 protected:
215 
216     // Taken from jerror.c
217     /*
218      * Error exit handler: must not return to caller.
219      *
220      * Applications may override this if they want to get control back after
221      * an error.  Typically one would longjmp somewhere instead of exiting.
222      * The setjmp buffer can be made a private field within an expanded error
223      * handler object.  Note that the info needed to generate an error message
224      * is stored in the error object, so you can generate the message now or
225      * later, at your convenience.
226      * You should make sure that the JPEG object is cleaned up (with jpeg_abort
227      * or jpeg_destroy) at some point.
228      */
error_exitboost::gil::reader_backend229     static void error_exit( j_common_ptr cinfo )
230     {
231         reader_backend< Device, jpeg_tag >* mgr = reinterpret_cast< reader_backend< Device, jpeg_tag >* >( cinfo->client_data );
232 
233         longjmp( mgr->_mark, 1 );
234     }
235 
raise_errorboost::gil::reader_backend236     void raise_error()
237     {
238         // we clean up in the destructor
239 
240         io_error( "jpeg is invalid." );
241     }
242 
243 private:
244 
245     // See jdatasrc.c for default implementation for the following static member functions.
246 
init_deviceboost::gil::reader_backend247     static void init_device( jpeg_decompress_struct* cinfo )
248     {
249         gil_jpeg_source_mgr* src = reinterpret_cast< gil_jpeg_source_mgr* >( cinfo->src );
250         src->_jsrc.bytes_in_buffer = 0;
251         src->_jsrc.next_input_byte = src->_this->buffer_;
252     }
253 
fill_bufferboost::gil::reader_backend254     static boolean fill_buffer( jpeg_decompress_struct* cinfo )
255     {
256         gil_jpeg_source_mgr* src = reinterpret_cast< gil_jpeg_source_mgr* >( cinfo->src );
257         size_t count = src->_this->_io_dev.read(src->_this->buffer_, sizeof(src->_this->buffer_) );
258 
259         if( count <= 0 )
260         {
261             // libjpeg does that: adding an EOF marker
262             src->_this->buffer_[0] = (JOCTET) 0xFF;
263             src->_this->buffer_[1] = (JOCTET) JPEG_EOI;
264             count = 2;
265         }
266 
267         src->_jsrc.next_input_byte = src->_this->buffer_;
268         src->_jsrc.bytes_in_buffer = count;
269 
270         return TRUE;
271     }
272 
skip_input_databoost::gil::reader_backend273     static void skip_input_data( jpeg_decompress_struct * cinfo, long num_bytes  )
274     {
275         gil_jpeg_source_mgr* src = reinterpret_cast< gil_jpeg_source_mgr* >( cinfo->src );
276 
277         if( num_bytes > 0 )
278         {
279             while( num_bytes > long( src->_jsrc.bytes_in_buffer ))
280             {
281                 num_bytes -= (long) src->_jsrc.bytes_in_buffer;
282                 fill_buffer( cinfo );
283             }
284 
285             src->_jsrc.next_input_byte += num_bytes;
286             src->_jsrc.bytes_in_buffer -= num_bytes;
287         }
288     }
289 
close_deviceboost::gil::reader_backend290     static void close_device( jpeg_decompress_struct* ) {}
291 
292 public:
293 
294     Device _io_dev;
295 
296     image_read_settings< jpeg_tag > _settings;
297     image_read_info< jpeg_tag >     _info;
298 
299     std::size_t _scanline_length;
300 
301     struct gil_jpeg_source_mgr
302     {
303         jpeg_source_mgr _jsrc;
304         reader_backend* _this;
305     };
306 
307     gil_jpeg_source_mgr _src;
308 
309     // libjpeg default is 4096 - see jdatasrc.c
310     JOCTET buffer_[4096];
311 };
312 
313 #if BOOST_WORKAROUND(BOOST_MSVC, >= 1400)
314 #pragma warning(pop)
315 #endif
316 
317 } // namespace gil
318 } // namespace boost
319 
320 #endif
321