Example: Changed glyph-outline.shape for use with FontConfig.
[shapes.git] / source / import_png.cc
blob6b38e68a91b1a46c1997102f6dc564c3015728a9
1 /* This file is part of Shapes.
3 * Shapes is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 3 of the License, or
6 * any later version.
8 * Shapes is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with Shapes. If not, see <http://www.gnu.org/licenses/>.
16 * Copyright 2009 Henrik Tidefelt
19 #include "drawabletypes.h"
20 #include "rasterloaders.h"
21 #include "statetypes.h"
22 #include "autoonoff.h"
23 #include "globals.h"
24 #include "config.h"
26 #include <fstream>
28 using namespace Shapes;
31 #ifdef HAVE_LIBPNG
33 #include <png.h>
35 namespace Shapes
37 namespace Kernel
40 void
41 Shapes_pnglib_error_fn( png_structp png_ptr,
42 png_const_charp error_msg )
44 throw Exceptions::ExternalError( strrefdup( error_msg ) );
47 void
48 Shapes_pnglib_warning_fn( png_structp png_ptr,
49 png_const_charp warning_msg )
51 std::cerr << "libpng warning: " << warning_msg << std::endl ;
54 void
55 Shapes_pnglib_read_data( png_structp png_ptr,
56 png_bytep data,
57 png_size_t length )
59 std::istream * iFile = static_cast< std::istream * >( png_get_io_ptr( png_ptr ) );
60 iFile->read( reinterpret_cast< char * >( data ), length );
65 namespace Helpers
68 void separateAlpha_bits( const unsigned char * row, size_t rowBytes, size_t bitsPerUnit, std::ostream & colorDst, size_t colorUnits, std::ostream & alphaDst, size_t alphaUnits, const png_color_16 * transparentColor, char * buf1, char * buf2 );
69 template< class C >
70 void separateAlpha( const unsigned char * row, size_t rowBytes, std::ostream & colorDst, size_t colorUnts, std::ostream & alphaDst, size_t alphaUnits, const png_color_16 * transparentColor, char * buf1, char * buf2 );
71 void applyPalette( const unsigned char * row, size_t rowBytes, size_t index_depth, const png_colorp palette, const png_bytep trans, int num_palette, std::ostream & colorDst, std::ostream & alphaDst );
76 #endif
79 RefCountPtr< const Lang::RasterImage >
80 Shapes::Helpers::RasterLoader_PNG::load( RefCountPtr< std::ifstream > & filePtr, const std::string & full_filename, Concrete::Length resolution, bool override, const char * core_title, const Ast::SourceLocation & callLoc ) const
82 #ifndef HAVE_LIBPNG
83 throw Exceptions::BuildRequirement( BUILD_REQ_LIBPNG, core_title, callLoc );
84 #else
86 std::ifstream & iFile = *filePtr;
88 const size_t SIGNATURE_SIZE = 8;
90 char header[ SIGNATURE_SIZE + 1 ];
91 iFile.read( header, SIGNATURE_SIZE );
92 bool is_png = png_sig_cmp( reinterpret_cast< png_bytep >( header ), 0, SIGNATURE_SIZE ) == 0;
93 if( ! is_png )
95 throw Exceptions::CoreRequirement( "The imported file does not have the magic signature of a PNG file.", core_title, callLoc );
99 png_structp png_ptr = png_create_read_struct( PNG_LIBPNG_VER_STRING,
100 static_cast< png_voidp >( 0 ), /* Pointer to additional information passed to error and warning handlers? */
101 Kernel::Shapes_pnglib_error_fn,
102 Kernel::Shapes_pnglib_warning_fn );
104 /* We use two error handling mechanisms in parallel, since I don't understand exactly how to use the longjmp function in the uer-defined
105 * error handler Shapes_pnglib_error_fn. Hence, we both use setjmp in case someone uses longjmp, and use try/catch to handle the errors
106 * thrown by Shapes_pnglib_error_fn.
109 png_set_error_fn( png_ptr,
110 static_cast< png_voidp >( 0 ), /* Pointer to additional information passed to error and warning handlers? */
111 Kernel::Shapes_pnglib_error_fn,
112 Kernel::Shapes_pnglib_warning_fn);
114 if( png_ptr == 0)
116 throw Exceptions::ExternalError( "Failed to allocate read structure for reading a PNG file." );
119 png_infop info_ptr = png_create_info_struct( png_ptr );
120 if( info_ptr == 0)
122 png_destroy_read_struct( & png_ptr,
123 static_cast< png_infopp >( 0 ), static_cast< png_infopp >( 0 ) );
124 throw Exceptions::ExternalError( "Failed to allocate info structure for reading a PNG file." );
127 png_infop end_info = png_create_info_struct( png_ptr );
128 if( end_info == 0 )
130 png_destroy_read_struct( & png_ptr, & info_ptr,
131 static_cast< png_infopp >( 0 ) );
132 throw Exceptions::ExternalError( "Failed to allocate end-info structure for reading a PNG file." );
135 if( setjmp( png_jmpbuf( png_ptr ) ) )
137 png_destroy_read_struct( & png_ptr, & info_ptr, & end_info );
138 throw Exceptions::ExternalError( "Caught a longjmp from libpng while reading a PNG file." );
143 png_set_sig_bytes( png_ptr, SIGNATURE_SIZE );
145 png_set_read_fn( png_ptr,
146 static_cast< voidp >( filePtr.getPtr( ) ),
147 Kernel::Shapes_pnglib_read_data );
150 png_read_png( png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, 0 );
152 png_uint_32 size_x;
153 png_uint_32 size_y;
154 int size_depth;
155 int color_type;
156 int interlace_method;
157 int compression_method;
158 int filter_method;
160 png_get_IHDR( png_ptr, info_ptr,
161 & size_x, & size_y, & size_depth,
162 & color_type, & interlace_method, & compression_method, & filter_method );
164 RefCountPtr< const Lang::ColorSpace > colorSpace = Lang::THE_COLOR_SPACE_DEVICE_GRAY; /* Initialize with anything. */
165 bool hasAlpha = false;
166 bool hasTransparentColor = false;
167 bool hasPalette = false;
168 png_colorp palette;
169 int num_palette;
170 switch( color_type )
172 case PNG_COLOR_TYPE_GRAY:
173 /* Use the default value set above! */
174 break;
175 case PNG_COLOR_TYPE_RGB:
176 colorSpace = Lang::THE_COLOR_SPACE_DEVICE_RGB;
177 break;
178 case PNG_COLOR_TYPE_GRAY_ALPHA:
179 colorSpace = Lang::THE_COLOR_SPACE_DEVICE_GRAY;
180 hasAlpha = true;
181 break;
182 case PNG_COLOR_TYPE_RGB_ALPHA:
183 colorSpace = Lang::THE_COLOR_SPACE_DEVICE_RGB;
184 hasAlpha = true;
185 break;
186 case PNG_COLOR_TYPE_PALETTE:
187 /* The palette colors use 8 bit in each of the three channels. */
188 colorSpace = Lang::THE_COLOR_SPACE_DEVICE_RGB;
189 png_get_PLTE( png_ptr, info_ptr, & palette, & num_palette );
190 hasPalette = true;
191 break;
192 default:
193 throw Exceptions::CoreRequirement( "The PNG file was using an unknown color type value.", core_title, callLoc );
196 png_bytep tRNS_trans;
197 png_color_16p tRNS_trans_values;
198 if( ( info_ptr->valid & PNG_INFO_tRNS ) != 0 )
200 hasAlpha = true;
201 int tRNS_num_trans;
202 png_get_tRNS( png_ptr, info_ptr, & tRNS_trans, & tRNS_num_trans, & tRNS_trans_values );
203 if( hasPalette )
205 if( tRNS_num_trans != num_palette )
207 throw Exceptions::CoreRequirement( "The PNG file has a transparency pallete of different size compared to the color palette.", core_title, callLoc );
210 else
212 hasTransparentColor = true;
216 size_t components = colorSpace->numberOfComponents( );
217 size_t png_channels = png_get_channels( png_ptr, info_ptr );
219 Concrete::Length resolution_x = resolution;
220 Concrete::Length resolution_y = resolution;
221 if( ! override && ( info_ptr->valid & PNG_INFO_pHYs ) != 0 )
223 switch( info_ptr->phys_unit_type )
225 case PNG_RESOLUTION_UNKNOWN:/* Only aspect ratio. */
227 Concrete::Length tmpRes = sqrt( static_cast< double >( info_ptr->x_pixels_per_unit ) * static_cast< double >( info_ptr->y_pixels_per_unit ) ) * resolution;
228 resolution_x = ( tmpRes / info_ptr->x_pixels_per_unit );
229 resolution_y = ( tmpRes / info_ptr->y_pixels_per_unit );
231 break;
232 case PNG_RESOLUTION_METER:/* Dots per meter. */
234 resolution_x = ( Concrete::Length( 100/(2.54/72) ) / info_ptr->x_pixels_per_unit );
235 resolution_y = ( Concrete::Length( 100/(2.54/72) ) / info_ptr->y_pixels_per_unit );
237 break;
238 default:
239 throw Exceptions::CoreRequirement( "The PNG file was using an unknown density unit value.", core_title, callLoc );
243 Lang::RasterImage * imagePtr = Lang::RasterImage::newInstance( size_x, size_y, hasPalette ? 8 : size_depth, resolution_x, resolution_y, colorSpace );
244 RefCountPtr< const Lang::RasterImage > image = RefCountPtr< const Lang::RasterImage >( RefCountPtr< const Lang::RasterImage >( imagePtr ) );
246 RefCountPtr< SimplePDF::PDF_Stream_out > maskStream = RefCountPtr< SimplePDF::PDF_Stream_out >( NullPtr< SimplePDF::PDF_Stream_out >( ) );
248 SimplePDF::PDF_Stream_out & stream = *(imagePtr->stream( ));
249 stream[ "Filter" ] = SimplePDF::newName( "FlateDecode" );
250 if( hasAlpha )
252 Lang::RasterImage * maskPtr = Lang::RasterImage::newInstance( size_x, size_y, hasPalette ? 8 : size_depth, resolution_x, resolution_y, Lang::THE_COLOR_SPACE_DEVICE_GRAY );
253 imagePtr->setSoftMask( RefCountPtr< const Lang::RasterImage >( RefCountPtr< const Lang::RasterImage >( maskPtr ) ) );
254 maskStream = maskPtr->stream( );
255 (*maskStream)[ "Filter" ] = SimplePDF::newName( "FlateDecode" );
257 const SimplePDF::PDF_Version::Version SOFTMASK_VERSION = SimplePDF::PDF_Version::PDF_1_4;
258 if( Kernel::the_PDF_version.greaterOrEqual( SOFTMASK_VERSION ) )
260 stream[ "SMask" ] = maskPtr->getResource( );
262 else
264 Kernel::the_PDF_version.message( SOFTMASK_VERSION, "A PNG alpha channel (soft mask) was ignored." );
268 size_t rowbytes = png_get_rowbytes( png_ptr, info_ptr );
269 char * buf1 = 0;
270 char * buf2 = 0;
271 PossiblyDeleteOnExit< char > buf1Deleter;
272 PossiblyDeleteOnExit< char > buf2Deleter;
273 if( hasAlpha )
275 buf1 = new char[ rowbytes ];
276 buf1Deleter.activate( buf1 );
277 buf2 = new char[ rowbytes ];
278 buf2Deleter.activate( buf2 );
280 png_bytep * rowBegin = png_get_rows( png_ptr, info_ptr );
281 for( png_bytep * srcRow = rowBegin; srcRow < rowBegin + size_y; ++srcRow )
283 /* Note that image data in PDF starts each row on a fresh byte boundary.
285 if( hasPalette )
287 if( hasAlpha )
289 Helpers::applyPalette( reinterpret_cast< unsigned char * >( *srcRow ), rowbytes, size_depth, palette, tRNS_trans, num_palette, stream.data, maskStream->data );
291 else
293 std::ostringstream ignore;
294 Helpers::applyPalette( reinterpret_cast< unsigned char * >( *srcRow ), rowbytes, size_depth, palette, 0, num_palette, stream.data, ignore );
297 else if( hasAlpha )
299 if( hasTransparentColor )
301 /* There may be some kind of filler (byte or bits) in the data, that we need to discard. */
302 switch( size_depth )
304 case 1:
305 case 2:
306 case 4:
307 Helpers::separateAlpha_bits( reinterpret_cast< unsigned char * >( *srcRow ), rowbytes, size_depth, stream.data, components, maskStream->data, png_channels - components, tRNS_trans_values, buf1, buf2 );
308 break;
309 case 8:
310 Helpers::separateAlpha< png_byte >( reinterpret_cast< unsigned char * >( *srcRow ), rowbytes, stream.data, components, maskStream->data, png_channels - components, tRNS_trans_values, buf1, buf2 );
311 break;
312 case 16:
313 Helpers::separateAlpha< png_uint_16 >( reinterpret_cast< unsigned char * >( *srcRow ), rowbytes, stream.data, components, maskStream->data, png_channels - components, tRNS_trans_values, buf1, buf2 );
314 break;
315 default:
316 throw Exceptions::InternalError( "Unexpected color depth in PNG image." );
319 else
321 switch( size_depth )
323 case 1:
324 case 2:
325 case 4:
326 Helpers::separateAlpha_bits( reinterpret_cast< unsigned char * >( *srcRow ), rowbytes, size_depth, stream.data, components, maskStream->data, 1, 0, buf1, buf2 );
327 break;
328 case 8:
329 Helpers::separateAlpha< png_byte >( reinterpret_cast< unsigned char * >( *srcRow ), rowbytes, stream.data, components, maskStream->data, 1, 0, buf1, buf2 );
330 break;
331 case 16:
332 Helpers::separateAlpha< png_uint_16 >( reinterpret_cast< unsigned char * >( *srcRow ), rowbytes, stream.data, components, maskStream->data, 1, 0, buf1, buf2 );
333 break;
334 default:
335 throw Exceptions::InternalError( "Unexpected color depth in PNG image." );
339 else if( png_channels > components )
341 /* There is some kind of filler (byte or bits) in the data, that we need to discard. */
342 std::ostringstream ignore;
343 switch( size_depth )
345 case 1:
346 case 2:
347 case 4:
348 Helpers::separateAlpha_bits( reinterpret_cast< unsigned char * >( *srcRow ), rowbytes, size_depth, stream.data, components, ignore, png_channels - components, 0, buf1, buf2 );
349 break;
350 case 8:
351 Helpers::separateAlpha< png_byte >( reinterpret_cast< unsigned char * >( *srcRow ), rowbytes, stream.data, components, ignore, png_channels - components, 0, buf1, buf2 );
352 break;
353 case 16:
354 Helpers::separateAlpha< png_uint_16 >( reinterpret_cast< unsigned char * >( *srcRow ), rowbytes, stream.data, components, ignore, png_channels - components, 0, buf1, buf2 );
355 break;
356 default:
357 throw Exceptions::InternalError( "Unexpected color depth in PNG image." );
360 else
362 stream.data.write( reinterpret_cast< char * >( *srcRow ), rowbytes );
366 /* If everything was OK, we just free allocated resources and return.
368 png_destroy_read_struct( & png_ptr, & info_ptr, & end_info );
370 return image;
372 catch( const Exceptions::Exception & ball )
374 png_destroy_read_struct( & png_ptr, & info_ptr, & end_info );
375 throw;
377 #endif
381 #ifdef HAVE_LIBPNG
383 void
384 Helpers::separateAlpha_bits( const unsigned char * row, size_t rowBytes, size_t bitsPerUnit, std::ostream & colorDst, size_t colorUnits, std::ostream & alphaDst, size_t alphaUnits, const png_color_16 * transparentColor, char * bufCo, char * bufAl )
386 char * dstCo = bufCo;
387 char * dstAl = bufAl;
389 png_uint_16 transparentColorArray[3];
390 if( transparentColor != 0 )
392 switch( colorUnits )
394 case 1:
395 transparentColorArray[0] = transparentColor->gray;
396 break;
397 case 3:
398 transparentColorArray[0] = transparentColor->red;
399 transparentColorArray[1] = transparentColor->green;
400 transparentColorArray[2] = transparentColor->blue;
401 break;
402 default:
403 throw Exceptions::InternalError( "Unexpected number of color channels in PNG image alpha bit separation." );
406 const unsigned char * src = row;
407 const unsigned char * srcEnd = row + rowBytes;
408 unsigned char srcByte = *src;
409 ++src;
410 unsigned char dstByteCo = 0; /* Initialize to inhibit compiler warnings. */
411 unsigned char dstByteAl = 0; /* Initialize to inhibit compiler warnings. */
412 unsigned char srcShift = 8 - bitsPerUnit;
413 unsigned char dstAvailCo = 8;
414 unsigned char dstAvailAl = 8;
415 unsigned char mask;
416 switch( bitsPerUnit )
418 case 1:
419 mask = 0x01;
420 break;
421 case 2:
422 mask = 0x03;
423 break;
424 case 4:
425 mask = 0x07;
426 break;
427 default:
428 throw Exceptions::InternalError( "Unexpected color depth in PNG image alpha bit separation." );
430 while( true )
432 bool trans = true;
433 const png_uint_16 * transparentColorSrc = transparentColorArray;
434 for( size_t i = 0; i < colorUnits; ++i, ++transparentColorSrc )
436 dstByteCo = ( dstByteCo << bitsPerUnit ) | ( ( srcByte >> srcShift ) & mask );
437 if( transparentColor != 0 && ( ( srcByte >> srcShift ) & mask ) != *transparentColorSrc )
439 trans = false;
441 dstAvailCo -= bitsPerUnit;
442 if( dstAvailCo == 0 )
444 *reinterpret_cast< unsigned char * >( dstCo ) = dstByteCo;
445 ++dstCo;
446 dstAvailCo = 8;
448 if( srcShift == 0 )
450 if( src == srcEnd )
452 goto srcComplete;
454 srcByte = *src;
455 ++src;
456 srcShift = 8;
458 srcShift -= bitsPerUnit;
460 if( transparentColor == 0 )
462 for( size_t i = 0; i < alphaUnits; ++i )
464 dstByteAl = ( dstByteAl << bitsPerUnit ) | ( ( srcByte >> srcShift ) & mask );
465 dstAvailAl -= bitsPerUnit;
466 if( dstAvailAl == 0 )
468 *reinterpret_cast< unsigned char * >( dstAl ) = dstByteAl;
469 ++dstAl;
470 dstAvailAl = 8;
472 if( srcShift == 0 )
474 if( src == srcEnd )
476 goto srcComplete;
478 srcByte = *src;
479 ++src;
480 srcShift = 8;
482 srcShift -= bitsPerUnit;
485 else
487 dstByteAl = ( dstByteAl << bitsPerUnit ) | ( trans ? mask : 0 );
488 dstAvailAl -= bitsPerUnit;
489 if( dstAvailAl == 0 )
491 *reinterpret_cast< unsigned char * >( dstAl ) = dstByteAl;
492 ++dstAl;
493 dstAvailAl = 8;
496 /* Just ignore filler data. */
497 for( size_t i = 0; i < alphaUnits; ++i )
499 if( srcShift == 0 )
501 if( src == srcEnd )
503 goto srcComplete;
505 srcByte = *src;
506 ++src;
507 srcShift = 8;
509 srcShift -= bitsPerUnit;
513 srcComplete:
515 if( dstAvailCo < 8 )
517 *reinterpret_cast< unsigned char * >( dstCo ) = ( dstByteCo << dstAvailCo );
518 ++dstCo;
520 if( dstAvailAl < 8 )
522 *reinterpret_cast< unsigned char * >( dstAl ) = ( dstByteAl << dstAvailAl );
523 ++dstAl;
526 colorDst.write( bufCo, dstCo - bufCo );
527 alphaDst.write( bufAl, dstAl - bufAl );
530 template< class C >
531 void
532 Helpers::separateAlpha( const unsigned char * row, size_t rowBytes, std::ostream & colorDst, size_t colorUnits, std::ostream & alphaDst, size_t alphaUnits, const png_color_16 * transparentColor, char * bufCo, char * bufAl )
534 C * dstCo = reinterpret_cast< C * >( bufCo );
535 C * dstAl = reinterpret_cast< C * >( bufAl );
537 C OPAQUE = 0;
539 png_uint_16 transparentColorArray[3];
540 if( transparentColor != 0 )
542 switch( colorUnits )
544 case 1:
545 transparentColorArray[0] = transparentColor->gray;
546 break;
547 case 3:
548 transparentColorArray[0] = transparentColor->red;
549 transparentColorArray[1] = transparentColor->green;
550 transparentColorArray[2] = transparentColor->blue;
551 break;
552 default:
553 throw Exceptions::InternalError( "Unexpected number of color channels in PNG image alpha bit separation." );
557 const C * src = reinterpret_cast< const C * >( row );
558 const C * srcEnd = reinterpret_cast< const C * >( row + rowBytes );
559 const C * tmpEnd = reinterpret_cast< const C * >( row );
560 for( ; src < srcEnd; )
562 bool trans = true;
563 const png_uint_16 * transparentColorSrc = transparentColorArray;
565 tmpEnd += colorUnits;
566 for( ; src != tmpEnd; ++src, ++dstCo, ++transparentColorSrc )
568 *reinterpret_cast< unsigned char * >( dstCo ) = *src;
569 if( transparentColor != 0 && *src != *transparentColorSrc )
571 trans = false;
574 tmpEnd += alphaUnits;
575 if( transparentColor == 0 )
577 for( ; src != tmpEnd; ++src, ++dstAl )
579 *reinterpret_cast< unsigned char * >( dstAl ) = *src;
582 else
584 *dstAl = trans ? (~OPAQUE) : OPAQUE;
585 ++dstAl;
587 /* Just ignore fill bytes. */
588 src = tmpEnd;
591 colorDst.write( bufCo, reinterpret_cast< char * >( dstCo ) - bufCo );
592 alphaDst.write( bufAl, reinterpret_cast< char * >( dstAl ) - bufAl );
595 void
596 Helpers::applyPalette( const unsigned char * row, size_t rowBytes, size_t index_depth, const png_colorp palette, const png_bytep trans, int num_palette, std::ostream & colorDst, std::ostream & alphaDst )
598 switch( index_depth )
600 case 1:
601 case 2:
602 case 4:
604 const unsigned char * src = row;
605 const unsigned char * srcEnd = row + rowBytes;
606 unsigned char srcByte = *src;
607 ++src;
608 unsigned char srcShift = 8 - index_depth;
609 unsigned char mask;
610 switch( index_depth )
612 case 1:
613 mask = 0x01;
614 break;
615 case 2:
616 mask = 0x03;
617 break;
618 case 4:
619 mask = 0x07;
620 break;
621 default:
622 throw Exceptions::InternalError( "Switch in PNG palette application was out of range." );
624 while( true )
626 unsigned char index = ( srcByte >> srcShift ) & mask;
627 if( index >= num_palette )
629 throw Exceptions::ExternalError( "PNG palette index is out of bounds." );
631 unsigned char buf[3];
632 buf[0] = palette[ index ].red;
633 buf[1] = palette[ index ].green;
634 buf[2] = palette[ index ].blue;
635 colorDst.write( reinterpret_cast< char * >( buf ), 3 );
636 if( trans != 0 )
638 alphaDst.write( reinterpret_cast< char * >( & ( trans[ index ] ) ), 1 );
640 if( srcShift == 0 )
642 if( src == srcEnd )
644 break;
646 srcByte = *src;
647 ++src;
648 srcShift = 8;
650 srcShift -= index_depth;
653 break;
654 case 8:
656 const unsigned char * srcEnd = row + rowBytes;
657 for( const unsigned char * src = row; src != srcEnd; ++src )
659 if( *src >= num_palette )
661 throw Exceptions::ExternalError( "PNG palette index is out of bounds." );
663 unsigned char buf[3];
664 buf[0] = palette[ *src ].red;
665 buf[1] = palette[ *src ].green;
666 buf[2] = palette[ *src ].blue;
667 colorDst.write( reinterpret_cast< char * >( buf ), 3 );
668 if( trans != 0 )
670 alphaDst.write( reinterpret_cast< char * >( & ( trans[ *src ] ) ), 1 );
674 break;
675 default:
676 throw Exceptions::InternalError( "Unexpected depth in PNG palette image (supported values are { 1, 2, 4, 8 })." );
680 #endif