xcf.c 18.5 KB
Newer Older
1
/* GIMP - The GNU Image Manipulation Program
2 3
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
4
 * This program is free software: you can redistribute it and/or modify
5
 * it under the terms of the GNU General Public License as published by
6
 * the Free Software Foundation; either version 3 of the License, or
7 8 9 10 11 12 13 14
 * (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
15
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
 */
Sven Neumann's avatar
Sven Neumann committed
17

18 19
#include "config.h"

Elliot Lee's avatar
Elliot Lee committed
20 21 22 23
#include <errno.h>
#include <stdlib.h>
#include <string.h>

24
#include <gdk-pixbuf/gdk-pixbuf.h>
25
#include <glib/gstdio.h>
26
#include <gegl.h>
Elliot Lee's avatar
Elliot Lee committed
27

28 29
#include "libgimpbase/gimpbase.h"

30
#include "core/core-types.h"
31

32
#include "core/gimp.h"
33
#include "core/gimpimage.h"
34
#include "core/gimpparamspecs.h"
35
#include "core/gimpprogress.h"
36

37
#include "plug-in/gimppluginmanager.h"
38
#include "plug-in/gimppluginprocedure.h"
39

40 41 42 43 44
#include "xcf.h"
#include "xcf-private.h"
#include "xcf-load.h"
#include "xcf-read.h"
#include "xcf-save.h"
45

46
#include "gimp-intl.h"
47

Elliot Lee's avatar
Elliot Lee committed
48

49 50 51
typedef GimpImage * GimpXcfLoaderFunc (Gimp     *gimp,
                                       XcfInfo  *info,
                                       GError  **error);
Elliot Lee's avatar
Elliot Lee committed
52 53


54 55 56 57 58 59 60 61 62 63 64 65
static GimpValueArray * xcf_load_invoker (GimpProcedure         *procedure,
                                          Gimp                  *gimp,
                                          GimpContext           *context,
                                          GimpProgress          *progress,
                                          const GimpValueArray  *args,
                                          GError               **error);
static GimpValueArray * xcf_save_invoker (GimpProcedure         *procedure,
                                          Gimp                  *gimp,
                                          GimpContext           *context,
                                          GimpProgress          *progress,
                                          const GimpValueArray  *args,
                                          GError               **error);
Elliot Lee's avatar
Elliot Lee committed
66 67


68
static GimpXcfLoaderFunc * const xcf_loaders[] =
Elliot Lee's avatar
Elliot Lee committed
69
{
70 71 72 73 74 75 76 77 78 79
  xcf_load_image,   /* version  0 */
  xcf_load_image,   /* version  1 */
  xcf_load_image,   /* version  2 */
  xcf_load_image,   /* version  3 */
  xcf_load_image,   /* version  4 */
  xcf_load_image,   /* version  5 */
  xcf_load_image,   /* version  6 */
  xcf_load_image,   /* version  7 */
  xcf_load_image,   /* version  8 */
  xcf_load_image,   /* version  9 */
80
  xcf_load_image,   /* version 10 */
81
  xcf_load_image,   /* version 11 */
82 83
  xcf_load_image,   /* version 12 */
  xcf_load_image    /* version 13 */
Elliot Lee's avatar
Elliot Lee committed
84 85 86 87
};


