file-mng.c 54.7 KB
Newer Older
1
2
3
4
/* GIMP - The GNU Image Manipulation Program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * Multiple-image Network Graphics (MNG) plug-in
5
 *
Mukund Sivaraman's avatar
Mukund Sivaraman committed
6
 * Copyright (C) 2002 Mukund Sivaraman <muks@mukund.org>
7
8
9
10
11
12
13
 * Portions are copyright of the authors of the file-gif-save, file-png-save
 * and file-jpeg-save plug-ins' code. The exact ownership of these code
 * fragments has not been determined.
 *
 * This work was sponsored by Xinit Systems Limited, UK.
 * http://www.xinitsystems.com/
 *
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 <http://www.gnu.org/licenses/>.
26
 * --
27
 *
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
 * For now, this MNG plug-in can only save images. It cannot load images.
 * Save your working copy as .xcf and use this for "exporting" your images
 * to MNG. Supports animation the same way as animated GIFs. Supports alpha
 * transparency. Uses the libmng library (http://www.libmng.com/).
 * The MIME content-type for MNG images is video/x-mng for now. Make sure
 * your web-server is configured appropriately if you want to serve MNG
 * images.
 *
 * Since libmng cannot write PNG, JNG and delta PNG chunks at this time
 * (when this text was written), this plug-in uses libpng and jpeglib to
 * create the data for the chunks.
 *
 */

#include "config.h"

44
#include <errno.h>
45
46
47
48
#include <stdlib.h>
#include <string.h>
#include <time.h>

49
50
#include <glib/gstdio.h>

51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif


/* libpng and jpeglib are currently used in this plug-in. */

#include <png.h>
#include <jpeglib.h>


/* Grrr. The grrr is because the following have to be defined
 * by the application as well for some reason, although they
 * were enabled when libmng was compiled. The authors of libmng
 * must look into this. */

#if !defined(MNG_SUPPORT_FULL)
#define MNG_SUPPORT_FULL 1
#endif

#if !defined(MNG_SUPPORT_READ)
#define MNG_SUPPORT_READ 1
#endif

#if !defined(MNG_SUPPORT_WRITE)
#define MNG_SUPPORT_WRITE 1
#endif

#if !defined(MNG_SUPPORT_DISPLAY)
#define MNG_SUPPORT_DISPLAY 1
#endif

#if !defined(MNG_ACCESS_CHUNKS)
#define MNG_ACCESS_CHUNKS 1
#endif

#include <libmng.h>

#include "libgimp/gimp.h"
#include "libgimp/gimpui.h"

#include "libgimp/stdplugins-intl.h"


95
#define SAVE_PROC      "file-mng-save"
96
#define PLUG_IN_BINARY "file-mng"
97
#define PLUG_IN_ROLE   "gimp-file-mng"
98
#define SCALE_WIDTH    125
99

Philip Lafleur's avatar
Philip Lafleur committed
100
101
102
103
104
105
106
enum
{
  CHUNKS_PNG_D,
  CHUNKS_JNG_D,
  CHUNKS_PNG,
  CHUNKS_JNG
};
107

Philip Lafleur's avatar
Philip Lafleur committed
108
109
110
111
112
enum
{
  DISPOSE_COMBINE,
  DISPOSE_REPLACE
};
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141


/* The contents of this struct remain static among multiple
 * invocations of the plug-in. */

/* TODO: describe the members of the struct */

struct mng_data_t
{
  gint32 interlaced;
  gint32 bkgd;
  gint32 gama;
  gint32 phys;
  gint32 time;
  gint32 default_chunks;

  gfloat quality;
  gfloat smoothing;

  gint32 compression_level;

  gint32 loop;
  gint32 default_delay;
  gint32 default_dispose;
};

/* Values of the instance of the above struct when the plug-in is
 * first invoked. */

142
static struct mng_data_t mng_data =
143
{
144
145
146
147
148
149
  FALSE,                        /* interlaced */
  FALSE,                        /* bkgd */
  FALSE,                        /* gama */
  TRUE,                         /* phys */
  TRUE,                         /* time */
  CHUNKS_PNG_D,                 /* default_chunks */
150

151
152
  0.75,                         /* quality */
  0.0,                          /* smoothing */
153

154
  9,                            /* compression_level */
155

156
157
158
  TRUE,                         /* loop */
  100,                          /* default_delay */
  DISPOSE_COMBINE               /* default_dispose */
159
160
161
};


162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
/* These are not saved or restored. */

struct mng_globals_t
{
  gboolean   has_trns;
  png_bytep  trans;
  int        num_trans;
  gboolean   has_plte;
  png_colorp palette;
  int        num_palette;
};

static struct mng_globals_t mngg;


