VideoTools
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
grpnm.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 // grpnm.cpp
19 //
20 
21 #include "gdef.h"
22 #include "grdef.h"
23 #include "grpnm.h"
24 #include "groot.h"
25 #include "gassert.h"
26 #include "gstr.h"
27 #include <algorithm> // std::min/max
28 #include <utility>
29 #include <iterator>
30 #include <exception>
31 #include <iostream>
32 #include <fstream>
33 #include <stdexcept>
34 #include <sstream>
35 
36 namespace
37 {
38  bool is_space( char c )
39  {
40  return c == '\n' || c == '\r' || c == ' ' || c == '\t' ;
41  }
42 
43  bool is_digit( char c )
44  {
45  return c >= '0' && c <= '9' ;
46  }
47 
48  unsigned int value256( unsigned int maxval , unsigned int n )
49  {
50  unsigned long x = n ;
51  x <<= 8 ;
52  x /= maxval ;
53  n = static_cast<unsigned int>(x) ;
54  return n > 255U ? 255U : n ;
55  }
56 
57  template <typename T>
58  T skip( T in , T end , size_t & offset )
59  {
60  bool in_comment = false ;
61  while( in != end )
62  {
63  if( *in == '#' ) in_comment = true ;
64  if( in_comment && *in == '\n' ) in_comment = false ;
65  if( !in_comment && !is_space(*in) ) break ;
66  ++in ; offset++ ;
67  }
68  return in ;
69  }
70 
71  template <typename T>
72  T skip_eol( T in , T end , size_t & offset )
73  {
74  if( in != end && *in == '\r' ) ++in , offset++ ;
75  if( in != end ) ++in , offset++ ;
76  return in ;
77  }
78 
79  template <typename T>
80  int get_n( T & in , T end , size_t & offset )
81  {
82  if( in == end ) return 0 ;
83  int result = 0 ;
84  while( in != end && is_digit(*in) )
85  {
86  result *= 10 ;
87  char c = *in++ ; offset++ ;
88  result += (c-'0') ;
89  }
90  return result ;
91  }
92 
93  template <typename T>
94  bool readInfoImp( T & in , T end , Gr::PnmInfo & info )
95  {
96  int pn = 0 ;
97  int dx = 0 ;
98  int dy = 0 ;
99  int maxval = 0 ;
100  size_t offset = 0U ;
101 
102  // read Pn
103  if( in == end ) return false ;
104  char p = *in++ ; offset++ ;
105  if( in == end || p != 'P' ) return false ;
106  char n = *in++ ; offset++ ;
107  if( ! ( n >= '1' && n <= '6' ) ) return false ;
108  pn = static_cast<int>( n - '0' ) ;
109 
110  bool with_maxval = ! ( pn == 1 || pn == 4 ) ;
111  bool binary = pn >= 4 ;
112  if( in == end ) return false ;
113 
114  // read dx
115  in = skip( in , end , offset ) ;
116  if( in == end ) return false ;
117  dx = get_n( in , end , offset ) ;
118 
119  // read dy
120  in = skip( in , end , offset ) ;
121  if( in == end ) return false ;
122  dy = get_n( in , end , offset ) ;
123 
124  // read maxval
125  maxval = 0 ;
126  if( with_maxval )
127  {
128  in = skip( in , end , offset ) ;
129  if( in == end ) return false ;
130  maxval = get_n( in , end , offset ) ;
131  }
132 
133  in = binary ? skip_eol(in,end,offset) : skip(in,end,offset) ;
134  if( in == end ) return false ;
135 
136  info = Gr::PnmInfo( pn , dx , dy , maxval , offset ) ;
137  return dx > 0 && dy > 0 && (!with_maxval||maxval>0) ;
138  }
139 
140  template <typename T>
141  Gr::PnmInfo readInfoImp( T & in , T end )
142  {
143  Gr::PnmInfo info ;
144  if( !readInfoImp( in , end , info ) )
145  return Gr::PnmInfo() ;
146  return info ;
147  }
148 
149  template <typename T>
150  struct ScalingAdaptor
151  {
152  ScalingAdaptor( T & t , const Gr::PnmInfo & info , int scale ) :
153  m_t(t) ,
154  m_info(info) ,
155  m_scale(scale) ,
156  m_x_in(0) ,
157  m_y_in(0) ,
158  m_x_out(0) ,
159  m_y_out(0) ,
160  m_x_out_max(-1) ,
161  m_y_out_max(-1)
162  {
163  }
164  void operator()( int x , int y , unsigned int r , unsigned int g , unsigned int b )
165  {
166  if( y == m_y_in && x == m_x_in )
167  {
168  m_x_out_max = m_x_out ;
169  m_y_out_max = m_y_out ;
170  m_t( m_x_out , m_y_out , r , g , b ) ;
171  m_x_in += m_scale ;
172  m_x_out++ ;
173  if( m_x_in >= m_info.dx() )
174  {
175  m_x_in = 0 ;
176  m_y_in += m_scale ;
177  m_x_out = 0 ;
178  m_y_out++ ;
179  }
180  }
181  }
182  int dx() const
183  {
184  G_ASSERT( Gr::scaled(m_info.dx(),m_scale) == (m_x_out_max+1) ) ;
185  return m_x_out_max + 1 ;
186  }
187  int dy() const
188  {
189  G_ASSERT( Gr::scaled(m_info.dy(),m_scale) == (m_y_out_max+1) ) ;
190  return m_y_out_max + 1 ;
191  }
192  private:
193  T & m_t ;
194  const Gr::PnmInfo & m_info ;
195  int m_scale ;
196  int m_x_in ;
197  int m_y_in ;
198  int m_x_out ;
199  int m_y_out ;
200  int m_x_out_max ;
201  int m_y_out_max ;
202  } ;
203 
204  struct ImageDataAdaptor
205  {
206  ImageDataAdaptor( Gr::ImageData & data ) :
207  m_data(data)
208  {
209  G_ASSERT( data.dx() > 0 ) ;
210  }
211  void operator()( int x , int y , unsigned int r , unsigned int g , unsigned int b )
212  {
213  m_data.rgb( x , y , r , g , b ) ;
214  }
215  private: Gr::ImageData & m_data ;
216  } ;
217 
218  struct RawAdaptor
219  {
220  RawAdaptor( std::vector<char> & out , const Gr::PnmInfo & info ) :
221  m_out(out) ,
222  m_channels(info.channels())
223  {
224  G_ASSERT( !out.empty() ) ;
225  m_out_p = m_out.begin() ;
226  }
227  void operator()( int , int , unsigned int r , unsigned int g , unsigned int b )
228  {
229  if( m_channels == 1 )
230  {
231  *m_out_p++ = r ;
232  }
233  else
234  {
235  *m_out_p++ = r ;
236  *m_out_p++ = g ;
237  *m_out_p++ = b ;
238  }
239  }
240  private:
241  std::vector<char> & m_out ;
242  std::vector<char>::iterator m_out_p ;
243  int m_channels ;
244  } ;
245 
246  template <typename Tout>
247  void readBodyImp( Tout & out , std::istream & in , const Gr::PnmInfo & info )
248  {
249  const bool is_binary = info.binary() ;
250  const bool is_boolean = info.pn() == 1 || info.pn() == 4 ; // 1bpp
251  if( is_binary && is_boolean )
252  {
253  const int dx_in = (1 + (info.dx()-1)/8) ; // bytes
254  const int info_dx = info.dx() ;
255  for( int y = 0 ; y < info.dy() ; y++ )
256  {
257  int x_out = 0 ;
258  for( int x_in = 0 ; x_in < dx_in ; x_in++ )
259  {
260  unsigned int n = in.get() ;
261  for( unsigned mask = 0x80 ; mask != 0U && x_out < info_dx ; mask >>= 1 )
262  out( x_out++ , y , (n & mask)?0U:255U /*sic*/ , (n & mask)?0U:255U , (n & mask)?0U:255U ) ;
263  }
264  }
265  }
266  else
267  {
268  const int channels_in = ( info.pn() == 3 || info.pn() == 6 ) ? 3 : 1 ;
269  for( int y = 0 ; y < info.dy() ; y++ )
270  {
271  for( int x = 0 ; x < info.dx() ; x++ )
272  {
273  unsigned int r = 0U ;
274  unsigned int g = 0U ;
275  unsigned int b = 0U ;
276  for( int c = 0 ; c < channels_in ; c++ )
277  {
278  unsigned int n = 0U ;
279  if( is_binary )
280  n = in.get() ;
281  else
282  in >> n ;
283 
284  if( is_boolean )
285  {
286  n = n ? 0U : 255U ; // sic
287  }
288  else if( info.maxval() != 0 && info.maxval() != 255 )
289  {
290  n = value256( info.maxval() , n ) ;
291  }
292 
293  if( c == 0 )
294  r = g = b = n ;
295  else if( c == 1 )
296  g = n ;
297  else if( c == 2 )
298  b = n ;
299  }
300  out( x , y , r , g , b ) ;
301  }
302  }
303  }
304  }
305 }
306 
307 Gr::PnmInfo::PnmInfo( int pn , int dx , int dy , int maxval , size_t offset ) :
308  m_pn(pn) ,
309  m_dx(dx) ,
310  m_dy(dy) ,
311  m_maxval(maxval) ,
312  m_offset(offset)
313 {
314  if( pn == 1 || pn == 4 )
315  m_maxval = 1 ;
316 }
317 
318 Gr::PnmInfo::PnmInfo( std::istream & in )
319 {
320  std::ios::fmtflags ff = in.flags() ;
321  in.unsetf( std::ios::skipws ) ;
322 
323  std::istream_iterator<char> p = std::istream_iterator<char>(in) ;
324  std::istream_iterator<char> end ;
325  PnmInfo info = readInfoImp( p , end ) ;
326 
327  in.unget() ; // beware istream-iterator's read-ahead -- extracts on op++(), not op*()
328 
329  in.flags( ff ) ;
330 
331  if( !info.valid() )
332  in.setstate( std::ios_base::failbit ) ; // sic
333 
334  *this = info ;
335 }
336 
337 Gr::PnmInfo::PnmInfo( const std::vector<char> & buffer )
338 {
339  std::vector<char>::const_iterator p = buffer.begin() ;
340  PnmInfo info = readInfoImp( p , buffer.end() ) ;
341  *this = info ;
342 }
343 
344 Gr::PnmInfo::PnmInfo( const char * buffer , size_t buffer_size )
345 {
346  const char * p = buffer ;
347  PnmInfo info = readInfoImp( p , buffer+buffer_size ) ;
348  *this = info ;
349 }
350 
351 Gr::PnmInfo::PnmInfo( const unsigned char * buffer , size_t buffer_size )
352 {
353  const unsigned char * p = buffer ;
354  PnmInfo info = readInfoImp( p , buffer+buffer_size ) ;
355  *this = info ;
356 }
357 
358 Gr::PnmInfo::PnmInfo( const ImageBuffer & image_buffer )
359 {
360  typedef traits::imagebuffer<ImageBuffer>::const_byte_iterator const_byte_iterator ;
361  const_byte_iterator p = imagebuffer::bytes_begin( image_buffer ) ;
362  PnmInfo info = readInfoImp( p , imagebuffer::bytes_end(image_buffer) ) ;
363  *this = info ;
364 }
365 
366 size_t Gr::PnmInfo::rowsize() const
367 {
368  return static_cast<size_t>( dx() * channels() ) ;
369 }
370 
371 Gr::PnmReader::PnmReader( int scale , bool monochrome_out ) :
372  m_scale(scale) ,
373  m_monochrome_out(monochrome_out)
374 {
375 }
376 
377 void Gr::PnmReader::setup( int scale , bool monochrome_out )
378 {
379  m_scale = scale ;
380  m_monochrome_out = monochrome_out ;
381 }
382 
383 void Gr::PnmReader::decode( ImageData & out , const G::Path & path )
384 {
385  std::ifstream in ;
386  {
387  G::Root claim_root ;
388  in.open( path.str().c_str() ) ;
389  }
390  m_info = PnmInfo( in ) ;
391  if( !m_info.valid() )
392  throw std::runtime_error( "failed to read pnm file format: [" + path.str() + "]" ) ;
393  if( !readBody(in,m_info,out,m_scale,m_monochrome_out) )
394  throw std::runtime_error( "failed to read pnm file: [" + path.str() + "]" ) ;
395 }
396 
397 void Gr::PnmReader::decode( ImageData & out , std::istream & in )
398 {
399  m_info = PnmInfo( in ) ;
400  if( !m_info.valid() || !readBody(in,m_info,out,m_scale,m_monochrome_out) )
401  throw std::runtime_error( "failed to read pnm data" ) ;
402 }
403 
404 void Gr::PnmReader::decode( ImageData & out , const char * p , size_t n )
405 {
406  std::istringstream in ;
407  in.rdbuf()->pubsetbuf( const_cast<char*>(p) , n ) ;
408 
409  m_info = PnmInfo( in ) ;
410  if( !m_info.valid() || !readBody(in,m_info,out,m_scale,m_monochrome_out) )
411  throw std::runtime_error( "failed to read pnm data" ) ;
412 }
413 
414 void Gr::PnmReader::decode( ImageData & out , const Gr::ImageBuffer & image_buffer )
415 {
417  imagebuf inbuf( image_buffer ) ;
418  std::istream in( &inbuf ) ;
419  m_info = PnmInfo( in ) ;
420  if( !m_info.valid() || !readBody(in,m_info,out,m_scale,m_monochrome_out) )
421  throw std::runtime_error( "failed to read pnm data" ) ;
422 }
423 
424 bool Gr::PnmReader::readBody( std::istream & in , const PnmInfo & info , ImageData & data , int scale , bool monochrome_out )
425 {
426  G_ASSERT( info.valid() ) ;
427  G_ASSERT( info.dy() >= 1 ) ;
428  G_ASSERT( scale >= 1 ) ;
429  if( !info.valid() || info.dy() < 1 || scale < 1 )
430  return false ;
431 
432  if( info.binary() && info.maxval() > 255 )
433  return false ;
434 
435  data.resize( scaled(info.dx(),scale) , scaled(info.dy(),scale) , monochrome_out ? 1 : info.channels() ) ;
436 
437  if( info.binary() && info.maxval() == 255 ) // optimisation
438  {
439  std::vector<char> buffer( info.rowsize() ) ;
440  unsigned char * buffer_p = reinterpret_cast<unsigned char*>(&buffer[0]) ;
441  const int data_dy = data.dy() ;
442  for( int y = 0 ; y < data_dy ; y++ )
443  {
444  in.read( &buffer[0] , buffer.size() ) ;
445  data.copyRowIn( y , buffer_p , buffer.size() , info.channels() , true , scale ) ;
446  for( int i = 0 ; (y+1) < data_dy && i < (scale-1) ; i++ )
447  in.ignore( buffer.size() ) ;
448  }
449  }
450  else
451  {
452  ImageDataAdaptor out( data ) ;
453  ScalingAdaptor<ImageDataAdaptor> out_scaled( out , info , scale ) ;
454  readBodyImp( out_scaled , in , info ) ;
455  }
456  bool ok = !in.fail() ;
457  return ok ;
458 }
459 
460 /// \file grpnm.cpp
std::string str() const
Returns the path string.
Definition: gpath.cpp:290
int dx() const
Returns the width.
Definition: grimagedata.h:329
bool valid() const
Returns true if successfully constructed.
Definition: grpnm.h:156
int dx() const
Returns the image width.
Definition: grpnm.h:150
A traits class that can be specialised for Gr::ImageBuffer candidates.
Definition: grtraits.h:34
A holder for image data, having eight bits per sample and one or three channels.
Definition: grimagedata.h:46
PnmReader(int scale=1, bool monochrome_out=false)
Constructor.
Definition: grpnm.cpp:371
void resize(int dx, int dy, int channels)
Resizes the image data.
Definition: grimagedata.cpp:89
int maxval() const
Returns the maximum value, or zero for bitmap formats (p1/p4).
Definition: grpnm.h:154
int pn() const
Returns the p-number.
Definition: grpnm.h:153
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
int dy() const
Returns the image height.
Definition: grpnm.h:151
bool binary() const
Returns true if a binary format.
Definition: grpnm.h:148
void decode(ImageData &out, const G::Path &in)
Decodes a pnm file into an image. Throws on error.
Definition: grpnm.cpp:383
void copyRowIn(int y, const unsigned char *row_buffer_in, size_t row_buffer_in_size, int channels_in, bool use_colourspace=false, int scale=1)
Sets a row of pixels by copying, with channel-count adjustment and optional scaling.
int channels() const
Returns the number of channels.
Definition: grpnm.h:152
void setup(int scale, bool monochrome_out=false)
Sets the decoding scale factor.
Definition: grpnm.cpp:377
A structure holding portable-anymap metadata.
Definition: grpnm.h:42
PnmInfo()
Default constructor for an invalid structure.
Definition: grpnm.h:147
int dy() const
Returns the height.
Definition: grimagedata.h:335
A Path object represents a file system path.
Definition: gpath.h:72
size_t rowsize() const
Returns dx() * channels().
Definition: grpnm.cpp:366