Imager release history. Older releases can be found in Changes.old
-Imager 0.86
-===========
+Imager 0.85_01 - unreleased
+==============
- add simple tests for the Imager::Test test_image generators
+ - io_glue I/O buffering re-work:
+
+ - reorganize io_glue to do it's own buffering by default
+
+ - the unbuffered functions are available as i_io_raw_read() (or
+ raw_read() from perl) etc but are not recommended for typical
+ use.
+
+ - use the new i_io_peekn() when checking for file magic to avoid
+ seek, allowing Imager to detect the file type and read the file
+ from an unseekable stream (for formats that don't use random
+ access)
+
+ - added several new I/O layer API functions.
+
+ - fix the TGA performance problem, most noticably on Win32
+ https://rt.cpan.org/Ticket/Display.html?id=70037
+
+ - TIFF now uses wrapper functions of the correct types to avoid casts
+ https://rt.cpan.org/Ticket/Display.html?id=69912
+
+ - the callback IO object did its own buffering, controlled by the
+ maxbuffer parameter supplied to the read() and write() methods.
+ This buffering has been removed, to avoid redundancy with the
+ common io_glue buffering. This also avoids a bug in that code
+ which could rarely pass a zero length to the read callback and
+ then panic about the result.
+
+ - the callback IO object now tests the result of calling the close
+ callback, which should return true for success.
+
+ - the PNM reader did its own buffering. This buffering has been
+ removed to avoid redundancy with the common io_glue buffering.
+
+ - previously the read handlers for fd and callback I/O layers would
+ call the underlying interface (POSIX read or your supplied callback)
+ until it filled the buffer. It now only makes one call.
+
+ - added public constructors for I/O layer objects (see Imager::IO)
+
+ - all core file handlers now use the i_io_foo() wrappers to gain
+ access to buffered I/O rather than calling the I/O layer
+ callbacks directly.
+
+ - all core file handlers now check for error on close.
+
+ - Backward compatibility: if you hava custom file handlers, you can
+ use i_io_write() etc since they're available as macros in older
+ versions of Imager.
+
+ - eliminate the final remnants of io_glue_commit_types().
+
+ - bump IMAGER_API_VERSION, since the above may break assumptions.
+
+ - removed the long unused i_gen_reader() and i_gen_writer() utility
+ functions.
+
Imager 0.85 - 29 Aug 2011
===========
io_glue_read_cb(GifFileType *gft, GifByteType *buf, int length) {
io_glue *ig = (io_glue *)gft->UserData;
- return ig->readcb(ig, buf, length);
+ return i_io_read(ig, buf, length);
}
i_img *
io_glue_write_cb(GifFileType *gft, const GifByteType *data, int length) {
io_glue *ig = (io_glue *)gft->UserData;
- return ig->writecb(ig, data, length);
+ return i_io_write(ig, data, length);
}
result = i_writegif_low(quant, GifFile, imgs, count);
- ig->closecb(ig);
+ if (i_io_close(ig))
+ return 0;
return result;
}
$|=1;
use Test::More;
use Imager qw(:all);
-use Imager::Test qw(is_color3 test_image test_image_raw);
+use Imager::Test qw(is_color3 test_image test_image_raw test_image_mono);
use Imager::File::GIF;
use Carp 'confess';
init_log("testout/t105gif.log",1);
-plan tests => 144;
+plan tests => 146;
my $green=i_color_new(0,255,0,255);
my $blue=i_color_new(0,0,255,255);
ok($im, "read image with zero-length extension");
}
+
+{ # check close failures are handled correctly
+ my $im = test_image_mono();
+ my $fail_close = sub {
+ Imager::i_push_error(0, "synthetic close failure");
+ return 0;
+ };
+ ok(!$im->write(type => "gif", callback => sub { 1 },
+ closecb => $fail_close),
+ "check failing close fails");
+ like($im->errstr, qr/synthetic close failure/,
+ "check error message");
+}
+
+
sub test_readgif_cb {
my ($size) = @_;
+#include "imext.h"
#include "msicon.h"
#include <string.h>
#include <stdlib.h>
#ifndef IMAGER_MSICON_H_
#define IMAGER_MSICON_H_
-#include "iolayer.h"
+#include "iolayert.h"
typedef struct ico_reader_tag ico_reader_t;
#!perl -w
use strict;
-use Test::More tests => 100;
-use Imager::Test qw(is_image);
+use Test::More tests => 102;
+use Imager::Test qw(is_image test_image);
BEGIN { use_ok('Imager::File::ICO'); }
$vs->box(filled => 1, color => '#333366');
is_image($im, $vs, "check we got the right colors");
}
+
+
+{ # check close failures are handled correctly
+ my $im = test_image();
+ my $fail_close = sub {
+ Imager::i_push_error(0, "synthetic close failure");
+ return 0;
+ };
+ ok(!$im->write(type => "ico", callback => sub { 1 },
+ closecb => $fail_close),
+ "check failing close fails");
+ like($im->errstr, qr/synthetic close failure/,
+ "check error message");
+}
# low level write failure tests for each entry point (fail on write)
{
my $im = Imager->new(xsize => 10, ysize => 10);
- ok(!$im->write(callback => \&write_failure, maxbuffer => 1, type=>'ico'),
+ my $io = Imager::io_new_cb(\&write_failure, undef, undef, undef, 1);
+ $io->set_buffered(0);
+ ok(!$im->write(io => $io, type=>'ico'),
"low level write failure (ico)");
is($im->errstr, "Write failure: synthetic error", "check message");
}
{
my $im = Imager->new(xsize => 10, ysize => 10);
- ok(!$im->write(callback => \&write_failure, maxbuffer => 1, type=>'cur'),
+ my $io = Imager::io_new_cb(\&write_failure, undef, undef, undef, 1);
+ $io->set_buffered(0);
+ ok(!$im->write(io => $io, type=>'cur'),
"low level write failure (cur)");
is($im->errstr, "Write failure: synthetic error", "check message");
}
{
my $im = Imager->new(xsize => 10, ysize => 10);
- ok(!Imager->write_multi({ callback => \&write_failure, maxbuffer => 1, type=>'ico' }, $im, $im),
+ my $io = Imager::io_new_cb(\&write_failure, undef, undef, undef, 1);
+ $io->set_buffered(0);
+ ok(!Imager->write_multi({ io => $io, type=>'ico' }, $im, $im),
"low level write_multi failure (ico)");
is(Imager->errstr, "Write failure: synthetic error", "check message");
Imager->_set_error('');
{
my $im = Imager->new(xsize => 10, ysize => 10);
- ok(!Imager->write_multi({ callback => \&write_failure, maxbuffer => 1, type=>'cur' }, $im, $im),
+ my $io = Imager::io_new_cb(\&write_failure, undef, undef, undef, 1);
+ $io->set_buffered(0);
+ ok(!Imager->write_multi({ io => $io, type=>'cur' }, $im, $im),
"low level write_multi failure (cur)");
is(Imager->errstr, "Write failure: synthetic error", "check message");
Imager->_set_error('');
{
my $im = Imager->new(xsize => 10, ysize => 10);
- ok(!$im->write(type => 'ico', callback => limited_write(6), maxbuffer => 1),
+ ok(!$im->write(type => 'ico', io => limited_write_io(6)),
"second write (resource) should fail (ico)");
is($im->errstr, "Write failure: limit reached", "check message");
$im->_set_error('');
- ok(!$im->write(type => 'cur', callback => limited_write(6), maxbuffer => 1),
+ ok(!$im->write(type => 'cur', io => limited_write_io(6)),
"second (resource) write should fail (cur)");
is($im->errstr, "Write failure: limit reached", "check message");
$im->_set_error('');
- ok(!$im->write(type => 'ico', callback => limited_write(22), maxbuffer => 1),
+ ok(!$im->write(type => 'ico', io => limited_write_io(22)),
"third write (bmi) should fail (32-bit)");
is($im->errstr, "Write failure: limit reached", "check message");
$im->_set_error('');
- ok(!$im->write(type => 'ico', callback => limited_write(62), maxbuffer => 1),
+ ok(!$im->write(type => 'ico', io => limited_write_io(62)),
"fourth write (data) should fail (32-bit)");
is($im->errstr, "Write failure: limit reached", "check message");
$im->_set_error('');
- ok(!$im->write(type => 'ico', callback => limited_write(462), maxbuffer => 1),
+ ok(!$im->write(type => 'ico', io => limited_write_io(462)),
"mask write should fail (32-bit)");
is($im->errstr, "Write failure: limit reached", "check message");
}
$im->addcolors(colors => [ $red, $blue ]);
$im->box(filled => 1, color => $red, ymax => 5);
$im->box(filled => 1, color => $blue, ymin => 6);
- ok(!$im->write(type => 'ico', callback => limited_write(22), maxbuffer => 1),
+ ok(!$im->write(type => 'ico', io => limited_write_io(22)),
"third write (bmi) should fail (1-bit)");
is($im->errstr, "Write failure: limit reached", "check message");
- ok(!$im->write(type => 'ico', callback => limited_write(66), maxbuffer => 1),
+ ok(!$im->write(type => 'ico', io => limited_write_io(66)),
"fourth write (palette) should fail (1-bit)");
is($im->errstr, "Write failure: limit reached", "check message");
- ok(!$im->write(type => 'ico', callback => limited_write(74), maxbuffer => 1),
+ ok(!$im->write(type => 'ico', io => limited_write_io(74)),
"fifth write (image) should fail (1-bit)");
is($im->errstr, "Write failure: limit reached", "check message");
my $data;
$im->addcolors(colors => [ ($red, $blue) x 8 ]);
$im->box(filled => 1, color => $red, ymax => 5);
$im->box(filled => 1, color => $blue, ymin => 6);
- ok(!$im->write(type => 'ico', callback => limited_write(22), maxbuffer => 1),
+ ok(!$im->write(type => 'ico', io => limited_write_io(22)),
"third write (bmi) should fail (4-bit)");
is($im->errstr, "Write failure: limit reached", "check message");
- ok(!$im->write(type => 'ico', callback => limited_write(66), maxbuffer => 1),
+ ok(!$im->write(type => 'ico', io => limited_write_io(66)),
"fourth write (palette) should fail (4-bit)");
is($im->errstr, "Write failure: limit reached", "check message");
- ok(!$im->write(type => 'ico', callback => limited_write(130), maxbuffer => 1),
+ ok(!$im->write(type => 'ico', io => limited_write_io(130)),
"fifth write (image) should fail (4-bit)");
is($im->errstr, "Write failure: limit reached", "check message");
my $data;
$im->addcolors(colors => [ ($red, $blue) x 9 ]);
$im->box(filled => 1, color => $red, ymax => 5);
$im->box(filled => 1, color => $blue, ymin => 6);
- ok(!$im->write(type => 'ico', callback => limited_write(22), maxbuffer => 1),
+ ok(!$im->write(type => 'ico', io => limited_write_io(22)),
"third write (bmi) should fail (8-bit)");
is($im->errstr, "Write failure: limit reached", "check message");
- ok(!$im->write(type => 'ico', callback => limited_write(62), maxbuffer => 1),
+ ok(!$im->write(type => 'ico', io => limited_write_io(62)),
"fourth write (palette) should fail (8-bit)");
is($im->errstr, "Write failure: limit reached", "check message");
- ok(!$im->write(type => 'ico', callback => limited_write(62 + 1024), maxbuffer => 1),
+ ok(!$im->write(type => 'ico', io => limited_write_io(62 + 1024)),
"fifth write (image) should fail (8-bit)");
is($im->errstr, "Write failure: limit reached", "check message");
- ok(!$im->write(type => 'ico', callback => limited_write(62 + 1024 + 10), maxbuffer => 1),
+ ok(!$im->write(type => 'ico', io => limited_write_io(62 + 1024 + 10)),
"sixth write (zeroes) should fail (8-bit)");
is($im->errstr, "Write failure: limit reached", "check message");
my $data;
return;
}
+sub limited_write_io {
+ my ($limit) = @_;
+
+ my $io = Imager::io_new_cb(limited_write($limit), undef, undef, undef, 1);
+ $io->set_buffered(0);
+
+ return $io;
+}
+
sub limited_write {
my ($limit) = @_;
sub _get_writer_io {
my ($self, $input) = @_;
+ my $buffered = exists $input->{buffered} ? $input->{buffered} : 1;
+
+ my $io;
+ my @extras;
if ($input->{io}) {
- return $input->{io};
+ $io = $input->{io};
}
elsif ($input->{fd}) {
- return io_new_fd($input->{fd});
+ $io = io_new_fd($input->{fd});
}
elsif ($input->{fh}) {
my $fd = fileno($input->{fh});
# flush anything that's buffered, and make sure anything else is flushed
$| = 1;
select($oldfh);
- return io_new_fd($fd);
+ $io = io_new_fd($fd);
}
elsif ($input->{file}) {
my $fh = new IO::File($input->{file},"w+");
return;
}
binmode($fh) or die;
- return (io_new_fd(fileno($fh)), $fh);
+ $io = io_new_fd(fileno($fh));
+ push @extras, $fh;
}
elsif ($input->{data}) {
- return io_new_bufchain();
+ $io = 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});
+ if ($input->{maxbuffer} && $input->{maxbuffer} == 1) {
+ $buffered = 0;
}
+ $io = 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;
}
+
+ unless ($buffered) {
+ $io->set_buffered(0);
+ }
+
+ return ($io, @extras);
}
# Read an image from file
compose() - L<Imager::Transformations/compose()> - compose one image
over another.
-convert() - L<Imager::Transformations/"Color transformations"> -
-transform the color space
+convert() - L<Imager::Transformations/convert()> - transform the color
+space
copy() - L<Imager::Transformations/copy()> - make a duplicate of an
image
deltag() - L<Imager::ImageTypes/deltag()> - delete image tags
-difference() - L<Imager::Filters/"Image Difference"> - produce a
-difference images from two input images.
+difference() - L<Imager::Filters/difference()> - produce a difference
+images from two input images.
-errstr() - L</"Basic Overview"> - the error from the last failed
-operation.
+errstr() - L</errstr()> - the error from the last failed operation.
-filter() - L<Imager::Filters> - image filtering
+filter() - L<Imager::Filters/filter()> - image filtering
findcolor() - L<Imager::ImageTypes/findcolor()> - search the image
palette, if it has one
preload() - L<Imager::Files/preload()>
-read() - L<Imager::Files> - read a single image from an image file
+read() - L<Imager::Files/read()> - read a single image from an image file
-read_multi() - L<Imager::Files> - read multiple images from an image
+read_multi() - L<Imager::Files/read_multi()> - read multiple images from an image
file
read_types() - L<Imager::Files/read_types()> - list image types Imager
virtual() - L<Imager::ImageTypes/virtual()> - whether the image has it's own
data
-write() - L<Imager::Files> - write an image to a file
+write() - L<Imager::Files/write()> - write an image to a file
-write_multi() - L<Imager::Files> - write multiple image to an image
+write_multi() - L<Imager::Files/write_multi()> - write multiple image to an image
file.
write_types() - L<Imager::Files/read_types()> - list image types Imager
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) {
- dTHX;
- 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 : -1;
-}
-
-static ssize_t call_reader(struct cbdata *cbd, void *buf, size_t size,
- size_t maxread) {
+static ssize_t
+call_reader(struct cbdata *cbd, void *buf, size_t size,
+ size_t maxread) {
dTHX;
int count;
int result;
if (SvOK(data)) {
STRLEN len;
- char *ptr = SvPV(data, len);
+ char *ptr = SvPVbyte(data, len);
if (len > maxread)
- croak("Too much data returned in reader callback");
+ croak("Too much data returned in reader callback (wanted %d, got %d, expected %d)",
+ (int)size, (int)len, (int)maxread);
memcpy(buf, ptr, len);
result = len;
return result;
}
-static ssize_t write_flush(struct cbdata *cbd) {
- dTHX;
- ssize_t result;
-
- if (cbd->used) {
- result = call_writer(cbd, cbd->buffer, cbd->used);
- cbd->used = 0;
- return result;
- }
- else {
- return 1; /* success of some sort */
- }
-}
-
-static off_t io_seeker(void *p, off_t offset, int whence) {
+static off_t
+io_seeker(void *p, off_t offset, int whence) {
dTHX;
struct cbdata *cbd = p;
int count;
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);
return result;
}
-static ssize_t io_writer(void *p, void const *data, size_t size) {
+static ssize_t
+io_writer(void *p, void const *data, size_t size) {
dTHX;
struct cbdata *cbd = p;
+ I32 count;
+ SV *sv;
+ dSP;
+ bool success;
- /* 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 */
- 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) {
- int write_res = write_flush(cbd);
- if (write_res <= 0) {
- return write_res;
- }
- 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);
+ if (!SvOK(cbd->writecb))
+ return -1;
+
+ ENTER;
+ SAVETMPS;
+ EXTEND(SP, 1);
+ PUSHMARK(SP);
+ PUSHs(sv_2mortal(newSVpv((char *)data, 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 : -1;
}
static ssize_t
io_reader(void *p, void *data, size_t size) {
- dTHX;
struct cbdata *cbd = p;
- ssize_t total;
- char *out = data; /* so we can do pointer arithmetic */
-
- /* printf("io_reader(%p, %p, %d)\n", p, data, size); */
- 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 = 0;
- 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 = i_min(size, cbd->used);
- memcpy(out, cbd->buffer, copy_size);
- cbd->where += copy_size;
- out += copy_size;
- total += copy_size;
- size -= copy_size;
- }
- if (did_read < 0)
- return -1;
- }
- 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;
- }
- if (did_read < 0)
- return -1;
- }
- return total;
+ return call_reader(cbd, data, size, size);
}
static int io_closer(void *p) {
dTHX;
struct cbdata *cbd = p;
-
- if (cbd->writing && cbd->used > 0) {
- if (write_flush(cbd) < 0)
- return -1;
- cbd->writing = 0;
- }
+ int success = 1;
if (SvOK(cbd->closecb)) {
dSP;
+ I32 count;
+ SV *sv;
ENTER;
SAVETMPS;
PUSHMARK(SP);
PUTBACK;
- perl_call_sv(cbd->closecb, G_VOID);
+ count = perl_call_sv(cbd->closecb, G_SCALAR);
SPAGAIN;
+
+ sv = POPs;
+ success = SvTRUE(sv);
+
PUTBACK;
FREETMPS;
LEAVE;
}
- return 0;
+ return success ? 0 : -1;
}
static void io_destroyer(void *p) {
myfree(cbd);
}
+static i_io_glue_t *
+do_io_new_buffer(pTHX_ SV *data_sv) {
+ const char *data;
+ STRLEN length;
+
+ data = SvPVbyte(data_sv, length);
+ SvREFCNT_inc(data_sv);
+ return io_new_buffer(data, length, my_SvREFCNT_dec, data_sv);
+}
+
+static i_io_glue_t *
+do_io_new_cb(pTHX_ SV *writecb, SV *readcb, SV *seekcb, SV *closecb) {
+ struct cbdata *cbd;
+
+ cbd = mymalloc(sizeof(struct cbdata));
+ cbd->writecb = newSVsv(writecb);
+ cbd->readcb = newSVsv(readcb);
+ cbd->seekcb = newSVsv(seekcb);
+ cbd->closecb = newSVsv(closecb);
+
+ return io_new_cb(cbd, io_reader, io_writer, io_seeker, io_closer,
+ io_destroyer);
+}
+
struct value_name {
char *name;
int value;
#define i_img_epsilonf() (DBL_EPSILON * 4)
+/* avoid some xsubpp strangeness */
+#define NEWLINE '\n'
+
MODULE = Imager PACKAGE = Imager::Color PREFIX = ICL_
Imager::Color
Imager::IO
-io_new_buffer(data)
- char *data
- PREINIT:
- size_t length;
+io_new_buffer(data_sv)
+ SV *data_sv
CODE:
- SvPV(ST(0), length);
- SvREFCNT_inc(ST(0));
- RETVAL = io_new_buffer(data, length, my_SvREFCNT_dec, ST(0));
+ RETVAL = do_io_new_buffer(aTHX_ data_sv);
OUTPUT:
RETVAL
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);
+ RETVAL = do_io_new_cb(aTHX_ writecb, readcb, seekcb, closecb);
OUTPUT:
RETVAL
-void
+SV *
io_slurp(ig)
Imager::IO ig
PREINIT:
unsigned char* data;
size_t tlength;
- PPCODE:
+ CODE:
data = NULL;
tlength = io_slurp(ig, &data);
- EXTEND(SP,1);
- PUSHs(sv_2mortal(newSVpv((char *)data,tlength)));
+ RETVAL = newSVpv((char *)data,tlength);
myfree(data);
+ OUTPUT:
+ RETVAL
undef_int
PUSHs(sv_2mortal(newSVuv(bytes)));
}
+MODULE = Imager PACKAGE = Imager::IO PREFIX = io_
+
+Imager::IO
+io_new_fd(class, fd)
+ int fd
+ CODE:
+ RETVAL = io_new_fd(fd);
+ OUTPUT:
+ RETVAL
+
+Imager::IO
+io_new_buffer(class, data_sv)
+ SV *data_sv
+ CODE:
+ RETVAL = do_io_new_buffer(aTHX_ data_sv);
+ OUTPUT:
+ RETVAL
+
+Imager::IO
+io_new_cb(class, writecb, readcb, seekcb, closecb)
+ SV *writecb;
+ SV *readcb;
+ SV *seekcb;
+ SV *closecb;
+ CODE:
+ RETVAL = do_io_new_cb(aTHX_ writecb, readcb, seekcb, closecb);
+ OUTPUT:
+ RETVAL
+
+Imager::IO
+io_new_bufchain(class)
+ CODE:
+ RETVAL = io_new_bufchain();
+ OUTPUT:
+ RETVAL
+
+SV *
+io_slurp(class, ig)
+ Imager::IO ig
+ PREINIT:
+ unsigned char* data;
+ size_t tlength;
+ CODE:
+ data = NULL;
+ tlength = io_slurp(ig, &data);
+ RETVAL = newSVpv((char *)data,tlength);
+ myfree(data);
+ OUTPUT:
+ RETVAL
+
MODULE = Imager PACKAGE = Imager::IO PREFIX = i_io_
-int
-i_io_write(ig, data_sv)
+IV
+i_io_raw_write(ig, data_sv)
Imager::IO ig
SV *data_sv
PREINIT:
}
#endif
data = SvPV(data_sv, size);
- RETVAL = i_io_write(ig, data, size);
+ RETVAL = i_io_raw_write(ig, data, size);
OUTPUT:
RETVAL
void
-i_io_read(ig, buffer_sv, size)
+i_io_raw_read(ig, buffer_sv, size)
Imager::IO ig
SV *buffer_sv
IV size
ssize_t result;
PPCODE:
if (size <= 0)
- croak("size negative in call to i_io_read()");
+ croak("size negative in call to i_io_raw_read()");
/* prevent an undefined value warning if they supplied an
undef buffer.
Orginally conditional on !SvOK(), but this will prevent the
sv_utf8_downgrade(buffer_sv, FALSE);
#endif
buffer = SvGROW(buffer_sv, size+1);
- result = i_io_read(ig, buffer, size);
+ result = i_io_raw_read(ig, buffer, size);
if (result >= 0) {
SvCUR_set(buffer_sv, result);
*SvEND(buffer_sv) = '\0';
SvSETMAGIC(ST(1));
void
-i_io_read2(ig, size)
+i_io_raw_read2(ig, size)
Imager::IO ig
IV size
PREINIT:
croak("size negative in call to i_io_read2()");
buffer_sv = newSV(size);
buffer = SvGROW(buffer_sv, size+1);
- result = i_io_read(ig, buffer, size);
+ result = i_io_raw_read(ig, buffer, size);
if (result >= 0) {
SvCUR_set(buffer_sv, result);
*SvEND(buffer_sv) = '\0';
}
off_t
-i_io_seek(ig, position, whence)
+i_io_raw_seek(ig, position, whence)
Imager::IO ig
off_t position
int whence
int
-i_io_close(ig)
+i_io_raw_close(ig)
Imager::IO ig
void
OUTPUT:
RETVAL
+int
+i_io_getc(ig)
+ Imager::IO ig
+
+int
+i_io_putc(ig, c)
+ Imager::IO ig
+ int c
+
+int
+i_io_close(ig)
+ Imager::IO ig
+
+int
+i_io_flush(ig)
+ Imager::IO ig
+
+int
+i_io_peekc(ig)
+ Imager::IO ig
+
+int
+i_io_seek(ig, off, whence)
+ Imager::IO ig
+ off_t off
+ int whence
+
+void
+i_io_peekn(ig, size)
+ Imager::IO ig
+ STRLEN size
+ PREINIT:
+ SV *buffer_sv;
+ void *buffer;
+ ssize_t result;
+ PPCODE:
+ buffer_sv = newSV(size+1);
+ buffer = SvGROW(buffer_sv, size+1);
+ result = i_io_peekn(ig, buffer, size);
+ if (result >= 0) {
+ SvCUR_set(buffer_sv, result);
+ *SvEND(buffer_sv) = '\0';
+ SvPOK_only(buffer_sv);
+ EXTEND(SP, 1);
+ PUSHs(sv_2mortal(buffer_sv));
+ }
+ else {
+ /* discard it */
+ SvREFCNT_dec(buffer_sv);
+ }
+
+void
+i_io_read(ig, buffer_sv, size)
+ Imager::IO ig
+ SV *buffer_sv
+ IV size
+ PREINIT:
+ void *buffer;
+ ssize_t result;
+ PPCODE:
+ if (size <= 0)
+ croak("size negative in call to i_io_read()");
+ /* prevent an undefined value warning if they supplied an
+ undef buffer.
+ Orginally conditional on !SvOK(), but this will prevent the
+ downgrade from croaking */
+ sv_setpvn(buffer_sv, "", 0);
+#ifdef SvUTF8
+ if (SvUTF8(buffer_sv))
+ sv_utf8_downgrade(buffer_sv, FALSE);
+#endif
+ buffer = SvGROW(buffer_sv, size+1);
+ result = i_io_read(ig, buffer, size);
+ if (result >= 0) {
+ SvCUR_set(buffer_sv, result);
+ *SvEND(buffer_sv) = '\0';
+ SvPOK_only(buffer_sv);
+ EXTEND(SP, 1);
+ PUSHs(sv_2mortal(newSViv(result)));
+ }
+ ST(1) = buffer_sv;
+ SvSETMAGIC(ST(1));
+
+void
+i_io_read2(ig, size)
+ Imager::IO ig
+ STRLEN size
+ PREINIT:
+ SV *buffer_sv;
+ void *buffer;
+ ssize_t result;
+ PPCODE:
+ if (size == 0)
+ croak("size zero in call to read2()");
+ buffer_sv = newSV(size);
+ buffer = SvGROW(buffer_sv, size+1);
+ result = i_io_read(ig, buffer, size);
+ if (result > 0) {
+ SvCUR_set(buffer_sv, result);
+ *SvEND(buffer_sv) = '\0';
+ SvPOK_only(buffer_sv);
+ EXTEND(SP, 1);
+ PUSHs(sv_2mortal(buffer_sv));
+ }
+ else {
+ /* discard it */
+ SvREFCNT_dec(buffer_sv);
+ }
+
+void
+i_io_gets(ig, size = 8192, eol = NEWLINE)
+ Imager::IO ig
+ STRLEN size
+ int eol
+ PREINIT:
+ SV *buffer_sv;
+ void *buffer;
+ ssize_t result;
+ PPCODE:
+ if (size < 2)
+ croak("size too small in call to gets()");
+ buffer_sv = sv_2mortal(newSV(size+1));
+ buffer = SvPVX(buffer_sv);
+ result = i_io_gets(ig, buffer, size+1, eol);
+ if (result > 0) {
+ SvCUR_set(buffer_sv, result);
+ *SvEND(buffer_sv) = '\0';
+ SvPOK_only(buffer_sv);
+ EXTEND(SP, 1);
+ PUSHs(buffer_sv);
+ }
+
+IV
+i_io_write(ig, data_sv)
+ Imager::IO ig
+ SV *data_sv
+ PREINIT:
+ void *data;
+ STRLEN size;
+ CODE:
+#ifdef SvUTF8
+ if (SvUTF8(data_sv)) {
+ data_sv = sv_2mortal(newSVsv(data_sv));
+ /* yes, we want this to croak() if the SV can't be downgraded */
+ sv_utf8_downgrade(data_sv, FALSE);
+ }
+#endif
+ data = SvPV(data_sv, size);
+ RETVAL = i_io_write(ig, data, size);
+ OUTPUT:
+ RETVAL
+
+void
+i_io_dump(ig, flags = I_IO_DUMP_DEFAULT)
+ Imager::IO ig
+ int flags
+
+bool
+i_io_set_buffered(ig, flag = 1)
+ Imager::IO ig
+ int flag
+
+bool
+i_io_is_buffered(ig)
+ Imager::IO ig
+
+bool
+i_io_eof(ig)
+ Imager::IO ig
+
+bool
+i_io_error(ig)
+ Imager::IO ig
+
MODULE = Imager PACKAGE = Imager
PROTOTYPES: ENABLE
BOOT:
PERL_SET_GLOBAL_CALLBACKS;
- PERL_PL_SET_GLOBAL_CALLBACKS;
\ No newline at end of file
+ PERL_PL_SET_GLOBAL_CALLBACKS;
mm_log((1,"wiol_fill_input_buffer(cinfo %p)\n", cinfo));
- nbytes = src->data->readcb(src->data, src->buffer, JPGS);
+ nbytes = i_io_read(src->data, src->buffer, JPGS);
if (nbytes <= 0) { /* Insert a fake EOI marker */
src->pub.next_input_byte = fake_eoi;
*/
mm_log((1,"wiol_empty_output_buffer(cinfo %p)\n", cinfo));
- rc = dest->data->writecb(dest->data, dest->buffer, JPGS);
+ rc = i_io_write(dest->data, dest->buffer, JPGS);
if (rc != JPGS) { /* XXX: Should raise some jpeg error */
myfree(dest->buffer);
/* yes, this needs to flush the buffer */
/* needs error handling */
- if (dest->data->writecb(dest->data, dest->buffer, nbytes) != nbytes) {
+ if (i_io_write(dest->data, dest->buffer, nbytes) != nbytes) {
myfree(dest->buffer);
ERREXIT(cinfo, JERR_FILE_WRITE);
}
-
+
if (dest != NULL) myfree(dest->buffer);
}
jpeg_destroy_compress(&cinfo);
- ig->closecb(ig);
+ if (i_io_close(ig))
+ return 0;
return(1);
}
$Imager::formats{"jpeg"}
or plan skip_all => "no jpeg support";
-plan tests => 101;
+plan tests => 103;
my $green=i_color_new(0,255,0,255);
my $blue=i_color_new(0,0,255,255);
# 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');
+my $io = Imager::io_new_fd(fileno(FH));
+$io->set_buffered(0);
+ok(!$imoo->write(io => $io, type=>'jpeg'), 'failure handling');
close FH;
print "# ",$imoo->errstr,"\n";
}
my $data;
ok($im->write(data => \$data, type=>'jpeg', jpegquality => 100),
- "write big file to ensure wiol_empty_output_buffer is called");
+ "write big file to ensure wiol_empty_output_buffer is called")
+ or print "# ", $im->errstr, "\n";
# code coverage - write failure path in wiol_empty_output_buffer
ok(!$im->write(callback => sub { return },
is_image($rdprog, $norm, "prog vs norm should be the same image");
}
+
+{ # check close failures are handled correctly
+ my $im = test_image();
+ my $fail_close = sub {
+ Imager::i_push_error(0, "synthetic close failure");
+ return 0;
+ };
+ ok(!$im->write(type => "jpeg", callback => sub { 1 },
+ closecb => $fail_close),
+ "check failing close fails");
+ like($im->errstr, qr/synthetic close failure/,
+ "check error message");
+}
if ($coverage) {
if ($Config{gccversion}) {
- push @ARGV, 'OPTIMIZE=-ftest-coverage -fprofile-arcs';
- #$opts{dynamic_lib} = { OTHERLDFLAGS => '-ftest-coverage -fprofile-arcs' };
+ push @ARGV, 'OPTIMIZE=-ftest-coverage -fprofile-arcs -g';
+ $opts{dynamic_lib} = { OTHERLDFLAGS => '-ftest-coverage -fprofile-arcs' };
}
else {
die "Don't know the coverage C flags for your compiler\n";
static void
wiol_read_data(png_structp png_ptr, png_bytep data, png_size_t length) {
io_glue *ig = png_get_io_ptr(png_ptr);
- int rc = ig->readcb(ig, data, length);
+ int rc = i_io_read(ig, data, length);
if (rc != length) png_error(png_ptr, "Read overflow error on an iolayer source.");
}
wiol_write_data(png_structp png_ptr, png_bytep data, png_size_t length) {
int rc;
io_glue *ig = png_get_io_ptr(png_ptr);
- rc = ig->writecb(ig, data, length);
+ rc = i_io_write(ig, data, length);
if (rc != length) png_error(png_ptr, "Write error on an iolayer source.");
}
png_destroy_write_struct(&png_ptr, &info_ptr);
- ig->closecb(ig);
+ if (i_io_close(ig))
+ return 0;
return(1);
}
use strict;
use Imager qw(:all);
use Test::More;
-use Imager::Test qw(test_image_raw);
+use Imager::Test qw(test_image_raw test_image);
-d "testout" or mkdir "testout";
$Imager::formats{"png"}
or plan skip_all => "No png support";
-plan tests => 33;
+plan tests => 35;
my $green = i_color_new(0, 255, 0, 255);
my $blue = i_color_new(0, 0, 255, 255);
'test write_multi() callback failure');
}
+{ # check close failures are handled correctly
+ my $im = test_image();
+ my $fail_close = sub {
+ Imager::i_push_error(0, "synthetic close failure");
+ return 0;
+ };
+ ok(!$im->write(type => "png", callback => sub { 1 },
+ closecb => $fail_close),
+ "check failing close fails");
+ like($im->errstr, qr/synthetic close failure/,
+ "check error message");
+}
+
{
ok(grep($_ eq 'png', Imager->read_types), "check png in read types");
ok(grep($_ eq 'png', Imager->write_types), "check png in write types");
mm_log((1,"i_readsgi(ig %p, partial %d)\n", ig, partial));
i_clear_error();
- if (ig->readcb(ig, headbuf, 512) != 512) {
+ if (i_io_read(ig, headbuf, 512) != 512) {
i_push_error(errno, "SGI image: could not read header");
return NULL;
}
for(y = 0; y < height; y++) {
int x;
- if (ig->readcb(ig, databuf, width) != width) {
+ if (i_io_read(ig, databuf, width) != width) {
i_push_error(0, "SGI image: cannot read image data");
i_img_destroy(img);
myfree(linebuf);
length_tab = mymalloc(height*channels*sizeof(unsigned long));
/* Read offset table */
- if (ig->readcb(ig, databuf, height * channels * 4) != height * channels * 4) {
+ if (i_io_read(ig, databuf, height * channels * 4) != height * channels * 4) {
i_push_error(0, "SGI image: short read reading RLE start table");
goto ErrorReturn;
}
/* Read length table */
- if (ig->readcb(ig, databuf, height*channels*4) != height*channels*4) {
+ if (i_io_read(ig, databuf, height*channels*4) != height*channels*4) {
i_push_error(0, "SGI image: short read reading RLE length table");
goto ErrorReturn;
}
int pixels_left = width;
i_sample_t sample;
- if (ig->seekcb(ig, start_tab[ci], SEEK_SET) != start_tab[ci]) {
+ if (i_io_seek(ig, start_tab[ci], SEEK_SET) != start_tab[ci]) {
i_push_error(0, "SGI image: cannot seek to RLE data");
goto ErrorReturn;
}
- if (ig->readcb(ig, databuf, datalen) != datalen) {
+ if (i_io_read(ig, databuf, datalen) != datalen) {
i_push_error(0, "SGI image: cannot read RLE data");
goto ErrorReturn;
}
for(y = 0; y < height; y++) {
int x;
- if (ig->readcb(ig, databuf, width*2) != width*2) {
+ if (i_io_read(ig, databuf, width*2) != width*2) {
i_push_error(0, "SGI image: cannot read image data");
i_img_destroy(img);
myfree(linebuf);
i_push_error(0, "SGI image: invalid RLE length value for BPC=2");
goto ErrorReturn;
}
- if (ig->seekcb(ig, start_tab[ci], SEEK_SET) != start_tab[ci]) {
+ if (i_io_seek(ig, start_tab[ci], SEEK_SET) != start_tab[ci]) {
i_push_error(0, "SGI image: cannot seek to RLE data");
goto ErrorReturn;
}
- if (ig->readcb(ig, databuf, datalen) != datalen) {
+ if (i_io_read(ig, databuf, datalen) != datalen) {
i_push_error(0, "SGI image: cannot read RLE data");
goto ErrorReturn;
}
for (c = 0; c < img->channels; ++c) {
for (y = img->ysize - 1; y >= 0; --y) {
i_gsamp(img, 0, width, y, linebuf, &c, 1);
- if (ig->writecb(ig, linebuf, width) != width) {
+ if (i_io_write(ig, linebuf, width) != width) {
i_push_error(errno, "SGI image: error writing image data");
myfree(linebuf);
return 0;
}
myfree(linebuf);
+ if (i_io_close(ig))
+ return 0;
+
return 1;
}
store_32(lengths + offset_pos, comp_size);
offset_pos += 4;
current_offset += comp_size;
- if (ig->writecb(ig, comp_buf, comp_size) != comp_size) {
+ if (i_io_write(ig, comp_buf, comp_size) != comp_size) {
i_push_error(errno, "SGI image: error writing RLE data");
goto Error;
}
myfree(comp_buf);
myfree(linebuf);
+ if (i_io_close(ig))
+ return 0;
+
return 1;
Error:
unsigned short samp16 = SampleFTo16(linebuf[x]);
store_16(outp, samp16);
}
- if (ig->writecb(ig, encbuf, width * 2) != width * 2) {
+ if (i_io_write(ig, encbuf, width * 2) != width * 2) {
i_push_error(errno, "SGI image: error writing image data");
myfree(linebuf);
myfree(encbuf);
myfree(linebuf);
myfree(encbuf);
+ if (i_io_close(ig))
+ return 0;
+
return 1;
}
store_32(lengths + offset_pos, comp_size);
offset_pos += 4;
current_offset += comp_size;
- if (ig->writecb(ig, comp_buf, comp_size) != comp_size) {
+ if (i_io_write(ig, comp_buf, comp_size) != comp_size) {
i_push_error(errno, "SGI image: error writing RLE data");
goto Error;
}
myfree(linebuf);
myfree(sampbuf);
+ if (i_io_close(ig))
+ return 0;
+
return 1;
Error:
#!perl -w
use strict;
use Imager;
-use Test::More tests => 55;
+use Test::More tests => 57;
use Imager::Test qw(test_image test_image_16 is_image);
use IO::Seekable;
);
for my $test (@tests) {
my ($im, $limit, $expected_msg, $desc) = @$test;
- my ($writecb, $seekcb) = limited_write($limit);
- ok(!$im->write(type => 'sgi', writecb => $writecb,
- seekcb => $seekcb, maxbuffer => 1),
+ my $io = limited_write_io($limit);
+ ok(!$im->write(type => 'sgi', io => $io),
"write should fail - $desc");
is($im->errstr, "$expected_msg: limit reached", "check error - $desc");
}
}
+
+{ # check close failures are handled correctly
+ my $im = test_image();
+ my $fail_close = sub {
+ Imager::i_push_error(0, "synthetic close failure");
+ return 0;
+ };
+ ok(!$im->write(type => "sgi", callback => sub { 1 },
+ closecb => $fail_close),
+ "check failing close fails");
+ like($im->errstr, qr/synthetic close failure/,
+ "check error message");
+}
+
+sub limited_write_io {
+ my ($limit) = @_;
+
+ my ($writecb, $seekcb) = limited_write($limit);
+
+ my $io = Imager::io_new_cb($writecb, undef, $seekcb, undef, 1);
+ $io->set_buffered(0);
+
+ return $io;
+}
+
sub limited_write {
my ($limit) = @_;
toff_t
comp_seek(thandle_t h, toff_t o, int w) {
io_glue *ig = (io_glue*)h;
- return (toff_t) ig->seekcb(ig, o, w);
+ return (toff_t) i_io_seek(ig, o, w);
}
/*
/* do nothing */
}
+static tsize_t
+comp_read(thandle_t h, tdata_t p, tsize_t size) {
+ return i_io_read((io_glue *)h, p, size);
+}
+
+static tsize_t
+comp_write(thandle_t h, tdata_t p, tsize_t size) {
+ return i_io_write((io_glue *)h, p, size);
+}
+
+static int
+comp_close(thandle_t h) {
+ return i_io_close((io_glue *)h);
+}
+
static i_img *read_one_tiff(TIFF *tif, int allow_incomplete) {
i_img *im;
uint32 width, height;
tif = TIFFClientOpen("(Iolayer)",
"rm",
(thandle_t) ig,
- (TIFFReadWriteProc) ig->readcb,
- (TIFFReadWriteProc) ig->writecb,
- (TIFFSeekProc) comp_seek,
- (TIFFCloseProc) ig->closecb,
- ig->sizecb ? (TIFFSizeProc) ig->sizecb : (TIFFSizeProc) sizeproc,
- (TIFFMapFileProc) comp_mmap,
- (TIFFUnmapFileProc) comp_munmap);
+ comp_read,
+ comp_write,
+ comp_seek,
+ comp_close,
+ sizeproc,
+ comp_mmap,
+ comp_munmap);
if (!tif) {
mm_log((1, "i_readtiff_wiol: Unable to open tif file\n"));
TIFFErrorHandler old_warn_handler;
i_img **results = NULL;
int result_alloc = 0;
- int dirnum = 0;
i_clear_error();
old_handler = TIFFSetErrorHandler(error_handler);
tif = TIFFClientOpen("(Iolayer)",
"rm",
(thandle_t) ig,
- (TIFFReadWriteProc) ig->readcb,
- (TIFFReadWriteProc) ig->writecb,
- (TIFFSeekProc) comp_seek,
- (TIFFCloseProc) ig->closecb,
- ig->sizecb ? (TIFFSizeProc) ig->sizecb : (TIFFSizeProc) sizeproc,
- (TIFFMapFileProc) comp_mmap,
- (TIFFUnmapFileProc) comp_munmap);
+ comp_read,
+ comp_write,
+ comp_seek,
+ comp_close,
+ sizeproc,
+ comp_mmap,
+ comp_munmap);
if (!tif) {
mm_log((1, "i_readtiff_wiol: Unable to open tif file\n"));
tif = TIFFClientOpen("No name",
"wm",
(thandle_t) ig,
- (TIFFReadWriteProc) ig->readcb,
- (TIFFReadWriteProc) ig->writecb,
- (TIFFSeekProc) comp_seek,
- (TIFFCloseProc) ig->closecb,
- ig->sizecb ? (TIFFSizeProc) ig->sizecb : (TIFFSizeProc) sizeproc,
- (TIFFMapFileProc) comp_mmap,
- (TIFFUnmapFileProc) comp_munmap);
+ comp_read,
+ comp_write,
+ comp_seek,
+ comp_close,
+ sizeproc,
+ comp_mmap,
+ comp_munmap);
TIFFSetErrorHandler(old_handler);
(void) TIFFClose(tif);
+ if (i_io_close(ig))
+ return 0;
+
return 1;
}
tif = TIFFClientOpen("No name",
"wm",
(thandle_t) ig,
- (TIFFReadWriteProc) ig->readcb,
- (TIFFReadWriteProc) ig->writecb,
- (TIFFSeekProc) comp_seek,
- (TIFFCloseProc) ig->closecb,
- ig->sizecb ? (TIFFSizeProc) ig->sizecb : (TIFFSizeProc) sizeproc,
- (TIFFMapFileProc) comp_mmap,
- (TIFFUnmapFileProc) comp_munmap);
+ comp_read,
+ comp_write,
+ comp_seek,
+ comp_close,
+ sizeproc,
+ comp_mmap,
+ comp_munmap);
(void) TIFFClose(tif);
TIFFSetErrorHandler(old_handler);
+ if (i_io_close(ig))
+ return 0;
+
return 1;
}
tif = TIFFClientOpen("No name",
"wm",
(thandle_t) ig,
- (TIFFReadWriteProc) ig->readcb,
- (TIFFReadWriteProc) ig->writecb,
- (TIFFSeekProc) comp_seek,
- (TIFFCloseProc) ig->closecb,
- ig->sizecb ? (TIFFSizeProc) ig->sizecb : (TIFFSizeProc) sizeproc,
- (TIFFMapFileProc) comp_mmap,
- (TIFFUnmapFileProc) comp_munmap);
+ comp_read,
+ comp_write,
+ comp_seek,
+ comp_close,
+ sizeproc,
+ comp_mmap,
+ comp_munmap);
(void) TIFFClose(tif);
TIFFSetErrorHandler(old_handler);
+ if (i_io_close(ig))
+ return 0;
+
return 1;
}
tif = TIFFClientOpen("No name",
"wm",
(thandle_t) ig,
- (TIFFReadWriteProc) ig->readcb,
- (TIFFReadWriteProc) ig->writecb,
- (TIFFSeekProc) comp_seek,
- (TIFFCloseProc) ig->closecb,
- ig->sizecb ? (TIFFSizeProc) ig->sizecb : (TIFFSizeProc) sizeproc,
- (TIFFMapFileProc) comp_mmap,
- (TIFFUnmapFileProc) comp_munmap);
+ comp_read,
+ comp_write,
+ comp_seek,
+ comp_close,
+ sizeproc,
+ comp_mmap,
+ comp_munmap);
(void) TIFFClose(tif);
TIFFSetErrorHandler(old_handler);
+ if (i_io_close(ig))
+ return 0;
+
return 1;
}
#!perl -w
use strict;
-use Test::More tests => 235;
+use Test::More tests => 239;
use Imager qw(:all);
use Imager::Test qw(is_image is_image_similar test_image test_image_16 test_image_double test_image_raw);
}
sub io_reader {
my ($size, $maxread) = @_;
- #print "io_reader($size, $maxread) pos $seekpos\n";
+ print "# io_reader($size, $maxread) pos $seekpos\n";
+ if ($seekpos + $maxread > length $work) {
+ $maxread = length($work) - $seekpos;
+ }
my $out = substr($work, $seekpos, $maxread);
$seekpos += length $out;
$out;
}
sub io_reader2 {
my ($size, $maxread) = @_;
- #print "io_reader2($size, $maxread) pos $seekpos\n";
+ 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";
+ print "# io_seeker($offset, $whence)\n";
if ($whence == SEEK_SET) {
$seekpos = $offset;
}
print D3 $work;
close D3;
+
+{ # check close failures are handled correctly
+ { # single image
+ my $im = test_image();
+ my $fail_close = sub {
+ Imager::i_push_error(0, "synthetic close failure");
+ return 0;
+ };
+ $work = '';
+ $seekpos = 0;
+ ok(!$im->write(type => "tiff",
+ readcb => \&io_reader,
+ writecb => \&io_writer,
+ seekcb => \&io_seeker,
+ closecb => $fail_close),
+ "check failing close fails");
+ like($im->errstr, qr/synthetic close failure/,
+ "check error message");
+ }
+ { # multiple images
+ my $im = test_image();
+ my $fail_close = sub {
+ Imager::i_push_error(0, "synthetic close failure");
+ return 0;
+ };
+ $work = '';
+ $seekpos = 0;
+ ok(!Imager->write_multi({type => "tiff",
+ readcb => \&io_reader,
+ writecb => \&io_writer,
+ seekcb => \&io_seeker,
+ closecb => $fail_close}, $im, $im),
+ "check failing close fails");
+ like(Imager->errstr, qr/synthetic close failure/,
+ "check error message");
+ }
+}
+
# multi-image write/read
my @imgs;
push(@imgs, map $ooim->copy(), 1..3);
or skip "Cannot open testout/t106_empty.tif for reading", 8;
binmode TIFF;
my $im = Imager->new(xsize=>100, ysize=>100);
- ok(!$im->write(fh => \*TIFF, type=>'tiff'),
+ ok(!$im->write(fh => \*TIFF, type=>'tiff', buffered => 0),
"fail to write to read only handle");
cmp_ok($im->errstr, '=~', 'Could not create TIFF object: Error writing TIFF header: write\(\)',
"check error message");
- ok(!Imager->write_multi({ type => 'tiff', fh => \*TIFF }, $im),
+ ok(!Imager->write_multi({ type => 'tiff', fh => \*TIFF, buffered => 0 }, $im),
"fail to write multi to read only handle");
cmp_ok(Imager->errstr, '=~', 'Could not create TIFF object: Error writing TIFF header: write\(\)',
"check error message");
- ok(!$im->write(fh => \*TIFF, type=>'tiff', class=>'fax'),
+ ok(!$im->write(fh => \*TIFF, type=>'tiff', class=>'fax', buffered => 0),
"fail to write to read only handle (fax)");
cmp_ok($im->errstr, '=~', 'Could not create TIFF object: Error writing TIFF header: write\(\)',
"check error message");
- ok(!Imager->write_multi({ type => 'tiff', fh => \*TIFF, class=>'fax' }, $im),
+ ok(!Imager->write_multi({ type => 'tiff', fh => \*TIFF, class=>'fax', buffered => 0 }, $im),
"fail to write multi to read only handle (fax)");
cmp_ok(Imager->errstr, '=~', 'Could not create TIFF object: Error writing TIFF header: write\(\)',
"check error message");
my $in_struct;
while (<FUNCS>) {
/^typedef struct/ && ++$in_struct;
- if ($in_struct && /\(\*f_(i_\w+)/) {
- push @funcs, $1;
+ if ($in_struct && /\(\*f_(io?_\w+)/) {
+ my $name = $1;
+ $name =~ s/_imp$//;
+ push @funcs, $name;
}
if (/^\} im_ext_funcs;$/) {
$in_struct
*/
int
i_writebmp_wiol(i_img *im, io_glue *ig) {
- io_glue_commit_types(ig);
i_clear_error();
/* pick a format */
mm_log((1, "i_readbmp_wiol(ig %p)\n", ig));
- io_glue_commit_types(ig);
i_clear_error();
if (!read_packed(ig, "CCVvvVVV!V!vvVVVVVV", &b_magic, &m_magic, &filesize,
switch (code) {
case 'v':
- if (ig->readcb(ig, buf, 2) != 2)
+ if (i_io_read(ig, buf, 2) != 2)
return 0;
work = buf[0] + ((i_packed_t)buf[1] << 8);
if (shrieking)
break;
case 'V':
- if (ig->readcb(ig, buf, 4) != 4)
+ if (i_io_read(ig, buf, 4) != 4)
return 0;
work = buf[0] + (buf[1] << 8) + ((i_packed_t)buf[2] << 16) + ((i_packed_t)buf[3] << 24);
if (shrieking)
break;
case 'C':
- if (ig->readcb(ig, buf, 1) != 1)
+ if (i_io_read(ig, buf, 1) != 1)
return 0;
*p = buf[0];
break;
case 'c':
- if (ig->readcb(ig, buf, 1) != 1)
+ if (i_io_read(ig, buf, 1) != 1)
return 0;
*p = (char)buf[0];
break;
case '3': /* extension - 24-bit number */
- if (ig->readcb(ig, buf, 3) != 3)
+ if (i_io_read(ig, buf, 3) != 3)
return 0;
*p = buf[0] + (buf[1] << 8) + ((i_packed_t)buf[2] << 16);
break;
case 'v':
buf[0] = i & 255;
buf[1] = i / 256;
- if (ig->writecb(ig, buf, 2) == -1)
+ if (i_io_write(ig, buf, 2) == -1)
return 0;
break;
buf[1] = (i >> 8) & 0xFF;
buf[2] = (i >> 16) & 0xFF;
buf[3] = (i >> 24) & 0xFF;
- if (ig->writecb(ig, buf, 4) == -1)
+ if (i_io_write(ig, buf, 4) == -1)
return 0;
break;
case 'C':
case 'c':
buf[0] = i & 0xFF;
- if (ig->writecb(ig, buf, 1) == -1)
+ if (i_io_write(ig, buf, 1) == -1)
return 0;
break;
if (mask != 0x80) {
*out++ = byte;
}
- if (ig->writecb(ig, packed, line_size) < 0) {
+ if (i_io_write(ig, packed, line_size) < 0) {
myfree(packed);
myfree(line);
i_push_error(0, "writing 1 bit/pixel packed data");
myfree(packed);
myfree(line);
- ig->closecb(ig);
+ if (i_io_close(ig))
+ return 0;
return 1;
}
for (x = 0; x < im->xsize; x += 2) {
*out++ = (line[x] << 4) + line[x+1];
}
- if (ig->writecb(ig, packed, line_size) < 0) {
+ if (i_io_write(ig, packed, line_size) < 0) {
myfree(packed);
myfree(line);
i_push_error(0, "writing 4 bit/pixel packed data");
myfree(packed);
myfree(line);
- ig->closecb(ig);
+ if (i_io_close(ig))
+ return 0;
return 1;
}
for (y = im->ysize-1; y >= 0; --y) {
i_gpal(im, 0, im->xsize, y, line);
- if (ig->writecb(ig, line, line_size) < 0) {
+ if (i_io_write(ig, line, line_size) < 0) {
myfree(line);
i_push_error(0, "writing 8 bit/pixel packed data");
return 0;
}
myfree(line);
- ig->closecb(ig);
+ if (i_io_close(ig))
+ return 0;
return 1;
}
samplep[0] = tmp;
samplep += 3;
}
- if (ig->writecb(ig, samples, line_size) < 0) {
+ if (i_io_write(ig, samples, line_size) < 0) {
i_push_error(0, "writing image data");
myfree(samples);
return 0;
}
myfree(samples);
- ig->closecb(ig);
+ if (i_io_close(ig))
+ return 0;
return 1;
}
rare */
char buffer;
while (base_offset < offbits) {
- if (ig->readcb(ig, &buffer, 1) != 1) {
+ if (i_io_read(ig, &buffer, 1) != 1) {
i_img_destroy(im);
i_push_error(0, "failed skipping to image data offset");
return NULL;
packed = mymalloc(line_size); /* checked 29jun05 tonyc */
line = mymalloc(xsize+8); /* checked 29jun05 tonyc */
while (y != lasty) {
- if (ig->readcb(ig, packed, line_size) != line_size) {
+ if (i_io_read(ig, packed, line_size) != line_size) {
myfree(packed);
myfree(line);
if (allow_incomplete) {
rare */
char buffer;
while (base_offset < offbits) {
- if (ig->readcb(ig, &buffer, 1) != 1) {
+ if (i_io_read(ig, &buffer, 1) != 1) {
i_img_destroy(im);
i_push_error(0, "failed skipping to image data offset");
return NULL;
if (compression == BI_RGB) {
i_tags_add(&im->tags, "bmp_compression_name", 0, "BI_RGB", -1, 0);
while (y != lasty) {
- if (ig->readcb(ig, packed, line_size) != line_size) {
+ if (i_io_read(ig, packed, line_size) != line_size) {
myfree(packed);
myfree(line);
if (allow_incomplete) {
x = 0;
while (1) {
/* there's always at least 2 bytes in a sequence */
- if (ig->readcb(ig, packed, 2) != 2) {
+ if (i_io_read(ig, packed, 2) != 2) {
myfree(packed);
myfree(line);
if (allow_incomplete) {
return im;
case BMPRLE_DELTA:
- if (ig->readcb(ig, packed, 2) != 2) {
+ if (i_io_read(ig, packed, 2) != 2) {
myfree(packed);
myfree(line);
if (allow_incomplete) {
}
size = (count + 1) / 2;
read_size = (size+1) / 2 * 2;
- if (ig->readcb(ig, packed, read_size) != read_size) {
+ if (i_io_read(ig, packed, read_size) != read_size) {
myfree(packed);
myfree(line);
if (allow_incomplete) {
rare */
char buffer;
while (base_offset < offbits) {
- if (ig->readcb(ig, &buffer, 1) != 1) {
+ if (i_io_read(ig, &buffer, 1) != 1) {
i_img_destroy(im);
i_push_error(0, "failed skipping to image data offset");
return NULL;
if (compression == BI_RGB) {
i_tags_add(&im->tags, "bmp_compression_name", 0, "BI_RGB", -1, 0);
while (y != lasty) {
- if (ig->readcb(ig, line, line_size) != line_size) {
+ if (i_io_read(ig, line, line_size) != line_size) {
myfree(line);
if (allow_incomplete) {
i_tags_setn(&im->tags, "i_incomplete", 1);
x = 0;
while (1) {
/* there's always at least 2 bytes in a sequence */
- if (ig->readcb(ig, packed, 2) != 2) {
+ if (i_io_read(ig, packed, 2) != 2) {
myfree(line);
if (allow_incomplete) {
i_tags_setn(&im->tags, "i_incomplete", 1);
return im;
case BMPRLE_DELTA:
- if (ig->readcb(ig, packed, 2) != 2) {
+ if (i_io_read(ig, packed, 2) != 2) {
myfree(line);
if (allow_incomplete) {
i_tags_setn(&im->tags, "i_incomplete", 1);
return NULL;
}
read_size = (count+1) / 2 * 2;
- if (ig->readcb(ig, line, read_size) != read_size) {
+ if (i_io_read(ig, line, read_size) != read_size) {
myfree(line);
if (allow_incomplete) {
i_tags_setn(&im->tags, "i_incomplete", 1);
/* there's a potential "palette" after the header */
for (i = 0; i < clr_used; ++clr_used) {
char buf[4];
- if (ig->readcb(ig, buf, 4) != 4) {
+ if (i_io_read(ig, buf, 4) != 4) {
i_push_error(0, "skipping colors");
return 0;
}
rare */
char buffer;
while (base_offset < offbits) {
- if (ig->readcb(ig, &buffer, 1) != 1) {
+ if (i_io_read(ig, &buffer, 1) != 1) {
i_push_error(0, "failed skipping to image data offset");
return NULL;
}
}
i_plin(im, 0, xsize, y, line);
if (extras)
- ig->readcb(ig, junk, extras);
+ i_io_read(ig, junk, extras);
y += yinc;
}
myfree(line);
}
}
-/*
-=back
-
-=head2 Stream reading and writing wrapper functions
-
-=over
-
-=item i_gen_reader(i_gen_read_data *info, char *buf, int length)
-
-Performs general read buffering for file readers that permit reading
-to be done through a callback.
-
-The final callback gets two parameters, a I<need> value, and a I<want>
-value, where I<need> is the amount of data that the file library needs
-to read, and I<want> is the amount of space available in the buffer
-maintained by these functions.
-
-This means if you need to read from a stream that you don't know the
-length of, you can return I<need> bytes, taking the performance hit of
-possibly expensive callbacks (eg. back to perl code), or if you are
-reading from a stream where it doesn't matter if some data is lost, or
-if the total length of the stream is known, you can return I<want>
-bytes.
-
-=cut
-*/
-
-int
-i_gen_reader(i_gen_read_data *gci, char *buf, int length) {
- int total;
-
- if (length < gci->length - gci->cpos) {
- /* simplest case */
- memcpy(buf, gci->buffer+gci->cpos, length);
- gci->cpos += length;
- return length;
- }
-
- total = 0;
- memcpy(buf, gci->buffer+gci->cpos, gci->length-gci->cpos);
- total += gci->length - gci->cpos;
- length -= gci->length - gci->cpos;
- buf += gci->length - gci->cpos;
- if (length < (int)sizeof(gci->buffer)) {
- int did_read;
- int copy_size;
- while (length
- && (did_read = (gci->cb)(gci->userdata, gci->buffer, length,
- sizeof(gci->buffer))) > 0) {
- gci->cpos = 0;
- gci->length = did_read;
-
- copy_size = i_min(length, gci->length);
- memcpy(buf, gci->buffer, copy_size);
- gci->cpos += copy_size;
- buf += copy_size;
- total += copy_size;
- length -= copy_size;
- }
- }
- else {
- /* just read the rest - too big for our buffer*/
- int did_read;
- while ((did_read = (gci->cb)(gci->userdata, buf, length, length)) > 0) {
- length -= did_read;
- total += did_read;
- buf += did_read;
- }
- }
- return total;
-}
-
-/*
-=item i_gen_read_data_new(i_read_callback_t cb, char *userdata)
-
-For use by callback file readers to initialize the reader buffer.
-
-Allocates, initializes and returns the reader buffer.
-
-See also L<image.c/free_gen_read_data> and L<image.c/i_gen_reader>.
-
-=cut
-*/
-i_gen_read_data *
-i_gen_read_data_new(i_read_callback_t cb, char *userdata) {
- i_gen_read_data *self = mymalloc(sizeof(i_gen_read_data));
- self->cb = cb;
- self->userdata = userdata;
- self->length = 0;
- self->cpos = 0;
-
- return self;
-}
-
-/*
-=item i_free_gen_read_data(i_gen_read_data *)
-
-Cleans up.
-
-=cut
-*/
-void i_free_gen_read_data(i_gen_read_data *self) {
- myfree(self);
-}
-
-/*
-=item i_gen_writer(i_gen_write_data *info, char const *data, int size)
-
-Performs write buffering for a callback based file writer.
-
-Failures are considered fatal, if a write fails then data will be
-dropped.
-
-=cut
-*/
-int
-i_gen_writer(
-i_gen_write_data *self,
-char const *data,
-int size)
-{
- if (self->filledto && self->filledto+size > self->maxlength) {
- if (self->cb(self->userdata, self->buffer, self->filledto)) {
- self->filledto = 0;
- }
- else {
- self->filledto = 0;
- return 0;
- }
- }
- if (self->filledto+size <= self->maxlength) {
- /* just save it */
- memcpy(self->buffer+self->filledto, data, size);
- self->filledto += size;
- return 1;
- }
- /* doesn't fit - hand it off */
- return self->cb(self->userdata, data, size);
-}
-
-/*
-=item i_gen_write_data_new(i_write_callback_t cb, char *userdata, int max_length)
-
-Allocates and initializes the data structure used by i_gen_writer.
-
-This should be released with L<image.c/i_free_gen_write_data>
-
-=cut
-*/
-i_gen_write_data *i_gen_write_data_new(i_write_callback_t cb,
- char *userdata, int max_length)
-{
- i_gen_write_data *self = mymalloc(sizeof(i_gen_write_data));
- self->cb = cb;
- self->userdata = userdata;
- self->maxlength = i_min(max_length, sizeof(self->buffer));
- if (self->maxlength < 0)
- self->maxlength = sizeof(self->buffer);
- self->filledto = 0;
-
- return self;
-}
-
-/*
-=item i_free_gen_write_data(i_gen_write_data *info, int flush)
-
-Cleans up the write buffer.
-
-Will flush any left-over data if I<flush> is non-zero.
-
-Returns non-zero if flush is zero or if info->cb() returns non-zero.
-
-Return zero only if flush is non-zero and info->cb() returns zero.
-ie. if it fails.
-
-=cut
-*/
-
-int i_free_gen_write_data(i_gen_write_data *info, int flush)
-{
- int result = !flush ||
- info->filledto == 0 ||
- info->cb(info->userdata, info->buffer, info->filledto);
- myfree(info);
-
- return result;
-}
-
struct magic_entry {
unsigned char *magic;
size_t magic_size;
unsigned char head[18];
ssize_t rc;
- io_glue_commit_types(data);
- rc = data->readcb(data, head, 18);
+ rc = i_io_peekn(data, head, 18);
if (rc == -1) return NULL;
- data->seekcb(data, -rc, SEEK_CUR);
+#if 0
+ {
+ int i;
+ fprintf(stderr, "%d bytes -", (int)rc);
+ for (i = 0; i < rc; ++i)
+ fprintf(stderr, " %02x", head[i]);
+ fprintf(stderr, "\n");
+ }
+#endif
for(i=0; i<sizeof(formats)/sizeof(formats[0]); i++) {
struct magic_entry const *entry = formats + i;
#endif /* End of freetype headers */
-/* functions for reading and writing formats */
-
-/* general reader callback
- userdata - data the user passed into the reader
- buffer - the buffer to fill with data
- need - the amount of data needed
- want - the amount of space we have to store data
- fill buffer and return the number of bytes read, 0 for eof, -1 for error
-*/
-
-typedef int (*i_read_callback_t)(char *userdata, char *buffer, int need,
- int want);
-
-/* i_gen_reader() translates the low-level requests from whatever library
- into buffered requests.
- but the called function can always bypass buffering by only ever
- reading I<need> bytes.
-*/
-#define CBBUFSIZ 4096
-
-typedef struct {
- i_read_callback_t cb;
- char *userdata;
- char buffer[CBBUFSIZ];
- int length;
- int cpos;
-} i_gen_read_data;
-
-extern int i_gen_reader(i_gen_read_data *info, char *buffer, int need);
-extern i_gen_read_data *i_gen_read_data_new(i_read_callback_t cb, char *userdata);
-extern void i_free_gen_read_data(i_gen_read_data *);
-
-/* general writer callback
- userdata - the data the user passed into the writer
- data - the data to write
- data_size - the number of bytes to write
- write the data, return non-zero on success, zero on failure.
-*/
-typedef int (*i_write_callback_t)(char *userdata, char const *data, int size);
-
-typedef struct {
- i_write_callback_t cb;
- char *userdata;
- char buffer[CBBUFSIZ];
- int maxlength;
- int filledto;
-} i_gen_write_data;
-
-extern int i_gen_writer(i_gen_write_data *info, char const *data, int size);
-extern i_gen_write_data *i_gen_write_data_new(i_write_callback_t cb, char *userdata, int maxlength);
-extern int i_free_gen_write_data(i_gen_write_data *, int flush);
-
extern void i_quant_makemap(i_quantize *quant, i_img **imgs, int count);
extern i_palidx *i_quant_translate(i_quantize *quant, i_img *img);
extern void i_quant_transparent(i_quantize *quant, i_palidx *indices, i_img *img, i_palidx trans_index);
i_render_color,
i_render_fill,
i_render_line,
- i_render_linef
+ i_render_linef,
+
+ /* level 6 */
+ i_io_getc_imp,
+ i_io_peekc_imp,
+ i_io_peekn,
+ i_io_putc_imp,
+ i_io_read,
+ i_io_write,
+ i_io_seek,
+ i_io_flush,
+ i_io_close,
+ i_io_set_buffered,
+ i_io_gets,
+ io_new_fd,
+ io_new_bufchain,
+ io_new_buffer,
+ io_new_cb,
+ io_slurp,
+ io_glue_destroy
};
/* in general these functions aren't called by Imager internally, but
#define i_render_linef(r, x, y, width, src, line, combine) \
((im_extt->f_i_render_linef)((r), (x), (y), (width), (src), (line), (combine)))
+#define i_io_getc_imp (im_extt->f_i_io_getc_imp)
+#define i_io_peekc_imp (im_extt->f_i_io_peekc_imp)
+#define i_io_peekn (im_extt->f_i_io_peekn)
+#define i_io_putc_imp (im_extt->f_i_io_putc_imp)
+#define i_io_read (im_extt->f_i_io_read)
+#define i_io_write (im_extt->f_i_io_write)
+#define i_io_seek (im_extt->f_i_io_seek)
+#define i_io_flush (im_extt->f_i_io_flush)
+#define i_io_close (im_extt->f_i_io_close)
+#define i_io_set_buffered (im_extt->f_i_io_set_buffered)
+#define i_io_gets (im_extt->f_i_io_gets)
+#define io_new_fd(fd) ((im_extt->f_io_new_fd)(fd))
+#define io_new_bufchain() ((im_extt->f_io_new_bufchain)())
+#define io_new_buffer(data, len, closecb, closedata) \
+ ((im_extt->f_io_new_buffer)((data), (len), (closecb), (closedata)))
+#define io_new_cb(p, readcb, writecb, seekcb, closecb, destroycb) \
+ ((im_extt->f_io_new_cb)((p), (readcb), (writecb), (seekcb), (closecb), (destroycb)))
+#define io_slurp(ig, datap) ((im_extt->f_io_slurp)((ig), (datap)))
+#define io_glue_destroy(ig) ((im_extt->f_io_glue_destroy)(ig))
+
#ifdef IMAGER_LOG
#define mm_log(x) { i_lhead(__FILE__,__LINE__); i_loog x; }
#else
Version 2 changed the types of some parameters and pointers. A
simple recompile should be enough in most cases.
+ Version 3 changed the behaviour of some of the I/O layer functions,
+ and in some cases the initial seek position when calling file
+ readers. Switching away from calling readcb etc to i_io_read() etc
+ should fix your code.
+
*/
-#define IMAGER_API_VERSION 2
+#define IMAGER_API_VERSION 3
/*
IMAGER_API_LEVEL is the level of the structure. New function pointers
will result in an increment of IMAGER_API_LEVEL.
*/
-#define IMAGER_API_LEVEL 6
+#define IMAGER_API_LEVEL 7
typedef struct {
int version;
i_img_dim width, const double *src,
i_fcolor *line, i_fill_combinef_f combine);
- /* IMAGER_API_LEVEL 6 functions will be added here */
+ /* Level 6 lost to mis-numbering */
+ /* IMAGER_API_LEVEL 7 */
+ int (*f_i_io_getc_imp)(io_glue *ig);
+ int (*f_i_io_peekc_imp)(io_glue *ig);
+ ssize_t (*f_i_io_peekn)(io_glue *ig, void *buf, size_t size);
+ int (*f_i_io_putc_imp)(io_glue *ig, int c);
+ ssize_t (*f_i_io_read)(io_glue *, void *buf, size_t size);
+ ssize_t (*f_i_io_write)(io_glue *, const void *buf, size_t size);
+ off_t (*f_i_io_seek)(io_glue *, off_t offset, int whence);
+ int (*f_i_io_flush)(io_glue *ig);
+ int (*f_i_io_close)(io_glue *ig);
+ int (*f_i_io_set_buffered)(io_glue *ig, int buffered);
+ ssize_t (*f_i_io_gets)(io_glue *ig, char *, size_t, int);
+
+ i_io_glue_t *(*f_io_new_fd)(int fd);
+ i_io_glue_t *(*f_io_new_bufchain)(void);
+ i_io_glue_t *(*f_io_new_buffer)(const char *data, size_t len, i_io_closebufp_t closecb, void *closedata);
+ i_io_glue_t *(*f_io_new_cb)(void *p, i_io_readl_t readcb, i_io_writel_t writecb, i_io_seekl_t seekcb, i_io_closel_t closecb, i_io_destroyl_t destroycb);
+ size_t (*f_io_slurp)(i_io_glue_t *ig, unsigned char **c);
+ void (*f_io_glue_destroy)(i_io_glue_t *ig);
+
+ /* IMAGER_API_LEVEL 8 functions will be added here */
+
} im_ext_funcs;
#define PERL_FUNCTION_TABLE_NAME "Imager::__ext_func_table"
#include "imageri.h"
#define IOL_DEB(x)
+#define IOL_DEBs stderr
+#define IO_BUF_SIZE 8192
char *io_type_names[] = { "FDSEEK", "FDNOSEEK", "BUFFER", "CBSEEK", "CBNOSEEK", "BUFCHAIN" };
} io_blink;
-/* Structures that describe callback interfaces */
-
typedef struct {
- off_t offset;
- off_t cpos;
-} io_ex_rseek;
-
+ i_io_glue_t base;
+ int fd;
+} io_fdseek;
typedef struct {
- off_t offset;
+ i_io_glue_t base;
+ const char *data;
+ size_t len;
+ i_io_closebufp_t closecb; /* free memory mapped segment or decrement refcount */
+ void *closedata;
off_t cpos;
- io_blink *head;
- io_blink *tail;
- io_blink *cp;
-} io_ex_fseek;
+} io_buffer;
+typedef struct {
+ i_io_glue_t base;
+ void *p; /* Callback data */
+ i_io_readl_t readcb;
+ i_io_writel_t writecb;
+ i_io_seekl_t seekcb;
+ i_io_closel_t closecb;
+ i_io_destroyl_t destroycb;
+} io_cb;
typedef struct {
off_t offset; /* Offset of the source - not used */
off_t gpos; /* Global position in stream */
} io_ex_bchain;
-typedef struct {
- off_t offset; /* Offset of the source - not used */
- off_t cpos; /* Offset within the current */
-} io_ex_buffer;
-
-static void io_obj_setp_buffer(io_obj *io, char *p, size_t len, i_io_closebufp_t closecb, void *closedata);
-static void io_obj_setp_cb2 (io_obj *io, void *p, i_io_readl_t readcb, i_io_writel_t writecb, i_io_seekl_t seekcb, i_io_closel_t closecb, i_io_destroyl_t destroycb);
-
/* turn current offset, file length, whence and offset into a new offset */
#define calc_seek_offset(curr_off, length, offset, whence) \
(((whence) == SEEK_SET) ? (offset) : \
io_glue *ig = io_new_fd( fileno(stdin) );
method = io_reqmeth( IOL_NOSEEK | IOL_MMAP ); // not implemented yet
- io_glue_commit_types(ig); // always assume IOL_SEEK for now
+
switch (method) {
case IOL_NOSEEK:
code that uses ig->readcb()
=cut
*/
+static void
+i_io_init(io_glue *ig, int type, i_io_readp_t readcb, i_io_writep_t writecb,
+ i_io_seekp_t seekcb);
+
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 int fd_close(io_glue *ig);
static ssize_t fd_size(io_glue *ig);
static const char *my_strerror(int err);
+static void i_io_setup_buffer(io_glue *ig);
+static void
+i_io_start_write(io_glue *ig);
+static int
+i_io_read_fill(io_glue *ig, ssize_t needed);
+static void
+dump_data(unsigned char *start, unsigned char *end, int bias);
+static ssize_t realseek_read(io_glue *igo, void *buf, size_t count);
+static ssize_t realseek_write(io_glue *igo, const void *buf, size_t count);
+static int realseek_close(io_glue *igo);
+static off_t realseek_seek(io_glue *igo, off_t offset, int whence);
+static void realseek_destroy(io_glue *igo);
+static ssize_t buffer_read(io_glue *igo, void *buf, size_t count);
+static ssize_t buffer_write(io_glue *ig, const void *buf, size_t count);
+static int buffer_close(io_glue *ig);
+static off_t buffer_seek(io_glue *igo, off_t offset, int whence);
+static void buffer_destroy(io_glue *igo);
+static io_blink*io_blink_new(void);
+static void io_bchain_advance(io_ex_bchain *ieb);
+static void io_destroy_bufchain(io_ex_bchain *ieb);
+static ssize_t bufchain_read(io_glue *ig, void *buf, size_t count);
+static ssize_t bufchain_write(io_glue *ig, const void *buf, size_t count);
+static int bufchain_close(io_glue *ig);
+static off_t bufchain_seek(io_glue *ig, off_t offset, int whence);
+static void bufchain_destroy(io_glue *ig);
/*
- * Callbacks for sources that cannot seek
+ * Methods for setting up data source
*/
-/* fakeseek_read: read method for when emulating a seekable source
-static
-ssize_t
-fakeseek_read(io_glue *ig, void *buf, size_t count) {
- io_ex_fseek *exdata = ig->exdata;
- return 0;
+/*
+=item io_new_bufchain()
+=order 10
+=category I/O Layers
+
+returns a new io_glue object that has the 'empty' source and but can
+be written to and read from later (like a pseudo file).
+
+=cut
+*/
+
+io_glue *
+io_new_bufchain() {
+ io_glue *ig;
+ io_ex_bchain *ieb = mymalloc(sizeof(io_ex_bchain));
+
+ mm_log((1, "io_new_bufchain()\n"));
+
+ ig = mymalloc(sizeof(io_glue));
+ memset(ig, 0, sizeof(*ig));
+ i_io_init(ig, BUFCHAIN, bufchain_read, bufchain_write, bufchain_seek);
+
+ ieb->offset = 0;
+ ieb->length = 0;
+ ieb->cpos = 0;
+ ieb->gpos = 0;
+ ieb->tfill = 0;
+
+ ieb->head = io_blink_new();
+ ieb->cp = ieb->head;
+ ieb->tail = ieb->head;
+
+ ig->exdata = ieb;
+ ig->closecb = bufchain_close;
+ ig->destroycb = bufchain_destroy;
+
+ return ig;
}
+
+/*
+=item io_new_buffer(data, length)
+=order 10
+=category I/O Layers
+
+Returns a new io_glue object that has the source defined as reading
+from specified buffer. Note that the buffer is not copied.
+
+ data - buffer to read from
+ length - length of buffer
+
+=cut
*/
+io_glue *
+io_new_buffer(const char *data, size_t len, i_io_closebufp_t closecb, void *closedata) {
+ io_buffer *ig;
+
+ mm_log((1, "io_new_buffer(data %p, len %ld, closecb %p, closedata %p)\n", data, (long)len, closecb, closedata));
+
+ ig = mymalloc(sizeof(io_buffer));
+ memset(ig, 0, sizeof(*ig));
+ i_io_init(&ig->base, BUFFER, buffer_read, buffer_write, buffer_seek);
+ ig->data = data;
+ ig->len = len;
+ ig->closecb = closecb;
+ ig->closedata = closedata;
+
+ ig->cpos = 0;
+
+ ig->base.closecb = buffer_close;
+ ig->base.destroycb = buffer_destroy;
+
+ return (io_glue *)ig;
+}
/*
- * Callbacks for sources that can seek
- */
+=item io_new_fd(fd)
+=order 10
+=category I/O Layers
+
+returns a new io_glue object that has the source defined as reading
+from specified file descriptor. Note that the the interface to receiving
+data from the io_glue callbacks hasn't been done yet.
+
+ fd - file descriptor to read/write from
+
+=cut
+*/
+
+io_glue *
+io_new_fd(int fd) {
+ io_fdseek *ig;
+
+ mm_log((1, "io_new_fd(fd %d)\n", fd));
+
+ ig = mymalloc(sizeof(io_fdseek));
+ memset(ig, 0, sizeof(*ig));
+ i_io_init(&ig->base, FDSEEK, fd_read, fd_write, fd_seek);
+ ig->fd = fd;
+
+ ig->base.closecb = fd_close;
+ ig->base.sizecb = fd_size;
+ ig->base.destroycb = NULL;
+
+ mm_log((1, "(%p) <- io_new_fd\n", ig));
+ return (io_glue *)ig;
+}
/*
-=item realseek_read(ig, buf, count)
+=item io_new_cb(p, read_cb, write_cb, seek_cb, close_cb, destroy_cb)
+=category I/O Layers
+=order 10
-Does the reading from a source that can be seeked on
+Create a new I/O layer object that calls your supplied callbacks.
- ig - io_glue object
- buf - buffer to return data in
- count - number of bytes to read into buffer max
+In general the callbacks should behave like the corresponding POSIX
+primitives.
+
+=over
+
+=item *
+
+C<read_cb>(p, buffer, length) should read up to C<length> bytes into
+C<buffer> and return the number of bytes read. At end of file, return
+0. On error, return -1.
+
+=item *
+
+C<write_cb>(p, buffer, length) should write up to C<length> bytes from
+C<buffer> and return the number of bytes written. A return value <= 0
+will be treated as an error.
+
+=item *
+
+C<seekcb>(p, offset, whence) should seek and return the new offset.
+
+=item *
+
+C<close_cb>(p) should return 0 on success, -1 on failure.
+
+=item *
+
+C<destroy_cb>(p) should release any memory specific to your callback
+handlers.
+
+=back
=cut
*/
-static
-ssize_t
-realseek_read(io_glue *ig, void *buf, size_t count) {
- io_ex_rseek *ier = ig->exdata;
- void *p = ig->source.cb.p;
- ssize_t rc = 0;
- size_t bc = 0;
- char *cbuf = buf;
+io_glue *
+io_new_cb(void *p, i_io_readl_t readcb, i_io_writel_t writecb,
+ i_io_seekl_t seekcb, i_io_closel_t closecb,
+ i_io_destroyl_t destroycb) {
+ io_cb *ig;
- IOL_DEB( printf("realseek_read: buf = %p, count = %d\n",
- 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;
+ 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_cb));
+ memset(ig, 0, sizeof(*ig));
+ i_io_init(&ig->base, CBSEEK, realseek_read, realseek_write, realseek_seek);
+ mm_log((1, "(%p) <- io_new_cb\n", ig));
+
+ ig->base.closecb = realseek_close;
+ ig->base.destroycb = realseek_destroy;
+
+ ig->p = p;
+ ig->readcb = readcb;
+ ig->writecb = writecb;
+ ig->seekcb = seekcb;
+ ig->closecb = closecb;
+ ig->destroycb = destroycb;
+
+ return (io_glue *)ig;
+}
+
+/*
+=item io_slurp(ig, c)
+=category I/O Layers
+
+Takes the source that the io_glue is bound to and allocates space for
+a return buffer and returns the entire content in a single buffer.
+Note: This only works for io_glue objects created by
+io_new_bufchain(). It is useful for saving to scalars and such.
+
+ ig - io_glue object
+ c - pointer to a pointer to where data should be copied to
+
+ char *data;
+ size_t size = io_slurp(ig, &data);
+ ... do something with the data ...
+ myfree(data);
+
+io_slurp() will abort the program if the supplied I/O layer is not
+from io_new_bufchain().
+
+=cut
+*/
+
+size_t
+io_slurp(io_glue *ig, unsigned char **c) {
+ ssize_t rc;
+ off_t orgoff;
+ io_ex_bchain *ieb;
+ unsigned char *cc;
+ io_type inn = ig->type;
+
+ if ( inn != BUFCHAIN ) {
+ i_fatal(0, "io_slurp: called on a source that is not from a bufchain\n");
}
+
+ ieb = ig->exdata;
+ cc = *c = mymalloc( ieb->length );
- ier->cpos += bc;
- IOL_DEB( printf("realseek_read: rc = %d, bc = %d\n", rc, bc) );
- return rc < 0 ? rc : bc;
+ orgoff = ieb->gpos;
+
+ bufchain_seek(ig, 0, SEEK_SET);
+
+ rc = bufchain_read(ig, cc, ieb->length);
+
+ if (rc != ieb->length)
+ i_fatal(1, "io_slurp: bufchain_read returned an incomplete read: rc = %d, request was %d\n", rc, ieb->length);
+
+ return rc;
+}
+
+/*
+=item io_glue_destroy(ig)
+=category I/O Layers
+=order 90
+=synopsis io_glue_destroy(ig);
+
+Destroy an io_glue objects. Should clean up all related buffers.
+
+ ig - io_glue object to destroy.
+
+=cut
+*/
+
+void
+io_glue_destroy(io_glue *ig) {
+ mm_log((1, "io_glue_DESTROY(ig %p)\n", ig));
+
+ if (ig->destroycb)
+ ig->destroycb(ig);
+
+ if (ig->buffer)
+ myfree(ig->buffer);
+
+ myfree(ig);
+}
+
+/*
+=item i_io_getc(ig)
+=category I/O Layers
+
+A macro to read a single byte from a buffered I/O glue object.
+
+Returns EOF on failure, or a byte.
+
+=cut
+*/
+
+int
+i_io_getc_imp(io_glue *ig) {
+ if (ig->write_ptr)
+ return EOF;
+
+ if (ig->error || ig->buf_eof)
+ return EOF;
+
+ if (!ig->buffered) {
+ unsigned char buf;
+ ssize_t rc = i_io_raw_read(ig, &buf, 1);
+ if (rc > 0) {
+ return buf;
+ }
+ else if (rc == 0) {
+ ig->buf_eof = 1;
+ return EOF;
+ }
+ else {
+ ig->error = 1;
+ return EOF;
+ }
+ }
+
+ if (!ig->buffer)
+ i_io_setup_buffer(ig);
+
+ if (!ig->read_ptr || ig->read_ptr == ig->read_end) {
+ if (!i_io_read_fill(ig, 1))
+ return EOF;
+ }
+
+ return *(ig->read_ptr++);
+}
+
+/*
+=item i_io_peekc(ig)
+=category I/O Layers
+
+Read the next character from the stream without advancing the stream.
+
+On error or end of file, return EOF.
+
+For unbuffered streams a single character buffer will be setup.
+
+=cut
+*/
+
+int
+i_io_peekc_imp(io_glue *ig) {
+ if (ig->write_ptr)
+ return EOF;
+
+ if (!ig->buffer)
+ i_io_setup_buffer(ig);
+
+ if (!ig->buffered) {
+ ssize_t rc = i_io_raw_read(ig, ig->buffer, 1);
+ if (rc > 0) {
+ ig->read_ptr = ig->buffer;
+ ig->read_end = ig->buffer + 1;
+ return *(ig->buffer);
+ }
+ else if (rc == 0) {
+ ig->buf_eof = 1;
+ return EOF;
+ }
+ else {
+ ig->error = 1;
+ return EOF;
+ }
+ }
+
+ if (!ig->read_ptr || ig->read_ptr == ig->read_end) {
+ if (ig->error || ig->buf_eof)
+ return EOF;
+
+ if (!i_io_read_fill(ig, 1))
+ return EOF;
+ }
+
+ return *(ig->read_ptr);
+}
+
+/*
+=item i_io_peekn(ig, buffer, size)
+=category I/O Layers
+=synopsis ssize_t count = i_io_peekn(ig, buffer, sizeof(buffer));
+
+Buffer at least C<size> (at most C<< ig->buf_size >> bytes of data
+from the stream and return C<size> bytes of it to the caller in
+C<buffer>.
+
+This ignores the buffered state of the stream, and will always setup
+buffering if needed.
+
+If no C<type> parameter is provided to Imager::read() or
+Imager::read_multi(), Imager will call C<i_io_peekn()> when probing
+for the file format.
+
+Returns -1 on error, 0 if there is no data before EOF, or the number
+of bytes read into C<buffer>.
+
+=cut
+*/
+
+ssize_t
+i_io_peekn(io_glue *ig, void *buf, size_t size) {
+ IOL_DEB(fprintf(IOL_DEBs, "i_io_peekn(%p, %p, %d)\n", ig, buf, (int)size));
+
+ if (size == 0) {
+ i_push_error(0, "peekn size must be positive");
+ IOL_DEB(fprintf(IOL_DEBs, "i_io_peekn() => -1 (zero size)\n"));
+ return -1;
+ }
+
+ if (ig->write_ptr) {
+ IOL_DEB(fprintf(IOL_DEBs, "i_io_peekn() => -1 (write_ptr set)\n"));
+ return -1;
+ }
+
+ if (!ig->buffer)
+ i_io_setup_buffer(ig);
+
+ if ((!ig->read_ptr || size > ig->read_end - ig->read_ptr)
+ && !(ig->buf_eof || ig->error)) {
+ i_io_read_fill(ig, size);
+ }
+
+ if (size > ig->read_end - ig->read_ptr)
+ size = ig->read_end - ig->read_ptr;
+
+ if (size)
+ memcpy(buf, ig->read_ptr, size);
+ else if (ig->buf_eof) {
+ IOL_DEB(fprintf(IOL_DEBs, "i_io_peekn() => 0 (eof)\n"));
+ return 0;
+ }
+ else if (ig->error) {
+ IOL_DEB(fprintf(IOL_DEBs, "i_io_peekn() => -1 (error)\n"));
+ return -1;
+ }
+ else {
+ IOL_DEB(fprintf(IOL_DEBs, "i_io_peekn() - size 0 but not eof or error!\n"));
+ return -1;
+ }
+
+ IOL_DEB(fprintf(IOL_DEBs, "i_io_peekn() => %d\n", (int)size));
+
+ return size;
+}
+
+/*
+=item i_io_putc(ig, c)
+=category I/O Layers
+
+Write a single character to the stream.
+
+On success return c, on error returns EOF
+
+=cut
+*/
+
+int
+i_io_putc_imp(io_glue *ig, int c) {
+ IOL_DEB(fprintf(IOL_DEBs, "i_io_putc_imp(%p, %d)\n", ig, c));
+
+ if (!ig->buffered) {
+ char buf = c;
+ ssize_t write_result;
+
+ if (ig->error)
+ return EOF;
+
+ write_result = i_io_raw_write(ig, &buf, 1);
+ int result = c;
+ if (write_result != 1) {
+ ig->error = 1;
+ result = EOF;
+ IOL_DEB(fprintf(IOL_DEBs, " unbuffered putc() failed, setting error mode\n"));
+ }
+ IOL_DEB(fprintf(IOL_DEBs, " unbuffered: result %d\n", result));
+
+ return result;
+ }
+
+ if (ig->read_ptr)
+ return EOF;
+
+ if (ig->error)
+ return EOF;
+
+ if (!ig->buffer)
+ i_io_setup_buffer(ig);
+
+ if (ig->write_ptr && ig->write_ptr == ig->write_end) {
+ if (!i_io_flush(ig))
+ return EOF;
+ }
+
+ i_io_start_write(ig);
+
+ *(ig->write_ptr)++ = c;
+
+ return (unsigned char)c;
+}
+
+/*
+=item i_io_read(io, buffer, size)
+=category I/O Layers
+
+Read up to C<size> bytes from the stream C<io> into C<buffer>.
+
+Returns the number of bytes read. Returns 0 on end of file. Returns
+-1 on error.
+
+=cut
+*/
+
+ssize_t
+i_io_read(io_glue *ig, void *buf, size_t size) {
+ unsigned char *pbuf = buf;
+ ssize_t read_total = 0;
+
+ IOL_DEB(fprintf(IOL_DEBs, "i_io_read(%p, %p, %u)\n", ig, buf, (unsigned)size));
+
+ if (ig->write_ptr) {
+ IOL_DEB(fprintf(IOL_DEBs, "i_io_read() => -1 (write_ptr set)\n"));
+ return -1;
+ }
+
+ if (!ig->buffer && ig->buffered)
+ i_io_setup_buffer(ig);
+
+ if (ig->read_ptr && ig->read_ptr < ig->read_end) {
+ size_t alloc = ig->read_end - ig->read_ptr;
+
+ if (alloc > size)
+ alloc = size;
+
+ memcpy(pbuf, ig->read_ptr, alloc);
+ ig->read_ptr += alloc;
+ pbuf += alloc;
+ size -= alloc;
+ read_total += alloc;
+ }
+
+ if (size > 0 && !(ig->error || ig->buf_eof)) {
+ if (!ig->buffered || size > ig->buf_size) {
+ ssize_t rc;
+
+ while (size > 0 && (rc = i_io_raw_read(ig, pbuf, size)) > 0) {
+ size -= rc;
+ pbuf += rc;
+ read_total += rc;
+ }
+
+ IOL_DEB(fprintf(IOL_DEBs, "i_io_read() => %d (raw read)\n", (int)read_total));
+
+ if (rc < 0)
+ ig->error = 1;
+ else if (rc == 0)
+ ig->buf_eof = 1;
+
+ if (!read_total)
+ return rc;
+ }
+ else {
+ if (i_io_read_fill(ig, size)) {
+ size_t alloc = ig->read_end - ig->read_ptr;
+ if (alloc > size)
+ alloc = size;
+
+ memcpy(pbuf, ig->read_ptr, alloc);
+ ig->read_ptr += alloc;
+ pbuf += alloc;
+ size -= alloc;
+ read_total += alloc;
+ }
+ else {
+ if (!read_total && ig->error) {
+ IOL_DEB(fprintf(IOL_DEBs, "i_io_read() => -1 (fill failure)\n"));
+ return -1;
+ }
+ }
+ }
+ }
+
+ if (!read_total && ig->error)
+ read_total = -1;
+
+ IOL_DEB(fprintf(IOL_DEBs, "i_io_read() => %d\n", (int)read_total));
+
+ return read_total;
+}
+
+/*
+=item i_io_write(io, buffer, size)
+=category I/O Layers
+=synopsis ssize_t result = i_io_write(io, buffer, size)
+
+Write to the given I/O stream.
+
+Returns the number of bytes written.
+
+=cut
+*/
+
+ssize_t
+i_io_write(io_glue *ig, const void *buf, size_t size) {
+ const unsigned char *pbuf = buf;
+ size_t write_count = 0;
+
+ IOL_DEB(fprintf(IOL_DEBs, "i_io_write(%p, %p, %u)\n", ig, buf, (unsigned)size));
+
+ if (!ig->buffered) {
+ ssize_t result;
+
+ if (ig->error) {
+ IOL_DEB(fprintf(IOL_DEBs, " unbuffered, error state\n"));
+ return -1;
+ }
+
+ result = i_io_raw_write(ig, buf, size);
+
+ if (result != size) {
+ ig->error = 1;
+ IOL_DEB(fprintf(IOL_DEBs, " unbuffered, setting error flag\n"));
+ }
+
+ IOL_DEB(fprintf(IOL_DEBs, " unbuffered, result: %d\n", (int)result));
+
+ return result;
+ }
+
+ if (ig->read_ptr) {
+ IOL_DEB(fprintf(IOL_DEBs, "i_io_write() => -1 (read_ptr set)\n"));
+ return -1;
+ }
+
+ if (ig->error) {
+ IOL_DEB(fprintf(IOL_DEBs, "i_io_write() => -1 (error)\n"));
+ return -1;
+ }
+
+ if (!ig->buffer)
+ i_io_setup_buffer(ig);
+
+ if (!ig->write_ptr)
+ i_io_start_write(ig);
+
+ if (ig->write_ptr && ig->write_ptr + size <= ig->write_end) {
+ size_t alloc = ig->write_end - ig->write_ptr;
+ if (alloc > size)
+ alloc = size;
+ memcpy(ig->write_ptr, pbuf, alloc);
+ write_count += alloc;
+ size -= alloc;
+ pbuf += alloc;
+ ig->write_ptr += alloc;
+ }
+
+ if (size) {
+ if (!i_io_flush(ig)) {
+ IOL_DEB(fprintf(IOL_DEBs, "i_io_write() => %d (i_io_flush failure)\n", (int)write_count));
+ return write_count ? write_count : -1;
+ }
+
+ i_io_start_write(ig);
+
+ if (size > ig->buf_size) {
+ ssize_t rc;
+ while (size > 0 && (rc = i_io_raw_write(ig, pbuf, size)) > 0) {
+ write_count += rc;
+ pbuf += rc;
+ size -= rc;
+ }
+ if (rc <= 0) {
+ ig->error = 1;
+ if (!write_count) {
+ IOL_DEB(fprintf(IOL_DEBs, "i_io_write() => -1 (direct write failure)\n"));
+ return -1;
+ }
+ }
+ }
+ else {
+ memcpy(ig->write_ptr, pbuf, size);
+ write_count += size;
+ ig->write_ptr += size;
+ }
+ }
+
+ IOL_DEB(fprintf(IOL_DEBs, "i_io_write() => %d\n", (int)write_count));
+
+ return write_count;
+}
+
+/*
+=item i_io_seek(io, offset, whence)
+=category I/O Layers
+
+Seek within the stream.
+
+Acts like perl's seek.
+
+=cut
+ */
+
+off_t
+i_io_seek(io_glue *ig, off_t offset, int whence) {
+ off_t new_off;
+
+ IOL_DEB(fprintf(IOL_DEBs, "i_io_seek(%p, %ld, %d)\n", ig, (long)offset, whence));
+
+ if (ig->write_ptr && ig->write_ptr != ig->write_end) {
+ if (!i_io_flush(ig))
+ return (off_t)(-1);
+ }
+
+ if (whence == SEEK_CUR && ig->read_ptr && ig->read_ptr != ig->read_end)
+ offset -= ig->read_end - ig->read_ptr;
+
+ ig->read_ptr = ig->read_end = NULL;
+ ig->write_ptr = ig->write_end = NULL;
+ ig->error = 0;
+ ig->buf_eof = 0;
+
+ new_off = i_io_raw_seek(ig, offset, whence);
+ if (new_off < 0)
+ ig->error = 1;
+
+ IOL_DEB(fprintf(IOL_DEBs, "i_io_seek() => %ld\n", (long)new_off));
+
+ return new_off;
+}
+
+/*
+=item i_io_flush(io)
+=category I/O Layers
+
+Flush any buffered output.
+
+Returns true on success,
+
+=cut
+*/
+
+int
+i_io_flush(io_glue *ig) {
+ unsigned char *bufp;
+
+ IOL_DEB(fprintf(IOL_DEBs, "i_io_flush(%p)\n", ig));
+
+ if (ig->error) {
+ IOL_DEB(fprintf(IOL_DEBs, "i_io_flush() => 0 (error set)\n", ig));
+ return 0;
+ }
+
+ /* nothing to do */
+ if (!ig->write_ptr)
+ return 1;
+
+ bufp = ig->buffer;
+ while (bufp < ig->write_ptr) {
+ ssize_t rc = i_io_raw_write(ig, bufp, ig->write_ptr - bufp);
+ if (rc <= 0) {
+ IOL_DEB(fprintf(IOL_DEBs, "i_io_flush() => 0 (write error)\n", ig));
+ ig->error = 1;
+ return 0;
+ }
+
+ bufp += rc;
+ }
+
+ ig->write_ptr = ig->write_end = NULL;
+
+ IOL_DEB(fprintf(IOL_DEBs, "i_io_flush() => 1\n", ig));
+
+ return 1;
+}
+
+/*
+=item i_io_close(io)
+=category I/O Layers
+
+Flush any pending output and perform the close action for the stream.
+
+Returns 0 on success.
+
+=cut
+*/
+
+int
+i_io_close(io_glue *ig) {
+ int result = 0;
+
+ IOL_DEB(fprintf(IOL_DEBs, "i_io_close(%p)\n", ig));
+ if (ig->error)
+ result = -1;
+
+ if (ig->write_ptr && !i_io_flush(ig))
+ result = -1;
+
+ if (i_io_raw_close(ig))
+ result = -1;
+
+ IOL_DEB(fprintf(IOL_DEBs, "i_io_close() => %d\n", result));
+
+ return result;
+}
+
+/*
+=item i_io_gets(ig, buffer, size, end_of_line)
+=category I/O Layers
+=synopsis char buffer[BUFSIZ]
+=synopsis ssize_t len = i_io_gets(buffer, sizeof(buffer), '\n');
+
+Read up to C<size>-1 bytes from the stream C<ig> into C<buffer>.
+
+If the byte C<end_of_line> is seen then no further bytes will be read.
+
+Returns the number of bytes read.
+
+Always C<NUL> terminates the buffer.
+
+=cut
+*/
+
+ssize_t
+i_io_gets(io_glue *ig, char *buffer, size_t size, int eol) {
+ ssize_t read_count = 0;
+ if (size < 2)
+ return 0;
+ --size; /* room for nul */
+ while (size > 0) {
+ int byte = i_io_getc(ig);
+ if (byte == EOF)
+ break;
+ *buffer++ = byte;
+ ++read_count;
+ if (byte == eol)
+ break;
+ --size;
+ }
+ *buffer++ = '\0';
+
+ return read_count;
+}
+
+/*
+=item i_io_init(ig, readcb, writecb, seekcb)
+
+Do common initialization for io_glue objects.
+
+=cut
+*/
+
+static void
+i_io_init(io_glue *ig, int type, i_io_readp_t readcb, i_io_writep_t writecb,
+ i_io_seekp_t seekcb) {
+ ig->type = type;
+ ig->exdata = NULL;
+ ig->readcb = readcb;
+ ig->writecb = writecb;
+ ig->seekcb = seekcb;
+ ig->closecb = NULL;
+ ig->sizecb = NULL;
+ ig->destroycb = NULL;
+
+ ig->buffer = NULL;
+ ig->read_ptr = NULL;
+ ig->read_end = NULL;
+ ig->write_ptr = NULL;
+ ig->write_end = NULL;
+ ig->buf_size = IO_BUF_SIZE;
+ ig->buf_eof = 0;
+ ig->error = 0;
+ ig->buffered = 1;
+}
+
+/*
+=item i_io_set_buffered(io, buffered)
+=category I/O Layers
+
+Set the buffering mode of the stream.
+
+If you switch buffering off on a stream with buffering on:
+
+=over
+
+=item *
+
+any buffered output will be flushed.
+
+=item *
+
+any existing buffered input will be consumed before reads become
+unbuffered.
+
+=back
+
+Returns true on success. This may fail if any buffered output cannot
+be flushed.
+
+=cut
+*/
+
+int
+i_io_set_buffered(io_glue *ig, int buffered) {
+ if (!buffered && ig->write_ptr) {
+ if (!i_io_flush(ig)) {
+ ig->error = 1;
+ return 0;
+ }
+ }
+ ig->buffered = buffered;
+
+ return 1;
+}
+
+/*
+=item i_io_dump(ig)
+
+Dump the base fields of an io_glue object to stdout.
+
+=cut
+*/
+void
+i_io_dump(io_glue *ig, int flags) {
+ fprintf(IOL_DEBs, "ig %p:\n", ig);
+ fprintf(IOL_DEBs, " type: %d\n", ig->type);
+ fprintf(IOL_DEBs, " exdata: %p\n", ig->exdata);
+ if (flags & I_IO_DUMP_CALLBACKS) {
+ fprintf(IOL_DEBs, " readcb: %p\n", ig->readcb);
+ fprintf(IOL_DEBs, " writecb: %p\n", ig->writecb);
+ fprintf(IOL_DEBs, " seekcb: %p\n", ig->seekcb);
+ fprintf(IOL_DEBs, " closecb: %p\n", ig->closecb);
+ fprintf(IOL_DEBs, " sizecb: %p\n", ig->sizecb);
+ }
+ if (flags & I_IO_DUMP_BUFFER) {
+ fprintf(IOL_DEBs, " buffer: %p\n", ig->buffer);
+ fprintf(IOL_DEBs, " read_ptr: %p\n", ig->read_ptr);
+ if (ig->read_ptr) {
+ fprintf(IOL_DEBs, " ");
+ dump_data(ig->read_ptr, ig->read_end, 0);
+ putc('\n', IOL_DEBs);
+ }
+ fprintf(IOL_DEBs, " read_end: %p\n", ig->read_end);
+ fprintf(IOL_DEBs, " write_ptr: %p\n", ig->write_ptr);
+ if (ig->write_ptr) {
+ fprintf(IOL_DEBs, " ");
+ dump_data(ig->buffer, ig->write_ptr, 1);
+ putc('\n', IOL_DEBs);
+ }
+ fprintf(IOL_DEBs, " write_end: %p\n", ig->write_end);
+ fprintf(IOL_DEBs, " buf_size: %u\n", (unsigned)(ig->buf_size));
+ }
+ if (flags & I_IO_DUMP_STATUS) {
+ fprintf(IOL_DEBs, " buf_eof: %d\n", ig->buf_eof);
+ fprintf(IOL_DEBs, " error: %d\n", ig->error);
+ fprintf(IOL_DEBs, " buffered: %d\n", ig->buffered);
+ }
+}
+
+/*
+=back
+
+=head1 INTERNAL FUNCTIONS
+
+=over
+
+=item my_strerror
+
+Calls strerror() and ensures we don't return NULL.
+
+On some platforms it's possible for strerror() to return NULL, this
+wrapper ensures we only get non-NULL values.
+
+=cut
+*/
+
+static
+const char *my_strerror(int err) {
+ const char *result = strerror(err);
+
+ if (!result)
+ result = "Unknown error";
+
+ return result;
+}
+
+static void
+i_io_setup_buffer(io_glue *ig) {
+ ig->buffer = mymalloc(ig->buf_size);
+}
+
+static void
+i_io_start_write(io_glue *ig) {
+ ig->write_ptr = ig->buffer;
+ ig->write_end = ig->buffer + ig->buf_size;
+}
+
+static int
+i_io_read_fill(io_glue *ig, ssize_t needed) {
+ unsigned char *buf_end = ig->buffer + ig->buf_size;
+ unsigned char *buf_start = ig->buffer;
+ unsigned char *work = ig->buffer;
+ ssize_t rc;
+ int good = 0;
+
+ IOL_DEB(fprintf(IOL_DEBs, "i_io_read_fill(%p, %d)\n", ig, (int)needed));
+
+ /* these conditions may be unused, callers should also be checking them */
+ if (ig->error || ig->buf_eof)
+ return 0;
+
+ if (needed > ig->buf_size)
+ needed = ig->buf_size;
+
+ if (ig->read_ptr && ig->read_ptr < ig->read_end) {
+ size_t kept = ig->read_end - ig->read_ptr;
+
+ if (needed < kept) {
+ IOL_DEB(fprintf(IOL_DEBs, "i_io_read_fill(%u) -> 1 (already have enough)\n", (unsigned)needed));
+ return 1;
+ }
+
+ if (ig->read_ptr != ig->buffer)
+ memmove(ig->buffer, ig->read_ptr, kept);
+
+ good = 1; /* we have *something* available to read */
+ work = buf_start + kept;
+ needed -= kept;
+ }
+ else {
+ work = ig->buffer;
+ }
+
+ while (work < buf_end && (rc = i_io_raw_read(ig, work, buf_end - work)) > 0) {
+ work += rc;
+ good = 1;
+ if (needed < rc)
+ break;
+
+ needed -= rc;
+ }
+
+ if (rc < 0) {
+ ig->error = 1;
+ IOL_DEB(fprintf(IOL_DEBs, " i_io_read_fill -> rc %d, setting error\n",
+ (int)rc));
+ }
+ else if (rc == 0) {
+ ig->buf_eof = 1;
+ IOL_DEB(fprintf(IOL_DEBs, " i_io_read_fill -> rc 0, setting eof\n"));
+ }
+
+ if (good) {
+ ig->read_ptr = buf_start;
+ ig->read_end = work;
+ }
+
+ IOL_DEB(fprintf(IOL_DEBs, "i_io_read_fill => %d, %u buffered\n", good,
+ (unsigned)(ig->read_end - ig->read_ptr)));
+ return good;
+}
+
+/*
+=item dump_data(start, end, bias)
+
+Hex dump the data between C<start> and C<end>.
+
+If there is more than a pleasing amount of data, either dump the
+beginning (C<bias == 0>) or dump the end C(<bias != 0>) of the range.
+
+=cut
+*/
+
+static void
+dump_data(unsigned char *start, unsigned char *end, int bias) {
+ unsigned char *p;
+ size_t count = end - start;
+
+ if (start == end) {
+ fprintf(IOL_DEBs, "(empty)");
+ return;
+ }
+
+ if (count > 15) {
+ if (bias) {
+ fprintf(IOL_DEBs, "... ");
+ start = end - 14;
+ }
+ else {
+ end = start + 14;
+ }
+
+ for (p = start; p < end; ++p) {
+ fprintf(IOL_DEBs, " %02x", *p);
+ }
+ putc(' ', IOL_DEBs);
+ putc('<', IOL_DEBs);
+ for (p = start; p < end; ++p) {
+ if (*p < ' ' || *p > '~')
+ putc('.', IOL_DEBs);
+ else
+ putc(*p, IOL_DEBs);
+ }
+ putc('>', IOL_DEBs);
+ if (!bias)
+ fprintf(IOL_DEBs, " ...");
+ }
+ else {
+ for (p = start; p < end; ++p) {
+ fprintf(IOL_DEBs, " %02x", *p);
+ }
+ putc(' ', IOL_DEBs);
+ for (p = start; p < end; ++p) {
+ if (*p < ' ' || *p > '~')
+ putc('.', IOL_DEBs);
+ else
+ putc(*p, IOL_DEBs);
+ }
+ }
+}
+
+/*
+ * Callbacks for sources that cannot seek
+ */
+
+/*
+ * Callbacks for sources that can seek
+ */
+
+/*
+=item realseek_read(ig, buf, count)
+
+Does the reading from a source that can be seeked on
+
+ ig - io_glue object
+ buf - buffer to return data in
+ count - number of bytes to read into buffer max
+
+=cut
+*/
+
+static
+ssize_t
+realseek_read(io_glue *igo, void *buf, size_t count) {
+ io_cb *ig = (io_cb *)igo;
+ void *p = ig->p;
+ ssize_t rc = 0;
+
+ IOL_DEB( fprintf(IOL_DEBs, "realseek_read: buf = %p, count = %u\n",
+ buf, (unsigned)count) );
+ rc = ig->readcb(p,buf,count);
+
+ IOL_DEB( fprintf(IOL_DEBs, "realseek_read: rc = %d\n", (int)rc) );
+
+ return rc;
}
static
ssize_t
-realseek_write(io_glue *ig, const void *buf, size_t count) {
- io_ex_rseek *ier = ig->exdata;
- void *p = ig->source.cb.p;
+realseek_write(io_glue *igo, const void *buf, size_t count) {
+ io_cb *ig = (io_cb *)igo;
+ void *p = ig->p;
ssize_t rc = 0;
size_t bc = 0;
char *cbuf = (char*)buf;
- IOL_DEB( printf("realseek_write: ig = %p, ier->cpos = %ld, buf = %p, "
- "count = %d\n", ig, (long) ier->cpos, buf, count) );
+ IOL_DEB( fprintf(IOL_DEBs, "realseek_write: ig = %p, buf = %p, "
+ "count = %u\n", ig, buf, (unsigned)count) );
/* 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 ) {
+ while( count!=bc && (rc = ig->writecb(p,cbuf+bc,count-bc))>0 ) {
bc+=rc;
}
- ier->cpos += bc;
- IOL_DEB( printf("realseek_write: rc = %d, bc = %d\n", rc, bc) );
+ IOL_DEB( fprintf(IOL_DEBs, "realseek_write: rc = %d, bc = %u\n", (int)rc, (unsigned)bc) );
return rc < 0 ? rc : bc;
}
static
int
-realseek_close(io_glue *ig) {
+realseek_close(io_glue *igo) {
+ io_cb *ig = (io_cb *)igo;
+
+ IOL_DEB(fprintf(IOL_DEBs, "realseek_close(%p)\n", ig));
mm_log((1, "realseek_close(ig %p)\n", ig));
- if (ig->source.cb.closecb)
- return ig->source.cb.closecb(ig->source.cb.p);
+ if (ig->closecb)
+ return ig->closecb(ig->p);
else
return 0;
}
static
off_t
-realseek_seek(io_glue *ig, off_t offset, int whence) {
- /* io_ex_rseek *ier = ig->exdata; Needed later */
- void *p = ig->source.cb.p;
+realseek_seek(io_glue *igo, off_t offset, int whence) {
+ io_cb *ig = (io_cb *)igo;
+ void *p = ig->p;
off_t rc;
- IOL_DEB( printf("realseek_seek(ig %p, offset %ld, whence %d)\n", ig, (long) offset, whence) );
- rc = ig->source.cb.seekcb(p, offset, whence);
+ IOL_DEB( fprintf(IOL_DEBs, "realseek_seek(ig %p, offset %ld, whence %d)\n", ig, (long) offset, whence) );
+ rc = ig->seekcb(p, offset, whence);
- IOL_DEB( printf("realseek_seek: rc %ld\n", (long) rc) );
+ IOL_DEB( fprintf(IOL_DEBs, "realseek_seek: rc %ld\n", (long) rc) );
return rc;
/* FIXME: How about implementing this offset handling stuff? */
}
static
void
-realseek_destroy(io_glue *ig) {
- io_ex_rseek *ier = ig->exdata;
+realseek_destroy(io_glue *igo) {
+ io_cb *ig = (io_cb *)igo;
- if (ig->source.cb.destroycb)
- ig->source.cb.destroycb(ig->source.cb.p);
-
- myfree(ier);
+ if (ig->destroycb)
+ ig->destroycb(ig->p);
}
/*
static
ssize_t
-buffer_read(io_glue *ig, void *buf, size_t count) {
- io_ex_buffer *ieb = ig->exdata;
+buffer_read(io_glue *igo, void *buf, size_t count) {
+ io_buffer *ig = (io_buffer *)igo;
- IOL_DEB( printf("buffer_read: ieb->cpos = %ld, buf = %p, count = %d\n", (long) ieb->cpos, buf, count) );
+ IOL_DEB( fprintf(IOL_DEBs, "buffer_read: ig->cpos = %ld, buf = %p, count = %u\n", (long) ig->cpos, buf, (unsigned)count) );
- if ( ieb->cpos+count > ig->source.buffer.len ) {
- mm_log((1,"buffer_read: short read: cpos=%ld, len=%ld, count=%ld\n", (long)ieb->cpos, (long)ig->source.buffer.len, (long)count));
- count = ig->source.buffer.len - ieb->cpos;
+ if ( ig->cpos+count > ig->len ) {
+ mm_log((1,"buffer_read: short read: cpos=%ld, len=%ld, count=%ld\n", (long)ig->cpos, (long)ig->len, (long)count));
+ count = ig->len - ig->cpos;
}
- memcpy(buf, ig->source.buffer.data+ieb->cpos, count);
- ieb->cpos += count;
- IOL_DEB( printf("buffer_read: count = %ld\n", (long)count) );
+ memcpy(buf, ig->data+ig->cpos, count);
+ ig->cpos += count;
+ IOL_DEB( fprintf(IOL_DEBs, "buffer_read: count = %ld\n", (long)count) );
return count;
}
static
off_t
-buffer_seek(io_glue *ig, off_t offset, int whence) {
- io_ex_buffer *ieb = ig->exdata;
+buffer_seek(io_glue *igo, off_t offset, int whence) {
+ io_buffer *ig = (io_buffer *)igo;
off_t reqpos =
- calc_seek_offset(ieb->cpos, ig->source.buffer.len, offset, whence);
+ calc_seek_offset(ig->cpos, ig->len, offset, whence);
- if (reqpos > ig->source.buffer.len) {
+ if (reqpos > ig->len) {
mm_log((1, "seeking out of readable range\n"));
return (off_t)-1;
}
return (off_t)-1;
}
- ieb->cpos = reqpos;
- IOL_DEB( printf("buffer_seek(ig %p, offset %ld, whence %d)\n", ig, (long) offset, whence) );
+ ig->cpos = reqpos;
+ IOL_DEB( fprintf(IOL_DEBs, "buffer_seek(ig %p, offset %ld, whence %d)\n", ig, (long) offset, whence) );
return reqpos;
/* FIXME: How about implementing this offset handling stuff? */
static
void
-buffer_destroy(io_glue *ig) {
- io_ex_buffer *ieb = ig->exdata;
+buffer_destroy(io_glue *igo) {
+ io_buffer *ig = (io_buffer *)igo;
- if (ig->source.buffer.closecb) {
+ if (ig->closecb) {
mm_log((1,"calling close callback %p for io_buffer\n",
- ig->source.buffer.closecb));
- ig->source.buffer.closecb(ig->source.buffer.closedata);
+ ig->closecb));
+ ig->closecb(ig->closedata);
}
- myfree(ieb);
}
=cut
*/
-void
+static void
io_destroy_bufchain(io_ex_bchain *ieb) {
io_blink *cp;
mm_log((1, "io_destroy_bufchain(ieb %p)\n", ieb));
}
*/
-
-
-
-
-
-
-
-
-
-
/*
=item bufchain_read(ig, buf, count)
mm_log((1, "bufchain_write: ig = %p, buf = %p, count = %ld\n", ig, buf, (long)count));
- IOL_DEB( printf("bufchain_write: ig = %p, ieb->cpos = %ld, buf = %p, count = %ld\n", ig, (long) ieb->cpos, buf, (long)count) );
+ IOL_DEB( fprintf(IOL_DEBs, "bufchain_write: ig = %p, ieb->cpos = %ld, buf = %p, count = %ld\n", ig, (long) ieb->cpos, buf, (long)count) );
while(count) {
mm_log((2, "bufchain_write: - looping - count = %ld\n", (long)count));
int
bufchain_close(io_glue *ig) {
mm_log((1, "bufchain_close(ig %p)\n",ig));
- IOL_DEB( printf("bufchain_close(ig %p)\n", ig) );
+ IOL_DEB( fprintf(IOL_DEBs, "bufchain_close(ig %p)\n", ig) );
return 0;
}
myfree(ieb);
}
-/*
- * Methods for setting up data source
- */
-
-/*
-=item io_obj_setp_buffer(io, p, len)
-
-Sets an io_object for reading from a buffer source
-
- io - io object that describes a source
- p - pointer to buffer
- len - length of buffer
-
-=cut
-*/
-
-static void
-io_obj_setp_buffer(io_obj *io, char *p, size_t len, i_io_closebufp_t closecb,
- void *closedata) {
- io->buffer.type = BUFFER;
- io->buffer.data = p;
- io->buffer.len = len;
- io->buffer.closecb = closecb;
- io->buffer.closedata = closedata;
-}
-
-
-
-/*
-=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
- closecb - flush any pending data
- destroycb - release any extra resources
-
-=cut
-*/
-
-static void
-io_obj_setp_cb2(io_obj *io, void *p, i_io_readl_t readcb, i_io_writel_t writecb, i_io_seekl_t seekcb, i_io_closel_t closecb, i_io_destroyl_t 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;
-}
-
-/*
-=item io_glue_commit_types(ig)
-
-This is now effectively a no-op.
-
-=cut
-*/
-
-void
-io_glue_commit_types(io_glue *ig) {
- io_type inn = ig->source.type;
-
- mm_log((1, "io_glue_commit_types(ig %p)\n", ig));
- mm_log((1, "io_glue_commit_types: source type %d (%s)\n", inn, io_type_names[inn]));
-
- if (ig->flags & 0x01) {
- mm_log((1, "io_glue_commit_types: type already set up\n"));
- return;
- }
-
- ig->flags |= 0x01; /* indicate source has been setup already */
-}
-
-/*
-=item io_glue_gettypes(ig, reqmeth)
-
-Returns a set of compatible interfaces to read data with.
-
- ig - io_glue object
- reqmeth - request mask
-
-The request mask is a bit mask (of something that hasn't been implemented yet)
-of interfaces that it would like to read data from the source which the ig
-describes.
-
-=cut
-*/
-
-void
-io_glue_gettypes(io_glue *ig, int reqmeth) {
-
- ig = NULL;
- reqmeth = 0;
-
- /* FIXME: Implement this function! */
- /* if (ig->source.type =
- if (reqmeth & IO_BUFF) */
-
-}
-
-
-/*
-=item io_new_bufchain()
-
-returns a new io_glue object that has the 'empty' source and but can
-be written to and read from later (like a pseudo file).
-
-=cut
-*/
-
-io_glue *
-io_new_bufchain() {
- io_glue *ig;
- io_ex_bchain *ieb = mymalloc(sizeof(io_ex_bchain));
-
- mm_log((1, "io_new_bufchain()\n"));
-
- ig = mymalloc(sizeof(io_glue));
- memset(ig, 0, sizeof(*ig));
- ig->source.type = BUFCHAIN;
-
- ieb->offset = 0;
- ieb->length = 0;
- ieb->cpos = 0;
- ieb->gpos = 0;
- ieb->tfill = 0;
-
- ieb->head = io_blink_new();
- ieb->cp = ieb->head;
- ieb->tail = ieb->head;
-
- ig->exdata = ieb;
- ig->readcb = bufchain_read;
- ig->writecb = bufchain_write;
- ig->seekcb = bufchain_seek;
- ig->closecb = bufchain_close;
- ig->destroycb = bufchain_destroy;
-
- return ig;
-}
-
-/*
-=item io_new_buffer(data, len)
-
-Returns a new io_glue object that has the source defined as reading
-from specified buffer. Note that the buffer is not copied.
-
- data - buffer to read from
- len - length of buffer
-
-=cut
-*/
-
-io_glue *
-io_new_buffer(char *data, size_t len, i_io_closebufp_t closecb, void *closedata) {
- io_glue *ig;
- io_ex_buffer *ieb = mymalloc(sizeof(io_ex_buffer));
-
- mm_log((1, "io_new_buffer(data %p, len %ld, closecb %p, closedata %p)\n", data, (long)len, closecb, closedata));
-
- ig = mymalloc(sizeof(io_glue));
- memset(ig, 0, sizeof(*ig));
- io_obj_setp_buffer(&ig->source, data, len, closecb, closedata);
- ig->flags = 0;
-
- ieb->offset = 0;
- ieb->cpos = 0;
-
- ig->exdata = ieb;
- ig->readcb = buffer_read;
- ig->writecb = buffer_write;
- ig->seekcb = buffer_seek;
- ig->closecb = buffer_close;
- ig->destroycb = buffer_destroy;
-
- return ig;
-}
-
-
-/*
-=item io_new_fd(fd)
-
-returns a new io_glue object that has the source defined as reading
-from specified filedescriptor. Note that the the interface to recieving
-data from the io_glue callbacks hasn't been done yet.
-
- fd - file descriptor to read/write from
-
-=cut
-*/
-
-io_glue *
-io_new_fd(int fd) {
- io_glue *ig;
-
- 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;
- ig->flags = 0;
-
- ig->exdata = NULL;
- ig->readcb = fd_read;
- ig->writecb = fd_write;
- ig->seekcb = fd_seek;
- ig->closecb = fd_close;
- ig->sizecb = fd_size;
- ig->destroycb = NULL;
-
- mm_log((1, "(%p) <- io_new_fd\n", ig));
- return ig;
-}
-
-io_glue *io_new_cb(void *p, i_io_readl_t readcb, i_io_writel_t writecb,
- i_io_seekl_t seekcb, i_io_closel_t closecb,
- i_io_destroyl_t destroycb) {
- io_glue *ig;
- io_ex_rseek *ier = mymalloc(sizeof(io_ex_rseek));
-
- 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));
-
- ier->offset = 0;
- ier->cpos = 0;
-
- ig->exdata = ier;
- ig->readcb = realseek_read;
- ig->writecb = realseek_write;
- ig->seekcb = realseek_seek;
- ig->closecb = realseek_close;
- ig->destroycb = realseek_destroy;
-
- return ig;
-}
-
-/*
-=item io_slurp(ig)
-
-Takes the source that the io_glue is bound to and allocates space
-for a return buffer and returns the entire content in a single buffer.
-Note: This only works for io_glue objects that contain a bufchain. It
-is usefull for saving to scalars and such.
-
- ig - io_glue object
- c - pointer to a pointer to where data should be copied to
-
-=cut
-*/
-
-size_t
-io_slurp(io_glue *ig, unsigned char **c) {
- ssize_t rc;
- off_t orgoff;
- io_ex_bchain *ieb;
- unsigned char *cc;
- io_type inn = ig->source.type;
-
- if ( inn != BUFCHAIN ) {
- i_fatal(0, "io_slurp: called on a source that is not from a bufchain\n");
- }
-
- ieb = ig->exdata;
- cc = *c = mymalloc( ieb->length );
-
- orgoff = ieb->gpos;
-
- bufchain_seek(ig, 0, SEEK_SET);
-
- rc = bufchain_read(ig, cc, ieb->length);
-
- if (rc != ieb->length)
- i_fatal(1, "io_slurp: bufchain_read returned an incomplete read: rc = %d, request was %d\n", rc, ieb->length);
-
- return rc;
-}
-
/*
=item fd_read(ig, buf, count)
+Read callback for file descriptor IO objects.
+
=cut
*/
-static ssize_t fd_read(io_glue *ig, void *buf, size_t count) {
+static ssize_t fd_read(io_glue *igo, void *buf, size_t count) {
+ io_fdseek *ig = (io_fdseek *)igo;
ssize_t result;
#ifdef _MSC_VER
- result = _read(ig->source.fdseek.fd, buf, count);
+ result = _read(ig->fd, buf, count);
#else
- result = read(ig->source.fdseek.fd, buf, count);
+ result = read(ig->fd, buf, count);
#endif
+ IOL_DEB(fprintf(IOL_DEBs, "fd_read(%p, %p, %u) => %d\n", ig, buf,
+ (unsigned)count, (int)result));
+
/* 0 is valid - means EOF */
if (result < 0) {
i_push_errorf(0, "read() failure: %s (%d)", my_strerror(errno), errno);
return result;
}
-static ssize_t fd_write(io_glue *ig, const void *buf, size_t count) {
+static ssize_t fd_write(io_glue *igo, const void *buf, size_t count) {
+ io_fdseek *ig = (io_fdseek *)igo;
ssize_t result;
#ifdef _MSC_VER
- result = _write(ig->source.fdseek.fd, buf, count);
+ result = _write(ig->fd, buf, count);
#else
- result = write(ig->source.fdseek.fd, buf, count);
+ result = write(ig->fd, buf, count);
#endif
+ IOL_DEB(fprintf(IOL_DEBs, "fd_write(%p, %p, %u) => %d\n", ig, buf,
+ (unsigned)count, (int)result));
+
if (result <= 0) {
i_push_errorf(errno, "write() failure: %s (%d)", my_strerror(errno), errno);
}
return result;
}
-static off_t fd_seek(io_glue *ig, off_t offset, int whence) {
+static off_t fd_seek(io_glue *igo, off_t offset, int whence) {
+ io_fdseek *ig = (io_fdseek *)igo;
off_t result;
#ifdef _MSC_VER
- result = _lseek(ig->source.fdseek.fd, offset, whence);
+ result = _lseek(ig->fd, offset, whence);
#else
- result = lseek(ig->source.fdseek.fd, offset, whence);
+ result = lseek(ig->fd, offset, whence);
#endif
if (result == (off_t)-1) {
return -1;
}
-/*
-=item io_glue_destroy(ig)
-
-A destructor method for io_glue objects. Should clean up all related buffers.
-Might leave us with a dangling pointer issue on some buffers.
-
- ig - io_glue object to destroy.
-
-=cut
-*/
-
-void
-io_glue_destroy(io_glue *ig) {
- mm_log((1, "io_glue_DESTROY(ig %p)\n", ig));
-
- if (ig->destroycb)
- ig->destroycb(ig);
-
- myfree(ig);
-}
-
-/*
-=back
-
-=head1 INTERNAL FUNCTIONS
-
-=over
-
-=item my_strerror
-
-Calls strerror() and ensures we don't return NULL.
-
-On some platforms it's possible for strerror() to return NULL, this
-wrapper ensures we only get non-NULL values.
-
-=cut
-*/
-
-static
-const char *my_strerror(int err) {
- const char *result = strerror(err);
-
- if (!result)
- result = "Unknown error";
-
- return result;
-}
/*
=back
/* How the IO layer works:
*
- * Start by getting an io_glue object. Then define its
- * datasource via io_obj_setp_buffer or io_obj_setp_cb. Before
- * using the io_glue object be sure to call io_glue_commit_types().
- * After that data can be read via the io_glue->readcb() method.
+ * Start by getting an io_glue object by calling the appropriate
+ * io_new...() function. After that data can be read via the
+ * io_glue->readcb() method.
*
*/
#define IO_TEMP_SEEK 1<<1L
-void io_glue_commit_types(io_glue *ig);
void io_glue_gettypes (io_glue *ig, int reqmeth);
/* XS functions */
io_glue *io_new_fd(int fd);
io_glue *io_new_bufchain(void);
-io_glue *io_new_buffer(char *data, size_t len, i_io_closebufp_t closecb, void *closedata);
+io_glue *io_new_buffer(const char *data, size_t len, i_io_closebufp_t closecb, void *closedata);
io_glue *io_new_cb(void *p, i_io_readl_t readcb, i_io_writel_t writecb, i_io_seekl_t seekcb, i_io_closel_t closecb, i_io_destroyl_t destroycb);
size_t io_slurp(io_glue *ig, unsigned char **c);
void io_glue_destroy(io_glue *ig);
+void i_io_dump(io_glue *ig, int flags);
+
+/* Buffered I/O */
+extern int i_io_getc_imp(io_glue *ig);
+extern int i_io_peekc_imp(io_glue *ig);
+extern ssize_t i_io_peekn(io_glue *ig, void *buf, size_t size);
+extern int i_io_putc_imp(io_glue *ig, int c);
+extern ssize_t i_io_read(io_glue *ig, void *buf, size_t size);
+extern ssize_t i_io_write(io_glue *ig, const void *buf, size_t size);
+extern off_t i_io_seek(io_glue *ig, off_t offset, int whence);
+extern int i_io_flush(io_glue *ig);
+extern int i_io_close(io_glue *ig);
+extern int i_io_set_buffered(io_glue *ig, int buffered);
+extern ssize_t i_io_gets(io_glue *ig, char *, size_t, int);
+
#endif /* _IOLAYER_H_ */
/* Structures to describe data sources */
-typedef struct {
- io_type type;
- int fd;
-} io_fdseek;
-
-typedef struct {
- io_type type; /* Must be first parameter */
- char *name; /* Data source name */
- char *data;
- size_t len;
- i_io_closebufp_t closecb; /* free memory mapped segment or decrement refcount */
- void *closedata;
-} io_buffer;
-
-typedef struct {
- io_type type; /* Must be first parameter */
- char *name; /* Data source name */
- void *p; /* Callback data */
- i_io_readl_t readcb;
- i_io_writel_t writecb;
- i_io_seekl_t seekcb;
- i_io_closel_t closecb;
- i_io_destroyl_t destroycb;
-} io_cb;
-
-typedef union {
- io_type type;
- io_fdseek fdseek;
- io_buffer buffer;
- io_cb cb;
-} io_obj;
-
struct i_io_glue_t {
- io_obj source;
- int flags; /* Flags */
- void *exdata; /* Pair specific data */
+ io_type type;
+ void *exdata;
i_io_readp_t readcb;
i_io_writep_t writecb;
i_io_seekp_t seekcb;
i_io_closep_t closecb;
i_io_sizep_t sizecb;
i_io_destroyp_t destroycb;
+ unsigned char *buffer;
+ unsigned char *read_ptr;
+ unsigned char *read_end;
+ unsigned char *write_ptr;
+ unsigned char *write_end;
+ size_t buf_size;
+
+ /* non-zero if we encountered EOF */
+ int buf_eof;
+
+ /* non-zero if we've seen an error */
+ int error;
+
+ /* if non-zero we do write buffering (enabled by default) */
+ int buffered;
};
-#define i_io_type(ig) ((ig)->source.ig_type)
-#define i_io_read(ig, buf, size) ((ig)->readcb((ig), (buf), (size)))
-#define i_io_write(ig, data, size) ((ig)->writecb((ig), (data), (size)))
-#define i_io_seek(ig, offset, whence) ((ig)->seekcb((ig), (offset), (whence)))
-#define i_io_close(ig) ((ig)->closecb(ig))
+#define I_IO_DUMP_CALLBACKS 1
+#define I_IO_DUMP_BUFFER 2
+#define I_IO_DUMP_STATUS 4
+#define I_IO_DUMP_DEFAULT (I_IO_DUMP_BUFFER | I_IO_DUMP_STATUS)
+#define i_io_type(ig) ((ig)->source.ig_type)
+#define i_io_raw_read(ig, buf, size) ((ig)->readcb((ig), (buf), (size)))
+#define i_io_raw_write(ig, data, size) ((ig)->writecb((ig), (data), (size)))
+#define i_io_raw_seek(ig, offset, whence) ((ig)->seekcb((ig), (offset), (whence)))
+#define i_io_raw_close(ig) ((ig)->closecb(ig))
+#define i_io_is_buffered(ig) ((int)((ig)->buffered))
+
+#define i_io_getc(ig) \
+ ((ig)->read_ptr < (ig)->read_end ? \
+ *((ig)->read_ptr++) : \
+ i_io_getc_imp(ig))
+#define i_io_peekc(ig) \
+ ((ig)->read_ptr < (ig)->read_end ? \
+ *((ig)->read_ptr) : \
+ i_io_peekc_imp(ig))
+#define i_io_putc(ig, c) \
+ ((ig)->write_ptr < (ig)->write_end && !(ig)->error ? \
+ *(ig)->write_ptr++ = (c) : \
+ i_io_putc_imp(ig, (c)))
+#define i_io_eof(ig) \
+ ((ig)->read_ptr == (ig)->read_end && (ig)->buf_eof)
+#define i_io_error(ig) \
+ ((ig)->read_ptr == (ig)->read_end && (ig)->error)
#endif
i_fr_triangle, 0, i_fts_grid, 9, 1, segs);
i_fill_destroy(fill);
+ # I/O Layers
+ ssize_t count = i_io_peekn(ig, buffer, sizeof(buffer));
+ ssize_t result = i_io_write(io, buffer, size)
+ char buffer[BUFSIZ]
+ ssize_t len = i_io_gets(buffer, sizeof(buffer), '\n');
+ io_glue_destroy(ig);
+
# Image
# Image creation/destruction
From: File fills.c
+=back
+
+=head2 I/O Layers
+
+=over
+
+=item io_new_bufchain()
+
+returns a new io_glue object that has the 'empty' source and but can
+be written to and read from later (like a pseudo file).
+
+
+=for comment
+From: File iolayer.c
+
+=item io_new_buffer(data, length)
+
+Returns a new io_glue object that has the source defined as reading
+from specified buffer. Note that the buffer is not copied.
+
+ data - buffer to read from
+ length - length of buffer
+
+
+=for comment
+From: File iolayer.c
+
+=item io_new_cb(p, read_cb, write_cb, seek_cb, close_cb, destroy_cb)
+
+Create a new I/O layer object that calls your supplied callbacks.
+
+In general the callbacks should behave like the corresponding POSIX
+primitives.
+
+=over
+
+=item *
+
+C<read_cb>(p, buffer, length) should read up to C<length> bytes into
+C<buffer> and return the number of bytes read. At end of file, return
+0. On error, return -1.
+
+=item *
+
+C<write_cb>(p, buffer, length) should write up to C<length> bytes from
+C<buffer> and return the number of bytes written. A return value <= 0
+will be treated as an error.
+
+=item *
+
+C<seekcb>(p, offset, whence) should seek and return the new offset.
+
+=item *
+
+C<close_cb>(p) should return 0 on success, -1 on failure.
+
+=item *
+
+C<destroy_cb>(p) should release any memory specific to your callback
+handlers.
+
+=back
+
+
+=for comment
+From: File iolayer.c
+
+=item io_new_fd(fd)
+
+returns a new io_glue object that has the source defined as reading
+from specified file descriptor. Note that the the interface to receiving
+data from the io_glue callbacks hasn't been done yet.
+
+ fd - file descriptor to read/write from
+
+
+=for comment
+From: File iolayer.c
+
+=item i_io_close(io)
+
+Flush any pending output and perform the close action for the stream.
+
+Returns 0 on success.
+
+
+=for comment
+From: File iolayer.c
+
+=item i_io_flush(io)
+
+Flush any buffered output.
+
+Returns true on success,
+
+
+=for comment
+From: File iolayer.c
+
+=item i_io_getc(ig)
+
+A macro to read a single byte from a buffered I/O glue object.
+
+Returns EOF on failure, or a byte.
+
+
+=for comment
+From: File iolayer.c
+
+=item i_io_gets(ig, buffer, size, end_of_line)
+
+ char buffer[BUFSIZ]
+ ssize_t len = i_io_gets(buffer, sizeof(buffer), '\n');
+
+Read up to C<size>-1 bytes from the stream C<ig> into C<buffer>.
+
+If the byte C<end_of_line> is seen then no further bytes will be read.
+
+Returns the number of bytes read.
+
+Always C<NUL> terminates the buffer.
+
+
+=for comment
+From: File iolayer.c
+
+=item i_io_peekc(ig)
+
+Read the next character from the stream without advancing the stream.
+
+On error or end of file, return EOF.
+
+For unbuffered streams a single character buffer will be setup.
+
+
+=for comment
+From: File iolayer.c
+
+=item i_io_peekn(ig, buffer, size)
+
+ ssize_t count = i_io_peekn(ig, buffer, sizeof(buffer));
+
+Buffer at least C<size> (at most C<< ig->buf_size >> bytes of data
+from the stream and return C<size> bytes of it to the caller in
+C<buffer>.
+
+This ignores the buffered state of the stream, and will always setup
+buffering if needed.
+
+If no C<type> parameter is provided to Imager::read() or
+Imager::read_multi(), Imager will call C<i_io_peekn()> when probing
+for the file format.
+
+Returns -1 on error, 0 if there is no data before EOF, or the number
+of bytes read into C<buffer>.
+
+
+=for comment
+From: File iolayer.c
+
+=item i_io_putc(ig, c)
+
+Write a single character to the stream.
+
+On success return c, on error returns EOF
+
+
+=for comment
+From: File iolayer.c
+
+=item i_io_read(io, buffer, size)
+
+Read up to C<size> bytes from the stream C<io> into C<buffer>.
+
+Returns the number of bytes read. Returns 0 on end of file. Returns
+-1 on error.
+
+
+=for comment
+From: File iolayer.c
+
+=item i_io_seek(io, offset, whence)
+
+Seek within the stream.
+
+Acts like perl's seek.
+
+
+=for comment
+From: File iolayer.c
+
+=item i_io_set_buffered(io, buffered)
+
+Set the buffering mode of the stream.
+
+If you switch buffering off on a stream with buffering on:
+
+=over
+
+=item *
+
+any buffered output will be flushed.
+
+=item *
+
+any existing buffered input will be consumed before reads become
+unbuffered.
+
+=back
+
+Returns true on success. This may fail if any buffered output cannot
+be flushed.
+
+
+=for comment
+From: File iolayer.c
+
+=item i_io_write(io, buffer, size)
+
+ ssize_t result = i_io_write(io, buffer, size)
+
+Write to the given I/O stream.
+
+Returns the number of bytes written.
+
+
+=for comment
+From: File iolayer.c
+
+=item io_slurp(ig, c)
+
+Takes the source that the io_glue is bound to and allocates space for
+a return buffer and returns the entire content in a single buffer.
+Note: This only works for io_glue objects created by
+io_new_bufchain(). It is useful for saving to scalars and such.
+
+ ig - io_glue object
+ c - pointer to a pointer to where data should be copied to
+
+ char *data;
+ size_t size = io_slurp(ig, &data);
+ ... do something with the data ...
+ myfree(data);
+
+io_slurp() will abort the program if the supplied I/O layer is not
+from io_new_bufchain().
+
+
+=for comment
+From: File iolayer.c
+
+=item io_glue_destroy(ig)
+
+ io_glue_destroy(ig);
+
+Destroy an io_glue objects. Should clean up all related buffers.
+
+ ig - io_glue object to destroy.
+
+
+=for comment
+From: File iolayer.c
+
+
=back
=head2 Image
=over
-=item read
+=item read()
Reading writing to and from files is simple, use the C<read()>
method to read an image:
$img->read(file => $filename)
or die "Cannot read $filename: ", $img->errstr;
-The read() method accepts the C<allow_partial> parameter. If this is
-non-zero then read() can return true on an incomplete image and set
+The read() method accepts the C<allow_incomplete> parameter. If this
+is non-zero then read() can return true on an incomplete image and set
the C<i_incomplete> tag.
From Imager 0.68 you can supply most read() parameters to the new()
my $img = Imager->new(file => $filename)
or die "Cannot read $filename: ", Imager->errstr;
-=item write
+=item write()
and the C<write()> method to write an image:
$img->write(file=>$filename, type=>$type)
or die "Cannot write $filename: ", $img->errstr;
-=item read_multi
+=item read_multi()
If you're reading from a format that supports multiple images per
file, use the C<read_multi()> method:
As with the read() method, Imager will normally detect the C<type>
automatically.
-=item write_multi
+=item write_multi()
and if you want to write multiple images to a single file use the
C<write_multi()> method:
=item *
-C<callback> - Imager will make calls back to your supplied coderefs to
-read, write and seek from/to/through the image file.
+C<callback>, C<readcb>, C<writecb>, C<seekcb>, C<closecb> - Imager
+will make calls back to your supplied coderefs to read, write and seek
+from/to/through the image file. See L</"I/O Callbacks"> below for details.
-When reading from a file you can use either C<callback> or C<readcb>
-to supply the read callback, and when writing C<callback> or
-C<writecb> to supply the write callback.
+=item *
-When writing you can also supply the C<maxbuffer> 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.
+C<io> - an L<Imager::IO> object.
-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.
+=back
-Your write callback takes exactly one parameter, a scalar containing
-the data to be written. Return true for success.
+X<buffering>X<unbuffered>By default Imager will use buffered I/O when
+reading or writing an image. You can disabled buffering for output by
+supplying a C<< buffered => 0 >> parameter to C<write()> or
+C<write_multi()>.
-The seek callback takes 2 parameters, a I<POSITION>, and a I<WHENCE>,
-defined in the same way as perl's seek function.
+=head2 I/O Callbacks
-You can also supply a C<closecb> which is called with no parameters
-when there is no more data to be written. This could be used to flush
-buffered data.
+When reading from a file you can use either C<callback> or C<readcb>
+to supply the read callback, and when writing C<callback> or
+C<writecb> to supply the write callback.
+
+Some file formats, currently only C<TIFF>, also require a C<seekcb>
+parameter to change position in the file. If no C<seekcb> parameter
+is provided a default will be provided that fails.
+
+You can also provide a C<closecb> parameter called when writing the
+file is complete.
# contrived
my $data;
Imager->write_multi({ callback => \&mywrite, type => 'gif'}, @images)
or die Imager->errstr;
-Note that for reading you'll almost always need to provide a
-C<seekcb>.
+=head3 C<readcb>
+
+The read callback is called with 2 parameters:
+
+=over
+
+=item *
+
+C<size> - the minimum amount of data required.
+
+=item *
+
+C<maxsize> - previously this was the maximum amount of data returnable
+- currently it's always the same as C<size>
=back
+Your read callback should return the data as a scalar:
+
+=over
+
+=item *
+
+on success, a string containing the bytes read.
+
+=item *
+
+on end of file, an empty string
+
+=item *
+
+on error, C<undef>.
+
+=back
+
+If your return value contains more data than C<size> Imager will
+panic.
+
+Your return value must not contain any characters over C<\xFF> or
+Imager will panic.
+
+=head3 C<writecb>
+
+Your write callback takes exactly one parameter, a scalar containing
+the data to be written.
+
+Return true for success.
+
+=head3 C<seekcb>
+
+The seek callback takes 2 parameters, a I<POSITION>, and a I<WHENCE>,
+defined in the same way as perl's seek function.
+
+Previously you always needed a C<seekcb> callback if you called
+Imager's L</read()> or L</read_multi()> without a C<type> parameter,
+but this is no longer necessary unless the file handler requires
+seeking, such as for TIFF files.
+
+Returns the new position in the file, or -1 on failure.
+
+=head3 C<closecb>
+
+You can also supply a C<closecb> which is called with no parameters
+when there is no more data to be written. This could be used to flush
+buffered data.
+
+Return true on success.
+
=head2 Guessing types
+X<FORMATGUESS>
When writing to a file, if you don't supply a C<type> parameter Imager
will attempt to guess it from the file name. This is done by calling
=over
=item def_guess_type()
+X<methods, def_guess_type()>
This is the default function Imager uses to derive a file type from a
file name. This is a function, not a method.
=over
-=item filter
+=item filter()
Parameters:
If you're writing an Imager file handler your code will be passed an
Imager::IO object to write to or read from.
-=head1 METHODS
+X<UTF-8>X<Unicode>Note that Imager::IO can only work with collections of bytes -
+if you need to read UTF-8 data you will need to read the bytes and
+decode them. If you want to write UTF-8 data you will need to encode
+your characters to bytes and write the bytes.
+
+=head1 CONSTRUCTORS
+
+=over
+
+=item new_fd($fd)
+
+Create a new I/O layer based on a file descriptor.
+
+ my $io = Imager::IO->new(fileno($fh));
+
+=item new_buffer($data)
+
+Create a new I/O layer based on a memory buffer.
+
+The supplied variable must not be changed on the the life of the I/O
+object.
+
+Buffer I/O layers are read only.
+
+=item new_cb($writecb, $readcb, $seekcb, $closecb)
+
+Create a new I/O layer based on callbacks. See
+L<Imager::Files/"I/O Callbacks"> for details on the behavior of
+the callbacks.
+
+=item new_bufchain()
+
+Create a new C<bufchain> based I/O layer. This accumulates the file
+data as a chain of buffers starting from an empty stream.
+
+Use the L</slurp()> method to retrieve the accumulated content into a
+perl string.
+
+=back
+
+=head1 BUFFERED I/O METHODS
+
+These methods use buffered I/O to improve performance unless you call
+set_buffered() to disable buffering.
+
+Prior to Imager 0.86 the write and read methods performed raw I/O.
=over
-=item write
+=item write($data)
Call to write to the file. Returns the number of bytes written. The
data provided may contain only characters \x00 to \xFF - characters
isn't the number of bytes supplied you'll want to treat it as an error
anyway.
-=item read
+=item read($buffer, $size)
my $buffer;
my $count = $io->read($buffer, $max_bytes);
success or an empty list on failure. Note that a read of zero bytes
is B<not> a failure, this indicates end of file.
-=item read2
+=item read2($size)
my $buffer = $io->read2($max_bytes);
An alternative interface to read, that might be simpler to use in some
cases.
-Returns the data read or an empty list.
+Returns the data read or an empty list. At end of file the data read
+will be an empty string.
-=item seek
+=item seek($offset, $whence)
my $new_position = $io->seek($offset, $whence);
Note that seeking past the end of the file may or may not result in an
error.
+Any buffered output will be flushed, if flushing fails, seek() will
+return -1.
+
Returns the new position in the file, or -1 on error.
-=item close
+=item getc()
+
+Return the next byte from the stream.
+
+Returns the ordinal of the byte or -1 on error or end of file.
+
+ while ((my $c = $io->getc) != -1) {
+ print chr($c);
+ }
+
+=item gets()
+
+=item gets($max_size)
+
+=item gets($max_size, $end_of_line)
+
+Returns the next line of input from the stream, as terminated by
+C<end_of_line>.
+
+The default C<max_size> is 8192.
+
+The default C<end_of_line> is C<ord "\n">.
+
+Returns nothing if the stream is in error or at end of file.
+
+Returns the line as a string, including the line terminator (if one
+was found) on success.
+
+ while (defined(my $line = $io->gets)) {
+ # do something with $line
+ }
+
+=item peekc()
+
+Return the buffered next character from the stream, loading the buffer
+if necessary.
+
+For an unbuffered stream a buffer will be setup and loaded with a
+single character.
+
+Returns the ordinal of the byte or -1 on error or end of file.
+
+ my $c = $io->peekc;
+
+=item peekn($size)
+
+Returns up to the next C<size> bytes from the file as a string.
+
+Only up to the stream buffer size bytes (currently 8192) can be peeked.
+
+This method ignores the buffering state of the stream.
+
+Returns nothing on EOF.
+
+ my $s = $io->peekn(4);
+ if ($s =~ /^(II|MM)\*\0/) {
+ print "TIFF image";
+ }
+
+=item putc($code)
+
+Write a single character to the stream.
+
+Returns C<code> on success, or -1 on failure.
+
+=item close()
my $result = $io->close;
-Call when you're with the file. If the IO object is connected to a
-file this won't close the file handle, but buffers may be flushed (if
-any).
+Call when you're done with the file. If the IO object is connected to
+a file this won't close the file handle, but buffers may be flushed
+(if any).
+
+Returns 0 on success, -1 on failure.
+
+=item eof()
+
+ $io->eof
+
+Test if the stream is at end of file. No further read requests will
+be passed to your read callback until you seek().
+
+=item error()
+
+Test if the stream has encountered a read or write error.
+
+ my $data = $io->read2(100);
+ $io->error
+ and die "Failed";
+
+When the stream has the error flag set no further read or write
+requests will be passed to your callbacks until you seek.
+
+=item flush()
+
+ $io->flush
+ or die "Flush error";
+
+Flush any buffered output. This will not call lower write layers when
+the stream has it's error flag set.
+
+Returns a true value on success.
+
+=item is_buffered()
+
+Test if buffering is enabled for this stream.
+
+Returns a true value if the stream is buffered.
+
+=item set_buffered($enabled)
+
+If C<$enabled> is a non-zero integer, enable buffering, other disable
+it.
+
+Disabling buffering will flush any buffered output, but any buffered
+input will be retained and consumed by input methods.
+
+Returns true if any buffered output was flushed successfully, false if
+there was an error flushing output.
+
+=back
+
+=head1 RAW I/O METHODS
+
+These call the underlying I/O abstraction directly.
+
+=over
+
+=item raw_write()
+
+Call to write to the file. Returns the number of bytes written. The
+data provided may contain only characters \x00 to \xFF - characters
+outside this range will cause this method to croak().
+
+If you supply a UTF-8 flagged string it will be converted to a byte
+string, which may have a performance impact.
+
+Returns -1 on error, though in most cases if the result of the write
+isn't the number of bytes supplied you'll want to treat it as an error
+anyway.
+
+=item raw_read()
+
+ my $buffer;
+ my $count = $io->raw_read($buffer, $max_bytes);
+
+Reads up to I<$max_bytes> bytes from the current position in the file
+and stores them in I<$buffer>. Returns the number of bytes read on
+success or an empty list on failure. Note that a read of zero bytes
+is B<not> a failure, this indicates end of file.
+
+=item raw_read2()
+
+ my $buffer = $io->raw_read2($max_bytes);
+
+An alternative interface to raw_read, that might be simpler to use in some
+cases.
+
+Returns the data read or an empty list.
+
+=item raw_seek()
+
+ my $new_position = $io->raw_seek($offset, $whence);
+
+Seek to a new position in the file. Possible values for I<$whence> are:
+
+=over
+
+=item *
+
+C<SEEK_SET> - I<$offset> is the new position in the file.
+
+=item *
+
+C<SEEK_CUR> - I<$offset> is the offset from the current position in
+the file.
+
+=item *
+
+C<SEEK_END> - I<$offset> is the offset relative to the end of the
+file.
+
+=back
+
+Note that seeking past the end of the file may or may not result in an
+error.
+
+Returns the new position in the file, or -1 on error.
+
+=item raw_close()
+
+ my $result = $io->raw_close;
+
+Call when you're done with the file. If the IO object is connected to
+a file this won't close the file handle.
Returns 0 on success, -1 on failure.
=back
+=head1 UTILITY METHODS
+
+=over
+
+=item slurp()
+
+Retrieve the data accumulated from an I/O layer object created with
+the new_bufchain() method.
+
+ my $data = $io->slurp;
+
+=item dump()
+
+Dump the internal buffering state of the I/O object to C<stderr>.
+
+ $io->dump();
+
+=back
+
=head1 AUTHOR
Tony Cook <tonyc@cpan.org>
*/
-#define BSIZ 1024
#define misspace(x) (x==' ' || x=='\n' || x=='\r' || x=='\t' || x=='\f' || x=='\v')
#define misnumber(x) (x <= '9' && x>='0')
static char *typenames[]={"ascii pbm", "ascii pgm", "ascii ppm", "binary pbm", "binary pgm", "binary ppm"};
/*
- * Type to encapsulate the local buffer
- * management skipping over in a file
- */
-
-typedef struct {
- io_glue *ig;
- int len;
- int cp;
- char buf[BSIZ];
-} mbuf;
-
-
-static
-void init_buf(mbuf *mb, io_glue *ig) {
- mb->len = 0;
- mb->cp = 0;
- mb->ig = ig;
-}
-
-
-
-/*
-=item gnext(mbuf *mb)
-
-Fetches a character and advances in stream by one character.
-Returns a pointer to the byte or NULL on failure (internal).
-
- mb - buffer object
-
-=cut
-*/
-
-#define gnext(mb) (((mb)->cp == (mb)->len) ? gnextf(mb) : (mb)->buf + (mb)->cp++)
-
-static
-char *
-gnextf(mbuf *mb) {
- io_glue *ig = mb->ig;
- if (mb->cp == mb->len) {
- mb->cp = 0;
- mb->len = ig->readcb(ig, mb->buf, BSIZ);
- if (mb->len == -1) {
- i_push_error(errno, "file read error");
- mm_log((1, "i_readpnm: read error\n"));
- return NULL;
- }
- if (mb->len == 0) {
- mm_log((1, "i_readpnm: end of file\n"));
- return NULL;
- }
- }
- return &mb->buf[mb->cp++];
-}
-
-
-/*
-=item gpeek(mbuf *mb)
-
-Fetches a character but does NOT advance. Returns a pointer to
-the byte or NULL on failure (internal).
-
- mb - buffer object
-
-=cut
-*/
-
-#define gpeek(mb) ((mb)->cp == (mb)->len ? gpeekf(mb) : (mb)->buf + (mb)->cp)
-
-static
-char *
-gpeekf(mbuf *mb) {
- io_glue *ig = mb->ig;
- if (mb->cp == mb->len) {
- mb->cp = 0;
- mb->len = ig->readcb(ig, mb->buf, BSIZ);
- if (mb->len == -1) {
- i_push_error(errno, "read error");
- mm_log((1, "i_readpnm: read error\n"));
- return NULL;
- }
- if (mb->len == 0) {
- mm_log((1, "i_readpnm: end of file\n"));
- return NULL;
- }
- }
- return &mb->buf[mb->cp];
-}
-
-int
-gread(mbuf *mb, unsigned char *buf, size_t read_size) {
- int total_read = 0;
- if (mb->cp != mb->len) {
- int avail_size = mb->len - mb->cp;
- int use_size = read_size > avail_size ? avail_size : read_size;
- memcpy(buf, mb->buf+mb->cp, use_size);
- mb->cp += use_size;
- total_read += use_size;
- read_size -= use_size;
- buf += use_size;
- }
- if (read_size) {
- io_glue *ig = mb->ig;
- int read_res = i_io_read(ig, buf, read_size);
- if (read_res >= 0) {
- total_read += read_res;
- }
- }
- return total_read;
-}
-
-
-/*
-=item skip_spaces(mb)
+=item skip_spaces(ig)
Advances in stream until it is positioned at a
non white space character. (internal)
- mb - buffer object
+ ig - io_glue
=cut
*/
static
int
-skip_spaces(mbuf *mb) {
- char *cp;
- while( (cp = gpeek(mb)) && misspace(*cp) ) if ( !gnext(mb) ) break;
- if (!cp) return 0;
+skip_spaces(io_glue *ig) {
+ int c;
+ while( (c = i_io_peekc(ig)) != EOF && misspace(c) ) {
+ if ( i_io_getc(ig) == EOF )
+ break;
+ }
+ if (c == EOF)
+ return 0;
+
return 1;
}
/*
-=item skip_comment(mb)
+=item skip_comment(ig)
Advances in stream over whitespace and a comment if one is found. (internal)
- mb - buffer object
+ ig - io_glue object
=cut
*/
static
int
-skip_comment(mbuf *mb) {
- char *cp;
+skip_comment(io_glue *ig) {
+ int c;
- if (!skip_spaces(mb)) return 0;
+ if (!skip_spaces(ig))
+ return 0;
- if (!(cp = gpeek(mb))) return 0;
- if (*cp == '#') {
- while( (cp = gpeek(mb)) && (*cp != '\n' && *cp != '\r') ) {
- if ( !gnext(mb) ) break;
+ if ((c = i_io_peekc(ig)) == EOF)
+ return 0;
+
+ if (c == '#') {
+ while( (c = i_io_peekc(ig)) != EOF && (c != '\n' && c != '\r') ) {
+ if ( i_io_getc(ig) == EOF )
+ break;
}
}
- if (!cp) return 0;
+ if (c == EOF)
+ return 0;
return 1;
}
static
int
-gnum(mbuf *mb, int *i) {
- char *cp;
+gnum(io_glue *ig, int *i) {
+ int c;
*i = 0;
- if (!skip_spaces(mb)) return 0;
+ if (!skip_spaces(ig)) return 0;
- if (!(cp = gpeek(mb)))
+ if ((c = i_io_peekc(ig)) == EOF)
return 0;
- if (!misnumber(*cp))
+ if (!misnumber(c))
return 0;
- while( (cp = gpeek(mb)) && misnumber(*cp) ) {
- int work = *i*10+(*cp-'0');
+ while( (c = i_io_peekc(ig)) != EOF && misnumber(c) ) {
+ int work = *i * 10 + (c - '0');
if (work < *i) {
/* overflow */
i_push_error(0, "integer overflow");
return 0;
}
*i = work;
- cp = gnext(mb);
+ i_io_getc(ig);
}
+
return 1;
}
static
i_img *
-read_pgm_ppm_bin8(mbuf *mb, i_img *im, int width, int height,
+read_pgm_ppm_bin8(io_glue *ig, i_img *im, int width, int height,
int channels, int maxval, int allow_incomplete) {
i_color *line, *linep;
int read_size;
for(y=0;y<height;y++) {
linep = line;
readp = read_buf;
- if (gread(mb, read_buf, read_size) != read_size) {
+ if (i_io_read(ig, read_buf, read_size) != read_size) {
myfree(line);
myfree(read_buf);
if (allow_incomplete) {
static
i_img *
-read_pgm_ppm_bin16(mbuf *mb, i_img *im, int width, int height,
+read_pgm_ppm_bin16(io_glue *ig, i_img *im, int width, int height,
int channels, int maxval, int allow_incomplete) {
i_fcolor *line, *linep;
int read_size;
for(y=0;y<height;y++) {
linep = line;
readp = read_buf;
- if (gread(mb, read_buf, read_size) != read_size) {
+ if (i_io_read(ig, read_buf, read_size) != read_size) {
myfree(line);
myfree(read_buf);
if (allow_incomplete) {
static
i_img *
-read_pbm_bin(mbuf *mb, i_img *im, int width, int height, int allow_incomplete) {
+read_pbm_bin(io_glue *ig, i_img *im, int width, int height, int allow_incomplete) {
i_palidx *line, *linep;
int read_size;
unsigned char *read_buf, *readp;
read_size = (width + 7) / 8;
read_buf = mymalloc(read_size);
for(y = 0; y < height; y++) {
- if (gread(mb, read_buf, read_size) != read_size) {
+ if (i_io_read(ig, read_buf, read_size) != read_size) {
myfree(line);
myfree(read_buf);
if (allow_incomplete) {
*/
static
i_img *
-read_pbm_ascii(mbuf *mb, i_img *im, int width, int height, int allow_incomplete) {
+read_pbm_ascii(io_glue *ig, i_img *im, int width, int height, int allow_incomplete) {
i_palidx *line, *linep;
int x, y;
for(y = 0; y < height; y++) {
linep = line;
for(x = 0; x < width; ++x) {
- char *cp;
- skip_spaces(mb);
- if (!(cp = gnext(mb)) || (*cp != '0' && *cp != '1')) {
+ int c;
+ skip_spaces(ig);
+ if ((c = i_io_getc(ig)) == EOF || (c != '0' && c != '1')) {
myfree(line);
if (allow_incomplete) {
i_tags_setn(&im->tags, "i_incomplete", 1);
return im;
}
else {
- if (cp)
+ if (c != EOF)
i_push_error(0, "invalid data for ascii pnm");
else
i_push_error(0, "short read - file truncated?");
return NULL;
}
}
- *linep++ = *cp == '0' ? 0 : 1;
+ *linep++ = c == '0' ? 0 : 1;
}
i_ppal(im, 0, width, y, line);
}
static
i_img *
-read_pgm_ppm_ascii(mbuf *mb, i_img *im, int width, int height, int channels,
+read_pgm_ppm_ascii(io_glue *ig, i_img *im, int width, int height, int channels,
int maxval, int allow_incomplete) {
i_color *line, *linep;
int x, y, ch;
for(ch=0; ch<channels; ch++) {
int sample;
- if (!gnum(mb, &sample)) {
+ if (!gnum(ig, &sample)) {
myfree(line);
if (allow_incomplete) {
i_tags_setn(&im->tags, "i_incomplete", 1);
return im;
}
else {
- if (gpeek(mb))
+ if (i_io_peekc(ig) != EOF)
i_push_error(0, "invalid data for ascii pnm");
else
i_push_error(0, "short read - file truncated?");
static
i_img *
-read_pgm_ppm_ascii_16(mbuf *mb, i_img *im, int width, int height,
+read_pgm_ppm_ascii_16(io_glue *ig, i_img *im, int width, int height,
int channels, int maxval, int allow_incomplete) {
i_fcolor *line, *linep;
int x, y, ch;
for(ch=0; ch<channels; ch++) {
int sample;
- if (!gnum(mb, &sample)) {
+ if (!gnum(ig, &sample)) {
myfree(line);
if (allow_incomplete) {
i_tags_setn(&im->tags, "i_incomplete", 1);
return im;
}
else {
- if (gpeek(mb))
+ if (i_io_peekc(ig) != EOF)
i_push_error(0, "invalid data for ascii pnm");
else
i_push_error(0, "short read - file truncated?");
=cut
*/
-static i_img *i_readpnm_wiol_low( mbuf*, int);
i_img *
-i_readpnm_wiol(io_glue *ig, int allow_incomplete) {
- mbuf buf;
- io_glue_commit_types(ig);
- init_buf(&buf, ig);
-
- return i_readpnm_wiol_low( &buf, allow_incomplete );
-}
-
-static i_img *
-i_readpnm_wiol_low( mbuf *buf, int allow_incomplete) {
+i_readpnm_wiol( io_glue *ig, int allow_incomplete) {
i_img* im;
int type;
int width, height, maxval, channels;
int rounder;
- char *cp;
+ int c;
i_clear_error();
- mm_log((1,"i_readpnm(ig %p, allow_incomplete %d)\n", buf->ig, allow_incomplete));
+ mm_log((1,"i_readpnm(ig %p, allow_incomplete %d)\n", ig, allow_incomplete));
- cp = gnext(buf);
+ c = i_io_getc(ig);
- if (!cp || *cp != 'P') {
+ if (c != 'P') {
i_push_error(0, "bad header magic, not a PNM file");
mm_log((1, "i_readpnm: Could not read header of file\n"));
return NULL;
}
- if ( !(cp = gnext(buf)) ) {
+ if ((c = i_io_getc(ig)) == EOF ) {
mm_log((1, "i_readpnm: Could not read header of file\n"));
return NULL;
}
- type = *cp-'0';
+ type = c - '0';
if (type < 1 || type > 6) {
i_push_error(0, "unknown PNM file type, not a PNM file");
return NULL;
}
- if ( !(cp = gnext(buf)) ) {
+ if ( (c = i_io_getc(ig)) == EOF ) {
mm_log((1, "i_readpnm: Could not read header of file\n"));
return NULL;
}
- if ( !misspace(*cp) ) {
+ if ( !misspace(c) ) {
i_push_error(0, "unexpected character, not a PNM file");
mm_log((1, "i_readpnm: Not a pnm file\n"));
return NULL;
/* Read sizes and such */
- if (!skip_comment(buf)) {
+ if (!skip_comment(ig)) {
i_push_error(0, "while skipping to width");
mm_log((1, "i_readpnm: error reading before width\n"));
return NULL;
}
- if (!gnum(buf, &width)) {
+ if (!gnum(ig, &width)) {
i_push_error(0, "could not read image width");
mm_log((1, "i_readpnm: error reading width\n"));
return NULL;
}
- if (!skip_comment(buf)) {
+ if (!skip_comment(ig)) {
i_push_error(0, "while skipping to height");
mm_log((1, "i_readpnm: error reading before height\n"));
return NULL;
}
- if (!gnum(buf, &height)) {
+ if (!gnum(ig, &height)) {
i_push_error(0, "could not read image height");
mm_log((1, "i_readpnm: error reading height\n"));
return NULL;
}
if (!(type == 1 || type == 4)) {
- if (!skip_comment(buf)) {
+ if (!skip_comment(ig)) {
i_push_error(0, "while skipping to maxval");
mm_log((1, "i_readpnm: error reading before maxval\n"));
return NULL;
}
- if (!gnum(buf, &maxval)) {
+ if (!gnum(ig, &maxval)) {
i_push_error(0, "could not read maxval");
mm_log((1, "i_readpnm: error reading maxval\n"));
return NULL;
} else maxval=1;
rounder = maxval / 2;
- if (!(cp = gnext(buf)) || !misspace(*cp)) {
+ if ((c = i_io_getc(ig)) == EOF || !misspace(c)) {
i_push_error(0, "garbage in header, invalid PNM file");
mm_log((1, "i_readpnm: garbage in header\n"));
return NULL;
switch (type) {
case 1: /* Ascii types */
- im = read_pbm_ascii(buf, im, width, height, allow_incomplete);
+ im = read_pbm_ascii(ig, im, width, height, allow_incomplete);
break;
case 2:
case 3:
if (maxval > 255)
- im = read_pgm_ppm_ascii_16(buf, im, width, height, channels, maxval, allow_incomplete);
+ im = read_pgm_ppm_ascii_16(ig, im, width, height, channels, maxval, allow_incomplete);
else
- im = read_pgm_ppm_ascii(buf, im, width, height, channels, maxval, allow_incomplete);
+ im = read_pgm_ppm_ascii(ig, im, width, height, channels, maxval, allow_incomplete);
break;
case 4: /* binary pbm */
- im = read_pbm_bin(buf, im, width, height, allow_incomplete);
+ im = read_pbm_bin(ig, im, width, height, allow_incomplete);
break;
case 5: /* binary pgm */
case 6: /* binary ppm */
if (maxval > 255)
- im = read_pgm_ppm_bin16(buf, im, width, height, channels, maxval, allow_incomplete);
+ im = read_pgm_ppm_bin16(ig, im, width, height, channels, maxval, allow_incomplete);
else
- im = read_pgm_ppm_bin8(buf, im, width, height, channels, maxval, allow_incomplete);
+ im = read_pgm_ppm_bin8(ig, im, width, height, channels, maxval, allow_incomplete);
break;
default:
i_img **i_readpnm_multi_wiol(io_glue *ig, int *count, int allow_incomplete) {
i_img **results = NULL;
i_img *img = NULL;
- char *cp = NULL;
- mbuf buf;
+ char c = EOF;
int result_alloc = 0,
value = 0,
eof = 0;
*count=0;
- io_glue_commit_types(ig);
- init_buf(&buf, ig);
+
do {
mm_log((1, "read image %i\n", 1+*count));
- img = i_readpnm_wiol_low( &buf, allow_incomplete );
+ img = i_readpnm_wiol( ig, allow_incomplete );
if( !img ) {
free_images( results, *count );
return NULL;
if( i_tags_get_int(&img->tags, "i_incomplete", 0, &value ) && value) {
eof = 1;
}
- else if( skip_spaces( &buf ) && ( cp=gpeek( &buf ) ) && *cp == 'P' ) {
+ else if( skip_spaces( ig ) && ( c=i_io_peekc( ig ) ) != EOF && c == 'P' ) {
eof = 0;
}
else {
/* Add code to get the filename info from the iolayer */
/* Also add code to check for mmapped code */
- io_glue_commit_types(ig);
-
if (i_img_is_monochrome(im, &zero_is_white)) {
- return write_pbm(im, ig, zero_is_white);
+ if (!write_pbm(im, ig, zero_is_white))
+ return 0;
}
else {
int type;
sprintf(header,"P%d\n#CREATOR: Imager\n%" i_DF " %" i_DF"\n%d\n",
type, i_DFc(im->xsize), i_DFc(im->ysize), maxval);
- if (ig->writecb(ig,header,strlen(header)) != strlen(header)) {
+ if (i_io_write(ig,header,strlen(header)) != strlen(header)) {
i_push_error(errno, "could not write ppm header");
mm_log((1,"i_writeppm: unable to write ppm header.\n"));
return(0);
if (!im->virtual && im->bits == i_8_bits && im->type == i_direct_type
&& im->channels == want_channels) {
- if (ig->writecb(ig,im->idata,im->bytes) != im->bytes) {
+ if (i_io_write(ig,im->idata,im->bytes) != im->bytes) {
i_push_error(errno, "could not write ppm data");
return 0;
}
return 0;
}
}
- ig->closecb(ig);
+ if (i_io_close(ig)) {
+ i_push_errorf(i_io_error(ig), "Error closing stream: %d", i_io_error(ig));
+ return 0;
+ }
return(1);
}
i_clear_error();
- io_glue_commit_types(ig);
mm_log((1, "i_readraw(ig %p,x %" i_DF ",y %" i_DF ",datachannels %d,storechannels %d,intrl %d)\n",
ig, i_DFc(x), i_DFc(y), datachannels, storechannels, intrl));
k=0;
while( k<im->ysize ) {
- rc = ig->readcb(ig, inbuffer, inbuflen);
+ rc = i_io_read(ig, inbuffer, inbuflen);
if (rc != inbuflen) {
if (rc < 0)
i_push_error(0, "error reading file");
i_writeraw_wiol(i_img* im, io_glue *ig) {
ssize_t rc;
- io_glue_commit_types(ig);
i_clear_error();
mm_log((1,"writeraw(im %p,ig %p)\n", im, ig));
if (im == NULL) { mm_log((1,"Image is empty\n")); return(0); }
if (!im->virtual) {
- rc = ig->writecb(ig,im->idata,im->bytes);
+ rc = i_io_write(ig,im->idata,im->bytes);
if (rc != im->bytes) {
i_push_error(errno, "Could not write to file");
mm_log((1,"i_writeraw: Couldn't write to file\n"));
rc = line_size;
while (rc == line_size && y < im->ysize) {
i_gsamp(im, 0, im->xsize, y, data, NULL, im->channels);
- rc = ig->writecb(ig, data, line_size);
+ rc = i_io_write(ig, data, line_size);
++y;
}
if (rc != line_size) {
rc = line_size;
while (rc == line_size && y < im->ysize) {
i_gpal(im, 0, im->xsize, y, data);
- rc = ig->writecb(ig, data, line_size);
+ rc = i_io_write(ig, data, line_size);
++y;
}
myfree(data);
}
}
- ig->closecb(ig);
+ if (i_io_close(ig))
+ return 0;
return(1);
}
return $pod->{identifiers} || [];
}
+sub _get_syms {
+ my ($self, $package) = @_;
+
+ if ($self->{module}) {
+ eval "require $self->{module}";
+ return if $@;
+
+ # fake out require
+ (my $file = $package) =~ s(::)(/)g;
+ $file .= ".pm";
+ $INC{$file} = 1;
+ }
+
+ return $self->SUPER::_get_syms($package);
+}
+
1;
#!perl -w
use strict;
-use Test::More tests => 68;
+use Test::More tests => 246;
# for SEEK_SET etc, Fcntl doesn't provide these in 5.005_03
use IO::Seekable;
-d "testout" or mkdir "testout";
+$| = 1;
+
Imager->open_log(log => "testout/t07iolayer.log");
undef($/);
binmode(FH);
$data = <FH>;
close(FH);
-my $IO3 = Imager::io_new_buffer($data);
+my $IO3 = Imager::IO->new_buffer($data);
#undef($data);
$im = Imager::i_readpnm_wiol($IO3, -1);
open(FH, "<testimg/penguin-base.ppm") or die $!;
binmode(FH);
$fd = fileno(FH);
-my $IO4 = Imager::io_new_fd( $fd );
+my $IO4 = Imager::IO->new_fd( $fd );
my $im2 = Imager::i_readpnm_wiol($IO4, -1);
close(FH);
undef($IO4);
$pos += length $out;
$out;
}
-my $IO7 = Imager::io_new_cb(undef, \&io_reader, undef, undef);
+my $IO7 = Imager::IO->new_cb(undef, \&io_reader, undef, undef);
ok($IO7, "making readcb object");
my $im4 = Imager::i_readpnm_wiol($IO7, -1);
ok($im4, "read from cb");
my $io9 = Imager::io_new_buffer($buf_data);
is(ref $io9, "Imager::IO", "check class");
my $work;
- is($io9->read($work, 4), 4, "read 4 from buffer object");
+ is($io9->raw_read($work, 4), 4, "read 4 from buffer object");
is($work, "Test", "check data read");
- is($io9->read($work, 5), 5, "read the rest");
+ is($io9->raw_read($work, 5), 5, "read the rest");
is($work, " data", "check data read");
- is($io9->seek(5, SEEK_SET), 5, "seek");
- is($io9->read($work, 5), 4, "short read");
+ is($io9->raw_seek(5, SEEK_SET), 5, "seek");
+ is($io9->raw_read($work, 5), 4, "short read");
is($work, "data", "check data read");
- is($io9->seek(-1, SEEK_CUR), 8, "seek relative");
- is($io9->seek(-5, SEEK_END), 4, "seek relative to end");
- is($io9->seek(-10, SEEK_CUR), -1, "seek failure");
+ is($io9->raw_seek(-1, SEEK_CUR), 8, "seek relative");
+ is($io9->raw_seek(-5, SEEK_END), 4, "seek relative to end");
+ is($io9->raw_seek(-10, SEEK_CUR), -1, "seek failure");
undef $io9;
}
{
- my $io = Imager::io_new_bufchain();
+ my $io = Imager::IO->new_bufchain();
is(ref $io, "Imager::IO", "check class");
- is($io->write("testdata"), 8, "check write");
- is($io->seek(-8, SEEK_CUR), 0, "seek relative");
+ is($io->raw_write("testdata"), 8, "check write");
+ is($io->raw_seek(-8, SEEK_CUR), 0, "seek relative");
my $work;
- is($io->read($work, 8), 8, "check read");
+ is($io->raw_read($work, 8), 8, "check read");
is($work, "testdata", "check data read");
- is($io->seek(-3, SEEK_END), 5, "seek end relative");
- is($io->read($work, 5), 3, "short read");
+ is($io->raw_seek(-3, SEEK_END), 5, "seek end relative");
+ is($io->raw_read($work, 5), 3, "short read");
is($work, "ata", "check read data");
- is($io->seek(4, SEEK_SET), 4, "absolute seek to write some");
- is($io->write("testdata"), 8, "write");
- is($io->seek(0, SEEK_CUR), 12, "check size");
- $io->close();
+ is($io->raw_seek(4, SEEK_SET), 4, "absolute seek to write some");
+ is($io->raw_write("testdata"), 8, "write");
+ is($io->raw_seek(0, SEEK_CUR), 12, "check size");
+ $io->raw_close();
# grab the data
my $data = Imager::io_slurp($io);
my $fail_io = Imager::io_new_cb(\&fail_write, \&fail_read, \&fail_seek, undef, 1);
# scalar context
my $buffer;
- my $read_result = $fail_io->read($buffer, 10);
+ my $read_result = $fail_io->raw_read($buffer, 10);
is($read_result, undef, "read failure undef in scalar context");
- my @read_result = $fail_io->read($buffer, 10);
+ my @read_result = $fail_io->raw_read($buffer, 10);
is(@read_result, 0, "empty list in list context");
- $read_result = $fail_io->read2(10);
- is($read_result, undef, "read2 failure (scalar)");
- @read_result = $fail_io->read2(10);
- is(@read_result, 0, "read2 failure (list)");
+ $read_result = $fail_io->raw_read2(10);
+ is($read_result, undef, "raw_read2 failure (scalar)");
+ @read_result = $fail_io->raw_read2(10);
+ is(@read_result, 0, "raw_read2 failure (list)");
- my $write_result = $fail_io->write("test");
+ my $write_result = $fail_io->raw_write("test");
is($write_result, -1, "failed write");
- my $seek_result = $fail_io->seek(-1, SEEK_SET);
+ my $seek_result = $fail_io->raw_seek(-1, SEEK_SET);
is($seek_result, -1, "failed seek");
}
my $good_io = Imager::io_new_cb(\&good_write, \&good_read, \&good_seek, undef, 1);
# scalar context
my $buffer;
- my $read_result = $good_io->read($buffer, 10);
- is($read_result, 10, "read success (scalar)");
- is($buffer, "testdatate", "check data");
- my @read_result = $good_io->read($buffer, 10);
- is_deeply(\@read_result, [ 10 ], "read success (list)");
- is($buffer, "testdatate", "check data");
- $read_result = $good_io->read2(10);
- is($read_result, "testdatate", "read2 success (scalar)");
- @read_result = $good_io->read2(10);
- is_deeply(\@read_result, [ "testdatate" ], "read2 success (list)");
+ my $read_result = $good_io->raw_read($buffer, 10);
+ is($read_result, 8, "read success (scalar)");
+ is($buffer, "testdata", "check data");
+ my @read_result = $good_io->raw_read($buffer, 10);
+ is_deeply(\@read_result, [ 8 ], "read success (list)");
+ is($buffer, "testdata", "check data");
+ $read_result = $good_io->raw_read2(10);
+ is($read_result, "testdata", "read2 success (scalar)");
+ @read_result = $good_io->raw_read2(10);
+ is_deeply(\@read_result, [ "testdata" ], "read2 success (list)");
}
{ # end of file
my $eof_io = Imager::io_new_cb(undef, \&eof_read, undef, undef, 1);
my $buffer;
- my $read_result = $eof_io->read($buffer, 10);
+ my $read_result = $eof_io->raw_read($buffer, 10);
is($read_result, 0, "read eof (scalar)");
is($buffer, '', "check data");
- my @read_result = $eof_io->read($buffer, 10);
+ my @read_result = $eof_io->raw_read($buffer, 10);
is_deeply(\@read_result, [ 0 ], "read eof (list)");
is($buffer, '', "check data");
}
{ # no callbacks
my $none_io = Imager::io_new_cb(undef, undef, undef, undef, 0);
- is($none_io->write("test"), -1, "write with no writecb should fail");
+ is($none_io->raw_write("test"), -1, "write with no writecb should fail");
my $buffer;
- is($none_io->read($buffer, 10), undef, "read with no readcb should fail");
- is($none_io->seek(0, SEEK_SET), -1, "seek with no seekcb should fail");
+ is($none_io->raw_read($buffer, 10), undef, "read with no readcb should fail");
+ is($none_io->raw_seek(0, SEEK_SET), -1, "seek with no seekcb should fail");
}
SKIP:
is(ord $data, 0x100, "make sure we got what we expected");
my $result =
eval {
- $io->write($data);
+ $io->raw_write($data);
};
ok($@, "should have croaked")
and print "# $@\n";
sub { print "# seek\n"; 0 },
sub { print "# close\n"; 1 });
my $buffer;
- is($io->read($buffer, 10), 10, "read 10");
+ is($io->raw_read($buffer, 10), 10, "read 10");
is($buffer, "xxxxxxxxxx", "read value");
- ok($io->write("foo"), "write");
- is($io->close, 0, "close");
+ ok($io->raw_write("foo"), "write");
+ is($io->raw_close, 0, "close");
+}
+
+SKIP:
+{ # fd_seek write failure
+ -c "/dev/full"
+ or skip("No /dev/full", 3);
+ open my $fh, "> /dev/full"
+ or skip("Can't open /dev/full: $!", 3);
+ my $io = Imager::io_new_fd(fileno($fh));
+ ok($io, "make fd io for /dev/full");
+ Imager::i_clear_error();
+ is($io->raw_write("test"), -1, "fail to write");
+ my $msg = Imager->_error_as_msg;
+ like($msg, qr/^write\(\) failure: /, "check error message");
+ print "# $msg\n";
+
+ # /dev/full succeeds on seek on Linux
+
+ undef $io;
+}
+
+SKIP:
+{ # fd_seek seek failure
+ my $seekfail = "testout/t07seekfail.dat";
+ open my $fh, "> $seekfail"
+ or skip("Can't open $seekfail: $!", 3);
+ my $io = Imager::io_new_fd(fileno($fh));
+ ok($io, "make fd io for $seekfail");
+
+ Imager::i_clear_error();
+ is($io->raw_seek(-1, SEEK_SET), -1, "shouldn't be able to seek to -1");
+ my $msg = Imager->_error_as_msg;
+ like($msg, qr/^lseek\(\) failure: /, "check error message");
+ print "# $msg\n";
+
+ undef $io;
+ close $fh;
+ unlink $seekfail;
+}
+
+SKIP:
+{ # fd_seek read failure
+ open my $fh, "> testout/t07writeonly.txt"
+ or skip("Can't open testout/t07writeonly.txt: $!", 3);
+ my $io = Imager::io_new_fd(fileno($fh));
+ ok($io, "make fd io for write-only");
+
+ Imager::i_clear_error();
+ my $buf;
+ is($io->raw_read($buf, 10), undef,
+ "file open for write shouldn't be readable");
+ my $msg = Imager->_error_as_msg;
+ like($msg, qr/^read\(\) failure: /, "check error message");
+ print "# $msg\n";
+
+ undef $io;
+}
+
+SKIP:
+{ # fd_seek eof
+ open my $fh, "> testout/t07readeof.txt"
+ or skip("Can't open testout/t07readeof.txt: $!", 5);
+ binmode $fh;
+ print $fh "test";
+ close $fh;
+ open my $fhr, "< testout/t07readeof.txt",
+ or skip("Can't open testout/t07readeof.txt: $!", 5);
+ my $io = Imager::io_new_fd(fileno($fhr));
+ ok($io, "make fd io for read eof");
+
+ Imager::i_clear_error();
+ my $buf;
+ is($io->raw_read($buf, 10), 4,
+ "10 byte read on 4 byte file should return 4");
+ my $msg = Imager->_error_as_msg;
+ is($msg, "", "should be no error message")
+ or print STDERR "# read(4) message is: $msg\n";
+
+ Imager::i_clear_error();
+ $buf = '';
+ is($io->raw_read($buf, 10), 0,
+ "10 byte read at end of 4 byte file should return 0 (eof)");
+
+ $msg = Imager->_error_as_msg;
+ is($msg, "", "should be no error message")
+ or print STDERR "# read(4), eof message is: $msg\n";
+
+ undef $io;
+}
+
+{ # buffered I/O
+ my $data="P2\n2 2\n255\n 255 0\n0 255\n";
+ my $io = Imager::io_new_buffer($data);
+
+ my $c = $io->getc();
+
+ is($c, ord "P", "getc");
+ my $peekc = $io->peekc();
+
+ is($peekc, ord "2", "peekc");
+
+ my $peekn = $io->peekn(2);
+ is($peekn, "2\n", "peekn");
+
+ $c = $io->getc();
+ is($c, ord "2", "getc after peekc/peekn");
+
+ is($io->seek(0, SEEK_SET), "0", "seek");
+ is($io->getc, ord "P", "check we got back to the start");
+}
+
+{ # test closecb result is propagated
+ my $success_cb = sub { 1 };
+ my $failure_cb = sub { 0 };
+
+ {
+ my $io = Imager::io_new_cb(undef, $success_cb, undef, $success_cb);
+ is($io->close(), 0, "test successful close");
+ }
+ {
+ my $io = Imager::io_new_cb(undef, $success_cb, undef, $failure_cb);
+ is($io->close(), -1, "test failed close");
+ }
+}
+
+{ # buffered coverage/function tests
+ # some data to play with
+ my $base = pack "C*", map rand(26) + ord("a"), 0 .. 20_001;
+
+ { # buffered accessors
+ my $io = Imager::io_new_buffer($base);
+ ok($io->set_buffered(0), "set unbuffered");
+ ok(!$io->is_buffered, "verify unbuffered");
+ ok($io->set_buffered(1), "set buffered");
+ ok($io->is_buffered, "verify buffered");
+ }
+
+ { # initial i_io_read(), buffered
+ my $pos = 0;
+ my $ops = "";
+ my $work = $base;
+ my $read = sub {
+ my ($size) = @_;
+
+ my $req_size = $size;
+
+ if ($pos + $size > length $work) {
+ $size = length($work) - $pos;
+ }
+
+ my $result = substr($work, $pos, $size);
+ $pos += $size;
+ $ops .= "R$req_size>$size;";
+
+ print "# read $req_size>$size\n";
+
+ return $result;
+ };
+ my $write = sub {
+ my ($data) = @_;
+
+ substr($work, $pos, length($data), $data);
+
+ return 1;
+ };
+ {
+ my $io = Imager::io_new_cb(undef, $read, undef, undef);
+ my $buf;
+ is($io->read($buf, 1000), 1000, "read initial 1000");
+ is($buf, substr($base, 0, 1000), "check data read");
+ is($ops, "R8192>8192;", "check read op happened to buffer size");
+
+ undef $buf;
+ is($io->read($buf, 1001), 1001, "read another 1001");
+ is($buf, substr($base, 1000, 1001), "check data read");
+ is($ops, "R8192>8192;", "should be no further reads");
+
+ undef $buf;
+ is($io->read($buf, 40_000), length($base) - 2001,
+ "read the rest in one chunk");
+ is($buf, substr($base, 2001), "check the data read");
+ my $buffer_left = 8192 - 2001;
+ my $after_buffer = length($base) - 8192;
+ is($ops, "R8192>8192;R".(40_000 - $buffer_left).">$after_buffer;R21999>0;",
+ "check we tried to read the remainder");
+ }
+ {
+ # read after write errors
+ my $io = Imager::io_new_cb($write, $read, undef, undef);
+ is($io->write("test"), 4, "write 4 bytes, io in write mode");
+ is($io->read2(10), undef, "read should fail");
+ is($io->peekn(10), undef, "peekn should fail");
+ is($io->getc(), -1, "getc should fail");
+ is($io->peekc(), -1, "peekc should fail");
+ }
+ }
+
+ {
+ my $io = Imager::io_new_buffer($base);
+ print "# buffer fill check\n";
+ ok($io, "make memory io");
+ my $buf;
+ is($io->read($buf, 4096), 4096, "read 4k");
+ is($buf, substr($base, 0, 4096), "check data is correct");
+
+ # peek a bit
+ undef $buf;
+ is($io->peekn(5120), substr($base, 4096, 5120),
+ "peekn() 5120, which should exceed the buffer, and only read the left overs");
+ }
+
+ { # initial peekn
+ my $io = Imager::io_new_buffer($base);
+ is($io->peekn(10), substr($base, 0, 10),
+ "make sure initial peekn() is sane");
+ is($io->read2(10), substr($base, 0, 10),
+ "and that reading 10 gets the expected data");
+ }
+
+ { # oversize peekn
+ my $io = Imager::io_new_buffer($base);
+ is($io->peekn(10_000), substr($base, 0, 8192),
+ "peekn() larger than buffer should return buffer-size bytes");
+ }
+
+ { # small peekn then large peekn with a small I/O back end
+ # this might happen when reading from a socket
+ my $work = $base;
+ my $pos = 0;
+ my $ops = '';
+ my $reader = sub {
+ my ($size) = @_;
+
+ my $req_size = $size;
+ # do small reads, to trigger a possible bug
+ if ($size > 10) {
+ $size = 10;
+ }
+
+ if ($pos + $size > length $work) {
+ $size = length($work) - $pos;
+ }
+
+ my $result = substr($work, $pos, $size);
+ $pos += $size;
+ $ops .= "R$req_size>$size;";
+
+ print "# read $req_size>$size\n";
+
+ return $result;
+ };
+ my $io = Imager::io_new_cb(undef, $reader, undef, undef);
+ ok($io, "small reader io");
+ is($io->peekn(25), substr($base, 0, 25), "peek 25");
+ is($ops, "R8192>10;R8182>10;R8172>10;",
+ "check we got the raw calls expected");
+ is($io->peekn(65), substr($base, 0, 65), "peek 65");
+ is($ops, "R8192>10;R8182>10;R8172>10;R8162>10;R8152>10;R8142>10;R8132>10;",
+ "check we got the raw calls expected");
+ }
+ for my $buffered (1, 0) { # peekn followed by errors
+ my $buffered_desc = $buffered ? "buffered" : "unbuffered";
+ my $read = 0;
+ my $base = "abcdef";
+ my $pos = 0;
+ my $reader = sub {
+ my $size = shift;
+ my $req_size = $size;
+ if ($pos + $size > length $base) {
+ $size = length($base) - $pos;
+ }
+ # error instead of eof
+ if ($size == 0) {
+ print "# read $req_size>error\n";
+ return;
+ }
+ my $result = substr($base, $pos, $size);
+ $pos += $size;
+
+ print "# read $req_size>$size\n";
+
+ return $result;
+ };
+ my $io = Imager::io_new_cb(undef, $reader, undef, undef);
+ ok($io, "make $buffered_desc cb with error after 6 bytes");
+ is($io->peekn(5), "abcde",
+ "peekn until just before error ($buffered_desc)");
+ is($io->peekn(6), "abcdef", "peekn until error ($buffered_desc)");
+ is($io->peekn(7), "abcdef", "peekn past error ($buffered_desc)");
+ ok(!$io->error,
+ "should be no error indicator, since data buffered ($buffered_desc)");
+ ok(!$io->eof,
+ "should be no eof indicator, since data buffered ($buffered_desc)");
+
+ # consume it
+ is($io->read2(6), "abcdef", "consume the buffer ($buffered_desc)");
+ is($io->peekn(10), undef,
+ "peekn should get an error indicator ($buffered_desc)");
+ ok($io->error, "should be an error state ($buffered_desc)");
+ ok(!$io->eof, "but not eof ($buffered_desc)");
+ }
+ { # peekn on an empty file
+ my $io = Imager::io_new_buffer("");
+ is($io->peekn(10), "", "peekn on empty source");
+ ok($io->eof, "should be in eof state");
+ ok(!$io->error, "but not error");
+ }
+ { # peekn on error source
+ my $io = Imager::io_new_cb(undef, sub { return; }, undef, undef);
+ is($io->peekn(10), undef, "peekn on empty source");
+ ok($io->error, "should be in error state");
+ ok(!$io->eof, "but not eof");
+ }
+ { # peekn on short source
+ my $io = Imager::io_new_buffer("abcdef");
+ is($io->peekn(4), "abcd", "peekn 4 on 6 byte source");
+ is($io->peekn(10), "abcdef", "followed by peekn 10 on 6 byte source");
+ is($io->peekn(10), "abcdef", "and again, now eof is set");
+ }
+ { # peekn(0)
+ Imager::i_clear_error();
+ my $io = Imager::io_new_buffer("abcdef");
+ is($io->peekn(0), undef, "peekn 0 on 6 byte source");
+ my $msg = Imager->_error_as_msg;
+ is($msg, "peekn size must be positive");
+ }
+ { # getc through a whole file (buffered)
+ my $io = Imager::io_new_buffer($base);
+ my $out = '';
+ while ((my $c = $io->getc()) != -1) {
+ $out .= chr($c);
+ }
+ is($out, $base, "getc should return the file byte by byte (buffered)");
+ is($io->getc, -1, "another getc after eof should fail too");
+ ok($io->eof, "should be marked eof");
+ ok(!$io->error, "shouldn't be marked in error");
+ }
+ { # getc through a whole file (unbuffered)
+ my $io = Imager::io_new_buffer($base);
+ $io->set_buffered(0);
+ my $out = '';
+ while ((my $c = $io->getc()) != -1) {
+ $out .= chr($c);
+ }
+ is($out, $base, "getc should return the file byte by byte (unbuffered)");
+ is($io->getc, -1, "another getc after eof should fail too");
+ ok($io->eof, "should be marked eof");
+ ok(!$io->error, "shouldn't be marked in error");
+ }
+ { # buffered getc with an error
+ my $io = Imager::io_new_cb(undef, sub { return; }, undef, undef);
+ is($io->getc, -1, "buffered getc error");
+ ok($io->error, "io marked in error");
+ ok(!$io->eof, "but not eof");
+ }
+ { # unbuffered getc with an error
+ my $io = Imager::io_new_cb(undef, sub { return; }, undef, undef);
+ $io->set_buffered(0);
+ is($io->getc, -1, "unbuffered getc error");
+ ok($io->error, "io marked in error");
+ ok(!$io->eof, "but not eof");
+ }
+ { # initial peekc - buffered
+ my $io = Imager::io_new_buffer($base);
+ my $c = $io->peekc;
+ is($c, ord($base), "buffered peekc matches");
+ is($io->peekc, $c, "duplicate peekc matchess");
+ }
+ { # initial peekc - unbuffered
+ my $io = Imager::io_new_buffer($base);
+ $io->set_buffered(0);
+ my $c = $io->peekc;
+ is($c, ord($base), "unbuffered peekc matches");
+ is($io->peekc, $c, "duplicate peekc matchess");
+ }
+ { # initial peekc eof - buffered
+ my $io = Imager::io_new_cb(undef, sub { "" }, undef, undef);
+ my $c = $io->peekc;
+ is($c, -1, "buffered eof peekc is -1");
+ is($io->peekc, $c, "duplicate matches");
+ ok($io->eof, "io marked eof");
+ ok(!$io->error, "but not error");
+ }
+ { # initial peekc eof - unbuffered
+ my $io = Imager::io_new_cb(undef, sub { "" }, undef, undef);
+ $io->set_buffered(0);
+ my $c = $io->peekc;
+ is($c, -1, "buffered eof peekc is -1");
+ is($io->peekc, $c, "duplicate matches");
+ ok($io->eof, "io marked eof");
+ ok(!$io->error, "but not error");
+ }
+ { # initial peekc error - buffered
+ my $io = Imager::io_new_cb(undef, sub { return; }, undef, undef);
+ my $c = $io->peekc;
+ is($c, -1, "buffered error peekc is -1");
+ is($io->peekc, $c, "duplicate matches");
+ ok($io->error, "io marked error");
+ ok(!$io->eof, "but not eof");
+ }
+ { # initial peekc error - unbuffered
+ my $io = Imager::io_new_cb(undef, sub { return; }, undef, undef);
+ $io->set_buffered(0);
+ my $c = $io->peekc;
+ is($c, -1, "unbuffered error peekc is -1");
+ is($io->peekc, $c, "duplicate matches");
+ ok($io->error, "io marked error");
+ ok(!$io->eof, "but not eof");
+ }
+ { # initial putc
+ my $io = Imager::io_new_bufchain();
+ is($io->putc(ord "A"), ord "A", "initial putc buffered");
+ is($io->close, 0, "close it");
+ is(Imager::io_slurp($io), "A", "check it was written");
+ }
+ { # initial putc - unbuffered
+ my $io = Imager::io_new_bufchain();
+ $io->set_buffered(0);
+ is($io->putc(ord "A"), ord "A", "initial putc unbuffered");
+ is($io->close, 0, "close it");
+ is(Imager::io_slurp($io), "A", "check it was written");
+ }
+ { # putc unbuffered with error
+ my $io = Imager::io_new_cb(undef, undef, undef, undef);
+ $io->set_buffered(0);
+ is($io->putc(ord "A"), -1, "initial putc unbuffered error");
+ ok($io->error, "io in error");
+ is($io->putc(ord "B"), -1, "still in error");
+ }
+ { # writes while in read state
+ my $io = Imager::io_new_cb(sub { 1 }, sub { return "AA" }, undef, undef);
+ is($io->getc, ord "A", "read to setup read buffer");
+ is($io->putc(ord "B"), -1, "putc should fail");
+ is($io->write("test"), -1, "write should fail");
+ }
+ { # buffered putc error handling
+ # tests the check for error state in the buffered putc code
+ my $io = Imager::io_new_cb(undef, undef, undef, undef);
+ $io->putc(ord "A");
+ ok(!$io->flush, "flush should fail");
+ ok($io->error, "should be in error state");
+ is($io->putc(ord "B"), -1, "check for error");
+ }
+ { # buffered putc flush error handling
+ # test handling of flush failure and of the error state resulting
+ # from that
+ my $io = Imager::io_new_cb(undef, undef, undef, undef);
+ my $i = 0;
+ while (++$i < 100_000 && $io->putc(ord "A") == ord "A") {
+ # until we have to flush and fail doing do
+ }
+ is($i, 8193, "should have failed on 8193rd byte");
+ ok($io->error, "should be in error state");
+ is($io->putc(ord "B"), -1, "next putc should fail");
+ }
+ { # buffered write flush error handling
+ # test handling of flush failure and of the error state resulting
+ # from that
+ my $io = Imager::io_new_cb(undef, undef, undef, undef);
+ my $i = 0;
+ while (++$i < 100_000 && $io->write("A") == 1) {
+ # until we have to flush and fail doing do
+ }
+ is($i, 8193, "should have failed on 8193rd byte");
+ ok($io->error, "should be in error state");
+ is($io->write("B"), -1, "next write should fail");
+ }
+ { # buffered read error
+ my $io = Imager::io_new_cb(undef, undef, undef, undef);
+ is($io->read2(10), undef, "initial read returning error");
+ ok($io->error, "should be in error state");
+ }
+ { # unbuffered read error
+ my $io = Imager::io_new_cb(undef, undef, undef, undef);
+ $io->set_buffered(0);
+ is($io->read2(10), undef, "initial read returning error");
+ ok($io->error, "should be in error state");
+ }
+ { # unbuffered write error
+ my $count = 0;
+ my $io = Imager::io_new_cb(sub { return $count++; }, undef, undef, undef);
+ $io->set_buffered(0);
+ is($io->write("A"), -1, "unbuffered write failure");
+ ok($io->error, "should be in error state");
+ is($io->write("BC"), -1, "should still fail");
+ }
+ { # buffered write + large write
+ my $io = Imager::io_new_bufchain();
+ is($io->write(substr($base, 0, 4096)), 4096,
+ "should be buffered");
+ is($io->write(substr($base, 4096)), length($base) - 4096,
+ "large write, should fill buffer and fall back to direct write");
+ is($io->close, 0, "close it");
+ is(Imager::io_slurp($io), $base, "make sure the data is correct");
+ }
+ { # initial large write with failure
+ # tests error handling for the case where we bypass the buffer
+ # when the write is too large to fit
+ my $io = Imager::io_new_cb(undef, undef, undef, undef);
+ ok($io->flush, "flush with nothing buffered should succeed");
+ is($io->write($base), -1, "large write failure");
+ ok($io->error, "should be in error state");
+ is($io->close, -1, "should fail to close");
+ }
+ { # write that causes a flush then fills the buffer a bit
+ my $io = Imager::io_new_bufchain();
+ is($io->write(substr($base, 0, 6000)), 6000, "fill the buffer a bit");
+ is($io->write(substr($base, 6000, 4000)), 4000,
+ "cause it to flush and then fill some more");
+ is($io->write(substr($base, 10000)), length($base)-10000,
+ "write out the rest of our test data");
+ is($io->close, 0, "close the stream");
+ is(Imager::io_slurp($io), $base, "make sure the data is right");
+ }
+ { # failure on flush on close
+ my $io = Imager::io_new_cb(undef, undef, undef, undef);
+ is($io->putc(ord "A"), ord "A", "something in the buffer");
+ ok(!$io->error, "should be no error yet");
+ is($io->close, -1, "close should failure due to flush error");
+ }
+ { # seek failure
+ my $io = Imager::io_new_cb(undef, undef, undef, undef);
+ is($io->seek(0, SEEK_SET), -1, "seek failure");
+ }
+ { # read a little and seek
+ my $io = Imager::io_new_buffer($base);
+ is($io->getc, ord $base, "read one");
+ is($io->getc, ord substr($base, 1, 1), "read another");
+ is($io->seek(-1, SEEK_CUR), 1, "seek relative back to origin+1");
+ is($io->getc, ord substr($base, 1, 1), "read another again");
+ }
+ { # seek with failing flush
+ my $io = Imager::io_new_cb(undef, undef, undef, undef);
+ is($io->putc(ord "A"), ord "A", "write one");
+ ok(!$io->error, "not in error mode (yet)");
+ is($io->seek(0, SEEK_SET), -1, "seek failure due to flush");
+ ok($io->error, "in error mode");
+ }
+ { # gets()
+ my $data = "test1\ntest2\ntest3";
+ my $io = Imager::io_new_buffer($data);
+ is($io->gets(6), "test1\n", "gets(6)");
+ is($io->gets(5), "test2", "gets(5) (short for the line)");
+ is($io->gets(10), "\n", "gets(10) the rest of the line (the newline)");
+ is($io->gets(), "test3", "gets(default) unterminated line");
+ }
+ { # more gets()
+ my $data = "test1\ntest2\ntest3";
+ my $io = Imager::io_new_buffer($data);
+ is($io->gets(6, ord("1")), "test1", "gets(6) (line terminator 1)");
+ is($io->gets(6, ord("2")), "\ntest2", "gets(6) (line terminator 2)");
+ is($io->gets(6, ord("3")), "\ntest3", "gets(6) (line terminator 3)");
+ is($io->getc, -1, "should be eof");
+ }
}
Imager->close_log;
#!perl -w
use strict;
-use Test::More tests => 47;
+use Test::More tests => 53;
use Imager qw(:all);
-use Imager::Test qw/is_color3 is_color4/;
+use Imager::Test qw/is_color3 is_color4 test_image test_image_mono/;
-d "testout" or mkdir "testout";
open RAW, "< testout/t103_empty.raw"
or die "Cannot open testout/t103_empty.raw: $!";
my $im = Imager->new(xsize => 50, ysize=>50);
- ok(!$im->write(fh => \*RAW, type => 'raw'),
+ ok(!$im->write(fh => \*RAW, type => 'raw', buffered => 0),
"write to open for read handle");
cmp_ok($im->errstr, '=~', '^Could not write to file: write\(\) failure',
"check error message");
"check last channel zeroed");
}
+{
+ my @ims = ( basic => test_image(), mono => test_image_mono() );
+ push @ims, masked => test_image()->masked();
+
+ my $fail_close = sub {
+ Imager::i_push_error(0, "synthetic close failure");
+ return 0;
+ };
+
+ while (my ($type, $im) = splice(@ims, 0, 2)) {
+ my $io = Imager::io_new_cb(sub { 1 }, undef, undef, $fail_close);
+ ok(!$im->write(io => $io, type => "raw"),
+ "write $type image with a failing close handler");
+ like($im->errstr, qr/synthetic close failure/,
+ "check error message");
+ }
+}
+
Imager->close_log;
unless ($ENV{IMAGER_KEEP_FILES}) {
#!perl -w
use Imager ':all';
-use Test::More tests => 195;
+use Test::More tests => 205;
use strict;
-use Imager::Test qw(test_image_raw test_image_16 is_color3 is_color1 is_image);
+use Imager::Test qw(test_image_raw test_image_16 is_color3 is_color1 is_image test_image_named);
+
+$| = 1;
-d "testout" or mkdir "testout";
"compare written and read greyscale images");
my $ooim = Imager->new;
-ok($ooim->read(file=>"testimg/simple.pbm"), "read simple pbm, via OO");
+ok($ooim->read(file=>"testimg/simple.pbm"), "read simple pbm, via OO")
+ or print "# ", $ooim->errstr, "\n";
check_gray(Imager::i_get_pixel($ooim->{IMG}, 0, 0), 0);
check_gray(Imager::i_get_pixel($ooim->{IMG}, 0, 1), 255);
ok($im->write(file=>"testout/t104_alpha.ppm", type=>'pnm'),
"should succeed writing 4 channel image");
my $imread = Imager->new;
- ok($imread->read(file => 'testout/t104_alpha.ppm'), "read it back");
+ ok($imread->read(file => 'testout/t104_alpha.ppm'), "read it back")
+ or print "# ", $imread->errstr, "\n";
is_color3($imread->getpixel('x' => 0, 'y' => 0), 0, 0, 0,
"check transparent became black");
is_color3($imread->getpixel('x' => 8, 'y' => 0), 255, 224, 192,
}
}
-Imager->close_log;
-
{ # image too large handling
{
ok(!Imager->new(file => "testimg/toowide.ppm", filetype => "pnm"),
}
}
+{ # make sure close is checked for each image type
+ my $fail_close = sub {
+ Imager::i_push_error(0, "synthetic close failure");
+ return 0;
+ };
+
+ for my $type (qw(basic basic16 gray gray16 mono)) {
+ my $im = test_image_named($type);
+ my $io = Imager::io_new_cb(sub { 1 }, undef, undef, $fail_close);
+ ok(!$im->write(io => $io, type => "pnm"),
+ "write $type image with a failing close handler");
+ like($im->errstr, qr/synthetic close failure/,
+ "check error message");
+ }
+}
+
+Imager->close_log;
+
unless ($ENV{IMAGER_KEEP_FILES}) {
unlink "testout/t104ppm.log";
unlink map "testout/$_", @files;
#!perl -w
use strict;
-use Test::More tests => 213;
+use Test::More tests => 215;
use Imager qw(:all);
use Imager::Test qw(test_image_raw is_image is_color3 test_image);
$im->read(file => "testimg/$file")
or die "Cannot read $file: ", $im->errstr;
- ok(!$im->write(type => 'bmp', callback => limited_write($limit),
- maxbuffer => 1),
+ my $io = Imager::io_new_cb(limited_write($limit), undef, undef, undef, 1);
+ $io->set_buffered(0);
+ print "# writing with limit of $limit\n";
+ ok(!$im->write(type => 'bmp', io => $io),
"$test_index - $desc: write should fail");
is($im->errstr, $error, "$test_index - $desc: check error message");
is($size, 67800, "check data size");
}
+{ # check close failures are handled correctly
+ my $im = test_image();
+ my $fail_close = sub {
+ Imager::i_push_error(0, "synthetic close failure");
+ return 0;
+ };
+ ok(!$im->write(type => "bmp", callback => sub { 1 },
+ closecb => $fail_close),
+ "check failing close fails");
+ like($im->errstr, qr/synthetic close failure/,
+ "check error message");
+}
+
Imager->close_log;
unless ($ENV{IMAGER_KEEP_FILES}) {
#!perl -w
use Imager qw(:all);
use strict;
-use Test::More tests=>66;
-use Imager::Test qw(is_color4 is_image);
+use Test::More tests=>68;
+use Imager::Test qw(is_color4 is_image test_image);
-d "testout" or mkdir "testout";
}
}
+{ # check close failures are handled correctly
+ my $im = test_image();
+ my $fail_close = sub {
+ Imager::i_push_error(0, "synthetic close failure");
+ return 0;
+ };
+ ok(!$im->write(type => "tga", callback => sub { 1 },
+ closecb => $fail_close),
+ "check failing close fails");
+ like($im->errstr, qr/synthetic close failure/,
+ "check error message");
+}
+
sub write_test {
my ($im, $filename, $wierdpack, $compress, $idstring) = @_;
local *FH;
-d "testout" or mkdir "testout";
-plan tests => 9;
+plan tests => 16;
require Inline;
Inline->import(with => 'Imager');
Inline->import("FORCE"); # force rebuild
+#Inline->import(C => Config => OPTIMIZE => "-g");
Inline->bind(C => <<'EOS');
#include <math.h>
return im;
}
+void
+io_fd(int fd) {
+ Imager::IO io = io_new_fd(fd);
+ i_io_write(io, "test", 4);
+ i_io_close(io);
+ io_glue_destroy(io);
+}
+
+int
+io_bufchain_test() {
+ Imager::IO io = io_new_bufchain();
+ unsigned char *result;
+ size_t size;
+ if (i_io_write(io, "test2", 5) != 5) {
+ fprintf(stderr, "write failed\n");
+ return 0;
+ }
+ if (!i_io_flush(io)) {
+ fprintf(stderr, "flush failed\n");
+ return 0;
+ }
+ if (i_io_close(io) != 0) {
+ fprintf(stderr, "close failed\n");
+ return 0;
+ }
+ size = io_slurp(io, &result);
+ if (size != 5) {
+ fprintf(stderr, "wrong size\n");
+ return 0;
+ }
+ if (memcmp(result, "test2", 5)) {
+ fprintf(stderr, "data mismatch\n");
+ return 0;
+ }
+ if (i_io_seek(io, 0, 0) != 0) {
+ fprintf(stderr, "seek failure\n");
+ return 0;
+ }
+ myfree(result);
+ io_glue_destroy(io);
+
+ return 1;
+}
+
+const char *
+io_buffer_test(SV *in) {
+ STRLEN len;
+ const char *in_str = SvPV(in, len);
+ static char buf[100];
+ Imager::IO io = io_new_buffer(in_str, len, NULL, NULL);
+ ssize_t read_size;
+
+ read_size = i_io_read(io, buf, sizeof(buf)-1);
+ io_glue_destroy(io);
+ if (read_size < 0 || read_size >= sizeof(buf)) {
+ return "";
+ }
+
+ buf[read_size] = '\0';
+
+ return buf;
+}
+
+const char *
+io_peekn_test(SV *in) {
+ STRLEN len;
+ const char *in_str = SvPV(in, len);
+ static char buf[100];
+ Imager::IO io = io_new_buffer(in_str, len, NULL, NULL);
+ ssize_t read_size;
+
+ read_size = i_io_peekn(io, buf, sizeof(buf)-1);
+ io_glue_destroy(io);
+ if (read_size < 0 || read_size >= sizeof(buf)) {
+ return "";
+ }
+
+ buf[read_size] = '\0';
+
+ return buf;
+}
+
+const char *
+io_gets_test(SV *in) {
+ STRLEN len;
+ const char *in_str = SvPV(in, len);
+ static char buf[100];
+ Imager::IO io = io_new_buffer(in_str, len, NULL, NULL);
+ ssize_t read_size;
+
+ read_size = i_io_gets(io, buf, sizeof(buf), 's');
+ io_glue_destroy(io);
+ if (read_size < 0 || read_size >= sizeof(buf)) {
+ return "";
+ }
+
+ return buf;
+}
+
+int
+io_getc_test(SV *in) {
+ STRLEN len;
+ const char *in_str = SvPV(in, len);
+ static char buf[100];
+ Imager::IO io = io_new_buffer(in_str, len, NULL, NULL);
+ int result;
+
+ result = i_io_getc(io);
+ io_glue_destroy(io);
+
+ return result;
+}
+
+int
+io_peekc_test(SV *in) {
+ STRLEN len;
+ const char *in_str = SvPV(in, len);
+ static char buf[100];
+ Imager::IO io = io_new_buffer(in_str, len, NULL, NULL);
+ int result;
+
+ i_io_set_buffered(io, 0);
+
+ result = i_io_peekc(io);
+ io_glue_destroy(io);
+
+ return result;
+}
+
EOS
my $im = Imager->new(xsize=>50, ysize=>50);
is ($imb->REFCNT, $im2b->REFCNT,
"check refcnt of imager object hash between normal and typemap generated");
}
+
+SKIP:
+{
+ use IO::File;
+ my $fd_filename = "testout/t82fd.txt";
+ {
+ my $fh = IO::File->new($fd_filename, "w")
+ or skip("Can't create file: $!", 1);
+ io_fd(fileno($fh));
+ $fh->close;
+ }
+ {
+ my $fh = IO::File->new($fd_filename, "r")
+ or skip("Can't open file: $!", 1);
+ my $data = <$fh>;
+ is($data, "test", "make sure data written to fd");
+ }
+ unlink $fd_filename;
+}
+
+ok(io_bufchain_test(), "check bufchain functions");
+
+is(io_buffer_test("test3"), "test3", "check io_new_buffer() and i_io_read");
+
+is(io_peekn_test("test5"), "test5", "check i_io_peekn");
+
+is(io_gets_test("test"), "tes", "check i_io_gets()");
+
+is(io_getc_test("ABC"), ord "A", "check i_io_getc(_imp)?");
+
+is(io_getc_test("XYZ"), ord "X", "check i_io_peekc(_imp)?");
use lib 't';
use Test::More;
use ExtUtils::Manifest qw(maniread);
+#sub Pod::Coverage::TRACE_ALL() { 1 }
eval "use Test::Pod::Coverage 1.08;";
# 1.08 required for coverage_class support
plan skip_all => "Test::Pod::Coverage 1.08 required for POD coverage" if $@;
);
my @trustme = ( '^open$', );
-plan tests => 19;
+plan tests => 20;
{
pod_coverage_ok('Imager', { also_private => \@private,
pod_coverage_ok('Imager::Regops');
pod_coverage_ok('Imager::Transform');
pod_coverage_ok('Imager::Test');
+ pod_coverage_ok('Imager::IO',
+ {
+ pod_from => "lib/Imager/IO.pod",
+ coverage_class => "Pod::Coverage::Imager",
+ module => "Imager",
+ });
}
{
Imager
Imager's
JPEG
+POSIX
PNG
PNM
RGB
tga_source_read(tga_source *s, unsigned char *buf, size_t pixels) {
int cp = 0, j, k;
if (!s->compressed) {
- if (s->ig->readcb(s->ig, buf, pixels*s->bytepp) != pixels*s->bytepp) return 0;
+ if (i_io_read(s->ig, buf, pixels*s->bytepp) != pixels*s->bytepp) return 0;
return 1;
}
if (s->len == 0) s->state = NoInit;
switch (s->state) {
case NoInit:
- if (s->ig->readcb(s->ig, &s->hdr, 1) != 1) return 0;
+ if (i_io_read(s->ig, &s->hdr, 1) != 1) return 0;
s->len = (s->hdr &~(1<<7))+1;
s->state = (s->hdr & (1<<7)) ? Rle : Raw;
printf("%04d %s: %d\n", cnt++, s->state==Rle?"RLE":"RAW", s->len);
*/
}
- if (s->state == Rle && s->ig->readcb(s->ig, s->cval, s->bytepp) != s->bytepp) return 0;
+ if (s->state == Rle && i_io_read(s->ig, s->cval, s->bytepp) != s->bytepp) return 0;
break;
case Rle:
break;
case Raw:
ml = i_min(s->len, pixels-cp);
- if (s->ig->readcb(s->ig, buf+cp*s->bytepp, ml*s->bytepp) != ml*s->bytepp) return 0;
+ if (i_io_read(s->ig, buf+cp*s->bytepp, ml*s->bytepp) != ml*s->bytepp) return 0;
cp += ml;
s->len -= ml;
break;
int cp = 0;
if (!s->compressed) {
- if (s->ig->writecb(s->ig, buf, pixels*s->bytepp) != pixels*s->bytepp) return 0;
+ if (i_io_write(s->ig, buf, pixels*s->bytepp) != pixels*s->bytepp) return 0;
return 1;
}
while(tlen) {
unsigned char clen = (tlen>128) ? 128 : tlen;
clen--;
- if (s->ig->writecb(s->ig, &clen, 1) != 1) return 0;
+ if (i_io_write(s->ig, &clen, 1) != 1) return 0;
clen++;
- if (s->ig->writecb(s->ig, buf+cp*s->bytepp, clen*s->bytepp) != clen*s->bytepp) return 0;
+ if (i_io_write(s->ig, buf+cp*s->bytepp, clen*s->bytepp) != clen*s->bytepp) return 0;
tlen -= clen;
cp += clen;
}
while (tlen) {
unsigned char clen = (tlen>128) ? 128 : tlen;
clen = (clen - 1) | 0x80;
- if (s->ig->writecb(s->ig, &clen, 1) != 1) return 0;
+ if (i_io_write(s->ig, &clen, 1) != 1) return 0;
clen = (clen & ~0x80) + 1;
- if (s->ig->writecb(s->ig, buf+cp*s->bytepp, s->bytepp) != s->bytepp) return 0;
+ if (i_io_write(s->ig, buf+cp*s->bytepp, s->bytepp) != s->bytepp) return 0;
tlen -= clen;
cp += clen;
}
palbsize = colourmaplength*bytepp;
palbuf = mymalloc(palbsize);
- if (ig->readcb(ig, palbuf, palbsize) != palbsize) {
+ if (i_io_read(ig, palbuf, palbsize) != palbsize) {
i_push_error(errno, "could not read targa colourmap");
return 0;
}
color_pack(palbuf+i*bytepp, bitspp, &val);
}
- if (ig->writecb(ig, palbuf, palbsize) != palbsize) {
+ if (i_io_write(ig, palbuf, palbsize) != palbsize) {
i_push_error(errno, "could not write targa colourmap");
return 0;
}
mm_log((1,"i_readtga(ig %p, length %d)\n", ig, length));
- io_glue_commit_types(ig);
-
- if (ig->readcb(ig, &headbuf, 18) != 18) {
+ if (i_io_read(ig, &headbuf, 18) != 18) {
i_push_error(errno, "could not read targa header");
return NULL;
}
if (header.idlength) {
/* max of 256, so this is safe */
idstring = mymalloc(header.idlength+1);
- if (ig->readcb(ig, idstring, header.idlength) != header.idlength) {
+ if (i_io_read(ig, idstring, header.idlength) != header.idlength) {
i_push_error(errno, "short read on targa idstring");
return NULL;
}
tga_header_pack(&header, headbuf);
- if (ig->writecb(ig, &headbuf, sizeof(headbuf)) != sizeof(headbuf)) {
+ if (i_io_write(ig, &headbuf, sizeof(headbuf)) != sizeof(headbuf)) {
i_push_error(errno, "could not write targa header");
return 0;
}
if (idlen) {
- if (ig->writecb(ig, idstring, idlen) != idlen) {
+ if (i_io_write(ig, idstring, idlen) != idlen) {
i_push_error(errno, "could not write targa idstring");
return 0;
}
if (!tga_palette_write(ig, img, bitspp, i_colorcount(img))) return 0;
if (!img->virtual && !dest.compressed) {
- if (ig->writecb(ig, img->idata, img->bytes) != img->bytes) {
+ if (i_io_write(ig, img->idata, img->bytes) != img->bytes) {
i_push_error(errno, "could not write targa image data");
return 0;
}
myfree(vals);
}
- ig->closecb(ig);
+ if (i_io_close(ig))
+ return 0;
return 1;
}