177
178
/* The output FILE pointer which is used by libmng;
 * passed around as user data. */
179
180
181
182
struct mnglib_userdata_t
{
  FILE *fp;
};
183
184


Philip Lafleur's avatar
Philip Lafleur committed
185
186
187
188
/*
 * Function prototypes
 */

189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
static mng_ptr   myalloc       (mng_size_t  size);
static void      myfree        (mng_ptr     ptr,
                                mng_size_t  size);
static mng_bool  myopenstream  (mng_handle  handle);
static mng_bool  myclosestream (mng_handle  handle);
static mng_bool  mywritedata   (mng_handle  handle,
                                mng_ptr     buf,
                                mng_uint32  size,
                                mng_uint32 *written_size);


static gint32    parse_chunks_type_from_layer_name   (const gchar *str);
static gint32    parse_disposal_type_from_layer_name (const gchar *str);
static gint32    parse_ms_tag_from_layer_name        (const gchar *str);
static gint      find_unused_ia_colour               (guchar      *pixels,
                                                      gint         numpixels,
                                                      gint        *colors);
static gboolean  ia_has_transparent_pixels           (guchar      *pixels,
                                                      gint         numpixels);

static gboolean  respin_cmap     (png_structp       png_ptr,
                                  png_infop         png_info_ptr,
                                  guchar           *remap,
                                  gint32            image_id,
213
214
                                  GimpDrawable     *drawable,
                                  int              *bit_depth);
215
216
217
218
219
220
221
222
223
224
225
226
227
228

static gboolean  mng_save_image  (const gchar      *filename,
                                  gint32            image_id,
                                  gint32            drawable_id,
                                  gint32            original_image_id,
                                  GError          **error);
static gboolean  mng_save_dialog (gint32            image_id);

static void      query           (void);
static void      run             (const gchar      *name,
                                  gint              nparams,
                                  const GimpParam  *param,
                                  gint             *nreturn_vals,
                                  GimpParam       **return_vals);
Mukund Sivaraman's avatar
Mukund Sivaraman committed
229

Philip Lafleur's avatar
Philip Lafleur committed
230
231
232
233

/*
 * Callbacks for libmng
 */
234
235
236
237
238

static mng_ptr
myalloc (mng_size_t size)
{
  gpointer ptr;
Mukund Sivaraman's avatar
Mukund Sivaraman committed
239

240
  ptr = g_try_malloc (size);
241
242
243
244
245
246
247
248

  if (ptr != NULL)
    memset (ptr, 0, size);

  return ((mng_ptr) ptr);
}

static void
Philip Lafleur's avatar
Philip Lafleur committed
249
250
myfree (mng_ptr    ptr,
        mng_size_t size)
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
{
  g_free (ptr);
}

static mng_bool
myopenstream (mng_handle handle)
{
  return MNG_TRUE;
}

static mng_bool
myclosestream (mng_handle handle)
{
  return MNG_TRUE;
}

static mng_bool
Philip Lafleur's avatar
Philip Lafleur committed
268
269
270
271
mywritedata (mng_handle  handle,
             mng_ptr     buf,
             mng_uint32  size,
             mng_uint32 *written_size)
272
273
274
275
276
277
{
  struct mnglib_userdata_t *userdata =
    (struct mnglib_userdata_t *) mng_get_userdata (handle);


  *written_size = (mng_uint32) fwrite ((void *) buf, 1,
278
                                       (size_t) size, userdata->fp);
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294

  return MNG_TRUE;
}


/* Parses which output chunk type to use for this layer
 * from the layer name. */

static gint32
parse_chunks_type_from_layer_name (const gchar *str)
{
  guint i;

  for (i = 0; (i + 5) <= strlen (str); i++)
    {
      if (g_ascii_strncasecmp (str + i, "(png)", 5) == 0)
295
        return CHUNKS_PNG;
296
      else if (g_ascii_strncasecmp (str + i, "(jng)", 5) == 0)
297
        return CHUNKS_JNG;
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
    }

  return mng_data.default_chunks;
}


/* Parses which disposal type to use for this layer
 * from the layer name. */

static gint32
parse_disposal_type_from_layer_name (const gchar *str)
{
  guint i;

  for (i = 0; (i + 9) <= strlen (str); i++)
    {
      if (g_ascii_strncasecmp (str + i, "(combine)", 9) == 0)
315
        return DISPOSE_COMBINE;
316
      else if (g_ascii_strncasecmp (str + i, "(replace)", 9) == 0)
317
        return DISPOSE_REPLACE;
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
    }

  return mng_data.default_dispose;
}


/* Parses the millisecond delay to use for this layer
 * from the layer name. */

