i_tt_text
i_tt_bbox
- i_readjpeg_wiol
- i_writejpeg_wiol
-
i_readpnm_wiol
i_writeppm_wiol
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;
#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
--- /dev/null
+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
--- /dev/null
+#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;
--- /dev/null
+#!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
+}
--- /dev/null
+#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
+*/
--- /dev/null
+/* 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 */
--- /dev/null
+/*
+=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
+*/
--- /dev/null
+#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
--- /dev/null
+#!perl -w
+use strict;
+use Test::More tests => 1;
+use_ok("Imager::File::JPEG");
--- /dev/null
+#!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");
+}
+
+
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
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
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
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
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
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
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
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
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
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,
"libpath=s" => \@libpaths,
"verbose|v" => \$VERBOSE,
"nolog" => \$NOLOG,
- "noexif" => \$noexif,
'coverage' => \$coverage,
"assert|a" => \$assert);
}
-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{"$_"} : $_,
[
"PNG",
"GIF",
+ "TIFF",
+ "JPEG",
],
},
resources =>
}
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',
$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)
Build for coverage testing.
--assert
Build with assertions active.
- --noexif
- Disable EXIF parsing.
EOS
exit 1;
/*
=item i_get_file_background(im, &bg)
+=category Files
+
Retrieve the file write background color tag from the image.
If not present, returns black.
/*
=item i_get_file_backgroundf(im, &bg)
+=category Files
+
Retrieve the file write background color tag from the image as a
floating point color.
+++ /dev/null
-#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
-*/
+++ /dev/null
-/* 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 */
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
#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
/* 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;
+++ /dev/null
-/*
-=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
-*/
=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>)
=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)
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
}
/*
-=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
*/
+++ /dev/null
-#!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");
-}
-
-
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;
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" },