libvips/libvips/foreign/vips2jpeg.c

1263 lines
30 KiB
C

/* wrap jpeg libray for write
*
* 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
* 17/7/10
* - use g_assert()
* - allow space for the header in init_destination(), helps writing very
* small JPEGs (thanks Tim Elliott)
* 18/7/10
* - collect im_vips2bufjpeg() output in a list of blocks ... we no
* longer overallocate or underallocate
* 8/7/11
* - oop CMYK write was not inverting, thanks Ole
* 12/10/2011
* - write XMP data
* 18/10/2011
* - update Orientation as well
* 3/11/11
* - rebuild exif tags from coded metadata values
* 24/11/11
* - turn into a set of write fns ready to be called from a class
* 7/8/12
* - use VIPS_META_RESOLUTION_UNIT to select resoltuion unit
* 16/11/12
* - read ifds from exif fields
* - optionally parse rationals as a/b
* - update exif image dimensions
* 21/11/12
* - attach IPCT data (app13), thanks Gary
* 2/10/13 Lovell Fuller
* - add optimize_coding parameter
*/
/*
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., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA
*/
/*
These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
*/
/*
#define DEBUG
#define VIPS_DEBUG
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#include <vips/intl.h>
#ifdef HAVE_JPEG
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <setjmp.h>
#include <vips/vips.h>
#include <vips/debug.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 "jpeg.h"
#include "vipsjpeg.h"
/* New output message method - send to VIPS.
*/
void
vips__new_output_message( j_common_ptr cinfo )
{
char buffer[JMSG_LENGTH_MAX];
(*cinfo->err->format_message)( cinfo, buffer );
vips_error( "VipsJpeg", _( "%s" ), buffer );
#ifdef DEBUG
printf( "vips__new_output_message: \"%s\"\n", buffer );
#endif /*DEBUG*/
}
/* New error_exit handler.
*/
void
vips__new_error_exit( j_common_ptr cinfo )
{
ErrorManager *eman = (ErrorManager *) cinfo->err;
#ifdef DEBUG
printf( "vips__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 {
VipsImage *in;
struct jpeg_compress_struct cinfo;
ErrorManager eman;
JSAMPROW *row_pointer;
char *profile_bytes;
unsigned int profile_length;
VipsImage *inverted;
} Write;
static void
write_destroy( Write *write )
{
jpeg_destroy_compress( &write->cinfo );
VIPS_FREEF( fclose, write->eman.fp );
VIPS_FREE( write->row_pointer );
VIPS_FREE( write->profile_bytes );
VIPS_UNREF( write->inverted );
g_free( write );
}
static Write *
write_new( VipsImage *in )
{
Write *write;
if( !(write = g_new0( Write, 1 )) )
return( NULL );
write->in = in;
write->row_pointer = NULL;
write->cinfo.err = jpeg_std_error( &write->eman.pub );
write->eman.pub.error_exit = vips__new_error_exit;
write->eman.pub.output_message = vips__new_output_message;
write->eman.pub.output_message = vips__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_double_to_rational( double value, ExifRational *rv )
{
unsigned int scale;
/* We scale up to fill uint32, then set that as the
* denominator. Try to avoid generating 0.
*/
scale = (unsigned int) ((UINT_MAX - 1000) / value);
scale = scale == 0 ? 1 : scale;
rv->numerator = value * scale;
rv->denominator = scale;
}
static void
vips_exif_double_to_srational( double value, ExifSRational *srv )
{
int scale;
/* We scale up to fill int32, then set that as the
* denominator. Try to avoid generating 0.
*/
scale = (int) ((INT_MAX - 1000) / value);
scale = scale == 0 ? 1 : scale;
srv->numerator = value * scale;
srv->denominator = scale;
}
/* Parse a char* into an ExifRational. We allow floats as well.
*/
static void
vips_exif_parse_rational( const char *str, ExifRational *rv )
{
if( sscanf( str, " %u / %u ", &rv->numerator, &rv->denominator ) == 2 )
return;
vips_exif_double_to_rational( g_ascii_strtod( str, NULL ), rv );
}
/* Parse a char* into an ExifSRational. We allow floats as well.
*/
static void
vips_exif_parse_srational( const char *str, ExifSRational *srv )
{
if( sscanf( str, " %d / %d ",
&srv->numerator, &srv->denominator ) == 2 )
return;
vips_exif_double_to_srational( g_ascii_strtod( str, NULL ), srv );
}
/* Does both signed and unsigned rationals from a char*.
*/
static void
vips_exif_set_rational( ExifData *ed,
ExifEntry *entry, unsigned long component, void *data )
{
char *value = (char *) data;
ExifByteOrder bo;
size_t sizeof_component;
size_t offset;
if( entry->components <= component ) {
VIPS_DEBUG_MSG( "vips_exif_set_rational: "
"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_rational: %s = \"%s\"\n",
exif_tag_get_title( entry->tag ), value );
if( entry->format == EXIF_FORMAT_RATIONAL ) {
ExifRational rv;
vips_exif_parse_rational( value, &rv );
VIPS_DEBUG_MSG( "vips_exif_set_rational: %u / %u\n",
rv.numerator,
rv.denominator );
exif_set_rational( entry->data + offset, bo, rv );
}
else if( entry->format == EXIF_FORMAT_SRATIONAL ) {
ExifSRational srv;
vips_exif_parse_srational( value, &srv );
VIPS_DEBUG_MSG( "vips_exif_set_rational: %d / %d\n",
srv.numerator, srv.denominator );
exif_set_srational( entry->data + offset, bo, srv );
}
}
/* Does both signed and unsigned rationals from a double*.
*
* Don't change the exit entry if the value currently there is a good
* approximation of the double we are trying to set.
*/
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;
double old_value;
if( entry->components <= component ) {
VIPS_DEBUG_MSG( "vips_exif_set_double: "
"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 rv;
rv = exif_get_rational( entry->data + offset, bo );
old_value = (double) rv.numerator / rv.denominator;
if( abs( old_value - value ) > 0.0001 ) {
vips_exif_double_to_rational( value, &rv );
VIPS_DEBUG_MSG( "vips_exif_set_double: %u / %u\n",
rv.numerator,
rv.denominator );
exif_set_rational( entry->data + offset, bo, rv );
}
}
else if( entry->format == EXIF_FORMAT_SRATIONAL ) {
ExifSRational srv;
srv = exif_get_srational( entry->data + offset, bo );
old_value = (double) srv.numerator / srv.denominator;
if( abs( old_value - value ) > 0.0001 ) {
vips_exif_double_to_srational( value, &srv );
VIPS_DEBUG_MSG( "vips_exif_set_double: %d / %d\n",
srv.numerator, srv.denominator );
exif_set_srational( entry->data + offset, bo, srv );
}
}
}
typedef void (*write_fn)( ExifData *ed,
ExifEntry *entry, unsigned long component, void *data );
/* Write a tag. Update what's there, or make a new one.
*/
static int
write_tag( ExifData *ed, int ifd, ExifTag tag, write_fn fn, void *data )
{
ExifEntry *entry;
if( (entry = exif_content_get_entry( ed->ifd[ifd], tag )) ) {
fn( ed, entry, 0, data );
}
else {
entry = exif_entry_new();
/* tag must be set before calling exif_content_add_entry.
*/
entry->tag = tag;
exif_content_add_entry( ed->ifd[ifd], 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, VipsImage *im )
{
double xres, yres;
const char *p;
int unit;
VIPS_DEBUG_MSG( "set_exif_resolution: vips res of %g, %g\n",
im->Xres, im->Yres );
/* Default to inches, more progs support it.
*/
unit = 2;
if( vips_image_get_typeof( im, VIPS_META_RESOLUTION_UNIT ) &&
!vips_image_get_string( im, VIPS_META_RESOLUTION_UNIT, &p ) &&
vips_isprefix( "cm", p ) )
unit = 3;
switch( unit ) {
case 2:
xres = im->Xres * 25.4;
yres = im->Yres * 25.4;
break;
case 3:
xres = im->Xres * 10.0;
yres = im->Yres * 10.0;
break;
default:
vips_warn( "VipsJpeg",
"%s", _( "unknown EXIF resolution unit" ) );
return( 0 );
}
/* Main image xres/yres/unit are in ifd0. ifd1 has the thumbnail
* xres/yres/unit.
*/
if( write_tag( ed, 0, EXIF_TAG_X_RESOLUTION,
vips_exif_set_double, (void *) &xres ) ||
write_tag( ed, 0, EXIF_TAG_Y_RESOLUTION,
vips_exif_set_double, (void *) &yres ) ||
write_tag( ed, 0, EXIF_TAG_RESOLUTION_UNIT,
vips_exif_set_int, (void *) &unit ) ) {
vips_error( "VipsJpeg",
"%s", _( "error setting JPEG resolution" ) );
return( -1 );
}
return( 0 );
}
/* Exif also tracks image dimensions.
*/
static int
set_exif_dimensions( ExifData *ed, VipsImage *im )
{
VIPS_DEBUG_MSG( "set_exif_dimensions: vips size of %d, %d\n",
im->Xsize, im->Ysize );
if( write_tag( ed, 2, EXIF_TAG_PIXEL_X_DIMENSION,
vips_exif_set_int, (void *) &im->Xsize ) ||
write_tag( ed, 2, EXIF_TAG_PIXEL_Y_DIMENSION,
vips_exif_set_int, (void *) &im->Ysize ) ) {
vips_error( "VipsJpeg",
"%s", _( "error setting JPEG dimensions" ) );
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 )
vips_exif_set_rational( ed, entry, i, (void *) p );
/* 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];
const char *value;
vips_snprintf( name, 256, "exif-ifd%d-%s",
exif_entry_get_ifd( entry ),
exif_tag_get_title( entry->tag ) );
if( vips_image_get_typeof( ve->image, name ) ) {
(void) 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_blob( Write *write, const char *field, int app )
{
unsigned char *data;
size_t data_length;
if( vips_image_get_typeof( write->in, field ) ) {
if( vips_image_get_blob( write->in, field,
(void *) &data, &data_length ) )
return( -1 );
#ifdef DEBUG
printf( "write_blob: attaching %zd bytes of %s\n",
data_length, field );
#endif /*DEBUG*/
jpeg_write_marker( &write->cinfo, app, data, data_length );
}
return( 0 );
}
static int
write_exif( Write *write )
{
#ifdef HAVE_EXIF
unsigned char *data;
size_t data_length;
unsigned int idl;
ExifData *ed;
/* Either parse from the embedded EXIF, or if there's none, make
* some fresh EXIF we can write the resolution to.
*/
if( vips_image_get_typeof( write->in, VIPS_META_EXIF_NAME ) ) {
if( vips_image_get_blob( write->in, VIPS_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 );
}
/* Update EXIF image dimensions from the vips image header.
*/
if( set_exif_dimensions( 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 ) {
vips_error( "VipsJpeg", "%s", _( "error saving EXIF" ) );
exif_data_free( ed );
return( -1 );
}
data_length = idl;
#ifdef DEBUG
printf( "write_exif: 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( write_blob( write, VIPS_META_EXIF_NAME, JPEG_APP0 + 1 ) )
return( -1 );
#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 */
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 =
vips__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( "write_profile_file: attached profile \"%s\"\n", profile );
#endif /*DEBUG*/
return( 0 );
}
static int
write_profile_meta( Write *write )
{
void *data;
size_t data_length;
if( vips_image_get_blob( write->in, VIPS_META_ICC_NAME,
&data, &data_length ) )
return( -1 );
write_profile_data( &write->cinfo, data, data_length );
#ifdef DEBUG
printf( "write_profile_meta: attached %zd byte profile from 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)
VIPS_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,
gboolean optimize_coding )
{
VipsImage *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 == VIPS_FORMAT_UCHAR );
g_assert( in->Coding == VIPS_CODING_NONE );
g_assert( in->Bands == 1 || in->Bands == 3 || in->Bands == 4 );
/* Check input image.
*/
if( vips_image_pio_input( in ) )
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 == VIPS_INTERPRETATION_CMYK ) {
space = JCS_CMYK;
/* IJG always sets an Adobe marker, so we should invert CMYK.
*/
if( vips_invert( in, &write->inverted, NULL ) )
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 = VIPS_ARRAY( NULL, in->Ysize, JSAMPROW )) )
return( -1 );
/* Rest to default.
*/
jpeg_set_defaults( &write->cinfo );
jpeg_set_quality( &write->cinfo, qfac, TRUE );
/* Compute optimal Huffman coding tables.
*/
write->cinfo.optimize_coding = optimize_coding;
/* Build compress tables.
*/
jpeg_start_compress( &write->cinfo, TRUE );
/* Write any APP markers we need.
*/
if( write_exif( write ) ||
write_blob( write, VIPS_META_XMP_NAME, JPEG_APP0 + 1 ) ||
write_blob( write, VIPS_META_IPCT_NAME, JPEG_APP0 + 13 ) )
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 &&
vips_image_get_typeof( in, VIPS_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 );
}
/* Write an image to a jpeg file.
*/
int
vips__jpeg_write_file( VipsImage *in,
const char *filename, int Q, const char *profile,
gboolean optimize_coding )
{
Write *write;
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 = vips__file_open_write( filename, FALSE )) ) {
write_destroy( write );
return( -1 );
}
jpeg_stdio_dest( &write->cinfo, write->eman.fp );
/* Convert!
*/
if( write_vips( write, Q, profile, optimize_coding ) ) {
write_destroy( write );
return( -1 );
}
write_destroy( write );
return( 0 );
}
/* 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 */
size_t size; /* Max size */
size_t 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 size_t
block_length( Block *block )
{
size_t 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 = %zd\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 = %zd, used = %zd\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.
*/
void **obuf; /* Allocated buffer, and size */
size_t *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;
size_t 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 = vips_malloc( NULL, 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, void **obuf, size_t *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->obuf = obuf;
buf->olen = olen;
}
int
vips__jpeg_write_buffer( VipsImage *in,
void **obuf, size_t *olen, int Q, const char *profile,
gboolean optimize_coding )
{
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, obuf, olen );
/* Convert!
*/
if( write_vips( write, Q, profile, optimize_coding ) ) {
write_destroy( write );
return( -1 );
}
write_destroy( write );
return( 0 );
}
const char *vips__jpeg_suffs[] = { ".jpg", ".jpeg", ".jpe", NULL };
#endif /*HAVE_JPEG*/