void
88
xcf_init (Gimp *gimp)
Elliot Lee's avatar
Elliot Lee committed
89
{
90
  GimpPlugInProcedure *proc;
91
  GFile               *file;
92
  GimpProcedure       *procedure;
93

94 95
  g_return_if_fail (GIMP_IS_GIMP (gimp));

96 97 98 99 100 101 102
  /* So this is sort of a hack, but its better than it was before.  To
   * do this right there would be a file load-save handler type and
   * the whole interface would change but there isn't, and currently
   * the plug-in structure contains all the load-save info, so it
   * makes sense to use that for the XCF load/save handlers, even
   * though they are internal.  The only thing it requires is using a
   * PlugInProcDef struct.  -josh
103
   */
104 105

  /*  gimp-xcf-save  */
106 107 108 109
  file = g_file_new_for_path ("gimp-xcf-save");
  procedure = gimp_plug_in_procedure_new (GIMP_PLUGIN, file);
  g_object_unref (file);

110 111 112 113
  procedure->proc_type    = GIMP_INTERNAL;
  procedure->marshal_func = xcf_save_invoker;

  proc = GIMP_PLUG_IN_PROCEDURE (procedure);
114
  proc->menu_label = g_strdup (N_("GIMP XCF image"));
115
  gimp_plug_in_procedure_set_icon (proc, GIMP_ICON_TYPE_ICON_NAME,
116 117 118
                                   (const guint8 *) "gimp-wilber",
                                   strlen ("gimp-wilber") + 1);
  gimp_plug_in_procedure_set_image_types (proc, "RGB*, GRAY*, INDEXED*");
119
  gimp_plug_in_procedure_set_file_proc (proc, "xcf", "", NULL);
120
  gimp_plug_in_procedure_set_mime_types (proc, "image/x-xcf");
121
  gimp_plug_in_procedure_set_handles_uri (proc);
122

123
  gimp_object_set_static_name (GIMP_OBJECT (procedure), "gimp-xcf-save");
124 125
  gimp_procedure_set_static_strings (procedure,
                                     "gimp-xcf-save",
Sven Neumann's avatar
Sven Neumann committed
126 127
                                     "Saves file in the .xcf file format",
                                     "The XCF file format has been designed "
128
                                     "specifically for loading and saving "
129
                                     "tiled and layered images in GIMP. "
130 131 132 133 134 135 136
                                     "This procedure will save the specified "
                                     "image in the xcf file format.",
                                     "Spencer Kimball & Peter Mattis",
                                     "Spencer Kimball & Peter Mattis",
                                     "1995-1996",
                                     NULL);

137 138 139 140 141 142 143 144 145 146
  gimp_procedure_add_argument (procedure,
                               gimp_param_spec_int32 ("dummy-param",
                                                      "Dummy Param",
                                                      "Dummy parameter",
                                                      G_MININT32, G_MAXINT32, 0,
                                                      GIMP_PARAM_READWRITE));
  gimp_procedure_add_argument (procedure,
                               gimp_param_spec_image_id ("image",
                                                         "Image",
                                                         "Input image",
147
                                                         gimp, FALSE,
148 149 150 151 152
                                                         GIMP_PARAM_READWRITE));
  gimp_procedure_add_argument (procedure,
                               gimp_param_spec_drawable_id ("drawable",
                                                            "Drawable",
                                                            "Active drawable of input image",
153
                                                            gimp, TRUE,
154 155 156 157 158 159
                                                            GIMP_PARAM_READWRITE));
  gimp_procedure_add_argument (procedure,
                               gimp_param_spec_string ("filename",
                                                       "Filename",
                                                       "The name of the file "
                                                       "to save the image in, "
160 161
                                                       "in URI format and "
                                                       "UTF-8 encoding",
162 163
                                                       TRUE, FALSE, TRUE,
                                                       NULL,
164 165 166 167 168 169
                                                       GIMP_PARAM_READWRITE));
  gimp_procedure_add_argument (procedure,
                               gimp_param_spec_string ("raw-filename",
                                                       "Raw filename",
                                                       "The basename of the "
                                                       "file, in UTF-8",
170 171
                                                       FALSE, FALSE, TRUE,
                                                       NULL,
172
                                                       GIMP_PARAM_READWRITE));
173
  gimp_plug_in_manager_add_procedure (gimp->plug_in_manager, proc);
174
  g_object_unref (procedure);
175

176
  /*  gimp-xcf-load  */
177 178 179 180
  file = g_file_new_for_path ("gimp-xcf-load");
  procedure = gimp_plug_in_procedure_new (GIMP_PLUGIN, file);
  g_object_unref (file);

181 182 183 184
  procedure->proc_type    = GIMP_INTERNAL;
  procedure->marshal_func = xcf_load_invoker;

  proc = GIMP_PLUG_IN_PROCEDURE (procedure);
185
  proc->menu_label = g_strdup (N_("GIMP XCF image"));
186
  gimp_plug_in_procedure_set_icon (proc, GIMP_ICON_TYPE_ICON_NAME,
187 188 189
                                   (const guint8 *) "gimp-wilber",
                                   strlen ("gimp-wilber") + 1);
  gimp_plug_in_procedure_set_image_types (proc, NULL);
190 191
  gimp_plug_in_procedure_set_file_proc (proc, "xcf", "",
                                        "0,string,gimp\\040xcf\\040");
192
  gimp_plug_in_procedure_set_mime_types (proc, "image/x-xcf");
193
  gimp_plug_in_procedure_set_handles_uri (proc);
194

195
  gimp_object_set_static_name (GIMP_OBJECT (procedure), "gimp-xcf-load");
196 197
  gimp_procedure_set_static_strings (procedure,
                                     "gimp-xcf-load",
Sven Neumann's avatar
Sven Neumann committed
198 199
                                     "Loads file saved in the .xcf file format",
                                     "The XCF file format has been designed "
200
                                     "specifically for loading and saving "
201
                                     "tiled and layered images in GIMP. "
202 203 204 205 206 207 208
                                     "This procedure will load the specified "
                                     "file.",
                                     "Spencer Kimball & Peter Mattis",
                                     "Spencer Kimball & Peter Mattis",
                                     "1995-1996",
                                     NULL);

209 210 211 212 213 214 215 216 217 218 219 220 221
  gimp_procedure_add_argument (procedure,
                               gimp_param_spec_int32 ("dummy-param",
                                                      "Dummy Param",
                                                      "Dummy parameter",
                                                      G_MININT32, G_MAXINT32, 0,
                                                      GIMP_PARAM_READWRITE));
  gimp_procedure_add_argument (procedure,
                               gimp_param_spec_string ("filename",
                                                       "Filename",
                                                       "The name of the file "
                                                       "to load, in the "
                                                       "on-disk character "
                                                       "set and encoding",
222 223
                                                       TRUE, FALSE, TRUE,
                                                       NULL,
224 225 226 227 228 229
                                                       GIMP_PARAM_READWRITE));
  gimp_procedure_add_argument (procedure,
                               gimp_param_spec_string ("raw-filename",
                                                       "Raw filename",
                                                       "The basename of the "
                                                       "file, in UTF-8",
230 231
                                                       FALSE, FALSE, TRUE,
                                                       NULL,
232 233 234 235 236 237
                                                       GIMP_PARAM_READWRITE));

  gimp_procedure_add_return_value (procedure,
                                   gimp_param_spec_image_id ("image",
                                                             "Image",
                                                             "Output image",
238
                                                             gimp, FALSE,
239
                                                             GIMP_PARAM_READWRITE));
240
  gimp_plug_in_manager_add_procedure (gimp->plug_in_manager, proc);
241
  g_object_unref (procedure);
Elliot Lee's avatar
Elliot Lee committed
242 243
}

