From: Tony Cook Date: Fri, 4 Jan 2002 02:33:55 +0000 (+0000) Subject: enhanced iolayer X-Git-Tag: Imager-0.48^2~464 X-Git-Url: http://git.imager.perl.org/imager.git/commitdiff_plain/10461f9a4b1fd147a584973ecc92eeafc941226f enhanced iolayer multi image/file tiff support --- diff --git a/Changes b/Changes index cb1d6923..31a01734 100644 --- a/Changes +++ b/Changes @@ -566,8 +566,18 @@ Revision history for Perl extension Imager. - applied T1 afm patch from Claes Jacobsson - split IM_INCPATH and IM_LIBPATH with $Config{path_sep}, so they work on Windows - - Added memory pools for easy cleanup of temp buffers - - Added read support for sgi .rgb files. + - Added memory pools for easy cleanup of temp buffers + - Added read support for sgi .rgb files. + - io_new_fd() now creates a FDSEEK io object + - implemented i_readgif_wiol() + - Imager->read() now uses i_readgif_wiol(); + - extend callback iolayers at C and Perl levels + - implemented i_writegif_wiol() + - split out Perl iolayer initialization into private methods + - add tests for each type of iolayer in t50basicoo.t + - read/write multi-image tiff files + - tests in t50basicoo.t for multi-image/file + ================================================================= For latest versions check the Imager-devel pages: diff --git a/Imager.pm b/Imager.pm index 4dfb83d7..9c585710 100644 --- a/Imager.pm +++ b/Imager.pm @@ -85,6 +85,7 @@ use Imager::Font; i_writepng_wiol i_readgif + i_readgif_wiol i_readgif_callback i_writegif i_writegifmc @@ -853,12 +854,108 @@ sub deltag { } } +my @needseekcb = qw/tiff/; +my %needseekcb = map { $_, $_ } @needseekcb; + + +sub _get_reader_io { + my ($self, $input, $type) = @_; + + if ($input->{fd}) { + return io_new_fd($input->{fd}); + } + elsif ($input->{fh}) { + my $fd = fileno($input->{fh}); + unless ($fd) { + $self->_set_error("Handle in fh option not opened"); + return; + } + return io_new_fd($fd); + } + elsif ($input->{file}) { + my $file = IO::File->new($input->{file}, "r"); + unless ($file) { + $self->_set_error("Could not open $input->{file}: $!"); + return; + } + binmode $file; + return (io_new_fd(fileno($file)), $file); + } + elsif ($input->{data}) { + return io_new_buffer($input->{data}); + } + elsif ($input->{callback} || $input->{readcb}) { + if ($needseekcb{$type} && !$input->{seekcb}) { + $self->_set_error("Format $type needs a seekcb parameter"); + } + if ($input->{maxbuffer}) { + return io_new_cb($input->{writecb}, + $input->{callback} || $input->{readcb}, + $input->{seekcb}, $input->{closecb}, + $input->{maxbuffer}); + } + else { + return io_new_cb($input->{writecb}, + $input->{callback} || $input->{readcb}, + $input->{seekcb}, $input->{closecb}); + } + } + else { + $self->_set_error("file/fd/fh/data/callback parameter missing"); + return; + } +} + +sub _get_writer_io { + my ($self, $input, $type) = @_; + + if ($input->{fd}) { + return io_new_fd($input->{fd}); + } + elsif ($input->{fh}) { + my $fd = fileno($input->{fh}); + unless ($fd) { + $self->_set_error("Handle in fh option not opened"); + return; + } + return io_new_fd($fd); + } + elsif ($input->{file}) { + my $fh = new IO::File($input->{file},"w+"); + unless ($fh) { + $self->_set_error("Could not open file $input->{file}: $!"); + return; + } + binmode($fh) or die; + return (io_new_fd(fileno($fh)), $fh); + } + elsif ($input->{data}) { + return io_new_bufchain(); + } + elsif ($input->{callback} || $input->{writecb}) { + if ($input->{maxbuffer}) { + return io_new_cb($input->{callback} || $input->{writecb}, + $input->{readcb}, + $input->{seekcb}, $input->{closecb}, + $input->{maxbuffer}); + } + else { + return io_new_cb($input->{callback} || $input->{writecb}, + $input->{readcb}, + $input->{seekcb}, $input->{closecb}); + } + } + else { + $self->_set_error("file/fd/fh/data/callback parameter missing"); + return; + } +} + # Read an image from file sub read { my $self = shift; my %input=@_; - my ($fh, $fd, $IO); if (defined($self->{IMG})) { # let IIM_DESTROY do the destruction, since the image may be @@ -867,21 +964,6 @@ sub read { undef($self->{IMG}); } - if (!$input{fd} and !$input{file} and !$input{data}) { - $self->{ERRSTR}='no file, fd or data parameter'; return undef; - } - if ($input{file}) { - $fh = new IO::File($input{file},"r"); - if (!defined $fh) { - $self->{ERRSTR}='Could not open file'; return undef; - } - binmode($fh); - $fd = $fh->fileno(); - } - if ($input{fd}) { - $fd=$input{fd}; - } - # FIXME: Find the format here if not specified # yes the code isn't here yet - next week maybe? # Next week? Are you high or something? That comment @@ -891,15 +973,20 @@ sub read { if (!$input{'type'} and $input{file}) { $input{'type'}=$FORMATGUESS->($input{file}); } + unless ($input{'type'}) { + $self->_set_error('type parameter missing and not possible to guess from extension'); + return undef; + } if (!$formats{$input{'type'}}) { $self->{ERRSTR}='format not supported'; return undef; } - my %iolready=(jpeg=>1, png=>1, tiff=>1, pnm=>1, raw=>1, bmp=>1, tga=>1, rgb=>1); + my %iolready=(jpeg=>1, png=>1, tiff=>1, pnm=>1, raw=>1, bmp=>1, tga=>1, rgb=>1, gif=>1); if ($iolready{$input{'type'}}) { # Setup data source - $IO = defined $fd ? io_new_fd($fd) : io_new_buffer($input{data}); + my ($IO, $fh) = $self->_get_reader_io(\%input, $input{'type'}) + or return; if ( $input{'type'} eq 'jpeg' ) { ($self->{IMG},$self->{IPTCRAW})=i_readjpeg_wiol( $IO ); @@ -946,6 +1033,29 @@ sub read { $self->{DEBUG} && print "loading a bmp file\n"; } + if ( $input{'type'} eq 'gif' ) { + if ($input{colors} && !ref($input{colors})) { + # must be a reference to a scalar that accepts the colour map + $self->{ERRSTR} = "option 'colors' must be a scalar reference"; + return undef; + } + if ($input{colors}) { + my $colors; + ($self->{IMG}, $colors) =i_readgif_wiol( $IO ); + if ($colors) { + ${ $input{colors} } = [ map { NC(@$_) } @$colors ]; + } + } + else { + $self->{IMG} =i_readgif_wiol( $IO ); + } + if ( !defined($self->{IMG}) ) { + $self->{ERRSTR}=$self->_error_as_msg(); + return undef; + } + $self->{DEBUG} && print "loading a gif file\n"; + } + if ( $input{'type'} eq 'tga' ) { $self->{IMG}=i_readtga_wiol( $IO, -1 ); # Fixme, check if that length parameter is ever needed if ( !defined($self->{IMG}) ) { @@ -1003,6 +1113,7 @@ sub read { return undef; } + my ($fh, $fd); if ($input{file}) { $fh = new IO::File($input{file},"r"); if (!defined $fh) { @@ -1062,13 +1173,13 @@ sub write { compress=>1, wierdpack=>0, fax_fine=>1, @_); - my ($fh, $rc, $fd, $IO); + my $rc; - my %iolready=( tiff=>1, raw=>1, png=>1, pnm=>1, bmp=>1, jpeg=>1, tga=>1 ); # this will be SO MUCH BETTER once they are all in there + my %iolready=( tiff=>1, raw=>1, png=>1, pnm=>1, bmp=>1, jpeg=>1, tga=>1, + gif=>1 ); # this will be SO MUCH BETTER once they are all in there unless ($self->{IMG}) { $self->{ERRSTR}='empty input image'; return undef; } - if (!$input{file} and !$input{'fd'} and !$input{'data'}) { $self->{ERRSTR}='file/fd/data parameter missing'; return undef; } if (!$input{'type'} and $input{file}) { $input{'type'}=$FORMATGUESS->($input{file}); } @@ -1079,21 +1190,11 @@ sub write { if (!$formats{$input{'type'}}) { $self->{ERRSTR}='format not supported'; return undef; } - if (exists $input{'fd'}) { - $fd=$input{'fd'}; - } elsif (exists $input{'data'}) { - $IO = Imager::io_new_bufchain(); - } else { - $fh = new IO::File($input{file},"w+"); - if (!defined $fh) { $self->{ERRSTR}='Could not open file'; return undef; } - binmode($fh) or die; - $fd = $fh->fileno(); - } + my ($IO, $fh) = $self->_get_writer_io(\%input, $input{'type'}) + or return undef; + # this conditional is probably obsolete if ($iolready{$input{'type'}}) { - if (defined $fd) { - $IO = io_new_fd($fd); - } if ($input{'type'} eq 'tiff') { if (defined $input{class} && $input{class} eq 'fax') { @@ -1144,6 +1245,19 @@ sub write { return undef; } $self->{DEBUG} && print "writing a tga file\n"; + } elsif ( $input{'type'} eq 'gif' ) { + # compatibility with the old interfaces + if ($input{gifquant} eq 'lm') { + $input{make_colors} = 'addi'; + $input{translate} = 'perturb'; + $input{perturb} = $input{lmdither}; + } elsif ($input{gifquant} eq 'gen') { + # just pass options through + } else { + $input{make_colors} = 'webmap'; # ignored + $input{translate} = 'giflib'; + } + $rc = i_writegif_wiol($IO, \%input, $self->{IMG}); } if (exists $input{'data'}) { @@ -1155,98 +1269,51 @@ sub write { ${$input{data}} = $data; } return $self; - } else { - if ( $input{'type'} eq 'gif' ) { - if (not $input{gifplanes}) { - my $gp; - my $count=i_count_colors($self->{IMG}, 256); - $gp=8 if $count == -1; - $gp=1 if not $gp and $count <= 2; - $gp=2 if not $gp and $count <= 4; - $gp=3 if not $gp and $count <= 8; - $gp=4 if not $gp and $count <= 16; - $gp=5 if not $gp and $count <= 32; - $gp=6 if not $gp and $count <= 64; - $gp=7 if not $gp and $count <= 128; - $input{gifplanes} = $gp || 8; - } - - if ($input{gifplanes}>8) { - $input{gifplanes}=8; - } - if ($input{gifquant} eq 'gen' || $input{callback}) { - - - if ($input{gifquant} eq 'lm') { - - $input{make_colors} = 'addi'; - $input{translate} = 'perturb'; - $input{perturb} = $input{lmdither}; - } elsif ($input{gifquant} eq 'gen') { - # just pass options through - } else { - $input{make_colors} = 'webmap'; # ignored - $input{translate} = 'giflib'; - } - - if ($input{callback}) { - defined $input{maxbuffer} or $input{maxbuffer} = -1; - $rc = i_writegif_callback($input{callback}, $input{maxbuffer}, - \%input, $self->{IMG}); - } else { - $rc = i_writegif_gen($fd, \%input, $self->{IMG}); - } - - } elsif ($input{gifquant} eq 'lm') { - $rc=i_writegif($self->{IMG},$fd,$input{gifplanes},$input{lmdither},$input{lmfixed}); - } else { - $rc=i_writegifmc($self->{IMG},$fd,$input{gifplanes}); - } - if ( !defined($rc) ) { - $self->{ERRSTR} = "Writing GIF file: "._error_as_msg(); return undef; - } - $self->{DEBUG} && print "writing a gif file\n"; - - } } + return $self; } sub write_multi { my ($class, $opts, @images) = @_; + if (!$opts->{'type'} && $opts->{'file'}) { + $opts->{'type'} = $FORMATGUESS->($opts->{'file'}); + } + unless ($opts->{'type'}) { + $class->_set_error('type parameter missing and not possible to guess from extension'); + return; + } + # translate to ImgRaw + if (grep !UNIVERSAL::isa($_, 'Imager') || !$_->{IMG}, @images) { + $class->_set_error('Usage: Imager->write_multi({ options }, @images)'); + return 0; + } + my @work = map $_->{IMG}, @images; + my ($IO, $file) = $class->_get_writer_io($opts, $opts->{'type'}) + or return undef; if ($opts->{'type'} eq 'gif') { my $gif_delays = $opts->{gif_delays}; local $opts->{gif_delays} = $gif_delays; - unless (ref $opts->{gif_delays}) { + if ($opts->{gif_delays} && !ref $opts->{gif_delays}) { # assume the caller wants the same delay for each frame $opts->{gif_delays} = [ ($gif_delays) x @images ]; } - # translate to ImgRaw - if (grep !UNIVERSAL::isa($_, 'Imager') || !$_->{IMG}, @images) { - $ERRSTR = "Usage: Imager->write_multi({ options }, @images)"; - return 0; - } - my @work = map $_->{IMG}, @images; - if ($opts->{callback}) { - # Note: you may need to fix giflib for this one to work - my $maxbuffer = $opts->{maxbuffer}; - defined $maxbuffer or $maxbuffer = -1; # max by default - return i_writegif_callback($opts->{callback}, $maxbuffer, - $opts, @work); - } - if ($opts->{fd}) { - return i_writegif_gen($opts->{fd}, $opts, @work); + my $res = i_writegif_wiol($IO, $opts, @work); + $res or $class->_set_error($class->_error_as_msg()); + return $res; + } + elsif ($opts->{'type'} eq 'tiff') { + my $res; + $opts->{fax_fine} = 1 unless exists $opts->{fax_fine}; + if ($opts->{'class'} && $opts->{'class'} eq 'fax') { + $res = i_writetiff_multi_wiol_faxable($IO, $opts->{fax_fine}, @work); } else { - my $fh = IO::File->new($opts->{file}, "w+"); - unless ($fh) { - $ERRSTR = "Error creating $opts->{file}: $!"; - return 0; - } - binmode($fh); - return i_writegif_gen(fileno($fh), $opts, @work); + $res = i_writetiff_multi_wiol($IO, @work); } + $res or $class->_set_error($class->_error_as_msg()); + return $res; } else { $ERRSTR = "Sorry, write_multi doesn't support $opts->{'type'} yet"; @@ -1267,52 +1334,24 @@ sub read_multi { $ERRSTR = "No type parameter supplied and it couldn't be guessed"; return; } - my $fd; - my $file; - if ($opts{file}) { - $file = IO::File->new($opts{file}, "r"); - unless ($file) { - $ERRSTR = "Could not open file $opts{file}: $!"; - return; - } - binmode $file; - $fd = fileno($file); - } - elsif ($opts{fh}) { - $fd = fileno($opts{fh}); - unless ($fd) { - $ERRSTR = "File handle specified with fh option not open"; - return; - } - } - elsif ($opts{fd}) { - $fd = $opts{fd}; - } - elsif ($opts{callback} || $opts{data}) { - # don't fail here - } - else { - $ERRSTR = "You need to specify one of file, fd, fh, callback or data"; - return; - } + my ($IO, $file) = $class->_get_reader_io(\%opts, $opts{'type'}) + or return; if ($opts{'type'} eq 'gif') { my @imgs; - if ($fd) { - @imgs = i_readgif_multi($fd); + @imgs = i_readgif_multi_wiol($IO); + if (@imgs) { + return map { + bless { IMG=>$_, DEBUG=>$DEBUG, ERRSTR=>undef }, 'Imager' + } @imgs; } else { - if (Imager::i_giflib_version() < 4.0) { - $ERRSTR = "giflib3.x does not support callbacks"; - return; - } - if ($opts{callback}) { - @imgs = i_readgif_multi_callback($opts{callback}) - } - else { - @imgs = i_readgif_multi_scalar($opts{data}); - } + $ERRSTR = _error_as_msg(); + return; } + } + elsif ($opts{'type'} eq 'tiff') { + my @imgs = i_readtiff_multi_wiol($IO, -1); if (@imgs) { return map { bless { IMG=>$_, DEBUG=>$DEBUG, ERRSTR=>undef }, 'Imager' @@ -2230,6 +2269,17 @@ sub errstr { ref $_[0] ? $_[0]->{ERRSTR} : $ERRSTR } +sub _set_error { + my ($self, $msg) = @_; + + if (ref $self) { + $self->{ERRSTR} = $msg; + } + else { + $ERRSTR = $msg; + } +} + # Default guess for the type of an image from extension sub def_guess_type { @@ -2244,6 +2294,7 @@ sub def_guess_type { return 'tga' if ($ext eq "tga"); return 'rgb' if ($ext eq "rgb"); return 'gif' if ($ext eq "gif"); + return 'raw' if ($ext eq "raw"); return (); } @@ -2327,9 +2378,8 @@ Imager - Perl extension for Generating 24 bit Images =head1 SYNOPSIS - use Imager qw(init); + use Imager; - init(); $img = Imager->new(); $img->open(file=>'image.ppm',type=>'pnm') || print "failed: ",$img->{ERRSTR},"\n"; @@ -2433,6 +2483,105 @@ downwards. =head2 Reading and writing images +You can read and write a variety of images formats, assuming you have +the appropriate libraries, and images can be read or written to/from +files, file handles, file descriptors, scalars, or through callbacks. + +To see which image formats Imager is compiled to support the following +code snippet is sufficient: + + use Imager; + print join " ", keys %Imager::formats; + +This will include some other information identifying libraries rather +than file formats. + +Reading writing to and from files is simple, use the C +method to read an image: + + my $img = Imager->new; + $img->read(file=>$filename, type=>$type) + or die "Cannot read $filename: ", $img->errstr; + +and the C method to write an image: + + $img->write(file=>$filename, type=>$type) + or die "Cannot write $filename: ", $img->errstr; + +If the I includes an extension that Imager recognizes, then +you don't need the I, but you may want to provide one anyway. +Imager currently does not check the files magic to determine the +format. It is possible to override the method for determining the +filetype from the filename. If the data is given in another form than +a file name a + +When you read an image, Imager may set some tags, possibly including +information about the spatial resolution, textual information, and +animation information. See L for specifics. + +When reading or writing you can specify one of a variety of sources or +targets: + +=over + +=item file + +The C parameter is the name of the image file to be written to +or read from. If Imager recognizes the extension of the file you do +not need to supply a C. + +=item fh + +C is a file handle, typically either returned from +C<new()>>, or a glob from an C call. You should call +C on the handle before passing it to Imager. + +=item fd + +C is a file descriptor. You can get this by calling the +C function on a file handle, or by using one of the standard +file descriptor numbers. + +=item data + +When reading data, C is a scalar containing the image file data, +when writing, C is a reference to the scalar to save the image +file data too. For GIF images you will need giflib 4 or higher, and +you may need to patch giflib to use this option for writing. + +=item callback + +Imager will make calls back to your supplied coderefs to read, write +and seek from/to/through the image file. + +When reading from a file you can use either C or C +to supply the read callback, and when writing C or +C to supply the write callback. + +When writing you can also supply the C option to set the +maximum amount of data that will be buffered before your write +callback is called. Note: the amount of data supplied to your +callback can be smaller or larger than this size. + +The read callback is called with 2 parameters, the minimum amount of +data required, and the maximum amount that Imager will store in it's C +level buffer. You may want to return the minimum if you have a slow +data source, or the maximum if you have a fast source and want to +prevent many calls to your perl callback. The read data should be +returned as a scalar. + +Your write callback takes exactly one parameter, a scalar containing +the data to be written. Return true for success. + +The seek callback takes 2 parameters, a I, and a I, +defined in the same way as perl's seek function. + +You can also supply a C which is called with no parameters +when there is no more data to be written. This could be used to flush +buffered data. + +=back + C<$img-Eread()> generally takes two parameters, 'file' and 'type'. If the type of the file can be determined from the suffix of the file it can be omitted. Format dependant parameters are: For images of diff --git a/Imager.xs b/Imager.xs index 1b7ec5b3..1d8ad779 100644 --- a/Imager.xs +++ b/Imager.xs @@ -130,6 +130,297 @@ static int write_callback(char *userdata, char const *data, int size) { return success; } +#define CBDATA_BUFSIZE 8192 + +struct cbdata { + /* the SVs we use to call back to Perl */ + SV *writecb; + SV *readcb; + SV *seekcb; + SV *closecb; + + /* we need to remember whether the buffer contains write data or + read data + */ + int reading; + int writing; + + /* how far we've read into the buffer (not used for writing) */ + int where; + + /* the amount of space used/data available in the buffer */ + int used; + + /* the maximum amount to fill the buffer before flushing + If any write is larger than this then the buffer is flushed and + the full write is performed. The write is _not_ split into + maxwrite sized calls + */ + int maxlength; + + char buffer[CBDATA_BUFSIZE]; +}; + +/* + +call_writer(cbd, buf, size) + +Low-level function to call the perl writer callback. + +*/ + +static ssize_t call_writer(struct cbdata *cbd, void const *buf, size_t size) { + int count; + int success; + SV *sv; + dSP; + + if (!SvOK(cbd->writecb)) + return -1; + + ENTER; + SAVETMPS; + EXTEND(SP, 1); + PUSHMARK(SP); + PUSHs(sv_2mortal(newSVpv((char *)buf, size))); + PUTBACK; + + count = perl_call_sv(cbd->writecb, G_SCALAR); + + SPAGAIN; + if (count != 1) + croak("Result of perl_call_sv(..., G_SCALAR) != 1"); + + sv = POPs; + success = SvTRUE(sv); + + + PUTBACK; + FREETMPS; + LEAVE; + + return success ? size : 0; +} + +static ssize_t call_reader(struct cbdata *cbd, void *buf, size_t size, + size_t maxread) { + int count; + int result; + SV *data; + dSP; + + if (!SvOK(cbd->readcb)) + return -1; + + ENTER; + SAVETMPS; + EXTEND(SP, 2); + PUSHMARK(SP); + PUSHs(sv_2mortal(newSViv(size))); + PUSHs(sv_2mortal(newSViv(maxread))); + PUTBACK; + + count = perl_call_sv(cbd->readcb, G_SCALAR); + + SPAGAIN; + + if (count != 1) + croak("Result of perl_call_sv(..., G_SCALAR) != 1"); + + data = POPs; + + if (SvOK(data)) { + STRLEN len; + char *ptr = SvPV(data, len); + if (len > maxread) + croak("Too much data returned in reader callback"); + + memcpy(buf, ptr, len); + result = len; + } + else { + result = -1; + } + + PUTBACK; + FREETMPS; + LEAVE; + + return result; +} + +static ssize_t write_flush(struct cbdata *cbd) { + ssize_t result; + + result = call_writer(cbd, cbd->buffer, cbd->used); + cbd->used = 0; + return result; +} + +static off_t io_seeker(void *p, off_t offset, int whence) { + struct cbdata *cbd = p; + int count; + off_t result; + dSP; + + if (!SvOK(cbd->seekcb)) + return -1; + + if (cbd->writing) { + if (cbd->used && write_flush(cbd) <= 0) + return -1; + cbd->writing = 0; + } + if (whence == SEEK_CUR && cbd->reading && cbd->where != cbd->used) { + offset -= cbd->where - cbd->used; + } + cbd->reading = 0; + cbd->where = cbd->used = 0; + + ENTER; + SAVETMPS; + EXTEND(SP, 2); + PUSHMARK(SP); + PUSHs(sv_2mortal(newSViv(offset))); + PUSHs(sv_2mortal(newSViv(whence))); + PUTBACK; + + count = perl_call_sv(cbd->seekcb, G_SCALAR); + + SPAGAIN; + + if (count != 1) + croak("Result of perl_call_sv(..., G_SCALAR) != 1"); + + result = POPi; + + PUTBACK; + FREETMPS; + LEAVE; + + return result; +} + +static ssize_t io_writer(void *p, void const *data, size_t size) { + struct cbdata *cbd = p; + + /*printf("io_writer(%p, %p, %u)\n", p, data, size);*/ + if (!cbd->writing) { + if (cbd->reading && cbd->where < cbd->used) { + /* we read past the place where the caller expected us to be + so adjust our position a bit */ + *(char *)0 = 0; + if (io_seeker(p, cbd->where - cbd->used, SEEK_CUR) < 0) { + return -1; + } + cbd->reading = 0; + } + cbd->where = cbd->used = 0; + } + cbd->writing = 1; + if (cbd->used && cbd->used + size > cbd->maxlength) { + if (write_flush(cbd) <= 0) { + return 0; + } + cbd->used = 0; + } + if (cbd->used+size <= cbd->maxlength) { + memcpy(cbd->buffer + cbd->used, data, size); + cbd->used += size; + return size; + } + /* it doesn't fit - just pass it up */ + return call_writer(cbd, data, size); +} + +static ssize_t io_reader(void *p, void *data, size_t size) { + struct cbdata *cbd = p; + ssize_t total; + char *out = data; /* so we can do pointer arithmetic */ + int i; + + if (cbd->writing) { + if (write_flush(cbd) <= 0) + return 0; + cbd->writing = 0; + } + + cbd->reading = 1; + if (size <= cbd->used - cbd->where) { + /* simplest case */ + memcpy(data, cbd->buffer+cbd->where, size); + cbd->where += size; + return size; + } + total = 0; + memcpy(out, cbd->buffer + cbd->where, cbd->used - cbd->where); + total += cbd->used - cbd->where; + size -= cbd->used - cbd->where; + out += cbd->used - cbd->where; + if (size < sizeof(cbd->buffer)) { + int did_read; + int copy_size; + while (size + && (did_read = call_reader(cbd, cbd->buffer, size, + sizeof(cbd->buffer))) > 0) { + cbd->where = 0; + cbd->used = did_read; + + copy_size = min(size, cbd->used); + memcpy(out, cbd->buffer, copy_size); + cbd->where += copy_size; + out += copy_size; + total += copy_size; + size -= copy_size; + } + } + else { + /* just read the rest - too big for our buffer*/ + int did_read; + while ((did_read = call_reader(cbd, out, size, size)) > 0) { + size -= did_read; + total += did_read; + out += did_read; + } + } + + return total; +} + +static void io_closer(void *p) { + struct cbdata *cbd = p; + + if (cbd->writing && cbd->used > 0) { + write_flush(cbd); + cbd->writing = 0; + } + + if (SvOK(cbd->closecb)) { + dSP; + + ENTER; + SAVETMPS; + PUSHMARK(SP); + PUTBACK; + + perl_call_sv(cbd->closecb, G_VOID); + + SPAGAIN; + PUTBACK; + FREETMPS; + LEAVE; + } +} + +static void io_destroyer(void *p) { + struct cbdata *cbd = p; + + SvREFCNT_dec(cbd->writecb); + SvREFCNT_dec(cbd->readcb); + SvREFCNT_dec(cbd->seekcb); + SvREFCNT_dec(cbd->closecb); +} + struct value_name { char *name; int value; @@ -717,7 +1008,34 @@ io_new_buffer(data) RETVAL = io_new_buffer(data, length, my_SvREFCNT_dec, ST(0)); OUTPUT: RETVAL - + +Imager::IO +io_new_cb(writecb, readcb, seekcb, closecb, maxwrite = CBDATA_BUFSIZE) + SV *writecb; + SV *readcb; + SV *seekcb; + SV *closecb; + int maxwrite; + PREINIT: + struct cbdata *cbd; + CODE: + cbd = mymalloc(sizeof(struct cbdata)); + SvREFCNT_inc(writecb); + cbd->writecb = writecb; + SvREFCNT_inc(readcb); + cbd->readcb = readcb; + SvREFCNT_inc(seekcb); + cbd->seekcb = seekcb; + SvREFCNT_inc(closecb); + cbd->closecb = closecb; + cbd->reading = cbd->writing = cbd->where = cbd->used = 0; + if (maxwrite > CBDATA_BUFSIZE) + maxwrite = CBDATA_BUFSIZE; + cbd->maxlength = maxwrite; + RETVAL = io_new_cb(cbd, io_reader, io_writer, io_seeker, io_closer, + io_destroyer); + OUTPUT: + RETVAL void io_slurp(ig) @@ -1425,23 +1743,123 @@ i_readtiff_wiol(ig, length) Imager::IO ig int length +void +i_readtiff_multi_wiol(ig, length) + Imager::IO ig + int length + PREINIT: + i_img **imgs; + int count; + int i; + PPCODE: + imgs = i_readtiff_multi_wiol(ig, length, &count); + if (imgs) { + EXTEND(SP, count); + for (i = 0; i < count; ++i) { + SV *sv = sv_newmortal(); + sv_setref_pv(sv, "Imager::ImgRaw", (void *)imgs[i]); + PUSHs(sv); + } + myfree(imgs); + } + undef_int i_writetiff_wiol(im, ig) Imager::ImgRaw im Imager::IO ig +undef_int +i_writetiff_multi_wiol(ig, ...) + Imager::IO ig + PREINIT: + int i; + int img_count; + i_img **imgs; + CODE: + if (items < 2) + croak("Usage: i_writetiff_multi_wiol(ig, images...)"); + img_count = items - 1; + RETVAL = 1; + if (img_count < 1) { + RETVAL = 0; + i_clear_error(); + i_push_error(0, "You need to specify images to save"); + } + else { + imgs = mymalloc(sizeof(i_img *) * img_count); + for (i = 0; i < img_count; ++i) { + SV *sv = ST(1+i); + imgs[i] = NULL; + if (SvROK(sv) && sv_derived_from(sv, "Imager::ImgRaw")) { + imgs[i] = (i_img *)SvIV((SV*)SvRV(sv)); + } + else { + i_clear_error(); + i_push_error(0, "Only images can be saved"); + myfree(imgs); + RETVAL = 0; + break; + } + } + if (RETVAL) { + RETVAL = i_writetiff_multi_wiol(ig, imgs, img_count); + } + myfree(imgs); + } + OUTPUT: + RETVAL + undef_int i_writetiff_wiol_faxable(im, ig, fine) Imager::ImgRaw im Imager::IO ig int fine - -#endif /* HAVE_LIBTIFF */ - +undef_int +i_writetiff_multi_wiol_faxable(ig, fine, ...) + Imager::IO ig + int fine + PREINIT: + int i; + int img_count; + i_img **imgs; + CODE: + if (items < 3) + croak("Usage: i_writetiff_multi_wiol_faxable(ig, fine, images...)"); + img_count = items - 2; + RETVAL = 1; + if (img_count < 1) { + RETVAL = 0; + i_clear_error(); + i_push_error(0, "You need to specify images to save"); + } + else { + imgs = mymalloc(sizeof(i_img *) * img_count); + for (i = 0; i < img_count; ++i) { + SV *sv = ST(2+i); + imgs[i] = NULL; + if (SvROK(sv) && sv_derived_from(sv, "Imager::ImgRaw")) { + imgs[i] = (i_img *)SvIV((SV*)SvRV(sv)); + } + else { + i_clear_error(); + i_push_error(0, "Only images can be saved"); + myfree(imgs); + RETVAL = 0; + break; + } + } + if (RETVAL) { + RETVAL = i_writetiff_multi_wiol_faxable(ig, imgs, img_count, fine); + } + myfree(imgs); + } + OUTPUT: + RETVAL +#endif /* HAVE_LIBTIFF */ #ifdef HAVE_LIBPNG @@ -1626,6 +2044,59 @@ i_writegif_callback(cb, maxbuffer,...) cleanup_gif_opts(&opts); cleanup_quant_opts(&quant); +undef_int +i_writegif_wiol(ig, opts,...) + Imager::IO ig + PREINIT: + i_quantize quant; + i_gif_opts opts; + i_img **imgs = NULL; + int img_count; + int i; + HV *hv; + CODE: + if (items < 3) + croak("Usage: i_writegif_wiol(IO,hashref, images...)"); + if (!SvROK(ST(1)) || ! SvTYPE(SvRV(ST(1)))) + croak("i_writegif_callback: Second argument must be a hash ref"); + hv = (HV *)SvRV(ST(1)); + memset(&quant, 0, sizeof(quant)); + quant.mc_size = 256; + memset(&opts, 0, sizeof(opts)); + handle_quant_opts(&quant, hv); + handle_gif_opts(&opts, hv); + img_count = items - 2; + RETVAL = 1; + if (img_count < 1) { + RETVAL = 0; + } + else { + imgs = mymalloc(sizeof(i_img *) * img_count); + for (i = 0; i < img_count; ++i) { + SV *sv = ST(2+i); + imgs[i] = NULL; + if (SvROK(sv) && sv_derived_from(sv, "Imager::ImgRaw")) { + imgs[i] = (i_img *)SvIV((SV*)SvRV(sv)); + } + else { + RETVAL = 0; + break; + } + } + if (RETVAL) { + RETVAL = i_writegif_wiol(ig, &quant, &opts, imgs, img_count); + } + myfree(imgs); + if (RETVAL) { + copy_colors_back(hv, &quant); + } + } + ST(0) = sv_newmortal(); + if (RETVAL == 0) ST(0)=&PL_sv_undef; + else sv_setiv(ST(0), (IV)RETVAL); + cleanup_gif_opts(&opts); + cleanup_quant_opts(&quant); + void i_readgif(fd) int fd @@ -1674,9 +2145,53 @@ i_readgif(fd) PUSHs(newRV_noinc((SV*)ct)); } +void +i_readgif_wiol(ig) + Imager::IO ig + PREINIT: + int* colour_table; + int colours, q, w; + i_img* rimg; + SV* temp[3]; + AV* ct; + SV* r; + PPCODE: + colour_table = NULL; + colours = 0; + if(GIMME_V == G_ARRAY) { + rimg = i_readgif_wiol(ig,&colour_table,&colours); + } else { + /* don't waste time with colours if they aren't wanted */ + rimg = i_readgif_wiol(ig,NULL,NULL); + } + + if (colour_table == NULL) { + EXTEND(SP,1); + r=sv_newmortal(); + sv_setref_pv(r, "Imager::ImgRaw", (void*)rimg); + PUSHs(r); + } else { + /* the following creates an [[r,g,b], [r, g, b], [r, g, b]...] */ + /* I don't know if I have the reference counts right or not :( */ + /* Neither do I :-) */ + /* No Idea here either */ + ct=newAV(); + av_extend(ct, colours); + for(q=0; q RGB table lookup. -- Finish antialiased filled polygon function. - - advanced font layout (spacing, kerning, alignment) (Artur?) - ways to check if characters are present in a font, eg. checking if diff --git a/bmp.c b/bmp.c index 2b1fd34f..286b3dc8 100644 --- a/bmp.c +++ b/bmp.c @@ -432,6 +432,8 @@ write_1bit_data(io_glue *ig, i_img *im) { myfree(packed); myfree(line); + ig->closecb(ig); + return 1; } @@ -480,6 +482,8 @@ write_4bit_data(io_glue *ig, i_img *im) { myfree(packed); myfree(line); + ig->closecb(ig); + return 1; } @@ -517,6 +521,8 @@ write_8bit_data(io_glue *ig, i_img *im) { } myfree(line); + ig->closecb(ig); + return 1; } @@ -545,6 +551,7 @@ write_24bit_data(io_glue *ig, i_img *im) { return 0; chans = im->channels >= 3 ? bgr_chans : grey_chans; samples = mymalloc(line_size); + memset(samples, 0, line_size); for (y = im->ysize-1; y >= 0; --y) { i_gsamp(im, 0, im->xsize, y, samples, chans, 3); if (ig->writecb(ig, samples, line_size) < 0) { @@ -555,6 +562,8 @@ write_24bit_data(io_glue *ig, i_img *im) { } myfree(samples); + ig->closecb(ig); + return 1; } diff --git a/gif.c b/gif.c index bb981c73..2dc13c74 100644 --- a/gif.c +++ b/gif.c @@ -1,6 +1,11 @@ #include "image.h" #include - +#ifdef _MSCVER +#include +#else +#include +#endif +#include /* XXX: Reading still needs to support reading all those gif properties */ /* @@ -570,9 +575,9 @@ i_img **i_readgif_multi_low(GifFileType *GifFile, int *count) { results = mymalloc(result_alloc * sizeof(i_img *)); } else { - i_img **newresults; + /* myrealloc never fails (it just dies if it can't allocate) */ result_alloc *= 2; - newresults = myrealloc(results, result_alloc * sizeof(i_img *)); + results = myrealloc(results, result_alloc * sizeof(i_img *)); } } results[*count-1] = img; @@ -732,6 +737,49 @@ i_img **i_readgif_multi_low(GifFileType *GifFile, int *count) { return results; } +#if IM_GIFMAJOR >= 4 +/* giflib declares this incorrectly as EgifOpen */ +extern GifFileType *EGifOpen(void *userData, OutputFunc writeFunc); + +static int io_glue_read_cb(GifFileType *gft, GifByteType *buf, int length); +#endif + +/* +=item i_readgif_multi_wiol(ig, int *count) + +=cut +*/ + +i_img ** +i_readgif_multi_wiol(io_glue *ig, int *count) { + io_glue_commit_types(ig); + + if (ig->source.type == FDSEEK || ig->source.type == FDNOSEEK) { + return i_readgif_multi(ig->source.fdseek.fd, count); + } + else { +#if IM_GIFMAJOR >= 4 + GifFileType *GifFile; + + i_clear_error(); + + if ((GifFile = DGifOpen((void *)ig, io_glue_read_cb )) == NULL) { + gif_push_error(); + i_push_error(0, "Cannot create giflib callback object"); + mm_log((1,"i_readgif_multi_wiol: Unable to open callback datasource.\n")); + return NULL; + } + + return i_readgif_multi_low(GifFile, count); +#else + i_clear_error(); + i_push_error(0, "callbacks not supported with giflib3"); + + return NULL; +#endif + } +} + /* =item i_readgif_multi(int fd, int *count) @@ -977,10 +1025,60 @@ i_readgif_callback(i_read_callback_t cb, char *userdata, int **colour_table, int return result; #else + i_clear_error(); + i_push_error(0, "callbacks not supported with giflib3"); + return NULL; #endif } +#if IM_GIFMAJOR >= 4 + +static int +io_glue_read_cb(GifFileType *gft, GifByteType *buf, int length) { + io_glue *ig = (io_glue *)gft->UserData; + + return ig->readcb(ig, buf, length); +} + +#endif + +i_img * +i_readgif_wiol(io_glue *ig, int **color_table, int *colors) { + io_glue_commit_types(ig); + + if (ig->source.type == FDSEEK || ig->source.type == FDNOSEEK) { + int fd = dup(ig->source.fdseek.fd); + if (fd < 0) { + i_push_error(errno, "dup() failed"); + return 0; + } + return i_readgif(fd, color_table, colors); + } + else { +#if IM_GIFMAJOR >= 4 + GifFileType *GifFile; + + i_clear_error(); + + if ((GifFile = DGifOpen((void *)ig, io_glue_read_cb )) == NULL) { + gif_push_error(); + i_push_error(0, "Cannot create giflib callback object"); + mm_log((1,"i_readgif_wiol: Unable to open callback datasource.\n")); + return NULL; + } + + return i_readgif_low(GifFile, color_table, colors); + +#else + i_clear_error(); + i_push_error(0, "callbacks not supported with giflib3"); + + return NULL; +#endif + } +} + /* =item do_write(GifFileType *gf, i_gif_opts *opts, i_img *img, i_palidx *data) @@ -1149,6 +1247,10 @@ static ColorMapObject *make_gif_map(i_quantize *quant, i_gif_opts *opts, /* giflib spews for 1 colour maps, reasonable, I suppose */ if (map_size == 1) map_size = 2; + while (i < map_size) { + colors[i].Red = colors[i].Green = colors[i].Blue = 0; + ++i; + } map = MakeMapObject(map_size, colors); mm_log((1, "XXX map is at %p and colors at %p\n", map, map->Colors)); @@ -1705,8 +1807,6 @@ i_writegif_callback(i_quantize *quant, i_write_callback_t cb, char *userdata, #if IM_GIFMAJOR >= 4 GifFileType *gf; i_gen_write_data *gwd = i_gen_write_data_new(cb, userdata, maxlength); - /* giflib declares this incorrectly as EgifOpen */ - extern GifFileType *EGifOpen(void *userData, OutputFunc writeFunc); int result; i_clear_error(); @@ -1725,10 +1825,74 @@ i_writegif_callback(i_quantize *quant, i_write_callback_t cb, char *userdata, result = i_writegif_low(quant, gf, imgs, count, opts); return free_gen_write_data(gwd, result); #else + i_clear_error(); + i_push_error(0, "callbacks not supported with giflib3"); + return 0; #endif } +#if IM_GIFMAJOR >= 4 + +static int +io_glue_write_cb(GifFileType *gft, const GifByteType *data, int length) { + io_glue *ig = (io_glue *)gft->UserData; + + return ig->writecb(ig, data, length); +} + +#endif + +/* +=item i_writegif_wiol(ig, quant, opts, imgs, count) + +=cut +*/ +undef_int +i_writegif_wiol(io_glue *ig, i_quantize *quant, i_gif_opts *opts, i_img **imgs, + int count) { + io_glue_commit_types(ig); + + if (ig->source.type == FDSEEK || ig->source.type == FDNOSEEK) { + int fd = dup(ig->source.fdseek.fd); + if (fd < 0) { + i_push_error(errno, "dup() failed"); + return 0; + } + /* giflib opens the fd with fdopen(), which is then closed when fclose() + is called - dup it so the caller's fd isn't closed */ + return i_writegif_gen(quant, fd, imgs, count, opts); + } + else { +#if IM_GIFMAJOR >= 4 + GifFileType *GifFile; + int result; + + i_clear_error(); + + gif_set_version(quant, opts); + + if ((GifFile = EGifOpen((void *)ig, io_glue_write_cb )) == NULL) { + gif_push_error(); + i_push_error(0, "Cannot create giflib callback object"); + mm_log((1,"i_writegif_wiol: Unable to open callback datasource.\n")); + return 0; + } + + result = i_writegif_low(quant, GifFile, imgs, count, opts); + + ig->closecb(ig); + + return result; +#else + i_clear_error(); + i_push_error(0, "callbacks not supported with giflib3"); + + return 0; +#endif + } +} + /* =item gif_error_msg(int code) diff --git a/image.h b/image.h index 19bd8e84..dfaf2829 100644 --- a/image.h +++ b/image.h @@ -525,8 +525,11 @@ undef_int i_writejpeg_wiol(i_img *im, io_glue *ig, int qfactor); #ifdef HAVE_LIBTIFF i_img * i_readtiff_wiol(io_glue *ig, int length); +i_img ** i_readtiff_multi_wiol(io_glue *ig, int length, int *count); undef_int i_writetiff_wiol(i_img *im, io_glue *ig); +undef_int i_writetiff_multi_wiol(io_glue *ig, i_img **imgs, int count); undef_int i_writetiff_wiol_faxable(i_img *im, io_glue *ig, int fine); +undef_int i_writetiff_multi_wiol_faxable(io_glue *ig, i_img **imgs, int count, int fine); #endif /* HAVE_LIBTIFF */ @@ -537,17 +540,20 @@ undef_int i_writepng_wiol(i_img *im, io_glue *ig); #ifdef HAVE_LIBGIF i_img *i_readgif(int fd, int **colour_table, int *colours); +i_img *i_readgif_wiol(io_glue *ig, int **colour_table, int *colours); i_img *i_readgif_scalar(char *data, int length, int **colour_table, int *colours); i_img *i_readgif_callback(i_read_callback_t callback, char *userdata, int **colour_table, int *colours); extern i_img **i_readgif_multi(int fd, int *count); extern i_img **i_readgif_multi_scalar(char *data, int length, int *count); extern i_img **i_readgif_multi_callback(i_read_callback_t callback, char *userdata, int *count); +extern i_img **i_readgif_multi_wiol(io_glue *ig, int *count); undef_int i_writegif(i_img *im,int fd,int colors,int pixdev,int fixedlen,i_color fixed[]); undef_int i_writegifmc(i_img *im,int fd,int colors); undef_int i_writegifex(i_img *im,int fd); undef_int i_writegif_gen(i_quantize *quant, int fd, i_img **imgs, int count, i_gif_opts *opts); undef_int i_writegif_callback(i_quantize *quant, i_write_callback_t cb, char *userdata, int maxbuffer, i_img **imgs, int count, i_gif_opts *opts); - +undef_int i_writegif_wiol(io_glue *ig, i_quantize *quant, i_gif_opts *opts, + i_img **imgs, int count); void i_qdist(i_img *im); #endif /* HAVE_LIBGIF */ diff --git a/iolayer.c b/iolayer.c index f7f12c21..efcbf552 100644 --- a/iolayer.c +++ b/iolayer.c @@ -57,8 +57,11 @@ Some of these functions are internal. =cut */ - - +static ssize_t fd_read(io_glue *ig, void *buf, size_t count); +static ssize_t fd_write(io_glue *ig, const void *buf, size_t count); +static off_t fd_seek(io_glue *ig, off_t offset, int whence); +static void fd_close(io_glue *ig); +static ssize_t fd_size(io_glue *ig); /* * Callbacks for sources that cannot seek @@ -95,14 +98,18 @@ static ssize_t realseek_read(io_glue *ig, void *buf, size_t count) { io_ex_rseek *ier = ig->exdata; - int fd = (int)ig->source.cb.p; + void *p = ig->source.cb.p; ssize_t rc = 0; size_t bc = 0; char *cbuf = buf; - IOL_DEB( printf("realseek_read: fd = %d, ier->cpos = %ld, buf = %p, count = %d\n", fd, (long) ier->cpos, buf, count) ); - /* Is this a good idea? Would it be better to handle differently? skip handling? */ - while( count!=bc && (rc = ig->source.cb.readcb(fd,cbuf+bc,count-bc))>0 ) bc+=rc; + IOL_DEB( printf("realseek_read: fd = %d, ier->cpos = %ld, buf = %p, " + "count = %d\n", fd, (long) ier->cpos, buf, count) ); + /* Is this a good idea? Would it be better to handle differently? + skip handling? */ + while( count!=bc && (rc = ig->source.cb.readcb(p,cbuf+bc,count-bc))>0 ) { + bc+=rc; + } ier->cpos += bc; IOL_DEB( printf("realseek_read: rc = %d, bc = %d\n", rc, bc) ); @@ -126,15 +133,19 @@ static ssize_t realseek_write(io_glue *ig, const void *buf, size_t count) { io_ex_rseek *ier = ig->exdata; - int fd = (int)ig->source.cb.p; + void *p = ig->source.cb.p; ssize_t rc = 0; size_t bc = 0; char *cbuf = (char*)buf; - IOL_DEB( printf("realseek_write: fd = %d, ier->cpos = %ld, buf = %p, count = %d\n", fd, (long) ier->cpos, buf, count) ); - /* Is this a good idea? Would it be better to handle differently? skip handling? */ + IOL_DEB( printf("realseek_write: ig = %p, ier->cpos = %ld, buf = %p, " + "count = %d\n", ig, (long) ier->cpos, buf, count) ); - while( count!=bc && (rc = ig->source.cb.writecb(fd,cbuf+bc,count-bc))>0 ) bc+=rc; + /* Is this a good idea? Would it be better to handle differently? + skip handling? */ + while( count!=bc && (rc = ig->source.cb.writecb(p,cbuf+bc,count-bc))>0 ) { + bc+=rc; + } ier->cpos += bc; IOL_DEB( printf("realseek_write: rc = %d, bc = %d\n", rc, bc) ); @@ -145,19 +156,19 @@ realseek_write(io_glue *ig, const void *buf, size_t count) { /* =item realseek_close(ig) -Closes a source that can be seeked on. Not sure if this should be an actual close -or not. Does nothing for now. Should be fixed. +Closes a source that can be seeked on. Not sure if this should be an +actual close or not. Does nothing for now. Should be fixed. ig - data source -=cut -*/ +=cut */ static void realseek_close(io_glue *ig) { mm_log((1, "realseek_close(ig %p)\n", ig)); - /* FIXME: Do stuff here */ + if (ig->source.cb.closecb) + ig->source.cb.closecb(ig->source.cb.p); } @@ -177,38 +188,16 @@ static off_t realseek_seek(io_glue *ig, off_t offset, int whence) { /* io_ex_rseek *ier = ig->exdata; Needed later */ - int fd = (int)ig->source.cb.p; + void *p = ig->source.cb.p; int rc; IOL_DEB( printf("realseek_seek(ig %p, offset %ld, whence %d)\n", ig, (long) offset, whence) ); - rc = lseek(fd, offset, whence); + rc = ig->source.cb.seekcb(p, offset, whence); IOL_DEB( printf("realseek_seek: rc %ld\n", (long) rc) ); return rc; /* FIXME: How about implementing this offset handling stuff? */ } - - - - - - - - - - - - - - - - - - - - - - /* * Callbacks for sources that are a fixed size buffer */ @@ -790,26 +779,36 @@ io_obj_setp_bufchain(io_obj *io) { /* -=item io_obj_setp_cb(io, p, readcb, writecb, seekcb) +=item io_obj_setp_cb2(io, p, readcb, writecb, seekcb, closecb, destroycb) Sets an io_object for reading from a source that uses callbacks io - io object that describes a source - p - pointer to data for callbacks - readcb - read callback to read from source - writecb - write callback to write to source - seekcb - seek callback to seek on source + p - pointer to data for callbacks + readcb - read callback to read from source + writecb - write callback to write to source + seekcb - seek callback to seek on source + closecb - flush any pending data + destroycb - release any extra resources =cut */ void -io_obj_setp_cb(io_obj *io, void *p, readl readcb, writel writecb, seekl seekcb) { - io->cb.type = CBSEEK; - io->cb.p = p; - io->cb.readcb = readcb; - io->cb.writecb = writecb; - io->cb.seekcb = seekcb; +io_obj_setp_cb2(io_obj *io, void *p, readl readcb, writel writecb, seekl seekcb, closel closecb, destroyl destroycb) { + io->cb.type = CBSEEK; + io->cb.p = p; + io->cb.readcb = readcb; + io->cb.writecb = writecb; + io->cb.seekcb = seekcb; + io->cb.closecb = closecb; + io->cb.destroycb = destroycb; +} + +void +io_obj_setp_cb(io_obj *io, void *p, readl readcb, writel writecb, + seekl seekcb) { + io_obj_setp_cb2(io, p, readcb, writecb, seekcb, NULL, NULL); } /* @@ -878,6 +877,15 @@ io_glue_commit_types(io_glue *ig) { ig->closecb = buffer_close; } break; + case FDSEEK: + { + ig->exdata = NULL; + ig->readcb = fd_read; + ig->writecb = fd_write; + ig->seekcb = fd_seek; + ig->closecb = fd_close; + break; + } } } @@ -972,16 +980,32 @@ io_new_fd(int fd) { mm_log((1, "io_new_fd(fd %d)\n", fd)); ig = mymalloc(sizeof(io_glue)); memset(ig, 0, sizeof(*ig)); + ig->source.type = FDSEEK; + ig->source.fdseek.fd = fd; +#if 0 #ifdef _MSC_VER io_obj_setp_cb(&ig->source, (void*)fd, _read, _write, _lseek); #else io_obj_setp_cb(&ig->source, (void*)fd, read, write, lseek); +#endif #endif mm_log((1, "(%p) <- io_new_fd\n", ig)); return ig; } +io_glue *io_new_cb(void *p, readl readcb, writel writecb, seekl seekcb, + closel closecb, destroyl destroycb) { + io_glue *ig; + mm_log((1, "io_new_cb(p %p, readcb %p, writecb %p, seekcb %p, closecb %p, " + "destroycb %p)\n", p, readcb, writecb, seekcb, closecb, destroycb)); + ig = mymalloc(sizeof(io_glue)); + memset(ig, 0, sizeof(ig)); + io_obj_setp_cb2(&ig->source, p, readcb, writecb, seekcb, closecb, destroycb); + mm_log((1, "(%p) <- io_new_cb\n", ig)); + + return ig; +} /* =item io_slurp(ig) @@ -1024,6 +1048,44 @@ io_slurp(io_glue *ig, unsigned char **c) { return rc; } +/* +=item fd_read(ig, buf, count) + +=cut +*/ +static ssize_t fd_read(io_glue *ig, void *buf, size_t count) { +#ifdef _MSC_VER + return _read(ig->source.fdseek.fd, buf, count); +#else + return read(ig->source.fdseek.fd, buf, count); +#endif +} + +static ssize_t fd_write(io_glue *ig, const void *buf, size_t count) { +#ifdef _MSC_VER + return _write(ig->source.fdseek.fd, buf, count); +#else + return write(ig->source.fdseek.fd, buf, count); +#endif +} + +static off_t fd_seek(io_glue *ig, off_t offset, int whence) { +#ifdef _MSC_VER + return _lseek(ig->source.fdseek.fd, offset, whence); +#else + return lseek(ig->source.fdseek.fd, offset, whence); +#endif +} + +static void fd_close(io_glue *ig) { + /* no, we don't close it */ +} + +static ssize_t fd_size(io_glue *ig) { + mm_log((1, "fd_size(ig %p) unimplemented\n", ig)); + + return -1; +} /* =item io_glue_DESTROY(ig) @@ -1050,9 +1112,10 @@ io_glue_DESTROY(io_glue *ig) { } break; case CBSEEK: - default: { io_ex_rseek *ier = ig->exdata; + if (ig->source.cb.destroycb) + ig->source.cb.destroycb(ig->source.cb.p); myfree(ier); } break; @@ -1066,6 +1129,8 @@ io_glue_DESTROY(io_glue *ig) { myfree(ieb); } break; + default: + break; } myfree(ig); } diff --git a/iolayer.h b/iolayer.h index a17d0cec..8d00258b 100644 --- a/iolayer.h +++ b/iolayer.h @@ -47,10 +47,12 @@ typedef void (*closebufp)(void *p); /* Callbacks we get */ -typedef ssize_t(*readl) (int fd, void *buf, size_t count); -typedef ssize_t(*writel)(int fd, const void *buf, size_t count); -typedef off_t (*seekl) (int fd, off_t offset, int whence); -typedef ssize_t(*sizel) (int fd); +typedef ssize_t(*readl) (void *p, void *buf, size_t count); +typedef ssize_t(*writel)(void *p, const void *buf, size_t count); +typedef off_t (*seekl) (void *p, off_t offset, int whence); +typedef void (*closel)(void *p); +typedef void (*destroyl)(void *p); +typedef ssize_t(*sizel) (void *p); extern char *io_type_names[]; @@ -122,6 +124,8 @@ typedef struct { readl readcb; writel writecb; seekl seekcb; + closel closecb; + destroyl destroycb; } io_cb; typedef union { @@ -144,6 +148,7 @@ typedef struct _io_glue { void io_obj_setp_buffer(io_obj *io, char *p, size_t len, closebufp closecb, void *closedata); void io_obj_setp_cb (io_obj *io, void *p, readl readcb, writel writecb, seekl seekcb); +void io_obj_setp_cb2 (io_obj *io, void *p, readl readcb, writel writecb, seekl seekcb, closel closecb, destroyl destroycb); void io_glue_commit_types(io_glue *ig); void io_glue_gettypes (io_glue *ig, int reqmeth); @@ -152,6 +157,7 @@ void io_glue_gettypes (io_glue *ig, int reqmeth); io_glue *io_new_fd(int fd); io_glue *io_new_bufchain(void); io_glue *io_new_buffer(char *data, size_t len, closebufp closecb, void *closedata); +io_glue *io_new_cb(void *p, readl readcb, writel writecb, seekl seekcb, closel closecb, destroyl destroycb); size_t io_slurp(io_glue *ig, unsigned char **c); void io_glue_DESTROY(io_glue *ig); diff --git a/jpeg.c b/jpeg.c index 98c6e961..e3bc7725 100644 --- a/jpeg.c +++ b/jpeg.c @@ -488,6 +488,8 @@ i_writejpeg_wiol(i_img *im, io_glue *ig, int qfactor) { jpeg_destroy_compress(&cinfo); + ig->closecb(ig); + return(1); } diff --git a/lib/Imager/Font.pm b/lib/Imager/Font.pm index 9655cbdf..9807df0d 100644 --- a/lib/Imager/Font.pm +++ b/lib/Imager/Font.pm @@ -475,7 +475,7 @@ Checks if the characters in $text are defined by the font. In a list context returns a list of true or false value corresponding to the characters in $text, true if the character is defined, false if not. In scalar context returns a string of NUL or non-NUL -characters. Supports UTF8. +characters. Supports UTF8 where the font driver supports UTF8. Not all fonts support this method (use $font->can("has_chars") to check.) diff --git a/png.c b/png.c index 76e18d40..65c3c181 100644 --- a/png.c +++ b/png.c @@ -181,6 +181,8 @@ i_writepng_wiol(i_img *im, io_glue *ig) { png_destroy_write_struct(&png_ptr, (png_infopp)NULL); + ig->closecb(ig); + return(1); } diff --git a/pnm.c b/pnm.c index 5073a8c1..aff05f8b 100644 --- a/pnm.c +++ b/pnm.c @@ -498,6 +498,7 @@ i_writeppm_wiol(i_img *im, io_glue *ig) { mm_log((1,"i_writeppm: ppm/pgm is 1 or 3 channel only (current image is %d)\n",im->channels)); return(0); } + ig->closecb(ig); return(1); } diff --git a/raw.c b/raw.c index 928b33af..0d0868b3 100644 --- a/raw.c +++ b/raw.c @@ -155,5 +155,8 @@ i_writeraw_wiol(i_img* im, io_glue *ig) { } } } + + ig->closecb(ig); + return(1); } diff --git a/t/t07iolayer.t b/t/t07iolayer.t index 174466ab..7a87f03c 100644 --- a/t/t07iolayer.t +++ b/t/t07iolayer.t @@ -1,4 +1,4 @@ -BEGIN { $|=1; print "1..7\n"; } +BEGIN { $|=1; print "1..20\n"; } END { print "not ok 1\n" unless $loaded; }; use Imager qw(:all); ++$loaded; @@ -35,7 +35,7 @@ $IO3 = Imager::io_new_buffer($data); $im = Imager::i_readpnm_wiol($IO3, -1); print "ok 3\n"; - +undef $IO3; open(FH, "= 4.0) { + ++$can_write_callback; unless (fork) { # this can SIGSEGV with some versions of giflib open FH, ">testout/t105_anim_cb.gif" or die $!; @@ -188,6 +190,7 @@ if (!i_has_format("gif")) { exit; } if (wait > 0 && $?) { + $can_write_callback = 0; print "not ok 14 # you probably need to patch giflib\n"; print <{IMG}, $cmp4->{IMG}); print "# diff $diff\n"; ok($diff == 0, "written image doesn't match read"); + + my $work; + my $seekpos; + sub io_writer { + my ($what) = @_; + if ($seekpos > length $work) { + $work .= "\0" x ($seekpos - length $work); + } + substr($work, $seekpos, length $what) = $what; + $seekpos += length $what; + + 1; + } + sub io_reader { + my ($size, $maxread) = @_; + #print "io_reader($size, $maxread) pos $seekpos\n"; + my $out = substr($work, $seekpos, $maxread); + $seekpos += length $out; + $out; + } + sub io_reader2 { + my ($size, $maxread) = @_; + #print "io_reader2($size, $maxread) pos $seekpos\n"; + my $out = substr($work, $seekpos, $size); + $seekpos += length $out; + $out; + } + use IO::Seekable; + sub io_seeker { + my ($offset, $whence) = @_; + #print "io_seeker($offset, $whence)\n"; + if ($whence == SEEK_SET) { + $seekpos = $offset; + } + elsif ($whence == SEEK_CUR) { + $seekpos += $offset; + } + else { # SEEK_END + $seekpos = length($work) + $offset; + } + #print "-> $seekpos\n"; + $seekpos; + } + my $did_close; + sub io_closer { + ++$did_close; + } + + # read via cb + $work = $tiffdata; + $seekpos = 0; + my $IO2 = Imager::io_new_cb(undef, \&io_reader, \&io_seeker, undef); + ok($IO2, "new readcb obj"); + my $img5 = i_readtiff_wiol($IO2, -1); + ok($img5, "read via cb"); + ok(i_img_diff($img5, $img) == 0, "read from cb diff"); + + # read via cb2 + $work = $tiffdata; + $seekpos = 0; + my $IO3 = Imager::io_new_cb(undef, \&io_reader2, \&io_seeker, undef); + ok($IO3, "new readcb2 obj"); + my $img6 = i_readtiff_wiol($IO3, -1); + ok($img6, "read via cb2"); + ok(i_img_diff($img6, $img) == 0, "read from cb2 diff"); + + # write via cb + $work = ''; + $seekpos = 0; + my $IO4 = Imager::io_new_cb(\&io_writer, \&io_reader, \&io_seeker, + \&io_closer); + ok($IO4, "new writecb obj"); + ok(i_writetiff_wiol($img, $IO4), "write to cb"); + ok($work eq $odata, "write cb match"); + ok($did_close, "write cb did close"); + open D1, ">d1.tiff" or die; + print D1 $work; + close D1; + open D2, ">d2.tiff" or die; + print D2 $tiffdata; + close D2; + + # write via cb2 + $work = ''; + $seekpos = 0; + $did_close = 0; + my $IO5 = Imager::io_new_cb(\&io_writer, \&io_reader, \&io_seeker, + \&io_closer, 1); + ok($IO5, "new writecb obj 2"); + ok(i_writetiff_wiol($img, $IO5), "write to cb2"); + ok($work eq $odata, "write cb2 match"); + ok($did_close, "write cb2 did close"); + + open D3, ">d3.tiff" or die; + print D3 $work; + close D3; + + # multi-image write/read + my @imgs; + push(@imgs, map $ooim->copy(), 1..3); + for my $i (0..$#imgs) { + $imgs[$i]->addtag(name=>"tiff_pagename", value=>"Page ".($i+1)); + } + my $rc = Imager->write_multi({file=>'testout/t106_multi.tif'}, @imgs); + ok($rc, "writing multiple images to tiff"); + my @out = Imager->read_multi(file=>'testout/t106_multi.tif'); + ok(@out == @imgs, "reading multiple images from tiff"); + @out == @imgs or print "# ",scalar @out, " ",Imager->errstr,"\n"; + for my $i (0..$#imgs) { + ok(i_img_diff($imgs[$i]{IMG}, $out[$i]{IMG}) == 0, + "comparing image $i"); + my ($tag) = $out[$i]->tags(name=>'tiff_pagename'); + ok($tag eq "Page ".($i+1), + "tag doesn't match original image"); + } + + # multi-image fax files + ok(Imager->write_multi({file=>'testout/t106_faxmulti.tiff', class=>'fax'}, + $oofim, $oofim), "write multi fax image"); + @imgs = Imager->read_multi(file=>'testout/t106_faxmulti.tiff'); + ok(@imgs == 2, "reading multipage fax"); + ok(Imager::i_img_diff($imgs[0]{IMG}, $oofim->{IMG}) == 0, + "compare first fax image"); + ok(Imager::i_img_diff($imgs[1]{IMG}, $oofim->{IMG}) == 0, + "compare second fax image"); } sub ok { diff --git a/t/t35ttfont.t b/t/t35ttfont.t index d4fb51f2..af0a9e54 100644 --- a/t/t35ttfont.t +++ b/t/t35ttfont.t @@ -66,6 +66,8 @@ i_tt_text($ttraw,$backgr,100,100,$bgcolor,50.0,'test',4,1); my $ugly = Imager::i_tt_new("./fontfiles/ImUgly.ttf"); i_tt_text($ugly, $backgr,100, 50, $bgcolor, 14, 'g%g', 3, 1); i_tt_text($ugly, $backgr,150, 50, $bgcolor, 14, 'delta', 5, 1); +i_tt_text($ttraw, $backgr, 20, 10, $bgcolor, 14, 'abcdefghijklmnopqrstuvwxyz{|}', 29, 1); +i_tt_text($ttraw, $backgr, 20, 30, $bgcolor, 14, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 26, 1); open(FH,">testout/t35ttfont2.ppm") || die "cannot open testout/t35ttfont.ppm\n"; diff --git a/t/t50basicoo.t b/t/t50basicoo.t index 4da6d08c..61373a1e 100644 --- a/t/t50basicoo.t +++ b/t/t50basicoo.t @@ -1,52 +1,377 @@ -# Before `make install' is performed this script should be runnable with -# `make test'. After `make install' it should work as `perl test.pl' - +#!perl -w ######################### We start with some black magic to print on failure. -# Change 1..1 below to 1..last_test_to_print . -# (It may become useful if the test is moved to ./t subdirectory.) +# this used to do the check for the load of Imager, but I want to be able +# to count tests, which means I need to load Imager first +# since many of the early tests already do this, we don't really need to -BEGIN { $| = 1; print "1..2\n"; } -END {print "not ok 1\n" unless $loaded;} +use strict; use Imager; +use IO::Seekable; -$loaded = 1; +Imager::init("log"=>"testout/t50basicoo.log"); -print "ok 1\n"; +# single image/file types +my @types = qw( jpeg png raw ppm gif tiff bmp tga ); -Imager::init("log"=>"testout/t50basicoo.log"); +# multiple image/file formats +my @mtypes = qw(tiff gif); + +my %hsh=%Imager::formats; + +my $test_num = 0; +my $count; +for my $type (@types) { + $count += 31 if $hsh{$type}; +} +for my $type (@mtypes) { + $count += 7 if $hsh{$type}; +} -%hsh=%Imager::formats; +print "1..$count\n"; print "# avaliable formats:\n"; for(keys %hsh) { print "# $_\n"; } #print Dumper(\%hsh); -$img = Imager->new(); - -@types = qw( jpeg png raw ppm gif tiff ); +my $img = Imager->new(); +my %files; @files{@types} = ({ file => "testout/t101.jpg" }, { file => "testout/t102.png" }, - { file => "testout/t103.raw", xsize=>150, ysize=>150, type=>"raw" }, + { file => "testout/t103.raw", xsize=>150, ysize=>150 + #, type=>"raw" # TODO: was this there for a reason? + }, { file => "testout/t104.ppm" }, { file => "testout/t105.gif" }, - { file => "testout/t106.tiff" }); + { file => "testout/t106.tiff" }, + { file => "testout/t107_24bit.bmp" }, + { file => "testout/t108_24bit.tga" }, ); +my %writeopts = + ( + gif=> { make_colors=>'webmap', translate=>'closest', gifquant=>'gen' }, + ); -for $type (@types) { +for my $type (@types) { next unless $hsh{$type}; + print "# type $type\n"; my %opts = %{$files{$type}}; my @a = map { "$_=>${opts{$_}}" } keys %opts; print "#opening Format: $type, options: @a\n"; - $img->read( %opts ) or die "failed: ",$img->errstr,"\n"; + ok($img->read( %opts ), "reading from file", $img); + #or die "failed: ",$img->errstr,"\n"; + + my %mopts = %opts; + delete $mopts{file}; + + # read from a file handle + my $fh = IO::File->new($opts{file}, "r"); + if (ok($fh, "opening $opts{file}")) { + binmode $fh; + my $fhimg = Imager->new; + my $fhrc = $fhimg->read(fh=>$fh, %mopts); + if (ok(!$fhrc, "check that type is required")) { + ok ($fhimg->errstr =~ /type parameter missing/, "check for no type error"); + } + else { + skip("previous test failed"); + } + if (ok($fhimg->read(fh=>$fh, %mopts, type=>$type), "read from fh")) { + ok(Imager::i_img_diff($img->{IMG}, $fhimg->{IMG}) == 0, + "image comparison after fh read"); + } + else { + skip("no image to compare"); + } + ok($fh->seek(0, SEEK_SET), "seek after read"); + + # read from a fd + my $fdimg = Imager->new; + if (ok($fdimg->read(fd=>fileno($fh), %mopts, type=>$type), + "read from fd")) { + ok(Imager::i_img_diff($img->{IMG}, $fdimg->{IMG}) == 0, + "image comparistion after fd read"); + } + else { + skip("no image to compare"); + } + ok($fh->seek(0, SEEK_SET), "seek after fd read"); + ok($fh->close, "close fh after reads"); + } + else { + skip("couldn't open the damn file: $!", 7); + } + + if ($type ne 'gif' || Imager::i_giflib_version() >= 4) { + # read from a memory buffer + open DATA, "< $opts{file}" + or die "Cannot open $opts{file}: $!"; + binmode DATA; + my $data = do { local $/; }; + close DATA; + my $bimg = Imager->new; + + if (ok($bimg->read(data=>$data, %mopts, type=>$type), "read from buffer", + $img)) { + ok(Imager::i_img_diff($img->{IMG}, $bimg->{IMG}) == 0, + "comparing buffer read image"); + } + else { + skip("nothing to compare"); + } + + # read from callbacks, both with minimum and maximum reads + my $buf = $data; + my $seekpos = 0; + my $reader_min = + sub { + my ($size, $maxread) = @_; + my $out = substr($buf, $seekpos, $size); + $seekpos += length $out; + $out; + }; + my $reader_max = + sub { + my ($size, $maxread) = @_; + my $out = substr($buf, $seekpos, $maxread); + $seekpos += length $out; + $out; + }; + my $seeker = + sub { + my ($offset, $whence) = @_; + #print "io_seeker($offset, $whence)\n"; + if ($whence == SEEK_SET) { + $seekpos = $offset; + } + elsif ($whence == SEEK_CUR) { + $seekpos += $offset; + } + else { # SEEK_END + $seekpos = length($buf) + $offset; + } + #print "-> $seekpos\n"; + $seekpos; + }; + my $cbimg = Imager->new; + ok($cbimg->read(callback=>$reader_min, seekcb=>$seeker, type=>$type, %mopts), + "read from callback min", $cbimg); + ok(Imager::i_img_diff($cbimg->{IMG}, $img->{IMG}) == 0, + "comparing mincb image"); + $seekpos = 0; + ok($cbimg->read(callback=>$reader_max, seekcb=>$seeker, type=>$type, %mopts), + "read from callback max", $cbimg); + ok(Imager::i_img_diff($cbimg->{IMG}, $img->{IMG}) == 0, + "comparing maxcb image"); + } + else { + skip("giflib < 4 doesn't support callbacks", 6); + } } -$img2 = $img->crop(width=>50, height=>50); +for my $type (@types) { + next unless $hsh{$type}; + + print "# write tests for $type\n"; + # test writes + next unless $hsh{$type}; + my $file = "testout/t50out.$type"; + my $wimg = Imager->new; + # if this doesn't work, we're so screwed up anyway + + ok($wimg->read(file=>"testout/t104.ppm"), + "cannot read base file", $wimg); + + # first to a file + print "# writing $type to a file\n"; + my %extraopts; + %extraopts = %{$writeopts{$type}} if $writeopts{$type}; + ok($wimg->write(file=>$file, %extraopts), + "writing $type to a file $file", $wimg); + + print "# writing $type to a FH\n"; + # to a FH + my $fh = IO::File->new($file, "w+") + or die "Could not create $file: $!"; + binmode $fh; + ok($wimg->write(fh=>$fh, %extraopts, type=>$type), + "writing $type to a FH", $wimg); + ok($fh->seek(0, SEEK_END) > 0, + "seek after writing $type to a FH"); + ok(print($fh "SUFFIX\n"), + "write to FH after writing $type"); + ok($fh->close, "closing FH after writing $type"); + + if ($type ne 'gif' || Imager::i_giflib_version() >= 4) { + if (ok(open(DATA, "< $file"), "opening data source")) { + binmode DATA; + my $data = do { local $/; }; + close DATA; + + # writing to a buffer + print "# writing $type to a buffer\n"; + my $buf = ''; + ok($wimg->write(data=>\$buf, %extraopts, type=>$type), + "writing $type to a buffer", $wimg); + $buf .= "SUFFIX\n"; + open DATA, "> testout/t50_buf.$type" + or die "Cannot create $type buffer file: $!"; + binmode DATA; + print DATA $buf; + close DATA; + ok($data eq $buf, "comparing file data to buffer"); + + $buf = ''; + my $seekpos = 0; + my $did_close; + my $writer = + sub { + my ($what) = @_; + if ($seekpos > length $buf) { + $buf .= "\0" x ($seekpos - length $buf); + } + substr($buf, $seekpos, length $what) = $what; + $seekpos += length $what; + $did_close = 0; # the close must be last + 1; + }; + my $reader_min = + sub { + my ($size, $maxread) = @_; + my $out = substr($buf, $seekpos, $size); + $seekpos += length $out; + $out; + }; + my $reader_max = + sub { + my ($size, $maxread) = @_; + my $out = substr($buf, $seekpos, $maxread); + $seekpos += length $out; + $out; + }; + use IO::Seekable; + my $seeker = + sub { + my ($offset, $whence) = @_; + #print "io_seeker($offset, $whence)\n"; + if ($whence == SEEK_SET) { + $seekpos = $offset; + } + elsif ($whence == SEEK_CUR) { + $seekpos += $offset; + } + else { # SEEK_END + $seekpos = length($buf) + $offset; + } + #print "-> $seekpos\n"; + $seekpos; + }; + + my $closer = sub { ++$did_close; }; + + print "# writing $type via callbacks (mb=1)\n"; + ok($wimg->write(writecb=>$writer, seekcb=>$seeker, closecb=>$closer, + readcb=>$reader_min, + %extraopts, type=>$type, maxbuffer=>1), + "writing $type to callback (mb=1)", $wimg); + + ok($did_close, "checking closecb called"); + $buf .= "SUFFIX\n"; + ok($data eq $buf, "comparing callback output to file data"); + print "# writing $type via callbacks (no mb)\n"; + $buf = ''; + $did_close = 0; + $seekpos = 0; + # we don't use the closecb here - used to make sure we don't get + # a warning/error on an attempt to call an undef close sub + ok($wimg->write(writecb=>$writer, seekcb=>$seeker, readcb=>$reader_min, + %extraopts, type=>$type), + "writing $type to callback (no mb)", $wimg); + $buf .= "SUFFIX\n"; + ok($data eq $buf, "comparing callback output to file data"); + } + else { + skip("couldn't open data source", 7); + } + } + else { + skip("giflib < 4 doesn't support callbacks", 8); + } +} + +my $img2 = $img->crop(width=>50, height=>50); $img2 -> write(file=> 'testout/t50.ppm', type=>'pnm'); undef($img); +# multi image/file tests +print "# multi-image write tests\n"; +for my $type (@mtypes) { + next unless $hsh{$type}; + print "# $type\n"; + + my $file = "testout/t50out.$type"; + my $wimg = Imager->new; + + # if this doesn't work, we're so screwed up anyway + ok($wimg->read(file=>"testout/t50out.$type"), + "reading base file", $wimg); + + ok(my $wimg2 = $wimg->copy, "copying base image", $wimg); + ok($wimg2->flip(dir=>'h'), "flipping base image", $wimg2); + + my @out = ($wimg, $wimg2); + my %extraopts; + %extraopts = %{$writeopts{$type}} if $writeopts{$type}; + ok(Imager->write_multi({ file=>"testout/t50_multi.$type", %extraopts }, + @out), + "writing multiple to a file", "Imager"); + + # make sure we get the same back + my @images = Imager->read_multi(file=>"testout/t50_multi.$type"); + if (ok(@images == @out, "checking read image count")) { + for my $i (0 .. $#out) { + my $diff = Imager::i_img_diff($out[$i]{IMG}, $images[$i]{IMG}); + print "# diff $diff\n"; + ok($diff == 0, "comparing image $i"); + } + } + else { + skip("wrong number of images read", 2); + } +} + + Imager::malloc_state(); -print "ok 2\n"; +#print "ok 2\n"; + +sub ok { + my ($ok, $msg, $img) = @_; + + ++$test_num; + if ($ok) { + print "ok $test_num # $msg\n"; + } + else { + my $err; + $err = $img->errstr if $img; + # VMS (if we ever support it) wants the whole line in one print + my $line = "not ok $test_num # line ".(caller)[2].": $msg"; + $line .= ": $err" if $err; + print $line, "\n"; + } + + $ok; +} + +sub skip { + my ($why, $skipcount) = @_; + + $skipcount ||= 1; + for (1.. $skipcount) { + ++$test_num; + print "ok $test_num # skipped $why\n"; + } +} diff --git a/tga.c b/tga.c index 46f39003..361f732e 100644 --- a/tga.c +++ b/tga.c @@ -868,6 +868,9 @@ i_writetga_wiol(i_img *img, io_glue *ig, int wierdpack, int compress, char *idst myfree(buf); myfree(vals); } + + ig->closecb(ig); + return 1; } diff --git a/tiff.c b/tiff.c index 1485a199..8f72bc27 100644 --- a/tiff.c +++ b/tiff.c @@ -86,26 +86,12 @@ comp_seek(thandle_t h, toff_t o, int w) { return (toff_t) ig->seekcb(ig, o, w); } - -/* -=item i_readtiff_wiol(ig, length) - -Retrieve an image and stores in the iolayer object. Returns NULL on fatal error. - - ig - io_glue object - length - maximum length to read from data source, before closing it - -=cut -*/ - -i_img* -i_readtiff_wiol(io_glue *ig, int length) { +static i_img *read_one_tiff(TIFF *tif) { i_img *im; uint32 width, height; uint16 channels; uint32* raster = NULL; int tiled, error; - TIFF* tif; float xres, yres; uint16 resunit; int gotXres, gotYres; @@ -113,37 +99,9 @@ i_readtiff_wiol(io_glue *ig, int length) { uint16 bits_per_sample; int i; int ch; - TIFFErrorHandler old_handler; - - i_clear_error(); - old_handler = TIFFSetErrorHandler(error_handler); error = 0; - /* Add code to get the filename info from the iolayer */ - /* Also add code to check for mmapped code */ - - io_glue_commit_types(ig); - mm_log((1, "i_readtiff_wiol(ig %p, length %d)\n", ig, length)); - - tif = TIFFClientOpen("(Iolayer)", - "rm", - (thandle_t) ig, - (TIFFReadWriteProc) ig->readcb, - (TIFFReadWriteProc) ig->writecb, - (TIFFSeekProc) comp_seek, - (TIFFCloseProc) ig->closecb, - (TIFFSizeProc) ig->sizecb, - (TIFFMapFileProc) NULL, - (TIFFUnmapFileProc) NULL); - - if (!tif) { - mm_log((1, "i_readtiff_wiol: Unable to open tif file\n")); - i_push_error(0, "opening file"); - TIFFSetErrorHandler(old_handler); - return NULL; - } - TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &width); TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &height); TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLESPERPIXEL, &channels); @@ -207,17 +165,13 @@ i_readtiff_wiol(io_glue *ig, int length) { if (!TIFFGetField(tif, TIFFTAG_COLORMAP, maps+0, maps+1, maps+2)) { i_push_error(0, "Cannot get colormap for paletted image"); - TIFFSetErrorHandler(old_handler); i_img_destroy(im); - TIFFClose(tif); return NULL; } buffer = (unsigned char *)_TIFFmalloc(width+2); if (!buffer) { i_push_error(0, "out of memory"); - TIFFSetErrorHandler(old_handler); i_img_destroy(im); - TIFFClose(tif); return NULL; } row = 0; @@ -270,8 +224,6 @@ i_readtiff_wiol(io_glue *ig, int length) { if (!raster) { i_img_destroy(im); i_push_error(0, "No space for raster buffer"); - TIFFSetErrorHandler(old_handler); - TIFFClose(tif); return NULL; } @@ -309,8 +261,6 @@ i_readtiff_wiol(io_glue *ig, int length) { if (!raster) { i_img_destroy(im); i_push_error(0, "No space for raster buffer"); - TIFFSetErrorHandler(old_handler); - TIFFClose(tif); return NULL; } @@ -346,29 +296,227 @@ i_readtiff_wiol(io_glue *ig, int length) { } if (raster) _TIFFfree( raster ); + + return im; +} + +/* +=item i_readtiff_wiol(im, ig) + +=cut +*/ +i_img* +i_readtiff_wiol(io_glue *ig, int length) { + TIFF* tif; + TIFFErrorHandler old_handler; + i_img *im; + + i_clear_error(); + old_handler = TIFFSetErrorHandler(error_handler); + + /* Add code to get the filename info from the iolayer */ + /* Also add code to check for mmapped code */ + + io_glue_commit_types(ig); + mm_log((1, "i_readtiff_wiol(ig %p, length %d)\n", ig, length)); + + tif = TIFFClientOpen("(Iolayer)", + "rm", + (thandle_t) ig, + (TIFFReadWriteProc) ig->readcb, + (TIFFReadWriteProc) ig->writecb, + (TIFFSeekProc) comp_seek, + (TIFFCloseProc) ig->closecb, + (TIFFSizeProc) ig->sizecb, + (TIFFMapFileProc) NULL, + (TIFFUnmapFileProc) NULL); + + if (!tif) { + mm_log((1, "i_readtiff_wiol: Unable to open tif file\n")); + i_push_error(0, "opening file"); + TIFFSetErrorHandler(old_handler); + return NULL; + } + + im = read_one_tiff(tif); + if (TIFFLastDirectory(tif)) mm_log((1, "Last directory of tiff file\n")); TIFFSetErrorHandler(old_handler); TIFFClose(tif); return im; } +/* +=item i_readtiff_multi_wiol(ig, length, *count) +Reads multiple images from a TIFF. -/* -=item i_writetif_wiol(im, ig) +=cut +*/ +i_img** +i_readtiff_multi_wiol(io_glue *ig, int length, int *count) { + TIFF* tif; + TIFFErrorHandler old_handler; + i_img **results = NULL; + int result_alloc = 0; + int dirnum = 0; -Stores an image in the iolayer object. + i_clear_error(); + old_handler = TIFFSetErrorHandler(error_handler); - im - image object to write out - ig - io_object that defines source to write to + /* Add code to get the filename info from the iolayer */ + /* Also add code to check for mmapped code */ -=cut -*/ + io_glue_commit_types(ig); + mm_log((1, "i_readtiff_wiol(ig %p, length %d)\n", ig, length)); + + tif = TIFFClientOpen("(Iolayer)", + "rm", + (thandle_t) ig, + (TIFFReadWriteProc) ig->readcb, + (TIFFReadWriteProc) ig->writecb, + (TIFFSeekProc) comp_seek, + (TIFFCloseProc) ig->closecb, + (TIFFSizeProc) ig->sizecb, + (TIFFMapFileProc) NULL, + (TIFFUnmapFileProc) NULL); + + if (!tif) { + mm_log((1, "i_readtiff_wiol: Unable to open tif file\n")); + i_push_error(0, "opening file"); + TIFFSetErrorHandler(old_handler); + return NULL; + } -/* FIXME: Add an options array in here soonish */ + *count = 0; + do { + i_img *im = read_one_tiff(tif); + if (!im) + break; + if (++*count > result_alloc) { + if (result_alloc == 0) { + result_alloc = 5; + results = mymalloc(result_alloc * sizeof(i_img *)); + } + else { + i_img **newresults; + result_alloc *= 2; + newresults = myrealloc(results, result_alloc * sizeof(i_img *)); + } + } + results[*count-1] = im; + } while (TIFFSetDirectory(tif, ++dirnum)); + + TIFFSetErrorHandler(old_handler); + TIFFClose(tif); + return results; +} undef_int -i_writetiff_wiol(i_img *im, io_glue *ig) { +i_writetiff_low_faxable(TIFF *tif, i_img *im, int fine) { + uint32 width, height; + unsigned char *linebuf = NULL; + uint32 y; + int rc; + uint32 x; + int luma_mask; + uint32 rowsperstrip; + float vres = fine ? 196 : 98; + int luma_chan; + + width = im->xsize; + height = im->ysize; + + switch (im->channels) { + case 1: + case 2: + luma_chan = 0; + break; + case 3: + case 4: + luma_chan = 1; + break; + default: + /* This means a colorspace we don't handle yet */ + mm_log((1, "i_writetiff_wiol_faxable: don't handle %d channel images.\n", im->channels)); + return 0; + } + + /* Add code to get the filename info from the iolayer */ + /* Also add code to check for mmapped code */ + + + mm_log((1, "i_writetiff_wiol_faxable: width=%d, height=%d, channels=%d\n", width, height, im->channels)); + + if (!TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, width) ) + { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField width=%d failed\n", width)); return 0; } + if (!TIFFSetField(tif, TIFFTAG_IMAGELENGTH, height) ) + { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField length=%d failed\n", height)); return 0; } + if (!TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 1)) + { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField samplesperpixel=1 failed\n")); return 0; } + if (!TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT)) + { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField Orientation=topleft\n")); return 0; } + if (!TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 1) ) + { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField bitpersample=1\n")); return 0; } + if (!TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG)) + { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField planarconfig\n")); return 0; } + if (!TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK)) + { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField photometric=%d\n", PHOTOMETRIC_MINISBLACK)); return 0; } + if (!TIFFSetField(tif, TIFFTAG_COMPRESSION, 3)) + { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField compression=3\n")); return 0; } + + linebuf = (unsigned char *)_TIFFmalloc( TIFFScanlineSize(tif) ); + + if (!TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif, -1))) { + mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField rowsperstrip=-1\n")); return 0; } + + TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rowsperstrip); + TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rc); + + mm_log((1, "i_writetiff_wiol_faxable: TIFFGetField rowsperstrip=%d\n", rowsperstrip)); + mm_log((1, "i_writetiff_wiol_faxable: TIFFGetField scanlinesize=%d\n", TIFFScanlineSize(tif) )); + mm_log((1, "i_writetiff_wiol_faxable: TIFFGetField planarconfig=%d == %d\n", rc, PLANARCONFIG_CONTIG)); + + if (!TIFFSetField(tif, TIFFTAG_XRESOLUTION, (float)204)) + { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField Xresolution=204\n")); return 0; } + if (!TIFFSetField(tif, TIFFTAG_YRESOLUTION, vres)) + { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField Yresolution=196\n")); return 0; } + if (!TIFFSetField(tif, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH)) { + mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField ResolutionUnit=%d\n", RESUNIT_INCH)); return 0; + } + + if (!save_tiff_tags(tif, im)) { + return 0; + } + + for (y=0; y8) bits=8; + i_gsamp(im, x, x+8, y, luma, &luma_chan, 1); + for(bitpos=0;bitpos=128)?bitval:0); + bitval >>= 1; + } + linebufpos++; + } + if (TIFFWriteScanline(tif, linebuf, y, 0) < 0) { + mm_log((1, "i_writetiff_wiol_faxable: TIFFWriteScanline failed.\n")); + break; + } + } + if (linebuf) _TIFFfree(linebuf); + + return 1; +} + +undef_int +i_writetiff_low(TIFF *tif, i_img *im) { uint32 width, height; uint16 channels; uint16 predictor = 0; @@ -383,17 +531,12 @@ i_writetiff_wiol(i_img *im, io_glue *ig) { tsize_t linebytes; int ch, ci, rc; uint32 x; - TIFF* tif; int got_xres, got_yres, got_aspectonly, aspect_only, resunit; double xres, yres; uint16 bitspersample = 8; uint16 samplesperpixel; uint16 *colors = NULL; - char *cc = mymalloc( 123 ); - myfree(cc); - - width = im->xsize; height = im->ysize; channels = im->channels; @@ -418,37 +561,35 @@ i_writetiff_wiol(i_img *im, io_glue *ig) { /* Add code to get the filename info from the iolayer */ /* Also add code to check for mmapped code */ - io_glue_commit_types(ig); - mm_log((1, "i_writetiff_wiol(im 0x%p, ig 0x%p)\n", im, ig)); + /*io_glue_commit_types(ig);*/ + /*mm_log((1, "i_writetiff_wiol(im 0x%p, ig 0x%p)\n", im, ig));*/ - /* FIXME: Enable the mmap interface */ + mm_log((1, "i_writetiff_low: width=%d, height=%d, channels=%d\n", width, height, channels)); - tif = TIFFClientOpen("No name", - "wm", - (thandle_t) ig, - (TIFFReadWriteProc) ig->readcb, - (TIFFReadWriteProc) ig->writecb, - (TIFFSeekProc) comp_seek, - (TIFFCloseProc) ig->closecb, - (TIFFSizeProc) ig->sizecb, - (TIFFMapFileProc) NULL, - (TIFFUnmapFileProc) NULL); - - - - if (!tif) { - mm_log((1, "i_writetiff_wiol: Unable to open tif file for writing\n")); - return 0; + if (!TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, width) ) { + mm_log((1, "i_writetiff_wiol: TIFFSetField width=%d failed\n", width)); + return 0; + } + if (!TIFFSetField(tif, TIFFTAG_IMAGELENGTH, height) ) { + mm_log((1, "i_writetiff_wiol: TIFFSetField length=%d failed\n", height)); + return 0; + } + if (!TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT)) { + mm_log((1, "i_writetiff_wiol: TIFFSetField Orientation=topleft\n")); + return 0; + } + if (!TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG)) { + mm_log((1, "i_writetiff_wiol: TIFFSetField planarconfig\n")); + return 0; + } + if (!TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, photometric)) { + mm_log((1, "i_writetiff_wiol: TIFFSetField photometric=%d\n", photometric)); + return 0; + } + if (!TIFFSetField(tif, TIFFTAG_COMPRESSION, compression)) { + mm_log((1, "i_writetiff_wiol: TIFFSetField compression=%d\n", compression)); + return 0; } - - mm_log((1, "i_writetiff_wiol: width=%d, height=%d, channels=%d\n", width, height, channels)); - - if (!TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, width) ) { mm_log((1, "i_writetiff_wiol: TIFFSetField width=%d failed\n", width)); return 0; } - if (!TIFFSetField(tif, TIFFTAG_IMAGELENGTH, height) ) { mm_log((1, "i_writetiff_wiol: TIFFSetField length=%d failed\n", height)); return 0; } - if (!TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT)) { mm_log((1, "i_writetiff_wiol: TIFFSetField Orientation=topleft\n")); return 0; } - if (!TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG)) { mm_log((1, "i_writetiff_wiol: TIFFSetField planarconfig\n")); return 0; } - if (!TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, photometric)) { mm_log((1, "i_writetiff_wiol: TIFFSetField photometric=%d\n", photometric)); return 0; } - if (!TIFFSetField(tif, TIFFTAG_COMPRESSION, compression)) { mm_log((1, "i_writetiff_wiol: TIFFSetField compression=%d\n", compression)); return 0; } samplesperpixel = channels; if (photometric == PHOTOMETRIC_PALETTE) { uint16 *out[3]; @@ -503,8 +644,14 @@ i_writetiff_wiol(i_img *im, io_glue *ig) { switch (compression) { case COMPRESSION_JPEG: mm_log((1, "i_writetiff_wiol: jpeg compression\n")); - if (!TIFFSetField(tif, TIFFTAG_JPEGQUALITY, quality) ) { mm_log((1, "i_writetiff_wiol: TIFFSetField jpegquality=%d\n", quality)); return 0; } - if (!TIFFSetField(tif, TIFFTAG_JPEGCOLORMODE, jpegcolormode)) { mm_log((1, "i_writetiff_wiol: TIFFSetField jpegcolormode=%d\n", jpegcolormode)); return 0; } + if (!TIFFSetField(tif, TIFFTAG_JPEGQUALITY, quality) ) { + mm_log((1, "i_writetiff_wiol: TIFFSetField jpegquality=%d\n", quality)); + return 0; + } + if (!TIFFSetField(tif, TIFFTAG_JPEGCOLORMODE, jpegcolormode)) { + mm_log((1, "i_writetiff_wiol: TIFFSetField jpegcolormode=%d\n", jpegcolormode)); + return 0; + } break; case COMPRESSION_LZW: mm_log((1, "i_writetiff_wiol: lzw compression\n")); @@ -512,7 +659,10 @@ i_writetiff_wiol(i_img *im, io_glue *ig) { case COMPRESSION_DEFLATE: mm_log((1, "i_writetiff_wiol: deflate compression\n")); if (predictor != 0) - if (!TIFFSetField(tif, TIFFTAG_PREDICTOR, predictor)) { mm_log((1, "i_writetiff_wiol: TIFFSetField predictor=%d\n", predictor)); return 0; } + if (!TIFFSetField(tif, TIFFTAG_PREDICTOR, predictor)) { + mm_log((1, "i_writetiff_wiol: TIFFSetField predictor=%d\n", predictor)); + return 0; + } break; case COMPRESSION_PACKBITS: mm_log((1, "i_writetiff_wiol: packbits compression\n")); @@ -565,27 +715,20 @@ i_writetiff_wiol(i_img *im, io_glue *ig) { } } if (!TIFFSetField(tif, TIFFTAG_XRESOLUTION, (float)xres)) { - TIFFClose(tif); - i_img_destroy(im); i_push_error(0, "cannot set TIFFTAG_XRESOLUTION tag"); return 0; } if (!TIFFSetField(tif, TIFFTAG_YRESOLUTION, (float)yres)) { - TIFFClose(tif); - i_img_destroy(im); i_push_error(0, "cannot set TIFFTAG_YRESOLUTION tag"); return 0; } if (!TIFFSetField(tif, TIFFTAG_RESOLUTIONUNIT, (uint16)resunit)) { - TIFFClose(tif); - i_img_destroy(im); i_push_error(0, "cannot set TIFFTAG_RESOLUTIONUNIT tag"); return 0; } } if (!save_tiff_tags(tif, im)) { - TIFFClose(tif); return 0; } @@ -596,7 +739,9 @@ i_writetiff_wiol(i_img *im, io_glue *ig) { pack_4bit_hl(linebuf, width); if (TIFFWriteScanline(tif, linebuf, y, 0) < 0) { mm_log((1, "i_writetiff_wiol: TIFFWriteScanline failed.\n")); - break; + if (linebuf) _TIFFfree(linebuf); + if (colors) _TIFFfree(colors); + return 0; } } } @@ -610,67 +755,97 @@ i_writetiff_wiol(i_img *im, io_glue *ig) { } if (TIFFWriteScanline(tif, linebuf, y, 0) < 0) { mm_log((1, "i_writetiff_wiol: TIFFWriteScanline failed.\n")); - break; + if (linebuf) _TIFFfree(linebuf); + if (colors) _TIFFfree(colors); + return 0; } } } - (void) TIFFClose(tif); if (linebuf) _TIFFfree(linebuf); if (colors) _TIFFfree(colors); return 1; } /* -=item i_writetiff_wiol_faxable(i_img *, io_glue *) +=item i_writetiff_multi_wiol(ig, imgs, count, fine_mode) -Stores an image in the iolayer object in faxable tiff format. +Stores an image in the iolayer object. - im - image object to write out ig - io_object that defines source to write to + imgs,count - the images to write -Note, this may be rewritten to use to simply be a call to a -lower-level function that gives more options for writing tiff at some -point. - -=cut +=cut */ undef_int -i_writetiff_wiol_faxable(i_img *im, io_glue *ig, int fine) { - uint32 width, height; - unsigned char *linebuf = NULL; - uint32 y; - int rc; - uint32 x; +i_writetiff_multi_wiol(io_glue *ig, i_img **imgs, int count) { TIFF* tif; - int luma_mask; - uint32 rowsperstrip; - float vres = fine ? 196 : 98; - int luma_chan; + int i; - width = im->xsize; - height = im->ysize; + io_glue_commit_types(ig); + i_clear_error(); + mm_log((1, "i_writetiff_multi_wiol(ig 0x%p, imgs 0x%p, count %d)\n", + ig, imgs, count)); - switch (im->channels) { - case 1: - case 2: - luma_chan = 0; - break; - case 3: - case 4: - luma_chan = 1; - break; - default: - /* This means a colorspace we don't handle yet */ - mm_log((1, "i_writetiff_wiol_faxable: don't handle %d channel images.\n", im->channels)); + /* FIXME: Enable the mmap interface */ + + tif = TIFFClientOpen("No name", + "wm", + (thandle_t) ig, + (TIFFReadWriteProc) ig->readcb, + (TIFFReadWriteProc) ig->writecb, + (TIFFSeekProc) comp_seek, + (TIFFCloseProc) ig->closecb, + (TIFFSizeProc) ig->sizecb, + (TIFFMapFileProc) NULL, + (TIFFUnmapFileProc) NULL); + + + + if (!tif) { + mm_log((1, "i_writetiff_mulit_wiol: Unable to open tif file for writing\n")); return 0; } - /* Add code to get the filename info from the iolayer */ - /* Also add code to check for mmapped code */ + for (i = 0; i < count; ++i) { + if (!i_writetiff_low(tif, imgs[i])) { + TIFFClose(tif); + return 0; + } + + if (!TIFFWriteDirectory(tif)) { + i_push_error(0, "Cannot write TIFF directory"); + TIFFClose(tif); + return 0; + } + } + + (void) TIFFClose(tif); + return 1; +} + +/* +=item i_writetiff_multi_wiol_faxable(ig, imgs, count, fine_mode) + +Stores an image in the iolayer object. + + ig - io_object that defines source to write to + imgs,count - the images to write + fine_mode - select fine or normal mode fax images + +=cut +*/ + + +undef_int +i_writetiff_multi_wiol_faxable(io_glue *ig, i_img **imgs, int count, int fine) { + TIFF* tif; + int i; io_glue_commit_types(ig); - mm_log((1, "i_writetiff_wiol_faxable(im 0x%p, ig 0x%p)\n", im, ig)); + i_clear_error(); + mm_log((1, "i_writetiff_multi_wiol(ig 0x%p, imgs 0x%p, count %d)\n", + ig, imgs, count)); /* FIXME: Enable the mmap interface */ @@ -684,99 +859,152 @@ i_writetiff_wiol_faxable(i_img *im, io_glue *ig, int fine) { (TIFFSizeProc) ig->sizecb, (TIFFMapFileProc) NULL, (TIFFUnmapFileProc) NULL); + + if (!tif) { - mm_log((1, "i_writetiff_wiol_faxable: Unable to open tif file for writing\n")); + mm_log((1, "i_writetiff_mulit_wiol: Unable to open tif file for writing\n")); return 0; } - mm_log((1, "i_writetiff_wiol_faxable: width=%d, height=%d, channels=%d\n", width, height, im->channels)); - - if (!TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, width) ) - { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField width=%d failed\n", width)); return 0; } - if (!TIFFSetField(tif, TIFFTAG_IMAGELENGTH, height) ) - { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField length=%d failed\n", height)); return 0; } - if (!TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 1)) - { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField samplesperpixel=1 failed\n")); return 0; } - if (!TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT)) - { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField Orientation=topleft\n")); return 0; } - if (!TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 1) ) - { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField bitpersample=1\n")); return 0; } - if (!TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG)) - { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField planarconfig\n")); return 0; } - if (!TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK)) - { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField photometric=%d\n", PHOTOMETRIC_MINISBLACK)); return 0; } - if (!TIFFSetField(tif, TIFFTAG_COMPRESSION, 3)) - { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField compression=3\n")); return 0; } + for (i = 0; i < count; ++i) { + if (!i_writetiff_low_faxable(tif, imgs[i], fine)) { + TIFFClose(tif); + return 0; + } - linebuf = (unsigned char *)_TIFFmalloc( TIFFScanlineSize(tif) ); - - if (!TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif, -1))) { - mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField rowsperstrip=-1\n")); return 0; } + if (!TIFFWriteDirectory(tif)) { + i_push_error(0, "Cannot write TIFF directory"); + TIFFClose(tif); + return 0; + } + } - TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rowsperstrip); - TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rc); + (void) TIFFClose(tif); + return 1; +} - mm_log((1, "i_writetiff_wiol_faxable: TIFFGetField rowsperstrip=%d\n", rowsperstrip)); - mm_log((1, "i_writetiff_wiol_faxable: TIFFGetField scanlinesize=%d\n", TIFFScanlineSize(tif) )); - mm_log((1, "i_writetiff_wiol_faxable: TIFFGetField planarconfig=%d == %d\n", rc, PLANARCONFIG_CONTIG)); +/* +=item i_writetiff_wiol(im, ig) - if (!TIFFSetField(tif, TIFFTAG_XRESOLUTION, (float)204)) - { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField Xresolution=204\n")); return 0; } - if (!TIFFSetField(tif, TIFFTAG_YRESOLUTION, vres)) - { mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField Yresolution=196\n")); return 0; } - if (!TIFFSetField(tif, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH)) { - mm_log((1, "i_writetiff_wiol_faxable: TIFFSetField ResolutionUnit=%d\n", RESUNIT_INCH)); return 0; +Stores an image in the iolayer object. + + im - image object to write out + ig - io_object that defines source to write to + +=cut +*/ +undef_int +i_writetiff_wiol(i_img *img, io_glue *ig) { + TIFF* tif; + int i; + + io_glue_commit_types(ig); + i_clear_error(); + mm_log((1, "i_writetiff_wiol(img %p, ig 0x%p)\n", img, ig)); + + /* FIXME: Enable the mmap interface */ + + tif = TIFFClientOpen("No name", + "wm", + (thandle_t) ig, + (TIFFReadWriteProc) ig->readcb, + (TIFFReadWriteProc) ig->writecb, + (TIFFSeekProc) comp_seek, + (TIFFCloseProc) ig->closecb, + (TIFFSizeProc) ig->sizecb, + (TIFFMapFileProc) NULL, + (TIFFUnmapFileProc) NULL); + + + + if (!tif) { + mm_log((1, "i_writetiff_wiol: Unable to open tif file for writing\n")); + return 0; } - if (!save_tiff_tags(tif, im)) { + if (!i_writetiff_low(tif, img)) { TIFFClose(tif); return 0; } - for (y=0; y8) bits=8; - i_gsamp(im, x, x+8, y, luma, &luma_chan, 1); - for(bitpos=0;bitpos=128)?bitval:0); - bitval >>= 1; - } - linebufpos++; - } - if (TIFFWriteScanline(tif, linebuf, y, 0) < 0) { - mm_log((1, "i_writetiff_wiol_faxable: TIFFWriteScanline failed.\n")); - break; - } + (void) TIFFClose(tif); + return 1; +} + + + +/* +=item i_writetiff_wiol_faxable(i_img *, io_glue *) + +Stores an image in the iolayer object in faxable tiff format. + + im - image object to write out + ig - io_object that defines source to write to + +Note, this may be rewritten to use to simply be a call to a +lower-level function that gives more options for writing tiff at some +point. + +=cut +*/ + +undef_int +i_writetiff_wiol_faxable(i_img *im, io_glue *ig, int fine) { + TIFF* tif; + int i; + + io_glue_commit_types(ig); + i_clear_error(); + mm_log((1, "i_writetiff_wiol(img %p, ig 0x%p)\n", im, ig)); + + /* FIXME: Enable the mmap interface */ + + tif = TIFFClientOpen("No name", + "wm", + (thandle_t) ig, + (TIFFReadWriteProc) ig->readcb, + (TIFFReadWriteProc) ig->writecb, + (TIFFSeekProc) comp_seek, + (TIFFCloseProc) ig->closecb, + (TIFFSizeProc) ig->sizecb, + (TIFFMapFileProc) NULL, + (TIFFUnmapFileProc) NULL); + + + + if (!tif) { + mm_log((1, "i_writetiff_wiol: Unable to open tif file for writing\n")); + return 0; } + + if (!i_writetiff_low_faxable(tif, im, fine)) { + TIFFClose(tif); + return 0; + } + (void) TIFFClose(tif); - if (linebuf) _TIFFfree(linebuf); return 1; } static int save_tiff_tags(TIFF *tif, i_img *im) { int i; - + for (i = 0; i < text_tag_count; ++i) { int entry; if (i_tags_find(&im->tags, text_tag_names[i].name, 0, &entry)) { if (!TIFFSetField(tif, text_tag_names[i].tag, - im->tags.tags[entry].data)) { - i_push_errorf(0, "cannot save %s to TIFF", text_tag_names[i].name); - return 0; + im->tags.tags[entry].data)) { + i_push_errorf(0, "cannot save %s to TIFF", text_tag_names[i].name); + return 0; } } } - + return 1; } + /* =item expand_4bit_hl(buf, count)