bmp-save.c 32.3 KB
Newer Older
1 2 3
/* bmpwrite.c   Writes Bitmap files. Even RLE encoded ones.      */
/*              (Windows (TM) doesn't read all of those, but who */
/*              cares? ;-)                                       */
4 5 6 7
/*              I changed a few things over the time, so perhaps */
/*              it dos now, but now there's no Windows left on   */
/*              my computer...                                   */

8
/* Alexander.Schulz@stud.uni-karlsruhe.de                        */
Elliot Lee's avatar
Elliot Lee committed
9

10
/*
11
 * GIMP - The GNU Image Manipulation Program
12 13
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
14
 * This program is free software: you can redistribute it and/or modify
15
 * it under the terms of the GNU General Public License as published by
16
 * the Free Software Foundation; either version 3 of the License, or
17 18 19 20 21 22 23 24
 * (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
25
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
26 27 28
 * ----------------------------------------------------------------------------
 */

Tor Lillqvist's avatar
Tor Lillqvist committed
29
#include "config.h"
Elliot Lee's avatar
Elliot Lee committed
30

31
#include <errno.h>
Elliot Lee's avatar
Elliot Lee committed
32
#include <string.h>
33 34

#include <glib/gstdio.h>
35

Elliot Lee's avatar
Elliot Lee committed
36
#include <libgimp/gimp.h>
37 38
#include <libgimp/gimpui.h>

Elliot Lee's avatar
Elliot Lee committed
39
#include "bmp.h"
40
#include "bmp-save.h"
41

42
#include "libgimp/stdplugins-intl.h"
Elliot Lee's avatar
Elliot Lee committed
43

44

45 46
typedef enum
{
David Odin's avatar
David Odin committed
47 48 49 50 51 52
  RGB_565,
  RGBA_5551,
  RGB_555,
  RGB_888,
  RGBA_8888,
  RGBX_8888
53
} RGBMode;
54

Elliot Lee's avatar
Elliot Lee committed
55

David Odin's avatar
David Odin committed
56 57 58 59
static  void      write_image     (FILE   *f,
                                   guchar *src,
                                   gint    width,
                                   gint    height,
60
                                   gint    use_run_length_encoding,
David Odin's avatar
David Odin committed
61 62 63 64
                                   gint    channels,
                                   gint    bpp,
                                   gint    spzeile,
                                   gint    MapSize,
65 66 67
                                   RGBMode rgb_format,
                                   gint    mask_info_size,
                                   gint    color_space_size);
68 69 70

static  gboolean  save_dialog     (gint    channels);

Elliot Lee's avatar
Elliot Lee committed
71

72 73 74 75 76 77 78 79 80
static struct
{
  RGBMode rgb_format;
  gint    use_run_length_encoding;

  /* Whether or not to write BITMAPV5HEADER color space data */
  gint    dont_write_color_space_data;
} BMPSaveData;

81

82
static void
David Odin's avatar
David Odin committed
83 84 85 86 87
write_color_map (FILE *f,
                 gint  red[MAXCOLORS],
                 gint  green[MAXCOLORS],
                 gint  blue[MAXCOLORS],
                 gint  size)
88 89
{
  gchar trgb[4];
David Odin's avatar
David Odin committed
90
  gint  i;
91 92 93 94 95 96 97 98 99 100 101 102

  size /= 4;
  trgb[3] = 0;
  for (i = 0; i < size; i++)
    {
      trgb[0] = (guchar) blue[i];
      trgb[1] = (guchar) green[i];
      trgb[2] = (guchar) red[i];
      Write (f, trgb, 4);
    }
}

103 104 105 106
static gboolean
warning_dialog (const gchar *primary,
                const gchar *secondary)
{
David Odin's avatar
David Odin committed
107
  GtkWidget *dialog;
108 109
  gboolean   ok;

David Odin's avatar
David Odin committed
110 111
  dialog = gtk_message_dialog_new (NULL, 0,
                                   GTK_MESSAGE_WARNING, GTK_BUTTONS_OK_CANCEL,
Sven Neumann's avatar
Sven Neumann committed
112
                                   "%s", primary);
113

David Odin's avatar
David Odin committed
114
  gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
Sven Neumann's avatar
Sven Neumann committed
115
                                            "%s", secondary);
116

David Odin's avatar
David Odin committed
117
  gimp_window_set_transient (GTK_WINDOW (dialog));
118

David Odin's avatar
David Odin committed
119
  ok = (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK);
120

David Odin's avatar
David Odin committed
121
  gtk_widget_destroy (dialog);
122 123 124 125

  return ok;
}

126
GimpPDBStatusType
127 128 129 130 131
save_image (const gchar  *filename,
            gint32        image,
            gint32        drawable_ID,
            GimpRunMode   run_mode,
            GError      **error)
