/* * Animation Optimizer plug-in version 1.1.2 * * (c) Adam D. Moss, 1997-2003 * adam@gimp.org * adam@foxbox.org * * PIKA - Photo and Image Kooker Application * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /* #define EXPERIMENTAL_BACKDROP_CODE */ #include "config.h" #include #include #include "libpika/stdplugins-intl.h" #define OPTIMIZE_PROC "plug-in-animationoptimize" #define OPTIMIZE_DIFF_PROC "plug-in-animationoptimize-diff" #define UNOPTIMIZE_PROC "plug-in-animationunoptimize" #define REMOVE_BACKDROP_PROC "plug-in-animation-remove-backdrop" #define FIND_BACKDROP_PROC "plug-in-animation-find-backdrop" typedef enum { DISPOSE_UNDEFINED = 0x00, DISPOSE_COMBINE = 0x01, DISPOSE_REPLACE = 0x02 } DisposeType; typedef enum { OPOPTIMIZE = 0L, OPUNOPTIMIZE = 1L, OPFOREGROUND = 2L, OPBACKGROUND = 3L } operatingMode; typedef struct _Optimize Optimize; typedef struct _OptimizeClass OptimizeClass; struct _Optimize { PikaPlugIn parent_instance; }; struct _OptimizeClass { PikaPlugInClass parent_class; }; #define OPTIMIZE_TYPE (optimize_get_type ()) #define OPTIMIZE (obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), OPTIMIZE_TYPE, Optimize)) GType optimize_get_type (void) G_GNUC_CONST; static GList * optimize_query_procedures (PikaPlugIn *plug_in); static PikaProcedure * optimize_create_procedure (PikaPlugIn *plug_in, const gchar *name); static PikaValueArray * optimize_run (PikaProcedure *procedure, PikaRunMode run_mode, PikaImage *image, gint n_drawables, PikaDrawable **drawables, const PikaValueArray *args, gpointer run_data); static PikaImage * do_optimizations (PikaRunMode run_mode, PikaImage *image, gboolean diff_only); /* tag util functions*/ static gint parse_ms_tag (const gchar *str); static DisposeType parse_disposal_tag (const gchar *str); static DisposeType get_frame_disposal (guint whichframe); static guint32 get_frame_duration (guint whichframe); static void remove_disposal_tag (gchar *dest, gchar *src); static void remove_ms_tag (gchar *dest, gchar *src); static gboolean is_disposal_tag (const gchar *str, DisposeType *disposal, gint *taglength); static gboolean is_ms_tag (const gchar *str, gint *duration, gint *taglength); G_DEFINE_TYPE (Optimize, optimize, PIKA_TYPE_PLUG_IN) PIKA_MAIN (OPTIMIZE_TYPE) DEFINE_STD_SET_I18N /* Global widgets'n'stuff */ static guint width, height; static gint32 total_frames; static PikaLayer **layers; static guchar pixelstep; static guchar *palette; static gint ncolors; static operatingMode opmode; static void optimize_class_init (OptimizeClass *klass) { PikaPlugInClass *plug_in_class = PIKA_PLUG_IN_CLASS (klass); plug_in_class->query_procedures = optimize_query_procedures; plug_in_class->create_procedure = optimize_create_procedure; plug_in_class->set_i18n = STD_SET_I18N; } static void optimize_init (Optimize *optimize) { } static GList * optimize_query_procedures (PikaPlugIn *plug_in) { GList *list = NULL; list = g_list_append (list, g_strdup (OPTIMIZE_PROC)); list = g_list_append (list, g_strdup (OPTIMIZE_DIFF_PROC)); list = g_list_append (list, g_strdup (UNOPTIMIZE_PROC)); #ifdef EXPERIMENTAL_BACKDROP_CODE list = g_list_append (list, g_strdup (REMOVE_BACKDROP_PROC)); list = g_list_append (list, g_strdup (FIND_BACKDROP_PROC)); #endif return list; } static PikaProcedure * optimize_create_procedure (PikaPlugIn *plug_in, const gchar *name) { PikaProcedure *procedure = NULL; if (! strcmp (name, OPTIMIZE_PROC)) { procedure = pika_image_procedure_new (plug_in, name, PIKA_PDB_PROC_TYPE_PLUGIN, optimize_run, NULL, NULL); pika_procedure_set_sensitivity_mask (procedure, PIKA_PROCEDURE_SENSITIVE_DRAWABLE | PIKA_PROCEDURE_SENSITIVE_DRAWABLES | PIKA_PROCEDURE_SENSITIVE_NO_DRAWABLES); pika_procedure_set_menu_label (procedure, _("Optimize (for _GIF)")); pika_procedure_set_documentation (procedure, _("Modify image to reduce size when " "saved as GIF animation"), "This procedure applies various " "optimizations to a PIKA layer-based " "animation in an attempt to reduce the " "final file size. If a frame of the" "animation can use the 'combine' " "mode, this procedure attempts to " "maximize the number of ajdacent " "pixels having the same color, which" "improves the compression for some " "image formats such as GIF or MNG.", name); } else if (! strcmp (name, OPTIMIZE_DIFF_PROC)) { procedure = pika_image_procedure_new (plug_in, name, PIKA_PDB_PROC_TYPE_PLUGIN, optimize_run, NULL, NULL); pika_procedure_set_sensitivity_mask (procedure, PIKA_PROCEDURE_SENSITIVE_DRAWABLE | PIKA_PROCEDURE_SENSITIVE_DRAWABLES | PIKA_PROCEDURE_SENSITIVE_NO_DRAWABLES); pika_procedure_set_menu_label (procedure, _("_Optimize (Difference)")); pika_procedure_set_documentation (procedure, _("Reduce file size where " "combining layers is possible"), "This procedure applies various " "optimizations to a PIKA layer-based " "animation in an attempt to reduce " "the final file size. If a frame of " "the animation can use the 'combine' " "mode, this procedure uses a simple " "difference between the frames.", name); } else if (! strcmp (name, UNOPTIMIZE_PROC)) { procedure = pika_image_procedure_new (plug_in, name, PIKA_PDB_PROC_TYPE_PLUGIN, optimize_run, NULL, NULL); pika_procedure_set_sensitivity_mask (procedure, PIKA_PROCEDURE_SENSITIVE_DRAWABLE | PIKA_PROCEDURE_SENSITIVE_DRAWABLES | PIKA_PROCEDURE_SENSITIVE_NO_DRAWABLES); pika_procedure_set_menu_label (procedure, _("_Unoptimize")); pika_procedure_set_documentation (procedure, _("Remove optimization to make " "editing easier"), "This procedure 'simplifies' a PIKA " "layer-based animation that has been " "optimized for animation. This makes " "editing the animation much easier.", name); } else if (! strcmp (name, REMOVE_BACKDROP_PROC)) { procedure = pika_image_procedure_new (plug_in, name, PIKA_PDB_PROC_TYPE_PLUGIN, optimize_run, NULL, NULL); pika_procedure_set_sensitivity_mask (procedure, PIKA_PROCEDURE_SENSITIVE_DRAWABLE | PIKA_PROCEDURE_SENSITIVE_DRAWABLES | PIKA_PROCEDURE_SENSITIVE_NO_DRAWABLES); pika_procedure_set_menu_label (procedure, _("_Remove Backdrop")); pika_procedure_set_documentation (procedure, "This procedure attempts to remove " "the backdrop from a PIKA layer-based " "animation, leaving the foreground " "animation over transparency.", NULL, name); } else if (! strcmp (name, FIND_BACKDROP_PROC)) { procedure = pika_image_procedure_new (plug_in, name, PIKA_PDB_PROC_TYPE_PLUGIN, optimize_run, NULL, NULL); pika_procedure_set_sensitivity_mask (procedure, PIKA_PROCEDURE_SENSITIVE_DRAWABLE | PIKA_PROCEDURE_SENSITIVE_DRAWABLES | PIKA_PROCEDURE_SENSITIVE_NO_DRAWABLES); pika_procedure_set_menu_label (procedure, _("_Find Backdrop")); pika_procedure_set_documentation (procedure, "This procedure attempts to remove " "the foreground from a PIKA " "layer-based animation, leaving" " a one-layered image containing only " "the constant backdrop image.", NULL, name); } if (procedure) { pika_procedure_set_image_types (procedure, "*"); pika_procedure_add_menu_path (procedure, "/Filters/Animation"); pika_procedure_set_attribution (procedure, "Adam D. Moss ", "Adam D. Moss ", "1997-2003"); PIKA_PROC_VAL_IMAGE (procedure, "result", "Result", "Resultimg image", FALSE, G_PARAM_READWRITE); } return procedure; } static PikaValueArray * optimize_run (PikaProcedure *procedure, PikaRunMode run_mode, PikaImage *image, gint n_drawables, PikaDrawable **drawables, const PikaValueArray *args, gpointer run_data) { PikaValueArray *return_vals; const gchar *name = pika_procedure_get_name (procedure); gboolean diff_only = FALSE; gegl_init (NULL, NULL); if (! strcmp (name, OPTIMIZE_PROC)) { opmode = OPOPTIMIZE; } else if (! strcmp (name, OPTIMIZE_DIFF_PROC)) { opmode = OPOPTIMIZE; diff_only = TRUE; } else if (! strcmp (name, UNOPTIMIZE_PROC)) { opmode = OPUNOPTIMIZE; } else if (strcmp (name, FIND_BACKDROP_PROC)) { opmode = OPBACKGROUND; } else if (strcmp (name, REMOVE_BACKDROP_PROC)) { opmode = OPFOREGROUND; } image = do_optimizations (run_mode, image, diff_only); if (run_mode != PIKA_RUN_NONINTERACTIVE) pika_displays_flush (); return_vals = pika_procedure_new_return_values (procedure, PIKA_PDB_SUCCESS, NULL); PIKA_VALUES_SET_IMAGE (return_vals, 1, image); return return_vals; } /* Rendering Functions */ static void total_alpha (guchar *imdata, guint32 numpix, guchar bytespp) { /* Set image to total-transparency w/black */ memset (imdata, 0, numpix * bytespp); } static const Babl * get_format (PikaDrawable *drawable) { if (pika_drawable_is_rgb (drawable)) { if (pika_drawable_has_alpha (drawable)) return babl_format ("R'G'B'A u8"); else return babl_format ("R'G'B' u8"); } else if (pika_drawable_is_gray (drawable)) { if (pika_drawable_has_alpha (drawable)) return babl_format ("Y'A u8"); else return babl_format ("Y' u8"); } return pika_drawable_get_format (drawable); } static void compose_row (gint frame_num, DisposeType dispose, gint row_num, guchar *dest, gint dest_width, PikaDrawable *drawable, gboolean cleanup) { static guchar *line_buf = NULL; GeglBuffer *src_buffer; const Babl *format; guchar *srcptr; gint rawx, rawy, rawbpp, rawwidth, rawheight; gint i; gboolean has_alpha; if (cleanup) { if (line_buf) { g_free (line_buf); line_buf = NULL; } return; } if (dispose == DISPOSE_REPLACE) { total_alpha (dest, dest_width, pixelstep); } pika_drawable_get_offsets (drawable, &rawx, &rawy); rawwidth = pika_drawable_get_width (drawable); rawheight = pika_drawable_get_height (drawable); /* this frame has nothing to give us for this row; return */ if (row_num >= rawheight + rawy || row_num < rawy) return; format = get_format (drawable); has_alpha = pika_drawable_has_alpha (drawable); rawbpp = babl_format_get_bytes_per_pixel (format); if (line_buf) { g_free (line_buf); line_buf = NULL; } line_buf = g_malloc (rawwidth * rawbpp); /* Initialise and fetch the raw new frame row */ src_buffer = pika_drawable_get_buffer (drawable); gegl_buffer_get (src_buffer, GEGL_RECTANGLE (0, row_num - rawy, rawwidth, 1), 1.0, format, line_buf, GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); g_object_unref (src_buffer); /* render... */ srcptr = line_buf; for (i=rawx; i=0 && i= 128) { for (j=0; j best_count) { best_count = count[j][i]; best_r = red[j][i]; best_g = green[j][i]; best_b = blue[j][i]; } } back_frame[width * pixelstep * row +i*pixelstep + 0] = best_r; if (pixelstep == 4) { back_frame[width * pixelstep * row +i*pixelstep + 1] = best_g; back_frame[width * pixelstep * row +i*pixelstep + 2] = best_b; } back_frame[width * pixelstep * row +i*pixelstep +pixelstep-1] = (best_count == 0) ? 0 : 255; if (best_count == 0) g_warning ("yayyyy!"); } /* memcpy (&back_frame[width * pixelstep * row], these_rows[0], width * pixelstep);*/ } for (this_frame_num=0; this_frame_numrbox_right) rbox_right=xit; if (yitrbox_bottom) rbox_bottom=yit; } if (keep_pix) { if (xitbbox_right) bbox_right=xit; if (yitbbox_bottom) bbox_bottom=yit; } else { /* pixel didn't change this frame - make * it transparent in our optimized buffer! */ opti_frame[yit*width*pixelstep + xit*pixelstep + pixelstep-1] = 0; } } /* xit */ } /* yit */ if (!can_combine) { bbox_left = rbox_left; bbox_top = rbox_top; bbox_right = rbox_right; bbox_bottom = rbox_bottom; } bbox_right++; bbox_bottom++; if (can_combine && !diff_only) { /* Try to optimize the pixel data for RLE or LZW compression * by making some transparent pixels non-transparent if they * would have the same color as the adjacent pixels. This * gives a better compression if the algorithm compresses * the image line by line. * See: http://bugzilla.gnome.org/show_bug.cgi?id=66367 * It may not be very efficient to add two additional passes * over the pixels, but this hopefully makes the code easier * to maintain and less error-prone. */ for (yit = bbox_top; yit < bbox_bottom; yit++) { /* Compare with previous pixels from left to right */ for (xit = bbox_left + 1; xit < bbox_right; xit++) { if (!(opti_frame[yit*width*pixelstep + xit*pixelstep + pixelstep-1]&128) && (opti_frame[yit*width*pixelstep + (xit-1)*pixelstep + pixelstep-1]&128) && (last_frame[yit*width*pixelstep + xit*pixelstep + pixelstep-1]&128)) { for (byteit=0; byteit= bbox_left; xit--) { if (!(opti_frame[yit*width*pixelstep + xit*pixelstep + pixelstep-1]&128) && (opti_frame[yit*width*pixelstep + (xit+1)*pixelstep + pixelstep-1]&128) && (last_frame[yit*width*pixelstep + xit*pixelstep + pixelstep-1]&128)) { for (byteit=0; byteit=length) || (!g_ascii_isdigit (str[offset]))) return 0; do { sum *= 10; sum += str[offset] - '0'; offset++; } while ((offset