898 lines
21 KiB
C
898 lines
21 KiB
C
/* 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
|
|
*/
|
|
|
|
/*
|
|
|
|
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;
|
|
im_threadgroup_t *tg;
|
|
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_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_FREEF( im_close, write->inverted );
|
|
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",
|
|
"%s", _( "unable to convert to saveable format" ) );
|
|
write_destroy( write );
|
|
return( NULL );
|
|
}
|
|
|
|
write->tg = 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_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", "%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_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;
|
|
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.
|
|
*/
|
|
write->tg = im_threadgroup_create( in );
|
|
write->row_pointer = IM_ARRAY( NULL, write->tg->nlines, JSAMPROW );
|
|
if( !write->tg || !write->row_pointer )
|
|
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.
|
|
*/
|
|
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().
|
|
*/
|
|
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.
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/* 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.
|
|
|
|
FIXME ... argh, the retuen length should really be a size_t, but we can't fix this now sadly
|
|
|
|
*/
|
|
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" );
|
|
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*/
|