Elliot Lee's avatar
Elliot Lee committed
132
{
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
  FILE           *outfile;
  BitmapFileHead  bitmap_file_head;
  BitmapHead      bitmap_head;
  gint            Red[MAXCOLORS];
  gint            Green[MAXCOLORS];
  gint            Blue[MAXCOLORS];
  guchar         *cmap;
  gint            rows, cols, Spcols, channels, MapSize, SpZeile;
  glong           BitsPerPixel;
  gint            colors;
  guchar         *pixels;
  GeglBuffer     *buffer;
  const Babl     *format;
  GimpImageType    drawable_type;
  gint            drawable_width;
  gint            drawable_height;
  gint            i;
  gint            mask_info_size;
  gint            color_space_size;
  guint32         Mask[4];
Elliot Lee's avatar
Elliot Lee committed
153

154
  buffer = gimp_drawable_get_buffer (drawable_ID);
155

156 157 158
  drawable_type   = gimp_drawable_type   (drawable_ID);
  drawable_width  = gimp_drawable_width  (drawable_ID);
  drawable_height = gimp_drawable_height (drawable_ID);
159

Elliot Lee's avatar
Elliot Lee committed
160 161
  switch (drawable_type)
    {
162
    case GIMP_RGBA_IMAGE:
163
      format       = babl_format ("R'G'B'A u8");
164 165 166 167
      colors       = 0;
      BitsPerPixel = 32;
      MapSize      = 0;
      channels     = 4;
168
      BMPSaveData.rgb_format = RGBA_8888;
169 170
      break;

171
    case GIMP_RGB_IMAGE:
172
      format       = babl_format ("R'G'B' u8");
173
      colors       = 0;
Elliot Lee's avatar
Elliot Lee committed
174
      BitsPerPixel = 24;
175 176
      MapSize      = 0;
      channels     = 3;
177
      BMPSaveData.rgb_format = RGB_888;
Elliot Lee's avatar
Elliot Lee committed
178
      break;
179

180
    case GIMP_GRAYA_IMAGE:
181 182 183 184
      if (run_mode == GIMP_RUN_INTERACTIVE &&
          ! warning_dialog (_("Cannot export indexed image with "
                              "transparency in BMP file format."),
                            _("Alpha channel will be ignored.")))
185
        return GIMP_PDB_CANCEL;
186

187 188
     /* fallthrough */

189
    case GIMP_GRAY_IMAGE:
190 191 192
      colors       = 256;
      BitsPerPixel = 8;
      MapSize      = 1024;
193 194

      if (drawable_type == GIMP_GRAYA_IMAGE)
195 196 197 198
        {
          format   = babl_format ("Y'A u8");
          channels = 2;
        }
199
      else
200 201 202 203
        {
          format   = babl_format ("Y' u8");
          channels = 1;
        }
204

Elliot Lee's avatar
Elliot Lee committed
205
      for (i = 0; i < colors; i++)
206 207 208 209 210
        {
          Red[i]   = i;
          Green[i] = i;
          Blue[i]  = i;
        }
Elliot Lee's avatar
Elliot Lee committed
211
      break;
212

213
    case GIMP_INDEXEDA_IMAGE:
214 215 216 217
      if (run_mode == GIMP_RUN_INTERACTIVE &&
          ! warning_dialog (_("Cannot export indexed image with "
                              "transparency in BMP file format."),
                            _("Alpha channel will be ignored.")))
218
        return GIMP_PDB_CANCEL;
219

220 221
     /* fallthrough */

222
    case GIMP_INDEXED_IMAGE:
223
      format   = gimp_drawable_get_format (drawable_ID);
224
      cmap     = gimp_image_get_colormap (image, &colors);
225
      MapSize  = 4 * colors;
226 227 228 229 230

      if (drawable_type == GIMP_INDEXEDA_IMAGE)
        channels = 2;
      else
        channels = 1;
231 232

      if (colors > 16)
233
        BitsPerPixel = 8;
234
      else if (colors > 2)
235
        BitsPerPixel = 4;
236
      else
237
        BitsPerPixel = 1;
238

Elliot Lee's avatar
Elliot Lee committed
239
      for (i = 0; i < colors; i++)
240 241 242 243 244
        {
          Red[i]   = *cmap++;
          Green[i] = *cmap++;
          Blue[i]  = *cmap++;
        }
Elliot Lee's avatar
Elliot Lee committed
245
      break;
246 247 248

    default:
      g_assert_not_reached ();
Elliot Lee's avatar
Elliot Lee committed
249 250
    }

251
  BMPSaveData.use_run_length_encoding = 0;
252
  BMPSaveData.dont_write_color_space_data = 0;
253 254
  mask_info_size = 0;

255
  if (run_mode != GIMP_RUN_NONINTERACTIVE)
256 257 258 259
    {
      gimp_get_data (SAVE_PROC, &BMPSaveData);
    }

260 261 262
  if (run_mode == GIMP_RUN_INTERACTIVE &&
      (BitsPerPixel == 8 ||
       BitsPerPixel == 4))
263
    {
264
      if (! save_dialog (1))
265
        return GIMP_PDB_CANCEL;
266
    }
267 268
  else if (BitsPerPixel == 24 ||
           BitsPerPixel == 32)
269
    {
270 271 272 273 274
      if (run_mode == GIMP_RUN_INTERACTIVE)
        {
          if (! save_dialog (channels))
            return GIMP_PDB_CANCEL;
        }
275

276
      /* mask_info_size is only set to non-zero for 16- and 32-bpp */
277
      switch (BMPSaveData.rgb_format)
278 279 280 281 282
        {
        case RGB_888:
          BitsPerPixel = 24;
          break;
        case RGBA_8888:
283
          BitsPerPixel   = 32;
284
          mask_info_size = 16;
285 286
          break;
        case RGBX_8888:
287
          BitsPerPixel   = 32;
288 289 290
          mask_info_size = 16;
          break;
        case RGB_565:
291
          BitsPerPixel   = 16;
292 293 294
          mask_info_size = 16;
          break;
        case RGBA_5551:
295
          BitsPerPixel   = 16;
296 297 298
          mask_info_size = 16;
          break;
        case RGB_555:
299
          BitsPerPixel   = 16;
300
          mask_info_size = 16;
301 302 303 304 305
          break;
        default:
          g_return_val_if_reached (GIMP_PDB_EXECUTION_ERROR);
        }
    }
306

307 308
  gimp_set_data (SAVE_PROC, &BMPSaveData, sizeof (BMPSaveData));

309
  /* Let's begin the progress */
310
  gimp_progress_init_printf (_("Exporting '%s'"),
311 312
                             gimp_filename_to_utf8 (filename));

313
  /* Let's take some file */
314
  outfile = g_fopen (filename, "wb");
Elliot Lee's avatar
Elliot Lee committed
315 316
  if (!outfile)
    {
317 318 319
      g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
                   _("Could not open '%s' for writing: %s"),
                   gimp_filename_to_utf8 (filename), g_strerror (errno));
320
      return GIMP_PDB_EXECUTION_ERROR;
Elliot Lee's avatar
Elliot Lee committed
321
    }
