1 /*
2 * Animation Optimizer plug-in version 1.1.2
3 *
4 * (c) Adam D. Moss, 1997-2003
5 * adam@gimp.org
6 * adam@foxbox.org
7 *
8 * GIMP - The GNU Image Manipulation Program
9 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
10 *
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program. If not, see <https://www.gnu.org/licenses/>.
23 */
24
25 /*
26 #define EXPERIMENTAL_BACKDROP_CODE
27 */
28
29
30 #include "config.h"
31
32 #include <string.h>
33
34 #include <libgimp/gimp.h>
35
36 #include "libgimp/stdplugins-intl.h"
37
38
39 #define OPTIMIZE_PROC "plug-in-animationoptimize"
40 #define OPTIMIZE_DIFF_PROC "plug-in-animationoptimize-diff"
41 #define UNOPTIMIZE_PROC "plug-in-animationunoptimize"
42 #define REMOVE_BACKDROP_PROC "plug-in-animation-remove-backdrop"
43 #define FIND_BACKDROP_PROC "plug-in-animation-find-backdrop"
44
45
46 typedef enum
47 {
48 DISPOSE_UNDEFINED = 0x00,
49 DISPOSE_COMBINE = 0x01,
50 DISPOSE_REPLACE = 0x02
51 } DisposeType;
52
53
54 typedef enum
55 {
56 OPOPTIMIZE = 0L,
57 OPUNOPTIMIZE = 1L,
58 OPFOREGROUND = 2L,
59 OPBACKGROUND = 3L
60 } operatingMode;
61
62
63 /* Declare local functions. */
64 static void query (void);
65 static void run (const gchar *name,
66 gint nparams,
67 const GimpParam *param,
68 gint *nreturn_vals,
69 GimpParam **return_vals);
70
71 static gint32 do_optimizations (GimpRunMode run_mode,
72 gboolean diff_only);
73
74 /* tag util functions*/
75 static gint parse_ms_tag (const gchar *str);
76 static DisposeType parse_disposal_tag (const gchar *str);
77 static DisposeType get_frame_disposal (guint whichframe);
78 static guint32 get_frame_duration (guint whichframe);
79 static void remove_disposal_tag (gchar *dest,
80 gchar *src);
81 static void remove_ms_tag (gchar *dest,
82 gchar *src);
83 static gboolean is_disposal_tag (const gchar *str,
84 DisposeType *disposal,
85 gint *taglength);
86 static gboolean is_ms_tag (const gchar *str,
87 gint *duration,
88 gint *taglength);
89
90
91 const GimpPlugInInfo PLUG_IN_INFO =
92 {
93 NULL, /* init_proc */
94 NULL, /* quit_proc */
95 query, /* query_proc */
96 run, /* run_proc */
97 };
98
99
100 /* Global widgets'n'stuff */
101 static guint width, height;
102 static gint32 image_id;
103 static gint32 new_image_id;
104 static gint32 total_frames;
105 static gint32 *layers;
106 static GimpImageBaseType imagetype;
107 static GimpImageType drawabletype_alpha;
108 static guchar pixelstep;
109 static guchar *palette;
110 static gint ncolors;
111 static operatingMode opmode;
112
113
MAIN()114 MAIN ()
115
116 static void
117 query (void)
118 {
119 static const GimpParamDef args[] =
120 {
121 { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },
122 { GIMP_PDB_IMAGE, "image", "Input image" },
123 { GIMP_PDB_DRAWABLE, "drawable", "Input drawable (unused)" }
124 };
125 static const GimpParamDef return_args[] =
126 {
127 { GIMP_PDB_IMAGE, "result", "Resulting image" }
128 };
129
130 gimp_install_procedure (OPTIMIZE_PROC,
131 N_("Modify image to reduce size when saved as GIF animation"),
132 "This procedure applies various optimizations to"
133 " a GIMP layer-based animation in an attempt to"
134 " reduce the final file size. If a frame of the"
135 " animation can use the 'combine' mode, this"
136 " procedure attempts to maximize the number of"
137 " ajdacent pixels having the same color, which"
138 " improves the compression for some image formats"
139 " such as GIF or MNG.",
140 "Adam D. Moss <adam@gimp.org>",
141 "Adam D. Moss <adam@gimp.org>",
142 "1997-2003",
143 N_("Optimize (for _GIF)"),
144 "RGB*, INDEXED*, GRAY*",
145 GIMP_PLUGIN,
146 G_N_ELEMENTS (args),
147 G_N_ELEMENTS (return_args),
148 args, return_args);
149
150 gimp_install_procedure (OPTIMIZE_DIFF_PROC,
151 N_("Reduce file size where combining layers is possible"),
152 "This procedure applies various optimizations to"
153 " a GIMP layer-based animation in an attempt to"
154 " reduce the final file size. If a frame of the"
155 " animation can use the 'combine' mode, this"
156 " procedure uses a simple difference between the"
157 " frames.",
158 "Adam D. Moss <adam@gimp.org>",
159 "Adam D. Moss <adam@gimp.org>",
160 "1997-2001",
161 N_("_Optimize (Difference)"),
162 "RGB*, INDEXED*, GRAY*",
163 GIMP_PLUGIN,
164 G_N_ELEMENTS (args),
165 G_N_ELEMENTS (return_args),
166 args, return_args);
167
168 gimp_install_procedure (UNOPTIMIZE_PROC,
169 N_("Remove optimization to make editing easier"),
170 "This procedure 'simplifies' a GIMP layer-based"
171 " animation that has been optimized for animation. "
172 "This makes editing the animation much easier.",
173 "Adam D. Moss <adam@gimp.org>",
174 "Adam D. Moss <adam@gimp.org>",
175 "1997-2001",
176 N_("_Unoptimize"),
177 "RGB*, INDEXED*, GRAY*",
178 GIMP_PLUGIN,
179 G_N_ELEMENTS (args),
180 G_N_ELEMENTS (return_args),
181 args, return_args);
182
183 gimp_plugin_menu_register (OPTIMIZE_PROC, "<Image>/Filters/Animation");
184 gimp_plugin_menu_register (OPTIMIZE_DIFF_PROC, "<Image>/Filters/Animation");
185 gimp_plugin_menu_register (UNOPTIMIZE_PROC, "<Image>/Filters/Animation");
186
187 #ifdef EXPERIMENTAL_BACKDROP_CODE
188 gimp_install_procedure (REMOVE_BACKDROP_PROC,
189 "This procedure attempts to remove the backdrop"
190 " from a GIMP layer-based animation, leaving"
191 " the foreground animation over transparency.",
192 "",
193 "Adam D. Moss <adam@gimp.org>",
194 "Adam D. Moss <adam@gimp.org>",
195 "2001",
196 N_("_Remove Backdrop"),
197 "RGB*, INDEXED*, GRAY*",
198 GIMP_PLUGIN,
199 G_N_ELEMENTS (args),
200 G_N_ELEMENTS (return_args),
201 args, return_args);
202
203 gimp_install_procedure (FIND_BACKDROP_PROC,
204 "This procedure attempts to remove the foreground"
205 " from a GIMP layer-based animation, leaving"
206 " a one-layered image containing only the"
207 " constant backdrop image.",
208 "",
209 "Adam D. Moss <adam@gimp.org>",
210 "Adam D. Moss <adam@gimp.org>",
211 "2001",
212 N_("_Find Backdrop"),
213 "RGB*, INDEXED*, GRAY*",
214 GIMP_PLUGIN,
215 G_N_ELEMENTS (args),
216 G_N_ELEMENTS (return_args),
217 args, return_args);
218
219 gimp_plugin_menu_register (REMOVE_BACKDROP_PROC, "<Image>/Filters/Animation");
220 gimp_plugin_menu_register (FIND_BACKDROP_PROC, "<Image>/Filters/Animation");
221 #endif
222 }
223
224 static void
run(const gchar * name,gint n_params,const GimpParam * param,gint * nreturn_vals,GimpParam ** return_vals)225 run (const gchar *name,
226 gint n_params,
227 const GimpParam *param,
228 gint *nreturn_vals,
229 GimpParam **return_vals)
230 {
231 static GimpParam values[2];
232 GimpRunMode run_mode;
233 GimpPDBStatusType status = GIMP_PDB_SUCCESS;
234 gboolean diff_only = FALSE;
235
236 *nreturn_vals = 2;
237 *return_vals = values;
238
239 run_mode = param[0].data.d_int32;
240
241 INIT_I18N ();
242 gegl_init (NULL, NULL);
243
244 if (run_mode == GIMP_RUN_NONINTERACTIVE && n_params != 3)
245 {
246 status = GIMP_PDB_CALLING_ERROR;
247 }
248
249 /* Check the procedure name we were called with, to decide
250 what needs to be done. */
251 if (strcmp (name, OPTIMIZE_PROC) == 0)
252 opmode = OPOPTIMIZE;
253 else if (strcmp (name, OPTIMIZE_DIFF_PROC) == 0)
254 {
255 opmode = OPOPTIMIZE;
256 diff_only = TRUE;
257 }
258 else if (strcmp (name, UNOPTIMIZE_PROC) == 0)
259 opmode = OPUNOPTIMIZE;
260 else if (strcmp (name, FIND_BACKDROP_PROC) == 0)
261 opmode = OPBACKGROUND;
262 else if (strcmp (name, REMOVE_BACKDROP_PROC) == 0)
263 opmode = OPFOREGROUND;
264 else
265 g_error("GAH!!!");
266
267 if (status == GIMP_PDB_SUCCESS)
268 {
269 image_id = param[1].data.d_image;
270
271 new_image_id = do_optimizations (run_mode, diff_only);
272
273 if (run_mode != GIMP_RUN_NONINTERACTIVE)
274 gimp_displays_flush();
275 }
276
277 values[0].type = GIMP_PDB_STATUS;
278 values[0].data.d_status = status;
279
280 values[1].type = GIMP_PDB_IMAGE;
281 values[1].data.d_image = new_image_id;
282 }
283
284
285
286 /* Rendering Functions */
287
288 static void
total_alpha(guchar * imdata,guint32 numpix,guchar bytespp)289 total_alpha (guchar *imdata,
290 guint32 numpix,
291 guchar bytespp)
292 {
293 /* Set image to total-transparency w/black
294 */
295
296 memset (imdata, 0, numpix * bytespp);
297 }
298
299 static const Babl *
get_format(gint32 drawable_ID)300 get_format (gint32 drawable_ID)
301 {
302 if (gimp_drawable_is_rgb (drawable_ID))
303 {
304 if (gimp_drawable_has_alpha (drawable_ID))
305 return babl_format ("R'G'B'A u8");
306 else
307 return babl_format ("R'G'B' u8");
308 }
309 else if (gimp_drawable_is_gray (drawable_ID))
310 {
311 if (gimp_drawable_has_alpha (drawable_ID))
312 return babl_format ("Y'A u8");
313 else
314 return babl_format ("Y' u8");
315 }
316
317 return gimp_drawable_get_format (drawable_ID);
318 }
319
320 static void
compose_row(gint frame_num,DisposeType dispose,gint row_num,guchar * dest,gint dest_width,gint32 drawable_ID,gboolean cleanup)321 compose_row (gint frame_num,
322 DisposeType dispose,
323 gint row_num,
324 guchar *dest,
325 gint dest_width,
326 gint32 drawable_ID,
327 gboolean cleanup)
328 {
329 static guchar *line_buf = NULL;
330 GeglBuffer *src_buffer;
331 const Babl *format;
332 guchar *srcptr;
333 gint rawx, rawy, rawbpp, rawwidth, rawheight;
334 gint i;
335 gboolean has_alpha;
336
337 if (cleanup)
338 {
339 if (line_buf)
340 {
341 g_free (line_buf);
342 line_buf = NULL;
343 }
344
345 return;
346 }
347
348 if (dispose == DISPOSE_REPLACE)
349 {
350 total_alpha (dest, dest_width, pixelstep);
351 }
352
353 gimp_drawable_offsets (drawable_ID, &rawx, &rawy);
354
355 rawwidth = gimp_drawable_width (drawable_ID);
356 rawheight = gimp_drawable_height (drawable_ID);
357
358 /* this frame has nothing to give us for this row; return */
359 if (row_num >= rawheight + rawy ||
360 row_num < rawy)
361 return;
362
363 format = get_format (drawable_ID);
364
365 has_alpha = gimp_drawable_has_alpha (drawable_ID);
366 rawbpp = babl_format_get_bytes_per_pixel (format);
367
368 if (line_buf)
369 {
370 g_free (line_buf);
371 line_buf = NULL;
372 }
373 line_buf = g_malloc (rawwidth * rawbpp);
374
375 /* Initialise and fetch the raw new frame row */
376
377 src_buffer = gimp_drawable_get_buffer (drawable_ID);
378
379 gegl_buffer_get (src_buffer, GEGL_RECTANGLE (0, row_num - rawy,
380 rawwidth, 1), 1.0,
381 format, line_buf,
382 GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
383
384 g_object_unref (src_buffer);
385
386 /* render... */
387
388 srcptr = line_buf;
389
390 for (i=rawx; i<rawwidth+rawx; i++)
391 {
392 if (i>=0 && i<dest_width)
393 {
394 if ((!has_alpha) || ((*(srcptr+rawbpp-1))&128))
395 {
396 gint pi;
397
398 for (pi = 0; pi < pixelstep-1; pi++)
399 {
400 dest[i*pixelstep +pi] = *(srcptr + pi);
401 }
402
403 dest[i*pixelstep + pixelstep - 1] = 255;
404 }
405 }
406
407 srcptr += rawbpp;
408 }
409 }
410
411
412 static gint32
do_optimizations(GimpRunMode run_mode,gboolean diff_only)413 do_optimizations (GimpRunMode run_mode,
414 gboolean diff_only)
415 {
416 static guchar *rawframe = NULL;
417 guchar *srcptr;
418 guchar *destptr;
419 gint row, this_frame_num;
420 guint32 frame_sizebytes;
421 gint32 new_layer_id;
422 DisposeType dispose;
423 guchar *this_frame = NULL;
424 guchar *last_frame = NULL;
425 guchar *opti_frame = NULL;
426 guchar *back_frame = NULL;
427
428 gint this_delay;
429 gint cumulated_delay = 0;
430 gint last_true_frame = -1;
431 gint buflen;
432
433 gchar *oldlayer_name;
434 gchar *newlayer_name;
435
436 gboolean can_combine;
437
438 gint32 bbox_top, bbox_bottom, bbox_left, bbox_right;
439 gint32 rbox_top, rbox_bottom, rbox_left, rbox_right;
440
441 switch (opmode)
442 {
443 case OPUNOPTIMIZE:
444 gimp_progress_init (_("Unoptimizing animation"));
445 break;
446 case OPFOREGROUND:
447 gimp_progress_init (_("Removing animation background"));
448 break;
449 case OPBACKGROUND:
450 gimp_progress_init (_("Finding animation background"));
451 break;
452 case OPOPTIMIZE:
453 default:
454 gimp_progress_init (_("Optimizing animation"));
455 break;
456 }
457
458 width = gimp_image_width (image_id);
459 height = gimp_image_height (image_id);
460 layers = gimp_image_get_layers (image_id, &total_frames);
461 imagetype = gimp_image_base_type (image_id);
462 pixelstep = (imagetype == GIMP_RGB) ? 4 : 2;
463
464 drawabletype_alpha = (imagetype == GIMP_RGB) ? GIMP_RGBA_IMAGE :
465 ((imagetype == GIMP_INDEXED) ? GIMP_INDEXEDA_IMAGE : GIMP_GRAYA_IMAGE);
466
467 frame_sizebytes = width * height * pixelstep;
468
469 this_frame = g_malloc (frame_sizebytes);
470 last_frame = g_malloc (frame_sizebytes);
471 opti_frame = g_malloc (frame_sizebytes);
472
473 if (opmode == OPBACKGROUND ||
474 opmode == OPFOREGROUND)
475 back_frame = g_malloc (frame_sizebytes);
476
477 total_alpha (this_frame, width*height, pixelstep);
478 total_alpha (last_frame, width*height, pixelstep);
479
480 new_image_id = gimp_image_new(width, height, imagetype);
481 gimp_image_undo_disable (new_image_id);
482
483 if (imagetype == GIMP_INDEXED)
484 {
485 palette = gimp_image_get_colormap (image_id, &ncolors);
486 gimp_image_set_colormap (new_image_id, palette, ncolors);
487 }
488
489 #if 1
490 if (opmode == OPBACKGROUND ||
491 opmode == OPFOREGROUND)
492 {
493 /* iterate through all rows of all frames, find statistical
494 mode for each pixel position. */
495 gint i,j;
496 guchar **these_rows;
497 guchar **red;
498 guchar **green;
499 guchar **blue;
500 guint **count;
501 guint *num_colors;
502
503 these_rows = g_new (guchar *, total_frames);
504 red = g_new (guchar *, total_frames);
505 green = g_new (guchar *, total_frames);
506 blue = g_new (guchar *, total_frames);
507 count = g_new (guint *, total_frames);
508
509 num_colors = g_new (guint, width);
510
511 for (this_frame_num=0; this_frame_num<total_frames; this_frame_num++)
512 {
513 these_rows[this_frame_num] = g_malloc(width * pixelstep);
514
515 red[this_frame_num] = g_new (guchar, width);
516 green[this_frame_num] = g_new (guchar, width);
517 blue[this_frame_num] = g_new (guchar, width);
518
519 count[this_frame_num] = g_new0(guint, width);
520 }
521
522 for (row = 0; row < height; row++)
523 {
524 memset(num_colors, 0, width * sizeof(guint));
525
526 for (this_frame_num=0; this_frame_num<total_frames; this_frame_num++)
527 {
528 gint32 drawable_ID = layers[total_frames-(this_frame_num+1)];
529
530 dispose = get_frame_disposal (this_frame_num);
531
532 compose_row (this_frame_num,
533 dispose,
534 row,
535 these_rows[this_frame_num],
536 width,
537 drawable_ID,
538 FALSE);
539 }
540
541 for (this_frame_num=0; this_frame_num<total_frames; this_frame_num++)
542 {
543 for (i=0; i<width; i++)
544 {
545 if (these_rows[this_frame_num][i * pixelstep + pixelstep -1]
546 >= 128)
547 {
548 for (j=0; j<num_colors[i]; j++)
549 {
550
551 switch (pixelstep)
552 {
553 case 4:
554 if (these_rows[this_frame_num][i * 4 +0] ==
555 red[j][i] &&
556 these_rows[this_frame_num][i * 4 +1] ==
557 green[j][i] &&
558 these_rows[this_frame_num][i * 4 +2] ==
559 blue[j][i])
560 {
561 (count[j][i])++;
562 goto same;
563 }
564 break;
565 case 2:
566 if (these_rows[this_frame_num][i * 2 +0] ==
567 red[j][i])
568 {
569 (count[j][i])++;
570 goto same;
571 }
572 break;
573 default:
574 g_error ("Eeep!");
575 break;
576 }
577 }
578
579 count[num_colors[i]][i] = 1;
580 red[num_colors[i]][i] =
581 these_rows[this_frame_num][i * pixelstep];
582 if (pixelstep == 4)
583 {
584 green[num_colors[i]][i] =
585 these_rows[this_frame_num][i * 4 +1];
586 blue[num_colors[i]][i] =
587 these_rows[this_frame_num][i * 4 +2];
588 }
589 num_colors[i]++;
590 }
591 same:
592 /* nop */;
593 }
594 }
595
596 for (i=0; i<width; i++)
597 {
598 guint best_count = 0;
599 guchar best_r = 255, best_g = 0, best_b = 255;
600
601 for (j=0; j<num_colors[i]; j++)
602 {
603 if (count[j][i] > best_count)
604 {
605 best_count = count[j][i];
606 best_r = red[j][i];
607 best_g = green[j][i];
608 best_b = blue[j][i];
609 }
610 }
611
612 back_frame[width * pixelstep * row +i*pixelstep + 0] = best_r;
613 if (pixelstep == 4)
614 {
615 back_frame[width * pixelstep * row +i*pixelstep + 1] =
616 best_g;
617 back_frame[width * pixelstep * row +i*pixelstep + 2] =
618 best_b;
619 }
620 back_frame[width * pixelstep * row +i*pixelstep +pixelstep-1] =
621 (best_count == 0) ? 0 : 255;
622
623 if (best_count == 0)
624 g_warning("yayyyy!");
625 }
626 /* memcpy(&back_frame[width * pixelstep * row],
627 these_rows[0],
628 width * pixelstep);*/
629 }
630
631 for (this_frame_num=0; this_frame_num<total_frames; this_frame_num++)
632 {
633 g_free (these_rows[this_frame_num]);
634 g_free (red[this_frame_num]);
635 g_free (green[this_frame_num]);
636 g_free (blue[this_frame_num]);
637 g_free (count[this_frame_num]);
638 }
639
640 g_free (these_rows);
641 g_free (red);
642 g_free (green);
643 g_free (blue);
644 g_free (count);
645 g_free (num_colors);
646 }
647 #endif
648
649 if (opmode == OPBACKGROUND)
650 {
651 GeglBuffer *buffer;
652 const Babl *format;
653
654 new_layer_id = gimp_layer_new (new_image_id,
655 "Backgroundx",
656 width, height,
657 drawabletype_alpha,
658 100.0,
659 gimp_image_get_default_new_layer_mode (new_image_id));
660
661 gimp_image_insert_layer (new_image_id, new_layer_id, -1, 0);
662
663 buffer = gimp_drawable_get_buffer (new_layer_id);
664
665 format = get_format (new_layer_id);
666
667 gegl_buffer_set (buffer, GEGL_RECTANGLE (0, 0, width, height), 0,
668 format, back_frame,
669 GEGL_ABYSS_NONE);
670
671 g_object_unref (buffer);
672 }
673 else
674 {
675 for (this_frame_num=0; this_frame_num<total_frames; this_frame_num++)
676 {
677 /*
678 * BUILD THIS FRAME into our 'this_frame' buffer.
679 */
680
681 gint32 drawable_ID = layers[total_frames-(this_frame_num+1)];
682
683 /* Image has been closed/etc since we got the layer list? */
684 /* FIXME - How do we tell if a gimp_drawable_get() fails? */
685 if (gimp_drawable_width (drawable_ID) == 0)
686 {
687 gimp_quit ();
688 }
689
690 this_delay = get_frame_duration (this_frame_num);
691 dispose = get_frame_disposal (this_frame_num);
692
693 for (row = 0; row < height; row++)
694 {
695 compose_row (this_frame_num,
696 dispose,
697 row,
698 &this_frame[pixelstep*width * row],
699 width,
700 drawable_ID,
701 FALSE
702 );
703 }
704
705 if (opmode == OPFOREGROUND)
706 {
707 gint xit, yit, byteit;
708
709 for (yit=0; yit<height; yit++)
710 {
711 for (xit=0; xit<width; xit++)
712 {
713 for (byteit=0; byteit<pixelstep-1; byteit++)
714 {
715 if (back_frame[yit*width*pixelstep + xit*pixelstep
716 + byteit]
717 !=
718 this_frame[yit*width*pixelstep + xit*pixelstep
719 + byteit])
720 {
721 goto enough;
722 }
723 }
724 this_frame[yit*width*pixelstep + xit*pixelstep
725 + pixelstep - 1] = 0;
726 enough:
727 /* nop */;
728 }
729 }
730 }
731
732 can_combine = FALSE;
733 bbox_left = 0;
734 bbox_top = 0;
735 bbox_right = width;
736 bbox_bottom = height;
737 rbox_left = 0;
738 rbox_top = 0;
739 rbox_right = width;
740 rbox_bottom = height;
741
742 /* copy 'this' frame into a buffer which we can safely molest */
743 memcpy (opti_frame, this_frame, frame_sizebytes);
744 /*
745 *
746 * OPTIMIZE HERE!
747 *
748 */
749 if (
750 (this_frame_num != 0) /* Can't delta bottom frame! */
751 && (opmode == OPOPTIMIZE)
752 )
753 {
754 gint xit, yit, byteit;
755
756 can_combine = TRUE;
757
758 /*
759 * SEARCH FOR BOUNDING BOX
760 */
761 bbox_left = width;
762 bbox_top = height;
763 bbox_right = 0;
764 bbox_bottom = 0;
765 rbox_left = width;
766 rbox_top = height;
767 rbox_right = 0;
768 rbox_bottom = 0;
769
770 for (yit=0; yit<height; yit++)
771 {
772 for (xit=0; xit<width; xit++)
773 {
774 gboolean keep_pix;
775 gboolean opaq_pix;
776
777 /* Check if 'this' and 'last' are transparent */
778 if (!(this_frame[yit*width*pixelstep + xit*pixelstep
779 + pixelstep-1]&128)
780 &&
781 !(last_frame[yit*width*pixelstep + xit*pixelstep
782 + pixelstep-1]&128))
783 {
784 keep_pix = FALSE;
785 opaq_pix = FALSE;
786 goto decided;
787 }
788 /* Check if just 'this' is transparent */
789 if ((last_frame[yit*width*pixelstep + xit*pixelstep
790 + pixelstep-1]&128)
791 &&
792 !(this_frame[yit*width*pixelstep + xit*pixelstep
793 + pixelstep-1]&128))
794 {
795 keep_pix = TRUE;
796 opaq_pix = FALSE;
797 can_combine = FALSE;
798 goto decided;
799 }
800 /* Check if just 'last' is transparent */
801 if (!(last_frame[yit*width*pixelstep + xit*pixelstep
802 + pixelstep-1]&128)
803 &&
804 (this_frame[yit*width*pixelstep + xit*pixelstep
805 + pixelstep-1]&128))
806 {
807 keep_pix = TRUE;
808 opaq_pix = TRUE;
809 goto decided;
810 }
811 /* If 'last' and 'this' are opaque, we have
812 * to check if they're the same color - we
813 * only have to keep the pixel if 'last' or
814 * 'this' are opaque and different.
815 */
816 keep_pix = FALSE;
817 opaq_pix = TRUE;
818 for (byteit=0; byteit<pixelstep-1; byteit++)
819 {
820 if ((last_frame[yit*width*pixelstep + xit*pixelstep
821 + byteit]
822 !=
823 this_frame[yit*width*pixelstep + xit*pixelstep
824 + byteit])
825 )
826 {
827 keep_pix = TRUE;
828 goto decided;
829 }
830 }
831 decided:
832 if (opaq_pix)
833 {
834 if (xit<rbox_left) rbox_left=xit;
835 if (xit>rbox_right) rbox_right=xit;
836 if (yit<rbox_top) rbox_top=yit;
837 if (yit>rbox_bottom) rbox_bottom=yit;
838 }
839 if (keep_pix)
840 {
841 if (xit<bbox_left) bbox_left=xit;
842 if (xit>bbox_right) bbox_right=xit;
843 if (yit<bbox_top) bbox_top=yit;
844 if (yit>bbox_bottom) bbox_bottom=yit;
845 }
846 else
847 {
848 /* pixel didn't change this frame - make
849 * it transparent in our optimized buffer!
850 */
851 opti_frame[yit*width*pixelstep + xit*pixelstep
852 + pixelstep-1] = 0;
853 }
854 } /* xit */
855 } /* yit */
856
857 if (!can_combine)
858 {
859 bbox_left = rbox_left;
860 bbox_top = rbox_top;
861 bbox_right = rbox_right;
862 bbox_bottom = rbox_bottom;
863 }
864
865 bbox_right++;
866 bbox_bottom++;
867
868 if (can_combine && !diff_only)
869 {
870 /* Try to optimize the pixel data for RLE or LZW compression
871 * by making some transparent pixels non-transparent if they
872 * would have the same color as the adjacent pixels. This
873 * gives a better compression if the algorithm compresses
874 * the image line by line.
875 * See: http://bugzilla.gnome.org/show_bug.cgi?id=66367
876 * It may not be very efficient to add two additional passes
877 * over the pixels, but this hopefully makes the code easier
878 * to maintain and less error-prone.
879 */
880 for (yit = bbox_top; yit < bbox_bottom; yit++)
881 {
882 /* Compare with previous pixels from left to right */
883 for (xit = bbox_left + 1; xit < bbox_right; xit++)
884 {
885 if (!(opti_frame[yit*width*pixelstep
886 + xit*pixelstep
887 + pixelstep-1]&128)
888 && (opti_frame[yit*width*pixelstep
889 + (xit-1)*pixelstep
890 + pixelstep-1]&128)
891 && (last_frame[yit*width*pixelstep
892 + xit*pixelstep
893 + pixelstep-1]&128))
894 {
895 for (byteit=0; byteit<pixelstep-1; byteit++)
896 {
897 if (opti_frame[yit*width*pixelstep
898 + (xit-1)*pixelstep
899 + byteit]
900 !=
901 last_frame[yit*width*pixelstep
902 + xit*pixelstep
903 + byteit])
904 {
905 goto skip_right;
906 }
907 }
908 /* copy the color and alpha */
909 for (byteit=0; byteit<pixelstep; byteit++)
910 {
911 opti_frame[yit*width*pixelstep
912 + xit*pixelstep
913 + byteit]
914 = last_frame[yit*width*pixelstep
915 + xit*pixelstep
916 + byteit];
917 }
918 }
919 skip_right:
920 /* nop */;
921 } /* xit */
922
923 /* Compare with next pixels from right to left */
924 for (xit = bbox_right - 2; xit >= bbox_left; xit--)
925 {
926 if (!(opti_frame[yit*width*pixelstep
927 + xit*pixelstep
928 + pixelstep-1]&128)
929 && (opti_frame[yit*width*pixelstep
930 + (xit+1)*pixelstep
931 + pixelstep-1]&128)
932 && (last_frame[yit*width*pixelstep
933 + xit*pixelstep
934 + pixelstep-1]&128))
935 {
936 for (byteit=0; byteit<pixelstep-1; byteit++)
937 {
938 if (opti_frame[yit*width*pixelstep
939 + (xit+1)*pixelstep
940 + byteit]
941 !=
942 last_frame[yit*width*pixelstep
943 + xit*pixelstep
944 + byteit])
945 {
946 goto skip_left;
947 }
948 }
949 /* copy the color and alpha */
950 for (byteit=0; byteit<pixelstep; byteit++)
951 {
952 opti_frame[yit*width*pixelstep
953 + xit*pixelstep
954 + byteit]
955 = last_frame[yit*width*pixelstep
956 + xit*pixelstep
957 + byteit];
958 }
959 }
960 skip_left:
961 /* nop */;
962 } /* xit */
963 } /* yit */
964 }
965
966 /*
967 * Collapse opti_frame data down such that the data
968 * which occupies the bounding box sits at the start
969 * of the data (for convenience with ..set_rect()).
970 */
971 destptr = opti_frame;
972 /*
973 * If can_combine, then it's safe to use our optimized
974 * alpha information. Otherwise, an opaque pixel became
975 * transparent this frame, and we'll have to use the
976 * actual true frame's alpha.
977 */
978 if (can_combine)
979 srcptr = opti_frame;
980 else
981 srcptr = this_frame;
982 for (yit=bbox_top; yit<bbox_bottom; yit++)
983 {
984 for (xit=bbox_left; xit<bbox_right; xit++)
985 {
986 for (byteit=0; byteit<pixelstep; byteit++)
987 {
988 *(destptr++) = srcptr[yit*pixelstep*width +
989 pixelstep*xit + byteit];
990 }
991 }
992 }
993 } /* !bot frame? */
994 else
995 {
996 memcpy (opti_frame, this_frame, frame_sizebytes);
997 }
998
999 /*
1000 *
1001 * REMEMBER THE ANIMATION STATUS TO DELTA AGAINST NEXT TIME
1002 *
1003 */
1004 memcpy (last_frame, this_frame, frame_sizebytes);
1005
1006
1007 /*
1008 *
1009 * PUT THIS FRAME INTO A NEW LAYER IN THE NEW IMAGE
1010 *
1011 */
1012
1013 oldlayer_name =
1014 gimp_item_get_name(layers[total_frames-(this_frame_num+1)]);
1015
1016 buflen = strlen(oldlayer_name) + 40;
1017
1018 newlayer_name = g_malloc(buflen);
1019
1020 remove_disposal_tag(newlayer_name, oldlayer_name);
1021 g_free(oldlayer_name);
1022
1023 oldlayer_name = g_malloc(buflen);
1024
1025 remove_ms_tag(oldlayer_name, newlayer_name);
1026
1027 g_snprintf(newlayer_name, buflen, "%s(%dms)%s",
1028 oldlayer_name, this_delay,
1029 (this_frame_num == 0) ? "" :
1030 can_combine ? "(combine)" : "(replace)");
1031
1032 g_free(oldlayer_name);
1033
1034 /* Empty frame! */
1035 if (bbox_right <= bbox_left ||
1036 bbox_bottom <= bbox_top)
1037 {
1038 cumulated_delay += this_delay;
1039
1040 g_free (newlayer_name);
1041
1042 oldlayer_name = gimp_item_get_name (last_true_frame);
1043
1044 buflen = strlen (oldlayer_name) + 40;
1045
1046 newlayer_name = g_malloc (buflen);
1047
1048 remove_disposal_tag (newlayer_name, oldlayer_name);
1049 g_free (oldlayer_name);
1050
1051 oldlayer_name = g_malloc (buflen);
1052
1053 remove_ms_tag (oldlayer_name, newlayer_name);
1054
1055 g_snprintf (newlayer_name, buflen, "%s(%dms)%s",
1056 oldlayer_name, cumulated_delay,
1057 (this_frame_num == 0) ? "" :
1058 can_combine ? "(combine)" : "(replace)");
1059
1060 gimp_item_set_name (last_true_frame, newlayer_name);
1061
1062 g_free (newlayer_name);
1063 }
1064 else
1065 {
1066 GeglBuffer *buffer;
1067 const Babl *format;
1068
1069 cumulated_delay = this_delay;
1070
1071 last_true_frame =
1072 new_layer_id = gimp_layer_new (new_image_id,
1073 newlayer_name,
1074 bbox_right-bbox_left,
1075 bbox_bottom-bbox_top,
1076 drawabletype_alpha,
1077 100.0,
1078 gimp_image_get_default_new_layer_mode (new_image_id));
1079 g_free (newlayer_name);
1080
1081 gimp_image_insert_layer (new_image_id, new_layer_id, -1, 0);
1082
1083 buffer = gimp_drawable_get_buffer (new_layer_id);
1084
1085 format = get_format (new_layer_id);
1086
1087 gegl_buffer_set (buffer,
1088 GEGL_RECTANGLE (0, 0,
1089 bbox_right-bbox_left,
1090 bbox_bottom-bbox_top), 0,
1091 format, opti_frame,
1092 GEGL_AUTO_ROWSTRIDE);
1093
1094 g_object_unref (buffer);
1095 gimp_item_transform_translate (new_layer_id, bbox_left, bbox_top);
1096 }
1097
1098 gimp_progress_update (((gdouble) this_frame_num + 1.0) /
1099 ((gdouble) total_frames));
1100 }
1101
1102 gimp_progress_update (1.0);
1103 }
1104
1105 gimp_image_undo_enable (new_image_id);
1106
1107 if (run_mode != GIMP_RUN_NONINTERACTIVE)
1108 gimp_display_new (new_image_id);
1109
1110 g_free (rawframe);
1111 rawframe = NULL;
1112
1113 g_free (last_frame);
1114 last_frame = NULL;
1115
1116 g_free (this_frame);
1117 this_frame = NULL;
1118
1119 g_free (opti_frame);
1120 opti_frame = NULL;
1121
1122 g_free (back_frame);
1123 back_frame = NULL;
1124
1125 return new_image_id;
1126 }
1127
1128 /* Util. */
1129
1130 static DisposeType
get_frame_disposal(guint whichframe)1131 get_frame_disposal (guint whichframe)
1132 {
1133 gchar *layer_name;
1134 DisposeType disposal;
1135
1136 layer_name = gimp_item_get_name(layers[total_frames-(whichframe+1)]);
1137 disposal = parse_disposal_tag(layer_name);
1138 g_free(layer_name);
1139
1140 return disposal;
1141 }
1142
1143 static guint32
get_frame_duration(guint whichframe)1144 get_frame_duration (guint whichframe)
1145 {
1146 gchar* layer_name;
1147 gint duration = 0;
1148
1149 layer_name = gimp_item_get_name(layers[total_frames-(whichframe+1)]);
1150 if (layer_name)
1151 {
1152 duration = parse_ms_tag(layer_name);
1153 g_free(layer_name);
1154 }
1155
1156 if (duration < 0) duration = 100; /* FIXME for default-if-not-said */
1157 if (duration == 0) duration = 100; /* FIXME - 0-wait is nasty */
1158
1159 return (guint32) duration;
1160 }
1161
1162 static gboolean
is_ms_tag(const gchar * str,gint * duration,gint * taglength)1163 is_ms_tag (const gchar *str,
1164 gint *duration,
1165 gint *taglength)
1166 {
1167 gint sum = 0;
1168 gint offset;
1169 gint length;
1170
1171 length = strlen(str);
1172
1173 if (str[0] != '(')
1174 return FALSE;
1175
1176 offset = 1;
1177
1178 /* eat any spaces between open-parenthesis and number */
1179 while ((offset<length) && (str[offset] == ' '))
1180 offset++;
1181
1182 if ((offset>=length) || (!g_ascii_isdigit (str[offset])))
1183 return 0;
1184
1185 do
1186 {
1187 sum *= 10;
1188 sum += str[offset] - '0';
1189 offset++;
1190 }
1191 while ((offset<length) && (g_ascii_isdigit (str[offset])));
1192
1193 if (length-offset <= 2)
1194 return FALSE;
1195
1196 /* eat any spaces between number and 'ms' */
1197 while ((offset<length) && (str[offset] == ' '))
1198 offset++;
1199
1200 if ((length-offset <= 2) ||
1201 (g_ascii_toupper (str[offset]) != 'M') ||
1202 (g_ascii_toupper (str[offset+1]) != 'S'))
1203 return FALSE;
1204
1205 offset += 2;
1206
1207 /* eat any spaces between 'ms' and close-parenthesis */
1208 while ((offset<length) && (str[offset] == ' '))
1209 offset++;
1210
1211 if ((length-offset < 1) || (str[offset] != ')'))
1212 return FALSE;
1213
1214 offset++;
1215
1216 *duration = sum;
1217 *taglength = offset;
1218
1219 return TRUE;
1220 }
1221
1222 static int
parse_ms_tag(const char * str)1223 parse_ms_tag (const char *str)
1224 {
1225 gint i;
1226 gint rtn;
1227 gint dummy;
1228 gint length;
1229
1230 length = strlen (str);
1231
1232 for (i = 0; i < length; i++)
1233 {
1234 if (is_ms_tag (&str[i], &rtn, &dummy))
1235 return rtn;
1236 }
1237
1238 return -1;
1239 }
1240
1241 static gboolean
is_disposal_tag(const gchar * str,DisposeType * disposal,gint * taglength)1242 is_disposal_tag (const gchar *str,
1243 DisposeType *disposal,
1244 gint *taglength)
1245 {
1246 if (strlen (str) != 9)
1247 return FALSE;
1248
1249 if (strncmp (str, "(combine)", 9) == 0)
1250 {
1251 *taglength = 9;
1252 *disposal = DISPOSE_COMBINE;
1253 return TRUE;
1254 }
1255 else if (strncmp (str, "(replace)", 9) == 0)
1256 {
1257 *taglength = 9;
1258 *disposal = DISPOSE_REPLACE;
1259 return TRUE;
1260 }
1261
1262 return FALSE;
1263 }
1264
1265
1266 static DisposeType
parse_disposal_tag(const gchar * str)1267 parse_disposal_tag (const gchar *str)
1268 {
1269 DisposeType rtn;
1270 gint i, dummy;
1271 gint length;
1272
1273 length = strlen(str);
1274
1275 for (i=0; i<length; i++)
1276 {
1277 if (is_disposal_tag (&str[i], &rtn, &dummy))
1278 {
1279 return rtn;
1280 }
1281 }
1282
1283 return DISPOSE_UNDEFINED; /* FIXME */
1284 }
1285
1286 static void
remove_disposal_tag(gchar * dest,gchar * src)1287 remove_disposal_tag (gchar *dest,
1288 gchar *src)
1289 {
1290 gint offset = 0;
1291 gint destoffset = 0;
1292 gint length;
1293 int taglength;
1294 DisposeType dummy;
1295
1296 length = strlen(src);
1297
1298 strcpy(dest, src);
1299
1300 while (offset<=length)
1301 {
1302 if (is_disposal_tag(&src[offset], &dummy, &taglength))
1303 {
1304 offset += taglength;
1305 }
1306 dest[destoffset] = src[offset];
1307 destoffset++;
1308 offset++;
1309 }
1310
1311 dest[offset] = '\0';
1312 }
1313
1314 static void
remove_ms_tag(gchar * dest,gchar * src)1315 remove_ms_tag (gchar *dest,
1316 gchar *src)
1317 {
1318 gint offset = 0;
1319 gint destoffset = 0;
1320 gint length;
1321 gint taglength;
1322 gint dummy;
1323
1324 length = strlen(src);
1325
1326 strcpy(dest, src);
1327
1328 while (offset<=length)
1329 {
1330 if (is_ms_tag(&src[offset], &dummy, &taglength))
1331 {
1332 offset += taglength;
1333 }
1334 dest[destoffset] = src[offset];
1335 destoffset++;
1336 offset++;
1337 }
1338
1339 dest[offset] = '\0';
1340 }
1341