static gint32
parse_ms_tag_from_layer_name (const gchar *str)
{
  guint offset = 0;
  gint32 sum = 0;
  guint length = strlen (str);

  while (TRUE)
    {
      while ((offset < length) && (str[offset] != '('))
337
        offset++;
338
339

      if (offset >= length)
340
        return mng_data.default_delay;
341
342
343
344

      offset++;

      if (g_ascii_isdigit (str[offset]))
345
        break;
346
347
348
349
350
351
352
353
354
355
356
357
358
    }

  do
    {
      sum *= 10;
      sum += str[offset] - '0';
      offset++;
    }
  while ((offset < length) && (g_ascii_isdigit (str[offset])));

  if ((length - offset) <= 2)
    return mng_data.default_delay;

359
360
  if ((g_ascii_toupper (str[offset]) != 'M') ||
      (g_ascii_toupper (str[offset + 1]) != 'S'))
361
362
363
364
365
366
    return mng_data.default_delay;

  return sum;
}


367
368
/* Try to find a colour in the palette which isn't actually
 * used in the image, so that we can use it as the transparency
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
 * index. Taken from png.c */
static gint
find_unused_ia_colour (guchar *pixels,
                       gint    numpixels,
                       gint   *colors)
{
  gint     i;
  gboolean ix_used[256];
  gboolean trans_used = FALSE;

  for (i = 0; i < *colors; i++)
    {
      ix_used[i] = FALSE;
    }

  for (i = 0; i < numpixels; i++)
    {
386
      /* If alpha is over a threshold, the colour index in the
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
       * palette is taken. Otherwise, this pixel is transparent. */
      if (pixels[i * 2 + 1] > 127)
        ix_used[pixels[i * 2]] = TRUE;
      else
        trans_used = TRUE;
    }

  /* If there is no transparency, ignore alpha. */
  if (trans_used == FALSE)
    return -1;

  for (i = 0; i < *colors; i++)
    {
      if (ix_used[i] == FALSE)
        {
          return i;
        }
    }

  /* Couldn't find an unused colour index within the number of
     bits per pixel we wanted.  Will have to increment the number
     of colours in the image and assign a transparent pixel there. */
  if ((*colors) < 256)
    {
      (*colors)++;
      return ((*colors) - 1);
    }

Mukund Sivaraman's avatar
Mukund Sivaraman committed
415
  return -1;
416
417
418
}


419
420
421
422
423
424
425
426
427
428
429
430
431
static gboolean
ia_has_transparent_pixels (guchar *pixels,
                           gint    numpixels)
{
  while (numpixels --)
    {
      if (pixels [1] <= 127)
        return TRUE;
      pixels += 2;
    }
  return FALSE;
}

432
433
434
435
436
437
438
439
440
441
442
443
static int
get_bit_depth_for_palette (int num_palette)
{
  if (num_palette <= 2)
    return 1;
  else if (num_palette <= 4)
    return 2;
  else if (num_palette <= 16)
    return 4;
  else
    return 8;
}
444

445
446
/* Spins the color map (palette) putting the transparent color at
 * index 0 if there is space. If there isn't any space, warn the user
447
448
 * and forget about transparency. Returns TRUE if the colormap has
 * been changed and FALSE otherwise.
449
 */
450

451
static gboolean
452
453
respin_cmap (png_structp  pp,
             png_infop    info,
454
             guchar       *remap,
455
             gint32       image_id,
456
457
             GimpDrawable *drawable,
             int          *bit_depth)
