/* PIKA - Photo and Image Kooker Application
 * a rebranding of The GNU Image Manipulation Program (created with heckimp)
 * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
 *
 * Original copyright, applying to most contents (license remains unchanged): 
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
#include "config.h"
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "libpika/stdplugins-intl.h"
#include "jpeg.h"
#include "jpeg-settings.h"
#include "jpeg-load.h"
#include "jpeg-save.h"
typedef struct _Jpeg      Jpeg;
typedef struct _JpegClass JpegClass;
struct _Jpeg
{
  PikaPlugIn      parent_instance;
};
struct _JpegClass
{
  PikaPlugInClass parent_class;
};
#define JPEG_TYPE  (jpeg_get_type ())
#define JPEG (obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), JPEG_TYPE, Jpeg))
GType                   jpeg_get_type         (void) G_GNUC_CONST;
static GList          * jpeg_query_procedures (PikaPlugIn           *plug_in);
static PikaProcedure  * jpeg_create_procedure (PikaPlugIn           *plug_in,
                                               const gchar          *name);
static PikaValueArray * jpeg_load             (PikaProcedure        *procedure,
                                               PikaRunMode           run_mode,
                                               GFile                *file,
                                               const PikaValueArray *args,
                                               gpointer              run_data);
static PikaValueArray * jpeg_load_thumb       (PikaProcedure        *procedure,
                                               GFile                *file,
                                               gint                  size,
                                               const PikaValueArray *args,
                                               gpointer              run_data);
static PikaValueArray * jpeg_save             (PikaProcedure        *procedure,
                                               PikaRunMode           run_mode,
                                               PikaImage            *image,
                                               gint                  n_drawables,
                                               PikaDrawable        **drawables,
                                               GFile                *file,
                                               const PikaValueArray *args,
                                               gpointer              run_data);
G_DEFINE_TYPE (Jpeg, jpeg, PIKA_TYPE_PLUG_IN)
PIKA_MAIN (JPEG_TYPE)
DEFINE_STD_SET_I18N
gboolean         undo_touched      = FALSE;
PikaDisplay     *display           = NULL;
gboolean         separate_display  = FALSE;
PikaImage       *orig_image_global = NULL;
PikaDrawable    *drawable_global   = NULL;
static void
jpeg_class_init (JpegClass *klass)
{
  PikaPlugInClass *plug_in_class = PIKA_PLUG_IN_CLASS (klass);
  plug_in_class->query_procedures = jpeg_query_procedures;
  plug_in_class->create_procedure = jpeg_create_procedure;
  plug_in_class->set_i18n         = STD_SET_I18N;
}
static void
jpeg_init (Jpeg *jpeg)
{
}
static GList *
jpeg_query_procedures (PikaPlugIn *plug_in)
{
  GList *list = NULL;
  list = g_list_append (list, g_strdup (LOAD_THUMB_PROC));
  list = g_list_append (list, g_strdup (LOAD_PROC));
  list = g_list_append (list, g_strdup (SAVE_PROC));
  return list;
}
static PikaProcedure *
jpeg_create_procedure (PikaPlugIn  *plug_in,
                       const gchar *name)
{
  PikaProcedure *procedure = NULL;
  if (! strcmp (name, LOAD_PROC))
    {
      procedure = pika_load_procedure_new (plug_in, name,
                                           PIKA_PDB_PROC_TYPE_PLUGIN,
                                           jpeg_load, NULL, NULL);
      pika_procedure_set_menu_label (procedure, _("JPEG image"));
      pika_procedure_set_documentation (procedure,
                                        "Loads files in the JPEG file format",
                                        "Loads files in the JPEG file format",
                                        name);
      pika_procedure_set_attribution (procedure,
                                      "Spencer Kimball, Peter Mattis & others",
                                      "Spencer Kimball & Peter Mattis",
                                      "1995-2007");
      pika_file_procedure_set_mime_types (PIKA_FILE_PROCEDURE (procedure),
                                          "image/jpeg");
      pika_file_procedure_set_extensions (PIKA_FILE_PROCEDURE (procedure),
                                          "jpg,jpeg,jpe");
      pika_file_procedure_set_magics (PIKA_FILE_PROCEDURE (procedure),
                                      "0,string,\xff\xd8\xff");
      pika_load_procedure_set_thumbnail_loader (PIKA_LOAD_PROCEDURE (procedure),
                                                LOAD_THUMB_PROC);
    }
  else if (! strcmp (name, LOAD_THUMB_PROC))
    {
      procedure = pika_thumbnail_procedure_new (plug_in, name,
                                                PIKA_PDB_PROC_TYPE_PLUGIN,
                                                jpeg_load_thumb, NULL, NULL);
      pika_procedure_set_documentation (procedure,
                                        "Loads a thumbnail from a JPEG image",
                                        "Loads a thumbnail from a JPEG image, "
                                        "if one exists",
                                        name);
      pika_procedure_set_attribution (procedure,
                                      "Mukund Sivaraman , "
                                      "Sven Neumann ",
                                      "Mukund Sivaraman , "
                                      "Sven Neumann ",
                                      "November 15, 2004");
    }
  else if (! strcmp (name, SAVE_PROC))
    {
      procedure = pika_save_procedure_new (plug_in, name,
                                           PIKA_PDB_PROC_TYPE_PLUGIN,
                                           jpeg_save, NULL, NULL);
      pika_procedure_set_image_types (procedure, "RGB*, GRAY*");
      pika_procedure_set_menu_label (procedure, _("JPEG image"));
      pika_procedure_set_documentation (procedure,
                                        "Saves files in the JPEG file format",
                                        "Saves files in the lossy, widely "
                                        "supported JPEG format",
                                        name);
      pika_procedure_set_attribution (procedure,
                                      "Spencer Kimball, Peter Mattis & others",
                                      "Spencer Kimball & Peter Mattis",
                                      "1995-2007");
      pika_file_procedure_set_format_name (PIKA_FILE_PROCEDURE (procedure),
                                           _("JPEG"));
      pika_file_procedure_set_mime_types (PIKA_FILE_PROCEDURE (procedure),
                                          "image/jpeg");
      pika_file_procedure_set_extensions (PIKA_FILE_PROCEDURE (procedure),
                                          "jpg,jpeg,jpe");
      /* See bugs #63610 and #61088 for a discussion about the quality
       * settings
       */
      PIKA_PROC_ARG_DOUBLE (procedure, "quality",
                            _("_Quality"),
                            _("Quality of exported image"),
                            0.0, 1.0, 0.9,
                            G_PARAM_READWRITE);
      PIKA_PROC_ARG_DOUBLE (procedure, "smoothing",
                            _("S_moothing"),
                            _("Smoothing factor for exported image"),
                            0.0, 1.0, 0.0,
                            G_PARAM_READWRITE);
      PIKA_PROC_ARG_BOOLEAN (procedure, "optimize",
                             _("Optimi_ze"),
                             _("Use optimized tables during Huffman coding"),
                             TRUE,
                             G_PARAM_READWRITE);
      PIKA_PROC_ARG_BOOLEAN (procedure, "progressive",
                             _("_Progressive"),
                             _("Create progressive JPEG images"),
                             TRUE,
                             G_PARAM_READWRITE);
      PIKA_PROC_ARG_BOOLEAN (procedure, "cmyk",
                             _("Export as CM_YK"),
                             _("Create a CMYK JPEG image using the soft-proofing color profile"),
                             FALSE,
                             G_PARAM_READWRITE);
      PIKA_PROC_ARG_INT (procedure, "sub-sampling",
                         _("Su_bsampling"),
                         _("Sub-sampling type { 0 == 4:2:0 (chroma quartered), "
                           "1 == 4:2:2 (chroma halved horizontally), "
                           "2 == 4:4:4 (best quality), "
                           "3 == 4:4:0 (chroma halved vertically)"),
                         JPEG_SUBSAMPLING_2x2_1x1_1x1,
                         JPEG_SUBSAMPLING_1x2_1x1_1x1,
                         JPEG_SUBSAMPLING_1x1_1x1_1x1,
                         G_PARAM_READWRITE);
      PIKA_PROC_ARG_BOOLEAN (procedure, "baseline",
                             _("Baseline"),
                             _("Force creation of a baseline JPEG "
                               "(non-baseline JPEGs can't be read by all decoders)"),
                             TRUE,
                             G_PARAM_READWRITE);
      PIKA_PROC_ARG_INT (procedure, "restart",
                         _("Inter_val (MCU rows):"),
                         _("Interval of restart markers "
                           "(in MCU rows, 0 = no restart markers)"),
                         0, 64, 0,
                         G_PARAM_READWRITE);
      PIKA_PROC_ARG_INT (procedure, "dct",
                         _("_DCT method"),
                         _("DCT method to use { INTEGER (0), FIXED (1), "
                           "FLOAT (2) }"),
                         0, 2, 0,
                         G_PARAM_READWRITE);
      /* Some auxiliary arguments mostly for interactive usage. */
      PIKA_PROC_AUX_ARG_BOOLEAN (procedure, "use-original-quality",
                                 _("_Use quality settings from original image"),
                                 _("If the original image was loaded from a JPEG "
                                   "file using non-standard quality settings "
                                   "(quantization tables), enable this option to "
                                   "get almost the same quality and file size."),
                                 FALSE,
                                 G_PARAM_READWRITE);
      PIKA_PROC_AUX_ARG_INT (procedure, "original-quality",
                             NULL, NULL,
                             -1, 100, -1,
                             G_PARAM_READWRITE);
      PIKA_PROC_AUX_ARG_INT (procedure, "original-sub-sampling",
                             NULL, NULL,
                             JPEG_SUBSAMPLING_2x2_1x1_1x1,
                             JPEG_SUBSAMPLING_1x2_1x1_1x1,
                             JPEG_SUBSAMPLING_2x2_1x1_1x1,
                             G_PARAM_READWRITE);
      PIKA_PROC_AUX_ARG_INT (procedure, "original-num-quant-tables",
                             NULL, NULL,
                             -1, 4, -1,
                             G_PARAM_READWRITE);
      PIKA_PROC_AUX_ARG_BOOLEAN (procedure, "show-preview",
                                 _("Sho_w preview in image window"),
                                 _("Creates a temporary layer with an export preview"),
                                 FALSE,
                                 G_PARAM_READWRITE);
      PIKA_PROC_AUX_ARG_BOOLEAN (procedure, "use-arithmetic-coding",
                                 _("Use _arithmetic coding"),
                                 _("Older software may have trouble opening "
                                   "arithmetic-coded images"),
                                 FALSE,
                                 G_PARAM_READWRITE);
      PIKA_PROC_AUX_ARG_BOOLEAN (procedure, "use-restart",
                                 _("Use restart mar_kers"),
                                 NULL, FALSE,
                                 G_PARAM_READWRITE);
      pika_save_procedure_set_support_exif      (PIKA_SAVE_PROCEDURE (procedure), TRUE);
      pika_save_procedure_set_support_iptc      (PIKA_SAVE_PROCEDURE (procedure), TRUE);
      pika_save_procedure_set_support_xmp       (PIKA_SAVE_PROCEDURE (procedure), TRUE);
      pika_save_procedure_set_support_profile   (PIKA_SAVE_PROCEDURE (procedure), TRUE);
      pika_save_procedure_set_support_thumbnail (PIKA_SAVE_PROCEDURE (procedure), TRUE);
      pika_save_procedure_set_support_comment   (PIKA_SAVE_PROCEDURE (procedure), TRUE);
    }
  return procedure;
}
static PikaValueArray *
jpeg_load (PikaProcedure        *procedure,
           PikaRunMode           run_mode,
           GFile                *file,
           const PikaValueArray *args,
           gpointer              run_data)
{
  PikaValueArray *return_vals;
  PikaImage      *image;
  gboolean        resolution_loaded  = FALSE;
  gboolean        ps_metadata_loaded = FALSE;
  GError         *error              = NULL;
  gegl_init (NULL, NULL);
  preview_image = NULL;
  preview_layer = NULL;
  switch (run_mode)
    {
    case PIKA_RUN_INTERACTIVE:
    case PIKA_RUN_WITH_LAST_VALS:
      pika_ui_init (PLUG_IN_BINARY);
      break;
    default:
      break;
    }
  image = load_image (file, run_mode, FALSE,
                      &resolution_loaded, &ps_metadata_loaded, &error);
  if (image)
    {
      PikaMetadata *metadata;
      metadata = pika_image_metadata_load_prepare (image, "image/jpeg",
                                                   file, NULL);
      if (metadata)
        {
          PikaMetadataLoadFlags flags = PIKA_METADATA_LOAD_ALL;
          if (resolution_loaded)
            flags &= ~PIKA_METADATA_LOAD_RESOLUTION;
          pika_image_metadata_load_finish (image, "image/jpeg",
                                           metadata, flags);
          g_object_unref (metadata);
        }
    }
  if (! image)
    return pika_procedure_new_return_values (procedure,
                                             PIKA_PDB_EXECUTION_ERROR,
                                             error);
  return_vals = pika_procedure_new_return_values (procedure,
                                                  PIKA_PDB_SUCCESS,
                                                  NULL);
  PIKA_VALUES_SET_IMAGE (return_vals, 1, image);
  return return_vals;
}
static PikaValueArray *
jpeg_load_thumb (PikaProcedure        *procedure,
                 GFile                *file,
                 gint                  size,
                 const PikaValueArray *args,
                 gpointer              run_data)
{
  PikaValueArray *return_vals;
  PikaImage      *image;
  gint            width  = 0;
  gint            height = 0;
  PikaImageType   type   = -1;
  GError         *error  = NULL;
  gegl_init (NULL, NULL);
  preview_image = NULL;
  preview_layer = NULL;
  image = load_thumbnail_image (file, &width, &height, &type,
                                &error);
  if (! image)
    return pika_procedure_new_return_values (procedure,
                                             PIKA_PDB_EXECUTION_ERROR,
                                             error);
  return_vals = pika_procedure_new_return_values (procedure,
                                                  PIKA_PDB_SUCCESS,
                                                  NULL);
  PIKA_VALUES_SET_IMAGE (return_vals, 1, image);
  PIKA_VALUES_SET_INT   (return_vals, 2, width);
  PIKA_VALUES_SET_INT   (return_vals, 3, height);
  PIKA_VALUES_SET_ENUM  (return_vals, 4, type);
  PIKA_VALUES_SET_INT   (return_vals, 5, 1); /* 1 layer */
  return return_vals;
}
static PikaValueArray *
jpeg_save (PikaProcedure        *procedure,
           PikaRunMode           run_mode,
           PikaImage            *image,
           gint                  n_drawables,
           PikaDrawable        **drawables,
           GFile                *file,
           const PikaValueArray *args,
           gpointer              run_data)
{
  PikaPDBStatusType      status = PIKA_PDB_SUCCESS;
  PikaProcedureConfig   *config;
  PikaMetadata          *metadata;
  PikaImage             *orig_image;
  PikaExportReturn       export = PIKA_EXPORT_CANCEL;
  GError                *error  = NULL;
  gint                   orig_num_quant_tables = -1;
  gint                   orig_quality          = -1;
  JpegSubsampling        orig_subsmp           = JPEG_SUBSAMPLING_2x2_1x1_1x1;
  gegl_init (NULL, NULL);
  config = pika_procedure_create_config (procedure);
  metadata = pika_procedure_config_begin_export (config, image, run_mode,
                                                 args, "image/jpeg");
  preview_image = NULL;
  preview_layer = NULL;
  orig_image = image;
  switch (run_mode)
    {
    case PIKA_RUN_INTERACTIVE:
    case PIKA_RUN_WITH_LAST_VALS:
      pika_ui_init (PLUG_IN_BINARY);
      export = pika_export_image (&image, &n_drawables, &drawables, "JPEG",
                                  PIKA_EXPORT_CAN_HANDLE_RGB |
                                  PIKA_EXPORT_CAN_HANDLE_GRAY);
      switch (export)
        {
        case PIKA_EXPORT_EXPORT:
          display = NULL;
          separate_display = TRUE;
          break;
        case PIKA_EXPORT_IGNORE:
          break;
        case PIKA_EXPORT_CANCEL:
          return pika_procedure_new_return_values (procedure,
                                                   PIKA_PDB_CANCEL,
                                                   NULL);
          break;
        }
      break;
    default:
      break;
    }
  if (n_drawables != 1)
    {
      g_set_error (&error, G_FILE_ERROR, 0,
                   _("JPEG format does not support multiple layers."));
      return pika_procedure_new_return_values (procedure,
                                               PIKA_PDB_CALLING_ERROR,
                                               error);
    }
  /* Override preferences from JPG export defaults (if saved). */
  switch (run_mode)
    {
    case PIKA_RUN_NONINTERACTIVE:
      g_object_set (config, "show-preview", FALSE, NULL);
      break;
    case PIKA_RUN_INTERACTIVE:
    case PIKA_RUN_WITH_LAST_VALS:
        {
          /* restore the values found when loading the file (if available) */
          gdouble dquality;
          gint    quality;
          gint    subsmp;
          jpeg_restore_original_settings (orig_image,
                                          &orig_quality,
                                          &orig_subsmp,
                                          &orig_num_quant_tables);
          g_object_get (config,
                        "quality",      &dquality,
                        "sub-sampling", &subsmp,
                        NULL);
          quality = (gint) (dquality * 100.0);
          /* If this image was loaded from a JPEG file and has not
           * been saved yet, try to use some of the settings from the
           * original file if they are better than the default values.
           */
          if (orig_quality > quality)
            {
              quality = orig_quality;
              dquality = (gdouble) quality / 100.0;
              g_object_set (config, "quality", dquality, NULL);
            }
          if (orig_quality > 0)
            {
              /* Skip changing subsampling to original if we already have
               * best setting or if original have worst setting
               */
              if (!(subsmp == JPEG_SUBSAMPLING_1x1_1x1_1x1 ||
                    orig_subsmp == JPEG_SUBSAMPLING_2x2_1x1_1x1))
                {
                  subsmp = orig_subsmp;
                  g_object_set (config, "sub-sampling", orig_subsmp, NULL);
                }
              if (orig_quality == quality && orig_subsmp == subsmp)
                {
                  g_object_set (config, "use-original-quality", TRUE, NULL);
                }
            }
        }
      break;
    }
  g_object_set (config,
                "original-sub-sampling",     orig_subsmp,
                "original-quality",          orig_quality,
                "original-num-quant-tables", orig_num_quant_tables,
                NULL);
  if (run_mode == PIKA_RUN_INTERACTIVE)
    {
      gboolean show_preview = FALSE;
      g_object_get (config, "show-preview", &show_preview, NULL);
      if (show_preview)
        {
          /* we freeze undo saving so that we can avoid sucking up
           * tile cache with our unneeded preview steps. */
          pika_image_undo_freeze (image);
          undo_touched = TRUE;
        }
      /* prepare for the preview */
      preview_image     = image;
      orig_image_global = orig_image;
      drawable_global   = drawables[0];
      /*  First acquire information with a dialog  */
      if (! save_dialog (procedure, config, drawables[0], orig_image))
        {
          status = PIKA_PDB_CANCEL;
        }
      if (undo_touched)
        {
          /* thaw undo saving and flush the displays to have them
           * reflect the current shortcuts
           */
          pika_image_undo_thaw (image);
          pika_displays_flush ();
        }
    }
  if (status == PIKA_PDB_SUCCESS)
    {
      if (! save_image (file, config,
                        image, drawables[0], orig_image, FALSE,
                        &error))
        {
          status = PIKA_PDB_EXECUTION_ERROR;
        }
    }
  if (status == PIKA_PDB_SUCCESS)
    {
      if (metadata)
        pika_metadata_set_bits_per_sample (metadata, 8);
    }
  pika_procedure_config_end_export (config, image, file, status);
  g_object_unref (config);
  if (export == PIKA_EXPORT_EXPORT)
    {
      /* If the image was exported, delete the new display. This also
       * deletes the image.
       */
      if (display)
        {
          pika_display_delete (display);
          pika_display_present (pika_default_display ());
        }
      else
        {
          pika_image_delete (image);
        }
      g_free (drawables);
    }
  return pika_procedure_new_return_values (procedure, status, error);
}
/*
 * Here's the routine that will replace the standard error_exit method:
 */
void
my_error_exit (j_common_ptr cinfo)
{
  /* cinfo->err really points to a my_error_mgr struct, so coerce pointer */
  my_error_ptr myerr = (my_error_ptr) cinfo->err;
  /* Always display the message. */
  /* We could postpone this until after returning, if we chose. */
  (*cinfo->err->output_message) (cinfo);
  /* Return control to the setjmp point */
  longjmp (myerr->setjmp_buffer, 1);
}
void
my_output_message (j_common_ptr cinfo)
{
  gchar  buffer[JMSG_LENGTH_MAX + 1];
  (*cinfo->err->format_message)(cinfo, buffer);
  g_message ("%s", buffer);
}