322

Elliot Lee's avatar
Elliot Lee committed
323
  /* fetch the image */
324 325 326 327 328 329 330 331
  pixels = g_new (guchar, drawable_width * drawable_height * channels);

  gegl_buffer_get (buffer, GEGL_RECTANGLE (0, 0,
                                           drawable_width, drawable_height), 1.0,
                   format, pixels,
                   GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);

  g_object_unref (buffer);
332

Elliot Lee's avatar
Elliot Lee committed
333
  /* Now, we need some further information ... */
334 335
  cols = drawable_width;
  rows = drawable_height;
336

Elliot Lee's avatar
Elliot Lee committed
337
  /* ... that we write to our headers. */
David Odin's avatar
David Odin committed
338
  if ((BitsPerPixel <= 8) && (cols % (8 / BitsPerPixel)))
339 340 341 342 343 344 345 346 347
    Spcols = (((cols / (8 / BitsPerPixel)) + 1) * (8 / BitsPerPixel));
  else
    Spcols = cols;

  if ((((Spcols * BitsPerPixel) / 8) % 4) == 0)
    SpZeile = ((Spcols * BitsPerPixel) / 8);
  else
    SpZeile = ((gint) (((Spcols * BitsPerPixel) / 8) / 4) + 1) * 4;

348 349
  if (! BMPSaveData.dont_write_color_space_data)
    color_space_size = 68;
350 351
  else
    color_space_size = 0;
352

353
  bitmap_file_head.bfSize    = (0x36 + MapSize + (rows * SpZeile) +
354
                                mask_info_size + color_space_size);
355 356 357
  bitmap_file_head.zzHotX    =  0;
  bitmap_file_head.zzHotY    =  0;
  bitmap_file_head.bfOffs    = (0x36 + MapSize +
358
                                mask_info_size + color_space_size);
359
  bitmap_file_head.biSize    =  40 + mask_info_size + color_space_size;
360

361 362 363 364
  bitmap_head.biWidth  = cols;
  bitmap_head.biHeight = rows;
  bitmap_head.biPlanes = 1;
  bitmap_head.biBitCnt = BitsPerPixel;
365

366
  if (BMPSaveData.use_run_length_encoding == 0)
367 368 369 370 371 372
    {
      if (mask_info_size > 0)
        bitmap_head.biCompr = 3; /* BI_BITFIELDS */
      else
        bitmap_head.biCompr = 0; /* BI_RGB */
    }
373
  else if (BitsPerPixel == 8)
374 375 376
    {
      bitmap_head.biCompr = 1;
    }
377
  else if (BitsPerPixel == 4)
378 379 380
    {
      bitmap_head.biCompr = 2;
    }
381
  else
382 383 384
    {
      bitmap_head.biCompr = 0;
    }
385

386
  bitmap_head.biSizeIm = SpZeile * rows;
387

388
  {
389 390
    gdouble xresolution;
    gdouble yresolution;
391 392
    gimp_image_get_resolution (image, &xresolution, &yresolution);

393
    if (xresolution > GIMP_MIN_RESOLUTION &&
394
        yresolution > GIMP_MIN_RESOLUTION)
395
      {
396 397 398 399 400 401 402 403
        /*
         * xresolution and yresolution are in dots per inch.
         * the BMP spec says that biXPels and biYPels are in
         * pixels per meter as long ints (actually, "DWORDS"),
         * so...
         *    n dots    inch     100 cm   m dots
         *    ------ * ------- * ------ = ------
         *     inch    2.54 cm     m       inch
404 405
         *
         * We add 0.5 for proper rounding.
406
         */
407 408
        bitmap_head.biXPels = (long int) (xresolution * 100.0 / 2.54 + 0.5);
        bitmap_head.biYPels = (long int) (yresolution * 100.0 / 2.54 + 0.5);
409 410
      }
  }
411

412
  if (BitsPerPixel <= 8)
413
    bitmap_head.biClrUsed = colors;
414
  else
415
    bitmap_head.biClrUsed = 0;
416