244
void
245
xcf_exit (Gimp *gimp)
246
{
247
  g_return_if_fail (GIMP_IS_GIMP (gimp));
248 249
}

250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273
GimpImage *
xcf_load_stream (Gimp          *gimp,
                 GInputStream  *input,
                 GFile         *input_file,
                 GimpProgress  *progress,
                 GError       **error)
{
  XcfInfo      info  = { 0, };
  const gchar *filename;
  GimpImage   *image = NULL;
  gchar        id[14];
  gboolean     success;

  g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
  g_return_val_if_fail (G_IS_INPUT_STREAM (input), NULL);
  g_return_val_if_fail (input_file == NULL || G_IS_FILE (input_file), NULL);
  g_return_val_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress), NULL);
  g_return_val_if_fail (error == NULL || *error == NULL, NULL);

  if (input_file)
    filename = gimp_file_get_utf8_name (input_file);
  else
    filename = _("Memory Stream");

274 275 276 277 278 279 280
  info.gimp             = gimp;
  info.input            = input;
  info.seekable         = G_SEEKABLE (input);
  info.bytes_per_offset = 4;
  info.progress         = progress;
  info.file             = input_file;
  info.compression      = COMPRESS_NONE;
281 282 283 284 285 286

  if (progress)
    gimp_progress_start (progress, FALSE, _("Opening '%s'"), filename);

  success = TRUE;