458
{
459
460
461
  static guchar  trans[] = { 0 };
  guchar        *before;
  guchar        *pixels;
462
  gint           numpixels;
463
464
465
466
  gint           colors;
  gint           transparent;
  gint           cols, rows;
  GimpPixelRgn   pixel_rgn;
467

468
  before = gimp_image_get_colormap (image_id, &colors);
469

Mukund Sivaraman's avatar
Mukund Sivaraman committed
470
  /* Make sure there is something in the colormap */
471
472
473
474
475
476
  if (colors == 0)
    {
      before = g_new0 (guchar, 3);
      colors = 1;
    }

477
478
479
  cols      = drawable->width;
  rows      = drawable->height;
  numpixels = cols * rows;
480
481
482
483

  gimp_pixel_rgn_init (&pixel_rgn, drawable, 0, 0,
                       drawable->width, drawable->height, FALSE, FALSE);

484
  pixels = (guchar *) g_malloc (numpixels * 2);
485
486
487
488

  gimp_pixel_rgn_get_rect (&pixel_rgn, pixels, 0, 0,
                           drawable->width, drawable->height);

489
  if (ia_has_transparent_pixels (pixels, numpixels))
490
    {
491
492
493
494
      transparent = find_unused_ia_colour (pixels, numpixels, &colors);

      if (transparent != -1)
        {
495
          static png_color palette[256] = { {0, 0, 0} };
496
          gint i;
497

498
499
500
501
          /* Set tRNS chunk values for writing later. */
          mngg.has_trns = TRUE;
          mngg.trans = trans;
          mngg.num_trans = 1;
502

503
504
          /* Transform all pixels with a value = transparent to
           * 0 and vice versa to compensate for re-ordering in palette
Mukund Sivaraman's avatar
Mukund Sivaraman committed
505
506
           * due to png_set_tRNS().
           */
507

508
509
          remap[0] = transparent;
          remap[transparent] = 0;
510

511
512
          /* Copy from index 0 to index transparent - 1 to index 1 to
           * transparent of after, then from transparent+1 to colors-1
Mukund Sivaraman's avatar
Mukund Sivaraman committed
513
514
           * unchanged, and finally from index transparent to index 0.
           */
515

516
          for (i = 1; i < colors; i++)
517
518
519
520
521
522
            {
              palette[i].red = before[3 * remap[i]];
              palette[i].green = before[3 * remap[i] + 1];
              palette[i].blue = before[3 * remap[i] + 2];
            }

523
524
525
526
527
          /* Set PLTE chunk values for writing later. */
          mngg.has_plte = TRUE;
          mngg.palette = palette;
          mngg.num_palette = colors;

528
          *bit_depth = get_bit_depth_for_palette (colors);
529
530
531
532

          return TRUE;
        }
      else
Mukund Sivaraman's avatar
Mukund Sivaraman committed
533
534
535
536
        {
          g_message (_("Couldn't losslessly save transparency, "
                       "saving opacity instead."));
        }
537
    }
538

539
540
541
  mngg.has_plte = TRUE;
  mngg.palette = (png_colorp) before;
  mngg.num_palette = colors;
542
  *bit_depth = get_bit_depth_for_palette (colors);
543

544
  return FALSE;
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
581
static mng_retcode
mng_putchunk_plte_wrapper (mng_handle    handle,
                           gint          numcolors,
                           const guchar *colormap)
{
  mng_palette8 palette;

  memset (palette, 0, sizeof palette);
  if (0 < numcolors)
    memcpy (palette, colormap, numcolors * sizeof palette[0]);

  return mng_putchunk_plte (handle, numcolors, palette);
}

static mng_retcode
mng_putchunk_trns_wrapper (mng_handle    handle,
                           gint          n_alphas,
                           const guchar *buffer)
{
  const mng_bool mng_global = TRUE;
  const mng_bool mng_empty  = TRUE;
  mng_uint8arr   alphas;

  memset (alphas, 0, sizeof alphas);
  if (buffer && 0 < n_alphas)
    memcpy (alphas, buffer, n_alphas * sizeof alphas[0]);

  return mng_putchunk_trns (handle,
                            ! mng_empty,
                            ! mng_global,
                            MNG_COLORTYPE_INDEXED,
                            n_alphas,
                            alphas,
                            0, 0, 0, 0, 0, alphas);
}
582

Mukund Sivaraman's avatar
Mukund Sivaraman committed
583
static gboolean
584
585
586
587
588
mng_save_image (const gchar  *filename,
                gint32        image_id,
                gint32        drawable_id,
                gint32        original_image_id,
                GError      **error)
589
{
Mukund Sivaraman's avatar
Mukund Sivaraman committed
590
  gboolean        ret = FALSE;
591
592
593
594
595
596
597
  gint            rows, cols;
  volatile gint   i;
  time_t          t;
  struct tm      *gmt;

  gint            num_layers;
  gint32         *layers;
598
599

  struct mnglib_userdata_t *userdata;
600
601
602
  mng_handle      handle;
  guint32         mng_ticks_per_second;
  guint32         mng_simplicity_profile;
603
604
605
606

  layers = gimp_image_get_layers (image_id, &num_layers);

  if (num_layers < 1)
Mukund Sivaraman's avatar
Mukund Sivaraman committed
607
    return FALSE;
608
609
610
611
612
613
614
615
616

  if (num_layers > 1)
    mng_ticks_per_second = 1000;
  else
    mng_ticks_per_second = 0;

  rows = gimp_image_height (image_id);
  cols = gimp_image_width (image_id);

Mukund Sivaraman's avatar
Mukund Sivaraman committed
617
618
619
620
621
622
623
  mng_simplicity_profile = (MNG_SIMPLICITY_VALID |
                            MNG_SIMPLICITY_SIMPLEFEATURES |
                            MNG_SIMPLICITY_COMPLEXFEATURES);

  /* JNG and delta-PNG chunks exist */
  mng_simplicity_profile |= (MNG_SIMPLICITY_JNG |
                             MNG_SIMPLICITY_DELTAPNG);
624
625
626
627

  for (i = 0; i < num_layers; i++)
    if (gimp_drawable_has_alpha (layers[i]))
      {
Mukund Sivaraman's avatar
Mukund Sivaraman committed
628
629
630
631
632
633
634
635
636
637
638
639
        /* internal transparency exists */
        mng_simplicity_profile |= MNG_SIMPLICITY_TRANSPARENCY;

        /* validity of following flags */
        mng_simplicity_profile |= 0x00000040;

        /* semi-transparency exists */
        mng_simplicity_profile |= 0x00000100;

        /* background transparency should happen */
        mng_simplicity_profile |= 0x00000080;

640
        break;
641
642
      }

643
  userdata = g_new0 (struct mnglib_userdata_t, 1);
Mukund Sivaraman's avatar
Mukund Sivaraman committed
644
  userdata->fp = g_fopen (filename, "wb");
645

Mukund Sivaraman's avatar
Mukund Sivaraman committed
646
  if (NULL == userdata->fp)
647
    {
648
649
650
      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));
Mukund Sivaraman's avatar
Mukund Sivaraman committed
651
      goto err;
652
653
    }

