VideoTools
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
grpng_png.cpp
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 // grpng_png.cpp
19 //
20 
21 #include "gdef.h"
22 #include "grdef.h"
23 #include "grpng.h"
24 #include "groot.h"
25 #include "gdebug.h"
26 #include <png.h>
27 #include <cstring>
28 #include <cstdio> // std::tmpfile()
29 #include <algorithm>
30 
31 typedef int static_assert_png_byte_is_char[sizeof(png_byte)==1?1:-1] ;
32 
33 // temporary compile-time fix for mac
34 #ifndef PNG_TRANSFORM_GRAY_TO_RGB
35 #define PNG_TRANSFORM_GRAY_TO_RGB 0
36 #endif
37 
38 #if GCONFIG_HAVE_FMEMOPEN
39 namespace
40 {
41  FILE * fmemopen_( void * p , size_t n , const char * mode )
42  {
43  return fmemopen( p , n , mode ) ;
44  }
45 }
46 #else
47 namespace
48 {
49  FILE * fmemopen_( void * p , size_t n , const char * )
50  {
51  FILE * fp = std::tmpfile() ;
52  if( fp )
53  {
54  size_t rc = std::fwrite( p , n , 1U , fp ) ;
55  if( rc != 1U )
56  {
57  std::fclose( fp ) ;
58  return nullptr ;
59  }
60  std::fseek( fp , 0 , SEEK_SET ) ;
61  }
62  return fp ;
63  }
64 }
65 #endif
66 
67 bool Gr::Png::available()
68 {
69  return true ;
70 }
71 
72 /// \class Gr::PngImp
73 /// A private base class that manages png_struct and png_info structures,
74 /// and handles error callbacks.
75 ///
77 {
78 public:
79  struct FileCloser /// RAII class to do std::fclose().
80  {
81  FILE * m_fp ;
82  explicit FileCloser( FILE * fp ) : m_fp(fp) {}
83  ~FileCloser() { if( m_fp ) std::fclose( m_fp ) ; }
84  } ;
85 
86 public:
87  explicit PngImp( bool reader ) ;
88  ~PngImp() ;
89  void pngReset() ;
90 
91 private:
92  bool m_reader ;
93 
94 protected:
95  png_struct * m_png_struct ;
96  png_info * m_png_info ;
97 
98 private:
99  PngImp( const PngImp & ) ;
100  void operator=( const PngImp & ) ;
101  void init_( bool ) ;
102  void cleanup_() ;
103  static void error_fn( png_struct * , const char * ) ;
104  static void warning_fn( png_struct * , const char * ) ;
105 } ;
106 
107 /// \class Gr::PngReaderImp
108 /// A private pimple-pattern class for Gr::PngReader.
109 ///
110 class Gr::PngReaderImp : private PngImp
111 {
112 public:
113  typedef PngReader::Map Map ;
114  PngReaderImp() ;
115  ~PngReaderImp() ;
116  void decode( ImageData & , const G::Path & path , int scale , bool monochrome_out ) ;
117  void decode( ImageData & , const char * data_p , size_t data_size , int scale , bool monochrome_out ) ;
118  void decode( ImageData & , const ImageBuffer & , int scale , bool monochrome_out ) ;
119  Map tags() const ;
120 
121 private:
122  PngReaderImp( const PngReaderImp & ) ;
123  void operator=( const PngReaderImp & ) ;
124  void decodeImp( ImageData & , FILE * , const ImageBuffer * , int scale , bool monochrome_out ) ;
125  void init_io( png_struct * , FILE * ) ;
126  static void read( png_struct * , unsigned char * p , size_t ) ;
127  void reset( FILE * fp ) ;
128  void reset( const ImageBuffer * bp ) ;
129 
130 private:
132  Map m_tags ;
133  unique_ptr<imagebuf> m_read_state ;
134 } ;
135 
136 /// \class Gr::PngTagsImp
137 /// A Gr::PngWriter implementation class that holds tags.
138 ///
140 {
141 public:
142  typedef std::multimap<std::string,std::string> Map ;
143  PngTagsImp() ;
144  explicit PngTagsImp( const Map & tags ) ;
145  int n() const ;
146  png_text * p() const ;
147 
148 private:
149  PngTagsImp( const PngTagsImp & ) ;
150  void operator=( const PngTagsImp & ) ;
151 
152 private:
153  std::vector<std::string> m_strings ;
154  std::vector<png_text> m_text ;
155 } ;
156 
157 /// \class Gr::PngWriterImp
158 /// A private pimple-pattern class for Gr::PngWriter.
159 ///
160 class Gr::PngWriterImp : private PngImp
161 {
162 public:
163  typedef PngWriter::Map Map ;
164  typedef PngWriter::Output Output ;
165  PngWriterImp( const ImageData & , Map tags ) ;
166  void write( const G::Path & path ) ;
167  void write( Output & out ) ;
168 
169 private:
170  static void flush_imp( png_struct * ) ;
171  static void write_imp( png_struct * p , png_byte * data , png_size_t n ) ;
172 
173 private:
174  const ImageData & m_data ;
175  PngTagsImp m_tags ;
176 } ;
177 
178 // ==
179 
180 Gr::PngImp::PngImp( bool reader ) :
181  m_reader(reader) ,
182  m_png_struct(nullptr) ,
183  m_png_info(nullptr)
184 {
185  init_( reader ) ;
186 }
187 
188 void Gr::PngImp::init_( bool reader )
189 {
190  m_png_struct = reader ?
191  png_create_read_struct( PNG_LIBPNG_VER_STRING , reinterpret_cast<png_voidp>(this) , error_fn , warning_fn ) :
192  png_create_write_struct( PNG_LIBPNG_VER_STRING , reinterpret_cast<png_voidp>(this) , error_fn , warning_fn ) ;
193 
194  if( m_png_struct == nullptr )
195  throw Png::Error( "png_create_struct failed" ) ;
196 
197  m_png_info = png_create_info_struct( m_png_struct ) ;
198  if( m_png_info == nullptr )
199  {
200  cleanup_() ;
201  throw Png::Error( "png_create_info_struct failed" ) ;
202  }
203 }
204 
205 void Gr::PngImp::pngReset()
206 {
207  cleanup_() ;
208  init_( m_reader ) ;
209 }
210 
211 Gr::PngImp::~PngImp()
212 {
213  cleanup_() ;
214 }
215 
216 void Gr::PngImp::cleanup_()
217 {
218  if( m_reader )
219  png_destroy_read_struct( &m_png_struct , &m_png_info , nullptr ) ;
220  else
221  png_destroy_write_struct( &m_png_struct , &m_png_info ) ;
222 
223  m_png_struct = nullptr ;
224  m_png_info = nullptr ;
225 }
226 
227 void Gr::PngImp::error_fn( png_struct * p , const char * what )
228 {
229  G_DEBUG( "Gr::PngImp::error: error callback from libpng: " << what ) ;
230 #if PNG_LIBPNG_VER_MAJOR > 1 || PNG_LIBPNG_VER_MINOR >= 5
231  png_longjmp( p , 99 ) ; // cannot throw :-<
232 #else
233  longjmp( p->jmpbuf , 99 ) ; // cannot throw :-<
234 #endif
235 }
236 
237 void Gr::PngImp::warning_fn( png_struct * , const char * what )
238 {
239  G_DEBUG( "Gr::PngImp::warning: warning callback from libpng: " << what ) ;
240 }
241 
242 // ==
243 
244 void Gr::PngInfo::init( std::istream & stream )
245 {
246  unsigned char buffer[31] = { 0 } ;
247  stream.read( reinterpret_cast<char*>(buffer) , sizeof(buffer) ) ;
248  std::pair<int,int> pair = parse( buffer , stream.gcount() ) ;
249  m_dx = pair.first ;
250  m_dy = pair.second ;
251 }
252 
253 void Gr::PngInfo::init( const unsigned char * p_in , size_t n )
254 {
255  std::pair<int,int> pair = parse( p_in , n ) ;
256  m_dx = pair.first ;
257  m_dy = pair.second ;
258 }
259 
260 // ==
261 
262 Gr::PngReader::PngReader( int scale , bool monochrome_out ) :
263  m_scale(scale) ,
264  m_monochrome_out(monochrome_out)
265 {
266 }
267 
268 void Gr::PngReader::setup( int scale , bool monochrome_out )
269 {
270  m_scale = scale ;
271  m_monochrome_out = monochrome_out ;
272 }
273 
274 void Gr::PngReader::decode( ImageData & out , const G::Path & path )
275 {
276  PngReaderImp imp ;
277  imp.decode( out , path , m_scale , m_monochrome_out ) ;
278  m_tags = imp.tags() ;
279 }
280 
281 void Gr::PngReader::decode( ImageData & out , const char * p , size_t n )
282 {
283  PngReaderImp imp ;
284  imp.decode( out , p , n , m_scale , m_monochrome_out ) ;
285  m_tags = imp.tags() ;
286 }
287 
288 void Gr::PngReader::decode( ImageData & out , const unsigned char * p , size_t n )
289 {
290  decode( out , reinterpret_cast<const char*>(p) , n ) ;
291 }
292 
293 void Gr::PngReader::decode( ImageData & out , const ImageBuffer & b )
294 {
295  PngReaderImp imp ;
296  imp.decode( out , b , m_scale , m_monochrome_out ) ;
297  m_tags = imp.tags() ;
298 }
299 
301 {
302 }
303 
304 Gr::PngReader::Map Gr::PngReader::tags() const
305 {
306  return m_tags ;
307 }
308 
309 // ==
310 
311 Gr::PngReaderImp::PngReaderImp() :
312  PngImp(true)
313 {
314 }
315 
316 Gr::PngReaderImp::~PngReaderImp()
317 {
318 }
319 
320 void Gr::PngReaderImp::decode( ImageData & out , const char * p , size_t n , int scale , bool monochrome_out )
321 {
322  FILE * fp = fmemopen_( const_cast<char*>(p) , n , "rb" ) ;
323  if( fp == nullptr )
324  throw Png::Error( "fmemopen failed" ) ;
325  PngImp::FileCloser closer( fp ) ;
326  decodeImp( out , fp , nullptr , scale , monochrome_out ) ;
327 }
328 
329 void Gr::PngReaderImp::decode( ImageData & out , const G::Path & path , int scale , bool monochrome_out )
330 {
331  FILE * fp = nullptr ;
332  {
333  G::Root claim_root ;
334  fp = std::fopen( path.str().c_str() , "rb" ) ;
335  }
336  if( fp == nullptr )
337  throw Png::Error( "cannot open png file" , path.str() ) ;
338  PngImp::FileCloser closer( fp ) ;
339  decodeImp( out , fp , nullptr , scale , monochrome_out ) ;
340 }
341 
342 void Gr::PngReaderImp::decode( ImageData & out , const ImageBuffer & image_buffer , int scale , bool monochrome_out )
343 {
344  decodeImp( out , nullptr , &image_buffer , scale , monochrome_out ) ;
345 }
346 
347 void Gr::PngReaderImp::init_io( png_struct * png , FILE * fp )
348 {
349  if( fp != nullptr )
350  png_init_io( png , fp ) ;
351  else
352  png_set_read_fn( png , this , &PngReaderImp::read ) ;
353 }
354 
355 void Gr::PngReaderImp::decodeImp( ImageData & out , FILE * fp , const ImageBuffer * bp , int scale , bool monochrome_out )
356 {
357  if( fp == nullptr && bp == nullptr )
358  return ;
359 
360  if( bp != nullptr )
361  m_read_state.reset( new imagebuf(*bp) ) ;
362 
363  const int transforms =
364  PNG_TRANSFORM_STRIP_16 | // chop 16-bits down to eight
365  PNG_TRANSFORM_STRIP_ALPHA | // discard alpha channel
366  PNG_TRANSFORM_PACKING | // expand 1, 2 and 4 bits to eight
367  PNG_TRANSFORM_EXPAND | // set_expand()
368  PNG_TRANSFORM_GRAY_TO_RGB ; // expand greyscale to RGB
369 
370  // get the image size and allocate storage
371  {
372  bool ok = false ;
373  int dx = 0 ;
374  int dy = 0 ;
375  if( ! setjmp( png_jmpbuf(m_png_struct) ) )
376  {
377  try
378  {
379  init_io( m_png_struct , fp ) ;
380  png_read_info( m_png_struct , m_png_info ) ;
381  dx = png_get_image_width( m_png_struct , m_png_info ) ;
382  dy = png_get_image_height( m_png_struct , m_png_info ) ;
383  ok = true ;
384  }
385  catch(...) // setjmp/longjmp
386  {
387  }
388  }
389  if( !ok || dx <= 0 || dy <= 0 )
390  throw Png::Error( "png_read_info failed" ) ;
391  out.resize( dx , dy , 3 ) ;
392  pngReset() ; // PngImp::pngReset()
393  reset( fp ) ;
394  reset( bp ) ;
395 
396  }
397 
398  // decode
399  {
400  bool ok = false ;
401  png_text * text_p = nullptr ;
402  int text_n = 0 ;
403  png_byte ** out_pp = out.rowPointers() ;
404  if( ! setjmp( png_jmpbuf(m_png_struct) ) )
405  {
406  try
407  {
408  init_io( m_png_struct , fp ) ;
409  png_set_rows( m_png_struct , m_png_info , out_pp ) ; // use our storage
410  png_read_png( m_png_struct , m_png_info , transforms , nullptr ) ;
411  png_get_text( m_png_struct , m_png_info , &text_p , &text_n ) ;
412  int depth = png_get_bit_depth( m_png_struct , m_png_info ) ;
413  ok = depth == 8 ;
414  }
415  catch(...) // setjmp/longjmp
416  {
417  }
418  }
419  if( !ok )
420  throw Png::Error( "png_read_png failed" ) ;
421  for( int i = 0 ; i < text_n ; i++ )
422  {
423  if( text_p && text_p[i].compression == PNG_TEXT_COMPRESSION_NONE )
424  m_tags.insert( Map::value_type(text_p[i].key,text_p[i].text) ) ;
425  }
426  }
427 
428  // scale the output (optimisation opportunity here to not do the colourspace
429  // stuff if the image was monochrome to start with)
430  //
431  out.scale( scale , monochrome_out , true ) ;
432 }
433 
434 void Gr::PngReaderImp::read( png_struct * png , unsigned char * p , size_t n )
435 {
436  PngReaderImp * imp = static_cast<PngReaderImp*>( png_get_io_ptr( png ) ) ;
437  if( imp == nullptr || imp->m_read_state.get() == nullptr || p == nullptr )
438  png_error( png , "read error" ) ;
439 
440  size_t rc = static_cast<size_t>( imp->m_read_state->sgetn( reinterpret_cast<char*>(p) , n ) ) ;
441  if( rc != n )
442  png_error( png , "read error" ) ;
443 }
444 
445 Gr::PngReader::Map Gr::PngReaderImp::tags() const
446 {
447  return m_tags ;
448 }
449 
450 void Gr::PngReaderImp::reset( FILE * fp )
451 {
452  int rc = fp == nullptr ? 0 : std::fseek( fp , 0 , SEEK_SET ) ;
453  if( rc != 0 )
454  throw Png::Error( "fseek error" ) ;
455 }
456 
457 void Gr::PngReaderImp::reset( const ImageBuffer * bp )
458 {
459  if( bp != nullptr )
460  m_read_state.reset( new imagebuf(*bp) ) ;
461 }
462 
463 // ==
464 
465 Gr::PngTagsImp::PngTagsImp()
466 {
467 }
468 
469 Gr::PngTagsImp::PngTagsImp( const Map & tags )
470 {
471  m_text.reserve( tags.size() ) ;
472  Map::const_iterator const end = tags.end() ;
473  for( Map::const_iterator p = tags.begin() ; p != end ; ++p )
474  {
475  if( (*p).first.length() == 0U || (*p).first.length() > 78 )
476  throw Png::Error( "invalid tag key" ) ;
477  static png_text zero ;
478  png_text text( zero ) ;
479  text.compression = PNG_TEXT_COMPRESSION_NONE ;
480  m_strings.push_back( (*p).first + '\0' ) ;
481  text.key = const_cast<char*>(m_strings.back().data()) ;
482  m_strings.push_back( (*p).second + '\0' ) ;
483  text.text = const_cast<char*>(m_strings.back().data()) ;
484  m_text.push_back( text ) ;
485  }
486 }
487 
488 int Gr::PngTagsImp::n() const
489 {
490  return m_text.size() ;
491 }
492 
493 png_text * Gr::PngTagsImp::p() const
494 {
495  typedef std::vector<png_text> Vector ;
496  Vector & text = const_cast<Vector&>(m_text) ;
497  return m_text.size() ? &text[0] : nullptr ;
498 }
499 
500 // ==
501 
502 Gr::PngWriter::PngWriter( const ImageData & data , Map tags ) :
503  m_imp(new PngWriterImp(data,tags))
504 {
505 }
506 
508 {
509  delete m_imp ;
510 }
511 
512 void Gr::PngWriter::write( const G::Path & path )
513 {
514  m_imp->write( path ) ;
515 }
516 
517 void Gr::PngWriter::write( Output & out )
518 {
519  m_imp->write( out ) ;
520 }
521 
522 // ==
523 
524 Gr::PngWriterImp::PngWriterImp( const ImageData & data , Map tags_in ) :
525  PngImp(false) ,
526  m_data(data) ,
527  m_tags(tags_in)
528 {
529  bool ok = false ;
530  {
531  png_uint_32 dx = static_cast<png_uint_32>( m_data.dx() ) ;
532  png_uint_32 dy = static_cast<png_uint_32>( m_data.dy() ) ;
533  png_byte ** row_pointers = const_cast<ImageData&>(m_data).rowPointers() ;
534  png_text * tags_p = m_tags.p() ;
535  int tags_n = m_tags.n() ;
536 
537  if( ! setjmp( png_jmpbuf(m_png_struct) ) )
538  {
539  try
540  {
541  png_set_IHDR( m_png_struct , m_png_info , dx , dy , 8 ,
542  data.channels() == 3 ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_GRAY ,
543  PNG_INTERLACE_NONE , PNG_COMPRESSION_TYPE_DEFAULT , PNG_FILTER_TYPE_DEFAULT ) ;
544  png_set_rows( m_png_struct , m_png_info , row_pointers ) ;
545  if( tags_n != 0 )
546  png_set_text( m_png_struct , m_png_info , tags_p , tags_n ) ;
547  ok = true ;
548  }
549  catch(...) // setjmp/longjmp
550  {
551  }
552  }
553  }
554  if( !ok )
555  throw Png::Error( "png_set_rows failed" ) ;
556 }
557 
558 void Gr::PngWriterImp::write( const G::Path & path )
559 {
560  FILE * fp = nullptr ;
561  {
562  G::Root claim_root ;
563  fp = std::fopen( path.str().c_str() , "wb" ) ;
564  }
565  if( fp == nullptr )
566  throw Png::Error( "cannot create output" , path.str() ) ;
567  PngImp::FileCloser closer( fp ) ;
568 
569  bool ok = false ;
570  {
571  if( ! setjmp( png_jmpbuf(m_png_struct) ) )
572  {
573  try
574  {
575  png_init_io( m_png_struct , fp ) ;
576  png_write_png( m_png_struct , m_png_info , PNG_TRANSFORM_PACKING , nullptr ) ;
577  ok = true ;
578  }
579  catch(...) // setjmp/longjmp
580  {
581  }
582  }
583  }
584  if( !ok )
585  throw Png::Error( "png_write_png failed" ) ;
586 }
587 
588 void Gr::PngWriterImp::write( Gr::PngWriter::Output & out )
589 {
590  bool ok = false ;
591  {
592  if( ! setjmp( png_jmpbuf(m_png_struct) ) )
593  {
594  try
595  {
596  png_set_write_fn( m_png_struct , reinterpret_cast<void*>(&out) , write_imp , flush_imp ) ;
597  png_write_png( m_png_struct , m_png_info , PNG_TRANSFORM_PACKING , nullptr ) ;
598  ok = true ;
599  }
600  catch(...) // setjmp/longjmp
601  {
602  }
603  }
604  }
605  if( !ok )
606  throw Png::Error( "png_write_png failed" ) ;
607 }
608 
609 void Gr::PngWriterImp::write_imp( png_struct * p , png_byte * data , png_size_t n )
610 {
611  Output * out = reinterpret_cast<Output*>( png_get_io_ptr(p) ) ;
612  (*out)( data , n ) ;
613 }
614 
615 void Gr::PngWriterImp::flush_imp( png_struct * )
616 {
617 }
618 
~PngReader()
Destructor.
Definition: grpng_none.cpp:29
std::string str() const
Returns the path string.
Definition: gpath.cpp:290
Map tags() const
Returns the text tags from the last decode().
Definition: grpng_none.cpp:34
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
A private base class that manages png_struct and png_info structures, and handles error callbacks...
Definition: grpng_png.cpp:76
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
RAII class to do std::fclose().
Definition: grpng_png.cpp:79
PngWriter(const ImageData &, Map tags=Map())
Constructor with the raw image data prepared in a ImageData object.
Definition: grpng_none.cpp:35
A private pimple-pattern class for Gr::PngWriter.
Definition: grpng_none.cpp:26
void decode(ImageData &out, const G::Path &in)
Decodes a png file into an image. Throws on error.
Definition: grpng_none.cpp:32
A private pimple-pattern class for Gr::PngReader.
Definition: grpng_none.cpp:25
A Gr::PngWriter implementation class that holds tags.
Definition: grpng_png.cpp:139
PngReader(int scale=1, bool monochrome_out=false)
Constructor.
Definition: grpng_none.cpp:28
void setup(int scale, bool monochrome_out=false)
Sets the decoding scale factor.
Definition: grpng_none.cpp:30
static bool available()
Returns true if the png library is available.
Definition: grpng_none.cpp:27
~PngWriter()
Destructor.
Definition: grpng_png.cpp:507
A Path object represents a file system path.
Definition: gpath.h:72
Abstract interface for Gr::PngWriter::write().
Definition: grpng.h:158
void write(const G::Path &path)
Writes to file.
Definition: grpng_none.cpp:36