287
  xcf_read_int8 (&info, (guint8 *) id, 14);
288 289 290 291 292 293 294 295 296

  if (! g_str_has_prefix (id, "gimp xcf "))
    {
      success = FALSE;
    }
  else if (strcmp (id + 9, "file") == 0)
    {
      info.file_version = 0;
    }
297 298
  else if (id[9]  == 'v' &&
           id[13] == '\0')
299 300 301 302 303 304 305 306
    {
      info.file_version = atoi (id + 10);
    }
  else
    {
      success = FALSE;
    }

307 308 309
  if (info.file_version >= 11)
    info.bytes_per_offset = 8;

310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361
  if (success)
    {
      if (info.file_version >= 0 &&
          info.file_version < G_N_ELEMENTS (xcf_loaders))
        {
          image = (*(xcf_loaders[info.file_version])) (gimp, &info, error);

          if (! image)
            success = FALSE;

          g_input_stream_close (info.input, NULL, NULL);
        }
      else
        {
          g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
                       _("XCF error: unsupported XCF file version %d "
                         "encountered"), info.file_version);
          success = FALSE;
        }
    }

  if (progress)
    gimp_progress_end (progress);

  return image;
}

gboolean
xcf_save_stream (Gimp           *gimp,
                 GimpImage      *image,
                 GOutputStream  *output,
                 GFile          *output_file,
                 GimpProgress   *progress,
                 GError        **error)
{
  XcfInfo      info     = { 0, };
  const gchar *filename;
  gboolean     success  = FALSE;
  GError      *my_error = NULL;

  g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE);
  g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
  g_return_val_if_fail (G_IS_OUTPUT_STREAM (output), FALSE);
  g_return_val_if_fail (output_file == NULL || G_IS_FILE (output_file), FALSE);
  g_return_val_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress), FALSE);
  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  if (output_file)
    filename = gimp_file_get_utf8_name (output_file);
  else
    filename = _("Memory Stream");

362 363 364 365 366 367
  info.gimp             = gimp;
  info.output           = output;
  info.seekable         = G_SEEKABLE (output);
  info.bytes_per_offset = 4;
  info.progress         = progress;
  info.file             = output_file;
368

369
  if (gimp_image_get_xcf_compression (image))
370
    info.compression = COMPRESS_ZLIB;
371 372
  else
    info.compression = COMPRESS_RLE;
373 374 375 376

  info.file_version = gimp_image_get_xcf_version (image,
                                                  info.compression ==
                                                  COMPRESS_ZLIB,
377
                                                  NULL, NULL, NULL);
378

379 380
  if (info.file_version >= 11)
    info.bytes_per_offset = 8;
381

382 383 384 385 386 387 388 389 390 391 392 393 394
  if (progress)
    gimp_progress_start (progress, FALSE, _("Saving '%s'"), filename);

  success = xcf_save_image (&info, image, &my_error);

  if (success)
    {
      if (progress)
        gimp_progress_set_text (progress, _("Closing '%s'"), filename);

      success = g_output_stream_close (info.output, NULL, &my_error);
    }

395
  if (! success && my_error)
396 397 398 399 400 401 402 403 404 405 406 407
    g_propagate_prefixed_error (error, my_error,
                                _("Error writing '%s': "), filename);

  if (progress)
    gimp_progress_end (progress);

  return success;
}


/*  private functions  */

