1 /*
2  * GFLI 1.3
3  *
4  * A gimp plug-in to read and write FLI and FLC movies.
5  *
6  * Copyright (C) 1998 Jens Ch. Restemeier <jchrr@hrz.uni-bielefeld.de>
7  *
8  * This program is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 3 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
20  *
21  * This is a first loader for FLI and FLC movies. It uses as the same method as
22  * the gif plug-in to store the animation (i.e. 1 layer/frame).
23  *
24  * Current disadvantages:
25  * - Generates A LOT OF warnings.
26  * - Consumes a lot of memory (See wish-list: use the original data or
27  *   compression).
28  * - doesn't support palette changes between two frames.
29  *
30  * Wish-List:
31  * - I'd like to have a different format for storing animations, so I can use
32  *   Layers and Alpha-Channels for effects. An older version of
33  *   this plug-in created one image per frame, and went real soon out of
34  *   memory.
35  * - I'd like a method that requests unmodified frames from the original
36  *   image, and stores modified without destroying the original file.
37  * - I'd like a way to store additional information about a image to it, for
38  *   example copyright stuff or a timecode.
39  * - I've thought about a small utility to mix MIDI events as custom chunks
40  *   between the FLI chunks. Anyone interested in implementing this ?
41  */
42 
43 /*
44  * History:
45  * 1.0 first release
46  * 1.1 first support for FLI saving (BRUN and LC chunks)
47  * 1.2 support for load/save ranges, fixed SGI & SUN problems (I hope...), fixed FLC
48  * 1.3 made saving actually work, alpha channel is silently ignored;
49        loading was broken too, fixed it  --Sven
50  */
51 
52 #include <config.h>
53 
54 #include <errno.h>
55 #include <string.h>
56 
57 #include <glib/gstdio.h>
58 
59 #include <libgimp/gimp.h>
60 #include <libgimp/gimpui.h>
61 
62 #include "fli.h"
63 
64 #include "libgimp/stdplugins-intl.h"
65 
66 
67 #define LOAD_PROC      "file-fli-load"
68 #define SAVE_PROC      "file-fli-save"
69 #define INFO_PROC      "file-fli-info"
70 #define PLUG_IN_BINARY "file-fli"
71 #define PLUG_IN_ROLE   "gimp-file-fli"
72 
73 
74 static void      query       (void);
75 static void      run         (const gchar      *name,
76                               gint              nparams,
77                               const GimpParam  *param,
78                               gint             *nreturn_vals,
79                               GimpParam       **return_vals);
80 
81 /* return the image-ID of the new image, or -1 in case of an error */
82 static gint32    load_image  (const  gchar  *filename,
83                               gint32         from_frame,
84                               gint32         to_frame,
85                               GError       **error);
86 static gboolean  load_dialog (const gchar   *filename);
87 
88 static gboolean  save_image  (const gchar   *filename,
89                               gint32         image_id,
90                               gint32         from_frame,
91                               gint32         to_frame,
92                               GError       **error);
93 static gboolean  save_dialog (gint32         image_id);
94 
95 static gboolean  get_info    (const gchar   *filename,
96                               gint32        *width,
97                               gint32        *height,
98                               gint32        *frames,
99                               GError       **error);
100 
101 /*
102  * GIMP interface
103  */
104 const GimpPlugInInfo PLUG_IN_INFO =
105 {
106   NULL,  /* init_proc  */
107   NULL,  /* quit_proc  */
108   query, /* query_proc */
109   run,   /* run_proc   */
110 };
111 
112 static const GimpParamDef load_args[] =
113 {
114   { GIMP_PDB_INT32,  "run-mode",     "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }"   },
115   { GIMP_PDB_STRING, "filename",     "The name of the file to load"   },
116   { GIMP_PDB_STRING, "raw-filename", "The name entered"               },
117   { GIMP_PDB_INT32,  "from-frame",   "Load beginning from this frame" },
118   { GIMP_PDB_INT32,  "to-frame",     "End loading with this frame"    }
119 };
120 
121 static const GimpParamDef load_return_vals[] =
122 {
123   { GIMP_PDB_IMAGE, "image", "Output image" },
124 };
125 
126 static const GimpParamDef save_args[] =
127 {
128   { GIMP_PDB_INT32,    "run-mode",     "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },
129   { GIMP_PDB_IMAGE,    "image",        "Input image" },
130   { GIMP_PDB_DRAWABLE, "drawable",     "Input drawable (unused)" },
131   { GIMP_PDB_STRING,   "filename",     "The name of the file to export" },
132   { GIMP_PDB_STRING,   "raw-filename", "The name entered" },
133   { GIMP_PDB_INT32,    "from-frame",   "Export beginning from this frame" },
134   { GIMP_PDB_INT32,    "to-frame",     "End exporting with this frame" },
135 };
136 
137 static const GimpParamDef info_args[] =
138 {
139   { GIMP_PDB_STRING, "filename", "The name of the file to get info" },
140 };
141 static const GimpParamDef info_return_vals[] =
142 {
143   { GIMP_PDB_INT32, "width",  "Width of one frame" },
144   { GIMP_PDB_INT32, "height", "Height of one frame" },
145   { GIMP_PDB_INT32, "frames", "Number of Frames" },
146 };
147 
148 
149 static gint32 from_frame;
150 static gint32 to_frame;
151 
MAIN()152 MAIN ()
153 
154 static void
155 query (void)
156 {
157   /*
158    * Load/export procedures
159    */
160   gimp_install_procedure (LOAD_PROC,
161                           "load FLI-movies",
162                           "This is an experimantal plug-in to handle FLI movies",
163                           "Jens Ch. Restemeier",
164                           "Jens Ch. Restemeier",
165                           "1997",
166                           N_("AutoDesk FLIC animation"),
167                           NULL,
168                           GIMP_PLUGIN,
169                           G_N_ELEMENTS (load_args) - 2,
170                           G_N_ELEMENTS (load_return_vals),
171                           load_args,
172                           load_return_vals);
173 
174   gimp_register_file_handler_mime (LOAD_PROC, "image/x-flic");
175   gimp_register_magic_load_handler (LOAD_PROC,
176                                     "fli,flc",
177                                     "",
178                                     "");
179 
180   gimp_install_procedure (SAVE_PROC,
181                           "export FLI-movies",
182                           "This is an experimantal plug-in to handle FLI movies",
183                           "Jens Ch. Restemeier",
184                           "Jens Ch. Restemeier",
185                           "1997",
186                           N_("AutoDesk FLIC animation"),
187                           "INDEXED,GRAY",
188                           GIMP_PLUGIN,
189                           G_N_ELEMENTS (save_args), 0,
190                           save_args, NULL);
191 
192   gimp_register_file_handler_mime (SAVE_PROC, "image/x-flic");
193   gimp_register_save_handler (SAVE_PROC,
194                               "fli,flc",
195                               "");
196 
197   /*
198    * Utility functions:
199    */
200   gimp_install_procedure (INFO_PROC,
201                           "Get information about a Fli movie",
202                           "This is a experimantal plug-in to handle FLI movies",
203                           "Jens Ch. Restemeier",
204                           "Jens Ch. Restemeier",
205                           "1997",
206                           NULL,
207                           NULL,
208                           GIMP_PLUGIN,
209                           G_N_ELEMENTS (info_args),
210                           G_N_ELEMENTS (info_return_vals),
211                           info_args,
212                           info_return_vals);
213 }
214 
215 static void
run(const gchar * name,gint nparams,const GimpParam * param,gint * nreturn_vals,GimpParam ** return_vals)216 run (const gchar      *name,
217      gint              nparams,
218      const GimpParam  *param,
219      gint             *nreturn_vals,
220      GimpParam       **return_vals)
221 {
222   static GimpParam   values[5];
223   GimpPDBStatusType  status = GIMP_PDB_SUCCESS;
224   GimpRunMode        run_mode;
225   gint32             pc;
226   gint32             image_ID;
227   gint32             drawable_ID;
228   gint32             orig_image_ID;
229   GimpExportReturn   export = GIMP_EXPORT_CANCEL;
230   GError            *error  = NULL;
231 
232   INIT_I18N ();
233   gegl_init (NULL, NULL);
234 
235   run_mode = param[0].data.d_int32;
236 
237   *nreturn_vals = 1;
238   *return_vals  = values;
239 
240   values[0].type          = GIMP_PDB_STATUS;
241   values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR;
242 
243   if (strcmp (name, LOAD_PROC) == 0)
244     {
245       switch (run_mode)
246         {
247         case GIMP_RUN_NONINTERACTIVE:
248           /*
249            * check for valid parameters:
250            * (Or can I trust GIMP ?)
251            */
252           if ((nparams < G_N_ELEMENTS (load_args) - 2) ||
253               (G_N_ELEMENTS (load_args) < nparams))
254             {
255               status = GIMP_PDB_CALLING_ERROR;
256               break;
257             }
258           for (pc = 0; pc < G_N_ELEMENTS (load_args) - 2; pc++)
259             {
260               if (load_args[pc].type != param[pc].type)
261                 {
262                   status = GIMP_PDB_CALLING_ERROR;
263                   break;
264                 }
265             }
266           for (pc = G_N_ELEMENTS (load_args) - 2; pc < nparams; pc++)
267             {
268               if (load_args[pc].type != param[pc].type)
269                 {
270                   status = GIMP_PDB_CALLING_ERROR;
271                   break;
272                 }
273             }
274 
275           to_frame   = ((nparams < G_N_ELEMENTS (load_args) - 1) ?
276                         1 : param[3].data.d_int32);
277           from_frame = ((nparams < G_N_ELEMENTS (load_args)) ?
278                         -1 : param[4].data.d_int32);
279 
280           image_ID = load_image (param[1].data.d_string,
281                                  from_frame, to_frame, &error);
282 
283           if (image_ID != -1)
284             {
285               *nreturn_vals = 2;
286               values[1].type         = GIMP_PDB_IMAGE;
287               values[1].data.d_image = image_ID;
288             }
289           else
290             {
291               status = GIMP_PDB_EXECUTION_ERROR;
292             }
293           break;
294 
295         case GIMP_RUN_INTERACTIVE:
296           if (load_dialog (param[1].data.d_string))
297             {
298               image_ID = load_image (param[1].data.d_string,
299                                      from_frame, to_frame, &error);
300 
301               if (image_ID != -1)
302                 {
303                   *nreturn_vals = 2;
304                   values[1].type         = GIMP_PDB_IMAGE;
305                   values[1].data.d_image = image_ID;
306                 }
307               else
308                 {
309                   status = GIMP_PDB_EXECUTION_ERROR;
310                 }
311             }
312           else
313             {
314               status = GIMP_PDB_CANCEL;
315             }
316           break;
317 
318         case GIMP_RUN_WITH_LAST_VALS:
319           status = GIMP_PDB_CALLING_ERROR;
320           break;
321         }
322     }
323   else if (strcmp (name, SAVE_PROC) == 0)
324     {
325       image_ID    = orig_image_ID = param[1].data.d_int32;
326       drawable_ID = param[2].data.d_int32;
327 
328       switch (run_mode)
329         {
330         case GIMP_RUN_NONINTERACTIVE:
331           if (nparams != G_N_ELEMENTS (save_args))
332             {
333               status = GIMP_PDB_CALLING_ERROR;
334               break;
335             }
336           for (pc = 0; pc < G_N_ELEMENTS (save_args); pc++)
337             {
338               if (save_args[pc].type!=param[pc].type)
339                 {
340                   status = GIMP_PDB_CALLING_ERROR;
341                   break;
342                 }
343             }
344           if (! save_image (param[3].data.d_string, image_ID,
345                             param[5].data.d_int32,
346                             param[6].data.d_int32, &error))
347             {
348               status = GIMP_PDB_EXECUTION_ERROR;
349             }
350           break;
351 
352         case GIMP_RUN_INTERACTIVE:
353         case GIMP_RUN_WITH_LAST_VALS:
354           gimp_ui_init (PLUG_IN_BINARY, FALSE);
355 
356           export = gimp_export_image (&image_ID, &drawable_ID, "FLI",
357                                       GIMP_EXPORT_CAN_HANDLE_INDEXED |
358                                       GIMP_EXPORT_CAN_HANDLE_GRAY    |
359                                       GIMP_EXPORT_CAN_HANDLE_ALPHA   |
360                                       GIMP_EXPORT_CAN_HANDLE_LAYERS);
361 
362           if (export == GIMP_EXPORT_CANCEL)
363             {
364               values[0].data.d_status = GIMP_PDB_CANCEL;
365               return;
366             }
367 
368           if (save_dialog (param[1].data.d_image))
369             {
370               if (! save_image (param[3].data.d_string,
371                                 image_ID, from_frame, to_frame, &error))
372                 {
373                   status = GIMP_PDB_EXECUTION_ERROR;
374                 }
375             }
376           else
377             {
378               status = GIMP_PDB_CANCEL;
379             }
380           break;
381         }
382 
383       if (export == GIMP_EXPORT_EXPORT)
384         gimp_image_delete (image_ID);
385     }
386   else if (strcmp (name, INFO_PROC) == 0)
387     {
388       gint32 width, height, frames;
389 
390       /*
391        * check for valid parameters;
392        */
393       if (nparams != G_N_ELEMENTS (info_args))
394         status = GIMP_PDB_CALLING_ERROR;
395 
396       if (status == GIMP_PDB_SUCCESS)
397         {
398           for (pc = 0; pc < G_N_ELEMENTS (save_args); pc++)
399             {
400               if (info_args[pc].type != param[pc].type)
401                 {
402                   status = GIMP_PDB_CALLING_ERROR;
403                   break;
404                 }
405             }
406         }
407 
408       if (status == GIMP_PDB_SUCCESS)
409         {
410           if (get_info (param[0].data.d_string,
411                         &width, &height, &frames, &error))
412             {
413               *nreturn_vals = 4;
414               values[1].type = GIMP_PDB_INT32;
415               values[1].data.d_int32 = width;
416               values[2].type = GIMP_PDB_INT32;
417               values[2].data.d_int32 = height;
418               values[3].type = GIMP_PDB_INT32;
419               values[3].data.d_int32 = frames;
420             }
421           else
422             {
423               status = GIMP_PDB_EXECUTION_ERROR;
424             }
425         }
426     }
427   else
428     {
429       status = GIMP_PDB_CALLING_ERROR;
430     }
431 
432   if (status != GIMP_PDB_SUCCESS && error)
433     {
434       *nreturn_vals = 2;
435       values[1].type          = GIMP_PDB_STRING;
436       values[1].data.d_string = error->message;
437     }
438 
439   values[0].data.d_status = status;
440 }
441 
442 /*
443  * Open FLI animation and return header-info
444  */
445 static gboolean
get_info(const gchar * filename,gint32 * width,gint32 * height,gint32 * frames,GError ** error)446 get_info (const gchar  *filename,
447           gint32       *width,
448           gint32       *height,
449           gint32       *frames,
450           GError      **error)
451 {
452   FILE *file;
453   s_fli_header fli_header;
454 
455   *width = 0; *height = 0; *frames = 0;
456 
457   file = g_fopen (filename ,"rb");
458 
459   if (!file)
460     {
461       g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
462                    _("Could not open '%s' for reading: %s"),
463                    gimp_filename_to_utf8 (filename), g_strerror (errno));
464       return FALSE;
465     }
466 
467   fli_read_header (file, &fli_header);
468   fclose (file);
469 
470   *width  = fli_header.width;
471   *height = fli_header.height;
472   *frames = fli_header.frames;
473 
474   return TRUE;
475 }
476 
477 /*
478  * load fli animation and store as framestack
479  */
480 static gint32
load_image(const gchar * filename,gint32 from_frame,gint32 to_frame,GError ** error)481 load_image (const gchar  *filename,
482             gint32        from_frame,
483             gint32        to_frame,
484             GError      **error)
485 {
486   FILE         *file;
487   GeglBuffer   *buffer;
488   gint32        image_id, layer_ID;
489   guchar       *fb, *ofb, *fb_x;
490   guchar        cm[768], ocm[768];
491   s_fli_header  fli_header;
492   gint          cnt;
493 
494   gimp_progress_init_printf (_("Opening '%s'"),
495                              gimp_filename_to_utf8 (filename));
496 
497   file = g_fopen (filename ,"rb");
498   if (!file)
499     {
500       g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
501                    _("Could not open '%s' for reading: %s"),
502                    gimp_filename_to_utf8 (filename), g_strerror (errno));
503       return -1;
504     }
505 
506   fli_read_header (file, &fli_header);
507   if (fli_header.magic == NO_HEADER)
508     {
509       fclose (file);
510       return -1;
511     }
512   else
513     {
514       fseek (file, 128, SEEK_SET);
515     }
516 
517   /*
518    * Fix parameters
519    */
520   if ((from_frame==-1) && (to_frame==-1))
521     {
522       /* to make scripting easier: */
523       from_frame=1; to_frame=fli_header.frames;
524     }
525   if (to_frame<from_frame)
526     {
527       to_frame = fli_header.frames;
528     }
529   if (from_frame < 1)
530     {
531       from_frame = 1;
532     }
533   if (to_frame < 1)
534     {
535       /* nothing to do ... */
536       fclose (file);
537       return -1;
538     }
539   if (from_frame >= fli_header.frames)
540     {
541       /* nothing to do ... */
542       fclose (file);
543       return -1;
544     }
545   if (to_frame>fli_header.frames)
546     {
547       to_frame = fli_header.frames;
548     }
549 
550   image_id = gimp_image_new (fli_header.width, fli_header.height, GIMP_INDEXED);
551   gimp_image_set_filename (image_id, filename);
552 
553   fb = g_malloc (fli_header.width * fli_header.height);
554   ofb = g_malloc (fli_header.width * fli_header.height);
555 
556   /*
557    * Skip to the beginning of requested frames:
558    */
559   for (cnt = 1; cnt < from_frame; cnt++)
560     {
561       fli_read_frame (file, &fli_header, ofb, ocm, fb, cm);
562       memcpy (ocm, cm, 768);
563       fb_x = fb; fb = ofb; ofb = fb_x;
564     }
565   /*
566    * Load range
567    */
568   for (cnt = from_frame; cnt <= to_frame; cnt++)
569     {
570       gchar *name_buf = g_strdup_printf (_("Frame (%i)"), cnt);
571 
572       layer_ID = gimp_layer_new (image_id, name_buf,
573                                  fli_header.width, fli_header.height,
574                                  GIMP_INDEXED_IMAGE,
575                                  100,
576                                  gimp_image_get_default_new_layer_mode (image_id));
577       g_free (name_buf);
578 
579       buffer = gimp_drawable_get_buffer (layer_ID);
580 
581       fli_read_frame (file, &fli_header, ofb, ocm, fb, cm);
582 
583       gegl_buffer_set (buffer, GEGL_RECTANGLE (0, 0,
584                                                fli_header.width,
585                                                fli_header.height), 0,
586                        NULL, fb, GEGL_AUTO_ROWSTRIDE);
587 
588       g_object_unref (buffer);
589 
590       if (cnt > 0)
591         gimp_layer_add_alpha (layer_ID);
592 
593       gimp_image_insert_layer (image_id, layer_ID, -1, 0);
594 
595       if (cnt < to_frame)
596         {
597           memcpy (ocm, cm, 768);
598           fb_x = fb; fb = ofb; ofb = fb_x;
599         }
600 
601       gimp_progress_update ((double) cnt + 1 / (double)(to_frame - from_frame));
602     }
603 
604   gimp_image_set_colormap (image_id, cm, 256);
605 
606   fclose (file);
607 
608   g_free (fb);
609   g_free (ofb);
610 
611   gimp_progress_update (1.0);
612 
613   return image_id;
614 }
615 
616 
617 #define MAXDIFF 195075    /*  3 * SQR (255) + 1  */
618 
619 /*
620  * get framestack and store as fli animation
621  * (some code was taken from the GIF plugin.)
622  */
623 static gboolean
save_image(const gchar * filename,gint32 image_id,gint32 from_frame,gint32 to_frame,GError ** error)624 save_image (const gchar  *filename,
625             gint32        image_id,
626             gint32        from_frame,
627             gint32        to_frame,
628             GError      **error)
629 {
630   FILE         *file;
631   gint32       *framelist;
632   gint          nframes;
633   gint          colors, i;
634   guchar       *cmap;
635   guchar        bg;
636   guchar        red, green, blue;
637   gint          diff, sum, max;
638   gint          offset_x, offset_y, xc, yc, xx, yy;
639   guint         rows, cols, bytes;
640   guchar       *src_row;
641   guchar       *fb, *ofb;
642   guchar        cm[768];
643   GimpRGB       background;
644   s_fli_header  fli_header;
645   gint          cnt;
646 
647   framelist = gimp_image_get_layers (image_id, &nframes);
648 
649   if ((from_frame == -1) && (to_frame == -1))
650     {
651       /* to make scripting easier: */
652       from_frame = 0; to_frame = nframes;
653     }
654   if (to_frame < from_frame)
655     {
656       to_frame = nframes;
657     }
658   if (from_frame < 1)
659     {
660       from_frame = 1;
661     }
662   if (to_frame < 1)
663     {
664       /* nothing to do ... */
665       return FALSE;
666     }
667   if (from_frame > nframes)
668     {
669       /* nothing to do ... */
670       return FALSE;
671     }
672   if (to_frame > nframes)
673     {
674       to_frame = nframes;
675     }
676 
677   gimp_context_get_background (&background);
678   gimp_rgb_get_uchar (&background, &red, &green, &blue);
679 
680   switch (gimp_image_base_type (image_id))
681     {
682     case GIMP_GRAY:
683       /* build grayscale palette */
684       for (i = 0; i < 256; i++)
685         {
686           cm[i*3+0] = cm[i*3+1] = cm[i*3+2] = i;
687         }
688       bg = GIMP_RGB_LUMINANCE (red, green, blue) + 0.5;
689       break;
690 
691     case GIMP_INDEXED:
692       max = MAXDIFF;
693       bg = 0;
694       cmap = gimp_image_get_colormap (image_id, &colors);
695       for (i = 0; i < MIN (colors, 256); i++)
696         {
697           cm[i*3+0] = cmap[i*3+0];
698           cm[i*3+1] = cmap[i*3+1];
699           cm[i*3+2] = cmap[i*3+2];
700 
701           diff = red - cm[i*3+0];
702           sum = SQR (diff);
703           diff = green - cm[i*3+1];
704           sum +=  SQR (diff);
705           diff = blue - cm[i*3+2];
706           sum += SQR (diff);
707 
708           if (sum < max)
709             {
710               bg = i;
711               max = sum;
712             }
713         }
714       for (i = colors; i < 256; i++)
715         {
716           cm[i*3+0] = cm[i*3+1] = cm[i*3+2] = i;
717         }
718       break;
719 
720     default:
721       g_message (_("Sorry, I can export only INDEXED and GRAY images."));
722       return FALSE;
723     }
724 
725   gimp_progress_init_printf (_("Exporting '%s'"),
726                              gimp_filename_to_utf8 (filename));
727 
728   /*
729    * First build the fli header.
730    */
731   fli_header.filesize = 0;  /* will be fixed when writing the header */
732   fli_header.frames   = 0;  /* will be fixed during the write */
733   fli_header.width    = gimp_image_width (image_id);
734   fli_header.height   = gimp_image_height (image_id);
735 
736   if ((fli_header.width == 320) && (fli_header.height == 200))
737     {
738       fli_header.magic = HEADER_FLI;
739     }
740   else
741     {
742       fli_header.magic = HEADER_FLC;
743     }
744   fli_header.depth    = 8;  /* I've never seen a depth != 8 */
745   fli_header.flags    = 3;
746   fli_header.speed    = 1000 / 25;
747   fli_header.created  = 0;  /* program ID. not necessary... */
748   fli_header.updated  = 0;  /* date in MS-DOS format. ignore...*/
749   fli_header.aspect_x = 1;  /* aspect ratio. Will be added as soon.. */
750   fli_header.aspect_y = 1;  /* ... as GIMP supports it. */
751   fli_header.oframe1  = fli_header.oframe2 = 0; /* will be fixed during the write */
752 
753   file = g_fopen (filename ,"wb");
754   if (!file)
755     {
756       g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
757                    _("Could not open '%s' for writing: %s"),
758                    gimp_filename_to_utf8 (filename), g_strerror (errno));
759       return FALSE;
760     }
761   fseek (file, 128, SEEK_SET);
762 
763   fb = g_malloc (fli_header.width * fli_header.height);
764   ofb = g_malloc (fli_header.width * fli_header.height);
765 
766   /* initialize with bg color */
767   memset (fb, bg, fli_header.width * fli_header.height);
768 
769   /*
770    * Now write all frames
771    */
772   for (cnt = from_frame; cnt <= to_frame; cnt++)
773     {
774       GeglBuffer *buffer;
775       const Babl *format = NULL;
776 
777       buffer = gimp_drawable_get_buffer (framelist[nframes-cnt]);
778 
779       if (gimp_drawable_is_gray (framelist[nframes-cnt]))
780         {
781           if (gimp_drawable_has_alpha (framelist[nframes-cnt]))
782             format = babl_format ("Y' u8");
783           else
784             format = babl_format ("Y'A u8");
785         }
786       else
787         {
788           format = gegl_buffer_get_format (buffer);
789         }
790 
791       cols = gegl_buffer_get_width  (buffer);
792       rows = gegl_buffer_get_height (buffer);
793 
794       gimp_drawable_offsets (framelist[nframes-cnt], &offset_x, &offset_y);
795 
796       bytes = babl_format_get_bytes_per_pixel (format);
797 
798       src_row = g_malloc (cols * bytes);
799 
800       /* now paste it into the framebuffer, with the necessary offset */
801       for (yc = 0, yy = offset_y; yc < rows; yc++, yy++)
802         {
803           if (yy >= 0 && yy < fli_header.height)
804             {
805               gegl_buffer_get (buffer, GEGL_RECTANGLE (0, yc, cols, 1), 1.0,
806                                format, src_row,
807                                GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
808 
809               for (xc = 0, xx = offset_x; xc < cols; xc++, xx++)
810                 {
811                   if (xx >= 0 && xx < fli_header.width)
812                     fb[yy * fli_header.width + xx] = src_row[xc * bytes];
813                 }
814             }
815         }
816 
817       g_free (src_row);
818       g_object_unref (buffer);
819 
820       /* save the frame */
821       if (cnt > from_frame)
822         {
823           /* save frame, allow all codecs */
824           fli_write_frame (file, &fli_header, ofb, cm, fb, cm, W_ALL);
825         }
826       else
827         {
828           /* save first frame, no delta information, allow all codecs */
829           fli_write_frame (file, &fli_header, NULL, NULL, fb, cm, W_ALL);
830         }
831 
832       if (cnt < to_frame)
833         memcpy (ofb, fb, fli_header.width * fli_header.height);
834 
835       gimp_progress_update ((double) cnt + 1 / (double)(to_frame - from_frame));
836     }
837 
838   /*
839    * finish fli
840    */
841   fli_write_header (file, &fli_header);
842   fclose (file);
843 
844   g_free (fb);
845   g_free (ofb);
846   g_free (framelist);
847 
848   gimp_progress_update (1.0);
849 
850   return TRUE;
851 }
852 
853 /*
854  * Dialogs for interactive usage
855  */
856 static gboolean
load_dialog(const gchar * filename)857 load_dialog (const gchar *filename)
858 {
859   GtkWidget     *dialog;
860   GtkWidget     *table;
861   GtkWidget     *spinbutton;
862   GtkAdjustment *adj;
863   gint32         width, height, nframes;
864   gboolean       run;
865 
866   get_info (filename, &width, &height, &nframes, NULL);
867 
868   from_frame = 1;
869   to_frame   = nframes;
870 
871   gimp_ui_init (PLUG_IN_BINARY, FALSE);
872 
873   dialog = gimp_dialog_new (_("GFLI 1.3 - Load framestack"), PLUG_IN_ROLE,
874                             NULL, 0,
875                             gimp_standard_help_func, LOAD_PROC,
876 
877                             _("_Cancel"), GTK_RESPONSE_CANCEL,
878                             _("_Open"),   GTK_RESPONSE_OK,
879 
880                             NULL);
881 
882   gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
883                                            GTK_RESPONSE_OK,
884                                            GTK_RESPONSE_CANCEL,
885                                            -1);
886 
887   table = gtk_table_new (2, 2, FALSE);
888   gtk_container_set_border_width (GTK_CONTAINER (table), 12);
889   gtk_table_set_row_spacings (GTK_TABLE (table), 6);
890   gtk_table_set_col_spacings (GTK_TABLE (table), 6);
891   gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
892                       table, FALSE, FALSE, 0);
893   gtk_widget_show (table);
894 
895   /*
896    * Maybe I add on-the-fly RGB conversion, to keep palettechanges...
897    * But for now you can set a start- and a end-frame:
898    */
899   adj = (GtkAdjustment *) gtk_adjustment_new (from_frame, 1, nframes, 1, 10, 0);
900   spinbutton = gimp_spin_button_new (adj, 1, 0);
901   gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE);
902   gimp_table_attach_aligned (GTK_TABLE (table), 0, 0,
903                              C_("frame-range", "_From:"), 0.0, 0.5,
904                              spinbutton, 1, TRUE);
905   g_signal_connect (adj, "value-changed",
906                     G_CALLBACK (gimp_int_adjustment_update),
907                     &from_frame);
908 
909   adj = (GtkAdjustment *) gtk_adjustment_new (to_frame, 1, nframes, 1, 10, 0);
910   spinbutton = gimp_spin_button_new (adj, 1, 0);
911   gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE);
912   gimp_table_attach_aligned (GTK_TABLE (table), 0, 1,
913                              C_("frame-range", "_To:"), 0.0, 0.5,
914                              spinbutton, 1, TRUE);
915   g_signal_connect (adj, "value-changed",
916                     G_CALLBACK (gimp_int_adjustment_update),
917                     &to_frame);
918 
919   gtk_widget_show (dialog);
920 
921   run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK);
922 
923   gtk_widget_destroy (dialog);
924 
925   return run;
926 }
927 
928 static gboolean
save_dialog(gint32 image_id)929 save_dialog (gint32 image_id)
930 {
931   GtkWidget     *dialog;
932   GtkWidget     *table;
933   GtkWidget     *spinbutton;
934   GtkAdjustment *adj;
935   gint           nframes;
936   gboolean       run;
937 
938   g_free (gimp_image_get_layers (image_id, &nframes));
939 
940   from_frame = 1;
941   to_frame   = nframes;
942 
943   dialog = gimp_export_dialog_new (_("GFLI 1.3"), PLUG_IN_BINARY, SAVE_PROC);
944 
945   table = gtk_table_new (2, 2, FALSE);
946   gtk_container_set_border_width (GTK_CONTAINER (table), 12);
947   gtk_table_set_row_spacings (GTK_TABLE (table), 6);
948   gtk_table_set_col_spacings (GTK_TABLE (table), 6);
949   gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)),
950                       table, FALSE, FALSE, 0);
951   gtk_widget_show (table);
952 
953   /*
954    * Maybe I add on-the-fly RGB conversion, to keep palettechanges...
955    * But for now you can set a start- and a end-frame:
956    */
957   adj = (GtkAdjustment *) gtk_adjustment_new (from_frame, 1, nframes, 1, 10, 0);
958   spinbutton = gimp_spin_button_new (adj, 1, 0);
959   gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE);
960   gimp_table_attach_aligned (GTK_TABLE (table), 0, 0,
961                              C_("frame-range", "_From:"), 0.0, 0.5,
962                              spinbutton, 1, TRUE);
963   g_signal_connect (adj, "value-changed",
964                     G_CALLBACK (gimp_int_adjustment_update),
965                     &from_frame);
966 
967   adj = (GtkAdjustment *) gtk_adjustment_new (to_frame, 1, nframes, 1, 10, 0);
968   spinbutton = gimp_spin_button_new (adj, 1, 0);
969   gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE);
970   gimp_table_attach_aligned (GTK_TABLE (table), 0, 1,
971                              C_("frame-range", "_To:"), 0.0, 0.5,
972                              spinbutton, 1, TRUE);
973   g_signal_connect (adj, "value-changed",
974                     G_CALLBACK (gimp_int_adjustment_update),
975                     &to_frame);
976 
977   gtk_widget_show (dialog);
978 
979   run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK);
980 
981   gtk_widget_destroy (dialog);
982 
983   return run;
984 }
985