From: Tony Cook Date: Fri, 24 Mar 2006 09:49:57 +0000 (+0000) Subject: - implement reading MS Windows icon files X-Git-Tag: Imager-0.51_01~25 X-Git-Url: http://git.imager.perl.org/imager.git/commitdiff_plain/b00687b2d7615b870be0fde040ec3bfc3529070c - implement reading MS Windows icon files - add t/Pod/Coverage/Imager.pm to MANIFEST - skip some more trash in MANIFEST.SKIP --- diff --git a/ICO/ICO.pm b/ICO/ICO.pm new file mode 100644 index 00000000..6f038d62 --- /dev/null +++ b/ICO/ICO.pm @@ -0,0 +1,78 @@ +package Imager::File::ICO; +use strict; +use Imager; +use vars qw($VERSION @ISA); + +BEGIN { + $VERSION = "0.01"; + + eval { + require XSLoader; + XSLoader::load('Imager::File::ICO', $VERSION); + 1; + } or do { + require DynaLoader; + push @ISA, 'DynaLoader'; + bootstrap Imager::File::ICO $VERSION; + }; +} + +Imager->register_reader + ( + type=>'ico', + 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; + }, + ); + +1; + +__END__ + +=head1 NAME + +Imager::File::ICO - read MS Icon files + +=head1 SYNOPSIS + + use Imager; + + my $img = Imager->new; + $img->read(file=>"foo.ico") + or die $img->errstr; + + my @imgs = Imager->read_multi(file => "foo.ico") + or die Imager->errstr; + +=head1 DESCRIPTION + + + +=head1 AUTHOR + +=head1 SEE ALSO + +Imager, Imager::Files. + +=cut diff --git a/ICO/ICO.xs b/ICO/ICO.xs new file mode 100644 index 00000000..d33bd2b5 --- /dev/null +++ b/ICO/ICO.xs @@ -0,0 +1,40 @@ +#include "EXTERN.h" +#include "perl.h" +#include "XSUB.h" +#include "imext.h" +#include "imperl.h" +#include "imicon.h" + +DEFINE_IMAGER_CALLBACKS; + +MODULE = Imager::File::ICO PACKAGE = Imager::File::ICO + +PROTOTYPES: DISABLE + +Imager::ImgRaw +i_readico_single(ig, index) + Imager::IO ig + int index + +void +i_readico_multi(ig) + Imager::IO ig + PREINIT: + i_img **imgs; + int count; + int i; + PPCODE: + imgs = i_readico_multi(ig, &count); + if (imgs) { + EXTEND(SP, count); + for (i = 0; i < count; ++i) { + SV *sv = sv_newmortal(); + sv_setref_pv(sv, "Imager::ImgRaw", (void *)imgs[i]); + PUSHs(sv); + } + myfree(imgs); + } + + +BOOT: + PERL_INITIALIZE_IMAGER_CALLBACKS; diff --git a/ICO/Makefile.PL b/ICO/Makefile.PL new file mode 100644 index 00000000..aa037ae8 --- /dev/null +++ b/ICO/Makefile.PL @@ -0,0 +1,17 @@ +use ExtUtils::MakeMaker; + +my %opts = + ( + NAME => 'Imager::File::ICO', + VERSION_FROM => 'ICO.pm', + OBJECT => 'ICO.o msicon.o imicon.o', + INC => '-I..' + ); +if ($ExtUtils::MakeMaker::VERSION > 6.06) { + $opts{AUTHOR} = 'Tony Cook '; + $opts{ABSTRACT} = 'Icon Image file support'; +} + +WriteMakefile(%opts); + + diff --git a/ICO/imicon.c b/ICO/imicon.c new file mode 100644 index 00000000..56555367 --- /dev/null +++ b/ICO/imicon.c @@ -0,0 +1,182 @@ +#include "imext.h" +#include "imicon.h" +#include "msicon.h" + +static void +ico_push_error(int error) { + char error_buf[ICO_MAX_MESSAGE]; + + ico_error_message(error, error_buf, sizeof(error_buf)); + i_push_error(error, error_buf); +} + +static +i_img * +read_one_icon(ico_reader_t *file, int index) { + ico_image_t *image; + int error; + i_img *result; + + image = ico_image_read(file, index, &error); + if (!image) { + ico_reader_close(file); + ico_push_error(error); + return NULL; + } + + if (image->direct) { + int x, y; + i_color *line_buf = mymalloc(image->width * sizeof(i_color)); + i_color *outp; + ico_color_t *inp = image->image_data; + + result = i_img_8_new(image->width, image->height, 4); + if (!result) { + ico_image_release(image); + ico_reader_close(file); + return NULL; + } + + for (y = 0; y < image->height; ++y) { + outp = line_buf; + for (x = 0; x < image->width; ++x) { + outp->rgba.r = inp->r; + outp->rgba.g = inp->g; + outp->rgba.b = inp->b; + outp->rgba.a = inp->a; + ++outp; + ++inp; + } + i_plin(result, 0, image->width, y, line_buf); + } + + myfree(line_buf); + } + else { + int pal_index; + int y; + unsigned char *image_data; + + result = i_img_pal_new(image->width, image->height, 3, 256); + if (!result) { + ico_image_release(image); + ico_reader_close(file); + return NULL; + } + + /* fill in the palette */ + for (pal_index = 0; pal_index < image->palette_size; ++pal_index) { + i_color c; + c.rgba.r = image->palette[pal_index].r; + c.rgba.g = image->palette[pal_index].g; + c.rgba.b = image->palette[pal_index].b; + c.rgba.a = 255; /* so as to not confuse some code */ + + 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; + } + } + + /* fill in the image data */ + image_data = image->image_data; + for (y = 0; y < image->height; ++y) { + i_ppal(result, 0, image->width, y, image_data); + image_data += image->width; + } + } + + { + unsigned char *inp = image->mask_data; + char *outp; + int x, y; + char *mask; + /* fill in the mask tag */ + /* space for " .\n", width + 1 chars per line and NUL */ + mask = mymalloc(3 + (image->width + 1) * image->height + 1); + + outp = mask; + *outp++ = '.'; + *outp++ = '*'; + *outp++ = '\n'; + for (y = 0; y < image->height; ++y) { + for (x = 0; x < image->width; ++x) { + *outp++ = *inp++ ? '*' : '.'; + } + if (y != image->height - 1) /* not on the last line */ + *outp++ = '\n'; + } + *outp++ = '\0'; + + i_tags_set(&result->tags, "ico_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); + + ico_image_release(image); + + return result; +} + +i_img * +i_readico_single(io_glue *ig, int index) { + ico_reader_t *file; + i_img *result; + int error; + + file = ico_reader_open(ig, &error); + if (!file) { + ico_push_error(error); + return NULL; + } + + if (index < 0 && index >= ico_image_count(file)) { + i_push_error(0, "page out of range"); + return NULL; + } + + result = read_one_icon(file, index); + ico_reader_close(file); + + return result; +} + +i_img ** +i_readico_multi(io_glue *ig, int *count) { + ico_reader_t *file; + int index; + int error; + i_img **imgs; + + file = ico_reader_open(ig, &error); + if (!file) { + ico_push_error(error); + return NULL; + } + + imgs = mymalloc(sizeof(i_img *) * ico_image_count(file)); + + *count = 0; + for (index = 0; index < ico_image_count(file); ++index) { + i_img *im = read_one_icon(file, index); + if (!im) + break; + + imgs[(*count)++] = im; + } + + ico_reader_close(file); + + if (*count == 0) { + myfree(imgs); + return NULL; + } + + return imgs; +} diff --git a/ICO/imicon.h b/ICO/imicon.h new file mode 100644 index 00000000..855e7c96 --- /dev/null +++ b/ICO/imicon.h @@ -0,0 +1,11 @@ +#ifndef IMAGER_IMICON_H +#define IMAGER_IMICON_H + +#include "imext.h" + +extern i_img * +i_readico_single(io_glue *ig, int index); +extern i_img ** +i_readico_multi(io_glue *ig, int *count); + +#endif diff --git a/ICO/msicon.c b/ICO/msicon.c new file mode 100644 index 00000000..5e07fdeb --- /dev/null +++ b/ICO/msicon.c @@ -0,0 +1,767 @@ +#include "msicon.h" +#include +#include +#include +#include + +static int +read_palette(ico_reader_t *file, ico_image_t *image, int *error); +static int +read_32bit_data(ico_reader_t *file, ico_image_t *image, int *error); +static int +read_8bit_data(ico_reader_t *file, ico_image_t *image, int *error); +static int +read_4bit_data(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); + +typedef struct { + int width; + int height; + long offset; + long size; +} ico_reader_image_entry; + +/* this was previously declared, now define it */ +struct ico_reader_tag { + /* the file we're dealing with */ + i_io_glue_t *ig; + + /* number of images in the file */ + int count; + + /* type of resource - 1=icon, 2=cursor */ + int type; + + /* image information from the header */ + ico_reader_image_entry *images; +}; + +static +int read_packed(io_glue *ig, const char *format, ...); + +/* +=head1 NAME + +msicon.c - functions for working with .ICO files. + +=head1 SYNOPSIS + + // reading + int error; + ico_reader_t *file = ico_reader_open(ig, &error); + if (!file) { + char buffer[100]; + ico_error_message(error, buffer, sizeof(buffer)); + fputs(buffer, stderr); + exit(1); + } + int count = ico_image_count(file); + for (i = 0; i < count; ++i) { + ico_image_t *im = ico_image_read(file, index); + printf("%d x %d image %d\n", im->width, im->height, + im->direct ? "direct" : "paletted"); + ico_image_release(im); + } + ico_reader_close(file); + +=head1 DESCRIPTION + +This is intended as a general interface to reading MS Icon files, and +is written to be independent of Imager, even though it is part of +Imager. You just need to supply something that acts like Imager's +io_glue. + +It relies on icon images being generally small, and reads the entire +image into memory when reading. + +=head1 READING ICON FILES + +=over + +=item ico_reader_open(ig, &error) + +Parameters: + +=over + +=item * + +io_glue *ig - an Imager IO object. This must be seekable. + +=item * + +int *error - pointer to an integer which an error code will be +returned in on failure. + +=back + +=cut +*/ + +ico_reader_t * +ico_reader_open(i_io_glue_t *ig, int *error) { + long res1, type, count; + ico_reader_t *file = NULL; + int i; + + if (!read_packed(ig, "www", &res1, &type, &count)) { + *error = ICOERR_Short_File; + return NULL; + } + if (res1 != 0 || (type != 1 && type != 2) || count == 0) { + *error = ICOERR_Invalid_File; + return NULL; + } + + file = malloc(sizeof(ico_reader_t)); + if (!file) { + *error = ICOERR_Out_Of_Memory; + return NULL; + } + file->count = count; + file->type = type; + file->ig = ig; + file->images = malloc(sizeof(ico_reader_image_entry) * count); + if (file->images == NULL) { + *error = ICOERR_Out_Of_Memory; + free(file); + return NULL; + } + + 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; + } + + image->width = width; + image->height = height; + image->offset = image_offset; + image->size = bytes_in_res; + } + + return file; +} + +/* +=item ico_image_count + + // number of images in the file + count = ico_image_count(file); + +=cut +*/ + +int +ico_image_count(ico_reader_t *file) { + return file->count; +} + +/* +=item ico_type + + // type of file - 1 for icon, 2 for cursor + type = ico_type(file); + +=cut +*/ + +int +ico_type(ico_reader_t *file) { + return file->type; +} + +/* +=item ico_image_read + +Read an image from the file given it's index. + +=cut +*/ + +ico_image_t * +ico_image_read(ico_reader_t *file, int index, int *error) { + io_glue *ig = file->ig; + ico_reader_image_entry *im; + long bi_size, width, height, planes, bit_count; + ico_image_t *result; + + if (index < 0 || index >= file->count) { + *error = ICOERR_Bad_Image_Index; + return NULL; + } + + im = file->images + index; + if (i_io_seek(ig, im->offset, SEEK_SET) != im->offset) { + *error = ICOERR_File_Error; + return NULL; + } + + if (!read_packed(ig, "dddww xxxx xxxx xxxx xxxx xxxx xxxx", &bi_size, + &width, &height, &planes, &bit_count)) { + *error = ICOERR_Short_File; + return NULL; + } + + /* the bitmapinfoheader height includes the height of + the and and xor masks */ + if (bi_size != 40 || width != im->width || height != im->height * 2 + || planes != 1) { /* don't know how to handle planes != 1 */ + *error = ICOERR_Invalid_File; + return NULL; + } + + result = malloc(sizeof(ico_image_t)); + if (!result) { + *error = ICOERR_Out_Of_Memory; + return NULL; + } + result->width = width; + result->height = im->height; + result->direct = bit_count > 8; + result->bit_count = bit_count; + result->palette = NULL; + result->image_data = NULL; + result->mask_data = NULL; + + if (result->direct) { + result->palette_size = 0; + + result->image_data = malloc(result->width * result->height * sizeof(ico_color_t)); + if (!result->image_data) { + free(result); + *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; + 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; + } + + if (!read_palette(file, result, error)) { + free(result->palette); + free(result->image_data); + free(result); + return 0; + } + + switch (bit_count) { + case 1: + read_result = read_1bit_data(file, result, error); + break; + + case 4: + read_result = read_4bit_data(file, result, error); + break; + + case 8: + read_result = read_8bit_data(file, result, error); + break; + + default: + read_result = 0; + *error = ICOERR_Unknown_Bits; + break; + } + + if (!read_result) { + free(result->palette); + free(result->image_data); + free(result); + return 0; + } + } + + result->mask_data = malloc(result->width * result->height); + if (!result->mask_data) { + *error = ICOERR_Out_Of_Memory; + free(result->palette); + free(result->image_data); + free(result); + return 0; + } + + if (!read_mask(file, result, error)) { + free(result->mask_data); + free(result->palette); + free(result->image_data); + free(result); + return 0; + } + + return result; +} + +/* +=item ico_image_release + +Release an image structure returned by ico_image_read. + +=cut +*/ + +void +ico_image_release(ico_image_t *image) { + free(image->mask_data); + free(image->palette); + free(image->image_data); + free(image); +} + +/* +=item ico_reader_close + +Releases the file structure. + +=cut +*/ + +void +ico_reader_close(ico_reader_t *file) { + i_io_close(file->ig); + free(file->images); + free(file); +} + +/* +=item ico_error_message + +Converts an error code into an error message. + +=cut +*/ + +size_t +ico_error_message(int error, char *buffer, size_t buffer_size) { + char const *msg; + size_t size; + + switch (error) { + case ICOERR_Short_File: + msg = "Short read"; + break; + + case ICOERR_File_Error: + msg = "I/O error"; + break; + + case ICOERR_Invalid_File: + msg = "Not an icon file"; + break; + + case ICOERR_Unknown_Bits: + msg = "Unknown value for bits/pixel"; + break; + + case ICOERR_Bad_Image_Index: + msg = "Image index out of range"; + break; + + case ICOERR_Out_Of_Memory: + msg = "Out of memory"; + break; + + default: + msg = "Unknown error code"; + break; + } + + size = strlen(msg) + 1; + if (size > buffer_size) + size = buffer_size; + memcpy(buffer, msg, size); + buffer[size-1] = '\0'; + + return size; +} + +/* +=back + +=head1 PRIVATE FUNCTIONS + +=over + +=item read_packed + +Reads packed data from a stream, unpacking it. + +=cut +*/ + +static +int read_packed(io_glue *ig, const char *format, ...) { + unsigned char buffer[100]; + va_list ap; + long *p; + int size; + const char *formatp; + unsigned char *bufp; + + /* read efficiently, work out the size of the buffer */ + size = 0; + formatp = format; + while (*formatp) { + switch (*formatp++) { + case 'b': + case 'x': size += 1; 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); + } + + if (i_io_read(ig, buffer, size) != size) { + return 0; + } + + va_start(ap, format); + + bufp = buffer; + while (*format) { + + switch (*format) { + case 'b': + p = va_arg(ap, long *); + *p = *bufp++; + break; + + case 'w': + p = va_arg(ap, long *); + *p = bufp[0] + (bufp[1] << 8); + bufp += 2; + break; + + case 'd': + p = va_arg(ap, long *); + *p = bufp[0] + (bufp[1] << 8) + (bufp[2] << 16) + (bufp[3] << 24); + bufp += 4; + break; + + case 'x': + ++bufp; /* skip a byte */ + break; + + case ' ': + /* nothing to do */ + break; + } + ++format; + } + return 1; +} + +/* +=item read_palette + +Reads the palette data for an icon image. + +=cut +*/ + +static +int +read_palette(ico_reader_t *file, ico_image_t *image, int *error) { + int palette_bytes = image->palette_size * 4; + unsigned char *read_buffer = malloc(palette_bytes); + unsigned char *inp; + ico_color_t *outp; + int i; + + if (!read_buffer) { + *error = ICOERR_Out_Of_Memory; + return 0; + } + + if (i_io_read(file->ig, read_buffer, palette_bytes) != palette_bytes) { + *error = ICOERR_Short_File; + free(read_buffer); + return 0; + } + + inp = read_buffer; + outp = image->palette; + for (i = 0; i < image->palette_size; ++i) { + outp->b = *inp++; + outp->g = *inp++; + outp->r = *inp++; + outp->a = 255; + ++inp; + ++outp; + } + free(read_buffer); + + return 1; +} + +/* +=item read_32bit_data + +Reads 32 bit image data. + +=cut +*/ + +static +int +read_32bit_data(ico_reader_t *file, ico_image_t *image, int *error) { + int line_bytes = image->width * 4; + unsigned char *buffer = malloc(line_bytes); + int y; + int x; + unsigned char *inp; + ico_color_t *outp; + + if (!buffer) { + *error = ICOERR_Out_Of_Memory; + return 0; + } + + for (y = image->height - 1; y >= 0; --y) { + if (i_io_read(file->ig, buffer, line_bytes) != line_bytes) { + free(buffer); + *error = ICOERR_Short_File; + return 0; + } + outp = image->image_data; + outp += y * image->width; + inp = buffer; + for (x = 0; x < image->width; ++x) { + outp->b = inp[0]; + outp->g = inp[1]; + outp->r = inp[2]; + outp->a = inp[3]; + ++outp; + inp += 4; + } + } + free(buffer); + + return 1; +} + +/* +=item read_8bit_data + +Reads 8 bit image data. + +=cut +*/ + +static +int +read_8bit_data(ico_reader_t *file, ico_image_t *image, int *error) { + int line_bytes = (image->width + 3) / 4 * 4; + unsigned char *buffer = malloc(line_bytes); + int y; + int x; + unsigned char *inp, *outp; + + if (!buffer) { + *error = ICOERR_Out_Of_Memory; + return 0; + } + + for (y = image->height - 1; y >= 0; --y) { + outp = image->image_data; + outp += y * image->width; + if (i_io_read(file->ig, buffer, line_bytes) != line_bytes) { + free(buffer); + *error = ICOERR_Short_File; + return 0; + } + for (x = 0, inp = buffer; x < image->width; ++x) { + *outp++ = *inp++; + } + } + free(buffer); + + return 1; +} + +/* +=item read_4bit_data + +Reads 4 bit image data. + +=cut +*/ + +static +int +read_4bit_data(ico_reader_t *file, ico_image_t *image, int *error) { + /* 2 pixels per byte, rounded up to the nearest dword */ + int line_bytes = ((image->width + 1) / 2 + 3) / 4 * 4; + unsigned char *read_buffer = malloc(line_bytes); + int y; + int x; + unsigned char *inp, *outp; + + if (!read_buffer) { + *error = ICOERR_Out_Of_Memory; + return 0; + } + + for (y = image->height - 1; y >= 0; --y) { + if (i_io_read(file->ig, read_buffer, line_bytes) != line_bytes) { + free(read_buffer); + *error = ICOERR_Short_File; + return 0; + } + + outp = image->image_data; + outp += y * image->width; + inp = read_buffer; + for (x = 0; x < image->width; ++x) { + /* yes, this is kind of ugly */ + if (x & 1) { + *outp++ = *inp++ & 0x0F; + } + else { + *outp++ = *inp >> 4; + } + } + } + free(read_buffer); + + return 1; +} + +/* +=item read_1bit_data + +Reads 1 bit image data. + +=cut +*/ + +static +int +read_1bit_data(ico_reader_t *file, ico_image_t *image, int *error) { + /* 8 pixels per byte, rounded up to the nearest dword */ + int line_bytes = ((image->width + 7) / 8 + 3) / 4 * 4; + unsigned char *read_buffer = malloc(line_bytes); + int y; + int x; + unsigned char *inp, *outp; + + if (!read_buffer) { + *error = ICOERR_Out_Of_Memory; + return 0; + } + + for (y = image->height - 1; y >= 0; --y) { + if (i_io_read(file->ig, read_buffer, line_bytes) != line_bytes) { + free(read_buffer); + *error = ICOERR_Short_File; + return 0; + } + + outp = image->image_data; + outp += y * image->width; + inp = read_buffer; + for (x = 0; x < image->width; ++x) { + *outp++ = (*inp >> (7 - (x & 7))) & 1; + if ((x & 7) == 7) + ++inp; + } + } + free(read_buffer); + + return 1; +} + +/* this is very similar to the 1 bit reader */ +/* +=item read_mask + +Reads the AND mask from an icon image. + +=cut +*/ + +static +int +read_mask(ico_reader_t *file, ico_image_t *image, int *error) { + /* 8 pixels per byte, rounded up to the nearest dword */ + int line_bytes = ((image->width + 7) / 8 + 3) / 4 * 4; + unsigned char *read_buffer = malloc(line_bytes); + int y; + int x; + unsigned char *inp, *outp; + + if (!read_buffer) { + *error = ICOERR_Out_Of_Memory; + return 0; + } + + for (y = image->height - 1; y >= 0; --y) { + if (i_io_read(file->ig, read_buffer, line_bytes) != line_bytes) { + free(read_buffer); + *error = ICOERR_Short_File; + return 0; + } + + outp = image->mask_data + y * image->width; + inp = read_buffer; + for (x = 0; x < image->width; ++x) { + *outp++ = (*inp >> (7 - (x & 7))) & 1; + if ((x & 7) == 7) + ++inp; + } + } + free(read_buffer); + + return 1; +} + +/* +=back + +=head1 AUTHOR + +Tony Cook + +=head1 REVISION + +$Revision$ + +=cut +*/ diff --git a/ICO/msicon.h b/ICO/msicon.h new file mode 100644 index 00000000..0b26b604 --- /dev/null +++ b/ICO/msicon.h @@ -0,0 +1,47 @@ +#ifndef IMAGER_MSICON_H_ +#define IMAGER_MSICON_H_ + +#include "iolayer.h" + +typedef struct ico_reader_tag ico_reader_t; + +#define ICON_ICON 1 +#define ICON_CURSOR 2 + +typedef struct { + unsigned char r, g, b, a; +} ico_color_t; + +typedef struct { + int width; + int height; + int direct; + int bit_count; + void *image_data; + int palette_size; + ico_color_t *palette; + unsigned char *mask_data; +} ico_image_t; + +extern ico_reader_t *ico_reader_open(i_io_glue_t *ig, int *error); +extern int ico_image_count(ico_reader_t *file); +extern int ico_type(ico_reader_t *file); +extern ico_image_t *ico_image_read(ico_reader_t *file, int index, int *error); +extern void ico_image_release(ico_image_t *image); +extern void ico_reader_close(ico_reader_t *file); + +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_Invalid_File 200 +#define ICOERR_Unknown_Bits 201 + +#define ICOERR_Bad_Image_Index 300 + +#define ICOERR_Out_Of_Memory 400 + +#endif diff --git a/ICO/t/t10icon.t b/ICO/t/t10icon.t new file mode 100644 index 00000000..c89ae9ab --- /dev/null +++ b/ICO/t/t10icon.t @@ -0,0 +1,161 @@ +#!perl -w +use strict; +use Test::More tests => 60; + +BEGIN { use_ok('Imager::File::ICO'); } + +-d 'testout' or mkdir 'testout'; + +my $im = Imager->new; +ok($im->read(file => "testimg/rgba3232.ico", type=>"ico"), + "read 32 bit") + or print "# ", $im->errstr, "\n"; +is($im->getwidth, 32, "check width"); +is($im->getwidth, 32, "check height"); +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->tags(name => 'ico_mask'), $mask, "check ico_mask_tag"); + +# compare the pixels +# ppm can't store 4 channels +SKIP: +{ + my $work = $im->convert(preset=>'noalpha'); + my $comp = Imager->new; + $comp->read(file => "testimg/rgba3232.ppm") + or skip "could not read 24-bit comparison file:". $comp->errstr, 1; + is(Imager::i_img_diff($comp->{IMG}, $work->{IMG}), 0, + "compare image data"); +} + +ok($im->read(file => 'testimg/pal83232.ico', type=>'ico'), + "read 8 bit") + or print "# ", $im->errstr, "\n"; +is($im->getwidth, 32, "check width"); +is($im->getwidth, 32, "check height"); +is($im->type, 'paletted', "check type"); +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; + $comp->read(file => "testimg/pal83232.ppm") + or skip "could not read 8-bit comparison file:". $comp->errstr, 1; + is(Imager::i_img_diff($comp->{IMG}, $im->{IMG}), 0, + "compare image data"); +} +$im->write(file=>'testout/pal83232.ppm'); + +ok($im->read(file => 'testimg/pal43232.ico', type=>'ico'), + "read 4 bit") + or print "# ", $im->errstr, "\n"; +is($im->getwidth, 32, "check width"); +is($im->getwidth, 32, "check height"); +is($im->type, 'paletted', "check type"); +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; + $comp->read(file => "testimg/pal43232.ppm") + or skip "could not read 4-bit comparison file:". $comp->errstr, 1; + is(Imager::i_img_diff($comp->{IMG}, $im->{IMG}), 0, + "compare image data"); +} + +$im->write(file=>'testout/pal43232.ppm'); +ok($im->read(file => 'testimg/pal13232.ico', type=>'ico'), + "read 1 bit") + or print "# ", $im->errstr, "\n"; +is($im->getwidth, 32, "check width"); +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"); +$im->write(file=>'testout/pal13232.ppm'); + +# combo was created with the GIMP, which has a decent mechanism for selecting +# the output format +# you get different size icon images by making different size layers. +my @imgs = Imager->read_multi(file => 'testimg/combo.ico', type=>'ico'); +is(scalar(@imgs), 3, "read multiple"); +is($imgs[0]->getwidth, 48, "image 0 width"); +is($imgs[0]->getheight, 48, "image 0 height"); +is($imgs[1]->getwidth, 32, "image 1 width"); +is($imgs[1]->getheight, 32, "image 1 height"); +is($imgs[2]->getwidth, 16, "image 2 width"); +is($imgs[2]->getheight, 16, "image 2 height"); +is($imgs[0]->type, 'direct', "image 0 type"); +is($imgs[1]->type, 'paletted', "image 1 type"); +is($imgs[2]->type, 'paletted', "image 2 type"); +is($imgs[1]->colorcount, 256, "image 1 colorcount"); +is($imgs[2]->colorcount, 16, "image 2 colorcount"); + +is_deeply([ $imgs[0]->getpixel(x=>0, 'y'=>0)->rgba ], [ 231, 17, 67, 255 ], + "check image data 0(0,0)"); +is_deeply([ $imgs[1]->getpixel(x=>0, 'y'=>0)->rgba ], [ 231, 17, 67, 255 ], + "check image data 1(0,0)"); +is_deeply([ $imgs[2]->getpixel(x=>0, 'y'=>0)->rgba ], [ 231, 17, 67, 255 ], + "check image data 2(0,0)"); + +is_deeply([ $imgs[0]->getpixel(x=>47, 'y'=>0)->rgba ], [ 131, 231, 17, 255 ], + "check image data 0(47,0)"); +is_deeply([ $imgs[1]->getpixel(x=>31, 'y'=>0)->rgba ], [ 131, 231, 17, 255 ], + "check image data 1(31,0)"); +is_deeply([ $imgs[2]->getpixel(x=>15, 'y'=>0)->rgba ], [ 131, 231, 17, 255 ], + "check image data 2(15,0)"); + +is_deeply([ $imgs[0]->getpixel(x=>0, 'y'=>47)->rgba ], [ 17, 42, 231, 255 ], + "check image data 0(0,47)"); +is_deeply([ $imgs[1]->getpixel(x=>0, 'y'=>31)->rgba ], [ 17, 42, 231, 255 ], + "check image data 1(0,31)"); +is_deeply([ $imgs[2]->getpixel(x=>0, 'y'=>15)->rgba ], [ 17, 42, 231, 255 ], + "check image data 2(0,15)"); + +is_deeply([ $imgs[0]->getpixel(x=>47, 'y'=>47)->rgba ], [ 17, 231, 177, 255 ], + "check image data 0(47,47)"); +is_deeply([ $imgs[1]->getpixel(x=>31, 'y'=>31)->rgba ], [ 17, 231, 177, 255 ], + "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)"); diff --git a/ICO/t/t20readone.t b/ICO/t/t20readone.t new file mode 100644 index 00000000..9fc04c81 --- /dev/null +++ b/ICO/t/t20readone.t @@ -0,0 +1,10 @@ +#!perl -w +use strict; +use Test::More tests => 1; +use Imager; + +# checks that we load the ICO handler automatically +my $im = Imager->new; +ok($im->read(file => 'testimg/rgba3232.ico'), + "check that icon reader loaded correctly for singles") + or print "# ", $im->errstr, "\n"; diff --git a/ICO/t/t21readmult.t b/ICO/t/t21readmult.t new file mode 100644 index 00000000..1a62c6e9 --- /dev/null +++ b/ICO/t/t21readmult.t @@ -0,0 +1,10 @@ +#!perl -w +use strict; +use Test::More tests => 1; +use Imager; + +# checks that we load the ICO handler automatically +my @imgs = Imager->read_multi(file => 'testimg/combo.ico') + or print "# ",Imager->errstr,"\n"; +is(@imgs, 3, + "check that icon reader loaded correctly for multiples"); diff --git a/ICO/testimg/combo.ico b/ICO/testimg/combo.ico new file mode 100644 index 00000000..bffefaac Binary files /dev/null and b/ICO/testimg/combo.ico differ diff --git a/ICO/testimg/pal13232.ico b/ICO/testimg/pal13232.ico new file mode 100644 index 00000000..701e14f6 Binary files /dev/null and b/ICO/testimg/pal13232.ico differ diff --git a/ICO/testimg/pal43232.ico b/ICO/testimg/pal43232.ico new file mode 100644 index 00000000..47b6cccd Binary files /dev/null and b/ICO/testimg/pal43232.ico differ diff --git a/ICO/testimg/pal43232.ppm b/ICO/testimg/pal43232.ppm new file mode 100644 index 00000000..7a0204f6 --- /dev/null +++ b/ICO/testimg/pal43232.ppm @@ -0,0 +1,5 @@ +P6 +#CREATOR: Imager +32 32 +255 +9999999999999999993| 93| 3| 3| 3| 3| 3| 3| 3| `¿4r¡r¡Áµ(99999999999999993| 93| 93| 3| 3| 3| `¿43| `¿4r¡r¡Áµ(Áµ(Áµ(999999999999993| 3| 3| 3| 3| 3| 3| 3| 3| 3| `¿4r¡r¡Áµ(Áµ(Áµ(ïš*ïš*9999999999993| 93| 93| 3| 3| 3| 3| `¿4r¡r¡Áµ(Áµ(Áµ(ïš*Áµ(ïš*ïš*ïš*9999999993| 3| 3| 3| 3| 3| 3| 3| 3| `¿43| r¡r¡Áµ(Áµ(Áµ(Áµ(ïš*ïš*ïš*ïš*ÕO*ÕO*9999999993| 93| 3| 3| 3| `¿43| r¡r¡r¡Áµ(Áµ(Áµ(ïš*ïš*ïš*ïš*ïš*ëA#ïš*ëA#ëA#999993| 3| 3| 3| 3| 3| 3| 3| 3| 3| `¿4r¡r¡Áµ(Áµ(Áµ(ïš*ïš*ïš*ïš*ïš*ëA#ëA#ëA#ëA#ëA#ëA#9993| 93| 3| 3| 3| 3| 3| 3| `¿4r¡r¡Áµ(Áµ(ïš*Áµ(ïš*ïš*ïš*ïš*ïš*ÕO*ëA#ëA#ëA#ëA#ëA#ëA#ëA#3| 3| 3| 3| 3| 3| 3| 3| 3| `¿43| r¡r¡Áµ(Áµ(Áµ(ïš*Áµ(ïš*ïš*ïš*ÕO*ëA#ëA#ëA#ëA#ëA#ëA#ëA#ëA#ÕO*ÕO*93| 3| 3| 3| 3| 3| 3| r¡r¡Áµ(Áµ(Áµ(Áµ(ïš*ïš*ïš*ïš*ïš*ëA#ëA#ëA#ëA#ëA#ëA#ëA#ëA#ëA#ÕO*ÕO*ÕO*ÕO*3| 3| 3| 3| 3| `¿4r¡r¡Áµ(Áµ(Áµ(ïš*ïš*ïš*ïš*ïš*ëA#ëA#ëA#ëA#ëA#ëA#ëA#ëA#ëA#ÕO*ÕO*ÕO*ÕO*ÕO*ÕO*”OB3| 3| `¿4r¡r¡r¡Áµ(Áµ(Áµ(Áµ(ïš*ïš*ïš*ïš*ëA#ïš*ëA#ëA#ëA#ëA#ëA#ëA#ëA#ÕO*ÕO*ÕO*ÕO*ÕO*”OBÕO*”OB”OB3| r¡r¡Áµ(Áµ(Áµ(ïš*ïš*ïš*ïš*ïš*ïš*ëA#ëA#ëA#ëA#ëA#ëA#ëA#ëA#ÕO*ÕO*ÕO*ÕO*ÕO*ÕO*”OBÕO*”OB”OB”OB”OBr¡Áµ(Áµ(Áµ(Áµ(ïš*ïš*ïš*ïš*ÕO*ëA#ëA#ëA#ëA#ëA#ëA#ëA#ÕO*ëA#ÕO*ÕO*ÕO*ÕO*ÕO*ÕO*”OB”OB”OB”OB”OBƒOˆƒOˆÁµ(Áµ(ïš*ïš*ïš*ïš*ïš*ëA#ëA#ëA#ëA#ëA#ëA#ëA#ëA#ëA#ëA#ÕO*ÕO*ÕO*ÕO*”OBÕO*”OB”OB”OB”OBƒOˆƒOˆƒOˆƒOˆ}Víïš*ïš*ïš*ïš*ïš*ëA#ïš*ëA#ëA#ëA#ëA#ëA#ëA#ëA#ÕO*ÕO*ÕO*ÕO*ÕO*”OBÕO*”OB”OB”OB”OBƒOˆƒOˆƒOˆƒOˆ}Ví}Ví}Víïš*ïš*ÕO*ëA#ëA#ëA#ëA#ëA#ëA#ëA#ëA#ÕO*ÕO*ÕO*ÕO*ÕO*ÕO*”OB”OB”OB”OB”OBƒOˆƒOˆƒOˆƒOˆƒOˆ}Ví}Ví}Ví}Ví}Víïš*ëA#ëA#ëA#ëA#ëA#ëA#ëA#ëA#ÕO*ÕO*ÕO*ÕO*ÕO*”OBÕO*”OB”OB”OB”OBƒOˆƒOˆƒOˆƒOˆ}Ví}Ví}Ví}Ví}Ví}Ví}Ví}VíëA#ëA#ëA#ëA#ëA#ëA#ÕO*ÕO*ÕO*ÕO*ÕO*ÕO*”OBÕO*”OB”OB”OB”OBƒOˆƒOˆƒOˆƒOˆ}Ví}Ví}Ví}Ví}Ví}Ví}Ví}V톂ô†‚ôëA#ëA#ëA#ëA#ëA#ÕO*ÕO*ÕO*ÕO*”OBÕO*”OB”OB”OB”OBƒOˆƒOˆƒOˆƒOˆƒOˆ}Ví}Ví}Ví}Ví}Ví}Ví}V톂ô†‚ô†‚ô†‚ô‘°ùëA#ëA#ÕO*ÕO*ÕO*ÕO*ÕO*ÕO*”OBÕO*”OB”OB”OBƒOˆƒOˆƒOˆƒOˆ}Ví}Ví}Ví}Ví}Ví}Ví}Ví}V톂ô†‚ô†‚ô†‚ô‘°ù‘°ù‘°ùÕO*ÕO*ÕO*ÕO*”OBÕO*”OB”OB”OB”OB”OBƒOˆƒOˆƒOˆƒOˆ}Ví}Ví}Ví}Ví}Ví}Ví}Ví}V톂ô†‚ô†‚ô†‚ô‘°ù‘°ùæöæöæöÕO*ÕO*”OBÕO*”OB”OB”OB”OBƒOˆƒOˆƒOˆƒOˆ†‚ô}Ví}Ví}Ví}Ví}Ví}Ví}V톂ô†‚ô†‚ô†‚ô‘°ù‘°ù‘°ùæöæöæö”öĔöÄÕO*ÕO*”OB”OB”OB”OBƒOˆƒOˆƒOˆƒOˆ}Ví}Ví}Ví}Ví}Ví}Ví}Ví}Ví}V톂ô†‚ô†‚ô‘°ù‘°ù‘°ùæöæöæö”öĔöĔöÄ`¿4”OB”OB”OB”OBƒOˆƒOˆƒOˆ†‚ô}Ví}Ví}Ví}Ví}Ví}Ví}Ví}V톂ô†‚ô‘°ù†‚ô‘°ù‘°ùæöæöæö”öĔöĔöĔöÄ`¿4`¿4`¿4”OBƒOˆƒOˆƒOˆƒOˆƒOˆ}Ví}Ví}Ví}Ví}Ví}Ví}V톂ô†‚ô†‚ô†‚ô‘°ù‘°ù‘°ùæöæöæö”öĔöĔöÄ`¿4`¿4`¿4`¿4`¿4`¿4ƒOˆƒOˆƒOˆ}Ví}Ví}Ví}Ví}Ví}Ví}Ví}V톂ô†‚ô†‚ô†‚ô‘°ù‘°ù‘°ùæöæö”öĔöĔöĔöÄ`¿4`¿4`¿4`¿4`¿4`¿4`¿4`¿4ƒOˆ}Ví}Ví}Ví}Ví}Ví}Ví}Ví}V톂ô†‚ô†‚ô‘°ù‘°ù‘°ùæöæöæö”öĔöĔöĔöÄ`¿4`¿4`¿4`¿4`¿4`¿4`¿4r¡r¡r¡}Ví}Ví}Ví}Ví}Ví}V톂ô†‚ô†‚ô†‚ô‘°ù‘°ù‘°ùæöæö”öĔöĔöĔöÄ`¿4`¿4`¿4`¿4`¿4`¿4`¿4`¿4r¡r¡r¡r¡3| }Ví}Ví}Ví}V톂ô†‚ô†‚ô†‚ô‘°ù‘°ù‘°ùæöæöæö”öĔöĔöÄ`¿4`¿4`¿4`¿4`¿4`¿4`¿4`¿4r¡r¡r¡3| r¡3| r¡}V톂ô†‚ô†‚ô†‚ô‘°ù‘°ù‘°ùæöæöæö”öĔöĔöĔöÄ`¿4`¿4`¿4`¿4`¿4`¿4`¿4r¡r¡r¡r¡r¡r¡3| Áµ(9r¡†‚ô†‚ô†‚ô‘°ù‘°ù‘°ùæöæöæö”öĔöĔöÄ`¿4`¿4`¿4`¿4`¿4`¿4`¿4`¿4r¡r¡r¡r¡r¡3| Áµ(93| r¡r¡r¡ \ No newline at end of file diff --git a/ICO/testimg/pal83232.ico b/ICO/testimg/pal83232.ico new file mode 100644 index 00000000..37ae6936 Binary files /dev/null and b/ICO/testimg/pal83232.ico differ diff --git a/ICO/testimg/pal83232.ppm b/ICO/testimg/pal83232.ppm new file mode 100644 index 00000000..f4b30831 Binary files /dev/null and b/ICO/testimg/pal83232.ppm differ diff --git a/ICO/testimg/rgba3232.ico b/ICO/testimg/rgba3232.ico new file mode 100644 index 00000000..5e6fc54f Binary files /dev/null and b/ICO/testimg/rgba3232.ico differ diff --git a/ICO/testimg/rgba3232.ppm b/ICO/testimg/rgba3232.ppm new file mode 100644 index 00000000..47dcecaa Binary files /dev/null and b/ICO/testimg/rgba3232.ppm differ diff --git a/MANIFEST b/MANIFEST index 2fb5b74b..6bee1b17 100644 --- a/MANIFEST +++ b/MANIFEST @@ -13,6 +13,24 @@ Flines/Flines.pm Flines/Flines.xs Flines/Makefile.PL Flines/t/t00flines.t +ICO/ICO.pm Windows Icon file support +ICO/ICO.xs +ICO/Makefile.PL +ICO/imicon.c +ICO/imicon.h +ICO/msicon.c +ICO/msicon.h +ICO/t/t10icon.t +ICO/t/t20readone.t +ICO/t/t21readmult.t +ICO/testimg/combo.ico +ICO/testimg/pal13232.ico +ICO/testimg/pal43232.ico +ICO/testimg/pal43232.ppm +ICO/testimg/pal83232.ico +ICO/testimg/pal83232.ppm +ICO/testimg/rgba3232.ico +ICO/testimg/rgba3232.ppm Imager.pm Imager.xs MANIFEST @@ -158,6 +176,7 @@ samples/tk-photo.pl spot.perl For making an ordered dither matrix from a spot function stackmach.c stackmach.h +t/Pod/Coverage/Imager.pm t/Test/Builder.pm t/Test/More.pm t/t00basic.t diff --git a/MANIFEST.SKIP b/MANIFEST.SKIP index 3fb55bf5..2f31f948 100644 --- a/MANIFEST.SKIP +++ b/MANIFEST.SKIP @@ -4,6 +4,7 @@ # editor trash ~$ +^\#.*\#$ # stuff we don't distribute ^TODO$ @@ -40,3 +41,6 @@ Makefile\.old ^meta\.tmp$ /meta\.tmp$ ^imconfig\.h$ +^ICO/ICO\.c$ +^ICO/testout +