/* * Multiple-image Network Graphics (MNG) plug-in for The GIMP -- an image * manipulation program * * Copyright (C) 2002 S. Mukund * Portions are copyright of the authors of the file-gif-save, file-png-save * and file-jpeg-save plug-ins' code. The exact ownership of these code * fragments has not been determined. * * This work was sponsored by Xinit Systems Limited, UK. * http://www.xinitsystems.com/ * * THIS SOURCE CODE DOES NOT INCLUDE ANY FUNCTIONALITY FOR READING * OR WRITING CONTENT IN THE GIF IMAGE FORMAT. * * 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 2 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * -- * * For now, this MNG plug-in can only save images. It cannot load images. * Save your working copy as .xcf and use this for "exporting" your images * to MNG. Supports animation the same way as animated GIFs. Supports alpha * transparency. Uses the libmng library (http://www.libmng.com/). * The MIME content-type for MNG images is video/x-mng for now. Make sure * your web-server is configured appropriately if you want to serve MNG * images. * * Since libmng cannot write PNG, JNG and delta PNG chunks at this time * (when this text was written), this plug-in uses libpng and jpeglib to * create the data for the chunks. * */ #include "config.h" #include #include #include #include #include #ifdef HAVE_UNISTD_H #include #endif /* libpng and jpeglib are currently used in this plug-in. */ #include #include /* Grrr. The grrr is because the following have to be defined * by the application as well for some reason, although they * were enabled when libmng was compiled. The authors of libmng * must look into this. */ #if !defined(MNG_SUPPORT_FULL) #define MNG_SUPPORT_FULL 1 #endif #if !defined(MNG_SUPPORT_READ) #define MNG_SUPPORT_READ 1 #endif #if !defined(MNG_SUPPORT_WRITE) #define MNG_SUPPORT_WRITE 1 #endif #if !defined(MNG_SUPPORT_DISPLAY) #define MNG_SUPPORT_DISPLAY 1 #endif #if !defined(MNG_ACCESS_CHUNKS) #define MNG_ACCESS_CHUNKS 1 #endif #include #include "libgimp/gimp.h" #include "libgimp/gimpui.h" #include "libgimp/stdplugins-intl.h" #define SCALE_WIDTH 125 enum { CHUNKS_PNG_D, CHUNKS_JNG_D, CHUNKS_PNG, CHUNKS_JNG }; enum { DISPOSE_COMBINE, DISPOSE_REPLACE }; /* The contents of this struct remain static among multiple * invocations of the plug-in. */ /* TODO: describe the members of the struct */ struct mng_data_t { gint32 interlaced; gint32 bkgd; gint32 gama; gint32 phys; gint32 time; gint32 default_chunks; gfloat quality; gfloat smoothing; gint32 compression_level; gint32 loop; gint32 default_delay; gint32 default_dispose; }; /* Values of the instance of the above struct when the plug-in is * first invoked. */ struct mng_data_t mng_data = { FALSE, /* interlaced */ FALSE, /* bkgd */ FALSE, /* gama */ TRUE, /* phys */ TRUE, /* time */ CHUNKS_PNG_D, /* default_chunks */ 0.75, /* quality */ 0.0, /* smoothing */ 9, /* compression_level */ TRUE, /* loop */ 100, /* default_delay */ DISPOSE_COMBINE /* default_dispose */ }; /* The output FILE pointer which is used by libmng; * passed around as user data. */ struct mnglib_userdata_t { FILE *fp; }; /* * Function prototypes */ static mng_ptr myalloc (mng_size_t size); static void myfree (mng_ptr ptr, mng_size_t size); static mng_bool myopenstream (mng_handle handle); static mng_bool myclosestream (mng_handle handle); static mng_bool mywritedata (mng_handle handle, mng_ptr buf, mng_uint32 size, mng_uint32 *written_size); static gint32 parse_chunks_type_from_layer_name (const gchar *str); static gint32 parse_disposal_type_from_layer_name (const gchar *str); static gint32 parse_ms_tag_from_layer_name (const gchar *str); static gint find_unused_ia_colour (guchar *pixels, gint numpixels, gint *colors); static gboolean ia_has_transparent_pixels (guchar *pixels, gint numpixels); static gboolean respin_cmap (png_structp png_ptr, png_infop png_info_ptr, guchar *remap, gint32 image_id, GimpDrawable *drawable); static gint mng_save_image (const gchar *filename, gint32 image_id, gint32 drawable_id, gint32 original_image_id); static gint mng_save_dialog (gint32 image_id); static void query (void); static void run (const gchar *name, gint nparams, const GimpParam *param, gint *nreturn_vals, GimpParam **return_vals); /* * Callbacks for libmng */ static mng_ptr myalloc (mng_size_t size) { gpointer ptr; ptr = g_try_malloc ((gulong) size); if (ptr != NULL) memset (ptr, 0, size); return ((mng_ptr) ptr); } static void myfree (mng_ptr ptr, mng_size_t size) { g_free (ptr); } static mng_bool myopenstream (mng_handle handle) { return MNG_TRUE; } static mng_bool myclosestream (mng_handle handle) { return MNG_TRUE; } static mng_bool mywritedata (mng_handle handle, mng_ptr buf, mng_uint32 size, mng_uint32 *written_size) { struct mnglib_userdata_t *userdata = (struct mnglib_userdata_t *) mng_get_userdata (handle); *written_size = (mng_uint32) fwrite ((void *) buf, 1, (size_t) size, userdata->fp); return MNG_TRUE; } /* Parses which output chunk type to use for this layer * from the layer name. */ static gint32 parse_chunks_type_from_layer_name (const gchar *str) { guint i; for (i = 0; (i + 5) <= strlen (str); i++) { if (g_ascii_strncasecmp (str + i, "(png)", 5) == 0) return CHUNKS_PNG; else if (g_ascii_strncasecmp (str + i, "(jng)", 5) == 0) return CHUNKS_JNG; } return mng_data.default_chunks; } /* Parses which disposal type to use for this layer * from the layer name. */ static gint32 parse_disposal_type_from_layer_name (const gchar *str) { guint i; for (i = 0; (i + 9) <= strlen (str); i++) { if (g_ascii_strncasecmp (str + i, "(combine)", 9) == 0) return DISPOSE_COMBINE; else if (g_ascii_strncasecmp (str + i, "(replace)", 9) == 0) return DISPOSE_REPLACE; } return mng_data.default_dispose; } /* Parses the millisecond delay to use for this layer * from the layer name. */ static gint32 parse_ms_tag_from_layer_name (const gchar *str) { guint offset = 0; gint32 sum = 0; guint length = strlen (str); while (TRUE) { while ((offset < length) && (str[offset] != '(')) offset++; if (offset >= length) return mng_data.default_delay; offset++; if (g_ascii_isdigit (str[offset])) break; } do { sum *= 10; sum += str[offset] - '0'; offset++; } while ((offset < length) && (g_ascii_isdigit (str[offset]))); if ((length - offset) <= 2) return mng_data.default_delay; if ((g_ascii_toupper (str[offset]) != 'M') || (g_ascii_toupper (str[offset + 1]) != 'S')) return mng_data.default_delay; return sum; } /* Try to find a colour in the palette which isn't actually * used in the image, so that we can use it as the transparency * index. Taken from png.c */ static gint find_unused_ia_colour (guchar *pixels, gint numpixels, gint *colors) { gint i; gboolean ix_used[256]; gboolean trans_used = FALSE; for (i = 0; i < *colors; i++) { ix_used[i] = FALSE; } for (i = 0; i < numpixels; i++) { /* If alpha is over a threshold, the colour index in the * palette is taken. Otherwise, this pixel is transparent. */ if (pixels[i * 2 + 1] > 127) ix_used[pixels[i * 2]] = TRUE; else trans_used = TRUE; } /* If there is no transparency, ignore alpha. */ if (trans_used == FALSE) return -1; for (i = 0; i < *colors; i++) { if (ix_used[i] == FALSE) { return i; } } /* Couldn't find an unused colour index within the number of bits per pixel we wanted. Will have to increment the number of colours in the image and assign a transparent pixel there. */ if ((*colors) < 256) { (*colors)++; return ((*colors) - 1); } return (-1); } static gboolean ia_has_transparent_pixels (guchar *pixels, gint numpixels) { while (numpixels --) { if (pixels [1] <= 127) return TRUE; pixels += 2; } return FALSE; } /* Spins the color map (palette) putting the transparent color at * index 0 if there is space. If there isn't any space, warn the user * and forget about transparency. Returns TRUE if the colormap has * been changed and FALSE otherwise. */ static gboolean respin_cmap (png_structp png_ptr, png_infop png_info_ptr, guchar *remap, gint32 image_id, GimpDrawable *drawable) { static guchar trans[] = { 0 }; guchar *before; guchar *pixels; gint numpixels; gint colors; gint transparent; gint cols, rows; GimpPixelRgn pixel_rgn; before = gimp_image_get_colormap (image_id, &colors); /* * Make sure there is something in the colormap. */ if (colors == 0) { before = g_new0 (guchar, 3); colors = 1; } cols = drawable->width; rows = drawable->height; numpixels = cols * rows; gimp_pixel_rgn_init (&pixel_rgn, drawable, 0, 0, drawable->width, drawable->height, FALSE, FALSE); pixels = (guchar *) g_malloc (numpixels * 2); gimp_pixel_rgn_get_rect (&pixel_rgn, pixels, 0, 0, drawable->width, drawable->height); if (ia_has_transparent_pixels (pixels, numpixels)) { transparent = find_unused_ia_colour (pixels, numpixels, &colors); if (transparent != -1) { png_color palette[256]; gint i; png_set_tRNS (png_ptr, png_info_ptr, (png_bytep) trans, 1, NULL); /* Transform all pixels with a value = transparent to * 0 and vice versa to compensate for re-ordering in palette * due to png_set_tRNS() */ remap[0] = transparent; remap[transparent] = 0; /* Copy from index 0 to index transparent - 1 to index 1 to * transparent of after, then from transparent+1 to colors-1 * unchanged, and finally from index transparent to index 0. */ for (i = 0; i < colors; i++) { palette[i].red = before[3 * remap[i]]; palette[i].green = before[3 * remap[i] + 1]; palette[i].blue = before[3 * remap[i] + 2]; } png_set_PLTE (png_ptr, png_info_ptr, (png_colorp) palette, colors); return TRUE; } else g_message (_("Couldn't losslessly save transparency, " "saving opacity instead.")); } png_set_PLTE (png_ptr, png_info_ptr, (png_colorp) before, colors); return FALSE; } static gint mng_save_image (const gchar *filename, gint32 image_id, gint32 drawable_id, gint32 original_image_id) { gint rows, cols; gdouble gamma; volatile gint i; time_t t; struct tm *gmt; gint num_layers; gint32 *layers; struct mnglib_userdata_t *userdata; mng_handle handle; mng_retcode ret; guint32 mng_ticks_per_second; guint32 mng_simplicity_profile; layers = gimp_image_get_layers (image_id, &num_layers); if (num_layers < 1) return 0; if (num_layers > 1) mng_ticks_per_second = 1000; else mng_ticks_per_second = 0; rows = gimp_image_height (image_id); cols = gimp_image_width (image_id); mng_simplicity_profile = (MNG_SIMPLICITY_VALID | MNG_SIMPLICITY_SIMPLEFEATURES | MNG_SIMPLICITY_COMPLEXFEATURES); mng_simplicity_profile |= (MNG_SIMPLICITY_JNG | MNG_SIMPLICITY_DELTAPNG); /* JNG and delta-PNG chunks exist */ for (i = 0; i < num_layers; i++) if (gimp_drawable_has_alpha (layers[i])) { mng_simplicity_profile |= MNG_SIMPLICITY_TRANSPARENCY; /* internal transparency exists */ mng_simplicity_profile |= 0x00000040; /* validity of following */ mng_simplicity_profile |= 0x00000100; /* semi-transparency exists */ mng_simplicity_profile |= 0x00000080; /* background transparency should happen */ break; } userdata = g_new0 (struct mnglib_userdata_t, 1); if ((userdata->fp = fopen (filename, "wb")) == NULL) { g_message (_("Could not open '%s' for writing: %s"), gimp_filename_to_utf8 (filename), g_strerror (errno)); g_free (userdata); return 0; } if ((handle = mng_initialize ((mng_ptr) userdata, myalloc, myfree, MNG_NULL)) == NULL) { g_warning ("Unable to mng_initialize() in mng_save_image()"); fclose (userdata->fp); g_free (userdata); return 0; } if (((ret = mng_setcb_openstream (handle, myopenstream)) != MNG_NOERROR) || ((ret = mng_setcb_closestream (handle, myclosestream)) != MNG_NOERROR) || ((ret = mng_setcb_writedata (handle, mywritedata)) != MNG_NOERROR)) { g_warning ("Unable to setup callbacks in mng_save_image()"); mng_cleanup (&handle); fclose (userdata->fp); g_free (userdata); return 0; } if ((ret = mng_create (handle)) != MNG_NOERROR) { g_warning ("Unable to mng_create() image in mng_save_image()"); mng_cleanup (&handle); fclose (userdata->fp); g_free (userdata); return 0; } if ((ret = mng_putchunk_mhdr (handle, cols, rows, mng_ticks_per_second, 1, num_layers, mng_data.default_delay, mng_simplicity_profile)) != MNG_NOERROR) { g_warning ("Unable to mng_putchunk_mhdr() in mng_save_image()"); mng_cleanup (&handle); fclose (userdata->fp); g_free (userdata); return 0; } if ((num_layers > 1) && (mng_data.loop)) { if ((ret = mng_putchunk_term (handle, MNG_TERMACTION_REPEAT, MNG_ITERACTION_LASTFRAME, parse_ms_tag_from_layer_name (gimp_drawable_get_name (layers[0])), 0x7fffffff)) != MNG_NOERROR) { g_warning ("Unable to mng_putchunk_term() in mng_save_image()"); mng_cleanup (&handle); fclose (userdata->fp); g_free (userdata); return 0; } } else { if ((ret = mng_putchunk_term (handle, MNG_TERMACTION_LASTFRAME, MNG_ITERACTION_LASTFRAME, parse_ms_tag_from_layer_name (gimp_drawable_get_name (layers[0])), 0x7fffffff)) != MNG_NOERROR) { g_warning ("Unable to mng_putchunk_term() in mng_save_image()"); mng_cleanup (&handle); fclose (userdata->fp); g_free (userdata); return 0; } } /* For now, we hardwire a comment */ if ((ret = mng_putchunk_text (handle, strlen (MNG_TEXT_TITLE), MNG_TEXT_TITLE, 22, "Created using The GIMP")) != MNG_NOERROR) { g_warning ("Unable to mng_putchunk_text() in mng_save_image()"); mng_cleanup (&handle); fclose (userdata->fp); g_free (userdata); return 0; } #if 0 /* how do we get this to work? */ if (mng_data.bkgd) { GimpRGB bgcolor; guchar red, green, blue; gimp_context_get_background(&bgcolor); gimp_rgb_get_uchar(&bgcolor, &red, &green, &blue); if ((ret = mng_putchunk_back(handle, red, green, blue, MNG_BACKGROUNDCOLOR_MANDATORY, 0, MNG_BACKGROUNDIMAGE_NOTILE)) != MNG_NOERROR) { g_warning("Unable to mng_putchunk_back() in mng_save_image()"); mng_cleanup(&handle); fclose(userdata->fp); g_free(userdata); return 0; } if ((ret = mng_putchunk_bkgd(handle, MNG_FALSE, 2, 0, gimp_rgb_intensity_uchar(&bgcolor), red, green, blue)) != MNG_NOERROR) { g_warning("Unable to mng_putchunk_bkgd() in mng_save_image()"); mng_cleanup(&handle); fclose(userdata->fp); g_free(userdata); return 0; } } #endif if (mng_data.gama) { gamma = gimp_gamma (); if ((ret = mng_putchunk_gama (handle, MNG_FALSE, (1.0 / ((gamma != 1.0) ? gamma : 2.2)) * 100000)) != MNG_NOERROR) { g_warning ("Unable to mng_putchunk_gama() in mng_save_image()"); mng_cleanup (&handle); fclose (userdata->fp); g_free (userdata); return 0; } } #if 0 /* how do we get this to work? */ if (mng_data.phys) { gimp_image_get_resolution(original_image_id, &xres, &yres); if ((ret = mng_putchunk_phyg(handle, MNG_FALSE, (mng_uint32) (xres * 39.37), (mng_uint32) (yres * 39.37), 1)) != MNG_NOERROR) { g_warning("Unable to mng_putchunk_phyg() in mng_save_image()"); mng_cleanup(&handle); fclose(userdata->fp); g_free(userdata); return 0; } if ((ret = mng_putchunk_phys(handle, MNG_FALSE, (mng_uint32) (xres * 39.37), (mng_uint32) (yres * 39.37), 1)) != MNG_NOERROR) { g_warning("Unable to mng_putchunk_phys() in mng_save_image()"); mng_cleanup(&handle); fclose(userdata->fp); g_free(userdata); return 0; } } #endif if (mng_data.time) { t = time (NULL); gmt = gmtime (&t); if ((ret = mng_putchunk_time (handle, gmt->tm_year + 1900, gmt->tm_mon + 1, gmt->tm_mday, gmt->tm_hour, gmt->tm_min, gmt->tm_sec)) != MNG_NOERROR) { g_warning ("Unable to mng_putchunk_time() in mng_save_image()"); mng_cleanup (&handle); fclose (userdata->fp); g_free (userdata); return 0; } } if (gimp_image_base_type (image_id) == GIMP_INDEXED) { guchar *palette; gint numcolors; palette = gimp_image_get_colormap (image_id, &numcolors); if (numcolors != 0 && (ret = mng_putchunk_plte (handle, numcolors, (mng_palette8e *) palette)) != MNG_NOERROR) { g_warning ("Unable to mng_putchunk_plte() in mng_save_image()"); mng_cleanup (&handle); fclose (userdata->fp); g_free (userdata); return 0; } } for (i = (num_layers - 1); i >= 0; i--) { gint num_colors; GimpImageType layer_drawable_type; GimpDrawable *layer_drawable; gint layer_offset_x, layer_offset_y; gint layer_rows, layer_cols; gchar *layer_name; gint layer_chunks_type; volatile gint layer_bpp; GimpPixelRgn layer_pixel_rgn; guint8 layer_mng_colortype; guint8 layer_mng_compression_type; guint8 layer_mng_interlace_type; gboolean layer_has_unique_palette; gchar frame_mode; int frame_delay; gchar *temp_file_name; png_structp png_ptr; png_infop png_info_ptr; FILE *infile, *outfile; int num_passes; int tile_height; guchar **layer_pixels, *layer_pixel; int pass, j, k, begin, end, num; guchar *fixed; guchar layer_remap[256]; layer_name = gimp_drawable_get_name (layers[i]); layer_chunks_type = parse_chunks_type_from_layer_name (layer_name); layer_drawable_type = gimp_drawable_type (layers[i]); layer_drawable = gimp_drawable_get (layers[i]); layer_rows = layer_drawable->height; layer_cols = layer_drawable->width; gimp_drawable_offsets (layers[i], &layer_offset_x, &layer_offset_y); layer_has_unique_palette = TRUE; for (j = 0; j < 256; j++) layer_remap[j] = j; switch (layer_drawable_type) { case GIMP_RGB_IMAGE: layer_bpp = 3; layer_mng_colortype = MNG_COLORTYPE_RGB; break; case GIMP_RGBA_IMAGE: layer_bpp = 4; layer_mng_colortype = MNG_COLORTYPE_RGBA; break; case GIMP_GRAY_IMAGE: layer_bpp = 1; layer_mng_colortype = MNG_COLORTYPE_GRAY; break; case GIMP_GRAYA_IMAGE: layer_bpp = 2; layer_mng_colortype = MNG_COLORTYPE_GRAYA; break; case GIMP_INDEXED_IMAGE: layer_bpp = 1; layer_mng_colortype = MNG_COLORTYPE_INDEXED; break; case GIMP_INDEXEDA_IMAGE: layer_bpp = 2; layer_mng_colortype = MNG_COLORTYPE_INDEXED | MNG_COLORTYPE_GRAYA; break; default: g_warning ("Unsupported GimpImageType in mng_save_image()"); mng_cleanup (&handle); fclose (userdata->fp); g_free (userdata); return 0; } /* Delta PNG chunks are not yet supported */ /* if (i == (num_layers - 1)) */ { if (layer_chunks_type == CHUNKS_JNG_D) layer_chunks_type = CHUNKS_JNG; else if (layer_chunks_type == CHUNKS_PNG_D) layer_chunks_type = CHUNKS_PNG; } switch (layer_chunks_type) { case CHUNKS_PNG_D: layer_mng_compression_type = MNG_COMPRESSION_DEFLATE; if (mng_data.interlaced != 0) layer_mng_interlace_type = MNG_INTERLACE_ADAM7; else layer_mng_interlace_type = MNG_INTERLACE_NONE; break; case CHUNKS_JNG_D: layer_mng_compression_type = MNG_COMPRESSION_DEFLATE; if (mng_data.interlaced != 0) layer_mng_interlace_type = MNG_INTERLACE_ADAM7; else layer_mng_interlace_type = MNG_INTERLACE_NONE; break; case CHUNKS_PNG: layer_mng_compression_type = MNG_COMPRESSION_DEFLATE; if (mng_data.interlaced != 0) layer_mng_interlace_type = MNG_INTERLACE_ADAM7; else layer_mng_interlace_type = MNG_INTERLACE_NONE; break; case CHUNKS_JNG: layer_mng_compression_type = MNG_COMPRESSION_BASELINEJPEG; if (mng_data.interlaced != 0) layer_mng_interlace_type = MNG_INTERLACE_PROGRESSIVE; else layer_mng_interlace_type = MNG_INTERLACE_SEQUENTIAL; break; default: g_warning ("Huh? Programmer stupidity error with 'layer_chunks_type'"); mng_cleanup (&handle); fclose (userdata->fp); g_free (userdata); return 0; } if ((i == (num_layers - 1)) || (parse_disposal_type_from_layer_name (layer_name) != DISPOSE_COMBINE)) frame_mode = MNG_FRAMINGMODE_3; else frame_mode = MNG_FRAMINGMODE_1; frame_delay = parse_ms_tag_from_layer_name (layer_name); if ((ret = mng_putchunk_fram (handle, MNG_FALSE, frame_mode, 0, NULL, 2, 0, 2, 0, frame_delay, 0, 0, layer_offset_x, layer_offset_x + layer_cols, layer_offset_y, layer_offset_y + layer_rows, 0, 0)) != MNG_NOERROR) { g_warning ("Unable to mng_putchunk_fram() in mng_save_image()"); mng_cleanup (&handle); fclose (userdata->fp); g_free (userdata); return 0; } if ((layer_offset_x > 0) || (layer_offset_y > 0)) if ((ret = mng_putchunk_defi (handle, 0, 0, 1, 1, layer_offset_x, layer_offset_y, 1, layer_offset_x, layer_offset_x + layer_cols, layer_offset_y, layer_offset_y + layer_rows)) != MNG_NOERROR) { g_warning ("Unable to mng_putchunk_defi() in mng_save_image()"); mng_cleanup (&handle); fclose (userdata->fp); g_free (userdata); return 0; } if ((temp_file_name = gimp_temp_name ("mng")) == NULL) { g_warning ("gimp_temp_name() failed in mng_save_image("); mng_cleanup (&handle); fclose (userdata->fp); g_free (userdata); return 0; } if ((outfile = fopen (temp_file_name, "wb")) == NULL) { g_message (_("Could not open '%s' for writing: %s"), gimp_filename_to_utf8 (temp_file_name), g_strerror (errno)); unlink (temp_file_name); mng_cleanup (&handle); fclose (userdata->fp); g_free (userdata); return 0; } if ((png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING, (png_voidp) NULL, (png_error_ptr) NULL, (png_error_ptr) NULL)) == NULL) { g_warning ("Unable to png_create_write_struct() in mng_save_image()"); fclose (outfile); unlink (temp_file_name); mng_cleanup (&handle); fclose (userdata->fp); g_free (userdata); return 0; } if ((png_info_ptr = png_create_info_struct (png_ptr)) == NULL) { g_warning ("Unable to png_create_info_struct() in mng_save_image()"); png_destroy_write_struct (&png_ptr, (png_infopp) NULL); fclose (outfile); unlink (temp_file_name); mng_cleanup (&handle); fclose (userdata->fp); g_free (userdata); return 0; } if (setjmp (png_ptr->jmpbuf) != 0) { g_warning ("HRM saving PNG in mng_save_image()"); png_destroy_write_struct (&png_ptr, (png_infopp) NULL); fclose (outfile); unlink (temp_file_name); mng_cleanup (&handle); fclose (userdata->fp); g_free (userdata); return 0; } png_init_io (png_ptr, outfile); png_set_compression_level (png_ptr, mng_data.compression_level); png_info_ptr->width = layer_cols; png_info_ptr->height = layer_rows; png_info_ptr->interlace_type = ((mng_data.interlaced == 0) ? 0 : 1); png_info_ptr->bit_depth = 8; switch (layer_drawable_type) { case GIMP_RGB_IMAGE: png_info_ptr->color_type = PNG_COLOR_TYPE_RGB; break; case GIMP_RGBA_IMAGE: png_info_ptr->color_type = PNG_COLOR_TYPE_RGB_ALPHA; break; case GIMP_GRAY_IMAGE: png_info_ptr->color_type = PNG_COLOR_TYPE_GRAY; break; case GIMP_GRAYA_IMAGE: png_info_ptr->color_type = PNG_COLOR_TYPE_GRAY_ALPHA; break; case GIMP_INDEXED_IMAGE: png_info_ptr->color_type = PNG_COLOR_TYPE_PALETTE; png_info_ptr->valid |= PNG_INFO_PLTE; png_info_ptr->palette = (png_colorp) gimp_image_get_colormap (image_id, &num_colors); png_info_ptr->num_palette = num_colors; break; case GIMP_INDEXEDA_IMAGE: png_info_ptr->color_type = PNG_COLOR_TYPE_PALETTE; layer_has_unique_palette = respin_cmap (png_ptr, png_info_ptr, layer_remap, image_id, layer_drawable); break; default: g_warning ("This can't be!\n"); return 0; } if ((png_info_ptr->valid & PNG_INFO_PLTE) == PNG_INFO_PLTE) { if (png_info_ptr->num_palette <= 2) png_info_ptr->bit_depth = 1; else if (png_info_ptr->num_palette <= 4) png_info_ptr->bit_depth = 2; else if (png_info_ptr->num_palette <= 16) png_info_ptr->bit_depth = 4; } png_write_info (png_ptr, png_info_ptr); if (mng_data.interlaced != 0) num_passes = png_set_interlace_handling (png_ptr); else num_passes = 1; if ((png_info_ptr->color_type == PNG_COLOR_TYPE_PALETTE) && (png_info_ptr->bit_depth < 8)) png_set_packing (png_ptr); tile_height = gimp_tile_height (); layer_pixel = g_new (guchar, tile_height * layer_cols * layer_bpp); layer_pixels = g_new (guchar *, tile_height); for (j = 0; j < tile_height; j++) layer_pixels[j] = layer_pixel + (layer_cols * layer_bpp * j); gimp_pixel_rgn_init (&layer_pixel_rgn, layer_drawable, 0, 0, layer_cols, layer_rows, FALSE, FALSE); for (pass = 0; pass < num_passes; pass++) { for (begin = 0, end = tile_height; begin < layer_rows; begin += tile_height, end += tile_height) { if (end > layer_rows) end = layer_rows; num = end - begin; gimp_pixel_rgn_get_rect (&layer_pixel_rgn, layer_pixel, 0, begin, layer_cols, num); if ((png_info_ptr->valid & PNG_INFO_tRNS) == PNG_INFO_tRNS) { for (j = 0; j < num; j++) { fixed = layer_pixels[j]; for (k = 0; k < layer_cols; k++) fixed[k] = ((fixed[k * 2 + 1] > 127) ? layer_remap[fixed[k * 2]] : 0); } } else if (((png_info_ptr->valid & PNG_INFO_PLTE) == PNG_INFO_PLTE) && (layer_bpp == 2)) { for (j = 0; j < num; j++) { fixed = layer_pixels[j]; for (k = 0; k < layer_cols; k++) fixed[k] = fixed[k * 2]; } } png_write_rows (png_ptr, layer_pixels, num); } } png_write_end (png_ptr, png_info_ptr); png_destroy_write_struct (&png_ptr, &png_info_ptr); g_free (layer_pixels); g_free (layer_pixel); fclose (outfile); if ((infile = fopen (temp_file_name, "rb")) == NULL) { g_message (_("Could not open '%s' for reading: %s"), gimp_filename_to_utf8 (temp_file_name), g_strerror (errno)); unlink (temp_file_name); mng_cleanup (&handle); fclose (userdata->fp); g_free (userdata); return 0; } fseek (infile, 8L, SEEK_SET); while (!feof (infile)) { unsigned char chunksize_chars[4]; unsigned long chunksize; unsigned char chunkname[5]; guchar *chunkbuffer; long chunkwidth; long chunkheight; char chunkbitdepth; char chunkcolortype; char chunkcompression; char chunkfilter; char chunkinterlaced; if (fread (chunksize_chars, 1, 4, infile) != 4) break; if (fread (chunkname, 1, 4, infile) != 4) break; chunkname[4] = 0; chunksize = (chunksize_chars[0] << 24) | (chunksize_chars[1] << 16) | (chunksize_chars[2] << 8) | chunksize_chars[3]; chunkbuffer = NULL; if (chunksize > 0) { chunkbuffer = g_new (guchar, chunksize); if (fread (chunkbuffer, 1, chunksize, infile) != chunksize) break; } if (strncmp (chunkname, "IHDR", 4) == 0) { chunkwidth = (chunkbuffer[0] << 24) | (chunkbuffer[1] << 16) | (chunkbuffer[2] << 8) | chunkbuffer[3]; chunkheight = (chunkbuffer[4] << 24) | (chunkbuffer[5] << 16) | (chunkbuffer[6] << 8) | chunkbuffer[7]; chunkbitdepth = chunkbuffer[8]; chunkcolortype = chunkbuffer[9]; chunkcompression = chunkbuffer[10]; chunkfilter = chunkbuffer[11]; chunkinterlaced = chunkbuffer[12]; if ((ret = mng_putchunk_ihdr (handle, chunkwidth, chunkheight, chunkbitdepth, chunkcolortype, chunkcompression, chunkfilter, chunkinterlaced)) != MNG_NOERROR) { g_warning ("Unable to mng_putchunk_ihdr() in mng_save_image()"); mng_cleanup (&handle); fclose (userdata->fp); g_free (userdata); return 0; } } else if (strncmp (chunkname, "IDAT", 4) == 0) { if ((ret = mng_putchunk_idat (handle, chunksize, chunkbuffer)) != MNG_NOERROR) { g_warning ("Unable to mng_putchunk_idat() in mng_save_image()"); mng_cleanup (&handle); fclose (userdata->fp); g_free (userdata); return 0; } } else if (strncmp (chunkname, "IEND", 4) == 0) { if ((ret = mng_putchunk_iend (handle)) != MNG_NOERROR) { g_warning ("Unable to mng_putchunk_iend() in mng_save_image()"); mng_cleanup (&handle); fclose (userdata->fp); g_free (userdata); return 0; } } else if (strncmp (chunkname, "PLTE", 4) == 0) { /* if this frame's palette is the same as the global palette, write a 0-color palette chunk */ if ((ret = mng_putchunk_plte (handle, layer_has_unique_palette ? (chunksize / 3) : 0, (mng_palette8e *) chunkbuffer)) != MNG_NOERROR) { g_warning ("Unable to mng_putchunk_plte() in mng_save_image()"); mng_cleanup (&handle); fclose (userdata->fp); g_free (userdata); return 0; } } else if (strncmp (chunkname, "tRNS", 4) == 0) { if ((ret = mng_putchunk_trns (handle, 0, 0, 3, chunksize, (mng_uint8 *) chunkbuffer, 0, 0, 0, 0, 0, (mng_uint8 *) chunkbuffer)) != MNG_NOERROR) { g_warning ("Unable to mng_putchunk_trns() in mng_save_image()"); mng_cleanup (&handle); fclose (userdata->fp); g_free (userdata); return 0; } } if (chunksize > 0) g_free (chunkbuffer); /* read 4 bytes after the chunk */ fread (chunkname, 1, 4, infile); } fclose (infile); unlink (temp_file_name); } if ((ret = mng_putchunk_mend (handle)) != MNG_NOERROR) { g_warning ("Unable to mng_putchunk_mend() in mng_save_image()"); mng_cleanup (&handle); fclose (userdata->fp); g_free (userdata); return 0; } if ((ret = mng_write (handle)) != MNG_NOERROR) { g_warning ("Unable to mng_write() the image in mng_save_image()"); mng_cleanup (&handle); fclose (userdata->fp); g_free (userdata); return 0; } mng_cleanup (&handle); fclose (userdata->fp); g_free (userdata); return TRUE; } /* The interactive dialog. */ static gint mng_save_dialog (gint32 image_id) { GtkWidget *dlg; GtkWidget *main_vbox; GtkWidget *frame; GtkWidget *vbox; GtkWidget *table; GtkWidget *toggle; GtkWidget *hbox; GtkWidget *combo; GtkWidget *label; GtkWidget *scale; GtkObject *scale_adj; GtkWidget *spinbutton; GtkObject *spinbutton_adj; gint num_layers; gboolean run; dlg = gimp_dialog_new (_("Save as MNG"), "mng", NULL, 0, gimp_standard_help_func, "file-mng-save", GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL); main_vbox = gtk_vbox_new (FALSE, 12); gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12); gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dlg)->vbox), main_vbox); frame = gimp_frame_new (_("MNG Options")); gtk_box_pack_start (GTK_BOX (main_vbox), frame, TRUE, TRUE, 0); vbox = gtk_vbox_new (FALSE, 6); gtk_container_add (GTK_CONTAINER (frame), vbox); toggle = gtk_check_button_new_with_label (_("Interlace")); gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0); g_signal_connect (toggle, "toggled", G_CALLBACK (gimp_toggle_button_update), &mng_data.interlaced); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), mng_data.interlaced); gtk_widget_show (toggle); toggle = gtk_check_button_new_with_label (_("Save background color")); gtk_widget_set_sensitive (toggle, FALSE); gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0); g_signal_connect (toggle, "toggled", G_CALLBACK (gimp_toggle_button_update), &mng_data.bkgd); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), mng_data.bkgd); gtk_widget_show (toggle); toggle = gtk_check_button_new_with_label (_("Save gamma")); gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0); g_signal_connect (toggle, "toggled", G_CALLBACK (gimp_toggle_button_update), &mng_data.gama); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), mng_data.gama); gtk_widget_show (toggle); toggle = gtk_check_button_new_with_label (_("Save resolution")); gtk_widget_set_sensitive (toggle, FALSE); gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0); g_signal_connect (toggle, "toggled", G_CALLBACK (gimp_toggle_button_update), &mng_data.phys); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), mng_data.phys); gtk_widget_show (toggle); toggle = gtk_check_button_new_with_label (_("Save creation time")); gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0); g_signal_connect (toggle, "toggled", G_CALLBACK (gimp_toggle_button_update), &mng_data.time); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), mng_data.time); gtk_widget_show (toggle); table = gtk_table_new (2, 4, FALSE); gtk_table_set_col_spacings (GTK_TABLE (table), 6); gtk_table_set_row_spacings (GTK_TABLE (table), 6); gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0); gtk_widget_show (table); gimp_image_get_layers (image_id, &num_layers); if (num_layers == 1) combo = gimp_int_combo_box_new (_("PNG"), CHUNKS_PNG_D, _("JNG"), CHUNKS_JNG_D, NULL); else combo = gimp_int_combo_box_new (_("PNG + delta PNG"), CHUNKS_PNG_D, _("JNG + delta PNG"), CHUNKS_JNG_D, _("All PNG"), CHUNKS_PNG, _("All JNG"), CHUNKS_JNG, NULL); gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo), mng_data.default_chunks); g_signal_connect (combo, "changed", G_CALLBACK (gimp_int_combo_box_get_active), &mng_data.default_chunks); gtk_widget_set_sensitive (combo, FALSE); gimp_table_attach_aligned (GTK_TABLE (table), 0, 0, _("Default chunks type:"), 0.0, 0.5, combo, 1, FALSE); combo = gimp_int_combo_box_new (_("Combine"), DISPOSE_COMBINE, _("Replace"), DISPOSE_REPLACE, NULL); gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo), mng_data.default_dispose); g_signal_connect (combo, "changed", G_CALLBACK (gimp_int_combo_box_get_active), &mng_data.default_dispose); gimp_table_attach_aligned (GTK_TABLE (table), 0, 1, _("Default frame disposal:"), 0.0, 0.5, combo, 1, FALSE); scale_adj = gtk_adjustment_new (mng_data.compression_level, 0.0, 9.0, 1.0, 1.0, 0.0); scale = gtk_hscale_new (GTK_ADJUSTMENT (scale_adj)); gtk_widget_set_size_request (scale, SCALE_WIDTH, -1); gtk_scale_set_value_pos (GTK_SCALE (scale), GTK_POS_TOP); gtk_scale_set_digits (GTK_SCALE (scale), 0); gtk_range_set_update_policy (GTK_RANGE (scale), GTK_UPDATE_DELAYED); gimp_table_attach_aligned (GTK_TABLE (table), 0, 2, _("PNG compression level:"), 0.0, 0.9, scale, 1, FALSE); g_signal_connect (scale_adj, "value_changed", G_CALLBACK (gimp_int_adjustment_update), &mng_data.compression_level); gimp_help_set_help_data (scale, _("Choose a high compression level " "for small file size"), NULL); scale_adj = gtk_adjustment_new (mng_data.quality, 0.0, 1.0, 0.01, 0.01, 0.0); scale = gtk_hscale_new (GTK_ADJUSTMENT (scale_adj)); gtk_widget_set_size_request (scale, SCALE_WIDTH, -1); gtk_scale_set_value_pos (GTK_SCALE (scale), GTK_POS_TOP); gtk_scale_set_digits (GTK_SCALE (scale), 2); gtk_range_set_update_policy (GTK_RANGE (scale), GTK_UPDATE_DELAYED); gtk_widget_set_sensitive (scale, FALSE); gimp_table_attach_aligned (GTK_TABLE (table), 0, 3, _("JPEG compression quality:"), 0.0, 0.9, scale, 1, FALSE); g_signal_connect (scale_adj, "value_changed", G_CALLBACK (gimp_int_adjustment_update), &mng_data.quality); scale_adj = gtk_adjustment_new (mng_data.smoothing, 0.0, 1.0, 0.01, 0.01, 0.0); scale = gtk_hscale_new (GTK_ADJUSTMENT (scale_adj)); gtk_widget_set_size_request (scale, SCALE_WIDTH, -1); gtk_scale_set_value_pos (GTK_SCALE (scale), GTK_POS_TOP); gtk_scale_set_digits (GTK_SCALE (scale), 2); gtk_range_set_update_policy (GTK_RANGE (scale), GTK_UPDATE_DELAYED); gtk_widget_set_sensitive (scale, FALSE); gimp_table_attach_aligned (GTK_TABLE (table), 0, 4, _("JPEG smoothing factor:"), 0.0, 0.9, scale, 1, FALSE); g_signal_connect (scale_adj, "value_changed", G_CALLBACK (gimp_int_adjustment_update), &mng_data.smoothing); gtk_widget_show (vbox); gtk_widget_show (frame); frame = gimp_frame_new (_("Animated MNG options")); gtk_box_pack_start (GTK_BOX (main_vbox), frame, TRUE, TRUE, 0); vbox = gtk_vbox_new (FALSE, 6); gtk_container_add (GTK_CONTAINER (frame), vbox); toggle = gtk_check_button_new_with_label (_("Loop")); gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0); g_signal_connect (toggle, "toggled", G_CALLBACK (gimp_toggle_button_update), &mng_data.loop); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), mng_data.loop); gtk_widget_show (toggle); hbox = gtk_hbox_new (FALSE, 4); gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); label = gtk_label_new (_("Default frame delay:")); gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); gtk_widget_show (label); spinbutton = gimp_spin_button_new (&spinbutton_adj, mng_data.default_delay, 0, 65000, 10, 100, 0, 1, 0); g_signal_connect (spinbutton_adj, "value_changed", G_CALLBACK (gimp_int_adjustment_update), &mng_data.default_delay); gtk_box_pack_start (GTK_BOX (hbox), spinbutton, FALSE, FALSE, 0); gtk_widget_show (spinbutton); label = gtk_label_new (_("milliseconds")); gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); gtk_widget_show (label); gtk_widget_show (hbox); gtk_widget_show (vbox); gtk_widget_show (frame); gtk_widget_set_sensitive (frame, num_layers > 1); gtk_widget_show (main_vbox); gtk_widget_show (dlg); run = (gimp_dialog_run (GIMP_DIALOG (dlg)) == GTK_RESPONSE_OK); gtk_widget_destroy (dlg); return run; } /* GIMP calls these methods. */ static void query (void) { static GimpParamDef save_args[] = { { GIMP_PDB_INT32, "run_mode", "Interactive, non-interactive" }, { GIMP_PDB_IMAGE, "image", "Input image" }, { GIMP_PDB_DRAWABLE, "drawable", "Drawable to save" }, { GIMP_PDB_STRING, "filename", "The name of the file to save the image in" }, { GIMP_PDB_STRING, "raw_filename", "The name of the file to save the image in" }, { GIMP_PDB_INT32, "interlace", "Use interlacing" }, { GIMP_PDB_INT32, "compression", "PNG deflate compression level (0 - 9)" }, { GIMP_PDB_FLOAT, "quality", "JPEG quality factor (0.00 - 1.00)" }, { GIMP_PDB_FLOAT, "smoothing", "JPEG smoothing factor (0.00 - 1.00)" }, { GIMP_PDB_INT32, "loop", "(ANIMATED MNG) Loop infinitely" }, { GIMP_PDB_INT32, "default_delay", "(ANIMATED MNG) Default delay between frames in milliseconds" }, { GIMP_PDB_INT32, "default_chunks", "(ANIMATED MNG) Default chunks type (0 = PNG + Delta PNG; 1 = JNG + Delta PNG; 2 = All PNG; 3 = All JNG)" }, { GIMP_PDB_INT32, "default_dispose", "(ANIMATED MNG) Default dispose type (0 = combine; 1 = replace)" }, { GIMP_PDB_INT32, "bkgd", "Write bKGD (background color) chunk" }, { GIMP_PDB_INT32, "gama", "Write gAMA (gamma) chunk"}, { GIMP_PDB_INT32, "phys", "Write pHYs (image resolution) chunk" }, { GIMP_PDB_INT32, "time", "Write tIME (creation time) chunk" } }; gimp_install_procedure ("file_mng_save", "Saves images in the MNG file format", "This plug-in saves images in the Multiple-image " "Network Graphics (MNG) format which can be used as " "a replacement for animated GIFs, and more.", "S. Mukund ", "S. Mukund ", "November 19, 2002", N_("MNG animation"), "RGB*,GRAY*,INDEXED*", GIMP_PLUGIN, G_N_ELEMENTS (save_args), 0, save_args, NULL); gimp_register_file_handler_mime ("file_mng_save", "image/x-mng"); gimp_register_save_handler ("file_mng_save", "mng", ""); } static void run (const gchar *name, gint nparams, const GimpParam *param, gint *nreturn_vals, GimpParam **return_vals) { static GimpParam values[1]; INIT_I18N (); *nreturn_vals = 1; *return_vals = values; values[0].type = GIMP_PDB_STATUS; values[0].data.d_status = GIMP_PDB_SUCCESS; if (strcmp (name, "file_mng_save") == 0) { GimpRunMode run_mode; gint32 image_id, original_image_id; gint32 drawable_id; GimpExportReturn export = GIMP_EXPORT_IGNORE; run_mode = param[0].data.d_int32; image_id = original_image_id = param[1].data.d_int32; drawable_id = param[2].data.d_int32; if ((run_mode == GIMP_RUN_INTERACTIVE) || (run_mode == GIMP_RUN_WITH_LAST_VALS)) { gimp_procedural_db_get_data ("file_mng_save", &mng_data); gimp_ui_init ("mng", FALSE); export = gimp_export_image (&image_id, &drawable_id, "MNG", (GIMP_EXPORT_CAN_HANDLE_RGB | GIMP_EXPORT_CAN_HANDLE_GRAY | GIMP_EXPORT_CAN_HANDLE_INDEXED | GIMP_EXPORT_CAN_HANDLE_ALPHA | GIMP_EXPORT_CAN_HANDLE_LAYERS_AS_ANIMATION)); } if (export == GIMP_EXPORT_CANCEL) values[0].data.d_status = GIMP_PDB_CANCEL; else if ((export == GIMP_EXPORT_IGNORE) || (export == GIMP_EXPORT_EXPORT)) { if (run_mode == GIMP_RUN_INTERACTIVE) { if (mng_save_dialog (image_id) == 0) values[0].data.d_status = GIMP_PDB_CANCEL; } else if (run_mode == GIMP_RUN_NONINTERACTIVE) { if (nparams != 17) { g_message ("Incorrect number of parameters " "passed to file-mng-save()"); values[0].data.d_status = GIMP_PDB_CALLING_ERROR; } else { mng_data.interlaced = param[5].data.d_int32; mng_data.compression_level = param[6].data.d_int32; mng_data.quality = param[7].data.d_float; mng_data.smoothing = param[8].data.d_float; mng_data.loop = param[9].data.d_int32; mng_data.default_delay = param[10].data.d_int32; mng_data.default_chunks = param[11].data.d_int32; mng_data.default_dispose = param[12].data.d_int32; mng_data.bkgd = param[13].data.d_int32; mng_data.gama = param[14].data.d_int32; mng_data.phys = param[15].data.d_int32; mng_data.time = param[16].data.d_int32; if ((mng_data.compression_level < 0) || (mng_data.compression_level > 9)) { g_warning ("Parameter 'compression_level' passed to file-mng-save() must be in the range 0 - 9; Clamping it to the default value of 6."); mng_data.compression_level = 6; } if ((mng_data.quality < ((float) 0)) || (mng_data.quality > ((float) 1))) { g_warning ("Parameter 'quality' passed to file-mng-save() must be in the range 0.00 - 1.00; Clamping it to the default value of 0.75."); mng_data.quality = 0.75; } if ((mng_data.smoothing < ((float) 0)) || (mng_data.smoothing > ((float) 1))) { g_warning ("Parameter 'smoothing' passed to file-mng-save() must be in the range 0.00 - 1.00; Clamping it to the default value of 0.00."); mng_data.smoothing = 0.0; } if ((mng_data.default_chunks < 0) || (mng_data.default_chunks > 3)) { g_warning ("Parameter 'default_chunks' passed to file-mng-save() must be in the range 0 - 2."); values[0].data.d_status = GIMP_PDB_CALLING_ERROR; } if ((mng_data.default_dispose < 0) || (mng_data.default_dispose > 1)) { g_warning ("Parameter 'default_dispose' passed to file-mng-save() must be in the range 0 - 1."); values[0].data.d_status = GIMP_PDB_CALLING_ERROR; } } } if (values[0].data.d_status == GIMP_PDB_SUCCESS) { if (mng_save_image (param[3].data.d_string, image_id, drawable_id, original_image_id) != 0) gimp_set_data ("file_mng_save", &mng_data, sizeof (mng_data)); else values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; } if (export == GIMP_EXPORT_EXPORT) gimp_image_delete (image_id); } } else values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; } /* Only query and run are implemented by this plug-in. */ GimpPlugInInfo PLUG_IN_INFO = { NULL, NULL, query, run }; MAIN ()