417
  bitmap_head.biClrImp = bitmap_head.biClrUsed;
418

Elliot Lee's avatar
Elliot Lee committed
419
#ifdef DEBUG
420 421 422 423 424 425 426
  printf ("\nSize: %u, Colors: %u, Bits: %u, Width: %u, Height: %u, Comp: %u, Zeile: %u\n",
          (int)bitmap_file_head.bfSize,
          (int)bitmap_head.biClrUsed,
          bitmap_head.biBitCnt,
          (int)bitmap_head.biWidth,
          (int)bitmap_head.biHeight,
          (int)bitmap_head.biCompr,SpZeile);
Elliot Lee's avatar
Elliot Lee committed
427
#endif
428

Elliot Lee's avatar
Elliot Lee committed
429
  /* And now write the header and the colormap (if any) to disk */
430 431 432

  Write (outfile, "BM", 2);

433 434 435 436 437
  bitmap_file_head.bfSize = GUINT32_TO_LE (bitmap_file_head.bfSize);
  bitmap_file_head.zzHotX = GUINT16_TO_LE (bitmap_file_head.zzHotX);
  bitmap_file_head.zzHotY = GUINT16_TO_LE (bitmap_file_head.zzHotY);
  bitmap_file_head.bfOffs = GUINT32_TO_LE (bitmap_file_head.bfOffs);
  bitmap_file_head.biSize = GUINT32_TO_LE (bitmap_file_head.biSize);
438

439
  Write (outfile, &bitmap_file_head.bfSize, 16);
440

441 442 443 444 445 446 447 448 449 450
  bitmap_head.biWidth   = GINT32_TO_LE  (bitmap_head.biWidth);
  bitmap_head.biHeight  = GINT32_TO_LE  (bitmap_head.biHeight);
  bitmap_head.biPlanes  = GUINT16_TO_LE (bitmap_head.biPlanes);
  bitmap_head.biBitCnt  = GUINT16_TO_LE (bitmap_head.biBitCnt);
  bitmap_head.biCompr   = GUINT32_TO_LE (bitmap_head.biCompr);
  bitmap_head.biSizeIm  = GUINT32_TO_LE (bitmap_head.biSizeIm);
  bitmap_head.biXPels   = GUINT32_TO_LE (bitmap_head.biXPels);
  bitmap_head.biYPels   = GUINT32_TO_LE (bitmap_head.biYPels);
  bitmap_head.biClrUsed = GUINT32_TO_LE (bitmap_head.biClrUsed);
  bitmap_head.biClrImp  = GUINT32_TO_LE (bitmap_head.biClrImp);
451

452
  Write (outfile, &bitmap_head, 36);
453

454
  if (mask_info_size > 0)
455
    {
456
      switch (BMPSaveData.rgb_format)
David Odin's avatar
David Odin committed
457 458 459 460
        {
        default:
        case RGB_888:
        case RGBX_8888:
461 462 463
          Mask[0] = 0x00ff0000;
          Mask[1] = 0x0000ff00;
          Mask[2] = 0x000000ff;
David Odin's avatar
David Odin committed
464 465
          Mask[3] = 0x00000000;
          break;
466

David Odin's avatar
David Odin committed
467
        case RGBA_8888:
468 469 470 471
          Mask[0] = 0x00ff0000;
          Mask[1] = 0x0000ff00;
          Mask[2] = 0x000000ff;
          Mask[3] = 0xff000000;
David Odin's avatar
David Odin committed
472
          break;
473

David Odin's avatar
David Odin committed
474 475 476 477 478 479
        case RGB_565:
          Mask[0] = 0xf800;
          Mask[1] = 0x7e0;
          Mask[2] = 0x1f;
          Mask[3] = 0x0;
          break;
480

David Odin's avatar
David Odin committed
481 482 483 484 485 486
        case RGBA_5551:
          Mask[0] = 0x7c00;
          Mask[1] = 0x3e0;
          Mask[2] = 0x1f;
          Mask[3] = 0x8000;
          break;
487

David Odin's avatar
David Odin committed
488 489 490 491 492 493 494
        case RGB_555:
          Mask[0] = 0x7c00;
          Mask[1] = 0x3e0;
          Mask[2] = 0x1f;
          Mask[3] = 0x0;
          break;
        }
495

496 497 498 499 500 501
      Mask[0] = GUINT32_TO_LE (Mask[0]);
      Mask[1] = GUINT32_TO_LE (Mask[1]);
      Mask[2] = GUINT32_TO_LE (Mask[2]);
      Mask[3] = GUINT32_TO_LE (Mask[3]);

      Write (outfile, &Mask, mask_info_size);
502 503
    }

