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] ;
40 std::string message( j_common_ptr p )
42 char buffer[JMSG_LENGTH_MAX] ;
43 (p->err->format_message)( p , buffer ) ;
46 void outputMessage( j_common_ptr p )
49 G_DEBUG(
"Gr::Jpeg::outputMessage: " << message(p) ) ;
51 void error( j_common_ptr p )
53 std::string e = message( p ) ;
55 throw Gr::Jpeg::Error( e ) ;
61 class JpegBufferSource ;
62 class JpegBufferDestination ;
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 ) ;
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] ) ;
103 m_p(imagebuffer::row_begin(m_b))
105 G_DEBUG(
"Gr::JpegBufferSource::ctor: installing " << (
void*)(
this) ) ;
108 void Gr::JpegBufferSource::init_source( j_decompress_ptr )
112 boolean Gr::JpegBufferSource::fill_buffer( j_decompress_ptr cinfo )
114 JpegBufferSource * src =
static_cast<JpegBufferSource*
>( cinfo->src ) ;
115 G_ASSERT( src !=
nullptr && src->init_source == init_source ) ;
117 src->fill_buffer_imp() ;
121 void Gr::JpegBufferSource::fill_buffer_imp()
123 if( m_p == imagebuffer::row_end(m_b) )
125 static const JOCTET eoi[] = { 0xff , JPEG_EOI , 0 , 0 } ;
126 next_input_byte = eoi ;
127 bytes_in_buffer = 2 ;
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) ;
138 void Gr::JpegBufferSource::skip_input( j_decompress_ptr cinfo ,
long n )
140 JpegBufferSource * src =
static_cast<JpegBufferSource*
>( cinfo->src ) ;
141 G_ASSERT( src !=
nullptr && src->init_source == init_source ) ;
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) ) ;
148 void Gr::JpegBufferSource::skip_input_imp(
size_t n )
150 while( n > bytes_in_buffer )
152 n -= bytes_in_buffer ;
155 next_input_byte += n ;
156 bytes_in_buffer -= n ;
159 void Gr::JpegBufferSource::term_source( j_decompress_ptr cinfo )
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 ) ;
185 size_t m_buffer_size ;
192 cinfo->dest->init_destination = init_destination ;
193 cinfo->dest->empty_output_buffer = empty_output_buffer ;
194 cinfo->dest->term_destination = term_destination ;
198 Gr::JpegBufferDestination::JpegBufferDestination(
Gr::ImageBuffer & b ) :
200 m_p(imagebuffer::row_begin(b)) ,
201 m_buffer_size(G::limits::file_buffer)
204 m_buffer_size = 10U ;
209 void Gr::JpegBufferDestination::next()
215 void Gr::JpegBufferDestination::set()
217 if( m_p == imagebuffer::row_end(m_b) )
219 m_p = imagebuffer::row_add( m_b ) ;
222 if( imagebuffer::row_empty(m_p) )
223 imagebuffer::row_resize( m_p , m_buffer_size ) ;
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 ) ;
230 void Gr::JpegBufferDestination::close()
232 if( m_p != imagebuffer::row_end(m_b) )
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 ) ;
243 void Gr::JpegBufferDestination::init_destination( j_compress_ptr cinfo )
247 boolean Gr::JpegBufferDestination::empty_output_buffer( j_compress_ptr cinfo )
249 JpegBufferDestination * dst =
static_cast<JpegBufferDestination*
>( cinfo->dest ) ;
250 G_ASSERT( dst !=
nullptr && dst->init_destination == init_destination ) ;
255 void Gr::JpegBufferDestination::term_destination( j_compress_ptr cinfo )
257 JpegBufferDestination * dst =
static_cast<JpegBufferDestination*
>( cinfo->dest ) ;
258 G_ASSERT( dst !=
nullptr && dst->init_destination == init_destination ) ;
277 void decode(
ImageData & , FILE * ,
int scale ,
bool monochrome_out ) ;
278 void decode(
ImageData & ,
const unsigned char * ,
size_t ,
int scale ,
bool monochrome_out ) ;
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 ) ;
295 void configure(
int ,
bool ) ;
296 void readGeometry() ;
297 void reduce(
ImageData & ,
int ,
bool ) ;
298 static int pre(
int ) ;
299 static int post(
int ) ;
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 ;
317 FileStream(
const G::Path & path ,
const char * mode ) :
322 m_fp = std::fopen( path.
str().c_str() , mode ) ;
324 if( m_fp ==
nullptr )
325 throw Gr::Jpeg::Error(
"cannot open file" ) ;
333 std::fclose( m_fp ) ;
336 FileStream(
const FileStream & ) ;
337 void operator=(
const FileStream & ) ;
345 Gr::JpegReaderImp::JpegReaderImp() :
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 ) ;
355 Gr::JpegReaderImp::~JpegReaderImp()
357 jpeg_destroy_decompress( &m ) ;
360 void Gr::JpegReaderImp::decode( ImageData & out , FILE * fp ,
int scale ,
bool monochrome_out )
362 start( fp , pre(scale) , monochrome_out ) ;
364 createBuffers( out ) ;
366 reduce( out , post(scale) , monochrome_out ) ;
370 void Gr::JpegReaderImp::decode( ImageData & out ,
const unsigned char * p ,
size_t n ,
int scale ,
bool monochrome_out )
372 start( p , n , pre(scale) , monochrome_out ) ;
374 createBuffers( out ) ;
376 reduce( out , post(scale) , monochrome_out ) ;
380 void Gr::JpegReaderImp::decode( ImageData & out ,
const ImageBuffer & b ,
int scale ,
bool monochrome_out )
382 start( b , pre(scale) , monochrome_out ) ;
384 createBuffers( out ) ;
386 reduce( out , post(scale) , monochrome_out ) ;
390 int Gr::JpegReaderImp::pre(
int scale )
394 else if( scale == 2 || scale == 4 || scale == 8 )
396 else if( (scale & 7) == 0 )
402 int Gr::JpegReaderImp::post(
int scale )
406 else if( scale == 2 || scale == 4 || scale == 8 )
408 else if( (scale & 7) == 0 )
414 void Gr::JpegReaderImp::createBuffers( ImageData & data )
416 data.resize( m_dx , m_dy , 3 ) ;
419 void Gr::JpegReaderImp::readGeometry()
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" ) ;
427 jpeg_decompress_struct * Gr::JpegReaderImp::p()
432 const jpeg_decompress_struct * Gr::JpegReaderImp::p()
const
437 j_common_ptr Gr::JpegReaderImp::base()
439 return reinterpret_cast<j_common_ptr
>(p()) ;
442 int Gr::JpegReaderImp::channels()
const
444 return m.output_components ;
447 void Gr::JpegReaderImp::start( FILE * fp ,
int scale ,
bool monochrome_out )
449 jpeg_stdio_src( &m , fp ) ;
450 jpeg_read_header( &m , TRUE ) ;
451 configure( scale , monochrome_out ) ;
455 void Gr::JpegReaderImp::start(
const unsigned char * p ,
size_t n ,
int scale ,
bool monochrome_out )
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 ) ;
464 void Gr::JpegReaderImp::start(
const ImageBuffer & b ,
int scale ,
bool monochrome_out )
466 m_buffer_source.reset( JpegBufferSource::install(&m,b) ) ;
467 jpeg_read_header( &m , TRUE ) ;
468 configure( scale , monochrome_out ) ;
472 void Gr::JpegReaderImp::configure(
int scale ,
bool monochrome_out )
474 m.out_color_space = monochrome_out ? JCS_GRAYSCALE : JCS_RGB ;
476 m.scale_denom = scale ;
480 void Gr::JpegReaderImp::readPixels( ImageData & data_out )
482 G_ASSERT( m_dx == data_out.dx() && m_dy == data_out.dy() ) ;
484 jpeg_decompress_struct * ptr = p() ;
485 const unsigned int dy = ptr->output_height ;
486 if( data_out.channels() == m.output_components )
488 unsigned char ** row_pp = data_out.rowPointers() ;
489 for(
unsigned int y = 0U ; ptr->output_scanline < dy ; y++ , row_pp++ )
491 unsigned char * row_p = *row_pp ;
492 jpeg_read_scanlines( ptr , &row_p , 1 ) ;
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++ )
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 ) ;
504 m_line_buffer.clear() ;
508 void Gr::JpegReaderImp::reduce( ImageData & data_out ,
int post_scale ,
bool monochrome_out )
510 G_ASSERT( data_out.channels() == 3 ) ;
511 if( post_scale > 1 || monochrome_out )
513 data_out.scale( post_scale , monochrome_out ,
false ) ;
517 void Gr::JpegReaderImp::startImp()
519 jpeg_start_decompress( &m ) ;
522 void Gr::JpegReaderImp::finish()
524 jpeg_finish_decompress( &m ) ;
531 m_monochrome_out(monochrome_out)
538 m_monochrome_out = monochrome_out ;
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 ) ;
550 if( m_imp.get() == nullptr ) m_imp.reset(
new JpegReaderImp ) ;
551 m_imp->decode( out , p , n , m_scale , m_monochrome_out ) ;
556 decode( out , reinterpret_cast<const unsigned char*>(p) , n ) ;
561 if( m_imp.get() == nullptr ) m_imp.reset(
new JpegReaderImp ) ;
562 m_imp->decode( out , b , m_scale , m_monochrome_out ) ;
579 void setup(
int scale ,
bool monochrome_out ) ;
581 void encode(
const ImageData & , std::vector<char> & out ) ;
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 ;
599 Gr::JpegWriterImp::JpegWriterImp(
int scale ,
bool monochrome_out ) :
601 m_monochrome_out(monochrome_out)
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 ) ;
609 void Gr::JpegWriterImp::init(
const ImageData & in )
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 ) ;
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 ) ;
621 Gr::JpegWriterImp::~JpegWriterImp()
623 jpeg_destroy_compress( &m ) ;
626 void Gr::JpegWriterImp::setup(
int scale ,
bool monochrome_out )
629 m_monochrome_out= monochrome_out ;
632 void Gr::JpegWriterImp::encode(
const ImageData & in ,
const G::Path & path )
634 if( path ==
G::Path() )
throw Gr::Jpeg::Error(
"empty filename" ) ;
636 FileStream file( path.
str() ,
"wb" ) ;
637 jpeg_stdio_dest( &m , file.fp() ) ;
641 void Gr::JpegWriterImp::encode(
const ImageData & in , std::vector<char> & out )
644 unsigned char * p = out.empty() ?
nullptr :
reinterpret_cast<unsigned char*
>(&out[0]) ;
645 unsigned long n = out.size() ;
647 jpeg_mem_dest( &m , &p , &n ) ;
649 if( p == reinterpret_cast<unsigned char*>(&out[0]) )
651 G_ASSERT( n <= out.size() ) ;
656 G_DEBUG(
"Gr::JpegWriterImp::write: copying from libjpeg re-allocation" ) ;
657 G_ASSERT( p !=
nullptr && n > 0 ) ;
if( p ==
nullptr )
throw Jpeg::Error() ;
659 std::memcpy( &out[0] , p , n ) ;
664 void Gr::JpegWriterImp::encode(
const ImageData & in ,
ImageBuffer & out )
667 m_buffer_destination.reset( JpegBufferDestination::install(&m,out) ) ;
671 void Gr::JpegWriterImp::compress(
const ImageData & in )
673 jpeg_start_compress( &m , TRUE ) ;
674 if( m_scale == 1 && !m_monochrome_out )
676 while( m.next_scanline < m.image_height )
679 jpeg_write_scanlines( &m , &row_pointer , 1 ) ;
684 m_line_buffer.resize( sizet(in.dx(),m_monochrome_out?1:in.channels()) ) ;
685 while( m.next_scanline < m.image_height )
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 ) ;
693 jpeg_finish_compress( &m ) ;
709 m_imp->setup( scale , monochrome_out ) ;
714 m_imp->encode( in , path_out ) ;
719 m_imp->encode( in , buffer_out ) ;
724 m_imp->encode( in , image_buffer_out ) ;
std::string str() const
Returns the path string.
static std::string printable(const std::string &in, char escape= '\\')
Returns a printable represention of the given input string.
A traits class that can be specialised for Gr::ImageBuffer candidates.
A holder for image data, having eight bits per sample and one or three channels.
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.
static bool at(Severity)
Returns true if G::LogOutput::output() would log at the given level.
A class which acquires the process's special privileges on construction and releases them on destruct...
Vectors ImageBuffer
An ImageBuffer is used to hold raw image data, typically in more than one chunk.
void setup(int scale, bool monochrome_out=false)
Sets the decoding scale factor.
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...
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.
A libjpeg 'decompression source manager' that reads from Gr::ImageBuffer.
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.