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