From 4fd37502bc36b29b6018a17cf8fbe7cb0cd926b8 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 30 Nov 2011 11:51:01 +0000 Subject: [PATCH] turn format/*jpeg* into stubs the old im_vips2jpeg.c and im_jpeg2vips.c are now just stubs calling the new foreign/ things --- TODO | 9 + libvips/deprecated/lazy.c | 17 +- libvips/foreign/jpegload.c | 102 +++ libvips/foreign/jpegsave.c | 108 +++ libvips/format/im_jpeg2vips.c | 1079 +--------------------------- libvips/format/im_vips2jpeg.c | 1201 +------------------------------- libvips/include/vips/foreign.h | 7 + libvips/iofuncs/cache.c | 8 +- 8 files changed, 285 insertions(+), 2246 deletions(-) diff --git a/TODO b/TODO index 5ff6a4fc..12062e12 100644 --- a/TODO +++ b/TODO @@ -10,6 +10,15 @@ +- "header" with more than one arg should put the filename before each printed + line so eg. + + $ header *.jpg | grep -i prof + + works + + + - make compat wrappers for old im_jpeg2vips() and im_vips2jpeg() make sure we are using the operation cache for jpegload diff --git a/libvips/deprecated/lazy.c b/libvips/deprecated/lazy.c index e918cd9e..35d8c084 100644 --- a/libvips/deprecated/lazy.c +++ b/libvips/deprecated/lazy.c @@ -217,13 +217,18 @@ vips_image_open_lazy( VipsImage *image, return( -1 ); vips_demand_hint( image, VIPS_DEMAND_STYLE_THINSTRIP, NULL ); - /* Then 'start' creates the real image and 'gen' paints 'out' with - * pixels from the real image on demand. + /* If there's no load function, assume header has done everything + * already. */ - if( vips_image_generate( image, - open_lazy_start, open_lazy_generate, vips_stop_one, - lazy, NULL ) ) - return( -1 ); + if( format->load ) { + /* Then 'start' creates the real image and 'gen' paints 'image' + * with pixels from the real image on demand. + */ + if( vips_image_generate( image, + open_lazy_start, open_lazy_generate, vips_stop_one, + lazy, NULL ) ) + return( -1 ); + } return( 0 ); } diff --git a/libvips/foreign/jpegload.c b/libvips/foreign/jpegload.c index 291881f5..6daff1f7 100644 --- a/libvips/foreign/jpegload.c +++ b/libvips/foreign/jpegload.c @@ -235,6 +235,68 @@ vips_foreign_load_jpeg_file_init( VipsForeignLoadJpegFile *file ) { } +/** + * vips_jpegload: + * @filename: file to load + * @out: decompressed image + * @flags: image flags + * @shrink: shrink by this much on load + * @fail: fail on warnings + * + * Read a JPEG file into a VIPS image. It can read most 8-bit JPEG images, + * including CMYK and YCbCr. + * + * @shrink means shrink by this integer factor during load. Possible values + * are 1, 2, 4 and 8. Shrinking during read is very much faster than + * decompressing the whole image and then shrinking later. + * + * Setting @fail to true makes the JPEG reader fail on any warnings. + * This can be useful for detecting truncated files, for example. Normally + * reading these produces a warning, but no fatal error. + * + * Example: + * + * |[ + * vips_jpegload( "fred.jpg", &out, + * "shrink", 8, + * "fail", TRUE, + * NULL ); + * ]| + * + * Any embedded ICC profiles are ignored: you always just get the RGB from + * the file. Instead, the embedded profile will be attached to the image as + * metadata. You need to use something like im_icc_import() to get CIE + * values from the file. Any EXIF data is also attached as VIPS metadata. + * + * The int metadata item "jpeg-multiscan" is set to the result of + * jpeg_has_multiple_scans(). Interlaced jpeg images need a large amount of + * memory to load, so this field gives callers a chance to handle these + * images differently. + * + * The EXIF thumbnail, if present, is attached to the image as + * "jpeg-thumbnail-data". See vips_image_get_blob(). + * + * This function only reads the image header and does not decompress any pixel + * data. Decompression only occurs when pixels are accessed by some other + * function. + * + * See also: vips_jpegload_buffer(), vips_image_new_from_file(). + * + * Returns: 0 on success, -1 on error. + */ +int +vips_jpegload( const char *filename, VipsImage **out, ... ) +{ + va_list ap; + int result; + + va_start( ap, out ); + result = vips_call_split( "jpegload", ap, filename, out ); + va_end( ap ); + + return( result ); +} + typedef struct _VipsForeignLoadJpegBuffer { VipsForeignLoadJpeg parent_object; @@ -304,3 +366,43 @@ static void vips_foreign_load_jpeg_buffer_init( VipsForeignLoadJpegBuffer *buffer ) { } + + +/** + * vips_jpegload_buffer: + * @buf: memory area to load + * @len: size of memory area + * @out: image to write + * + * Read a JPEG-formatted memory block into a VIPS image. It can read most + * 8-bit JPEG images, including CMYK and YCbCr. + * + * This function is handy for processing JPEG image thumbnails. + * + * Caution: on return only the header will have been read, the pixel data is + * not decompressed until the first pixel is read. Therefore you must not free + * @buf until you have read pixel data from @out. + * + * See also: vips_jpegload(). + * + * Returns: 0 on success, -1 on error. + */ +int +vips_jpegload_buffer( void *buf, size_t len, VipsImage **out, ... ) +{ + va_list ap; + VipsArea *area; + int result; + + /* We don't take a copy of the data or free it. + */ + area = vips_area_new_blob( NULL, buf, len ); + + va_start( ap, out ); + result = vips_call_split( "jpegload_buffer", ap, area, out ); + va_end( ap ); + + vips_area_unref( area ); + + return( result ); +} diff --git a/libvips/foreign/jpegsave.c b/libvips/foreign/jpegsave.c index 1139c78a..ae31a969 100644 --- a/libvips/foreign/jpegsave.c +++ b/libvips/foreign/jpegsave.c @@ -200,6 +200,47 @@ vips_foreign_save_jpeg_file_init( VipsForeignSaveJpegFile *file ) { } + +/** + * vips_jpegsave: + * @in: image to save + * @filename: file to write to + * @Q: quality factor + * @profile: attach this ICC profile + * + * Write a VIPS image to a file as JPEG. + * + * Use @Q to set the JPEG compression factor. Default 75. + * + * Use @profile to give the filename of a profile to be em,bedded in the JPEG. + * This does not affect the pixels which are written, just the way + * they are tagged. You can use the special string "none" to mean + * "don't attach a profile". + * + * If no profile is specified and the VIPS header + * contains an ICC profile named VIPS_META_ICC_NAME ("icc-profile-data"), the + * profile from the VIPS header will be attached. + * + * The image is automatically converted to RGB, Monochrome or CMYK before + * saving. Any metadata attached to the image is saved as EXIF, if possible. + * + * See also: vips_jpegsave_buffer(), vips_image_write_file(). + * + * Returns: 0 on success, -1 on error. + */ +int +vips_jpegsave( VipsImage *in, const char *filename, ... ) +{ + va_list ap; + int result; + + va_start( ap, filename ); + result = vips_call_split( "jpegsave", ap, in, filename ); + va_end( ap ); + + return( result ); +} + typedef struct _VipsForeignSaveJpegBuffer { VipsForeignSaveJpeg parent_object; @@ -267,6 +308,48 @@ vips_foreign_save_jpeg_buffer_init( VipsForeignSaveJpegBuffer *file ) { } + +/** + * vips_jpegsave_buffer: + * @in: image to save + * @buf: return output buffer here + * @len: return output length here + * @Q: JPEG quality factor + * @profile: attach this ICC profile + * + * As vips_jpegsave(), but save to a memory buffer. + * + * The address of the buffer is returned in @obuf, the length of the buffer in + * @olen. You are responsible for freeing the buffer with g_free() when you + * are done with it. + * + * See also: vips_jpegsave(), vips_image_write_to_file(). + * + * Returns: 0 on success, -1 on error. + */ +int +vips_jpegsave_buffer( VipsImage *in, void **buf, size_t *len, ... ) +{ + va_list ap; + VipsArea *area; + int result; + + va_start( ap, len ); + result = vips_call_split( "jpegsave_buffer", ap, in, &area ); + va_end( ap ); + + if( buf ) { + *buf = area->data; + area->free_fn = NULL; + } + if( buf ) + *len = area->length; + + vips_area_unref( area ); + + return( result ); +} + typedef struct _VipsForeignSaveJpegMime { VipsForeignSaveJpeg parent_object; @@ -323,3 +406,28 @@ static void vips_foreign_save_jpeg_mime_init( VipsForeignSaveJpegMime *mime ) { } + +/** + * vips_jpegsave_mime: + * @in: image to save + * @Q: JPEG quality factor + * @profile: attach this ICC profile + * + * As vips_jpegsave(), but save as a mime jpeg on stdout. + * + * See also: vips_jpegsave(), vips_image_write_to_file(). + * + * Returns: 0 on success, -1 on error. + */ +int +vips_jpegsave_mime( VipsImage *in, ... ) +{ + va_list ap; + int result; + + va_start( ap, in ); + result = vips_call_split( "jpegsave_mime", ap, in ); + va_end( ap ); + + return( result ); +} diff --git a/libvips/format/im_jpeg2vips.c b/libvips/format/im_jpeg2vips.c index e3c7f8a6..fa22c5a2 100644 --- a/libvips/format/im_jpeg2vips.c +++ b/libvips/format/im_jpeg2vips.c @@ -39,6 +39,8 @@ * - read XMP data * 3/11/11 * - attach exif tags as coded values + * 30/11/11 + * - now just a stub */ /* @@ -77,719 +79,9 @@ #endif /*HAVE_CONFIG_H*/ #include -#ifndef HAVE_JPEG - -#include - -int -im_jpeg2vips( const char *name, IMAGE *out ) -{ - im_error( "im_jpeg2vips", "%s", - _( "JPEG support disabled" ) ); - - return( -1 ); -} - -#else /*HAVE_JPEG*/ - -#include #include -#include -#include - -#ifdef HAVE_EXIF -#ifdef UNTAGGED_EXIF -#include -#include -#include -#include -#else /*!UNTAGGED_EXIF*/ -#include -#include -#include -#include -#endif /*UNTAGGED_EXIF*/ -#endif /*HAVE_EXIF*/ #include -#include -#include - -/* jpeglib includes jconfig.h, which can define HAVE_STDLIB_H ... which we - * also define. Make sure it's turned off. - */ -#ifdef HAVE_STDLIB_H -#undef HAVE_STDLIB_H -#endif /*HAVE_STDLIB_H*/ - -#include -#include - -/* Define a new error handler for when we bomb out. - */ -typedef struct { - /* Public fields. - */ - struct jpeg_error_mgr pub; - - /* Private stuff for us. - */ - jmp_buf jmp; /* longjmp() here to get back to VIPS */ - FILE *fp; /* fclose() if non-NULL */ -} ErrorManager; - -/* New output message method - send to VIPS. - */ -METHODDEF(void) -new_output_message( j_common_ptr cinfo ) -{ - char buffer[JMSG_LENGTH_MAX]; - - (*cinfo->err->format_message)( cinfo, buffer ); - im_error( "im_jpeg2vips", _( "%s" ), buffer ); - -#ifdef DEBUG - printf( "im_jpeg2vips: new_output_message: \"%s\"\n", buffer ); -#endif /*DEBUG*/ -} - -/* New error_exit handler. - */ -METHODDEF(void) -new_error_exit( j_common_ptr cinfo ) -{ - ErrorManager *eman = (ErrorManager *) cinfo->err; - -#ifdef DEBUG - printf( "im_jpeg2vips: new_error_exit\n" ); -#endif /*DEBUG*/ - - /* Close the fp if necessary. - */ - if( eman->fp ) { - (void) fclose( eman->fp ); - eman->fp = NULL; - } - - /* Send the error message to VIPS. This method is overridden above. - */ - (*cinfo->err->output_message)( cinfo ); - - /* Jump back. - */ - longjmp( eman->jmp, 1 ); -} - -#ifdef HAVE_EXIF -#ifdef DEBUG_VERBOSE -/* Print exif for debugging ... hacked from exif-0.6.9/actions.c - */ -static void -show_tags( ExifData *data ) -{ - int i; - unsigned int tag; - const char *name; - - printf( "show EXIF tags:\n" ); - - for( i = 0; i < EXIF_IFD_COUNT; i++ ) - printf( "%-7.7s", exif_ifd_get_name( i ) ); - printf( "\n" ); - - for( tag = 0; tag < 0xffff; tag++ ) { - name = exif_tag_get_title( tag ); - if( !name ) - continue; - printf( " 0x%04x %-29.29s", tag, name ); - for( i = 0; i < EXIF_IFD_COUNT; i++ ) - if( exif_content_get_entry( data->ifd[i], tag ) ) - printf( " * " ); - else - printf( " - " ); - printf( "\n" ); - } -} - -static void -show_entry( ExifEntry *entry, void *client ) -{ - char exif_text[256]; - - printf( "%s", exif_tag_get_title( entry->tag ) ); - printf( "|" ); - printf( "%s", exif_entry_get_value( entry, exif_text, 256 ) ); - printf( "|" ); - printf( "%s", exif_format_get_name( entry->format ) ); - printf( "|" ); - printf( "%d bytes", entry->size ); - printf( "\n" ); -} - -static void -show_ifd( ExifContent *content, void *client ) -{ - exif_content_foreach_entry( content, show_entry, client ); - printf( "-\n" ); -} - -void -show_values( ExifData *data ) -{ - ExifByteOrder order; - - order = exif_data_get_byte_order( data ); - printf( "EXIF tags in '%s' byte order\n", - exif_byte_order_get_name( order ) ); - - printf( "%-20.20s", "Tag" ); - printf( "|" ); - printf( "%-58.58s", "Value" ); - printf( "\n" ); - - exif_data_foreach_content( data, show_ifd, NULL ); - - if( data->size ) - printf( "contains thumbnail of %d bytes\n", data->size ); -} -#endif /*DEBUG_VERBOSE*/ -#endif /*HAVE_EXIF*/ - -#ifdef HAVE_EXIF - -static int -vips_exif_get_int( ExifData *ed, - ExifEntry *entry, unsigned long component, int *out ) -{ - ExifByteOrder bo = exif_data_get_byte_order( ed ); - size_t sizeof_component = entry->size / entry->components; - size_t offset = component * sizeof_component; - - if( entry->format == EXIF_FORMAT_SHORT ) - *out = exif_get_short( entry->data + offset, bo ); - else if( entry->format == EXIF_FORMAT_SSHORT ) - *out = exif_get_sshort( entry->data + offset, bo ); - else if( entry->format == EXIF_FORMAT_LONG ) - /* This won't work for huge values, but who cares. - */ - *out = (int) exif_get_long( entry->data + offset, bo ); - else if( entry->format == EXIF_FORMAT_SLONG ) - *out = exif_get_slong( entry->data + offset, bo ); - else - return( -1 ); - - return( 0 ); -} - -static int -vips_exif_get_double( ExifData *ed, - ExifEntry *entry, unsigned long component, double *out ) -{ - ExifByteOrder bo = exif_data_get_byte_order( ed ); - size_t sizeof_component = entry->size / entry->components; - size_t offset = component * sizeof_component; - - if( entry->format == EXIF_FORMAT_RATIONAL ) { - ExifRational value; - - value = exif_get_rational( entry->data + offset, bo ); - *out = (double) value.numerator / value.denominator; - } - else if( entry->format == EXIF_FORMAT_SRATIONAL ) { - ExifSRational value; - - value = exif_get_srational( entry->data + offset, bo ); - *out = (double) value.numerator / value.denominator; - } - else - return( -1 ); - - return( 0 ); -} - -/* Save an exif value to a string in a way that we can restore. We only bother - * for the simple formats (that a client might try to change) though. - * - * Keep in sync with vips_exif_from_s() in vips2jpeg. - */ -static void -vips_exif_to_s( ExifData *ed, ExifEntry *entry, VipsBuf *buf ) -{ - unsigned long i; - int iv; - double dv; - char txt[256]; - - if( entry->format == EXIF_FORMAT_ASCII ) - vips_buf_appendf( buf, "%s ", entry->data ); - - else if( entry->components < 10 && - !vips_exif_get_int( ed, entry, 0, &iv ) ) { - for( i = 0; i < entry->components; i++ ) { - vips_exif_get_int( ed, entry, i, &iv ); - vips_buf_appendf( buf, "%d ", iv ); - } - } - else if( entry->components < 10 && - !vips_exif_get_double( ed, entry, 0, &dv ) ) { - for( i = 0; i < entry->components; i++ ) { - vips_exif_get_double( ed, entry, i, &dv ); - /* Need to be locale independent. - */ - g_ascii_dtostr( txt, 256, dv ); - vips_buf_appendf( buf, "%s ", txt ); - } - } - else - vips_buf_appendf( buf, "%s ", - exif_entry_get_value( entry, txt, 256 ) ); - - vips_buf_appendf( buf, "(%s, %s, %lu components, %d bytes)", - exif_entry_get_value( entry, txt, 256 ), - exif_format_get_name( entry->format ), - entry->components, - entry->size ); -} - -typedef struct _VipsExif { - VipsImage *image; - ExifData *ed; -} VipsExif; - -static void -attach_exif_entry( ExifEntry *entry, VipsExif *ve ) -{ - char name_txt[256]; - VipsBuf name = VIPS_BUF_STATIC( name_txt ); - char value_txt[256]; - VipsBuf value = VIPS_BUF_STATIC( value_txt ); - - vips_buf_appendf( &name, "exif-%s", exif_tag_get_title( entry->tag ) ); - vips_exif_to_s( ve->ed, entry, &value ); - - /* Can't do anything sensible with the error return. - */ - (void) im_meta_set_string( ve->image, - vips_buf_all( &name ), vips_buf_all( &value ) ); -} - -static void -attach_exif_content( ExifContent *content, VipsExif *ve ) -{ - exif_content_foreach_entry( content, - (ExifContentForeachEntryFunc) attach_exif_entry, ve ); -} - -/* Just find the first occurence of the tag (is this correct?) - */ -static ExifEntry * -find_entry( ExifData *ed, ExifTag tag ) -{ - int i; - - for( i = 0; i < EXIF_IFD_COUNT; i++ ) { - ExifEntry *entry; - - if( (entry = exif_content_get_entry( ed->ifd[i], tag )) ) - return( entry ); - } - - return( NULL ); -} - -static int -get_entry_rational( ExifData *ed, ExifTag tag, double *out ) -{ - ExifEntry *entry; - - if( !(entry = find_entry( ed, tag )) || - entry->format != EXIF_FORMAT_RATIONAL || - entry->components != 1 ) - return( -1 ); - - return( vips_exif_get_double( ed, entry, 0, out ) ); -} - -static int -get_entry_short( ExifData *ed, ExifTag tag, int *out ) -{ - ExifEntry *entry; - - if( !(entry = find_entry( ed, tag )) || - entry->format != EXIF_FORMAT_SHORT || - entry->components != 1 ) - return( -1 ); - - return( vips_exif_get_int( ed, entry, 0, out ) ); -} - -static void -set_vips_resolution( IMAGE *im, ExifData *ed ) -{ - double xres, yres; - int unit; - - if( get_entry_rational( ed, EXIF_TAG_X_RESOLUTION, &xres ) || - get_entry_rational( ed, EXIF_TAG_Y_RESOLUTION, &yres ) || - get_entry_short( ed, EXIF_TAG_RESOLUTION_UNIT, &unit ) ) { - im_warn( "im_jpeg2vips", - "%s", _( "error reading resolution" ) ); - return; - } - - switch( unit ) { - case 2: - /* In inches. - */ - xres /= 25.4; - yres /= 25.4; - break; - - case 3: - /* In cm. - */ - xres /= 10.0; - yres /= 10.0; - break; - - default: - im_warn( "im_jpeg2vips", "%s", _( "bad resolution unit" ) ); - return; - } - - im->Xres = xres; - im->Yres = yres; -} - -static int -attach_thumbnail( IMAGE *im, ExifData *ed ) -{ - if( ed->size > 0 ) { - char *thumb_copy; - - thumb_copy = im_malloc( NULL, ed->size ); - memcpy( thumb_copy, ed->data, ed->size ); - - if( im_meta_set_blob( im, "jpeg-thumbnail-data", - (im_callback_fn) im_free, thumb_copy, ed->size ) ) { - im_free( thumb_copy ); - return( -1 ); - } - } - - return( 0 ); -} -#endif /*HAVE_EXIF*/ - -static int -read_exif( IMAGE *im, void *data, int data_length ) -{ - char *data_copy; - - /* Only use the first one. - */ - if( im_header_get_typeof( im, IM_META_EXIF_NAME ) ) { -#ifdef DEBUG - printf( "read_exif: second EXIF block, ignoring\n" ); -#endif /*DEBUG*/ - - return( 0 ); - } - -#ifdef DEBUG - printf( "read_exif: attaching %d bytes of exif\n", data_length ); -#endif /*DEBUG*/ - - /* Always attach a copy of the unparsed exif data. - */ - if( !(data_copy = im_malloc( NULL, data_length )) ) - return( -1 ); - memcpy( data_copy, data, data_length ); - if( im_meta_set_blob( im, IM_META_EXIF_NAME, - (im_callback_fn) im_free, data_copy, data_length ) ) { - im_free( data_copy ); - return( -1 ); - } - -#ifdef HAVE_EXIF -{ - ExifData *ed; - - if( !(ed = exif_data_new_from_data( data, data_length )) ) - return( -1 ); - - if( ed->size > 0 ) { - VipsExif ve; - -#ifdef DEBUG_VERBOSE - show_tags( ed ); - show_values( ed ); -#endif /*DEBUG_VERBOSE*/ - - /* Attach informational fields for what we find. - - FIXME ... better to have this in the UI layer? - - Or we could attach non-human-readable tags here (int, - double etc) and then move the human stuff to the UI - layer? - - */ - ve.image = im; - ve.ed = ed; - exif_data_foreach_content( ed, - (ExifDataForeachContentFunc) attach_exif_content, &ve ); - - /* Look for resolution fields and use them to set the VIPS - * xres/yres fields. - */ - set_vips_resolution( im, ed ); - - attach_thumbnail( im, ed ); - } - - exif_data_free( ed ); -} -#endif /*HAVE_EXIF*/ - - return( 0 ); -} - -static int -read_xmp( IMAGE *im, void *data, int data_length ) -{ - char *data_copy; - - /* XMP sections start "http". Only use the first one. - */ - if( im_header_get_typeof( im, VIPS_META_XMP_NAME ) ) { -#ifdef DEBUG - printf( "read_xmp: second XMP block, ignoring\n" ); -#endif /*DEBUG*/ - - return( 0 ); - } - -#ifdef DEBUG - printf( "read_xmp: attaching %d bytes of XMP\n", data_length ); -#endif /*DEBUG*/ - - /* Always attach a copy of the unparsed exif data. - */ - if( !(data_copy = im_malloc( NULL, data_length )) ) - return( -1 ); - memcpy( data_copy, data, data_length ); - if( im_meta_set_blob( im, VIPS_META_XMP_NAME, - (im_callback_fn) im_free, data_copy, data_length ) ) { - im_free( data_copy ); - return( -1 ); - } - - return( 0 ); -} - -/* Number of app2 sections we can capture. Each one can be 64k, so 6400k should - * be enough for anyone (haha). - */ -#define MAX_APP2_SECTIONS (100) - -/* Read a cinfo to a VIPS image. Set invert_pels if the pixel reader needs to - * do 255-pel. - */ -static int -read_jpeg_header( struct jpeg_decompress_struct *cinfo, - IMAGE *out, gboolean *invert_pels, int shrink ) -{ - jpeg_saved_marker_ptr p; - int type; - - /* Capture app2 sections here for assembly. - */ - void *app2_data[MAX_APP2_SECTIONS] = { 0 }; - int app2_data_length[MAX_APP2_SECTIONS] = { 0 }; - int data_length; - int i; - - /* Read JPEG header. libjpeg will set out_color_space sanely for us - * for YUV YCCK etc. - */ - jpeg_read_header( cinfo, TRUE ); - cinfo->scale_denom = shrink; - cinfo->scale_num = 1; - jpeg_calc_output_dimensions( cinfo ); - - *invert_pels = FALSE; - switch( cinfo->out_color_space ) { - case JCS_GRAYSCALE: - type = IM_TYPE_B_W; - break; - - case JCS_CMYK: - type = IM_TYPE_CMYK; - /* Photoshop writes CMYK JPEG inverted :-( Maybe this is a - * way to spot photoshop CMYK JPGs. - */ - if( cinfo->saw_Adobe_marker ) - *invert_pels = TRUE; - break; - - case JCS_RGB: - default: - type = IM_TYPE_sRGB; - break; - } - - /* Set VIPS header. - */ - im_initdesc( out, - cinfo->output_width, cinfo->output_height, - cinfo->output_components, - IM_BBITS_BYTE, IM_BANDFMT_UCHAR, IM_CODING_NONE, type, - 1.0, 1.0, 0, 0 ); - - /* Interlaced jpegs need lots of memory to read, so our caller needs - * to know. - */ - (void) im_meta_set_int( out, "jpeg-multiscan", - jpeg_has_multiple_scans( cinfo ) ); - - /* Look for EXIF and ICC profile. - */ - for( p = cinfo->marker_list; p; p = p->next ) { -#ifdef DEBUG -{ - int i; - - printf( "read_jpeg_header: seen %d bytes of APP%d\n", - p->data_length, - p->marker - JPEG_APP0 ); - - for( i = 0; i < 10; i++ ) - printf( "\t%d) '%c' (%d)\n", - i, p->data[i], p->data[i] ); -} -#endif /*DEBUG*/ - - switch( p->marker ) { - case JPEG_APP0 + 1: - /* Possible EXIF or XMP data. - */ - if( p->data_length > 4 && - im_isprefix( "Exif", (char *) p->data ) && - read_exif( out, p->data, p->data_length ) ) - return( -1 ); - - if( p->data_length > 4 && - im_isprefix( "http", (char *) p->data ) && - read_xmp( out, p->data, p->data_length ) ) - return( -1 ); - - break; - - case JPEG_APP0 + 2: - /* ICC profile. - */ - if( p->data_length > 14 && - im_isprefix( "ICC_PROFILE", - (char *) p->data ) ) { - /* cur_marker numbers from 1, according to - * spec. - */ - int cur_marker = p->data[12] - 1; - - if( cur_marker >= 0 && - cur_marker < MAX_APP2_SECTIONS ) { - app2_data[cur_marker] = p->data + 14; - app2_data_length[cur_marker] = - p->data_length - 14; - } - } - break; - - default: - break; - } - } - - /* Assemble ICC sections. - */ - data_length = 0; - for( i = 0; i < MAX_APP2_SECTIONS && app2_data[i]; i++ ) - data_length += app2_data_length[i]; - if( data_length ) { - unsigned char *data; - int p; - -#ifdef DEBUG - printf( "read_jpeg_header: assembled %d byte ICC profile\n", - data_length ); -#endif /*DEBUG*/ - - if( !(data = im_malloc( NULL, data_length )) ) - return( -1 ); - - p = 0; - for( i = 0; i < MAX_APP2_SECTIONS && app2_data[i]; i++ ) { - memcpy( data + p, app2_data[i], app2_data_length[i] ); - p += app2_data_length[i]; - } - - if( im_meta_set_blob( out, IM_META_ICC_NAME, - (im_callback_fn) im_free, data, data_length ) ) { - im_free( data ); - return( -1 ); - } - } - - return( 0 ); -} - -/* Read a cinfo to a VIPS image. - */ -static int -read_jpeg_image( struct jpeg_decompress_struct *cinfo, IMAGE *out, - gboolean invert_pels ) -{ - int x, y, sz; - JSAMPROW row_pointer[1]; - - /* Check VIPS. - */ - if( im_outcheck( out ) || im_setupout( out ) ) - return( -1 ); - - /* Get size of output line and make a buffer. - */ - sz = cinfo->output_width * cinfo->output_components; - row_pointer[0] = (JSAMPLE *) (*cinfo->mem->alloc_large) - ( (j_common_ptr) cinfo, JPOOL_IMAGE, sz ); - - /* Start up decompressor. - */ - jpeg_start_decompress( cinfo ); - - /* Process image. - */ - for( y = 0; y < out->Ysize; y++ ) { - /* We set an error handler that longjmps() out, so I don't - * think this can fail. - */ - jpeg_read_scanlines( cinfo, &row_pointer[0], 1 ); - - if( invert_pels ) { - for( x = 0; x < sz; x++ ) - row_pointer[0][x] = 255 - row_pointer[0][x]; - } - if( im_writeline( y, out, row_pointer[0] ) ) - return( -1 ); - } - - /* Stop decompressor. - */ - jpeg_finish_decompress( cinfo ); - - return( 0 ); -} /* Read a JPEG file into a VIPS image. */ @@ -800,12 +92,8 @@ jpeg2vips( const char *name, IMAGE *out, gboolean header_only ) char mode[FILENAME_MAX]; char *p, *q; int shrink; - struct jpeg_decompress_struct cinfo; - ErrorManager eman; - FILE *fp; - int result; - gboolean invert_pels; gboolean fail_on_warn; + VipsImage *t; /* By default, we ignore any warnings. We want to get as much of * the user's data as we can. @@ -832,357 +120,45 @@ jpeg2vips( const char *name, IMAGE *out, gboolean header_only ) fail_on_warn = TRUE; } - /* Make jpeg dcompression object. - */ - cinfo.err = jpeg_std_error( &eman.pub ); - eman.pub.error_exit = new_error_exit; - eman.pub.output_message = new_output_message; - eman.fp = NULL; - if( setjmp( eman.jmp ) ) { - /* Here for longjmp() from new_error_exit(). - */ - jpeg_destroy_decompress( &cinfo ); + if( vips_jpegload( filename, &t, + "shrink", shrink, + "fail", fail_on_warn, + NULL ) ) + return( -1 ); + if( vips_image_write( t, out ) ) { + g_object_unref( t ); return( -1 ); } - jpeg_create_decompress( &cinfo ); + g_object_unref( t ); - /* Make input. - */ - if( !(fp = im__file_open_read( filename, NULL, FALSE )) ) - return( -1 ); - eman.fp = fp; - jpeg_stdio_src( &cinfo, fp ); - - /* Need to read in APP1 (EXIF metadata) and APP2 (ICC profile). - */ - jpeg_save_markers( &cinfo, JPEG_APP0 + 1, 0xffff ); - jpeg_save_markers( &cinfo, JPEG_APP0 + 2, 0xffff ); - - /* Convert! - */ - result = read_jpeg_header( &cinfo, out, &invert_pels, shrink ); - if( !header_only && !result ) - result = read_jpeg_image( &cinfo, out, invert_pels ); - - /* Close and tidy. - */ - fclose( fp ); - eman.fp = NULL; - jpeg_destroy_decompress( &cinfo ); - - if( eman.pub.num_warnings != 0 ) { - if( fail_on_warn ) { - im_error( "im_jpeg2vips", "%s", im_error_buffer() ); - result = -1; - } - else { - im_warn( "im_jpeg2vips", _( "read gave %ld warnings" ), - eman.pub.num_warnings ); - im_warn( "im_jpeg2vips", "%s", im_error_buffer() ); - } - } - - return( result ); + return( 0 ); } -/** - * im_jpeg2vips: - * @filename: file to load - * @out: image to write to - * - * Read a JPEG file into a VIPS image. It can read most 8-bit JPEG images, - * including CMYK and YCbCr. - * - * You can embed options in the filename. They have the form: - * - * |[ - * filename.jpg:shrink-factor,fail - * ]| - * - * - * - * - * shrink-factor - * Shrink by this integer factor during load. Allowed values are 1, 2, 4 - * and 8. Shrinking during read is very much faster than decompressing the - * whole image and then shrinking. - * - * - * - * - * fail - * This makes the JPEG reader fail on any warnings. This can be useful for - * detecting truncated files, for example. Normally reading these produces - * a warning, but no fatal error. - * - * - * - * - * Example: - * - * |[ - * im_jpeg2vips( "fred.jpg:8" out ); - * im_jpeg2vips( "fred.jpg:,fail" out ); - * ]| - * - * The first example will shrink by a factor of 8 during load. The second will - * fail with an error if there are any problems loading the file. - * - * Any embedded ICC profiles are ignored: you always just get the RGB from - * the file. Instead, the embedded profile will be attached to the image as - * metadata. You need to use something like im_icc_import() to get CIE - * values from the file. Any EXIF data is also attached as VIPS metadata. - * - * The int metadata item "jpeg-multiscan" is set to the result of - * jpeg_has_multiple_scans(). Interlaced jpeg images need a large amount of - * memory to load, so this field gives callers a chance to handle these - * images differently. - * - * The EXIF thumbnail, if present, is attached to the image as - * "jpeg-thumbnail-data". See im_meta_get_blob(). - * - * See also: #VipsFormat, im_vips2jpeg(). - * - * Returns: 0 on success, -1 on error. - */ int im_jpeg2vips( const char *filename, IMAGE *out ) { return( jpeg2vips( filename, out, FALSE ) ); } -/* Just like the above, but we read from a memory buffer. - */ -typedef struct { - /* Public jpeg fields. - */ - struct jpeg_source_mgr pub; - - /* Private stuff during read. - */ - gboolean start_of_file; /* have we gotten any data yet? */ - JOCTET *buf; - size_t len; -} InputBuffer; - -/* - * Initialize source --- called by jpeg_read_header - * before any data is actually read. - */ - -static void -init_source (j_decompress_ptr cinfo) -{ - InputBuffer *src = (InputBuffer *) cinfo->src; - - /* We reset the empty-input-file flag for each image, - * but we don't clear the input buffer. - * This is correct behavior for reading a series of images from one source. - */ - src->start_of_file = TRUE; -} - -/* - * Fill the input buffer --- called whenever buffer is emptied. - * - * In typical applications, this should read fresh data into the buffer - * (ignoring the current state of next_input_byte & bytes_in_buffer), - * reset the pointer & count to the start of the buffer, and return TRUE - * indicating that the buffer has been reloaded. It is not necessary to - * fill the buffer entirely, only to obtain at least one more byte. - * - * There is no such thing as an EOF return. If the end of the file has been - * reached, the routine has a choice of ERREXIT() or inserting fake data into - * the buffer. In most cases, generating a warning message and inserting a - * fake EOI marker is the best course of action --- this will allow the - * decompressor to output however much of the image is there. However, - * the resulting error message is misleading if the real problem is an empty - * input file, so we handle that case specially. - * - * In applications that need to be able to suspend compression due to input - * not being available yet, a FALSE return indicates that no more data can be - * obtained right now, but more may be forthcoming later. In this situation, - * the decompressor will return to its caller (with an indication of the - * number of scanlines it has read, if any). The application should resume - * decompression after it has loaded more data into the input buffer. Note - * that there are substantial restrictions on the use of suspension --- see - * the documentation. - * - * When suspending, the decompressor will back up to a convenient restart point - * (typically the start of the current MCU). next_input_byte & bytes_in_buffer - * indicate where the restart point will be if the current call returns FALSE. - * Data beyond this point must be rescanned after resumption, so move it to - * the front of the buffer rather than discarding it. - */ - -static boolean -fill_input_buffer (j_decompress_ptr cinfo) -{ - InputBuffer *src = (InputBuffer *) cinfo->src; - size_t nbytes; - - if (src->start_of_file) { - nbytes = src->len; - } - else { - WARNMS(cinfo, JWRN_JPEG_EOF); - /* Insert a fake EOI marker */ - src->buf[0] = (JOCTET) 0xFF; - src->buf[1] = (JOCTET) JPEG_EOI; - nbytes = 2; - } - - src->pub.next_input_byte = src->buf; - src->pub.bytes_in_buffer = nbytes; - src->start_of_file = FALSE; - - return TRUE; -} - -/* - * Skip data --- used to skip over a potentially large amount of - * uninteresting data (such as an APPn marker). - * - * Writers of suspendable-input applications must note that skip_input_data - * is not granted the right to give a suspension return. If the skip extends - * beyond the data currently in the buffer, the buffer can be marked empty so - * that the next read will cause a fill_input_buffer call that can suspend. - * Arranging for additional bytes to be discarded before reloading the input - * buffer is the application writer's problem. - */ - -static void -skip_input_data (j_decompress_ptr cinfo, long num_bytes) -{ - InputBuffer *src = (InputBuffer *) cinfo->src; - - /* Just skip fwd. - */ - if (num_bytes > 0) { - src->pub.next_input_byte += (size_t) num_bytes; - src->pub.bytes_in_buffer -= (size_t) num_bytes; - } -} - -/* - * An additional method that can be provided by data source modules is the - * resync_to_restart method for error recovery in the presence of RST markers. - * For the moment, this source module just uses the default resync method - * provided by the JPEG library. That method assumes that no backtracking - * is possible. - */ - -/* - * Terminate source --- called by jpeg_finish_decompress - * after all data has been read. Often a no-op. - * - * NB: *not* called by jpeg_abort or jpeg_destroy; surrounding - * application must deal with any cleanup that should happen even - * for error exit. - */ - -static void -term_source (j_decompress_ptr cinfo) -{ - /* no work necessary here */ -} - -/* - * Prepare for input from a memory buffer. The caller needs to free the - * buffer after decompress is done, we don't take ownership. - */ - -static void -buf_source (j_decompress_ptr cinfo, void *buf, size_t len) -{ - InputBuffer *src; - - /* The source object and input buffer are made permanent so that a series - * of JPEG images can be read from the same file by calling jpeg_stdio_src - * only before the first one. (If we discarded the buffer at the end of - * one image, we'd likely lose the start of the next one.) - * This makes it unsafe to use this manager and a different source - * manager serially with the same JPEG object. Caveat programmer. - */ - if (cinfo->src == NULL) { /* first time for this JPEG object? */ - cinfo->src = (struct jpeg_source_mgr *) - (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, - sizeof(InputBuffer)); - src = (InputBuffer *) cinfo->src; - src->buf = buf; - src->len = len; - } - - src = (InputBuffer *) cinfo->src; - src->pub.init_source = init_source; - src->pub.fill_input_buffer = fill_input_buffer; - src->pub.skip_input_data = skip_input_data; - src->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */ - src->pub.term_source = term_source; - src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */ - src->pub.next_input_byte = NULL; /* until buffer loaded */ -} - -/** - * im_bufjpeg2vips: - * @buf: memory area to load - * @len: size of memory area - * @out: image to write - * @header_only: set to just read the header - * - * Read a JPEG-formatted memory block into a VIPS image. It can read most - * 8-bit JPEG images, including CMYK and YCbCr. - * - * This function is handy for processing JPEG image thumbnails. - * - * See also: #VipsFormat, im_jpeg2vips(). - * - * Returns: 0 on success, -1 on error. - */ int im_bufjpeg2vips( void *buf, size_t len, IMAGE *out, gboolean header_only ) { - struct jpeg_decompress_struct cinfo; - ErrorManager eman; - int result; - gboolean invert_pels; + VipsImage *t; - /* Make jpeg dcompression object. - */ - cinfo.err = jpeg_std_error( &eman.pub ); - eman.pub.error_exit = new_error_exit; - eman.pub.output_message = new_output_message; - eman.fp = NULL; - if( setjmp( eman.jmp ) ) { - /* Here for longjmp() from new_error_exit(). - */ - jpeg_destroy_decompress( &cinfo ); + /* header_only is now automatic ... this call will only decompress on + * pixel access. + */ + if( vips_call( "jpegload_buffer", buf, len, &t, NULL ) ) + return( -1 ); + if( vips_image_write( t, out ) ) { + g_object_unref( t ); return( -1 ); } - jpeg_create_decompress( &cinfo ); + g_object_unref( t ); - /* Make input. - */ - buf_source( &cinfo, buf, len ); - - /* Need to read in APP1 (EXIF metadata) and APP2 (ICC profile). - */ - jpeg_save_markers( &cinfo, JPEG_APP0 + 1, 0xffff ); - jpeg_save_markers( &cinfo, JPEG_APP0 + 2, 0xffff ); - - /* Convert! - */ - result = read_jpeg_header( &cinfo, out, &invert_pels, 1 ); - if( !header_only && !result ) - result = read_jpeg_image( &cinfo, out, invert_pels ); - - /* Close and tidy. - */ - jpeg_destroy_decompress( &cinfo ); - - return( result ); + return( 0 ); } static int @@ -1197,12 +173,6 @@ isjpeg( const char *filename ) return( 0 ); } -static int -jpeg2vips_header( const char *name, IMAGE *out ) -{ - return( jpeg2vips( name, out, TRUE ) ); -} - static const char *jpeg_suffs[] = { ".jpg", ".jpeg", ".jpe", NULL }; /* jpeg format adds no new members. @@ -1220,8 +190,7 @@ vips_format_jpeg_class_init( VipsFormatJpegClass *class ) object_class->description = _( "JPEG" ); format_class->is_a = isjpeg; - format_class->header = jpeg2vips_header; - format_class->load = im_jpeg2vips; + format_class->header = im_jpeg2vips; format_class->save = im_vips2jpeg; format_class->suffs = jpeg_suffs; } @@ -1232,5 +201,3 @@ vips_format_jpeg_init( VipsFormatJpeg *object ) } G_DEFINE_TYPE( VipsFormatJpeg, vips_format_jpeg, VIPS_TYPE_FORMAT ); - -#endif /*HAVE_JPEG*/ diff --git a/libvips/format/im_vips2jpeg.c b/libvips/format/im_vips2jpeg.c index 7da77bdf..d938e049 100644 --- a/libvips/format/im_vips2jpeg.c +++ b/libvips/format/im_vips2jpeg.c @@ -46,6 +46,8 @@ * - update Orientation as well * 3/11/11 * - rebuild exif tags from coded metadata values + * 30/11/11 + * - now just a stub calling the new system */ /* @@ -85,72 +87,11 @@ #endif /*HAVE_CONFIG_H*/ #include -#ifndef HAVE_JPEG - -#include - -int -im_vips2jpeg( IMAGE *in, const char *filename ) -{ - im_error( "im_vips2jpeg", "%s", - _( "JPEG support disabled" ) ); - - return( -1 ); -} - -int -im_vips2bufjpeg( IMAGE *in, IMAGE *out, int qfac, char **obuf, int *olen ) -{ - im_error( "im_vips2bufjpeg", "%s", - _( "JPEG support disabled" ) ); - - return( -1 ); -} - -int -im_vips2mimejpeg( IMAGE *in, int qfac ) -{ - im_error( "im_vips2mimejpeg", "%s", - _( "JPEG support disabled" ) ); - - return( -1 ); -} - -#else /*HAVE_JPEG*/ - -#include -#include #include -#include - -#ifdef HAVE_EXIF -#ifdef UNTAGGED_EXIF -#include -#include -#include -#include -#else /*!UNTAGGED_EXIF*/ -#include -#include -#include -#include -#endif /*UNTAGGED_EXIF*/ -#endif /*HAVE_EXIF*/ +#include #include #include -#include -#include - -/* jpeglib includes jconfig.h, which can define HAVE_STDLIB_H ... which we - * also define. Make sure it's turned off. - */ -#ifdef HAVE_STDLIB_H -#undef HAVE_STDLIB_H -#endif /*HAVE_STDLIB_H*/ - -#include -#include /* Convert to a saveable format. * @@ -162,6 +103,11 @@ im_vips2mimejpeg( IMAGE *in, int qfac ) * * Need to im_close() the result IMAGE. */ + +/* Kept for back compat while we swap the rest of VipsFormat to the new + * system. + */ + IMAGE * im__convert_saveable( IMAGE *in, im__saveable_t saveable, int format_table[10] ) @@ -363,770 +309,14 @@ im__convert_saveable( IMAGE *in, return( out ); } -/* Define a new error handler for when we bomb out. - */ -typedef struct { - /* Public fields. - */ - struct jpeg_error_mgr pub; - - /* Private stuff for us. - */ - jmp_buf jmp; /* longjmp() here to get back to VIPS */ - FILE *fp; /* fclose() if non-NULL */ -} ErrorManager; - -/* New output message method - send to VIPS. - */ -METHODDEF(void) -new_output_message( j_common_ptr cinfo ) -{ - char buffer[JMSG_LENGTH_MAX]; - - (*cinfo->err->format_message)( cinfo, buffer ); - im_error( "vips_jpeg", _( "%s" ), buffer ); - -#ifdef DEBUG - printf( "vips_jpeg.c: new_output_message: \"%s\"\n", buffer ); -#endif /*DEBUG*/ -} - -/* New error_exit handler. - */ -METHODDEF(void) -new_error_exit( j_common_ptr cinfo ) -{ - ErrorManager *eman = (ErrorManager *) cinfo->err; - -#ifdef DEBUG - printf( "vips_jpeg.c: new_error_exit\n" ); -#endif /*DEBUG*/ - - /* Close the fp if necessary. - */ - if( eman->fp ) { - (void) fclose( eman->fp ); - eman->fp = NULL; - } - - /* Send the error message to VIPS. This method is overridden above. - */ - (*cinfo->err->output_message)( cinfo ); - - /* Jump back. - */ - longjmp( eman->jmp, 1 ); -} - -/* What we track during a JPEG write. - */ -typedef struct { - IMAGE *in; - struct jpeg_compress_struct cinfo; - ErrorManager eman; - JSAMPROW *row_pointer; - char *profile_bytes; - unsigned int profile_length; - IMAGE *inverted; -} Write; - -static void -write_destroy( Write *write ) -{ - jpeg_destroy_compress( &write->cinfo ); - IM_FREEF( im_close, write->in ); - IM_FREEF( fclose, write->eman.fp ); - IM_FREE( write->row_pointer ); - IM_FREE( write->profile_bytes ); - IM_FREEF( im_close, write->inverted ); - im_free( write ); -} - -#define UC IM_BANDFMT_UCHAR - -/* Type promotion for save ... just always go to uchar. - */ -static int bandfmt_jpeg[10] = { -/* UC C US S UI I F X D DX */ - UC, UC, UC, UC, UC, UC, UC, UC, UC, UC -}; - -static Write * -write_new( IMAGE *in ) -{ - Write *write; - - if( !(write = IM_NEW( NULL, Write )) ) - return( NULL ); - memset( write, 0, sizeof( Write ) ); - - if( !(write->in = im__convert_saveable( in, - IM__RGB_CMYK, bandfmt_jpeg )) ) { - im_error( "im_vips2jpeg", - "%s", _( "unable to convert to saveable format" ) ); - write_destroy( write ); - return( NULL ); - } - write->row_pointer = NULL; - write->cinfo.err = jpeg_std_error( &write->eman.pub ); - write->eman.pub.error_exit = new_error_exit; - write->eman.pub.output_message = new_output_message; - write->eman.fp = NULL; - write->profile_bytes = NULL; - write->profile_length = 0; - write->inverted = NULL; - - return( write ); -} - -#ifdef HAVE_EXIF -static void -vips_exif_set_int( ExifData *ed, - ExifEntry *entry, unsigned long component, void *data ) -{ - int value = *((int *) data); - - ExifByteOrder bo; - size_t sizeof_component; - size_t offset = component; - - if( entry->components <= component ) { - VIPS_DEBUG_MSG( "vips_exif_set_int: too few components\n" ); - return; - } - - /* Wait until after the component check to make sure we cant get /0. - */ - bo = exif_data_get_byte_order( ed ); - sizeof_component = entry->size / entry->components; - offset = component * sizeof_component; - - VIPS_DEBUG_MSG( "vips_exif_set_int: %s = %d\n", - exif_tag_get_title( entry->tag ), value ); - - if( entry->format == EXIF_FORMAT_SHORT ) - exif_set_short( entry->data + offset, bo, value ); - else if( entry->format == EXIF_FORMAT_SSHORT ) - exif_set_sshort( entry->data + offset, bo, value ); - else if( entry->format == EXIF_FORMAT_LONG ) - exif_set_long( entry->data + offset, bo, value ); - else if( entry->format == EXIF_FORMAT_SLONG ) - exif_set_slong( entry->data + offset, bo, value ); -} - -static void -vips_exif_set_double( ExifData *ed, - ExifEntry *entry, unsigned long component, void *data ) -{ - double value = *((double *) data); - - ExifByteOrder bo; - size_t sizeof_component; - size_t offset; - - if( entry->components <= component ) { - VIPS_DEBUG_MSG( "vips_exif_set_int: too few components\n" ); - return; - } - - /* Wait until after the component check to make sure we cant get /0. - */ - bo = exif_data_get_byte_order( ed ); - sizeof_component = entry->size / entry->components; - offset = component * sizeof_component; - - VIPS_DEBUG_MSG( "vips_exif_set_double: %s = %g\n", - exif_tag_get_title( entry->tag ), value ); - - if( entry->format == EXIF_FORMAT_RATIONAL ) { - ExifRational rational; - unsigned int scale; - - /* We scale up to fill uint32, then set that as the - * denominator. Try to avoid generating 0. - */ - scale = (int) ((UINT_MAX - 1000) / value); - scale = scale == 0 ? 1 : scale; - rational.numerator = value * scale; - rational.denominator = scale; - - exif_set_rational( entry->data + offset, bo, rational ); - } - else if( entry->format == EXIF_FORMAT_SRATIONAL ) { - ExifSRational rational; - int scale; - - scale = (int) ((INT_MAX - 1000) / value); - scale = scale == 0 ? 1 : scale; - rational.numerator = value * scale; - rational.denominator = scale; - - exif_set_srational( entry->data + offset, bo, rational ); - } -} - -typedef void (*write_fn)( ExifData *ed, - ExifEntry *entry, unsigned long component, void *data ); - -/* Write a component in a tag everywhere it appears. - */ -static int -write_tag( ExifData *ed, - ExifTag tag, ExifFormat format, write_fn fn, void *data ) -{ - int found; - int i; - - found = 0; - for( i = 0; i < EXIF_IFD_COUNT; i++ ) { - ExifEntry *entry; - - if( (entry = exif_content_get_entry( ed->ifd[i], tag )) && - entry->format == format ) { - fn( ed, entry, 0, data ); - found = 1; - } - } - - if( !found ) { - /* There was no tag we could update ... make one in ifd[0]. - */ - ExifEntry *entry; - - entry = exif_entry_new(); - - /* tag must be set before calling exif_content_add_entry. - */ - entry->tag = tag; - - exif_content_add_entry( ed->ifd[0], entry ); - exif_entry_initialize( entry, tag ); - exif_entry_unref( entry ); - - fn( ed, entry, 0, data ); - } - - return( 0 ); -} - -/* This is different, we set the xres/yres from the vips header rather than - * from the exif tags on the image metadata. - */ -static int -set_exif_resolution( ExifData *ed, IMAGE *im ) -{ - double xres, yres; - int unit; - - /* Always save as inches - more progs support it for read. - */ - xres = im->Xres * 25.4; - yres = im->Yres * 25.4; - unit = 2; - - if( write_tag( ed, EXIF_TAG_X_RESOLUTION, EXIF_FORMAT_RATIONAL, - vips_exif_set_double, (void *) &xres ) || - write_tag( ed, EXIF_TAG_Y_RESOLUTION, EXIF_FORMAT_RATIONAL, - vips_exif_set_double, (void *) &yres ) || - write_tag( ed, EXIF_TAG_RESOLUTION_UNIT, EXIF_FORMAT_SHORT, - vips_exif_set_int, (void *) &unit ) ) { - im_error( "im_jpeg2vips", - "%s", _( "error setting JPEG resolution" ) ); - return( -1 ); - } - - return( 0 ); -} - -/* See also vips_exif_to_s() ... keep in sync. - */ -static void -vips_exif_from_s( ExifData *ed, ExifEntry *entry, const char *value ) -{ - unsigned long i; - const char *p; - - if( entry->format != EXIF_FORMAT_SHORT && - entry->format != EXIF_FORMAT_SSHORT && - entry->format != EXIF_FORMAT_LONG && - entry->format != EXIF_FORMAT_SLONG && - entry->format != EXIF_FORMAT_RATIONAL && - entry->format != EXIF_FORMAT_SRATIONAL ) - return; - if( entry->components >= 10 ) - return; - - /* Skip any leading spaces. - */ - p = value; - while( *p == ' ' ) - p += 1; - - for( i = 0; i < entry->components; i++ ) { - if( entry->format == EXIF_FORMAT_SHORT || - entry->format == EXIF_FORMAT_SSHORT || - entry->format == EXIF_FORMAT_LONG || - entry->format == EXIF_FORMAT_SLONG ) { - int value = atof( p ); - - vips_exif_set_int( ed, entry, i, &value ); - } - else if( entry->format == EXIF_FORMAT_RATIONAL || - entry->format == EXIF_FORMAT_SRATIONAL ) { - double value = g_ascii_strtod( p, NULL ); - - vips_exif_set_double( ed, entry, i, &value ); - } - - /* Skip to the next set of spaces, then to the beginning of - * the next item. - */ - while( *p && *p != ' ' ) - p += 1; - while( *p == ' ' ) - p += 1; - if( !*p ) - break; - } -} - -typedef struct _VipsExif { - VipsImage *image; - ExifData *ed; -} VipsExif; - -static void -vips_exif_update_entry( ExifEntry *entry, VipsExif *ve ) -{ - char name[256]; - char *value; - - im_snprintf( name, 256, "exif-%s", exif_tag_get_title( entry->tag ) ); - if( !vips_image_get_string( ve->image, name, &value ) ) - vips_exif_from_s( ve->ed, entry, value ); -} - -static void -vips_exif_update_content( ExifContent *content, VipsExif *ve ) -{ - exif_content_foreach_entry( content, - (ExifContentForeachEntryFunc) vips_exif_update_entry, ve ); -} - -static void -vips_exif_update( ExifData *ed, VipsImage *image ) -{ - VipsExif ve; - - VIPS_DEBUG_MSG( "vips_exif_update: \n" ); - - ve.image = image; - ve.ed = ed; - exif_data_foreach_content( ed, - (ExifDataForeachContentFunc) vips_exif_update_content, &ve ); -} - - -#endif /*HAVE_EXIF*/ - -static int -write_exif( Write *write ) -{ - unsigned char *data; - size_t data_length; - unsigned int idl; -#ifdef HAVE_EXIF - ExifData *ed; - - /* Either parse from the embedded EXIF, or if there's none, make - * some fresh EXIF we can write the resolution to. - */ - if( im_header_get_typeof( write->in, IM_META_EXIF_NAME ) ) { - if( im_meta_get_blob( write->in, IM_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( set_exif_resolution( 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 ) { - im_error( "im_jpeg2vips", "%s", _( "error saving EXIF" ) ); - exif_data_free( ed ); - return( -1 ); - } - data_length = idl; - -#ifdef DEBUG - printf( "im_vips2jpeg: 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( im_header_get_typeof( write->in, IM_META_EXIF_NAME ) ) { - if( im_meta_get_blob( write->in, IM_META_EXIF_NAME, - (void *) &data, &data_length ) ) - return( -1 ); - -#ifdef DEBUG - printf( "im_vips2jpeg: attaching %zd bytes of EXIF\n", - data_length ); -#endif /*DEBUG*/ - - jpeg_write_marker( &write->cinfo, JPEG_APP0 + 1, - data, data_length ); - } -#endif /*!HAVE_EXIF*/ - - return( 0 ); -} - -static int -write_xmp( Write *write ) -{ - unsigned char *data; - size_t data_length; - - /* No libexif ... just copy the embedded EXIF over. - */ - if( im_header_get_typeof( write->in, VIPS_META_XMP_NAME ) ) { - if( im_meta_get_blob( write->in, VIPS_META_XMP_NAME, - (void *) &data, &data_length ) ) - return( -1 ); - -#ifdef DEBUG - printf( "im_vips2jpeg: attaching %zd bytes of XMP\n", - data_length ); -#endif /*DEBUG*/ - - jpeg_write_marker( &write->cinfo, JPEG_APP0 + 1, - data, data_length ); - } - - return( 0 ); -} - -/* ICC writer from lcms, slight tweaks. - */ - -#define ICC_MARKER (JPEG_APP0 + 2) /* JPEG marker code for ICC */ -#define ICC_OVERHEAD_LEN 14 /* size of non-profile data in APP2 */ -#define MAX_BYTES_IN_MARKER 65533 /* maximum data len of a JPEG marker */ -#define MAX_DATA_BYTES_IN_MARKER (MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN) - -/* - * This routine writes the given ICC profile data into a JPEG file. - * It *must* be called AFTER calling jpeg_start_compress() and BEFORE - * the first call to jpeg_write_scanlines(). - * (This ordering ensures that the APP2 marker(s) will appear after the - * SOI and JFIF or Adobe markers, but before all else.) - */ - -static void -write_profile_data (j_compress_ptr cinfo, - const JOCTET *icc_data_ptr, - unsigned int icc_data_len) -{ - unsigned int num_markers; /* total number of markers we'll write */ - int cur_marker = 1; /* per spec, counting starts at 1 */ - unsigned int length; /* number of bytes to write in this marker */ - - /* rounding up will fail for length == 0 */ - g_assert( icc_data_len > 0 ); - - /* Calculate the number of markers we'll need, rounding up of course */ - num_markers = (icc_data_len + MAX_DATA_BYTES_IN_MARKER - 1) / - MAX_DATA_BYTES_IN_MARKER; - - while (icc_data_len > 0) { - /* length of profile to put in this marker */ - length = icc_data_len; - if (length > MAX_DATA_BYTES_IN_MARKER) - length = MAX_DATA_BYTES_IN_MARKER; - icc_data_len -= length; - - /* Write the JPEG marker header (APP2 code and marker length) */ - jpeg_write_m_header(cinfo, ICC_MARKER, - (unsigned int) (length + ICC_OVERHEAD_LEN)); - - /* Write the marker identifying string "ICC_PROFILE" (null-terminated). - * We code it in this less-than-transparent way so that the code works - * even if the local character set is not ASCII. - */ - jpeg_write_m_byte(cinfo, 0x49); - jpeg_write_m_byte(cinfo, 0x43); - jpeg_write_m_byte(cinfo, 0x43); - jpeg_write_m_byte(cinfo, 0x5F); - jpeg_write_m_byte(cinfo, 0x50); - jpeg_write_m_byte(cinfo, 0x52); - jpeg_write_m_byte(cinfo, 0x4F); - jpeg_write_m_byte(cinfo, 0x46); - jpeg_write_m_byte(cinfo, 0x49); - jpeg_write_m_byte(cinfo, 0x4C); - jpeg_write_m_byte(cinfo, 0x45); - jpeg_write_m_byte(cinfo, 0x0); - - /* Add the sequencing info */ - jpeg_write_m_byte(cinfo, cur_marker); - jpeg_write_m_byte(cinfo, (int) num_markers); - - /* Add the profile data */ - while (length--) { - jpeg_write_m_byte(cinfo, *icc_data_ptr); - icc_data_ptr++; - } - cur_marker++; - } -} - -/* Write an ICC Profile from a file into the JPEG stream. - */ -static int -write_profile_file( Write *write, const char *profile ) -{ - if( !(write->profile_bytes = - im__file_read_name( profile, VIPS_ICC_DIR, - &write->profile_length )) ) - return( -1 ); - write_profile_data( &write->cinfo, - (JOCTET *) write->profile_bytes, write->profile_length ); - -#ifdef DEBUG - printf( "im_vips2jpeg: attached profile \"%s\"\n", profile ); -#endif /*DEBUG*/ - - return( 0 ); -} - -static int -write_profile_meta( Write *write ) -{ - void *data; - size_t data_length; - - if( im_meta_get_blob( write->in, IM_META_ICC_NAME, - &data, &data_length ) ) - return( -1 ); - - write_profile_data( &write->cinfo, data, data_length ); - -#ifdef DEBUG - printf( "im_vips2jpeg: attached %zd byte profile from VIPS header\n", - data_length ); -#endif /*DEBUG*/ - - return( 0 ); -} - -static int -write_jpeg_block( REGION *region, Rect *area, void *a ) -{ - Write *write = (Write *) a; - int i; - - for( i = 0; i < area->height; i++ ) - write->row_pointer[i] = (JSAMPROW) - IM_REGION_ADDR( region, 0, area->top + i ); - - /* We are running in a background thread. We need to catch any - * longjmp()s from jpeg_write_scanlines() here. - */ - if( setjmp( write->eman.jmp ) ) - return( -1 ); - - jpeg_write_scanlines( &write->cinfo, write->row_pointer, area->height ); - - return( 0 ); -} - -/* Write a VIPS image to a JPEG compress struct. - */ -static int -write_vips( Write *write, int qfac, const char *profile ) -{ - IMAGE *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 == IM_BANDFMT_UCHAR ); - g_assert( in->Coding == IM_CODING_NONE ); - g_assert( in->Bands == 1 || in->Bands == 3 || in->Bands == 4 ); - - /* Check input image. - */ - if( im_pincheck( in ) ) - return( -1 ); - if( qfac < 0 || qfac > 100 ) { - im_error( "im_vips2jpeg", - "%s", _( "qfac should be in 0-100" ) ); - 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 == IM_TYPE_CMYK ) { - space = JCS_CMYK; - /* IJG always sets an Adobe marker, so we should invert CMYK. - */ - if( !(write->inverted = im_open( "vips2jpeg_invert", "p" )) || - im_invert( in, write->inverted ) ) - 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 = IM_ARRAY( NULL, in->Ysize, JSAMPROW )) ) - return( -1 ); - - /* Rest to default. - */ - jpeg_set_defaults( &write->cinfo ); - jpeg_set_quality( &write->cinfo, qfac, TRUE ); - - /* Build compress tables. - */ - jpeg_start_compress( &write->cinfo, TRUE ); - - /* Write any APP markers we need. - */ - if( write_exif( write ) ) - return( -1 ); - - if( write_xmp( write ) ) - 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 && - im_header_get_typeof( in, IM_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 ); -} - -/** - * im_vips2jpeg: - * @in: image to save - * @filename: file to write to - * - * Write a VIPS image to a file as JPEG. - * - * You can embed options in the filename. They have the form: - * - * |[ - * filename.jpg:compression,profile - * ]| - * - * - * - * - * compression - * Compress with this quality factor. Default 75. - * - * - * - * - * profile - * Attach this ICC profile. For example, "fred.jpg:,/home/john/srgb.icc" will - * embed the profile stored in the file "/home/john/srgb.icc" in the JPEG - * image. This does not affect the pixels which are written, just the way - * they are tagged. You can use the special string "none" to mean - * "don't attach a profile". - * - * - * - * - * If no profile is specified in the save string and the VIPS header - * contains an ICC profile named IM_META_ICC_NAME ("icc-profile-data"), the - * profile from the VIPS header will be attached. - * - * The image is automatically converted to RGB, Monochrome or CMYK before - * saving. Any metadata attached to the image is saved as EXIF, if possible. - * - * Example: - * - * |[ - * im_vips2jpeg( in, "fred.jpg:99,none" ); - * ]| - * - * Will write "fred.jpg" at high-quality with no ICC profile. - * - * See also: #VipsFormat, im_jpeg2vips(). - * - * Returns: 0 on success, -1 on error. - */ int im_vips2jpeg( IMAGE *in, const char *filename ) { - Write *write; int qfac = 75; + + /* profile has to default to NULL, meaning "no param". If we default + * to "none" we will not attach the profile from the metadata. + */ char *profile = NULL; char *p, *q; @@ -1154,377 +344,28 @@ im_vips2jpeg( IMAGE *in, const char *filename ) return( -1 ); } - if( !(write = write_new( in )) ) - return( -1 ); - - if( setjmp( write->eman.jmp ) ) { - /* Here for longjmp() from new_error_exit(). - */ - write_destroy( write ); - - return( -1 ); - } - - /* Can't do this in write_new(), has to be after we've made the - * setjmp(). - */ - jpeg_create_compress( &write->cinfo ); - - /* Make output. - */ - if( !(write->eman.fp = im__file_open_write( name, FALSE )) ) { - write_destroy( write ); - return( -1 ); - } - jpeg_stdio_dest( &write->cinfo, write->eman.fp ); - - /* Convert! - */ - if( write_vips( write, qfac, profile ) ) { - write_destroy( write ); - return( -1 ); - } - write_destroy( write ); - - return( 0 ); + return( vips_jpegsave( in, name, + "Q", qfac, "profile", profile, NULL ) ); } -/* We can't predict how large the output buffer we need is, because we might - * need space for ICC profiles and stuff. So we write to a linked list of mem - * buffers and add a new one as they fill. - */ - -#define BUFFER_SIZE (10000) - -/* A buffer. - */ -typedef struct _Block { - j_compress_ptr cinfo; - - struct _Block *first; - struct _Block *next; - - JOCTET *data; /* Allocated area */ - int size; /* Max size */ - int used; /* How much has been used */ -} Block; - -static Block * -block_new( j_compress_ptr cinfo ) -{ - Block *block; - - block = (Block *) (*cinfo->mem->alloc_large) - ( (j_common_ptr) cinfo, JPOOL_IMAGE, sizeof( Block ) ); - - block->cinfo = cinfo; - block->first = block; - block->next = NULL; - block->data = (JOCTET *) (*cinfo->mem->alloc_large) - ( (j_common_ptr) cinfo, JPOOL_IMAGE, BUFFER_SIZE ); - block->size = BUFFER_SIZE; - block->used = 0; - - return( block ); -} - -static Block * -block_last( Block *block ) -{ - while( block->next ) - block = block->next; - - return( block ); -} - -static Block * -block_append( Block *block ) -{ - Block *new; - - g_assert( block ); - - new = block_new( block->cinfo ); - new->first = block->first; - block_last( block )->next = new; - - return( new ); -} - -static int -block_length( Block *block ) -{ - int len; - - len = 0; - for( block = block->first; block; block = block->next ) - len += block->used; - - return( len ); -} - -static void -block_copy( Block *block, void *dest ) -{ - JOCTET *p; - - p = dest; - for( block = block->first; block; block = block->next ) { - memcpy( p, block->data, block->used ); - p += block->used; - } -} - -#ifdef DEBUG -static void -block_print( Block *block ) -{ - int i; - - printf( "total length = %d\n", block_length( block ) ); - printf( "set of blocks:\n" ); - - i = 0; - for( block = block->first; block; block = block->next ) { - printf( "%d) %p, first = %p, next = %p" - "\t data = %p, size = %d, used = %d\n", - i, block, block->first, block->next, - block->data, block->size, block->used ); - i += 1; - } -} -#endif /*DEBUG*/ - -/* Just like the above, but we write to a memory buffer. - * - * A memory buffer for the compressed image. - */ -typedef struct { - /* Public jpeg fields. - */ - struct jpeg_destination_mgr pub; - - /* Private stuff during write. - */ - - /* Build the output area here in chunks. - */ - Block *block; - - /* Copy the compressed area here. - */ - IMAGE *out; /* Allocate relative to this */ - char **obuf; /* Allocated buffer, and size */ - int *olen; -} OutputBuffer; - -/* Init dest method. - */ -METHODDEF(void) -init_destination( j_compress_ptr cinfo ) -{ - OutputBuffer *buf = (OutputBuffer *) cinfo->dest; - - /* Allocate relative to the image we are writing .. freed when we junk - * this output. - */ - buf->block = block_new( cinfo ); - - /* Set buf pointers for library. - */ - buf->pub.next_output_byte = buf->block->data; - buf->pub.free_in_buffer = buf->block->size; -} - -/* Buffer full method ... allocate a new output block. - */ -METHODDEF(boolean) -empty_output_buffer( j_compress_ptr cinfo ) -{ - OutputBuffer *buf = (OutputBuffer *) cinfo->dest; - - /* Record how many bytes we used. empty_output_buffer() is always - * called when the buffer is exactly full. - */ - buf->block->used = buf->block->size; - - /* New block and reset. - */ - buf->block = block_append( buf->block ); - buf->pub.next_output_byte = buf->block->data; - buf->pub.free_in_buffer = buf->block->size; - - /* TRUE means we've made some more space. - */ - return( 1 ); -} - -/* Cleanup. Copy the set of blocks out as a big lump. - */ -METHODDEF(void) -term_destination( j_compress_ptr cinfo ) -{ - OutputBuffer *buf = (OutputBuffer *) cinfo->dest; - - int len; - void *obuf; - - /* Record the number of bytes we wrote in the final buffer. - * pub.free_in_buffer is valid here. - */ - buf->block->used = buf->block->size - buf->pub.free_in_buffer; - -#ifdef DEBUG - block_print( buf->block ); -#endif /*DEBUG*/ - - /* ... and we can count up our buffers now. - */ - len = block_length( buf->block ); - - /* Allocate and copy to the output area. - */ - if( !(obuf = im_malloc( buf->out, len )) ) - ERREXIT( cinfo, JERR_FILE_WRITE ); - *(buf->obuf) = obuf; - *(buf->olen) = len; - - block_copy( buf->block, obuf ); -} - -/* Set dest to one of our objects. - */ -static void -buf_dest( j_compress_ptr cinfo, IMAGE *out, char **obuf, int *olen ) -{ - OutputBuffer *buf; - - /* The destination object is made permanent so that multiple JPEG - * images can be written to the same file without re-executing - * jpeg_stdio_dest. This makes it dangerous to use this manager and - * a different destination manager serially with the same JPEG object, - * because their private object sizes may be different. - * - * Caveat programmer. - */ - if( !cinfo->dest ) { /* first time for this JPEG object? */ - cinfo->dest = (struct jpeg_destination_mgr *) - (*cinfo->mem->alloc_small) - ( (j_common_ptr) cinfo, JPOOL_PERMANENT, - sizeof( OutputBuffer ) ); - } - - buf = (OutputBuffer *) cinfo->dest; - buf->pub.init_destination = init_destination; - buf->pub.empty_output_buffer = empty_output_buffer; - buf->pub.term_destination = term_destination; - - /* Save output parameters. - */ - buf->out = out; - buf->obuf = obuf; - buf->olen = olen; -} - -/** - * im_vips2bufjpeg: - * @in: image to save - * @out: allocate output buffer local to this - * @qfac: JPEG quality factor - * @obuf: return output buffer here - * @olen: return output length here - * - * As im_vips2jpeg(), but save as a memory buffer. The memory is allocated - * local to @out (that is, when @out is closed the memory will be released, - * pass %NULL to release yourself). - * - * The address of the buffer is returned in @obuf, the length of the buffer in - * @olen. @olen should really be a size_t rather than an int :-( - * - * See also: #VipsFormat, im_vips2jpeg(). - * - * Returns: 0 on success, -1 on error. - */ int im_vips2bufjpeg( IMAGE *in, IMAGE *out, int qfac, char **obuf, int *olen ) { - Write *write; + size_t len; - if( !(write = write_new( in )) ) + if( vips_jpegsave_buffer( in, (void **) obuf, &len, "Q", qfac, NULL ) ) return( -1 ); + im_add_callback( out, "close", + (im_callback_fn) vips_free, obuf, NULL ); - /* Clear output parameters. - */ - *obuf = NULL; - *olen = 0; - - /* Make jpeg compression object. - */ - if( setjmp( write->eman.jmp ) ) { - /* Here for longjmp() from new_error_exit(). - */ - write_destroy( write ); - - return( -1 ); - } - jpeg_create_compress( &write->cinfo ); - - /* Attach output. - */ - buf_dest( &write->cinfo, out, obuf, olen ); - - /* Convert! - */ - if( write_vips( write, qfac, NULL ) ) { - write_destroy( write ); - - return( -1 ); - } - write_destroy( write ); + if( olen ) + *olen = len; return( 0 ); } -/** - * im_vips2mimejpeg: - * @in: image to save - * @qfac: JPEG quality factor - * - * As im_vips2jpeg(), but save as a mime jpeg on stdout. - * - * See also: #VipsFormat, im_vips2jpeg(). - * - * Returns: 0 on success, -1 on error. - */ int im_vips2mimejpeg( IMAGE *in, int qfac ) { - IMAGE *base; - int len; - char *buf; - - if( !(base = im_open( "im_vips2mimejpeg:1", "p" )) ) - return( -1 ); - if( im_vips2bufjpeg( in, base, qfac, &buf, &len ) ) { - im_close( base ); - return( -1 ); - } - - /* Write as a MIME type. - */ - printf( "Content-length: %d\r\n", len ); - printf( "Content-type: image/jpeg\r\n" ); - printf( "\r\n" ); - if( fwrite( buf, sizeof( char ), len, stdout ) != (size_t) len ) { - im_error( "im_vips2mimejpeg", - "%s", _( "error writing output" ) ); - return( -1 ); - } - - fflush( stdout ); - im_close( base ); - - return( 0 ); + return( vips_jpegsave_mime( in, "Q", qfac, NULL ) ); } - -#endif /*HAVE_JPEG*/ diff --git a/libvips/include/vips/foreign.h b/libvips/include/vips/foreign.h index cadae687..04024f1d 100644 --- a/libvips/include/vips/foreign.h +++ b/libvips/include/vips/foreign.h @@ -229,6 +229,13 @@ int vips_foreign_write( VipsImage *in, const char *filename, ... ); void vips_foreign_operation_init( void ); +int vips_jpegload( const char *filename, VipsImage **out, ... ); +int vips_jpegload_buffer( void *buf, size_t len, VipsImage **out, ... ); + +int vips_jpegsave( VipsImage *in, const char *filename, ... ); +int vips_jpegsave_buffer( VipsImage *in, void **buf, size_t *len, ... ); +int vips_jpegsave_mime( VipsImage *in, ... ); + #ifdef __cplusplus } #endif /*__cplusplus*/ diff --git a/libvips/iofuncs/cache.c b/libvips/iofuncs/cache.c index 19c9f25e..c5160656 100644 --- a/libvips/iofuncs/cache.c +++ b/libvips/iofuncs/cache.c @@ -147,22 +147,22 @@ vips_value_hash( GParamSpec *pspec, GValue *value ) else if( generic == G_TYPE_PARAM_STRING ) { const char *s = g_value_get_string( value ); - return( g_str_hash( s ) ); + return( s ? g_str_hash( s ) : 0 ); } else if( generic == G_TYPE_PARAM_BOXED ) { void *p = g_value_get_boxed( value ); - return( g_direct_hash( p ) ); + return( p ? g_direct_hash( p ) : 0 ); } else if( generic == G_TYPE_PARAM_POINTER ) { void *p = g_value_get_pointer( value ); - return( g_direct_hash( p ) ); + return( p ? g_direct_hash( p ) : 0 ); } else if( generic == G_TYPE_PARAM_OBJECT ) { void *p = g_value_get_object( value ); - return( g_direct_hash( p ) ); + return( p ? g_direct_hash( p ) : 0 ); } else { /* Fallback: convert to a string and hash that.