move JPEG into it's own module
authorTony Cook <tony@develop=help.com>
Tue, 31 Aug 2010 13:03:23 +0000 (13:03 +0000)
committerTony Cook <tony@develop=help.com>
Tue, 31 Aug 2010 13:03:23 +0000 (13:03 +0000)
33 files changed:
Imager.pm
Imager.xs
JPEG/JPEG.pm [new file with mode: 0644]
JPEG/JPEG.xs [new file with mode: 0644]
JPEG/Makefile.PL [new file with mode: 0644]
JPEG/imexif.c [new file with mode: 0644]
JPEG/imexif.h [new file with mode: 0644]
JPEG/imjpeg.c [new file with mode: 0644]
JPEG/imjpeg.h [new file with mode: 0644]
JPEG/t/t00load.t [new file with mode: 0644]
JPEG/t/t10jpeg.t [new file with mode: 0644]
JPEG/testimg/209_yonge.jpg [new file with mode: 0644]
JPEG/testimg/exiftest.jpg [new file with mode: 0755]
JPEG/testimg/scmyk.jpg [new file with mode: 0644]
JPEG/testimg/zerotype.jpg [new file with mode: 0755]
MANIFEST
Makefile.PL
image.c
imexif.c [deleted file]
imexif.h [deleted file]
imext.c
imext.h
imexttypes.h
jpeg.c [deleted file]
lib/Imager/APIRef.pod
paste.im
t/t101jpeg.t [deleted file]
t/t101nojpeg.t
t/t50basicoo.t
testimg/209_yonge.jpg [deleted file]
testimg/exiftest.jpg [deleted file]
testimg/scmyk.jpg [deleted file]
testimg/zerotype.jpg [deleted file]

index 89bbf44..88297ec 100644 (file)
--- a/Imager.pm
+++ b/Imager.pm
@@ -74,9 +74,6 @@ use Imager::Font;
                i_tt_text
                i_tt_bbox
 
-               i_readjpeg_wiol
-               i_writejpeg_wiol
-
                i_readpnm_wiol
                i_writeppm_wiol
 
@@ -1368,16 +1365,6 @@ sub read {
     return;
   }
 
-  # Setup data source
-  if ( $input{'type'} eq 'jpeg' ) {
-    ($self->{IMG},$self->{IPTCRAW}) = i_readjpeg_wiol( $IO );
-    if ( !defined($self->{IMG}) ) {
-      $self->{ERRSTR}=$self->_error_as_msg(); return undef;
-    }
-    $self->{DEBUG} && print "loading a jpeg file\n";
-    return $self;
-  }
-
   my $allow_incomplete = $input{allow_incomplete};
   defined $allow_incomplete or $allow_incomplete = 0;
 
index a4f11c7..696eebf 100644 (file)
--- a/Imager.xs
+++ b/Imager.xs
@@ -2334,57 +2334,11 @@ i_tt_glyph_name(handle, text_sv, utf8 = 0)
 
 #endif 
 
-
-#ifdef HAVE_LIBJPEG
-undef_int
-i_writejpeg_wiol(im, ig, qfactor)
-    Imager::ImgRaw     im
-        Imager::IO     ig
-              int     qfactor
-
-
-void
-i_readjpeg_wiol(ig)
-        Imager::IO     ig
-            PREINIT:
-             char*    iptc_itext;
-              int     tlength;
-            i_img*    rimg;
-                SV*    r;
-            PPCODE:
-             iptc_itext = NULL;
-             rimg = i_readjpeg_wiol(ig,-1,&iptc_itext,&tlength);
-             if (iptc_itext == NULL) {
-                   r = sv_newmortal();
-                   EXTEND(SP,1);
-                   sv_setref_pv(r, "Imager::ImgRaw", (void*)rimg);
-                   PUSHs(r);
-             } else {
-                   r = sv_newmortal();
-                   EXTEND(SP,2);
-                   sv_setref_pv(r, "Imager::ImgRaw", (void*)rimg);
-                   PUSHs(r);
-                   PUSHs(sv_2mortal(newSVpv(iptc_itext,tlength)));
-                    myfree(iptc_itext);
-             }
-
-int
-i_exif_enabled()
-
-#endif
-
-
 const char *
 i_test_format_probe(ig, length)
         Imager::IO     ig
               int     length
 
-
-
-
-
-
-
 Imager::ImgRaw
 i_readpnm_wiol(ig, allow_incomplete)
         Imager::IO     ig