654
  handle = mng_initialize ((mng_ptr) userdata, myalloc, myfree, NULL);
Mukund Sivaraman's avatar
Mukund Sivaraman committed
655
  if (NULL == handle)
656
    {
657
      g_warning ("Unable to mng_initialize() in mng_save_image()");
Mukund Sivaraman's avatar
Mukund Sivaraman committed
658
      goto err2;
659
660
    }

Mukund Sivaraman's avatar
Mukund Sivaraman committed
661
662
663
  if ((mng_setcb_openstream (handle, myopenstream) != MNG_NOERROR) ||
      (mng_setcb_closestream (handle, myclosestream) != MNG_NOERROR) ||
      (mng_setcb_writedata (handle, mywritedata) != MNG_NOERROR))
664
    {
665
      g_warning ("Unable to setup callbacks in mng_save_image()");
Mukund Sivaraman's avatar
Mukund Sivaraman committed
666
      goto err3;
667
668
    }

Mukund Sivaraman's avatar
Mukund Sivaraman committed
669
  if (mng_create (handle) != MNG_NOERROR)
670
    {
671
      g_warning ("Unable to mng_create() image in mng_save_image()");
Mukund Sivaraman's avatar
Mukund Sivaraman committed
672
      goto err3;
673
674
    }

Mukund Sivaraman's avatar
Mukund Sivaraman committed
675
676
677
  if (mng_putchunk_mhdr (handle, cols, rows, mng_ticks_per_second, 1,
                         num_layers, mng_data.default_delay,
                         mng_simplicity_profile) != MNG_NOERROR)
678
    {
679
      g_warning ("Unable to mng_putchunk_mhdr() in mng_save_image()");
Mukund Sivaraman's avatar
Mukund Sivaraman committed
680
      goto err3;
681
682
683
684
    }

  if ((num_layers > 1) && (mng_data.loop))
    {
Mukund Sivaraman's avatar
Mukund Sivaraman committed
685
      gint32 ms =
686
        parse_ms_tag_from_layer_name (gimp_item_get_name (layers[0]));
Mukund Sivaraman's avatar
Mukund Sivaraman committed
687
688
689
690

      if (mng_putchunk_term (handle, MNG_TERMACTION_REPEAT,
                             MNG_ITERACTION_LASTFRAME,
                             ms, 0x7fffffff) != MNG_NOERROR)
691
692
        {
          g_warning ("Unable to mng_putchunk_term() in mng_save_image()");
Mukund Sivaraman's avatar
Mukund Sivaraman committed
693
          goto err3;
694
        }
695
696
697
    }
  else
    {
Mukund Sivaraman's avatar
Mukund Sivaraman committed
698
      gint32 ms =
699
        parse_ms_tag_from_layer_name (gimp_item_get_name (layers[0]));
Mukund Sivaraman's avatar
Mukund Sivaraman committed
700
701
702
703

      if (mng_putchunk_term (handle, MNG_TERMACTION_LASTFRAME,
                             MNG_ITERACTION_LASTFRAME,
                             ms, 0x7fffffff) != MNG_NOERROR)
704
705
        {
          g_warning ("Unable to mng_putchunk_term() in mng_save_image()");
Mukund Sivaraman's avatar
Mukund Sivaraman committed
706
          goto err3;
707
        }
708
709
710
    }


711
712
  /* For now, we hardwire a comment */

Mukund Sivaraman's avatar
Mukund Sivaraman committed
713
714
  if (mng_putchunk_text (handle,
                         strlen (MNG_TEXT_TITLE), MNG_TEXT_TITLE,
715
                         18, "Created using GIMP") != MNG_NOERROR)
