VideoTools
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
grjpeg_jpeg.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 // grjpeg_jpeg.cpp
19 //
20 
21 #include "gdef.h"
22 #include "grdef.h"
23 #include "grjpeg.h"
24 #include "gstr.h"
25 #include "groot.h"
26 #include "gtest.h"
27 #include "glimits.h"
28 #include "gassert.h"
29 #include "glog.h"
30 #include <jpeglib.h>
31 #include <limits>
32 #include <cstdio>
33 #include <cstdlib> // std::free()
34 
35 typedef int static_assert_jsample_is_char[sizeof(JSAMPLE)==1?1:-1] ;
36 typedef int static_assert_joctet_is_char[sizeof(JOCTET)==1?1:-1] ;
37 
38 namespace
39 {
40  std::string message( j_common_ptr p )
41  {
42  char buffer[JMSG_LENGTH_MAX] ;
43  (p->err->format_message)( p , buffer ) ;
44  return G::Str::lower(G::Str::printable(buffer)) ;
45  }
46  void outputMessage( j_common_ptr p )
47  {
48  if( G::Log::at( G::Log::s_Debug ) )
49  G_DEBUG( "Gr::Jpeg::outputMessage: " << message(p) ) ;
50  }
51  void error( j_common_ptr p )
52  {
53  std::string e = message( p ) ;
54  //G_WARNING( "Gr::Jpeg::error: " << e ) ;
55  throw Gr::Jpeg::Error( e ) ;
56  }
57 }
58 
59 namespace Gr
60 {
61  class JpegBufferSource ;
62  class JpegBufferDestination ;
63 }
64 
65 /// \class Gr::JpegBufferSource
66 /// A libjpeg 'decompression source manager' that reads from Gr::ImageBuffer.
67 ///
68 class Gr::JpegBufferSource : public jpeg_source_mgr
69 {
70 public:
71  static JpegBufferSource * install( j_decompress_ptr cinfo , const Gr::ImageBuffer & b ) ;
72 
73 private:
74  explicit JpegBufferSource( const Gr::ImageBuffer & b ) ;
75  static void init_source( j_decompress_ptr ) ;
76  static boolean fill_buffer( j_decompress_ptr cinfo ) ;
77  static void skip_input( j_decompress_ptr cinfo , long n ) ;
78  static void term_source( j_decompress_ptr cinfo ) ;
79  void fill_buffer_imp() ;
80  void skip_input_imp( size_t n ) ;
81 
82 private:
83  const ImageBuffer & m_b ;
85 } ;
86 
87 Gr::JpegBufferSource * Gr::JpegBufferSource::install( j_decompress_ptr cinfo , const Gr::ImageBuffer & b )
88 {
89  JpegBufferSource * p = new JpegBufferSource( b ) ;
90  cinfo->src = p ;
91  cinfo->src->init_source = init_source ;
92  cinfo->src->fill_input_buffer = fill_buffer ;
93  cinfo->src->skip_input_data = skip_input ;
94  cinfo->src->resync_to_restart = jpeg_resync_to_restart ;
95  cinfo->src->term_source = term_source ;
96  cinfo->src->bytes_in_buffer = 0 ;
97  cinfo->src->next_input_byte = reinterpret_cast<const unsigned char *>( &(b.at(0))[0] ) ;
98  return p ;
99 }
100 
101 Gr::JpegBufferSource::JpegBufferSource( const Gr::ImageBuffer & b ) :
102  m_b(b) ,
103  m_p(imagebuffer::row_begin(m_b))
104 {
105  G_DEBUG( "Gr::JpegBufferSource::ctor: installing " << (void*)(this) ) ;
106 }
107 
108 void Gr::JpegBufferSource::init_source( j_decompress_ptr )
109 {
110 }
111 
112 boolean Gr::JpegBufferSource::fill_buffer( j_decompress_ptr cinfo )
113 {
114  JpegBufferSource * src = static_cast<JpegBufferSource*>( cinfo->src ) ;
115  G_ASSERT( src != nullptr && src->init_source == init_source ) ;
116 
117  src->fill_buffer_imp() ;
118  return TRUE ;
119 }
120 
121 void Gr::JpegBufferSource::fill_buffer_imp()
122 {
123  if( m_p == imagebuffer::row_end(m_b) )
124  {
125  static const JOCTET eoi[] = { 0xff , JPEG_EOI , 0 , 0 } ;
126  next_input_byte = eoi ;
127  bytes_in_buffer = 2 ;
128  }
129  else
130  {
131  const char * p = imagebuffer::row_ptr(m_p) ;
132  next_input_byte = reinterpret_cast<const unsigned char *>(p) ;
133  bytes_in_buffer = imagebuffer::row_size(m_p) ;
134  ++m_p ;
135  }
136 }
137 
138 void Gr::JpegBufferSource::skip_input( j_decompress_ptr cinfo , long n )
139 {
140  JpegBufferSource * src = static_cast<JpegBufferSource*>( cinfo->src ) ;
141  G_ASSERT( src != nullptr && src->init_source == init_source ) ;
142 
143  if( n < 0 || static_cast<unsigned long>(n) > std::numeric_limits<size_t>::max() )
144  throw Gr::Jpeg::Error( "skip_input error" ) ;
145  src->skip_input_imp( static_cast<size_t>(n) ) ;
146 }
147 
148 void Gr::JpegBufferSource::skip_input_imp( size_t n )
149 {
150  while( n > bytes_in_buffer )
151  {
152  n -= bytes_in_buffer ;
153  fill_buffer_imp() ;
154  }
155  next_input_byte += n ;
156  bytes_in_buffer -= n ;
157 }
158 
159 void Gr::JpegBufferSource::term_source( j_decompress_ptr cinfo )
160 {
161 }
162 
163 // ==
164 
165 /// \class Gr::JpegBufferDestination
166 /// A libjpeg 'decompression destination manager' that writes to Gr::ImageBuffer.
167 ///
168 class Gr::JpegBufferDestination : public jpeg_destination_mgr
169 {
170 public:
171  static JpegBufferDestination * install( j_compress_ptr cinfo , Gr::ImageBuffer & b ) ;
172 
173 private:
174  explicit JpegBufferDestination( Gr::ImageBuffer & b ) ;
175  static void init_destination( j_compress_ptr ) ;
176  static boolean empty_output_buffer( j_compress_ptr cinfo ) ;
177  static void term_destination( j_compress_ptr ) ;
178  void next() ;
179  void set() ;
180  void close() ;
181 
182 private:
183  ImageBuffer & m_b ;
185  size_t m_buffer_size ;
186 } ;
187 
188 Gr::JpegBufferDestination * Gr::JpegBufferDestination::install( j_compress_ptr cinfo , Gr::ImageBuffer & b )
189 {
191  cinfo->dest = p ;
192  cinfo->dest->init_destination = init_destination ;
193  cinfo->dest->empty_output_buffer = empty_output_buffer ;
194  cinfo->dest->term_destination = term_destination ;
195  return p ;
196 }
197 
198 Gr::JpegBufferDestination::JpegBufferDestination( Gr::ImageBuffer & b ) :
199  m_b(b) ,
200  m_p(imagebuffer::row_begin(b)) ,
201  m_buffer_size(G::limits::file_buffer)
202 {
203  if( G::Test::enabled("tiny-image-buffers") )
204  m_buffer_size = 10U ;
205 
206  set() ;
207 }
208 
209 void Gr::JpegBufferDestination::next()
210 {
211  ++m_p ;
212  set() ;
213 }
214 
215 void Gr::JpegBufferDestination::set()
216 {
217  if( m_p == imagebuffer::row_end(m_b) )
218  {
219  m_p = imagebuffer::row_add( m_b ) ;
220  }
221 
222  if( imagebuffer::row_empty(m_p) )
223  imagebuffer::row_resize( m_p , m_buffer_size ) ;
224 
225  char * p = imagebuffer::row_ptr( m_p ) ;
226  next_output_byte = reinterpret_cast<JOCTET*>(p) ;
227  free_in_buffer = imagebuffer::row_size( m_p ) ;
228 }
229 
230 void Gr::JpegBufferDestination::close()
231 {
232  if( m_p != imagebuffer::row_end(m_b) )
233  {
234  size_t residue = static_cast<size_t>(free_in_buffer) ;
235  G_ASSERT( residue <= imagebuffer::row_size(m_p) ) ;
236  residue = std::min( imagebuffer::row_size(m_p) , residue ) ;
237  imagebuffer::row_resize( m_p , imagebuffer::row_size(m_p) - residue ) ;
238  if( !imagebuffer::row_empty(m_p) ) ++m_p ;
239  imagebuffer::row_erase( m_b , m_p ) ;
240  }
241 }
242 
243 void Gr::JpegBufferDestination::init_destination( j_compress_ptr cinfo )
244 {
245 }
246 
247 boolean Gr::JpegBufferDestination::empty_output_buffer( j_compress_ptr cinfo )
248 {
249  JpegBufferDestination * dst = static_cast<JpegBufferDestination*>( cinfo->dest ) ;
250  G_ASSERT( dst != nullptr && dst->init_destination == init_destination ) ;
251  dst->next() ;
252  return TRUE ;
253 }
254 
255 void Gr::JpegBufferDestination::term_destination( j_compress_ptr cinfo )
256 {
257  JpegBufferDestination * dst = static_cast<JpegBufferDestination*>( cinfo->dest ) ;
258  G_ASSERT( dst != nullptr && dst->init_destination == init_destination ) ;
259  dst->close() ;
260 }
261 
262 // ==
263 
265 {
266  return true ;
267 }
268 
269 /// \class Gr::JpegReaderImp
270 /// A private implementation class for Gr::JpegReader.
271 ///
273 {
274 public:
275  JpegReaderImp() ;
276  ~JpegReaderImp() ;
277  void decode( ImageData & , FILE * , int scale , bool monochrome_out ) ;
278  void decode( ImageData & , const unsigned char * , size_t , int scale , bool monochrome_out ) ;
279  void decode( ImageData & , const ImageBuffer & , int scale , bool monochrome_out ) ;
280 
281 private:
282  JpegReaderImp( const JpegReaderImp & ) ;
283  void operator=( const JpegReaderImp & ) ;
284  void createBuffers( ImageData & ) ;
285  jpeg_decompress_struct * p() ;
286  const jpeg_decompress_struct * p() const ;
287  j_common_ptr base() ;
288  int channels() const ;
289  void start( FILE * , int , bool ) ;
290  void start( const unsigned char * , size_t , int , bool ) ;
291  void start( const ImageBuffer & , int , bool ) ;
292  void finish() ;
293  void readPixels( ImageData & ) ;
294  void startImp() ;
295  void configure( int , bool ) ;
296  void readGeometry() ;
297  void reduce( ImageData & , int , bool ) ;
298  static int pre( int ) ;
299  static int post( int ) ;
300 
301 private:
302  int m_dx ;
303  int m_dy ;
304  jpeg_error_mgr m_err ;
305  jpeg_decompress_struct m ;
306  std::vector<unsigned char> m_line_buffer ;
307  unique_ptr<JpegBufferSource> m_buffer_source ;
308 } ;
309 
310 // ==
311 
312 namespace
313 {
314  class FileStream
315  {
316  public:
317  FileStream( const G::Path & path , const char * mode ) :
318  m_fp(nullptr)
319  {
320  {
321  G::Root claim_root ;
322  m_fp = std::fopen( path.str().c_str() , mode ) ;
323  }
324  if( m_fp == nullptr )
325  throw Gr::Jpeg::Error( "cannot open file" ) ;
326  }
327  FILE * fp()
328  {
329  return m_fp ;
330  }
331  ~FileStream()
332  {
333  std::fclose( m_fp ) ;
334  }
335  private:
336  FileStream( const FileStream & ) ;
337  void operator=( const FileStream & ) ;
338  private:
339  FILE * m_fp ;
340  } ;
341 }
342 
343 // ==
344 
345 Gr::JpegReaderImp::JpegReaderImp() :
346  m_dx(0) ,
347  m_dy(0)
348 {
349  m.err = jpeg_std_error( &m_err ) ;
350  m_err.error_exit = error ;
351  m_err.output_message = outputMessage ;
352  jpeg_create_decompress( &m ) ;
353 }
354 
355 Gr::JpegReaderImp::~JpegReaderImp()
356 {
357  jpeg_destroy_decompress( &m ) ;
358 }
359 
360 void Gr::JpegReaderImp::decode( ImageData & out , FILE * fp , int scale , bool monochrome_out )
361 {
362  start( fp , pre(scale) , monochrome_out ) ;
363  readGeometry() ;
364  createBuffers( out ) ;
365  readPixels( out ) ;
366  reduce( out , post(scale) , monochrome_out ) ;
367  finish() ;
368 }
369 
370 void Gr::JpegReaderImp::decode( ImageData & out , const unsigned char * p , size_t n , int scale , bool monochrome_out )
371 {
372  start( p , n , pre(scale) , monochrome_out ) ;
373  readGeometry() ;
374  createBuffers( out ) ;
375  readPixels( out ) ;
376  reduce( out , post(scale) , monochrome_out ) ;
377  finish() ;
378 }
379 
380 void Gr::JpegReaderImp::decode( ImageData & out , const ImageBuffer & b , int scale , bool monochrome_out )
381 {
382  start( b , pre(scale) , monochrome_out ) ;
383  readGeometry() ;
384  createBuffers( out ) ;
385  readPixels( out ) ;
386  reduce( out , post(scale) , monochrome_out ) ;
387  finish() ;
388 }
389 
390 int Gr::JpegReaderImp::pre( int scale )
391 {
392  if( scale == 1 )
393  return 1 ;
394  else if( scale == 2 || scale == 4 || scale == 8 )
395  return scale ;
396  else if( (scale & 7) == 0 )
397  return 8 ;
398  else
399  return 1 ;
400 }
401 
402 int Gr::JpegReaderImp::post( int scale )
403 {
404  if( scale == 1 )
405  return 1 ;
406  else if( scale == 2 || scale == 4 || scale == 8 )
407  return 1 ;
408  else if( (scale & 7) == 0 )
409  return scale >> 3 ;
410  else
411  return scale ;
412 }
413 
414 void Gr::JpegReaderImp::createBuffers( ImageData & data )
415 {
416  data.resize( m_dx , m_dy , 3 ) ;
417 }
418 
419 void Gr::JpegReaderImp::readGeometry()
420 {
421  m_dx = m.output_width ;
422  m_dy = m.output_height ;
423  if( m.output_components != 3 && m.output_components != 1 )
424  throw Jpeg::Error( "unsupported number of channels" ) ;
425 }
426 
427 jpeg_decompress_struct * Gr::JpegReaderImp::p()
428 {
429  return &m ;
430 }
431 
432 const jpeg_decompress_struct * Gr::JpegReaderImp::p() const
433 {
434  return &m ;
435 }
436 
437 j_common_ptr Gr::JpegReaderImp::base()
438 {
439  return reinterpret_cast<j_common_ptr>(p()) ;
440 }
441 
442 int Gr::JpegReaderImp::channels() const
443 {
444  return m.output_components ;
445 }
446 
447 void Gr::JpegReaderImp::start( FILE * fp , int scale , bool monochrome_out )
448 {
449  jpeg_stdio_src( &m , fp ) ;
450  jpeg_read_header( &m , TRUE ) ;
451  configure( scale , monochrome_out ) ;
452  startImp() ;
453 }
454 
455 void Gr::JpegReaderImp::start( const unsigned char * p , size_t n , int scale , bool monochrome_out )
456 {
457  unsigned char * ncp = const_cast<unsigned char*>(p) ;
458  jpeg_mem_src( &m , ncp , n ) ;
459  jpeg_read_header( &m , TRUE ) ;
460  configure( scale , monochrome_out ) ;
461  startImp() ;
462 }
463 
464 void Gr::JpegReaderImp::start( const ImageBuffer & b , int scale , bool monochrome_out )
465 {
466  m_buffer_source.reset( JpegBufferSource::install(&m,b) ) ;
467  jpeg_read_header( &m , TRUE ) ;
468  configure( scale , monochrome_out ) ;
469  startImp() ;
470 }
471 
472 void Gr::JpegReaderImp::configure( int scale , bool monochrome_out )
473 {
474  m.out_color_space = monochrome_out ? JCS_GRAYSCALE : JCS_RGB ;
475  m.scale_num = 1 ; // numerator
476  m.scale_denom = scale ; // denominator
477  //m.dct_method = JDCT_FASTEST ;
478 }
479 
480 void Gr::JpegReaderImp::readPixels( ImageData & data_out )
481 {
482  G_ASSERT( m_dx == data_out.dx() && m_dy == data_out.dy() ) ;
483 
484  jpeg_decompress_struct * ptr = p() ;
485  const unsigned int dy = ptr->output_height ;
486  if( data_out.channels() == m.output_components ) // optimisation
487  {
488  unsigned char ** row_pp = data_out.rowPointers() ;
489  for( unsigned int y = 0U ; ptr->output_scanline < dy ; y++ , row_pp++ )
490  {
491  unsigned char * row_p = *row_pp ;
492  jpeg_read_scanlines( ptr , &row_p , 1 ) ;
493  }
494  }
495  else
496  {
497  m_line_buffer.resize( m.output_width * m.output_components ) ;
498  unsigned char * line_buffer_p = &m_line_buffer[0] ;
499  for( unsigned int y = 0U ; ptr->output_scanline < dy ; y++ )
500  {
501  jpeg_read_scanlines( ptr , &line_buffer_p , 1 ) ;
502  data_out.copyRowIn( y , &m_line_buffer[0] , m_line_buffer.size() , m.output_components , false/*sic*/ ) ;
503  }
504  m_line_buffer.clear() ;
505  }
506 }
507 
508 void Gr::JpegReaderImp::reduce( ImageData & data_out , int post_scale , bool monochrome_out )
509 {
510  G_ASSERT( data_out.channels() == 3 ) ;
511  if( post_scale > 1 || monochrome_out )
512  {
513  data_out.scale( post_scale , monochrome_out , false/*since decoded to greyscale*/ ) ;
514  }
515 }
516 
517 void Gr::JpegReaderImp::startImp()
518 {
519  jpeg_start_decompress( &m ) ;
520 }
521 
522 void Gr::JpegReaderImp::finish()
523 {
524  jpeg_finish_decompress( &m ) ;
525 }
526 
527 // ==
528 
529 Gr::JpegReader::JpegReader( int scale , bool monochrome_out ) :
530  m_scale(scale) ,
531  m_monochrome_out(monochrome_out)
532 {
533 }
534 
535 void Gr::JpegReader::setup( int scale , bool monochrome_out )
536 {
537  m_scale = scale ;
538  m_monochrome_out = monochrome_out ;
539 }
540 
541 void Gr::JpegReader::decode( ImageData & out , const G::Path & path )
542 {
543  if( m_imp.get() == nullptr ) m_imp.reset( new JpegReaderImp ) ;
544  FileStream file( path , "rb" ) ;
545  m_imp->decode( out , file.fp() , m_scale , m_monochrome_out ) ;
546 }
547 
548 void Gr::JpegReader::decode( ImageData & out , const unsigned char * p , size_t n )
549 {
550  if( m_imp.get() == nullptr ) m_imp.reset( new JpegReaderImp ) ;
551  m_imp->decode( out , p , n , m_scale , m_monochrome_out ) ;
552 }
553 
554 void Gr::JpegReader::decode( ImageData & out , const char * p , size_t n )
555 {
556  decode( out , reinterpret_cast<const unsigned char*>(p) , n ) ;
557 }
558 
560 {
561  if( m_imp.get() == nullptr ) m_imp.reset( new JpegReaderImp ) ;
562  m_imp->decode( out , b , m_scale , m_monochrome_out ) ;
563 }
564 
566 {
567 }
568 
569 // ==
570 
571 /// \class Gr::JpegWriterImp
572 /// A private pimple class for Gr::JpegWriter.
573 ///
575 {
576 public:
577  JpegWriterImp( int scale , bool monochrome_out ) ;
578  ~JpegWriterImp() ;
579  void setup( int scale , bool monochrome_out ) ;
580  void encode( const ImageData & , const G::Path & path_out ) ;
581  void encode( const ImageData & , std::vector<char> & out ) ;
582  void encode( const ImageData & , ImageBuffer & out ) ;
583 
584 private:
585  JpegWriterImp( const JpegWriterImp & ) ;
586  void operator=( const JpegWriterImp & ) ;
587  void init( const ImageData & ) ;
588  void compress( const ImageData & ) ;
589 
590 private:
591  int m_scale ;
592  bool m_monochrome_out ;
593  jpeg_error_mgr m_err ;
594  jpeg_compress_struct m ;
595  unique_ptr<JpegBufferDestination> m_buffer_destination ;
596  std::vector<char> m_line_buffer ;
597 } ;
598 
599 Gr::JpegWriterImp::JpegWriterImp( int scale , bool monochrome_out ) :
600  m_scale(scale) ,
601  m_monochrome_out(monochrome_out)
602 {
603  m.err = jpeg_std_error( &m_err ) ;
604  m_err.error_exit = error ;
605  m_err.output_message = outputMessage ;
606  jpeg_create_compress( &m ) ;
607 }
608 
609 void Gr::JpegWriterImp::init( const ImageData & in )
610 {
611  m.image_height = scaled( in.dy() , m_scale ) ;
612  m.image_width = scaled( in.dx() , m_scale ) ;
613  m.input_components = (in.channels()==1 || m_monochrome_out) ? 1 : 3 ;
614  m.in_color_space = (in.channels()==1 || m_monochrome_out) ? JCS_GRAYSCALE : JCS_RGB ;
615  jpeg_set_defaults( &m ) ;
616 
617  if( G::Test::enabled("jpeg-quality-high") ) jpeg_set_quality( &m , 100 , TRUE ) ;
618  if( G::Test::enabled("jpeg-quality-low") ) jpeg_set_quality( &m , 20 , TRUE ) ;
619 }
620 
621 Gr::JpegWriterImp::~JpegWriterImp()
622 {
623  jpeg_destroy_compress( &m ) ;
624 }
625 
626 void Gr::JpegWriterImp::setup( int scale , bool monochrome_out )
627 {
628  m_scale = scale ;
629  m_monochrome_out= monochrome_out ;
630 }
631 
632 void Gr::JpegWriterImp::encode( const ImageData & in , const G::Path & path )
633 {
634  if( path == G::Path() ) throw Gr::Jpeg::Error( "empty filename" ) ;
635  init( in ) ;
636  FileStream file( path.str() , "wb" ) ;
637  jpeg_stdio_dest( &m , file.fp() ) ;
638  compress( in ) ;
639 }
640 
641 void Gr::JpegWriterImp::encode( const ImageData & in , std::vector<char> & out )
642 {
643  init( in ) ;
644  unsigned char * p = out.empty() ? nullptr : reinterpret_cast<unsigned char*>(&out[0]) ;
645  unsigned long n = out.size() ;
646  m.dest = nullptr ; // sic
647  jpeg_mem_dest( &m , &p , &n ) ;
648  compress( in ) ;
649  if( p == reinterpret_cast<unsigned char*>(&out[0]) )
650  {
651  G_ASSERT( n <= out.size() ) ;
652  out.resize( n ) ;
653  }
654  else
655  {
656  G_DEBUG( "Gr::JpegWriterImp::write: copying from libjpeg re-allocation" ) ;
657  G_ASSERT( p != nullptr && n > 0 ) ; if( p == nullptr ) throw Jpeg::Error() ;
658  out.resize( n ) ;
659  std::memcpy( &out[0] , p , n ) ;
660  std::free( p ) ;
661  }
662 }
663 
664 void Gr::JpegWriterImp::encode( const ImageData & in , ImageBuffer & out )
665 {
666  init( in ) ;
667  m_buffer_destination.reset( JpegBufferDestination::install(&m,out) ) ;
668  compress( in ) ;
669 }
670 
671 void Gr::JpegWriterImp::compress( const ImageData & in )
672 {
673  jpeg_start_compress( &m , TRUE ) ;
674  if( m_scale == 1 && !m_monochrome_out ) // optimisation
675  {
676  while( m.next_scanline < m.image_height )
677  {
678  jpeg_byte * row_pointer = const_cast<jpeg_byte*>(in.rowPointers()[m.next_scanline]) ;
679  jpeg_write_scanlines( &m , &row_pointer , 1 ) ;
680  }
681  }
682  else
683  {
684  m_line_buffer.resize( sizet(in.dx(),m_monochrome_out?1:in.channels()) ) ;
685  while( m.next_scanline < m.image_height )
686  {
687  int y_in = m.next_scanline * m_scale ; G_ASSERT( y_in < in.dy() ) ;
688  in.copyRowOut( y_in , m_line_buffer , m_scale , m_monochrome_out ) ;
689  jpeg_byte * row_pointer = reinterpret_cast<unsigned char *>(&m_line_buffer[0]) ;
690  jpeg_write_scanlines( &m , &row_pointer , 1 ) ;
691  }
692  }
693  jpeg_finish_compress( &m ) ;
694 }
695 
696 // ==
697 
698 Gr::JpegWriter::JpegWriter( int scale , bool monochrome_out ) :
699  m_imp(new JpegWriterImp(scale,monochrome_out))
700 {
701 }
702 
704 {
705 }
706 
707 void Gr::JpegWriter::setup( int scale , bool monochrome_out )
708 {
709  m_imp->setup( scale , monochrome_out ) ;
710 }
711 
712 void Gr::JpegWriter::encode( const ImageData & in , const G::Path & path_out )
713 {
714  m_imp->encode( in , path_out ) ;
715 }
716 
717 void Gr::JpegWriter::encode( const ImageData & in , std::vector<char> & buffer_out )
718 {
719  m_imp->encode( in , buffer_out ) ;
720 }
721 
722 void Gr::JpegWriter::encode( const ImageData & in , ImageBuffer & image_buffer_out )
723 {
724  m_imp->encode( in , image_buffer_out ) ;
725 }
726 
std::string str() const
Returns the path string.
Definition: gpath.cpp:290
static std::string printable(const std::string &in, char escape= '\\')
Returns a printable represention of the given input string.
Definition: gstr.cpp:663
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
void encode(const ImageData &in, const G::Path &path_out)
Encodes to a file.
A private pimple class for Gr::JpegWriter.
unsigned char jpeg_byte
Equivalent to libjpeg JSAMPLE. A static-assert checks the size.
Definition: grjpeg.h:39
static bool at(Severity)
Returns true if G::LogOutput::output() would log at the given level.
Definition: glog.cpp: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
void setup(int scale, bool monochrome_out=false)
Sets the decoding scale factor.
~JpegReader()
Destructor.
static std::string lower(const std::string &s)
Returns a copy of 's' in which all Latin-1 uppercase characters have been replaced by lowercase chara...
Definition: gstr.cpp:561
static bool available()
Returns true if a jpeg library is available.
A private implementation class for Gr::JpegReader.
static bool enabled()
Returns true if test features are enabled.
Definition: gtest.cpp:49
~JpegWriter()
Destructor.
A libjpeg 'decompression source manager' that reads from Gr::ImageBuffer.
Definition: grjpeg_jpeg.cpp:68
void decode(ImageData &out, const G::Path &in)
Decodes a jpeg file into an image. Throws on error.
JpegReader(int scale=1, bool monochrome_out=false)
Constructor.
void setup(int scale, bool monochrome_out=false)
Sets the encoding scale factor.
A libjpeg 'decompression destination manager' that writes to Gr::ImageBuffer.
JpegWriter(int scale=1, bool monochrome_out=false)
Constructor.
A Path object represents a file system path.
Definition: gpath.h:72