504 505
  if (! BMPSaveData.dont_write_color_space_data)
    {
506 507
      guint32 buf[0x11];

508
      /* Write V5 color space fields */
509

510
      /* bV5CSType = LCS_sRGB */
511
      buf[0x00] = GUINT32_TO_LE (0x73524742);
512

513
      /* bV5Endpoints is set to 0 (ignored) */
514 515
      for (i = 0; i < 0x09; i++)
        buf[i + 1] = 0x00;
516

517
      /* bV5GammaRed is set to 0 (ignored) */
518
      buf[0x0a] = GUINT32_TO_LE (0x0);
519

520
      /* bV5GammaGreen is set to 0 (ignored) */
521
      buf[0x0b] = GUINT32_TO_LE (0x0);
522

523
      /* bV5GammaBlue is set to 0 (ignored) */
524
      buf[0x0c] = GUINT32_TO_LE (0x0);
525

526
      /* bV5Intent = LCS_GM_GRAPHICS */
527
      buf[0x0d] = GUINT32_TO_LE (0x00000002);
528

529
      /* bV5ProfileData is set to 0 (ignored) */
530
      buf[0x0e] = GUINT32_TO_LE (0x0);
531

532
      /* bV5ProfileSize is set to 0 (ignored) */
533
      buf[0x0f] = GUINT32_TO_LE (0x0);
534

535
      /* bV5Reserved = 0 */
536
      buf[0x10] = GUINT32_TO_LE (0x0);
537

538
      Write (outfile, buf, color_space_size);
539
    }
540

541 542
  write_color_map (outfile, Red, Green, Blue, MapSize);

Elliot Lee's avatar
Elliot Lee committed
543
  /* After that is done, we write the image ... */
544

David Odin's avatar
David Odin committed
545 546
  write_image (outfile,
               pixels, cols, rows,
547 548
               BMPSaveData.use_run_length_encoding,
               channels, BitsPerPixel, SpZeile,
549 550
               MapSize, BMPSaveData.rgb_format,
               mask_info_size, color_space_size);
Elliot Lee's avatar
Elliot Lee committed
551 552

  /* ... and exit normally */
553 554 555 556

  fclose (outfile);
  g_free (pixels);

557
  return GIMP_PDB_SUCCESS;
Elliot Lee's avatar
Elliot Lee committed
558 559
}

560 561 562 563 564
static inline void
Make565 (guchar  r,
         guchar  g,
         guchar  b,
         guchar *buf)
565
{
566 567 568 569 570 571 572 573
  gint p;

  p = ((((gint) (r / 255.0 * 31.0 + 0.5)) << 11) |
       (((gint) (g / 255.0 * 63.0 + 0.5)) <<  5) |
       (((gint) (b / 255.0 * 31.0 + 0.5))));

  buf[0] = (guchar) (p & 0xff);
  buf[1] = (guchar) (p >> 8);
574 575
}

576 577 578 579 580 581
static inline void
Make5551 (guchar  r,
          guchar  g,
          guchar  b,
          guchar  a,
          guchar *buf)
582
{
583 584 585 586 587 588 589 590 591
  gint p;

  p = ((((gint) (r / 255.0 * 31.0 + 0.5)) << 10) |
       (((gint) (g / 255.0 * 31.0 + 0.5)) <<  5) |
       (((gint) (b / 255.0 * 31.0 + 0.5)))       |
       (((gint) (a / 255.0 +  0.5)        << 15)));

  buf[0] = (guchar) (p & 0xff);
  buf[1] = (guchar) (p >> 8);
592 593
}

594
static void
David Odin's avatar
David Odin committed
595 596 597 598
write_image (FILE   *f,
             guchar *src,
             gint    width,
             gint    height,
599
             gint    use_run_length_encoding,
David Odin's avatar
David Odin committed
600 601 602 603
             gint    channels,
             gint    bpp,
             gint    spzeile,
             gint    MapSize,
604 605 606
             RGBMode rgb_format,
             gint    mask_info_size,
             gint    color_space_size)
Elliot Lee's avatar
Elliot Lee committed
607
{
David Odin's avatar
David Odin committed
608
  guchar  buf[16] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0 };
609
  guint32 uint32buf;
610
  guchar *temp, v;
David Odin's avatar
David Odin committed
611 612 613 614
  guchar *row, *ketten;
  gint    xpos, ypos, i, j, rowstride, length, thiswidth;
  gint    breite, k;
  guchar  n, r, g, b, a;
615 616
  gint    cur_progress;
  gint    max_progress;
617 618 619

  xpos = 0;
  rowstride = width * channels;
620

621 622 623
  cur_progress = 0;
  max_progress = height;

624
  /* We'll begin with the 16/24/32 bit Bitmaps, they are easy :-) */
625

626
  if (bpp > 8)