716
717
    {
      g_warning ("Unable to mng_putchunk_text() in mng_save_image()");
Mukund Sivaraman's avatar
Mukund Sivaraman committed
718
      goto err3;
719
720
    }

Philip Lafleur's avatar
Philip Lafleur committed
721
#if 0
722

Mukund Sivaraman's avatar
Mukund Sivaraman committed
723
724
725
726
727
728
729
730
731
732
733
734
  /* how do we get this to work? */
  if (mng_data.bkgd)
    {
      GimpRGB bgcolor;
      guchar red, green, blue;

      gimp_context_get_background (&bgcolor);
      gimp_rgb_get_uchar (&bgcolor, &red, &green, &blue);

      if (mng_putchunk_back (handle, red, green, blue,
                             MNG_BACKGROUNDCOLOR_MANDATORY,
                             0, MNG_BACKGROUNDIMAGE_NOTILE) != MNG_NOERROR)
735
        {
736
          g_warning ("Unable to mng_putchunk_back() in mng_save_image()");
Mukund Sivaraman's avatar
Mukund Sivaraman committed
737
          goto err3;
738
        }
Mukund Sivaraman's avatar
Mukund Sivaraman committed
739
740
741
742
743

      if (mng_putchunk_bkgd (handle, MNG_FALSE, 2, 0,
                             gimp_rgb_luminance_uchar (&bgcolor),
                             red, green, blue) != MNG_NOERROR)
        {
744
          g_warning ("Unable to mng_putchunk_bkgd() in mng_save_image()");
Mukund Sivaraman's avatar
Mukund Sivaraman committed
745
746
747
748
          goto err3;
        }
    }

Philip Lafleur's avatar
Philip Lafleur committed
749
#endif
750
751
752

  if (mng_data.gama)
    {
Mukund Sivaraman's avatar
Mukund Sivaraman committed
753
754
      if (mng_putchunk_gama (handle, MNG_FALSE,
                             (1.0 / (gimp_gamma ()) * 100000)) != MNG_NOERROR)
755
756
        {
          g_warning ("Unable to mng_putchunk_gama() in mng_save_image()");
Mukund Sivaraman's avatar
Mukund Sivaraman committed
757
          goto err3;
758
        }
759
760
    }

Philip Lafleur's avatar
Philip Lafleur committed
761
#if 0
762

Mukund Sivaraman's avatar
Mukund Sivaraman committed
763
  /* how do we get this to work? */
Mukund Sivaraman's avatar
Mukund Sivaraman committed
764
765
766
  if (mng_data.phys)
    {
      gimp_image_get_resolution(original_image_id, &xres, &yres);
Mukund Sivaraman's avatar
Mukund Sivaraman committed
767
768
769
770

      if (mng_putchunk_phyg (handle, MNG_FALSE,
                             (mng_uint32) (xres * 39.37),
                             (mng_uint32) (yres * 39.37), 1) != MNG_NOERROR)
771
        {
772
          g_warning ("Unable to mng_putchunk_phyg() in mng_save_image()");
Mukund Sivaraman's avatar
Mukund Sivaraman committed
773
          goto err3;
Mukund Sivaraman's avatar
Mukund Sivaraman committed
774
        }
775

Mukund Sivaraman's avatar
Mukund Sivaraman committed
776
777
778
      if (mng_putchunk_phys (handle, MNG_FALSE,
                             (mng_uint32) (xres * 39.37),
                             (mng_uint32) (yres * 39.37), 1) != MNG_NOERROR)
Mukund Sivaraman's avatar
Mukund Sivaraman committed
779
        {
780
          g_warning ("Unable to mng_putchunk_phys() in mng_save_image()");
Mukund Sivaraman's avatar
Mukund Sivaraman committed
781
          goto err3;
782
        }
Mukund Sivaraman's avatar
Mukund Sivaraman committed
783
    }
Mukund Sivaraman's avatar
Mukund Sivaraman committed
784

Philip Lafleur's avatar
Philip Lafleur committed
785
#endif
786
787
788
789
790
791

  if (mng_data.time)
    {
      t = time (NULL);
      gmt = gmtime (&t);

Mukund Sivaraman's avatar
Mukund Sivaraman committed
792
793
794
      if (mng_putchunk_time (handle, gmt->tm_year + 1900, gmt->tm_mon + 1,
                             gmt->tm_mday, gmt->tm_hour, gmt->tm_min,
                             gmt->tm_sec) != MNG_NOERROR)
795
796
        {
          g_warning ("Unable to mng_putchunk_time() in mng_save_image()");
Mukund Sivaraman's avatar
Mukund Sivaraman committed
797
          goto err3;
798
        }
799
800
    }

