Soft mask and palette support for PNG raster image import.
[shapes.git] / source / import_png.cc
blob956e6b60706a3794dcd2fa249a5f0dfd4f7ce7b7
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( "libpng", core_title, callLoc );
84 #else
85 if( resolution == 0 )
87 throw Exceptions::CoreRequirement( "The resolution must be given for raster image files of type PNG.", core_title, callLoc );
90 std::ifstream & iFile = *filePtr;
92 const size_t SIGNATURE_SIZE = 8;
94 char header[ SIGNATURE_SIZE + 1 ];
95 iFile.read( header, SIGNATURE_SIZE );
96 bool is_png = png_sig_cmp( reinterpret_cast< png_bytep >( header ), 0, SIGNATURE_SIZE ) == 0;
97 if( ! is_png )
99 throw Exceptions::CoreRequirement( "The imported file does not have the magic signature of a PNG file.", core_title, callLoc );
103 png_structp png_ptr = png_create_read_struct( PNG_LIBPNG_VER_STRING,
104 static_cast< png_voidp >( 0 ), /* Pointer to additional information passed to error and warning handlers? */
105 Kernel::Shapes_pnglib_error_fn,
106 Kernel::Shapes_pnglib_warning_fn );
108 /* We use two error handling mechanisms in parallel, since I don't understand exactly how to use the longjmp function in the uer-defined
109 * error handler Shapes_pnglib_error_fn. Hence, we both use setjmp in case someone uses longjmp, and use try/catch to handle the errors
110 * thrown by Shapes_pnglib_error_fn.
113 png_set_error_fn( png_ptr,
114 static_cast< png_voidp >( 0 ), /* Pointer to additional information passed to error and warning handlers? */
115 Kernel::Shapes_pnglib_error_fn,
116 Kernel::Shapes_pnglib_warning_fn);
118 if( png_ptr == 0)
120 throw Exceptions::ExternalError( "Failed to allocate read structure for reading a PNG file." );
123 png_infop info_ptr = png_create_info_struct( png_ptr );
124 if( info_ptr == 0)
126 png_destroy_read_struct( & png_ptr,
127 static_cast< png_infopp >( 0 ), static_cast< png_infopp >( 0 ) );
128 throw Exceptions::ExternalError( "Failed to allocate info structure for reading a PNG file." );
131 png_infop end_info = png_create_info_struct( png_ptr );
132 if( end_info == 0 )
134 png_destroy_read_struct( & png_ptr, & info_ptr,
135 static_cast< png_infopp >( 0 ) );
136 throw Exceptions::ExternalError( "Failed to allocate end-info structure for reading a PNG file." );
139 if( setjmp( png_jmpbuf( png_ptr ) ) )
141 png_destroy_read_struct( & png_ptr, & info_ptr, & end_info );
142 throw Exceptions::ExternalError( "Caught a longjmp from libpng while reading a PNG file." );
147 png_set_sig_bytes( png_ptr, SIGNATURE_SIZE );
149 png_set_read_fn( png_ptr,
150 static_cast< voidp >( filePtr.getPtr( ) ),
151 Kernel::Shapes_pnglib_read_data );
154 png_read_png( png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, 0 );
156 png_uint_32 size_x;
157 png_uint_32 size_y;
158 int size_depth;
159 int color_type;
160 int interlace_method;
161 int compression_method;
162 int filter_method;
164 png_get_IHDR( png_ptr, info_ptr,
165 & size_x, & size_y, & size_depth,
166 & color_type, & interlace_method, & compression_method, & filter_method );
168 RefCountPtr< const Lang::ColorSpace > colorSpace = Lang::THE_COLOR_SPACE_DEVICE_GRAY; /* Initialize with anything. */
169 bool hasAlpha = false;
170 bool hasTransparentColor = false;
171 bool hasPalette = false;
172 png_colorp palette;
173 int num_palette;
174 switch( color_type )
176 case PNG_COLOR_TYPE_GRAY:
177 /* Use the default value set above! */
178 break;
179 case PNG_COLOR_TYPE_RGB:
180 colorSpace = Lang::THE_COLOR_SPACE_DEVICE_RGB;
181 break;
182 case PNG_COLOR_TYPE_GRAY_ALPHA:
183 colorSpace = Lang::THE_COLOR_SPACE_DEVICE_GRAY;
184 hasAlpha = true;
185 break;
186 case PNG_COLOR_TYPE_RGB_ALPHA:
187 colorSpace = Lang::THE_COLOR_SPACE_DEVICE_RGB;
188 hasAlpha = true;
189 break;
190 case PNG_COLOR_TYPE_PALETTE:
191 /* The palette colors use 8 bit in each of the three channels. */
192 colorSpace = Lang::THE_COLOR_SPACE_DEVICE_RGB;
193 png_get_PLTE( png_ptr, info_ptr, & palette, & num_palette );
194 hasPalette = true;
195 break;
196 default:
197 throw Exceptions::CoreRequirement( "The PNG file was using an unknown color type value.", core_title, callLoc );
200 png_bytep tRNS_trans;
201 png_color_16p tRNS_trans_values;
202 if( ( info_ptr->valid & PNG_INFO_tRNS ) != 0 )
204 hasAlpha = true;
205 int tRNS_num_trans;
206 png_get_tRNS( png_ptr, info_ptr, & tRNS_trans, & tRNS_num_trans, & tRNS_trans_values );
207 if( hasPalette )
209 if( tRNS_num_trans != num_palette )
211 throw Exceptions::CoreRequirement( "The PNG file has a transparency pallete of different size compared to the color palette.", core_title, callLoc );
214 else
216 hasTransparentColor = true;
220 size_t components = colorSpace->numberOfComponents( );
221 size_t png_channels = png_get_channels( png_ptr, info_ptr );
223 Lang::RasterImage * imagePtr = Lang::RasterImage::newInstance( size_x, size_y, hasPalette ? 8 : size_depth, resolution, resolution, colorSpace );
224 RefCountPtr< const Lang::RasterImage > image = RefCountPtr< const Lang::RasterImage >( RefCountPtr< const Lang::RasterImage >( imagePtr ) );
226 RefCountPtr< SimplePDF::PDF_Stream_out > maskStream = RefCountPtr< SimplePDF::PDF_Stream_out >( NullPtr< SimplePDF::PDF_Stream_out >( ) );
228 SimplePDF::PDF_Stream_out & stream = *(imagePtr->stream( ));
229 stream[ "Filter" ] = SimplePDF::newName( "FlateDecode" );
230 if( hasAlpha )
232 Lang::RasterImage * maskPtr = Lang::RasterImage::newInstance( size_x, size_y, hasPalette ? 8 : size_depth, resolution, resolution, Lang::THE_COLOR_SPACE_DEVICE_GRAY );
233 imagePtr->setSoftMask( RefCountPtr< const Lang::RasterImage >( RefCountPtr< const Lang::RasterImage >( maskPtr ) ) );
234 maskStream = maskPtr->stream( );
235 (*maskStream)[ "Filter" ] = SimplePDF::newName( "FlateDecode" );
237 const SimplePDF::PDF_Version::Version SOFTMASK_VERSION = SimplePDF::PDF_Version::PDF_1_4;
238 if( Kernel::the_PDF_version.greaterOrEqual( SOFTMASK_VERSION ) )
240 stream[ "SMask" ] = maskPtr->getResource( );
242 else
244 Kernel::the_PDF_version.message( SOFTMASK_VERSION, "A PNG alpha channel (soft mask) was ignored." );
248 size_t rowbytes = png_get_rowbytes( png_ptr, info_ptr );
249 char * buf1 = 0;
250 char * buf2 = 0;
251 PossiblyDeleteOnExit< char > buf1Deleter;
252 PossiblyDeleteOnExit< char > buf2Deleter;
253 if( hasAlpha )
255 buf1 = new char[ rowbytes ];
256 buf1Deleter.activate( buf1 );
257 buf2 = new char[ rowbytes ];
258 buf2Deleter.activate( buf2 );
260 png_bytep * rowBegin = png_get_rows( png_ptr, info_ptr );
261 for( png_bytep * srcRow = rowBegin; srcRow < rowBegin + size_y; ++srcRow )
263 /* Note that image data in PDF starts each row on a fresh byte boundary.
265 if( hasPalette )
267 if( hasAlpha )
269 Helpers::applyPalette( reinterpret_cast< unsigned char * >( *srcRow ), rowbytes, size_depth, palette, tRNS_trans, num_palette, stream.data, maskStream->data );
271 else
273 std::ostringstream ignore;
274 Helpers::applyPalette( reinterpret_cast< unsigned char * >( *srcRow ), rowbytes, size_depth, palette, 0, num_palette, stream.data, ignore );
277 else if( hasAlpha )
279 if( hasTransparentColor )
281 /* There may be some kind of filler (byte or bits) in the data, that we need to discard. */
282 switch( size_depth )
284 case 1:
285 case 2:
286 case 4:
287 Helpers::separateAlpha_bits( reinterpret_cast< unsigned char * >( *srcRow ), rowbytes, size_depth, stream.data, components, maskStream->data, png_channels - components, tRNS_trans_values, buf1, buf2 );
288 break;
289 case 8:
290 Helpers::separateAlpha< png_byte >( reinterpret_cast< unsigned char * >( *srcRow ), rowbytes, stream.data, components, maskStream->data, png_channels - components, tRNS_trans_values, buf1, buf2 );
291 break;
292 case 16:
293 Helpers::separateAlpha< png_uint_16 >( reinterpret_cast< unsigned char * >( *srcRow ), rowbytes, stream.data, components, maskStream->data, png_channels - components, tRNS_trans_values, buf1, buf2 );
294 break;
295 default:
296 throw Exceptions::InternalError( "Unexpected color depth in PNG image." );
299 else
301 switch( size_depth )
303 case 1:
304 case 2:
305 case 4:
306 Helpers::separateAlpha_bits( reinterpret_cast< unsigned char * >( *srcRow ), rowbytes, size_depth, stream.data, components, maskStream->data, 1, 0, buf1, buf2 );
307 break;
308 case 8:
309 Helpers::separateAlpha< png_byte >( reinterpret_cast< unsigned char * >( *srcRow ), rowbytes, stream.data, components, maskStream->data, 1, 0, buf1, buf2 );
310 break;
311 case 16:
312 Helpers::separateAlpha< png_uint_16 >( reinterpret_cast< unsigned char * >( *srcRow ), rowbytes, stream.data, components, maskStream->data, 1, 0, buf1, buf2 );
313 break;
314 default:
315 throw Exceptions::InternalError( "Unexpected color depth in PNG image." );
319 else if( png_channels > components )
321 /* There is some kind of filler (byte or bits) in the data, that we need to discard. */
322 std::ostringstream ignore;
323 switch( size_depth )
325 case 1:
326 case 2:
327 case 4:
328 Helpers::separateAlpha_bits( reinterpret_cast< unsigned char * >( *srcRow ), rowbytes, size_depth, stream.data, components, ignore, png_channels - components, 0, buf1, buf2 );
329 break;
330 case 8:
331 Helpers::separateAlpha< png_byte >( reinterpret_cast< unsigned char * >( *srcRow ), rowbytes, stream.data, components, ignore, png_channels - components, 0, buf1, buf2 );
332 break;
333 case 16:
334 Helpers::separateAlpha< png_uint_16 >( reinterpret_cast< unsigned char * >( *srcRow ), rowbytes, stream.data, components, ignore, png_channels - components, 0, buf1, buf2 );
335 break;
336 default:
337 throw Exceptions::InternalError( "Unexpected color depth in PNG image." );
340 else
342 stream.data.write( reinterpret_cast< char * >( *srcRow ), rowbytes );
346 /* If everything was OK, we just free allocated resources and return.
348 png_destroy_read_struct( & png_ptr, & info_ptr, & end_info );
350 return image;
352 catch( const Exceptions::Exception & ball )
354 png_destroy_read_struct( & png_ptr, & info_ptr, & end_info );
355 throw;
357 #endif
361 #ifdef HAVE_LIBPNG
363 void
364 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 )
366 char * dstCo = bufCo;
367 char * dstAl = bufAl;
369 png_uint_16 transparentColorArray[3];
370 if( transparentColor != 0 )
372 switch( colorUnits )
374 case 1:
375 transparentColorArray[0] = transparentColor->gray;
376 break;
377 case 3:
378 transparentColorArray[0] = transparentColor->red;
379 transparentColorArray[1] = transparentColor->green;
380 transparentColorArray[2] = transparentColor->blue;
381 break;
382 default:
383 throw Exceptions::InternalError( "Unexpected number of color channels in PNG image alpha bit separation." );
386 const unsigned char * src = row;
387 const unsigned char * srcEnd = row + rowBytes;
388 unsigned char srcByte = *src;
389 ++src;
390 unsigned char dstByteCo = 0; /* Initialize to inhibit compiler warnings. */
391 unsigned char dstByteAl = 0; /* Initialize to inhibit compiler warnings. */
392 unsigned char srcShift = 8 - bitsPerUnit;
393 unsigned char dstAvailCo = 8;
394 unsigned char dstAvailAl = 8;
395 unsigned char mask;
396 switch( bitsPerUnit )
398 case 1:
399 mask = 0x01;
400 break;
401 case 2:
402 mask = 0x03;
403 break;
404 case 4:
405 mask = 0x07;
406 break;
407 default:
408 throw Exceptions::InternalError( "Unexpected color depth in PNG image alpha bit separation." );
410 while( true )
412 bool trans = true;
413 const png_uint_16 * transparentColorSrc = transparentColorArray;
414 for( size_t i = 0; i < colorUnits; ++i, ++transparentColorSrc )
416 dstByteCo = ( dstByteCo << bitsPerUnit ) | ( ( srcByte >> srcShift ) & mask );
417 if( transparentColor != 0 && ( ( srcByte >> srcShift ) & mask ) != *transparentColorSrc )
419 trans = false;
421 dstAvailCo -= bitsPerUnit;
422 if( dstAvailCo == 0 )
424 *reinterpret_cast< unsigned char * >( dstCo ) = dstByteCo;
425 ++dstCo;
426 dstAvailCo = 8;
428 if( srcShift == 0 )
430 if( src == srcEnd )
432 goto srcComplete;
434 srcByte = *src;
435 ++src;
436 srcShift = 8;
438 srcShift -= bitsPerUnit;
440 if( transparentColor == 0 )
442 for( size_t i = 0; i < alphaUnits; ++i )
444 dstByteAl = ( dstByteAl << bitsPerUnit ) | ( ( srcByte >> srcShift ) & mask );
445 dstAvailAl -= bitsPerUnit;
446 if( dstAvailAl == 0 )
448 *reinterpret_cast< unsigned char * >( dstAl ) = dstByteAl;
449 ++dstAl;
450 dstAvailAl = 8;
452 if( srcShift == 0 )
454 if( src == srcEnd )
456 goto srcComplete;
458 srcByte = *src;
459 ++src;
460 srcShift = 8;
462 srcShift -= bitsPerUnit;
465 else
467 dstByteAl = ( dstByteAl << bitsPerUnit ) | ( trans ? mask : 0 );
468 dstAvailAl -= bitsPerUnit;
469 if( dstAvailAl == 0 )
471 *reinterpret_cast< unsigned char * >( dstAl ) = dstByteAl;
472 ++dstAl;
473 dstAvailAl = 8;
476 /* Just ignore filler data. */
477 for( size_t i = 0; i < alphaUnits; ++i )
479 if( srcShift == 0 )
481 if( src == srcEnd )
483 goto srcComplete;
485 srcByte = *src;
486 ++src;
487 srcShift = 8;
489 srcShift -= bitsPerUnit;
493 srcComplete:
495 if( dstAvailCo < 8 )
497 *reinterpret_cast< unsigned char * >( dstCo ) = ( dstByteCo << dstAvailCo );
498 ++dstCo;
500 if( dstAvailAl < 8 )
502 *reinterpret_cast< unsigned char * >( dstAl ) = ( dstByteAl << dstAvailAl );
503 ++dstAl;
506 colorDst.write( bufCo, dstCo - bufCo );
507 alphaDst.write( bufAl, dstAl - bufAl );
510 template< class C >
511 void
512 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 )
514 C * dstCo = reinterpret_cast< C * >( bufCo );
515 C * dstAl = reinterpret_cast< C * >( bufAl );
517 C OPAQUE = 0;
519 png_uint_16 transparentColorArray[3];
520 if( transparentColor != 0 )
522 switch( colorUnits )
524 case 1:
525 transparentColorArray[0] = transparentColor->gray;
526 break;
527 case 3:
528 transparentColorArray[0] = transparentColor->red;
529 transparentColorArray[1] = transparentColor->green;
530 transparentColorArray[2] = transparentColor->blue;
531 break;
532 default:
533 throw Exceptions::InternalError( "Unexpected number of color channels in PNG image alpha bit separation." );
537 const C * src = reinterpret_cast< const C * >( row );
538 const C * srcEnd = reinterpret_cast< const C * >( row + rowBytes );
539 const C * tmpEnd = reinterpret_cast< const C * >( row );
540 for( ; src < srcEnd; )
542 bool trans = true;
543 const png_uint_16 * transparentColorSrc = transparentColorArray;
545 tmpEnd += colorUnits;
546 for( ; src != tmpEnd; ++src, ++dstCo, ++transparentColorSrc )
548 *reinterpret_cast< unsigned char * >( dstCo ) = *src;
549 if( transparentColor != 0 && *src != *transparentColorSrc )
551 trans = false;
554 tmpEnd += alphaUnits;
555 if( transparentColor == 0 )
557 for( ; src != tmpEnd; ++src, ++dstAl )
559 *reinterpret_cast< unsigned char * >( dstAl ) = *src;
562 else
564 *dstAl = trans ? (~OPAQUE) : OPAQUE;
565 ++dstAl;
567 /* Just ignore fill bytes. */
568 src = tmpEnd;
571 colorDst.write( bufCo, reinterpret_cast< char * >( dstCo ) - bufCo );
572 alphaDst.write( bufAl, reinterpret_cast< char * >( dstAl ) - bufAl );
575 void
576 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 )
578 switch( index_depth )
580 case 1:
581 case 2:
582 case 4:
584 const unsigned char * src = row;
585 const unsigned char * srcEnd = row + rowBytes;
586 unsigned char srcByte = *src;
587 ++src;
588 unsigned char srcShift = 8 - index_depth;
589 unsigned char mask;
590 switch( index_depth )
592 case 1:
593 mask = 0x01;
594 break;
595 case 2:
596 mask = 0x03;
597 break;
598 case 4:
599 mask = 0x07;
600 break;
601 default:
602 throw Exceptions::InternalError( "Switch in PNG palette application was out of range." );
604 while( true )
606 unsigned char index = ( srcByte >> srcShift ) & mask;
607 if( index >= num_palette )
609 throw Exceptions::ExternalError( "PNG palette index is out of bounds." );
611 unsigned char buf[3];
612 buf[0] = palette[ index ].red;
613 buf[1] = palette[ index ].green;
614 buf[2] = palette[ index ].blue;
615 colorDst.write( reinterpret_cast< char * >( buf ), 3 );
616 if( trans != 0 )
618 alphaDst.write( reinterpret_cast< char * >( & ( trans[ index ] ) ), 1 );
620 if( srcShift == 0 )
622 if( src == srcEnd )
624 break;
626 srcByte = *src;
627 ++src;
628 srcShift = 8;
630 srcShift -= index_depth;
633 break;
634 case 8:
636 const unsigned char * srcEnd = row + rowBytes;
637 for( const unsigned char * src = row; src != srcEnd; ++src )
639 if( *src >= num_palette )
641 throw Exceptions::ExternalError( "PNG palette index is out of bounds." );
643 unsigned char buf[3];
644 buf[0] = palette[ *src ].red;
645 buf[1] = palette[ *src ].green;
646 buf[2] = palette[ *src ].blue;
647 colorDst.write( reinterpret_cast< char * >( buf ), 3 );
648 if( trans != 0 )
650 alphaDst.write( reinterpret_cast< char * >( & ( trans[ *src ] ) ), 1 );
654 break;
655 default:
656 throw Exceptions::InternalError( "Unexpected depth in PNG palette image (supported values are { 1, 2, 4, 8 })." );
660 #endif