gimp/plug-ins/winicon/icoload.c

582 lines
16 KiB
C

/* The GIMP -- an image manipulation program
* Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
*
* GIMP Plug-in for Windows Icon files.
* Copyright (C) 2002 Christian Kreibich <christian@whoop.org>.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "config.h"
#include <errno.h>
#include <string.h>
#include <glib/gstdio.h>
#include <libgimp/gimp.h>
/* #define ICO_DBG */
#include "main.h"
#include "libgimp/stdplugins-intl.h"
#define A_VAL(p) ((guchar *)(p))[3]
#define R_VAL(p) ((guchar *)(p))[2]
#define G_VAL(p) ((guchar *)(p))[1]
#define B_VAL(p) ((guchar *)(p))[0]
#define A_VAL_GIMP(p) ((guchar *)(p))[3]
#define R_VAL_GIMP(p) ((guchar *)(p))[0]
#define G_VAL_GIMP(p) ((guchar *)(p))[1]
#define B_VAL_GIMP(p) ((guchar *)(p))[2]
static gint ico_read_int8 (FILE *fp,
guint8 *data,
gint count);
static gint ico_read_int16 (FILE *fp,
guint16 *data,
gint count);
static gint ico_read_int32 (FILE *fp,
guint32 *data,
gint count);
static gboolean ico_init (const gchar *filename,
MsIcon *ico);
static void ico_read_entry (MsIcon *ico,
MsIconEntry *entry);
static void ico_read_data (MsIcon *ico,
gint icon_num);
static gint
ico_read_int32 (FILE *fp,
guint32 *data,
gint count)
{
gint i, total;
total = count;
if (count > 0)
{
ico_read_int8 (fp, (guint8 *) data, count * 4);
for (i = 0; i < count; i++)
data[i] = GUINT32_FROM_LE (data[i]);
}
return total * 4;
}
static gint
ico_read_int16 (FILE *fp,
guint16 *data,
gint count)
{
gint i, total;
total = count;
if (count > 0)
{
ico_read_int8 (fp, (guint8 *) data, count * 2);
for (i = 0; i < count; i++)
data[i] = GUINT16_FROM_LE (data[i]);
}
return total * 2;
}
static gint
ico_read_int8 (FILE *fp,
guint8 *data,
gint count)
{
gint total;
gint bytes;
total = count;
while (count > 0)
{
bytes = fread ((gchar *) data, sizeof (gchar), count, fp);
if (bytes <= 0) /* something bad happened */
break;
count -= bytes;
data += bytes;
}
return total;
}
static gboolean
ico_init (const gchar *filename,
MsIcon *ico)
{
memset (ico, 0, sizeof (MsIcon));
if (! (ico->fp = g_fopen (filename, "r")))
{
g_message (_("Could not open '%s' for reading: %s"),
gimp_filename_to_utf8 (filename), g_strerror (errno));
return FALSE;
}
ico->filename = filename;
ico->cp += ico_read_int16 (ico->fp, &ico->reserved, 1);
ico->cp += ico_read_int16 (ico->fp, &ico->resource_type, 1);
/* Icon files use 1 as resource type, that's what I wrote this for.
From descriptions on the web it seems as if this loader should
also be able to handle Win 3.11 - Win 95 cursor files (.cur),
which use resource type 2. I haven't tested this though. */
if (ico->reserved != 0 ||
(ico->resource_type != 1 && ico->resource_type != 2))
{
ico_cleanup (ico);
return FALSE;
}
return TRUE;
}
static void
ico_read_entry (MsIcon *ico,
MsIconEntry *entry)
{
g_return_if_fail (ico != NULL);
g_return_if_fail (entry != NULL);
ico->cp += ico_read_int8 (ico->fp, &entry->width, 1);
ico->cp += ico_read_int8 (ico->fp, &entry->height, 1);
ico->cp += ico_read_int8 (ico->fp, &entry->num_colors, 1);
ico->cp += ico_read_int8 (ico->fp, &entry->reserved, 1);
ico->cp += ico_read_int16 (ico->fp, &entry->num_planes, 1);
ico->cp += ico_read_int16 (ico->fp, &entry->bpp, 1);
ico->cp += ico_read_int32 (ico->fp, &entry->size, 1);
ico->cp += ico_read_int32 (ico->fp, &entry->offset, 1);
D(("Read entry with w: "
"%i, h: %i, num_colors: %i, bpp: %i, size %i, offset %i\n",
entry->width, entry->height, entry->num_colors, entry->bpp,
entry->size, entry->offset));
}
static void
ico_read_data (MsIcon *ico,
gint icon_num)
{
MsIconData *data;
MsIconEntry *entry;
gint length;
g_return_if_fail (ico != NULL);
D(("Reading data for icon %i ------------------------------\n", icon_num));
entry = &ico->icon_dir[icon_num];
data = &ico->icon_data[icon_num];
ico->cp = entry->offset;
if (fseek (ico->fp, entry->offset, SEEK_SET) < 0)
return;
D((" starting at offset %i\n", entry->offset));
ico->cp += ico_read_int32 (ico->fp, &data->header_size, 1);
ico->cp += ico_read_int32 (ico->fp, &data->width, 1);
ico->cp += ico_read_int32 (ico->fp, &data->height, 1);
ico->cp += ico_read_int16 (ico->fp, &data->planes, 1);
ico->cp += ico_read_int16 (ico->fp, &data->bpp, 1);
ico->cp += ico_read_int32 (ico->fp, &data->compression, 1);
ico->cp += ico_read_int32 (ico->fp, &data->image_size, 1);
ico->cp += ico_read_int32 (ico->fp, &data->x_res, 1);
ico->cp += ico_read_int32 (ico->fp, &data->y_res, 1);
ico->cp += ico_read_int32 (ico->fp, &data->used_clrs, 1);
ico->cp += ico_read_int32 (ico->fp, &data->important_clrs, 1);
D((" header size %i, "
"w %i, h %i, planes %i, size %i, bpp %i, used %i, imp %i.\n",
data->header_size, data->width, data->height,
data->planes, data->image_size, data->bpp,
data->used_clrs, data->important_clrs));
if (data->bpp <= 8)
{
if (data->used_clrs == 0)
data->used_clrs = (1 << data->bpp);
D((" allocating a %i-slot palette for %i bpp.\n",
data->used_clrs, data->bpp));
data->palette = g_new0 (guint32, data->used_clrs);
ico->cp += ico_read_int8 (ico->fp,
(guint8 *) data->palette, data->used_clrs * 4);
}
data->xor_map = ico_alloc_map (entry->width, entry->height,
data->bpp, &length);
ico->cp += ico_read_int8 (ico->fp, data->xor_map, length);
D((" length of xor_map: %i\n", length));
/* Read in and_map. It's padded out to 32 bits per line: */
data->and_map = ico_alloc_map (entry->width, entry->height, 1, &length);
ico->cp += ico_read_int8 (ico->fp, data->and_map, length);
D((" length of and_map: %i\n", length));
}
gint
ico_get_bit_from_data (const guint8 *data,
gint line_width,
gint bit)
{
gint line;
gint width32;
gint offset;
gint result;
/* width per line in multiples of 32 bits */
width32 = (line_width % 32 == 0 ? line_width/32 : line_width/32 + 1);
line = bit / line_width;
offset = bit % line_width;
result = (data[line * width32 * 4 + offset/8] & (1 << (7 - (offset % 8))));
return (result ? 1 : 0);
}
gint
ico_get_nibble_from_data (const guint8 *data,
gint line_width,
gint nibble)
{
gint line;
gint width32;
gint offset;
gint result;
/* width per line in multiples of 32 bits */
width32 = (line_width % 8 == 0 ? line_width/8 : line_width/8 + 1);
line = nibble / line_width;
offset = nibble % line_width;
result =
(data[line * width32 * 4 + offset/2] & (0x0F << (4 * (1 - offset % 2))));
if (offset % 2 == 0)
result = result >> 4;
return result;
}
gint
ico_get_byte_from_data (guint8 *data,
gint line_width,
gint byte)
{
gint line;
gint width32;
gint offset;
/* width per line in multiples of 32 bits */
width32 = (line_width % 4 == 0 ? line_width / 4 : line_width / 4 + 1);
line = byte / line_width;
offset = byte % line_width;
return data[line * width32 * 4 + offset];
}
static gint32
ico_load_layer (gint32 image,
MsIcon *ico,
gint i)
{
GimpDrawable *drawable;
GimpPixelRgn pixel_rgn;
MsIconData *idata;
MsIconEntry *ientry;
guint8 *xor_map;
guint8 *and_map;
guint32 *palette;
guint32 *dest_vec;
gint32 layer;
gchar buf[MAXLEN];
gint x, y, w, h;
idata = &ico->icon_data[i];
ientry = &ico->icon_dir[i];
xor_map = ico->icon_data[i].xor_map;
and_map = ico->icon_data[i].and_map;
palette = ico->icon_data[i].palette;
w = ico->icon_dir[i].width;
h = ico->icon_dir[i].height;
gimp_progress_update ((gdouble) i / (gdouble) ico->icon_count);
g_snprintf (buf, sizeof (buf), _("Icon #%i"), i + 1);
layer = gimp_layer_new (image, buf, w, h,
GIMP_RGBA_IMAGE, 100, GIMP_NORMAL_MODE);
gimp_image_add_layer (image, layer, i);
drawable = gimp_drawable_get (layer);
dest_vec = g_new (guint32, w * h);
switch (idata->bpp)
{
case 1:
for (y = 0; y < h; y++)
for (x = 0; x < w; x++)
{
guint32 color = palette[ico_get_bit_from_data (xor_map,
w, y * w + x)];
guint32 *dest = dest_vec + (h - 1 - y) * w + x;
R_VAL_GIMP (dest) = R_VAL (&color);
G_VAL_GIMP (dest) = G_VAL (&color);
B_VAL_GIMP (dest) = B_VAL (&color);
if (ico_get_bit_from_data (and_map, w, y * w + x))
A_VAL_GIMP (dest) = 0;
else
A_VAL_GIMP (dest) = 255;
}
break;
case 4:
for (y = 0; y < h; y++)
for (x = 0; x < w; x++)
{
guint32 color = palette[ico_get_nibble_from_data (xor_map,
w, y * w + x)];
guint32 *dest = dest_vec + (h - 1 - y) * w + x;
R_VAL_GIMP (dest) = R_VAL (&color);
G_VAL_GIMP (dest) = G_VAL (&color);
B_VAL_GIMP (dest) = B_VAL (&color);
if (ico_get_bit_from_data (and_map, w, y * w + x))
A_VAL_GIMP (dest) = 0;
else
A_VAL_GIMP (dest) = 255;
}
break;
case 8:
for (y = 0; y < h; y++)
for (x = 0; x < w; x++)
{
guint32 color = palette[ico_get_byte_from_data (xor_map,
w, y * w + x)];
guint32 *dest = dest_vec + (h - 1 - y) * w + x;
R_VAL_GIMP (dest) = R_VAL (&color);
G_VAL_GIMP (dest) = G_VAL (&color);
B_VAL_GIMP (dest) = B_VAL (&color);
if (ico_get_bit_from_data (and_map, w, y * w + x))
A_VAL_GIMP (dest) = 0;
else
A_VAL_GIMP (dest) = 255;
}
break;
default:
{
gint bytespp = idata->bpp/8;
for (y = 0; y < h; y++)
for (x = 0; x < w; x++)
{
guint32 *dest = dest_vec + (h - 1 - y) * w + x;
B_VAL_GIMP (dest) = xor_map[(y * w + x) * bytespp];
G_VAL_GIMP (dest) = xor_map[(y * w + x) * bytespp + 1];
R_VAL_GIMP (dest) = xor_map[(y * w + x) * bytespp + 2];
if (idata->bpp < 32)
{
if (ico_get_bit_from_data (and_map, w, y * w + x))
A_VAL_GIMP (dest) = 0;
else
A_VAL_GIMP (dest) = 255;
}
else
{
A_VAL_GIMP (dest) = xor_map[(y * w + x) * bytespp + 3];
}
}
}
}
gimp_pixel_rgn_init (&pixel_rgn, drawable,
0, 0, drawable->width, drawable->height,
TRUE, FALSE);
gimp_pixel_rgn_set_rect (&pixel_rgn, (const guchar *) dest_vec,
0, 0, drawable->width, drawable->height);
g_free (dest_vec);
gimp_drawable_detach (drawable);
return layer;
}
gint32
ico_load_image (const gchar *filename)
{
MsIcon ico;
gint32 image;
gint32 layer;
gint width = 0;
gint height = 0;
gint i;
gimp_progress_init_printf (_("Opening '%s'"),
gimp_filename_to_utf8 (filename));
if (! ico_init (filename, &ico))
return -1;
ico.cp += ico_read_int16 (ico.fp, &ico.icon_count, 1);
ico.icon_dir = g_new0 (MsIconEntry, ico.icon_count);
ico.icon_data = g_new0 (MsIconData, ico.icon_count);
D(("*** %s: Microsoft icon file, containing %i icon(s)\n",
ico.filename, ico.icon_count));
for (i = 0; i < ico.icon_count; i++)
ico_read_entry (&ico, &ico.icon_dir[i]);
/* Do a quick scan of the icons in the file to find the largest icon */
for (i = 0; i < ico.icon_count; i++)
{
if (ico.icon_dir[i].width > width)
width = ico.icon_dir[i].width;
if (ico.icon_dir[i].height > height)
height = ico.icon_dir[i].height;
}
if (width < 1 || height < 1)
return -1;
image = gimp_image_new (width, height, GIMP_RGB);
gimp_image_set_filename (image, ico.filename);
/* Scan icons again and set up a layer for each icon */
for (i = 0; i < ico.icon_count; i++)
ico_read_data (&ico, i);
layer = ico_load_layer (image, &ico, 0);
for (i = 1; i < ico.icon_count; i++)
ico_load_layer (image, &ico, i);
gimp_image_set_active_layer (image, layer);
D(("*** icon successfully loaded.\n\n"));
gimp_progress_update (1.0);
ico_cleanup (&ico);
return image;
}
gint32
ico_load_thumbnail_image (const gchar *filename,
gint *width,
gint *height)
{
MsIcon ico;
gint32 image;
gint32 layer;
gint w = 0;
gint h = 0;
gint match = 0;
gint i;
gimp_progress_init_printf (_("Opening thumbnail for '%s'"),
gimp_filename_to_utf8 (filename));
if (! ico_init (filename, &ico))
return -1;
ico.cp += ico_read_int16 (ico.fp, &ico.icon_count, 1);
ico.icon_dir = g_new0 (MsIconEntry, ico.icon_count);
ico.icon_data = g_new0 (MsIconData, ico.icon_count);
D(("*** %s: Microsoft icon file, containing %i icon(s)\n",
ico.filename, ico.icon_count));
for (i = 0; i < ico.icon_count; i++)
ico_read_entry (&ico, &ico.icon_dir[i]);
/* Do a quick scan of the icons in the file to find the best match */
for (i = 0; i < ico.icon_count; i++)
{
if ((ico.icon_dir[i].width > w && w < *width) ||
(ico.icon_dir[i].height > h && h < *height))
{
w = ico.icon_dir[i].width;
h = ico.icon_dir[i].height;
match = i;
}
}
if (w < 1 || h < 1)
return -1;
ico_read_data (&ico, match);
image = gimp_image_new (w, h, GIMP_RGB);
layer = ico_load_layer (image, &ico, match);
/* Do a quick scan of the icons in the file to find the largest icon */
for (i = 0, w = 0, h = 0; i < ico.icon_count; i++)
{
if (ico.icon_dir[i].width > w)
w = ico.icon_dir[i].width;
if (ico.icon_dir[i].height > h)
h = ico.icon_dir[i].height;
}
*width = w;
*height = h;
D(("*** thumbnail successfully loaded.\n\n"));
gimp_progress_update (1.0);
ico_cleanup (&ico);
return image;
}