diff --git a/JPEG/JPEG.pm b/JPEG/JPEG.pm
new file mode 100644 (file)
index 0000000..d43ce15
--- /dev/null
@@ -0,0 +1,89 @@
+package Imager::File::JPEG;
+use strict;
+use Imager;
+use vars qw($VERSION @ISA);
+
+BEGIN {
+  $VERSION = "0.77";
+
+  eval {
+    require XSLoader;
+    XSLoader::load('Imager::File::JPEG', $VERSION);
+    1;
+  } or do {
+    require DynaLoader;
+    push @ISA, 'DynaLoader';
+    bootstrap Imager::File::JPEG $VERSION;
+  };
+}
+
+Imager->register_reader
+  (
+   type=>'jpeg',
+   single => 
+   sub { 
+     my ($im, $io, %hsh) = @_;
+
+     ($im->{IMG},$im->{IPTCRAW}) = i_readjpeg_wiol( $io );
+
+     unless ($im->{IMG}) {
+       $im->_set_error(Imager->_error_as_msg);
+       return;
+     }
+     return $im;
+   },
+  );
+
+Imager->register_writer
+  (
+   type=>'jpeg',
+   single => 
+   sub { 
+     my ($im, $io, %hsh) = @_;
+
+     $im->_set_opts(\%hsh, "i_", $im);
+     $im->_set_opts(\%hsh, "jpeg_", $im);
+     $im->_set_opts(\%hsh, "exif_", $im);
+
+     my $quality = $hsh{jpegquality};
+     defined $quality or $quality = 75;
+
+     if ( !i_writejpeg_wiol($im->{IMG}, $io, $quality)) {
+       $im->_set_error(Imager->_error_as_msg);
+       return;
+     }
+
+     return $im;
+   },
+  );
+
+__END__
+
+=head1 NAME
+
+Imager::File::JPEG - read and write JPEG files
+
+=head1 SYNOPSIS
+
+  use Imager;
+
+  my $img = Imager->new;
+  $img->read(file=>"foo.jpg")
+    or die $img->errstr;
+
+  $img->write(file => "foo.jpg")
+    or die $img->errstr;
+
+=head1 DESCRIPTION
+
+Imager's JPEG support is documented in L<Imager::Files>.
+
+=head1 AUTHOR
+
+Tony Cook <tony@imager.perl.org>
+
+=head1 SEE ALSO
+
+Imager, Imager::Files.
+
+=cut
diff --git a/JPEG/JPEG.xs b/JPEG/JPEG.xs
new file mode 100644 (file)
index 0000000..e1a55f9
--- /dev/null
@@ -0,0 +1,49 @@
+#define PERL_NO_GET_CONTEXT
+#ifdef __cplusplus
+extern "C" {
+#endif
+#include "EXTERN.h"
+#include "perl.h"
+#include "XSUB.h"
+#include "imext.h"
+#include "imperl.h"
+#include "imjpeg.h"
+
+DEFINE_IMAGER_CALLBACKS;
+
+MODULE = Imager::File::JPEG  PACKAGE = Imager::File::JPEG
+
+undef_int
+i_writejpeg_wiol(im, ig, qfactor)
+    Imager::ImgRaw     im
+        Imager::IO     ig
+              int     qfactor
+
+
+void
+i_readjpeg_wiol(ig)
+        Imager::IO     ig
+            PREINIT:
+             char*    iptc_itext;
+              int     tlength;
+            i_img*    rimg;
+                SV*    r;
+            PPCODE:
+             iptc_itext = NULL;
+             rimg = i_readjpeg_wiol(ig,-1,&iptc_itext,&tlength);
+             if (iptc_itext == NULL) {
+                   r = sv_newmortal();
+                   EXTEND(SP,1);
+                   sv_setref_pv(r, "Imager::ImgRaw", (void*)rimg);
+                   PUSHs(r);
+             } else {
+                   r = sv_newmortal();
+                   EXTEND(SP,2);
+                   sv_setref_pv(r, "Imager::ImgRaw", (void*)rimg);
+                   PUSHs(r);
+                   PUSHs(sv_2mortal(newSVpv(iptc_itext,tlength)));
+                    myfree(iptc_itext);
+             }
+
+BOOT:
+       PERL_INITIALIZE_IMAGER_CALLBACKS;
diff --git a/JPEG/Makefile.PL b/JPEG/Makefile.PL
new file mode 100644 (file)
index 0000000..7606b43
--- /dev/null
@@ -0,0 +1,113 @@
+#!perl -w
+use strict;
+use ExtUtils::MakeMaker qw(WriteMakefile WriteEmptyMakefile);
+use Getopt::Long;
+use Config;
+
+my $verbose = $ENV{IM_VERBOSE};
+my @libpaths;
+my @incpaths;
+
+GetOptions("incpath=s", \@incpaths,
+           "libpath=s" => \@libpaths,
+           "verbose|v" => \$verbose);
+
+our $BUILDING_IMAGER;
+
+my $MM_ver = eval $ExtUtils::MakeMaker::VERSION;
+
+my %opts = 
+  (
+   NAME => 'Imager::File::JPEG',
+   VERSION_FROM => 'JPEG.pm',
+   OBJECT => 'JPEG.o imjpeg.o imexif.o',
+  );
+
+my @inc;
+if ($BUILDING_IMAGER) {
+  push @inc, "-I..";
+  unshift @INC, "../lib";
+}
+else {
+  unshift @INC, "inc";
+  print "JPEG: building independently\n";
+  require Imager::ExtUtils;
+  push @inc, Imager::ExtUtils->includes;
+  $opts{TYPEMAPS} = [ Imager::ExtUtils->typemap ];
+
+  # Imager required configure through use
+  my @Imager_req = ( Imager => "0.77" );
+  if ($MM_ver >= 6.46) {
+    $opts{META_MERGE} =
+      {
+       configure_requires => 
+       {
+       @Imager_req,
+       },
+       build_requires => 
+       {
+       @Imager_req,
+       "Test::More" => "0.47",
+       },
+       resources =>
+       {
+       homepage => "http://imager.perl.org/",
+       repository =>
+       {
+        url => "http://imager.perl.org/svn/trunk/Imager-File-JPEG",
+        web => "http://imager.perl.org/svnweb/public/browse/trunk/Imager-File-JPEG",
+        type => "svn",
+       },
+       },
+      };
+    $opts{PREREQ_PM} =
+      {
+       @Imager_req,
+      };
+  }
+}
+
+require Imager::Probe;
+
+my %probe =
+  (
+   name => "JPEG",
+   inccheck => sub { -e File::Spec->catfile($_[0], "jpeglib.h") },
+   libbase => "jpeg",
+   #testcode => _jpeg_test_code(),
+   #testcodeheaders => [ "stdio.h", "stddef.h", "jpeglib.h", "jerror.h" ],
+   incpath => join($Config{path_sep}, @incpaths),
+   libpath => join($Config{path_sep}, @libpaths),
+  );
+
+my $probe_res = Imager::Probe->probe(\%probe);
+if ($probe_res) {
+  push @inc, $probe_res->{INC};
+  $opts{LIBS} = $probe_res->{LIBS};
+  
+  $opts{INC} = "@inc";
+  
+  if ($MM_ver > 6.06) {
+    $opts{AUTHOR} = 'Tony Cook <tony@imager.perl.org>';
+    $opts{ABSTRACT} = 'JPEG Image file support';
+  }
+  
+  WriteMakefile(%opts);
+}
+else {
+  if ($BUILDING_IMAGER) {
+    WriteEmptyMakefile(%opts);
+  }
+  else {
+    # fail in good way
+    die "OS unsupported: JPEG libraries or headers not found\n";
+  }
+}
+
+sub _jpeg_test_code {
+  return <<'CODE';
+
+/*fprintf(stderr, "PNG: library version %ld, header version %ld\n", (long)png_access_version_number(),  (long)PNG_LIBPNG_VER);*/
+return 0;
+CODE
+}
diff --git a/JPEG/imexif.c b/JPEG/imexif.c
new file mode 100644 (file)
index 0000000..648e2de
--- /dev/null
@@ -0,0 +1,1586 @@
+#include "imext.h"
+#include "imexif.h"
+#include <stdlib.h>
+#include <float.h>
+#include <string.h>
+
+/*
+=head1 NAME
+
+imexif.c - EXIF support for Imager
+
+=head1 SYNOPSIS
+
+  if (i_int_decode_exif(im, app1data, app1datasize)) {
+    // exif block seen
+  }
+
+=head1 DESCRIPTION
+
+This code provides a basic EXIF data decoder.  It is intended to be
+called from the JPEG reader code when an APP1 data block is found, and
+will set tags in the supplied image.
+
+=cut
+*/
+
+typedef enum tiff_type_tag {
+  tt_intel = 'I',
+  tt_motorola = 'M'
+} tiff_type;
+
+typedef enum {
+  ift_byte = 1,
+  ift_ascii = 2,
+  ift_short = 3,
+  ift_long = 4,
+  ift_rational = 5,
+  ift_sbyte = 6,
+  ift_undefined = 7,
+  ift_sshort = 8,
+  ift_slong = 9,
+  ift_srational = 10,
+  ift_float = 11,
+  ift_double = 12,
+  ift_last = 12 /* keep the same as the highest type code */
+} ifd_entry_type;
+
+static int type_sizes[] =
+  {
+    0, /* not used */
+    1, /* byte */
+    1, /* ascii */
+    2, /* short */
+    4, /* long */
+    8, /* rational */
+    1, /* sbyte */
+    1, /* undefined */
+    2, /* sshort */
+    4, /* slong */
+    8, /* srational */
+    4, /* float */
+    8, /* double */
+  };
+
+typedef struct {
+  int tag;
+  int type;
+  int count;
+  int item_size;
+  int size;
+  int offset;
+} ifd_entry;
+
+typedef struct {
+  int tag;
+  char const *name;
+} tag_map;
+
+typedef struct {
+  int tag;
+  char const *name;
+  tag_map const *map;
+  int map_count;
+} tag_value_map;
+
+#define PASTE(left, right) PASTE_(left, right)
+#define PASTE_(left, right) left##right
+#define QUOTE(value) #value
+
+#define VALUE_MAP_ENTRY(name) \
+  { \
+    PASTE(tag_, name), \
+    "exif_" QUOTE(name) "_name", \
+    PASTE(name, _values), \
+    ARRAY_COUNT(PASTE(name, _values)) \
+  }
+
+/* we don't process every tag */
+#define tag_make 271
+#define tag_model 272
+#define tag_orientation 274
+#define tag_x_resolution 282
+#define tag_y_resolution 283
+#define tag_resolution_unit 296
+#define tag_copyright 33432
+#define tag_software 305
+#define tag_artist 315
+#define tag_date_time 306
+#define tag_image_description 270
+
+#define tag_exif_ifd 34665
+#define tag_gps_ifd 34853
+
+#define resunit_none 1
+#define resunit_inch 2
+#define resunit_centimeter 3
+
+/* tags from the EXIF ifd */
+#define tag_exif_version 0x9000
+#define tag_flashpix_version 0xA000
+#define tag_color_space 0xA001
+#define tag_component_configuration 0x9101
+#define tag_component_bits_per_pixel 0x9102
+#define tag_pixel_x_dimension 0xA002
+#define tag_pixel_y_dimension 0xA003
+#define tag_maker_note 0x927C
+#define tag_user_comment 0x9286
+#define tag_related_sound_file 0xA004
+#define tag_date_time_original 0x9003
+#define tag_date_time_digitized 0x9004
+#define tag_sub_sec_time 0x9290
+#define tag_sub_sec_time_original 0x9291
+#define tag_sub_sec_time_digitized 0x9292
+#define tag_image_unique_id 0xA420
+#define tag_exposure_time 0x829a
+#define tag_f_number 0x829D
+#define tag_exposure_program 0x8822
+#define tag_spectral_sensitivity 0x8824
+#define tag_iso_speed_ratings 0x8827
+#define tag_oecf 0x8828
+#define tag_shutter_speed 0x9201
+#define tag_aperture 0x9202
+#define tag_brightness 0x9203
+#define tag_exposure_bias 0x9204
+#define tag_max_aperture 0x9205
+#define tag_subject_distance 0x9206
+#define tag_metering_mode 0x9207
+#define tag_light_source 0x9208
+#define tag_flash 0x9209
+#define tag_focal_length 0x920a
+#define tag_subject_area 0x9214
+#define tag_flash_energy 0xA20B
+#define tag_spatial_frequency_response 0xA20C
+#define tag_focal_plane_x_resolution 0xA20e
+#define tag_focal_plane_y_resolution 0xA20F
+#define tag_focal_plane_resolution_unit 0xA210
+#define tag_subject_location 0xA214
+#define tag_exposure_index 0xA215
+#define tag_sensing_method 0xA217
+#define tag_file_source 0xA300
+#define tag_scene_type 0xA301
+#define tag_cfa_pattern 0xA302
+#define tag_custom_rendered 0xA401
+#define tag_exposure_mode 0xA402
+#define tag_white_balance 0xA403
+#define tag_digital_zoom_ratio 0xA404
+#define tag_focal_length_in_35mm_film 0xA405
+#define tag_scene_capture_type 0xA406
+#define tag_gain_control 0xA407
+#define tag_contrast 0xA408
+#define tag_saturation 0xA409
+#define tag_sharpness 0xA40A
+#define tag_device_setting_description 0xA40B
+#define tag_subject_distance_range 0xA40C
+
+/* GPS tags */
+#define tag_gps_version_id 0
+#define tag_gps_latitude_ref 1
+#define tag_gps_latitude 2
+#define tag_gps_longitude_ref 3
+#define tag_gps_longitude 4
+#define tag_gps_altitude_ref 5
+#define tag_gps_altitude 6
+#define tag_gps_time_stamp 7
+#define tag_gps_satellites 8
+#define tag_gps_status 9
+#define tag_gps_measure_mode 10
+#define tag_gps_dop 11
+#define tag_gps_speed_ref 12
+#define tag_gps_speed 13
+#define tag_gps_track_ref 14
+#define tag_gps_track 15
+#define tag_gps_img_direction_ref 16
+#define tag_gps_img_direction 17
+#define tag_gps_map_datum 18
+#define tag_gps_dest_latitude_ref 19
+#define tag_gps_dest_latitude 20
+#define tag_gps_dest_longitude_ref 21
+#define tag_gps_dest_longitude 22
+#define tag_gps_dest_bearing_ref 23
+#define tag_gps_dest_bearing 24
+#define tag_gps_dest_distance_ref 25
+#define tag_gps_dest_distance 26
+#define tag_gps_processing_method 27
+#define tag_gps_area_information 28
+#define tag_gps_date_stamp 29
+#define tag_gps_differential 30
+
+/* don't use this on pointers */
+#define ARRAY_COUNT(array) (sizeof(array)/sizeof(*array))
+
+/* in memory tiff structure */
+typedef struct {
+  /* the data we use as a tiff */
+  unsigned char *base;
+  size_t size;
+
+  /* intel or motorola byte order */
+  tiff_type type;
+
+  /* initial ifd offset */
+  unsigned long first_ifd_offset;
+  
+  /* size (in entries) and data */
+  int ifd_size; 
+  ifd_entry *ifd;
+  unsigned long next_ifd;
+} imtiff;
+
+static int tiff_init(imtiff *tiff, unsigned char *base, size_t length);
+static int tiff_load_ifd(imtiff *tiff, unsigned long offset);
+static void tiff_final(imtiff *tiff);
+static void tiff_clear_ifd(imtiff *tiff);
+#if 0 /* currently unused, but that may change */
+static int tiff_get_bytes(imtiff *tiff, unsigned char *to, size_t offset, 
+                         size_t count);
+#endif
+static int tiff_get_tag_double(imtiff *, int index, double *result);
+static int tiff_get_tag_int(imtiff *, int index, int *result);
+static unsigned tiff_get16(imtiff *, unsigned long offset);
+static unsigned tiff_get32(imtiff *, unsigned long offset);
+static int tiff_get16s(imtiff *, unsigned long offset);
+static int tiff_get32s(imtiff *, unsigned long offset);
+static double tiff_get_rat(imtiff *, unsigned long offset);
+static double tiff_get_rats(imtiff *, unsigned long offset);
+static void save_ifd0_tags(i_img *im, imtiff *tiff, unsigned long *exif_ifd_offset, unsigned long *gps_ifd_offset);
+static void save_exif_ifd_tags(i_img *im, imtiff *tiff);
+static void save_gps_ifd_tags(i_img *im, imtiff *tiff);
+static void
+copy_string_tags(i_img *im, imtiff *tiff, tag_map *map, int map_count);
+static void
+copy_int_tags(i_img *im, imtiff *tiff, tag_map *map, int map_count);
+static void
+copy_rat_tags(i_img *im, imtiff *tiff, tag_map *map, int map_count);
+static void
+copy_num_array_tags(i_img *im, imtiff *tiff, tag_map *map, int map_count);
+static void
+copy_name_tags(i_img *im, imtiff *tiff, tag_value_map *map, int map_count);
+static void process_maker_note(i_img *im, imtiff *tiff, unsigned long offset, size_t size);
+
+/*
+=head1 PUBLIC FUNCTIONS
+
+These functions are available to other parts of Imager.  They aren't
+intended to be called from outside of Imager.
+
+=over
+
+=item i_int_decode_exit
+
+i_int_decode_exif(im, data_base, data_size);
+
+The data from data_base for data_size bytes will be scanned for EXIF
+data.
+
+Any data found will be used to set tags in the supplied image.
+
+The intent is that invalid EXIF data will simply fail to set tags, and
+write to the log.  In no case should this code exit when supplied
+invalid data.
+
+Returns true if an Exif header was seen.
+
+*/
+
+int
+i_int_decode_exif(i_img *im, unsigned char *data, size_t length) {
+  imtiff tiff;
+  unsigned long exif_ifd_offset = 0;
+  unsigned long gps_ifd_offset = 0;
+  /* basic checks - must start with "Exif\0\0" */
+
+  if (length < 6 || memcmp(data, "Exif\0\0", 6) != 0) {
+    return 0;
+  }
+
+  data += 6;
+  length -= 6;
+
+  if (!tiff_init(&tiff, data, length)) {
+    mm_log((2, "Exif header found, but no valid TIFF header\n"));
+    return 1;
+  }
+  if (!tiff_load_ifd(&tiff, tiff.first_ifd_offset)) {
+    mm_log((2, "Exif header found, but could not load IFD 0\n"));
+    tiff_final(&tiff);
+    return 1;
+  }
+
+  save_ifd0_tags(im, &tiff, &exif_ifd_offset, &gps_ifd_offset);
+
+  if (exif_ifd_offset) {
+    if (tiff_load_ifd(&tiff, exif_ifd_offset)) {
+      save_exif_ifd_tags(im, &tiff);
+    }
+    else {
+      mm_log((2, "Could not load Exif IFD\n"));
+    }
+  }
+
+  if (gps_ifd_offset) {
+    if (tiff_load_ifd(&tiff, gps_ifd_offset)) {
+      save_gps_ifd_tags(im, &tiff);
+    }
+    else {
+      mm_log((2, "Could not load GPS IFD\n"));
+    }
+  }
+
+  tiff_final(&tiff);
+
+  return 1;
+}
+
+/*
+
+=back
+
+=head1 INTERNAL FUNCTIONS
+
+=head2 EXIF Processing 
+
+=over
+
+=item save_ifd0_tags
+
+save_ifd0_tags(im, tiff, &exif_ifd_offset, &gps_ifd_offset)
+
+Scans the currently loaded IFD for tags expected in IFD0 and sets them
+in the image.
+
+Sets *exif_ifd_offset to the offset of the EXIF IFD if found.
+
+=cut
+
+*/
+
+static tag_map ifd0_string_tags[] =
+  {
+    { tag_make, "exif_make" },
+    { tag_model, "exif_model" },
+    { tag_copyright, "exif_copyright" },
+    { tag_software, "exif_software" },
+    { tag_artist, "exif_artist" },
+    { tag_date_time, "exif_date_time" },
+    { tag_image_description, "exif_image_description" },
+  };
+
+static const int ifd0_string_tag_count = ARRAY_COUNT(ifd0_string_tags);
+
+static tag_map ifd0_int_tags[] =
+  {
+    { tag_orientation, "exif_orientation", },
+    { tag_resolution_unit, "exif_resolution_unit" },
+  };
+
+static const int ifd0_int_tag_count = ARRAY_COUNT(ifd0_int_tags);
+
+static tag_map ifd0_rat_tags[] =
+  {
+    { tag_x_resolution, "exif_x_resolution" },
+    { tag_y_resolution, "exif_y_resolution" },
+  };
+
+static tag_map resolution_unit_values[] =
+  {
+    { 1, "none" },
+    { 2, "inches" },
+    { 3, "centimeters" },
+  };
+
+static tag_value_map ifd0_values[] =
+  {
+    VALUE_MAP_ENTRY(resolution_unit),
+  };
+
+static void
+save_ifd0_tags(i_img *im, imtiff *tiff, unsigned long *exif_ifd_offset,
+              unsigned long *gps_ifd_offset) {
+  int tag_index;
+  int work;
+  ifd_entry *entry;
+
+  for (tag_index = 0, entry = tiff->ifd; 
+       tag_index < tiff->ifd_size; ++tag_index, ++entry) {
+    switch (entry->tag) {
+    case tag_exif_ifd:
+      if (tiff_get_tag_int(tiff, tag_index, &work))
+       *exif_ifd_offset = work;
+      break;
+
+    case tag_gps_ifd:
+      if (tiff_get_tag_int(tiff, tag_index, &work))
+       *gps_ifd_offset = work;
+      break;
+    }
+  }
+
+  copy_string_tags(im, tiff, ifd0_string_tags, ifd0_string_tag_count);
+  copy_int_tags(im, tiff, ifd0_int_tags, ifd0_int_tag_count);
+  copy_rat_tags(im, tiff, ifd0_rat_tags, ARRAY_COUNT(ifd0_rat_tags));
+  copy_name_tags(im, tiff, ifd0_values, ARRAY_COUNT(ifd0_values));
+  /* copy_num_array_tags(im, tiff, ifd0_num_arrays, ARRAY_COUNT(ifd0_num_arrays)); */
+}
+
+/*
+=item save_exif_ifd_tags
+
+save_exif_ifd_tags(im, tiff)
+
+Scans the currently loaded IFD for the tags expected in the EXIF IFD
+and sets them as tags in the image.
+
+=cut
+
+*/
+
+static tag_map exif_ifd_string_tags[] =
+  {
+    { tag_exif_version, "exif_version", },
+    { tag_flashpix_version, "exif_flashpix_version", },
+    { tag_related_sound_file, "exif_related_sound_file", },
+    { tag_date_time_original, "exif_date_time_original", },
+    { tag_date_time_digitized, "exif_date_time_digitized", },
+    { tag_sub_sec_time, "exif_sub_sec_time" },
+    { tag_sub_sec_time_original, "exif_sub_sec_time_original" },
+    { tag_sub_sec_time_digitized, "exif_sub_sec_time_digitized" },
+    { tag_image_unique_id, "exif_image_unique_id" },
+    { tag_spectral_sensitivity, "exif_spectral_sensitivity" },
+  };
+
+static const int exif_ifd_string_tag_count = ARRAY_COUNT(exif_ifd_string_tags);
+
+static tag_map exif_ifd_int_tags[] =
+  {
+    { tag_color_space, "exif_color_space" },
+    { tag_exposure_program, "exif_exposure_program" },
+    { tag_metering_mode, "exif_metering_mode" },
+    { tag_light_source, "exif_light_source" },
+    { tag_flash, "exif_flash" },
+    { tag_focal_plane_resolution_unit, "exif_focal_plane_resolution_unit" },
+    { tag_subject_location, "exif_subject_location" },
+    { tag_sensing_method, "exif_sensing_method" },
+    { tag_custom_rendered, "exif_custom_rendered" },
+    { tag_exposure_mode, "exif_exposure_mode" },
+    { tag_white_balance, "exif_white_balance" },
+    { tag_focal_length_in_35mm_film, "exif_focal_length_in_35mm_film" },
+    { tag_scene_capture_type, "exif_scene_capture_type" },
+    { tag_contrast, "exif_contrast" },
+    { tag_saturation, "exif_saturation" },
+    { tag_sharpness, "exif_sharpness" },
+    { tag_subject_distance_range, "exif_subject_distance_range" },
+  };
+
+
+static const int exif_ifd_int_tag_count = ARRAY_COUNT(exif_ifd_int_tags);
+
+static tag_map exif_ifd_rat_tags[] =
+  {
+    { tag_exposure_time, "exif_exposure_time" },
+    { tag_f_number, "exif_f_number" },
+    { tag_shutter_speed, "exif_shutter_speed" },
+    { tag_aperture, "exif_aperture" },
+    { tag_brightness, "exif_brightness" },
+    { tag_exposure_bias, "exif_exposure_bias" },
+    { tag_max_aperture, "exif_max_aperture" },
+    { tag_subject_distance, "exif_subject_distance" },
+    { tag_focal_length, "exif_focal_length" },
+    { tag_flash_energy, "exif_flash_energy" },
+    { tag_focal_plane_x_resolution, "exif_focal_plane_x_resolution" },
+    { tag_focal_plane_y_resolution, "exif_focal_plane_y_resolution" },
+    { tag_exposure_index, "exif_exposure_index" },
+    { tag_digital_zoom_ratio, "exif_digital_zoom_ratio" },
+    { tag_gain_control, "exif_gain_control" },
+  };
+
+static const int exif_ifd_rat_tag_count = ARRAY_COUNT(exif_ifd_rat_tags);
+
+static tag_map exposure_mode_values[] =
+  {
+    { 0, "Auto exposure" },
+    { 1, "Manual exposure" },
+    { 2, "Auto bracket" },
+  };
+static tag_map color_space_values[] =
+  {
+    { 1, "sRGB" },
+    { 0xFFFF, "Uncalibrated" },
+  };
+
+static tag_map exposure_program_values[] =
+  {
+    { 0, "Not defined" },
+    { 1, "Manual" },
+    { 2, "Normal program" },
+    { 3, "Aperture priority" },
+    { 4, "Shutter priority" },
+    { 5, "Creative program" },
+    { 6, "Action program" },
+    { 7, "Portrait mode" },
+    { 8, "Landscape mode" },
+  };
+
+static tag_map metering_mode_values[] =
+  {
+    { 0, "unknown" },
+    { 1, "Average" },
+    { 2, "CenterWeightedAverage" },
+    { 3, "Spot" },
+    { 4, "MultiSpot" },
+    { 5, "Pattern" },
+    { 6, "Partial" },
+    { 255, "other" },
+  };
+
+static tag_map light_source_values[] =
+  {
+    { 0, "unknown" },
+    { 1, "Daylight" },
+    { 2, "Fluorescent" },
+    { 3, "Tungsten (incandescent light)" },
+    { 4, "Flash" },
+    { 9, "Fine weather" },
+    { 10, "Cloudy weather" },
+    { 11, "Shade" },
+    { 12, "Daylight fluorescent (D 5700 Ð 7100K)" },
+    { 13, "Day white fluorescent (N 4600 Ð 5400K)" },
+    { 14, "Cool white fluorescent (W 3900 Ð 4500K)" },
+    { 15, "White fluorescent (WW 3200 Ð 3700K)" },
+    { 17, "Standard light A" },
+    { 18, "Standard light B" },
+    { 19, "Standard light C" },
+    { 20, "D55" },
+    { 21, "D65" },
+    { 22, "D75" },
+    { 23, "D50" },
+    { 24, "ISO studio tungsten" },
+    { 255, "other light source" },
+  };
+
+static tag_map flash_values[] =
+  {
+    { 0x0000, "Flash did not fire." },
+    { 0x0001, "Flash fired." },
+    { 0x0005, "Strobe return light not detected." },
+    { 0x0007, "Strobe return light detected." },
+    { 0x0009, "Flash fired, compulsory flash mode" },
+    { 0x000D, "Flash fired, compulsory flash mode, return light not detected" },
+    { 0x000F, "Flash fired, compulsory flash mode, return light detected" },
+    { 0x0010, "Flash did not fire, compulsory flash mode" },
+    { 0x0018, "Flash did not fire, auto mode" },
+    { 0x0019, "Flash fired, auto mode" },
+    { 0x001D, "Flash fired, auto mode, return light not detected" },
+    { 0x001F, "Flash fired, auto mode, return light detected" },
+    { 0x0020, "No flash function" },
+    { 0x0041, "Flash fired, red-eye reduction mode" },
+    { 0x0045, "Flash fired, red-eye reduction mode, return light not detected" },
+    { 0x0047, "Flash fired, red-eye reduction mode, return light detected" },
+    { 0x0049, "Flash fired, compulsory flash mode, red-eye reduction mode" },
+    { 0x004D, "Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected" },
+    { 0x004F, "Flash fired, compulsory flash mode, red-eye reduction mode, return light detected" },
+    { 0x0059, "Flash fired, auto mode, red-eye reduction mode" },
+    { 0x005D, "Flash fired, auto mode, return light not detected, red-eye reduction mode" },
+    { 0x005F, "Flash fired, auto mode, return light detected, red-eye reduction mode" },
+  };
+
+static tag_map sensing_method_values[] =
+  {
+    { 1, "Not defined" },
+    { 2, "One-chip color area sensor" },
+    { 3, "Two-chip color area sensor" },
+    { 4, "Three-chip color area sensor" },
+    { 5, "Color sequential area sensor" },
+    { 7, "Trilinear sensor" },
+    { 8, "Color sequential linear sensor" },
+  };
+
+static tag_map custom_rendered_values[] =
+  {
+    { 0, "Normal process" },
+    { 1, "Custom process" },
+  };
+
+static tag_map white_balance_values[] =
+  {
+    { 0, "Auto white balance" },
+    { 1, "Manual white balance" },
+  };
+
+static tag_map scene_capture_type_values[] =
+  {
+    { 0, "Standard" },
+    { 1, "Landscape" },
+    { 2, "Portrait" },
+    { 3, "Night scene" },
+  };
+
+static tag_map gain_control_values[] =
+  {
+    { 0, "None" },
+    { 1, "Low gain up" },
+    { 2, "High gain up" },
+    { 3, "Low gain down" },
+    { 4, "High gain down" },
+  };
+
+static tag_map contrast_values[] =
+  {
+    { 0, "Normal" },
+    { 1, "Soft" },
+    { 2, "Hard" },
+  };
+
+static tag_map saturation_values[] =
+  {
+    { 0, "Normal" },
+    { 1, "Low saturation" },
+    { 2, "High saturation" },
+  };
+
+static tag_map sharpness_values[] =
+  {
+    { 0, "Normal" },
+    { 1, "Soft" },
+    { 2, "Hard" },
+  };
+
+static tag_map subject_distance_range_values[] =
+  {
+    { 0, "unknown" },
+    { 1, "Macro" },
+    { 2, "Close view" },
+    { 3, "Distant view" },
+  };
+
+#define focal_plane_resolution_unit_values resolution_unit_values
+
+static tag_value_map exif_ifd_values[] =
+  {
+    VALUE_MAP_ENTRY(exposure_mode),
+    VALUE_MAP_ENTRY(color_space),
+    VALUE_MAP_ENTRY(exposure_program),
+    VALUE_MAP_ENTRY(metering_mode),
+    VALUE_MAP_ENTRY(light_source),
+    VALUE_MAP_ENTRY(flash),
+    VALUE_MAP_ENTRY(sensing_method),
+    VALUE_MAP_ENTRY(custom_rendered),
+    VALUE_MAP_ENTRY(white_balance),
+    VALUE_MAP_ENTRY(scene_capture_type),
+    VALUE_MAP_ENTRY(gain_control),
+    VALUE_MAP_ENTRY(contrast),
+    VALUE_MAP_ENTRY(saturation),
+    VALUE_MAP_ENTRY(sharpness),
+    VALUE_MAP_ENTRY(subject_distance_range),
+    VALUE_MAP_ENTRY(focal_plane_resolution_unit),
+  };
+
+static tag_map exif_num_arrays[] =
+  {
+    { tag_iso_speed_ratings, "exif_iso_speed_ratings" },
+    { tag_subject_area, "exif_subject_area" },
+    { tag_subject_location, "exif_subject_location" },
+  };
+
+static void
+save_exif_ifd_tags(i_img *im, imtiff *tiff) {
+  int i, tag_index;
+  ifd_entry *entry;
+  char *user_comment;
+  unsigned long maker_note_offset = 0;
+  size_t maker_note_size = 0;
+
+  for (tag_index = 0, entry = tiff->ifd; 
+       tag_index < tiff->ifd_size; ++tag_index, ++entry) {
+    switch (entry->tag) {
+    case tag_user_comment:
+      /* I don't want to trash the source, so work on a copy */
+      user_comment = mymalloc(entry->size);
+      memcpy(user_comment, tiff->base + entry->offset, entry->size);
+      /* the first 8 bytes indicate the encoding, make them into spaces
+        for better presentation */
+      for (i = 0; i < entry->size && i < 8; ++i) {
+       if (user_comment[i] == '\0')
+         user_comment[i] = ' ';
+      }
+      /* find the actual end of the string */
+      while (i < entry->size && user_comment[i])
+       ++i;
+      i_tags_set(&im->tags, "exif_user_comment", user_comment, i);
+      myfree(user_comment);
+      break;
+
+    case tag_maker_note:
+      maker_note_offset = entry->offset;
+      maker_note_size = entry->size;
+      break;
+
+      /* the following aren't processed yet */
+    case tag_oecf:
+    case tag_spatial_frequency_response:
+    case tag_file_source:
+    case tag_scene_type:
+    case tag_cfa_pattern:
+    case tag_device_setting_description:
+    case tag_subject_area:
+      break;
+    }
+  }
+
+  copy_string_tags(im, tiff, exif_ifd_string_tags, exif_ifd_string_tag_count);
+  copy_int_tags(im, tiff, exif_ifd_int_tags, exif_ifd_int_tag_count);
+  copy_rat_tags(im, tiff, exif_ifd_rat_tags, exif_ifd_rat_tag_count);
+  copy_name_tags(im, tiff, exif_ifd_values, ARRAY_COUNT(exif_ifd_values));
+  copy_num_array_tags(im, tiff, exif_num_arrays, ARRAY_COUNT(exif_num_arrays));
+
+  /* This trashes the IFD - make sure it's done last */
+  if (maker_note_offset) {
+    process_maker_note(im, tiff, maker_note_offset, maker_note_size);
+  }
+}
+
+static tag_map gps_ifd_string_tags[] =
+  {
+    { tag_gps_version_id, "exif_gps_version_id" },
+    { tag_gps_latitude_ref, "exif_gps_latitude_ref" },
+    { tag_gps_longitude_ref, "exif_gps_longitude_ref" },
+    { tag_gps_altitude_ref, "exif_gps_altitude_ref" },
+    { tag_gps_satellites, "exif_gps_satellites" },
+    { tag_gps_status, "exif_gps_status" },
+    { tag_gps_measure_mode, "exif_gps_measure_mode" },
+    { tag_gps_speed_ref, "exif_gps_speed_ref" },
+    { tag_gps_track_ref, "exif_gps_track_ref" },
+  };
+
+static tag_map gps_ifd_int_tags[] =
+  {
+    { tag_gps_differential, "exif_gps_differential" },
+  };
+
+static tag_map gps_ifd_rat_tags[] =
+  {
+    { tag_gps_altitude, "exif_gps_altitude" },
+    { tag_gps_time_stamp, "exif_gps_time_stamp" },
+    { tag_gps_dop, "exif_gps_dop" },
+    { tag_gps_speed, "exif_gps_speed" },
+    { tag_gps_track, "exif_track" }
+  };
+
+static tag_map gps_differential_values [] =
+  {
+    { 0, "without differential correction" },
+    { 1, "Differential correction applied" },
+  };
+
+static tag_value_map gps_ifd_values[] =
+  {
+    VALUE_MAP_ENTRY(gps_differential),
+  };
+
+static tag_map gps_num_arrays[] =
+  {
+    { tag_gps_latitude, "exif_gps_latitude" },
+    { tag_gps_longitude, "exif_gps_longitude" },
+  };
+
+static void
+save_gps_ifd_tags(i_img *im, imtiff *tiff) {
+  /* int i, tag_index; 
+  int work;
+  ifd_entry *entry; */
+
+  /* for (tag_index = 0, entry = tiff->ifd; 
+       tag_index < tiff->ifd_size; ++tag_index, ++entry) {
+    switch (entry->tag) {
+      break;
+    }
+    }*/
+
+  copy_string_tags(im, tiff, gps_ifd_string_tags, 
+        ARRAY_COUNT(gps_ifd_string_tags));
+  copy_int_tags(im, tiff, gps_ifd_int_tags, ARRAY_COUNT(gps_ifd_int_tags));
+  copy_rat_tags(im, tiff, gps_ifd_rat_tags, ARRAY_COUNT(gps_ifd_rat_tags));
+  copy_name_tags(im, tiff, gps_ifd_values, ARRAY_COUNT(gps_ifd_values));
+  copy_num_array_tags(im, tiff, gps_num_arrays, ARRAY_COUNT(gps_num_arrays));
+}
+
+/*
+=item process_maker_note
+
+This is a stub for processing the maker note tag.
+
+Maker notes aren't covered by EXIF itself and in general aren't
+documented by the manufacturers.
+
+=cut
+*/
+
+static void
+process_maker_note(i_img *im, imtiff *tiff, unsigned long offset, size_t size) {
+  /* this will be added in a future release */
+}
+
+/*
+=back
+
+=head2 High level TIFF functions
+
+To avoid relying upon tifflib when we're not processing an image we
+have some simple in-memory TIFF file management.
+
+=over
+
+=item tiff_init
+
+imtiff tiff;
+if (tiff_init(tiff, data_base, data_size)) {
+  // success
+}
+
+Initialize the tiff data structure.
+
+Scans for the byte order and version markers, and stores the offset to
+the first IFD (IFD0 in EXIF) in first_ifd_offset.
+
+=cut
+*/
+
+static int
+tiff_init(imtiff *tiff, unsigned char *data, size_t length) {
+  int version;
+
+  tiff->base = data;
+  tiff->size = length;
+  if (length < 8) /* well... would have to be much bigger to be useful */
+    return 0;
+  if (data[0] == 'M' && data[1] == 'M')
+    tiff->type = tt_motorola;
+  else if (data[0] == 'I' && data[1] == 'I') 
+    tiff->type = tt_intel;
+  else
+    return 0; /* invalid header */
+
+  version = tiff_get16(tiff, 2);
+  if (version != 42)
+    return 0;
+
+  tiff->first_ifd_offset = tiff_get32(tiff, 4);
+  if (tiff->first_ifd_offset > length || tiff->first_ifd_offset < 8)
+    return 0;
+
+  tiff->ifd_size = 0;
+  tiff->ifd = NULL;
+  tiff->next_ifd = 0;
+
+  return 1;
+}
+
+/*
+=item tiff_final
+
+tiff_final(&tiff)
+
+Clean up the tiff structure initialized by tiff_init()
+
+=cut
+*/
+
+static void
+tiff_final(imtiff *tiff) {
+  tiff_clear_ifd(tiff);
+}
+
+/*
+=item tiff_load_ifd
+
+if (tiff_load_ifd(tiff, offset)) {
+  // process the ifd
+}
+
+Loads the IFD from the given offset into the tiff objects ifd.
+
+This can fail if the IFD extends beyond end of file, or if any data
+offsets combined with their sizes, extends beyond end of file.
+
+Returns true on success.
+
+=cut
+*/
+
+static int
+tiff_load_ifd(imtiff *tiff, unsigned long offset) {
+  unsigned count;
+  int ifd_size;
+  ifd_entry *entries = NULL;
+  int i;
+  unsigned long base;
+
+  tiff_clear_ifd(tiff);
+
+  /* rough check count + 1 entry + next offset */
+  if (offset + (2+12+4) > tiff->size) {
+    mm_log((2, "offset %uld beyond end off Exif block"));
+    return 0;
+  }
+
+  count = tiff_get16(tiff, offset);
+  
+  /* check we can fit the whole thing */
+  ifd_size = 2 + count * 12 + 4; /* count + count entries + next offset */
+  if (offset + ifd_size > tiff->size) {
+    mm_log((2, "offset %uld beyond end off Exif block"));
+    return 0;
+  }
+
+  entries = mymalloc(count * sizeof(ifd_entry));
+  memset(entries, 0, count * sizeof(ifd_entry));
+  base = offset + 2;
+  for (i = 0; i < count; ++i) {
+    ifd_entry *entry = entries + i;
+    entry->tag = tiff_get16(tiff, base);
+    entry->type = tiff_get16(tiff, base+2);
+    entry->count = tiff_get32(tiff, base+4);
+    if (entry->type >= 1 && entry->type <= ift_last) {
+      entry->item_size = type_sizes[entry->type];
+      entry->size = entry->item_size * entry->count;
+      if (entry->size / entry->item_size != entry->count) {
+       myfree(entries);
+       mm_log((1, "Integer overflow calculating tag data size processing EXIF block\n"));
+       return 0;
+      }
+      else if (entry->size <= 4) {
+       entry->offset = base + 8;
+      }
+      else {
+       entry->offset = tiff_get32(tiff, base+8);
+       if (entry->offset + entry->size > tiff->size) {
+         mm_log((2, "Invalid data offset processing IFD\n"));
+         myfree(entries);
+         return 0;
+       }
+      }
+    }
+    else {
+      entry->size = 0;
+      entry->offset = 0;
+    }
+    base += 12;
+  }
+
+  tiff->ifd_size = count;
+  tiff->ifd = entries;
+  tiff->next_ifd = tiff_get32(tiff, base);
+
+  return 1;
+}
+
+/*
+=item tiff_clear_ifd
+
+tiff_clear_ifd(tiff)
+
+Releases any memory associated with the stored IFD and resets the IFD
+pointers.
+
+This is called by tiff_load_ifd() and tiff_final().
+
+=cut
+*/
+
+static void
+tiff_clear_ifd(imtiff *tiff) {
+  if (tiff->ifd_size && tiff->ifd) {
+    myfree(tiff->ifd);
+    tiff->ifd_size = 0;
+    tiff->ifd = NULL;
+  }
+}
+
+/*
+=item tiff_get_tag_double
+
+  double value;
+  if (tiff_get_tag(tiff, index, &value)) {
+    // process value
+  }
+
+Attempts to retrieve a double value from the given index in the
+current IFD.
+
+The value must have a count of 1.
+
+=cut
+*/
+
+static int
+tiff_get_tag_double_array(imtiff *tiff, int index, double *result, 
+                         int array_index) {
+  ifd_entry *entry;
+  unsigned long offset;
+  if (index < 0 || index >= tiff->ifd_size) {
+    mm_log((3, "tiff_get_tag_double_array() tag index out of range"));
+    return 0;
+  }
+  
+  entry = tiff->ifd + index;
+  if (array_index < 0 || array_index >= entry->count) {
+    mm_log((3, "tiff_get_tag_double_array() array index out of range"));
+    return 0;
+  }
+
+  offset = entry->offset + array_index * entry->item_size;
+
+  switch (entry->type) {
+  case ift_short:
+    *result = tiff_get16(tiff, offset);
+    return 1;
+   
+  case ift_long:
+    *result = tiff_get32(tiff, offset);
+    return 1;
+
+  case ift_rational:
+    *result = tiff_get_rat(tiff, offset);
+    return 1;
+
+  case ift_sshort:
+    *result = tiff_get16s(tiff, offset);
+    return 1;
+
+  case ift_slong:
+    *result = tiff_get32s(tiff, offset);
+    return 1;
+
+  case ift_srational:
+    *result = tiff_get_rats(tiff, offset);
+    return 1;
+
+  case ift_byte:
+    *result = *(tiff->base + offset);
+    return 1;
+  }
+
+  return 0;
+}
+
+/*
+=item tiff_get_tag_double
+
+  double value;
+  if (tiff_get_tag(tiff, index, &value)) {
+    // process value
+  }
+
+Attempts to retrieve a double value from the given index in the
+current IFD.
+
+The value must have a count of 1.
+
+=cut
+*/
+
+static int
+tiff_get_tag_double(imtiff *tiff, int index, double *result) {
+  ifd_entry *entry;
+  if (index < 0 || index >= tiff->ifd_size) {
+    mm_log((3, "tiff_get_tag_double() index out of range"));
+    return 0;
+  }
+  
+  entry = tiff->ifd + index;
+  if (entry->count != 1) {
+    mm_log((3, "tiff_get_tag_double() called on tag with multiple values"));
+    return 0;
+  }
+
+  return tiff_get_tag_double_array(tiff, index, result, 0);
+}
+
+/*
+=item tiff_get_tag_int_array
+
+  int value;
+  if (tiff_get_tag_int_array(tiff, index, &value, array_index)) {
+    // process value
+  }
+
+Attempts to retrieve an integer value from the given index in the
+current IFD.
+
+=cut
+*/
+
+static int
+tiff_get_tag_int_array(imtiff *tiff, int index, int *result, int array_index) {
+  ifd_entry *entry;
+  unsigned long offset;
+  if (index < 0 || index >= tiff->ifd_size) {
+    mm_log((3, "tiff_get_tag_int_array() tag index out of range"));
+    return 0;
+  }
+  
+  entry = tiff->ifd + index;
+  if (array_index < 0 || array_index >= entry->count) {
+    mm_log((3, "tiff_get_tag_int_array() array index out of range"));
+    return 0;
+  }
+
+  offset = entry->offset + array_index * entry->item_size;
+
+  switch (entry->type) {
+  case ift_short:
+    *result = tiff_get16(tiff, offset);
+    return 1;
+   
+  case ift_long:
+    *result = tiff_get32(tiff, offset);
+    return 1;
+
+  case ift_sshort:
+    *result = tiff_get16s(tiff, offset);
+    return 1;
+
+  case ift_slong:
+    *result = tiff_get32s(tiff, offset);
+    return 1;
+
+  case ift_byte:
+    *result = *(tiff->base + offset);
+    return 1;
+  }
+
+  return 0;
+}
+
+/*
+=item tiff_get_tag_int
+
+  int value;
+  if (tiff_get_tag_int(tiff, index, &value)) {
+    // process value
+  }
+
+Attempts to retrieve an integer value from the given index in the
+current IFD.
+
+The value must have a count of 1.
+
+=cut
+*/
+
+static int
+tiff_get_tag_int(imtiff *tiff, int index, int *result) {
+  ifd_entry *entry;
+  if (index < 0 || index >= tiff->ifd_size) {
+    mm_log((3, "tiff_get_tag_int() index out of range"));
+    return 0;
+  }
+
+  entry = tiff->ifd + index;
+  if (entry->count != 1) {
+    mm_log((3, "tiff_get_tag_int() called on tag with multiple values"));
+    return 0;
+  }
+
+  return tiff_get_tag_int_array(tiff, index, result, 0);
+}
+
+/*
+=back
+
+=head2 Table-based tag setters
+
+This set of functions checks for matches between the current IFD and
+tags supplied in an array, when there's a match it sets the
+appropriate tag in the image.
+
+=over
+
+=item copy_int_tags
+
+Scans the IFD for integer tags and sets them in the image,
+
+=cut
+*/
+
+static void
+copy_int_tags(i_img *im, imtiff *tiff, tag_map *map, int map_count) {
+  int i, tag_index;
+  ifd_entry *entry;
+
+  for (tag_index = 0, entry = tiff->ifd; 
+       tag_index < tiff->ifd_size; ++tag_index, ++entry) {
+    for (i = 0; i < map_count; ++i) {
+      int value;
+      if (map[i].tag == entry->tag
+         && tiff_get_tag_int(tiff, tag_index, &value)) {
+       i_tags_setn(&im->tags, map[i].name, value);
+       break;
+      }
+    }
+  }
+}
+
+/*
+=item copy_rat_tags
+
+Scans the IFD for rational tags and sets them in the image.
+
+=cut
+*/
+
+static void
+copy_rat_tags(i_img *im, imtiff *tiff, tag_map *map, int map_count) {
+  int i, tag_index;
+  ifd_entry *entry;
+
+  for (tag_index = 0, entry = tiff->ifd; 
+       tag_index < tiff->ifd_size; ++tag_index, ++entry) {
+    for (i = 0; i < map_count; ++i) {
+      double value;
+      if (map[i].tag == entry->tag
+         && tiff_get_tag_double(tiff, tag_index, &value)) {
+       i_tags_set_float2(&im->tags, map[i].name, 0, value, 6);
+       break;
+      }
+    }
+  }
+}
+
+/*
+=item copy_string_tags
+
+Scans the IFD for string tags and sets them in the image.
+
+=cut
+*/
+
+static void
+copy_string_tags(i_img *im, imtiff *tiff, tag_map *map, int map_count) {
+  int i, tag_index;
+  ifd_entry *entry;
+
+  for (tag_index = 0, entry = tiff->ifd; 
+       tag_index < tiff->ifd_size; ++tag_index, ++entry) {
+    for (i = 0; i < map_count; ++i) {
+      if (map[i].tag == entry->tag) {
+       int len = entry->type == ift_ascii ? entry->size - 1 : entry->size;
+       i_tags_set(&im->tags, map[i].name,
+                  (char const *)(tiff->base + entry->offset), len);
+       break;
+      }
+    }
+  }
+}
+
+/*
+=item copy_num_array_tags
+
+Scans the IFD for arrays of numbers and sets them in the image.
+
+=cut
+*/
+
+/* a more general solution would be better in some ways, but we don't need it */
+#define MAX_ARRAY_VALUES 10
+#define MAX_ARRAY_STRING (MAX_ARRAY_VALUES * 20)
+
+static void
+copy_num_array_tags(i_img *im, imtiff *tiff, tag_map *map, int map_count) {
+  int i, j, tag_index;
+  ifd_entry *entry;
+
+  for (tag_index = 0, entry = tiff->ifd; 
+       tag_index < tiff->ifd_size; ++tag_index, ++entry) {
+    for (i = 0; i < map_count; ++i) {
+      if (map[i].tag == entry->tag && entry->count <= MAX_ARRAY_VALUES) {
+       if (entry->type == ift_rational || entry->type == ift_srational) {
+         double value;
+         char workstr[MAX_ARRAY_STRING];
+         *workstr = '\0';
+         for (j = 0; j < entry->count; ++j) {
+           if (!tiff_get_tag_double_array(tiff, tag_index, &value, j)) {
+             mm_log((3, "unexpected failure from tiff_get_tag_double_array(..., %d, ..., %d)\n", tag_index, j));
+             return;
+           }
+           if (j) 
+             strcat(workstr, " ");
+           sprintf(workstr + strlen(workstr), "%.6g", value);
+         }
+         i_tags_set(&im->tags, map[i].name, workstr, -1);
+       }
+       else if (entry->type == ift_short || entry->type == ift_long
+                || entry->type == ift_sshort || entry->type == ift_slong
+                || entry->type == ift_byte) {
+         int value;
+         char workstr[MAX_ARRAY_STRING];
+         *workstr = '\0';
+         for (j = 0; j < entry->count; ++j) {
+           if (!tiff_get_tag_int_array(tiff, tag_index, &value, j)) {
+             mm_log((3, "unexpected failure from tiff_get_tag_int_array(..., %d, ..., %d)\n", tag_index, j));
+             return;
+           }
+           if (j) 
+             strcat(workstr, " ");
+           sprintf(workstr + strlen(workstr), "%d", value);
+         }
+         i_tags_set(&im->tags, map[i].name, workstr, -1);
+       }
+       break;
+      }
+    }
+  }
+}
+
+/*
+=item copy_name_tags
+
+This function maps integer values to descriptions for those values.
+
+In general we handle the integer value through copy_int_tags() and
+then the same tage with a "_name" suffix here.
+
+=cut
+*/
+
+static void
+copy_name_tags(i_img *im, imtiff *tiff, tag_value_map *map, int map_count) {
+  int i, j, tag_index;
+  ifd_entry *entry;
+
+  for (tag_index = 0, entry = tiff->ifd; 
+       tag_index < tiff->ifd_size; ++tag_index, ++entry) {
+    for (i = 0; i < map_count; ++i) {
+      int value;
+      if (map[i].tag == entry->tag
+         && tiff_get_tag_int(tiff, tag_index, &value)) {
+       tag_map const *found = NULL;
+       for (j = 0; j < map[i].map_count; ++j) {
+         if (value == map[i].map[j].tag) {
+           found = map[i].map + j;
+           break;
+         }
+       }
+       if (found) {
+         i_tags_set(&im->tags, map[i].name, found->name, -1);
+       }
+       break;
+      }
+    }
+  }
+}
+
+
+/*
+=back
+
+=head2 Low level data access functions
+
+These functions use the byte order in the tiff object to extract
+various types of data from the tiff data.
+
+These functions will abort if called with an out of range offset.
+
+The intent is that any offset checks should have been done by the caller.
+
+=over
+
+=item tiff_get16
+
+Retrieve a 16 bit unsigned integer from offset.
+
+=cut
+*/
+
+static unsigned
+tiff_get16(imtiff *tiff, unsigned long offset) {
+  if (offset + 2 > tiff->size) {
+    mm_log((3, "attempt to get16 at %uld in %uld image", offset, tiff->size));
+    return 0;
+  }
+
+  if (tiff->type == tt_intel) 
+    return tiff->base[offset] + 0x100 * tiff->base[offset+1];
+  else
+    return tiff->base[offset+1] + 0x100 * tiff->base[offset];
+}
+
+/*
+=item tiff_get32
+
+Retrieve a 32-bit unsigned integer from offset.
+
+=cut
+*/
+
+static unsigned
+tiff_get32(imtiff *tiff, unsigned long offset) {
+  if (offset + 4 > tiff->size) {
+    mm_log((3, "attempt to get16 at %uld in %uld image", offset, tiff->size));
+    return 0;
+  }
+
+  if (tiff->type == tt_intel) 
+    return tiff->base[offset] + 0x100 * tiff->base[offset+1] 
+      + 0x10000 * tiff->base[offset+2] + 0x1000000 * tiff->base[offset+3];
+  else
+    return tiff->base[offset+3] + 0x100 * tiff->base[offset+2]
+      + 0x10000 * tiff->base[offset+1] + 0x1000000 * tiff->base[offset];
+}
+
+#if 0 /* currently unused, but that may change */
+
+/*
+=item tiff_get_bytes
+
+Retrieve a byte string from offset.
+
+This isn't used much, you can usually deal with the data in-situ.
+This is intended for use when you need to modify the data in some way.
+
+=cut
+*/
+
+static int
+tiff_get_bytes(imtiff *tiff, unsigned char *data, size_t offset, 
+              size_t size) {
+  if (offset + size > tiff->size)
+    return 0;
+
+  memcpy(data, tiff->base+offset, size);
+
+  return 1;
+}
+
+#endif
+
+/*
+=item tiff_get16s
+
+Retrieve a 16-bit signed integer from offset.
+
+=cut
+*/
+
+static int
+tiff_get16s(imtiff *tiff, unsigned long offset) {
+  int result;
+
+  if (offset + 2 > tiff->size) {
+    mm_log((3, "attempt to get16 at %uld in %uld image", offset, tiff->size));
+    return 0;
+  }
+
+  if (tiff->type == tt_intel) 
+    result = tiff->base[offset] + 0x100 * tiff->base[offset+1];
+  else
+    result = tiff->base[offset+1] + 0x100 * tiff->base[offset];
+
+  if (result > 0x7FFF)
+    result -= 0x10000;
+
+  return result;
+}
+
+/*
+=item tiff_get32s
+
+Retrieve a 32-bit signed integer from offset.
+
+=cut
+*/
+
+static int
+tiff_get32s(imtiff *tiff, unsigned long offset) {
+  unsigned work;
+
+  if (offset + 4 > tiff->size) {
+    mm_log((3, "attempt to get16 at %uld in %uld image", offset, tiff->size));
+    return 0;
+  }
+
+  if (tiff->type == tt_intel) 
+    work = tiff->base[offset] + 0x100 * tiff->base[offset+1] 
+      + 0x10000 * tiff->base[offset+2] + 0x1000000 * tiff->base[offset+3];
+  else
+    work = tiff->base[offset+3] + 0x100 * tiff->base[offset+2]
+      + 0x10000 * tiff->base[offset+1] + 0x1000000 * tiff->base[offset];
+
+  /* not really needed on 32-bit int machines */
+  if (work > 0x7FFFFFFFUL)
+    return work - 0x80000000UL;
+  else
+    return work;
+}
+
+/*
+=item tiff_get_rat
+
+Retrieve an unsigned rational from offset.
+
+=cut
+*/
+
+static double
+tiff_get_rat(imtiff *tiff, unsigned long offset) {
+  unsigned long numer, denom;
+  if (offset + 8 > tiff->size) {
+    mm_log((3, "attempt to get_rat at %lu in %lu image", offset, tiff->size));
+    return 0;
+  }
+
+  numer = tiff_get32(tiff, offset);
+  denom = tiff_get32(tiff, offset+4);
+
+  if (denom == 0) {
+    return -DBL_MAX;
+  }
+
+  return (double)numer / denom;
+}
+
+/*
+=item tiff_get_rats
+
+Retrieve an signed rational from offset.
+
+=cut
+*/
+
+static double
+tiff_get_rats(imtiff *tiff, unsigned long offset) {
+  long numer, denom;
+  if (offset + 8 > tiff->size) {
+    mm_log((3, "attempt to get_rat at %lu in %lu image", offset, tiff->size));
+    return 0;
+  }
+
+  numer = tiff_get32s(tiff, offset);
+  denom = tiff_get32s(tiff, offset+4);
+
+  if (denom == 0) {
+    return -DBL_MAX;
+  }
+
+  return (double)numer / denom;
+}
+
+/*
+=back
+
+=head1 SEE ALSO
+
+L<Imager>, jpeg.c
+
+http://www.exif.org/
+
+=head1 AUTHOR
+
+Tony Cook <tony@imager.perl.org>
+
+=head1 REVISION
+
+$Revision$
+
+=cut
+*/
diff --git a/JPEG/imexif.h b/JPEG/imexif.h
new file mode 100644 (file)
index 0000000..e5f332a
--- /dev/null
@@ -0,0 +1,10 @@
+/* imexif.h - interface to Exif handling */
+#ifndef IMAGER_IMEXIF_H
+#define IMAGER_IMEXIF_H
+
+#include <stddef.h>
+#include "imdatatypes.h"
+
+extern int i_int_decode_exif(i_img *im, unsigned char *data, size_t length);
+
+#endif /* ifndef IMAGER_IMEXIF_H */
diff --git a/JPEG/imjpeg.c b/JPEG/imjpeg.c
new file mode 100644 (file)
index 0000000..318965b
--- /dev/null
@@ -0,0 +1,717 @@
+/*
+=head1 NAME
+
+jpeg.c - implement saving and loading JPEG images
+
+=head1 SYNOPSIS
+
+  io_glue *ig;
+  if (!i_writejpeg_wiol(im, ig, quality)) {
+    .. error ..
+  }
+  im = i_readjpeg_wiol(ig, length, iptc_text, itlength);
+
+=head1 DESCRIPTION
+
+Reads and writes JPEG images
+
+=over
+
+=cut
+*/
+
+#include "imjpeg.h"
+#include "imext.h"
+#include <stdio.h>
+#include <sys/stat.h>
+#ifndef _MSC_VER
+#include <unistd.h>
+#endif
+#include <setjmp.h>
+
+#include "jpeglib.h"
+#include "jerror.h"
+#include <errno.h>
+#include <stdlib.h>
+
+#define JPEG_APP13       0xED    /* APP13 marker code */
+#define JPEG_APP1 (JPEG_APP0 + 1)
+#define JPGS 16384
+
+static unsigned char fake_eoi[]={(JOCTET) 0xFF,(JOCTET) JPEG_EOI};
+
+/* Bad design right here */
+
+static int tlength=0;
+static char **iptc_text=NULL;
+
+
+/* Source and Destination managers */
+
+
+typedef struct {
+  struct jpeg_source_mgr pub;  /* public fields */
+  io_glue *data;
+  JOCTET *buffer;              /* start of buffer */
+  int length;                  /* Do I need this? */
+  boolean start_of_file;       /* have we gotten any data yet? */
+} wiol_source_mgr;
+
+typedef struct {
+  struct jpeg_destination_mgr pub; /* public fields */
+  io_glue *data;
+  JOCTET *buffer;              /* start of buffer */
+  boolean start_of_file;       /* have we gotten any data yet? */
+} wiol_destination_mgr;
+
+typedef wiol_source_mgr *wiol_src_ptr;
+typedef wiol_destination_mgr *wiol_dest_ptr;
+
+
+/*
+ * Methods for io manager objects 
+ * 
+ * Methods for source managers: 
+ *
+ * init_source         (j_decompress_ptr cinfo);
+ * skip_input_data     (j_decompress_ptr cinfo, long num_bytes);
+ * fill_input_buffer   (j_decompress_ptr cinfo);
+ * term_source         (j_decompress_ptr cinfo);
+ */
+
+
+
+static void
+wiol_init_source (j_decompress_ptr cinfo) {
+  wiol_src_ptr src = (wiol_src_ptr) cinfo->src;
+  
+  /* We reset the empty-input-file flag for each image, but we don't clear
+   * the input buffer.   This is correct behavior for reading a series of
+   * images from one source.
+   */
+  src->start_of_file = TRUE;
+}
+
+
+
+static boolean
+wiol_fill_input_buffer(j_decompress_ptr cinfo) {
+  wiol_src_ptr src = (wiol_src_ptr) cinfo->src;
+  ssize_t nbytes; /* We assume that reads are "small" */
+  
+  mm_log((1,"wiol_fill_input_buffer(cinfo 0x%p)\n", cinfo));
+  
+  nbytes = src->data->readcb(src->data, src->buffer, JPGS);
+  
+  if (nbytes <= 0) { /* Insert a fake EOI marker */
+    src->pub.next_input_byte = fake_eoi;
+    src->pub.bytes_in_buffer = 2;
+  } else {
+    src->pub.next_input_byte = src->buffer;
+    src->pub.bytes_in_buffer = nbytes;
+  }
+  src->start_of_file = FALSE;
+  return TRUE;
+}
+
+
+static void
+wiol_skip_input_data (j_decompress_ptr cinfo, long num_bytes) {
+  wiol_src_ptr src = (wiol_src_ptr) cinfo->src;
+  
+  /* Just a dumb implementation for now.  Could use fseek() except
+   * it doesn't work on pipes.  Not clear that being smart is worth
+   * any trouble anyway --- large skips are infrequent.
+   */
+  
+  if (num_bytes > 0) {
+    while (num_bytes > (long) src->pub.bytes_in_buffer) {
+      num_bytes -= (long) src->pub.bytes_in_buffer;
+      (void) wiol_fill_input_buffer(cinfo);
+      /* note we assume that fill_input_buffer will never return FALSE,
+       * so suspension need not be handled.
+       */
+    }
+    src->pub.next_input_byte += (size_t) num_bytes;
+    src->pub.bytes_in_buffer -= (size_t) num_bytes;
+  }
+}
+
+static void
+wiol_term_source (j_decompress_ptr cinfo) {
+  /* no work necessary here */ 
+  wiol_src_ptr src;
+  if (cinfo->src != NULL) {
+    src = (wiol_src_ptr) cinfo->src;
+    myfree(src->buffer);
+  }
+}
+
+
+/* Source manager Constructor */
+
+static void
+jpeg_wiol_src(j_decompress_ptr cinfo, io_glue *ig, int length) {
+  wiol_src_ptr src;
+  
+  if (cinfo->src == NULL) {    /* first time for this JPEG object? */
+    cinfo->src = (struct jpeg_source_mgr *) (*cinfo->mem->alloc_small) 
+      ((j_common_ptr) cinfo, JPOOL_PERMANENT,sizeof(wiol_source_mgr));
+    src = (wiol_src_ptr) cinfo->src;
+  }
+
+  /* put the request method call in here later */
+  
+  src         = (wiol_src_ptr) cinfo->src;
+  src->data   = ig;
+  src->buffer = mymalloc( JPGS );
+  src->length = length;
+
+  src->pub.init_source       = wiol_init_source;
+  src->pub.fill_input_buffer = wiol_fill_input_buffer;
+  src->pub.skip_input_data   = wiol_skip_input_data;
+  src->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */
+  src->pub.term_source       = wiol_term_source;
+  src->pub.bytes_in_buffer   = 0; /* forces fill_input_buffer on first read */
+  src->pub.next_input_byte   = NULL; /* until buffer loaded */
+}
+
+
+
+
+/*
+ * Methods for destination managers:
+ *
+ * init_destination    (j_compress_ptr cinfo);
+ * empty_output_buffer (j_compress_ptr cinfo);
+ * term_destination    (j_compress_ptr cinfo);
+ *
+ */
+
+static void
+wiol_init_destination (j_compress_ptr cinfo) {
+  wiol_dest_ptr dest = (wiol_dest_ptr) cinfo->dest;
+  
+  /* We reset the empty-input-file flag for each image, but we don't clear
+   * the input buffer.   This is correct behavior for reading a series of
+   * images from one source.
+   */
+  dest->start_of_file = TRUE;
+}
+
+static boolean
+wiol_empty_output_buffer(j_compress_ptr cinfo) {
+  wiol_dest_ptr dest = (wiol_dest_ptr) cinfo->dest;
+  ssize_t rc;
+  /*
+    Previously this code was checking free_in_buffer to see how much 
+    needed to be written.  This does not follow the documentation:
+
+                       "In typical applications, it should write out the
+        *entire* buffer (use the saved start address and buffer length;
+        ignore the current state of next_output_byte and free_in_buffer)."
+
+  ssize_t nbytes     = JPGS - dest->pub.free_in_buffer;
+  */
+
+  mm_log((1,"wiol_empty_output_buffer(cinfo 0x%p)\n", cinfo));
+  rc = dest->data->writecb(dest->data, dest->buffer, JPGS);
+
+  if (rc != JPGS) { /* XXX: Should raise some jpeg error */
+    myfree(dest->buffer);
+    mm_log((1, "wiol_empty_output_buffer: Error: nbytes = %d != rc = %d\n", JPGS, rc));
+    ERREXIT(cinfo, JERR_FILE_WRITE);
+  }
+  dest->pub.free_in_buffer = JPGS;
+  dest->pub.next_output_byte = dest->buffer;
+  return TRUE;
+}
+
+static void
+wiol_term_destination (j_compress_ptr cinfo) {
+  wiol_dest_ptr dest = (wiol_dest_ptr) cinfo->dest;
+  size_t nbytes = JPGS - dest->pub.free_in_buffer;
+  /* yes, this needs to flush the buffer */
+  /* needs error handling */
+
+  if (dest->data->writecb(dest->data, dest->buffer, nbytes) != nbytes) {
+    myfree(dest->buffer);
+    ERREXIT(cinfo, JERR_FILE_WRITE);
+  }
+  
+  if (dest != NULL) myfree(dest->buffer);
+}
+
+
+/* Destination manager Constructor */
+
+static void
+jpeg_wiol_dest(j_compress_ptr cinfo, io_glue *ig) {
+  wiol_dest_ptr dest;
+  
+  if (cinfo->dest == NULL) {   /* first time for this JPEG object? */
+    cinfo->dest = 
+      (struct jpeg_destination_mgr *)
+      (*cinfo->mem->alloc_small) 
+      ((j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof(wiol_destination_mgr));
+  }
+  
+  dest = (wiol_dest_ptr) cinfo->dest;
+  dest->data                    = ig;
+  dest->buffer                  = mymalloc( JPGS );
+
+  dest->pub.init_destination    = wiol_init_destination;
+  dest->pub.empty_output_buffer = wiol_empty_output_buffer;
+  dest->pub.term_destination    = wiol_term_destination;
+  dest->pub.free_in_buffer      = JPGS;
+  dest->pub.next_output_byte    = dest->buffer;
+}
+
+LOCAL(unsigned int)
+jpeg_getc (j_decompress_ptr cinfo)
+/* Read next byte */
+{
+  struct jpeg_source_mgr * datasrc = cinfo->src;
+
+  if (datasrc->bytes_in_buffer == 0) {
+    if (! (*datasrc->fill_input_buffer) (cinfo))
+      { fprintf(stderr,"Jpeglib: cant suspend.\n"); exit(3); }
+      /*      ERREXIT(cinfo, JERR_CANT_SUSPEND);*/
+  }
+  datasrc->bytes_in_buffer--;
+  return GETJOCTET(*datasrc->next_input_byte++);
+}
+
+METHODDEF(boolean)
+APP13_handler (j_decompress_ptr cinfo) {
+  INT32 length;
+  unsigned int cnt=0;
+  
+  length = jpeg_getc(cinfo) << 8;
+  length += jpeg_getc(cinfo);
+  length -= 2; /* discount the length word itself */
+  
+  tlength=length;
+
+  if ( ((*iptc_text)=mymalloc(length)) == NULL ) return FALSE;
+  while (--length >= 0) (*iptc_text)[cnt++] = jpeg_getc(cinfo); 
+  return TRUE;
+}
+
+METHODDEF(void)
+my_output_message (j_common_ptr cinfo) {
+  char buffer[JMSG_LENGTH_MAX];
+
+  /* Create the message */
+  (*cinfo->err->format_message) (cinfo, buffer);
+
+  i_push_error(0, buffer);
+
+  /* Send it to stderr, adding a newline */
+  mm_log((1, "%s\n", buffer));
+}
+
+struct my_error_mgr {
+  struct jpeg_error_mgr pub;   /* "public" fields */
+  jmp_buf setjmp_buffer;       /* for return to caller */
+};
+
+typedef struct my_error_mgr * my_error_ptr;
+
+/* Here's the routine that will replace the standard error_exit method */
+
+METHODDEF(void)
+my_error_exit (j_common_ptr cinfo) {
+  /* cinfo->err really points to a my_error_mgr struct, so coerce pointer */
+  my_error_ptr myerr = (my_error_ptr) cinfo->err;
+  
+  /* Always display the message. */
+  /* We could postpone this until after returning, if we chose. */
+  (*cinfo->err->output_message) (cinfo);
+
+  /* Return control to the setjmp point */
+  longjmp(myerr->setjmp_buffer, 1);
+}
+
+static void 
+transfer_cmyk_inverted(i_color *out, JSAMPARRAY in, int width) {
+  JSAMPROW inrow = *in;
+  while (width--) {
+    /* extract and convert to real CMYK */
+    /* horribly enough this is correct given cmyk values are inverted */
+    int c = *inrow++;
+    int m = *inrow++;
+    int y = *inrow++;
+    int k = *inrow++;
+    out->rgba.r = (c * k) / MAXJSAMPLE;
+    out->rgba.g = (m * k) / MAXJSAMPLE;
+    out->rgba.b = (y * k) / MAXJSAMPLE;
+    ++out;
+  }
+}
+
+static void
+transfer_rgb(i_color *out, JSAMPARRAY in, int width) {
+  JSAMPROW inrow = *in;
+  while (width--) {
+    out->rgba.r = *inrow++;
+    out->rgba.g = *inrow++;
+    out->rgba.b = *inrow++;
+    ++out;
+  }
+}
+
+static void
+transfer_gray(i_color *out, JSAMPARRAY in, int width) {
+  JSAMPROW inrow = *in;
+  while (width--) {
+    out->gray.gray_color = *inrow++;
+    ++out;
+  }
+}
+
+typedef void (*transfer_function_t)(i_color *out, JSAMPARRAY in, int width);
+
+/*
+=item i_readjpeg_wiol(data, length, iptc_itext, itlength)
+
+=cut
+*/
+i_img*
+i_readjpeg_wiol(io_glue *data, int length, char** iptc_itext, int *itlength) {
+  i_img * volatile im = NULL;
+  int seen_exif = 0;
+  i_color * volatile line_buffer = NULL;
+  struct jpeg_decompress_struct cinfo;
+  struct my_error_mgr jerr;
+  JSAMPARRAY buffer;           /* Output row buffer */
+  int row_stride;              /* physical row width in output buffer */
+  jpeg_saved_marker_ptr markerp;
+  transfer_function_t transfer_f;
+  int channels;
+  volatile int src_set = 0;
+
+  mm_log((1,"i_readjpeg_wiol(data 0x%p, length %d,iptc_itext 0x%p)\n", data, length, iptc_itext));
+
+  i_clear_error();
+
+  iptc_text = iptc_itext;
+  cinfo.err = jpeg_std_error(&jerr.pub);
+  jerr.pub.error_exit     = my_error_exit;
+  jerr.pub.output_message = my_output_message;
+
+  /* Set error handler */
+  if (setjmp(jerr.setjmp_buffer)) {
+    if (src_set)
+      wiol_term_source(&cinfo);
+    jpeg_destroy_decompress(&cinfo); 
+    *iptc_itext=NULL;
+    *itlength=0;
+    if (line_buffer)
+      myfree(line_buffer);
+    if (im)
+      i_img_destroy(im);
+    return NULL;
+  }
+  
+  jpeg_create_decompress(&cinfo);
+  jpeg_set_marker_processor(&cinfo, JPEG_APP13, APP13_handler);
+  jpeg_save_markers(&cinfo, JPEG_APP1, 0xFFFF);
+  jpeg_save_markers(&cinfo, JPEG_COM, 0xFFFF);
+  jpeg_wiol_src(&cinfo, data, length);
+  src_set = 1;
+
+  (void) jpeg_read_header(&cinfo, TRUE);
+  (void) jpeg_start_decompress(&cinfo);
+
+  channels = cinfo.output_components;
+  switch (cinfo.out_color_space) {
+  case JCS_GRAYSCALE:
+    if (cinfo.output_components != 1) {
+      mm_log((1, "i_readjpeg: grayscale image with %d channels\n", cinfo.output_components));
+      i_push_errorf(0, "grayscale image with invalid components %d", cinfo.output_components);
+      wiol_term_source(&cinfo);
+      jpeg_destroy_decompress(&cinfo);
+      return NULL;
+    }
+    transfer_f = transfer_gray;
+    break;
+  
+  case JCS_RGB:
+    transfer_f = transfer_rgb;
+    if (cinfo.output_components != 3) {
+      mm_log((1, "i_readjpeg: RGB image with %d channels\n", cinfo.output_components));
+      i_push_errorf(0, "RGB image with invalid components %d", cinfo.output_components);
+      wiol_term_source(&cinfo);
+      jpeg_destroy_decompress(&cinfo);
+      return NULL;
+    }
+    break;
+
+  case JCS_CMYK:
+    if (cinfo.output_components == 4) {
+      /* we treat the CMYK values as inverted, because that's what that
+        buggy photoshop does, and everyone has to follow the gorilla.
+
+        Is there any app that still produces correct CMYK JPEGs?
+      */
+      transfer_f = transfer_cmyk_inverted;
+      channels = 3;
+    }
+    else {
+      mm_log((1, "i_readjpeg: cmyk image with %d channels\n", cinfo.output_components));
+      i_push_errorf(0, "CMYK image with invalid components %d", cinfo.output_components);
+      wiol_term_source(&cinfo);
+      jpeg_destroy_decompress(&cinfo);
+      return NULL;
+    }
+    break;
+
+  default:
+    mm_log((1, "i_readjpeg: unknown color space %d\n", cinfo.out_color_space));
+    i_push_errorf(0, "Unknown color space %d", cinfo.out_color_space);
+    wiol_term_source(&cinfo);
+    jpeg_destroy_decompress(&cinfo);
+    return NULL;
+  }
+
+  if (!i_int_check_image_file_limits(cinfo.output_width, cinfo.output_height,
+                                    channels, sizeof(i_sample_t))) {
+    mm_log((1, "i_readjpeg: image size exceeds limits\n"));
+    wiol_term_source(&cinfo);
+    jpeg_destroy_decompress(&cinfo);
+    return NULL;
+  }
+
+  im = i_img_8_new(cinfo.output_width, cinfo.output_height, channels);
+  if (!im) {
+    wiol_term_source(&cinfo);
+    jpeg_destroy_decompress(&cinfo);
+    return NULL;
+  }
+  row_stride = cinfo.output_width * cinfo.output_components;
+  buffer = (*cinfo.mem->alloc_sarray) ((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);
+  line_buffer = mymalloc(sizeof(i_color) * cinfo.output_width);
+  while (cinfo.output_scanline < cinfo.output_height) {
+    (void) jpeg_read_scanlines(&cinfo, buffer, 1);
+    transfer_f(line_buffer, buffer, cinfo.output_width);
+    i_plin(im, 0, cinfo.output_width, cinfo.output_scanline-1, line_buffer);
+  }
+  myfree(line_buffer);
+  line_buffer = NULL;
+
+  /* check for APP1 marker and save */
+  markerp = cinfo.marker_list;
+  while (markerp != NULL) {
+    if (markerp->marker == JPEG_COM) {
+      i_tags_set(&im->tags, "jpeg_comment", (const char *)markerp->data,
+                markerp->data_length);
+    }
+    else if (markerp->marker == JPEG_APP1 && !seen_exif) {
+      seen_exif = i_int_decode_exif(im, markerp->data, markerp->data_length);
+    }
+
+    markerp = markerp->next;
+  }
+
+  i_tags_setn(&im->tags, "jpeg_out_color_space", cinfo.out_color_space);
+  i_tags_setn(&im->tags, "jpeg_color_space", cinfo.jpeg_color_space);
+
+  if (cinfo.saw_JFIF_marker) {
+    double xres = cinfo.X_density;
+    double yres = cinfo.Y_density;
+    
+    i_tags_setn(&im->tags, "jpeg_density_unit", cinfo.density_unit);
+    switch (cinfo.density_unit) {
+    case 0: /* values are just the aspect ratio */
+      i_tags_setn(&im->tags, "i_aspect_only", 1);
+      i_tags_set(&im->tags, "jpeg_density_unit_name", "none", -1);
+      break;
+
+    case 1: /* per inch */
+      i_tags_set(&im->tags, "jpeg_density_unit_name", "inch", -1);
+      break;
+
+    case 2: /* per cm */
+      i_tags_set(&im->tags, "jpeg_density_unit_name", "centimeter", -1);
+      xres *= 2.54;
+      yres *= 2.54;
+      break;
+    }
+    i_tags_set_float2(&im->tags, "i_xres", 0, xres, 6);
+    i_tags_set_float2(&im->tags, "i_yres", 0, yres, 6);
+  }
+
+  (void) jpeg_finish_decompress(&cinfo);
+  jpeg_destroy_decompress(&cinfo);
+  *itlength=tlength;
+
+  i_tags_set(&im->tags, "i_format", "jpeg", 4);
+
+  mm_log((1,"i_readjpeg_wiol -> (0x%x)\n",im));
+  return im;
+}
+
+/*
+=item i_writejpeg_wiol(im, ig, qfactor)
+
+=cut
+*/
+
+undef_int
+i_writejpeg_wiol(i_img *im, io_glue *ig, int qfactor) {
+  JSAMPLE *image_buffer;
+  int quality;
+  int got_xres, got_yres, aspect_only, resunit;
+  double xres, yres;
+  int comment_entry;
+  int want_channels = im->channels;
+
+  struct jpeg_compress_struct cinfo;
+  struct my_error_mgr jerr;
+
+  JSAMPROW row_pointer[1];     /* pointer to JSAMPLE row[s] */
+  int row_stride;              /* physical row width in image buffer */
+  unsigned char * data = NULL;
+  i_color *line_buf = NULL;
+
+  mm_log((1,"i_writejpeg(im %p, ig %p, qfactor %d)\n", im, ig, qfactor));
+  
+  i_clear_error();
+
+  if (!(im->channels==1 || im->channels==3)) { 
+    want_channels = im->channels - 1;
+  }
+  quality = qfactor;
+
+  cinfo.err = jpeg_std_error(&jerr.pub);
+  jerr.pub.error_exit = my_error_exit;
+  jerr.pub.output_message = my_output_message;
+  
+  jpeg_create_compress(&cinfo);
+
+  if (setjmp(jerr.setjmp_buffer)) {
+    jpeg_destroy_compress(&cinfo);
+    if (data)
+      myfree(data);
+    if (line_buf)
+      myfree(line_buf);
+    return 0;
+  }
+
+  jpeg_wiol_dest(&cinfo, ig);
+
+  cinfo.image_width  = im -> xsize;    /* image width and height, in pixels */
+  cinfo.image_height = im -> ysize;
+
+  if (want_channels==3) {
+    cinfo.input_components = 3;                /* # of color components per pixel */
+    cinfo.in_color_space = JCS_RGB;    /* colorspace of input image */
+  }
+
+  if (want_channels==1) {
+    cinfo.input_components = 1;                /* # of color components per pixel */
+    cinfo.in_color_space = JCS_GRAYSCALE;      /* colorspace of input image */
+  }
+
+  jpeg_set_defaults(&cinfo);
+  jpeg_set_quality(&cinfo, quality, TRUE);  /* limit to baseline-JPEG values */
+
+  got_xres = i_tags_get_float(&im->tags, "i_xres", 0, &xres);
+  got_yres = i_tags_get_float(&im->tags, "i_yres", 0, &yres);
+  if (!i_tags_get_int(&im->tags, "i_aspect_only", 0,&aspect_only))
+    aspect_only = 0;
+  if (!i_tags_get_int(&im->tags, "jpeg_density_unit", 0, &resunit))
+    resunit = 1; /* per inch */
+  if (resunit < 0 || resunit > 2) /* default to inch if invalid */
+    resunit = 1;
+  if (got_xres || got_yres) {
+    if (!got_xres)
+      xres = yres;
+    else if (!got_yres)
+      yres = xres;
+    if (aspect_only)
+      resunit = 0; /* standard tags override format tags */
+    if (resunit == 2) {
+      /* convert to per cm */
+      xres /= 2.54;
+      yres /= 2.54;
+    }
+    cinfo.density_unit = resunit;
+    cinfo.X_density = (int)(xres + 0.5);
+    cinfo.Y_density = (int)(yres + 0.5);
+  }
+
+  jpeg_start_compress(&cinfo, TRUE);
+
+  if (i_tags_find(&im->tags, "jpeg_comment", 0, &comment_entry)) {
+    jpeg_write_marker(&cinfo, JPEG_COM, 
+                      (const JOCTET *)im->tags.tags[comment_entry].data,
+                     im->tags.tags[comment_entry].size);
+  }
+
+  row_stride = im->xsize * im->channels;       /* JSAMPLEs per row in image_buffer */
+
+  if (!im->virtual && im->type == i_direct_type && im->bits == i_8_bits
+      && im->channels == want_channels) {
+    image_buffer=im->idata;
+
+    while (cinfo.next_scanline < cinfo.image_height) {
+      /* jpeg_write_scanlines expects an array of pointers to scanlines.
+       * Here the array is only one element long, but you could pass
+       * more than one scanline at a time if that's more convenient.
+       */
+      row_pointer[0] = & image_buffer[cinfo.next_scanline * row_stride];
+      (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
+    }
+  }
+  else {
+    i_color bg;
+
+    i_get_file_background(im, &bg);
+    data = mymalloc(im->xsize * im->channels);
+    if (data) {
+      while (cinfo.next_scanline < cinfo.image_height) {
+        /* jpeg_write_scanlines expects an array of pointers to scanlines.
+         * Here the array is only one element long, but you could pass
+         * more than one scanline at a time if that's more convenient.
+         */
+        i_gsamp_bg(im, 0, im->xsize, cinfo.next_scanline, data, 
+                  want_channels, &bg);
+        row_pointer[0] = data;
+        (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
+      }
+      myfree(data);
+    }
+    else {
+      jpeg_destroy_compress(&cinfo);
+      i_push_error(0, "out of memory");
+      return 0; /* out of memory? */
+    }
+  }
+
+  /* Step 6: Finish compression */
+
+  jpeg_finish_compress(&cinfo);
+
+  jpeg_destroy_compress(&cinfo);
+
+  ig->closecb(ig);
+
+  return(1);
+}
+
+/*
+=back
+
+=head1 AUTHOR
+
+Arnar M. Hrafnkelsson, addi@umich.edu
+
+=head1 SEE ALSO
+
+Imager(3)
+
+=cut
+*/
diff --git a/JPEG/imjpeg.h b/JPEG/imjpeg.h
new file mode 100644 (file)
index 0000000..3c9be13
--- /dev/null
@@ -0,0 +1,12 @@
+#ifndef IMAGER_IMJPEG_H
+#define IMAGER_IMJPEG_H
+
+#include "imdatatypes.h"
+
+i_img*
+i_readjpeg_wiol(io_glue *data, int length, char** iptc_itext, int *itlength);
+
+undef_int
+i_writejpeg_wiol(i_img *im, io_glue *ig, int qfactor);
+
+#endif
diff --git a/JPEG/t/t00load.t b/JPEG/t/t00load.t
new file mode 100644 (file)
index 0000000..49ddaf6
--- /dev/null
@@ -0,0 +1,4 @@
+#!perl -w
+use strict;
+use Test::More tests => 1;
+use_ok("Imager::File::JPEG");
diff --git a/JPEG/t/t10jpeg.t b/JPEG/t/t10jpeg.t
new file mode 100644 (file)
index 0000000..1512666
--- /dev/null
@@ -0,0 +1,411 @@
+#!perl -w
+use strict;
+use Imager qw(:all);
+use Test::More;
+use Imager::Test qw(is_color_close3 test_image_raw);
+
+-d "testout" or mkdir "testout";
+
+init_log("testout/t101jpeg.log",1);
+
+$Imager::formats{"jpeg"}
+  or plan skip_all => "no jpeg support";
+
+plan tests => 94;
+
+my $green=i_color_new(0,255,0,255);
+my $blue=i_color_new(0,0,255,255);
+my $red=i_color_new(255,0,0,255);
+
+my $img=test_image_raw();
+my $cmpimg=Imager::ImgRaw::new(150,150,3);
+
+open(FH,">testout/t101.jpg")
+  || die "cannot open testout/t101.jpg for writing\n";
+binmode(FH);
+my $IO = Imager::io_new_fd(fileno(FH));
+ok(Imager::File::JPEG::i_writejpeg_wiol($img,$IO,30), "write jpeg low level");
+close(FH);
+
+open(FH, "testout/t101.jpg") || die "cannot open testout/t101.jpg\n";
+binmode(FH);
+$IO = Imager::io_new_fd(fileno(FH));
+($cmpimg,undef) = Imager::File::JPEG::i_readjpeg_wiol($IO);
+close(FH);
+
+my $diff = sqrt(i_img_diff($img,$cmpimg))/150*150;
+print "# jpeg average mean square pixel difference: ",$diff,"\n";
+ok($cmpimg, "read jpeg low level");
+
+ok($diff < 10000, "difference between original and jpeg within bounds");
+
+Imager::i_log_entry("Starting 4\n", 1);
+my $imoo = Imager->new;
+ok($imoo->read(file=>'testout/t101.jpg'), "read jpeg OO");
+
+ok($imoo->write(file=>'testout/t101_oo.jpg'), "write jpeg OO");
+Imager::i_log_entry("Starting 5\n", 1);
+my $oocmp = Imager->new;
+ok($oocmp->read(file=>'testout/t101_oo.jpg'), "read jpeg OO for comparison");
+
+$diff = sqrt(i_img_diff($imoo->{IMG},$oocmp->{IMG}))/150*150;
+print "# OO image difference $diff\n";
+ok($diff < 10000, "difference between original and jpeg within bounds");
+
+# write failure test
+open FH, "< testout/t101.jpg" or die "Cannot open testout/t101.jpg: $!";
+binmode FH;
+ok(!$imoo->write(fd=>fileno(FH), type=>'jpeg'), 'failure handling');
+close FH;
+print "# ",$imoo->errstr,"\n";
+
+# check that the i_format tag is set
+my @fmt = $imoo->tags(name=>'i_format');
+is($fmt[0], 'jpeg', 'i_format tag');
+
+{ # check file limits are checked
+  my $limit_file = "testout/t101.jpg";
+  ok(Imager->set_file_limits(reset=>1, width=>149), "set width limit 149");
+  my $im = Imager->new;
+  ok(!$im->read(file=>$limit_file),
+     "should fail read due to size limits");
+  print "# ",$im->errstr,"\n";
+  like($im->errstr, qr/image width/, "check message");
+  
+  ok(Imager->set_file_limits(reset=>1, height=>149), "set height limit 149");
+  ok(!$im->read(file=>$limit_file),
+     "should fail read due to size limits");
+  print "# ",$im->errstr,"\n";
+  like($im->errstr, qr/image height/, "check message");
+  
+  ok(Imager->set_file_limits(reset=>1, width=>150), "set width limit 150");
+  ok($im->read(file=>$limit_file),
+     "should succeed - just inside width limit");
+  ok(Imager->set_file_limits(reset=>1, height=>150), "set height limit 150");
+  ok($im->read(file=>$limit_file),
+     "should succeed - just inside height limit");
+  
+  # 150 x 150 x 3 channel image uses 67500 bytes
+  ok(Imager->set_file_limits(reset=>1, bytes=>67499),
+     "set bytes limit 67499");
+  ok(!$im->read(file=>$limit_file),
+       "should fail - too many bytes");
+  print "# ",$im->errstr,"\n";
+  like($im->errstr, qr/storage size/, "check error message");
+  ok(Imager->set_file_limits(reset=>1, bytes=>67500),
+     "set bytes limit 67500");
+  ok($im->read(file=>$limit_file),
+     "should succeed - just inside bytes limit");
+  Imager->set_file_limits(reset=>1);
+}
+
+SKIP:
+{
+  # we don't test them all
+  my %expected_tags =
+    (
+     exif_date_time_original => "2005:11:25 00:00:00",
+     exif_flash => 0,
+     exif_image_description => "Imager Development Notes",
+     exif_make => "Canon",
+     exif_model => "CanoScan LiDE 35",
+     exif_resolution_unit => 2,
+     exif_resolution_unit_name => "inches",
+     exif_user_comment => "        Part of notes from reworking i_arc() and friends.",
+     exif_white_balance => 0,
+     exif_white_balance_name => "Auto white balance",
+    );
+  
+  my $im = Imager->new;
+  $im->read(file=>"testimg/exiftest.jpg")
+    or skip("Could not read test image:".$im->errstr, scalar keys %expected_tags);
+  
+  for my $key (keys %expected_tags) {
+    is($expected_tags{$key}, $im->tags(name => $key),
+       "test value of exif tag $key");
+  }
+}
+
+{
+  # tests that the density values are set and read correctly
+  # tests jpeg_comment too
+  my @density_tests =
+    (
+     [ 't101cm100.jpg', 
+       { 
+       jpeg_density_unit => 2, 
+       i_xres => 254, 
+       i_yres => 254
+       },
+       { 
+       jpeg_density_unit => 2, 
+       i_xres => 254, 
+       i_yres => 254,
+       i_aspect_only => undef,
+       },
+     ],
+     [
+      't101xonly.jpg',
+      {
+       i_xres => 100,
+      },
+      {
+       i_xres => 100,
+       i_yres => 100,
+       jpeg_density_unit => 1,
+       i_aspect_only => undef,
+      },
+     ],
+     [
+      't101yonly.jpg',
+      {
+       i_yres => 100,
+      },
+      {
+       i_xres => 100,
+       i_yres => 100,
+       jpeg_density_unit => 1,
+       i_aspect_only => undef,
+      },
+     ],
+     [
+      't101asponly.jpg',
+      {
+       i_xres => 50,
+       i_yres => 100,
+       i_aspect_only => 1,
+      },
+      {
+       i_xres => 50,
+       i_yres => 100,
+       i_aspect_only => 1,
+       jpeg_density_unit => 0,
+      },
+     ],
+     [
+      't101com.jpg',
+      {
+       jpeg_comment => 'test comment'
+      },
+     ],
+    );
+  
+  print "# test density tags\n";
+  # I don't care about the content
+  my $base_im = Imager->new(xsize => 10, ysize => 10);
+  for my $test (@density_tests) {
+    my ($filename, $out_tags, $expect_tags) = @$test;
+    $expect_tags ||= $out_tags;
+    
+    my $work = $base_im->copy;
+    for my $key (keys %$out_tags) {
+      $work->addtag(name => $key, value => $out_tags->{$key});
+    }
+    
+    ok($work->write(file=>"testout/$filename", type=>'jpeg'),
+       "save $filename");
+    
+    my $check = Imager->new;
+    ok($check->read(file=> "testout/$filename"),
+       "read $filename");
+    
+    my %tags;
+    for my $key (keys %$expect_tags) {
+      $tags{$key} = $check->tags(name=>$key);
+    }
+    is_deeply($expect_tags, \%tags, "check tags for $filename");
+  }
+}
+
+{ # Issue # 17981
+  # the test image has a zero-length user_comment field
+  # the code would originally attempt to convert '\0' to ' '
+  # for the first 8 bytes, even if the string was less than 
+  # 8 bytes long
+  my $im = Imager->new;
+  ok($im->read(file => 'testimg/209_yonge.jpg', type=>'jpeg'),
+     "test read of image with invalid exif_user_comment");
+  is($im->tags(name=>'exif_user_comment'), '',
+     "check exif_user_comment set correctly");
+}
+
+{ # test parseiptc handling no IPTC data correctly
+  my $saw_warn;
+  local $SIG{__WARN__} = 
+    sub {
+      ++$saw_warn;
+      print "# @_\n";
+    };
+  my $im = Imager->new;
+  ok($im->read(file => 'testout/t101.jpg', type=>'jpeg'),
+     "read jpeg with no IPTC data");
+  ok(!defined $im->{IPTCRAW}, "no iptc data");
+  my %iptc = $im->parseiptc;
+  ok(!$saw_warn, "should be no warnings");
+}
+
+{ # Issue # 18397
+  # attempting to write a 4 channel image to a bufchain would
+  # cause a seg fault.
+  # it should fail still
+  # overridden by # 29876
+  # give 4/2 channel images a background color when saving to JPEG
+  my $im = Imager->new(xsize => 16, ysize => 16, channels => 4);
+  $im->box(filled => 1, xmin => 8, color => '#FFE0C0');
+  my $data;
+  ok($im->write(data => \$data, type => 'jpeg'),
+     "should write with a black background");
+  my $imread = Imager->new;
+  ok($imread->read(data => $data, type => 'jpeg'), 'read it back');
+  is_color_close3($imread->getpixel('x' => 0, 'y' => 0), 0, 0, 0, 4,
+                 "check it's black");
+  is_color_close3($imread->getpixel('x' => 15, 'y' => 9), 255, 224, 192, 4,
+                 "check filled area filled");
+  
+  # write with a red background
+  $data = '';
+  ok($im->write(data => \$data, type => 'jpeg', i_background => '#FF0000'),
+     "write with red background");
+  ok($imread->read(data => $data, type => 'jpeg'), "read it back");
+  is_color_close3($imread->getpixel('x' => 0, 'y' => 0), 255, 0, 0, 4,
+                 "check it's red");
+  is_color_close3($imread->getpixel('x' => 15, 'y' => 9), 255, 224, 192, 4,
+                 "check filled area filled");
+}
+SKIP:
+{ # Issue # 18496
+  # If a jpeg with EXIF data containing an (invalid) IFD entry with a 
+  # type of zero is read then Imager crashes with a Floating point 
+  # exception
+  # testimg/zerojpeg.jpg was manually modified from exiftest.jpg to
+  # reproduce the problem.
+  my $im = Imager->new;
+  ok($im->read(file=>'testimg/zerotype.jpg'), "shouldn't crash");
+}
+
+SKIP:
+{ # code coverage - make sure wiol_skip_input_data is called
+  open BASEDATA, "< testimg/exiftest.jpg"
+    or skip "can't open base data", 1;
+  binmode BASEDATA;
+  my $data = do { local $/; <BASEDATA> };
+  close BASEDATA;
+  
+  substr($data, 3, 1) eq "\xE1"
+    or skip "base data isn't as expected", 1;
+  # inserting a lot of marker data here means we take the branch in 
+  # wiol_skip_input_data that refills the buffer
+  my $marker = "\xFF\xE9"; # APP9 marker
+  $marker .= pack("n", 8192) . "x" x 8190;
+  $marker x= 10; # make it take up a lot of space
+  substr($data, 2, 0) = $marker;
+  my $im = Imager->new;
+  ok($im->read(data => $data), "read with a skip of data");
+}
+
+SKIP:
+{ # code coverage - take the branch that provides a fake EOI
+  open BASEDATA, "< testimg/exiftest.jpg"
+    or skip "can't open base data", 1;
+  binmode BASEDATA;
+  my $data = do { local $/; <BASEDATA> };
+  close BASEDATA;
+  substr($data, -1000) = '';
+  
+  my $im = Imager->new;
+  ok($im->read(data => $data), "read with image data truncated");
+}
+
+{ # code coverage - make sure wiol_empty_output_buffer is called
+  my $im = Imager->new(xsize => 1000, ysize => 1000);
+  for my $x (0 .. 999) {
+    $im->line(x1 => $x, y1 => 0, x2 => $x, y2 => 999,
+             color => Imager::Color->new(rand 256, rand 256, rand 256));
+  }
+  my $data;
+  ok($im->write(data => \$data, type=>'jpeg', jpegquality => 100), 
+     "write big file to ensure wiol_empty_output_buffer is called");
+  
+  # code coverage - write failure path in wiol_empty_output_buffer
+  ok(!$im->write(callback => sub { return },
+                type => 'jpeg', jpegquality => 100),
+     "fail to write")
+    and print "# ", $im->errstr, "\n";
+}
+
+{ # code coverage - virtual image branch in i_writejpeg_wiol()
+  my $im = $imoo->copy;
+  my $immask = $im->masked;
+  ok($immask, "made a virtual image (via masked)");
+  ok($immask->virtual, "check it's virtual");
+  my $mask_data;
+  ok($immask->write(data => \$mask_data, type => 'jpeg'),
+     "write masked version");
+  my $base_data;
+  ok($im->write(data => \$base_data, type=>'jpeg'),
+     "write normal version");
+  is($base_data, $mask_data, "check the data written matches");
+}
+
+SKIP:
+{ # code coverage - IPTC data
+  # this is dummy data
+  my $iptc = "\x04\x04" .
+    "\034\002x   My Caption"
+      . "\034\002P   Tony Cook"
+       . "\034\002i   Dummy Headline!"
+         . "\034\002n   No Credit Given";
+  
+  my $app13 = "\xFF\xED" . pack("n", 2 + length $iptc) . $iptc;
+  
+  open BASEDATA, "< testimg/exiftest.jpg"
+    or skip "can't open base data", 1;
+  binmode BASEDATA;
+  my $data = do { local $/; <BASEDATA> };
+  close BASEDATA;
+  substr($data, 2, 0) = $app13;
+  
+  my $im = Imager->new;
+  ok($im->read(data => $data), "read with app13 data");
+  my %iptc = $im->parseiptc;
+  is($iptc{caption}, 'My Caption', 'check iptc caption');
+  is($iptc{photogr}, 'Tony Cook', 'check iptc photogr');
+  is($iptc{headln}, 'Dummy Headline!', 'check iptc headln');
+  is($iptc{credit}, 'No Credit Given', 'check iptc credit');
+}
+
+{ # handling of CMYK jpeg
+  # http://rt.cpan.org/Ticket/Display.html?id=20416
+  my $im = Imager->new;
+  ok($im->read(file => 'testimg/scmyk.jpg'), 'read a CMYK jpeg');
+  is($im->getchannels, 3, "check channel count");
+  my $col = $im->getpixel(x => 0, 'y' => 0);
+  ok($col, "got the 'black' pixel");
+  # this is jpeg, so we can't compare colors exactly
+  # older versions returned this pixel at a light color, but
+  # it's black in the image
+  my ($r, $g, $b) = $col->rgba;
+  cmp_ok($r, '<', 10, 'black - red low');
+  cmp_ok($g, '<', 10, 'black - green low');
+  cmp_ok($b, '<', 10, 'black - blue low');
+  $col = $im->getpixel(x => 15, 'y' => 0);
+  ok($col, "got the dark blue");
+  ($r, $g, $b) = $col->rgba;
+  cmp_ok($r, '<', 10, 'dark blue - red low');
+  cmp_ok($g, '<', 10, 'dark blue - green low');
+  cmp_ok($b, '>', 110, 'dark blue - blue middle (bottom)');
+  cmp_ok($b, '<', 130, 'dark blue - blue middle (top)');
+  $col = $im->getpixel(x => 0, 'y' => 15);
+  ok($col, "got the red");
+  ($r, $g, $b) = $col->rgba;
+  cmp_ok($r, '>', 245, 'red - red high');
+  cmp_ok($g, '<', 10, 'red - green low');
+  cmp_ok($b, '<', 10, 'red - blue low');
+}
+
+{
+  ok(grep($_ eq 'jpeg', Imager->read_types), "check jpeg in read types");
+  ok(grep($_ eq 'jpeg', Imager->write_types), "check jpeg in write types");
+}
+
+
diff --git a/JPEG/testimg/209_yonge.jpg b/JPEG/testimg/209_yonge.jpg
new file mode 100644 (file)
index 0000000..2da5733
Binary files /dev/null and b/JPEG/testimg/209_yonge.jpg differ
diff --git a/JPEG/testimg/exiftest.jpg b/JPEG/testimg/exiftest.jpg
new file mode 100755 (executable)
index 0000000..59d5291
Binary files /dev/null and b/JPEG/testimg/exiftest.jpg differ
diff --git a/JPEG/testimg/scmyk.jpg b/JPEG/testimg/scmyk.jpg
new file mode 100644 (file)
index 0000000..3886a7a
Binary files /dev/null and b/JPEG/testimg/scmyk.jpg differ
diff --git a/JPEG/testimg/zerotype.jpg b/JPEG/testimg/zerotype.jpg
new file mode 100755 (executable)
index 0000000..d5f2d90
Binary files /dev/null and b/JPEG/testimg/zerotype.jpg differ
index eac2b5a..d6df085 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -62,6 +62,19 @@ ICO/testimg/rgba3232.ico
 ICO/testimg/rgba3232.ppm
 Imager.pm
 Imager.xs
+JPEG/JPEG.pm
+JPEG/JPEG.xs
+JPEG/Makefile.PL
+JPEG/imexif.c  Experimental JPEG EXIF decoding
+JPEG/imexif.h
+JPEG/imjpeg.c
+JPEG/imjpeg.h
+JPEG/t/t00load.t
+JPEG/t/t10jpeg.t               Test jpeg support
+JPEG/testimg/209_yonge.jpg     Regression test: #17981
+JPEG/testimg/exiftest.jpg      Test image for EXIF parsing
+JPEG/testimg/scmyk.jpg Simple CMYK JPEG image
+JPEG/testimg/zerotype.jpg      Image with a zero type entry in the EXIF data
 MANIFEST
 MANIFEST.SKIP
 META.yml        Module meta-data
@@ -103,6 +116,7 @@ TIFF/TIFF.pm
 TIFF/TIFF.xs
 TIFF/imtiff.c
 TIFF/imtiff.h
+TIFF/t/t10tiff.t       Test tiff support
 TIFF/testimg/alpha.tif Alpha scaling test image
 TIFF/testimg/comp4.bmp       Compressed 4-bit/pixel BMP
 TIFF/testimg/comp4.tif       4-bit/pixel paletted TIFF
@@ -191,8 +205,6 @@ imextdef.h
 imextpl.h      Included by external modules for Perl API access
 imextpltypes.h Define Perl API function table type
 imexttypes.h    Define API function table type
-imexif.c       Experimental JPEG EXIF decoding
-imexif.h
 img16.c
 imgdouble.c     Implements double/sample images
 imio.h
@@ -204,7 +216,6 @@ io.c
 iolayer.c
 iolayer.h
 iolayert.h     IO layer types
-jpeg.c
 lib/Imager/API.pod
 lib/Imager/APIRef.pod    API function reference
 lib/Imager/Color.pm
@@ -296,14 +307,12 @@ t/t023palette.t           Test paletted images
 t/t05error.t
 t/t07iolayer.t
 t/t1000files.t          Format independent file tests
-t/t101jpeg.t           Test jpeg support
 t/t101nojpeg.t         Test handling when jpeg not available
 t/t102nopng.t          Test handling when png not available
 t/t103raw.t
 t/t104ppm.t
 t/t105nogif.t          Test handling when gif not available
 t/t106notiff.t         Test handling when tiff not available
-t/t106tiff.t           Test tiff support
 t/t107bmp.t
 t/t108tga.t
 t/t15color.t
@@ -343,7 +352,6 @@ t/t99thread.t               Test wrt to perl threads
 t/tr18561.t            Regression tests
 t/tr18561b.t
 tags.c
-testimg/209_yonge.jpg  Regression test: #17981
 testimg/alpha16.tga    16-bit/pixel TGA with alpha "channel" RT 32926
 testimg/bad1oflow.bmp   1-bit/pixel, overflow integer on 32-bit machines
 testimg/bad1wid0.bmp    1-bit/pixel, zero width
@@ -370,7 +378,6 @@ testimg/bad_asc.ppm ASCII PPM with invalid image data
 testimg/base.jpg       Base JPEG test image
 testimg/comp4.bmp       Compressed 4-bit/pixel BMP
 testimg/comp8.bmp       Compressed 8-bit/pixel BMP
-testimg/exiftest.jpg   Test image for EXIF parsing
 testimg/gimpgrad        A GIMP gradient file
 testimg/imager.pbm     Test bi-level
 testimg/junk.ppm
@@ -386,7 +393,6 @@ testimg/newgimpgrad.ggr Test GIMP Gradient file (newer type)
 testimg/penguin-base.ppm
 testimg/pgm.pgm                Simple pgm for testing the right sample is in the right place
 testimg/scale.ppm
-testimg/scmyk.jpg      Simple CMYK JPEG image
 testimg/short1.bmp      1-bit/pixel, data missing from EOF
 testimg/short24.bmp     24-bit/pixel, data missing from EOF
 testimg/short4.bmp      truncated 4bit/pixel uncompressed BMP
@@ -414,7 +420,6 @@ testimg/winrgb4.bmp  4-bit bmp base
 testimg/winrgb4off.bmp  4-bit bmp with image data offset from header
 testimg/winrgb8.bmp  8-bit bmp base
 testimg/winrgb8off.bmp  8-bit bmp with image data offset from header
-testimg/zerotype.jpg   Image with a zero type entry in the EXIF data
 tga.c           Reading and writing Targa files
 trans2.c
 transform.perl Shell interface to Imager::Transform
index bd144e1..4f4df4c 100644 (file)
@@ -43,7 +43,6 @@ my @enable; # list of drivers to enable
 my @disable; # or list of drivers to disable
 my @incpaths; # places to look for headers
 my @libpaths; # places to look for libraries
-my $noexif; # non-zero to disable EXIF parsing of JPEGs
 my $coverage; # build for coverage testing
 my $assert; # build with assertions
 GetOptions("help" => \$help,
@@ -53,7 +52,6 @@ GetOptions("help" => \$help,
            "libpath=s" => \@libpaths,
            "verbose|v" => \$VERBOSE,
            "nolog" => \$NOLOG,
-          "noexif" => \$noexif,
           'coverage' => \$coverage,
           "assert|a" => \$assert);
 
@@ -147,12 +145,6 @@ for my $frmkey (sort { $formats{$a}{order} <=> $formats{$b}{order} } keys %forma
 
 }
 
-unless ($noexif) {
-  print "EXIF support enabled\n";
-  push @defines, [ 'IMEXIF_ENABLE', 1, "Enable experimental EXIF support" ];
-  $F_OBJECT .= ' imexif.o';
-}
-
 my $F_INC  = join ' ', map "-I$_", map / / ? qq{"$_"} : $_, 
   grep !$definc{$_}, @incs;
 $F_LIBS = join(' ',map "-L$_", map / / ? qq{"$_"} : $_, 
@@ -216,6 +208,8 @@ if ($MM_ver >= 6.46) {
       [
        "PNG",
        "GIF",
+       "TIFF",
+       "JPEG",
       ],
      },
      resources =>
@@ -533,17 +527,17 @@ sub init {
   }
   push @libs, grep -d, qw(/usr/local/lib);
 
-  $formats{'jpeg'}={
-                   order=>'21',
-                   def=>'HAVE_LIBJPEG',
-                   inccheck=>sub { -e catfile($_[0], 'jpeglib.h') },
-                   libcheck=>sub { $_[0] eq "libjpeg$aext" or $_ eq "libjpeg.$lext" },
-                   libfiles=>'-ljpeg',
-                   objfiles=>'jpeg.o',
-                   docs=>q{
-                           In order to use jpeg with this module you need to have libjpeg
-                           installed on your computer}
-                  };
+  $formats{'jpeg'}={
+#                  order=>'21',
+#                  def=>'HAVE_LIBJPEG',
+#                  inccheck=>sub { -e catfile($_[0], 'jpeglib.h') },
+#                  libcheck=>sub { $_[0] eq "libjpeg$aext" or $_ eq "libjpeg.$lext" },
+#                  libfiles=>'-ljpeg',
+#                  objfiles=>'jpeg.o',
+#                  docs=>q{
+#                          In order to use jpeg with this module you need to have libjpeg
+#                          installed on your computer}
+#                 };
 
 #   $formats{'tiff'}={
 #                  order=>'23',
@@ -966,7 +960,7 @@ Usage: $0 [--enable feature1,feature2,...] [other options]
        $0 [--disable feature1,feature2,...]  [other options]
        $0 --help
 Possible feature names are:
-  jpeg T1-fonts TT-fonts freetype2
+  T1-fonts TT-fonts freetype2
 Other options:
   --verbose | -v
     Verbose library probing (or set IM_VERBOSE in the environment)
@@ -980,8 +974,6 @@ Other options:
     Build for coverage testing.
   --assert
     Build with assertions active.
-  --noexif
-    Disable EXIF parsing.
 EOS
   exit 1;
 
diff --git a/image.c b/image.c
index 74a4f0b..db4e037 100644 (file)
--- a/image.c
+++ b/image.c
@@ -2385,6 +2385,8 @@ i_img_is_monochrome(i_img *im, int *zero_is_white) {
 /*
 =item i_get_file_background(im, &bg)
 
+=category Files
+
 Retrieve the file write background color tag from the image.
 
 If not present, returns black.
@@ -2405,6 +2407,8 @@ i_get_file_background(i_img *im, i_color *bg) {
 /*
 =item i_get_file_backgroundf(im, &bg)
 
+=category Files
+
 Retrieve the file write background color tag from the image as a
 floating point color.
 
diff --git a/imexif.c b/imexif.c
deleted file mode 100644 (file)
index ff78d96..0000000
--- a/imexif.c
+++ /dev/null
@@ -1,1565 +0,0 @@
-#include "imexif.h"
-#include <stdlib.h>
-#include <float.h>
-
-/*
-=head1 NAME
-
-imexif.c - EXIF support for Imager
-
-=head1 SYNOPSIS
-
-  if (i_int_decode_exif(im, app1data, app1datasize)) {
-    // exif block seen
-  }
-
-=head1 DESCRIPTION
-
-This code provides a basic EXIF data decoder.  It is intended to be
-called from the JPEG reader code when an APP1 data block is found, and
-will set tags in the supplied image.
-
-=cut
-*/
-
-typedef enum tiff_type_tag {
-  tt_intel = 'I',
-  tt_motorola = 'M'
-} tiff_type;
-
-typedef enum {
-  ift_byte = 1,
-  ift_ascii = 2,
-  ift_short = 3,
-  ift_long = 4,
-  ift_rational = 5,
-  ift_sbyte = 6,
-  ift_undefined = 7,
-  ift_sshort = 8,
-  ift_slong = 9,
-  ift_srational = 10,
-  ift_float = 11,
-  ift_double = 12,
-  ift_last = 12 /* keep the same as the highest type code */
-} ifd_entry_type;
-
-static int type_sizes[] =
-  {
-    0, /* not used */
-    1, /* byte */
-    1, /* ascii */
-    2, /* short */
-    4, /* long */
-    8, /* rational */
-    1, /* sbyte */
-    1, /* undefined */
-    2, /* sshort */
-    4, /* slong */
-    8, /* srational */
-    4, /* float */
-    8, /* double */
-  };
-
-typedef struct {
-  int tag;
-  int type;
-  int count;
-  int item_size;
-  int size;
-  int offset;
-} ifd_entry;
-
-typedef struct {
-  int tag;
-  char const *name;
-} tag_map;
-
-typedef struct {
-  int tag;
-  char const *name;
-  tag_map const *map;
-  int map_count;
-} tag_value_map;
-
-#define PASTE(left, right) PASTE_(left, right)
-#define PASTE_(left, right) left##right
-#define QUOTE(value) #value
-
-#define VALUE_MAP_ENTRY(name) \
-  { \
-    PASTE(tag_, name), \
-    "exif_" QUOTE(name) "_name", \
-    PASTE(name, _values), \
-    ARRAY_COUNT(PASTE(name, _values)) \
-  }
-
-/* we don't process every tag */
-#define tag_make 271
-#define tag_model 272
-#define tag_orientation 274
-#define tag_x_resolution 282
-#define tag_y_resolution 283
-#define tag_resolution_unit 296
-#define tag_copyright 33432
-#define tag_software 305
-#define tag_artist 315
-#define tag_date_time 306
-#define tag_image_description 270
-
-#define tag_exif_ifd 34665
-#define tag_gps_ifd 34853
-
-#define resunit_none 1
-#define resunit_inch 2
-#define resunit_centimeter 3
-
-/* tags from the EXIF ifd */
-#define tag_exif_version 0x9000
-#define tag_flashpix_version 0xA000
-#define tag_color_space 0xA001
-#define tag_component_configuration 0x9101
-#define tag_component_bits_per_pixel 0x9102
-#define tag_pixel_x_dimension 0xA002
-#define tag_pixel_y_dimension 0xA003
-#define tag_maker_note 0x927C
-#define tag_user_comment 0x9286
-#define tag_related_sound_file 0xA004
-#define tag_date_time_original 0x9003
-#define tag_date_time_digitized 0x9004
-#define tag_sub_sec_time 0x9290
-#define tag_sub_sec_time_original 0x9291
-#define tag_sub_sec_time_digitized 0x9292
-#define tag_image_unique_id 0xA420
-#define tag_exposure_time 0x829a
-#define tag_f_number 0x829D
-#define tag_exposure_program 0x8822
-#define tag_spectral_sensitivity 0x8824
-#define tag_iso_speed_ratings 0x8827
-#define tag_oecf 0x8828
-#define tag_shutter_speed 0x9201
-#define tag_aperture 0x9202
-#define tag_brightness 0x9203
-#define tag_exposure_bias 0x9204
-#define tag_max_aperture 0x9205
-#define tag_subject_distance 0x9206
-#define tag_metering_mode 0x9207
-#define tag_light_source 0x9208
-#define tag_flash 0x9209
-#define tag_focal_length 0x920a
-#define tag_subject_area 0x9214
-#define tag_flash_energy 0xA20B
-#define tag_spatial_frequency_response 0xA20C
-#define tag_focal_plane_x_resolution 0xA20e
-#define tag_focal_plane_y_resolution 0xA20F
-#define tag_focal_plane_resolution_unit 0xA210
-#define tag_subject_location 0xA214
-#define tag_exposure_index 0xA215
-#define tag_sensing_method 0xA217
-#define tag_file_source 0xA300
-#define tag_scene_type 0xA301
-#define tag_cfa_pattern 0xA302
-#define tag_custom_rendered 0xA401
-#define tag_exposure_mode 0xA402
-#define tag_white_balance 0xA403
-#define tag_digital_zoom_ratio 0xA404
-#define tag_focal_length_in_35mm_film 0xA405
-#define tag_scene_capture_type 0xA406
-#define tag_gain_control 0xA407
-#define tag_contrast 0xA408
-#define tag_saturation 0xA409
-#define tag_sharpness 0xA40A
-#define tag_device_setting_description 0xA40B
-#define tag_subject_distance_range 0xA40C
-
-/* GPS tags */
-#define tag_gps_version_id 0
-#define tag_gps_latitude_ref 1
-#define tag_gps_latitude 2
-#define tag_gps_longitude_ref 3
-#define tag_gps_longitude 4
-#define tag_gps_altitude_ref 5
-#define tag_gps_altitude 6
-#define tag_gps_time_stamp 7
-#define tag_gps_satellites 8
-#define tag_gps_status 9
-#define tag_gps_measure_mode 10
-#define tag_gps_dop 11
-#define tag_gps_speed_ref 12
-#define tag_gps_speed 13
-#define tag_gps_track_ref 14
-#define tag_gps_track 15
-#define tag_gps_img_direction_ref 16
-#define tag_gps_img_direction 17
-#define tag_gps_map_datum 18
-#define tag_gps_dest_latitude_ref 19
-#define tag_gps_dest_latitude 20
-#define tag_gps_dest_longitude_ref 21
-#define tag_gps_dest_longitude 22
-#define tag_gps_dest_bearing_ref 23
-#define tag_gps_dest_bearing 24
-#define tag_gps_dest_distance_ref 25
-#define tag_gps_dest_distance 26
-#define tag_gps_processing_method 27
-#define tag_gps_area_information 28
-#define tag_gps_date_stamp 29
-#define tag_gps_differential 30
-
-/* don't use this on pointers */
-#define ARRAY_COUNT(array) (sizeof(array)/sizeof(*array))
-
-/* in memory tiff structure */
-typedef struct {
-  /* the data we use as a tiff */
-  unsigned char *base;
-  size_t size;
-
-  /* intel or motorola byte order */
-  tiff_type type;
-
-  /* initial ifd offset */
-  unsigned long first_ifd_offset;
-  
-  /* size (in entries) and data */
-  int ifd_size; 
-  ifd_entry *ifd;
-  unsigned long next_ifd;
-} imtiff;
-
-static int tiff_init(imtiff *tiff, unsigned char *base, size_t length);
-static int tiff_load_ifd(imtiff *tiff, unsigned long offset);
-static void tiff_final(imtiff *tiff);
-static void tiff_clear_ifd(imtiff *tiff);
-#if 0 /* currently unused, but that may change */
-static int tiff_get_bytes(imtiff *tiff, unsigned char *to, size_t offset, 
-                         size_t count);
-#endif
-static int tiff_get_tag_double(imtiff *, int index, double *result);
-static int tiff_get_tag_int(imtiff *, int index, int *result);
-static unsigned tiff_get16(imtiff *, unsigned long offset);
-static unsigned tiff_get32(imtiff *, unsigned long offset);
-static int tiff_get16s(imtiff *, unsigned long offset);
-static int tiff_get32s(imtiff *, unsigned long offset);
-static double tiff_get_rat(imtiff *, unsigned long offset);
-static double tiff_get_rats(imtiff *, unsigned long offset);
-static void save_ifd0_tags(i_img *im, imtiff *tiff, unsigned long *exif_ifd_offset, unsigned long *gps_ifd_offset);
-static void save_exif_ifd_tags(i_img *im, imtiff *tiff);
-static void save_gps_ifd_tags(i_img *im, imtiff *tiff);
-static void
-copy_string_tags(i_img *im, imtiff *tiff, tag_map *map, int map_count);
-static void
-copy_int_tags(i_img *im, imtiff *tiff, tag_map *map, int map_count);
-static void
-copy_rat_tags(i_img *im, imtiff *tiff, tag_map *map, int map_count);
-static void
-copy_num_array_tags(i_img *im, imtiff *tiff, tag_map *map, int map_count);
-static void
-copy_name_tags(i_img *im, imtiff *tiff, tag_value_map *map, int map_count);
-static void process_maker_note(i_img *im, imtiff *tiff, unsigned long offset, size_t size);
-
-/*
-=head1 PUBLIC FUNCTIONS
-
-These functions are available to other parts of Imager.  They aren't
-intended to be called from outside of Imager.
-
-=over
-
-=item i_int_decode_exit
-
-i_int_decode_exif(im, data_base, data_size);
-
-The data from data_base for data_size bytes will be scanned for EXIF
-data.
-
-Any data found will be used to set tags in the supplied image.
-
-The intent is that invalid EXIF data will simply fail to set tags, and
-write to the log.  In no case should this code exit when supplied
-invalid data.
-
-Returns true if an Exif header was seen.
-
-*/
-
-int
-i_int_decode_exif(i_img *im, unsigned char *data, size_t length) {
-  imtiff tiff;
-  unsigned long exif_ifd_offset = 0;
-  unsigned long gps_ifd_offset = 0;
-  /* basic checks - must start with "Exif\0\0" */
-
-  if (length < 6 || memcmp(data, "Exif\0\0", 6) != 0) {
-    return 0;
-  }
-
-  data += 6;
-  length -= 6;
-
-  if (!tiff_init(&tiff, data, length)) {
-    mm_log((2, "Exif header found, but no valid TIFF header\n"));
-    return 1;
-  }
-  if (!tiff_load_ifd(&tiff, tiff.first_ifd_offset)) {
-    mm_log((2, "Exif header found, but could not load IFD 0\n"));
-    tiff_final(&tiff);
-    return 1;
-  }
-
-  save_ifd0_tags(im, &tiff, &exif_ifd_offset, &gps_ifd_offset);
-
-  if (exif_ifd_offset) {
-    if (tiff_load_ifd(&tiff, exif_ifd_offset)) {
-      save_exif_ifd_tags(im, &tiff);
-    }
-    else {
-      mm_log((2, "Could not load Exif IFD\n"));
-    }
-  }
-
-  if (gps_ifd_offset) {
-    if (tiff_load_ifd(&tiff, gps_ifd_offset)) {
-      save_gps_ifd_tags(im, &tiff);
-    }
-    else {
-      mm_log((2, "Could not load GPS IFD\n"));
-    }
-  }
-
-  tiff_final(&tiff);
-
-  return 1;
-}
-
-/*
-
-=back
-
-=head1 INTERNAL FUNCTIONS
-
-=head2 EXIF Processing 
-
-=over
-
-=item save_ifd0_tags
-
-save_ifd0_tags(im, tiff, &exif_ifd_offset, &gps_ifd_offset)
-
-Scans the currently loaded IFD for tags expected in IFD0 and sets them
-in the image.
-
-Sets *exif_ifd_offset to the offset of the EXIF IFD if found.
-
-=cut
-
-*/
-
-static tag_map ifd0_string_tags[] =
-  {
-    { tag_make, "exif_make" },
-    { tag_model, "exif_model" },
-    { tag_copyright, "exif_copyright" },
-    { tag_software, "exif_software" },
-    { tag_artist, "exif_artist" },
-    { tag_date_time, "exif_date_time" },
-    { tag_image_description, "exif_image_description" },
-  };
-
-static const int ifd0_string_tag_count = ARRAY_COUNT(ifd0_string_tags);
-
-static tag_map ifd0_int_tags[] =
-  {
-    { tag_orientation, "exif_orientation", },
-    { tag_resolution_unit, "exif_resolution_unit" },
-  };
-
-static const int ifd0_int_tag_count = ARRAY_COUNT(ifd0_int_tags);
-
-static tag_map ifd0_rat_tags[] =
-  {
-    { tag_x_resolution, "exif_x_resolution" },
-    { tag_y_resolution, "exif_y_resolution" },
-  };
-
-static tag_map resolution_unit_values[] =
-  {
-    { 1, "none" },
-    { 2, "inches" },
-    { 3, "centimeters" },
-  };
-
-static tag_value_map ifd0_values[] =
-  {
-    VALUE_MAP_ENTRY(resolution_unit),
-  };
-
-static void
-save_ifd0_tags(i_img *im, imtiff *tiff, unsigned long *exif_ifd_offset,
-              unsigned long *gps_ifd_offset) {
-  int tag_index;
-  int work;
-  ifd_entry *entry;
-
-  for (tag_index = 0, entry = tiff->ifd; 
-       tag_index < tiff->ifd_size; ++tag_index, ++entry) {
-    switch (entry->tag) {
-    case tag_exif_ifd:
-      if (tiff_get_tag_int(tiff, tag_index, &work))
-       *exif_ifd_offset = work;
-      break;
-
-    case tag_gps_ifd:
-      if (tiff_get_tag_int(tiff, tag_index, &work))
-       *gps_ifd_offset = work;
-      break;
-    }
-  }
-
-  copy_string_tags(im, tiff, ifd0_string_tags, ifd0_string_tag_count);
-  copy_int_tags(im, tiff, ifd0_int_tags, ifd0_int_tag_count);
-  copy_rat_tags(im, tiff, ifd0_rat_tags, ARRAY_COUNT(ifd0_rat_tags));
-  copy_name_tags(im, tiff, ifd0_values, ARRAY_COUNT(ifd0_values));
-  /* copy_num_array_tags(im, tiff, ifd0_num_arrays, ARRAY_COUNT(ifd0_num_arrays)); */
-}
-
-/*
-=item save_exif_ifd_tags
-
-save_exif_ifd_tags(im, tiff)
-
-Scans the currently loaded IFD for the tags expected in the EXIF IFD
-and sets them as tags in the image.
-
-=cut
-
-*/
-
-static tag_map exif_ifd_string_tags[] =
-  {
-    { tag_exif_version, "exif_version", },
-    { tag_flashpix_version, "exif_flashpix_version", },
-    { tag_related_sound_file, "exif_related_sound_file", },
-    { tag_date_time_original, "exif_date_time_original", },
-    { tag_date_time_digitized, "exif_date_time_digitized", },
-    { tag_sub_sec_time, "exif_sub_sec_time" },
-    { tag_sub_sec_time_original, "exif_sub_sec_time_original" },
-    { tag_sub_sec_time_digitized, "exif_sub_sec_time_digitized" },
-    { tag_image_unique_id, "exif_image_unique_id" },
-    { tag_spectral_sensitivity, "exif_spectral_sensitivity" },
-  };
-
-static const int exif_ifd_string_tag_count = ARRAY_COUNT(exif_ifd_string_tags);
-
-static tag_map exif_ifd_int_tags[] =
-  {
-    { tag_color_space, "exif_color_space" },
-    { tag_exposure_program, "exif_exposure_program" },
-    { tag_metering_mode, "exif_metering_mode" },
-    { tag_light_source, "exif_light_source" },
-    { tag_flash, "exif_flash" },
-    { tag_focal_plane_resolution_unit, "exif_focal_plane_resolution_unit" },
-    { tag_subject_location, "exif_subject_location" },
-    { tag_sensing_method, "exif_sensing_method" },
-    { tag_custom_rendered, "exif_custom_rendered" },
-    { tag_exposure_mode, "exif_exposure_mode" },
-    { tag_white_balance, "exif_white_balance" },
-    { tag_focal_length_in_35mm_film, "exif_focal_length_in_35mm_film" },
-    { tag_scene_capture_type, "exif_scene_capture_type" },
-    { tag_contrast, "exif_contrast" },
-    { tag_saturation, "exif_saturation" },
-    { tag_sharpness, "exif_sharpness" },
-    { tag_subject_distance_range, "exif_subject_distance_range" },
-  };
-
-
-static const int exif_ifd_int_tag_count = ARRAY_COUNT(exif_ifd_int_tags);
-
-static tag_map exif_ifd_rat_tags[] =
-  {
-    { tag_exposure_time, "exif_exposure_time" },
-    { tag_f_number, "exif_f_number" },
-    { tag_shutter_speed, "exif_shutter_speed" },
-    { tag_aperture, "exif_aperture" },
-    { tag_brightness, "exif_brightness" },
-    { tag_exposure_bias, "exif_exposure_bias" },
-    { tag_max_aperture, "exif_max_aperture" },
-    { tag_subject_distance, "exif_subject_distance" },
-    { tag_focal_length, "exif_focal_length" },
-    { tag_flash_energy, "exif_flash_energy" },
-    { tag_focal_plane_x_resolution, "exif_focal_plane_x_resolution" },
-    { tag_focal_plane_y_resolution, "exif_focal_plane_y_resolution" },
-    { tag_exposure_index, "exif_exposure_index" },
-    { tag_digital_zoom_ratio, "exif_digital_zoom_ratio" },
-    { tag_gain_control, "exif_gain_control" },
-  };
-
-static const int exif_ifd_rat_tag_count = ARRAY_COUNT(exif_ifd_rat_tags);
-
-static tag_map exposure_mode_values[] =
-  {
-    { 0, "Auto exposure" },
-    { 1, "Manual exposure" },
-    { 2, "Auto bracket" },
-  };
-static tag_map color_space_values[] =
-  {
-    { 1, "sRGB" },
-    { 0xFFFF, "Uncalibrated" },
-  };
-
-static tag_map exposure_program_values[] =
-  {
-    { 0, "Not defined" },
-    { 1, "Manual" },
-    { 2, "Normal program" },
-    { 3, "Aperture priority" },
-    { 4, "Shutter priority" },
-    { 5, "Creative program" },
-    { 6, "Action program" },
-    { 7, "Portrait mode" },
-    { 8, "Landscape mode" },
-  };
-
-static tag_map metering_mode_values[] =
-  {
-    { 0, "unknown" },
-    { 1, "Average" },
-    { 2, "CenterWeightedAverage" },
-    { 3, "Spot" },
-    { 4, "MultiSpot" },
-    { 5, "Pattern" },
-    { 6, "Partial" },
-    { 255, "other" },
-  };
-
-static tag_map light_source_values[] =
-  {
-    { 0, "unknown" },
-    { 1, "Daylight" },
-    { 2, "Fluorescent" },
-    { 3, "Tungsten (incandescent light)" },
-    { 4, "Flash" },
-    { 9, "Fine weather" },
-    { 10, "Cloudy weather" },
-    { 11, "Shade" },
-    { 12, "Daylight fluorescent (D 5700 Ð 7100K)" },
-    { 13, "Day white fluorescent (N 4600 Ð 5400K)" },
-    { 14, "Cool white fluorescent (W 3900 Ð 4500K)" },
-    { 15, "White fluorescent (WW 3200 Ð 3700K)" },
-    { 17, "Standard light A" },
-    { 18, "Standard light B" },
-    { 19, "Standard light C" },
-    { 20, "D55" },
-    { 21, "D65" },
-    { 22, "D75" },
-    { 23, "D50" },
-    { 24, "ISO studio tungsten" },
-    { 255, "other light source" },
-  };
-
-static tag_map flash_values[] =
-  {
-    { 0x0000, "Flash did not fire." },
-    { 0x0001, "Flash fired." },
-    { 0x0005, "Strobe return light not detected." },
-    { 0x0007, "Strobe return light detected." },
-    { 0x0009, "Flash fired, compulsory flash mode" },
-    { 0x000D, "Flash fired, compulsory flash mode, return light not detected" },
-    { 0x000F, "Flash fired, compulsory flash mode, return light detected" },
-    { 0x0010, "Flash did not fire, compulsory flash mode" },
-    { 0x0018, "Flash did not fire, auto mode" },
-    { 0x0019, "Flash fired, auto mode" },
-    { 0x001D, "Flash fired, auto mode, return light not detected" },
-    { 0x001F, "Flash fired, auto mode, return light detected" },
-    { 0x0020, "No flash function" },
-    { 0x0041, "Flash fired, red-eye reduction mode" },
-    { 0x0045, "Flash fired, red-eye reduction mode, return light not detected" },
-    { 0x0047, "Flash fired, red-eye reduction mode, return light detected" },
-    { 0x0049, "Flash fired, compulsory flash mode, red-eye reduction mode" },
-    { 0x004D, "Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected" },
-    { 0x004F, "Flash fired, compulsory flash mode, red-eye reduction mode, return light detected" },
-    { 0x0059, "Flash fired, auto mode, red-eye reduction mode" },
-    { 0x005D, "Flash fired, auto mode, return light not detected, red-eye reduction mode" },
-    { 0x005F, "Flash fired, auto mode, return light detected, red-eye reduction mode" },
-  };
-
-static tag_map sensing_method_values[] =
-  {
-    { 1, "Not defined" },
-    { 2, "One-chip color area sensor" },
-    { 3, "Two-chip color area sensor" },
-    { 4, "Three-chip color area sensor" },
-    { 5, "Color sequential area sensor" },
-    { 7, "Trilinear sensor" },
-    { 8, "Color sequential linear sensor" },
-  };
-
-static tag_map custom_rendered_values[] =
-  {
-    { 0, "Normal process" },
-    { 1, "Custom process" },
-  };
-
-static tag_map white_balance_values[] =
-  {
-    { 0, "Auto white balance" },
-    { 1, "Manual white balance" },
-  };
-
-static tag_map scene_capture_type_values[] =
-  {
-    { 0, "Standard" },
-    { 1, "Landscape" },
-    { 2, "Portrait" },
-    { 3, "Night scene" },
-  };
-
-static tag_map gain_control_values[] =
-  {
-    { 0, "None" },
-    { 1, "Low gain up" },
-    { 2, "High gain up" },
-    { 3, "Low gain down" },
-    { 4, "High gain down" },
-  };
-
-static tag_map contrast_values[] =
-  {
-    { 0, "Normal" },
-    { 1, "Soft" },
-    { 2, "Hard" },
-  };
-
-static tag_map saturation_values[] =
-  {
-    { 0, "Normal" },
-    { 1, "Low saturation" },
-    { 2, "High saturation" },
-  };
-
-static tag_map sharpness_values[] =
-  {
-    { 0, "Normal" },
-    { 1, "Soft" },
-    { 2, "Hard" },
-  };
-
-static tag_map subject_distance_range_values[] =
-  {
-    { 0, "unknown" },
-    { 1, "Macro" },
-    { 2, "Close view" },
-    { 3, "Distant view" },
-  };
-
-#define focal_plane_resolution_unit_values resolution_unit_values
-
-static tag_value_map exif_ifd_values[] =
-  {
-    VALUE_MAP_ENTRY(exposure_mode),
-    VALUE_MAP_ENTRY(color_space),
-    VALUE_MAP_ENTRY(exposure_program),
-    VALUE_MAP_ENTRY(metering_mode),
-    VALUE_MAP_ENTRY(light_source),
-    VALUE_MAP_ENTRY(flash),
-    VALUE_MAP_ENTRY(sensing_method),
-    VALUE_MAP_ENTRY(custom_rendered),
-    VALUE_MAP_ENTRY(white_balance),
-    VALUE_MAP_ENTRY(scene_capture_type),
-    VALUE_MAP_ENTRY(gain_control),
-    VALUE_MAP_ENTRY(contrast),
-    VALUE_MAP_ENTRY(saturation),
-    VALUE_MAP_ENTRY(sharpness),
-    VALUE_MAP_ENTRY(subject_distance_range),
-    VALUE_MAP_ENTRY(focal_plane_resolution_unit),
-  };
-
-static tag_map exif_num_arrays[] =
-  {
-    { tag_iso_speed_ratings, "exif_iso_speed_ratings" },
-    { tag_subject_area, "exif_subject_area" },
-    { tag_subject_location, "exif_subject_location" },
-  };
-
-static void
-save_exif_ifd_tags(i_img *im, imtiff *tiff) {
-  int i, tag_index;
-  ifd_entry *entry;
-  char *user_comment;
-  unsigned long maker_note_offset = 0;
-  size_t maker_note_size = 0;
-
-  for (tag_index = 0, entry = tiff->ifd; 
-       tag_index < tiff->ifd_size; ++tag_index, ++entry) {
-    switch (entry->tag) {
-    case tag_user_comment:
-      /* I don't want to trash the source, so work on a copy */
-      user_comment = mymalloc(entry->size);
-      memcpy(user_comment, tiff->base + entry->offset, entry->size);
-      /* the first 8 bytes indicate the encoding, make them into spaces
-        for better presentation */
-      for (i = 0; i < entry->size && i < 8; ++i) {
-       if (user_comment[i] == '\0')
-         user_comment[i] = ' ';
-      }
-      /* find the actual end of the string */
-      while (i < entry->size && user_comment[i])
-       ++i;
-      i_tags_add(&im->tags, "exif_user_comment", 0, user_comment, i, 0);
-      myfree(user_comment);
-      break;
-
-    case tag_maker_note:
-      maker_note_offset = entry->offset;
-      maker_note_size = entry->size;
-      break;
-
-      /* the following aren't processed yet */
-    case tag_oecf:
-    case tag_spatial_frequency_response:
-    case tag_file_source:
-    case tag_scene_type:
-    case tag_cfa_pattern:
-    case tag_device_setting_description:
-    case tag_subject_area:
-      break;
-    }
-  }
-
-  copy_string_tags(im, tiff, exif_ifd_string_tags, exif_ifd_string_tag_count);
-  copy_int_tags(im, tiff, exif_ifd_int_tags, exif_ifd_int_tag_count);
-  copy_rat_tags(im, tiff, exif_ifd_rat_tags, exif_ifd_rat_tag_count);
-  copy_name_tags(im, tiff, exif_ifd_values, ARRAY_COUNT(exif_ifd_values));
-  copy_num_array_tags(im, tiff, exif_num_arrays, ARRAY_COUNT(exif_num_arrays));
-
-  /* This trashes the IFD - make sure it's done last */
-  if (maker_note_offset) {
-    process_maker_note(im, tiff, maker_note_offset, maker_note_size);
-  }
-}
-
-static tag_map gps_ifd_string_tags[] =
-  {
-    { tag_gps_version_id, "exif_gps_version_id" },
-    { tag_gps_latitude_ref, "exif_gps_latitude_ref" },
-    { tag_gps_longitude_ref, "exif_gps_longitude_ref" },
-    { tag_gps_altitude_ref, "exif_gps_altitude_ref" },
-    { tag_gps_satellites, "exif_gps_satellites" },
-    { tag_gps_status, "exif_gps_status" },
-    { tag_gps_measure_mode, "exif_gps_measure_mode" },
-    { tag_gps_speed_ref, "exif_gps_speed_ref" },
-    { tag_gps_track_ref, "exif_gps_track_ref" },
-  };
-
-static tag_map gps_ifd_int_tags[] =
-  {
-    { tag_gps_differential, "exif_gps_differential" },
-  };
-
-static tag_map gps_ifd_rat_tags[] =
-  {
-    { tag_gps_altitude, "exif_gps_altitude" },
-    { tag_gps_time_stamp, "exif_gps_time_stamp" },
-    { tag_gps_dop, "exif_gps_dop" },
-    { tag_gps_speed, "exif_gps_speed" },
-    { tag_gps_track, "exif_track" }
-  };
-
-static tag_map gps_differential_values [] =
-  {
-    { 0, "without differential correction" },
-    { 1, "Differential correction applied" },
-  };
-
-static tag_value_map gps_ifd_values[] =
-  {
-    VALUE_MAP_ENTRY(gps_differential),
-  };
-
-static tag_map gps_num_arrays[] =
-  {
-    { tag_gps_latitude, "exif_gps_latitude" },
-    { tag_gps_longitude, "exif_gps_longitude" },
-  };
-
-static void
-save_gps_ifd_tags(i_img *im, imtiff *tiff) {
-  /* int i, tag_index; 
-  int work;
-  ifd_entry *entry; */
-
-  /* for (tag_index = 0, entry = tiff->ifd; 
-       tag_index < tiff->ifd_size; ++tag_index, ++entry) {
-    switch (entry->tag) {
-      break;
-    }
-    }*/
-
-  copy_string_tags(im, tiff, gps_ifd_string_tags, 
-        ARRAY_COUNT(gps_ifd_string_tags));
-  copy_int_tags(im, tiff, gps_ifd_int_tags, ARRAY_COUNT(gps_ifd_int_tags));
-  copy_rat_tags(im, tiff, gps_ifd_rat_tags, ARRAY_COUNT(gps_ifd_rat_tags));
-  copy_name_tags(im, tiff, gps_ifd_values, ARRAY_COUNT(gps_ifd_values));
-  copy_num_array_tags(im, tiff, gps_num_arrays, ARRAY_COUNT(gps_num_arrays));
-}
-
-/*
-=item process_maker_note
-
-This is a stub for processing the maker note tag.
-
-Maker notes aren't covered by EXIF itself and in general aren't
-documented by the manufacturers.
-
-=cut
-*/
-
-static void
-process_maker_note(i_img *im, imtiff *tiff, unsigned long offset, size_t size) {
-  /* this will be added in a future release */
-}
-
-/*
-=back
-
-=head2 High level TIFF functions
-
-To avoid relying upon tifflib when we're not processing an image we
-have some simple in-memory TIFF file management.
-
-=over
-
-=item tiff_init
-
-imtiff tiff;
-if (tiff_init(tiff, data_base, data_size)) {
-  // success
-}
-
-Initialize the tiff data structure.
-
-Scans for the byte order and version markers, and stores the offset to
-the first IFD (IFD0 in EXIF) in first_ifd_offset.
-
-=cut
-*/
-
-static int
-tiff_init(imtiff *tiff, unsigned char *data, size_t length) {
-  int version;
-
-  tiff->base = data;
-  tiff->size = length;
-  if (length < 8) /* well... would have to be much bigger to be useful */
-    return 0;
-  if (data[0] == 'M' && data[1] == 'M')
-    tiff->type = tt_motorola;
-  else if (data[0] == 'I' && data[1] == 'I') 
-    tiff->type = tt_intel;
-  else
-    return 0; /* invalid header */
-
-  version = tiff_get16(tiff, 2);
-  if (version != 42)
-    return 0;
-
-  tiff->first_ifd_offset = tiff_get32(tiff, 4);
-  if (tiff->first_ifd_offset > length || tiff->first_ifd_offset < 8)
-    return 0;
-
-  tiff->ifd_size = 0;
-  tiff->ifd = NULL;
-  tiff->next_ifd = 0;
-
-  return 1;
-}
-
-/*
-=item tiff_final
-
-tiff_final(&tiff)
-
-Clean up the tiff structure initialized by tiff_init()
-
-=cut
-*/
-
-static void
-tiff_final(imtiff *tiff) {
-  tiff_clear_ifd(tiff);
-}
-
-/*
-=item tiff_load_ifd
-
-if (tiff_load_ifd(tiff, offset)) {
-  // process the ifd
-}
-
-Loads the IFD from the given offset into the tiff objects ifd.
-
-This can fail if the IFD extends beyond end of file, or if any data
-offsets combined with their sizes, extends beyond end of file.
-
-Returns true on success.
-
-=cut
-*/
-
-static int
-tiff_load_ifd(imtiff *tiff, unsigned long offset) {
-  unsigned count;
-  int ifd_size;
-  ifd_entry *entries = NULL;
-  int i;
-  unsigned long base;
-
-  tiff_clear_ifd(tiff);
-
-  /* rough check count + 1 entry + next offset */
-  if (offset + (2+12+4) > tiff->size) {
-    mm_log((2, "offset %uld beyond end off Exif block"));
-    return 0;
-  }
-
-  count = tiff_get16(tiff, offset);
-  
-  /* check we can fit the whole thing */
-  ifd_size = 2 + count * 12 + 4; /* count + count entries + next offset */
-  if (offset + ifd_size > tiff->size) {
-    mm_log((2, "offset %uld beyond end off Exif block"));
-    return 0;
-  }
-
-  entries = mymalloc(count * sizeof(ifd_entry));
-  memset(entries, 0, count * sizeof(ifd_entry));
-  base = offset + 2;
-  for (i = 0; i < count; ++i) {
-    ifd_entry *entry = entries + i;
-    entry->tag = tiff_get16(tiff, base);
-    entry->type = tiff_get16(tiff, base+2);
-    entry->count = tiff_get32(tiff, base+4);
-    if (entry->type >= 1 && entry->type <= ift_last) {
-      entry->item_size = type_sizes[entry->type];
-      entry->size = entry->item_size * entry->count;
-      if (entry->size / entry->item_size != entry->count) {
-       myfree(entries);
-       mm_log((1, "Integer overflow calculating tag data size processing EXIF block\n"));
-       return 0;
-      }
-      else if (entry->size <= 4) {
-       entry->offset = base + 8;
-      }
-      else {
-       entry->offset = tiff_get32(tiff, base+8);
-       if (entry->offset + entry->size > tiff->size) {
-         mm_log((2, "Invalid data offset processing IFD\n"));
-         myfree(entries);
-         return 0;
-       }
-      }
-    }
-    else {
-      entry->size = 0;
-      entry->offset = 0;
-    }
-    base += 12;
-  }
-
-  tiff->ifd_size = count;
-  tiff->ifd = entries;
-  tiff->next_ifd = tiff_get32(tiff, base);
-
-  return 1;
-}
-
-/*
-=item tiff_clear_ifd
-
-tiff_clear_ifd(tiff)
-
-Releases any memory associated with the stored IFD and resets the IFD
-pointers.
-
-This is called by tiff_load_ifd() and tiff_final().
-
-=cut
-*/
-
-static void
-tiff_clear_ifd(imtiff *tiff) {
-  if (tiff->ifd_size && tiff->ifd) {
-    myfree(tiff->ifd);
-    tiff->ifd_size = 0;
-    tiff->ifd = NULL;
-  }
-}
-
-/*
-=item tiff_get_tag_double
-
-  double value;
-  if (tiff_get_tag(tiff, index, &value)) {
-    // process value
-  }
-
-Attempts to retrieve a double value from the given index in the
-current IFD.
-
-The value must have a count of 1.
-
-=cut
-*/
-
-static int
-tiff_get_tag_double_array(imtiff *tiff, int index, double *result, 
-                         int array_index) {
-  ifd_entry *entry;
-  unsigned long offset;
-  if (index < 0 || index >= tiff->ifd_size) {
-    i_fatal(3, "tiff_get_tag_double_array() tag index out of range");
-  }
-  
-  entry = tiff->ifd + index;
-  if (array_index < 0 || array_index >= entry->count) {
-    mm_log((3, "tiff_get_tag_double_array() array index out of range"));
-    return 0;
-  }
-
-  offset = entry->offset + array_index * entry->item_size;
-
-  switch (entry->type) {
-  case ift_short:
-    *result = tiff_get16(tiff, offset);
-    return 1;
-   
-  case ift_long:
-    *result = tiff_get32(tiff, offset);
-    return 1;
-
-  case ift_rational:
-    *result = tiff_get_rat(tiff, offset);
-    return 1;
-
-  case ift_sshort:
-    *result = tiff_get16s(tiff, offset);
-    return 1;
-
-  case ift_slong:
-    *result = tiff_get32s(tiff, offset);
-    return 1;
-
-  case ift_srational:
-    *result = tiff_get_rats(tiff, offset);
-    return 1;
-
-  case ift_byte:
-    *result = *(tiff->base + offset);
-    return 1;
-  }
-
-  return 0;
-}
-
-/*
-=item tiff_get_tag_double
-
-  double value;
-  if (tiff_get_tag(tiff, index, &value)) {
-    // process value
-  }
-
-Attempts to retrieve a double value from the given index in the
-current IFD.
-
-The value must have a count of 1.
-
-=cut
-*/
-
-static int
-tiff_get_tag_double(imtiff *tiff, int index, double *result) {
-  ifd_entry *entry;
-  if (index < 0 || index >= tiff->ifd_size) {
-    i_fatal(3, "tiff_get_tag_double() index out of range");
-  }
-  
-  entry = tiff->ifd + index;
-  if (entry->count != 1) {
-    mm_log((3, "tiff_get_tag_double() called on tag with multiple values"));
-    return 0;
-  }
-
-  return tiff_get_tag_double_array(tiff, index, result, 0);
-}
-
-/*
-=item tiff_get_tag_int_array
-
-  int value;
-  if (tiff_get_tag_int_array(tiff, index, &value, array_index)) {
-    // process value
-  }
-
-Attempts to retrieve an integer value from the given index in the
-current IFD.
-
-=cut
-*/
-
-static int
-tiff_get_tag_int_array(imtiff *tiff, int index, int *result, int array_index) {
-  ifd_entry *entry;
-  unsigned long offset;
-  if (index < 0 || index >= tiff->ifd_size) {
-    i_fatal(3, "tiff_get_tag_int_array() tag index out of range");
-  }
-  
-  entry = tiff->ifd + index;
-  if (array_index < 0 || array_index >= entry->count) {
-    i_fatal(3, "tiff_get_tag_int_array() array index out of range");
-  }
-
-  offset = entry->offset + array_index * entry->item_size;
-
-  switch (entry->type) {
-  case ift_short:
-    *result = tiff_get16(tiff, offset);
-    return 1;
-   
-  case ift_long:
-    *result = tiff_get32(tiff, offset);
-    return 1;
-
-  case ift_sshort:
-    *result = tiff_get16s(tiff, offset);
-    return 1;
-
-  case ift_slong:
-    *result = tiff_get32s(tiff, offset);
-    return 1;
-
-  case ift_byte:
-    *result = *(tiff->base + offset);
-    return 1;
-  }
-
-  return 0;
-}
-
-/*
-=item tiff_get_tag_int
-
-  int value;
-  if (tiff_get_tag_int(tiff, index, &value)) {
-    // process value
-  }
-
-Attempts to retrieve an integer value from the given index in the
-current IFD.
-
-The value must have a count of 1.
-
-=cut
-*/
-
-static int
-tiff_get_tag_int(imtiff *tiff, int index, int *result) {
-  ifd_entry *entry;
-  if (index < 0 || index >= tiff->ifd_size) {
-    i_fatal(3, "tiff_get_tag_int() index out of range");
-  }
-
-  entry = tiff->ifd + index;
-  if (entry->count != 1) {
-    mm_log((3, "tiff_get_tag_int() called on tag with multiple values"));
-    return 0;
-  }
-
-  return tiff_get_tag_int_array(tiff, index, result, 0);
-}
-
-/*
-=back
-
-=head2 Table-based tag setters
-
-This set of functions checks for matches between the current IFD and
-tags supplied in an array, when there's a match it sets the
-appropriate tag in the image.
-
-=over
-
-=item copy_int_tags
-
-Scans the IFD for integer tags and sets them in the image,
-
-=cut
-*/
-
-static void
-copy_int_tags(i_img *im, imtiff *tiff, tag_map *map, int map_count) {
-  int i, tag_index;
-  ifd_entry *entry;
-
-  for (tag_index = 0, entry = tiff->ifd; 
-       tag_index < tiff->ifd_size; ++tag_index, ++entry) {
-    for (i = 0; i < map_count; ++i) {
-      int value;
-      if (map[i].tag == entry->tag
-         && tiff_get_tag_int(tiff, tag_index, &value)) {
-       i_tags_addn(&im->tags, map[i].name, 0, value);
-       break;
-      }
-    }
-  }
-}
-
-/*
-=item copy_rat_tags
-
-Scans the IFD for rational tags and sets them in the image.
-
-=cut
-*/
-
-static void
-copy_rat_tags(i_img *im, imtiff *tiff, tag_map *map, int map_count) {
-  int i, tag_index;
-  ifd_entry *entry;
-
-  for (tag_index = 0, entry = tiff->ifd; 
-       tag_index < tiff->ifd_size; ++tag_index, ++entry) {
-    for (i = 0; i < map_count; ++i) {
-      double value;
-      if (map[i].tag == entry->tag
-         && tiff_get_tag_double(tiff, tag_index, &value)) {
-       i_tags_set_float2(&im->tags, map[i].name, 0, value, 6);
-       break;
-      }
-    }
-  }
-}
-
-/*
-=item copy_string_tags
-
-Scans the IFD for string tags and sets them in the image.
-
-=cut
-*/
-
-static void
-copy_string_tags(i_img *im, imtiff *tiff, tag_map *map, int map_count) {
-  int i, tag_index;
-  ifd_entry *entry;
-
-  for (tag_index = 0, entry = tiff->ifd; 
-       tag_index < tiff->ifd_size; ++tag_index, ++entry) {
-    for (i = 0; i < map_count; ++i) {
-      if (map[i].tag == entry->tag) {
-       int len = entry->type == ift_ascii ? entry->size - 1 : entry->size;
-       i_tags_add(&im->tags, map[i].name, 0,
-                  (char const *)(tiff->base + entry->offset), len, 0);
-       break;
-      }
-    }
-  }
-}
-
-/*
-=item copy_num_array_tags
-
-Scans the IFD for arrays of numbers and sets them in the image.
-
-=cut
-*/
-
-/* a more general solution would be better in some ways, but we don't need it */
-#define MAX_ARRAY_VALUES 10
-#define MAX_ARRAY_STRING (MAX_ARRAY_VALUES * 20)
-
-static void
-copy_num_array_tags(i_img *im, imtiff *tiff, tag_map *map, int map_count) {
-  int i, j, tag_index;
-  ifd_entry *entry;
-
-  for (tag_index = 0, entry = tiff->ifd; 
-       tag_index < tiff->ifd_size; ++tag_index, ++entry) {
-    for (i = 0; i < map_count; ++i) {
-      if (map[i].tag == entry->tag && entry->count <= MAX_ARRAY_VALUES) {
-       if (entry->type == ift_rational || entry->type == ift_srational) {
-         double value;
-         char workstr[MAX_ARRAY_STRING];
-         *workstr = '\0';
-         for (j = 0; j < entry->count; ++j) {
-           if (!tiff_get_tag_double_array(tiff, tag_index, &value, j)) {
-             i_fatal(3, "unexpected failure from tiff_get_tag_double_array(..., %d, ..., %d)\n", tag_index, j);
-           }
-           if (j) 
-             strcat(workstr, " ");
-           sprintf(workstr + strlen(workstr), "%.6g", value);
-         }
-         i_tags_add(&im->tags, map[i].name, 0, workstr, -1, 0);
-       }
-       else if (entry->type == ift_short || entry->type == ift_long
-                || entry->type == ift_sshort || entry->type == ift_slong
-                || entry->type == ift_byte) {
-         int value;
-         char workstr[MAX_ARRAY_STRING];
-         *workstr = '\0';
-         for (j = 0; j < entry->count; ++j) {
-           if (!tiff_get_tag_int_array(tiff, tag_index, &value, j)) {
-             i_fatal(3, "unexpected failure from tiff_get_tag_int_array(..., %d, ..., %d)\n", tag_index, j);
-           }
-           if (j) 
-             strcat(workstr, " ");
-           sprintf(workstr + strlen(workstr), "%d", value);
-         }
-         i_tags_add(&im->tags, map[i].name, 0, workstr, -1, 0);
-       }
-       break;
-      }
-    }
-  }
-}
-
-/*
-=item copy_name_tags
-
-This function maps integer values to descriptions for those values.
-
-In general we handle the integer value through copy_int_tags() and
-then the same tage with a "_name" suffix here.
-
-=cut
-*/
-
-static void
-copy_name_tags(i_img *im, imtiff *tiff, tag_value_map *map, int map_count) {
-  int i, j, tag_index;
-  ifd_entry *entry;
-
-  for (tag_index = 0, entry = tiff->ifd; 
-       tag_index < tiff->ifd_size; ++tag_index, ++entry) {
-    for (i = 0; i < map_count; ++i) {
-      int value;
-      if (map[i].tag == entry->tag
-         && tiff_get_tag_int(tiff, tag_index, &value)) {
-       tag_map const *found = NULL;
-       for (j = 0; j < map[i].map_count; ++j) {
-         if (value == map[i].map[j].tag) {
-           found = map[i].map + j;
-           break;
-         }
-       }
-       if (found) {
-         i_tags_add(&im->tags, map[i].name, 0, found->name, -1, 0);
-       }
-       break;
-      }
-    }
-  }
-}
-
-
-/*
-=back
-
-=head2 Low level data access functions
-
-These functions use the byte order in the tiff object to extract
-various types of data from the tiff data.
-
-These functions will abort if called with an out of range offset.
-
-The intent is that any offset checks should have been done by the caller.
-
-=over
-
-=item tiff_get16
-
-Retrieve a 16 bit unsigned integer from offset.
-
-=cut
-*/
-
-static unsigned
-tiff_get16(imtiff *tiff, unsigned long offset) {
-  if (offset + 2 > tiff->size)
-    i_fatal(3, "attempt to get16 at %uld in %uld image", offset, tiff->size);
-
-  if (tiff->type == tt_intel) 
-    return tiff->base[offset] + 0x100 * tiff->base[offset+1];
-  else
-    return tiff->base[offset+1] + 0x100 * tiff->base[offset];
-}
-
-/*
-=item tiff_get32
-
-Retrieve a 32-bit unsigned integer from offset.
-
-=cut
-*/
-
-static unsigned
-tiff_get32(imtiff *tiff, unsigned long offset) {
-  if (offset + 4 > tiff->size)
-    i_fatal(3, "attempt to get16 at %uld in %uld image", offset, tiff->size);
-
-  if (tiff->type == tt_intel) 
-    return tiff->base[offset] + 0x100 * tiff->base[offset+1] 
-      + 0x10000 * tiff->base[offset+2] + 0x1000000 * tiff->base[offset+3];
-  else
-    return tiff->base[offset+3] + 0x100 * tiff->base[offset+2]
-      + 0x10000 * tiff->base[offset+1] + 0x1000000 * tiff->base[offset];
-}
-
-#if 0 /* currently unused, but that may change */
-
-/*
-=item tiff_get_bytes
-
-Retrieve a byte string from offset.
-
-This isn't used much, you can usually deal with the data in-situ.
-This is intended for use when you need to modify the data in some way.
-
-=cut
-*/
-
-static int
-tiff_get_bytes(imtiff *tiff, unsigned char *data, size_t offset, 
-              size_t size) {
-  if (offset + size > tiff->size)
-    return 0;
-
-  memcpy(data, tiff->base+offset, size);
-
-  return 1;
-}
-
-#endif
-
-/*
-=item tiff_get16s
-
-Retrieve a 16-bit signed integer from offset.
-
-=cut
-*/
-
-static int
-tiff_get16s(imtiff *tiff, unsigned long offset) {
-  int result;
-
-  if (offset + 2 > tiff->size)
-    i_fatal(3, "attempt to get16 at %uld in %uld image", offset, tiff->size);
-
-  if (tiff->type == tt_intel) 
-    result = tiff->base[offset] + 0x100 * tiff->base[offset+1];
-  else
-    result = tiff->base[offset+1] + 0x100 * tiff->base[offset];
-
-  if (result > 0x7FFF)
-    result -= 0x10000;
-
-  return result;
-}
-
-/*
-=item tiff_get32s
-
-Retrieve a 32-bit signed integer from offset.
-
-=cut
-*/
-
-static int
-tiff_get32s(imtiff *tiff, unsigned long offset) {
-  unsigned work;
-
-  if (offset + 4 > tiff->size)
-    i_fatal(3, "attempt to get16 at %uld in %uld image", offset, tiff->size);
-
-  if (tiff->type == tt_intel) 
-    work = tiff->base[offset] + 0x100 * tiff->base[offset+1] 
-      + 0x10000 * tiff->base[offset+2] + 0x1000000 * tiff->base[offset+3];
-  else
-    work = tiff->base[offset+3] + 0x100 * tiff->base[offset+2]
-      + 0x10000 * tiff->base[offset+1] + 0x1000000 * tiff->base[offset];
-
-  /* not really needed on 32-bit int machines */
-  if (work > 0x7FFFFFFFUL)
-    return work - 0x80000000UL;
-  else
-    return work;
-}
-
-/*
-=item tiff_get_rat
-
-Retrieve an unsigned rational from offset.
-
-=cut
-*/
-
-static double
-tiff_get_rat(imtiff *tiff, unsigned long offset) {
-  unsigned long numer, denom;
-  if (offset + 8 > tiff->size)
-    i_fatal(3, "attempt to get_rat at %lu in %lu image", offset, tiff->size);
-
-  numer = tiff_get32(tiff, offset);
-  denom = tiff_get32(tiff, offset+4);
-
-  if (denom == 0) {
-    return -DBL_MAX;
-  }
-
-  return (double)numer / denom;
-}
-
-/*
-=item tiff_get_rats
-
-Retrieve an signed rational from offset.
-
-=cut
-*/
-
-static double
-tiff_get_rats(imtiff *tiff, unsigned long offset) {
-  long numer, denom;
-  if (offset + 8 > tiff->size)
-    i_fatal(3, "attempt to get_rat at %lu in %lu image", offset, tiff->size);
-
-  numer = tiff_get32s(tiff, offset);
-  denom = tiff_get32s(tiff, offset+4);
-
-  if (denom == 0) {
-    return -DBL_MAX;
-  }
-
-  return (double)numer / denom;
-}
-
-/*
-=back
-
-=head1 SEE ALSO
-
-L<Imager>, jpeg.c
-
-http://www.exif.org/
-
-=head1 AUTHOR
-
-Tony Cook <tony@imager.perl.org>
-
-=head1 REVISION
-
-$Revision$
-
-=cut
-*/
diff --git a/imexif.h b/imexif.h
deleted file mode 100644 (file)
index 70a2b16..0000000
--- a/imexif.h
+++ /dev/null
@@ -1,10 +0,0 @@
-/* imexif.h - interface to Exif handling */
-#ifndef IMAGER_IMEXIF_H
-#define IMAGER_IMEXIF_H
-
-#include <stddef.h>
-#include "imageri.h"
-
-extern int i_int_decode_exif(i_img *im, unsigned char *data, size_t length);
-
-#endif /* ifndef IMAGER_IMEXIF_H */
diff --git a/imext.c b/imext.c
index 09f4e5d..30c7255 100644 (file)
--- a/imext.c
+++ b/imext.c
@@ -119,7 +119,11 @@ im_ext_funcs imager_function_table =
     i_img_init,
 
     /* IMAGER_API_LEVEL 5 functions */
-    i_img_is_monochrome
+    i_img_is_monochrome,
+    i_gsamp_bg,
+    i_gsampf_bg,
+    i_get_file_background,
+    i_get_file_backgroundf
   };
 
 /* in general these functions aren't called by Imager internally, but
diff --git a/imext.h b/imext.h
index acd8267..1e2ed44 100644 (file)
--- a/imext.h
+++ b/imext.h
@@ -210,4 +210,13 @@ extern im_ext_funcs *imager_function_ext_table;
 
 #define i_img_is_monochrome(img, zero_is_white) ((im_extt->f_i_img_is_monochrome)((img), (zero_is_white)))
 
+#define i_gsamp_bg(im, l, r, y, samples, out_channels, bg) \
+  ((im_extt->f_i_gsamp_bg)((im), (l), (r), (y), (samples), (out_channels), (bg)))
+#define i_gsampf_bg(im, l, r, y, samples, out_channels, bg) \
+  ((im_extt->f_i_gsampf_bg)((im), (l), (r), (y), (samples), (out_channels), (bg)))
+#define i_get_file_background(im, bg) \
+  ((im_extt->f_i_get_file_background)((im), (bg)))
+#define i_get_file_backgroundf(im, bg) \
+  ((im_extt->f_i_get_file_backgroundf)((im), (bg)))
+
 #endif
index c94090b..b35a68c 100644 (file)
@@ -158,6 +158,12 @@ typedef struct {
   /* IMAGER_API_LEVEL 5 functions will be added here */
   /* added i_psampf?_bits macros */
   int (*f_i_img_is_monochrome)(i_img *, int *zero_is_white);
+  int (*f_i_gsamp_bg)(i_img *im, int l, int r, int y, i_sample_t *samples,
+                     int out_channels, i_color const * bg);
+  int (*f_i_gsampf_bg)(i_img *im, int l, int r, int y, i_fsample_t *samples,
+                     int out_channels, i_fcolor const * bg);
+  void (*f_i_get_file_background)(i_img *im, i_color *bg);
+  void (*f_i_get_file_backgroundf)(i_img *im, i_fcolor *bg);
 
   /* IMAGER_API_LEVEL 6 functions will be added here */
 } im_ext_funcs;
diff --git a/jpeg.c b/jpeg.c
deleted file mode 100644 (file)
index 4f2243f..0000000
--- a/jpeg.c
+++ /dev/null
@@ -1,725 +0,0 @@
-/*
-=head1 NAME
-
-jpeg.c - implement saving and loading JPEG images
-
-=head1 SYNOPSIS
-
-  io_glue *ig;
-  if (!i_writejpeg_wiol(im, ig, quality)) {
-    .. error ..
-  }
-  im = i_readjpeg_wiol(ig, length, iptc_text, itlength);
-
-=head1 DESCRIPTION
-
-Reads and writes JPEG images
-
-=over
-
-=cut
-*/
-
-#include <stdio.h>
-#include <sys/stat.h>
-#ifndef _MSC_VER
-#include <unistd.h>
-#endif
-#include <setjmp.h>
-
-#include "iolayer.h"
-#include "imageri.h"
-#include "jpeglib.h"
-#include "jerror.h"
-#include <errno.h>
-#ifdef IMEXIF_ENABLE
-#include "imexif.h"
-#endif
-
-#define JPEG_APP13       0xED    /* APP13 marker code */
-#define JPEG_APP1 (JPEG_APP0 + 1)
-#define JPGS 16384
-
-static unsigned char fake_eoi[]={(JOCTET) 0xFF,(JOCTET) JPEG_EOI};
-
-/* Bad design right here */
-
-static int tlength=0;
-static char **iptc_text=NULL;
-
-
-/* Source and Destination managers */
-
-
-typedef struct {
-  struct jpeg_source_mgr pub;  /* public fields */
-  io_glue *data;
-  JOCTET *buffer;              /* start of buffer */
-  int length;                  /* Do I need this? */
-  boolean start_of_file;       /* have we gotten any data yet? */
-} wiol_source_mgr;
-
-typedef struct {
-  struct jpeg_destination_mgr pub; /* public fields */
-  io_glue *data;
-  JOCTET *buffer;              /* start of buffer */
-  boolean start_of_file;       /* have we gotten any data yet? */
-} wiol_destination_mgr;
-
-typedef wiol_source_mgr *wiol_src_ptr;
-typedef wiol_destination_mgr *wiol_dest_ptr;
-
-
-/*
- * Methods for io manager objects 
- * 
- * Methods for source managers: 
- *
- * init_source         (j_decompress_ptr cinfo);
- * skip_input_data     (j_decompress_ptr cinfo, long num_bytes);
- * fill_input_buffer   (j_decompress_ptr cinfo);
- * term_source         (j_decompress_ptr cinfo);
- */
-
-
-
-static void
-wiol_init_source (j_decompress_ptr cinfo) {
-  wiol_src_ptr src = (wiol_src_ptr) cinfo->src;
-  
-  /* We reset the empty-input-file flag for each image, but we don't clear
-   * the input buffer.   This is correct behavior for reading a series of
-   * images from one source.
-   */
-  src->start_of_file = TRUE;
-}
-
-
-
-static boolean
-wiol_fill_input_buffer(j_decompress_ptr cinfo) {
-  wiol_src_ptr src = (wiol_src_ptr) cinfo->src;
-  ssize_t nbytes; /* We assume that reads are "small" */
-  
-  mm_log((1,"wiol_fill_input_buffer(cinfo 0x%p)\n", cinfo));
-  
-  nbytes = src->data->readcb(src->data, src->buffer, JPGS);
-  
-  if (nbytes <= 0) { /* Insert a fake EOI marker */
-    src->pub.next_input_byte = fake_eoi;
-    src->pub.bytes_in_buffer = 2;
-  } else {
-    src->pub.next_input_byte = src->buffer;
-    src->pub.bytes_in_buffer = nbytes;
-  }
-  src->start_of_file = FALSE;
-  return TRUE;
-}
-
-
-static void
-wiol_skip_input_data (j_decompress_ptr cinfo, long num_bytes) {
-  wiol_src_ptr src = (wiol_src_ptr) cinfo->src;
-  
-  /* Just a dumb implementation for now.  Could use fseek() except
-   * it doesn't work on pipes.  Not clear that being smart is worth
-   * any trouble anyway --- large skips are infrequent.
-   */
-  
-  if (num_bytes > 0) {
-    while (num_bytes > (long) src->pub.bytes_in_buffer) {
-      num_bytes -= (long) src->pub.bytes_in_buffer;
-      (void) wiol_fill_input_buffer(cinfo);
-      /* note we assume that fill_input_buffer will never return FALSE,
-       * so suspension need not be handled.
-       */
-    }
-    src->pub.next_input_byte += (size_t) num_bytes;
-    src->pub.bytes_in_buffer -= (size_t) num_bytes;
-  }
-}
-
-static void
-wiol_term_source (j_decompress_ptr cinfo) {
-  /* no work necessary here */ 
-  wiol_src_ptr src;
-  if (cinfo->src != NULL) {
-    src = (wiol_src_ptr) cinfo->src;
-    myfree(src->buffer);
-  }
-}
-
-
-/* Source manager Constructor */
-
-static void
-jpeg_wiol_src(j_decompress_ptr cinfo, io_glue *ig, int length) {
-  wiol_src_ptr src;
-  
-  if (cinfo->src == NULL) {    /* first time for this JPEG object? */
-    cinfo->src = (struct jpeg_source_mgr *) (*cinfo->mem->alloc_small) 
-      ((j_common_ptr) cinfo, JPOOL_PERMANENT,sizeof(wiol_source_mgr));
-    src = (wiol_src_ptr) cinfo->src;
-  }
-
-  /* put the request method call in here later */
-  io_glue_commit_types(ig);
-  
-  src         = (wiol_src_ptr) cinfo->src;
-  src->data   = ig;
-  src->buffer = mymalloc( JPGS );
-  src->length = length;
-
-  src->pub.init_source       = wiol_init_source;
-  src->pub.fill_input_buffer = wiol_fill_input_buffer;
-  src->pub.skip_input_data   = wiol_skip_input_data;
-  src->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */
-  src->pub.term_source       = wiol_term_source;
-  src->pub.bytes_in_buffer   = 0; /* forces fill_input_buffer on first read */
-  src->pub.next_input_byte   = NULL; /* until buffer loaded */
-}
-
-
-
-
-/*
- * Methods for destination managers:
- *
- * init_destination    (j_compress_ptr cinfo);
- * empty_output_buffer (j_compress_ptr cinfo);
- * term_destination    (j_compress_ptr cinfo);
- *
- */
-
-static void
-wiol_init_destination (j_compress_ptr cinfo) {
-  wiol_dest_ptr dest = (wiol_dest_ptr) cinfo->dest;
-  
-  /* We reset the empty-input-file flag for each image, but we don't clear
-   * the input buffer.   This is correct behavior for reading a series of
-   * images from one source.
-   */
-  dest->start_of_file = TRUE;
-}
-
-static boolean
-wiol_empty_output_buffer(j_compress_ptr cinfo) {
-  wiol_dest_ptr dest = (wiol_dest_ptr) cinfo->dest;
-  ssize_t rc;
-  /*
-    Previously this code was checking free_in_buffer to see how much 
-    needed to be written.  This does not follow the documentation:
-
-                       "In typical applications, it should write out the
-        *entire* buffer (use the saved start address and buffer length;
-        ignore the current state of next_output_byte and free_in_buffer)."
-
-  ssize_t nbytes     = JPGS - dest->pub.free_in_buffer;
-  */
-
-  mm_log((1,"wiol_empty_output_buffer(cinfo 0x%p)\n", cinfo));
-  rc = dest->data->writecb(dest->data, dest->buffer, JPGS);
-
-  if (rc != JPGS) { /* XXX: Should raise some jpeg error */
-    myfree(dest->buffer);
-    mm_log((1, "wiol_empty_output_buffer: Error: nbytes = %d != rc = %d\n", JPGS, rc));
-    ERREXIT(cinfo, JERR_FILE_WRITE);
-  }
-  dest->pub.free_in_buffer = JPGS;
-  dest->pub.next_output_byte = dest->buffer;
-  return TRUE;
-}
-
-static void
-wiol_term_destination (j_compress_ptr cinfo) {
-  wiol_dest_ptr dest = (wiol_dest_ptr) cinfo->dest;
-  size_t nbytes = JPGS - dest->pub.free_in_buffer;
-  /* yes, this needs to flush the buffer */
-  /* needs error handling */
-
-  if (dest->data->writecb(dest->data, dest->buffer, nbytes) != nbytes) {
-    myfree(dest->buffer);
-    ERREXIT(cinfo, JERR_FILE_WRITE);
-  }
-  
-  if (dest != NULL) myfree(dest->buffer);
-}
-
-
-/* Destination manager Constructor */
-
-static void
-jpeg_wiol_dest(j_compress_ptr cinfo, io_glue *ig) {
-  wiol_dest_ptr dest;
-  
-  if (cinfo->dest == NULL) {   /* first time for this JPEG object? */
-    cinfo->dest = 
-      (struct jpeg_destination_mgr *)
-      (*cinfo->mem->alloc_small) 
-      ((j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof(wiol_destination_mgr));
-  }
-  
-  dest = (wiol_dest_ptr) cinfo->dest;
-  dest->data                    = ig;
-  dest->buffer                  = mymalloc( JPGS );
-
-  dest->pub.init_destination    = wiol_init_destination;
-  dest->pub.empty_output_buffer = wiol_empty_output_buffer;
-  dest->pub.term_destination    = wiol_term_destination;
-  dest->pub.free_in_buffer      = JPGS;
-  dest->pub.next_output_byte    = dest->buffer;
-}
-
-LOCAL(unsigned int)
-jpeg_getc (j_decompress_ptr cinfo)
-/* Read next byte */
-{
-  struct jpeg_source_mgr * datasrc = cinfo->src;
-
-  if (datasrc->bytes_in_buffer == 0) {
-    if (! (*datasrc->fill_input_buffer) (cinfo))
-      { fprintf(stderr,"Jpeglib: cant suspend.\n"); exit(3); }
-      /*      ERREXIT(cinfo, JERR_CANT_SUSPEND);*/
-  }
-  datasrc->bytes_in_buffer--;
-  return GETJOCTET(*datasrc->next_input_byte++);
-}
-
-METHODDEF(boolean)
-APP13_handler (j_decompress_ptr cinfo) {
-  INT32 length;
-  unsigned int cnt=0;
-  
-  length = jpeg_getc(cinfo) << 8;
-  length += jpeg_getc(cinfo);
-  length -= 2; /* discount the length word itself */
-  
-  tlength=length;
-
-  if ( ((*iptc_text)=mymalloc(length)) == NULL ) return FALSE;
-  while (--length >= 0) (*iptc_text)[cnt++] = jpeg_getc(cinfo); 
-  return TRUE;
-}
-
-METHODDEF(void)
-my_output_message (j_common_ptr cinfo) {
-  char buffer[JMSG_LENGTH_MAX];
-
-  /* Create the message */
-  (*cinfo->err->format_message) (cinfo, buffer);
-
-  i_push_error(0, buffer);
-
-  /* Send it to stderr, adding a newline */
-  mm_log((1, "%s\n", buffer));
-}
-
-struct my_error_mgr {
-  struct jpeg_error_mgr pub;   /* "public" fields */
-  jmp_buf setjmp_buffer;       /* for return to caller */
-};
-
-typedef struct my_error_mgr * my_error_ptr;
-
-/* Here's the routine that will replace the standard error_exit method */
-
-METHODDEF(void)
-my_error_exit (j_common_ptr cinfo) {
-  /* cinfo->err really points to a my_error_mgr struct, so coerce pointer */
-  my_error_ptr myerr = (my_error_ptr) cinfo->err;
-  
-  /* Always display the message. */
-  /* We could postpone this until after returning, if we chose. */
-  (*cinfo->err->output_message) (cinfo);
-
-  /* Return control to the setjmp point */
-  longjmp(myerr->setjmp_buffer, 1);
-}
-
-static void 
-transfer_cmyk_inverted(i_color *out, JSAMPARRAY in, int width) {
-  JSAMPROW inrow = *in;
-  while (width--) {
-    /* extract and convert to real CMYK */
-    /* horribly enough this is correct given cmyk values are inverted */
-    int c = *inrow++;
-    int m = *inrow++;
-    int y = *inrow++;
-    int k = *inrow++;
-    out->rgba.r = (c * k) / MAXJSAMPLE;
-    out->rgba.g = (m * k) / MAXJSAMPLE;
-    out->rgba.b = (y * k) / MAXJSAMPLE;
-    ++out;
-  }
-}
-
-static void
-transfer_rgb(i_color *out, JSAMPARRAY in, int width) {
-  JSAMPROW inrow = *in;
-  while (width--) {
-    out->rgba.r = *inrow++;
-    out->rgba.g = *inrow++;
-    out->rgba.b = *inrow++;
-    ++out;
-  }
-}
-
-static void
-transfer_gray(i_color *out, JSAMPARRAY in, int width) {
-  JSAMPROW inrow = *in;
-  while (width--) {
-    out->gray.gray_color = *inrow++;
-    ++out;
-  }
-}
-
-typedef void (*transfer_function_t)(i_color *out, JSAMPARRAY in, int width);
-
-/*
-=item i_readjpeg_wiol(data, length, iptc_itext, itlength)
-
-=cut
-*/
-i_img*
-i_readjpeg_wiol(io_glue *data, int length, char** iptc_itext, int *itlength) {
-  i_img * volatile im = NULL;
-#ifdef IMEXIF_ENABLE
-  int seen_exif = 0;
-#endif
-  i_color * volatile line_buffer = NULL;
-  struct jpeg_decompress_struct cinfo;
-  struct my_error_mgr jerr;
-  JSAMPARRAY buffer;           /* Output row buffer */
-  int row_stride;              /* physical row width in output buffer */
-  jpeg_saved_marker_ptr markerp;
-  transfer_function_t transfer_f;
-  int channels;
-  volatile int src_set = 0;
-
-  mm_log((1,"i_readjpeg_wiol(data 0x%p, length %d,iptc_itext 0x%p)\n", data, length, iptc_itext));
-
-  i_clear_error();
-
-  iptc_text = iptc_itext;
-  cinfo.err = jpeg_std_error(&jerr.pub);
-  jerr.pub.error_exit     = my_error_exit;
-  jerr.pub.output_message = my_output_message;
-
-  /* Set error handler */
-  if (setjmp(jerr.setjmp_buffer)) {
-    if (src_set)
-      wiol_term_source(&cinfo);
-    jpeg_destroy_decompress(&cinfo); 
-    *iptc_itext=NULL;
-    *itlength=0;
-    if (line_buffer)
-      myfree(line_buffer);
-    if (im)
-      i_img_destroy(im);
-    return NULL;
-  }
-  
-  jpeg_create_decompress(&cinfo);
-  jpeg_set_marker_processor(&cinfo, JPEG_APP13, APP13_handler);
-  jpeg_save_markers(&cinfo, JPEG_APP1, 0xFFFF);
-  jpeg_save_markers(&cinfo, JPEG_COM, 0xFFFF);
-  jpeg_wiol_src(&cinfo, data, length);
-  src_set = 1;
-
-  (void) jpeg_read_header(&cinfo, TRUE);
-  (void) jpeg_start_decompress(&cinfo);
-
-  channels = cinfo.output_components;
-  switch (cinfo.out_color_space) {
-  case JCS_GRAYSCALE:
-    if (cinfo.output_components != 1) {
-      mm_log((1, "i_readjpeg: grayscale image with %d channels\n", cinfo.output_components));
-      i_push_errorf(0, "grayscale image with invalid components %d", cinfo.output_components);
-      wiol_term_source(&cinfo);
-      jpeg_destroy_decompress(&cinfo);
-      return NULL;
-    }
-    transfer_f = transfer_gray;
-    break;
-  
-  case JCS_RGB:
-    transfer_f = transfer_rgb;
-    if (cinfo.output_components != 3) {
-      mm_log((1, "i_readjpeg: RGB image with %d channels\n", cinfo.output_components));
-      i_push_errorf(0, "RGB image with invalid components %d", cinfo.output_components);
-      wiol_term_source(&cinfo);
-      jpeg_destroy_decompress(&cinfo);
-      return NULL;
-    }
-    break;
-
-  case JCS_CMYK:
-    if (cinfo.output_components == 4) {
-      /* we treat the CMYK values as inverted, because that's what that
-        buggy photoshop does, and everyone has to follow the gorilla.
-
-        Is there any app that still produces correct CMYK JPEGs?
-      */
-      transfer_f = transfer_cmyk_inverted;
-      channels = 3;
-    }
-    else {
-      mm_log((1, "i_readjpeg: cmyk image with %d channels\n", cinfo.output_components));
-      i_push_errorf(0, "CMYK image with invalid components %d", cinfo.output_components);
-      wiol_term_source(&cinfo);
-      jpeg_destroy_decompress(&cinfo);
-      return NULL;
-    }
-    break;
-
-  default:
-    mm_log((1, "i_readjpeg: unknown color space %d\n", cinfo.out_color_space));
-    i_push_errorf(0, "Unknown color space %d", cinfo.out_color_space);
-    wiol_term_source(&cinfo);
-    jpeg_destroy_decompress(&cinfo);
-    return NULL;
-  }
-
-  if (!i_int_check_image_file_limits(cinfo.output_width, cinfo.output_height,
-                                    channels, sizeof(i_sample_t))) {
-    mm_log((1, "i_readjpeg: image size exceeds limits\n"));
-    wiol_term_source(&cinfo);
-    jpeg_destroy_decompress(&cinfo);
-    return NULL;
-  }
-
-  im=i_img_empty_ch(NULL, cinfo.output_width, cinfo.output_height, channels);
-  if (!im) {
-    wiol_term_source(&cinfo);
-    jpeg_destroy_decompress(&cinfo);
-    return NULL;
-  }
-  row_stride = cinfo.output_width * cinfo.output_components;
-  buffer = (*cinfo.mem->alloc_sarray) ((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);
-  line_buffer = mymalloc(sizeof(i_color) * cinfo.output_width);
-  while (cinfo.output_scanline < cinfo.output_height) {
-    (void) jpeg_read_scanlines(&cinfo, buffer, 1);
-    transfer_f(line_buffer, buffer, cinfo.output_width);
-    i_plin(im, 0, cinfo.output_width, cinfo.output_scanline-1, line_buffer);
-  }
-  myfree(line_buffer);
-  line_buffer = NULL;
-
-  /* check for APP1 marker and save */
-  markerp = cinfo.marker_list;
-  while (markerp != NULL) {
-    if (markerp->marker == JPEG_COM) {
-      i_tags_add(&im->tags, "jpeg_comment", 0, (const char *)markerp->data,
-                markerp->data_length, 0);
-    }
-#ifdef IMEXIF_ENABLE
-    else if (markerp->marker == JPEG_APP1 && !seen_exif) {
-      seen_exif = i_int_decode_exif(im, markerp->data, markerp->data_length);
-    }
-#endif      
-
-    markerp = markerp->next;
-  }
-
-  i_tags_addn(&im->tags, "jpeg_out_color_space", 0, cinfo.out_color_space);
-  i_tags_addn(&im->tags, "jpeg_color_space", 0, cinfo.jpeg_color_space);
-
-  if (cinfo.saw_JFIF_marker) {
-    double xres = cinfo.X_density;
-    double yres = cinfo.Y_density;
-    
-    i_tags_addn(&im->tags, "jpeg_density_unit", 0, cinfo.density_unit);
-    switch (cinfo.density_unit) {
-    case 0: /* values are just the aspect ratio */
-      i_tags_addn(&im->tags, "i_aspect_only", 0, 1);
-      i_tags_add(&im->tags, "jpeg_density_unit_name", 0, "none", -1, 0);
-      break;
-
-    case 1: /* per inch */
-      i_tags_add(&im->tags, "jpeg_density_unit_name", 0, "inch", -1, 0);
-      break;
-
-    case 2: /* per cm */
-      i_tags_add(&im->tags, "jpeg_density_unit_name", 0, "centimeter", -1, 0);
-      xres *= 2.54;
-      yres *= 2.54;
-      break;
-    }
-    i_tags_set_float2(&im->tags, "i_xres", 0, xres, 6);
-    i_tags_set_float2(&im->tags, "i_yres", 0, yres, 6);
-  }
-
-  (void) jpeg_finish_decompress(&cinfo);
-  jpeg_destroy_decompress(&cinfo);
-  *itlength=tlength;
-
-  i_tags_add(&im->tags, "i_format", 0, "jpeg", 4, 0);
-
-  mm_log((1,"i_readjpeg_wiol -> (0x%x)\n",im));
-  return im;
-}
-
-/*
-=item i_writejpeg_wiol(im, ig, qfactor)
-
-=cut
-*/
-
-undef_int
-i_writejpeg_wiol(i_img *im, io_glue *ig, int qfactor) {
-  JSAMPLE *image_buffer;
-  int quality;
-  int got_xres, got_yres, aspect_only, resunit;
-  double xres, yres;
-  int comment_entry;
-  int want_channels = im->channels;
-
-  struct jpeg_compress_struct cinfo;
-  struct my_error_mgr jerr;
-
-  JSAMPROW row_pointer[1];     /* pointer to JSAMPLE row[s] */
-  int row_stride;              /* physical row width in image buffer */
-  unsigned char * data = NULL;
-  i_color *line_buf = NULL;
-
-  mm_log((1,"i_writejpeg(im %p, ig %p, qfactor %d)\n", im, ig, qfactor));
-  
-  i_clear_error();
-  io_glue_commit_types(ig);
-
-  if (!(im->channels==1 || im->channels==3)) { 
-    want_channels = im->channels - 1;
-  }
-  quality = qfactor;
-
-  cinfo.err = jpeg_std_error(&jerr.pub);
-  jerr.pub.error_exit = my_error_exit;
-  jerr.pub.output_message = my_output_message;
-  
-  jpeg_create_compress(&cinfo);
-
-  if (setjmp(jerr.setjmp_buffer)) {
-    jpeg_destroy_compress(&cinfo);
-    if (data)
-      myfree(data);
-    if (line_buf)
-      myfree(line_buf);
-    return 0;
-  }
-
-  jpeg_wiol_dest(&cinfo, ig);
-
-  cinfo.image_width  = im -> xsize;    /* image width and height, in pixels */
-  cinfo.image_height = im -> ysize;
-
-  if (want_channels==3) {
-    cinfo.input_components = 3;                /* # of color components per pixel */
-    cinfo.in_color_space = JCS_RGB;    /* colorspace of input image */
-  }
-
-  if (want_channels==1) {
-    cinfo.input_components = 1;                /* # of color components per pixel */
-    cinfo.in_color_space = JCS_GRAYSCALE;      /* colorspace of input image */
-  }
-
-  jpeg_set_defaults(&cinfo);
-  jpeg_set_quality(&cinfo, quality, TRUE);  /* limit to baseline-JPEG values */
-
-  got_xres = i_tags_get_float(&im->tags, "i_xres", 0, &xres);
-  got_yres = i_tags_get_float(&im->tags, "i_yres", 0, &yres);
-  if (!i_tags_get_int(&im->tags, "i_aspect_only", 0,&aspect_only))
-    aspect_only = 0;
-  if (!i_tags_get_int(&im->tags, "jpeg_density_unit", 0, &resunit))
-    resunit = 1; /* per inch */
-  if (resunit < 0 || resunit > 2) /* default to inch if invalid */
-    resunit = 1;
-  if (got_xres || got_yres) {
-    if (!got_xres)
-      xres = yres;
-    else if (!got_yres)
-      yres = xres;
-    if (aspect_only)
-      resunit = 0; /* standard tags override format tags */
-    if (resunit == 2) {
-      /* convert to per cm */
-      xres /= 2.54;
-      yres /= 2.54;
-    }
-    cinfo.density_unit = resunit;
-    cinfo.X_density = (int)(xres + 0.5);
-    cinfo.Y_density = (int)(yres + 0.5);
-  }
-
-  jpeg_start_compress(&cinfo, TRUE);
-
-  if (i_tags_find(&im->tags, "jpeg_comment", 0, &comment_entry)) {
-    jpeg_write_marker(&cinfo, JPEG_COM, 
-                      (const JOCTET *)im->tags.tags[comment_entry].data,
-                     im->tags.tags[comment_entry].size);
-  }
-
-  row_stride = im->xsize * im->channels;       /* JSAMPLEs per row in image_buffer */
-
-  if (!im->virtual && im->type == i_direct_type && im->bits == i_8_bits
-      && im->channels == want_channels) {
-    image_buffer=im->idata;
-
-    while (cinfo.next_scanline < cinfo.image_height) {
-      /* jpeg_write_scanlines expects an array of pointers to scanlines.
-       * Here the array is only one element long, but you could pass
-       * more than one scanline at a time if that's more convenient.
-       */
-      row_pointer[0] = & image_buffer[cinfo.next_scanline * row_stride];
-      (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
-    }
-  }
-  else {
-    i_color bg;
-
-    i_get_file_background(im, &bg);
-    data = mymalloc(im->xsize * im->channels);
-    if (data) {
-      while (cinfo.next_scanline < cinfo.image_height) {
-        /* jpeg_write_scanlines expects an array of pointers to scanlines.
-         * Here the array is only one element long, but you could pass
-         * more than one scanline at a time if that's more convenient.
-         */
-        i_gsamp_bg(im, 0, im->xsize, cinfo.next_scanline, data, 
-                  want_channels, &bg);
-        row_pointer[0] = data;
-        (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
-      }
-      myfree(data);
-    }
-    else {
-      jpeg_destroy_compress(&cinfo);
-      i_push_error(0, "out of memory");
-      return 0; /* out of memory? */
-    }
-  }
-
-  /* Step 6: Finish compression */
-
-  jpeg_finish_compress(&cinfo);
-
-  jpeg_destroy_compress(&cinfo);
-
-  ig->closecb(ig);
-
-  return(1);
-}
-
-/*
-=back
-
-=head1 AUTHOR
-
-Arnar M. Hrafnkelsson, addi@umich.edu
-
-=head1 SEE ALSO
-
-Imager(3)
-
-=cut
-*/
index df36a72..253578a 100644 (file)
@@ -488,6 +488,19 @@ Returns the number of samples read (which should be (C<right>-C<left>)
 =for comment
 From: File imext.c
 
+=item i_gsampf_bg(im, l, r, y, samples, out_channels, background)
+
+
+Like C<i_gsampf()> but applies the source image color over a supplied
+background color.
+
+This is intended for output to image formats that don't support alpha
+channels.
+
+
+=for comment
+From: File paste.im
+
 =item i_line(C<im>, C<x1>, C<y1>, C<x2>, C<y2>, C<color>, C<endp>)
 
 
@@ -642,6 +655,31 @@ From: File error.c
 
 =over
 
+=item i_get_file_background(im, &bg)
+
+
+Retrieve the file write background color tag from the image.
+
+If not present, returns black.
+
+
+=for comment
+From: File image.c
+
+=item i_get_file_backgroundf(im, &bg)
+
+
+Retrieve the file write background color tag from the image as a
+floating point color.
+
+Implemented in terms of i_get_file_background().
+
+If not present, returns black.
+
+
+=for comment
+From: File image.c
+
 =item i_get_image_file_limits(&width, &height, &bytes)
 
 
@@ -1475,6 +1513,22 @@ removed.
 From: File tags.c
 
 
+=back
+
+
+=head1 UNDOCUMENTED
+
+The following API functions are undocumented so far, hopefully this
+will change:
+
+=over
+
+=item *
+
+B<i_gsamp_bg>
+
+
+
 =back
 
 
index a168c46..6c440ce 100644 (file)
--- a/paste.im
+++ b/paste.im
@@ -329,16 +329,25 @@ i_adapt_fcolors_bg
 }
 
 /*
-=item i_gsamp_bg(im, l, r, y, samples, out_channels, bg)
+=item i_gsamp_bg(im, l, r, y, samples, out_channels, background)
 
-=item i_gsampf_bg(im, l, r, y, samples, out_channels, bg)
+=category Drawing
 
-This is similar to i_adapt_colors_bg() except it can only strip an
-alpha channel.  It cannot be used to convert a source RGB image to
-greyscale.
+Like C<i_gsampf()> but applies the source image color over a supplied
+background color.
 
-The samples parameter MUST include enough space for all samples of the
-source image.
+This is intended for output to image formats that don't support alpha
+channels.
+
+=item i_gsampf_bg(im, l, r, y, samples, out_channels, background)
+
+=category Drawing
+
+Like C<i_gsampf()> but applies the source image color over a supplied
+background color.
+
+This is intended for output to image formats that don't support alpha
+channels.
 
 =cut
 */
diff --git a/t/t101jpeg.t b/t/t101jpeg.t
deleted file mode 100644 (file)
index 982b410..0000000
+++ /dev/null
@@ -1,415 +0,0 @@
-#!perl -w
-use strict;
-use Imager qw(:all);
-use Test::More;
-use Imager::Test qw(is_color_close3 test_image_raw);
-
-init_log("testout/t101jpeg.log",1);
-
-i_has_format("jpeg")
-  or plan skip_all => "no jpeg support";
-
-plan tests => 94;
-
-my $green=i_color_new(0,255,0,255);
-my $blue=i_color_new(0,0,255,255);
-my $red=i_color_new(255,0,0,255);
-
-my $img=test_image_raw();
-my $cmpimg=Imager::ImgRaw::new(150,150,3);
-
-open(FH,">testout/t101.jpg")
-  || die "cannot open testout/t101.jpg for writing\n";
-binmode(FH);
-my $IO = Imager::io_new_fd(fileno(FH));
-ok(i_writejpeg_wiol($img,$IO,30), "write jpeg low level");
-close(FH);
-
-open(FH, "testout/t101.jpg") || die "cannot open testout/t101.jpg\n";
-binmode(FH);
-$IO = Imager::io_new_fd(fileno(FH));
-($cmpimg,undef) = i_readjpeg_wiol($IO);
-close(FH);
-
-my $diff = sqrt(i_img_diff($img,$cmpimg))/150*150;
-print "# jpeg average mean square pixel difference: ",$diff,"\n";
-ok($cmpimg, "read jpeg low level");
-
-ok($diff < 10000, "difference between original and jpeg within bounds");
-
-Imager::i_log_entry("Starting 4\n", 1);
-my $imoo = Imager->new;
-ok($imoo->read(file=>'testout/t101.jpg'), "read jpeg OO");
-
-ok($imoo->write(file=>'testout/t101_oo.jpg'), "write jpeg OO");
-Imager::i_log_entry("Starting 5\n", 1);
-my $oocmp = Imager->new;
-ok($oocmp->read(file=>'testout/t101_oo.jpg'), "read jpeg OO for comparison");
-
-$diff = sqrt(i_img_diff($imoo->{IMG},$oocmp->{IMG}))/150*150;
-print "# OO image difference $diff\n";
-ok($diff < 10000, "difference between original and jpeg within bounds");
-
-# write failure test
-open FH, "< testout/t101.jpg" or die "Cannot open testout/t101.jpg: $!";
-binmode FH;
-ok(!$imoo->write(fd=>fileno(FH), type=>'jpeg'), 'failure handling');
-close FH;
-print "# ",$imoo->errstr,"\n";
-
-# check that the i_format tag is set
-my @fmt = $imoo->tags(name=>'i_format');
-is($fmt[0], 'jpeg', 'i_format tag');
-
-{ # check file limits are checked
-  my $limit_file = "testout/t101.jpg";
-  ok(Imager->set_file_limits(reset=>1, width=>149), "set width limit 149");
-  my $im = Imager->new;
-  ok(!$im->read(file=>$limit_file),
-     "should fail read due to size limits");
-  print "# ",$im->errstr,"\n";
-  like($im->errstr, qr/image width/, "check message");
-  
-  ok(Imager->set_file_limits(reset=>1, height=>149), "set height limit 149");
-  ok(!$im->read(file=>$limit_file),
-     "should fail read due to size limits");
-  print "# ",$im->errstr,"\n";
-  like($im->errstr, qr/image height/, "check message");
-  
-  ok(Imager->set_file_limits(reset=>1, width=>150), "set width limit 150");
-  ok($im->read(file=>$limit_file),
-     "should succeed - just inside width limit");
-  ok(Imager->set_file_limits(reset=>1, height=>150), "set height limit 150");
-  ok($im->read(file=>$limit_file),
-     "should succeed - just inside height limit");
-  
-  # 150 x 150 x 3 channel image uses 67500 bytes
-  ok(Imager->set_file_limits(reset=>1, bytes=>67499),
-     "set bytes limit 67499");
-  ok(!$im->read(file=>$limit_file),
-       "should fail - too many bytes");
-  print "# ",$im->errstr,"\n";
-  like($im->errstr, qr/storage size/, "check error message");
-  ok(Imager->set_file_limits(reset=>1, bytes=>67500),
-     "set bytes limit 67500");
-  ok($im->read(file=>$limit_file),
-     "should succeed - just inside bytes limit");
-  Imager->set_file_limits(reset=>1);
-}
-
-SKIP:
-{
-  # we don't test them all
-  my %expected_tags =
-    (
-     exif_date_time_original => "2005:11:25 00:00:00",
-     exif_flash => 0,
-     exif_image_description => "Imager Development Notes",
-     exif_make => "Canon",
-     exif_model => "CanoScan LiDE 35",
-     exif_resolution_unit => 2,
-     exif_resolution_unit_name => "inches",
-     exif_user_comment => "        Part of notes from reworking i_arc() and friends.",
-     exif_white_balance => 0,
-     exif_white_balance_name => "Auto white balance",
-    );
-  
-  # exif tests
-  Imager::i_exif_enabled()
-      or skip("no exif support", scalar keys %expected_tags);
-  
-  my $im = Imager->new;
-  $im->read(file=>"testimg/exiftest.jpg")
-    or skip("Could not read test image:".$im->errstr, scalar keys %expected_tags);
-  
-  for my $key (keys %expected_tags) {
-    is($expected_tags{$key}, $im->tags(name => $key),
-       "test value of exif tag $key");
-  }
-}
-
-{
-  # tests that the density values are set and read correctly
-  # tests jpeg_comment too
-  my @density_tests =
-    (
-     [ 't101cm100.jpg', 
-       { 
-       jpeg_density_unit => 2, 
-       i_xres => 254, 
-       i_yres => 254
-       },
-       { 
-       jpeg_density_unit => 2, 
-       i_xres => 254, 
-       i_yres => 254,
-       i_aspect_only => undef,
-       },
-     ],
-     [
-      't101xonly.jpg',
-      {
-       i_xres => 100,
-      },
-      {
-       i_xres => 100,
-       i_yres => 100,
-       jpeg_density_unit => 1,
-       i_aspect_only => undef,
-      },
-     ],
-     [
-      't101yonly.jpg',
-      {
-       i_yres => 100,
-      },
-      {
-       i_xres => 100,
-       i_yres => 100,
-       jpeg_density_unit => 1,
-       i_aspect_only => undef,
-      },
-     ],
-     [
-      't101asponly.jpg',
-      {
-       i_xres => 50,
-       i_yres => 100,
-       i_aspect_only => 1,
-      },
-      {
-       i_xres => 50,
-       i_yres => 100,
-       i_aspect_only => 1,
-       jpeg_density_unit => 0,
-      },
-     ],
-     [
-      't101com.jpg',
-      {
-       jpeg_comment => 'test comment'
-      },
-     ],
-    );
-  
-  print "# test density tags\n";
-  # I don't care about the content
-  my $base_im = Imager->new(xsize => 10, ysize => 10);
-  for my $test (@density_tests) {
-    my ($filename, $out_tags, $expect_tags) = @$test;
-    $expect_tags ||= $out_tags;
-    
-    my $work = $base_im->copy;
-    for my $key (keys %$out_tags) {
-      $work->addtag(name => $key, value => $out_tags->{$key});
-    }
-    
-    ok($work->write(file=>"testout/$filename", type=>'jpeg'),
-       "save $filename");
-    
-    my $check = Imager->new;
-    ok($check->read(file=> "testout/$filename"),
-       "read $filename");
-    
-    my %tags;
-    for my $key (keys %$expect_tags) {
-      $tags{$key} = $check->tags(name=>$key);
-    }
-    is_deeply($expect_tags, \%tags, "check tags for $filename");
-  }
-}
-
-{ # Issue # 17981
-  # the test image has a zero-length user_comment field
-  # the code would originally attempt to convert '\0' to ' '
-  # for the first 8 bytes, even if the string was less than 
-  # 8 bytes long
-  my $im = Imager->new;
-  ok($im->read(file => 'testimg/209_yonge.jpg', type=>'jpeg'),
-     "test read of image with invalid exif_user_comment");
-  is($im->tags(name=>'exif_user_comment'), '',
-     "check exif_user_comment set correctly");
-}
-
-{ # test parseiptc handling no IPTC data correctly
-  my $saw_warn;
-  local $SIG{__WARN__} = 
-    sub {
-      ++$saw_warn;
-      print "# @_\n";
-    };
-  my $im = Imager->new;
-  ok($im->read(file => 'testout/t101.jpg', type=>'jpeg'),
-     "read jpeg with no IPTC data");
-  ok(!defined $im->{IPTCRAW}, "no iptc data");
-  my %iptc = $im->parseiptc;
-  ok(!$saw_warn, "should be no warnings");
-}
-
-{ # Issue # 18397
-  # attempting to write a 4 channel image to a bufchain would
-  # cause a seg fault.
-  # it should fail still
-  # overridden by # 29876
-  # give 4/2 channel images a background color when saving to JPEG
-  my $im = Imager->new(xsize => 16, ysize => 16, channels => 4);
-  $im->box(filled => 1, xmin => 8, color => '#FFE0C0');
-  my $data;
-  ok($im->write(data => \$data, type => 'jpeg'),
-     "should write with a black background");
-  my $imread = Imager->new;
-  ok($imread->read(data => $data, type => 'jpeg'), 'read it back');
-  is_color_close3($imread->getpixel('x' => 0, 'y' => 0), 0, 0, 0, 4,
-                 "check it's black");
-  is_color_close3($imread->getpixel('x' => 15, 'y' => 9), 255, 224, 192, 4,
-                 "check filled area filled");
-  
-  # write with a red background
-  $data = '';
-  ok($im->write(data => \$data, type => 'jpeg', i_background => '#FF0000'),
-     "write with red background");
-  ok($imread->read(data => $data, type => 'jpeg'), "read it back");
-  is_color_close3($imread->getpixel('x' => 0, 'y' => 0), 255, 0, 0, 4,
-                 "check it's red");
-  is_color_close3($imread->getpixel('x' => 15, 'y' => 9), 255, 224, 192, 4,
-                 "check filled area filled");
-}
-SKIP:
-{ # Issue # 18496
-  # If a jpeg with EXIF data containing an (invalid) IFD entry with a 
-  # type of zero is read then Imager crashes with a Floating point 
-  # exception
-  # testimg/zerojpeg.jpg was manually modified from exiftest.jpg to
-  # reproduce the problem.
-  Imager::i_exif_enabled()
-      or skip("no exif support", 1);
-  my $im = Imager->new;
-  ok($im->read(file=>'testimg/zerotype.jpg'), "shouldn't crash");
-}
-
-SKIP:
-{ # code coverage - make sure wiol_skip_input_data is called
-  open BASEDATA, "< testimg/exiftest.jpg"
-    or skip "can't open base data", 1;
-  binmode BASEDATA;
-  my $data = do { local $/; <BASEDATA> };
-  close BASEDATA;
-  
-  substr($data, 3, 1) eq "\xE1"
-    or skip "base data isn't as expected", 1;
-  # inserting a lot of marker data here means we take the branch in 
-  # wiol_skip_input_data that refills the buffer
-  my $marker = "\xFF\xE9"; # APP9 marker
-  $marker .= pack("n", 8192) . "x" x 8190;
-  $marker x= 10; # make it take up a lot of space
-  substr($data, 2, 0) = $marker;
-  my $im = Imager->new;
-  ok($im->read(data => $data), "read with a skip of data");
-}
-
-SKIP:
-{ # code coverage - take the branch that provides a fake EOI
-  open BASEDATA, "< testimg/exiftest.jpg"
-    or skip "can't open base data", 1;
-  binmode BASEDATA;
-  my $data = do { local $/; <BASEDATA> };
-  close BASEDATA;
-  substr($data, -1000) = '';
-  
-  my $im = Imager->new;
-  ok($im->read(data => $data), "read with image data truncated");
-}
-
-{ # code coverage - make sure wiol_empty_output_buffer is called
-  my $im = Imager->new(xsize => 1000, ysize => 1000);
-  for my $x (0 .. 999) {
-    $im->line(x1 => $x, y1 => 0, x2 => $x, y2 => 999,
-             color => Imager::Color->new(rand 256, rand 256, rand 256));
-  }
-  my $data;
-  ok($im->write(data => \$data, type=>'jpeg', jpegquality => 100), 
-     "write big file to ensure wiol_empty_output_buffer is called");
-  
-  # code coverage - write failure path in wiol_empty_output_buffer
-  ok(!$im->write(callback => sub { return },
-                type => 'jpeg', jpegquality => 100),
-     "fail to write")
-    and print "# ", $im->errstr, "\n";
-}
-
-{ # code coverage - virtual image branch in i_writejpeg_wiol()
-  my $im = $imoo->copy;
-  my $immask = $im->masked;
-  ok($immask, "made a virtual image (via masked)");
-  ok($immask->virtual, "check it's virtual");
-  my $mask_data;
-  ok($immask->write(data => \$mask_data, type => 'jpeg'),
-     "write masked version");
-  my $base_data;
-  ok($im->write(data => \$base_data, type=>'jpeg'),
-     "write normal version");
-  is($base_data, $mask_data, "check the data written matches");
-}
-
-SKIP:
-{ # code coverage - IPTC data
-  # this is dummy data
-  my $iptc = "\x04\x04" .
-    "\034\002x   My Caption"
-      . "\034\002P   Tony Cook"
-       . "\034\002i   Dummy Headline!"
-         . "\034\002n   No Credit Given";
-  
-  my $app13 = "\xFF\xED" . pack("n", 2 + length $iptc) . $iptc;
-  
-  open BASEDATA, "< testimg/exiftest.jpg"
-    or skip "can't open base data", 1;
-  binmode BASEDATA;
-  my $data = do { local $/; <BASEDATA> };
-  close BASEDATA;
-  substr($data, 2, 0) = $app13;
-  
-  my $im = Imager->new;
-  ok($im->read(data => $data), "read with app13 data");
-  my %iptc = $im->parseiptc;
-  is($iptc{caption}, 'My Caption', 'check iptc caption');
-  is($iptc{photogr}, 'Tony Cook', 'check iptc photogr');
-  is($iptc{headln}, 'Dummy Headline!', 'check iptc headln');
-  is($iptc{credit}, 'No Credit Given', 'check iptc credit');
-}
-
-{ # handling of CMYK jpeg
-  # http://rt.cpan.org/Ticket/Display.html?id=20416
-  my $im = Imager->new;
-  ok($im->read(file => 'testimg/scmyk.jpg'), 'read a CMYK jpeg');
-  is($im->getchannels, 3, "check channel count");
-  my $col = $im->getpixel(x => 0, 'y' => 0);
-  ok($col, "got the 'black' pixel");
-  # this is jpeg, so we can't compare colors exactly
-  # older versions returned this pixel at a light color, but
-  # it's black in the image
-  my ($r, $g, $b) = $col->rgba;
-  cmp_ok($r, '<', 10, 'black - red low');
-  cmp_ok($g, '<', 10, 'black - green low');
-  cmp_ok($b, '<', 10, 'black - blue low');
-  $col = $im->getpixel(x => 15, 'y' => 0);
-  ok($col, "got the dark blue");
-  ($r, $g, $b) = $col->rgba;
-  cmp_ok($r, '<', 10, 'dark blue - red low');
-  cmp_ok($g, '<', 10, 'dark blue - green low');
-  cmp_ok($b, '>', 110, 'dark blue - blue middle (bottom)');
-  cmp_ok($b, '<', 130, 'dark blue - blue middle (top)');
-  $col = $im->getpixel(x => 0, 'y' => 15);
-  ok($col, "got the red");
-  ($r, $g, $b) = $col->rgba;
-  cmp_ok($r, '>', 245, 'red - red high');
-  cmp_ok($g, '<', 10, 'red - green low');
-  cmp_ok($b, '<', 10, 'red - blue low');
-}
-
-{
-  ok(grep($_ eq 'jpeg', Imager->read_types), "check jpeg in read types");
-  ok(grep($_ eq 'jpeg', Imager->write_types), "check jpeg in write types");
-}
-
-
index 4eced40..24b632d 100644 (file)
@@ -5,7 +5,7 @@ use Imager qw(:all);
 
 init_log("testout/t101jpeg.log",1);
 
-i_has_format("jpeg")
+$Imager::formats{"jpeg"}
   and plan skip_all => "have jpeg support - this tests the lack of it";
 
 plan tests => 6;
index ff38449..2ea9f14 100644 (file)
@@ -40,7 +40,7 @@ for(keys %hsh) { print "# $_\n"; }
 my $img = Imager->new();
 
 my %files;
-@files{@types} = ({ file => "testimg/209_yonge.jpg"  },
+@files{@types} = ({ file => "JPEG/testimg/209_yonge.jpg"  },
                  { file => "testimg/test.png"  },
                  { file => "testimg/test.raw", xsize=>150, ysize=>150, type=>'raw', interleave => 0},
                  { file => "testimg/penguin-base.ppm"  },
diff --git a/testimg/209_yonge.jpg b/testimg/209_yonge.jpg
deleted file mode 100644 (file)
index 2da5733..0000000
Binary files a/testimg/209_yonge.jpg and /dev/null differ
diff --git a/testimg/exiftest.jpg b/testimg/exiftest.jpg
deleted file mode 100755 (executable)
index 59d5291..0000000
Binary files a/testimg/exiftest.jpg and /dev/null differ
diff --git a/testimg/scmyk.jpg b/testimg/scmyk.jpg
deleted file mode 100644 (file)
index 3886a7a..0000000
Binary files a/testimg/scmyk.jpg and /dev/null differ
diff --git a/testimg/zerotype.jpg b/testimg/zerotype.jpg
deleted file mode 100755 (executable)
index d5f2d90..0000000
Binary files a/testimg/zerotype.jpg and /dev/null differ