},
);
+# the readers can read CUR files too
+Imager->register_reader
+ (
+ type=>'cur',
+ single =>
+ sub {
+ my ($im, $io, %hsh) = @_;
+ $im->{IMG} = i_readico_single($io, $hsh{page} || 0);
+
+ unless ($im->{IMG}) {
+ $im->_set_error(Imager->_error_as_msg);
+ return;
+ }
+ return $im;
+ },
+ multiple =>
+ sub {
+ my ($io, %hsh) = @_;
+
+ my @imgs = i_readico_multi($io);
+ unless (@imgs) {
+ Imager->_set_error(Imager->_error_as_msg);
+ return;
+ }
+ return map {
+ bless { IMG => $_, DEBUG => $Imager::DEBUG, ERRSTR => undef }, 'Imager'
+ } @imgs;
+ },
+ );
+
+Imager->register_writer
+ (
+ type=>'ico',
+ single =>
+ sub {
+ my ($im, $io, %hsh) = @_;
+
+ unless (i_writeico_wiol($io, $im->{IMG})) {
+ $im->_set_error(Imager->_error_as_msg);
+ return;
+ }
+ return $im;
+ },
+ multiple =>
+ sub {
+ my ($class, $io, $opts, @images) = @_;
+
+ if (!i_writeico_multi_wiol($io, map $_->{IMG}, @images)) {
+ Imager->_set_error(Imager->_error_as_msg);
+ return;
+ }
+
+ return 1;
+ },
+ );
+
+Imager->register_writer
+ (
+ type=>'cur',
+ single =>
+ sub {
+ my ($im, $io, %hsh) = @_;
+
+ unless (i_writecur_wiol($io, $im->{IMG})) {
+ $im->_set_error(Imager->_error_as_msg);
+ return;
+ }
+ return $im;
+ },
+ multiple =>
+ sub {
+ my ($class, $io, $opts, @images) = @_;
+
+ if (!i_writecur_multi_wiol($io, map $_->{IMG}, @images)) {
+ Imager->_set_error(Imager->_error_as_msg);
+ return;
+ }
+
+ return 1;
+ },
+ );
+
1;
__END__
my @imgs = Imager->read_multi(file => "foo.ico")
or die Imager->errstr;
-=head1 DESCRIPTION
+ $img->write(file => "foo.ico")
+ or die $img->errstr;
+ Imager->write_multi({ file => "foo.ico" }, @imgs)
+ or die Imager->errstr;
+
+=head1 DESCRIPTION
+Imager's MS Icon support is documented in L<Imager::Files>.
=head1 AUTHOR
+Tony Cook <tony@imager.perl.org>
+
=head1 SEE ALSO
Imager, Imager::Files.
myfree(imgs);
}
+int
+i_writeico_wiol(ig, im)
+ Imager::IO ig
+ Imager::ImgRaw im
+
+undef_int
+i_writeico_multi_wiol(ig, ...)
+ Imager::IO ig
+ PREINIT:
+ int i;
+ int img_count;
+ i_img **imgs;
+ CODE:
+ if (items < 2)
+ croak("Usage: i_writeico_multi_wiol(ig, images...)");
+ img_count = items - 1;
+ RETVAL = 1;
+ if (img_count < 1) {
+ RETVAL = 0;
+ i_clear_error();
+ i_push_error(0, "You need to specify images to save");
+ }
+ else {
+ imgs = mymalloc(sizeof(i_img *) * img_count);
+ for (i = 0; i < img_count; ++i) {
+ SV *sv = ST(1+i);
+ imgs[i] = NULL;
+ if (SvROK(sv) && sv_derived_from(sv, "Imager::ImgRaw")) {
+ imgs[i] = INT2PTR(i_img *, SvIV((SV*)SvRV(sv)));
+ }
+ else {
+ i_clear_error();
+ i_push_error(0, "Only images can be saved");
+ myfree(imgs);
+ RETVAL = 0;
+ break;
+ }
+ }
+ if (RETVAL) {
+ RETVAL = i_writeico_multi_wiol(ig, imgs, img_count);
+ }
+ myfree(imgs);
+ }
+ OUTPUT:
+ RETVAL
+
+int
+i_writecur_wiol(ig, im)
+ Imager::IO ig
+ Imager::ImgRaw im
+
+undef_int
+i_writecur_multi_wiol(ig, ...)
+ Imager::IO ig
+ PREINIT:
+ int i;
+ int img_count;
+ i_img **imgs;
+ CODE:
+ if (items < 2)
+ croak("Usage: i_writecur_multi_wiol(ig, images...)");
+ img_count = items - 1;
+ RETVAL = 1;
+ if (img_count < 1) {
+ RETVAL = 0;
+ i_clear_error();
+ i_push_error(0, "You need to specify images to save");
+ }
+ else {
+ imgs = mymalloc(sizeof(i_img *) * img_count);
+ for (i = 0; i < img_count; ++i) {
+ SV *sv = ST(1+i);
+ imgs[i] = NULL;
+ if (SvROK(sv) && sv_derived_from(sv, "Imager::ImgRaw")) {
+ imgs[i] = INT2PTR(i_img *, SvIV((SV*)SvRV(sv)));
+ }
+ else {
+ i_clear_error();
+ i_push_error(0, "Only images can be saved");
+ myfree(imgs);
+ RETVAL = 0;
+ break;
+ }
+ }
+ if (RETVAL) {
+ RETVAL = i_writecur_multi_wiol(ig, imgs, img_count);
+ }
+ myfree(imgs);
+ }
+ OUTPUT:
+ RETVAL
BOOT:
PERL_INITIALIZE_IMAGER_CALLBACKS;
+#!perl -w
+use strict;
use ExtUtils::MakeMaker;
my %opts =
#include "imext.h"
#include "imicon.h"
#include "msicon.h"
+#include <string.h>
static void
ico_push_error(int error) {
image = ico_image_read(file, index, &error);
if (!image) {
- ico_reader_close(file);
ico_push_error(error);
+ i_push_error(0, "error reading ICO/CUR image");
return NULL;
}
if (image->direct) {
int x, y;
- i_color *line_buf = mymalloc(image->width * sizeof(i_color));
+ i_color *line_buf;
i_color *outp;
ico_color_t *inp = image->image_data;
+ if (!i_int_check_image_file_limits(image->width, image->height, 4, 1)) {
+ ico_image_release(image);
+ return NULL;
+ }
+
result = i_img_8_new(image->width, image->height, 4);
if (!result) {
ico_image_release(image);
- ico_reader_close(file);
return NULL;
}
+ line_buf = mymalloc(image->width * sizeof(i_color));
+
for (y = 0; y < image->height; ++y) {
outp = line_buf;
for (x = 0; x < image->width; ++x) {
int y;
unsigned char *image_data;
+ if (!i_int_check_image_file_limits(image->width, image->height, 3, 1)) {
+ ico_image_release(image);
+ return NULL;
+ }
+
result = i_img_pal_new(image->width, image->height, 3, 256);
if (!result) {
ico_image_release(image);
- ico_reader_close(file);
return NULL;
}
if (i_addcolors(result, &c, 1) < 0) {
i_push_error(0, "could not add color to palette");
ico_image_release(image);
- ico_reader_close(file);
i_img_destroy(result);
return NULL;
}
}
*outp++ = '\0';
- i_tags_set(&result->tags, "ico_mask", mask, (outp-mask)-1);
+ if (ico_type(file) == ICON_ICON)
+ i_tags_set(&result->tags, "ico_mask", mask, (outp-mask)-1);
+ else
+ i_tags_set(&result->tags, "cur_mask", mask, (outp-mask)-1);
myfree(mask);
}
- i_tags_setn(&result->tags, "ico_bits", image->bit_count);
- i_tags_set(&result->tags, "ico_type", ico_type(file) == ICON_ICON ? "icon" : "cursor", -1);
- i_tags_set(&result->tags, "i_format", "ico", 3);
+ if (ico_type(file) == ICON_ICON) {
+ i_tags_setn(&result->tags, "ico_bits", image->bit_count);
+ i_tags_set(&result->tags, "i_format", "ico", 3);
+ }
+ else {
+ i_tags_setn(&result->tags, "cur_bits", image->bit_count);
+ i_tags_set(&result->tags, "i_format", "cur", 3);
+ i_tags_setn(&result->tags, "cur_hotspotx", image->hotspot_x);
+ i_tags_setn(&result->tags, "cur_hotspoty", image->hotspot_y);
+ }
ico_image_release(image);
i_img *result;
int error;
+ i_clear_error();
+
file = ico_reader_open(ig, &error);
if (!file) {
ico_push_error(error);
+ i_push_error(0, "error opening ICO/CUR file");
return NULL;
}
- if (index < 0 && index >= ico_image_count(file)) {
- i_push_error(0, "page out of range");
- return NULL;
- }
+ /* the index is range checked by msicon.c - don't duplicate it here */
result = read_one_icon(file, index);
ico_reader_close(file);
int error;
i_img **imgs;
+ i_clear_error();
+
file = ico_reader_open(ig, &error);
if (!file) {
ico_push_error(error);
+ i_push_error(0, "error opening ICO/CUR file");
return NULL;
}
*count = 0;
for (index = 0; index < ico_image_count(file); ++index) {
i_img *im = read_one_icon(file, index);
- if (!im)
+ if (!im)
break;
imgs[(*count)++] = im;
return imgs;
}
+
+static int
+validate_image(i_img *im) {
+ if (im->xsize > 255 || im->ysize > 255) {
+ i_push_error(0, "image too large for ico file");
+ return 0;
+ }
+ if (im->channels < 1 || im->channels > 4) {
+ /* this shouldn't happen, but check anyway */
+ i_push_error(0, "invalid channels");
+ return 0;
+ }
+
+ return 1;
+}
+
+static int
+translate_mask(i_img *im, unsigned char *out, const char *in) {
+ int x, y;
+ int one, zero;
+ int len = strlen(in);
+ int pos;
+ int newline; /* set to the first newline type we see */
+ int notnewline; /* set to whatever in ( "\n\r" newline isn't ) */
+
+ if (len < 3)
+ return 0;
+
+ zero = in[0];
+ one = in[1];
+ if (in[2] == '\n' || in[2] == '\r') {
+ newline = in[2];
+ notnewline = '\n' + '\r' - newline;
+ }
+ else {
+ return 0;
+ }
+
+ pos = 3;
+ y = 0;
+ while (y < im->ysize && pos < len) {
+ x = 0;
+ while (x < im->xsize && pos < len) {
+ if (in[pos] == newline) {
+ /* don't process it, we look for it later */
+ break;
+ }
+ else if (in[pos] == notnewline) {
+ ++pos; /* just drop it */
+ }
+ else if (in[pos] == one) {
+ *out++ = 1;
+ ++x;
+ ++pos;
+ }
+ else if (in[pos] == zero) {
+ *out++ = 0;
+ ++x;
+ ++pos;
+ }
+ else if (in[pos] == ' ' || in[pos] == '\t') {
+ /* just ignore whitespace */
+ ++pos;
+ }
+ else {
+ return 0;
+ }
+ }
+ while (x++ < im->xsize) {
+ *out++ = 0;
+ }
+ while (pos < len && in[pos] != newline)
+ ++pos;
+ if (pos < len && in[pos] == newline)
+ ++pos; /* actually skip the newline */
+
+ ++y;
+ }
+ while (y++ < im->ysize) {
+ for (x = 0; x < im->xsize; ++x)
+ *out++ = 0;
+ }
+
+ return 1;
+}
+
+static void
+derive_mask(i_img *im, ico_image_t *ico) {
+
+ if (im->channels == 1 || im->channels == 3) {
+ /* msicon.c's default mask is what we want */
+ myfree(ico->mask_data);
+ ico->mask_data = NULL;
+ }
+ else {
+ int channel = im->channels - 1;
+ i_sample_t *linebuf = mymalloc(sizeof(i_sample_t) * im->xsize);
+ int x, y;
+ unsigned char *out = ico->mask_data;
+
+ for (y = 0; y < im->ysize; ++y) {
+ i_gsamp(im, 0, im->xsize, y, linebuf, &channel, 1);
+ for (x = 0; x < im->xsize; ++x) {
+ *out++ = linebuf[x] == 255 ? 0 : 1;
+ }
+ }
+ myfree(linebuf);
+ }
+}
+
+static void
+fill_image_base(i_img *im, ico_image_t *ico, const char *mask_name) {
+ int x, y;
+
+ ico->width = im->xsize;
+ ico->height = im->ysize;
+ ico->direct = im->type == i_direct_type;
+ if (ico->direct) {
+ int channels[4];
+ int set_alpha = 0;
+ ico_color_t *out;
+ i_sample_t *in;
+ unsigned char *linebuf = mymalloc(ico->width * 4);
+ ico->image_data = mymalloc(sizeof(ico_color_t) * ico->width * ico->height);
+
+ switch (im->channels) {
+ case 1:
+ channels[0] = channels[1] = channels[2] = channels[3] = 0;
+ ++set_alpha;
+ break;
+
+ case 2:
+ channels[0] = channels[1] = channels[2] = 0;
+ channels[3] = 1;
+ break;
+
+ case 3:
+ channels[0] = 0;
+ channels[1] = 1;
+ channels[2] = 2;
+ channels[3] = 2;
+ ++set_alpha;
+ break;
+
+ case 4:
+ channels[0] = 0;
+ channels[1] = 1;
+ channels[2] = 2;
+ channels[3] = 3;
+ break;
+ }
+
+ out = ico->image_data;
+ for (y = 0; y < im->ysize; ++y) {
+ i_gsamp(im, 0, im->xsize, y, linebuf, channels, 4);
+ in = linebuf;
+ for (x = 0; x < im->xsize; ++x) {
+ out->r = *in++;
+ out->g = *in++;
+ out->b = *in++;
+ out->a = set_alpha ? 255 : *in;
+ in++;
+ ++out;
+ }
+ }
+ myfree(linebuf);
+ ico->palette = NULL;
+ }
+ else {
+ unsigned char *out;
+ i_color *colors;
+ int i;
+ i_palidx *in;
+ i_palidx *linebuf = mymalloc(sizeof(i_palidx) * ico->width);
+
+ ico->image_data = mymalloc(sizeof(ico_color_t) * ico->width * ico->height);
+
+ out = ico->image_data;
+ for (y = 0; y < im->ysize; ++y) {
+ i_gpal(im, 0, im->xsize, y, linebuf);
+ in = linebuf;
+ for (x = 0; x < im->xsize; ++x) {
+ *out++ = *in++;
+ }
+ }
+ myfree(linebuf);
+
+ ico->palette_size = i_colorcount(im);
+ ico->palette = mymalloc(sizeof(ico_color_t) * ico->palette_size);
+ colors = mymalloc(sizeof(i_color) * ico->palette_size);
+ i_getcolors(im, 0, colors, ico->palette_size);
+ for (i = 0; i < ico->palette_size; ++i) {
+ if (im->channels == 1 || im->channels == 2) {
+ ico->palette[i].r = ico->palette[i].g =
+ ico->palette[i].b = colors[i].rgba.r;
+ }
+ else {
+ ico->palette[i].r = colors[i].rgba.r;
+ ico->palette[i].g = colors[i].rgba.g;
+ ico->palette[i].b = colors[i].rgba.b;
+ }
+ }
+ myfree(colors);
+ }
+
+ {
+ /* build the mask */
+ int mask_index;
+
+ ico->mask_data = mymalloc(im->xsize * im->ysize);
+
+ if (!i_tags_find(&im->tags, mask_name, 0, &mask_index)
+ || !im->tags.tags[mask_index].data
+ || !translate_mask(im, ico->mask_data,
+ im->tags.tags[mask_index].data)) {
+ derive_mask(im, ico);
+ }
+ }
+}
+
+static void
+unfill_image(ico_image_t *ico) {
+ myfree(ico->image_data);
+ if (ico->palette)
+ myfree(ico->palette);
+ if (ico->mask_data)
+ myfree(ico->mask_data);
+}
+
+static void
+fill_image_icon(i_img *im, ico_image_t *ico) {
+ fill_image_base(im, ico, "ico_mask");
+ ico->hotspot_x = ico->hotspot_y = 0;
+}
+
+int
+i_writeico_wiol(i_io_glue_t *ig, i_img *im) {
+ ico_image_t ico;
+ int error;
+
+ i_clear_error();
+
+ if (!validate_image(im))
+ return 0;
+
+ fill_image_icon(im, &ico);
+
+ if (!ico_write(ig, &ico, 1, ICON_ICON, &error)) {
+ ico_push_error(error);
+ unfill_image(&ico);
+ return 0;
+ }
+
+ unfill_image(&ico);
+
+ if (i_io_close(ig) < 0) {
+ i_push_error(0, "error closing output");
+ return 0;
+ }
+
+ return 1;
+}
+
+int
+i_writeico_multi_wiol(i_io_glue_t *ig, i_img **ims, int count) {
+ ico_image_t *icons;
+ int error;
+ int i;
+
+ i_clear_error();
+
+ if (count > 0xFFFF) {
+ i_push_error(0, "too many images for ico files");
+ return 0;
+ }
+
+ for (i = 0; i < count; ++i)
+ if (!validate_image(ims[i]))
+ return 0;
+
+ icons = mymalloc(sizeof(ico_image_t) * count);
+
+ for (i = 0; i < count; ++i)
+ fill_image_icon(ims[i], icons + i);
+
+ if (!ico_write(ig, icons, count, ICON_ICON, &error)) {
+ ico_push_error(error);
+ for (i = 0; i < count; ++i)
+ unfill_image(icons + i);
+ myfree(icons);
+ return 0;
+ }
+
+ for (i = 0; i < count; ++i)
+ unfill_image(icons + i);
+ myfree(icons);
+
+ if (i_io_close(ig) < 0) {
+ i_push_error(0, "error closing output");
+ return 0;
+ }
+
+ return 1;
+}
+
+void
+fill_image_cursor(i_img *im, ico_image_t *ico) {
+ int hotx, hoty;
+ fill_image_base(im, ico, "ico_mask");
+
+ if (!i_tags_get_int(&im->tags, "cur_hotspotx", 0, &hotx))
+ hotx = 0;
+ if (!i_tags_get_int(&im->tags, "cur_hotspoty", 0, &hoty))
+ hoty = 0;
+
+ if (hotx < 0)
+ hotx = 0;
+ else if (hotx >= im->xsize)
+ hotx = im->xsize - 1;
+
+ if (hoty < 0)
+ hoty = 0;
+ else if (hoty >= im->ysize)
+ hoty = im->ysize - 1;
+
+ ico->hotspot_x = hotx;
+ ico->hotspot_y = hoty;
+}
+
+int
+i_writecur_wiol(i_io_glue_t *ig, i_img *im) {
+ ico_image_t ico;
+ int error;
+
+ i_clear_error();
+
+ if (!validate_image(im))
+ return 0;
+
+ fill_image_cursor(im, &ico);
+
+ if (!ico_write(ig, &ico, 1, ICON_CURSOR, &error)) {
+ ico_push_error(error);
+ unfill_image(&ico);
+ return 0;
+ }
+
+ unfill_image(&ico);
+
+ if (i_io_close(ig) < 0) {
+ i_push_error(0, "error closing output");
+ return 0;
+ }
+
+ return 1;
+}
+
+int
+i_writecur_multi_wiol(i_io_glue_t *ig, i_img **ims, int count) {
+ ico_image_t *icons;
+ int error;
+ int i;
+
+ i_clear_error();
+
+ if (count > 0xFFFF) {
+ i_push_error(0, "too many images for ico files");
+ return 0;
+ }
+
+ for (i = 0; i < count; ++i)
+ if (!validate_image(ims[i]))
+ return 0;
+
+ icons = mymalloc(sizeof(ico_image_t) * count);
+
+ for (i = 0; i < count; ++i)
+ fill_image_cursor(ims[i], icons + i);
+
+ if (!ico_write(ig, icons, count, ICON_CURSOR, &error)) {
+ ico_push_error(error);
+ for (i = 0; i < count; ++i)
+ unfill_image(icons + i);
+ myfree(icons);
+ return 0;
+ }
+
+ for (i = 0; i < count; ++i)
+ unfill_image(icons + i);
+ myfree(icons);
+
+ if (i_io_close(ig) < 0) {
+ i_push_error(0, "error closing output");
+ return 0;
+ }
+
+ return 1;
+}
+
extern i_img **
i_readico_multi(io_glue *ig, int *count);
+extern int
+i_writeico_wiol(i_io_glue_t *ig, i_img *im);
+
+extern int
+i_writeico_multi_wiol(i_io_glue_t *ig, i_img **im, int count);
+
+extern int
+i_writecur_wiol(i_io_glue_t *ig, i_img *im);
+
+extern int
+i_writecur_multi_wiol(i_io_glue_t *ig, i_img **im, int count);
+
#endif
--- /dev/null
+package Imager::File::CUR;
+use strict;
+
+# all the work is done by Imager::File::ICO
+use Imager::File::ICO;
+
+1;
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
+#include <assert.h>
+static
+int read_packed(io_glue *ig, const char *format, ...);
static int
read_palette(ico_reader_t *file, ico_image_t *image, int *error);
static int
read_1bit_data(ico_reader_t *file, ico_image_t *image, int *error);
static int
read_mask(ico_reader_t *file, ico_image_t *image, int *error);
+static int
+ico_write_validate(ico_image_t const *images, int image_count, int *error);
+static int
+ico_image_size(ico_image_t const *image, int *bits, int *colors);
+static int
+write_packed(i_io_glue_t *ig, char const *format, ...);
+static int
+write_palette(i_io_glue_t *ig, ico_image_t const *image, int *error);
+static int
+write_32_bit(i_io_glue_t *ig, ico_image_t const *image, int *error);
+static int
+write_8_bit(i_io_glue_t *ig, ico_image_t const *image, int *error);
+static int
+write_4_bit(i_io_glue_t *ig, ico_image_t const *image, int *error);
+static int
+write_1_bit(i_io_glue_t *ig, ico_image_t const *image, int *error);
+static int
+write_mask(i_io_glue_t *ig, ico_image_t const *image, int *error);
typedef struct {
int width;
int height;
long offset;
long size;
+ int hotspot_x, hotspot_y;
} ico_reader_image_entry;
/* this was previously declared, now define it */
ico_reader_image_entry *images;
};
-static
-int read_packed(io_glue *ig, const char *format, ...);
-
/*
=head1 NAME
for (i = 0; i < count; ++i) {
long width, height, bytes_in_res, image_offset;
+
ico_reader_image_entry *image = file->images + i;
- if (!read_packed(ig, "bbxxxxxxdd", &width, &height, &bytes_in_res,
- &image_offset)) {
- free(file->images);
- free(file);
- *error = ICOERR_Short_File;
- return NULL;
+ if (type == ICON_ICON) {
+ if (!read_packed(ig, "bb xxxxxx dd", &width, &height, &bytes_in_res,
+ &image_offset)) {
+ free(file->images);
+ free(file);
+ *error = ICOERR_Short_File;
+ return NULL;
+ }
+ image->hotspot_x = image->hotspot_y = 0;
+ }
+ else {
+ long hotspot_x, hotspot_y;
+
+ if (!read_packed(ig, "bb xx ww dd", &width, &height,
+ &hotspot_x, &hotspot_y, &bytes_in_res,
+ &image_offset)) {
+ free(file->images);
+ free(file);
+ *error = ICOERR_Short_File;
+ return NULL;
+ }
+ image->hotspot_x = hotspot_x;
+ image->hotspot_y = hotspot_y;
}
image->width = width;
/*
=item ico_type
- // type of file - 1 for icon, 2 for cursor
+ // type of file - ICON_ICON for icon, ICON_CURSOR for cursor
type = ico_type(file);
=cut
return NULL;
}
+ if (bit_count != 1 && bit_count != 4 && bit_count != 8 && bit_count != 32) {
+ *error = ICOERR_Unknown_Bits;
+ return 0;
+ }
+
result = malloc(sizeof(ico_image_t));
if (!result) {
*error = ICOERR_Out_Of_Memory;
result->palette = NULL;
result->image_data = NULL;
result->mask_data = NULL;
+ result->hotspot_x = im->hotspot_x;
+ result->hotspot_y = im->hotspot_y;
- if (result->direct) {
+ if (bit_count == 32) {
result->palette_size = 0;
result->image_data = malloc(result->width * result->height * sizeof(ico_color_t));
*error = ICOERR_Out_Of_Memory;
return NULL;
}
- if (bit_count == 32) {
- if (!read_32bit_data(file, result, error)) {
- free(result->image_data);
- free(result);
- return NULL;
- }
- }
- else {
- *error = ICOERR_Unknown_Bits;
+ if (!read_32bit_data(file, result, error)) {
free(result->image_data);
free(result);
return NULL;
}
else {
int read_result;
+
result->palette_size = 1 << bit_count;
result->palette = malloc(sizeof(ico_color_t) * result->palette_size);
- result->image_data = malloc(result->width * result->height);
if (!result->palette) {
free(result);
*error = ICOERR_Out_Of_Memory;
return NULL;
}
+ result->image_data = malloc(result->width * result->height);
+ if (!result->image_data) {
+ *error = ICOERR_Out_Of_Memory;
+ free(result->palette);
+ free(result);
+ return 0;
+ }
+
if (!read_palette(file, result, error)) {
free(result->palette);
free(result->image_data);
break;
default:
+ assert(0); /* this can't happen in theory */
read_result = 0;
- *error = ICOERR_Unknown_Bits;
break;
}
/*
=item ico_reader_close
-Releases the file structure.
+Releases the read file structure.
=cut
*/
}
/*
+=back
+
+=head1 WRITING ICON FILES
+
+=over
+
+=item ico_write(ig, images, image_count, type, &error)
+
+Parameters:
+
+=over
+
+=item *
+
+io_glue *ig - an Imager IO object. This only needs to implement
+writing for ico_write()
+
+=item *
+
+ico_image_t *images - array of images to be written.
+
+=item *
+
+int image_count - number of images
+
+=item *
+
+int type - must be ICON_ICON or ICON_CURSOR
+
+=item *
+
+int *error - set to an error code on failure.
+
+=back
+
+Returns non-zero on success.
+
+=cut
+*/
+
+int
+ico_write(i_io_glue_t *ig, ico_image_t const *images, int image_count,
+ int type, int *error) {
+ int i;
+ int start_offset = 6 + 16 * image_count;
+ int current_offset = start_offset;
+
+ if (type != ICON_ICON && type != ICON_CURSOR) {
+ *error = ICOERR_Bad_File_Type;
+ return 0;
+ }
+
+ /* validate the images */
+ if (!ico_write_validate(images, image_count, error))
+ return 0;
+
+ /* write the header */
+ if (!write_packed(ig, "www", 0, type, image_count)) {
+ *error = ICOERR_Write_Failure;
+ return 0;
+ }
+
+ /* work out the offsets of each image */
+ for (i = 0; i < image_count; ++i) {
+ ico_image_t const *image = images + i;
+ int bits, colors;
+ int size = ico_image_size(image, &bits, &colors);
+
+ if (type == ICON_ICON) {
+ if (!write_packed(ig, "bbbbwwdd", image->width, image->height,
+ colors, 0, 1, bits, (unsigned long)size,
+ (unsigned long)current_offset)) {
+ *error = ICOERR_Write_Failure;
+ return 0;
+ }
+ }
+ else {
+ int hotspot_x = image->hotspot_x;
+ int hotspot_y = image->hotspot_y;
+
+ if (hotspot_x < 0)
+ hotspot_x = 0;
+ else if (hotspot_x >= image->width)
+ hotspot_x = image->width - 1;
+ if (hotspot_y < 0)
+ hotspot_y = 0;
+ else if (hotspot_y >= image->height)
+ hotspot_y = image->height - 1;
+
+ if (!write_packed(ig, "bbbbwwdd", image->width, image->height,
+ colors, 0, hotspot_x, hotspot_y, (unsigned long)size,
+ (unsigned long)current_offset)) {
+ *error = ICOERR_Write_Failure;
+ return 0;
+ }
+ }
+ current_offset += size;
+ }
+
+ /* write out each image */
+ for (i = 0; i < image_count; ++i) {
+ ico_image_t const *image = images + i;
+
+ if (image->direct) {
+ if (!write_32_bit(ig, image, error))
+ return 0;
+ }
+ else {
+ if (image->palette_size <= 2) {
+ if (!write_1_bit(ig, image, error))
+ return 0;
+ }
+ else if (image->palette_size <= 16) {
+ if (!write_4_bit(ig, image, error))
+ return 0;
+ }
+ else {
+ if (!write_8_bit(ig, image, error))
+ return 0;
+ }
+ }
+ if (!write_mask(ig, image, error))
+ return 0;
+ }
+
+ return 1;
+}
+
+/*
+=back
+
+=head1 ERROR MESSAGES
+
+=over
+
=item ico_error_message
Converts an error code into an error message.
msg = "I/O error";
break;
+ case ICOERR_Write_Failure:
+ msg = "Write failure";
+ break;
+
case ICOERR_Invalid_File:
msg = "Not an icon file";
break;
msg = "Image index out of range";
break;
+ case ICOERR_Bad_File_Type:
+ msg = "Bad file type parameter";
+ break;
+
+ case ICOERR_Invalid_Width:
+ msg = "Invalid image width";
+ break;
+
+ case ICOERR_Invalid_Height:
+ msg = "Invalid image height";
+ break;
+
+ case ICOERR_Invalid_Palette:
+ msg = "Invalid Palette";
+ break;
+
+ case ICOERR_No_Data:
+ msg = "No image data in image supplied to ico_write";
+ break;
+
case ICOERR_Out_Of_Memory:
msg = "Out of memory";
break;
unsigned char *read_buffer = malloc(line_bytes);
int y;
int x;
+ int mask;
unsigned char *inp, *outp;
if (!read_buffer) {
outp = image->mask_data + y * image->width;
inp = read_buffer;
+ mask = 0x80;
for (x = 0; x < image->width; ++x) {
- *outp++ = (*inp >> (7 - (x & 7))) & 1;
- if ((x & 7) == 7)
+ *outp++ = (*inp & mask) ? 1 : 0;
+ mask >>= 1;
+ if (!mask) {
+ mask = 0x80;
++inp;
+ }
}
}
free(read_buffer);
return 1;
}
+/*
+=item ico_write_validate
+
+Check each image to make sure it can go into an icon file.
+
+=cut
+*/
+
+static int
+ico_write_validate(ico_image_t const *images, int image_count, int *error) {
+ int i;
+
+ for (i = 0; i < image_count; ++i) {
+ ico_image_t const *image = images + i;
+
+ if (image->width < 1 || image->width > 255) {
+ *error = ICOERR_Invalid_Width;
+ return 0;
+ }
+ if (image->height < 1 || image->height > 255) {
+ *error = ICOERR_Invalid_Height;
+ return 0;
+ }
+ if (!image->image_data) {
+ *error = ICOERR_No_Data;
+ return 0;
+ }
+ if (!image->direct) {
+ if (image->palette_size < 0 || image->palette_size > 256
+ || !image->palette) {
+ *error = ICOERR_Invalid_Palette;
+ return 0;
+ }
+ }
+ }
+
+ return 1;
+}
+
+/*
+=item ico_image_size
+
+Calculate how much space the icon takes up in the file.
+
+=cut
+*/
+
+static int
+ico_image_size(ico_image_t const *image, int *bits, int *colors) {
+ int size = 40; /* start with the BITMAPINFOHEADER */
+
+ /* add in the image area */
+ if (image->direct) {
+ *bits = 32;
+ *colors = 0;
+ size += image->width * 4 * image->height;
+ }
+ else {
+ if (image->palette_size <= 2) {
+ *bits = 1;
+ *colors = 2;
+ }
+ else if (image->palette_size <= 16) {
+ *bits = 4;
+ *colors = 16;
+ }
+ else {
+ *bits = 8;
+ *colors = 0;
+ }
+
+ /* palette size */
+ size += *colors * 4;
+
+ /* image data size */
+ size += (image->width * *bits + 31) / 32 * 4 * image->height;
+ }
+
+ /* add in the mask */
+ size += (image->width + 31) / 32 * 4 * image->height;
+
+ return size;
+}
+
+/*
+=item write_packed
+
+Pack numbers given a format to a stream.
+
+=cut
+*/
+
+static int
+write_packed(i_io_glue_t *ig, char const *format, ...) {
+ unsigned char buffer[100];
+ va_list ap;
+ unsigned long p;
+ int size;
+ const char *formatp;
+ unsigned char *bufp;
+
+ /* write efficiently, work out the size of the buffer */
+ size = 0;
+ formatp = format;
+ while (*formatp) {
+ switch (*formatp++) {
+ case 'b': size++; break;
+ case 'w': size += 2; break;
+ case 'd': size += 4; break;
+ case ' ': break; /* space to separate components */
+ default:
+ fprintf(stderr, "invalid unpack char in %s\n", format);
+ exit(1);
+ }
+ }
+
+ if (size > sizeof(buffer)) {
+ /* catch if we need a bigger buffer, but 100 is plenty */
+ fprintf(stderr, "format %s too long for buffer\n", format);
+ exit(1);
+ }
+
+ va_start(ap, format);
+
+ bufp = buffer;
+ while (*format) {
+
+ switch (*format) {
+ case 'b':
+ p = va_arg(ap, int);
+ *bufp++ = p;
+ break;
+
+ case 'w':
+ p = va_arg(ap, int);
+ *bufp++ = p & 0xFF;
+ *bufp++ = (p >> 8) & 0xFF;
+ break;
+
+ case 'd':
+ p = va_arg(ap, unsigned long);
+ *bufp++ = p & 0xFF;
+ *bufp++ = (p >> 8) & 0xFF;
+ *bufp++ = (p >> 16) & 0xFF;
+ *bufp++ = (p >> 24) & 0xFF;
+ break;
+
+ case ' ':
+ /* nothing to do */
+ break;
+ }
+ ++format;
+ }
+
+ if (i_io_write(ig, buffer, size) != size)
+ return 0;
+
+ return 1;
+}
+
+/*
+=item write_palette
+
+Write the palette for an icon.
+
+=cut
+*/
+
+static int
+write_palette(i_io_glue_t *ig, ico_image_t const *image, int *error) {
+ int full_size = image->palette_size;
+ unsigned char *writebuf, *outp;
+ ico_color_t *colorp;
+ int i;
+
+ if (image->palette_size <= 2)
+ full_size = 2;
+ else if (image->palette_size <= 16)
+ full_size = 16;
+ else
+ full_size = 256;
+
+ writebuf = calloc(full_size, 4);
+ if (!writebuf) {
+ *error = ICOERR_Out_Of_Memory;
+ return 0;
+ }
+ outp = writebuf;
+ colorp = image->palette;
+ for (i = 0; i < image->palette_size; ++i) {
+ *outp++ = colorp->b;
+ *outp++ = colorp->g;
+ *outp++ = colorp->r;
+ *outp++ = 0xFF;
+ ++colorp;
+ }
+ for (; i < full_size; ++i) {
+ *outp++ = 0;
+ *outp++ = 0;
+ *outp++ = 0;
+ *outp++ = 0;
+ }
+
+ if (i_io_write(ig, writebuf, full_size * 4) != full_size * 4) {
+ *error = ICOERR_Write_Failure;
+ free(writebuf);
+ return 0;
+ }
+
+ free(writebuf);
+
+ return 1;
+}
+
+/*
+=item write_bitmapinfoheader
+
+Write the BITMAPINFOHEADER for an icon image.
+
+=cut
+*/
+
+static int
+write_bitmapinfoheader(i_io_glue_t *ig, ico_image_t const *image, int *error,
+ int bit_count, int clr_used) {
+ if (!write_packed(ig, "d dd w w d d dd dd",
+ 40UL, /* biSize */
+ (unsigned long)image->width,
+ (unsigned long)2 * image->height, /* biWidth/biHeight */
+ 1, bit_count, /* biPlanes, biBitCount */
+ 0UL, 0UL, /* biCompression, biSizeImage */
+ 0UL, 0UL, /* bi(X|Y)PetsPerMeter */
+ (unsigned long)clr_used, /* biClrUsed */
+ 0UL)) { /* biClrImportant */
+ *error = ICOERR_Write_Failure;
+ return 0;
+ }
+
+ return 1;
+}
+
+/*
+=item write_32_bit
+
+Write 32-bit image data to the icon.
+
+=cut
+*/
+
+static int
+write_32_bit(i_io_glue_t *ig, ico_image_t const *image, int *error) {
+ unsigned char *writebuf;
+ ico_color_t *data = image->image_data, *colorp;
+ unsigned char *writep;
+ int x, y;
+
+ if (!write_bitmapinfoheader(ig, image, error, 32, 0)) {
+ return 0;
+ }
+
+ writebuf = malloc(image->width * 4);
+ if (!writebuf) {
+ *error = ICOERR_Out_Of_Memory;
+ return 0;
+ }
+
+ for (y = image->height-1; y >= 0; --y) {
+ writep = writebuf;
+ colorp = data + y * image->width;
+ for (x = 0; x < image->width; ++x) {
+ *writep++ = colorp->b;
+ *writep++ = colorp->g;
+ *writep++ = colorp->r;
+ *writep++ = colorp->a;
+ ++colorp;
+ }
+ if (i_io_write(ig, writebuf, image->width * 4) != image->width * 4) {
+ *error = ICOERR_Write_Failure;
+ free(writebuf);
+ return 0;
+ }
+ }
+
+ free(writebuf);
+
+ return 1;
+}
+
+/*
+=item write_8_bit
+
+Write 8 bit image data.
+
+=cut
+*/
+
+static int
+write_8_bit(i_io_glue_t *ig, ico_image_t const *image, int *error) {
+ static const unsigned char zeros[3] = { '\0' };
+ int y;
+ const unsigned char *data = image->image_data;
+ int zero_count = (0U - (unsigned)image->width) & 3;
+
+ if (!write_bitmapinfoheader(ig, image, error, 8, 256)) {
+ return 0;
+ }
+
+ if (!write_palette(ig, image, error))
+ return 0;
+
+ for (y = image->height-1; y >= 0; --y) {
+ if (i_io_write(ig, data + y * image->width,
+ image->width) != image->width) {
+ *error = ICOERR_Write_Failure;
+ return 0;
+ }
+ if (zero_count) {
+ if (i_io_write(ig, zeros, zero_count) != zero_count) {
+ *error = ICOERR_Write_Failure;
+ return 0;
+ }
+ }
+ }
+
+ return 1;
+}
+
+/*
+=item write_4_bit
+
+Write 4 bit image data.
+
+=cut
+*/
+
+static int
+write_4_bit(i_io_glue_t *ig, ico_image_t const *image, int *error) {
+ int line_size = ((image->width + 1) / 2 + 3) / 4 * 4;
+ unsigned char *writebuf, *outp;
+ int x, y;
+ unsigned char const *data = image->image_data;
+ unsigned char const *pixelp;
+
+ if (!write_bitmapinfoheader(ig, image, error, 4, 16)) {
+ return 0;
+ }
+
+ if (!write_palette(ig, image, error))
+ return 0;
+
+ writebuf = malloc(line_size);
+ if (!writebuf) {
+ *error = ICOERR_Out_Of_Memory;
+ return 0;
+ }
+
+ for (y = image->height-1; y >= 0; --y) {
+ pixelp = data + y * image->width;
+ outp = writebuf;
+ memset(writebuf, 0, line_size);
+ for (x = 0; x < image->width; ++x) {
+ if (x & 1) {
+ *outp |= *pixelp++ & 0x0F;
+ ++outp;
+ }
+ else {
+ *outp |= *pixelp++ << 4;
+ }
+ }
+
+ if (i_io_write(ig, writebuf, line_size) != line_size) {
+ *error = ICOERR_Write_Failure;
+ free(writebuf);
+ return 0;
+ }
+ }
+
+ free(writebuf);
+
+ return 1;
+}
+
+/*
+=item write_1_bit
+
+Write 1 bit image data.
+
+=cut
+*/
+
+static int
+write_1_bit(i_io_glue_t *ig, ico_image_t const *image, int *error) {
+ int line_size = (image->width + 31) / 32 * 4;
+ unsigned char *writebuf = malloc(line_size);
+ unsigned char *outp;
+ unsigned char const *data, *pixelp;
+ int x,y;
+ unsigned mask;
+
+ if (!write_bitmapinfoheader(ig, image, error, 1, 2)) {
+ return 0;
+ }
+
+ if (!write_palette(ig, image, error))
+ return 0;
+
+ if (!writebuf) {
+ *error = ICOERR_Out_Of_Memory;
+ return 0;
+ }
+
+ data = image->image_data;
+ for (y = image->height-1; y >= 0; --y) {
+ memset(writebuf, 0, line_size);
+ pixelp = data + y * image->width;
+ outp = writebuf;
+ mask = 0x80;
+ for (x = 0; x < image->width; ++x) {
+ if (*pixelp)
+ *outp |= mask;
+ mask >>= 1;
+ if (!mask) {
+ mask = 0x80;
+ outp++;
+ }
+ }
+ if (i_io_write(ig, writebuf, line_size) != line_size) {
+ *error = ICOERR_Write_Failure;
+ free(writebuf);
+ return 0;
+ }
+ }
+
+ free(writebuf);
+
+ return 1;
+}
+
+/*
+=item write_mask
+
+Write the AND mask.
+
+=cut
+*/
+
+static int
+write_mask(i_io_glue_t *ig, ico_image_t const *image, int *error) {
+ int line_size = (image->width + 31) / 32 * 4;
+ unsigned char *writebuf = malloc(line_size);
+ unsigned char *outp;
+ unsigned char const *data, *pixelp;
+ int x,y;
+ unsigned mask;
+
+ if (!writebuf) {
+ *error = ICOERR_Out_Of_Memory;
+ return 0;
+ }
+
+ data = image->mask_data;
+ if (data) {
+ for (y = image->height-1; y >= 0; --y) {
+ memset(writebuf, 0, line_size);
+ pixelp = data + y * image->width;
+ outp = writebuf;
+ mask = 0x80;
+ for (x = 0; x < image->width; ++x) {
+ if (*pixelp)
+ *outp |= mask;
+ mask >>= 1;
+ if (!mask) {
+ mask = 0x80;
+ outp++;
+ }
+ ++pixelp;
+ }
+ if (i_io_write(ig, writebuf, line_size) != line_size) {
+ *error = ICOERR_Write_Failure;
+ free(writebuf);
+ return 0;
+ }
+ }
+ }
+ else {
+ memset(writebuf, 0, line_size);
+ for (y = image->height-1; y >= 0; --y) {
+ if (i_io_write(ig, writebuf, line_size) != line_size) {
+ *error = ICOERR_Write_Failure;
+ free(writebuf);
+ return 0;
+ }
+ }
+ }
+
+ free(writebuf);
+
+ return 1;
+}
+
/*
=back
int palette_size;
ico_color_t *palette;
unsigned char *mask_data;
+ int hotspot_x, hotspot_y;
} ico_image_t;
extern ico_reader_t *ico_reader_open(i_io_glue_t *ig, int *error);
extern void ico_image_release(ico_image_t *image);
extern void ico_reader_close(ico_reader_t *file);
+extern int ico_write(i_io_glue_t *ig, ico_image_t const *images,
+ int image_count, int type, int *error);
+
extern size_t ico_error_message(int error, char *buffer, size_t buffer_size);
#define ICO_MAX_MESSAGE 80
#define ICOERR_Short_File 100
#define ICOERR_File_Error 101
+#define ICOERR_Write_Failure 102
#define ICOERR_Invalid_File 200
#define ICOERR_Unknown_Bits 201
#define ICOERR_Bad_Image_Index 300
+#define ICOERR_Bad_File_Type 301
+#define ICOERR_Invalid_Width 302
+#define ICOERR_Invalid_Height 303
+#define ICOERR_Invalid_Palette 304
+#define ICOERR_No_Data 305
#define ICOERR_Out_Of_Memory 400
#!perl -w
use strict;
-use Test::More tests => 60;
+use Test::More tests => 94;
BEGIN { use_ok('Imager::File::ICO'); }
-d 'testout' or mkdir 'testout';
my $im = Imager->new;
+# type=>'ico' or 'cur' and read ico and cur since they're pretty much
+# the same
ok($im->read(file => "testimg/rgba3232.ico", type=>"ico"),
"read 32 bit")
or print "# ", $im->errstr, "\n";
is($im->type, 'direct', "check type");
is($im->tags(name => 'ico_bits'), 32, "check ico_bits tag");
is($im->tags(name => 'i_format'), 'ico', "check i_format tag");
-is($im->tags(name => 'ico_type'), 'icon', "check ico_type tag");
my $mask = '.*
..........................******
..........................******
is($im->colorcount, 256, "color count");
is($im->tags(name => 'ico_bits'), 8, "check ico_bits tag");
is($im->tags(name => 'i_format'), 'ico', "check i_format tag");
-is($im->tags(name => 'ico_type'), 'icon', "check ico_type tag");
SKIP:
{
my $comp = Imager->new;
is($im->colorcount, 16, "color count");
is($im->tags(name => 'ico_bits'), 4, "check ico_bits tag");
is($im->tags(name => 'i_format'), 'ico', "check i_format tag");
-is($im->tags(name => 'ico_type'), 'icon', "check ico_type tag");
SKIP:
{
my $comp = Imager->new;
is($im->getwidth, 32, "check height");
is($im->type, 'paletted', "check type");
is($im->colorcount, 2, "color count");
-is($im->tags(name => 'ico_bits'), 1, "check ico_bits tag");
-is($im->tags(name => 'i_format'), 'ico', "check i_format tag");
-is($im->tags(name => 'ico_type'), 'cursor', "check ico_type tag");
+is($im->tags(name => 'cur_bits'), 1, "check ico_bits tag");
+is($im->tags(name => 'i_format'), 'cur', "check i_format tag");
$im->write(file=>'testout/pal13232.ppm');
# combo was created with the GIMP, which has a decent mechanism for selecting
"check image data 1(31,31)");
is_deeply([ $imgs[2]->getpixel(x=>15, 'y'=>15)->rgba ], [ 17, 231, 177, 255 ],
"check image data 2(15,15)");
+
+$im = Imager->new(xsize=>32, ysize=>32);
+$im->box(filled=>1, color=>'FF0000');
+$im->box(filled=>1, color=>'0000FF', xmin => 6, ymin=>0, xmax => 21, ymax=>15);
+$im->box(filled=>1, color=>'00FF00', xmin => 10, ymin=>16, xmax => 25, ymax=>31);
+
+ok($im->write(file=>'testout/t10_32.ico', type=>'ico'),
+ "write 32-bit icon");
+
+my $im2 = Imager->new;
+ok($im2->read(file=>'testout/t10_32.ico', type=>'ico'),
+ "read it back in");
+
+is(Imager::i_img_diff($im->{IMG}, $im2->{IMG}), 0,
+ "check they're the same");
+is($im->bits, $im2->bits, "check same bits");
+
+{
+ my $im = Imager->new(xsize => 32, ysize => 32);
+ $im->box(filled=>1, color=>'#FF00FF');
+ my $data;
+ ok(Imager->write_multi({ data => \$data, type=>'ico' }, $im, $im),
+ "write multi icons");
+ ok(length $data, "and it wrote data");
+ my @im = Imager->read_multi(data => $data);
+ is(@im, 2, "got all the images back");
+ is(Imager::i_img_diff($im->{IMG}, $im[0]{IMG}), 0, "check first image");
+ is(Imager::i_img_diff($im->{IMG}, $im[1]{IMG}), 0, "check second image");
+}
+
+{ # 1 channel image
+ my $im = Imager->new(xsize => 32, ysize => 32, channels => 1);
+ $im->box(filled=>1, color => [ 128, 0, 0 ]);
+ my $data;
+ ok($im->write(data => \$data, type=>'ico'), "write 1 channel image");
+ my $im2 = Imager->new;
+ ok($im2->read(data => $data), "read it back");
+ is($im2->getchannels, 4, "check channels");
+ my $imrgb = $im->convert(preset => 'rgb')
+ ->convert(preset => 'addalpha');
+ is(Imager::i_img_diff($imrgb->{IMG}, $im2->{IMG}), 0,
+ "check image matches expected");
+}
+
+{ # 2 channel image
+ my $base = Imager->new(xsize => 32, ysize => 32, channels => 2);
+ $base->box(filled => 1, color => [ 64, 192, 0 ]);
+ my $data;
+ ok($base->write(data => \$data, type=>'ico'), "write 2 channel image");
+ my $read = Imager->new;
+ ok($read->read(data => $data), "read it back");
+ is($read->getchannels, 4, "check channels");
+ my $imrgb = $base->convert(preset => 'rgb');
+ is(Imager::i_img_diff($imrgb->{IMG}, $read->{IMG}), 0,
+ "check image matches expected");
+}
+
+{ # 4 channel image
+ my $base = Imager->new(xsize => 32, ysize => 32, channels => 4);
+ $base->box(filled=>1, ymax => 15, color => [ 255, 0, 255, 128 ]);
+ $base->box(filled=>1, ymin => 16, color => [ 0, 255, 255, 255 ]);
+ my $data;
+ ok($base->write(data => \$data, type=>'ico'), "write 4 channel image");
+ my $read = Imager->new;
+ ok($read->read(data => $data, type=>'ico'), "read it back")
+ or print "# ", $read->errstr, "\n";
+ is(Imager::i_img_diff($base->{IMG}, $read->{IMG}), 0,
+ "check image matches expected");
+}
+
+{ # mask handling
+ my $base = Imager->new(xsize => 16, ysize => 16, channels => 3);
+ $base->box(filled=>1, xmin => 5, xmax => 10, color => '#0000FF');
+ $base->box(filled=>1, ymin => 5, ymax => 10, color => '#0000FF');
+ my $mask = <<EOS; # CR in this to test it's skipped correctly
+01
+0000011111100000
+00000111111 00000xx
+00000111111000
+00000111111000
+0000011111100000
+1111111111111111
+1111111111111111
+1111111111111111
+1111111111111111
+1111111111111111
+1111111111111111
+1010101010101010
+1010101010101010
+1010101010101010
+1010101010101010
+1010101010101010
+EOS
+ $mask =~ s/\n/\r\n/g; # to test alternate newline handling is correct
+ $base->settag(name => 'ico_mask', value => $mask);
+ my $saved_mask = $base->tags(name => 'ico_mask');
+ my $data;
+ ok($base->write(data => \$data, type => 'ico'),
+ "write with mask tag set");
+ my $read = Imager->new;
+ ok($read->read(data => $data), "read it back");
+ my $mask2 = $mask;
+ $mask2 =~ tr/01/.*/;
+ $mask2 =~ s/\n$//;
+ $mask2 =~ tr/\r x//d;
+ $mask2 =~ s/^(.{3,19})$/$1 . "." x (16 - length $1)/gem;
+ my $read_mask = $read->tags(name => 'ico_mask');
+ is($read_mask, $mask2, "check mask is correct");
+}
+
+{ # mask too short to handle
+ my $mask = "xx";
+ my $base = Imager->new(xsize => 16, ysize => 16, channels => 3);
+ $base->box(filled=>1, xmin => 5, xmax => 10, color => '#0000FF');
+ $base->box(filled=>1, ymin => 5, ymax => 10, color => '#0000FF');
+ $base->settag(name => 'ico_mask', value => $mask);
+ my $data;
+ ok($base->write(data => \$data, type=>'ico'),
+ "save icon with short mask tag");
+ my $read = Imager->new;
+ ok($read->read(data => $data), "read it back");
+ my $read_mask = $read->tags(name => 'ico_mask');
+ my $expected_mask = ".*" . ( "\n" . "." x 16 ) x 16;
+ is($read_mask, $expected_mask, "check the mask");
+
+ # mask that doesn't match what we expect
+ $base->settag(name => 'ico_mask', value => 'abcd');
+ ok($base->write(data => \$data, type => 'ico'),
+ "write with bad format mask tag");
+ ok($read->read(data => $data), "read it back");
+ $read_mask = $read->tags(name => 'ico_mask');
+ is($read_mask, $expected_mask, "check the mask");
+
+ # mask with invalid char
+ $base->settag(name => 'ico_mask', value => ".*\n....xxx..");
+ ok($base->write(data => \$data, type => 'ico'),
+ "write with unexpected chars in mask");
+ ok($read->read(data => $data), "read it back");
+ $read_mask = $read->tags(name => 'ico_mask');
+ is($read_mask, $expected_mask, "check the mask");
+}
+
+{ # check handling of greyscale paletted
+ my $base = Imager->new(xsize => 16, ysize => 16, channels => 1,
+ type => 'paletted');
+ my @grays = map Imager::Color->new($_),
+ "000000", "666666", "CCCCCC", "FFFFFF";
+ ok($base->addcolors(colors => \@grays), "add some colors");
+ $base->box(filled => 1, color => $grays[1], xmax => 7, ymax => 7);
+ $base->box(filled => 1, color => $grays[1], xmax => 7, ymin => 8);
+ $base->box(filled => 1, color => $grays[1], xmin => 8, ymax => 7);
+ $base->box(filled => 1, color => $grays[1], xmin => 8, ymax => 8);
+ my $data;
+ ok($base->write(data => \$data, type => 'ico'),
+ "write grayscale paletted");
+ my $read = Imager->new;
+ ok($read->read(data => $data), "read it back")
+ or print "# ", $read->errstr, "\n";
+ is($read->type, 'paletted', "check type");
+ is($read->getchannels, 3, "check channels");
+ my $as_rgb = $base->convert(preset => 'rgb');
+ is(Imager::i_img_diff($base->{IMG}, $read->{IMG}), 0,
+ "check the image");
+}
--- /dev/null
+#!perl -w
+use strict;
+use Test::More tests => 25;
+
+BEGIN { use_ok('Imager::File::CUR'); }
+
+-d 'testout' or mkdir 'testout';
+
+my $im = Imager->new;
+
+ok($im->read(file => 'testimg/pal43232.cur', type=>'cur'),
+ "read 4 bit");
+is($im->getwidth, 32, "check width");
+is($im->getheight, 32, "check width");
+is($im->type, 'paletted', "check type");
+is($im->tags(name => 'cur_bits'), 4, "check cur_bits tag");
+is($im->tags(name => 'i_format'), 'cur', "check i_format tag");
+is($im->tags(name => 'cur_hotspotx'), 1, "check cur_hotspotx tag");
+is($im->tags(name => 'cur_hotspoty'), 18, "check cur_hotspoty tag");
+my $mask = ".*" . ("\n" . "." x 32) x 32;
+is($im->tags(name => 'cur_mask'), $mask, "check cur_mask tag");
+
+# these should get pushed back into range on saving
+$im->settag(name => 'cur_hotspotx', value => 32);
+$im->settag(name => 'cur_hotspoty', value => -1);
+ok($im->write(file=>'testout/hotspot.cur', type=>'cur'),
+ "save with oor hotspot")
+ or print "# ",$im->errstr, "\n";
+{
+ my $im2 = Imager->new;
+ ok($im2->read(file=>'testout/hotspot.cur', type=>'cur'),
+ "re-read the hotspot set cursor")
+ or print "# ", $im->errstr, "\n";
+ is($im2->tags(name => 'cur_hotspotx'), 31, "check cur_hotspotx tag");
+ is($im2->tags(name => 'cur_hotspoty'), 0, "check cur_hotspoty tag");
+}
+
+$im->settag(name => 'cur_hotspotx', value => -1);
+$im->settag(name => 'cur_hotspoty', value => 32);
+ok($im->write(file=>'testout/hotspot2.cur', type=>'cur'),
+ "save with oor hotspot")
+ or print "# ",$im->errstr, "\n";
+
+{
+ my $im2 = Imager->new;
+ ok($im2->read(file=>'testout/hotspot2.cur', type=>'cur'),
+ "re-read the hotspot set cursor")
+ or print "# ", $im->errstr, "\n";
+ is($im2->tags(name => 'cur_hotspotx'), 0, "check cur_hotspotx tag");
+ is($im2->tags(name => 'cur_hotspoty'), 31, "check cur_hotspoty tag");
+}
+
+{
+ my $data = '';
+ ok($im->write(data => \$data, type => 'cur'),
+ "write single to data");
+ print "# ", length $data, " bytes written\n";
+ my $im2 = Imager->new;
+ ok($im2->read(data => $data), "read back in");
+ is(Imager::i_img_diff($im->{IMG}, $im2->{IMG}), 0, "check image");
+}
+
+{
+ my $data = '';
+ ok(Imager->write_multi({ type => 'cur', data => \$data }, $im, $im),
+ "write multiple images");
+ print "# ", length $data, " bytes written\n";
+ my @im = Imager->read_multi(type => 'cur', data => $data)
+ or print "# ", Imager->errstr, "\n";
+ is(@im, 2, "read them back in");
+ is(Imager::i_img_diff($im->{IMG}, $im[0]{IMG}), 0, "check first image");
+ is(Imager::i_img_diff($im->{IMG}, $im[1]{IMG}), 0, "check second image");
+}
--- /dev/null
+#!perl -w
+use strict;
+use Test::More tests => 1;
+use Imager;
+
+# checks that we load the CUR handler automatically
+my $im = Imager->new;
+ok($im->read(file => 'testimg/pal43232.cur'),
+ "check that cursor reader loaded correctly for singles")
+ or print "# ", $im->errstr, "\n";
--- /dev/null
+#!perl -w
+use strict;
+use Test::More tests => 1;
+use Imager;
+
+# checks that we load the CUR handler automatically for multiple image reads
+my @im = Imager->read_multi(file=>'testimg/pal43232.cur');
+is(scalar(@im), 1,
+ "check that cursor reader loaded correctly for singles")
+ or print "# ", Imager->errstr, "\n";
--- /dev/null
+#!perl -w
+use strict;
+use Imager;
+use Test::More tests => 40;
+
+sub get_data;
+
+{ # test file limits are obeyed (paletted)
+ Imager->set_file_limits(reset => 1, width => 10);
+ my $im = Imager->new;
+ ok(!$im->read(file => 'testimg/pal13232.ico'), "can't read overwide image");
+ like($im->errstr, qr/image width/, "check message");
+}
+
+{ # test file limits are obeyed (direct)
+ Imager->set_file_limits(reset => 1, width => 10);
+ my $im = Imager->new;
+ ok(!$im->read(file => 'testimg/rgba3232.ico'), "can't read overwide image");
+ like($im->errstr, qr/image width/, "check message");
+}
+
+Imager->set_file_limits(reset => 1);
+
+{ # file too short for magic
+ my $im = Imager->new;
+ ok(!$im->read(data=>"XXXX", type=>'ico'), "Can't read short image file");
+ is($im->errstr, "error opening ICO/CUR file: Short read",
+ "check error message");
+}
+
+{ # read non-icon
+ my $im = Imager->new;
+ ok(!$im->read(file=>'t/t50readfail.t', type=>'ico'),
+ "script isn't an icon");
+ is($im->errstr, "error opening ICO/CUR file: Not an icon file",
+ "check message");
+}
+
+{ # file with not enough icon structures
+ my $im = Imager->new;
+ my $data = pack "H*", "00000100010000";
+ ok(!$im->read(data => $data, type=>'ico'),
+ "ico file broken at resource entries");
+ is($im->errstr, "error opening ICO/CUR file: Short read",
+ "check error message");
+}
+{
+ my $im = Imager->new;
+ my $data = pack "H*", "00000200010000";
+ ok(!$im->read(data => $data, type=>'cur'),
+ "cursor file broken at resource entries");
+ is($im->errstr, "error opening ICO/CUR file: Short read",
+ "check error message");
+}
+
+{ # read negative index image
+ my $im = Imager->new;
+ ok(!$im->read(file=>'testimg/pal13232.ico', type=>'ico', page=>-1),
+ "read page -1");
+ is($im->errstr, "error reading ICO/CUR image: Image index out of range",
+ "check error message");
+}
+
+{ # read too high image index
+ my $im = Imager->new;
+ ok(!$im->read(file=>'testimg/pal13232.ico', type=>'ico', page=>1),
+ "read page 1");
+ is($im->errstr, "error reading ICO/CUR image: Image index out of range",
+ "check error message");
+}
+
+{ # image offset beyond end of file
+ my $im = Imager->new;
+ my $data = get_data <<EOS;
+; header - icon with 1 image
+0000 0100 0100
+; image record 32 x 32, offset 0xFFFF
+20 20 00 00 0000 0000 00200000 FFFF0000
+EOS
+ ok(!$im->read(data => $data, type=>'ico'),
+ "read from icon with bad offset");
+ # bad offset causes the seek to fail on an in-memory "file"
+ # it may not fail this way on a real file.
+ is($im->errstr, "error reading ICO/CUR image: I/O error",
+ "check error message");
+}
+
+{ # short read on bmiheader
+ my $im = Imager->new;
+ my $data = get_data <<EOS;
+; header - icon with 1 image
+0000 0100 0100
+; image record 32 x 32, offset 0xFFFF
+20 20 00 00 0000 0000 00200000 16000000
+; bmiheader for the first image
+2800 0000 2000 0000 4000 0000 ; size, width, height
+; short here
+EOS
+ ok(!$im->read(data => $data, type=>'ico'),
+ "read from icon with a short bitmap header");
+ is($im->errstr, "error reading ICO/CUR image: Short read",
+ "check error message");
+}
+
+{ # invalid bmiheader
+ my $im = Imager->new;
+ my $data = get_data <<EOS;
+; header - icon with 1 image
+0000 0100 0100
+; image record 32 x 32, offset 0xFFFF
+20 20 00 00 0000 0000 00200000 16000000
+; bmiheader for the first image
+2000 0000 2000 0000 4000 0000 ; size should be 0x28, width, height
+0100 2000 ; planes, bit count
+; data we read but ignore
+0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+EOS
+ ok(!$im->read(data => $data, type=>'ico'),
+ "read from icon with an invalid sub-image header");
+ is($im->errstr, "error reading ICO/CUR image: Not an icon file",
+ "check error message");
+}
+
+{ # invalid bit count for "direct" image
+ my $im = Imager->new;
+ my $data = get_data <<EOS;
+; header - icon with 1 image
+0000 0100 0100
+; image record 32 x 32, offset 0xFFFF
+20 20 00 00 0000 0000 00200000 16000000
+; bmiheader for the first image
+2800 0000 2000 0000 4000 0000 ; size, width, height
+0100 2100 ; planes, bit count
+; data we read but ignore
+0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+EOS
+ ok(!$im->read(data => $data, type=>'ico'),
+ "read from icon with an invalid 'direct' bits per pixel");
+ is($im->errstr, "error reading ICO/CUR image: Unknown value for bits/pixel",
+ "check error message");
+}
+
+{ # short file reading palette
+ my $im = Imager->new;
+ my $data = get_data <<EOS;
+; header - icon with 1 image
+0000 0100 0100
+; image record 32 x 32, offset 0xFFFF
+20 20 00 00 0000 0000 00200000 16000000
+; bmiheader for the first image
+2800 0000 2000 0000 4000 0000 ; size, width, height
+0100 0100 ; planes, bit count == 1
+; data we read but ignore
+0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+; dummy palette - one color but 2 needed
+FFFFFF00
+EOS
+ ok(!$im->read(data => $data, type=>'ico'),
+ "read from icon with short palette");
+ is($im->errstr, "error reading ICO/CUR image: Short read",
+ "check error message");
+}
+
+{ # short file reading 1 bit image data
+ my $im = Imager->new;
+ my $data = get_data <<EOS;
+; header - icon with 1 image
+0000 0100 0100
+; image record 32 x 32, offset 0x20
+20 20 00 00 0000 0000 00200000 16000000
+; bmiheader for the first image
+2800 0000 2000 0000 4000 0000 ; size, width, height
+0100 0100 ; planes, bit count == 1
+; data we read but ignore
+0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+; palette
+00000000
+FFFFFF00
+; image data - short
+00 ff
+EOS
+ ok(!$im->read(data => $data, type=>'ico'),
+ "read from icon with short image data (1 bit)");
+ is($im->errstr, "error reading ICO/CUR image: Short read",
+ "check error message");
+}
+
+{ # short file reading 32 bit image data
+ my $im = Imager->new;
+ my $data = get_data <<EOS;
+; header - icon with 1 image
+0000 0100 0100
+; image record 32 x 32, offset 0x20
+20 20 00 00 0000 0000 00200000 16000000
+; bmiheader for the first image
+2800 0000 2000 0000 4000 0000 ; size, width, height
+0100 2000 ; planes, bit count == 32
+; data we read but ignore
+0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+; nopalette
+; image data - short
+FFFFFFFF
+EOS
+ ok(!$im->read(data => $data, type=>'ico'),
+ "read from icon with short image data (32 bit)");
+ is($im->errstr, "error reading ICO/CUR image: Short read",
+ "check error message");
+}
+
+{ # short file reading 4 bit image data
+ my $im = Imager->new;
+ my $data = get_data <<EOS;
+; header - icon with 1 image
+0000 0100 0100
+; image record 32 x 32, offset 0x20
+20 20 00 00 0000 0000 00200000 16000000
+; bmiheader for the first image
+2800 0000 2000 0000 4000 0000 ; size, width, height
+0100 0400 ; planes, bit count == 4
+; data we read but ignore
+0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+; 16-color palette
+00000000 FFFFFF00 00000000 FFFFFF00
+00000000 FFFFFF00 00000000 FFFFFF00
+00000000 FFFFFF00 00000000 FFFFFF00
+00000000 FFFFFF00 00000000 FFFFFF00
+; image data - short
+FFFFFFFF
+EOS
+ ok(!$im->read(data => $data, type=>'ico'),
+ "read from icon with short image data (4 bit)");
+ is($im->errstr, "error reading ICO/CUR image: Short read",
+ "check error message");
+}
+
+{ # short file reading 8 bit image data
+ my $im = Imager->new;
+ # base image header + palette + a little data
+ my $data = get_data <<EOS . "FFFFFFFF" x 256 . "FFFF FFFF";
+; header - icon with 1 image
+0000 0100 0100
+; image record 32 x 32, offset 0x20
+20 20 00 00 0000 0000 00200000 16000000
+; bmiheader for the first image
+2800 0000 2000 0000 4000 0000 ; size, width, height
+0100 0800 ; planes, bit count == 8
+; data we read but ignore
+0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+EOS;
+; palette and data above
+EOS
+ ok(!$im->read(data => $data, type=>'ico'),
+ "read from icon with short image data (8 bit)");
+ is($im->errstr, "error reading ICO/CUR image: Short read",
+ "check error message");
+}
+
+{ # short file reading mask data
+ my $im = Imager->new;
+ my $data = get_data <<EOS;
+; header - icon with 1 image
+0000 0100 0100
+; image record 16 x 16, 2 colors, reserved=0, planes=1,
+; sizeinbytes (ignored), offset 0x16
+10 10 02 00 0100 0100 00000000 16000000
+; bmiheader for the first image
+2800 0000 1000 0000 2000 0000 ; size, width, height
+0100 0100 ; planes, bit count == 1
+; data we read but ignore
+0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+; palette
+00000000
+FFFFFF00
+; image data - 16 x 16 bits
+; note that each line needs to be aligned on a 32-bit boundary
+00ff00ff 00000000
+00ff00ff 00000000
+00ff00ff 00000000
+00ff00ff 00000000
+00ff00ff 00000000
+00ff00ff 00000000
+00ff00ff 00000000
+00ff00ff 00000000
+00ff00ff 00000000
+00ff00ff 00000000
+00ff00ff 00000000
+00ff00ff 00000000
+00ff00ff 00000000
+00ff00ff 00000000
+00ff00ff 00000000
+00ff00ff 00000000
+; mask, short
+0ff0
+EOS
+ ok(!$im->read(data => $data, type=>'ico'),
+ "read from icon with short mask data");
+ is($im->errstr, "error reading ICO/CUR image: Short read",
+ "check error message");
+}
+
+{ # fail opening on a multi-read
+ ok(!Imager->read_multi(file=>'t/t50readfail.t', type=>'ico'),
+ "multi-read on non-icon");
+ is(Imager->errstr, "error opening ICO/CUR file: Not an icon file",
+ "check message");
+}
+
+{ # invalid bit count for "direct" image (read_multi)
+ my $data = get_data <<EOS;
+; header - icon with 1 image
+0000 0100 0100
+; image record 32 x 32, offset 0xFFFF
+20 20 00 00 0000 0000 00200000 16000000
+; bmiheader for the first image
+2800 0000 2000 0000 4000 0000 ; size, width, height
+0100 2100 ; planes, bit count
+; data we read but ignore
+0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
+EOS
+ ok(!Imager->read_multi(data => $data, type=>'ico'),
+ "read from icon with an invalid 'direct' bits per pixel (multi)");
+ is(Imager->errstr,
+ "error reading ICO/CUR image: Unknown value for bits/pixel",
+ "check error message");
+}
+
+
+# extract hex data from text
+# allows comments
+sub get_data {
+ my ($src) = @_;
+
+ $src =~ s/[\#;].*//mg;
+ $src =~ tr/0-9A-F//cd;
+
+ pack("H*", $src);
+}
--- /dev/null
+#!perl -w
+use strict;
+use Test::More tests => 69;
+use Imager ':handy';
+
+# this file tries to cover as much of the write error handling cases in
+# msicon.c/imicon.c as possible.
+#
+# coverage checked with gcc/gcov
+
+# image too big for format tests, for each entry point
+{
+ my $im = Imager->new(xsize => 256, ysize => 255);
+ my $data;
+ ok(!$im->write(data => \$data, type=>'ico'),
+ "image too large");
+ is($im->errstr, "image too large for ico file", "check message");
+}
+
+{
+ my $im = Imager->new(xsize => 256, ysize => 255);
+ my $data;
+ ok(!Imager->write_multi({ data => \$data, type=>'ico' }, $im, $im),
+ "image too large");
+ is(Imager->errstr, "image too large for ico file", "check message");
+ Imager->_set_error('');
+}
+
+{
+ my $im = Imager->new(xsize => 256, ysize => 255);
+ my $data;
+ ok(!$im->write(data => \$data, type=>'cur'),
+ "image too large");
+ is($im->errstr, "image too large for ico file", "check message");
+}
+
+{
+ my $im = Imager->new(xsize => 256, ysize => 255);
+ my $data;
+ ok(!Imager->write_multi({ data => \$data, type=>'cur' }, $im),
+ "image too large");
+ is(Imager->errstr, "image too large for ico file", "check message");
+ Imager->_set_error('');
+}
+
+# low level write failure tests for each entry point (fail on close)
+{
+ my $im = Imager->new(xsize => 10, ysize => 10);
+ ok(!$im->write(callback => \&write_failure, type=>'ico'),
+ "low level write failure (ico)");
+ is($im->errstr, "error closing output: synthetic error", "check message");
+}
+
+{
+ my $im = Imager->new(xsize => 10, ysize => 10);
+ ok(!$im->write(callback => \&write_failure, type=>'cur'),
+ "low level write failure (cur)");
+ is($im->errstr, "error closing output: synthetic error", "check message");
+}
+
+{
+ my $im = Imager->new(xsize => 10, ysize => 10);
+ ok(!Imager->write_multi({ callback => \&write_failure, type=>'ico' }, $im, $im),
+ "low level write_multi failure (ico)");
+ is(Imager->errstr, "error closing output: synthetic error", "check message");
+ Imager->_set_error('');
+}
+
+{
+ my $im = Imager->new(xsize => 10, ysize => 10);
+ ok(!Imager->write_multi({ callback => \&write_failure, type=>'cur' }, $im, $im),
+ "low level write_multi failure (cur)");
+ is(Imager->errstr, "error closing output: synthetic error", "check message");
+ Imager->_set_error('');
+}
+
+# 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'),
+ "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'),
+ "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),
+ "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),
+ "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 => WriteLimit->new(6), maxbuffer => 1),
+ "second write (resource) should fail (ico)");
+ is($im->errstr, "Write failure: limit reached", "check message");
+ $im->_set_error('');
+
+ ok(!$im->write(type => 'cur', callback => WriteLimit->new(6), maxbuffer => 1),
+ "second (resource) write should fail (cur)");
+ is($im->errstr, "Write failure: limit reached", "check message");
+ $im->_set_error('');
+
+ ok(!$im->write(type => 'ico', callback => WriteLimit->new(22), maxbuffer => 1),
+ "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 => WriteLimit->new(62), maxbuffer => 1),
+ "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 => WriteLimit->new(462), maxbuffer => 1),
+ "mask write should fail (32-bit)");
+ is($im->errstr, "Write failure: limit reached", "check message");
+}
+
+{ # 1 bit write fails
+ my $im = Imager->new(xsize => 10, ysize => 10, type => 'paletted');
+ my $red = NC(255, 0, 0);
+ my $blue = NC(0, 0, 255);
+ $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 => WriteLimit->new(22), maxbuffer => 1),
+ "third write (bmi) should fail (1-bit)");
+ is($im->errstr, "Write failure: limit reached", "check message");
+
+ ok(!$im->write(type => 'ico', callback => WriteLimit->new(66), maxbuffer => 1),
+ "fourth write (palette) should fail (1-bit)");
+ is($im->errstr, "Write failure: limit reached", "check message");
+ ok(!$im->write(type => 'ico', callback => WriteLimit->new(74), maxbuffer => 1),
+ "fifth write (image) should fail (1-bit)");
+ is($im->errstr, "Write failure: limit reached", "check message");
+ my $data;
+ ok($im->write(data => \$data, type => 'ico'), "write 1 bit successfully");
+ my $read = Imager->new;
+ ok($read->read(data => $data), "read it back");
+ is($read->type, 'paletted', "check type");
+ is($read->tags(name => 'ico_bits'), 1, "check bits");
+ is(Imager::i_img_diff($read, $im), 0, "check image correct");
+}
+
+{ # 4 bit write fails
+ my $im = Imager->new(xsize => 10, ysize => 10, type => 'paletted');
+ my $red = NC(255, 0, 0);
+ my $blue = NC(0, 0, 255);
+ $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 => WriteLimit->new(22), maxbuffer => 1),
+ "third write (bmi) should fail (4-bit)");
+ is($im->errstr, "Write failure: limit reached", "check message");
+
+ ok(!$im->write(type => 'ico', callback => WriteLimit->new(66), maxbuffer => 1),
+ "fourth write (palette) should fail (4-bit)");
+ is($im->errstr, "Write failure: limit reached", "check message");
+ ok(!$im->write(type => 'ico', callback => WriteLimit->new(130), maxbuffer => 1),
+ "fifth write (image) should fail (4-bit)");
+ is($im->errstr, "Write failure: limit reached", "check message");
+ my $data;
+ ok($im->write(data => \$data, type => 'ico'), "write 4 bit successfully");
+ my $read = Imager->new;
+ ok($read->read(data => $data), "read it back");
+ is($read->type, 'paletted', "check type");
+ is($read->tags(name => 'ico_bits'), 4, "check bits");
+ is(Imager::i_img_diff($read, $im), 0, "check image correct");
+}
+
+{ # 8 bit write fails
+ my $im = Imager->new(xsize => 10, ysize => 10, type => 'paletted');
+ my $red = NC(255, 0, 0);
+ my $blue = NC(0, 0, 255);
+ $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 => WriteLimit->new(22), maxbuffer => 1),
+ "third write (bmi) should fail (8-bit)");
+ is($im->errstr, "Write failure: limit reached", "check message");
+
+ ok(!$im->write(type => 'ico', callback => WriteLimit->new(62), maxbuffer => 1),
+ "fourth write (palette) should fail (8-bit)");
+ is($im->errstr, "Write failure: limit reached", "check message");
+ ok(!$im->write(type => 'ico', callback => WriteLimit->new(62 + 1024), maxbuffer => 1),
+ "fifth write (image) should fail (8-bit)");
+ is($im->errstr, "Write failure: limit reached", "check message");
+ ok(!$im->write(type => 'ico', callback => WriteLimit->new(62 + 1024 + 10), maxbuffer => 1),
+ "sixth write (zeroes) should fail (8-bit)");
+ is($im->errstr, "Write failure: limit reached", "check message");
+ my $data;
+ ok($im->write(data => \$data, type => 'ico'), "write 8 bit successfully");
+ my $read = Imager->new;
+ ok($read->read(data => $data), "read it back");
+ is($read->type, 'paletted', "check type");
+ is($read->tags(name => 'ico_bits'), 8, "check bits");
+ is(Imager::i_img_diff($read, $im), 0, "check image correct");
+}
+
+# write callback that fails
+sub write_failure {
+ print "# synthesized write failure\n";
+ Imager::i_push_error(0, "synthetic error");
+ return;
+}
+
+package WriteLimit;
+use overload
+ '&{}' => \&limited_write,
+ 'bool' => sub { 1 };
+
+sub new {
+ my ($class, $limit) = @_;
+
+ bless
+ {
+ do_write =>
+ sub {
+ my ($data) = @_;
+ $limit -= length $data;
+ if ($limit >= 0) {
+ print "# write of ", length $data, " bytes successful ($limit left)\n";
+ return 1;
+ }
+ else {
+ print "# write of ", length $data, " bytes failed\n";
+ Imager::i_push_error(0, "limit reached");
+ return;
+ }
+ }
+ },$class;
+}
+
+sub limited_write {
+ my ($self) = @_;
+ return $self->{do_write};
+}
--- /dev/null
+#!perl -w
+use strict;
+use Test::More tests => 1;
+use Imager;
+require '../t/testtools.pl';
+
+# checks that we load the ICO write handler automatically
+my $img = test_oo_img();
+ok($img->write(file => 'testout/icosing.ico'),
+ "write ico with autoload")
+ or print "# ",$img->errstr,"\n";
--- /dev/null
+#!perl -w
+use strict;
+use Test::More tests => 1;
+use Imager;
+require '../t/testtools.pl';
+
+# checks that we load the ICO write handler automatically
+my $img = test_oo_img();
+ok(Imager->write_multi({ file => 'testout/icomult.ico' }, $img, $img),
+ "write_multi ico with autoload")
+ or print "# ",Imager->errstr,"\n";
--- /dev/null
+#!perl -w
+use strict;
+use Test::More tests => 1;
+use Imager;
+require '../t/testtools.pl';
+
+# checks that we load the CUR write handler automatically
+my $img = test_oo_img();
+ok($img->write(file => 'testout/cursing.cur'),
+ "write cur with autoload")
+ or print "# ",$img->errstr,"\n";
--- /dev/null
+#!perl -w
+use strict;
+use Test::More tests => 1;
+use Imager;
+require '../t/testtools.pl';
+
+# checks that we load the CUR write handler automatically
+my $img = test_oo_img();
+ok(Imager->write_multi({ file => 'testout/icomult.cur' }, $img, $img),
+ "write_multi cur with autoload")
+ or print "# ",Imager->errstr,"\n";
# registered file readers
my %readers;
+# registered file writers
+my %writers;
+
# modules we attempted to autoload
my %attempted_to_load;
return 1;
}
+sub register_writer {
+ my ($class, %opts) = @_;
+
+ defined $opts{type}
+ or die "register_writer called with no type parameter\n";
+
+ my $type = $opts{type};
+
+ defined $opts{single} || defined $opts{multiple}
+ or die "register_writer called with no single or multiple parameter\n";
+
+ $writers{$type} = { };
+ if ($opts{single}) {
+ $writers{$type}{single} = $opts{single};
+ }
+ if ($opts{multiple}) {
+ $writers{$type}{multiple} = $opts{multiple};
+ }
+
+ return 1;
+}
+
# probes for an Imager::File::whatever module
sub _reader_autoload {
my $type = shift;
++$attempted_to_load{$file};
require $file;
};
+ if ($@) {
+ # try to get a reader specific module
+ my $file = "Imager/File/\U$type\EReader.pm";
+ unless ($attempted_to_load{$file}) {
+ eval {
+ ++$attempted_to_load{$file};
+ require $file;
+ };
+ }
+ }
+ }
+}
+
+# probes for an Imager::File::whatever module
+sub _writer_autoload {
+ my $type = shift;
+
+ return if $formats{$type} || $readers{$type};
+
+ return unless $type =~ /^\w+$/;
+
+ my $file = "Imager/File/\U$type\E.pm";
+
+ unless ($attempted_to_load{$file}) {
+ eval {
+ ++$attempted_to_load{$file};
+ require $file;
+ };
+ if ($@) {
+ # try to get a writer specific module
+ my $file = "Imager/File/\U$type\EWriter.pm";
+ unless ($attempted_to_load{$file}) {
+ eval {
+ ++$attempted_to_load{$file};
+ require $file;
+ };
+ }
+ }
}
}
return undef;
}
- if (!$formats{$input{'type'}}) { $self->{ERRSTR}='format not supported'; return undef; }
+ _writer_autoload($input{type});
- my ($IO, $fh) = $self->_get_writer_io(\%input, $input{'type'})
- or return undef;
-
- if ($input{'type'} eq 'tiff') {
- $self->_set_opts(\%input, "tiff_", $self)
- or return undef;
- $self->_set_opts(\%input, "exif_", $self)
+ my ($IO, $fh);
+ if ($writers{$input{type}} && $writers{$input{type}}{single}) {
+ ($IO, $fh) = $self->_get_writer_io(\%input, $input{'type'})
or return undef;
- if (defined $input{class} && $input{class} eq 'fax') {
- if (!i_writetiff_wiol_faxable($self->{IMG}, $IO, $input{fax_fine})) {
- $self->{ERRSTR} = $self->_error_as_msg();
- return undef;
- }
- } else {
- if (!i_writetiff_wiol($self->{IMG}, $IO)) {
- $self->{ERRSTR} = $self->_error_as_msg();
- return undef;
- }
- }
- } elsif ( $input{'type'} eq 'pnm' ) {
- $self->_set_opts(\%input, "pnm_", $self)
+ $writers{$input{type}}{single}->($self, $IO, %input)
or return undef;
- if ( ! i_writeppm_wiol($self->{IMG},$IO) ) {
- $self->{ERRSTR} = $self->_error_as_msg();
- return undef;
- }
- $self->{DEBUG} && print "writing a pnm file\n";
- } elsif ( $input{'type'} eq 'raw' ) {
- $self->_set_opts(\%input, "raw_", $self)
- or return undef;
- if ( !i_writeraw_wiol($self->{IMG},$IO) ) {
- $self->{ERRSTR} = $self->_error_as_msg();
- return undef;
- }
- $self->{DEBUG} && print "writing a raw file\n";
- } elsif ( $input{'type'} eq 'png' ) {
- $self->_set_opts(\%input, "png_", $self)
- or return undef;
- if ( !i_writepng_wiol($self->{IMG}, $IO) ) {
- $self->{ERRSTR}='unable to write png image';
- return undef;
- }
- $self->{DEBUG} && print "writing a png file\n";
- } elsif ( $input{'type'} eq 'jpeg' ) {
- $self->_set_opts(\%input, "jpeg_", $self)
- or return undef;
- $self->_set_opts(\%input, "exif_", $self)
- or return undef;
- if ( !i_writejpeg_wiol($self->{IMG}, $IO, $input{jpegquality})) {
- $self->{ERRSTR} = $self->_error_as_msg();
- return undef;
- }
- $self->{DEBUG} && print "writing a jpeg file\n";
- } elsif ( $input{'type'} eq 'bmp' ) {
- $self->_set_opts(\%input, "bmp_", $self)
- or return undef;
- if ( !i_writebmp_wiol($self->{IMG}, $IO) ) {
- $self->{ERRSTR}='unable to write bmp image';
- return undef;
- }
- $self->{DEBUG} && print "writing a bmp file\n";
- } elsif ( $input{'type'} eq 'tga' ) {
- $self->_set_opts(\%input, "tga_", $self)
- or return undef;
-
- if ( !i_writetga_wiol($self->{IMG}, $IO, $input{wierdpack}, $input{compress}, $input{idstring}) ) {
- $self->{ERRSTR}=$self->_error_as_msg();
+ }
+ else {
+ if (!$formats{$input{'type'}}) {
+ $self->{ERRSTR}='format not supported';
return undef;
}
- $self->{DEBUG} && print "writing a tga file\n";
- } elsif ( $input{'type'} eq 'gif' ) {
- $self->_set_opts(\%input, "gif_", $self)
+
+ ($IO, $fh) = $self->_get_writer_io(\%input, $input{'type'})
or return undef;
- # compatibility with the old interfaces
- if ($input{gifquant} eq 'lm') {
- $input{make_colors} = 'addi';
- $input{translate} = 'perturb';
- $input{perturb} = $input{lmdither};
- } elsif ($input{gifquant} eq 'gen') {
- # just pass options through
- } else {
- $input{make_colors} = 'webmap'; # ignored
- $input{translate} = 'giflib';
- }
- if (!i_writegif_wiol($IO, \%input, $self->{IMG})) {
- $self->{ERRSTR} = $self->_error_as_msg;
- return;
+
+ if ($input{'type'} eq 'tiff') {
+ $self->_set_opts(\%input, "tiff_", $self)
+ or return undef;
+ $self->_set_opts(\%input, "exif_", $self)
+ or return undef;
+
+ if (defined $input{class} && $input{class} eq 'fax') {
+ if (!i_writetiff_wiol_faxable($self->{IMG}, $IO, $input{fax_fine})) {
+ $self->{ERRSTR} = $self->_error_as_msg();
+ return undef;
+ }
+ } else {
+ if (!i_writetiff_wiol($self->{IMG}, $IO)) {
+ $self->{ERRSTR} = $self->_error_as_msg();
+ return undef;
+ }
+ }
+ } elsif ( $input{'type'} eq 'pnm' ) {
+ $self->_set_opts(\%input, "pnm_", $self)
+ or return undef;
+ if ( ! i_writeppm_wiol($self->{IMG},$IO) ) {
+ $self->{ERRSTR} = $self->_error_as_msg();
+ return undef;
+ }
+ $self->{DEBUG} && print "writing a pnm file\n";
+ } elsif ( $input{'type'} eq 'raw' ) {
+ $self->_set_opts(\%input, "raw_", $self)
+ or return undef;
+ if ( !i_writeraw_wiol($self->{IMG},$IO) ) {
+ $self->{ERRSTR} = $self->_error_as_msg();
+ return undef;
+ }
+ $self->{DEBUG} && print "writing a raw file\n";
+ } elsif ( $input{'type'} eq 'png' ) {
+ $self->_set_opts(\%input, "png_", $self)
+ or return undef;
+ if ( !i_writepng_wiol($self->{IMG}, $IO) ) {
+ $self->{ERRSTR}='unable to write png image';
+ return undef;
+ }
+ $self->{DEBUG} && print "writing a png file\n";
+ } elsif ( $input{'type'} eq 'jpeg' ) {
+ $self->_set_opts(\%input, "jpeg_", $self)
+ or return undef;
+ $self->_set_opts(\%input, "exif_", $self)
+ or return undef;
+ if ( !i_writejpeg_wiol($self->{IMG}, $IO, $input{jpegquality})) {
+ $self->{ERRSTR} = $self->_error_as_msg();
+ return undef;
+ }
+ $self->{DEBUG} && print "writing a jpeg file\n";
+ } elsif ( $input{'type'} eq 'bmp' ) {
+ $self->_set_opts(\%input, "bmp_", $self)
+ or return undef;
+ if ( !i_writebmp_wiol($self->{IMG}, $IO) ) {
+ $self->{ERRSTR}='unable to write bmp image';
+ return undef;
+ }
+ $self->{DEBUG} && print "writing a bmp file\n";
+ } elsif ( $input{'type'} eq 'tga' ) {
+ $self->_set_opts(\%input, "tga_", $self)
+ or return undef;
+
+ if ( !i_writetga_wiol($self->{IMG}, $IO, $input{wierdpack}, $input{compress}, $input{idstring}) ) {
+ $self->{ERRSTR}=$self->_error_as_msg();
+ return undef;
+ }
+ $self->{DEBUG} && print "writing a tga file\n";
+ } elsif ( $input{'type'} eq 'gif' ) {
+ $self->_set_opts(\%input, "gif_", $self)
+ or return undef;
+ # compatibility with the old interfaces
+ if ($input{gifquant} eq 'lm') {
+ $input{make_colors} = 'addi';
+ $input{translate} = 'perturb';
+ $input{perturb} = $input{lmdither};
+ } elsif ($input{gifquant} eq 'gen') {
+ # just pass options through
+ } else {
+ $input{make_colors} = 'webmap'; # ignored
+ $input{translate} = 'giflib';
+ }
+ if (!i_writegif_wiol($IO, \%input, $self->{IMG})) {
+ $self->{ERRSTR} = $self->_error_as_msg;
+ return;
+ }
}
}
sub write_multi {
my ($class, $opts, @images) = @_;
- if (!$opts->{'type'} && $opts->{'file'}) {
- $opts->{'type'} = $FORMATGUESS->($opts->{'file'});
+ my $type = $opts->{type};
+
+ if (!$type && $opts->{'file'}) {
+ $type = $FORMATGUESS->($opts->{'file'});
}
- unless ($opts->{'type'}) {
+ unless ($type) {
$class->_set_error('type parameter missing and not possible to guess from extension');
return;
}
$class->_set_opts($opts, "i_", @images)
or return;
my @work = map $_->{IMG}, @images;
- my ($IO, $file) = $class->_get_writer_io($opts, $opts->{'type'})
- or return undef;
- if ($opts->{'type'} eq 'gif') {
- $class->_set_opts($opts, "gif_", @images)
- or return;
- my $gif_delays = $opts->{gif_delays};
- local $opts->{gif_delays} = $gif_delays;
- if ($opts->{gif_delays} && !ref $opts->{gif_delays}) {
- # assume the caller wants the same delay for each frame
- $opts->{gif_delays} = [ ($gif_delays) x @images ];
- }
- my $res = i_writegif_wiol($IO, $opts, @work);
- $res or $class->_set_error($class->_error_as_msg());
- return $res;
- }
- elsif ($opts->{'type'} eq 'tiff') {
- $class->_set_opts($opts, "tiff_", @images)
- or return;
- $class->_set_opts($opts, "exif_", @images)
- or return;
- my $res;
- $opts->{fax_fine} = 1 unless exists $opts->{fax_fine};
- if ($opts->{'class'} && $opts->{'class'} eq 'fax') {
- $res = i_writetiff_multi_wiol_faxable($IO, $opts->{fax_fine}, @work);
+
+ _writer_autoload($type);
+
+ my ($IO, $file);
+ if ($writers{$type} && $writers{$type}{multiple}) {
+ ($IO, $file) = $class->_get_writer_io($opts, $type)
+ or return undef;
+
+ $writers{$type}{multiple}->($class, $IO, $opts, @images)
+ or return undef;
+ }
+ else {
+ if (!$formats{$type}) {
+ $class->_set_error("format $type not supported");
+ return undef;
+ }
+
+ ($IO, $file) = $class->_get_writer_io($opts, $type)
+ or return undef;
+
+ if ($type eq 'gif') {
+ $class->_set_opts($opts, "gif_", @images)
+ or return;
+ my $gif_delays = $opts->{gif_delays};
+ local $opts->{gif_delays} = $gif_delays;
+ if ($opts->{gif_delays} && !ref $opts->{gif_delays}) {
+ # assume the caller wants the same delay for each frame
+ $opts->{gif_delays} = [ ($gif_delays) x @images ];
+ }
+ unless (i_writegif_wiol($IO, $opts, @work)) {
+ $class->_set_error($class->_error_as_msg());
+ return undef;
+ }
+ }
+ elsif ($type eq 'tiff') {
+ $class->_set_opts($opts, "tiff_", @images)
+ or return;
+ $class->_set_opts($opts, "exif_", @images)
+ or return;
+ my $res;
+ $opts->{fax_fine} = 1 unless exists $opts->{fax_fine};
+ if ($opts->{'class'} && $opts->{'class'} eq 'fax') {
+ $res = i_writetiff_multi_wiol_faxable($IO, $opts->{fax_fine}, @work);
+ }
+ else {
+ $res = i_writetiff_multi_wiol($IO, @work);
+ }
+ unless ($res) {
+ $class->_set_error($class->_error_as_msg());
+ return undef;
+ }
}
else {
- $res = i_writetiff_multi_wiol($IO, @work);
+ $ERRSTR = "Sorry, write_multi doesn't support $type yet";
+ return 0;
}
- $res or $class->_set_error($class->_error_as_msg());
- return $res;
}
- else {
- $ERRSTR = "Sorry, write_multi doesn't support $opts->{'type'} yet";
- return 0;
+
+ if (exists $opts->{'data'}) {
+ my $data = io_slurp($IO);
+ if (!$data) {
+ Imager->_set_error('Could not slurp from buffer');
+ return undef;
+ }
+ ${$opts->{data}} = $data;
}
+ return 1;
}
# read multiple images from a file
}
}
- $ERRSTR = "Cannot read multiple images from $opts{'type'} files";
+ $ERRSTR = "Cannot read multiple images from $type files";
return;
}
return 'rgb' if ($ext eq "rgb");
return 'gif' if ($ext eq "gif");
return 'raw' if ($ext eq "raw");
+ return lc $ext; # best guess
return ();
}
FREETMPS;
LEAVE;
- return success ? size : 0;
+ return success ? size : -1;
}
static ssize_t call_reader(struct cbdata *cbd, void *buf, size_t size,
static ssize_t io_writer(void *p, void const *data, size_t size) {
struct cbdata *cbd = p;
- /*printf("io_writer(%p, %p, %u)\n", p, data, size);*/
+ /* 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
return total;
}
-static void io_closer(void *p) {
+static int io_closer(void *p) {
struct cbdata *cbd = p;
if (cbd->writing && cbd->used > 0) {
- write_flush(cbd);
+ if (write_flush(cbd) < 0)
+ return -1;
cbd->writing = 0;
}
FREETMPS;
LEAVE;
}
+
+ return 0;
}
static void io_destroyer(void *p) {
i_io_DESTROY(ig)
Imager::IO ig
-
MODULE = Imager PACKAGE = Imager
PROTOTYPES: ENABLE
++i;
}
+void
+i_clear_error()
+
+void
+i_push_error(code, msg)
+ int code
+ const char *msg
+
undef_int
i_nearest_color(im, ...)
Imager::ImgRaw im
ICO/Makefile.PL
ICO/imicon.c
ICO/imicon.h
+ICO/lib/Imager/File/CUR.pm
ICO/msicon.c
ICO/msicon.h
ICO/t/t10icon.t
ICO/t/t20readone.t
ICO/t/t21readmult.t
+ICO/t/t30cursor.t
+ICO/t/t40readcurone.t
+ICO/t/t41curmultread.t
+ICO/t/t50readfail.t
+ICO/t/t60writefail.t
+ICO/t/t70icosing.t
+ICO/t/t71icomult.t
+ICO/t/t72cursing.t
+ICO/t/t73curmult.t
ICO/testimg/combo.ico
ICO/testimg/pal13232.ico
+ICO/testimg/pal43232.cur
ICO/testimg/pal43232.ico
ICO/testimg/pal43232.ppm
ICO/testimg/pal83232.ico
^ICO/ICO\.c$
^ICO/testout
+# trash from profiling
+\.gcno$
+\.gcda$
+\.gcov$
# this is intended to only be running on the development
# machines
sub distcheck {
- if (-e '.svn') {
+ if (-e '.svn' && -e 'Changes') {
# update Changes if needed
my $write_changes;
# get the last revision from Changes
static int
test_magic(unsigned char *buffer, size_t length, struct magic_entry const *magic) {
- int c;
-
if (length < magic->magic_size)
return 0;
if (magic->mask) {
i_set_image_file_limits(int width, int height, int bytes);
extern int
i_get_image_file_limits(int *width, int *height, int *bytes);
+extern int
+i_int_check_image_file_limits(int width, int height, int channels, int sample_size);
/* memory allocation */
void* mymalloc(int size);
extern void i_get_combine(int combine, i_fill_combine_f *, i_fill_combinef_f *);
-extern int
-i_int_check_image_file_limits(int width, int height, int channels, int sample_size);
-
#define im_min(a, b) ((a) < (b) ? (a) : (b))
#define im_max(a, b) ((a) > (b) ? (a) : (b))
i_copyto_trans,
i_copy,
i_rubthru,
+
+ /* IMAGER_API_LEVEL 2 functions */
+ i_set_image_file_limits,
+ i_get_image_file_limits,
+ i_int_check_image_file_limits,
};
/* in general these functions aren't called by Imager internally, but
#define i_rubthru(im, src, tx, ty, src_minx, src_miny, src_maxx, src_maxy) \
((im_extt->f_i_rubthru)((im), (src), (tx), (ty), (src_minx), (src_miny), (src_maxx), (src_maxy)))
+#define i_set_image_file_limits(max_width, max_height, max_bytes) \
+ ((im_extt->f_i_set_image_file_limits)((max_width), (max_height), (max_bytes)))
+#define i_get_image_file_limits(max_width, max_height, max_bytes) \
+ ((im_extt->f_i_get_image_file_limits)((pmax_width), (pmax_height), (pmax_bytes)))
+#define i_int_check_image_file_limits(width, height, channels, sample_size) \
+ ((im_extt->f_i_int_check_image_file_limits)((width), (height), (channels), (sample_size)))
+
#endif
will result in an increment of IMAGER_API_LEVEL.
*/
-#define IMAGER_API_LEVEL 1
+#define IMAGER_API_LEVEL 2
typedef struct {
int version;
i_img *(*f_i_copy)(i_img *im);
int (*f_i_rubthru)(i_img *im, i_img *src, int tx, int ty, int src_minx, int src_miny, int src_maxx, int src_maxy);
- /* IMAGER_API_LEVEL 2 functions will be added here */
+ /* IMAGER_API_LEVEL 2 functions */
+ int (*f_i_set_image_file_limits)(int width, int height, int bytes);
+ int (*f_i_get_image_file_limits)(int *width, int *height, int *bytes);
+ int (*f_i_int_check_image_file_limits)(int width, int height, int channels, int sample_size);
+
+ /* IMAGER_API_LEVEL 3 functions will be added here */
} im_ext_funcs;
#define PERL_FUNCTION_TABLE_NAME "Imager::__ext_func_table"
static ssize_t fd_read(io_glue *ig, void *buf, size_t count);
static ssize_t fd_write(io_glue *ig, const void *buf, size_t count);
static off_t fd_seek(io_glue *ig, off_t offset, int whence);
-static void fd_close(io_glue *ig);
+static int fd_close(io_glue *ig);
static ssize_t fd_size(io_glue *ig);
static const char *my_strerror(int err);
size_t bc = 0;
char *cbuf = buf;
- IOL_DEB( printf("realseek_read: fd = %d, ier->cpos = %ld, buf = %p, "
- "count = %d\n", fd, (long) ier->cpos, buf, count) );
+ 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 ) {
=cut */
static
-void
+int
realseek_close(io_glue *ig) {
mm_log((1, "realseek_close(ig %p)\n", ig));
if (ig->source.cb.closecb)
- ig->source.cb.closecb(ig->source.cb.p);
+ return ig->source.cb.closecb(ig->source.cb.p);
+ else
+ return 0;
}
buffer_read(io_glue *ig, void *buf, size_t count) {
io_ex_buffer *ieb = ig->exdata;
- IOL_DEB( printf("buffer_read: fd = %d, ier->cpos = %ld, buf = %p, count = %d\n", fd, (long) ier->cpos, buf, count) );
+ IOL_DEB( printf("buffer_read: ieb->cpos = %ld, buf = %p, count = %d\n", (long) ieb->cpos, buf, count) );
if ( ieb->cpos+count > ig->source.buffer.len ) {
mm_log((1,"buffer_read: short read: cpos=%d, len=%d, count=%d\n", ieb->cpos, ig->source.buffer.len));
memcpy(buf, ig->source.buffer.data+ieb->cpos, count);
ieb->cpos += count;
- IOL_DEB( printf("buffer_read: rc = %d, count = %d\n", rc, count) );
+ IOL_DEB( printf("buffer_read: count = %d\n", count) );
return count;
}
*/
static
-void
+int
buffer_close(io_glue *ig) {
mm_log((1, "buffer_close(ig %p)\n", ig));
- /* FIXME: Do stuff here */
+
+ return 0;
}
*/
static
-void
+int
bufchain_close(io_glue *ig) {
mm_log((1, "bufchain_close(ig %p)\n",ig));
IOL_DEB( printf("bufchain_close(ig %p)\n", ig) );
- /* FIXME: Commit a seek point here */
-
+
+ return 0;
}
return result;
}
-static void fd_close(io_glue *ig) {
+static int fd_close(io_glue *ig) {
/* no, we don't close it */
+ return 0;
}
static ssize_t fd_size(io_glue *ig) {
typedef ssize_t(*i_io_readp_t) (io_glue *ig, void *buf, size_t count);
typedef ssize_t(*i_io_writep_t)(io_glue *ig, const void *buf, size_t count);
typedef off_t (*i_io_seekp_t) (io_glue *ig, off_t offset, int whence);
-typedef void (*i_io_closep_t)(io_glue *ig);
+typedef int (*i_io_closep_t)(io_glue *ig);
typedef ssize_t(*i_io_sizep_t) (io_glue *ig);
typedef void (*i_io_closebufp_t)(void *p);
typedef ssize_t(*i_io_readl_t) (void *p, void *buf, size_t count);
typedef ssize_t(*i_io_writel_t)(void *p, const void *buf, size_t count);
typedef off_t (*i_io_seekl_t) (void *p, off_t offset, int whence);
-typedef void (*i_io_closel_t)(void *p);
+typedef int (*i_io_closel_t)(void *p);
typedef void (*i_io_destroyl_t)(void *p);
typedef ssize_t(*i_io_sizel_t) (void *p);
There are no PNG specific tags.
+=head2 ICO (Microsoft Windows Icon) and CUR (Microsoft Windows Cursor)
+
+Icon and Cursor files are very similar, the only differences being a
+number in the header and the storage of the cursor hotspot. I've
+treated them separately so that you're not messing with tags to
+distinguish between them.
+
+The following tags are set when reading an icon image and are used
+when writing it:
+
+=over
+
+=item ico_mask
+
+This is the AND mask of the icon. When used as an icon in Windows 1
+bits in the mask correspond to pixels that are modified by the source
+image rather than simply replaced by the source image.
+
+Rather than requiring a binary bitmap this is accepted in a specific format:
+
+=over
+
+=item *
+
+first line consisting of the 0 placeholder, the 1 placeholder and a
+newline.
+
+=item *
+
+following lines which contain 0 and 1 placeholders for each scanline
+of the image, starting from the top of the image.
+
+=back
+
+When reading an image, '.' is used as the 0 placeholder and '*' as the
+1 placeholder. An example:
+
+ .*
+ ..........................******
+ ..........................******
+ ..........................******
+ ..........................******
+ ...........................*****
+ ............................****
+ ............................****
+ .............................***
+ .............................***
+ .............................***
+ .............................***
+ ..............................**
+ ..............................**
+ ...............................*
+ ...............................*
+ ................................
+ ................................
+ ................................
+ ................................
+ ................................
+ ................................
+ *...............................
+ **..............................
+ **..............................
+ ***.............................
+ ***.............................
+ ****............................
+ ****............................
+ *****...........................
+ *****...........................
+ *****...........................
+ *****...........................
+
+=back
+
+The following tags are set when reading an icon:
+
+=over
+
+=item ico_bits
+
+The number of bits per pixel used to store the image.
+
+=back
+
+For cursor files the following tags are set and read when reading and
+writing:
+
+=over
+
+=item cur_mask
+
+This is the same as the ico_mask above.
+
+=item cur_hotspotx
+
+=item cur_hotspoty
+
+The "hot" spot of the cursor image. This is the spot on the cursor
+that you click with. If you set these to out of range values they are
+clipped to the size of the image when written to the file.
+
+=back
+
+C<cur_bits> is set when reading a cursor.
+
+Examples:
+
+ my $img = Imager->new(xsize => 32, ysize => 32, channels => 4);
+ $im->box(color => 'FF0000');
+ $im->write(file => 'box.ico');
+
+ $im->settag(name => 'cur_hotspotx', value => 16);
+ $im->settag(name => 'cur_hotspoty', value => 16);
+ $im->write(file => 'box.cur');
+
=head1 ADDING NEW FORMATS
To support a new format for reading, call the register_reader() class
},
);
+=item register_writer
+
+Registers single or multiple image write functions.
+
+Parameters:
+
+=over
+
+=item *
+
+type - the identifier of the file format. This is typically the
+extension in lowercase.
+
+This parameter is required.
+
+=item *
+
+single - a code ref to write a single image to a file. This is
+supplied:
+
+=over
+
+=item *
+
+the object that write() was called on,
+
+=item *
+
+an Imager::IO object that should be used to write the file, and
+
+=item *
+
+all the parameters supplied to the write() method.
+
+=back
+
+The single parameter is required.
+
+=item *
+
+multiple - a code ref which is called to write multiple images to a
+file. This is supplied:
+
+=over
+
+=item *
+
+the class name write_multi() was called on, this is typically
+C<Imager>.
+
+=item *
+
+an Imager::IO object that should be used to write the file, and
+
+=item *
+
+all the parameters supplied to the read_multi() method.
+
+=back
+
+=back
+
=back
If you name the reader module C<Imager::File::>I<your-format-name>
where I<your-format-name> is a fully upper case version of the type
-value you would pass to read() or read_multi() then Imager will
-attempt to load that module if it has no other way to read that
-format.
+value you would pass to read(), read_multi(), write() or write_multi()
+then Imager will attempt to load that module if it has no other way to
+read or write that format.
For example, if you create a module Imager::File::GIF and the user has
built Imager without it's normal GIF support then an attempt to read a
GIF image will attempt to load Imager::File::GIF.
+If your module can only handle reading then you can name your module
+C<Imager::File::>I<your-format-name>C<Reader> and Imager will attempt
+to autoload it.
+
+If your module can only handle writing then you can name your module
+C<Imager::File::>I<your-format-name>C<Writer> and Imager will attempt
+to autoload it.
+
=head1 EXAMPLES
=head2 Producing an image from a CGI script
#!perl -w
use strict;
use lib 't';
-use Test::More tests => 101;
+use Test::More tests => 106;
use Imager qw(:all);
$^W=1; # warnings during command-line tests
$|=1; # give us some progress in the test harness
$im = Imager->new(xsize=>2, ysize=>2);
ok(!$im->write(file=>"testout/notiff.tif"), "should fail to write tiff");
is($im->errstr, 'format not supported', "check no tiff message");
- skip("no tiff support", 97);
+ skip("no tiff support", 102);
}
Imager::i_tags_add($img, "i_xres", 0, "300", 0);
"Error opening file: Not a TIFF (?:or MDI )?file, bad magic number (8483 \\(0x2123\\)|8993 \\(0x2321\\))",
"check error message");
}
+
+ { # write_multi to data
+ my $data;
+ my $im = Imager->new(xsize => 50, ysize => 50);
+ ok(Imager->write_multi({ data => \$data, type=>'tiff' }, $im, $im),
+ "write multi to in memory");
+ ok(length $data, "make sure something written");
+ my @im = Imager->read_multi(data => $data);
+ is(@im, 2, "make sure we can read it back");
+ is(Imager::i_img_diff($im[0]{IMG}, $im->{IMG}), 0,
+ "check first image");
+ is(Imager::i_img_diff($im[1]{IMG}, $im->{IMG}), 0,
+ "check second image");
+ }
}
+