/* * Animation Optimizer plug-in version 1.1.2 * * (c) Adam D. Moss, 1997-2003 * adam@gimp.org * adam@foxbox.org * * GIMP - The GNU Image Manipulation Program * 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 "libgimp/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; /* Declare local functions. */ static void query (void); static void run (const gchar *name, gint nparams, const GimpParam *param, gint *nreturn_vals, GimpParam **return_vals); static gint32 do_optimizations (GimpRunMode run_mode, 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); const GimpPlugInInfo PLUG_IN_INFO = { NULL, /* init_proc */ NULL, /* quit_proc */ query, /* query_proc */ run, /* run_proc */ }; /* Global widgets'n'stuff */ static guint width, height; static gint32 image_id; static gint32 new_image_id; static gint32 total_frames; static gint32 *layers; static GimpImageBaseType imagetype; static GimpImageType drawabletype_alpha; static guchar pixelstep; static guchar *palette; static gint ncolors; static operatingMode opmode; MAIN () static void query (void) { static const GimpParamDef args[] = { { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, { GIMP_PDB_IMAGE, "image", "Input image" }, { GIMP_PDB_DRAWABLE, "drawable", "Input drawable (unused)" } }; static const GimpParamDef return_args[] = { { GIMP_PDB_IMAGE, "result", "Resulting image" } }; gimp_install_procedure (OPTIMIZE_PROC, N_("Modify image to reduce size when saved as GIF animation"), "This procedure applies various optimizations to" " a GIMP 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.", "Adam D. Moss ", "Adam D. Moss ", "1997-2003", N_("Optimize (for _GIF)"), "RGB*, INDEXED*, GRAY*", GIMP_PLUGIN, G_N_ELEMENTS (args), G_N_ELEMENTS (return_args), args, return_args); gimp_install_procedure (OPTIMIZE_DIFF_PROC, N_("Reduce file size where combining layers is possible"), "This procedure applies various optimizations to" " a GIMP 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.", "Adam D. Moss ", "Adam D. Moss ", "1997-2001", N_("_Optimize (Difference)"), "RGB*, INDEXED*, GRAY*", GIMP_PLUGIN, G_N_ELEMENTS (args), G_N_ELEMENTS (return_args), args, return_args); gimp_install_procedure (UNOPTIMIZE_PROC, N_("Remove optimization to make editing easier"), "This procedure 'simplifies' a GIMP layer-based" " animation that has been optimized for animation. " "This makes editing the animation much easier.", "Adam D. Moss ", "Adam D. Moss ", "1997-2001", N_("_Unoptimize"), "RGB*, INDEXED*, GRAY*", GIMP_PLUGIN, G_N_ELEMENTS (args), G_N_ELEMENTS (return_args), args, return_args); gimp_plugin_menu_register (OPTIMIZE_PROC, "/Filters/Animation"); gimp_plugin_menu_register (OPTIMIZE_DIFF_PROC, "/Filters/Animation"); gimp_plugin_menu_register (UNOPTIMIZE_PROC, "/Filters/Animation"); #ifdef EXPERIMENTAL_BACKDROP_CODE gimp_install_procedure (REMOVE_BACKDROP_PROC, "This procedure attempts to remove the backdrop" " from a GIMP layer-based animation, leaving" " the foreground animation over transparency.", "", "Adam D. Moss ", "Adam D. Moss ", "2001", N_("_Remove Backdrop"), "RGB*, INDEXED*, GRAY*", GIMP_PLUGIN, G_N_ELEMENTS (args), G_N_ELEMENTS (return_args), args, return_args); gimp_install_procedure (FIND_BACKDROP_PROC, "This procedure attempts to remove the foreground" " from a GIMP layer-based animation, leaving" " a one-layered image containing only the" " constant backdrop image.", "", "Adam D. Moss ", "Adam D. Moss ", "2001", N_("_Find Backdrop"), "RGB*, INDEXED*, GRAY*", GIMP_PLUGIN, G_N_ELEMENTS (args), G_N_ELEMENTS (return_args), args, return_args); gimp_plugin_menu_register (REMOVE_BACKDROP_PROC, "/Filters/Animation"); gimp_plugin_menu_register (FIND_BACKDROP_PROC, "/Filters/Animation"); #endif } static void run (const gchar *name, gint n_params, const GimpParam *param, gint *nreturn_vals, GimpParam **return_vals) { static GimpParam values[2]; GimpRunMode run_mode; GimpPDBStatusType status = GIMP_PDB_SUCCESS; gboolean diff_only = FALSE; *nreturn_vals = 2; *return_vals = values; run_mode = param[0].data.d_int32; INIT_I18N (); gegl_init (NULL, NULL); if (run_mode == GIMP_RUN_NONINTERACTIVE && n_params != 3) { status = GIMP_PDB_CALLING_ERROR; } /* Check the procedure name we were called with, to decide what needs to be done. */ if (strcmp (name, OPTIMIZE_PROC) == 0) opmode = OPOPTIMIZE; else if (strcmp (name, OPTIMIZE_DIFF_PROC) == 0) { opmode = OPOPTIMIZE; diff_only = TRUE; } else if (strcmp (name, UNOPTIMIZE_PROC) == 0) opmode = OPUNOPTIMIZE; else if (strcmp (name, FIND_BACKDROP_PROC) == 0) opmode = OPBACKGROUND; else if (strcmp (name, REMOVE_BACKDROP_PROC) == 0) opmode = OPFOREGROUND; else g_error("GAH!!!"); if (status == GIMP_PDB_SUCCESS) { image_id = param[1].data.d_image; new_image_id = do_optimizations (run_mode, diff_only); if (run_mode != GIMP_RUN_NONINTERACTIVE) gimp_displays_flush(); } values[0].type = GIMP_PDB_STATUS; values[0].data.d_status = status; values[1].type = GIMP_PDB_IMAGE; values[1].data.d_image = new_image_id; } /* 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 (gint32 drawable_ID) { if (gimp_drawable_is_rgb (drawable_ID)) { if (gimp_drawable_has_alpha (drawable_ID)) return babl_format ("R'G'B'A u8"); else return babl_format ("R'G'B' u8"); } else if (gimp_drawable_is_gray (drawable_ID)) { if (gimp_drawable_has_alpha (drawable_ID)) return babl_format ("Y'A u8"); else return babl_format ("Y' u8"); } return gimp_drawable_get_format (drawable_ID); } static void compose_row (gint frame_num, DisposeType dispose, gint row_num, guchar *dest, gint dest_width, gint32 drawable_ID, 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); } gimp_drawable_offsets (drawable_ID, &rawx, &rawy); rawwidth = gimp_drawable_width (drawable_ID); rawheight = gimp_drawable_height (drawable_ID); /* this frame has nothing to give us for this row; return */ if (row_num >= rawheight + rawy || row_num < rawy) return; format = get_format (drawable_ID); has_alpha = gimp_drawable_has_alpha (drawable_ID); 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 = gimp_drawable_get_buffer (drawable_ID); 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