diff options
Diffstat (limited to '')
-rw-r--r-- | plug-ins/common/file-compressor.c | 1017 |
1 files changed, 1017 insertions, 0 deletions
diff --git a/plug-ins/common/file-compressor.c b/plug-ins/common/file-compressor.c new file mode 100644 index 0000000..a51da9b --- /dev/null +++ b/plug-ins/common/file-compressor.c @@ -0,0 +1,1017 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * Copyright (C) 1997 Daniel Risacher, magnus@alum.mit.edu + * + * 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 <https://www.gnu.org/licenses/>. + */ + +/* Minor changes to support file magic */ +/* 4 Oct 1997 -- Risacher */ + +/* compressor plug-in for GIMP */ +/* based on gz.c which in turn is */ +/* loosley based on url.c by */ +/* Josh MacDonald, jmacd@cs.berkeley.edu */ + +/* and, very loosely on hrz.c by */ +/* Albert Cahalan <acahalan at cs.uml.edu> */ + +/* LZMA compression code is based on code by Lasse Collin which was + * placed in the public-domain. */ + +/* This is reads and writes compressed image files for GIMP + * + * It should work with file names of the form + * filename.foo.[gz|bz2] where foo is some already-recognized extension + * + * and it also works for names of the form + * filename.xcf[gz|bz2] - which is equivalent to + * filename.xcf.[gz|bz2] + * + * I added the xcfgz bit because having a default extension of xcf.gz + * can confuse the file selection dialog box somewhat, forcing the + * user to type sometimes when he/she otherwise wouldn't need to. + * + * I later decided I didn't like it because I don't like to bloat + * the file-extension namespace. But I left in the recognition + * feature/bug so if people want to use files named foo.xcfgz by + * default, they can just hack their pluginrc file. + * + * to do this hack, change : + * "xcf.gz,gz,xcfgz" + * to + * "xcfgz,gz,xcf.gz" + * + * + * -Dan Risacher, 0430 CDT, 26 May 1997 + */ + +#include "config.h" + +#include <string.h> +#include <errno.h> + +#include <sys/types.h> + +#ifdef HAVE_SYS_PARAM_H +#include <sys/param.h> +#endif + +#ifdef HAVE_SYS_WAIT_H +#include <sys/wait.h> +#endif + +#include <sys/stat.h> +#include <fcntl.h> + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include <glib/gstdio.h> +#ifndef _O_BINARY +#define _O_BINARY 0 +#endif + +#include <libgimp/gimp.h> + +#include "libgimp/stdplugins-intl.h" + +#include <zlib.h> +#include <bzlib.h> +#include <lzma.h> + + +/* Author 1: Josh MacDonald (url.c) */ +/* Author 2: Daniel Risacher (gz.c) */ +/* Author 3: Michael Natterer (compressor.c) */ + +/* According to USAF Lt Steve Werhle, US DoD software development + * contracts average about $25 USD per source line of code (SLOC). By + * that metric, I figure this plug-in is worth about $10,000 USD */ +/* But you got it free. Magic of Gnu. */ + +typedef gboolean (*LoadFn) (const char *infile, + const char *outfile); +typedef gboolean (*SaveFn) (const char *infile, + const char *outfile); + +typedef struct _Compressor Compressor; + +struct _Compressor +{ + const gchar *file_type; + const gchar *mime_type; + const gchar *extensions; + const gchar *magic; + const gchar *xcf_extension; + const gchar *generic_extension; + + const gchar *load_proc; + const gchar *load_blurb; + const gchar *load_help; + LoadFn load_fn; + + const gchar *save_proc; + const gchar *save_blurb; + const gchar *save_help; + SaveFn save_fn; +}; + + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static GimpPDBStatusType save_image (const Compressor *compressor, + const gchar *filename, + gint32 image_ID, + gint32 drawable_ID, + gint32 run_mode, + GError **error); +static gint32 load_image (const Compressor *compressor, + const gchar *filename, + gint32 run_mode, + GimpPDBStatusType *status, + GError **error); + +static gboolean valid_file (const gchar *filename); +static const gchar * find_extension (const Compressor *compressor, + const gchar *filename); + +static gboolean gzip_load (const char *infile, + const char *outfile); +static gboolean gzip_save (const char *infile, + const char *outfile); + +static gboolean bzip2_load (const char *infile, + const char *outfile); +static gboolean bzip2_save (const char *infile, + const char *outfile); + +static gboolean xz_load (const char *infile, + const char *outfile); +static gboolean xz_save (const char *infile, + const char *outfile); +static goffset get_file_info (const gchar *filename); + + +static const Compressor compressors[] = +{ + { + N_("gzip archive"), + "application/x-gzip", + "xcf.gz,xcfgz", /* FIXME "xcf.gz,gz,xcfgz" */ + "0,string,\037\213", + ".xcfgz", + ".gz", + + "file-gz-load", + "loads files compressed with gzip", + "This procedure loads files in the gzip compressed format.", + gzip_load, + + "file-gz-save", + "saves files compressed with gzip", + "This procedure saves files in the gzip compressed format.", + gzip_save + }, + + { + N_("bzip archive"), + "application/x-bzip", + "xcf.bz2,xcfbz2", /* FIXME "xcf.bz2,bz2,xcfbz2" */ + "0,string,BZh", + ".xcfbz2", + ".bz2", + + "file-bz2-load", + "loads files compressed with bzip2", + "This procedure loads files in the bzip2 compressed format.", + bzip2_load, + + "file-bz2-save", + "saves files compressed with bzip2", + "This procedure saves files in the bzip2 compressed format.", + bzip2_save + }, + + { + N_("xz archive"), + "application/x-xz", + "xcf.xz,xcfxz", /* FIXME "xcf.xz,xz,xcfxz" */ + "0,string,\3757zXZ\x00", + ".xcfxz", + ".xz", + + "file-xz-load", + "loads files compressed with xz", + "This procedure loads files in the xz compressed format.", + xz_load, + + "file-xz-save", + "saves files compressed with xz", + "This procedure saves files in the xz compressed format.", + xz_save + } +}; + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + + +MAIN () + + +static void +query (void) +{ + static const GimpParamDef load_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_STRING, "filename", "The name of the file to load" }, + { GIMP_PDB_STRING, "raw-filename", "The name entered" } + }; + static const GimpParamDef load_return_vals[] = + { + { GIMP_PDB_IMAGE, "image", "Output image" }, + }; + + static const GimpParamDef save_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", "Drawable to save" }, + { GIMP_PDB_STRING, "filename", "The name of the file to " + "save the image in" }, + { GIMP_PDB_STRING, "raw-filename", "The name entered" }, + }; + + gint i; + + for (i = 0; i < G_N_ELEMENTS (compressors); i++) + { + const Compressor *compressor = &compressors[i]; + + gimp_install_procedure (compressor->load_proc, + compressor->load_blurb, + compressor->load_help, + "Daniel Risacher", + "Daniel Risacher, Spencer Kimball and Peter Mattis", + "1995-1997", + compressor->file_type, + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (load_args), + G_N_ELEMENTS (load_return_vals), + load_args, load_return_vals); + + gimp_register_file_handler_mime (compressor->load_proc, + compressor->mime_type); + gimp_register_magic_load_handler (compressor->load_proc, + compressor->extensions, + "", + compressor->magic); + + gimp_install_procedure (compressor->save_proc, + compressor->save_blurb, + compressor->save_help, + "Daniel Risacher", + "Daniel Risacher, Spencer Kimball and Peter Mattis", + "1995-1997", + compressor->file_type, + "RGB*, GRAY*, INDEXED*", + GIMP_PLUGIN, + G_N_ELEMENTS (save_args), 0, + save_args, NULL); + + gimp_register_file_handler_mime (compressor->save_proc, + compressor->mime_type); + gimp_register_save_handler (compressor->save_proc, + compressor->extensions, ""); + } +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[2]; + GimpRunMode run_mode; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + GError *error = NULL; + gint32 image_ID; + gint i; + + run_mode = param[0].data.d_int32; + + INIT_I18N(); + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + + /* We handle PDB errors by forwarding them to the caller in + * our return values. + */ + gimp_plugin_set_pdb_error_handler (GIMP_PDB_ERROR_HANDLER_PLUGIN); + + for (i = 0; i < G_N_ELEMENTS (compressors); i++) + { + const Compressor *compressor = &compressors[i]; + + if (! strcmp (name, compressor->load_proc)) + { + image_ID = load_image (compressor, + param[1].data.d_string, + param[0].data.d_int32, + &status, &error); + + if (image_ID != -1 && status == GIMP_PDB_SUCCESS) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_IMAGE; + values[1].data.d_image = image_ID; + } + + break; + } + else if (! strcmp (name, compressor->save_proc)) + { + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + break; + case GIMP_RUN_NONINTERACTIVE: + /* Make sure all the arguments are there! */ + if (nparams != 5) + status = GIMP_PDB_CALLING_ERROR; + break; + case GIMP_RUN_WITH_LAST_VALS: + break; + + default: + break; + } + + if (status == GIMP_PDB_SUCCESS) + status = save_image (compressor, + param[3].data.d_string, + param[1].data.d_int32, + param[2].data.d_int32, + param[0].data.d_int32, + &error); + + break; + } + } + + if (i == G_N_ELEMENTS (compressors)) + status = GIMP_PDB_CALLING_ERROR; + + if (status != GIMP_PDB_SUCCESS && error) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = error->message; + } + + values[0].data.d_status = status; +} + +static GimpPDBStatusType +save_image (const Compressor *compressor, + const gchar *filename, + gint32 image_ID, + gint32 drawable_ID, + gint32 run_mode, + GError **error) +{ + const gchar *ext; + gchar *tmpname; + + ext = find_extension (compressor, filename); + + if (! ext) + { + g_message (_("No sensible file extension, saving as compressed XCF.")); + ext = ".xcf"; + } + + /* get a temp name with the right extension and save into it. */ + + tmpname = gimp_temp_name (ext + 1); + + if (! (gimp_file_save (run_mode, + image_ID, + drawable_ID, + tmpname, + tmpname) && valid_file (tmpname))) + { + g_unlink (tmpname); + g_free (tmpname); + + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + "%s", gimp_get_pdb_error ()); + + return GIMP_PDB_EXECUTION_ERROR; + } + + gimp_progress_init_printf (_("Compressing '%s'"), + gimp_filename_to_utf8 (filename)); + + if (!compressor->save_fn (tmpname, filename)) + { + g_unlink (tmpname); + g_free (tmpname); + + return GIMP_PDB_EXECUTION_ERROR; + } + + g_unlink (tmpname); + gimp_progress_update (1.0); + g_free (tmpname); + + /* ask the core to save a thumbnail for compressed XCF files */ + if (strcmp (ext, ".xcf") == 0) + gimp_file_save_thumbnail (image_ID, filename); + + return GIMP_PDB_SUCCESS; +} + +static gint32 +load_image (const Compressor *compressor, + const gchar *filename, + gint32 run_mode, + GimpPDBStatusType *status, + GError **error) +{ + gint32 image_ID; + const gchar *ext; + gchar *tmpname; + + ext = find_extension (compressor, filename); + + if (! ext) + { + g_message (_("No sensible file extension, " + "attempting to load with file magic.")); + ext = ".foo"; + } + + /* find a temp name */ + tmpname = gimp_temp_name (ext + 1); + + if (!compressor->load_fn (filename, tmpname)) + { + g_free (tmpname); + *status = GIMP_PDB_EXECUTION_ERROR; + return -1; + } + + /* now that we uncompressed it, load the temp file */ + + image_ID = gimp_file_load (run_mode, tmpname, tmpname); + + g_unlink (tmpname); + g_free (tmpname); + + if (image_ID != -1) + { + *status = GIMP_PDB_SUCCESS; + + gimp_image_set_filename (image_ID, filename); + } + else + { + /* Forward the return status of the underlining plug-in for the + * given format. + */ + *status = gimp_get_pdb_status (); + + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + "%s", gimp_get_pdb_error ()); + } + + return image_ID; +} + +static gboolean +valid_file (const gchar *filename) +{ + GStatBuf buf; + + return g_stat (filename, &buf) == 0 && buf.st_size > 0; +} + +static const gchar * +find_extension (const Compressor *compressor, + const gchar *filename) +{ + gchar *filename_copy; + gchar *ext; + + /* we never free this copy - aren't we evil! */ + filename_copy = g_strdup (filename); + + /* find the extension, boy! */ + ext = strrchr (filename_copy, '.'); + + while (TRUE) + { + if (!ext || ext[1] == '\0' || strchr (ext, G_DIR_SEPARATOR)) + { + return NULL; + } + + if (0 == g_ascii_strcasecmp (ext, compressor->xcf_extension)) + { + return ".xcf"; /* we've found it */ + } + if (0 != g_ascii_strcasecmp (ext, compressor->generic_extension)) + { + return ext; + } + else + { + /* we found ".gz" so strip it, loop back, and look again */ + *ext = '\0'; + ext = strrchr (filename_copy, '.'); + } + } +} + +static gboolean +gzip_load (const char *infile, + const char *outfile) +{ + gboolean ret; + int fd; + gzFile in; + FILE *out; + char buf[16384]; + int len; + + ret = FALSE; + in = NULL; + out = NULL; + + fd = g_open (infile, O_RDONLY | _O_BINARY, 0); + if (fd == -1) + goto out; + + in = gzdopen (fd, "rb"); + if (!in) + { + close (fd); + goto out; + } + + out = g_fopen (outfile, "wb"); + if (!out) + goto out; + + while (TRUE) + { + len = gzread (in, buf, sizeof buf); + + if (len < 0) + break; + else if (len == 0) + { + ret = TRUE; + break; + } + + if (fwrite(buf, 1, len, out) != len) + break; + } + + out: + /* There is no need to close(fd) as it is closed by gzclose(). */ + if (in) + if (gzclose (in) != Z_OK) + ret = FALSE; + + if (out) + fclose (out); + + return ret; +} + +static gboolean +gzip_save (const char *infile, + const char *outfile) +{ + gboolean ret; + FILE *in; + int fd; + gzFile out; + char buf[16384]; + int len; + goffset tot = 0, file_size; + + ret = FALSE; + in = NULL; + out = NULL; + + in = g_fopen (infile, "rb"); + if (!in) + goto out; + + fd = g_open (outfile, O_CREAT | O_WRONLY | O_TRUNC | _O_BINARY, 0664); + if (fd == -1) + goto out; + + out = gzdopen (fd, "wb"); + if (!out) + { + close (fd); + goto out; + } + + file_size = get_file_info (infile); + while (TRUE) + { + len = fread (buf, 1, sizeof buf, in); + if (ferror (in)) + break; + + if (len < 0) + break; + else if (len == 0) + { + ret = TRUE; + break; + } + + if (gzwrite (out, buf, len) != len) + break; + + gimp_progress_update ((tot += len) * 1.0 / file_size); + } + + out: + if (in) + fclose (in); + + /* There is no need to close(fd) as it is closed by gzclose(). */ + if (out) + if (gzclose (out) != Z_OK) + ret = FALSE; + + return ret; +} + +static gboolean +bzip2_load (const char *infile, + const char *outfile) +{ + gboolean ret; + int fd; + BZFILE *in; + FILE *out; + char buf[16384]; + int len; + + ret = FALSE; + in = NULL; + out = NULL; + + fd = g_open (infile, O_RDONLY | _O_BINARY, 0); + if (fd == -1) + goto out; + + in = BZ2_bzdopen (fd, "rb"); + if (!in) + { + close (fd); + goto out; + } + + out = g_fopen (outfile, "wb"); + if (!out) + goto out; + + while (TRUE) + { + len = BZ2_bzread (in, buf, sizeof buf); + + if (len < 0) + break; + else if (len == 0) + { + ret = TRUE; + break; + } + + if (fwrite(buf, 1, len, out) != len) + break; + } + + out: + /* TODO: Check this in the case of BZ2_bzclose(): */ + /* There is no need to close(fd) as it is closed by BZ2_bzclose(). */ + if (in) + BZ2_bzclose (in); + + if (out) + fclose (out); + + return ret; +} + +static gboolean +bzip2_save (const char *infile, + const char *outfile) +{ + gboolean ret; + FILE *in; + int fd; + BZFILE *out; + char buf[16384]; + int len; + goffset tot = 0, file_size; + + ret = FALSE; + in = NULL; + out = NULL; + + in = g_fopen (infile, "rb"); + if (!in) + goto out; + + fd = g_open (outfile, O_CREAT | O_WRONLY | O_TRUNC | _O_BINARY, 0664); + if (fd == -1) + goto out; + + out = BZ2_bzdopen (fd, "wb"); + if (!out) + { + close (fd); + goto out; + } + + file_size = get_file_info (infile); + while (TRUE) + { + len = fread (buf, 1, sizeof buf, in); + if (ferror (in)) + break; + + if (len < 0) + break; + else if (len == 0) + { + ret = TRUE; + break; + } + + if (BZ2_bzwrite (out, buf, len) != len) + break; + + gimp_progress_update ((tot += len) * 1.0 / file_size); + } + + out: + if (in) + fclose (in); + + /* TODO: Check this in the case of BZ2_bzclose(): */ + /* There is no need to close(fd) as it is closed by BZ2_bzclose(). */ + if (out) + BZ2_bzclose (out); + + return ret; +} + +static gboolean +xz_load (const char *infile, + const char *outfile) +{ + gboolean ret; + FILE *in; + FILE *out; + lzma_stream strm = LZMA_STREAM_INIT; + lzma_action action; + guint8 inbuf[BUFSIZ]; + guint8 outbuf[BUFSIZ]; + lzma_ret status; + + ret = FALSE; + in = NULL; + out = NULL; + + in = g_fopen (infile, "rb"); + if (!in) + goto out; + + out = g_fopen (outfile, "wb"); + if (!out) + goto out; + + if (lzma_stream_decoder (&strm, UINT64_MAX, 0) != LZMA_OK) + goto out; + + strm.next_in = NULL; + strm.avail_in = 0; + strm.next_out = outbuf; + strm.avail_out = sizeof outbuf; + + action = LZMA_RUN; + status = LZMA_OK; + + while (status == LZMA_OK) + { + /* Fill the input buffer if it is empty. */ + if ((strm.avail_in == 0) && (!feof(in))) + { + strm.next_in = inbuf; + strm.avail_in = fread (inbuf, 1, sizeof inbuf, in); + + if (ferror (in)) + goto out; + + /* Once the end of the input file has been reached, we need to + tell lzma_code() that no more input will be coming and that + it should finish the encoding. */ + if (feof (in)) + action = LZMA_FINISH; + } + + status = lzma_code (&strm, action); + + if ((strm.avail_out == 0) || (status == LZMA_STREAM_END)) + { + /* When lzma_code() has returned LZMA_STREAM_END, the output + buffer is likely to be only partially full. Calculate how + much new data there is to be written to the output file. */ + size_t write_size = sizeof outbuf - strm.avail_out; + + if (fwrite (outbuf, 1, write_size, out) != write_size) + goto out; + + /* Reset next_out and avail_out. */ + strm.next_out = outbuf; + strm.avail_out = sizeof outbuf; + } + } + + if (status != LZMA_STREAM_END) + goto out; + + lzma_end (&strm); + ret = TRUE; + + out: + if (in) + fclose (in); + + if (out) + fclose (out); + + return ret; +} + +static gboolean +xz_save (const char *infile, + const char *outfile) +{ + gboolean ret; + FILE *in; + FILE *out; + lzma_stream strm = LZMA_STREAM_INIT; + lzma_action action; + guint8 inbuf[BUFSIZ]; + guint8 outbuf[BUFSIZ]; + lzma_ret status; + goffset tot = 0, file_size; + + ret = FALSE; + in = NULL; + out = NULL; + + in = g_fopen (infile, "rb"); + if (!in) + goto out; + + file_size = get_file_info (infile); + out = g_fopen (outfile, "wb"); + if (!out) + goto out; + + if (lzma_easy_encoder (&strm, + LZMA_PRESET_DEFAULT, + LZMA_CHECK_CRC64) != LZMA_OK) + goto out; + + strm.next_in = NULL; + strm.avail_in = 0; + strm.next_out = outbuf; + strm.avail_out = sizeof outbuf; + + action = LZMA_RUN; + status = LZMA_OK; + + while (status == LZMA_OK) + { + /* Fill the input buffer if it is empty. */ + if ((strm.avail_in == 0) && (!feof(in))) + { + strm.next_in = inbuf; + strm.avail_in = fread (inbuf, 1, sizeof inbuf, in); + + if (ferror (in)) + goto out; + + /* Once the end of the input file has been reached, we need to + tell lzma_code() that no more input will be coming and that + it should finish the encoding. */ + if (feof (in)) + action = LZMA_FINISH; + + gimp_progress_update ((tot += strm.avail_in) * 1.0 / file_size); + } + + status = lzma_code (&strm, action); + + if ((strm.avail_out == 0) || (status == LZMA_STREAM_END)) + { + /* When lzma_code() has returned LZMA_STREAM_END, the output + buffer is likely to be only partially full. Calculate how + much new data there is to be written to the output file. */ + size_t write_size = sizeof outbuf - strm.avail_out; + + if (fwrite (outbuf, 1, write_size, out) != write_size) + goto out; + + /* Reset next_out and avail_out. */ + strm.next_out = outbuf; + strm.avail_out = sizeof outbuf; + } + } + + if (status != LZMA_STREAM_END) + goto out; + + lzma_end (&strm); + ret = TRUE; + + out: + if (in) + fclose (in); + + if (out) + fclose (out); + + return ret; +} + +/* get file size from a filename */ +static goffset +get_file_info (const gchar *filename) +{ + GFile *file = g_file_new_for_path (filename); + GFileInfo *info; + goffset size = 1; + + info = g_file_query_info (file, + G_FILE_ATTRIBUTE_STANDARD_SIZE, + G_FILE_QUERY_INFO_NONE, + NULL, NULL); + + if (info) + { + size = g_file_info_get_size (info); + + g_object_unref (info); + } + + g_object_unref (file); + + return size; +} |