int vips__matrix_write_file( VipsImage *in, FILE *fp ) { VipsImage *mask; int x, y; if( vips_check_matrix( "vips2mask", in, &mask ) ) return( -1 ); fprintf( fp, "%d %d ", mask->Xsize, mask->Ysize ); if( vips_image_get_typeof( mask, "scale" ) && vips_image_get_typeof( mask, "offset" ) ) fprintf( fp, "%g %g ", vips_image_get_scale( mask ), vips_image_get_offset( mask ) ); fprintf( fp, "\n" ); for( y = 0; y < mask->Ysize; y++ ) { for( x = 0; x < mask->Xsize; x++ ) fprintf( fp, "%g ", *VIPS_MATRIX( mask, x, y ) ); fprintf( fp, "\n" ); } g_object_unref( mask ); return( 0 ); }
int vips__matrix_write( VipsImage *in, const char *filename ) { VipsImage *mask; FILE *fp; int x, y; if( vips_check_matrix( "vips2mask", in, &mask ) ) return( -1 ); if( !(fp = vips__file_open_write( filename, TRUE )) ) { g_object_unref( mask ); return( -1 ); } fprintf( fp, "%d %d ", mask->Xsize, mask->Ysize ); if( vips_image_get_typeof( mask, "scale" ) && vips_image_get_typeof( mask, "offset" ) ) fprintf( fp, "%g %g ", vips_image_get_scale( mask ), vips_image_get_offset( mask ) ); fprintf( fp, "\n" ); for( y = 0; y < mask->Ysize; y++ ) { for( x = 0; x < mask->Xsize; x++ ) fprintf( fp, "%g ", *VIPS_MATRIX( mask, x, y ) ); fprintf( fp, "\n" ); } g_object_unref( mask ); fclose( fp ); return( 0 ); }
static void vips_exif_exif_entry( ExifEntry *entry, VipsExifRemove *ve ) { const char *tag_name; char vips_name_txt[256]; VipsBuf vips_name = VIPS_BUF_STATIC( vips_name_txt ); if( !(tag_name = vips_exif_entry_get_name( entry )) ) return; vips_buf_appendf( &vips_name, "exif-ifd%d-%s", exif_entry_get_ifd( entry ), tag_name ); /* Does this field exist on the image? If not, schedule it for * removal. */ if( !vips_image_get_typeof( ve->image, vips_buf_all( &vips_name ) ) ) ve->to_remove = g_slist_prepend( ve->to_remove, entry ); /* Orientation is really set from the vips * VIPS_META_ORIENTATION tag. If that's been deleted, we must delete * any matching EXIF tags too. */ if( strcmp( tag_name, "Orientation" ) == 0 && vips_image_get_typeof( ve->image, VIPS_META_ORIENTATION ) ) ve->to_remove = g_slist_prepend( ve->to_remove, entry ); /* If this is a string tag, we must also remove it ready for * recreation, see the comment below. */ if( tag_is_encoding( entry->tag ) || tag_is_ascii( entry->tag ) || tag_is_utf16( entry->tag ) ) ve->to_remove = g_slist_prepend( ve->to_remove, entry ); }
static int attach_blob( VipsImage *im, const char *field, void *data, int data_length ) { char *data_copy; /* Only use the first one. */ if( vips_image_get_typeof( im, field ) ) { #ifdef DEBUG printf( "attach_blob: second %s block, ignoring\n", field ); #endif /*DEBUG*/ return( 0 ); } #ifdef DEBUG printf( "attach_blob: attaching %d bytes of %s\n", data_length, field ); #endif /*DEBUG*/ /* Always attach a copy of the unparsed exif data. */ if( !(data_copy = vips_malloc( NULL, data_length )) ) return( -1 ); memcpy( data_copy, data, data_length ); vips_image_set_blob( im, field, (VipsCallbackFn) vips_free, data_copy, data_length ); return( 0 ); }
/* Set IMAGEDESCRIPTION, if it's there. If @properties is TRUE, set from * vips' metadata. */ static int write_embed_imagedescription( Write *write, TIFF *tif ) { if( write->properties ) { char *doc; if( !(doc = vips__make_xml_metadata( "vips2tiff", write->im )) ) return( -1 ); TIFFSetField( tif, TIFFTAG_IMAGEDESCRIPTION, doc ); xmlFree( doc ); } else { const char *imagedescription; if( !vips_image_get_typeof( write->im, VIPS_META_IMAGEDESCRIPTION ) ) return( 0 ); if( vips_image_get_string( write->im, VIPS_META_IMAGEDESCRIPTION, &imagedescription ) ) return( -1 ); TIFFSetField( tif, TIFFTAG_IMAGEDESCRIPTION, imagedescription ); } #ifdef DEBUG printf( "vips2tiff: attached imagedescription from meta\n" ); #endif /*DEBUG*/ return( 0 ); }
static void vips_exif_update( ExifData *ed, VipsImage *image ) { VipsExifRemove ve; VIPS_DEBUG_MSG( "vips_exif_update: \n" ); /* If this exif came from the image (rather than being an exif block we * have made afresh), then any fields which are in the block but not on * the image must have been deliberately removed. Remove them from the * block as well. * * If there are any string-valued fields (eg. comment etc.) which * exist as libvips metadata tags, we must also remove those from the * exif block. * * libexif does not allow you to change string lengths, you must make * new tags, so we have to remove ready to re-add. */ if( vips_image_get_typeof( image, VIPS_META_EXIF_NAME ) ) { ve.image = image; ve.ed = ed; exif_data_foreach_content( ed, (ExifDataForeachContentFunc) vips_exif_exif_content, &ve ); } /* Walk the image and add any exif- that's set in image metadata. */ vips_image_map( image, vips_exif_image_field, ed ); }
/* Embed any IPCT metadata. */ static int write_embed_ipct( Write *write, TIFF *tif ) { void *data; size_t data_length; if( !vips_image_get_typeof( write->im, VIPS_META_IPCT_NAME ) ) return( 0 ); if( vips_image_get_blob( write->im, VIPS_META_IPCT_NAME, &data, &data_length ) ) return( -1 ); /* For no very good reason, libtiff stores IPCT as an array of * long, not byte. */ if( data_length & 3 ) { vips_warn( "vips2tiff", "%s", _( "rounding up IPCT data length" ) ); data_length /= 4; data_length += 1; } else data_length /= 4; TIFFSetField( tif, TIFFTAG_RICHTIFFIPTC, data_length, data ); #ifdef DEBUG printf( "vips2tiff: attached IPCT from meta\n" ); #endif /*DEBUG*/ return( 0 ); }
static VipsAngle get_angle( VipsImage *im ) { VipsAngle angle; const char *orientation; angle = VIPS_ANGLE_0; if( vips_image_get_typeof( im, ORIENTATION ) && !vips_image_get_string( im, ORIENTATION, &orientation ) ) { if( vips_isprefix( "6", orientation ) ) angle = VIPS_ANGLE_90; else if( vips_isprefix( "8", orientation ) ) angle = VIPS_ANGLE_270; else if( vips_isprefix( "3", orientation ) ) angle = VIPS_ANGLE_180; /* Other values do rotate + mirror, don't bother handling them * though, how common can mirroring be. * * See: * * http://www.80sidea.com/archives/2316 */ } return( angle ); }
static int write_blob( Write *write, const char *field, int app ) { unsigned char *data; size_t data_length; if( vips_image_get_typeof( write->in, field ) ) { if( vips_image_get_blob( write->in, field, (void *) &data, &data_length ) ) return( -1 ); /* Single jpeg markers can only hold 64kb, large objects must * be split into multiple markers. * * Unfortunately, how this splitting is done depends on the * data type. For example, ICC and XMP have completely * different ways of doing this. * * For now, just ignore oversize objects and warn. */ if( data_length > 65530 ) vips_warn( "VipsJpeg", _( "field \"%s\" is too large " "for a single JPEG marker, ignoring" ), field ); else { #ifdef DEBUG printf( "write_blob: attaching %zd bytes of %s\n", data_length, field ); #endif /*DEBUG*/ jpeg_write_marker( &write->cinfo, app, data, data_length ); } } return( 0 ); }
/* Set the EXIF resolution from the vips xres/yres tags. */ static int vips_exif_resolution_from_image( ExifData *ed, VipsImage *image ) { double xres, yres; const char *p; int unit; VIPS_DEBUG_MSG( "vips_exif_resolution_from_image: vips res of %g, %g\n", image->Xres, image->Yres ); /* Default to inches, more progs support it. */ unit = 2; if( vips_image_get_typeof( image, VIPS_META_RESOLUTION_UNIT ) && !vips_image_get_string( image, VIPS_META_RESOLUTION_UNIT, &p ) ) { if( vips_isprefix( "cm", p ) ) unit = 3; else if( vips_isprefix( "none", p ) ) unit = 1; } switch( unit ) { case 1: xres = image->Xres; yres = image->Yres; break; case 2: xres = image->Xres * 25.4; yres = image->Yres * 25.4; break; case 3: xres = image->Xres * 10.0; yres = image->Yres * 10.0; break; default: g_warning( "%s", _( "unknown EXIF resolution unit" ) ); return( 0 ); } /* Main image xres/yres/unit are in ifd0. ifd1 has the thumbnail * xres/yres/unit. */ vips_exif_set_tag( ed, 0, EXIF_TAG_X_RESOLUTION, vips_exif_set_double, (void *) &xres ); vips_exif_set_tag( ed, 0, EXIF_TAG_Y_RESOLUTION, vips_exif_set_double, (void *) &yres ); vips_exif_set_tag( ed, 0, EXIF_TAG_RESOLUTION_UNIT, vips_exif_set_int, (void *) &unit ); return( 0 ); }
/** * vips_image_get_scale: * @image: image to get from * * Matrix images can have an optional `scale` field for use by integer * convolution. * * Returns: the scale. */ double vips_image_get_scale( const VipsImage *image ) { double scale; scale = 1.0; if( vips_image_get_typeof( image, "scale" ) ) vips_image_get_double( image, "scale", &scale ); return( scale ); }
/** * vips_image_get_offset: * @image: image to get from * * Matrix images can have an optional `offset` field for use by integer * convolution. * * Returns: the offset. */ double vips_image_get_offset( const VipsImage *image ) { double offset; offset = 0.0; if( vips_image_get_typeof( image, "offset" ) ) vips_image_get_double( image, "offset", &offset ); return( offset ); }
static int vips_foreign_save_tiff_build( VipsObject *object ) { VipsForeignSave *save = (VipsForeignSave *) object; VipsForeignSaveTiff *tiff = (VipsForeignSaveTiff *) object; const char *p; if( VIPS_OBJECT_CLASS( vips_foreign_save_tiff_parent_class )-> build( object ) ) return( -1 ); /* Default xres/yres to the values from the image. */ if( !vips_object_argument_isset( object, "xres" ) ) tiff->xres = save->ready->Xres * 10.0; if( !vips_object_argument_isset( object, "yres" ) ) tiff->yres = save->ready->Yres * 10.0; /* resunit param overrides resunit metadata. */ if( !vips_object_argument_isset( object, "resunit" ) && vips_image_get_typeof( save->ready, VIPS_META_RESOLUTION_UNIT ) && !vips_image_get_string( save->ready, VIPS_META_RESOLUTION_UNIT, &p ) && vips_isprefix( "in", p ) ) tiff->resunit = VIPS_FOREIGN_TIFF_RESUNIT_INCH; if( tiff->resunit == VIPS_FOREIGN_TIFF_RESUNIT_INCH ) { tiff->xres *= 2.54; tiff->yres *= 2.54; } if( vips__tiff_write( save->ready, tiff->filename, tiff->compression, tiff->Q, tiff->predictor, tiff->profile, tiff->tile, tiff->tile_width, tiff->tile_height, tiff->pyramid, tiff->squash, tiff->miniswhite, tiff->resunit, tiff->xres, tiff->yres, tiff->bigtiff, tiff->rgbjpeg, tiff->properties ) ) return( -1 ); return( 0 ); }
static int write_embed_profile( Write *write, TIFF *tif ) { if( write->icc_profile && strcmp( write->icc_profile, "none" ) != 0 && embed_profile_file( tif, write->icc_profile ) ) return( -1 ); if( !write->icc_profile && vips_image_get_typeof( write->im, VIPS_META_ICC_NAME ) && embed_profile_meta( tif, write->im ) ) return( -1 ); return( 0 ); }
/* And orientation. */ static int vips_exif_set_orientation( ExifData *ed, VipsImage *im ) { int orientation; /* We set the tag, even if it's been deleted, since it's a required * field. */ if( !vips_image_get_typeof( im, VIPS_META_ORIENTATION ) || vips_image_get_int( im, VIPS_META_ORIENTATION, &orientation ) ) orientation = 1; VIPS_DEBUG_MSG( "set_exif_orientation: %d\n", orientation ); vips_exif_set_tag( ed, 0, EXIF_TAG_ORIENTATION, vips_exif_set_int, (void *) &orientation ); return( 0 ); }
/* Embed any XMP metadata. */ static int write_embed_photoshop( Write *write, TIFF *tif ) { void *data; size_t data_length; if( !vips_image_get_typeof( write->im, VIPS_META_PHOTOSHOP_NAME ) ) return( 0 ); if( vips_image_get_blob( write->im, VIPS_META_PHOTOSHOP_NAME, &data, &data_length ) ) return( -1 ); TIFFSetField( tif, TIFFTAG_PHOTOSHOP, data_length, data ); #ifdef DEBUG printf( "vips2tiff: attached photoshop data from meta\n" ); #endif /*DEBUG*/ return( 0 ); }
/* Try to read an embedded thumbnail. */ static VipsImage * thumbnail_get_thumbnail( VipsImage *im ) { void *ptr; size_t size; VipsImage *thumb; double residual; int jpegshrink; if( !vips_image_get_typeof( im, THUMBNAIL ) || vips_image_get_blob( im, THUMBNAIL, &ptr, &size ) || vips_jpegload_buffer( ptr, size, &thumb, NULL ) ) { vips_info( "vipsthumbnail", "no jpeg thumbnail" ); return( NULL ); } calculate_shrink( thumb->Xsize, thumb->Ysize, &residual ); if( residual > 1.0 ) { vips_info( "vipsthumbnail", "jpeg thumbnail too small" ); g_object_unref( thumb ); return( NULL ); } /* Reload with the correct downshrink. */ jpegshrink = thumbnail_find_jpegshrink( thumb ); vips_info( "vipsthumbnail", "loading jpeg thumbnail with factor %d pre-shrink", jpegshrink ); g_object_unref( thumb ); if( vips_jpegload_buffer( ptr, size, &thumb, "shrink", jpegshrink, NULL ) ) { vips_info( "vipsthumbnail", "jpeg thumbnail reload failed" ); return( NULL ); } vips_info( "vipsthumbnail", "using %dx%d jpeg thumbnail", thumb->Xsize, thumb->Ysize ); return( thumb ); }
/* And thumbnail. */ static int vips_exif_set_thumbnail( ExifData *ed, VipsImage *im ) { /* Delete any old thumbnail data. We should use the exif free func, * but the memory allocator is not exposed by libexif! Hopefully they * are just using free(). * * exif.c makes this assumption too when it tries to update a * thumbnail. */ if( ed->data ) { free( ed->data ); ed->data = NULL; } ed->size = 0; /* Update EXIF thumbnail from metadata, if any. */ if( vips_image_get_typeof( im, "jpeg-thumbnail-data" ) ) { void *data; size_t length; if( vips_image_get_blob( im, "jpeg-thumbnail-data", &data, &length ) ) return( -1 ); /* Again, we should use the exif allocator attached to this * entry, but it is not exposed! */ if( length > 0 && data ) { ed->data = malloc( length ); memcpy( ed->data, data, length ); ed->size = length; } } return( 0 ); }
/* Write a VIPS image to PNG. */ static int write_vips( Write *write, int compress, int interlace ) { VipsImage *in = write->in; int bit_depth; int color_type; int interlace_type; int i, nb_passes; g_assert( in->BandFmt == VIPS_FORMAT_UCHAR || in->BandFmt == VIPS_FORMAT_USHORT ); g_assert( in->Coding == VIPS_CODING_NONE ); g_assert( in->Bands > 0 && in->Bands < 5 ); /* Catch PNG errors. */ if( setjmp( png_jmpbuf( write->pPng ) ) ) return( -1 ); /* Check input image. */ if( vips_image_pio_input( in ) ) return( -1 ); if( compress < 0 || compress > 9 ) { vips_error( "vips2png", "%s", _( "compress should be in [0,9]" ) ); return( -1 ); } /* Set compression parameters. */ png_set_compression_level( write->pPng, compress ); bit_depth = in->BandFmt == VIPS_FORMAT_UCHAR ? 8 : 16; switch( in->Bands ) { case 1: color_type = PNG_COLOR_TYPE_GRAY; break; case 2: color_type = PNG_COLOR_TYPE_GRAY_ALPHA; break; case 3: color_type = PNG_COLOR_TYPE_RGB; break; case 4: color_type = PNG_COLOR_TYPE_RGB_ALPHA; break; default: g_assert( 0 ); /* Keep -Wall happy. */ return( 0 ); } interlace_type = interlace ? PNG_INTERLACE_ADAM7 : PNG_INTERLACE_NONE; png_set_IHDR( write->pPng, write->pInfo, in->Xsize, in->Ysize, bit_depth, color_type, interlace_type, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT ); /* Set resolution. libpnbg uses pixels per meter. */ png_set_pHYs( write->pPng, write->pInfo, VIPS_RINT( in->Xres * 1000 ), VIPS_RINT( in->Yres * 1000 ), PNG_RESOLUTION_METER ); /* Set ICC Profile. */ if( vips_image_get_typeof( in, VIPS_META_ICC_NAME ) ) { void *profile; size_t profile_length; if( vips_image_get_blob( in, VIPS_META_ICC_NAME, &profile, &profile_length ) ) return( -1 ); #ifdef DEBUG printf( "write_vips: attaching %zd bytes of ICC profile\n", profile_length ); #endif /*DEBUG*/ png_set_iCCP( write->pPng, write->pInfo, "icc", PNG_COMPRESSION_TYPE_BASE, profile, profile_length ); } png_write_info( write->pPng, write->pInfo ); /* If we're an intel byte order CPU and this is a 16bit image, we need * to swap bytes. */ if( bit_depth > 8 && !vips_amiMSBfirst() ) png_set_swap( write->pPng ); if( interlace ) nb_passes = png_set_interlace_handling( write->pPng ); else nb_passes = 1; /* Write data. */ for( i = 0; i < nb_passes; i++ ) if( vips_sink_disc( write->in, write_png_block, write ) ) return( -1 ); /* The setjmp() was held by our background writer: reset it. */ if( setjmp( png_jmpbuf( write->pPng ) ) ) return( -1 ); png_write_end( write->pPng, write->pInfo ); return( 0 ); }
static int write_exif( Write *write ) { #ifdef HAVE_EXIF unsigned char *data; size_t data_length; unsigned int idl; ExifData *ed; /* Either parse from the embedded EXIF, or if there's none, make * some fresh EXIF we can write the resolution to. */ if( vips_image_get_typeof( write->in, VIPS_META_EXIF_NAME ) ) { if( vips_image_get_blob( write->in, VIPS_META_EXIF_NAME, (void *) &data, &data_length ) ) return( -1 ); if( !(ed = exif_data_new_from_data( data, data_length )) ) return( -1 ); } else { ed = exif_data_new(); exif_data_set_option( ed, EXIF_DATA_OPTION_FOLLOW_SPECIFICATION ); exif_data_set_data_type( ed, EXIF_DATA_TYPE_COMPRESSED ); exif_data_set_byte_order( ed, EXIF_BYTE_ORDER_INTEL ); /* Create the mandatory EXIF fields with default data. */ exif_data_fix( ed ); } /* Update EXIF tags from the image metadata. */ vips_exif_update( ed, write->in ); /* Update EXIF resolution from the vips image header. */ if( vips__set_exif_resolution( ed, write->in ) ) { exif_data_free( ed ); return( -1 ); } /* Update EXIF image dimensions from the vips image header. */ if( set_exif_dimensions( ed, write->in ) ) { exif_data_free( ed ); return( -1 ); } /* Update EXIF orientation from the vips image header. */ if( set_exif_orientation( ed, write->in ) ) { exif_data_free( ed ); return( -1 ); } /* Reserialise and write. exif_data_save_data() returns an int for some * reason. */ exif_data_save_data( ed, &data, &idl ); if( !idl ) { vips_error( "VipsJpeg", "%s", _( "error saving EXIF" ) ); exif_data_free( ed ); return( -1 ); } data_length = idl; #ifdef DEBUG printf( "write_exif: attaching %zd bytes of EXIF\n", data_length ); #endif /*DEBUG*/ exif_data_free( ed ); jpeg_write_marker( &write->cinfo, JPEG_APP0 + 1, data, data_length ); free( data ); #else /*!HAVE_EXIF*/ /* No libexif ... just copy the embedded EXIF over. */ if( write_blob( write, VIPS_META_EXIF_NAME, JPEG_APP0 + 1 ) ) return( -1 ); #endif /*!HAVE_EXIF*/ return( 0 ); }
static VipsImage * thumbnail_shrink( VipsObject *process, VipsImage *in, VipsInterpolate *interp, VipsImage *sharpen ) { VipsImage **t = (VipsImage **) vips_object_local_array( process, 10 ); VipsInterpretation interpretation = linear_processing ? VIPS_INTERPRETATION_XYZ : VIPS_INTERPRETATION_sRGB; int shrink; double residual; int tile_width; int tile_height; int nlines; /* RAD needs special unpacking. */ if( in->Coding == VIPS_CODING_RAD ) { vips_info( "vipsthumbnail", "unpacking Rad to float" ); /* rad is scrgb. */ if( vips_rad2float( in, &t[0], NULL ) ) return( NULL ); in = t[0]; } /* In linear mode, we import right at the start. * * This is only going to work for images in device space. If you have * an image in PCS which also has an attached profile, strange things * will happen. */ if( linear_processing && in->Coding == VIPS_CODING_NONE && (in->BandFmt == VIPS_FORMAT_UCHAR || in->BandFmt == VIPS_FORMAT_USHORT) && (vips_image_get_typeof( in, VIPS_META_ICC_NAME ) || import_profile) ) { if( vips_image_get_typeof( in, VIPS_META_ICC_NAME ) ) vips_info( "vipsthumbnail", "importing with embedded profile" ); else vips_info( "vipsthumbnail", "importing with profile %s", import_profile ); if( vips_icc_import( in, &t[1], "input_profile", import_profile, "embedded", TRUE, "pcs", VIPS_PCS_XYZ, NULL ) ) return( NULL ); in = t[1]; } /* To the processing colourspace. This will unpack LABQ as well. */ vips_info( "vipsthumbnail", "converting to processing space %s", vips_enum_nick( VIPS_TYPE_INTERPRETATION, interpretation ) ); if( vips_colourspace( in, &t[2], interpretation, NULL ) ) return( NULL ); in = t[2]; shrink = calculate_shrink( in, &residual ); vips_info( "vipsthumbnail", "integer shrink by %d", shrink ); if( vips_shrink( in, &t[3], shrink, shrink, NULL ) ) return( NULL ); in = t[3]; /* We want to make sure we read the image sequentially. * However, the convolution we may be doing later will force us * into SMALLTILE or maybe FATSTRIP mode and that will break * sequentiality. * * So ... read into a cache where tiles are scanlines, and make sure * we keep enough scanlines to be able to serve a line of tiles. * * We use a threaded tilecache to avoid a deadlock: suppose thread1, * evaluating the top block of the output, is delayed, and thread2, * evaluating the second block, gets here first (this can happen on * a heavily-loaded system). * * With an unthreaded tilecache (as we had before), thread2 will get * the cache lock and start evaling the second block of the shrink. * When it reaches the png reader it will stall until the first block * has been used ... but it never will, since thread1 will block on * this cache lock. */ vips_get_tile_size( in, &tile_width, &tile_height, &nlines ); if( vips_tilecache( in, &t[4], "tile_width", in->Xsize, "tile_height", 10, "max_tiles", (nlines * 2) / 10, "access", VIPS_ACCESS_SEQUENTIAL, "threaded", TRUE, NULL ) || vips_affine( t[4], &t[5], residual, 0, 0, residual, "interpolate", interp, NULL ) ) return( NULL ); in = t[5]; vips_info( "vipsthumbnail", "residual scale by %g", residual ); vips_info( "vipsthumbnail", "%s interpolation", VIPS_OBJECT_GET_CLASS( interp )->nickname ); /* Colour management. * * In linear mode, just export. In device space mode, do a combined * import/export to transform to the target space. */ if( linear_processing ) { if( export_profile || vips_image_get_typeof( in, VIPS_META_ICC_NAME ) ) { vips_info( "vipsthumbnail", "exporting to device space with a profile" ); if( vips_icc_export( in, &t[7], "output_profile", export_profile, NULL ) ) return( NULL ); in = t[7]; } else { vips_info( "vipsthumbnail", "converting to sRGB" ); if( vips_colourspace( in, &t[6], VIPS_INTERPRETATION_sRGB, NULL ) ) return( NULL ); in = t[6]; } } else if( export_profile && (vips_image_get_typeof( in, VIPS_META_ICC_NAME ) || import_profile) ) { if( vips_image_get_typeof( in, VIPS_META_ICC_NAME ) ) vips_info( "vipsthumbnail", "importing with embedded profile" ); else vips_info( "vipsthumbnail", "importing with profile %s", import_profile ); vips_info( "vipsthumbnail", "exporting with profile %s", export_profile ); if( vips_icc_transform( in, &t[6], export_profile, "input_profile", import_profile, "embedded", TRUE, NULL ) ) return( NULL ); in = t[6]; } /* If we are upsampling, don't sharpen, since nearest looks dumb * sharpened. */ if( shrink >= 1 && residual <= 1.0 && sharpen ) { vips_info( "vipsthumbnail", "sharpening thumbnail" ); if( vips_conv( in, &t[8], sharpen, NULL ) ) return( NULL ); in = t[8]; } if( delete_profile && vips_image_get_typeof( in, VIPS_META_ICC_NAME ) ) { vips_info( "vipsthumbnail", "deleting profile from output image" ); if( !vips_image_remove( in, VIPS_META_ICC_NAME ) ) return( NULL ); } return( in ); }
static VipsImage * thumbnail_shrink( VipsObject *process, VipsImage *in ) { VipsImage **t = (VipsImage **) vips_object_local_array( process, 10 ); VipsInterpretation interpretation = linear_processing ? VIPS_INTERPRETATION_XYZ : VIPS_INTERPRETATION_sRGB; /* TRUE if we've done the import of an ICC transform and still need to * export. */ gboolean have_imported; /* TRUE if we've premultiplied and need to unpremultiply. */ gboolean have_premultiplied; VipsBandFormat unpremultiplied_format; /* Sniff the incoming image and try to guess what the alpha max is. */ double max_alpha; double shrink; /* RAD needs special unpacking. */ if( in->Coding == VIPS_CODING_RAD ) { vips_info( "vipsthumbnail", "unpacking Rad to float" ); /* rad is scrgb. */ if( vips_rad2float( in, &t[0], NULL ) ) return( NULL ); in = t[0]; } /* Try to guess what the maximum alpha might be. */ max_alpha = 255; if( in->BandFmt == VIPS_FORMAT_USHORT ) max_alpha = 65535; /* In linear mode, we import right at the start. * * We also have to import the whole image if it's CMYK, since * vips_colourspace() (see below) doesn't know about CMYK. * * This is only going to work for images in device space. If you have * an image in PCS which also has an attached profile, strange things * will happen. */ have_imported = FALSE; if( (linear_processing || in->Type == VIPS_INTERPRETATION_CMYK) && in->Coding == VIPS_CODING_NONE && (in->BandFmt == VIPS_FORMAT_UCHAR || in->BandFmt == VIPS_FORMAT_USHORT) && (vips_image_get_typeof( in, VIPS_META_ICC_NAME ) || import_profile) ) { if( vips_image_get_typeof( in, VIPS_META_ICC_NAME ) ) vips_info( "vipsthumbnail", "importing with embedded profile" ); else vips_info( "vipsthumbnail", "importing with profile %s", import_profile ); if( vips_icc_import( in, &t[1], "input_profile", import_profile, "embedded", TRUE, "pcs", VIPS_PCS_XYZ, NULL ) ) return( NULL ); in = t[1]; have_imported = TRUE; } /* To the processing colourspace. This will unpack LABQ as well. */ vips_info( "vipsthumbnail", "converting to processing space %s", vips_enum_nick( VIPS_TYPE_INTERPRETATION, interpretation ) ); if( vips_colourspace( in, &t[2], interpretation, NULL ) ) return( NULL ); in = t[2]; /* If there's an alpha, we have to premultiply before shrinking. See * https://github.com/jcupitt/libvips/issues/291 */ have_premultiplied = FALSE; if( in->Bands == 2 || (in->Bands == 4 && in->Type != VIPS_INTERPRETATION_CMYK) || in->Bands == 5 ) { vips_info( "vipsthumbnail", "premultiplying alpha" ); if( vips_premultiply( in, &t[3], "max_alpha", max_alpha, NULL ) ) return( NULL ); have_premultiplied = TRUE; /* vips_premultiply() makes a float image. When we * vips_unpremultiply() below, we need to cast back to the * pre-premultiply format. */ unpremultiplied_format = in->BandFmt; in = t[3]; } shrink = calculate_shrink( in ); if( vips_resize( in, &t[4], 1.0 / shrink, NULL ) ) return( NULL ); in = t[4]; if( have_premultiplied ) { vips_info( "vipsthumbnail", "unpremultiplying alpha" ); if( vips_unpremultiply( in, &t[5], "max_alpha", max_alpha, NULL ) || vips_cast( t[5], &t[6], unpremultiplied_format, NULL ) ) return( NULL ); in = t[6]; } /* Colour management. * * If we've already imported, just export. Otherwise, we're in * device space and we need a combined import/export to transform to * the target space. */ if( have_imported ) { if( export_profile || vips_image_get_typeof( in, VIPS_META_ICC_NAME ) ) { vips_info( "vipsthumbnail", "exporting to device space with a profile" ); if( vips_icc_export( in, &t[7], "output_profile", export_profile, NULL ) ) return( NULL ); in = t[7]; } else { vips_info( "vipsthumbnail", "converting to sRGB" ); if( vips_colourspace( in, &t[7], VIPS_INTERPRETATION_sRGB, NULL ) ) return( NULL ); in = t[7]; } } else if( export_profile && (vips_image_get_typeof( in, VIPS_META_ICC_NAME ) || import_profile) ) { VipsImage *out; vips_info( "vipsthumbnail", "exporting with profile %s", export_profile ); /* We first try with the embedded profile, if any, then if * that fails try again with the supplied fallback profile. */ out = NULL; if( vips_image_get_typeof( in, VIPS_META_ICC_NAME ) ) { vips_info( "vipsthumbnail", "importing with embedded profile" ); if( vips_icc_transform( in, &t[7], export_profile, "embedded", TRUE, NULL ) ) { vips_warn( "vipsthumbnail", _( "unable to import with " "embedded profile: %s" ), vips_error_buffer() ); vips_error_clear(); } else out = t[7]; } if( !out && import_profile ) { vips_info( "vipsthumbnail", "importing with fallback profile" ); if( vips_icc_transform( in, &t[7], export_profile, "input_profile", import_profile, "embedded", FALSE, NULL ) ) return( NULL ); out = t[7]; } /* If the embedded profile failed and there's no fallback or * the fallback failed, out will still be NULL. */ if( out ) in = out; } if( delete_profile && vips_image_get_typeof( in, VIPS_META_ICC_NAME ) ) { vips_info( "vipsthumbnail", "deleting profile from output image" ); if( !vips_image_remove( in, VIPS_META_ICC_NAME ) ) return( NULL ); } return( in ); }
/* Write a VIPS image to PNG. */ static int write_vips( Write *write, int compress, int interlace, const char *profile, VipsForeignPngFilter filter, gboolean strip, gboolean palette, int colours, int Q, double dither ) { VipsImage *in = write->in; int bit_depth; int color_type; int interlace_type; int i, nb_passes; g_assert( in->BandFmt == VIPS_FORMAT_UCHAR || in->BandFmt == VIPS_FORMAT_USHORT ); g_assert( in->Coding == VIPS_CODING_NONE ); g_assert( in->Bands > 0 && in->Bands < 5 ); /* Catch PNG errors. */ if( setjmp( png_jmpbuf( write->pPng ) ) ) return( -1 ); /* Check input image. If we are writing interlaced, we need to make 7 * passes over the image. We advertise ourselves as seq, so to ensure * we only suck once from upstream, switch to WIO. */ if( interlace ) { if( !(write->memory = vips_image_copy_memory( in )) ) return( -1 ); in = write->memory; } else { if( vips_image_pio_input( in ) ) return( -1 ); } if( compress < 0 || compress > 9 ) { vips_error( "vips2png", "%s", _( "compress should be in [0,9]" ) ); return( -1 ); } /* Set compression parameters. */ png_set_compression_level( write->pPng, compress ); /* Set row filter. */ png_set_filter( write->pPng, 0, filter ); bit_depth = in->BandFmt == VIPS_FORMAT_UCHAR ? 8 : 16; switch( in->Bands ) { case 1: color_type = PNG_COLOR_TYPE_GRAY; break; case 2: color_type = PNG_COLOR_TYPE_GRAY_ALPHA; break; case 3: color_type = PNG_COLOR_TYPE_RGB; break; case 4: color_type = PNG_COLOR_TYPE_RGB_ALPHA; break; default: vips_error( "vips2png", _( "can't save %d band image as png" ), in->Bands ); return( -1 ); } #ifdef HAVE_IMAGEQUANT /* Enable image quantisation to paletted 8bpp PNG if colours is set. */ if( palette ) { g_assert( colours >= 2 && colours <= 256 ); bit_depth = 8; color_type = PNG_COLOR_TYPE_PALETTE; } #else if( palette ) g_warning( "%s", _( "ignoring palette (no quantisation support)" ) ); #endif /*HAVE_IMAGEQUANT*/ interlace_type = interlace ? PNG_INTERLACE_ADAM7 : PNG_INTERLACE_NONE; png_set_IHDR( write->pPng, write->pInfo, in->Xsize, in->Ysize, bit_depth, color_type, interlace_type, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT ); /* Set resolution. libpng uses pixels per meter. */ png_set_pHYs( write->pPng, write->pInfo, VIPS_RINT( in->Xres * 1000 ), VIPS_RINT( in->Yres * 1000 ), PNG_RESOLUTION_METER ); /* Set ICC Profile. */ if( profile && !strip ) { if( strcmp( profile, "none" ) != 0 ) { void *data; size_t length; if( !(data = vips__file_read_name( profile, vips__icc_dir(), &length )) ) return( -1 ); #ifdef DEBUG printf( "write_vips: " "attaching %zd bytes of ICC profile\n", length ); #endif /*DEBUG*/ png_set_iCCP( write->pPng, write->pInfo, "icc", PNG_COMPRESSION_TYPE_BASE, data, length ); } } else if( vips_image_get_typeof( in, VIPS_META_ICC_NAME ) && !strip ) { void *data; size_t length; if( vips_image_get_blob( in, VIPS_META_ICC_NAME, &data, &length ) ) return( -1 ); #ifdef DEBUG printf( "write_vips: attaching %zd bytes of ICC profile\n", length ); #endif /*DEBUG*/ png_set_iCCP( write->pPng, write->pInfo, "icc", PNG_COMPRESSION_TYPE_BASE, data, length ); } if( vips_image_get_typeof( in, VIPS_META_XMP_NAME ) ) { const char *str; if( vips_image_get_string( in, VIPS_META_XMP_NAME, &str ) ) return( -1 ); vips__png_set_text( write->pPng, write->pInfo, "XML:com.adobe.xmp", str ); } /* Set any "png-comment-xx-yyy" metadata items. */ if( vips_image_map( in, write_png_comment, write ) ) return( -1 ); #ifdef HAVE_IMAGEQUANT if( palette ) { VipsImage *im_index; VipsImage *im_palette; int palette_count; png_color *png_palette; png_byte *png_trans; int trans_count; if( vips__quantise_image( in, &im_index, &im_palette, colours, Q, dither ) ) return( -1 ); palette_count = im_palette->Xsize; g_assert( palette_count <= PNG_MAX_PALETTE_LENGTH ); png_palette = (png_color *) png_malloc( write->pPng, palette_count * sizeof( png_color ) ); png_trans = (png_byte *) png_malloc( write->pPng, palette_count * sizeof( png_byte ) ); trans_count = 0; for( i = 0; i < palette_count; i++ ) { VipsPel *p = (VipsPel *) VIPS_IMAGE_ADDR( im_palette, i, 0 ); png_color *col = &png_palette[i]; col->red = p[0]; col->green = p[1]; col->blue = p[2]; png_trans[i] = p[3]; if( p[3] != 255 ) trans_count = i + 1; #ifdef DEBUG printf( "write_vips: palette[%d] %d %d %d %d\n", i + 1, p[0], p[1], p[2], p[3] ); #endif /*DEBUG*/ } #ifdef DEBUG printf( "write_vips: attaching %d color palette\n", palette_count ); #endif /*DEBUG*/ png_set_PLTE( write->pPng, write->pInfo, png_palette, palette_count ); if( trans_count ) { #ifdef DEBUG printf( "write_vips: attaching %d alpha values\n", trans_count ); #endif /*DEBUG*/ png_set_tRNS( write->pPng, write->pInfo, png_trans, trans_count, NULL ); } png_free( write->pPng, (void *) png_palette ); png_free( write->pPng, (void *) png_trans ); VIPS_UNREF( im_palette ); VIPS_UNREF( write->memory ); write->memory = im_index; in = write->memory; } #endif /*HAVE_IMAGEQUANT*/ png_write_info( write->pPng, write->pInfo ); /* If we're an intel byte order CPU and this is a 16bit image, we need * to swap bytes. */ if( bit_depth > 8 && !vips_amiMSBfirst() ) png_set_swap( write->pPng ); if( interlace ) nb_passes = png_set_interlace_handling( write->pPng ); else nb_passes = 1; /* Write data. */ for( i = 0; i < nb_passes; i++ ) if( vips_sink_disc( in, write_png_block, write ) ) return( -1 ); /* The setjmp() was held by our background writer: reset it. */ if( setjmp( png_jmpbuf( write->pPng ) ) ) return( -1 ); png_write_end( write->pPng, write->pInfo ); return( 0 ); }
/* Scan the exif block on the image, if any, and make a set of vips metadata * tags for what we find. */ int vips__exif_parse( VipsImage *image ) { void *data; size_t length; ExifData *ed; VipsExifParams params; const char *str; if( !vips_image_get_typeof( image, VIPS_META_EXIF_NAME ) ) return( 0 ); if( vips_image_get_blob( image, VIPS_META_EXIF_NAME, &data, &length ) ) return( -1 ); if( !(ed = vips_exif_load_data_without_fix( data, length )) ) return( -1 ); #ifdef DEBUG_VERBOSE show_tags( ed ); show_values( ed ); #endif /*DEBUG_VERBOSE*/ /* Look for resolution fields and use them to set the VIPS xres/yres * fields. * * If the fields are missing, set them from the image, which will have * previously had them set from something like JFIF. */ if( vips_image_resolution_from_exif( image, ed ) && vips_exif_resolution_from_image( ed, image ) ) { exif_data_free( ed ); return( -1 ); } /* Make sure all required fields are there before we attach the vips * metadata. */ exif_data_fix( ed ); /* Attach informational fields for what we find. */ params.image = image; params.ed = ed; exif_data_foreach_content( ed, (ExifDataForeachContentFunc) vips_exif_get_content, ¶ms ); vips_exif_get_thumbnail( image, ed ); exif_data_free( ed ); /* Orientation handling. ifd0 has the Orientation tag for the main * image. */ if( vips_image_get_typeof( image, "exif-ifd0-Orientation" ) != 0 && !vips_image_get_string( image, "exif-ifd0-Orientation", &str ) ) { int orientation; orientation = atoi( str ); orientation = VIPS_CLIP( 1, orientation, 8 ); vips_image_set_int( image, VIPS_META_ORIENTATION, orientation ); } return( 0 ); }
/* Examine the metadata tags on the image and update the EXIF block. */ int vips__exif_update( VipsImage *image ) { unsigned char *data; size_t length; unsigned int idl; ExifData *ed; /* Either parse from the embedded EXIF, or if there's none, make * some fresh EXIF we can write the resolution to. */ if( vips_image_get_typeof( image, VIPS_META_EXIF_NAME ) ) { if( vips_image_get_blob( image, VIPS_META_EXIF_NAME, (void *) &data, &length ) ) return( -1 ); if( !(ed = exif_data_new_from_data( data, length )) ) return( -1 ); } else { ed = exif_data_new(); exif_data_set_option( ed, EXIF_DATA_OPTION_FOLLOW_SPECIFICATION ); exif_data_set_data_type( ed, EXIF_DATA_TYPE_COMPRESSED ); exif_data_set_byte_order( ed, EXIF_BYTE_ORDER_INTEL ); /* Create the mandatory EXIF fields with default data. */ exif_data_fix( ed ); } /* Update EXIF tags from the image metadata. */ vips_exif_update( ed, image ); /* Update EXIF resolution from the vips image header. */ if( vips_exif_resolution_from_image( ed, image ) ) { exif_data_free( ed ); return( -1 ); } /* Update EXIF image dimensions from the vips image header. */ if( vips_exif_set_dimensions( ed, image ) ) { exif_data_free( ed ); return( -1 ); } /* Update EXIF orientation from the vips image header. */ if( vips_exif_set_orientation( ed, image ) ) { exif_data_free( ed ); return( -1 ); } /* Update the thumbnail. */ if( vips_exif_set_thumbnail( ed, image ) ) { exif_data_free( ed ); return( -1 ); } /* Reserialise and write. exif_data_save_data() returns an int for some * reason. */ exif_data_save_data( ed, &data, &idl ); if( !idl ) { vips_error( "exif", "%s", _( "error saving EXIF" ) ); exif_data_free( ed ); return( -1 ); } length = idl; #ifdef DEBUG printf( "vips__exif_update: generated %zd bytes of EXIF\n", length ); #endif /*DEBUG*/ vips_image_set_blob( image, VIPS_META_EXIF_NAME, (VipsCallbackFn) vips_free, data, length ); exif_data_free( ed ); return( 0 ); }
/* Write a TIFF header. width and height are the size of the VipsImage we are * writing (it may have been shrunk). */ static int write_tiff_header( Write *write, Layer *layer ) { TIFF *tif = layer->tif; int format; int orientation; /* Output base header fields. */ TIFFSetField( tif, TIFFTAG_IMAGEWIDTH, layer->width ); TIFFSetField( tif, TIFFTAG_IMAGELENGTH, layer->height ); TIFFSetField( tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG ); TIFFSetField( tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT ); TIFFSetField( tif, TIFFTAG_COMPRESSION, write->compression ); if( write->compression == COMPRESSION_JPEG ) TIFFSetField( tif, TIFFTAG_JPEGQUALITY, write->jpqual ); if( write->predictor != VIPS_FOREIGN_TIFF_PREDICTOR_NONE ) TIFFSetField( tif, TIFFTAG_PREDICTOR, write->predictor ); /* Don't write mad resolutions (eg. zero), it confuses some programs. */ TIFFSetField( tif, TIFFTAG_RESOLUTIONUNIT, write->resunit ); TIFFSetField( tif, TIFFTAG_XRESOLUTION, VIPS_FCLIP( 0.01, write->xres, 1000000 ) ); TIFFSetField( tif, TIFFTAG_YRESOLUTION, VIPS_FCLIP( 0.01, write->yres, 1000000 ) ); if( !write->strip ) if( write_embed_profile( write, tif ) || write_embed_xmp( write, tif ) || write_embed_ipct( write, tif ) || write_embed_photoshop( write, tif ) || write_embed_imagedescription( write, tif ) ) return( -1 ); if( vips_image_get_typeof( write->im, VIPS_META_ORIENTATION ) && !vips_image_get_int( write->im, VIPS_META_ORIENTATION, &orientation ) ) TIFFSetField( tif, TIFFTAG_ORIENTATION, orientation ); /* And colour fields. */ if( write->im->Coding == VIPS_CODING_LABQ ) { TIFFSetField( tif, TIFFTAG_SAMPLESPERPIXEL, 3 ); TIFFSetField( tif, TIFFTAG_BITSPERSAMPLE, 8 ); TIFFSetField( tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_CIELAB ); } else if( write->onebit ) { TIFFSetField( tif, TIFFTAG_SAMPLESPERPIXEL, 1 ); TIFFSetField( tif, TIFFTAG_BITSPERSAMPLE, 1 ); TIFFSetField( tif, TIFFTAG_PHOTOMETRIC, write->miniswhite ? PHOTOMETRIC_MINISWHITE : PHOTOMETRIC_MINISBLACK ); } else { int photometric; /* Number of bands that have colour in .. other bands are saved * as alpha. */ int colour_bands; int alpha_bands; TIFFSetField( tif, TIFFTAG_SAMPLESPERPIXEL, write->im->Bands ); TIFFSetField( tif, TIFFTAG_BITSPERSAMPLE, vips_format_sizeof( write->im->BandFmt ) << 3 ); if( write->im->Bands < 3 ) { /* Mono or mono + alpha. */ photometric = write->miniswhite ? PHOTOMETRIC_MINISWHITE : PHOTOMETRIC_MINISBLACK; colour_bands = 1; } else { /* Could be: RGB, CMYK, LAB, perhaps with extra alpha. */ if( write->im->Type == VIPS_INTERPRETATION_LAB || write->im->Type == VIPS_INTERPRETATION_LABS ) { photometric = PHOTOMETRIC_CIELAB; colour_bands = 3; } else if( write->im->Type == VIPS_INTERPRETATION_CMYK && write->im->Bands >= 4 ) { photometric = PHOTOMETRIC_SEPARATED; TIFFSetField( tif, TIFFTAG_INKSET, INKSET_CMYK ); colour_bands = 4; } else if( write->compression == COMPRESSION_JPEG && write->im->Bands == 3 && write->im->BandFmt == VIPS_FORMAT_UCHAR && (!write->rgbjpeg && write->jpqual < 90) ) { /* This signals to libjpeg that it can do * YCbCr chrominance subsampling from RGB, not * that we will supply the image as YCbCr. */ photometric = PHOTOMETRIC_YCBCR; TIFFSetField( tif, TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB ); colour_bands = 3; } else { /* Some kind of generic multi-band image .. * save the first three bands as RGB, the rest * as alpha. */ photometric = PHOTOMETRIC_RGB; colour_bands = 3; } } alpha_bands = VIPS_CLIP( 0, write->im->Bands - colour_bands, MAX_ALPHA ); if( alpha_bands > 0 ) { uint16 v[MAX_ALPHA]; int i; /* EXTRASAMPLE_UNASSALPHA means generic extra * alpha-like channels. ASSOCALPHA means pre-multipled * alpha only. */ for( i = 0; i < alpha_bands; i++ ) v[i] = EXTRASAMPLE_UNASSALPHA; TIFFSetField( tif, TIFFTAG_EXTRASAMPLES, alpha_bands, v ); } TIFFSetField( tif, TIFFTAG_PHOTOMETRIC, photometric ); } /* Layout. */ if( write->tile ) { TIFFSetField( tif, TIFFTAG_TILEWIDTH, write->tilew ); TIFFSetField( tif, TIFFTAG_TILELENGTH, write->tileh ); } else TIFFSetField( tif, TIFFTAG_ROWSPERSTRIP, write->tileh ); if( layer->above ) /* Pyramid layer. */ TIFFSetField( tif, TIFFTAG_SUBFILETYPE, FILETYPE_REDUCEDIMAGE ); /* Sample format. */ format = SAMPLEFORMAT_UINT; if( vips_band_format_isuint( write->im->BandFmt ) ) format = SAMPLEFORMAT_UINT; else if( vips_band_format_isint( write->im->BandFmt ) ) format = SAMPLEFORMAT_INT; else if( vips_band_format_isfloat( write->im->BandFmt ) ) format = SAMPLEFORMAT_IEEEFP; else if( vips_band_format_iscomplex( write->im->BandFmt ) ) format = SAMPLEFORMAT_COMPLEXIEEEFP; TIFFSetField( tif, TIFFTAG_SAMPLEFORMAT, format ); return( 0 ); }
/* Write a VIPS image to PNG. */ static int write_vips( Write *write, int compress, int interlace, const char *profile, VipsForeignPngFilter filter, gboolean strip ) { VipsImage *in = write->in; int bit_depth; int color_type; int interlace_type; int i, nb_passes; g_assert( in->BandFmt == VIPS_FORMAT_UCHAR || in->BandFmt == VIPS_FORMAT_USHORT ); g_assert( in->Coding == VIPS_CODING_NONE ); g_assert( in->Bands > 0 && in->Bands < 5 ); /* Catch PNG errors. */ if( setjmp( png_jmpbuf( write->pPng ) ) ) return( -1 ); /* Check input image. If we are writing interlaced, we need to make 7 * passes over the image. We advertise ourselves as seq, so to ensure * we only suck once from upstream, switch to WIO. */ if( interlace ) { if( !(write->memory = vips_image_copy_memory( in )) ) return( -1 ); in = write->memory; } else { if( vips_image_pio_input( in ) ) return( -1 ); } if( compress < 0 || compress > 9 ) { vips_error( "vips2png", "%s", _( "compress should be in [0,9]" ) ); return( -1 ); } /* Set compression parameters. */ png_set_compression_level( write->pPng, compress ); /* Set row filter. */ png_set_filter( write->pPng, 0, filter ); bit_depth = in->BandFmt == VIPS_FORMAT_UCHAR ? 8 : 16; switch( in->Bands ) { case 1: color_type = PNG_COLOR_TYPE_GRAY; break; case 2: color_type = PNG_COLOR_TYPE_GRAY_ALPHA; break; case 3: color_type = PNG_COLOR_TYPE_RGB; break; case 4: color_type = PNG_COLOR_TYPE_RGB_ALPHA; break; default: vips_error( "vips2png", _( "can't save %d band image as png" ), in->Bands ); return( -1 ); } interlace_type = interlace ? PNG_INTERLACE_ADAM7 : PNG_INTERLACE_NONE; png_set_IHDR( write->pPng, write->pInfo, in->Xsize, in->Ysize, bit_depth, color_type, interlace_type, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT ); /* Set resolution. libpng uses pixels per meter. */ png_set_pHYs( write->pPng, write->pInfo, VIPS_RINT( in->Xres * 1000 ), VIPS_RINT( in->Yres * 1000 ), PNG_RESOLUTION_METER ); /* Set ICC Profile. */ if( profile && !strip ) { if( strcmp( profile, "none" ) != 0 ) { void *data; size_t length; if( !(data = vips__file_read_name( profile, VIPS_ICC_DIR, &length )) ) return( -1 ); #ifdef DEBUG printf( "write_vips: " "attaching %zd bytes of ICC profile\n", length ); #endif /*DEBUG*/ png_set_iCCP( write->pPng, write->pInfo, "icc", PNG_COMPRESSION_TYPE_BASE, data, length ); } } else if( vips_image_get_typeof( in, VIPS_META_ICC_NAME ) && !strip ) { void *data; size_t length; if( vips_image_get_blob( in, VIPS_META_ICC_NAME, &data, &length ) ) return( -1 ); #ifdef DEBUG printf( "write_vips: attaching %zd bytes of ICC profile\n", length ); #endif /*DEBUG*/ png_set_iCCP( write->pPng, write->pInfo, "icc", PNG_COMPRESSION_TYPE_BASE, data, length ); } png_write_info( write->pPng, write->pInfo ); /* If we're an intel byte order CPU and this is a 16bit image, we need * to swap bytes. */ if( bit_depth > 8 && !vips_amiMSBfirst() ) png_set_swap( write->pPng ); if( interlace ) nb_passes = png_set_interlace_handling( write->pPng ); else nb_passes = 1; /* Write data. */ for( i = 0; i < nb_passes; i++ ) if( vips_sink_disc( in, write_png_block, write ) ) return( -1 ); /* The setjmp() was held by our background writer: reset it. */ if( setjmp( png_jmpbuf( write->pPng ) ) ) return( -1 ); png_write_end( write->pPng, write->pInfo ); return( 0 ); }
/* Write a VIPS image to a JPEG compress struct. */ static int write_vips( Write *write, int qfac, const char *profile, gboolean optimize_coding, gboolean progressive, gboolean strip, gboolean no_subsample, gboolean trellis_quant, gboolean overshoot_deringing, gboolean optimize_scans, int quant_table ) { VipsImage *in; J_COLOR_SPACE space; /* The image we'll be writing ... can change, see CMYK. */ in = write->in; /* Should have been converted for save. */ g_assert( in->BandFmt == VIPS_FORMAT_UCHAR ); g_assert( in->Coding == VIPS_CODING_NONE ); g_assert( in->Bands == 1 || in->Bands == 3 || in->Bands == 4 ); /* Check input image. */ if( vips_image_pio_input( in ) ) return( -1 ); /* Set compression parameters. */ write->cinfo.image_width = in->Xsize; write->cinfo.image_height = in->Ysize; write->cinfo.input_components = in->Bands; if( in->Bands == 4 && in->Type == VIPS_INTERPRETATION_CMYK ) { space = JCS_CMYK; /* IJG always sets an Adobe marker, so we should invert CMYK. */ if( vips_invert( in, &write->inverted, NULL ) ) return( -1 ); in = write->inverted; } else if( in->Bands == 3 ) space = JCS_RGB; else if( in->Bands == 1 ) space = JCS_GRAYSCALE; else /* Use luminance compression for all channels. */ space = JCS_UNKNOWN; write->cinfo.in_color_space = space; /* Build VIPS output stuff now we know the image we'll be writing. */ if( !(write->row_pointer = VIPS_ARRAY( NULL, in->Ysize, JSAMPROW )) ) return( -1 ); #ifdef HAVE_JPEG_EXT_PARAMS /* Reset compression profile to libjpeg defaults */ if( jpeg_c_int_param_supported( &write->cinfo, JINT_COMPRESS_PROFILE ) ) jpeg_c_set_int_param( &write->cinfo, JINT_COMPRESS_PROFILE, JCP_FASTEST ); #endif /* Reset to default. */ jpeg_set_defaults( &write->cinfo ); /* Compute optimal Huffman coding tables. */ write->cinfo.optimize_coding = optimize_coding; #ifdef HAVE_JPEG_EXT_PARAMS /* Apply trellis quantisation to each 8x8 block. Implies * "optimize_coding". */ if( trellis_quant ) { if( jpeg_c_bool_param_supported( &write->cinfo, JBOOLEAN_TRELLIS_QUANT ) ) { jpeg_c_set_bool_param( &write->cinfo, JBOOLEAN_TRELLIS_QUANT, TRUE ); write->cinfo.optimize_coding = TRUE; } else vips_warn( "vips2jpeg", "%s", _( "trellis_quant unsupported" ) ); } /* Apply overshooting to samples with extreme values e.g. 0 & 255 * for 8-bit. */ if( overshoot_deringing ) { if( jpeg_c_bool_param_supported( &write->cinfo, JBOOLEAN_OVERSHOOT_DERINGING ) ) jpeg_c_set_bool_param( &write->cinfo, JBOOLEAN_OVERSHOOT_DERINGING, TRUE ); else vips_warn( "vips2jpeg", "%s", _( "overshoot_deringing unsupported" ) ); } /* Split the spectrum of DCT coefficients into separate scans. * Requires progressive output. Must be set before * jpeg_simple_progression. */ if( optimize_scans ) { if( progressive ) { if( jpeg_c_bool_param_supported( &write->cinfo, JBOOLEAN_OPTIMIZE_SCANS ) ) jpeg_c_set_bool_param( &write->cinfo, JBOOLEAN_OPTIMIZE_SCANS, TRUE ); else vips_warn( "vips2jpeg", "%s", _( "Ignoring optimize_scans" ) ); } else vips_warn( "vips2jpeg", "%s", _( "Ignoring optimize_scans for baseline" ) ); } /* Use predefined quantization table. */ if( quant_table > 0 ) { if( jpeg_c_int_param_supported( &write->cinfo, JINT_BASE_QUANT_TBL_IDX ) ) jpeg_c_set_int_param( &write->cinfo, JINT_BASE_QUANT_TBL_IDX, quant_table ); else vips_warn( "vips2jpeg", "%s", _( "Setting quant_table unsupported" ) ); } #else /* Using jpeglib.h without extension parameters, warn of ignored * options. */ if( trellis_quant ) vips_warn( "vips2jpeg", "%s", _( "Ignoring trellis_quant" ) ); if( overshoot_deringing ) vips_warn( "vips2jpeg", "%s", _( "Ignoring overshoot_deringing" ) ); if( optimize_scans ) vips_warn( "vips2jpeg", "%s", _( "Ignoring optimize_scans" ) ); if( quant_table > 0 ) vips_warn( "vips2jpeg", "%s", _( "Ignoring quant_table" ) ); #endif /* Set compression quality. Must be called after setting params above. */ jpeg_set_quality( &write->cinfo, qfac, TRUE ); /* Enable progressive write. */ if( progressive ) jpeg_simple_progression( &write->cinfo ); /* Turn off chroma subsampling. Follow IM and do it automatically for * high Q. */ if( no_subsample || qfac > 90 ) { int i; for( i = 0; i < in->Bands; i++ ) { write->cinfo.comp_info[i].h_samp_factor = 1; write->cinfo.comp_info[i].v_samp_factor = 1; } } /* Don't write the APP0 JFIF headers if we are stripping. */ if( strip ) write->cinfo.write_JFIF_header = FALSE; /* Build compress tables. */ jpeg_start_compress( &write->cinfo, TRUE ); /* Write any APP markers we need. */ if( !strip ) { if( write_exif( write ) || write_blob( write, VIPS_META_XMP_NAME, JPEG_APP0 + 1 ) || write_blob( write, VIPS_META_IPCT_NAME, JPEG_APP0 + 13 ) ) return( -1 ); /* A profile supplied as an argument overrides an embedded * profile. "none" means don't attach a profile. */ if( profile && strcmp( profile, "none" ) != 0 && write_profile_file( write, profile ) ) return( -1 ); if( !profile && vips_image_get_typeof( in, VIPS_META_ICC_NAME ) && write_profile_meta( write ) ) return( -1 ); } /* Write data. Note that the write function grabs the longjmp()! */ if( vips_sink_disc( in, write_jpeg_block, write ) ) return( -1 ); /* We have to reinstate the setjmp() before we jpeg_finish_compress(). */ if( setjmp( write->eman.jmp ) ) return( -1 ); jpeg_finish_compress( &write->cinfo ); return( 0 ); }