/* tiff loading for GIMP
 *  -Peter Mattis
 *
 * The TIFF loading code has been completely revamped by Nick Lamb
 * njl195@zepler.org.uk -- 18 May 1998
 * And it now gains support for tiles (and doubtless a zillion bugs)
 * njl195@zepler.org.uk -- 12 June 1999
 * LZW patent fuss continues :(
 * njl195@zepler.org.uk -- 20 April 2000
 * The code for this filter is based on "tifftopnm" and "pnmtotiff",
 *  2 programs that are a part of the netpbm package.
 * khk@khk.net -- 13 May 2000
 * Added support for ICCPROFILE tiff tag. If this tag is present in a
 * TIFF file, then a parasite is created and vice versa.
 * peter@kirchgessner.net -- 29 Oct 2002
 * Progress bar only when run interactive
 * Added support for layer offsets - pablo.dangelo@web.de -- 7 Jan 2004
 * Honor EXTRASAMPLES tag while loading images with alphachannel
 * pablo.dangelo@web.de -- 16 Jan 2004
 */

/*
 * tifftopnm.c - converts a Tagged Image File to a portable anymap
 *
 * Derived by Jef Poskanzer from tif2ras.c, which is:
 *
 * Copyright (c) 1990 by Sun Microsystems, Inc.
 *
 * Author: Patrick J. Naughton
 * naughton@wind.sun.com
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and that
 * both that copyright notice and this permission notice appear in
 * supporting documentation.
 *
 * This file is provided AS IS with no warranties of any kind.  The author
 * shall have no liability with respect to the infringement of copyrights,
 * trade secrets or any patents by this file or any part thereof.  In no
 * event will the author be liable for any lost revenue or profits or
 * other special, indirect and consequential damages.
 */

#include "config.h"

#include <tiffio.h>
#include <gexiv2/gexiv2.h>

#include <libgimp/gimp.h>
#include <libgimp/gimpui.h>

#include "file-tiff-io.h"
#include "file-tiff-load.h"
#include "file-tiff-save.h"

#include "libgimp/stdplugins-intl.h"


#define LOAD_PROC      "file-tiff-load"
#define SAVE_PROC      "file-tiff-save"
#define SAVE2_PROC     "file-tiff-save2"
#define PLUG_IN_BINARY "file-tiff"


static void       query               (void);
static void       run                 (const gchar      *name,
                                       gint              nparams,
                                       const GimpParam  *param,
                                       gint             *nreturn_vals,
                                       GimpParam       **return_vals);

static gboolean   image_is_monochrome (gint32            image);


const GimpPlugInInfo PLUG_IN_INFO =
{
  NULL,  /* init_proc  */
  NULL,  /* quit_proc  */
  query, /* query_proc */
  run,   /* run_proc   */
};

static TiffSaveVals tsvals =
{
  COMPRESSION_NONE,    /*  compression         */
  TRUE,                /*  alpha handling      */
  TRUE,                /*  save transp. pixels */
  TRUE,                /*  save exif           */
  TRUE,                /*  save xmp            */
  TRUE,                /*  save iptc           */
  TRUE                 /*  save thumbnail      */
};

static gchar *image_comment = NULL;


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 of the file to load" }
  };
  static const GimpParamDef load_return_vals[] =
  {
    { GIMP_PDB_IMAGE, "image", "Output image" }
  };

