icoload.c 15.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
/* 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>
26 27

#include <glib/gstdio.h>
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42

#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]

43 44 45 46
#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]
47 48


49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
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);
64 65 66


static gint
67 68 69
ico_read_int32 (FILE    *fp,
                guint32 *data,
                gint     count)
70 71 72 73 74 75
{
  gint i, total;

  total = count;
  if (count > 0)
    {
76
      ico_read_int8 (fp, (guint8 *) data, count * 4);
77
      for (i = 0; i < count; i++)
78
        data[i] = GUINT32_FROM_LE (data[i]);
79 80 81 82 83 84 85
    }

  return total * 4;
}


static gint
86 87 88
ico_read_int16 (FILE    *fp,
                guint16 *data,
                gint     count)
89 90 91 92 93 94
{
  gint i, total;

  total = count;
  if (count > 0)
    {
95
      ico_read_int8 (fp, (guint8 *) data, count * 2);
96
      for (i = 0; i < count; i++)
97
        data[i] = GUINT16_FROM_LE (data[i]);
98 99 100 101 102 103 104 105
    }

  return total * 2;
}


static gint
ico_read_int8 (FILE   *fp,
106 107
               guint8 *data,
               gint    count)
108 109 110 111 112 113 114 115 116 117
{
  gint total;
  gint bytes;

  total = count;
  while (count > 0)
    {
      bytes = fread ((gchar *) data, sizeof (gchar), count, fp);
      if (bytes <= 0) /* something bad happened */
        break;
118

119 120 121 122 123 124 125 126 127 128 129 130 131 132
      count -= bytes;
      data += bytes;
    }

  return total;
}


static gboolean
ico_init (const gchar *filename,
          MsIcon      *ico)
{
  memset (ico, 0, sizeof (MsIcon));

133
  if (! (ico->fp = g_fopen (filename, "r")))
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
    {
      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)
{
164 165
  g_return_if_fail (ico != NULL);
  g_return_if_fail (entry != NULL);
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192

  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;

193
  g_return_if_fail (ico != NULL);
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229

  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)
230
        data->used_clrs = (1 << data->bpp);
231 232

      D(("  allocating a %i-slot palette for %i bpp.\n",
233
         data->used_clrs, data->bpp));
234 235

      data->palette = g_new0 (guint32, data->used_clrs);
236 237
      ico->cp += ico_read_int8 (ico->fp,
                                (guint8 *) data->palette, data->used_clrs * 4);
238 239 240 241 242 243 244 245 246 247 248 249 250 251
    }

  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
252 253 254
ico_get_bit_from_data (const guint8 *data,
                       gint          line_width,
                       gint          bit)
255 256 257 258 259 260 261 262 263 264 265
{
  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;

266
  result = (data[line * width32 * 4 + offset/8] & (1 << (7 - (offset % 8))));
267 268 269 270 271 272

  return (result ? 1 : 0);
}


gint
273 274 275
ico_get_nibble_from_data (const guint8 *data,
                          gint          line_width,
                          gint          nibble)
276 277 278 279 280 281 282 283 284 285 286
{
  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;

Sven Neumann's avatar
Sven Neumann committed
287
  result =
288
    (data[line * width32 * 4 + offset/2] & (0x0F << (4 * (1 - offset % 2))));
289

290
  if (offset % 2 == 0)
291 292 293 294 295 296 297 298 299 300 301 302 303 304 305
    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 */
Sven Neumann's avatar
Sven Neumann committed
306
  width32 = (line_width % 4 == 0 ? line_width / 4 : line_width / 4 + 1);
307 308 309 310 311 312
  line = byte / line_width;
  offset = byte % line_width;

  return data[line * width32 * 4 + offset];
}

313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448
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;
}
449

450 451
gint32
ico_load_image (const gchar *filename)
452
{
453 454 455 456 457 458
  MsIcon  ico;
  gint32  image;
  gint32  layer;
  gint    width  = 0;
  gint    height = 0;
  gint    i;
459

460
  gimp_progress_init_printf (_("Opening '%s'"),
461
                             gimp_filename_to_utf8 (filename));
462

463 464
  if (! ico_init (filename, &ico))
    return -1;
465

466 467 468
  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);
469

470 471
  D(("*** %s: Microsoft icon file, containing %i icon(s)\n",
         ico.filename, ico.icon_count));
472

473 474
  for (i = 0; i < ico.icon_count; i++)
    ico_read_entry (&ico, &ico.icon_dir[i]);
475

476 477 478 479 480
  /* 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;
481

482 483 484
      if (ico.icon_dir[i].height > height)
        height = ico.icon_dir[i].height;
    }
485

486 487
  if (width < 1 || height < 1)
    return -1;
488

489 490
  image = gimp_image_new (width, height, GIMP_RGB);
  gimp_image_set_filename (image, ico.filename);
491

492
  /* Scan icons again and set up a layer for each icon */
493 494 495
  for (i = 0; i < ico.icon_count; i++)
    ico_read_data (&ico, i);

496 497 498
  layer = ico_load_layer (image, &ico, 0);
  for (i = 1; i < ico.icon_count; i++)
    ico_load_layer (image, &ico, i);
499

500
  gimp_image_set_active_layer (image, layer);
501

502
  D(("*** icon successfully loaded.\n\n"));
503

Sven Neumann's avatar
Sven Neumann committed
504
  gimp_progress_update (1.0);
505

Sven Neumann's avatar
Sven Neumann committed
506
  ico_cleanup (&ico);
507

508
  return image;
509
}
510 511 512 513 514 515

gint32
ico_load_thumbnail_image (const gchar *filename,
                          gint        *width,
                          gint        *height)
{
516 517 518 519 520 521 522 523
  MsIcon  ico;
  gint32  image;
  gint32  layer;
  gint    w     = 0;
  gint    h     = 0;
  gint    match = 0;
  gint    i;

524
  gimp_progress_init_printf (_("Opening thumbnail for '%s'"),
525
                             gimp_filename_to_utf8 (filename));
526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580

  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;
581
}