627 628 629 630 631 632
    {
      for (ypos = height - 1; ypos >= 0; ypos--)  /* for each row   */
        {
          for (i = 0; i < width; i++)  /* for each pixel */
            {
              temp = src + (ypos * rowstride) + (xpos * channels);
633
              switch (rgb_format)
David Odin's avatar
David Odin committed
634 635 636 637 638 639 640
                {
                default:
                case RGB_888:
                  buf[2] = *temp++;
                  buf[1] = *temp++;
                  buf[0] = *temp++;
                  xpos++;
641
                  if (channels > 3 && (guchar) *temp == 0)
David Odin's avatar
David Odin committed
642 643 644 645 646 647
                    buf[0] = buf[1] = buf[2] = 0xff;
                  Write (f, buf, 3);
                  break;
                case RGBX_8888:
                  buf[2] = *temp++;
                  buf[1] = *temp++;
648 649
                  buf[0] = *temp++;
                  buf[3] = 0;
David Odin's avatar
David Odin committed
650
                  xpos++;
651
                  if (channels > 3 && (guchar) *temp == 0)
David Odin's avatar
David Odin committed
652 653 654 655 656 657
                    buf[0] = buf[1] = buf[2] = 0xff;
                  Write (f, buf, 4);
                  break;
                case RGBA_8888:
                  buf[2] = *temp++;
                  buf[1] = *temp++;
658 659
                  buf[0] = *temp++;
                  buf[3] = *temp;
David Odin's avatar
David Odin committed
660 661 662 663 664 665 666
                  xpos++;
                  Write (f, buf, 4);
                  break;
                case RGB_565:
                  r = *temp++;
                  g = *temp++;
                  b = *temp++;
667
                  if (channels > 3 && (guchar) *temp == 0)
David Odin's avatar
David Odin committed
668 669 670 671 672 673 674 675 676
                    r = g = b = 0xff;
                  Make565 (r, g, b, buf);
                  xpos++;
                  Write (f, buf, 2);
                  break;
                case RGB_555:
                  r = *temp++;
                  g = *temp++;
                  b = *temp++;
677
                  if (channels > 3 && (guchar) *temp == 0)
David Odin's avatar
David Odin committed
678 679 680 681 682 683 684 685 686 687 688 689 690 691 692
                    r = g = b = 0xff;
                  Make5551 (r, g, b, 0x0, buf);
                  xpos++;
                  Write (f, buf, 2);
                  break;
                case RGBA_5551:
                  r = *temp++;
                  g = *temp++;
                  b = *temp++;
                  a = *temp;
                  Make5551 (r, g, b, a, buf);
                  xpos++;
                  Write (f, buf, 2);
                  break;
                }
693 694
            }

695
          Write (f, &buf[4], spzeile - (width * (bpp/8)));
696 697 698 699 700 701 702 703

          cur_progress++;
          if ((cur_progress % 5) == 0)
            gimp_progress_update ((gdouble) cur_progress /
                                  (gdouble) max_progress);

          xpos = 0;
        }
704 705 706
    }
  else
    {
707
      switch (use_run_length_encoding)  /* now it gets more difficult */
708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724
        {               /* uncompressed 1,4 and 8 bit */
        case 0:
          {
            thiswidth = (width / (8 / bpp));
            if (width % (8 / bpp))
              thiswidth++;

            for (ypos = height - 1; ypos >= 0; ypos--) /* for each row */
              {
                for (xpos = 0; xpos < width;)  /* for each _byte_ */
                  {
                    v = 0;
                    for (i = 1;
                         (i <= (8 / bpp)) && (xpos < width);
                         i++, xpos++)  /* for each pixel */
                      {
                        temp = src + (ypos * rowstride) + (xpos * channels);
725
                        if (channels > 1 && *(temp+1) == 0) *temp = 0x0;
726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741
                        v=v | ((guchar) *temp << (8 - (i * bpp)));
                      }
                    Write (f, &v, 1);
                  }
                Write (f, &buf[3], spzeile - thiswidth);
                xpos = 0;

                cur_progress++;
                if ((cur_progress % 5) == 0)
                  gimp_progress_update ((gdouble) cur_progress /
                                        (gdouble) max_progress);
              }
            break;
          }
        default:
          {              /* Save RLE encoded file, quite difficult */
David Odin's avatar
David Odin committed
742
            length = 0;
743 744 745 746
            buf[12] = 0;
            buf[13] = 1;
            buf[14] = 0;
            buf[15] = 0;
David Odin's avatar
David Odin committed
747 748
            row = g_new (guchar, width / (8 / bpp) + 10);
            ketten = g_new (guchar, width / (8 / bpp) + 10);
749 750 751 752 753 754 755 756 757 758 759 760 761 762
            for (ypos = height - 1; ypos >= 0; ypos--)
              { /* each row separately */
                j = 0;
                /* first copy the pixels to a buffer,
                 * making one byte from two 4bit pixels
                 */
                for (xpos = 0; xpos < width;)
                  {
                    v = 0;
                    for (i = 1;
                         (i <= (8 / bpp)) && (xpos < width);
                         i++, xpos++)
                      { /* for each pixel */
                        temp = src + (ypos * rowstride) + (xpos * channels);
763
                        if (channels > 1 && *(temp+1) == 0) *temp = 0x0;
764 765
                        v = v | ((guchar) * temp << (8 - (i * bpp)));
                      }
David Odin's avatar
David Odin committed
766
                    row[j++] = v;
767 768 769 770
                  }
                breite = width / (8 / bpp);
                if (width % (8 / bpp))
                  breite++;
771

772
                /* then check for strings of equal bytes */
David Odin's avatar
David Odin committed
773
                for (i = 0; i < breite; i += j)
774 775 776 777
                  {
                    j = 0;
                    while ((i + j < breite) &&
                           (j < (255 / (8 / bpp))) &&
David Odin's avatar
David Odin committed
778
                           (row[i + j] == row[i]))
779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808
                      j++;

                    ketten[i] = j;
                  }

                /* then write the strings and the other pixels to the file */
                for (i = 0; i < breite;)
                  {
                    if (ketten[i] < 3)
                      /* strings of different pixels ... */
                      {
                        j = 0;
                        while ((i + j < breite) &&
                               (j < (255 / (8 / bpp))) &&
                               (ketten[i + j] < 3))
                          j += ketten[i + j];

                        /* this can only happen if j jumps over
                         * the end with a 2 in ketten[i+j]
                         */
                        if (j > (255 / (8 / bpp)))
                          j -= 2;
                        /* 00 01 and 00 02 are reserved */
                        if (j > 2)
                          {
                            Write (f, &buf[12], 1);
                            n = j * (8 / bpp);
                            if (n + i * (8 / bpp) > width)
                              n--;
                            Write (f, &n, 1);
David Odin's avatar
David Odin committed
809 810 811
                            length += 2;
                            Write (f, &row[i], j);
                            length += j;
812 813 814
                            if ((j) % 2)
                              {
                                Write (f, &buf[12], 1);
David Odin's avatar
David Odin committed
815
                                length++;
816 817 818 819 820 821 822 823 824 825
                              }
                          }
                        else
                          {
                            for (k = i; k < i + j; k++)
                              {
                                n = (8 / bpp);
                                if (n + i * (8 / bpp) > width)
                                  n--;
                                Write (f, &n, 1);
David Odin's avatar
David Odin committed
826
                                Write (f, &row[k], 1);
827
                                /*printf("%i.#|",n); */
David Odin's avatar
David Odin committed
828
                                length += 2;
829 830 831 832 833 834 835 836 837 838 839
                              }
                          }
                        i += j;
                      }
                    else
                      /* strings of equal pixels */
                      {
                        n = ketten[i] * (8 / bpp);
                        if (n + i * (8 / bpp) > width)
                          n--;
                        Write (f, &n, 1);
David Odin's avatar
David Odin committed
840
                        Write (f, &row[i], 1);
841
                        i += ketten[i];
David Odin's avatar
David Odin committed
842
                        length += 2;
843 844
                      }
                  }
845

846
                Write (f, &buf[14], 2);          /* End of row */
David Odin's avatar
David Odin committed
847
                length += 2;
848 849 850 851 852 853

                cur_progress++;
                if ((cur_progress % 5) == 0)
                  gimp_progress_update ((gdouble) cur_progress /
                                        (gdouble) max_progress);
              }
854

855 856 857 858
            fseek (f, -2, SEEK_CUR);     /* Overwrite last End of row ... */
            Write (f, &buf[12], 2);      /* ... with End of file */

            fseek (f, 0x22, SEEK_SET);            /* Write length of image */
859 860 861
            uint32buf = GUINT32_TO_LE (length);
            Write (f, &uint32buf, 4);

862
            fseek (f, 0x02, SEEK_SET);            /* Write length of file */
863
            length += (0x36 + MapSize + mask_info_size + color_space_size);
864 865 866
            uint32buf = GUINT32_TO_LE (length);
            Write (f, &uint32buf, 4);

867
            g_free (ketten);
David Odin's avatar
David Odin committed
868
            g_free (row);
869 870 871
            break;
          }
        }
Elliot Lee's avatar
Elliot Lee committed
872
    }
