#include "imext.h"
#include "msicon.h"
#include <string.h>
#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_24bit_data(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);
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 */
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;
};
/*
=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 (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;
}
/* a width or height of zero here indicates a width/height of 256 */
image->width = width ? width : 256;
image->height = height ? height : 256;
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 - ICON_ICON for icon, ICON_CURSOR 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;
}
if (bit_count != 1 && bit_count != 4 && bit_count != 8
&& bit_count != 24 && bit_count != 32) {
*error = ICOERR_Unknown_Bits;
return 0;
}
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;
result->hotspot_x = im->hotspot_x;
result->hotspot_y = im->hotspot_y;
if (bit_count == 32) {
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 (!read_32bit_data(file, result, error)) {
free(result->image_data);
free(result);
return NULL;
}
}
else if (bit_count == 24) {
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 (!read_24bit_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);
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);
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:
assert(0); /* this can't happen in theory */
read_result = 0;
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 read file structure.
=cut
*/
void
ico_reader_close(ico_reader_t *file) {
i_io_close(file->ig);
free(file->images);
free(file);
}
/*
=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);
int width_byte = image->width == 256 ? 0 : image->width;
int height_byte = image->height == 256 ? 0 : image->height;
if (type == ICON_ICON) {
if (!write_packed(ig, "bbbbwwdd", width_byte, height_byte,
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", width_byte, height_byte,
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.
=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_Write_Failure:
msg = "Write failure";
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_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;
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_24bit_data
Reads 24 bit image data.
=cut
*/
static
int
read_24bit_data(ico_reader_t *file, ico_image_t *image, int *error) {
int line_bytes = image->width * 3;
unsigned char *buffer;
int y;
int x;
unsigned char *inp;
ico_color_t *outp;
line_bytes = (line_bytes + 3) / 4 * 4;
buffer = malloc(line_bytes);
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 = 255;
++outp;
inp += 3;
}
}
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 <sigh> */
/*
=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;
int mask;
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;
mask = 0x80;
for (x = 0; x < image->width; ++x) {
*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 > 256) {
*error = ICOERR_Invalid_Width;
return 0;
}
if (image->height < 1 || image->height > 256) {
*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
=head1 AUTHOR
Tony Cook <tonyc@cpan.org>
=head1 REVISION
$Revision$
=cut
*/