801
802
803
804
805
  if (gimp_image_base_type (image_id) == GIMP_INDEXED)
    {
      guchar *palette;
      gint    numcolors;

806
      palette = gimp_image_get_colormap (image_id, &numcolors);
807

Mukund Sivaraman's avatar
Mukund Sivaraman committed
808
      if ((numcolors != 0) &&
809
810
          (mng_putchunk_plte_wrapper (handle, numcolors,
                                      palette) != MNG_NOERROR))
811
812
        {
          g_warning ("Unable to mng_putchunk_plte() in mng_save_image()");
Mukund Sivaraman's avatar
Mukund Sivaraman committed
813
          goto err3;
814
815
816
        }
    }

817
818
  for (i = (num_layers - 1); i >= 0; i--)
    {
Philip Lafleur's avatar
Philip Lafleur committed
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
      GimpImageType   layer_drawable_type;
      GimpDrawable   *layer_drawable;
      gint            layer_offset_x, layer_offset_y;
      gint            layer_rows, layer_cols;
      gchar          *layer_name;
      gint            layer_chunks_type;
      volatile gint   layer_bpp;
      GimpPixelRgn    layer_pixel_rgn;

      guint8          layer_mng_colortype;
      guint8          layer_mng_compression_type;
      guint8          layer_mng_interlace_type;
      gboolean        layer_has_unique_palette;

      gchar           frame_mode;
      int             frame_delay;
      gchar          *temp_file_name;
Mukund Sivaraman's avatar
Mukund Sivaraman committed
836
837
      png_structp     pp;
      png_infop       info;
Philip Lafleur's avatar
Philip Lafleur committed
838
839
840
841
842
843
844
      FILE           *infile, *outfile;
      int             num_passes;
      int             tile_height;
      guchar        **layer_pixels, *layer_pixel;
      int             pass, j, k, begin, end, num;
      guchar         *fixed;
      guchar          layer_remap[256];
845
846
      int             color_type;
      int             bit_depth;
Philip Lafleur's avatar
Philip Lafleur committed
847

848
      layer_name          = gimp_item_get_name (layers[i]);
849
      layer_chunks_type   = parse_chunks_type_from_layer_name (layer_name);
850
851
      layer_drawable_type = gimp_drawable_type (layers[i]);

852
853
854
      layer_drawable      = gimp_drawable_get (layers[i]);
      layer_rows          = layer_drawable->height;
      layer_cols          = layer_drawable->width;
855

Mukund Sivaraman's avatar
Mukund Sivaraman committed
856
      gimp_drawable_offsets (layers[i], &layer_offset_x, &layer_offset_y);
857
858
      layer_has_unique_palette = TRUE;

859
860
861
      for (j = 0; j < 256; j++)
        layer_remap[j] = j;

862
      switch (layer_drawable_type)
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
        {
        case GIMP_RGB_IMAGE:
          layer_bpp = 3;
          layer_mng_colortype = MNG_COLORTYPE_RGB;
          break;
        case GIMP_RGBA_IMAGE:
          layer_bpp = 4;
          layer_mng_colortype = MNG_COLORTYPE_RGBA;
          break;
        case GIMP_GRAY_IMAGE:
          layer_bpp = 1;
          layer_mng_colortype = MNG_COLORTYPE_GRAY;
          break;
        case GIMP_GRAYA_IMAGE:
          layer_bpp = 2;
          layer_mng_colortype = MNG_COLORTYPE_GRAYA;
          break;
        case GIMP_INDEXED_IMAGE:
          layer_bpp = 1;
          layer_mng_colortype = MNG_COLORTYPE_INDEXED;
          break;
        case GIMP_INDEXEDA_IMAGE:
          layer_bpp = 2;
          layer_mng_colortype = MNG_COLORTYPE_INDEXED | MNG_COLORTYPE_GRAYA;
          break;
        default:
          g_warning ("Unsupported GimpImageType in mng_save_image()");
Mukund Sivaraman's avatar
Mukund Sivaraman committed
890
          goto err3;
891
        }
892
893
894
895
896

      /* Delta PNG chunks are not yet supported */

      /* if (i == (num_layers - 1)) */
      {
897
898
899
900
        if (layer_chunks_type == CHUNKS_JNG_D)
          layer_chunks_type = CHUNKS_JNG;
        else if (layer_chunks_type == CHUNKS_PNG_D)
          layer_chunks_type = CHUNKS_PNG;
901
902
903
      }

      switch (layer_chunks_type)
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
        {
        case CHUNKS_PNG_D:
          layer_mng_compression_type = MNG_COMPRESSION_DEFLATE;
          if (mng_data.interlaced != 0)
            layer_mng_interlace_type = MNG_INTERLACE_ADAM7;
          else
            layer_mng_interlace_type = MNG_INTERLACE_NONE;
          break;
        case CHUNKS_JNG_D:
          layer_mng_compression_type = MNG_COMPRESSION_DEFLATE;
          if (mng_data.interlaced != 0)
            layer_mng_interlace_type = MNG_INTERLACE_ADAM7;
          else
            layer_mng_interlace_type = MNG_INTERLACE_NONE;
          break;
        case CHUNKS_PNG:
          layer_mng_compression_type = MNG_COMPRESSION_DEFLATE;
          if (mng_data.interlaced != 0)
            layer_mng_interlace_type = MNG_INTERLACE_ADAM7;
          else
            layer_mng_interlace_type = MNG_INTERLACE_NONE;
          break;
        case CHUNKS_JNG:
          layer_mng_compression_type = MNG_COMPRESSION_BASELINEJPEG;
          if (mng_data.interlaced != 0)
            layer_mng_interlace_type = MNG_INTERLACE_PROGRESSIVE;
          else
            layer_mng_interlace_type = MNG_INTERLACE_SEQUENTIAL;
          break;
        default:
Mukund Sivaraman's avatar
Mukund Sivaraman committed
934
935
936
          g_warning ("Huh? Programmer stupidity error "
                     "with 'layer_chunks_type'");
          goto err3;
937
        }
938

Mukund Sivaraman's avatar
Mukund Sivaraman committed
939
940
      if ((i == (num_layers - 1)) ||
          (parse_disposal_type_from_layer_name (layer_name) != DISPOSE_COMBINE))
941
        frame_mode = MNG_FRAMINGMODE_3;
942
      else
943
        frame_mode = MNG_FRAMINGMODE_1;
944
945
946

      frame_delay = parse_ms_tag_from_layer_name (layer_name);

Mukund Sivaraman's avatar
Mukund Sivaraman committed
947
948
949
950
951
952
953
954
955
956
      if (mng_putchunk_fram (handle, MNG_FALSE, frame_mode, 0, NULL,
                             MNG_CHANGEDELAY_DEFAULT,
                             MNG_CHANGETIMOUT_NO,
                             MNG_CHANGECLIPPING_DEFAULT,
                             MNG_CHANGESYNCID_NO,
                             frame_delay, 0, 0,
                             layer_offset_x,
                             layer_offset_x + layer_cols,
                             layer_offset_y,
                             layer_offset_y + layer_rows,
957
                             0, NULL) != MNG_NOERROR)
958
959
        {
          g_warning ("Unable to mng_putchunk_fram() in mng_save_image()");
Mukund Sivaraman's avatar
Mukund Sivaraman committed
960
          goto err3;
961
        }
962

963
      if ((layer_offset_x != 0) || (layer_offset_y != 0))
Mukund Sivaraman's avatar
Mukund Sivaraman committed
964
965
966
967
968
969
970
971
972
973
        {
          if (mng_putchunk_defi (handle, 0, 0, 1, 1, layer_offset_x,
                                 layer_offset_y, 1, layer_offset_x,
                                 layer_offset_x + layer_cols, layer_offset_y,
                                 layer_offset_y + layer_rows) != MNG_NOERROR)
            {
              g_warning ("Unable to mng_putchunk_defi() in mng_save_image()");
              goto err3;
            }
        }
974

975
      if ((temp_file_name = gimp_temp_name ("mng")) == NULL)
976
        {
Mukund Sivaraman's avatar
Mukund Sivaraman committed
977
978
          g_warning ("gimp_temp_name() failed in mng_save_image()");
          goto err3;
979
        }
980

981
      if ((outfile = g_fopen (temp_file_name, "wb")) == NULL)
982
        {
983
984
985
986
          g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
                       _("Could not open '%s' for writing: %s"),
                       gimp_filename_to_utf8 (temp_file_name),
                       g_strerror (errno));
987
          g_unlink (temp_file_name);
Mukund Sivaraman's avatar
Mukund Sivaraman committed
988
          goto err3;
989
        }
990

Mukund Sivaraman's avatar
Mukund Sivaraman committed
991
      pp = png_create_write_struct (PNG_LIBPNG_VER_STRING,
Mukund Sivaraman's avatar
Mukund Sivaraman committed
992
                                         NULL, NULL, NULL);
Mukund Sivaraman's avatar
Mukund Sivaraman committed
993
      if (NULL == pp)
994
        {
Mukund Sivaraman's avatar
Mukund Sivaraman committed
995
          g_warning ("Unable to png_create_write_struct() in mng_save_image()");
996
          fclose (outfile);
997
          g_unlink (temp_file_name);
Mukund Sivaraman's avatar
Mukund Sivaraman committed
998
          goto err3;
999
        }
1000

For faster browsing, not all history is shown. View entire blame