diff --git a/TODO b/TODO index dc1ea73d..52975e92 100644 --- a/TODO +++ b/TODO @@ -1,13 +1,3 @@ -- when we open with a mmap window and later do im_incheck(), do we remap the - whole file? - -- remove a lot of stuff - -- read_vips() in im_open.c:179 needs moving to format and breaking into is_a, - header, load and save, as tiff etc. - - make im_file2vips, im_vips2file? - - pluggable formats for each format, store: diff --git a/configure.in b/configure.in index 657eae58..b18fba5c 100644 --- a/configure.in +++ b/configure.in @@ -375,7 +375,6 @@ AC_OUTPUT([ libsrc/colour/Makefile libsrc/conversion/Makefile libsrc/convolution/Makefile - libsrc/format/Makefile libsrc/freq_filt/Makefile libsrc/histograms_lut/Makefile libsrc/inplace/Makefile diff --git a/include/vips/dispatch.h b/include/vips/dispatch.h index 48bfca51..ca91bf47 100644 --- a/include/vips/dispatch.h +++ b/include/vips/dispatch.h @@ -145,10 +145,10 @@ typedef struct { /* Function protos for formats. */ -typedef gboolean (*im_format_is_a_fn)( const char * ); -typedef int (*im_format_header_fn)( const char *, IMAGE * ); -typedef int (*im_format_load_fn)( const char *, IMAGE * ); -typedef int (*im_format_save_fn)( IMAGE *, const char * ); +typedef gboolean (*im_format_is_a)( const char * ); +typedef int (*im_format_header)( const char *, IMAGE * ); +typedef int (*im_format_load)( const char *, IMAGE * ); +typedef int (*im_forrmat_save)( IMAGE *, const char * ); /* A VIPS image format. */ @@ -156,11 +156,11 @@ typedef struct { const char *name; /* Format name, same as mime */ const char *name_user; /* I18n'd name for users */ int priority; /* Keep formats sorted by this, default 0 */ - const char **suffs; /* Allowed suffixes */ - im_format_is_a_fn is_a; /* Filename is in format */ - im_format_header_fn header;/* Load header only from filename */ - im_format_load_fn load; /* Load image from filename */ - im_format_save_fn save; /* Save image to filename */ + const char *suffs[]; /* Allowed suffixes */ + im_format_is_a is_a; /* Filename is in format */ + im_format_header header;/* Load header only from filename */ + im_format_load load; /* Load image from filename */ + im_format_save save; /* Save image to filename */ } im_format; /* A set of VIPS formats forming a format package. @@ -300,12 +300,6 @@ im_function *im_find_function( const char *name ); im_package *im_find_package( const char *name ); im_package *im_package_of_function( const char *name ); -/* Map over and find formats. - */ -void *im_map_formats( VSListMap2Fn fn, void *a, void *b ); -im_format *im_format_for_file( const char *filename ); -im_format *im_format_for_name( const char *filename ); - /* Allocate space for, and free im_object argument lists. */ int im_free_vargv( im_function *fn, im_object *vargv ); diff --git a/libsrc/Makefile.am b/libsrc/Makefile.am index 45f62199..1f068874 100644 --- a/libsrc/Makefile.am +++ b/libsrc/Makefile.am @@ -17,7 +17,6 @@ SUBDIRS = \ conversion \ convolution \ $(C_COMPILE_DIR) \ - format \ freq_filt \ histograms_lut \ inplace \ @@ -43,7 +42,6 @@ libvips_la_LIBADD = \ conversion/libconversion.la \ convolution/libconvolution.la \ $(C_LIB) \ - format/libformat.la \ freq_filt/libfreq_filt.la \ histograms_lut/libhistograms_lut.la \ inplace/libinplace.la \ diff --git a/libsrc/conversion/Makefile.am b/libsrc/conversion/Makefile.am index ec6fe1cd..6c03baaf 100644 --- a/libsrc/conversion/Makefile.am +++ b/libsrc/conversion/Makefile.am @@ -2,9 +2,12 @@ noinst_LTLIBRARIES = libconversion.la libconversion_la_SOURCES = \ im_bernd.c \ + im_vips2tiff.c \ + im_tiff2vips.c \ conver_dispatch.c \ dbh.h \ im_bandjoin.c \ + im_analyze2vips.c \ im_black.c \ im_c2amph.c \ im_c2rect.c \ @@ -13,18 +16,26 @@ libconversion_la_SOURCES = \ im_c2real.c \ im_clip.c \ im_copy.c \ + im_csv2vips.c \ + im_vips2csv.c \ im_extract.c \ + im_exr2vips.c \ im_falsecolour.c \ im_fliphor.c \ im_flipver.c \ im_gbandjoin.c \ im_insert.c \ + im_jpeg2vips.c \ im_lrjoin.c \ + im_magick2vips.c \ im_mask2vips.c \ im_msb.c \ + im_png2vips.c \ + im_ppm2vips.c \ im_recomb.c \ im_replicate.c \ im_grid.c \ + im_raw2vips.c \ im_ri2c.c \ im_rightshift_size.c \ im_rot180.c \ @@ -37,9 +48,13 @@ libconversion_la_SOURCES = \ im_system.c \ im_print.c \ im_tbjoin.c \ + im_tile_cache.c \ im_text.c \ im_thresh.c \ im_vips2mask.c \ + im_vips2jpeg.c \ + im_vips2ppm.c \ + im_vips2png.c \ im_wrap.c \ im_zoom.c diff --git a/libsrc/conversion/conver_dispatch.c b/libsrc/conversion/conver_dispatch.c index 2847425c..8cb0f0a4 100644 --- a/libsrc/conversion/conver_dispatch.c +++ b/libsrc/conversion/conver_dispatch.c @@ -1444,6 +1444,355 @@ static im_function zoom_desc = { zoom_args /* Arg list */ }; +static int +jpeg2vips_vec( im_object *argv ) +{ + char *in = argv[0]; + IMAGE *out = argv[1]; + + if( im_jpeg2vips( in, out ) ) + return( -1 ); + + return( 0 ); +} + +static im_arg_desc jpeg2vips_args[] = { + IM_INPUT_STRING( "in" ), + IM_OUTPUT_IMAGE( "out" ) +}; + +static im_function jpeg2vips_desc = { + "im_jpeg2vips", /* Name */ + "convert from jpeg", /* Description */ + 0, /* Flags */ + jpeg2vips_vec, /* Dispatch function */ + IM_NUMBER( jpeg2vips_args ), /* Size of arg list */ + jpeg2vips_args /* Arg list */ +}; + +static int +vips2jpeg_vec( im_object *argv ) +{ + IMAGE *in = argv[0]; + char *out = argv[1]; + + if( im_vips2jpeg( in, out ) ) + return( -1 ); + + return( 0 ); +} + +static im_arg_desc vips2jpeg_args[] = { + IM_INPUT_IMAGE( "in" ), + IM_INPUT_STRING( "out" ) +}; + +static im_function vips2jpeg_desc = { + "im_vips2jpeg", /* Name */ + "convert to jpeg", /* Description */ + 0, /* Flags */ + vips2jpeg_vec, /* Dispatch function */ + IM_NUMBER( vips2jpeg_args ), /* Size of arg list */ + vips2jpeg_args /* Arg list */ +}; + +static int +vips2mimejpeg_vec( im_object *argv ) +{ + IMAGE *in = argv[0]; + int qfac = *((int *) argv[1]); + + if( im_vips2mimejpeg( in, qfac ) ) + return( -1 ); + + return( 0 ); +} + +static im_arg_desc vips2mimejpeg_args[] = { + IM_INPUT_IMAGE( "in" ), + IM_INPUT_INT( "qfac" ) +}; + +static im_function vips2mimejpeg_desc = { + "im_vips2mimejpeg", /* Name */ + "convert to jpeg as mime type on stdout", /* Description */ + 0, /* Flags */ + vips2mimejpeg_vec, /* Dispatch function */ + IM_NUMBER( vips2mimejpeg_args ), /* Size of arg list */ + vips2mimejpeg_args /* Arg list */ +}; + +/* Args for vips2png. + */ +static im_arg_desc vips2png_args[] = { + IM_INPUT_IMAGE( "in" ), + IM_INPUT_STRING( "out" ) +}; + +/* Call im_vips2png via arg vector. + */ +static int +vips2png_vec( im_object *argv ) +{ + return( im_vips2png( argv[0], argv[1] ) ); +} + +/* Description of im_vips2png. + */ +static im_function vips2png_desc = { + "im_vips2png", /* Name */ + "convert VIPS image to PNG file", /* Description */ + 0, + vips2png_vec, /* Dispatch function */ + IM_NUMBER( vips2png_args ), /* Size of arg list */ + vips2png_args /* Arg list */ +}; + +/* Args for png2vips. + */ +static im_arg_desc png2vips_args[] = { + IM_INPUT_STRING( "in" ), + IM_OUTPUT_IMAGE( "out" ) +}; + +/* Call im_png2vips via arg vector. + */ +static int +png2vips_vec( im_object *argv ) +{ + return( im_png2vips( argv[0], argv[1] ) ); +} + +/* Description of im_png2vips. + */ +static im_function png2vips_desc = { + "im_png2vips", /* Name */ + "convert PNG file to VIPS image", /* Description */ + 0, + png2vips_vec, /* Dispatch function */ + IM_NUMBER( png2vips_args ), /* Size of arg list */ + png2vips_args /* Arg list */ +}; + +/* Args for exr2vips. + */ +static im_arg_desc exr2vips_args[] = { + IM_INPUT_STRING( "in" ), + IM_OUTPUT_IMAGE( "out" ) +}; + +/* Call im_exr2vips via arg vector. + */ +static int +exr2vips_vec( im_object *argv ) +{ + return( im_exr2vips( argv[0], argv[1] ) ); +} + +/* Description of im_exr2vips. + */ +static im_function exr2vips_desc = { + "im_exr2vips", /* Name */ + "convert an OpenEXR file to VIPS", /* Description */ + 0, + exr2vips_vec, /* Dispatch function */ + IM_NUMBER( exr2vips_args ), /* Size of arg list */ + exr2vips_args /* Arg list */ +}; + +/* Args for vips2tiff. + */ +static im_arg_desc vips2tiff_args[] = { + IM_INPUT_IMAGE( "in" ), + IM_INPUT_STRING( "out" ) +}; + +/* Call im_vips2tiff via arg vector. + */ +static int +vips2tiff_vec( im_object *argv ) +{ + return( im_vips2tiff( argv[0], argv[1] ) ); +} + +/* Description of im_vips2tiff. + */ +static im_function vips2tiff_desc = { + "im_vips2tiff", /* Name */ + "convert VIPS image to TIFF file", /* Description */ + 0, + vips2tiff_vec, /* Dispatch function */ + IM_NUMBER( vips2tiff_args ), /* Size of arg list */ + vips2tiff_args /* Arg list */ +}; + +/* Args for magick2vips. + */ +static im_arg_desc magick2vips_args[] = { + IM_INPUT_STRING( "in" ), + IM_OUTPUT_IMAGE( "out" ) +}; + +/* Call im_magick2vips via arg vector. + */ +static int +magick2vips_vec( im_object *argv ) +{ + return( im_magick2vips( argv[0], argv[1] ) ); +} + +/* Description of im_magick2vips. + */ +static im_function magick2vips_desc = { + "im_magick2vips", /* Name */ + "load file with libMagick", /* Description */ + 0, + magick2vips_vec, /* Dispatch function */ + IM_NUMBER( magick2vips_args ), /* Size of arg list */ + magick2vips_args /* Arg list */ +}; + +/* Args for tiff2vips. + */ +static im_arg_desc tiff2vips_args[] = { + IM_INPUT_STRING( "in" ), + IM_OUTPUT_IMAGE( "out" ) +}; + +/* Call im_tiff2vips via arg vector. + */ +static int +tiff2vips_vec( im_object *argv ) +{ + return( im_tiff2vips( argv[0], argv[1] ) ); +} + +/* Description of im_tiff2vips. + */ +static im_function tiff2vips_desc = { + "im_tiff2vips", /* Name */ + "convert TIFF file to VIPS image", /* Description */ + 0, + tiff2vips_vec, /* Dispatch function */ + IM_NUMBER( tiff2vips_args ), /* Size of arg list */ + tiff2vips_args /* Arg list */ +}; + +static int +analyze2vips_vec( im_object *argv ) +{ + const char *in = argv[0]; + IMAGE *out = argv[1]; + + return( im_analyze2vips( in, out ) ); +} + +static im_arg_desc analyze2vips_arg_types[] = { + IM_INPUT_STRING( "filename" ), + IM_OUTPUT_IMAGE( "im" ) +}; + +static im_function analyze2vips_desc = { + "im_analyze2vips", /* Name */ + "read a file in analyze format",/* Description */ + 0, /* Flags */ + analyze2vips_vec, /* Dispatch function */ + IM_NUMBER( analyze2vips_arg_types ),/* Size of arg list */ + analyze2vips_arg_types /* Arg list */ +}; + +static int +csv2vips_vec( im_object *argv ) +{ + const char *in = argv[0]; + IMAGE *out = argv[1]; + + return( im_csv2vips( in, out ) ); +} + +static im_arg_desc csv2vips_arg_types[] = { + IM_INPUT_STRING( "filename" ), + IM_OUTPUT_IMAGE( "im" ) +}; + +static im_function csv2vips_desc = { + "im_csv2vips", /* Name */ + "read a file in csv format",/* Description */ + 0, /* Flags */ + csv2vips_vec, /* Dispatch function */ + IM_NUMBER( csv2vips_arg_types ),/* Size of arg list */ + csv2vips_arg_types /* Arg list */ +}; + +static int +vips2csv_vec( im_object *argv ) +{ + IMAGE *in = argv[0]; + const char *filename = argv[1]; + + return( im_vips2csv( in, filename ) ); +} + +static im_arg_desc vips2csv_arg_types[] = { + IM_INPUT_IMAGE( "in" ), + IM_INPUT_STRING( "filename" ) +}; + +static im_function vips2csv_desc = { + "im_vips2csv", /* Name */ + "write an image in csv format", /* Description */ + 0, /* Flags */ + vips2csv_vec, /* Dispatch function */ + IM_NUMBER( vips2csv_arg_types ),/* Size of arg list */ + vips2csv_arg_types /* Arg list */ +}; + +static int +ppm2vips_vec( im_object *argv ) +{ + const char *in = argv[0]; + IMAGE *out = argv[1]; + + return( im_ppm2vips( in, out ) ); +} + +static im_arg_desc ppm2vips_arg_types[] = { + IM_INPUT_STRING( "filename" ), + IM_OUTPUT_IMAGE( "im" ) +}; + +static im_function ppm2vips_desc = { + "im_ppm2vips", /* Name */ + "read a file in pbm/pgm/ppm format", /* Description */ + 0, /* Flags */ + ppm2vips_vec, /* Dispatch function */ + IM_NUMBER( ppm2vips_arg_types ),/* Size of arg list */ + ppm2vips_arg_types /* Arg list */ +}; + +static int +vips2ppm_vec( im_object *argv ) +{ + IMAGE *im = argv[0]; + const char *filename = argv[1]; + + return( im_vips2ppm( im, filename ) ); +} + +static im_arg_desc vips2ppm_arg_types[] = { + IM_INPUT_IMAGE( "im" ), + IM_INPUT_STRING( "filename" ) +}; + +static im_function vips2ppm_desc = { + "im_vips2ppm", /* Name */ + "write a file in pbm/pgm/ppm format", /* Description */ + 0, /* Flags */ + vips2ppm_vec, /* Dispatch function */ + IM_NUMBER( vips2ppm_arg_types ),/* Size of arg list */ + vips2ppm_arg_types /* Arg list */ +}; + /* Call im_msb via arg vector. */ static int @@ -1585,6 +1934,7 @@ static im_function *conv_list[] = { ©_swap_desc, ©_set_desc, ©_set_meta_desc, + &csv2vips_desc, &extract_area_desc, &extract_areabands_desc, &extract_band_desc, @@ -1597,10 +1947,16 @@ static im_function *conv_list[] = { &grid_desc, &insert_desc, &insert_noexpand_desc, + &jpeg2vips_desc, &lrjoin_desc, + &magick2vips_desc, &mask2vips_desc, &msb_desc, &msb_band_desc, + &png2vips_desc, + &exr2vips_desc, + &ppm2vips_desc, + &analyze2vips_desc, &print_desc, &recomb_desc, &replicate_desc, @@ -1617,7 +1973,14 @@ static im_function *conv_list[] = { &tbjoin_desc, &text_desc, &thresh_desc, + &tiff2vips_desc, + &vips2csv_desc, + &vips2jpeg_desc, &vips2mask_desc, + &vips2mimejpeg_desc, + &vips2png_desc, + &vips2ppm_desc, + &vips2tiff_desc, &wrap_desc, &zoom_desc }; diff --git a/libsrc/conversion/dbh.h b/libsrc/conversion/dbh.h new file mode 100644 index 00000000..782cbf67 --- /dev/null +++ b/libsrc/conversion/dbh.h @@ -0,0 +1,98 @@ +/* + * + * (c) Copyright, 1986-1991 + * Biodynamics Research Unit + * Mayo Foundation + * + * dbh.h + * + * + * database sub-definitions + */ + +/* + * + * The previous-generation header for Analyze images. Although (c) Mayo + * Foundation, it has been in the public domain for many years. + * + * Analyze images have a 348 byte header stored in a file with a .hdr suffix, + * and a corresponding .img file containing just the pixel data. + * + */ + +struct header_key /* header_key */ + { /* off + size*/ + int sizeof_hdr; /* 0 + 4 */ + char data_type[10]; /* 4 + 10 */ + char db_name[18]; /* 14 + 18 */ + int extents; /* 32 + 4 */ + short int session_error; /* 36 + 2 */ + char regular; /* 38 + 1 */ + char hkey_un0; /* 39 + 1 */ + }; /* total=40 */ + +struct image_dimension /* image_dimension */ + { /* off + size*/ + short int dim[8]; /* 0 + 16 */ + char vox_units[4]; /* 16 + 4 */ + char cal_units[8]; /* 20 + 4 */ + short int unused1; /* 24 + 2 */ + short int datatype; /* 30 + 2 */ + short int bitpix; /* 32 + 2 */ + short int dim_un0; /* 34 + 2 */ + float pixdim[8]; /* 36 + 32 */ + + /* pixdim[] specifies the voxel dimensions: + pixdim[1] - voxel width + pixdim[2] - voxel height + pixdim[3] - interslice distance + ..etc + */ + float vox_offset; /* 68 + 4 */ + float funused1; /* 72 + 4 */ + float funused2; /* 76 + 4 */ + float funused3; /* 80 + 4 */ + float cal_max; /* 84 + 4 */ + float cal_min; /* 88 + 4 */ + int compressed; /* 92 + 4 */ + int verified; /* 96 + 4 */ + int glmax, glmin; /* 100 + 8 */ + }; + +struct data_history /* data_history */ + { /* off + size*/ + char descrip[80]; /* 0 + 80 */ + char aux_file[24]; /* 80 + 24 */ + char orient; /* 104 + 1 */ + char originator[10]; /* 105 + 10 */ + char generated[10]; /* 115 + 10 */ + char scannum[10]; /* 125 + 10 */ + char patient_id[10]; /* 135 + 10 */ + char exp_date[10]; /* 145 + 10 */ + char exp_time[10]; /* 155 + 10 */ + char hist_un0[3]; /* 165 + 3 */ + int views; /* 168 + 4 */ + int vols_added; /* 172 + 4 */ + int start_field; /* 176 + 4 */ + int field_skip; /* 180 + 4 */ + int omax,omin; /* 184 + 8 */ + int smax,smin; /* 192 + 8 */ + }; /* total=200 */ + +struct dsr /* dsr */ + { /* off + size*/ + struct header_key hk; /* 0 + 40 */ + struct image_dimension dime; /* 40 + 108 */ + struct data_history hist; /* 148 + 200 */ + }; /* total=348 */ + +/* Acceptable values for hdr.dime.datatype */ +#define DT_UNKNOWN 0 +#define DT_BINARY 1 +#define DT_UNSIGNED_CHAR 2 +#define DT_SIGNED_SHORT 4 +#define DT_SIGNED_INT 8 +#define DT_FLOAT 16 +#define DT_COMPLEX 32 +#define DT_DOUBLE 64 +#define DT_RGB 128 diff --git a/libsrc/conversion/im_analyze2vips.c b/libsrc/conversion/im_analyze2vips.c new file mode 100644 index 00000000..d5f9eeb2 --- /dev/null +++ b/libsrc/conversion/im_analyze2vips.c @@ -0,0 +1,583 @@ +/* Read a Analyze file. Old-style header (so called 7.5 format). + * + * 3/8/05 + * - dbh.h header from Ralph Myers + * 22/8/05 + * - better byteswapper + */ + +/* + + 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 + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include "dbh.h" + +#ifdef WITH_DMALLOC +#include +#endif /*WITH_DMALLOC*/ + +/* The things we can have in header fields. Can't use GType, since we want a + * static value we can use in a declaration. + */ +typedef enum { + BYTE, + SHORT, + INT, + FLOAT, + STRING +} Type; + +/* A field in the dsr header. + */ +typedef struct { + const char *name; /* Eg. "header_key.sizeof_hdr" */ + Type type; + glong offset; /* Offset in struct */ + int len; /* Sizeof ... useful for string types */ +} Field; + +static Field dsr_header[] = { + { "dsr-header_key.sizeof_hdr", INT, + G_STRUCT_OFFSET( struct dsr, hk.sizeof_hdr ), 4 }, + { "dsr-header_key.data_type", STRING, + G_STRUCT_OFFSET( struct dsr, hk.data_type ), 10 }, + { "dsr-header_key.db_name", STRING, + G_STRUCT_OFFSET( struct dsr, hk.db_name ), 18 }, + { "dsr-header_key.extents", INT, + G_STRUCT_OFFSET( struct dsr, hk.extents ), 4 }, + { "dsr-header_key.session_error", SHORT, + G_STRUCT_OFFSET( struct dsr, hk.session_error ), 2 }, + { "dsr-header_key.regular", BYTE, + G_STRUCT_OFFSET( struct dsr, hk.regular ), 1 }, + { "dsr-header_key.hkey_un0", BYTE, + G_STRUCT_OFFSET( struct dsr, hk.hkey_un0 ), 1 }, + + { "dsr-image_dimension.dim[0]", SHORT, + G_STRUCT_OFFSET( struct dsr, dime.dim[0] ), 2 }, + { "dsr-image_dimension.dim[1]", SHORT, + G_STRUCT_OFFSET( struct dsr, dime.dim[1] ), 2 }, + { "dsr-image_dimension.dim[2]", SHORT, + G_STRUCT_OFFSET( struct dsr, dime.dim[2] ), 2 }, + { "dsr-image_dimension.dim[3]", SHORT, + G_STRUCT_OFFSET( struct dsr, dime.dim[3] ), 2 }, + { "dsr-image_dimension.dim[4]", SHORT, + G_STRUCT_OFFSET( struct dsr, dime.dim[4] ), 2 }, + { "dsr-image_dimension.dim[5]", SHORT, + G_STRUCT_OFFSET( struct dsr, dime.dim[5] ), 2 }, + { "dsr-image_dimension.dim[6]", SHORT, + G_STRUCT_OFFSET( struct dsr, dime.dim[6] ), 2 }, + { "dsr-image_dimension.dim[7]", SHORT, + G_STRUCT_OFFSET( struct dsr, dime.dim[7] ), 2 }, + { "dsr-image_dimension.vox_units[0]", BYTE, + G_STRUCT_OFFSET( struct dsr, dime.vox_units[0] ), 1 }, + { "dsr-image_dimension.vox_units[1]", BYTE, + G_STRUCT_OFFSET( struct dsr, dime.vox_units[1] ), 1 }, + { "dsr-image_dimension.vox_units[2]", BYTE, + G_STRUCT_OFFSET( struct dsr, dime.vox_units[2] ), 1 }, + { "dsr-image_dimension.vox_units[3]", BYTE, + G_STRUCT_OFFSET( struct dsr, dime.vox_units[3] ), 1 }, + { "dsr-image_dimension.cal_units[0]", BYTE, + G_STRUCT_OFFSET( struct dsr, dime.cal_units[0] ), 1 }, + { "dsr-image_dimension.cal_units[1]", BYTE, + G_STRUCT_OFFSET( struct dsr, dime.cal_units[1] ), 1 }, + { "dsr-image_dimension.cal_units[2]", BYTE, + G_STRUCT_OFFSET( struct dsr, dime.cal_units[2] ), 1 }, + { "dsr-image_dimension.cal_units[3]", BYTE, + G_STRUCT_OFFSET( struct dsr, dime.cal_units[3] ), 1 }, + { "dsr-image_dimension.cal_units[4]", BYTE, + G_STRUCT_OFFSET( struct dsr, dime.cal_units[4] ), 1 }, + { "dsr-image_dimension.cal_units[5]", BYTE, + G_STRUCT_OFFSET( struct dsr, dime.cal_units[5] ), 1 }, + { "dsr-image_dimension.cal_units[6]", BYTE, + G_STRUCT_OFFSET( struct dsr, dime.cal_units[6] ), 1 }, + { "dsr-image_dimension.cal_units[7]", BYTE, + G_STRUCT_OFFSET( struct dsr, dime.cal_units[7] ), 1 }, + { "dsr-image_dimension.data_type", SHORT, + G_STRUCT_OFFSET( struct dsr, dime.datatype ), 2 }, + { "dsr-image_dimension.bitpix", SHORT, + G_STRUCT_OFFSET( struct dsr, dime.bitpix ), 2 }, + { "dsr-image_dimension.dim_un0", SHORT, + G_STRUCT_OFFSET( struct dsr, dime.dim_un0 ), 2 }, + { "dsr-image_dimension.pixdim[0]", FLOAT, + G_STRUCT_OFFSET( struct dsr, dime.pixdim[0] ), 4 }, + { "dsr-image_dimension.pixdim[1]", FLOAT, + G_STRUCT_OFFSET( struct dsr, dime.pixdim[1] ), 4 }, + { "dsr-image_dimension.pixdim[2]", FLOAT, + G_STRUCT_OFFSET( struct dsr, dime.pixdim[2] ), 4 }, + { "dsr-image_dimension.pixdim[3]", FLOAT, + G_STRUCT_OFFSET( struct dsr, dime.pixdim[3] ), 4 }, + { "dsr-image_dimension.pixdim[4]", FLOAT, + G_STRUCT_OFFSET( struct dsr, dime.pixdim[4] ), 4 }, + { "dsr-image_dimension.pixdim[5]", FLOAT, + G_STRUCT_OFFSET( struct dsr, dime.pixdim[5] ), 4 }, + { "dsr-image_dimension.pixdim[6]", FLOAT, + G_STRUCT_OFFSET( struct dsr, dime.pixdim[6] ), 4 }, + { "dsr-image_dimension.pixdim[7]", FLOAT, + G_STRUCT_OFFSET( struct dsr, dime.pixdim[7] ), 4 }, + { "dsr-image_dimension.vox_offset", FLOAT, + G_STRUCT_OFFSET( struct dsr, dime.vox_offset ), 4 }, + { "dsr-image_dimension.cal_max", FLOAT, + G_STRUCT_OFFSET( struct dsr, dime.cal_max ), 4 }, + { "dsr-image_dimension.cal_min", FLOAT, + G_STRUCT_OFFSET( struct dsr, dime.cal_min ), 4 }, + { "dsr-image_dimension.compressed", INT, + G_STRUCT_OFFSET( struct dsr, dime.compressed ), 4 }, + { "dsr-image_dimension.verified", INT, + G_STRUCT_OFFSET( struct dsr, dime.verified ), 4 }, + { "dsr-image_dimension.glmax", INT, + G_STRUCT_OFFSET( struct dsr, dime.glmax ), 4 }, + { "dsr-image_dimension.glmin", INT, + G_STRUCT_OFFSET( struct dsr, dime.glmin ), 4 }, + + { "dsr-data_history.descrip", STRING, + G_STRUCT_OFFSET( struct dsr, hist.descrip ), 80 }, + { "dsr-data_history.aux_file", STRING, + G_STRUCT_OFFSET( struct dsr, hist.aux_file ), 24 }, + { "dsr-data_history.orient", BYTE, + G_STRUCT_OFFSET( struct dsr, hist.orient ), 1 }, + { "dsr-data_history.originator", STRING, + G_STRUCT_OFFSET( struct dsr, hist.originator ), 10 }, + { "dsr-data_history.generated", STRING, + G_STRUCT_OFFSET( struct dsr, hist.generated ), 10 }, + { "dsr-data_history.scannum", STRING, + G_STRUCT_OFFSET( struct dsr, hist.scannum ), 10 }, + { "dsr-data_history.patient_id", STRING, + G_STRUCT_OFFSET( struct dsr, hist.patient_id ), 10 }, + { "dsr-data_history.exp_date", STRING, + G_STRUCT_OFFSET( struct dsr, hist.exp_date ), 10 }, + { "dsr-data_history.exp_time", STRING, + G_STRUCT_OFFSET( struct dsr, hist.exp_time ), 10 }, + { "dsr-data_history.hist_un0", STRING, + G_STRUCT_OFFSET( struct dsr, hist.hist_un0 ), 3 }, + { "dsr-data_history.views", INT, + G_STRUCT_OFFSET( struct dsr, hist.views ), 4 }, + { "dsr-data_history.vols_added", INT, + G_STRUCT_OFFSET( struct dsr, hist.vols_added ), 4 }, + { "dsr-data_history.start_field", INT, + G_STRUCT_OFFSET( struct dsr, hist.start_field ), 4 }, + { "dsr-data_history.field_skip", INT, + G_STRUCT_OFFSET( struct dsr, hist.field_skip ), 4 }, + { "dsr-data_history.omax", INT, + G_STRUCT_OFFSET( struct dsr, hist.omax ), 4 }, + { "dsr-data_history.omin", INT, + G_STRUCT_OFFSET( struct dsr, hist.omin ), 4 }, + { "dsr-data_history.smax", INT, + G_STRUCT_OFFSET( struct dsr, hist.smax ), 4 }, + { "dsr-data_history.smin", INT, + G_STRUCT_OFFSET( struct dsr, hist.smin ), 4 } +}; + +/* Given a filename, generate the names for the header and the image data. + * + * Eg. + * "fred" -> "fred.hdr", "fred.img" + * "fred.img" -> "fred.hdr", "fred.img" + */ +static void +generate_filenames( const char *path, char *header, char *image ) +{ + char name[FILENAME_MAX]; + char mode[FILENAME_MAX]; + + const char *olds[] = { ".img", ".hdr" }; + + /* Take off any modifiers. + */ + im_filename_split( path, name, mode ); + + im__change_suffix( name, header, FILENAME_MAX, ".hdr", olds, 2 ); + im__change_suffix( name, image, FILENAME_MAX, ".img", olds, 2 ); +} + +/* str is a str which may not be NULL-terminated. Return a pointer to a static + * buffer with a NULL-terminated version so we can safely printf() the string. + * Also, make sure the string is plain ascii. + */ +static char * +getstr( int mx, const char *str ) +{ + static char buf[256]; + int i; + + assert( mx < 256 ); + + strncpy( buf, str, mx ); + buf[mx]= '\0'; + + /* How annoying, patient_id has some funny ctrlchars in that mess up + * xml encode later. + */ + for( i = 0; i < mx && buf[i]; i++ ) + if( !isascii( buf[i] ) || buf[i] < 32 ) + buf[i] = '@'; + + return( buf ); +} + +#ifdef DEBUG +static void +print_dsr( struct dsr *d ) +{ + int i; + + for( i = 0; i < IM_NUMBER( dsr_header ); i++ ) { + printf( "%s = ", dsr_header[i].name ); + + switch( dsr_header[i].type ) { + case BYTE: + printf( "%d\n", G_STRUCT_MEMBER( char, d, + dsr_header[i].offset ) ); + break; + + case SHORT: + printf( "%d\n", G_STRUCT_MEMBER( short, d, + dsr_header[i].offset ) ); + break; + + case INT: + printf( "%d\n", G_STRUCT_MEMBER( int, d, + dsr_header[i].offset ) ); + break; + + case FLOAT: + printf( "%g\n", G_STRUCT_MEMBER( float, d, + dsr_header[i].offset ) ); + break; + + case STRING: + printf( "\"%s\"\n", getstr( dsr_header[i].len, + &G_STRUCT_MEMBER( char, d, + dsr_header[i].offset ) ) ); + break; + + default: + assert( 0 ); + } + } +} +#endif /*DEBUG*/ + +static struct dsr * +read_header( const char *header ) +{ + struct dsr *d; + unsigned int len; + + if( !(d = (struct dsr *) im__file_read_name( header, &len )) ) + return( NULL ); + if( len != sizeof( struct dsr ) ) { + im_error( "im_analyze2vips", + _( "header file size incorrect" ) ); + im_free( d ); + return( NULL ); + } + + /* Ouch! Should check at configure time I guess. + */ + assert( sizeof( struct dsr ) == 348 ); + + /* dsr headers are always SPARC byte order (MSB first). Do we need to + * swap? + */ + if( !im_amiMSBfirst() ) { + int i; + + for( i = 0; i < IM_NUMBER( dsr_header ); i++ ) { + unsigned char *p; + + + switch( dsr_header[i].type ) { + case SHORT: + p = &G_STRUCT_MEMBER( unsigned char, d, + dsr_header[i].offset ); + im__read_2byte( 1, p, &p ); + break; + + case INT: + case FLOAT: + p = &G_STRUCT_MEMBER( unsigned char, d, + dsr_header[i].offset ); + im__read_4byte( 1, p, &p ); + break; + + case BYTE: + case STRING: + break; + + default: + assert( 0 ); + } + } + } + + if( len != d->hk.sizeof_hdr ) { + im_error( "im_analyze2vips", + _( "header file size incorrect" ) ); + im_free( d ); + return( NULL ); + } + + return( d ); +} + +/* Try to get VIPS header properties from a dsr. + */ +static int +get_vips_properties( struct dsr *d, + int *width, int *height, int *bands, int *fmt ) +{ + int i; + + if( d->dime.dim[0] < 2 || d->dime.dim[0] > 7 ) { + im_error( "im_analyze2vips", + _( "%d-dimensional images not supported" ), + d->dime.dim[0] ); + return( -1 ); + } + + /* Size of base 2d images. + */ + *width = d->dime.dim[1]; + *height = d->dime.dim[2]; + + for( i = 3; i <= d->dime.dim[0]; i++ ) + *height *= d->dime.dim[i]; + + /* Check it's a datatype we can handle. + */ + switch( d->dime.datatype ) { + case DT_UNSIGNED_CHAR: + *bands = 1; + *fmt = IM_BANDFMT_UCHAR; + break; + + case DT_SIGNED_SHORT: + *bands = 1; + *fmt = IM_BANDFMT_SHORT; + break; + + case DT_SIGNED_INT: + *bands = 1; + *fmt = IM_BANDFMT_INT; + break; + + case DT_FLOAT: + *bands = 1; + *fmt = IM_BANDFMT_FLOAT; + break; + + case DT_COMPLEX: + *bands = 1; + *fmt = IM_BANDFMT_COMPLEX; + break; + + case DT_DOUBLE: + *bands = 1; + *fmt = IM_BANDFMT_DOUBLE; + break; + + case DT_RGB: + *bands = 3; + *fmt = IM_BANDFMT_UCHAR; + break; + + default: + im_error( "im_analyze2vips", + _( "datatype %d not supported" ), d->dime.datatype ); + return( -1 ); + } + +#ifdef DEBUG + printf( "get_vips_properties: width = %d\n", *width ); + printf( "get_vips_properties: height = %d\n", *height ); + printf( "get_vips_properties: bands = %d\n", *bands ); + printf( "get_vips_properties: fmt = %d\n", *fmt ); +#endif /*DEBUG*/ + + return( 0 ); +} + +static void +attach_meta( IMAGE *out, struct dsr *d ) +{ + int i; + + im_meta_set_blob( out, "dsr", + (im_callback_fn) im_free, d, d->hk.sizeof_hdr ); + + for( i = 0; i < IM_NUMBER( dsr_header ); i++ ) { + switch( dsr_header[i].type ) { + case BYTE: + im_meta_set_int( out, dsr_header[i].name, + G_STRUCT_MEMBER( char, d, + dsr_header[i].offset ) ); + break; + + case SHORT: + im_meta_set_int( out, dsr_header[i].name, + G_STRUCT_MEMBER( short, d, + dsr_header[i].offset ) ); + break; + + case INT: + im_meta_set_int( out, dsr_header[i].name, + G_STRUCT_MEMBER( int, d, + dsr_header[i].offset ) ); + break; + + case FLOAT: + im_meta_set_double( out, dsr_header[i].name, + G_STRUCT_MEMBER( float, d, + dsr_header[i].offset ) ); + break; + + case STRING: + im_meta_set_string( out, dsr_header[i].name, + getstr( dsr_header[i].len, + &G_STRUCT_MEMBER( char, d, + dsr_header[i].offset ) ) ); + break; + + default: + assert( 0 ); + } + } +} + +int +im_isanalyze( const char *filename ) +{ + char header[FILENAME_MAX]; + char image[FILENAME_MAX]; + struct dsr *d; + int width, height; + int bands; + int fmt; + + generate_filenames( filename, header, image ); + if( !(d = read_header( header )) ) + return( 0 ); + +#ifdef DEBUG + print_dsr( d ); +#endif /*DEBUG*/ + + if( get_vips_properties( d, &width, &height, &bands, &fmt ) ) { + im_free( d ); + return( 0 ); + } + im_free( d ); + + return( 1 ); +} + +int +im_analyze2vips_header( const char *filename, IMAGE *out ) +{ + char header[FILENAME_MAX]; + char image[FILENAME_MAX]; + struct dsr *d; + int width, height; + int bands; + int fmt; + + generate_filenames( filename, header, image ); + if( !(d = read_header( header )) ) + return( -1 ); + +#ifdef DEBUG + print_dsr( d ); +#endif /*DEBUG*/ + + if( get_vips_properties( d, &width, &height, &bands, &fmt ) ) { + im_free( d ); + return( -1 ); + } + + im_initdesc( out, width, height, bands, im_bits_of_fmt( fmt ), fmt, + IM_CODING_NONE, + bands == 1 ? IM_TYPE_B_W : IM_TYPE_sRGB, + 1.0, 1.0, + 0, 0 ); + + attach_meta( out, d ); + + return( 0 ); +} + +int +im_analyze2vips( const char *filename, IMAGE *out ) +{ + char header[FILENAME_MAX]; + char image[FILENAME_MAX]; + struct dsr *d; + IMAGE *t[2]; + int width, height; + int bands; + int fmt; + im_arch_type arch = im_amiMSBfirst() ? + IM_ARCH_NATIVE : IM_ARCH_BYTE_SWAPPED; + + generate_filenames( filename, header, image ); + if( !(d = read_header( header )) ) + return( -1 ); + +#ifdef DEBUG + print_dsr( d ); +#endif /*DEBUG*/ + + if( get_vips_properties( d, &width, &height, &bands, &fmt ) || + im_open_local_array( out, t, 2, "im_analyze2vips", "p" ) || + im_raw2vips( image, t[0], width, height, + bands * im_bits_of_fmt( fmt ) / 8, 0 ) || + im_copy_morph( t[0], t[1], bands, fmt, IM_CODING_NONE ) || + im_copy_from( t[1], out, arch ) ) { + im_free( d ); + return( -1 ); + } + + attach_meta( out, d ); + + return( 0 ); +} + diff --git a/libsrc/conversion/im_csv2vips.c b/libsrc/conversion/im_csv2vips.c new file mode 100644 index 00000000..8f07076e --- /dev/null +++ b/libsrc/conversion/im_csv2vips.c @@ -0,0 +1,316 @@ +/* Read a csv file. + * + * 19/12/05 JC + * - hacked from ppm reader + * 11/9/06 + * - now distingushes whitespace and separators, so we can have blank + * fields + * 20/9/06 + * - oop, unquoted trailing columns could get missed + * 17/5/07 + * - added im_csv2vips_header() + */ + +/* + + 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 + + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include +#include +#include +#include +#include + +#include + +#ifdef WITH_DMALLOC +#include +#endif /*WITH_DMALLOC*/ + +static int +skip_line( FILE *fp ) +{ + int ch; + + while( (ch = fgetc( fp )) != '\n' && ch != EOF ) + ; + + return( ch ); +} + +static int +skip_white( FILE *fp, const char whitemap[256] ) +{ + int ch; + + do { + ch = fgetc( fp ); + } while (ch != EOF && ch != '\n' && whitemap[ch] ); + + ungetc( ch, fp ); + + return( ch ); +} + +static int +skip_to_sep( FILE *fp, const char sepmap[256] ) +{ + int ch; + + do { + ch = fgetc( fp ); + } while (ch != EOF && ch != '\n' && !sepmap[ch] ); + + ungetc( ch, fp ); + + return( ch ); +} + +/* Read a single item. Syntax is: + * + * item : whitespace* double? whitespace* [EOF|EOL|separator] + * + * Return the char that caused failure on fail (EOF or \n). + */ +static int +read_double( FILE *fp, const char whitemap[256], const char sepmap[256], + int lineno, int colno, double *out ) +{ + int ch; + + /* The fscanf() may change this ... but all other cases need a zero. + */ + *out = 0; + + ch = skip_white( fp, whitemap ); + if( ch == EOF || ch == '\n' ) + return( ch ); + + if( !sepmap[ch] && fscanf( fp, "%lf", out ) != 1 ) { + /* Only a warning, since (for example) exported spreadsheets + * will often have text or date fields. + */ + im_warn( "im_csv2vips", + _( "error parsing number, line %d, column %d" ), + lineno, colno ); + + /* Step over the bad data to the next separator. + */ + ch = skip_to_sep( fp, sepmap ); + } + + /* Don't need to check result, we have read a field successfully. + */ + ch = skip_white( fp, whitemap ); + + /* If it's a separator, we have to step over it. + */ + if( ch != EOF && sepmap[ch] ) + (void) fgetc( fp ); + + return( 0 ); +} + +static int +read_csv( FILE *fp, IMAGE *out, + int start_skip, + const char *whitespace, const char *separator, + int lines ) +{ + int i; + char whitemap[256]; + char sepmap[256]; + const char *p; + fpos_t pos; + int columns; + int ch; + double d; + double *buf; + int y; + + /* Make our char maps. + */ + for( i = 0; i < 256; i++ ) { + whitemap[i] = 0; + sepmap[i] = 0; + } + for( p = whitespace; *p; p++ ) + whitemap[(int) *p] = 1; + for( p = separator; *p; p++ ) + sepmap[(int) *p] = 1; + + /* Skip first few lines. + */ + for( i = 0; i < start_skip; i++ ) + if( skip_line( fp ) == EOF ) { + im_error( "im_csv2vips", + _( "end of file while skipping start" ) ); + return( -1 ); + } + + /* Parse the first line to get number of columns. Only bother checking + * fgetpos() the first time we use it: assume it's working after this. + */ + if( fgetpos( fp, &pos ) ) { + im_error_system( errno, "im_csv2vips", _( "unable to seek" ) ); + return( -1 ); + } + for( columns = 0; (ch = read_double( fp, whitemap, sepmap, + start_skip + 1, columns + 1, &d )) == 0; columns++ ) + ; + fsetpos( fp, &pos ); + + if( columns == 0 ) { + im_error( "im_csv2vips", _( "empty line" ) ); + return( -1 ); + } + if( ch == -2 ) + /* Failed to parse a number. + */ + return( -1 ); + + /* If lines is -1, we have to parse the whole file to get the + * number of lines out. + */ + if( lines == -1 ) { + fgetpos( fp, &pos ); + for( lines = 0; skip_line( fp ) != EOF; lines++ ) + ; + fsetpos( fp, &pos ); + } + + im_initdesc( out, columns, lines, 1, + IM_BBITS_DOUBLE, IM_BANDFMT_DOUBLE, + IM_CODING_NONE, IM_TYPE_B_W, 1.0, 1.0, 0, 0 ); + + if( im_outcheck( out ) || im_setupout( out ) || + !(buf = IM_ARRAY( out, IM_IMAGE_N_ELEMENTS( out ), double )) ) + return( -1 ); + + for( y = 0; y < lines; y++ ) { + int x; + + for( x = 0; x < columns; x++ ) { + ch = read_double( fp, whitemap, sepmap, + y + start_skip + 1, x + 1, &d ); + if( ch == EOF ) { + im_error( "im_csv2vips", + _( "unexpected end of file" ) ); + return( -1 ); + } + else if( ch == '\n' ) { + im_error( "im_csv2vips", + _( "unexpected end of line" ) ); + return( -1 ); + } + else if( ch ) + /* Parse error. + */ + return( -1 ); + + buf[x] = d; + } + + if( im_writeline( y, out, (PEL *) buf ) ) + return( -1 ); + + /* Skip over the '\n' to the next line. + */ + skip_line( fp ); + } + + return( 0 ); +} + +int +im_csv2vips( const char *filename, IMAGE *out ) +{ + /* Read options. + */ + int start_skip = 0; + char *whitespace = " \""; + char *separator = ";,\t"; + int lines = -1; + + char name[FILENAME_MAX]; + char mode[FILENAME_MAX]; + char *p, *q, *r; + FILE *fp; + + /* Parse mode string. + */ + im_filename_split( filename, name, mode ); + p = &mode[0]; + while( (q = im_getnextoption( &p )) ) { + if( im_isprefix( "ski", q ) && (r = im_getsuboption( q )) ) + start_skip = atoi( r ); + else if( im_isprefix( "whi", q ) && (r = im_getsuboption( q )) ) + whitespace = r; + else if( im_isprefix( "sep", q ) && (r = im_getsuboption( q )) ) + separator = r; + else if( im_isprefix( "lin", q ) && (r = im_getsuboption( q )) ) + lines = atoi( r ); + } + + if( !(fp = fopen( name, "r" )) ) { + im_error( "im_csv2vips", + _( "unable to open \"%s\"" ), name ); + return( -1 ); + } + + if( read_csv( fp, out, start_skip, whitespace, separator, lines ) ) { + fclose( fp ); + return( -1 ); + } + fclose( fp ); + + return( 0 ); +} + +/* We can't just read the header of a CSV. Instead, we read to a temp image, + * then copy just the header to the output. + */ +int +im_csv2vips_header( const char *filename, IMAGE *out ) +{ + IMAGE *t; + + if( !(t = im_open( "im_csv2vips_header", "p" )) ) + return( -1 ); + if( im_csv2vips( filename, t ) || + im_cp_desc( out, t ) ) { + im_close( t ); + return( -1 ); + } + im_close( t ); + + return( 0 ); +} diff --git a/libsrc/conversion/im_exr2vips.c b/libsrc/conversion/im_exr2vips.c new file mode 100644 index 00000000..c5e58686 --- /dev/null +++ b/libsrc/conversion/im_exr2vips.c @@ -0,0 +1,427 @@ +/* Convert OpenEXR to VIPS + * + * 1/5/06 + * - from im_png2vips.c + * 17/5/06 + * - oops, buffer calcs were wrong + * 19/5/06 + * - added tiled read, with a separate cache + * - removed *255 we had before, better to do something clever with + * chromaticities + + - colour management + - attributes + - more of OpenEXR's pixel formats + - more than just RGBA channels + + the openexr C API is very limited ... it seems RGBA half pixels is + all you can do + + openexr lets you have different formats in different channels :-( + + there's no API to read the "chromaticities" attribute :-( + + */ + +/* + + 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 + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#ifndef HAVE_OPENEXR + +#include + +int +im_exr2vips( const char *name, IMAGE *out ) +{ + im_error( "im_exr2vips", _( "OpenEXR support disabled" ) ); + return( -1 ); +} + +int +im_exr2vips_header( const char *name, IMAGE *out ) +{ + im_error( "im_exr2vips_header", _( "OpenEXR support disabled" ) ); + return( -1 ); +} + +#else /*HAVE_OPENEXR*/ + +#include +#include +#include +#include + +#include +#include + +#include + +#ifdef WITH_DMALLOC +#include +#endif /*WITH_DMALLOC*/ + +/* What we track during a OpenEXR read. + */ +typedef struct { + char *name; + IMAGE *out; + + ImfTiledInputFile *tiles; + ImfInputFile *lines; + const ImfHeader *header; + Rect window; + int tile_width; + int tile_height; + + /* Need to single-thread calls to ReadTile. + */ + GMutex *lock; +} Read; + +static void +get_imf_error( void ) +{ + im_error( "im_exr2vips", _( "EXR error: %s" ), ImfErrorMessage() ); +} + +static void +read_destroy( Read *read ) +{ + IM_FREE( read->name ); + + IM_FREEF( ImfCloseTiledInputFile, read->tiles ); + IM_FREEF( ImfCloseInputFile, read->lines ); + + IM_FREEF( g_mutex_free, read->lock ); + + im_free( read ); +} + +static Read * +read_new( const char *name, IMAGE *out ) +{ + Read *read; + int xmin, ymin; + int xmax, ymax; + + if( !(read = IM_NEW( NULL, Read )) ) + return( NULL ); + + read->name = im_strdup( NULL, name ); + read->out = out; + read->tiles = NULL; + read->lines = NULL; + read->lock = NULL; + + if( im_add_close_callback( out, + (im_callback_fn) read_destroy, read, NULL ) ) { + read_destroy( read ); + return( NULL ); + } + + /* Try to open tiled first ... if that fails, fall back to scanlines. + + FIXME ... seems a bit ugly, but how else can you spot a tiled + EXR image? + + */ + if( !(read->tiles = ImfOpenTiledInputFile( read->name )) ) { + if( !(read->lines = ImfOpenInputFile( read->name )) ) { + get_imf_error(); + return( NULL ); + } + } + +#ifdef DEBUG + if( read->tiles ) + printf( "im_exr2vips: opening in tiles mode\n" ); + else + printf( "im_exr2vips: opening in scanline mode\n" ); +#endif /*DEBUG*/ + + if( read->tiles ) { + read->header = ImfTiledInputHeader( read->tiles ); + read->lock = g_mutex_new(); + read->tile_width = ImfTiledInputTileXSize( read->tiles ); + read->tile_height = ImfTiledInputTileYSize( read->tiles ); + } + else + read->header = ImfInputHeader( read->lines ); + + ImfHeaderDataWindow( read->header, &xmin, &ymin, &xmax, &ymax ); + read->window.left = xmin; + read->window.top = ymin; + read->window.width = xmax - xmin + 1; + read->window.height = ymax - ymin + 1; + + return( read ); +} + +/* Read a OpenEXR file (header) into a VIPS (header). + */ +static int +exr2vips_header( Read *read, IMAGE *out ) +{ + /* + + FIXME ... not really sRGB. I think EXR is actually linear (no + gamma). We ought to read the chromaticities from the header, put + through a 3x3 matrix and output as XYZ + + */ + im_initdesc( out, + read->window.width, read->window.height, 4, + IM_BBITS_FLOAT, IM_BANDFMT_FLOAT, + IM_CODING_NONE, IM_TYPE_sRGB, 1.0, 1.0, 0, 0 ); + + return( 0 ); +} + +/* Read a OpenEXR file header into a VIPS header. + */ +int +im_exr2vips_header( const char *name, IMAGE *out ) +{ + Read *read; + + if( !(read = read_new( name, out )) || + exr2vips_header( read, out ) ) + return( -1 ); + + return( 0 ); +} + +static int +fill_region( REGION *out, void *seq, void *a, void *b ) +{ + ImfRgba *imf_buffer = (ImfRgba *) seq; + Read *read = (Read *) a; + Rect *r = &out->valid; + + const int tw = read->tile_width; + const int th = read->tile_height; + + /* Find top left of tiles we need. + */ + const int xs = (r->left / tw) * tw; + const int ys = (r->top / th) * th; + + int x, y, z; + Rect image; + + /* Area of image. + */ + image.left = 0; + image.top = 0; + image.width = read->out->Xsize; + image.height = read->out->Ysize; + + for( y = ys; y < IM_RECT_BOTTOM( r ); y += th ) + for( x = xs; x < IM_RECT_RIGHT( r ); x += tw ) { + Rect tile; + Rect hit; + int result; + + if( !ImfTiledInputSetFrameBuffer( read->tiles, + imf_buffer - + (read->window.left + x) - + (read->window.top + y) * tw, + 1, tw ) ) { + get_imf_error(); + return( -1 ); + } + +#ifdef DEBUG + printf( "im_exr2vips: requesting tile %d x %d\n", + x / tw, y / th ); +#endif /*DEBUG*/ + + g_mutex_lock( read->lock ); + result = ImfTiledInputReadTile( read->tiles, + x / tw, y / th, 0, 0 ); + g_mutex_unlock( read->lock ); + + if( !result ) { + get_imf_error(); + return( -1 ); + } + + /* The tile in the file, in VIPS coordinates. + */ + tile.left = x; + tile.top = y; + tile.width = tw; + tile.height = th; + im_rect_intersectrect( &tile, &image, &tile ); + + /* The part of this tile that hits the region. + */ + im_rect_intersectrect( &tile, r, &hit ); + + /* Convert to float and write to the region. + */ + for( z = 0; z < hit.height; z++ ) { + ImfRgba *p = imf_buffer + + (hit.left - tile.left) + + (hit.top - tile.top + z) * tw; + float *q = (float *) IM_REGION_ADDR( out, + hit.left, hit.top + z ); + + ImfHalfToFloatArray( 4 * hit.width, + (ImfHalf *) p, q ); + } + } + + return( 0 ); +} + +/* Allocate a tile buffer. + */ +static void * +seq_start( IMAGE *out, void *a, void *b ) +{ + Read *read = (Read *) a; + ImfRgba *imf_buffer; + + if( !(imf_buffer = IM_ARRAY( out, + read->tile_width * read->tile_height, ImfRgba )) ) + return( NULL ); + + return( imf_buffer ); +} + +/* Read tilewise. + */ +static int +exr2vips_tiles( Read *read, IMAGE *out ) +{ + if( exr2vips_header( read, out ) || + im_poutcheck( out ) || + im_demand_hint( out, IM_SMALLTILE, NULL ) || + im_generate( out, seq_start, fill_region, NULL, read, NULL ) ) + return( -1 ); + + return( 0 ); +} + +/* Read scanlinewise. + */ +static int +exr2vips_lines( Read *read, IMAGE *out ) +{ + const int left = read->window.left; + const int top = read->window.top; + const int width = read->window.width; + const int height = read->window.height; + + ImfRgba *imf_buffer; + float *vips_buffer; + int y; + + if( !(imf_buffer = IM_ARRAY( out, width, ImfRgba )) || + !(vips_buffer = IM_ARRAY( out, 4 * width, float )) || + exr2vips_header( read, out ) || + im_outcheck( out ) || + im_setupout( out ) ) + return( -1 ); + + for( y = 0; y < height; y++ ) { + if( !ImfInputSetFrameBuffer( read->lines, + imf_buffer - left - (top + y) * width, + 1, width ) ) { + get_imf_error(); + return( -1 ); + } + if( !ImfInputReadPixels( read->lines, top + y, top + y ) ) { + get_imf_error(); + return( -1 ); + } + + ImfHalfToFloatArray( 4 * width, + (ImfHalf *) imf_buffer, vips_buffer ); + + if( im_writeline( y, out, (PEL *) vips_buffer ) ) + return( -1 ); + } + + return( 0 ); +} + +static int +exr2vips( Read *read ) +{ + if( read->tiles ) { + IMAGE *raw; + + /* Tile cache: keep enough for two complete rows of tiles. + * This lets us do (smallish) area ops, like im_conv(), while + * still only hitting each OpenEXR tile once. + */ + if( !(raw = im_open_local( read->out, "cache", "p" )) ) + return( -1 ); + if( exr2vips_tiles( read, raw ) ) + return( -1 ); + if( im_tile_cache( raw, read->out, + read->tile_width, read->tile_height, + 2 * (1 + raw->Xsize / read->tile_width) ) ) + return( -1 ); + } + else { + if( exr2vips_lines( read, read->out ) ) + return( -1 ); + } + + return( 0 ); +} + +/* Read a OpenEXR file into a VIPS image. + */ +int +im_exr2vips( const char *name, IMAGE *out ) +{ + Read *read; + +#ifdef DEBUG + printf( "im_exr2vips: reading \"%s\"\n", name ); +#endif /*DEBUG*/ + + if( !(read = read_new( name, out )) || + exr2vips( read ) ) + return( -1 ); + + return( 0 ); +} + +#endif /*HAVE_OPENEXR*/ diff --git a/libsrc/conversion/im_jpeg2vips.c b/libsrc/conversion/im_jpeg2vips.c new file mode 100644 index 00000000..a557df84 --- /dev/null +++ b/libsrc/conversion/im_jpeg2vips.c @@ -0,0 +1,714 @@ +/* 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 +#endif /*HAVE_CONFIG_H*/ +#include + +#ifndef HAVE_JPEG + +#include + +int +im_jpeg2vips_header( const char *name, IMAGE *out ) +{ + im_error( "im_jpeg2vips_header", _( "JPEG support disabled" ) ); + + return( -1 ); +} + +int +im_jpeg2vips( const char *name, IMAGE *out ) +{ + im_error( "im_jpeg2vips", _( "JPEG support disabled" ) ); + + return( -1 ); +} + +#else /*HAVE_JPEG*/ + +#include +#include +#include +#include +#include + +#ifdef HAVE_EXIF +#ifdef UNTAGGED_EXIF +#include +#include +#include +#include +#else /*!UNTAGGED_EXIF*/ +#include +#include +#include +#include +#endif /*UNTAGGED_EXIF*/ +#endif /*HAVE_EXIF*/ + +#include +#include + +/* jpeglib includes jconfig.h, which can define HAVE_STDLIB_H ... which we + * also define. Make sure it's turned off. + */ +#ifdef HAVE_STDLIB_H +#undef HAVE_STDLIB_H +#endif /*HAVE_STDLIB_H*/ + +#include +#include + +#ifdef WITH_DMALLOC +#include +#endif /*WITH_DMALLOC*/ + +/* Define a new error handler for when we bomb out. + */ +typedef struct { + /* Public fields. + */ + struct jpeg_error_mgr pub; + + /* Private stuff for us. + */ + jmp_buf jmp; /* longjmp() here to get back to VIPS */ + FILE *fp; /* fclose() if non-NULL */ +} ErrorManager; + +/* New output message method - send to VIPS. + */ +METHODDEF(void) +new_output_message( j_common_ptr cinfo ) +{ + char buffer[JMSG_LENGTH_MAX]; + + (*cinfo->err->format_message)( cinfo, buffer ); + im_error( "vips_jpeg", _( "%s" ), buffer ); + +#ifdef DEBUG + printf( "vips_jpeg.c: new_output_message: \"%s\"\n", buffer ); +#endif /*DEBUG*/ +} + +/* New error_exit handler. + */ +METHODDEF(void) +new_error_exit( j_common_ptr cinfo ) +{ + ErrorManager *eman = (ErrorManager *) cinfo->err; + +#ifdef DEBUG + printf( "vips_jpeg.c: new_error_exit\n" ); +#endif /*DEBUG*/ + + /* Close the fp if necessary. + */ + if( eman->fp ) { + (void) fclose( eman->fp ); + eman->fp = NULL; + } + + /* Send the error message to VIPS. This method is overridden above. + */ + (*cinfo->err->output_message)( cinfo ); + + /* Jump back. + */ + longjmp( eman->jmp, 1 ); +} + +#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]; + VBuf name; + char value_text[256]; + VBuf value; + char exif_value[256]; + + im_buf_init_static( &name, name_text, 256 ); + im_buf_init_static( &value, value_text, 256 ); + + im_buf_appendf( &name, "exif-%s", exif_tag_get_title( entry->tag ) ); + im_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, + im_buf_all( &name ), im_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", _( "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", _( "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", _( "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. + */ +#ifdef BINARY_OPEN + if( !(fp = fopen( filename, "rb" )) ) { +#else /*BINARY_OPEN*/ + if( !(fp = fopen( filename, "r" )) ) { +#endif /*BINARY_OPEN*/ + jpeg_destroy_decompress( &cinfo ); + im_error( "im_jpeg2vips", + _( "unable to open \"%s\"" ), 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 ) ); +} + +int +im_jpeg2vips_header( const char *name, IMAGE *out ) +{ + return( jpeg2vips( name, out, TRUE ) ); +} + +#endif /*HAVE_JPEG*/ diff --git a/libsrc/conversion/im_magick2vips.c b/libsrc/conversion/im_magick2vips.c new file mode 100644 index 00000000..feda6c01 --- /dev/null +++ b/libsrc/conversion/im_magick2vips.c @@ -0,0 +1,650 @@ +/* Read a file using libMagick + * + * 7/1/03 JC + * - from im_tiff2vips + * 3/2/03 JC + * - some InitializeMagick() fail with NULL arg + * 2/11/04 + * - im_magick2vips_header() also checks sensible width/height + * 28/10/05 + * - copy attributes to meta + * - write many-frame images as a big column if all frames have identical + * width/height/bands/depth + * 31/3/06 + * - test for magick attr support + * 8/5/06 + * - set RGB16/GREY16 if appropriate + * 10/8/07 + * - support 32/64 bit imagemagick too + * 21/2/08 + * - use MaxRGB if QuantumRange is missing (thanks Bob) + * - look for MAGICKCORE_HDRI_SUPPORT (thanks Marcel) + * - use image->attributes if GetNextImageAttribute() is missing + */ + +/* + + 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 + + */ + +/* Turn on debugging output. +#define DEBUG + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#ifndef HAVE_MAGICK + +#include + +int +im_magick2vips( const char *filename, IMAGE *im ) +{ + im_error( "im_magick2vips", _( "libMagick support disabled" ) ); + return( -1 ); +} + +int +im_magick2vips_header( const char *filename, IMAGE *im ) +{ + im_error( "im_magick2vips", _( "libMagick support disabled" ) ); + return( -1 ); +} + +#else /*HAVE_MAGICK*/ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#ifdef WITH_DMALLOC +#include +#endif /*WITH_DMALLOC*/ + +/* pre-float Magick used to call this MaxRGB. + */ +#if !defined(QuantumRange) +# define QuantumRange MaxRGB +#endif + +/* And this used to be UseHDRI. + */ +#if MAGICKCORE_HDRI_SUPPORT +# define UseHDRI=1 +#endif + +/* What we track during a read call. + */ +typedef struct _Read { + char *filename; + IMAGE *im; + + Image *image; + ImageInfo *image_info; + ExceptionInfo exception; + + int n_frames; + Image **frames; + int frame_height; + + /* Mutex to serialise calls to libMagick during threaded read. + */ + GMutex *lock; +} Read; + +static int +read_destroy( Read *read ) +{ +#ifdef DEBUG + printf( "im_magick2vips: read_destroy: %s\n", read->filename ); +#endif /*DEBUG*/ + + IM_FREEF( DestroyImage, read->image ); + IM_FREEF( DestroyImageInfo, read->image_info ); + IM_FREE( read->frames ); + IM_FREE( read->filename ); + DestroyExceptionInfo( &read->exception ); + IM_FREEF( g_mutex_free, read->lock ); + im_free( read ); + + return( 0 ); +} + +static Read * +read_new( const char *filename, IMAGE *im ) +{ + Read *read; + static int inited = 0; + + if( !inited ) { + InitializeMagick( "" ); + inited = 1; + } + + if( !(read = IM_NEW( NULL, Read )) ) + return( NULL ); + read->filename = im_strdup( NULL, filename ); + read->im = im; + read->image = NULL; + read->image_info = CloneImageInfo( NULL ); + GetExceptionInfo( &read->exception ); + read->n_frames = 0; + read->frames = NULL; + read->frame_height = 0; + read->lock = g_mutex_new(); + + if( im_add_close_callback( im, + (im_callback_fn) read_destroy, read, NULL ) ) { + read_destroy( read ); + return( NULL ); + } + + if( !read->filename || !read->image_info ) + return( NULL ); + + im_strncpy( read->image_info->filename, filename, MaxTextExtent ); + +#ifdef DEBUG + printf( "im_magick2vips: read_new: %s\n", read->filename ); +#endif /*DEBUG*/ + + return( read ); +} + +static int +get_bands( Image *image ) +{ + int bands; + + switch( image->colorspace ) { + case GRAYColorspace: + bands = 1; + break; + + case RGBColorspace: + bands = 3; + break; + + case sRGBColorspace: + bands = 3; + break; + + case CMYKColorspace: + bands = 4; + break; + + default: + im_error( "im_magick2vips", _( "unsupported colorspace %d" ), + (int) image->colorspace ); + return( -1 ); + } + + /* Alpha as well? + */ + if( image->matte ) { + assert( image->colorspace != CMYKColorspace ); + bands += 1; + } + + return( bands ); +} + +static int +parse_header( Read *read ) +{ + IMAGE *im = read->im; + Image *image = read->image; + +#ifdef HAVE_MAGICK_ATTR + const ImageAttribute *attr; +#endif /*HAVE_MAGICK_ATTR*/ + Image *p; + int i; + + im->Xsize = image->columns; + im->Ysize = image->rows; + read->frame_height = image->rows; + if( (im->Bands = get_bands( image )) < 0 ) + return( -1 ); + + switch( image->depth ) { + case 8: + im->BandFmt = IM_BANDFMT_UCHAR; + im->Bbits = IM_BBITS_BYTE; + break; + + case 16: + im->BandFmt = IM_BANDFMT_USHORT; + im->Bbits = IM_BBITS_SHORT; + break; + +#ifdef UseHDRI + case 32: + im->BandFmt = IM_BANDFMT_FLOAT; + im->Bbits = IM_BBITS_FLOAT; + break; + + case 64: + im->BandFmt = IM_BANDFMT_DOUBLE; + im->Bbits = IM_BBITS_DOUBLE; + break; +#else /*!UseHDRI*/ + case 32: + im->BandFmt = IM_BANDFMT_UINT; + im->Bbits = IM_BBITS_INT; + break; + + /* No 64-bit int format in VIPS. + */ +#endif /*UseHDRI*/ + + default: + im_error( "im_magick2vips", _( "unsupported bit depth %d" ), + (int) image->depth ); + return( -1 ); + } + + switch( image->colorspace ) { + case GRAYColorspace: + if( im->BandFmt == IM_BANDFMT_USHORT ) + im->Type = IM_TYPE_GREY16; + else + im->Type = IM_TYPE_B_W; + break; + + case RGBColorspace: + if( im->BandFmt == IM_BANDFMT_USHORT ) + im->Type = IM_TYPE_RGB16; + else + im->Type = IM_TYPE_RGB; + break; + + case sRGBColorspace: + if( im->BandFmt == IM_BANDFMT_USHORT ) + im->Type = IM_TYPE_RGB16; + else + im->Type = IM_TYPE_sRGB; + break; + + case CMYKColorspace: + im->Type = IM_TYPE_CMYK; + break; + + default: + im_error( "im_magick2vips", _( "unsupported colorspace %d" ), + (int) image->colorspace ); + return( -1 ); + } + + switch( image->units ) { + case PixelsPerInchResolution: + im->Xres = image->x_resolution / 25.4; + im->Yres = image->y_resolution / 25.4; + break; + + case PixelsPerCentimeterResolution: + im->Xres = image->x_resolution / 10.0; + im->Yres = image->y_resolution / 10.0; + break; + + default: + im->Xres = 1.0; + im->Yres = 1.0; + break; + } + + /* Other fields. + */ + im->Coding = IM_CODING_NONE; + +#ifdef HAVE_MAGICK_ATTR +#ifdef HAVE_GETNEXTIMAGEATTRIBUTE + /* Gah, magick6.something and later only. Attach any attributes. + */ + ResetImageAttributeIterator( image ); + while( (attr = GetNextImageAttribute( image )) ) { +#elif defined(HAVE_GETIMAGEATTRIBUTE) + /* GraphicsMagick is missing the iterator: we have to loop ourselves. + * ->attributes is marked as private in the header, but there's no + * getter so we have to access it directly. + */ + for( attr = image->attributes; attr; attr = attr->next ) { +#else /*stuff*/ + #error attributes enabled, but no access funcs found +#endif + char name_text[256]; + VBuf name; + + im_buf_init_static( &name, name_text, 256 ); + im_buf_appendf( &name, "magick-%s", attr->key ); + im_meta_set_string( im, im_buf_all( &name ), attr->value ); + +#ifdef DEBUG + printf( "key = \"%s\", value = \"%s\"\n", + attr->key, attr->value ); +#endif /*DEBUG*/ + } +#endif /*HAVE_MAGICK_ATTR*/ + + /* Do we have a set of equal-sized frames? Append them. + + FIXME ... there must be an attribute somewhere from dicom read + which says this is a volumetric image + + */ + read->n_frames = 0; + for( p = image; p; (p = GetNextImageInList( p )) ) { + if( p->columns != im->Xsize || + p->rows != im->Ysize || + p->depth != im->Bbits || + get_bands( p ) != im->Bands ) + break; + + read->n_frames += 1; + } + if( p ) + /* Nope ... just do the first image in the list. + */ + read->n_frames = 1; + + /* Record frame pointers. + */ + im->Ysize *= read->n_frames; + if( !(read->frames = IM_ARRAY( NULL, read->n_frames, Image * )) ) + return( -1 ); + p = image; + for( i = 0; i < read->n_frames; i++ ) { + read->frames[i] = p; + p = GetNextImageInList( p ); + } + + return( 0 ); +} + +/* Divide by this to get 0 - MAX from a Quantum. Eg. consider QuantumRange == + * 65535, MAX == 255 (a Q16 ImageMagic representing an 8-bit image). Make sure + * this can't be zero (if QuantumRange < MAX) .. can happen if we have a Q8 + * ImageMagick trying to represent a 16-bit image. + */ +#define SCALE( MAX ) \ + (QuantumRange < (MAX) ? \ + 1 : \ + ((QuantumRange + 1) / ((MAX) + 1))) + +#define GRAY_LOOP( TYPE, MAX ) { \ + TYPE *q = (TYPE *) q8; \ + \ + for( x = 0; x < n; x++ ) \ + q[x] = pixels[x].green / SCALE( MAX ); \ +} + +#define GRAYA_LOOP( TYPE, MAX ) { \ + TYPE *q = (TYPE *) q8; \ + \ + for( x = 0; x < n; x++ ) { \ + q[0] = pixels[x].green / SCALE( MAX ); \ + q[1] = pixels[x].opacity / SCALE( MAX ); \ + \ + q += 2; \ + } \ +} + +#define RGB_LOOP( TYPE, MAX ) { \ + TYPE *q = (TYPE *) q8; \ + \ + for( x = 0; x < n; x++ ) { \ + q[0] = pixels[x].red / SCALE( MAX ); \ + q[1] = pixels[x].green / SCALE( MAX ); \ + q[2] = pixels[x].blue / SCALE( MAX ); \ + \ + q += 3; \ + } \ +} + +#define RGBA_LOOP( TYPE, MAX ) { \ + TYPE *q = (TYPE *) q8; \ + \ + for( x = 0; x < n; x++ ) { \ + q[0] = pixels[x].red / SCALE( MAX ); \ + q[1] = pixels[x].green / SCALE( MAX ); \ + q[2] = pixels[x].blue / SCALE( MAX ); \ + q[3] = pixels[x].opacity / SCALE( MAX ); \ + \ + q += 4; \ + } \ +} + +static void +unpack_pixels( IMAGE *im, PEL *q8, PixelPacket *pixels, int n ) +{ + int x; + + switch( im->Bands ) { + case 1: + /* Gray. + */ + switch( im->BandFmt ) { + case IM_BANDFMT_UCHAR: + GRAY_LOOP( unsigned char, 255 ); break; + case IM_BANDFMT_USHORT: + GRAY_LOOP( unsigned short, 65535 ); break; + case IM_BANDFMT_UINT: + GRAY_LOOP( unsigned int, 4294967295UL ); break; + case IM_BANDFMT_DOUBLE: + GRAY_LOOP( double, QuantumRange ); break; + + default: + assert( 0 ); + } + break; + + case 2: + /* Gray plus alpha. + */ + switch( im->BandFmt ) { + case IM_BANDFMT_UCHAR: + GRAYA_LOOP( unsigned char, 255 ); break; + case IM_BANDFMT_USHORT: + GRAYA_LOOP( unsigned short, 65535 ); break; + case IM_BANDFMT_UINT: + GRAYA_LOOP( unsigned int, 4294967295UL ); break; + case IM_BANDFMT_DOUBLE: + GRAYA_LOOP( double, QuantumRange ); break; + + default: + assert( 0 ); + } + break; + + case 3: + /* RGB. + */ + switch( im->BandFmt ) { + case IM_BANDFMT_UCHAR: + RGB_LOOP( unsigned char, 255 ); break; + case IM_BANDFMT_USHORT: + RGB_LOOP( unsigned short, 65535 ); break; + case IM_BANDFMT_UINT: + RGB_LOOP( unsigned int, 4294967295UL ); break; + case IM_BANDFMT_DOUBLE: + RGB_LOOP( double, QuantumRange ); break; + + default: + assert( 0 ); + } + break; + + case 4: + /* RGBA or CMYK. + */ + switch( im->BandFmt ) { + case IM_BANDFMT_UCHAR: + RGBA_LOOP( unsigned char, 255 ); break; + case IM_BANDFMT_USHORT: + RGBA_LOOP( unsigned short, 65535 ); break; + case IM_BANDFMT_UINT: + RGBA_LOOP( unsigned int, 4294967295UL ); break; + case IM_BANDFMT_DOUBLE: + RGBA_LOOP( double, QuantumRange ); break; + + default: + assert( 0 ); + } + break; + + default: + assert( 0 ); + } +} + +static PixelPacket * +get_pixels( Image *image, int left, int top, int width, int height ) +{ + PixelPacket *pixels; + + if( !(pixels = GetImagePixels( image, left, top, width, height )) ) + return( NULL ); + +/* Can't happen if red/green/blue are doubles. + */ +#ifndef UseHDRI + /* Unpack palette. + */ + if( image->storage_class == PseudoClass ) { + IndexPacket *indexes = GetIndexes( image ); + int i; + + for( i = 0; i < width * height; i++ ) { + IndexPacket x = indexes[i]; + + if( x < image->colors ) { + pixels[i].red = image->colormap[x].red; + pixels[i].green = image->colormap[x].green; + pixels[i].blue = image->colormap[x].blue; + } + } + } +#endif /*UseHDRI*/ + + return( pixels ); +} + +static int +magick_fill_region( REGION *out, void *seq, void *a, void *b ) +{ + Read *read = (Read *) a; + Rect *r = &out->valid; + int y; + + for( y = 0; y < r->height; y++ ) { + int top = r->top + y; + int frame = top / read->frame_height; + int line = top % read->frame_height; + + PixelPacket *pixels; + + g_mutex_lock( read->lock ); + pixels = get_pixels( read->frames[frame], + r->left, line, r->width, 1 ); + g_mutex_unlock( read->lock ); + + if( !pixels ) { + im_error( "im_magick2vips", + _( "unable to read pixels" ) ); + return( -1 ); + } + + unpack_pixels( read->im, + (PEL *) IM_REGION_ADDR( out, r->left, top ), + pixels, r->width ); + } + + return( 0 ); +} + +int +im_magick2vips( const char *filename, IMAGE *im ) +{ + Read *read; + + if( !(read = read_new( filename, im )) ) + return( -1 ); + + read->image = ReadImage( read->image_info, &read->exception ); + if( !read->image ) { + im_error( "im_magick2vips", _( "unable to read file \"%s\"\n" + "libMagick error: %s %s" ), + filename, + read->exception.reason, read->exception.description ); + return( -1 ); + } + + if( parse_header( read ) || + im_poutcheck( im ) || + im_generate( im, NULL, magick_fill_region, NULL, read, NULL ) ) + return( -1 ); + + return( 0 ); +} + +int +im_magick2vips_header( const char *filename, IMAGE *im ) +{ + Read *read; + + if( !(read = read_new( filename, im )) ) + return( -1 ); + + read->image = PingImage( read->image_info, &read->exception ); + if( !read->image ) { + im_error( "im_magick2vips", _( "unable to ping file " + "\"%s\"\nlibMagick error: %s %s" ), + filename, + read->exception.reason, read->exception.description ); + return( -1 ); + } + + if( parse_header( read ) ) + return( -1 ); + + if( im->Xsize <= 0 || im->Ysize <= 0 ) { + im_error( "im_magick2vips", _( "bad image size" ) ); + return( -1 ); + } + + return( 0 ); +} + +#endif /*HAVE_MAGICK*/ diff --git a/libsrc/conversion/im_png2vips.c b/libsrc/conversion/im_png2vips.c new file mode 100644 index 00000000..a0d91ea8 --- /dev/null +++ b/libsrc/conversion/im_png2vips.c @@ -0,0 +1,381 @@ +/* Convert 1 to 4-band 8 or 16-bit VIPS images to/from PNG. + * + * 28/11/03 JC + * - better no-overshoot on tile loop + * 22/2/05 + * - read non-interlaced PNG with a line buffer (thanks Michel Brabants) + * 11/1/06 + * - read RGBA palette-ized images more robustly (thanks Tom) + * 20/4/06 + * - auto convert to sRGB/mono (with optional alpha) for save + * 1/5/06 + * - from vips_png.c + * 8/5/06 + * - set RGB16/GREY16 if appropriate + */ + +/* + + 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 + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#ifndef HAVE_PNG + +#include + +int +im_png2vips( const char *name, IMAGE *out ) +{ + im_error( "im_png2vips", _( "PNG support disabled" ) ); + return( -1 ); +} + +int +im_png2vips_header( const char *name, IMAGE *out ) +{ + im_error( "im_png2vips_header", _( "PNG support disabled" ) ); + return( -1 ); +} + +#else /*HAVE_PNG*/ + +#include +#include +#include + +#include + +#include + +#ifdef WITH_DMALLOC +#include +#endif /*WITH_DMALLOC*/ + +#if PNG_LIBPNG_VER < 10003 +#error "PNG library too old." +#endif + +static void +user_error_function( png_structp png_ptr, png_const_charp error_msg ) +{ + im_error( "im_png2vips", _( "PNG error: \"%s\"" ), error_msg ); +} + +static void +user_warning_function( png_structp png_ptr, png_const_charp warning_msg ) +{ + im_error( "im_png2vips", _( "PNG warning: \"%s\"" ), warning_msg ); +} + +/* What we track during a PNG read. + */ +typedef struct { + char *name; + IMAGE *out; + + FILE *fp; + png_structp pPng; + png_infop pInfo; + png_bytep *row_pointer; + png_bytep data; +} Read; + +static void +read_destroy( Read *read ) +{ + if( read->name ) { + im_free( read->name ); + read->name = NULL; + } + if( read->fp ) { + fclose( read->fp ); + read->fp = NULL; + } + if( read->pPng ) + png_destroy_read_struct( &read->pPng, &read->pInfo, NULL ); + if( read->row_pointer ) { + im_free( read->row_pointer ); + read->row_pointer = NULL; + } + if( read->data ) { + im_free( read->data ); + read->data = NULL; + } + + im_free( read ); +} + +static Read * +read_new( const char *name, IMAGE *out ) +{ + Read *read; + + if( !(read = IM_NEW( NULL, Read )) ) + return( NULL ); + + read->name = im_strdup( NULL, name ); + read->out = out; + read->fp = NULL; + read->pPng = NULL; + read->pInfo = NULL; + read->row_pointer = NULL; + read->data = NULL; + +#ifdef BINARY_OPEN + if( !(read->fp = fopen( name, "rb" )) ) { +#else /*BINARY_OPEN*/ + if( !(read->fp = fopen( name, "r" )) ) { +#endif /*BINARY_OPEN*/ + read_destroy( read ); + im_error( "im_png2vips", _( "unable to open \"%s\"" ), name ); + return( NULL ); + } + + if( !(read->pPng = png_create_read_struct( + PNG_LIBPNG_VER_STRING, NULL, + user_error_function, user_warning_function )) ) { + read_destroy( read ); + return( NULL ); + } + + /* Catch PNG errors from png_create_info_struct(). + */ + if( setjmp( read->pPng->jmpbuf ) ) { + read_destroy( read ); + return( NULL ); + } + + if( !(read->pInfo = png_create_info_struct( read->pPng )) ) { + read_destroy( read ); + return( NULL ); + } + + return( read ); +} + +/* Yuk! Have to malloc enough space for the whole image. Interlaced PNG + * is not really suitable for large objects ... + */ +static int +png2vips_interlace( Read *read ) +{ + const int rowbytes = IM_IMAGE_SIZEOF_LINE( read->out ); + int y; + + if( !(read->row_pointer = IM_ARRAY( NULL, + read->pInfo->height, png_bytep )) ) + return( -1 ); + if( !(read->data = (png_bytep) im_malloc( NULL, + read->pInfo->height * rowbytes )) ) + return( -1 ); + + for( y = 0; y < (int) read->pInfo->height; y++ ) + read->row_pointer[y] = read->data + y * rowbytes; + if( im_outcheck( read->out ) || + im_setupout( read->out ) || + setjmp( read->pPng->jmpbuf ) ) + return( -1 ); + + png_read_image( read->pPng, read->row_pointer ); + + for( y = 0; y < (int) read->pInfo->height; y++ ) + if( im_writeline( y, read->out, read->row_pointer[y] ) ) + return( -1 ); + + return( 0 ); +} + +/* Noninterlaced images can be read without needing enough RAM for the whole + * image. + */ +static int +png2vips_noninterlace( Read *read ) +{ + const int rowbytes = IM_IMAGE_SIZEOF_LINE( read->out ); + int y; + + if( !(read->data = (png_bytep) im_malloc( NULL, rowbytes )) ) + return( -1 ); + if( im_outcheck( read->out ) || + im_setupout( read->out ) || + setjmp( read->pPng->jmpbuf ) ) + return( -1 ); + + for( y = 0; y < (int) read->pInfo->height; y++ ) { + png_read_row( read->pPng, read->data, NULL ); + + if( im_writeline( y, read->out, read->data ) ) + return( -1 ); + } + + return( 0 ); +} + +/* Read a PNG file (header) into a VIPS (header). + */ +static int +png2vips( Read *read, int header_only ) +{ + int bands, bpp, type; + + if( setjmp( read->pPng->jmpbuf ) ) + return( -1 ); + + png_init_io( read->pPng, read->fp ); + png_read_info( read->pPng, read->pInfo ); + + /* png_get_channels() gives us 1 band for palette images ... so look + * at colour_type for output bands. + */ + switch( read->pInfo->color_type ) { + case PNG_COLOR_TYPE_PALETTE: + bands = 3; + + /* Don't know if this is really correct. If there are + * transparent pixels, assume we're going to output RGBA. + */ + if( read->pInfo->num_trans ) + bands = 4; + + break; + + case PNG_COLOR_TYPE_GRAY: bands = 1; break; + case PNG_COLOR_TYPE_GRAY_ALPHA: bands = 2; break; + case PNG_COLOR_TYPE_RGB: bands = 3; break; + case PNG_COLOR_TYPE_RGB_ALPHA: bands = 4; break; + + default: + im_error( "im_png2vips", _( "unsupported colour type" ) ); + return( -1 ); + } + + /* 8 or 16 bit. + */ + bpp = read->pInfo->bit_depth > 8 ? 2 : 1; + + if( bpp > 1 ) { + if( bands < 3 ) + type = IM_TYPE_GREY16; + else + type = IM_TYPE_RGB16; + } + else { + if( bands < 3 ) + type = IM_TYPE_B_W; + else + type = IM_TYPE_sRGB; + } + + /* Expand palette images. + */ + if( read->pInfo->color_type == PNG_COLOR_TYPE_PALETTE ) + png_set_expand( read->pPng ); + + /* Expand <8 bit images to full bytes. + */ + if( read->pInfo->bit_depth < 8 ) + png_set_packing( read->pPng ); + + /* If we're an INTEL byte order machine and this is 16bits, we need + * to swap bytes. + */ + if( read->pInfo->bit_depth > 8 && !im_amiMSBfirst() ) + png_set_swap( read->pPng ); + + /* Set VIPS header. + */ + im_initdesc( read->out, + read->pInfo->width, read->pInfo->height, bands, + bpp == 1 ? IM_BBITS_BYTE : IM_BBITS_SHORT, + bpp == 1 ? IM_BANDFMT_UCHAR : IM_BANDFMT_USHORT, + IM_CODING_NONE, type, 1.0, 1.0, 0, 0 ); + + if( !header_only ) { + if( png_set_interlace_handling( read->pPng ) > 1 ) { + if( png2vips_interlace( read ) ) + return( -1 ); + } + else { + if( png2vips_noninterlace( read ) ) + return( -1 ); + } + } + + return( 0 ); +} + +/* Read a PNG file header into a VIPS header. + */ +int +im_png2vips_header( const char *name, IMAGE *out ) +{ + Read *read; + + if( !(read = read_new( name, out )) ) + return( -1 ); + + if( png2vips( read, 1 ) ) { + read_destroy( read ); + return( -1 ); + } + + read_destroy( read ); + + return( 0 ); +} + +/* Read a PNG file into a VIPS image. + */ +int +im_png2vips( const char *name, IMAGE *out ) +{ + Read *read; + +#ifdef DEBUG + printf( "im_png2vips: reading \"%s\"\n", name ); +#endif /*DEBUG*/ + + if( !(read = read_new( name, out )) ) + return( -1 ); + + if( png2vips( read, 0 ) ) { + read_destroy( read ); + return( -1 ); + } + + read_destroy( read ); + + return( 0 ); +} + +#endif /*HAVE_PNG*/ diff --git a/libsrc/conversion/im_ppm2vips.c b/libsrc/conversion/im_ppm2vips.c new file mode 100644 index 00000000..2e188dc8 --- /dev/null +++ b/libsrc/conversion/im_ppm2vips.c @@ -0,0 +1,440 @@ +/* Read a ppm file. + * + * Stephen Chan ... original code + * + * 21/11/00 JC + * - hacked for VIPS + * - reads ppm/pgm/pbm + * - mmaps binary pgm/ppm + * - reads all ascii formats (slowly!) + * 22/11/00 JC + * - oops, ascii read was broken + * - does 16/32 bit ascii now as well + * 24/5/01 + * - im_ppm2vips_header() added + * 22/5/04 + * - does 16/32 bit binary too + * - tiny fix for missing file close on read error + * 19/8/05 + * - use im_raw2vips() for binary read + * 9/9/05 + * - tiny cleanups + */ + +/* + + 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 + + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include +#include +#include +#include + +#include + +#ifdef WITH_DMALLOC +#include +#endif /*WITH_DMALLOC*/ + +/* The largest number/field/whatever we can read. + */ +#define IM_MAX_THING (80) + +static void +skip_line( FILE *fp ) +{ + int ch; + + while( (ch = fgetc( fp )) != '\n' ) + ; +} + +static void +skip_white_space( FILE *fp ) +{ + int ch; + + while( isspace( ch = fgetc( fp ) ) ) + ; + ungetc( ch, fp ); + + if( ch == '#' ) { + skip_line( fp ); + skip_white_space( fp ); + } +} + +static int +read_uint( FILE *fp ) +{ + int i; + char buf[IM_MAX_THING]; + int ch; + + skip_white_space( fp ); + + /* Stop complaints about used-before-set on ch. + */ + ch = -1; + + for( i = 0; i < IM_MAX_THING - 1 && isdigit( ch = fgetc( fp ) ); i++ ) + buf[i] = ch; + buf[i] = '\0'; + + if( i == 0 ) { + im_error( "im_ppm2vips", _( "bad unsigned int" ) ); + return( -1 ); + } + + ungetc( ch, fp ); + + return( atoi( buf ) ); +} + +static int +read_header( FILE *fp, IMAGE *out, int *bits, int *ascii ) +{ + int width, height, bands, fmt, type; + int i; + char buf[IM_MAX_THING]; + int max_value; + + /* ppm types. + */ + static char *magic_names[] = { + "P1", /* pbm ... 1 band 1 bit, ascii */ + "P2", /* pgm ... 1 band many bit, ascii */ + "P3", /* ppm ... 3 band many bit, ascii */ + "P4", /* pbm ... 1 band 1 bit, binary */ + "P5", /* pgm ... 1 band 8 bit, binary */ + "P6" /* ppm ... 3 band 8 bit, binary */ + }; + + /* Characteristics, indexed by ppm type. + */ + static int lookup_bits[] = { + 1, 8, 8, 1, 8, 8 + }; + static int lookup_bands[] = { + 1, 1, 3, 1, 1, 3 + }; + static int lookup_ascii[] = { + 1, 1, 1, 0, 0, 0 + }; + + /* Read in the magic number. + */ + buf[0] = fgetc( fp ); + buf[1] = fgetc( fp ); + buf[2] = '\0'; + + for( i = 0; i < IM_NUMBER( magic_names ); i++ ) + if( strcmp( magic_names[i], buf ) == 0 ) + break; + if( i == IM_NUMBER( magic_names ) ) { + im_error( "im_ppm2vips", _( "bad magic number" ) ); + return( -1 ); + } + *bits = lookup_bits[i]; + bands = lookup_bands[i]; + *ascii = lookup_ascii[i]; + + /* Read in size. + */ + if( (width = read_uint( fp )) < 0 || + (height = read_uint( fp )) < 0 ) + return( -1 ); + + /* Read in max value for >1 bit images. + */ + if( *bits > 1 ) { + if( (max_value = read_uint( fp )) < 0 ) + return( -1 ); + + if( max_value > 255 ) + *bits = 16; + if( max_value > 65535 ) + *bits = 32; + } + + /* For binary images, there is always exactly 1 more whitespace + * character before the data starts. + */ + if( !*ascii && !isspace( fgetc( fp ) ) ) { + im_error( "im_ppm2vips", + _( "not whitespace before start of binary data" ) ); + return( -1 ); + } + + /* Choose a VIPS bandfmt. + */ + switch( *bits ) { + case 1: + case 8: + fmt = IM_BANDFMT_UCHAR; + break; + + case 16: + fmt = IM_BANDFMT_USHORT; + break; + + case 32: + fmt = IM_BANDFMT_UINT; + break; + + default: + assert( 0 ); + } + + if( bands == 1 ) { + if( fmt == IM_BANDFMT_USHORT ) + type = IM_TYPE_GREY16; + else + type = IM_TYPE_B_W; + } + else { + if( fmt == IM_BANDFMT_USHORT ) + type = IM_TYPE_RGB16; + else if( fmt == IM_BANDFMT_UINT ) + type = IM_TYPE_RGB; + else + type = IM_TYPE_sRGB; + } + + im_initdesc( out, width, height, bands, (*bits == 1) ? 8 : *bits, fmt, + IM_CODING_NONE, + type, + 1.0, 1.0, + 0, 0 ); + + return( 0 ); +} + +/* Read a ppm/pgm file using mmap(). + */ +static int +read_mmap( FILE *fp, const char *filename, IMAGE *out ) +{ + const int header_offset = ftell( fp ); + IMAGE *t[2]; + im_arch_type arch = im_amiMSBfirst() ? + IM_ARCH_NATIVE : IM_ARCH_BYTE_SWAPPED; + + if( im_open_local_array( out, t, 2, "im_ppm2vips", "p" ) || + im_raw2vips( filename, t[0], + out->Xsize, out->Ysize, + IM_IMAGE_SIZEOF_PEL( out ), header_offset ) || + im_copy_morph( t[0], t[1], + out->Bands, out->BandFmt, out->Coding ) || + im_copy_from( t[1], out, arch ) ) + return( -1 ); + + return( 0 ); +} + +/* Read an ascii ppm/pgm file. + */ +static int +read_ascii( FILE *fp, IMAGE *out ) +{ + int x, y; + PEL *buf; + + if( im_outcheck( out ) || im_setupout( out ) || + !(buf = IM_ARRAY( out, IM_IMAGE_SIZEOF_LINE( out ), PEL )) ) + return( -1 ); + + for( y = 0; y < out->Ysize; y++ ) { + for( x = 0; x < out->Xsize * out->Bands; x++ ) { + int val; + + if( (val = read_uint( fp )) < 0 ) + return( -1 ); + + switch( out->BandFmt ) { + case IM_BANDFMT_UCHAR: + buf[x] = IM_CLIP( 0, val, 255 ); + break; + + case IM_BANDFMT_USHORT: + ((unsigned short *) buf)[x] = + IM_CLIP( 0, val, 65535 ); + break; + + case IM_BANDFMT_UINT: + ((unsigned int *) buf)[x] = val; + break; + + default: + assert( 0 ); + } + } + + if( im_writeline( y, out, buf ) ) + return( -1 ); + } + + return( 0 ); +} + +/* Read an ascii 1 bit file. + */ +static int +read_1bit_ascii( FILE *fp, IMAGE *out ) +{ + int x, y; + PEL *buf; + + if( im_outcheck( out ) || im_setupout( out ) || + !(buf = IM_ARRAY( out, IM_IMAGE_SIZEOF_LINE( out ), PEL )) ) + return( -1 ); + + for( y = 0; y < out->Ysize; y++ ) { + for( x = 0; x < out->Xsize * out->Bands; x++ ) { + int val; + + if( (val = read_uint( fp )) < 0 ) + return( -1 ); + + if( val == 1 ) + buf[x] = 0; + else + buf[x] = 255; + } + + if( im_writeline( y, out, buf ) ) + return( -1 ); + } + + return( 0 ); +} + +/* Read a 1 bit binary file. + */ +static int +read_1bit_binary( FILE *fp, IMAGE *out ) +{ + int x, y, i; + int bits; + PEL *buf; + + if( im_outcheck( out ) || im_setupout( out ) || + !(buf = IM_ARRAY( out, IM_IMAGE_SIZEOF_LINE( out ), PEL )) ) + return( -1 ); + + bits = fgetc( fp ); + for( i = 0, y = 0; y < out->Ysize; y++ ) { + for( x = 0; x < out->Xsize * out->Bands; x++, i++ ) { + buf[x] = (bits & 128) ? 255 : 0; + bits <<= 1; + if( (i & 7) == 7 ) + bits = fgetc( fp ); + } + + if( im_writeline( y, out, buf ) ) + return( -1 ); + } + + return( 0 ); +} + +static int +parse_ppm( FILE *fp, const char *filename, IMAGE *out ) +{ + int bits; + int ascii; + + if( read_header( fp, out, &bits, &ascii ) ) + return( -1 ); + + /* What sort of read are we doing? + */ + if( !ascii && bits >= 8 ) + return( read_mmap( fp, filename, out ) ); + else if( !ascii && bits == 1 ) + return( read_1bit_binary( fp, out ) ); + else if( ascii && bits == 1 ) + return( read_1bit_ascii( fp, out ) ); + else + return( read_ascii( fp, out ) ); +} + +int +im_ppm2vips_header( const char *filename, IMAGE *out ) +{ + FILE *fp; + int bits; + int ascii; + +#ifdef BINARY_OPEN + if( !(fp = fopen( filename, "rb" )) ) { +#else /*BINARY_OPEN*/ + if( !(fp = fopen( filename, "r" )) ) { +#endif /*BINARY_OPEN*/ + im_error( "im_ppm2vips_header", + _( "unable to open \"%s\"" ), filename ); + return( -1 ); + } + + if( read_header( fp, out, &bits, &ascii ) ) { + fclose( fp ); + return( -1 ); + } + + fclose( fp ); + + return( 0 ); +} + +int +im_ppm2vips( const char *filename, IMAGE *out ) +{ + FILE *fp; + +#ifdef BINARY_OPEN + if( !(fp = fopen( filename, "rb" )) ) { +#else /*BINARY_OPEN*/ + if( !(fp = fopen( filename, "r" )) ) { +#endif /*BINARY_OPEN*/ + im_error( "im_ppm2vips", + _( "unable to open \"%s\"" ), filename ); + return( -1 ); + } + + if( parse_ppm( fp, filename, out ) ) { + fclose( fp ); + return( -1 ); + } + + fclose( fp ); + + return( 0 ); +} diff --git a/libsrc/conversion/im_raw2vips.c b/libsrc/conversion/im_raw2vips.c new file mode 100644 index 00000000..e8f50605 --- /dev/null +++ b/libsrc/conversion/im_raw2vips.c @@ -0,0 +1,64 @@ +/* Read raw image. Just a wrapper over im_binfile(). + * + * 3/8/05 + */ + +/* + + 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 + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include + +#ifdef WITH_DMALLOC +#include +#endif /*WITH_DMALLOC*/ + +int +im_raw2vips( const char *filename, IMAGE *out, + int width, int height, int bpp, int offset ) +{ + IMAGE *t; + + if( !(t = im_binfile( filename, width, height, bpp, offset )) ) + return( -1 ); + if( im_add_close_callback( out, + (im_callback_fn) im_close, t, NULL ) ) { + im_close( t ); + return( -1 ); + } + if( im_copy( t, out ) ) + return( -1 ); + + return( 0 ); +} diff --git a/libsrc/conversion/im_tiff2vips.c b/libsrc/conversion/im_tiff2vips.c new file mode 100644 index 00000000..6d5211e2 --- /dev/null +++ b/libsrc/conversion/im_tiff2vips.c @@ -0,0 +1,1502 @@ +/* TIFF parts: Copyright (c) 1988, 1990 by Sam Leffler. + * All rights reserved. + * + * This file is provided for unrestricted use provided that this + * legend is included on all tape media and as a part of the + * software program in whole or part. Users may copy, modify or + * distribute this file at will. + * ----------------------------- + * Modifications for VIPS: Kirk Martinez 1994 + * 22/11/94 JC + * - more general + * - memory leaks fixed + * 20/3/95 JC + * - TIFF error handler added + * - read errors detected correctly + * + * Modified to handle LAB in tiff format. + * It convert LAB-tiff format to IM_TYPE_LABQ in vips format. + * Copyright July-1995 Ahmed Abbood. + * + * + * 19/9/95 JC + * - now calls TIFFClose ... stupid + * 25/1/96 JC + * - typo on MINISBLACK ... + * 7/4/97 JC + * - completely redone for TIFF 6 + * - now full baseline TIFF 6 reader, and does CIELAB as well + * 11/4/97 JC + * - added partial read for tiled images + * 23/4/97 JC + * - extra subsample parameter + * - im_istiffpyramid() added + * 5/12/97 JC + * - if loading YCbCr, convert to IM_CODING_LABQ + * 1/5/98 JC + * - now reads 16-bit greyscale and RGB + * 26/10/98 JC + * - now used "rb" mode on systems that need binary open + * 12/11/98 JC + * - no sub-sampling if sub == 1 + * 26/2/99 JC + * - ooops, else missing for subsample stuff above + * 2/10/99 JC + * - tiled 16-bit greyscale read was broken + * - added mutex for TIFFReadTile() calls + * 11/5/00 JC + * - removed TIFFmalloc/TIFFfree usage + * 23/4/01 JC + * - HAVE_TIFF turns on TIFF goodness + * 24/5/01 JC + * - im_tiff2vips_header() added + * 11/7/01 JC + * - subsample now in input filename + * - ... and it's a page number (from 0) instead + * 21/8/02 JC + * - now reads CMYK + * - hmm, dpi -> ppm conversion was wrong! + * 10/9/02 JC + * - oops, handle TIFF errors better + * 2/12/02 JC + * - reads 8-bit RGBA + * 12/12/02 JC + * - reads 16-bit LAB + * 13/2/03 JC + * - pixels/cm res read was wrong + * 17/11/03 Andrey Kiselev + * - read 32-bit float greyscale and rgb + * 5/4/04 + * - better handling of edge tiles (thanks Ruven) + * 16/4/04 + * - cleanup + * - added broken tile read mode + * 18/5/04 Andrey Kiselev + * - better no resolution diagnostic + * 26/5/04 + * - reads 16 bit RGBA + * 28/7/04 + * - arrg, 16bit RGB was broken, thanks haida + * 26/11/04 + * - add a TIFF warning handler, stops occasional libMagick exceptions + * 9/3/05 + * - load 32-bit float LAB + * 8/4/05 + * - onebit read no longer reads one byte too many on multiple of 8 wide + * images + * 22/6/05 + * - 16 bit LAB read was broken + * 9/9/05 + * - read any ICCPROFILE tag + * 8/5/06 + * - set RGB16 and GREY16 Type + * 21/5/06 + * - use external im_tile_cache() operation for great code shrinkage + * - less RAM usage too, esp. with >1 CPU + * - should be slightly faster + * - removed 'broken' read option + * 18/7/07 Andrey Kiselev + * - remove "b" option on TIFFOpen() + * 9/4/08 + * - set IM_META_RESOLUTION_UNIT + * 17/4/08 + * - allow CMYKA (thanks Doron) + */ + +/* + + 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 + + */ + +/* Turn on debugging output. +#define DEBUG + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#ifndef HAVE_TIFF + +#include + +int +im_tiff2vips( const char *tiffile, IMAGE *im ) +{ + im_error( "im_tiff2vips", _( "TIFF support disabled" ) ); + return( -1 ); +} + +int +im_istiffpyramid( const char *name ) +{ + return( 0 ); +} + +int +im_tiff2vips_header( const char *tiffile, IMAGE *im ) +{ + im_error( "im_tiff2vips", _( "TIFF support disabled" ) ); + return( -1 ); +} + +#else /*HAVE_TIFF*/ + +#include +#include +#include +#include + +#include +#include + +#include + +#ifdef WITH_DMALLOC +#include +#endif /*WITH_DMALLOC*/ + +/* Scanline-type process function. + */ +typedef void (*scanline_process_fn)( PEL *q, PEL *p, int n, void *user ); + +/* Stuff we track during a read. + */ +typedef struct { + /* Parameters. + */ + char *filename; + IMAGE *out; + + /* From filename. + */ + int page; + + /* The TIFF we read. + */ + TIFF *tiff; + + /* Process for this image type. + */ + scanline_process_fn sfn; + void *client; + + /* Geometry. + */ + int twidth, theight; /* Tile size */ + + /* Only need one of these, since we mutex around TIFF*(). + */ + GMutex *tlock; /* Lock for TIFF*() calls */ +} ReadTiff; + +/* Reading a YCbCr image ... parameters we use for conversion. + */ +typedef struct { + /* Input and output. + */ + TIFF *tif; /* From here */ + IMAGE *im; /* To here */ + + /* RGB <-> YCbCr conversion. + */ + float LumaRed, LumaGreen, LumaBlue; + + /* RGB -> LAB conversion. + */ + void *table; +} YCbCrParams; + +/* Handle TIFF errors here. Shared with im_vips2tiff. These can be called from + * more than one thread, but im_error and im_warn have mutexes in, so that's + * OK. + */ +void +im__thandler_error( char *module, char *fmt, va_list ap ) +{ + im_verror( module, fmt, ap ); +} + +void +im__thandler_warning( char *module, char *fmt, va_list ap ) +{ + char buf[256]; + + im_vsnprintf( buf, 256, fmt, ap ); + im_warn( module, "%s", buf ); +} + +/* Test for field exists. + */ +static int +tfexists( TIFF *tif, ttag_t tag ) +{ + uint32 a, b; + + if( TIFFGetField( tif, tag, &a, &b ) ) + return( 1 ); + else + return( 0 ); +} + +/* Test a uint16 field. Field must be defined and equal to the value. + */ +static int +tfequals( TIFF *tif, ttag_t tag, uint16 val ) +{ + uint16 fld; + + if( !TIFFGetFieldDefaulted( tif, tag, &fld ) ) { + im_error( "im_tiff2vips", + _( "required field %d missing" ), tag ); + return( 0 ); + } + if( fld != val ) { + im_error( "im_tiff2vips", _( "required field %d=%d, not %d" ), + tag, fld, val ); + return( 0 ); + } + + /* All ok. + */ + return( 1 ); +} + +/* Get a uint32 field. + */ +static int +tfget32( TIFF *tif, ttag_t tag, int *out ) +{ + uint32 fld; + + if( !TIFFGetFieldDefaulted( tif, tag, &fld ) ) { + im_error( "im_tiff2vips", + _( "required field %d missing" ), tag ); + return( 0 ); + } + + /* All ok. + */ + *out = fld; + return( 1 ); +} + +/* Get a uint16 field. + */ +static int +tfget16( TIFF *tif, ttag_t tag, int *out ) +{ + uint16 fld; + + if( !TIFFGetFieldDefaulted( tif, tag, &fld ) ) { + im_error( "im_tiff2vips", + _( "required field %d missing" ), tag ); + return( 0 ); + } + + /* All ok. + */ + *out = fld; + return( 1 ); +} + +/* Per-scanline process function for IM_CODING_LABQ. + */ +static void +labpack_line( PEL *q, PEL *p, int n, void *dummy ) +{ + int x; + + for( x = 0; x < n; x++ ) { + q[0] = p[0]; + q[1] = p[1]; + q[2] = p[2]; + q[3] = 0; + + q += 4; + p += 3; + } +} + +/* Read an 8-bit LAB image. + */ +static int +parse_labpack( ReadTiff *rtiff, IMAGE *out ) +{ + if( !tfequals( rtiff->tiff, TIFFTAG_SAMPLESPERPIXEL, 3 ) || + !tfequals( rtiff->tiff, TIFFTAG_BITSPERSAMPLE, 8 ) ) + return( -1 ); + + out->Bands = 4; + out->Bbits = 8; + out->BandFmt = IM_BANDFMT_UCHAR; + out->Coding = IM_CODING_LABQ; + out->Type = IM_TYPE_LAB; + + rtiff->sfn = labpack_line; + rtiff->client = NULL; + + return( 0 ); +} + +/* Per-scanline process function for IM_CODING_LABQ. + */ +static void +labs_line( PEL *q, PEL *p, int n, void *dummy ) +{ + int x; + unsigned short *p1 = (unsigned short *) p; + short *q1 = (short *) q; + + for( x = 0; x < n; x++ ) { + q1[0] = p1[0] >> 1; + q1[1] = p1[1]; + q1[2] = p1[2]; + + q1 += 3; + p1 += 3; + } +} + +/* Read a 16-bit LAB image. + */ +static int +parse_labs( ReadTiff *rtiff, IMAGE *out ) +{ + if( !tfequals( rtiff->tiff, TIFFTAG_SAMPLESPERPIXEL, 3 ) || + !tfequals( rtiff->tiff, TIFFTAG_BITSPERSAMPLE, 16 ) ) + return( -1 ); + + out->Bands = 3; + out->Bbits = 16; + out->BandFmt = IM_BANDFMT_SHORT; + out->Coding = IM_CODING_NONE; + out->Type = IM_TYPE_LABS; + + rtiff->sfn = labs_line; + rtiff->client = NULL; + + return( 0 ); +} + +/* Per-scanline process function for IM_CODING_LABQ. + */ +static void +ycbcr_line( PEL *q, PEL *p, int n, void *dummy ) +{ + int x; + + for( x = 0; x < n; x++ ) { + q[0] = p[0]; + q[1] = p[1]; + q[2] = p[2]; + q[3] = 0; + + q += 4; + p += 3; + } +} + +/* Read a YCbCr image. + */ +static int +parse_ycbcr( ReadTiff *rtiff, IMAGE *out ) +{ + if( !tfequals( rtiff->tiff, TIFFTAG_SAMPLESPERPIXEL, 3 ) || + !tfequals( rtiff->tiff, TIFFTAG_BITSPERSAMPLE, 8 ) ) + return( -1 ); + + out->Bands = 4; + out->Bbits = 8; + out->BandFmt = IM_BANDFMT_UCHAR; + out->Coding = IM_CODING_LABQ; + out->Type = IM_TYPE_LAB; + + rtiff->sfn = ycbcr_line; + rtiff->client = NULL; + + return( 0 ); +} + +/* Per-scanline process function for 1 bit images. + */ +static void +onebit_line( PEL *q, PEL *p, int n, void *flg ) +{ + /* Extract PHOTOMETRIC_INTERPRETATION. + */ + int pm = *((int *) flg); + int x, i, z; + PEL bits; + + int black = (pm == PHOTOMETRIC_MINISBLACK) ? 0 : 255; + int white = black ^ -1; + + /* (sigh) how many times have I written this? + */ + for( x = 0, i = 0; i < (n >> 3); i++ ) { + bits = (PEL) p[i]; + + for( z = 0; z < 8; z++, x++ ) { + q[x] = (bits & 128) ? white : black; + bits <<= 1; + } + } + + /* Do last byte in line. + */ + if( n & 7 ) { + bits = p[i]; + for( z = 0; z < (n & 7); z++ ) { + q[x + z] = (bits & 128) ? white : black; + bits <<= 1; + } + } +} + +/* Read a 1-bit TIFF image. Pass in pixel values to use for black and white. + */ +static int +parse_onebit( ReadTiff *rtiff, int pm, IMAGE *out ) +{ + int *ppm; + + if( !tfequals( rtiff->tiff, TIFFTAG_SAMPLESPERPIXEL, 1 ) || + !tfequals( rtiff->tiff, TIFFTAG_BITSPERSAMPLE, 1 ) ) + return( -1 ); + + out->Bands = 1; + out->Bbits = 8; + out->BandFmt = IM_BANDFMT_UCHAR; + out->Coding = IM_CODING_NONE; + out->Type = IM_TYPE_B_W; + + /* Note pm for later. + */ + if( !(ppm = IM_ARRAY( out, 1, int )) ) + return( -1 ); + *ppm = pm; + + rtiff->sfn = onebit_line; + rtiff->client = ppm; + + return( 0 ); +} + +/* Per-scanline process function for 8-bit greyscale images. + */ +static void +greyscale8_line( PEL *q, PEL *p, int n, void *flg ) +{ + /* Extract swap mask. + */ + PEL mask = *((PEL *) flg); + int x; + + /* Read bytes, swapping sense if necessary. + */ + for( x = 0; x < n; x++ ) + q[x] = p[x] ^ mask; +} + +/* Read a 8-bit grey-scale TIFF image. + */ +static int +parse_greyscale8( ReadTiff *rtiff, int pm, IMAGE *out ) +{ + PEL *mask; + + if( !tfequals( rtiff->tiff, TIFFTAG_SAMPLESPERPIXEL, 1 ) || + !tfequals( rtiff->tiff, TIFFTAG_BITSPERSAMPLE, 8 ) ) + return( -1 ); + + /* Eor each pel with this later. + */ + if( !(mask = IM_ARRAY( out, 1, PEL )) ) + return( -1 ); + *mask = (pm == PHOTOMETRIC_MINISBLACK) ? 0 : 255; + + out->Bands = 1; + out->Bbits = 8; + out->BandFmt = IM_BANDFMT_UCHAR; + out->Coding = IM_CODING_NONE; + out->Type = IM_TYPE_B_W; + + rtiff->sfn = greyscale8_line; + rtiff->client = mask; + + return( 0 ); +} + +/* Per-scanline process function for 16-bit greyscale images. + */ +static void +greyscale16_line( PEL *q, PEL *p, int n, void *flg ) +{ + /* Extract swap mask. + */ + unsigned short mask = *((unsigned short *) flg); + unsigned short *p1 = (unsigned short *) p; + unsigned short *q1 = (unsigned short *) q; + int x; + + /* Read bytes, swapping sense if necessary. + */ + for( x = 0; x < n; x++ ) + q1[x] = p1[x] ^ mask; +} + +/* Read a 16-bit grey-scale TIFF image. + */ +static int +parse_greyscale16( ReadTiff *rtiff, int pm, IMAGE *out ) +{ + unsigned short *mask; + + if( !tfequals( rtiff->tiff, TIFFTAG_SAMPLESPERPIXEL, 1 ) || + !tfequals( rtiff->tiff, TIFFTAG_BITSPERSAMPLE, 16 ) ) + return( -1 ); + + /* Eor each pel with this later. + */ + if( !(mask = IM_ARRAY( out, 1, unsigned short )) ) + return( -1 ); + mask[0] = (pm == PHOTOMETRIC_MINISBLACK) ? 0 : 65535; + + out->Bands = 1; + out->Bbits = 16; + out->BandFmt = IM_BANDFMT_USHORT; + out->Coding = IM_CODING_NONE; + out->Type = IM_TYPE_GREY16; + + rtiff->sfn = greyscale16_line; + rtiff->client = mask; + + return( 0 ); +} + +/* Per-scanline process function for 32-bit floating point greyscale images. + */ +static void +greyscale32f_line( PEL *q, PEL *p, int n ) +{ + float *p1 = (float *) p; + float *q1 = (float *) q; + int x; + + for( x = 0; x < n; x++ ) + q1[x] = p1[x]; +} + +/* Read a 32-bit floating point greyscale TIFF image. What do we do about + * MINISWHITE/MINISBLACK (pm)? Not sure ... just ignore it. + */ +static int +parse_greyscale32f( ReadTiff *rtiff, int pm, IMAGE *out ) +{ + if( !tfequals( rtiff->tiff, TIFFTAG_SAMPLESPERPIXEL, 1 ) || + !tfequals( rtiff->tiff, TIFFTAG_BITSPERSAMPLE, 32 ) ) + return( -1 ); + + out->Bands = 1; + out->Bbits = 32; + out->BandFmt = IM_BANDFMT_FLOAT; + out->Coding = IM_CODING_NONE; + out->Type = IM_TYPE_B_W; + + rtiff->sfn = (scanline_process_fn) greyscale32f_line; + rtiff->client = NULL; + + return( 0 ); +} + +/* Per-scanline process function for palette images. + */ +static void +palette_line( PEL *q, PEL *p, int n, void *flg ) +{ + /* Extract maps. + */ + PEL *red = ((PEL **) flg)[0]; + PEL *green = ((PEL **) flg)[1]; + PEL *blue = ((PEL **) flg)[2]; + int x; + + /* Read bytes, generating colour. + */ + for( x = 0; x < n; x++ ) { + int i = *p++; + + q[0] = red[i]; + q[1] = green[i]; + q[2] = blue[i]; + + q += 3; + } +} + +/* Read a palette-ised TIFF image. Again, we only allow 8-bits for now. + */ +static int +parse_palette( ReadTiff *rtiff, IMAGE *out ) +{ + uint16 *tred, *tgreen, *tblue; + PEL *red, *green, *blue; + PEL **maps; + int i; + + if( !tfequals( rtiff->tiff, TIFFTAG_SAMPLESPERPIXEL, 1 ) || + !tfequals( rtiff->tiff, TIFFTAG_BITSPERSAMPLE, 8 ) ) + return( -1 ); + + /* Allocate mem for VIPS colour maps. + */ + if( !(red = IM_ARRAY( out, 256, PEL )) || + !(green = IM_ARRAY( out, 256, PEL )) || + !(blue = IM_ARRAY( out, 256, PEL )) || + !(maps = IM_ARRAY( out, 3, PEL * )) ) + return( -1 ); + + /* Get maps, convert to 8-bit data. + */ + if( !TIFFGetField( rtiff->tiff, + TIFFTAG_COLORMAP, &tred, &tgreen, &tblue ) ) { + im_error( "im_tiff2vips", _( "bad colormap" ) ); + return( -1 ); + } + for( i = 0; i < 256; i++ ) { + red[i] = tred[i] >> 8; + green[i] = tgreen[i] >> 8; + blue[i] = tblue[i] >> 8; + } + maps[0] = red; + maps[1] = green; + maps[2] = blue; + + out->Bands = 3; + out->Bbits = 8; + out->BandFmt = IM_BANDFMT_UCHAR; + out->Coding = IM_CODING_NONE; + out->Type = IM_TYPE_sRGB; + + rtiff->sfn = palette_line; + rtiff->client = maps; + + return( 0 ); +} + +/* Per-scanline process function for 8-bit RGB/RGBA/CMYK/CMYKA. + */ +static void +rgbcmyk8_line( PEL *q, PEL *p, int n, IMAGE *im ) +{ + int x, b; + + for( x = 0; x < n; x++ ) { + for( b = 0; b < im->Bands; b++ ) + q[b] = p[b]; + + q += im->Bands; + p += im->Bands; + } +} + +/* Read an 8-bit RGB/RGBA image. + */ +static int +parse_rgb8( ReadTiff *rtiff, IMAGE *out ) +{ + int bands; + + /* Check other TIFF fields to make sure we can read this. Can have 4 + * bands for RGBA. + */ + if( !tfequals( rtiff->tiff, TIFFTAG_BITSPERSAMPLE, 8 ) || + !tfget16( rtiff->tiff, TIFFTAG_SAMPLESPERPIXEL, &bands ) ) + return( -1 ); + if( bands != 3 && bands != 4 ) { + im_error( "im_tiff2vips", _( "3 or 4 bands RGB TIFF only" ) ); + return( -1 ); + } + + out->Bands = bands; + out->Bbits = 8; + out->BandFmt = IM_BANDFMT_UCHAR; + out->Coding = IM_CODING_NONE; + out->Type = IM_TYPE_sRGB; + + rtiff->sfn = (scanline_process_fn) rgbcmyk8_line; + rtiff->client = out; + + return( 0 ); +} + +/* Per-scanline process function for RGB/RGBA 16. + */ +static void +rgb16_line( PEL *q, PEL *p, int n, IMAGE *im ) +{ + int x, b; + unsigned short *p1 = (unsigned short *) p; + unsigned short *q1 = (unsigned short *) q; + + for( x = 0; x < n; x++ ) { + for( b = 0; b < im->Bands; b++ ) + q1[b] = p1[b]; + + q1 += im->Bands; + p1 += im->Bands; + } +} + +/* Read a 16-bit RGB/RGBA image. + */ +static int +parse_rgb16( ReadTiff *rtiff, IMAGE *out ) +{ + int bands; + + /* Check other TIFF fields to make sure we can read this. Can have 4 + * bands for RGBA. + */ + if( !tfequals( rtiff->tiff, TIFFTAG_BITSPERSAMPLE, 16 ) || + !tfget16( rtiff->tiff, TIFFTAG_SAMPLESPERPIXEL, &bands ) ) + return( -1 ); + if( bands != 3 && bands != 4 ) { + im_error( "im_tiff2vips", _( "3 or 4 bands RGB TIFF only" ) ); + return( -1 ); + } + + out->Bands = bands; + out->Bbits = 16; + out->BandFmt = IM_BANDFMT_USHORT; + out->Coding = IM_CODING_NONE; + out->Type = IM_TYPE_RGB16; + + rtiff->sfn = (scanline_process_fn) rgb16_line; + rtiff->client = out; + + return( 0 ); +} + +/* Per-scanline process function for 32f. + */ +static void +r32f_line( PEL *q, PEL *p, int n, void *dummy ) +{ + int x; + float *p1 = (float *) p; + float *q1 = (float *) q; + + for( x = 0; x < n; x++ ) { + q1[0] = p1[0]; + q1[1] = p1[1]; + q1[2] = p1[2]; + + q1 += 3; + p1 += 3; + } +} + +/* Read a 32-bit float image. RGB or LAB, with or without alpha. + */ +static int +parse_32f( ReadTiff *rtiff, int pm, IMAGE *out ) +{ + int bands; + + if( !tfget16( rtiff->tiff, TIFFTAG_SAMPLESPERPIXEL, &bands ) || + !tfequals( rtiff->tiff, TIFFTAG_BITSPERSAMPLE, 32 ) ) + return( -1 ); + + /* Can be 4 for images with an alpha channel. + */ + assert( bands == 3 || bands == 4 ); + + out->Bands = bands; + out->Bbits = 32; + out->BandFmt = IM_BANDFMT_FLOAT; + out->Coding = IM_CODING_NONE; + + switch( pm ) { + case PHOTOMETRIC_CIELAB: + out->Type = IM_TYPE_LAB; + break; + + case PHOTOMETRIC_RGB: + out->Type = IM_TYPE_sRGB; + break; + + default: + assert( 0 ); + } + + rtiff->sfn = r32f_line; + rtiff->client = NULL; + + return( 0 ); +} + +/* Read a CMYK image. + */ +static int +parse_cmyk( ReadTiff *rtiff, IMAGE *out ) +{ + int bands; + + /* Check other TIFF fields to make sure we can read this. Can have 5 + * bands for CMYKA. + */ + if( !tfequals( rtiff->tiff, TIFFTAG_BITSPERSAMPLE, 8 ) || + !tfequals( rtiff->tiff, TIFFTAG_INKSET, INKSET_CMYK ) || + !tfget16( rtiff->tiff, TIFFTAG_SAMPLESPERPIXEL, &bands ) ) + return( -1 ); + if( bands != 4 && bands != 5 ) { + im_error( "im_tiff2vips", _( "4 or 5 bands CMYK TIFF only" ) ); + return( -1 ); + } + + out->Bands = bands; + out->Bbits = 8; + out->BandFmt = IM_BANDFMT_UCHAR; + out->Coding = IM_CODING_NONE; + out->Type = IM_TYPE_CMYK; + + rtiff->sfn = (scanline_process_fn) rgbcmyk8_line; + rtiff->client = out; + + return( 0 ); +} + +/* Read resolution from a TIFF image. + */ +static int +parse_resolution( TIFF *tiff, IMAGE *out ) +{ + float x, y; + int ru; + + if( TIFFGetFieldDefaulted( tiff, TIFFTAG_XRESOLUTION, &x ) && + TIFFGetFieldDefaulted( tiff, TIFFTAG_YRESOLUTION, &y ) && + tfget16( tiff, TIFFTAG_RESOLUTIONUNIT, &ru ) ) { + switch( ru ) { + case RESUNIT_NONE: + break; + + case RESUNIT_INCH: + /* In pixels-per-inch ... convert to mm. + */ + x /= 10.0 * 2.54; + y /= 10.0 * 2.54; + im_meta_set_string( out, + IM_META_RESOLUTION_UNIT, "in" ); + break; + + case RESUNIT_CENTIMETER: + /* In pixels-per-centimetre ... convert to mm. + */ + x /= 10.0; + y /= 10.0; + im_meta_set_string( out, + IM_META_RESOLUTION_UNIT, "cm" ); + break; + + default: + im_error( "im_tiff2vips", + _( "unknown resolution unit" ) ); + return( -1 ); + } + } + else { + im_warn( "im_tiff2vips", _( "no resolution information for " + "TIFF image \"%s\" -- defaulting to 1 pixel per mm" ), + TIFFFileName( tiff ) ); + x = 1.0; + y = 1.0; + } + + out->Xres = x; + out->Yres = y; + + return( 0 ); +} + +/* Look at PhotometricInterpretation and BitsPerPixel, and try to figure out + * which of the image classes this is. + */ +static int +parse_header( ReadTiff *rtiff, IMAGE *out ) +{ + int pm, bps, format; + uint32 data_length; + void *data; + + /* Ban separate planes, too annoying. + */ + if( tfexists( rtiff->tiff, TIFFTAG_PLANARCONFIG ) && + !tfequals( rtiff->tiff, + TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG ) ) + return( -1 ); + + /* Always need dimensions. + */ + if( !tfget32( rtiff->tiff, TIFFTAG_IMAGEWIDTH, &out->Xsize ) || + !tfget32( rtiff->tiff, TIFFTAG_IMAGELENGTH, &out->Ysize ) || + parse_resolution( rtiff->tiff, out ) ) + return( -1 ); + + /* Try to find out which type of TIFF image it is. + */ + if( !tfget16( rtiff->tiff, TIFFTAG_PHOTOMETRIC, &pm ) || + !tfget16( rtiff->tiff, TIFFTAG_BITSPERSAMPLE, &bps ) ) + return( -1 ); + + switch( pm ) { + case PHOTOMETRIC_CIELAB: + switch( bps ) { + case 8: + if( parse_labpack( rtiff, out ) ) + return( -1 ); + break; + + case 16: + if( parse_labs( rtiff, out ) ) + return( -1 ); + break; + + case 32: + if( !tfget16( rtiff->tiff, + TIFFTAG_SAMPLEFORMAT, &format ) ) + return( -1 ); + + if( format == SAMPLEFORMAT_IEEEFP ) { + if( parse_32f( rtiff, pm, out ) ) + return( -1 ); + } + else { + im_error( "im_tiff2vips", + _( "unsupported sample " + "format %d for lab image" ), + format ); + return( -1 ); + } + + break; + + default: + im_error( "im_tiff2vips", + _( "unsupported depth %d for LAB image" ), + bps ); + return( -1 ); + } + + break; + + case PHOTOMETRIC_YCBCR: + /* Easy decision! + */ + if( parse_ycbcr( rtiff, out ) ) + return( -1 ); + + break; + + case PHOTOMETRIC_MINISWHITE: + case PHOTOMETRIC_MINISBLACK: + switch( bps ) { + case 1: + if( parse_onebit( rtiff, pm, out ) ) + return( -1 ); + + break; + + case 8: + if( parse_greyscale8( rtiff, pm, out ) ) + return( -1 ); + + break; + + case 16: + if( parse_greyscale16( rtiff, pm, out ) ) + return( -1 ); + + break; + + case 32: + if( !tfget16( rtiff->tiff, + TIFFTAG_SAMPLEFORMAT, &format ) ) + return( -1 ); + + if( format == SAMPLEFORMAT_IEEEFP ) { + if( parse_greyscale32f( rtiff, pm, out ) ) + return( -1 ); + } + else { + im_error( "im_tiff2vips", + _( "unsupported sample format " + "%d for greyscale image" ), + format ); + return( -1 ); + } + + break; + + default: + im_error( "im_tiff2vips", _( "unsupported depth %d " + "for greyscale image" ), bps ); + return( -1 ); + } + + break; + + case PHOTOMETRIC_PALETTE: + /* Full colour pallette. + */ + if( parse_palette( rtiff, out ) ) + return( -1 ); + + break; + + case PHOTOMETRIC_RGB: + /* Plain RGB. + */ + switch( bps ) { + case 8: + if( parse_rgb8( rtiff, out ) ) + return( -1 ); + break; + + case 16: + if( parse_rgb16( rtiff, out ) ) + return( -1 ); + break; + + case 32: + if( !tfget16( rtiff->tiff, + TIFFTAG_SAMPLEFORMAT, &format ) ) + return( -1 ); + + if( format == SAMPLEFORMAT_IEEEFP ) { + if( parse_32f( rtiff, pm, out ) ) + return( -1 ); + } + else { + im_error( "im_tiff2vips", + _( "unsupported sample " + "format %d for rgb image" ), + format ); + return( -1 ); + } + + break; + + default: + im_error( "im_tiff2vips", _( "unsupported depth %d " + "for RGB image" ), bps ); + return( -1 ); + } + + break; + + case PHOTOMETRIC_SEPARATED: + if( parse_cmyk( rtiff, out ) ) + return( -1 ); + + break; + + default: + im_error( "im_tiff2vips", _( "unknown photometric " + "interpretation %d" ), pm ); + return( -1 ); + } + + /* Read any ICC profile. + */ + if( TIFFGetField( rtiff->tiff, + TIFFTAG_ICCPROFILE, &data_length, &data ) ) { + void *data_copy; + + if( !(data_copy = im_malloc( NULL, data_length )) ) + return( -1 ); + memcpy( data_copy, data, data_length ); + if( im_meta_set_blob( out, IM_META_ICC_NAME, + (im_callback_fn) im_free, data_copy, data_length ) ) { + im_free( data_copy ); + return( -1 ); + } + } + + return( 0 ); +} + +/* Allocate a tile buffer. Have one of these for each thread so we can unpack + * to vips in parallel. + */ +static void * +seq_start( IMAGE *out, void *a, void *b ) +{ + ReadTiff *rtiff = (ReadTiff *) a; + tdata_t *buf; + + if( !(buf = im_malloc( NULL, TIFFTileSize( rtiff->tiff ) )) ) + return( NULL ); + + return( (void *) buf ); +} + +/* Loop over the output region, painting in tiles from the file. + */ +static int +fill_region( REGION *out, void *seq, void *a, void *b ) +{ + tdata_t *buf = (tdata_t *) seq; + ReadTiff *rtiff = (ReadTiff *) a; + Rect *r = &out->valid; + + /* Find top left of tiles we need. + */ + int xs = (r->left / rtiff->twidth) * rtiff->twidth; + int ys = (r->top / rtiff->theight) * rtiff->theight; + + /* Sizeof a line of bytes in the TIFF tile. + */ + int tls = TIFFTileSize( rtiff->tiff ) / rtiff->theight; + + /* Sizeof a pel in the TIFF file. This won't work for formats which + * are <1 byte per pel, like onebit :-( Fortunately, it's only used + * to calculate addresses within a tile, and because we are wrapped in + * im_tile_cache(), we will never have to calculate positions within a + * tile. + */ + int tps = tls / rtiff->twidth; + + int x, y, z; + + for( y = ys; y < IM_RECT_BOTTOM( r ); y += rtiff->theight ) + for( x = xs; x < IM_RECT_RIGHT( r ); x += rtiff->twidth ) { + Rect tile; + Rect hit; + + /* Read that tile. + */ + g_mutex_lock( rtiff->tlock ); + if( TIFFReadTile( rtiff->tiff, buf, + x, y, 0, 0 ) < 0 ) { + g_mutex_unlock( rtiff->tlock ); + return( -1 ); + } + g_mutex_unlock( rtiff->tlock ); + + /* The tile we read. + */ + tile.left = x; + tile.top = y; + tile.width = rtiff->twidth; + tile.height = rtiff->twidth; + + /* The section that hits the region we are building. + */ + im_rect_intersectrect( &tile, r, &hit ); + + /* Unpack to VIPS format. We can do this in parallel. + * Just unpack the section of the tile we need. + */ + for( z = 0; z < hit.height; z++ ) { + PEL *p = (PEL *) buf + + (hit.left - tile.left) * tps + + (hit.top - tile.top + z) * tls; + PEL *q = (PEL *) IM_REGION_ADDR( out, + hit.left, hit.top + z ); + + rtiff->sfn( q, p, hit.width, rtiff->client ); + } + } + + return( 0 ); +} + +static int +seq_stop( void *seq, void *a, void *b ) +{ + im_free( seq ); + + return( 0 ); +} + +/* Tile-type TIFF reader core - pass in a per-tile transform. Generate into + * the im and do it all partially. + */ +static int +read_tilewise( ReadTiff *rtiff, IMAGE *out ) +{ + IMAGE *raw; + + /* Tile cache: keep enough for two complete rows of tiles. + * This lets us do (smallish) area ops, like im_conv(), while + * still only hitting each TIFF tile once. + */ + if( !(raw = im_open_local( out, "cache", "p" )) ) + return( -1 ); + + /* Get tiling geometry. + */ + if( !tfget32( rtiff->tiff, TIFFTAG_TILEWIDTH, &rtiff->twidth ) || + !tfget32( rtiff->tiff, TIFFTAG_TILELENGTH, &rtiff->theight ) ) + return( -1 ); + + /* Make sure we can write PIO-style. + */ + if( im_poutcheck( raw ) ) + return( -1 ); + + /* Parse the TIFF header and set up raw. + */ + if( parse_header( rtiff, raw ) ) + return( -1 ); + + /* Process and save as VIPS. + */ + if( im_demand_hint( raw, IM_SMALLTILE, NULL ) || + im_generate( raw, + seq_start, fill_region, seq_stop, rtiff, NULL ) ) + return( -1 ); + + /* Copy to out, adding a cache. Enough tiles for two complete rows. + */ + if( im_tile_cache( raw, out, + rtiff->twidth, rtiff->theight, + 2 * (1 + raw->Xsize / rtiff->twidth) ) ) + return( -1 ); + + return( 0 ); +} + +/* Scanline-type TIFF reader core - pass in a per-scanline transform. + */ +static int +read_scanlinewise( ReadTiff *rtiff, IMAGE *out ) +{ + PEL *vbuf; + tdata_t tbuf; + int y; + + if( parse_header( rtiff, out ) ) + return( -1 ); + + /* Make sure we can write WIO-style. + */ + if( im_outcheck( out ) || im_setupout( out ) ) + return( -1 ); + + /* Make VIPS output buffer. + */ + if( !(vbuf = IM_ARRAY( out, IM_IMAGE_SIZEOF_LINE( out ), PEL )) ) + return( -1 ); + + /* Make TIFF line input buffer. + */ + if( !(tbuf = im_malloc( out, TIFFScanlineSize( rtiff->tiff ) )) ) + return( -1 ); + + for( y = 0; y < out->Ysize; y++ ) { + /* Read TIFF scanline. + */ + if( TIFFReadScanline( rtiff->tiff, tbuf, y, 0 ) < 0 ) { + im_error( "im_tiff2vips", _( "read error" ) ); + return( -1 ); + } + + /* Process and save as VIPS. + */ + rtiff->sfn( vbuf, tbuf, out->Xsize, rtiff->client ); + if( im_writeline( y, out, vbuf ) ) + return( -1 ); + } + + return( 0 ); +} + +/* Free a ReadTiff. + */ +static int +readtiff_destroy( ReadTiff *rtiff ) +{ + IM_FREEF( TIFFClose, rtiff->tiff ); + IM_FREEF( g_mutex_free, rtiff->tlock ); + + return( 0 ); +} + +/* Make a ReadTiff. + */ +static ReadTiff * +readtiff_new( const char *filename, IMAGE *out ) +{ + ReadTiff *rtiff; + char name[FILENAME_MAX]; + char mode[FILENAME_MAX]; + char *p, *q; + + if( !(rtiff = IM_NEW( out, ReadTiff )) ) + return( NULL ); + rtiff->filename = NULL; + rtiff->out = out; + im_filename_split( filename, name, mode ); + rtiff->filename = im_strdup( out, name ); + rtiff->page = 0; + rtiff->tiff = NULL; + rtiff->sfn = NULL; + rtiff->client = NULL; + rtiff->twidth = 0; + rtiff->theight = 0; + rtiff->tlock = g_mutex_new(); + + if( im_add_close_callback( out, + (im_callback_fn) readtiff_destroy, rtiff, NULL ) ) { + readtiff_destroy( rtiff ); + return( NULL ); + } + + /* Parse out params. + */ + p = &mode[0]; + if( (q = im_getnextoption( &p )) ) { + rtiff->page = atoi( q ); + + if( rtiff->page < 0 || rtiff->page > 1000 ) { + im_error( "im_tiff2vips", _( "bad page number %d" ), + rtiff->page ); + return( NULL ); + } + } + + return( rtiff ); +} + +/* Pull out the nth directory from a TIFF file. + */ +static TIFF * +get_directory( const char *filename, int page ) +{ + TIFF *tif; + int i; + + /* No need to use "b" and it means something different anyway. + */ + if( !(tif = TIFFOpen( filename, "r" )) ) { + im_error( "im_tiff2vips", + _( "unable to open \"%s\" for input" ), + filename ); + return( NULL ); + } + + for( i = 0; i < page; i++ ) + if( !TIFFReadDirectory( tif ) ) { + /* Run out of directories. + */ + TIFFClose( tif ); + return( NULL ); + } + + return( tif ); +} + +int +im_istiffpyramid( const char *name ) +{ + TIFF *tif; + + TIFFSetErrorHandler( (TIFFErrorHandler) im__thandler_error ); + TIFFSetWarningHandler( (TIFFErrorHandler) im__thandler_warning ); + + if( (tif = get_directory( name, 2 )) ) { + /* We can see page 2 ... assume it is. + */ + TIFFClose( tif ); + return( 1 ); + } + + return( 0 ); +} + +int +im_tiff2vips( const char *filename, IMAGE *out ) +{ + ReadTiff *rtiff; + +#ifdef DEBUG + printf( "im_tiff2vips: libtiff version is \"%s\"\n", TIFFGetVersion() ); +#endif /*DEBUG*/ + + TIFFSetErrorHandler( (TIFFErrorHandler) im__thandler_error ); + TIFFSetWarningHandler( (TIFFErrorHandler) im__thandler_warning ); + + if( !(rtiff = readtiff_new( filename, out )) ) + return( -1 ); + + if( !(rtiff->tiff = get_directory( rtiff->filename, rtiff->page )) ) { + im_error( "im_tiff2vips", _( "TIFF file does not " + "contain page %d" ), rtiff->page ); + return( -1 ); + } + + if( TIFFIsTiled( rtiff->tiff ) ) { + if( read_tilewise( rtiff, out ) ) + return( -1 ); + } + else { + if( read_scanlinewise( rtiff, out ) ) + return( -1 ); + } + + return( 0 ); +} + +/* Just parse the header. + */ +int +im_tiff2vips_header( const char *filename, IMAGE *out ) +{ + ReadTiff *rtiff; + + TIFFSetErrorHandler( (TIFFErrorHandler) im__thandler_error ); + TIFFSetWarningHandler( (TIFFErrorHandler) im__thandler_warning ); + + if( !(rtiff = readtiff_new( filename, out )) ) + return( -1 ); + + if( !(rtiff->tiff = get_directory( rtiff->filename, rtiff->page )) ) { + im_error( "im_tiff2vips", + _( "TIFF file does not contain page %d" ), + rtiff->page ); + return( -1 ); + } + + if( parse_header( rtiff, out ) ) + return( -1 ); + + return( 0 ); +} + +#endif /*HAVE_TIFF*/ diff --git a/libsrc/conversion/im_tile_cache.c b/libsrc/conversion/im_tile_cache.c new file mode 100644 index 00000000..1ce3dd16 --- /dev/null +++ b/libsrc/conversion/im_tile_cache.c @@ -0,0 +1,391 @@ +/* Tile tile cache from tiff2vips ... broken out so it can be shared with + * openexr read. + * + * This isn't the same as im_cache(): we don't sub-divide, and we + * single-thread our callee. + * + * 23/8/06 + * - take ownership of reused tiles in case the cache is being shared + * 13/2/07 + * - relase ownership after fillng with pixels in case we read across + * threads + */ + +/* + + 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 + + */ + +/* Turn on debugging output. +#define DEBUG + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include +#include +#include + +#include +#include + +#ifdef WITH_DMALLOC +#include +#endif /*WITH_DMALLOC*/ + +/* Lower and upper bounds for tile cache size. Choose an exact number based on + * tile size. + */ +#define IM_MAX_TILE_CACHE (250) +#define IM_MIN_TILE_CACHE (5) + +/* A tile in our cache. + */ +typedef struct { + struct _Read *read; + + REGION *region; /* REGION with private mem for data */ + int time; /* Time of last use for flush */ + int x; /* xy pos in VIPS image cods */ + int y; +} Tile; + +/* Stuff we track during a read. + */ +typedef struct _Read { + /* Parameters. + */ + IMAGE *in; + IMAGE *out; + int tile_width; + int tile_height; + int max_tiles; + + /* Cache. + */ + int time; /* Update ticks for LRU here */ + int ntiles; /* Current cache size */ + GMutex *lock; /* Lock everything here */ + GSList *cache; /* List of tiles */ +} Read; + +static void +tile_destroy( Tile *tile ) +{ + Read *read = tile->read; + + read->cache = g_slist_remove( read->cache, tile ); + read->ntiles -= 1; + assert( read->ntiles >= 0 ); + tile->read = NULL; + + IM_FREEF( im_region_free, tile->region ); + + im_free( tile ); +} + +static void +read_destroy( Read *read ) +{ + IM_FREEF( g_mutex_free, read->lock ); + + while( read->cache ) { + Tile *tile = (Tile *) read->cache->data; + + tile_destroy( tile ); + } + + im_free( read ); +} + +static Read * +read_new( IMAGE *in, IMAGE *out, + int tile_width, int tile_height, int max_tiles ) +{ + Read *read; + + if( !(read = IM_NEW( NULL, Read )) ) + return( NULL ); + read->in = in; + read->out = out; + read->tile_width = tile_width; + read->tile_height = tile_height; + read->max_tiles = max_tiles; + read->time = 0; + read->ntiles = 0; + read->lock = g_mutex_new(); + read->cache = NULL; + + if( im_add_close_callback( out, + (im_callback_fn) read_destroy, read, NULL ) ) { + read_destroy( read ); + return( NULL ); + } + + return( read ); +} + +static Tile * +tile_new( Read *read ) +{ + Tile *tile; + + if( !(tile = IM_NEW( NULL, Tile )) ) + return( NULL ); + + tile->read = read; + tile->region = NULL; + tile->time = read->time; + tile->x = -1; + tile->y = -1; + read->cache = g_slist_prepend( read->cache, tile ); + assert( read->ntiles >= 0 ); + read->ntiles += 1; + + if( !(tile->region = im_region_create( read->in )) ) { + tile_destroy( tile ); + return( NULL ); + } + + return( tile ); +} + +/* Do we have a tile in the cache? + */ +static Tile * +tile_search( Read *read, int x, int y ) +{ + GSList *p; + + for( p = read->cache; p; p = p->next ) { + Tile *tile = (Tile *) p->data; + + if( tile->x == x && tile->y == y ) + return( tile ); + } + + return( NULL ); +} + +static void +tile_touch( Tile *tile ) +{ + assert( tile->read->ntiles >= 0 ); + + tile->time = tile->read->time++; +} + +/* Fill a tile with pixels. + */ +static int +tile_fill( Tile *tile, int x, int y ) +{ + Rect area; + + tile->x = x; + tile->y = y; + +#ifdef DEBUG + printf( "im_tile_cache: filling tile %d x %d\n", tile->x, tile->y ); +#endif /*DEBUG*/ + + area.left = x; + area.top = y; + area.width = tile->read->tile_width; + area.height = tile->read->tile_height; + if( im_prepare( tile->region, &area ) ) + return( -1 ); + + /* Make sure these pixels aren't part of this thread's buffer cache + * ... they may be read out by another thread. + */ + im__region_no_ownership( tile->region ); + + tile_touch( tile ); + + return( 0 ); +} + +/* Find existing tile, make a new tile, or if we have a full set of tiles, + * reuse LRU. + */ +static Tile * +tile_find( Read *read, int x, int y ) +{ + Tile *tile; + int oldest; + GSList *p; + + /* In cache already? + */ + if( (tile = tile_search( read, x, y )) ) { + tile_touch( tile ); + + return( tile ); + } + + /* Cache not full? + */ + if( read->max_tiles == -1 || + read->ntiles < read->max_tiles ) { + if( !(tile = tile_new( read )) || + tile_fill( tile, x, y ) ) + return( NULL ); + + return( tile ); + } + + /* Reuse an old one. + */ + oldest = read->time; + tile = NULL; + for( p = read->cache; p; p = p->next ) { + Tile *t = (Tile *) p->data; + + if( t->time < oldest ) { + oldest = t->time; + tile = t; + } + } + + assert( tile ); + + /* The tile may have been created by another thread if we are sharing + * the tile cache between several readers. Take ownership of the tile + * to stop assert() failures in im_prepare(). This is safe, since we + * are in a mutex. + */ + im__region_take_ownership( tile->region ); + +#ifdef DEBUG + printf( "im_tile_cache: reusing tile %d x %d\n", tile->x, tile->y ); +#endif /*DEBUG*/ + + if( tile_fill( tile, x, y ) ) + return( NULL ); + + return( tile ); +} + +/* Copy rect from from to to. + */ +static void +copy_region( REGION *from, REGION *to, Rect *area ) +{ + int y; + + /* Area should be inside both from and to. + */ + assert( im_rect_includesrect( &from->valid, area ) ); + assert( im_rect_includesrect( &to->valid, area ) ); + + /* Loop down common area, copying. + */ + for( y = area->top; y < IM_RECT_BOTTOM( area ); y++ ) { + PEL *p = (PEL *) IM_REGION_ADDR( from, area->left, y ); + PEL *q = (PEL *) IM_REGION_ADDR( to, area->left, y ); + + memcpy( q, p, IM_IMAGE_SIZEOF_PEL( from->im ) * area->width ); + } +} + +/* Loop over the output region, filling with data from cache. + */ +static int +fill_region( REGION *out, void *seq, void *a, void *b ) +{ + Read *read = (Read *) a; + const int tw = read->tile_width; + const int th = read->tile_height; + Rect *r = &out->valid; + + /* Find top left of tiles we need. + */ + int xs = (r->left / tw) * tw; + int ys = (r->top / th) * th; + + int x, y; + + g_mutex_lock( read->lock ); + + for( y = ys; y < IM_RECT_BOTTOM( r ); y += th ) + for( x = xs; x < IM_RECT_RIGHT( r ); x += tw ) { + Tile *tile; + Rect tarea; + Rect hit; + + if( !(tile = tile_find( read, x, y )) ) { + g_mutex_unlock( read->lock ); + return( -1 ); + } + + /* The area of the tile. + */ + tarea.left = x; + tarea.top = y; + tarea.width = tw; + tarea.height = th; + + /* The part of the tile that we need. + */ + im_rect_intersectrect( &tarea, r, &hit ); + + copy_region( tile->region, out, &hit ); + } + + g_mutex_unlock( read->lock ); + + return( 0 ); +} + +int +im_tile_cache( IMAGE *in, IMAGE *out, + int tile_width, int tile_height, int max_tiles ) +{ + Read *read; + + if( tile_width <= 0 || tile_height <= 0 || max_tiles < -1 ) { + im_error( "im_tile_cache", _( "bad parameters" ) ); + return( -1 ); + } + if( im_piocheck( in, out ) ) + return( -1 ); + if( im_cp_desc( out, in ) ) + return( -1 ); + if( im_demand_hint( out, IM_SMALLTILE, in, NULL ) ) + return( -1 ); + + if( !(read = read_new( in, out, + tile_width, tile_height, max_tiles )) ) + return( -1 ); + if( im_generate( out, + NULL, fill_region, NULL, read, NULL ) ) + return( -1 ); + + return( 0 ); +} diff --git a/libsrc/conversion/im_vips2csv.c b/libsrc/conversion/im_vips2csv.c new file mode 100644 index 00000000..6d19f9dd --- /dev/null +++ b/libsrc/conversion/im_vips2csv.c @@ -0,0 +1,146 @@ +/* Write a csv file. + * + * 9/6/06 + * - hacked from im_debugim + * 23/10/06 + * - allow separator to be specified (default "\t", ) + * 17/11/06 + * - oops, was broken + */ + +/* + + 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 + + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include + +#include + +#ifdef WITH_DMALLOC +#include +#endif /*WITH_DMALLOC*/ + +#define PRINT_INT( TYPE ) fprintf( fp, "%d", *((TYPE*)p) ); +#define PRINT_FLOAT( TYPE ) fprintf( fp, "%g", *((TYPE*)p) ); +#define PRINT_COMPLEX( TYPE ) fprintf( fp, "(%g, %g)", \ + ((TYPE*)p)[0], ((TYPE*)p)[1] ); + +static int +vips2csv( IMAGE *in, FILE *fp, const char *sep ) +{ + int w = IM_IMAGE_N_ELEMENTS( in ); + int es = IM_IMAGE_SIZEOF_ELEMENT( in ); + + int x, y; + PEL *p; + + p = (PEL *) in->data; + for( y = 0; y < in->Ysize; y++ ) { + for( x = 0; x < w; x++ ) { + if( x > 0 ) + fprintf( fp, "%s", sep ); + + switch( in->BandFmt ) { + case IM_BANDFMT_UCHAR: + PRINT_INT( unsigned char ); break; + case IM_BANDFMT_CHAR: + PRINT_INT( char ); break; + case IM_BANDFMT_USHORT: + PRINT_INT( unsigned short ); break; + case IM_BANDFMT_SHORT: + PRINT_INT( short ); break; + case IM_BANDFMT_UINT: + PRINT_INT( unsigned int ); break; + case IM_BANDFMT_INT: + PRINT_INT( int ); break; + case IM_BANDFMT_FLOAT: + PRINT_FLOAT( float ); break; + case IM_BANDFMT_DOUBLE: + PRINT_FLOAT( double ); break; + case IM_BANDFMT_COMPLEX: + PRINT_COMPLEX( float ); break; + case IM_BANDFMT_DPCOMPLEX: + PRINT_COMPLEX( double ); break; + + default: + assert( 0 ); + } + + p += es; + } + + fprintf( fp, "\n" ); + } + + return( 0 ); +} + +int +im_vips2csv( IMAGE *in, const char *filename ) +{ + char *separator = "\t"; + + char name[FILENAME_MAX]; + char mode[FILENAME_MAX]; + FILE *fp; + char *p, *q, *r; + + /* Parse mode string. + */ + im_filename_split( filename, name, mode ); + p = &mode[0]; + while( (q = im_getnextoption( &p )) ) { + if( im_isprefix( "sep", q ) && (r = im_getsuboption( q )) ) + separator = r; + } + + if( im_incheck( in ) ) + return( -1 ); + if( in->Coding != IM_CODING_NONE ) { + im_error( "im_vips2csv", _( "input must be uncoded" ) ); + return( -1 ); + } + + if( !(fp = fopen( name, "w" )) ) { + im_error( "im_cvips2csv", _( "unable to open \"%s\"" ), + name ); + return( -1 ); + } + + if( vips2csv( in, fp, separator ) ) { + fclose( fp ); + return( -1 ); + } + + fclose( fp ); + + return( 0 ); +} diff --git a/libsrc/conversion/im_vips2jpeg.c b/libsrc/conversion/im_vips2jpeg.c new file mode 100644 index 00000000..2f08d635 --- /dev/null +++ b/libsrc/conversion/im_vips2jpeg.c @@ -0,0 +1,896 @@ +/* Convert 8-bit VIPS images to/from JPEG. + * + * 28/11/03 JC + * - better no-overshoot on tile loop + * 12/11/04 + * - better demand size choice for eval + * 30/6/05 JC + * - update im_error()/im_warn() + * - now loads and saves exif data + * 30/7/05 + * - now loads ICC profiles + * - now saves ICC profiles from the VIPS header + * 24/8/05 + * - jpeg load sets vips xres/yres from exif, if possible + * - jpeg save sets exif xres/yres from vips, if possible + * 29/8/05 + * - cut from old vips_jpeg.c + * 20/4/06 + * - auto convert to sRGB/mono for save + * 13/10/06 + * - add +#endif /*HAVE_CONFIG_H*/ +#include + +#ifndef HAVE_JPEG + +#include + +int +im_vips2jpeg( IMAGE *in, const char *filename ) +{ + im_error( "im_vips2jpeg", _( "JPEG support disabled" ) ); + + return( -1 ); +} + +int +im_vips2bufjpeg( IMAGE *in, IMAGE *out, int qfac, char **obuf, int *olen ) +{ + im_error( "im_vips2bufjpeg", _( "JPEG support disabled" ) ); + + return( -1 ); +} + +int +im_vips2mimejpeg( IMAGE *in, int qfac ) +{ + im_error( "im_vips2mimejpeg", _( "JPEG support disabled" ) ); + + return( -1 ); +} + +#else /*HAVE_JPEG*/ + +#include +#include +#include +#include +#include + +#ifdef HAVE_EXIF +#ifdef UNTAGGED_EXIF +#include +#include +#include +#include +#else /*!UNTAGGED_EXIF*/ +#include +#include +#include +#include +#endif /*UNTAGGED_EXIF*/ +#endif /*HAVE_EXIF*/ + +#include +#include +#include + +/* jpeglib includes jconfig.h, which can define HAVE_STDLIB_H ... which we + * also define. Make sure it's turned off. + */ +#ifdef HAVE_STDLIB_H +#undef HAVE_STDLIB_H +#endif /*HAVE_STDLIB_H*/ + +#include +#include + +#ifdef WITH_DMALLOC +#include +#endif /*WITH_DMALLOC*/ + +/* Define a new error handler for when we bomb out. + */ +typedef struct { + /* Public fields. + */ + struct jpeg_error_mgr pub; + + /* Private stuff for us. + */ + jmp_buf jmp; /* longjmp() here to get back to VIPS */ + FILE *fp; /* fclose() if non-NULL */ +} ErrorManager; + +/* New output message method - send to VIPS. + */ +METHODDEF(void) +new_output_message( j_common_ptr cinfo ) +{ + char buffer[JMSG_LENGTH_MAX]; + + (*cinfo->err->format_message)( cinfo, buffer ); + im_error( "vips_jpeg", _( "%s" ), buffer ); + +#ifdef DEBUG + printf( "vips_jpeg.c: new_output_message: \"%s\"\n", buffer ); +#endif /*DEBUG*/ +} + +/* New error_exit handler. + */ +METHODDEF(void) +new_error_exit( j_common_ptr cinfo ) +{ + ErrorManager *eman = (ErrorManager *) cinfo->err; + +#ifdef DEBUG + printf( "vips_jpeg.c: new_error_exit\n" ); +#endif /*DEBUG*/ + + /* Close the fp if necessary. + */ + if( eman->fp ) { + (void) fclose( eman->fp ); + eman->fp = NULL; + } + + /* Send the error message to VIPS. This method is overridden above. + */ + (*cinfo->err->output_message)( cinfo ); + + /* Jump back. + */ + longjmp( eman->jmp, 1 ); +} + +/* What we track during a JPEG write. + */ +typedef struct { + IMAGE *in; + struct jpeg_compress_struct cinfo; + ErrorManager eman; + im_threadgroup_t *tg; + JSAMPROW *row_pointer; + char *profile_bytes; + unsigned int profile_length; + 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", + _( "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", + _( "error setting JPEG resolution" ) ); + return( -1 ); + } + + return( 0 ); +} +#endif /*HAVE_EXIF*/ + +static int +write_exif( Write *write ) +{ + unsigned char *data; + size_t data_length; + unsigned int idl; +#ifdef HAVE_EXIF + ExifData *ed; + + /* Either parse from the embedded EXIF, or if there's none, make + * some fresh EXIF we can write the resolution to. + */ + if( im_header_get_type( write->in, IM_META_EXIF_NAME ) ) { + if( im_meta_get_blob( write->in, IM_META_EXIF_NAME, + (void *) &data, &data_length ) ) + return( -1 ); + + if( !(ed = exif_data_new_from_data( data, data_length )) ) + return( -1 ); + } + else + ed = exif_data_new(); + + /* Set EXIF resolution from VIPS. + */ + if( set_exif_resolution( ed, write->in ) ) { + exif_data_free( ed ); + return( -1 ); + } + + /* Reserialise and write. exif_data_save_data() returns an int for some + * reason. + */ + exif_data_save_data( ed, &data, &idl ); + if( !idl ) { + im_error( "im_jpeg2vips", _( "error saving EXIF" ) ); + exif_data_free( ed ); + return( -1 ); + } + data_length = idl; + +#ifdef DEBUG + printf( "im_vips2jpeg: attaching %d bytes of EXIF\n", data_length ); +#endif /*DEBUG*/ + + exif_data_free( ed ); + jpeg_write_marker( &write->cinfo, JPEG_APP0 + 1, data, data_length ); + free( data ); +#else /*!HAVE_EXIF*/ + /* No libexif ... just copy the embedded EXIF over. + */ + if( im_header_get_type( write->in, IM_META_EXIF_NAME ) ) { + if( im_meta_get_blob( write->in, IM_META_EXIF_NAME, + (void *) &data, &data_length ) ) + return( -1 ); + +#ifdef DEBUG + printf( "im_vips2jpeg: attaching %d bytes of EXIF\n", + data_length ); +#endif /*DEBUG*/ + + jpeg_write_marker( &write->cinfo, JPEG_APP0 + 1, + data, data_length ); + } +#endif /*!HAVE_EXIF*/ + + return( 0 ); +} + +/* ICC writer from lcms, slight tweaks. + */ + +#define ICC_MARKER (JPEG_APP0 + 2) /* JPEG marker code for ICC */ +#define ICC_OVERHEAD_LEN 14 /* size of non-profile data in APP2 */ +#define MAX_BYTES_IN_MARKER 65533 /* maximum data len of a JPEG marker */ +#define MAX_DATA_BYTES_IN_MARKER (MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN) + +/* + * This routine writes the given ICC profile data into a JPEG file. + * It *must* be called AFTER calling jpeg_start_compress() and BEFORE + * the first call to jpeg_write_scanlines(). + * (This ordering ensures that the APP2 marker(s) will appear after the + * SOI and JFIF or Adobe markers, but before all else.) + */ + +static void +write_profile_data (j_compress_ptr cinfo, + const JOCTET *icc_data_ptr, + unsigned int icc_data_len) +{ + unsigned int num_markers; /* total number of markers we'll write */ + int cur_marker = 1; /* per spec, counting starts at 1 */ + unsigned int length; /* number of bytes to write in this marker */ + + /* rounding up will fail for length == 0 */ + assert( icc_data_len > 0 ); + + /* Calculate the number of markers we'll need, rounding up of course */ + num_markers = (icc_data_len + MAX_DATA_BYTES_IN_MARKER - 1) / + MAX_DATA_BYTES_IN_MARKER; + + while (icc_data_len > 0) { + /* length of profile to put in this marker */ + length = icc_data_len; + if (length > MAX_DATA_BYTES_IN_MARKER) + length = MAX_DATA_BYTES_IN_MARKER; + icc_data_len -= length; + + /* Write the JPEG marker header (APP2 code and marker length) */ + jpeg_write_m_header(cinfo, ICC_MARKER, + (unsigned int) (length + ICC_OVERHEAD_LEN)); + + /* Write the marker identifying string "ICC_PROFILE" (null-terminated). + * We code it in this less-than-transparent way so that the code works + * even if the local character set is not ASCII. + */ + jpeg_write_m_byte(cinfo, 0x49); + jpeg_write_m_byte(cinfo, 0x43); + jpeg_write_m_byte(cinfo, 0x43); + jpeg_write_m_byte(cinfo, 0x5F); + jpeg_write_m_byte(cinfo, 0x50); + jpeg_write_m_byte(cinfo, 0x52); + jpeg_write_m_byte(cinfo, 0x4F); + jpeg_write_m_byte(cinfo, 0x46); + jpeg_write_m_byte(cinfo, 0x49); + jpeg_write_m_byte(cinfo, 0x4C); + jpeg_write_m_byte(cinfo, 0x45); + jpeg_write_m_byte(cinfo, 0x0); + + /* Add the sequencing info */ + jpeg_write_m_byte(cinfo, cur_marker); + jpeg_write_m_byte(cinfo, (int) num_markers); + + /* Add the profile data */ + while (length--) { + jpeg_write_m_byte(cinfo, *icc_data_ptr); + icc_data_ptr++; + } + cur_marker++; + } +} + +/* Write an ICC Profile from a file into the JPEG stream. + */ +static int +write_profile_file( Write *write, const char *profile ) +{ + if( !(write->profile_bytes = + im__file_read_name( profile, &write->profile_length )) ) + return( -1 ); + write_profile_data( &write->cinfo, + (JOCTET *) write->profile_bytes, write->profile_length ); + +#ifdef DEBUG + printf( "im_vips2jpeg: attached profile \"%s\"\n", profile ); +#endif /*DEBUG*/ + + return( 0 ); +} + +static int +write_profile_meta( Write *write ) +{ + void *data; + size_t data_length; + + if( im_meta_get_blob( write->in, IM_META_ICC_NAME, + &data, &data_length ) ) + return( -1 ); + + write_profile_data( &write->cinfo, data, data_length ); + +#ifdef DEBUG + printf( "im_vips2jpeg: attached %d byte profile from VIPS header\n", + data_length ); +#endif /*DEBUG*/ + + return( 0 ); +} + +static int +write_jpeg_block( REGION *region, Rect *area, void *a, void *b ) +{ + Write *write = (Write *) a; + int i; + + /* We are running in a background thread. We need to catch longjmp()s + * here instead. + */ + if( setjmp( write->eman.jmp ) ) + return( -1 ); + + for( i = 0; i < area->height; i++ ) + write->row_pointer[i] = (JSAMPROW) + IM_REGION_ADDR( region, 0, area->top + i ); + + jpeg_write_scanlines( &write->cinfo, write->row_pointer, area->height ); + + return( 0 ); +} + +/* Write a VIPS image to a JPEG compress struct. + */ +static int +write_vips( Write *write, int qfac, const char *profile ) +{ + IMAGE *in; + 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", _( "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. + */ +#ifdef BINARY_OPEN + if( !(write->eman.fp = fopen( name, "wb" )) ) { +#else /*BINARY_OPEN*/ + if( !(write->eman.fp = fopen( name, "w" )) ) { +#endif /*BINARY_OPEN*/ + write_destroy( write ); + im_error( "im_vips2jpeg", + _( "unable to open \"%s\"" ), name ); + + return( -1 ); + } + jpeg_stdio_dest( &write->cinfo, write->eman.fp ); + + /* Convert! + */ + if( write_vips( write, qfac, profile ) ) { + write_destroy( write ); + return( -1 ); + } + write_destroy( write ); + + return( 0 ); +} + +/* Just like the above, but we write to a memory buffer. + * + * A memory buffer for the compressed image. + */ +typedef struct { + /* Public jpeg fields. + */ + struct jpeg_destination_mgr pub; + + /* Private stuff during write. + */ + JOCTET *data; /* Allocated area */ + int used; /* Number of bytes written so far */ + int size; /* Max size */ + + /* Copy the compressed area here. + */ + IMAGE *out; /* Allocate relative to this */ + char **obuf; /* Allocated buffer, and size */ + int *olen; +} OutputBuffer; + +/* Init dest method. + */ +METHODDEF(void) +init_destination( j_compress_ptr cinfo ) +{ + OutputBuffer *buf = (OutputBuffer *) cinfo->dest; + int mx = cinfo->image_width * cinfo->image_height * + cinfo->input_components * sizeof( JOCTET ); + + /* Allocate relative to the image we are writing .. freed when we junk + * this output. + */ + buf->data = (JOCTET *) (*cinfo->mem->alloc_large) + ( (j_common_ptr) cinfo, JPOOL_IMAGE, mx ); + buf->used = 0; + buf->size = mx; + + /* Set buf pointers for library. + */ + buf->pub.next_output_byte = buf->data; + buf->pub.free_in_buffer = mx; +} + +/* Buffer full method ... should never get this. + */ +METHODDEF(boolean) +empty_output_buffer( j_compress_ptr cinfo ) +{ + /* Not really a file write error, but why not. Should never happen. + */ + ERREXIT( cinfo, JERR_FILE_WRITE ); + + return( 0 ); +} + +/* Cleanup. Write entire buffer as a MIME type. + */ +METHODDEF(void) +term_destination( j_compress_ptr cinfo ) +{ + OutputBuffer *buf = (OutputBuffer *) cinfo->dest; + int len = buf->size - buf->pub.free_in_buffer; + void *obuf; + + /* Allocate and copy to the VIPS output area. + */ + if( !(obuf = im_malloc( buf->out, len )) ) + ERREXIT( cinfo, JERR_FILE_WRITE ); + memcpy( obuf, buf->data, len ); + *(buf->obuf) = obuf; + *(buf->olen) = len; +} + +/* Set dest to one of our objects. + */ +static void +buf_dest( j_compress_ptr cinfo, IMAGE *out, char **obuf, int *olen ) +{ + OutputBuffer *buf; + + /* The destination object is made permanent so that multiple JPEG + * images can be written to the same file without re-executing + * jpeg_stdio_dest. This makes it dangerous to use this manager and + * a different destination manager serially with the same JPEG object, + * because their private object sizes may be different. + * + * Caveat programmer. + */ + if( !cinfo->dest ) { /* first time for this JPEG object? */ + cinfo->dest = (struct jpeg_destination_mgr *) + (*cinfo->mem->alloc_small) + ( (j_common_ptr) cinfo, JPOOL_PERMANENT, + sizeof( OutputBuffer ) ); + } + + buf = (OutputBuffer *) cinfo->dest; + buf->pub.init_destination = init_destination; + buf->pub.empty_output_buffer = empty_output_buffer; + buf->pub.term_destination = term_destination; + + /* Save output parameters. + */ + buf->out = out; + buf->obuf = obuf; + buf->olen = olen; +} + +/* As above, but save to a buffer. The buffer is allocated relative to out. + * On success, buf is set to the output buffer and len to the size of the + * compressed image. + */ +int +im_vips2bufjpeg( IMAGE *in, IMAGE *out, int qfac, char **obuf, int *olen ) +{ + Write *write; + + if( !(write = write_new( in )) ) + return( -1 ); + + /* Clear output parameters. + */ + *obuf = NULL; + *olen = 0; + + /* Make jpeg compression object. + */ + if( setjmp( write->eman.jmp ) ) { + /* Here for longjmp() from new_error_exit(). + */ + write_destroy( write ); + + return( -1 ); + } + jpeg_create_compress( &write->cinfo ); + + /* Attach output. + */ + buf_dest( &write->cinfo, out, obuf, olen ); + + /* Convert! + */ + if( write_vips( write, qfac, NULL ) ) { + write_destroy( write ); + + return( -1 ); + } + write_destroy( write ); + + return( 0 ); +} + +/* As above, but save as a mime jpeg on stdout. + */ +int +im_vips2mimejpeg( IMAGE *in, int qfac ) +{ + IMAGE *base; + int len; + char *buf; + + if( !(base = im_open( "im_vips2mimejpeg:1", "p" )) ) + return( -1 ); + if( im_vips2bufjpeg( in, base, qfac, &buf, &len ) ) { + im_close( base ); + return( -1 ); + } + + /* Write as a MIME type. + */ + printf( "Content-length: %d\r\n", len ); + printf( "Content-type: image/jpeg\r\n" ); + printf( "\r\n" ); + fwrite( buf, sizeof( char ), len, stdout ); + fflush( stdout ); + + im_close( base ); + + if( ferror( stdout ) ) { + im_error( "im_vips2mimejpeg", _( "error writing output" ) ); + return( -1 ); + } + + return( 0 ); +} + +#endif /*HAVE_JPEG*/ diff --git a/libsrc/conversion/im_vips2png.c b/libsrc/conversion/im_vips2png.c new file mode 100644 index 00000000..f66803c5 --- /dev/null +++ b/libsrc/conversion/im_vips2png.c @@ -0,0 +1,320 @@ +/* Convert 1 to 4-band 8 or 16-bit VIPS images to/from PNG. + * + * 28/11/03 JC + * - better no-overshoot on tile loop + * 22/2/05 + * - read non-interlaced PNG with a line buffer (thanks Michel Brabants) + * 11/1/06 + * - read RGBA palette-ized images more robustly (thanks Tom) + * 20/4/06 + * - auto convert to sRGB/mono (with optional alpha) for save + * 1/5/06 + * - from vips_png.c + * 2/11/07 + * - use im_wbuffer() API for BG writes + */ + +/* + + 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 + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#ifndef HAVE_PNG + +#include + +int +im_vips2png( IMAGE *in, const char *filename ) +{ + im_error( "im_vips2png", _( "PNG support disabled" ) ); + return( -1 ); +} + +#else /*HAVE_PNG*/ + +#include +#include +#include + +#include +#include + +#include + +#ifdef WITH_DMALLOC +#include +#endif /*WITH_DMALLOC*/ + +#if PNG_LIBPNG_VER < 10003 +#error "PNG library too old." +#endif + +static void +user_error_function( png_structp png_ptr, png_const_charp error_msg ) +{ + im_error( "im_vips2png", _( "PNG error: \"%s\"" ), error_msg ); +} + +static void +user_warning_function( png_structp png_ptr, png_const_charp warning_msg ) +{ + im_error( "im_vips2png", _( "PNG warning: \"%s\"" ), warning_msg ); +} + +/* What we track during a PNG write. + */ +typedef struct { + IMAGE *in; + im_threadgroup_t *tg; + + FILE *fp; + png_structp pPng; + png_infop pInfo; + png_bytep *row_pointer; +} Write; + +static void +write_destroy( Write *write ) +{ + IM_FREEF( im_threadgroup_free, write->tg ); + IM_FREEF( im_close, write->in ); + IM_FREEF( fclose, write->fp ); + if( write->pPng ) + png_destroy_write_struct( &write->pPng, &write->pInfo ); + IM_FREE( write->row_pointer ); + + 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__RGBA )) ) { + im_error( "im_vips2png", + _( "unable to convert to RGB for save" ) ); + write_destroy( write ); + return( NULL ); + } + + write->tg = im_threadgroup_create( write->in ); + write->row_pointer = IM_ARRAY( NULL, write->tg->nlines, png_bytep ); + write->fp = NULL; + write->pPng = NULL; + write->pInfo = NULL; + + if( !write->tg || !write->row_pointer ) { + write_destroy( write ); + return( NULL ); + } + + if( !(write->pPng = png_create_write_struct( + PNG_LIBPNG_VER_STRING, NULL, + user_error_function, user_warning_function )) ) { + write_destroy( write ); + return( NULL ); + } + + /* Catch PNG errors from png_create_info_struct(). + */ + if( setjmp( write->pPng->jmpbuf ) ) { + write_destroy( write ); + return( NULL ); + } + + if( !(write->pInfo = png_create_info_struct( write->pPng )) ) { + write_destroy( write ); + return( NULL ); + } + + return( write ); +} + +static int +write_png_block( REGION *region, Rect *area, void *a, void *b ) +{ + Write *write = (Write *) a; + int i; + + /* Catch PNG errors. Yuk. + */ + if( setjmp( write->pPng->jmpbuf ) ) + return( -1 ); + + for( i = 0; i < area->height; i++ ) + write->row_pointer[i] = (png_bytep) + IM_REGION_ADDR( region, 0, area->top + i ); + + png_write_rows( write->pPng, write->row_pointer, area->height ); + + return( 0 ); +} + +/* Write a VIPS image to PNG. + */ +static int +write_vips( Write *write, int compress, int interlace ) +{ + IMAGE *in = write->in; + + int i, nb_passes; + + assert( in->BandFmt == IM_BANDFMT_UCHAR ); + assert( in->Coding == IM_CODING_NONE ); + assert( in->Bands > 0 && in->Bands < 5 ); + + /* Catch PNG errors. + */ + if( setjmp( write->pPng->jmpbuf ) ) + return( -1 ); + + /* Check input image. + */ + if( im_pincheck( in ) ) + return( -1 ); + if( compress < 0 || compress > 9 ) { + im_error( "im_vips2png", _( "compress should be in [0,9]" ) ); + return( -1 ); + } + + /* Set compression parameters. + */ + png_set_compression_level( write->pPng, compress ); + + write->pInfo->width = in->Xsize; + write->pInfo->height = in->Ysize; + write->pInfo->bit_depth = (in->BandFmt == IM_BANDFMT_UCHAR ? 8 : 16); + write->pInfo->gamma = (float) 1.0; + + switch( in->Bands ) { + case 1: write->pInfo->color_type = PNG_COLOR_TYPE_GRAY; break; + case 2: write->pInfo->color_type = PNG_COLOR_TYPE_GRAY_ALPHA; break; + case 3: write->pInfo->color_type = PNG_COLOR_TYPE_RGB; break; + case 4: write->pInfo->color_type = PNG_COLOR_TYPE_RGB_ALPHA; break; + + default: + assert( 0 ); + } + + png_write_info( write->pPng, write->pInfo ); + + /* If we're an intel byte order CPU and this is a 16bit image, we need + * to swap bytes. + */ + if( write->pInfo->bit_depth > 8 && !im_amiMSBfirst() ) + png_set_swap( write->pPng ); + + if( interlace ) + nb_passes = png_set_interlace_handling( write->pPng ); + else + nb_passes = 1; + + /* Write data. + */ + for( i = 0; i < nb_passes; i++ ) + if( im_wbuffer( write->tg, write_png_block, write, NULL ) ) + return( -1 ); + + /* The setjmp() was held by our background writer: reset it. + */ + if( setjmp( write->pPng->jmpbuf ) ) + return( -1 ); + + png_write_end( write->pPng, write->pInfo ); + + return( 0 ); +} + +/* Write a VIPS image to a file as PNG. + */ +int +im_vips2png( IMAGE *in, const char *filename ) +{ + Write *write; + int compress; + int interlace; + + char *p, *q; + + char name[FILENAME_MAX]; + char mode[FILENAME_MAX]; + char buf[FILENAME_MAX]; + + if( !(write = write_new( in )) ) + return( -1 ); + + /* Extract write mode from filename and parse. + */ + im_filename_split( filename, name, mode ); + strcpy( buf, mode ); + p = &buf[0]; + compress = 6; + interlace = 0; + if( (q = im_getnextoption( &p )) ) + compress = atoi( q ); + if( (q = im_getnextoption( &p )) ) + interlace = atoi( q ); + + /* Make output. + */ +#ifdef BINARY_OPEN + if( !(write->fp = fopen( name, "wb" )) ) { +#else /*BINARY_OPEN*/ + if( !(write->fp = fopen( name, "w" )) ) { +#endif /*BINARY_OPEN*/ + write_destroy( write ); + im_error( "im_vips2png", _( "unable to open \"%s\"" ), name ); + + return( -1 ); + } + png_init_io( write->pPng, write->fp ); + + /* Convert it! + */ + if( write_vips( write, compress, interlace ) ) { + write_destroy( write ); + im_error( "im_vips2png", _( "unable to write \"%s\"" ), name ); + + return( -1 ); + } + write_destroy( write ); + + return( 0 ); +} + +#endif /*HAVE_PNG*/ diff --git a/libsrc/conversion/im_vips2ppm.c b/libsrc/conversion/im_vips2ppm.c new file mode 100644 index 00000000..073a8d9f --- /dev/null +++ b/libsrc/conversion/im_vips2ppm.c @@ -0,0 +1,293 @@ +/* Write a ppm file. + * + * 28/11/03 JC + * - better no-overshoot on tile loop + * 9/9/05 + * - tiny cleanups + * 3/11/07 + * - use im_wbuffer() for bg writes + */ + +/* + + 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 + + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef WITH_DMALLOC +#include +#endif /*WITH_DMALLOC*/ + +/* What we track during a PPM write. + */ +typedef struct { + IMAGE *in; + im_threadgroup_t *tg; + FILE *fp; + char *name; +} Write; + +static void +write_destroy( Write *write ) +{ + IM_FREEF( im_threadgroup_free, write->tg ); + IM_FREEF( fclose, write->fp ); + IM_FREE( write->name ); + + im_free( write ); +} + +static Write * +write_new( IMAGE *in, const char *name ) +{ + Write *write; + + if( !(write = IM_NEW( NULL, Write )) ) + return( NULL ); + + write->in = in; + write->tg = im_threadgroup_create( write->in ); + write->name = im_strdup( NULL, name ); + +#ifdef BINARY_OPEN + if( !(write->fp = fopen( name, "wb" )) ) { +#else /*BINARY_OPEN*/ + if( !(write->fp = fopen( name, "w" )) ) { +#endif /*BINARY_OPEN*/ + im_error( "im_vips2ppm", + _( "unable to open \"%s\" for output" ), name ); + } + + if( !write->tg || !write->name || !write->fp ) { + write_destroy( write ); + return( NULL ); + } + + return( write ); +} + +typedef int (*write_fn)( IMAGE *in, FILE *fp, PEL *p ); + +static int +write_ppm_line_ascii( IMAGE *in, FILE *fp, PEL *p ) +{ + const int sk = IM_IMAGE_SIZEOF_PEL( in ); + const int nb = IM_MIN( 3, in->Bands ); + int x, k; + + /* If IM_CODING_LABQ, write 3 bands. + */ + + for( x = 0; x < in->Xsize; x++ ) { + for( k = 0; k < nb; k++ ) { + switch( in->BandFmt ) { + case IM_BANDFMT_UCHAR: + fprintf( fp, "%d ", p[k] ); + break; + + case IM_BANDFMT_USHORT: + fprintf( fp, "%d ", ((unsigned short *) p)[k] ); + break; + + case IM_BANDFMT_UINT: + fprintf( fp, "%d ", ((unsigned int *) p)[k] ); + break; + + default: + assert( 0 ); + } + } + + fprintf( fp, " " ); + + p += sk; + } + + if( !fprintf( fp, "\n" ) ) { + im_error( "im_vips2ppm", _( "write error ... disc full?" ) ); + return( -1 ); + } + + return( 0 ); +} + +static int +write_ppm_line_binary( IMAGE *in, FILE *fp, PEL *p ) +{ + const int sk = IM_IMAGE_SIZEOF_PEL( in ); + const int nb = IM_MIN( 3, in->Bands ); + int x; + + for( x = 0; x < in->Xsize; x++ ) { + if( !fwrite( p, 1, nb, fp ) ) { + im_error( "im_vips2ppm", + _( "write error ... disc full?" ) ); + return( -1 ); + } + + p += sk; + } + + return( 0 ); +} + +static int +write_ppm_block( REGION *region, Rect *area, void *a, void *b ) +{ + Write *write = (Write *) a; + write_fn fn = (write_fn) b; + int i; + + for( i = 0; i < area->height; i++ ) { + PEL *p = (PEL *) IM_REGION_ADDR( region, 0, area->top + i ); + + if( fn( write->in, write->fp, p ) ) + return( -1 ); + } + + return( 0 ); +} + +static int +write_ppm( Write *write, int ascii ) +{ + IMAGE *in = write->in; + write_fn fn = ascii ? write_ppm_line_ascii : write_ppm_line_binary; + + int max_value; + char *magic; + time_t timebuf; + + switch( in->BandFmt ) { + case IM_BANDFMT_UCHAR: + max_value = UCHAR_MAX; + break; + + case IM_BANDFMT_USHORT: + max_value = USHRT_MAX; + break; + + case IM_BANDFMT_UINT: + max_value = UINT_MAX; + break; + + default: + assert( 0 ); + } + + if( in->Bands == 1 && ascii ) + magic = "P2"; + else if( in->Bands == 1 && !ascii ) + magic = "P5"; + else if( in->Bands == 3 && ascii ) + magic = "P3"; + else if( in->Bands == 3 && !ascii ) + magic = "P6"; + else + assert( 0 ); + + fprintf( write->fp, "%s\n", magic ); + time( &timebuf ); + fprintf( write->fp, "#im_vips2ppm - %s\n", ctime( &timebuf ) ); + fprintf( write->fp, "%d %d\n", in->Xsize, in->Ysize ); + fprintf( write->fp, "%d\n", max_value ); + + if( im_wbuffer( write->tg, write_ppm_block, write, fn ) ) + return( -1 ); + + return( 0 ); +} + +int +im_vips2ppm( IMAGE *in, const char *filename ) +{ + Write *write; + int ascii; + char name[FILENAME_MAX]; + char mode[FILENAME_MAX]; + + /* Default to binary output ... much smaller. + */ + ascii = 0; + + /* Extract write mode from filename. + */ + im_filename_split( filename, name, mode ); + if( strcmp( mode, "" ) != 0 ) { + if( im_isprefix( "binary", mode ) ) + ascii = 0; + else if( im_isprefix( "ascii", mode ) ) + ascii = 1; + else { + im_error( "im_vips2ppm", + _( "bad mode string, " + "should be \"binary\" or \"ascii\"" ) ); + return( -1 ); + } + } + + if( in->Bbits > 8 && !ascii ) { + im_error( "im_vips2ppm", + _( "can't write binary >8 bit images" ) ); + return( -1 ); + } + if( !im_isuint( in ) ) { + im_error( "im_vips2ppm", _( "unsigned int formats only" ) ); + return( -1 ); + } + if( in->Coding != IM_CODING_NONE && in->Coding != IM_CODING_LABQ ) { + im_error( "im_vips2ppm", + _( "uncoded or IM_CODING_LABQ only" ) ); + return( -1 ); + } + if( in->Bands != 1 && in->Bands != 3 ) { + im_error( "im_vips2ppm", _( "1 or 3 band images only" ) ); + return( -1 ); + } + + if( im_pincheck( in ) || !(write = write_new( in, name )) ) + return( -1 ); + + if( write_ppm( write, ascii ) ) { + write_destroy( write ); + return( -1 ); + } + write_destroy( write ); + + return( 0 ); +} diff --git a/libsrc/conversion/im_vips2tiff.c b/libsrc/conversion/im_vips2tiff.c new file mode 100644 index 00000000..d13ae714 --- /dev/null +++ b/libsrc/conversion/im_vips2tiff.c @@ -0,0 +1,1653 @@ +/* TIFF PARTS: + * Copyright (c) 1988, 1990 by Sam Leffler. + * All rights reserved. + * + * This file is provided for unrestricted use provided that this + * legend is included on all tape media and as a part of the + * software program in whole or part. Users may copy, modify or + * distribute this file at will. + + MODIFICATION FOR VIPS Copyright 1991, K.Martinez + * software may be distributed FREE, with these copyright notices + * no responsibility/warantee is implied or given + * + * + * Modified and added im_LabQ2LabC() function. It can write IM_TYPE_LABQ image + * in vips format to LAB in tiff format. + * Copyright 1994 Ahmed Abbood. + * + * 19/9/95 JC + * - calls TIFFClose() more reliably + * - tidied up + * 12/4/97 JC + * - thrown away and rewritten for TIFF 6 lib + * 22/4/97 JC + * - writes a pyramid! + * - to separate TIFF files tho' + * 23/4/97 JC + * - does 2nd gather pass to put pyramid into a single TIFF file + * - ... and shrinks IM_CODING_LABQ too + * 26/10/98 JC + * - binary open for stupid systems + * 7/6/99 JC + * - 16bit TIFF write too + * 9/7/99 JC + * - ZIP tiff added + * 11/5/00 JC + * - removed TIFFmalloc/TIFFfree + * 5/8/00 JC + * - mode string now part of filename + * 23/4/01 JC + * - HAVE_TIFF turns on TIFFness + * 19/3/02 ruven + * - pyramid stops at tile size, not 64x64 + * 29/4/02 JC + * - write any number of bands (but still with photometric RGB, so not + * very useful) + * 10/9/02 JC + * - oops, handle TIFF errors better + * - now writes CMYK correctly + * 13/2/03 JC + * - tries not to write mad resolutions + * 7/5/03 JC + * - only write CMYK if Type == CMYK + * - writes EXTRASAMPLES ALPHA for bands == 2 or 4 (if we're writing RGB) + * 17/11/03 JC + * - write float too + * 28/11/03 JC + * - read via a "p" so we work from mmap window images + * - uses threadgroups for speedup + * 9/3/04 JC + * - 1 bit write mode added + * 5/4/04 + * - better handling of edge tiles (thanks Ruven) + * 18/5/04 Andrey Kiselev + * - added res_inch/res_cm option + * 20/5/04 JC + * - allow single res number too + * 19/7/04 + * - write several scanlines at once, good speed up for some cases + * 22/9/04 + * - got rid of wrapper image so nip gets progress feedback + * - fixed tiny read-beyond-buffer issue for edge tiles + * 7/10/04 + * - added ICC profile embedding + * 13/12/04 + * - can now pyramid any non-complex type (thanks Ruven) + * 27/1/05 + * - added ccittfax4 as a compression option + * 9/3/05 + * - set PHOTOMETRIC_CIELAB for vips TYPE_LAB images ... so we can write + * float LAB as well as float RGB + * - also LABS images + * 22/6/05 + * - 16 bit LAB write was broken + * 9/9/05 + * - write any icc profile from meta + * 3/3/06 + * - raise tile buffer limit (thanks Ruven) + * 11/11/06 + * - set ORIENTATION_TOPLEFT (thanks Josef) + * 18/7/07 Andrey Kiselev + * - remove "b" option on TIFFOpen() + * - support TIFFTAG_PREDICTOR types for lzw and deflate compression + * 3/11/07 + * - use im_wbuffer() for background writes + * 15/2/08 + * - set TIFFTAG_JPEGQUALITY explicitly when we copy TIFF files, since + * libtiff doesn't keep this in the header (thanks Joe) + * 20/2/08 + * - use tiff error handler from im_tiff2vips.c + * 27/2/08 + * - don't try to copy icc profiles when building pyramids (thanks Joe) + * 9/4/08 + * - use IM_META_RESOLUTION_UNIT to set default resunit + * 17/4/08 + * - allow CMYKA (thanks Doron) + */ + +/* + + 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 + + */ + +/* Turn on IM_REGION_ADDR() range checks, don't delete intermediates. +#define DEBUG + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#ifndef HAVE_TIFF + +#include + +int +im_vips2tiff( IMAGE *im, const char *filename ) +{ + im_error( "im_vips2tiff", _( "TIFF support disabled" ) ); + + return( -1 ); +} + +#else /*HAVE_TIFF*/ + +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif /*HAVE_UNISTD_H*/ +#include +#include + +#include + +#include + +#ifdef WITH_DMALLOC +#include +#endif /*WITH_DMALLOC*/ + +/* Max no of tiles we buffer in a layer. Enough to buffer a line of 64x64 + * tiles on a 100k pixel across image. + */ +#define IM_MAX_LAYER_BUFFER (1000) + +/* Bits we OR together for quadrants in a tile. + */ +typedef enum pyramid_bits { + PYR_TL = 1, /* Top-left etc. */ + PYR_TR = 2, + PYR_BL = 4, + PYR_BR = 8, + PYR_ALL = 15, + PYR_NONE = 0 +} PyramidBits; + +/* A tile in our pyramid. + */ +typedef struct pyramid_tile { + REGION *tile; + PyramidBits bits; +} PyramidTile; + +/* A layer in the pyramid. + */ +typedef struct pyramid_layer { + /* Parameters. + */ + struct tiff_write *tw; /* Main TIFF write struct */ + int width, height; /* Layer size */ + int sub; /* Subsample factor for this layer */ + + char *lname; /* Name of this TIFF file */ + TIFF *tif; /* TIFF file we write this layer to */ + PEL *tbuf; /* TIFF output buffer */ + PyramidTile tiles[IM_MAX_LAYER_BUFFER]; + + struct pyramid_layer *below; /* Tiles go to here */ + struct pyramid_layer *above; /* Tiles come from here */ +} PyramidLayer; + +/* A TIFF image in the process of being written. + */ +typedef struct tiff_write { + IMAGE *im; /* Original input image */ + char *name; /* Final name we write to */ + char *mode; /* Mode string */ + + /* Read from im with these. + */ + REGION *reg; + im_threadgroup_t *tg; + + char *bname; /* Name for base layer */ + TIFF *tif; /* Image we write to */ + + PyramidLayer *layer; /* Top of pyramid, if in use */ + PEL *tbuf; /* TIFF output buffer */ + int tls; /* Tile line size */ + + int compression; /* Compression type */ + int jpqual; /* JPEG q-factor */ + int predictor; /* Predictor value */ + int tile; /* Tile or not */ + int tilew, tileh; /* Tile size */ + int pyramid; /* Write pyramid */ + int onebit; /* Write as 1-bit TIFF */ + int resunit; /* Resolution unit (inches or cm) */ + float xres; /* Resolution in X */ + float yres; /* Resolution in Y */ + int embed; /* Embed ICC profile */ + char *icc_profile; /* Profile to embed */ +} TiffWrite; + +/* Use these from im_tiff2vips(). + */ +void im__thandler_error( char *module, char *fmt, va_list ap ); +void im__thandler_warning( char *module, char *fmt, va_list ap ); + +/* Open TIFF for output. + */ +static TIFF * +tiff_openout( const char *name ) +{ + TIFF *tif; + + if( !(tif = TIFFOpen( name, "w" )) ) { + im_error( "im_vips2tiff", + _( "unable to open \"%s\" for output" ), name ); + return( NULL ); + } + + return( tif ); +} + +/* Open TIFF for input. + */ +static TIFF * +tiff_openin( const char *name ) +{ + TIFF *tif; + + if( !(tif = TIFFOpen( name, "r" )) ) { + im_error( "im_vips2tiff", + _( "unable to open \"%s\" for input" ), name ); + return( NULL ); + } + + return( tif ); +} + +/* Convert VIPS LabQ to TIFF LAB. Just take the first three bands. + */ +static void +LabQ2LabC( PEL *q, PEL *p, int n ) +{ + int x; + + for( x = 0; x < n; x++ ) { + /* Get most significant 8 bits of lab. + */ + q[0] = p[0]; + q[1] = p[1]; + q[2] = p[2]; + + p += 4; + q += 3; + } +} + +/* Pack 8 bit VIPS to 1 bit TIFF. + */ +static void +eightbit2onebit( PEL *q, PEL *p, int n ) +{ + int x; + PEL bits; + + bits = 0; + for( x = 0; x < n; x++ ) { + bits <<= 1; + if( p[x] ) + bits |= 1; + + if( (x & 0x7) == 0x7 ) { + *q++ = bits; + bits = 0; + } + } + + /* Any left-over bits? Need to be left-aligned. + */ + if( (x & 0x7) != 0 ) + *q++ = bits << (8 - (x & 0x7)); +} + +/* Convert VIPS LABS to TIFF 16 bit LAB. + */ +static void +LabS2Lab16( PEL *q, PEL *p, int n ) +{ + int x; + short *p1 = (short *) p; + unsigned short *q1 = (unsigned short *) q; + + for( x = 0; x < n; x++ ) { + /* TIFF uses unsigned 16 bit ... move zero, scale up L. + */ + q1[0] = (int) p1[0] << 1; + q1[1] = p1[1]; + q1[2] = p1[2]; + + p1 += 3; + q1 += 3; + } +} + +/* Pack a VIPS region into a TIFF tile buffer. + */ +static void +pack2tiff( TiffWrite *tw, REGION *in, PEL *q, Rect *area ) +{ + int y; + + for( y = area->top; y < IM_RECT_BOTTOM( area ); y++ ) { + PEL *p = (PEL *) IM_REGION_ADDR( in, area->left, y ); + + if( in->im->Coding == IM_CODING_LABQ ) + LabQ2LabC( q, p, area->width ); + else if( tw->onebit ) + eightbit2onebit( q, p, area->width ); + else if( in->im->BandFmt == IM_BANDFMT_SHORT && + in->im->Type == IM_TYPE_LABS ) + LabS2Lab16( q, p, area->width ); + else + memcpy( q, p, + area->width * IM_IMAGE_SIZEOF_PEL( in->im ) ); + + q += tw->tls; + } +} + +/* Embed an ICC profile from a file. + */ +static int +embed_profile_file( TIFF *tif, const char *profile ) +{ + char *buffer; + unsigned int length; + + if( !(buffer = im__file_read_name( profile, &length )) ) + return( -1 ); + TIFFSetField( tif, TIFFTAG_ICCPROFILE, length, buffer ); + im_free( buffer ); + +#ifdef DEBUG + printf( "im_vips2tiff: attached profile \"%s\"\n", profile ); +#endif /*DEBUG*/ + + return( 0 ); +} + +/* Embed an ICC profile from IMAGE metadata. + */ +static int +embed_profile_meta( TIFF *tif, IMAGE *im ) +{ + void *data; + size_t data_length; + + if( im_meta_get_blob( im, IM_META_ICC_NAME, &data, &data_length ) ) + return( -1 ); + TIFFSetField( tif, TIFFTAG_ICCPROFILE, data_length, data ); + +#ifdef DEBUG + printf( "im_vips2tiff: attached profile from meta\n" ); +#endif /*DEBUG*/ + + return( 0 ); +} + +static int +embed_profile( TiffWrite *tw, TIFF *tif ) +{ + if( tw->embed && embed_profile_file( tif, tw->icc_profile ) ) + return( -1 ); + if( !tw->embed && im_header_get_type( tw->im, IM_META_ICC_NAME ) && + embed_profile_meta( tif, tw->im ) ) + return( -1 ); + + return( 0 ); +} + +/* Write a TIFF header. width and height are the size of the IMAGE we are + * writing (may have been shrunk!). + */ +static int +write_tiff_header( TiffWrite *tw, TIFF *tif, int width, int height ) +{ + uint16 v[1]; + + /* Output base header fields. + */ + TIFFSetField( tif, TIFFTAG_IMAGEWIDTH, width ); + TIFFSetField( tif, TIFFTAG_IMAGELENGTH, height ); + TIFFSetField( tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG ); + TIFFSetField( tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT ); + TIFFSetField( tif, TIFFTAG_COMPRESSION, tw->compression ); + + /* Don't write mad resolutions (eg. zero), it confuses some programs. + */ + TIFFSetField( tif, TIFFTAG_RESOLUTIONUNIT, tw->resunit ); + TIFFSetField( tif, TIFFTAG_XRESOLUTION, + IM_CLIP( 0.01, tw->xres, 10000 ) ); + TIFFSetField( tif, TIFFTAG_YRESOLUTION, + IM_CLIP( 0.01, tw->yres, 10000 ) ); + + if( tw->compression == COMPRESSION_JPEG ) + TIFFSetField( tif, TIFFTAG_JPEGQUALITY, tw->jpqual ); + + if( tw->predictor != -1 ) + TIFFSetField( tif, TIFFTAG_PREDICTOR, tw->predictor ); + + /* Attach ICC profile. + */ + if( embed_profile( tw, tif ) ) + return( -1 ); + + /* And colour fields. + */ + if( tw->im->Coding == IM_CODING_LABQ ) { + TIFFSetField( tif, TIFFTAG_SAMPLESPERPIXEL, 3 ); + TIFFSetField( tif, TIFFTAG_BITSPERSAMPLE, 8 ); + TIFFSetField( tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_CIELAB ); + } + else if( tw->onebit ) { + TIFFSetField( tif, TIFFTAG_SAMPLESPERPIXEL, 1 ); + TIFFSetField( tif, TIFFTAG_BITSPERSAMPLE, 1 ); + TIFFSetField( tif, + TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK ); + } + else { + int photometric; + + TIFFSetField( tif, TIFFTAG_SAMPLESPERPIXEL, tw->im->Bands ); + TIFFSetField( tif, TIFFTAG_BITSPERSAMPLE, tw->im->Bbits ); + + switch( tw->im->Bands ) { + case 1: + case 2: + photometric = PHOTOMETRIC_MINISBLACK; + if( tw->im->Bands == 2 ) { + v[0] = EXTRASAMPLE_ASSOCALPHA; + TIFFSetField( tif, TIFFTAG_EXTRASAMPLES, 1, v ); + } + break; + + case 3: + case 4: + if( tw->im->Type == IM_TYPE_LAB || + tw->im->Type == IM_TYPE_LABS ) + photometric = PHOTOMETRIC_CIELAB; + else if( tw->im->Type == IM_TYPE_CMYK ) { + photometric = PHOTOMETRIC_SEPARATED; + TIFFSetField( tif, + TIFFTAG_INKSET, INKSET_CMYK ); + } + else + photometric = PHOTOMETRIC_RGB; + + if( tw->im->Type != IM_TYPE_CMYK && + tw->im->Bands == 4 ) { + v[0] = EXTRASAMPLE_ASSOCALPHA; + TIFFSetField( tif, TIFFTAG_EXTRASAMPLES, 1, v ); + } + break; + + case 5: + if( tw->im->Type == IM_TYPE_CMYK ) { + photometric = PHOTOMETRIC_SEPARATED; + TIFFSetField( tif, + TIFFTAG_INKSET, INKSET_CMYK ); + } + break; + + default: + assert( 0 ); + } + + TIFFSetField( tif, TIFFTAG_PHOTOMETRIC, photometric ); + } + + /* Layout. + */ + if( tw->tile ) { + TIFFSetField( tif, TIFFTAG_TILEWIDTH, tw->tilew ); + TIFFSetField( tif, TIFFTAG_TILELENGTH, tw->tileh ); + } + else + TIFFSetField( tif, TIFFTAG_ROWSPERSTRIP, 16 ); + + /* Sample format ... for float, we write IEEE. + */ + if( tw->im->BandFmt == IM_BANDFMT_FLOAT ) + TIFFSetField( tif, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_IEEEFP ); + + return( 0 ); +} + +/* Free a pyramid layer. + */ +static void +free_layer( PyramidLayer *layer ) +{ + int i; + + for( i = 0; i < IM_MAX_LAYER_BUFFER; i++ ) + if( layer->tiles[i].tile ) { + im_region_free( layer->tiles[i].tile ); + layer->tiles[i].tile = NULL; + } + + /* And close the TIFF file we are writing to. + */ + if( layer->tbuf ) { + im_free( layer->tbuf ); + layer->tbuf = NULL; + } + if( layer->tif ) { + TIFFClose( layer->tif ); + layer->tif = NULL; + } +} + +/* Free an entire pyramid. + */ +static void +free_pyramid( PyramidLayer *layer ) +{ + if( layer->below ) + free_pyramid( layer->below ); + + free_layer( layer ); +} + +/* Make a name for a new TIFF layer. Base it on sub factor. + */ +static char * +new_tiff_name( TiffWrite *tw, char *name, int sub ) +{ + char buf[FILENAME_MAX]; + char buf2[FILENAME_MAX]; + + /* Remove existing .tif/.tiff suffix, if any. + */ + strcpy( buf, name ); + if( im_ispostfix( buf, ".tif" ) ) + buf[strlen( buf ) - 4] = '\0'; + if( im_ispostfix( buf, ".tiff" ) ) + buf[strlen( buf ) - 5] = '\0'; + + im_snprintf( buf2, FILENAME_MAX, "%s.%d.tif", buf, sub ); + + return( im_strdup( tw->im, buf2 ) ); +} + +/* Build a pyramid. w & h are size of layer above this layer. Write new layer + * struct into *zap, return 0/-1 for success/fail. + */ +static int +build_pyramid( TiffWrite *tw, PyramidLayer *above, + PyramidLayer **zap, int w, int h ) +{ + PyramidLayer *layer = IM_NEW( tw->im, PyramidLayer ); + int i; + + if( !layer ) + return( -1 ); + layer->tw = tw; + layer->width = w / 2; + layer->height = h / 2; + + if( !above ) + /* Top of pyramid. + */ + layer->sub = 2; + else + layer->sub = above->sub * 2; + + layer->lname = NULL; + layer->tif = NULL; + layer->tbuf = NULL; + + for( i = 0; i < IM_MAX_LAYER_BUFFER; i++ ) { + layer->tiles[i].tile = NULL; + layer->tiles[i].bits = PYR_NONE; + } + + layer->below = NULL; + layer->above = above; + + /* Save layer, to make sure it gets freed properly. + */ + *zap = layer; + + if( layer->width > tw->tilew || layer->height > tw->tileh ) + if( build_pyramid( tw, layer, + &layer->below, layer->width, layer->height ) ) + return( -1 ); + + if( !(layer->lname = new_tiff_name( tw, tw->name, layer->sub )) ) + return( -1 ); + + /* Make output image. + */ + if( !(layer->tif = tiff_openout( layer->lname )) ) + return( -1 ); + + /* Write the TIFF header for this layer. + */ + if( write_tiff_header( tw, layer->tif, layer->width, layer->height ) ) + return( -1 ); + + if( !(layer->tbuf = im_malloc( NULL, TIFFTileSize( layer->tif ) )) ) + return( -1 ); + + return( 0 ); +} + +/* Pick a new tile to write to in this layer. Either reuse a tile we have + * previously filled, or make a new one. + */ +static int +find_new_tile( PyramidLayer *layer ) +{ + int i; + + /* Exisiting buffer we have finished with? + */ + for( i = 0; i < IM_MAX_LAYER_BUFFER; i++ ) + if( layer->tiles[i].bits == PYR_ALL ) + return( i ); + + /* Have to make a new one. + */ + for( i = 0; i < IM_MAX_LAYER_BUFFER; i++ ) + if( !layer->tiles[i].tile ) { + if( !(layer->tiles[i].tile = + im_region_create( layer->tw->im )) ) + return( -1 ); + return( i ); + } + + /* Out of space! + */ + im_error( "im_vips2tiff", _( "layer buffer exhausted -- " + "try making TIFF output tiles smaller" ) ); + + return( -1 ); +} + +/* Find a tile in the layer buffer - if it's not there, make a new one. + */ +static int +find_tile( PyramidLayer *layer, Rect *pos ) +{ + int i; + Rect quad; + Rect image; + Rect inter; + + /* Do we have a REGION for this position? + */ + for( i = 0; i < IM_MAX_LAYER_BUFFER; i++ ) { + REGION *reg = layer->tiles[i].tile; + + if( reg && reg->valid.left == pos->left && + reg->valid.top == pos->top ) + return( i ); + } + + /* Make a new one. + */ + if( (i = find_new_tile( layer )) < 0 ) + return( -1 ); + if( im_region_buffer( layer->tiles[i].tile, pos ) ) + return( -1 ); + layer->tiles[i].bits = PYR_NONE; + + /* Do any quadrants of this tile fall entirely outside the image? + * If they do, set their bits now. + */ + quad.width = layer->tw->tilew / 2; + quad.height = layer->tw->tileh / 2; + image.left = 0; + image.top = 0; + image.width = layer->width; + image.height = layer->height; + + quad.left = pos->left; + quad.top = pos->top; + im_rect_intersectrect( &quad, &image, &inter ); + if( im_rect_isempty( &inter ) ) + layer->tiles[i].bits |= PYR_TL; + + quad.left = pos->left + quad.width; + quad.top = pos->top; + im_rect_intersectrect( &quad, &image, &inter ); + if( im_rect_isempty( &inter ) ) + layer->tiles[i].bits |= PYR_TR; + + quad.left = pos->left; + quad.top = pos->top + quad.height; + im_rect_intersectrect( &quad, &image, &inter ); + if( im_rect_isempty( &inter ) ) + layer->tiles[i].bits |= PYR_BL; + + quad.left = pos->left + quad.width; + quad.top = pos->top + quad.height; + im_rect_intersectrect( &quad, &image, &inter ); + if( im_rect_isempty( &inter ) ) + layer->tiles[i].bits |= PYR_BR; + + return( i ); +} + +/* Shrink a region by a factor of two, writing the result to a specified + * offset in another region. IM_CODING_LABQ only. + */ +static void +shrink_region_labpack( REGION *from, Rect *area, + REGION *to, int xoff, int yoff ) +{ + int ls = IM_REGION_LSKIP( from ); + Rect *t = &to->valid; + + int x, y; + Rect out; + + /* Calculate output size and position. + */ + out.left = t->left + xoff; + out.top = t->top + yoff; + out.width = area->width / 2; + out.height = area->height / 2; + + /* Shrink ... ignore the extension byte for speed. + */ + for( y = 0; y < out.height; y++ ) { + PEL *p = (PEL *) + IM_REGION_ADDR( from, area->left, area->top + y * 2 ); + PEL *q = (PEL *) + IM_REGION_ADDR( to, out.left, out.top + y ); + + for( x = 0; x < out.width; x++ ) { + signed char *sp = (signed char *) p; + unsigned char *up = (unsigned char *) p; + + int l = up[0] + up[4] + + up[ls] + up[ls + 4]; + int a = sp[1] + sp[5] + + sp[ls + 1] + sp[ls + 5]; + int b = sp[2] + sp[6] + + sp[ls + 2] + sp[ls + 6]; + + q[0] = l >> 2; + q[1] = a >> 2; + q[2] = b >> 2; + q[3] = 0; + + q += 4; + p += 8; + } + } +} + +#define SHRINK_TYPE_INT( TYPE ) \ + for( x = 0; x < out.width; x++ ) { \ + TYPE *tp = (TYPE *) p; \ + TYPE *tp1 = (TYPE *) (p + ls); \ + TYPE *tq = (TYPE *) q; \ + \ + for( z = 0; z < nb; z++ ) { \ + int tot = tp[z] + tp[z + nb] + \ + tp1[z] + tp1[z + nb]; \ + \ + tq[z] = tot >> 2; \ + } \ + \ + /* Move on two pels in input. \ + */ \ + p += ps << 1; \ + q += ps; \ + } + +#define SHRINK_TYPE_FLOAT( TYPE ) \ + for( x = 0; x < out.width; x++ ) { \ + TYPE *tp = (TYPE *) p; \ + TYPE *tp1 = (TYPE *) (p + ls); \ + TYPE *tq = (TYPE *) q; \ + \ + for( z = 0; z < nb; z++ ) { \ + double tot = (double) tp[z] + tp[z + nb] + \ + tp1[z] + tp1[z + nb]; \ + \ + tq[z] = tot / 4; \ + } \ + \ + /* Move on two pels in input. \ + */ \ + p += ps << 1; \ + q += ps; \ + } + +/* Shrink a region by a factor of two, writing the result to a specified + * offset in another region. n-band, non-complex. + */ +static void +shrink_region( REGION *from, Rect *area, + REGION *to, int xoff, int yoff ) +{ + int ls = IM_REGION_LSKIP( from ); + int ps = IM_IMAGE_SIZEOF_PEL( from->im ); + int nb = from->im->Bands; + Rect *t = &to->valid; + + int x, y, z; + Rect out; + + /* Calculate output size and position. + */ + out.left = t->left + xoff; + out.top = t->top + yoff; + out.width = area->width / 2; + out.height = area->height / 2; + + for( y = 0; y < out.height; y++ ) { + PEL *p = (PEL *) + IM_REGION_ADDR( from, area->left, area->top + y * 2 ); + PEL *q = (PEL *) + IM_REGION_ADDR( to, out.left, out.top + y ); + + /* Process this line of pels. + */ + switch( from->im->BandFmt ) { + case IM_BANDFMT_UCHAR: + SHRINK_TYPE_INT( unsigned char ); break; + case IM_BANDFMT_CHAR: + SHRINK_TYPE_INT( signed char ); break; + case IM_BANDFMT_USHORT: + SHRINK_TYPE_INT( unsigned short ); break; + case IM_BANDFMT_SHORT: + SHRINK_TYPE_INT( signed short ); break; + case IM_BANDFMT_UINT: + SHRINK_TYPE_INT( unsigned int ); break; + case IM_BANDFMT_INT: + SHRINK_TYPE_INT( signed int ); break; + case IM_BANDFMT_FLOAT: + SHRINK_TYPE_FLOAT( float ); break; + case IM_BANDFMT_DOUBLE: + SHRINK_TYPE_FLOAT( double ); break; + + default: + assert( 0 ); + } + } +} + +/* Write a tile from a layer. + */ +static int +save_tile( TiffWrite *tw, TIFF *tif, PEL *tbuf, REGION *reg, Rect *area ) +{ + /* Have to repack pixels. + */ + pack2tiff( tw, reg, tbuf, area ); + +#ifdef DEBUG + printf( "Writing %dx%d pixels at position %dx%d to image %s\n", + tw->tilew, tw->tileh, area->left, area->top, + TIFFFileName( tif ) ); +#endif /*DEBUG*/ + + /* Write to TIFF! easy. + */ + if( TIFFWriteTile( tif, tbuf, area->left, area->top, 0, 0 ) < 0 ) { + im_error( "im_vips2tiff", _( "TIFF write tile failed" ) ); + return( -1 ); + } + + return( 0 ); +} + +/* A new tile has arrived! Shrink into this layer, if we fill a region, write + * it and recurse. + */ +static int +new_tile( PyramidLayer *layer, REGION *tile, Rect *area ) +{ + TiffWrite *tw = layer->tw; + int xoff, yoff; + + int t, ri, bo; + Rect out, new; + PyramidBits bit; + + /* Calculate pos and size of new pixels we make inside this layer. + */ + new.left = area->left / 2; + new.top = area->top / 2; + new.width = area->width / 2; + new.height = area->height / 2; + + /* Has size fallen to zero? Can happen if this is a one-pixel-wide + * strip. + */ + if( im_rect_isempty( &new ) ) + return( 0 ); + + /* Offset into this tile ... ie. which quadrant we are writing. + */ + xoff = new.left % layer->tw->tilew; + yoff = new.top % layer->tw->tileh; + + /* Calculate pos for tile we shrink into in this layer. + */ + out.left = new.left - xoff; + out.top = new.top - yoff; + + /* Clip against edge of image. + */ + ri = IM_MIN( layer->width, out.left + layer->tw->tilew ); + bo = IM_MIN( layer->height, out.top + layer->tw->tileh ); + out.width = ri - out.left; + out.height = bo - out.top; + + if( (t = find_tile( layer, &out )) < 0 ) + return( -1 ); + + /* Shrink into place. + */ + if( tw->im->Coding == IM_CODING_NONE ) + shrink_region( tile, area, + layer->tiles[t].tile, xoff, yoff ); + else + shrink_region_labpack( tile, area, + layer->tiles[t].tile, xoff, yoff ); + + /* Set that bit. + */ + if( xoff ) + if( yoff ) + bit = PYR_BR; + else + bit = PYR_TR; + else + if( yoff ) + bit = PYR_BL; + else + bit = PYR_TL; + if( layer->tiles[t].bits & bit ) { + im_error( "im_vips2tiff", _( "internal error #9876345" ) ); + return( -1 ); + } + layer->tiles[t].bits |= bit; + + if( layer->tiles[t].bits == PYR_ALL ) { + /* Save this complete tile. + */ + if( save_tile( tw, layer->tif, layer->tbuf, + layer->tiles[t].tile, &layer->tiles[t].tile->valid ) ) + return( -1 ); + + /* And recurse down the pyramid! + */ + if( layer->below && + new_tile( layer->below, + layer->tiles[t].tile, + &layer->tiles[t].tile->valid ) ) + return( -1 ); + } + + return( 0 ); +} + +/* Write as tiles. + */ +static int +write_tif_tile( TiffWrite *tw ) +{ + IMAGE *im = tw->im; + Rect area; + int x, y; + + if( !(tw->tbuf = im_malloc( NULL, TIFFTileSize( tw->tif ) )) ) + return( -1 ); + + /* Write pyramid too? Only bother if bigger than tile size. + */ + if( tw->pyramid && + (im->Xsize > tw->tilew || im->Ysize > tw->tileh) && + build_pyramid( tw, NULL, &tw->layer, im->Xsize, im->Ysize ) ) + return( -1 ); + + for( y = 0; y < im->Ysize; y += tw->tileh ) + for( x = 0; x < im->Xsize; x += tw->tilew ) { + /* Set up rect we write. + */ + area.left = x; + area.top = y; + area.width = IM_MIN( tw->tilew, im->Xsize - x ); + area.height = IM_MIN( tw->tileh, im->Ysize - y ); + + if( im_prepare_thread( tw->tg, tw->reg, &area ) ) + return( -1 ); + + /* Write to TIFF. + */ + if( save_tile( tw, tw->tif, tw->tbuf, tw->reg, &area ) ) + return( -1 ); + + /* Is there a pyramid? Write to that too. + */ + if( tw->layer && new_tile( tw->layer, tw->reg, &area ) ) + return( -1 ); + } + + return( 0 ); +} + +static int +write_tif_block( REGION *region, Rect *area, void *a, void *b ) +{ + TiffWrite *tw = (TiffWrite *) a; + IMAGE *im = tw->im; + + int y; + + for( y = 0; y < area->height; y++ ) { + PEL *p = (PEL *) IM_REGION_ADDR( region, 0, area->top + y ); + + /* Any repacking necessary. + */ + if( im->Coding == IM_CODING_LABQ ) { + LabQ2LabC( tw->tbuf, p, im->Xsize ); + p = tw->tbuf; + } + else if( im->BandFmt == IM_BANDFMT_SHORT && + im->Type == IM_TYPE_LABS ) { + LabS2Lab16( tw->tbuf, p, im->Xsize ); + p = tw->tbuf; + } + else if( tw->onebit ) { + eightbit2onebit( tw->tbuf, p, im->Xsize ); + p = tw->tbuf; + } + + if( TIFFWriteScanline( tw->tif, p, area->top + y, 0 ) < 0 ) + return( -1 ); + } + + return( 0 ); +} + +/* Write as scan-lines. + */ +static int +write_tif_strip( TiffWrite *tw ) +{ + g_assert( !tw->tbuf ); + + if( !(tw->tbuf = im_malloc( NULL, TIFFScanlineSize( tw->tif ) )) ) + return( -1 ); + + if( im_wbuffer( tw->tg, write_tif_block, tw, NULL ) ) + return( -1 ); + + return( 0 ); +} + +/* Delete any temp files we wrote. + */ +static void +delete_files( TiffWrite *tw ) +{ + PyramidLayer *layer = tw->layer; + + if( tw->bname ) { + unlink( tw->bname ); + tw->bname = NULL; + } + + for( layer = tw->layer; layer; layer = layer->below ) + if( layer->lname ) { + unlink( layer->lname ); + layer->lname = NULL; + } +} + +/* Free a TiffWrite. + */ +static void +free_tiff_write( TiffWrite *tw ) +{ +#ifndef DEBUG + delete_files( tw ); +#endif /*DEBUG*/ + + if( tw->tg ) { + im_threadgroup_free( tw->tg ); + tw->tg = NULL; + } + if( tw->reg ) { + im_region_free( tw->reg ); + tw->reg = NULL; + } + if( tw->tif ) { + TIFFClose( tw->tif ); + tw->tif = NULL; + } + if( tw->tbuf ) { + im_free( tw->tbuf ); + tw->tbuf = NULL; + } + if( tw->layer ) + free_pyramid( tw->layer ); + if( tw->icc_profile ) { + im_free( tw->icc_profile ); + tw->icc_profile = NULL; + } +} + +/* Round N down to P boundary. + */ +#define ROUND_DOWN(N,P) ((N) - ((N) % P)) + +/* Round N up to P boundary. + */ +#define ROUND_UP(N,P) (ROUND_DOWN( (N) + (P) - 1, (P) )) + +/* Make and init a TiffWrite. + */ +static TiffWrite * +make_tiff_write( IMAGE *im, const char *filename ) +{ + TiffWrite *tw; + char *p, *q, *r; + char name[FILENAME_MAX]; + char mode[FILENAME_MAX]; + char buf[FILENAME_MAX]; + + if( !(tw = IM_NEW( im, TiffWrite )) ) + return( NULL ); + tw->im = im; + im_filename_split( filename, name, mode ); + tw->name = im_strdup( im, name ); + tw->mode = im_strdup( im, mode ); + tw->tg = NULL; + tw->reg = NULL; + tw->bname = NULL; + tw->tif = NULL; + tw->layer = NULL; + tw->tbuf = NULL; + tw->compression = COMPRESSION_NONE; + tw->jpqual = 75; + tw->predictor = -1; + tw->tile = 0; + tw->tilew = 128; + tw->tileh = 128; + tw->pyramid = 0; + tw->onebit = 0; + tw->embed = 0; + tw->icc_profile = NULL; + + /* Output resolution settings ... default to VIPS-alike. + */ + tw->resunit = RESUNIT_CENTIMETER; + tw->xres = im->Xres * 10; + tw->yres = im->Yres * 10; + if( !im_meta_get_string( im, IM_META_RESOLUTION_UNIT, &p ) && + strcmp( p, "in" ) == 0 ) { + tw->resunit = RESUNIT_INCH; + tw->xres *= 2.54; + tw->yres *= 2.54; + } + + /* Parse mode string. + */ + strcpy( buf, mode ); + p = &buf[0]; + if( (q = im_getnextoption( &p )) ) { + if( im_isprefix( "none", q ) ) + tw->compression = COMPRESSION_NONE; + else if( im_isprefix( "packbits", q ) ) + tw->compression = COMPRESSION_PACKBITS; + else if( im_isprefix( "ccittfax4", q ) ) + tw->compression = COMPRESSION_CCITTFAX4; + else if( im_isprefix( "lzw", q ) ) { + tw->compression = COMPRESSION_LZW; + + if( (r = im_getsuboption( q )) ) + if( sscanf( r, "%d", &tw->predictor ) != 1 ) { + im_error( "im_vips2tiff", + _( "bad predictor " + "parameter" ) ); + return( NULL ); + } + } + else if( im_isprefix( "deflate", q ) ) { + tw->compression = COMPRESSION_ADOBE_DEFLATE; + + if( (r = im_getsuboption( q )) ) + if( sscanf( r, "%d", &tw->predictor ) != 1 ) { + im_error( "im_vips2tiff", + _( "bad predictor " + "parameter" ) ); + return( NULL ); + } + } + else if( im_isprefix( "jpeg", q ) ) { + tw->compression = COMPRESSION_JPEG; + + if( (r = im_getsuboption( q )) ) + if( sscanf( r, "%d", &tw->jpqual ) != 1 ) { + im_error( "im_vips2tiff", + _( "bad JPEG quality " + "parameter" ) ); + return( NULL ); + } + } + else { + im_error( "im_vips2tiff", _( "unknown compression mode " + "\"%s\"\nshould be one of \"none\", " + "\"packbits\", \"ccittfax4\", \"lzw\", " + "\"deflate\" or \"jpeg\"" ), q ); + return( NULL ); + } + } + if( (q = im_getnextoption( &p )) ) { + if( im_isprefix( "tile", q ) ) { + tw->tile = 1; + + if( (r = im_getsuboption( q )) ) { + if( sscanf( r, "%dx%d", + &tw->tilew, &tw->tileh ) != 2 ) { + im_error( "im_vips2tiff", _( "bad tile " + "sizes" ) ); + return( NULL ); + } + + if( tw->tilew < 10 || tw->tileh < 10 || + tw->tilew > 1000 || tw->tileh > 1000 ) { + im_error( "im_vips2tiff", _( "bad tile " + "size %dx%d" ), + tw->tilew, tw->tileh ); + return( NULL ); + } + + if( (tw->tilew & 0xf) != 0 || + (tw->tileh & 0xf) != 0 ) { + im_error( "im_vips2tiff", _( "tile " + "size not a multiple of 16" ) ); + return( NULL ); + } + } + } + else if( im_isprefix( "strip", q ) ) + tw->tile = 0; + else { + im_error( "im_vips2tiff", _( "unknown layout mode " + "\"%s\"\nshould be one of \"tile\" or " + "\"strip\"" ), q ); + return( NULL ); + } + } + if( (q = im_getnextoption( &p )) ) { + if( im_isprefix( "pyramid", q ) ) + tw->pyramid = 1; + else if( im_isprefix( "flat", q ) ) + tw->pyramid = 0; + else { + im_error( "im_vips2tiff", _( "unknown multi-res mode " + "\"%s\"\nshould be one of \"flat\" or " + "\"pyramid\"" ), q ); + return( NULL ); + } + } + if( (q = im_getnextoption( &p )) ) { + if( im_isprefix( "onebit", q ) ) + tw->onebit = 1; + else if( im_isprefix( "manybit", q ) ) + tw->onebit = 0; + else { + im_error( "im_vips2tiff", _( "unknown format " + "\"%s\"\nshould be one of \"onebit\" or " + "\"manybit\"" ), q ); + return( NULL ); + } + } + if( (q = im_getnextoption( &p )) ) { + if( im_isprefix( "res_cm", q ) ) { + if( tw->resunit == RESUNIT_INCH ) { + tw->xres /= 2.54; + tw->yres /= 2.54; + } + tw->resunit = RESUNIT_CENTIMETER; + } + else if( im_isprefix( "res_inch", q ) ) { + if( tw->resunit == RESUNIT_CENTIMETER ) { + tw->xres *= 2.54; + tw->yres *= 2.54; + } + tw->resunit = RESUNIT_INCH; + } + else { + im_error( "im_vips2tiff", _( "unknown resolution unit " + "\"%s\"\nshould be one of \"res_cm\" or " + "\"res_inch\"" ), q ); + return( NULL ); + } + + if( (r = im_getsuboption( q )) ) { + if( sscanf( r, "%fx%f", &tw->xres, &tw->yres ) != 2 ) { + if( sscanf( r, "%f", &tw->xres ) != 1 ) { + im_error( "im_vips2tiff", _( "bad " + "resolution values" ) ); + return( NULL ); + } + + tw->yres = tw->xres; + } + } + } + if( (q = im_getnextoption( &p )) && strcmp( q, "" ) != 0 ) { + tw->embed = 1; + tw->icc_profile = im_strdup( NULL, q ); + } + if( (q = im_getnextoption( &p )) ) { + im_error( "im_vips2tiff", + _( "unknown extra options \"%s\"" ), q ); + return( NULL ); + } + if( !tw->tile && tw->pyramid ) { + im_warn( "im_vips2tiff", _( "can't have strip pyramid -- " + "enabling tiling" ) ); + tw->tile = 1; + } + + /* We can only pyramid LABQ and non-complex images. + */ + if( tw->pyramid ) { + if( im->Coding == IM_CODING_NONE && im_iscomplex( im ) ) { + im_error( "im_vips2tiff", + _( "can only pyramid LABQ and " + "non-complex images" ) ); + return( NULL ); + } + } + + /* Only 1-bit-ize 8 bit mono images. + */ + if( tw->onebit ) { + if( im->Coding != IM_CODING_NONE || + im->BandFmt != IM_BANDFMT_UCHAR || + im->Bands != 1 ) + tw->onebit = 0; + } + + if( tw->onebit && tw->compression == COMPRESSION_JPEG ) { + im_warn( "im_vips2tiff", _( "can't have 1-bit JPEG -- " + "disabling JPEG" ) ); + tw->compression = COMPRESSION_NONE; + } + + /* Make region and threadgroup. + */ + if( !(tw->reg = im_region_create( tw->im )) || + !(tw->tg = im_threadgroup_create( tw->im )) ) { + free_tiff_write( tw ); + return( NULL ); + } + + /* Sizeof a line of bytes in the TIFF tile. + */ + if( im->Coding == IM_CODING_LABQ ) + tw->tls = tw->tilew * 3; + else if( tw->onebit ) + tw->tls = ROUND_UP( tw->tilew, 8 ) / 8; + else + tw->tls = IM_IMAGE_SIZEOF_PEL( im ) * tw->tilew; + + return( tw ); +} + +/* Copy fields. + */ +#define CopyField( tag, v ) \ + if( TIFFGetField( in, tag, &v ) ) TIFFSetField( out, tag, v ) + +/* Copy a TIFF file ... we know we wrote it, so just copy the tags we know + * we might have set. + */ +static int +tiff_copy( TiffWrite *tw, TIFF *out, TIFF *in ) +{ + uint32 i32; + uint16 i16; + float f; + tdata_t buf; + ttile_t tile; + ttile_t n; + + /* All the fields we might have set. + */ + CopyField( TIFFTAG_IMAGEWIDTH, i32 ); + CopyField( TIFFTAG_IMAGELENGTH, i32 ); + CopyField( TIFFTAG_PLANARCONFIG, i16 ); + CopyField( TIFFTAG_ORIENTATION, i16 ); + CopyField( TIFFTAG_XRESOLUTION, f ); + CopyField( TIFFTAG_YRESOLUTION, f ); + CopyField( TIFFTAG_RESOLUTIONUNIT, i16 ); + CopyField( TIFFTAG_COMPRESSION, i16 ); + CopyField( TIFFTAG_SAMPLESPERPIXEL, i16 ); + CopyField( TIFFTAG_BITSPERSAMPLE, i16 ); + CopyField( TIFFTAG_PHOTOMETRIC, i16 ); + CopyField( TIFFTAG_TILEWIDTH, i32 ); + CopyField( TIFFTAG_TILELENGTH, i32 ); + CopyField( TIFFTAG_ROWSPERSTRIP, i32 ); + + if( tw->predictor != -1 ) + TIFFSetField( out, TIFFTAG_PREDICTOR, tw->predictor ); + + /* TIFFTAG_JPEGQUALITY is a pesudo-tag, so we can't copy it. + * Set explicitly from TiffWrite. + */ + if( tw->compression == COMPRESSION_JPEG ) + TIFFSetField( out, TIFFTAG_JPEGQUALITY, tw->jpqual ); + + /* We can't copy profiles :( Set again from TiffWrite. + */ + if( embed_profile( tw, out ) ) + return( -1 ); + + buf = im_malloc( NULL, TIFFTileSize( in ) ); + n = TIFFNumberOfTiles( in ); + for( tile = 0; tile < n; tile++ ) { + tsize_t len; + + /* It'd be good to use TIFFReadRawTile()/TIFFWriteRawTile() + * here to save compression/decompression, but sadly it seems + * not to work :-( investigate at some point. + */ + len = TIFFReadEncodedTile( in, tile, buf, (tsize_t) -1 ); + if( len < 0 || + TIFFWriteEncodedTile( out, tile, buf, len ) < 0 ) { + im_free( buf ); + return( -1 ); + } + } + im_free( buf ); + + return( 0 ); +} + +/* Append a file to a TIFF file. + */ +static int +tiff_append( TiffWrite *tw, TIFF *out, const char *name ) +{ + TIFF *in; + + if( !(in = tiff_openin( name )) ) + return( -1 ); + + if( tiff_copy( tw, out, in ) ) { + TIFFClose( in ); + return( -1 ); + } + TIFFClose( in ); + + if( !TIFFWriteDirectory( out ) ) + return( -1 ); + + return( 0 ); +} + +/* Gather all of the files we wrote into single output file. + */ +static int +gather_pyramid( TiffWrite *tw ) +{ + PyramidLayer *layer; + TIFF *out; + +#ifdef DEBUG + printf( "Starting pyramid gather ...\n" ); +#endif /*DEBUG*/ + + if( !(out = tiff_openout( tw->name )) ) + return( -1 ); + + if( tiff_append( tw, out, tw->bname ) ) { + TIFFClose( out ); + return( -1 ); + } + + for( layer = tw->layer; layer; layer = layer->below ) + if( tiff_append( tw, out, layer->lname ) ) { + TIFFClose( out ); + return( -1 ); + } + + TIFFClose( out ); + +#ifdef DEBUG + printf( "Pyramid built\n" ); +#endif /*DEBUG*/ + + return( 0 ); +} + +int +im_vips2tiff( IMAGE *im, const char *filename ) +{ + TiffWrite *tw; + int res; + +#ifdef DEBUG + printf( "im_tiff2vips: libtiff version is \"%s\"\n", TIFFGetVersion() ); +#endif /*DEBUG*/ + + /* Override the default TIFF error handler. + */ + TIFFSetErrorHandler( (TIFFErrorHandler) im__thandler_error ); + TIFFSetWarningHandler( (TIFFErrorHandler) im__thandler_warning ); + + /* Check input image. + */ + if( im_pincheck( im ) ) + return( -1 ); + if( im->Coding != IM_CODING_LABQ && im->Coding != IM_CODING_NONE ) { + im_error( "im_vips2tiff", _( "unknown coding type" ) ); + return( -1 ); + } + if( im->BandFmt != IM_BANDFMT_UCHAR && + !(im->BandFmt == IM_BANDFMT_SHORT && + im->Type == IM_TYPE_LABS) && + im->BandFmt != IM_BANDFMT_USHORT && + im->BandFmt != IM_BANDFMT_FLOAT ) { + im_error( "im_vips2tiff", _( "unsigned 8-bit int, 16-bit int, " + "and 32-bit float only" ) ); + return( -1 ); + } + if( im->Coding == IM_CODING_NONE ) { + if( im->Bands < 1 || im->Bands > 5 ) { + im_error( "im_vips2tiff", _( "1 to 5 bands only" ) ); + return( -1 ); + } + } + + /* Make output image. If this is a pyramid, write the base image to + * fred.1.tif rather than fred.tif. + */ + if( !(tw = make_tiff_write( im, filename )) ) + return( -1 ); + if( tw->pyramid ) { + if( !(tw->bname = new_tiff_name( tw, tw->name, 1 )) || + !(tw->tif = tiff_openout( tw->bname )) ) { + free_tiff_write( tw ); + return( -1 ); + } + } + else { + /* No pyramid ... write straight to name. + */ + if( !(tw->tif = tiff_openout( tw->name )) ) { + free_tiff_write( tw ); + return( -1 ); + } + } + + /* Write the TIFF header for the full-res file. + */ + if( write_tiff_header( tw, tw->tif, im->Xsize, im->Ysize ) ) { + free_tiff_write( tw ); + return( -1 ); + } + + if( tw->tile ) + res = write_tif_tile( tw ); + else + res = write_tif_strip( tw ); + if( res ) { + free_tiff_write( tw ); + return( -1 ); + } + + /* Free pyramid resources ... this will TIFFClose() the intermediates, + * ready for us to read from them again. + */ + if( tw->layer ) + free_pyramid( tw->layer ); + if( tw->tif ) { + TIFFClose( tw->tif ); + tw->tif = NULL; + } + + /* Gather layers together into final pyramid file. + */ + if( tw->pyramid && gather_pyramid( tw ) ) { + free_tiff_write( tw ); + return( -1 ); + } + + free_tiff_write( tw ); + + return( 0 ); +} + +#endif /*HAVE_TIFF*/ diff --git a/libsrc/iofuncs/im_open.c b/libsrc/iofuncs/im_open.c index e1400184..fb712565 100644 --- a/libsrc/iofuncs/im_open.c +++ b/libsrc/iofuncs/im_open.c @@ -139,6 +139,33 @@ Modified: #include #endif /*WITH_DMALLOC*/ +/* Suffix sets. + */ +static const char *im_suffix_vips[] = { + "v", "", + NULL +}; +static const char *im_suffix_tiff[] = { + "tif", "tiff", + NULL +}; +static const char *im_suffix_jpeg[] = { + "jpeg", "jpg", "jfif", "jpe", + NULL +}; +static const char *im_suffix_ppm[] = { + "ppm", "pbm", "pgm", + NULL +}; +static const char *im_suffix_png[] = { + "png", + NULL +}; +static const char *im_suffix_csv[] = { + "csv", + NULL +}; + /* Progress feedback. Only really useful for testing, tbh. */ int im__progress = 0; @@ -198,30 +225,6 @@ read_vips( const char *filename ) return( im2 ); } -/* - else if( im_isvips( name ) ) { - if( mode[1] == 'w' ) { - if( !(im = im_init( filename )) ) - return( NULL ); - if( im_openinrw( im ) ) { - im_close( im ); - return( NULL ); - } - if( im->Bbits != IM_BBITS_BYTE && - im_isMSBfirst( im ) != - im_amiMSBfirst() ) { - im_close( im ); - im_error( "im_open", _( "open for read-" - "write for native format " - "images only" ) ); - return( NULL ); - } - } - else - im = read_vips( filename ); - } - */ - /* Delayed save: if we write to TIFF or to JPEG format, actually do the write * to a "p" and on preclose do im_vips2tiff() or whatever. Track save * parameters here. @@ -413,7 +416,6 @@ IMAGE * im_open( const char *filename, const char *mode ) { IMAGE *im; - im_format *format; /* Pass in a nonsense name for argv0 ... this init world is only here * for old programs which are missing an im_init_world() call. We must @@ -429,11 +431,107 @@ im_open( const char *filename, const char *mode ) switch( mode[0] ) { case 'r': - if( !(format = im_format_for_file( filename )) ) +{ + char name[FILENAME_MAX]; + char options[FILENAME_MAX]; + + /* Break any options off the name ... eg. "fred.tif:jpeg,tile" + * etc. + */ + im_filename_split( filename, name, options ); + + /* Check for other formats. + + FIXME ... should have a table to avoid all this + repetition + + */ + if( !im_existsf( "%s", name ) ) { + im_error( "im_open", + _( "\"%s\" is not readable" ), name ); return( NULL ); - if( !(im = open_sub( - format->header, format->load, filename )) ) + } + else if( im_istiff( name ) ) { + /* If TIFF open fails, try going through libmagick. + */ + if( !(im = open_sub( + im_tiff2vips_header, im_tiff2vips, + filename )) && + !(im = open_sub( + im_magick2vips_header, im_magick2vips, + filename )) ) + return( NULL ); + } + else if( im_isjpeg( name ) ) { + if( !(im = open_sub( + im_jpeg2vips_header, im_jpeg2vips, filename )) ) + return( NULL ); + } + else if( im_isexr( name ) ) { + if( !(im = open_sub( + im_exr2vips_header, im_exr2vips, filename )) ) + return( NULL ); + } + else if( im_isppm( name ) ) { + if( !(im = open_sub( + im_ppm2vips_header, im_ppm2vips, filename )) ) + return( NULL ); + } + else if( im_ispng( name ) ) { + if( !(im = open_sub( + im_png2vips_header, im_png2vips, filename )) ) + return( NULL ); + } + else if( im_filename_suffix_match( name, im_suffix_csv ) ) { + if( !(im = open_sub( + im_csv2vips_header, im_csv2vips, filename )) ) + return( NULL ); + } + else if( im_isvips( name ) ) { + if( mode[1] == 'w' ) { + /* Has to be native format for >8 bits. + */ + if( !(im = im_init( filename )) ) + return( NULL ); + if( im_openinrw( im ) ) { + im_close( im ); + return( NULL ); + } + if( im->Bbits != IM_BBITS_BYTE && + im_isMSBfirst( im ) != + im_amiMSBfirst() ) { + im_close( im ); + im_error( "im_open", _( "open for read-" + "write for native format " + "images only" ) ); + return( NULL ); + } + } + else + im = read_vips( filename ); + } + else if( im_isanalyze( name ) ) { + if( !(im = open_sub( + im_analyze2vips_header, im_analyze2vips, + filename )) ) + return( NULL ); + } + else if( im_ismagick( name ) ) { + /* Have this last as it can be very slow to detect + * failure. + */ + if( !(im = open_sub( + im_magick2vips_header, im_magick2vips, + filename )) ) + return( NULL ); + } + else { + im_error( "im_open", _( "\"%s\" is not " + "a supported format" ), filename ); return( NULL ); + } +} + break; case 'w': diff --git a/libsrc/iofuncs/im_open_vips.c b/libsrc/iofuncs/im_open_vips.c deleted file mode 100644 index ca677701..00000000 --- a/libsrc/iofuncs/im_open_vips.c +++ /dev/null @@ -1,819 +0,0 @@ -/* Read and write a VIPS file into an IMAGE * - * - * 22/5/08 - * - from im_open.c, im_openin.c, im_desc_hd.c, im_readhist.c, - * im_openout.c - */ - -/* - - 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 - */ - -#ifdef HAVE_CONFIG_H -#include -#endif /*HAVE_CONFIG_H*/ -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#ifdef HAVE_SYS_FILE_H -#include -#endif /*HAVE_SYS_FILE_H*/ -#include -#ifdef HAVE_UNISTD_H -#include -#endif /*HAVE_UNISTD_H*/ -#ifdef HAVE_IO_H -#include -#endif /*HAVE_IO_H*/ -#include -#include - -#ifdef OS_WIN32 -#include -#endif /*OS_WIN32*/ - -#include -#include -#include - -#ifdef WITH_DMALLOC -#include -#endif /*WITH_DMALLOC*/ - -/* Try to make an O_BINARY ... sometimes need the leading '_'. - */ -#ifdef BINARY_OPEN -#ifndef O_BINARY -#ifdef _O_BINARY -#define O_BINARY _O_BINARY -#endif /*_O_BINARY*/ -#endif /*!O_BINARY*/ -#endif /*BINARY_OPEN*/ - -/* Our XML namespace. - */ -#define NAMESPACE "http://www.vips.ecs.soton.ac.uk/vips" - -/* mmap() whole vs. window threshold ... an int, so we can tune easily from a - * debugger. - */ -#ifdef DEBUG -int im__mmap_limit = 1; -#else -int im__mmap_limit = IM__MMAP_LIMIT; -#endif /*DEBUG*/ - -/* Sort of open for read for image files. - */ -static int -im__open_image_file( const char *filename ) -{ - int fd; - - /* Try to open read-write, so that calls to im_makerw() will work. - * When we later mmap this file, we set read-only, so there - * is little danger of scrubbing over files we own. - */ -#ifdef BINARY_OPEN - if( (fd = open( filename, O_RDWR | O_BINARY )) == -1 ) { -#else /*BINARY_OPEN*/ - if( (fd = open( filename, O_RDWR )) == -1 ) { -#endif /*BINARY_OPEN*/ - /* Open read-write failed. Fall back to open read-only. - */ -#ifdef BINARY_OPEN - if( (fd = open( filename, O_RDONLY | O_BINARY )) == -1 ) { -#else /*BINARY_OPEN*/ - if( (fd = open( filename, O_RDONLY )) == -1 ) { -#endif /*BINARY_OPEN*/ - im_error( "im__open_image_file", - _( "unable to open \"%s\", %s" ), - filename, strerror( errno ) ); - return( -1 ); - } - } - - return( fd ); -} - -/* Predict the size of the header plus pixel data. Don't use off_t, - * it's sometimes only 32 bits (eg. on many windows build environments) and we - * want to always be 64 bit. - */ -static gint64 -im__image_pixel_length( IMAGE *im ) -{ - gint64 psize; - - switch( im->Coding ) { - case IM_CODING_LABQ: - case IM_CODING_NONE: - psize = (gint64) IM_IMAGE_SIZEOF_LINE( im ) * im->Ysize; - break; - - default: - psize = im->Length; - break; - } - - return( psize + im->sizeof_header ); -} - -/* Read short/int/float LSB and MSB first. - */ -void -im__read_4byte( int msb_first, unsigned char *to, unsigned char **from ) -{ - unsigned char *p = *from; - int out; - - if( msb_first ) - out = p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3]; - else - out = p[3] << 24 | p[2] << 16 | p[1] << 8 | p[0]; - - *from += 4; - *((guint32 *) to) = out; -} - -void -im__read_2byte( int msb_first, unsigned char *to, unsigned char **from ) -{ - int out; - unsigned char *p = *from; - - if( msb_first ) - out = p[0] << 8 | p[1]; - else - out = p[1] << 8 | p[0]; - - *from += 2; - *((guint16 *) to) = out; -} - -/* We always write in native byte order. - */ -void -im__write_4byte( unsigned char **to, unsigned char *from ) -{ - *((guint32 *) *to) = *((guint32 *) from); - *to += 4; -} - -void -im__write_2byte( unsigned char **to, unsigned char *from ) -{ - *((guint16 *) *to) = *((guint16 *) from); - *to += 2; -} - -/* offset, read, write functions. - */ -typedef struct _FieldIO { - glong offset; - void (*read)( int msb_first, unsigned char *to, unsigned char **from ); - void (*write)( unsigned char **to, unsigned char *from ); -} FieldIO; - -static FieldIO fields[] = { - { G_STRUCT_OFFSET( IMAGE, Xsize ), - im__read_4byte, im__write_4byte }, - { G_STRUCT_OFFSET( IMAGE, Ysize ), - im__read_4byte, im__write_4byte }, - { G_STRUCT_OFFSET( IMAGE, Bands ), - im__read_4byte, im__write_4byte }, - { G_STRUCT_OFFSET( IMAGE, Bbits ), - im__read_4byte, im__write_4byte }, - { G_STRUCT_OFFSET( IMAGE, BandFmt ), - im__read_4byte, im__write_4byte }, - { G_STRUCT_OFFSET( IMAGE, Coding ), - im__read_4byte, im__write_4byte }, - { G_STRUCT_OFFSET( IMAGE, Type ), - im__read_4byte, im__write_4byte }, - { G_STRUCT_OFFSET( IMAGE, Xres ), - im__read_4byte, im__write_4byte }, - { G_STRUCT_OFFSET( IMAGE, Yres ), - im__read_4byte, im__write_4byte }, - { G_STRUCT_OFFSET( IMAGE, Length ), - im__read_4byte, im__write_4byte }, - { G_STRUCT_OFFSET( IMAGE, Compression ), - im__read_2byte, im__write_2byte }, - { G_STRUCT_OFFSET( IMAGE, Level ), - im__read_2byte, im__write_2byte }, - { G_STRUCT_OFFSET( IMAGE, Xoffset ), - im__read_4byte, im__write_4byte }, - { G_STRUCT_OFFSET( IMAGE, Yoffset ), - im__read_4byte, im__write_4byte } -}; - -static int -im__read_header_bytes( IMAGE *im, unsigned char *from ) -{ - int msb_first; - int i; - - im__read_4byte( 1, (unsigned char *) &im->magic, &from ); - if( im->magic != IM_MAGIC_INTEL && im->magic != IM_MAGIC_SPARC ) { - im_error( "im_open", _( "\"%s\" is not a VIPS image" ), - im->filename ); - return( -1 ); - } - msb_first = im->magic == IM_MAGIC_SPARC; - - for( i = 0; i < IM_NUMBER( fields ); i++ ) - fields[i].read( msb_first, - &G_STRUCT_MEMBER( unsigned char, im, fields[i].offset ), - &from ); - - /* Set this ourselves ... bbits is deprecated in the file format. - */ - im->Bbits = im_bits_of_fmt( im->BandFmt ); - - return( 0 ); -} - -int -im__write_header_bytes( IMAGE *im, unsigned char *to ) -{ - guint32 magic; - int i; - unsigned char *q; - - /* Always write the magic number MSB first. - */ - magic = im_amiMSBfirst() ? IM_MAGIC_SPARC : IM_MAGIC_INTEL; - to[0] = magic >> 24; - to[1] = magic >> 16; - to[2] = magic >> 8; - to[3] = magic; - q = to + 4; - - for( i = 0; i < IM_NUMBER( fields ); i++ ) - fields[i].write( &q, - &G_STRUCT_MEMBER( unsigned char, im, - fields[i].offset ) ); - - /* Pad spares with zeros. - */ - while( q - to < im->sizeof_header ) - *q++ = 0; - - return( 0 ); -} - -/* Read a chunk of an fd into memory. Add a '\0' at the end. - */ -static char * -read_chunk( int fd, gint64 offset, size_t length ) -{ - char *buf; - - if( im__seek( fd, offset ) ) - return( NULL ); - if( !(buf = im_malloc( NULL, length + 1 )) ) - return( NULL ); - if( read( fd, buf, length ) != length ) { - im_free( buf ); - im_error( "im_readhist", _( "unable to read history" ) ); - return( NULL ); - } - buf[length] = '\0'; - - return( buf ); -} - -/* Does it look like an image has an extension block? - */ -static int -im__has_extension_block( IMAGE *im ) -{ - gint64 length; - gint64 psize; - - psize = im__image_pixel_length( im ); - if( (length = im_file_length( im->fd )) == -1 ) - return( 0 ); - - return( length - psize > 0 ); -} - -/* Read everything after the pixels into memory. - */ -static void * -im__read_extension_block( IMAGE *im, int *size ) -{ - gint64 length; - gint64 psize; - void *buf; - - psize = im__image_pixel_length( im ); - if( (length = im_file_length( im->fd )) == -1 ) - return( NULL ); - if( length - psize > 10 * 1024 * 1024 ) { - im_error( "im_readhist", - _( "more than a 10 megabytes of XML? " - "sufferin' succotash!" ) ); - return( NULL ); - } - if( length - psize == 0 ) - return( NULL ); - if( !(buf = read_chunk( im->fd, psize, length - psize )) ) - return( NULL ); - if( size ) - *size = length - psize; - -#ifdef DEBUG - printf( "im__read_extension_block: read %d bytes from %s\n", - (int) (length - psize), im->filename ); - printf( "data: \"%s\"\n", (char *) buf ); -#endif /*DEBUG*/ - - return( buf ); -} - -/* Read everything after the pixels into memory. - - FIXME ... why can't we use xmlParserInputBufferCreateFd and parse - directly from the fd rather than having to read the stupid thing into - memory - - the libxml API docs are impossible to decipher - - */ -static xmlDoc * -read_xml( IMAGE *im ) -{ - void *buf; - int size; - xmlDoc *doc; - xmlNode *node; - - if( !(buf = im__read_extension_block( im, &size )) ) - return( NULL ); - if( !(doc = xmlParseMemory( buf, size )) ) { - im_free( buf ); - return( NULL ); - } - im_free( buf ); - if( !(node = xmlDocGetRootElement( doc )) || - !node->nsDef || - !im_isprefix( NAMESPACE, (char *) node->nsDef->href ) ) { - im_error( "im__readhist", _( "incorrect namespace in XML" ) ); - xmlFreeDoc( doc ); - return( NULL ); - } - -#ifdef DEBUG - printf( "read_xml: namespace == %s\n", node->nsDef->href ); -#endif /*DEBUG*/ - - return( doc ); -} - -/* Find the first child node with a name. - */ -static xmlNode * -get_node( xmlNode *base, const char *name ) -{ - xmlNode *i; - - for( i = base->children; i; i = i->next ) - if( strcmp( (char *) i->name, name ) == 0 ) - return( i ); - - return( NULL ); -} - -/* Read a string property to a buffer. TRUE for success. - */ -static int -get_sprop( xmlNode *xnode, const char *name, char *buf, int sz ) -{ - char *value = (char *) xmlGetProp( xnode, (xmlChar *) name ); - - if( !value ) - return( 0 ); - - im_strncpy( buf, value, sz ); - IM_FREEF( xmlFree, value ); - - return( 1 ); -} - -/* Chop history into lines, add each one as a refstring. - */ -static void -set_history( IMAGE *im, char *history ) -{ - GSList *history_list; - char *p, *q; - - /* There can be history there already if we're rewinding. - */ - IM_FREEF( im__gslist_gvalue_free, im->history_list ); - - history_list = NULL; - - for( p = history; *p; p = q ) { - if( (q = strchr( p, '\n' )) ) - *q = '\0'; - else - q = p + strlen( p ); - - history_list = g_slist_prepend( history_list, - im__gvalue_ref_string_new( p ) ); - } - - im->history_list = g_slist_reverse( history_list ); -} - -/* Load header fields. - */ -static int -rebuild_header_builtin( IMAGE *im, xmlNode *i ) -{ - char name[256]; - - if( get_sprop( i, "name", name, 256 ) ) { - if( strcmp( name, "Hist" ) == 0 ) { - char *history; - - /* Have to take (another) copy, since we need to free - * with xmlFree(). - */ - history = (char *) xmlNodeGetContent( i ); - set_history( im, history ); - xmlFree( history ); - } - } - - return( 0 ); -} - -/* Load meta fields. - */ -static int -rebuild_header_meta( IMAGE *im, xmlNode *i ) -{ - char name[256]; - char type[256]; - - if( get_sprop( i, "name", name, 256 ) && - get_sprop( i, "type", type, 256 ) ) { - GType gtype = g_type_from_name( type ); - - /* Can we convert from IM_SAVE_STRING to type? - */ - if( gtype && - g_value_type_transformable( - IM_TYPE_SAVE_STRING, gtype ) ) { - char *content; - GValue save_value = { 0 }; - GValue value = { 0 }; - - content = (char *) xmlNodeGetContent( i ); - g_value_init( &save_value, IM_TYPE_SAVE_STRING ); - im_save_string_set( &save_value, content ); - xmlFree( content ); - - g_value_init( &value, gtype ); - if( !g_value_transform( &save_value, &value ) ) { - g_value_unset( &save_value ); - im_error( "im__readhist", _( "error " - "transforming from save format" ) ); - return( -1 ); - } - if( im_meta_set( im, name, &value ) ) { - g_value_unset( &save_value ); - g_value_unset( &value ); - return( -1 ); - } - g_value_unset( &save_value ); - g_value_unset( &value ); - } - } - - return( 0 ); -} - -static xmlDoc * -get_xml( IMAGE *im ) -{ - if( im_header_get_type( im, IM_META_XML ) ) { - xmlDoc *doc; - - if( im_meta_get_area( im, IM_META_XML, (void *) &doc ) ) - return( NULL ); - - return( doc ); - } - - return( NULL ); -} - -/* Rebuild header fields that depend on stuff saved in xml. - */ -static int -rebuild_header( IMAGE *im ) -{ - xmlDoc *doc; - - if( (doc = get_xml( im )) ) { - xmlNode *root; - xmlNode *block; - - if( !(root = xmlDocGetRootElement( doc )) ) - return( -1 ); - if( (block = get_node( root, "header" )) ) { - xmlNode *i; - - for( i = block->children; i; i = i->next ) - if( strcmp( (char *) i->name, "field" ) == 0 ) - if( rebuild_header_builtin( im, i ) ) - return( -1 ); - } - if( (block = get_node( root, "meta" )) ) { - xmlNode *i; - - for( i = block->children; i; i = i->next ) - if( strcmp( (char *) i->name, "field" ) == 0 ) - if( rebuild_header_meta( im, i ) ) - return( -1 ); - } - } - - return( 0 ); -} - -/* Called at the end of im__read_header ... get any XML after the pixel data - * and read it in. - */ -static int -im__readhist( IMAGE *im ) -{ - /* Junk any old xml meta. - */ - if( im_header_get_type( im, IM_META_XML ) ) - im_meta_set_area( im, IM_META_XML, NULL, NULL ); - - if( im__has_extension_block( im ) ) { - xmlDoc *doc; - - if( !(doc = read_xml( im )) ) - return( -1 ); - if( im_meta_set_area( im, IM_META_XML, - (im_callback_fn) xmlFreeDoc, doc ) ) { - xmlFreeDoc( doc ); - return( -1 ); - } - } - - if( rebuild_header( im ) ) - return( -1 ); - - return( 0 ); -} - -/* Open the filename, read the header, some sanity checking. - */ -static int -im__read_header( IMAGE *image ) -{ - /* We don't use im->sizeof_header here, but we know we're reading a - * VIPS image anyway. - */ - unsigned char header[IM_SIZEOF_HEADER]; - - gint64 length; - gint64 psize; - - image->dtype = IM_OPENIN; - if( (image->fd = im__open_image_file( image->filename )) == -1 ) - return( -1 ); - if( read( image->fd, header, IM_SIZEOF_HEADER ) != IM_SIZEOF_HEADER || - im__read_header_bytes( image, header ) ) { - im_error( "im_openin", - _( "unable to read header for \"%s\", %s" ), - image->filename, strerror( errno ) ); - return( -1 ); - } - - /* Predict and check the file size. - */ - psize = im__image_pixel_length( image ); - if( (length = im_file_length( image->fd )) == -1 ) - return( -1 ); - if( psize > length ) { - im_error( "im_openin", _( "unable to open \"%s\", %s" ), - image->filename, _( "file has been truncated" ) ); - return( -1 ); - } - - /* Set demand style. Allow the most permissive sort. - */ - image->dhint = IM_THINSTRIP; - - /* Set the history part of im descriptor. Don't return an error if this - * fails (due to eg. corrupted XML) because it's probably mostly - * harmless. - */ - if( im__readhist( image ) ) { - im_warn( "im_openin", _( "error reading XML: %s" ), - im_error_buffer() ); - im_error_clear(); - } - - return( 0 ); -} - -/* Open, then mmap() small images, leave large images to have a rolling mmap() - * window for each region. - */ -static int -im_openin( IMAGE *image ) -{ - gint64 size; - -#ifdef DEBUG - char *str; - - if( (str = g_getenv( "IM_MMAP_LIMIT" )) ) { - im__mmap_limit = atoi( str ); - printf( "im_openin: setting maplimit to %d from environment\n", - im__mmap_limit ); - } -#endif /*DEBUG*/ - - if( im__read_header( image ) ) - return( -1 ); - - size = (gint64) IM_IMAGE_SIZEOF_LINE( image ) * image->Ysize + - image->sizeof_header; - if( size < im__mmap_limit ) { - if( im_mapfile( image ) ) - return( -1 ); - image->data = image->baseaddr + image->sizeof_header; - image->dtype = IM_MMAPIN; - -#ifdef DEBUG - printf( "im_openin: completely mmap()ing \"%s\": it's small\n", - image->filename ); -#endif /*DEBUG*/ - } - else { -#ifdef DEBUG - printf( "im_openin: delaying mmap() of \"%s\": it's big!\n", - image->filename ); -#endif /*DEBUG*/ - } - - return( 0 ); -} - -/* Open, then mmap() read/write. - */ -static int -im_openinrw( IMAGE *image ) -{ - if( im__read_header( image ) ) - return( -1 ); - if( im_mapfilerw( image ) ) - return( -1 ); - image->data = image->baseaddr + image->sizeof_header; - image->dtype = IM_MMAPINRW; - -#ifdef DEBUG - printf( "im_openin: completely mmap()ing \"%s\" read-write\n", - image->filename ); -#endif /*DEBUG*/ - - return( 0 ); -} - -/* Open a VIPS image for reading and byte-swap the image data if necessary. A - * ":w" at the end of the filename means we open read-write. - */ -IMAGE * -im_vips_open( const char *filename ) -{ - char name[FILENAME_MAX]; - char mode[FILENAME_MAX]; - IMAGE *im; - - im_filename_split( filename, name, mode ); - - if( !(im = im_init( name )) ) - return( NULL ); - if( mode[0] == 'w' ) { - if( im_openinrw( im ) ) { - im_close( im ); - return( NULL ); - } - if( im->Bbits != IM_BBITS_BYTE && - im_isMSBfirst( im ) != im_amiMSBfirst() ) { - im_close( im ); - im_error( "im_open_vips", _( "open for read-write for " - "native format images only" ) ); - return( NULL ); - } - } - else { - if( im_openin( im ) ) { - im_close( im ); - return( NULL ); - } - } - - /* Not in native format? - */ - if( im_isMSBfirst( im ) != im_amiMSBfirst() ) { - /* Does it need swapping? - */ - switch( im->Coding ) { - case IM_CODING_LABQ: - break; - - case IM_CODING_NONE: - if( im->BandFmt != IM_BANDFMT_CHAR && - im->BandFmt != IM_BANDFMT_UCHAR ) { - IMAGE *im2; - - /* Needs swapping :( make a little pipeline up - * to do this for us. - */ - if( !(im2 = im_open( filename, "p" )) ) { - im_close( im ); - return( NULL ); - } - if( im_add_close_callback( im2, - (im_callback_fn)im_close, im, NULL ) ) { - im_close( im ); - im_close( im2 ); - return( NULL ); - } - if( im_copy_swap( im, im2 ) ) { - im_close( im2 ); - return( NULL ); - } - im = im2; - } - break; - - default: - im_close( im ); - im_error( "im_open", _( "unknown coding type" ) ); - return( NULL ); - } - } - - return( im ); -} - -IMAGE * -im_vips_openout( const char *filename ) -{ - IMAGE *image; - - if( !(image = im_init( filename )) ) - return( NULL ); - image->dtype = IM_OPENOUT; - - return( image ); -} - diff --git a/libsrc/iofuncs/package.c b/libsrc/iofuncs/package.c index 28792c63..9e86645c 100644 --- a/libsrc/iofuncs/package.c +++ b/libsrc/iofuncs/package.c @@ -61,7 +61,6 @@ extern im_package im__boolean; extern im_package im__colour; extern im_package im__conversion; extern im_package im__convolution; -extern im_package im__format; extern im_package im__freq_filt; extern im_package im__histograms_lut; extern im_package im__inplace; @@ -72,8 +71,6 @@ extern im_package im__other; extern im_package im__relational; extern im_package im__video; -extern im_format_package im__format_format; - /* im_guess_prefix() args. */ static im_arg_desc guess_prefix_args[] = { @@ -408,7 +405,6 @@ static im_package *built_in[] = { &im__colour, &im__conversion, &im__convolution, - &im__format, &im__freq_filt, &im__histograms_lut, &im__inplace, @@ -421,56 +417,12 @@ static im_package *built_in[] = { &im__video }; -/* List of loaded formats. - */ -static GSList *format_list = NULL; - -static gint -format_compare( im_format *a, im_format *b ) -{ - return( a->priority - b->priority ); -} - -/* Sort the format list after a change. - */ -static void -format_sort( void ) -{ - format_list = g_slist_sort( format_list, - (GCompareFunc) format_compare ); -} - -/* Remove a package of formats. - */ -static void -format_remove( im_format_package *format ) -{ - int i; - - for( i = 0; i < format->nfuncs; i++ ) - format_list = g_slist_remove( format_list, format->table[i] ); - format_sort(); -} - -/* Add a package of formats. - */ -static void -format_add( im_format_package *format ) -{ - int i; - - for( i = 0; i < format->nfuncs; i++ ) - format_list = g_slist_prepend( format_list, format->table[i] ); - format_sort(); -} - /* How we represent a loaded plugin. */ typedef struct _Plugin { GModule *module; /* As loaded by g_module_open() */ char *name; /* Name we loaded */ im_package *pack; /* Package table */ - im_format_package *format; /* Package format table */ } Plugin; /* List of loaded plugins. @@ -484,8 +436,6 @@ plugin_free( Plugin *plug ) { char *name = plug->name ? plug->name : ""; - if( plug->format ) - format_remove( plug->format ); if( plug->module ) { if( !g_module_close( plug->module ) ) { im_error( "plugin", @@ -525,7 +475,6 @@ im_load_plugin( const char *name ) plug->module = NULL; plug->name = NULL; plug->pack = NULL; - plug->format = NULL; plugin_list = g_slist_prepend( plugin_list, plug ); /* Attach name. @@ -560,44 +509,22 @@ im_load_plugin( const char *name ) return( NULL ); } - /* The format table is optional. - */ - if( !g_module_symbol( plug->module, - "format_table", (gpointer *) ((void *) &plug->format) ) ) - plug->format = NULL; - /* Sanity check. */ if( !plug->pack->name || plug->pack->nfuncs < 0 || plug->pack->nfuncs > 10000 ) { im_error( "plugin", - _( "corrupted package table in plugin \"%s\"" ), name ); + _( "corrupted package table in plugin \"%s\"" ), + name ); plugin_free( plug ); return( NULL ); } - if( plug->format ) { - if( !plug->format->name || plug->format->nfuncs < 0 || - plug->format->nfuncs > 10000 ) { - - im_error( "plugin", - _( "corrupted format table in plugin \"%s\"" ), - name ); - plugin_free( plug ); - - return( NULL ); - } - } #ifdef DEBUG printf( "added package \"%s\" ...\n", plug->pack->name ); #endif /*DEBUG*/ - /* Add any formats to our format list and sort again. - */ - if( plug->format ) - format_add( plug->format ); - return( plug->pack ); } @@ -771,89 +698,6 @@ im_package_of_function( const char *name ) return( pack ); } -/* Map a function over all formats. - */ -void * -im_map_formats( VSListMap2Fn fn, void *a, void *b ) -{ - return( im_slist_map2( format_list, fn, a, b ) ); -} - -/* Can this format open this file? - */ -static void * -format_for_file_sub( im_format *format, - const char *filename, const char *name ) -{ - if( format->is_a ) { - if( format->is_a( name ) ) - return( format ); - } - else if( im_filename_suffix_match( name, format->suffs ) ) - return( format ); - - return( NULL ); -} - -im_format * -im_format_for_file( const char *filename ) -{ - char name[FILENAME_MAX]; - char options[FILENAME_MAX]; - im_format *format; - - /* Break any options off the name ... eg. "fred.tif:jpeg,tile" - * etc. - */ - im_filename_split( filename, name, options ); - - if( !im_existsf( "%s", name ) ) { - im_error( "im_format_for_file", - _( "\"%s\" is not readable" ), name ); - return( NULL ); - } - - format = (im_format *) im_map_formats( - (VSListMap2Fn) format_for_file_sub, - (void *) filename, (void *) name ); - - if( !format ) { - im_error( "im_format_for_file", - _( "\"%s\" is not in a supported format" ), name ); - return( NULL ); - } - - return( format ); -} - -/* Can we write this filename with this format? - */ -static void * -format_for_name_sub( im_format *format, - const char *filename, const char *name ) -{ - if( im_filename_suffix_match( name, format->suffs ) ) - return( format ); - - return( NULL ); -} - -im_format * -im_format_for_name( const char *filename ) -{ - char name[FILENAME_MAX]; - char options[FILENAME_MAX]; - - /* Break any options off the name ... eg. "fred.tif:jpeg,tile" - * etc. - */ - im_filename_split( filename, name, options ); - - return( (im_format *) im_map_formats( - (VSListMap2Fn) format_for_name_sub, - (void *) filename, (void *) name ) ); -} - /* Free any store we allocated for the argument list. */ int diff --git a/libsrc/iofuncs/predicate.c b/libsrc/iofuncs/predicate.c index 287b9631..45b273fa 100644 --- a/libsrc/iofuncs/predicate.c +++ b/libsrc/iofuncs/predicate.c @@ -24,8 +24,6 @@ * - added exr * 3/8/07 * - cleanups - * 22/5/08 - * - image format stuff broken out */ /* @@ -76,6 +74,14 @@ #include +#ifdef HAVE_TIFF +#include +#endif /*HAVE_TIFF*/ + +#ifdef HAVE_PNG +#include +#endif /*HAVE_PNG*/ + #ifdef WITH_DMALLOC #include #endif /*WITH_DMALLOC*/ @@ -235,6 +241,195 @@ im_iscomplex( IMAGE *im ) } } +/* Read a few bytes from the start of a file. For sniffing file types. + */ +static int +get_bytes( const char *filename, unsigned char buf[], int len ) +{ + int fd; + + /* File may not even exist (for tmp images for example!) + * so no hasty messages. And the file might be truncated, so no error + * on read either. + */ +#ifdef BINARY_OPEN + if( (fd = open( filename, O_RDONLY | O_BINARY )) == -1 ) +#else /*BINARY_OPEN*/ + if( (fd = open( filename, O_RDONLY )) == -1 ) +#endif /*BINARY_OPEN*/ + return( 0 ); + if( read( fd, buf, len ) != len ) { + close( fd ); + return( 0 ); + } + close( fd ); + + return( 1 ); +} + +int +im_istiff( const char *filename ) +{ + unsigned char buf[2]; + + if( get_bytes( filename, buf, 2 ) ) + if( (buf[0] == 'M' && buf[1] == 'M') || + (buf[0] == 'I' && buf[1] == 'I') ) + return( 1 ); + + return( 0 ); +} + +#ifdef HAVE_PNG +int +im_ispng( const char *filename ) +{ + unsigned char buf[8]; + + return( get_bytes( filename, buf, 8 ) && + !png_sig_cmp( buf, 0, 8 ) ); +} +#else /*HAVE_PNG*/ +int +im_ispng( const char *filename ) +{ + return( 0 ); +} +#endif /*HAVE_PNG*/ + +#ifdef HAVE_MAGICK +int +im_ismagick( const char *filename ) +{ + IMAGE *im; + int result; + + if( !(im = im_open( "dummy", "p" )) ) + return( -1 ); + result = im_magick2vips_header( filename, im ); + im_clear_error_string(); + im_close( im ); + + return( result == 0 ); +} +#else /*HAVE_MAGICK*/ +int +im_ismagick( const char *filename ) +{ + return( 0 ); +} +#endif /*HAVE_MAGICK*/ + +int +im_isppm( const char *filename ) +{ + unsigned char buf[2]; + + if( get_bytes( filename, buf, 2 ) ) + if( buf[0] == 'P' && (buf[1] >= '1' || buf[1] <= '6') ) + return( 1 ); + + return( 0 ); +} + +#ifdef HAVE_TIFF + +/* Handle TIFF errors here. + */ +static void +vhandle( char *module, char *fmt, ... ) +{ + va_list ap; + + im_errormsg( "TIFF error in \"%s\": ", module ); + + va_start( ap, fmt ); + im_verrormsg( fmt, ap ); + va_end( ap ); +} + +int +im_istifftiled( const char *filename ) +{ + TIFF *tif; + int tiled; + + /* Override the default TIFF error handler. + */ + TIFFSetErrorHandler( (TIFFErrorHandler) vhandle ); + +#ifdef BINARY_OPEN + if( !(tif = TIFFOpen( filename, "rb" )) ) { +#else /*BINARY_OPEN*/ + if( !(tif = TIFFOpen( filename, "r" )) ) { +#endif /*BINARY_OPEN*/ + /* Not a TIFF file ... return False. + */ + im_clear_error_string(); + return( 0 ); + } + tiled = TIFFIsTiled( tif ); + TIFFClose( tif ); + + return( tiled ); +} + +#else /*HAVE_TIFF*/ + +int +im_istifftiled( const char *filename ) +{ + return( 0 ); +} + +#endif /*HAVE_TIFF*/ + +int +im_isjpeg( const char *filename ) +{ + unsigned char buf[2]; + + if( get_bytes( filename, buf, 2 ) ) + if( (int) buf[0] == 0xff && (int) buf[1] == 0xd8 ) + return( 1 ); + + return( 0 ); +} + +int +im_isvips( const char *filename ) +{ + unsigned char buf[4]; + + if( get_bytes( filename, buf, 4 ) ) { + if( buf[0] == 0x08 && buf[1] == 0xf2 && + buf[2] == 0xa6 && buf[3] == 0xb6 ) + /* SPARC-order VIPS image. + */ + return( 1 ); + else if( buf[3] == 0x08 && buf[2] == 0xf2 && + buf[1] == 0xa6 && buf[0] == 0xb6 ) + /* INTEL-order VIPS image. + */ + return( 1 ); + } + + return( 0 ); +} + +int +im_isexr( const char *filename ) +{ + unsigned char buf[4]; + + if( get_bytes( filename, buf, 4 ) ) + if( buf[0] == 0x76 && buf[1] == 0x2f && + buf[2] == 0x31 && buf[3] == 0x01 ) + return( 1 ); + + return( 0 ); +} + /* Test for file exists. */ int @@ -319,7 +514,7 @@ im_ispoweroftwo( int p ) /* Count set bits. Could use a LUT, I guess. */ for( i = 0, n = 0; p; i++, p >>= 1 ) - if( p & 1 ) + if( (p & 1) == 1 ) n++; /* Should be just one set bit.