873

874
  gimp_progress_update (1.0);
Elliot Lee's avatar
Elliot Lee committed
875 876
}

877
static void
878 879
format_callback (GtkToggleButton *toggle,
                 gpointer         data)
880
{
881 882
  if (gtk_toggle_button_get_active (toggle))
    BMPSaveData.rgb_format = GPOINTER_TO_INT (data);
883 884 885
}

static gboolean
886
save_dialog (gint channels)
887
{
David Odin's avatar
David Odin committed
888
  GtkWidget *dialog;
889
  GtkWidget *toggle;
David Odin's avatar
David Odin committed
890 891
  GtkWidget *vbox_main;
  GtkWidget *vbox;
892 893
  GtkWidget *vbox2;
  GtkWidget *expander;
894 895 896 897
  GtkWidget *frame;
  GSList    *group;
  gboolean   run;

898
  /* Dialog init */
899
  dialog = gimp_export_dialog_new ("BMP", PLUG_IN_BINARY, SAVE_PROC);
900

901 902
  gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);

903
  vbox_main = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
904
  gtk_container_set_border_width (GTK_CONTAINER (vbox_main), 12);
905 906
  gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)),
                      vbox_main, TRUE, TRUE, 0);
907 908
  gtk_widget_show (vbox_main);

909
  /* Run-Length Encoded */
910 911 912
  toggle = gtk_check_button_new_with_mnemonic (_("_Run-Length Encoded"));
  gtk_box_pack_start (GTK_BOX (vbox_main), toggle, FALSE, FALSE, 0);
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle),
913
                                BMPSaveData.use_run_length_encoding);
914 915 916 917 918 919
  gtk_widget_show (toggle);
  if (channels > 1)
    gtk_widget_set_sensitive (toggle, FALSE);

  g_signal_connect (toggle, "toggled",
                    G_CALLBACK (gimp_toggle_button_update),
920
                    &BMPSaveData.use_run_length_encoding);
921

922 923 924 925 926 927
  /* Compatibility Options */
  expander = gtk_expander_new_with_mnemonic (_("Co_mpatibility Options"));

  gtk_box_pack_start (GTK_BOX (vbox_main), expander, TRUE, TRUE, 0);
  gtk_widget_show (expander);