#define COMMON_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 of the file to save the image in" },\
    { GIMP_PDB_INT32,    "compression",  "Compression type: { NONE (0), LZW (1), PACKBITS (2), DEFLATE (3), JPEG (4), CCITT G3 Fax (5), CCITT G4 Fax (6) }" }

  static const GimpParamDef save_args_old[] =
  {
    COMMON_SAVE_ARGS
  };

  static const GimpParamDef save_args[] =
  {
    COMMON_SAVE_ARGS,
    { GIMP_PDB_INT32, "save-transp-pixels", "Keep the color data masked by an alpha channel intact" }
  };

  gimp_install_procedure (LOAD_PROC,
                          "loads files of the tiff file format",
                          "FIXME: write help for tiff_load",
                          "Spencer Kimball, Peter Mattis & Nick Lamb",
                          "Nick Lamb <njl195@zepler.org.uk>",
                          "1995-1996,1998-2003",
                          N_("TIFF image"),
                          NULL,
                          GIMP_PLUGIN,
                          G_N_ELEMENTS (load_args),
                          G_N_ELEMENTS (load_return_vals),
                          load_args, load_return_vals);

  gimp_register_file_handler_mime (LOAD_PROC, "image/tiff");
  gimp_register_magic_load_handler (LOAD_PROC,
                                    "tif,tiff",
                                    "",
                                    "0,string,II*\\0,0,string,MM\\0*");

  gimp_install_procedure (SAVE_PROC,
                          "saves files in the tiff file format",
                          "Saves files in the Tagged Image File Format.  "
                          "The value for the saved comment is taken "
                          "from the 'gimp-comment' parasite.",
                          "Spencer Kimball & Peter Mattis",
                          "Spencer Kimball & Peter Mattis",
                          "1995-1996,2000-2003",
                          N_("TIFF image"),
                          "RGB*, GRAY*, INDEXED",
                          GIMP_PLUGIN,
                          G_N_ELEMENTS (save_args_old), 0,
                          save_args_old, NULL);

  gimp_register_file_handler_mime (SAVE_PROC, "image/tiff");
  gimp_register_save_handler (SAVE_PROC, "tif,tiff", "");

  gimp_install_procedure (SAVE2_PROC,
                          "saves files in the tiff file format",
                          "Saves files in the Tagged Image File Format.  "
                          "The value for the saved comment is taken "
                          "from the 'gimp-comment' parasite.",
                          "Spencer Kimball & Peter Mattis",
                          "Spencer Kimball & Peter Mattis",
                          "1995-1996,2000-2003",
                          N_("TIFF image"),
                          "RGB*, GRAY*, INDEXED",
                          GIMP_PLUGIN,
                          G_N_ELEMENTS (save_args), 0,
                          save_args, NULL);
}

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;

  INIT_I18N ();
  gegl_init (NULL, NULL);

  run_mode = param[0].data.d_int32;

  *nreturn_vals = 1;
  *return_vals  = values;

  values[0].type          = GIMP_PDB_STATUS;
  values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR;

  if (strcmp (name, LOAD_PROC) == 0)
    {
      const gchar *filename = param[1].data.d_string;
      TIFF        *tif;

      tif = tiff_open (filename, "r", &error);

      if (tif)
        {
          TiffSelectedPages pages;

          pages.target = GIMP_PAGE_SELECTOR_TARGET_LAYERS;

          gimp_get_data (LOAD_PROC, &pages.target);

          pages.n_pages = pages.o_pages = TIFFNumberOfDirectories (tif);

          if (pages.n_pages == 0)
            {
              g_set_error (&error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
                           _("TIFF '%s' does not contain any directories"),
                           gimp_filename_to_utf8 (filename));

              status = GIMP_PDB_EXECUTION_ERROR;
            }
          else
            {
              gboolean run_it = FALSE;
              gint     i;

              if (run_mode != GIMP_RUN_INTERACTIVE)
                {
                  pages.pages = g_new (gint, pages.n_pages);

                  for (i = 0; i < pages.n_pages; i++)
                    pages.pages[i] = i;

                  run_it = TRUE;
                }
              else
                {
                  gimp_ui_init (PLUG_IN_BINARY, FALSE);
                }

              if (pages.n_pages == 1)
                {
                  pages.pages  = g_new0 (gint, pages.n_pages);
                  pages.target = GIMP_PAGE_SELECTOR_TARGET_LAYERS;

                  run_it = TRUE;
                }

              if ((! run_it) && (run_mode == GIMP_RUN_INTERACTIVE))
                run_it = load_dialog (tif, LOAD_PROC, &pages);

              if (run_it)
                {
                  gint32   image;
                  gboolean resolution_loaded = FALSE;

                  gimp_set_data (LOAD_PROC,
                                 &pages.target, sizeof (pages.target));

                  image = load_image (param[1].data.d_string, tif, &pages,
                                      &resolution_loaded,
                                      &error);

                  g_free (pages.pages);

                  if (image > 0)
                    {
                      GFile        *file;
                      GimpMetadata *metadata;

                      file = g_file_new_for_path (param[1].data.d_string);

                      metadata = gimp_image_metadata_load_prepare (image,
                                                                   "image/tiff",
                                                                   file, NULL);

                      if (metadata)
                        {
                          GimpMetadataLoadFlags flags = GIMP_METADATA_LOAD_ALL;

                          if (resolution_loaded)
                            flags &= ~GIMP_METADATA_LOAD_RESOLUTION;

                          gimp_image_metadata_load_finish (image, "image/tiff",
                                                           metadata, flags,
                                                           run_mode == GIMP_RUN_INTERACTIVE);

                          g_object_unref (metadata);
                        }

                      g_object_unref (file);

                      *nreturn_vals = 2;
                      values[1].type         = GIMP_PDB_IMAGE;
                      values[1].data.d_image = image;
                    }
                  else
                    {
                      status = GIMP_PDB_EXECUTION_ERROR;
                    }
                }
              else
                {
                  status = GIMP_PDB_CANCEL;
                }
            }

          TIFFClose (tif);
        }
      else
        {
          status = GIMP_PDB_EXECUTION_ERROR;
        }
    }
  else if ((strcmp (name, SAVE_PROC)  == 0) ||
           (strcmp (name, SAVE2_PROC) == 0))
    {
      /* Plug-in is either file_tiff_save or file_tiff_save2 */

      GimpMetadata          *metadata;
      GimpMetadataSaveFlags  metadata_flags;
      GimpParasite          *parasite;
      gint32                 image      = param[1].data.d_int32;
      gint32                 drawable   = param[2].data.d_int32;
      gint32                 orig_image = image;
      GimpExportReturn       export     = GIMP_EXPORT_CANCEL;

      switch (run_mode)
        {
        case GIMP_RUN_INTERACTIVE:
        case GIMP_RUN_WITH_LAST_VALS:
          gimp_ui_init (PLUG_IN_BINARY, FALSE);

          export = gimp_export_image (&image, &drawable, "TIFF",
                                      GIMP_EXPORT_CAN_HANDLE_RGB     |
                                      GIMP_EXPORT_CAN_HANDLE_GRAY    |
                                      GIMP_EXPORT_CAN_HANDLE_INDEXED |
                                      GIMP_EXPORT_CAN_HANDLE_ALPHA);

          if (export == GIMP_EXPORT_CANCEL)
            {
              values[0].data.d_status = GIMP_PDB_CANCEL;
              return;
            }
          break;
        default:
          break;
        }

      metadata = gimp_image_metadata_save_prepare (orig_image,
                                                   "image/tiff",
                                                   &metadata_flags);

      tsvals.save_exif      = (metadata_flags & GIMP_METADATA_SAVE_EXIF) != 0;
      tsvals.save_xmp       = (metadata_flags & GIMP_METADATA_SAVE_XMP) != 0;
      tsvals.save_iptc      = (metadata_flags & GIMP_METADATA_SAVE_IPTC) != 0;
      tsvals.save_thumbnail = (metadata_flags & GIMP_METADATA_SAVE_THUMBNAIL) != 0;

      parasite = gimp_image_get_parasite (orig_image, "gimp-comment");
      if (parasite)
        {
          image_comment = g_strndup (gimp_parasite_data (parasite),
                                     gimp_parasite_data_size (parasite));
          gimp_parasite_free (parasite);
        }

      switch (run_mode)
        {
        case GIMP_RUN_INTERACTIVE:
          /*  Possibly retrieve data  */
          gimp_get_data (SAVE_PROC, &tsvals);

          parasite = gimp_image_get_parasite (orig_image, "tiff-save-options");
          if (parasite)
            {
              const TiffSaveVals *pvals = gimp_parasite_data (parasite);

              if (pvals->compression == COMPRESSION_DEFLATE)
                tsvals.compression = COMPRESSION_ADOBE_DEFLATE;
              else
                tsvals.compression = pvals->compression;

              tsvals.save_transp_pixels = pvals->save_transp_pixels;
            }
          gimp_parasite_free (parasite);

          /*  First acquire information with a dialog  */
          if (! save_dialog (&tsvals,
                             SAVE_PROC,
                             gimp_drawable_has_alpha (drawable),
                             image_is_monochrome (image),
                             gimp_image_base_type (image) == GIMP_INDEXED,
                             &image_comment))
            {
              status = GIMP_PDB_CANCEL;
            }
          break;

        case GIMP_RUN_NONINTERACTIVE:
          /*  Make sure all the arguments are there!  */
          if (nparams == 6 || nparams == 7)
            {
              switch (param[5].data.d_int32)
                {
                case 0: tsvals.compression = COMPRESSION_NONE;          break;
                case 1: tsvals.compression = COMPRESSION_LZW;           break;
                case 2: tsvals.compression = COMPRESSION_PACKBITS;      break;
                case 3: tsvals.compression = COMPRESSION_ADOBE_DEFLATE; break;
                case 4: tsvals.compression = COMPRESSION_JPEG;          break;
                case 5: tsvals.compression = COMPRESSION_CCITTFAX3;     break;
                case 6: tsvals.compression = COMPRESSION_CCITTFAX4;     break;
                default: status = GIMP_PDB_CALLING_ERROR; break;
                }

              if (nparams == 7)
                tsvals.save_transp_pixels = param[6].data.d_int32;
              else
                tsvals.save_transp_pixels = TRUE;
            }
          else
            {
              status = GIMP_PDB_CALLING_ERROR;
            }
          break;

        case GIMP_RUN_WITH_LAST_VALS:
          /*  Possibly retrieve data  */
          gimp_get_data (SAVE_PROC, &tsvals);

          parasite = gimp_image_get_parasite (orig_image, "tiff-save-options");
          if (parasite)
            {
              const TiffSaveVals *pvals = gimp_parasite_data (parasite);

              tsvals.compression        = pvals->compression;
              tsvals.save_transp_pixels = pvals->save_transp_pixels;
            }
          gimp_parasite_free (parasite);
          break;

        default:
          break;
        }

      if (status == GIMP_PDB_SUCCESS)
        {
          gint saved_bpp;

          if (save_image (param[3].data.d_string, &tsvals,
                          image, drawable, orig_image, image_comment,
                          &saved_bpp, &error))
            {
              if (metadata)
                {
                  GFile *file;

                  /* See bug 758909: clear TIFFTAG_MIN/MAXSAMPLEVALUE because
                   * exiv2 saves them with wrong type and the original values
                   * could be invalid
                   */
                  gexiv2_metadata_clear_tag (metadata, "Exif.Image.0x0118");
                  gexiv2_metadata_clear_tag (metadata, "Exif.Image.0x0119");

                  gimp_metadata_set_bits_per_sample (metadata, saved_bpp);

                  if (tsvals.save_exif)
                    metadata_flags |= GIMP_METADATA_SAVE_EXIF;
                  else
                    metadata_flags &= ~GIMP_METADATA_SAVE_EXIF;

                  if (tsvals.save_xmp)
                    metadata_flags |= GIMP_METADATA_SAVE_XMP;
                  else
                    metadata_flags &= ~GIMP_METADATA_SAVE_XMP;

                  if (tsvals.save_iptc)
                    metadata_flags |= GIMP_METADATA_SAVE_IPTC;
                  else
                    metadata_flags &= ~GIMP_METADATA_SAVE_IPTC;

                  /* never save metadata thumbnails for TIFF, see bug #729952 */
                  metadata_flags &= ~GIMP_METADATA_SAVE_THUMBNAIL;

                  file = g_file_new_for_path (param[3].data.d_string);
                  gimp_image_metadata_save_finish (image,
                                                   "image/tiff",
                                                   metadata, metadata_flags,
                                                   file, NULL);
                  g_object_unref (file);
                }

              /*  Store mvals data  */
              gimp_set_data (SAVE_PROC, &tsvals, sizeof (TiffSaveVals));
            }
          else
            {
              status = GIMP_PDB_EXECUTION_ERROR;
            }
        }

      if (export == GIMP_EXPORT_EXPORT)
        gimp_image_delete (image);

      if (metadata)
        g_object_unref (metadata);
    }
  else
    {
      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 gboolean
image_is_monochrome (gint32 image)
{
  guchar   *colors;
  gint      num_colors;
  gboolean  monochrome = FALSE;

  g_return_val_if_fail (image != -1, FALSE);

  colors = gimp_image_get_colormap (image, &num_colors);

  if (colors)
    {
      if (num_colors == 2 || num_colors == 1)
        {
          const guchar  bw_map[] = { 0, 0, 0, 255, 255, 255 };
          const guchar  wb_map[] = { 255, 255, 255, 0, 0, 0 };

          if (memcmp (colors, bw_map, 3 * num_colors) == 0 ||
              memcmp (colors, wb_map, 3 * num_colors) == 0)
            {
              monochrome = TRUE;
            }
        }

      g_free (colors);
    }

  return monochrome;
}
