743 lines
16 KiB
C
743 lines
16 KiB
C
/* Convert 1 or 3-band 8-bit VIPS images to/from JPEG.
|
|
*
|
|
* 28/11/03 JC
|
|
* - better no-overshoot on tile loop
|
|
* 12/11/04
|
|
* - better demand size choice for eval
|
|
* 30/6/05 JC
|
|
* - update im_error()/im_warn()
|
|
* - now loads and saves exif data
|
|
* 30/7/05
|
|
* - now loads ICC profiles
|
|
* - now saves ICC profiles from the VIPS header
|
|
* 24/8/05
|
|
* - jpeg load sets vips xres/yres from exif, if possible
|
|
* - jpeg save sets exif xres/yres from vips, if possible
|
|
* 29/8/05
|
|
* - cut from old vips_jpeg.c
|
|
* 13/10/06
|
|
* - add </libexif/ prefix if required
|
|
* 11/2/08
|
|
* - spot CMYK jpegs and set Type
|
|
* - spot Adobe CMYK JPEG and invert ink density
|
|
* 15/2/08
|
|
* - added "shrink" 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., 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_jpeg2vips( const char *name, IMAGE *out )
|
|
{
|
|
im_error( "im_jpeg2vips", _( "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/buf.h>
|
|
#include <vips/internal.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 );
|
|
}
|
|
|
|
#ifdef HAVE_EXIF
|
|
#ifdef DEBUG_VERBOSE
|
|
/* Print exif for debugging ... hacked from exif-0.6.9/actions.c
|
|
*/
|
|
static void
|
|
show_tags( ExifData *data )
|
|
{
|
|
int i;
|
|
unsigned int tag;
|
|
const char *name;
|
|
|
|
printf( "show EXIF tags:\n" );
|
|
|
|
for( i = 0; i < EXIF_IFD_COUNT; i++ )
|
|
printf( "%-7.7s", exif_ifd_get_name( i ) );
|
|
printf( "\n" );
|
|
|
|
for( tag = 0; tag < 0xffff; tag++ ) {
|
|
name = exif_tag_get_title( tag );
|
|
if( !name )
|
|
continue;
|
|
printf( " 0x%04x %-29.29s", tag, name );
|
|
for( i = 0; i < EXIF_IFD_COUNT; i++ )
|
|
if( exif_content_get_entry( data->ifd[i], tag ) )
|
|
printf( " * " );
|
|
else
|
|
printf( " - " );
|
|
printf( "\n" );
|
|
}
|
|
}
|
|
|
|
static void
|
|
show_entry( ExifEntry *entry, void *client )
|
|
{
|
|
char exif_text[256];
|
|
|
|
printf( "%s", exif_tag_get_title( entry->tag ) );
|
|
printf( "|" );
|
|
printf( "%s", exif_entry_get_value( entry, exif_text, 256 ) );
|
|
printf( "|" );
|
|
printf( "%s", exif_format_get_name( entry->format ) );
|
|
printf( "|" );
|
|
printf( "%d bytes", entry->size );
|
|
printf( "\n" );
|
|
}
|
|
|
|
static void
|
|
show_ifd( ExifContent *content, void *client )
|
|
{
|
|
exif_content_foreach_entry( content, show_entry, client );
|
|
printf( "-\n" );
|
|
}
|
|
|
|
void
|
|
show_values( ExifData *data )
|
|
{
|
|
ExifByteOrder order;
|
|
|
|
order = exif_data_get_byte_order( data );
|
|
printf( "EXIF tags in '%s' byte order\n",
|
|
exif_byte_order_get_name( order ) );
|
|
|
|
printf( "%-20.20s", "Tag" );
|
|
printf( "|" );
|
|
printf( "%-58.58s", "Value" );
|
|
printf( "\n" );
|
|
|
|
exif_data_foreach_content( data, show_ifd, NULL );
|
|
|
|
if( data->size )
|
|
printf( "contains thumbnail of %d bytes\n", data->size );
|
|
}
|
|
#endif /*DEBUG_VERBOSE*/
|
|
#endif /*HAVE_EXIF*/
|
|
|
|
#ifdef HAVE_EXIF
|
|
static void
|
|
attach_exif_entry( ExifEntry *entry, IMAGE *im )
|
|
{
|
|
char name_text[256];
|
|
VipsBuf name;
|
|
char value_text[256];
|
|
VipsBuf value;
|
|
char exif_value[256];
|
|
|
|
vips_buf_init_static( &name, name_text, 256 );
|
|
vips_buf_init_static( &value, value_text, 256 );
|
|
|
|
vips_buf_appendf( &name, "exif-%s", exif_tag_get_title( entry->tag ) );
|
|
vips_buf_appendf( &value, "%s (%s, %d bytes)",
|
|
exif_entry_get_value( entry, exif_value, 256 ),
|
|
exif_format_get_name( entry->format ),
|
|
entry->size );
|
|
|
|
/* Can't do anything sensible with the error return.
|
|
*/
|
|
(void) im_meta_set_string( im,
|
|
vips_buf_all( &name ), vips_buf_all( &value ) );
|
|
}
|
|
|
|
static void
|
|
attach_exif_content( ExifContent *content, IMAGE *im )
|
|
{
|
|
exif_content_foreach_entry( content,
|
|
(ExifContentForeachEntryFunc) attach_exif_entry, im );
|
|
}
|
|
|
|
/* Just find the first occurence of the tag (is this correct?)
|
|
*/
|
|
static ExifEntry *
|
|
find_entry( ExifData *ed, ExifTag tag )
|
|
{
|
|
int i;
|
|
|
|
for( i = 0; i < EXIF_IFD_COUNT; i++ ) {
|
|
ExifEntry *entry;
|
|
|
|
if( (entry = exif_content_get_entry( ed->ifd[i], tag )) )
|
|
return( entry );
|
|
}
|
|
|
|
return( NULL );
|
|
}
|
|
|
|
static int
|
|
get_entry_rational( ExifData *ed, ExifTag tag, double *out )
|
|
{
|
|
ExifEntry *entry;
|
|
ExifRational rational;
|
|
|
|
if( !(entry = find_entry( ed, tag )) ||
|
|
entry->format != EXIF_FORMAT_RATIONAL ||
|
|
entry->components != 1 )
|
|
return( -1 );
|
|
|
|
rational = exif_get_rational( entry->data,
|
|
exif_data_get_byte_order( ed ) );
|
|
|
|
*out = (double) rational.numerator / rational.denominator;
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
static int
|
|
get_entry_short( ExifData *ed, ExifTag tag, int *out )
|
|
{
|
|
ExifEntry *entry;
|
|
|
|
if( !(entry = find_entry( ed, tag )) ||
|
|
entry->format != EXIF_FORMAT_SHORT ||
|
|
entry->components != 1 )
|
|
return( -1 );
|
|
|
|
*out = exif_get_short( entry->data,
|
|
exif_data_get_byte_order( ed ) );
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
static void
|
|
set_vips_resolution( IMAGE *im, ExifData *ed )
|
|
{
|
|
double xres, yres;
|
|
int unit;
|
|
|
|
if( get_entry_rational( ed, EXIF_TAG_X_RESOLUTION, &xres ) ||
|
|
get_entry_rational( ed, EXIF_TAG_Y_RESOLUTION, &yres ) ||
|
|
get_entry_short( ed, EXIF_TAG_RESOLUTION_UNIT, &unit ) ) {
|
|
im_warn( "im_jpeg2vips",
|
|
"%s", _( "error reading resolution" ) );
|
|
return;
|
|
}
|
|
|
|
switch( unit ) {
|
|
case 2:
|
|
/* In inches.
|
|
*/
|
|
xres /= 25.4;
|
|
yres /= 25.4;
|
|
break;
|
|
|
|
case 3:
|
|
/* In cm.
|
|
*/
|
|
xres /= 10.0;
|
|
yres /= 10.0;
|
|
break;
|
|
|
|
default:
|
|
im_warn( "im_jpeg2vips", "%s", _( "bad resolution unit" ) );
|
|
return;
|
|
}
|
|
|
|
im->Xres = xres;
|
|
im->Yres = yres;
|
|
}
|
|
#endif /*HAVE_EXIF*/
|
|
|
|
static int
|
|
read_exif( IMAGE *im, void *data, int data_length )
|
|
{
|
|
char *data_copy;
|
|
|
|
/* Always attach a copy of the unparsed exif data.
|
|
*/
|
|
if( !(data_copy = im_malloc( NULL, data_length )) )
|
|
return( -1 );
|
|
memcpy( data_copy, data, data_length );
|
|
if( im_meta_set_blob( im, IM_META_EXIF_NAME,
|
|
(im_callback_fn) im_free, data_copy, data_length ) ) {
|
|
im_free( data_copy );
|
|
return( -1 );
|
|
}
|
|
|
|
#ifdef HAVE_EXIF
|
|
{
|
|
ExifData *ed;
|
|
|
|
if( !(ed = exif_data_new_from_data( data, data_length )) )
|
|
return( -1 );
|
|
|
|
if( ed->size > 0 ) {
|
|
#ifdef DEBUG_VERBOSE
|
|
show_tags( ed );
|
|
show_values( ed );
|
|
#endif /*DEBUG_VERBOSE*/
|
|
|
|
/* Attach informational fields for what we find.
|
|
|
|
FIXME ... better to have this in the UI layer?
|
|
|
|
Or we could attach non-human-readable tags here (int, double
|
|
etc) and then move the human stuff to the UI layer?
|
|
|
|
*/
|
|
exif_data_foreach_content( ed,
|
|
(ExifDataForeachContentFunc) attach_exif_content, im );
|
|
|
|
/* Look for resolution fields and use them to set the VIPS
|
|
* xres/yres fields.
|
|
*/
|
|
set_vips_resolution( im, ed );
|
|
}
|
|
|
|
exif_data_free( ed );
|
|
}
|
|
#endif /*HAVE_EXIF*/
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
/* Number of app2 sections we can capture. Each one can be 64k, so 640k should
|
|
* be enough for anyone (haha).
|
|
*/
|
|
#define MAX_APP2_SECTIONS (10)
|
|
|
|
/* Read a cinfo to a VIPS image. Set invert_pels if the pixel reader needs to
|
|
* do 255-pel.
|
|
*/
|
|
static int
|
|
read_jpeg_header( struct jpeg_decompress_struct *cinfo,
|
|
IMAGE *out, gboolean *invert_pels, int shrink )
|
|
{
|
|
jpeg_saved_marker_ptr p;
|
|
int type;
|
|
|
|
/* Capture app2 sections here for assembly.
|
|
*/
|
|
void *app2_data[MAX_APP2_SECTIONS] = { 0 };
|
|
int app2_data_length[MAX_APP2_SECTIONS] = { 0 };
|
|
int data_length;
|
|
int i;
|
|
|
|
/* Read JPEG header. libjpeg will set out_color_space sanely for us
|
|
* for YUV YCCK etc.
|
|
*/
|
|
jpeg_read_header( cinfo, TRUE );
|
|
cinfo->scale_denom = shrink;
|
|
jpeg_calc_output_dimensions( cinfo );
|
|
*invert_pels = FALSE;
|
|
switch( cinfo->out_color_space ) {
|
|
case JCS_GRAYSCALE:
|
|
type = IM_TYPE_B_W;
|
|
break;
|
|
|
|
case JCS_CMYK:
|
|
type = IM_TYPE_CMYK;
|
|
/* Photoshop writes CMYK JPEG inverted :-( Maybe this is a
|
|
* way to spot photoshop CMYK JPGs.
|
|
*/
|
|
if( cinfo->saw_Adobe_marker )
|
|
*invert_pels = TRUE;
|
|
break;
|
|
|
|
case JCS_RGB:
|
|
default:
|
|
type = IM_TYPE_sRGB;
|
|
break;
|
|
}
|
|
|
|
/* Set VIPS header.
|
|
*/
|
|
im_initdesc( out,
|
|
cinfo->output_width, cinfo->output_height,
|
|
cinfo->output_components,
|
|
IM_BBITS_BYTE, IM_BANDFMT_UCHAR, IM_CODING_NONE, type,
|
|
1.0, 1.0, 0, 0 );
|
|
|
|
/* Look for EXIF and ICC profile.
|
|
*/
|
|
for( p = cinfo->marker_list; p; p = p->next ) {
|
|
switch( p->marker ) {
|
|
case JPEG_APP0 + 1:
|
|
/* EXIF data.
|
|
*/
|
|
#ifdef DEBUG
|
|
printf( "read_jpeg_header: seen %d bytes of EXIF\n",
|
|
p->data_length );
|
|
#endif /*DEBUG*/
|
|
if( read_exif( out, p->data, p->data_length ) )
|
|
return( -1 );
|
|
break;
|
|
|
|
case JPEG_APP0 + 2:
|
|
/* ICC profile.
|
|
*/
|
|
#ifdef DEBUG
|
|
printf( "read_jpeg_header: seen %d bytes of ICC\n",
|
|
p->data_length );
|
|
#endif /*DEBUG*/
|
|
|
|
if( p->data_length > 14 &&
|
|
im_isprefix( "ICC_PROFILE",
|
|
(char *) p->data ) ) {
|
|
/* cur_marker numbers from 1, according to
|
|
* spec.
|
|
*/
|
|
int cur_marker = p->data[12] - 1;
|
|
|
|
if( cur_marker >= 0 &&
|
|
cur_marker < MAX_APP2_SECTIONS ) {
|
|
app2_data[cur_marker] = p->data + 14;
|
|
app2_data_length[cur_marker] =
|
|
p->data_length - 14;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
#ifdef DEBUG
|
|
printf( "read_jpeg_header: seen %d bytes of data\n",
|
|
p->data_length );
|
|
#endif /*DEBUG*/
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Assemble ICC sections.
|
|
*/
|
|
data_length = 0;
|
|
for( i = 0; i < MAX_APP2_SECTIONS && app2_data[i]; i++ )
|
|
data_length += app2_data_length[i];
|
|
if( data_length ) {
|
|
unsigned char *data;
|
|
int p;
|
|
|
|
#ifdef DEBUG
|
|
printf( "read_jpeg_header: assembled %d byte ICC profile\n",
|
|
data_length );
|
|
#endif /*DEBUG*/
|
|
|
|
if( !(data = im_malloc( NULL, data_length )) )
|
|
return( -1 );
|
|
|
|
p = 0;
|
|
for( i = 0; i < MAX_APP2_SECTIONS && app2_data[i]; i++ ) {
|
|
memcpy( data + p, app2_data[i], app2_data_length[i] );
|
|
p += app2_data_length[i];
|
|
}
|
|
|
|
if( im_meta_set_blob( out, IM_META_ICC_NAME,
|
|
(im_callback_fn) im_free, data, data_length ) ) {
|
|
im_free( data );
|
|
return( -1 );
|
|
}
|
|
}
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
/* Read a cinfo to a VIPS image.
|
|
*/
|
|
static int
|
|
read_jpeg_image( struct jpeg_decompress_struct *cinfo, IMAGE *out,
|
|
gboolean invert_pels )
|
|
{
|
|
int x, y, sz;
|
|
JSAMPROW row_pointer[1];
|
|
|
|
/* Check VIPS.
|
|
*/
|
|
if( im_outcheck( out ) || im_setupout( out ) )
|
|
return( -1 );
|
|
|
|
/* Get size of output line and make a buffer.
|
|
*/
|
|
sz = cinfo->output_width * cinfo->output_components;
|
|
row_pointer[0] = (JSAMPLE *) (*cinfo->mem->alloc_large)
|
|
( (j_common_ptr) cinfo, JPOOL_IMAGE, sz );
|
|
|
|
/* Start up decompressor.
|
|
*/
|
|
jpeg_start_decompress( cinfo );
|
|
|
|
/* Process image.
|
|
*/
|
|
for( y = 0; y < out->Ysize; y++ ) {
|
|
if( jpeg_read_scanlines( cinfo, &row_pointer[0], 1 ) != 1 ) {
|
|
im_error( "im_jpeg2vips",
|
|
"%s", _( "truncated JPEG file" ) );
|
|
return( -1 );
|
|
}
|
|
if( invert_pels ) {
|
|
for( x = 0; x < sz; x++ )
|
|
row_pointer[0][x] = 255 - row_pointer[0][x];
|
|
}
|
|
if( im_writeline( y, out, row_pointer[0] ) )
|
|
return( -1 );
|
|
}
|
|
|
|
/* Stop decompressor.
|
|
*/
|
|
jpeg_finish_decompress( cinfo );
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
/* Read a JPEG file into a VIPS image.
|
|
*/
|
|
static int
|
|
jpeg2vips( const char *name, IMAGE *out, gboolean header_only )
|
|
{
|
|
char filename[FILENAME_MAX];
|
|
char mode[FILENAME_MAX];
|
|
char *p, *q;
|
|
int shrink;
|
|
struct jpeg_decompress_struct cinfo;
|
|
ErrorManager eman;
|
|
FILE *fp;
|
|
int result;
|
|
gboolean invert_pels;
|
|
|
|
/* Parse the filename.
|
|
*/
|
|
im_filename_split( name, filename, mode );
|
|
p = &mode[0];
|
|
shrink = 1;
|
|
if( (q = im_getnextoption( &p )) ) {
|
|
shrink = atoi( q );
|
|
|
|
if( shrink != 1 && shrink != 2 &&
|
|
shrink != 4 && shrink != 8 ) {
|
|
im_error( "im_jpeg2vips",
|
|
_( "bad shrink factor %d" ), shrink );
|
|
return( -1 );
|
|
}
|
|
}
|
|
|
|
/* Make jpeg compression object.
|
|
*/
|
|
cinfo.err = jpeg_std_error( &eman.pub );
|
|
eman.pub.error_exit = new_error_exit;
|
|
eman.pub.output_message = new_output_message;
|
|
eman.fp = NULL;
|
|
if( setjmp( eman.jmp ) ) {
|
|
/* Here for longjmp() from new_error_exit().
|
|
*/
|
|
jpeg_destroy_decompress( &cinfo );
|
|
|
|
return( -1 );
|
|
}
|
|
jpeg_create_decompress( &cinfo );
|
|
|
|
/* Make input.
|
|
*/
|
|
if( !(fp = im__file_open_read( filename )) )
|
|
return( -1 );
|
|
eman.fp = fp;
|
|
jpeg_stdio_src( &cinfo, fp );
|
|
|
|
/* Need to read in APP1 (EXIF metadata) and APP2 (ICC profile).
|
|
*/
|
|
jpeg_save_markers( &cinfo, JPEG_APP0 + 1, 0xffff );
|
|
jpeg_save_markers( &cinfo, JPEG_APP0 + 2, 0xffff );
|
|
|
|
/* Convert!
|
|
*/
|
|
result = read_jpeg_header( &cinfo, out, &invert_pels, shrink );
|
|
if( !header_only && !result )
|
|
result = read_jpeg_image( &cinfo, out, invert_pels );
|
|
|
|
/* Close and tidy.
|
|
*/
|
|
fclose( fp );
|
|
eman.fp = NULL;
|
|
jpeg_destroy_decompress( &cinfo );
|
|
|
|
if( eman.pub.num_warnings != 0 ) {
|
|
im_warn( "im_jpeg2vips", _( "read gave %ld warnings" ),
|
|
eman.pub.num_warnings );
|
|
im_warn( "im_jpeg2vips", "%s", im_error_buffer() );
|
|
}
|
|
|
|
return( result );
|
|
}
|
|
|
|
/* Read a JPEG file into a VIPS image.
|
|
*/
|
|
int
|
|
im_jpeg2vips( const char *name, IMAGE *out )
|
|
{
|
|
return( jpeg2vips( name, out, FALSE ) );
|
|
}
|
|
|
|
static int
|
|
isjpeg( const char *filename )
|
|
{
|
|
unsigned char buf[2];
|
|
|
|
if( im__get_bytes( filename, buf, 2 ) )
|
|
if( (int) buf[0] == 0xff && (int) buf[1] == 0xd8 )
|
|
return( 1 );
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
static int
|
|
jpeg2vips_header( const char *name, IMAGE *out )
|
|
{
|
|
return( jpeg2vips( name, out, TRUE ) );
|
|
}
|
|
|
|
static const char *jpeg_suffs[] = { ".jpg", ".jpeg", ".jpe", NULL };
|
|
|
|
/* jpeg format adds no new members.
|
|
*/
|
|
typedef VipsFormat VipsFormatJpeg;
|
|
typedef VipsFormatClass VipsFormatJpegClass;
|
|
|
|
static void
|
|
vips_format_jpeg_class_init( VipsFormatJpegClass *class )
|
|
{
|
|
VipsObjectClass *object_class = (VipsObjectClass *) class;
|
|
VipsFormatClass *format_class = (VipsFormatClass *) class;
|
|
|
|
object_class->nickname = "jpeg";
|
|
object_class->description = _( "JPEG" );
|
|
|
|
format_class->is_a = isjpeg;
|
|
format_class->header = jpeg2vips_header;
|
|
format_class->load = im_jpeg2vips;
|
|
format_class->save = im_vips2jpeg;
|
|
format_class->suffs = jpeg_suffs;
|
|
}
|
|
|
|
static void
|
|
vips_format_jpeg_init( VipsFormatJpeg *object )
|
|
{
|
|
}
|
|
|
|
G_DEFINE_TYPE( VipsFormatJpeg, vips_format_jpeg, VIPS_TYPE_FORMAT );
|
|
|
|
#endif /*HAVE_JPEG*/
|