928
  vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950
  gtk_container_set_border_width (GTK_CONTAINER (vbox2), 12);
  gtk_container_add (GTK_CONTAINER (expander), vbox2);
  gtk_widget_show (vbox2);

  toggle = gtk_check_button_new_with_mnemonic (_("_Do not write color space information"));
  gimp_help_set_help_data (toggle,
                           _("Some applications can not read BMP images that "
                             "include color space information. GIMP writes "
                             "color space information by default. Enabling "
                             "this option will cause GIMP to not write color "
                             "space information to the file."),
                           NULL);
  gtk_box_pack_start (GTK_BOX (vbox2), toggle, FALSE, FALSE, 0);
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle),
                                BMPSaveData.dont_write_color_space_data);
  gtk_widget_show (toggle);

  g_signal_connect (toggle, "toggled",
                    G_CALLBACK (gimp_toggle_button_update),
                    &BMPSaveData.dont_write_color_space_data);

  /* Advanced Options */
951 952 953 954 955 956 957 958
  expander = gtk_expander_new_with_mnemonic (_("_Advanced Options"));

  gtk_box_pack_start (GTK_BOX (vbox_main), expander, TRUE, TRUE, 0);
  gtk_widget_show (expander);

  if (channels < 3)
    gtk_widget_set_sensitive (expander, FALSE);

959
  vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
960 961 962 963
  gtk_container_set_border_width (GTK_CONTAINER (vbox2), 12);
  gtk_container_add (GTK_CONTAINER (expander), vbox2);
  gtk_widget_show (vbox2);

964 965 966
  group = NULL;

  frame = gimp_frame_new (_("16 bits"));
967
  gtk_box_pack_start (GTK_BOX (vbox2), frame, TRUE, TRUE, 0);
968 969
  gtk_widget_show (frame);

970
  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
971 972 973 974 975 976 977
  gtk_container_add (GTK_CONTAINER (frame), vbox);
  gtk_widget_show (vbox);

  toggle = gtk_radio_button_new_with_label (group, "R5 G6 B5");
  group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (toggle));
  gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0);
  gtk_widget_show (toggle);
978
  g_signal_connect (toggle, "toggled",
979 980 981 982 983 984
                    G_CALLBACK (format_callback),
                    GINT_TO_POINTER (RGB_565));

  toggle = gtk_radio_button_new_with_label (group, "A1 R5 G5 B5");
  group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (toggle));
  gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0);
985

986 987
  if (channels < 4)
    gtk_widget_set_sensitive (toggle, FALSE);
988

989 990
  gtk_widget_show (toggle);

991
  g_signal_connect (toggle, "toggled",
992 993 994 995 996 997
                    G_CALLBACK (format_callback),
                    GINT_TO_POINTER (RGBA_5551));
  toggle = gtk_radio_button_new_with_label (group, "X1 R5 G5 B5");
  group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (toggle));
  gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0);
  gtk_widget_show (toggle);
998
  g_signal_connect (toggle, "toggled",
999 1000 1001 1002
                    G_CALLBACK (format_callback),
                    GINT_TO_POINTER (RGB_555));

  frame = gimp_frame_new (_("24 bits"));
1003
  gtk_box_pack_start (GTK_BOX (vbox2), frame, FALSE, FALSE, 0);
1004 1005 1006 1007 1008 1009
  gtk_widget_show (frame);

  toggle = gtk_radio_button_new_with_label (group, "R8 G8 B8");
  group = gtk_radio_button_get_group (GTK_RADIO_BUTTON(toggle));
  gtk_container_add (GTK_CONTAINER (frame), toggle);
  gtk_widget_show (toggle);
1010
  g_signal_connect (toggle, "toggled",
1011 1012
                    G_CALLBACK (format_callback),
                    GINT_TO_POINTER (RGB_888));
1013 1014 1015 1016 1017
  if (channels < 4)
    {
      BMPSaveData.rgb_format = RGB_888;
      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), TRUE);
    }
1018 1019

  frame = gimp_frame_new (_("32 bits"));
1020
  gtk_box_pack_start (GTK_BOX (vbox2), frame, FALSE, FALSE, 0);
1021 1022
  gtk_widget_show (frame);

1023
  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
1024 1025 1026 1027 1028
  gtk_container_add (GTK_CONTAINER (frame), vbox);
  gtk_widget_show (vbox);

  toggle = gtk_radio_button_new_with_label (group, "A8 R8 G8 B8");
  group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (toggle));
1029
  gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0);
1030 1031 1032 1033 1034
  gtk_widget_show (toggle);
  g_signal_connect (toggle, "toggled",
                    G_CALLBACK (format_callback),
                    GINT_TO_POINTER (RGBA_8888));

1035

1036
  if (channels < 4)
1037 1038 1039 1040 1041 1042 1043 1044 1045
    {
      gtk_widget_set_sensitive (toggle, FALSE);
    }
  else
    {
      BMPSaveData.rgb_format = RGBA_8888;
      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), TRUE);
    }

1046 1047
  toggle = gtk_radio_button_new_with_label (group, "X8 R8 G8 B8");
  group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (toggle));
1048
  gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0);
1049
  gtk_widget_show (toggle);
1050
  g_signal_connect (toggle, "toggled",
1051 1052 1053
                    G_CALLBACK (format_callback),
                    GINT_TO_POINTER (RGBX_8888));

1054
  /* Dialog show */
David Odin's avatar
David Odin committed
1055
  gtk_widget_show (dialog);
1056

David Odin's avatar
David Odin committed
1057
  run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK);
1058

David Odin's avatar
David Odin committed
1059
  gtk_widget_destroy (dialog);
1060 1061 1062

  return run;
}