/* Convert 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 * 20/4/06 * - auto convert to sRGB/mono for save * 13/10/06 * - add #endif /*HAVE_CONFIG_H*/ #include #ifndef HAVE_JPEG #include int im_vips2jpeg( IMAGE *in, const char *filename ) { im_error( "im_vips2jpeg", _( "JPEG support disabled" ) ); return( -1 ); } int im_vips2bufjpeg( IMAGE *in, IMAGE *out, int qfac, char **obuf, int *olen ) { im_error( "im_vips2bufjpeg", _( "JPEG support disabled" ) ); return( -1 ); } int im_vips2mimejpeg( IMAGE *in, int qfac ) { im_error( "im_vips2mimejpeg", _( "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 #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 ); } /* What we track during a JPEG write. */ typedef struct { IMAGE *in; struct jpeg_compress_struct cinfo; ErrorManager eman; im_threadgroup_t *tg; JSAMPROW *row_pointer; char *profile_bytes; unsigned int profile_length; } Write; static void write_destroy( Write *write ) { jpeg_destroy_compress( &write->cinfo ); IM_FREEF( im_threadgroup_free, write->tg ); IM_FREEF( im_close, write->in ); IM_FREEF( fclose, write->eman.fp ); IM_FREE( write->row_pointer ); IM_FREE( write->profile_bytes ); im_free( write ); } 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 )) ) { im_error( "im_vips2jpeg", _( "unable to convert to saveable format" ) ); write_destroy( write ); return( NULL ); } write->tg = im_threadgroup_create( write->in ); write->row_pointer = IM_ARRAY( NULL, write->tg->nlines, JSAMPROW ); 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; if( !write->tg || !write->row_pointer ) { write_destroy( write ); return( NULL ); } return( write ); } #ifdef HAVE_EXIF static void write_rational( ExifEntry *entry, ExifByteOrder bo, void *data ) { ExifRational *v = (ExifRational *) data; exif_set_rational( entry->data, bo, *v ); } static void write_short( ExifEntry *entry, ExifByteOrder bo, void *data ) { ExifShort *v = (ExifShort *) data; exif_set_short( entry->data, bo, *v ); } typedef void (*write_fn)( ExifEntry *, ExifByteOrder, void * ); static int write_tag( ExifData *ed, ExifTag tag, ExifFormat f, write_fn fn, void *data ) { ExifByteOrder bo; int found; int i; bo = exif_data_get_byte_order( ed ); /* Need to set the tag in all sections which have it :-( */ found = 0; for( i = 0; i < EXIF_IFD_COUNT; i++ ) { ExifEntry *entry; if( (entry = exif_content_get_entry( ed->ifd[i], tag )) && entry->format == f && entry->components == 1 ) { fn( entry, bo, data ); found = 1; } } if( !found ) { /* There was no tag we could update ... make one in ifd[0]. */ ExifEntry *entry; entry = exif_entry_new(); exif_content_add_entry( ed->ifd[0], entry ); exif_entry_initialize( entry, tag ); fn( entry, bo, data ); } return( 0 ); } static int set_exif_resolution( ExifData *ed, IMAGE *im ) { double xres, yres; ExifRational xres_rational, yres_rational; ExifShort unit; /* Always save as inches - more progs support it for read. */ xres = im->Xres * 25.4; yres = im->Yres * 25.4; unit = 2; /* Wow, how dumb, fix this. */ xres_rational.numerator = xres * 100000; xres_rational.denominator = 100000; yres_rational.numerator = yres * 100000; yres_rational.denominator = 100000; if( write_tag( ed, EXIF_TAG_X_RESOLUTION, EXIF_FORMAT_RATIONAL, write_rational, &xres_rational ) || write_tag( ed, EXIF_TAG_Y_RESOLUTION, EXIF_FORMAT_RATIONAL, write_rational, &yres_rational ) || write_tag( ed, EXIF_TAG_RESOLUTION_UNIT, EXIF_FORMAT_SHORT, write_short, &unit ) ) { im_error( "im_jpeg2vips", _( "error setting JPEG resolution" ) ); return( -1 ); } return( 0 ); } #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_type( 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(); /* Set EXIF resolution from VIPS. */ 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", _( "error saving EXIF" ) ); exif_data_free( ed ); return( -1 ); } data_length = idl; #ifdef DEBUG printf( "im_vips2jpeg: attaching %d 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_type( 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 %d bytes of EXIF\n", data_length ); #endif /*DEBUG*/ jpeg_write_marker( &write->cinfo, JPEG_APP0 + 1, data, data_length ); } #endif /*!HAVE_EXIF*/ 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 */ 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, &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 %d byte profile from VIPS header\n", data_length ); #endif /*DEBUG*/ return( 0 ); } static int write_jpeg_block( REGION *region, Rect *area, void *a, void *b ) { Write *write = (Write *) a; int i; /* We are running in a background thread. We need to catch longjmp()s * here instead. */ if( setjmp( write->eman.jmp ) ) return( -1 ); for( i = 0; i < area->height; i++ ) write->row_pointer[i] = (JSAMPROW) IM_REGION_ADDR( region, 0, area->top + i ); 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 = write->in; J_COLOR_SPACE space; /* Should have been converted for save. */ assert( in->BandFmt == IM_BANDFMT_UCHAR ); assert( in->Coding == IM_CODING_NONE ); 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", _( "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; 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; /* 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 ); /* A profile supplied as an argument overrides an embedded profile. */ if( profile && write_profile_file( write, profile ) ) return( -1 ); if( !profile && im_header_get_type( in, IM_META_ICC_NAME ) && write_profile_meta( write ) ) return( -1 ); /* Write data. Note that the write function grabs the longjmp()! */ if( im_wbuffer( write->tg, write_jpeg_block, write, NULL ) ) return( -1 ); /* We have to reinstate the setjmp() before we jpeg_finish_compress(). * No need to destroy the write: our parent does that. */ if( setjmp( write->eman.jmp ) ) return( -1 ); jpeg_finish_compress( &write->cinfo ); return( 0 ); } /* Write a VIPS image to a file as JPEG. */ int im_vips2jpeg( IMAGE *in, const char *filename ) { Write *write; int qfac = 75; char *profile = NULL; char *p, *q; char name[FILENAME_MAX]; char mode[FILENAME_MAX]; char buf[FILENAME_MAX]; /* Parse mode from filename. */ im_filename_split( filename, name, mode ); strcpy( buf, mode ); p = &buf[0]; if( (q = im_getnextoption( &p )) ) { if( strcmp( q, "" ) != 0 ) qfac = atoi( mode ); } if( (q = im_getnextoption( &p )) ) { if( strcmp( q, "" ) != 0 ) profile = q; } if( (q = im_getnextoption( &p )) ) { im_error( "im_vips2jpeg", _( "unknown extra options \"%s\"" ), q ); 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. */ #ifdef BINARY_OPEN if( !(write->eman.fp = fopen( name, "wb" )) ) { #else /*BINARY_OPEN*/ if( !(write->eman.fp = fopen( name, "w" )) ) { #endif /*BINARY_OPEN*/ write_destroy( write ); im_error( "im_vips2jpeg", _( "unable to open \"%s\"" ), name ); 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 ); } /* 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. */ JOCTET *data; /* Allocated area */ int used; /* Number of bytes written so far */ int size; /* Max size */ /* 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; int mx = cinfo->image_width * cinfo->image_height * cinfo->input_components * sizeof( JOCTET ); /* Allocate relative to the image we are writing .. freed when we junk * this output. */ buf->data = (JOCTET *) (*cinfo->mem->alloc_large) ( (j_common_ptr) cinfo, JPOOL_IMAGE, mx ); buf->used = 0; buf->size = mx; /* Set buf pointers for library. */ buf->pub.next_output_byte = buf->data; buf->pub.free_in_buffer = mx; } /* Buffer full method ... should never get this. */ METHODDEF(boolean) empty_output_buffer( j_compress_ptr cinfo ) { /* Not really a file write error, but why not. Should never happen. */ ERREXIT( cinfo, JERR_FILE_WRITE ); return( 0 ); } /* Cleanup. Write entire buffer as a MIME type. */ METHODDEF(void) term_destination( j_compress_ptr cinfo ) { OutputBuffer *buf = (OutputBuffer *) cinfo->dest; int len = buf->size - buf->pub.free_in_buffer; void *obuf; /* Allocate and copy to the VIPS output area. */ if( !(obuf = im_malloc( buf->out, len )) ) ERREXIT( cinfo, JERR_FILE_WRITE ); memcpy( obuf, buf->data, len ); *(buf->obuf) = obuf; *(buf->olen) = len; } /* 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; } /* As above, but save to a buffer. The buffer is allocated relative to out. * On success, buf is set to the output buffer and len to the size of the * compressed image. */ int im_vips2bufjpeg( IMAGE *in, IMAGE *out, int qfac, char **obuf, int *olen ) { Write *write; if( !(write = write_new( in )) ) return( -1 ); /* 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 ); return( 0 ); } /* As above, but save as a mime jpeg on stdout. */ 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" ); fwrite( buf, sizeof( char ), len, stdout ); fflush( stdout ); im_close( base ); if( ferror( stdout ) ) { im_error( "im_vips2mimejpeg", _( "error writing output" ) ); return( -1 ); } return( 0 ); } #endif /*HAVE_JPEG*/