| /* 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 </libexif/ prefix if required |
| * 19/1/07 |
| * - oop, libexif confusion |
| * 2/11/07 |
| * - use im_wbuffer() API for BG writes |
| * 15/2/08 |
| * - write CMYK if Bands == 4 and Type == CMYK |
| * 12/5/09 |
| * - fix signed/unsigned warning |
| * 13/8/09 |
| * - allow "none" for profile, meaning don't embed one |
| * 4/2/10 |
| * - gtkdoc |
| */ |
| |
| /* |
| |
| This file is part of VIPS. |
| |
| VIPS is free software; you can redistribute it and/or modify |
| it under the terms of the GNU Lesser General Public License as published by |
| the Free Software Foundation; either version 2 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Lesser General Public License for more details. |
| |
| You should have received a copy of the GNU Lesser General Public License |
| along with this program; if not, write to the Free Software |
| Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| |
| */ |
| |
| /* |
| |
| These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk |
| |
| */ |
| |
| /* |
| #define DEBUG |
| #define DEBUG_VERBOSE |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif /*HAVE_CONFIG_H*/ |
| #include <vips/intl.h> |
| |
| #ifndef HAVE_JPEG |
| |
| #include <vips/vips.h> |
| |
| 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 <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <setjmp.h> |
| #include <assert.h> |
| |
| #ifdef HAVE_EXIF |
| #ifdef UNTAGGED_EXIF |
| #include <exif-data.h> |
| #include <exif-loader.h> |
| #include <exif-ifd.h> |
| #include <exif-utils.h> |
| #else /*!UNTAGGED_EXIF*/ |
| #include <libexif/exif-data.h> |
| #include <libexif/exif-loader.h> |
| #include <libexif/exif-ifd.h> |
| #include <libexif/exif-utils.h> |
| #endif /*UNTAGGED_EXIF*/ |
| #endif /*HAVE_EXIF*/ |
| |
| #include <vips/vips.h> |
| #include <vips/internal.h> |
| #include <vips/buf.h> |
| |
| /* 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 <jpeglib.h> |
| #include <jerror.h> |
| |
| #ifdef WITH_DMALLOC |
| #include <dmalloc.h> |
| #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; |
| 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 |
| 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", |
| "%s", _( "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_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(); |
| |
| /* 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", "%s", _( "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_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 %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, 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 %d 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; |
| |
| /* 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; |
| J_COLOR_SPACE space; |
| |
| /* The image we'll be writing ... can change, see CMYK. |
| */ |
| in = write->in; |
| |
| /* 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", |
| "%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, write->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 ); |
| |
| /* 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( write->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:<emphasis>compression</emphasis>,<emphasis>profile</emphasis> |
| * ]| |
| * |
| * <itemizedlist> |
| * <listitem> |
| * <para> |
| * <emphasis>compression</emphasis> |
| * Compress with this quality factor. Default 75. |
| * </para> |
| * </listitem> |
| * <listitem> |
| * <para> |
| * <emphasis>profile</emphasis> |
| * 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". |
| * </para> |
| * </listitem> |
| * </itemizedlist> |
| * |
| * 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; |
| 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. |
| */ |
| if( !(write->eman.fp = im__file_open_write( name )) ) { |
| 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 ); |
| } |
| |
| /* 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; |
| } |
| |
| /** |
| * 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; |
| |
| 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 ); |
| } |
| |
| /** |
| * 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 ); |
| } |
| |
| #endif /*HAVE_JPEG*/ |