/* Convert 1 or 3-band 8-bit VIPS images to/from JPEG. * * 28/11/03 JC * - better no-overshoot on tile loop * 12/11/04 * - better demand size choice for eval * 30/6/05 JC * - update im_error()/im_warn() * - now loads and saves exif data * 30/7/05 * - now loads ICC profiles * - now saves ICC profiles from the VIPS header * 24/8/05 * - jpeg load sets vips xres/yres from exif, if possible * - jpeg save sets exif xres/yres from vips, if possible * 29/8/05 * - cut from old vips_jpeg.c * 13/10/06 * - add #endif /*HAVE_CONFIG_H*/ #include #ifndef HAVE_JPEG #include int im_jpeg2vips_header( const char *name, IMAGE *out ) { im_error( "im_jpeg2vips_header", _( "JPEG support disabled" ) ); return( -1 ); } int im_jpeg2vips( const char *name, IMAGE *out ) { im_error( "im_jpeg2vips", _( "JPEG support disabled" ) ); return( -1 ); } #else /*HAVE_JPEG*/ #include #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 /* 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 #ifdef WITH_DMALLOC #include #endif /*WITH_DMALLOC*/ /* 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 ); } #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 void attach_exif_entry( ExifEntry *entry, IMAGE *im ) { char name_text[256]; VBuf name; char value_text[256]; VBuf value; char exif_value[256]; im_buf_init_static( &name, name_text, 256 ); im_buf_init_static( &value, value_text, 256 ); im_buf_appendf( &name, "exif-%s", exif_tag_get_title( entry->tag ) ); im_buf_appendf( &value, "%s (%s, %d bytes)", exif_entry_get_value( entry, exif_value, 256 ), exif_format_get_name( entry->format ), entry->size ); /* Can't do anything sensible with the error return. */ (void) im_meta_set_string( im, im_buf_all( &name ), im_buf_all( &value ) ); } static void attach_exif_content( ExifContent *content, IMAGE *im ) { exif_content_foreach_entry( content, (ExifContentForeachEntryFunc) attach_exif_entry, im ); } /* 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; ExifRational rational; if( !(entry = find_entry( ed, tag )) || entry->format != EXIF_FORMAT_RATIONAL || entry->components != 1 ) return( -1 ); rational = exif_get_rational( entry->data, exif_data_get_byte_order( ed ) ); *out = (double) rational.numerator / rational.denominator; return( 0 ); } 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 ); *out = exif_get_short( entry->data, exif_data_get_byte_order( ed ) ); return( 0 ); } 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", _( "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", _( "bad resolution unit" ) ); return; } im->Xres = xres; im->Yres = yres; } #endif /*HAVE_EXIF*/ static int read_exif( IMAGE *im, void *data, int data_length ) { char *data_copy; /* 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 ) { #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? */ exif_data_foreach_content( ed, (ExifDataForeachContentFunc) attach_exif_content, im ); /* Look for resolution fields and use them to set the VIPS * xres/yres fields. */ set_vips_resolution( im, ed ); } exif_data_free( ed ); } #endif /*HAVE_EXIF*/ return( 0 ); } /* Number of app2 sections we can capture. Each one can be 64k, so 640k should * be enough for anyone (haha). */ #define MAX_APP2_SECTIONS (10) /* 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; 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 ); /* Look for EXIF and ICC profile. */ for( p = cinfo->marker_list; p; p = p->next ) { switch( p->marker ) { case JPEG_APP0 + 1: /* EXIF data. */ #ifdef DEBUG printf( "read_jpeg_header: seen %d bytes of EXIF\n", p->data_length ); #endif /*DEBUG*/ if( read_exif( out, p->data, p->data_length ) ) return( -1 ); break; case JPEG_APP0 + 2: /* ICC profile. */ #ifdef DEBUG printf( "read_jpeg_header: seen %d bytes of ICC\n", p->data_length ); #endif /*DEBUG*/ 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: #ifdef DEBUG printf( "read_jpeg_header: seen %d bytes of data\n", p->data_length ); #endif /*DEBUG*/ 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++ ) { if( jpeg_read_scanlines( cinfo, &row_pointer[0], 1 ) != 1 ) { im_error( "im_jpeg2vips", _( "truncated JPEG file" ) ); return( -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. */ static int jpeg2vips( const char *name, IMAGE *out, gboolean header_only ) { char filename[FILENAME_MAX]; char mode[FILENAME_MAX]; char *p, *q; int shrink; struct jpeg_decompress_struct cinfo; ErrorManager eman; FILE *fp; int result; gboolean invert_pels; /* Parse the filename. */ im_filename_split( name, filename, mode ); p = &mode[0]; shrink = 1; if( (q = im_getnextoption( &p )) ) { shrink = atoi( q ); if( shrink != 1 && shrink != 2 && shrink != 4 && shrink != 8 ) { im_error( "im_jpeg2vips", _( "bad shrink factor %d" ), shrink ); return( -1 ); } } /* Make jpeg compression 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 ); return( -1 ); } jpeg_create_decompress( &cinfo ); /* Make input. */ #ifdef BINARY_OPEN if( !(fp = fopen( filename, "rb" )) ) { #else /*BINARY_OPEN*/ if( !(fp = fopen( filename, "r" )) ) { #endif /*BINARY_OPEN*/ jpeg_destroy_decompress( &cinfo ); im_error( "im_jpeg2vips", _( "unable to open \"%s\"" ), filename ); 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 ) { im_warn( "im_jpeg2vips", _( "read gave %ld warnings" ), eman.pub.num_warnings ); im_warn( "im_jpeg2vips", "%s", im_error_buffer() ); } return( result ); } /* Read a JPEG file into a VIPS image. */ int im_jpeg2vips( const char *name, IMAGE *out ) { return( jpeg2vips( name, out, FALSE ) ); } int im_jpeg2vips_header( const char *name, IMAGE *out ) { return( jpeg2vips( name, out, TRUE ) ); } #endif /*HAVE_JPEG*/