408 409 410 411 412 413 414
static GimpValueArray *
xcf_load_invoker (GimpProcedure         *procedure,
                  Gimp                  *gimp,
                  GimpContext           *context,
                  GimpProgress          *progress,
                  const GimpValueArray  *args,
                  GError               **error)
Elliot Lee's avatar
Elliot Lee committed
415
{
416
  GimpValueArray *return_vals;
417
  GimpImage      *image = NULL;
418 419
  const gchar    *uri;
  GFile          *file;
420
  GInputStream   *input;
421
  GError         *my_error = NULL;
Elliot Lee's avatar
Elliot Lee committed
422

423
  gimp_set_busy (gimp);
424

425 426
  uri  = g_value_get_string (gimp_value_array_index (args, 1));
  file = g_file_new_for_uri (uri);
Elliot Lee's avatar
Elliot Lee committed
427

428
  input = G_INPUT_STREAM (g_file_read (file, NULL, &my_error));
429

430
  if (input)
Elliot Lee's avatar
Elliot Lee committed
431
    {
432
      image = xcf_load_stream (gimp, input, file, progress, error);
433

434
      g_object_unref (input);
Elliot Lee's avatar
Elliot Lee committed
435
    }
436
  else
Sven Neumann's avatar
Sven Neumann committed
437
    {
438 439
      g_propagate_prefixed_error (error, my_error,
                                  _("Could not open '%s' for reading: "),
440
                                  gimp_file_get_utf8_name (file));
Sven Neumann's avatar
Sven Neumann committed
441
    }
442

443 444
  g_object_unref (file);

445
  return_vals = gimp_procedure_get_return_values (procedure, image != NULL,
446
                                                  error ? *error : NULL);
Elliot Lee's avatar
Elliot Lee committed
447

448
  if (image)
449
    gimp_value_set_image (gimp_value_array_index (return_vals, 1), image);
Elliot Lee's avatar
Elliot Lee committed
450

451
  gimp_unset_busy (gimp);
452

453
  return return_vals;
Elliot Lee's avatar
Elliot Lee committed
454 455
}

456 457 458 459 460 461 462
static GimpValueArray *
xcf_save_invoker (GimpProcedure         *procedure,
                  Gimp                  *gimp,
                  GimpContext           *context,
                  GimpProgress          *progress,
                  const GimpValueArray  *args,
                  GError               **error)
Elliot Lee's avatar
Elliot Lee committed
463
{
464 465
  GimpValueArray *return_vals;
  GimpImage      *image;
466 467
  const gchar    *uri;
  GFile          *file;
468
  GOutputStream  *output;
469 470
  gboolean        success  = FALSE;
  GError         *my_error = NULL;
Elliot Lee's avatar
Elliot Lee committed
471

472
  gimp_set_busy (gimp);
473

474 475 476
  image = gimp_value_get_image (gimp_value_array_index (args, 1), gimp);
  uri   = g_value_get_string (gimp_value_array_index (args, 3));
  file  = g_file_new_for_uri (uri);
477

478 479 480
  output = G_OUTPUT_STREAM (g_file_replace (file,
                                            NULL, FALSE, G_FILE_CREATE_NONE,
                                            NULL, &my_error));
481

482
  if (output)
Elliot Lee's avatar
Elliot Lee committed
483
    {
484
      success = xcf_save_stream (gimp, image, output, file, progress, error);
485

486
      g_object_unref (output);
Elliot Lee's avatar
Elliot Lee committed
487
    }
488 489 490 491
  else
    {
      g_propagate_prefixed_error (error, my_error,
                                  _("Error creating '%s': "),
492
                                  gimp_file_get_utf8_name (file));
493
    }
Elliot Lee's avatar
Elliot Lee committed
494

495 496
  g_object_unref (file);

497 498
  return_vals = gimp_procedure_get_return_values (procedure, success,
                                                  error ? *error : NULL);
Elliot Lee's avatar
Elliot Lee committed
499

500
  gimp_unset_busy (gimp);
501

502
  return return_vals;
Elliot Lee's avatar
Elliot Lee committed
503
}