VideoTools
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
grimagedecoder.cpp
Go to the documentation of this file.
1 //
2 // Copyright (C) 2017 Graeme Walker
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
16 // ===
17 //
18 // grimagedecoder.cpp
19 //
20 
21 #include "gdef.h"
22 #include "grdef.h"
23 #include "grimagedecoder.h"
24 #include "grimagetype.h"
25 #include "groot.h"
26 #include "grjpeg.h"
27 #include "grpng.h"
28 #include "grpnm.h"
29 #include "grcolourspace.h"
30 #include "glog.h"
31 #include "gassert.h"
32 #include <cstring> // memcpy()
33 
34 namespace
35 {
36  Gr::ImageType rawtype( const Gr::ImageData & image_data )
37  {
38  return Gr::ImageType::raw( image_data.dx() , image_data.dy() , image_data.channels() ) ;
39  }
40 }
41 
42 // ==
43 
45  m_scale(1) ,
46  m_monochrome_out(false)
47 {
48 }
49 
50 void Gr::ImageDecoder::setup( int scale , bool monochrome_out )
51 {
52  m_scale = scale ;
53  m_monochrome_out = monochrome_out ;
54 }
55 
56 Gr::ImageType Gr::ImageDecoder::decode( const G::Path & path , ImageData & out , const ScaleToFit & scale_to_fit )
57 {
58  return decodeFile( readType(path) , path , out , scale_to_fit ) ;
59 }
60 
61 Gr::ImageType Gr::ImageDecoder::decode( const ImageType & image_type , const G::Path & path , ImageData & out , ScaleToFit scale_to_fit )
62 {
63  return decodeFile( image_type , path , out , scale_to_fit ) ;
64 }
65 
66 Gr::ImageType Gr::ImageDecoder::readType( const G::Path & path , bool do_throw )
67 {
68  try
69  {
70  std::ifstream file ;
71  {
72  G::Root claim_root ;
73  file.open( path.str().c_str() ) ;
74  }
75  if( !file.good() )
76  throw Error( "cannot open [" + path.str() + "]" ) ;
77  return ImageType( file ) ;
78  }
79  catch( std::exception & )
80  {
81  if( do_throw ) throw ;
82  return ImageType() ;
83  }
84 }
85 
86 Gr::ImageType Gr::ImageDecoder::decodeFile( ImageType type_in , const G::Path & path , ImageData & out , const ScaleToFit & scale_to_fit )
87 {
88  G_DEBUG( "Gr::ImageDecoder::decodeFile: path=[" << path << "] type=[" << type_in << "]" ) ;
89 
90  int scale = scale_to_fit ? scale_to_fit(type_in) : m_scale ;
91 
92  if( type_in.isJpeg() && jpegAvailable() )
93  {
94  m_jpeg.setup( scale , m_monochrome_out ) ;
95  m_jpeg.decode( out , path ) ;
96  }
97  else if( type_in.isPng() && pngAvailable() )
98  {
99  m_png.setup( scale , m_monochrome_out ) ;
100  m_png.decode( out , path ) ;
101  }
102  else if( type_in.isPnm() )
103  {
104  m_pnm.setup( scale , m_monochrome_out ) ;
105  m_pnm.decode( out , path ) ;
106  }
107  else
108  {
109  throw Error( "invalid file format" , path.str() ) ;
110  }
111  return rawtype(out) ;
112 }
113 
114 Gr::ImageType Gr::ImageDecoder::decode( const ImageType & type_in , const std::vector<char> & data , ImageData & out , const ScaleToFit & scale_to_fit )
115 {
116  return decodeBuffer( type_in , data.data() , data.size() , out , scale_to_fit ) ;
117 }
118 
119 Gr::ImageType Gr::ImageDecoder::decode( const ImageType & type_in , const char * p , size_t n , ImageData & out , const ScaleToFit & scale_to_fit )
120 {
121  return decodeBuffer( type_in , p , n , out , scale_to_fit ) ;
122 }
123 
124 Gr::ImageType Gr::ImageDecoder::decodeBuffer( ImageType type_in , const char * p , size_t n , ImageData & out , const ScaleToFit & scale_to_fit )
125 {
126  G_DEBUG( "Gr::ImageDecoder::decodeBuffer: size=" << n << " type=[" << type_in << "]" ) ;
127 
128  int scale = scale_to_fit ? scale_to_fit(type_in) : m_scale ;
129 
130  if( type_in.isJpeg() && jpegAvailable() )
131  {
132  m_jpeg.setup( scale , m_monochrome_out ) ;
133  m_jpeg.decode( out , p , n ) ;
134  }
135  else if( type_in.isPng() && pngAvailable() )
136  {
137  m_png.setup( scale , m_monochrome_out ) ;
138  m_png.decode( out , p , n ) ;
139  }
140  else if( type_in.isPnm() )
141  {
142  m_pnm.setup( scale , m_monochrome_out ) ;
143  m_pnm.decode( out , p , n ) ;
144  }
145  else if( type_in.isRaw() )
146  {
147  // raw-to-raw copy
148  ImageType type_out = ImageType::raw( type_in , scale , m_monochrome_out ) ;
149  out.resize( type_out.dx() , type_out.dy() , type_out.channels() ) ;
150  out.copyIn( p , n , type_in.dx() , type_in.dy() , type_in.channels() , true/*colourspace*/ , scale ) ;
151  }
152  else
153  {
154  throw Error( "invalid format" ) ;
155  }
156  return rawtype( out ) ;
157 }
158 
159 Gr::ImageType Gr::ImageDecoder::decode( const ImageType & type_in , const ImageBuffer & image_buffer , ImageData & out , const ScaleToFit & scale_to_fit )
160 {
161  G_DEBUG( "Gr::ImageDecoder::decode: ImageBuffer type=[" << type_in << "]" ) ;
162 
163  int scale = scale_to_fit ? scale_to_fit(type_in) : m_scale ;
164 
165  if( type_in.isJpeg() && jpegAvailable() )
166  {
167  m_jpeg.setup( scale , m_monochrome_out ) ;
168  m_jpeg.decode( out , image_buffer ) ;
169  }
170  else if( type_in.isPng() && pngAvailable() )
171  {
172  m_png.setup( scale , m_monochrome_out ) ;
173  m_png.decode( out , image_buffer ) ;
174  }
175  else if( type_in.isPnm() )
176  {
177  m_pnm.setup( scale , m_monochrome_out ) ;
178  m_pnm.decode( out , image_buffer ) ;
179  }
180  else if( type_in.isRaw() )
181  {
182  // raw-to-raw copy
183  ImageType type_out = ImageType::raw( type_in , scale , m_monochrome_out ) ;
184  out.resize( type_out.dx() , type_out.dy() , type_out.channels() ) ;
185  out.copyIn( image_buffer , type_in.dx() , type_in.dy() , type_in.channels() , true/*colourspace*/ , scale ) ;
186  }
187  else
188  {
189  throw Error( "invalid format" ) ;
190  }
191  return rawtype( out ) ;
192 }
193 
194 Gr::ImageType Gr::ImageDecoder::decodeInPlace( ImageType type_in , char * & p , size_t size_in ,
195  Gr::ImageData & out_store )
196 {
197  G_ASSERT( out_store.type() == ImageData::Contiguous ) ;
198 
199  PnmInfo pnm_info ;
200  if( type_in.isPnm() )
201  pnm_info = PnmInfo( p , size_in ) ;
202 
203  ImageType type_out ;
204  if( type_in.isJpeg() && jpegAvailable() )
205  {
206  m_jpeg.setup( m_scale , m_monochrome_out ) ;
207  m_jpeg.decode( out_store , p , size_in ) ;
208  p = reinterpret_cast<char*>( out_store.p() ) ;
209  type_out = rawtype( out_store ) ;
210  }
211  else if( type_in.isPng() && pngAvailable() )
212  {
213  m_png.setup( m_scale , m_monochrome_out ) ;
214  m_png.decode( out_store , p , size_in ) ;
215  p = reinterpret_cast<char*>( out_store.p() ) ;
216  type_out = rawtype( out_store ) ;
217  }
218  else if( type_in.isPnm() && pnm_info.binary8() &&
219  ( m_scale > 1 || type_in.channels() != (m_monochrome_out?1:type_in.channels()) ) )
220  {
221  // strip the header
222  type_out = ImageType::raw( type_in , m_scale , m_monochrome_out ) ;
223  if( size_in <= pnm_info.offset() || (size_in-pnm_info.offset()) != type_out.size() )
224  throw Error( "invalid pnm size" ) ;
225  std::memmove( p , p+pnm_info.offset() , size_in-pnm_info.offset() ) ;
226 
227  // treat as raw
228  unsigned char * p_out = reinterpret_cast<unsigned char*>(p) ;
229  const unsigned char * p_in = p_out ;
230  size_t dp_in = sizet( m_scale , type_in.channels() ) ;
231  for( int y = 0 ; y < type_in.dy() ; y += m_scale )
232  {
233  for( int x = 0 ; x < type_in.dx() ; x += m_scale , p += dp_in )
234  {
235  if( m_monochrome_out && type_in.channels() == 3 )
236  *p_out++ = Gr::ColourSpace::y_int( p_in[0] , p_in[1] , p_in[2] ) ;
237  else if( m_monochrome_out )
238  *p_out++ = *p_in ;
239  else
240  { *p_out++ = p_in[0] ; *p_out++ = p_in[1] ; *p_out++ = p_in[2] ; }
241  }
242  }
243  }
244  else if( type_in.isPnm() && pnm_info.binary8() )
245  {
246  // just strip the header
247  type_out = ImageType::raw( type_in , m_scale , m_monochrome_out ) ;
248  if( size_in <= pnm_info.offset() || (size_in-pnm_info.offset()) != type_out.size() )
249  throw Error( "invalid pnm size" ) ;
250  std::memmove( p , p+pnm_info.offset() , size_in-pnm_info.offset() ) ;
251  }
252  else if( type_in.isPnm() )
253  {
254  m_pnm.setup( m_scale , m_monochrome_out ) ;
255  m_pnm.decode( out_store , p , size_in ) ;
256  p = reinterpret_cast<char*>( out_store.p() ) ;
257  type_out = rawtype( out_store ) ;
258  }
259  else if( type_in.isRaw() && (m_scale > 1 || type_in.channels() != (m_monochrome_out?1:type_in.channels()) ) )
260  {
261  type_out = ImageType::raw( type_in , m_scale , m_monochrome_out ) ;
262  unsigned char * p_out = reinterpret_cast<unsigned char*>(p) ;
263  const unsigned char * p_in = p_out ;
264  size_t dp_in = sizet( m_scale , type_in.channels() ) ;
265  for( int y = 0 ; y < type_in.dy() ; y += m_scale )
266  {
267  for( int x = 0 ; x < type_in.dx() ; x += m_scale , p += dp_in )
268  {
269  if( m_monochrome_out && type_in.channels() == 3 )
270  *p_out++ = Gr::ColourSpace::y_int( p_in[0] , p_in[1] , p_in[2] ) ;
271  else if( m_monochrome_out )
272  *p_out++ = *p_in ;
273  else
274  { *p_out++ = p_in[0] ; *p_out++ = p_in[1] ; *p_out++ = p_in[2] ; }
275  }
276  }
277  }
278  else if( type_in.isRaw() )
279  {
280  type_out = type_in ;
281  }
282  else
283  {
284  throw Error( "invalid format" ) ;
285  }
286  return type_out ;
287 }
288 
289 bool Gr::ImageDecoder::jpegAvailable()
290 {
291  if( !Jpeg::available() )
292  G_WARNING_ONCE( "Gr::ImageDecoder::jpegAvailable: no jpeg library built-in" ) ;
293  return Jpeg::available() ;
294 }
295 
296 bool Gr::ImageDecoder::pngAvailable()
297 {
298  if( !Png::available() )
299  G_WARNING_ONCE( "Gr::ImageDecoder::jpegAvailable: no png library built-in" ) ;
300  return Jpeg::available() ;
301 }
302 
303 // ==
304 
305 int Gr::ImageDecoder::ScaleToFit::operator()( const ImageType & type ) const
306 {
307  // work out an integral scale factor to more-or-less fit into the target
308  // image size -- the fudge factor says how much of the image can be
309  // lost in one dimension when getting the other dimension to fit, eg.
310  // 3 for a third, or zero to fit everything in
311  //
312  G_ASSERT( ff >= 0 && dx > 0 && dy > 0 ) ;
313  if( !type.valid() ) return 1 ;
314  int image_dx = type.dx() ; if( ff > 0 ) image_dx -= (image_dx/ff) ;
315  int image_dy = type.dy() ; if( ff > 0 ) image_dy -= (image_dy/ff) ;
316  int scale_x = (image_dx+dx-1) / std::max(1,dx) ;
317  int scale_y = (image_dy+dy-1) / std::max(1,dy) ;
318 
319  {
320  G_ASSERT( scaled(image_dx,scale_x) <= dx ) ;
321  G_ASSERT( scaled(image_dy,scale_y) <= dy ) ;
322  if( scale_x > 1 ) G_ASSERT( scaled(image_dx,scale_x-1) > dx ) ;
323  if( scale_y > 1 ) G_ASSERT( scaled(image_dy,scale_y-1) > dy ) ;
324  }
325 
326  int result = std::max( scale_x , scale_y ) ;
327  return result ;
328 }
329 
330 Gr::ImageDecoder::ScaleToFit::operator bool() const
331 {
332  const bool zero = dx <= 0 && dy <= 0 ;
333  return !zero ;
334 }
335 
336 /// \file grimagedecoder.cpp
std::string str() const
Returns the path string.
Definition: gpath.cpp:290
bool isRaw() const
Returns true if a raw image type.
int dx() const
Returns the width.
Definition: grimagedata.h:329
ImageType decodeInPlace(ImageType type_in, char *&p, size_t size_in, ImageData &store)
Decodes an image buffer with raw decoding done in-place.
A holder for image data, having eight bits per sample and one or three channels.
Definition: grimagedata.h:46
void setup(int scale, bool monochrome_out)
Sets the scale factor.
void resize(int dx, int dy, int channels)
Resizes the image data.
Definition: grimagedata.cpp:89
bool isJpeg() const
Returns true if a jpeg image type.
int channels() const
Returns the number of channels.
Definition: grimagetype.h:197
Type type() const
Returns the contiguous/segmented enumeration.
Definition: grimagedata.cpp:79
An encapsulation of image type, including width, height and number of channels, with support for a st...
Definition: grimagetype.h:43
A class which acquires the process's special privileges on construction and releases them on destruct...
Definition: groot.h:49
Vectors ImageBuffer
An ImageBuffer is used to hold raw image data, typically in more than one chunk.
Definition: grimagebuffer.h:47
static ImageType raw(int dx, int dy, int channels)
Factory function for a raw image type.
size_t size() const
Returns the product of dx, dy and channels.
ImageDecoder()
Default constructor.
static bool available()
Returns true if a jpeg library is available.
bool isPng() const
Returns true if a png image type.
int dy() const
Returns the image height.
Definition: grimagetype.h:191
void copyIn(const char *data_in, size_t data_size_in, int dx_in, int dy_in, int channels_in, bool use_colourspace=false, int scale=1)
Copies the image in from a raw buffer, with channel-count adjustment and optional scaling...
int channels() const
Returns the number of channels (zero, one or three).
Definition: grimagedata.h:341
static ImageType readType(const G::Path &path, bool do_throw=true)
A convenience function to read a file's image type.
unsigned char y_int(unsigned char r, unsigned char g, unsigned char b)
A fast conversion from rgb to y.
const unsigned char * p() const
Returns a const pointer to the image data, but throws if the data is not contiguous.
int dx() const
Returns the image width.
Definition: grimagetype.h:185
ImageType decode(const G::Path &path_in, ImageData &out, const ScaleToFit &=ScaleToFit())
Decodes a file, with a scale-to-fit option.
Describes scale-to-fit target dimensions.
A structure holding portable-anymap metadata.
Definition: grpnm.h:42
int dy() const
Returns the height.
Definition: grimagedata.h:335
static bool available()
Returns true if the png library is available.
Definition: grpng_none.cpp:27
A Path object represents a file system path.
Definition: gpath.h:72
bool isPnm() const
Returns true if a pnm image type.