Blob Blame History Raw

#include <gegl.h> 

#include <cstdlib>
#include <cstdio>
#include <cerrno>
#include <cmath>

#include <iostream>

#include <exiv2/image.hpp>
#include <exiv2/exif.hpp>

using namespace std;

enum
{
  ARG_COMMAND,
  ARG_OUTPUT,
  ARG_PATH_0,

  NUM_ARGS
};


static const
gchar *COMBINER_INPUT_PREFIX = "exposure-";


static void
check_usage (gint argc, gchar **argv)
{
  if (argc == 1)
    {
      g_print ("This tool combines multiple exposures of one scene into a "
               "single buffer.\n");
      goto die;
    }

  if (argc < NUM_ARGS)
    {
      g_print ("Error: Insufficient arguments\n");
      goto die;
    }

  return;

die:
  g_print ("Usage: %s <output> <input> [<input> ...]\n", argv[0]);
  exit (EXIT_FAILURE);
}


static gfloat
expcombine_get_file_ev (const gchar *path)
{
  /* Open the file and read in the metadata */
  Exiv2::Image::AutoPtr image;
  try 
    {
      image = Exiv2::ImageFactory::open (path);
      image->readMetadata ();
    }
  catch (Exiv2::Error ex)
    {
      g_print ("Error: unable to read metadata from path: '%s'\n", path);
      exit (EXIT_FAILURE);
    }

  Exiv2::ExifData &exifData = image->exifData ();
  if (exifData.empty ())
      return NAN;

  /* Calculate the APEX brightness / EV */
  gfloat time, aperture, gain = 1.0f;

  time     = exifData["Exif.Photo.ExposureTime"].value().toFloat();
  aperture = exifData["Exif.Photo.FNumber"     ].value().toFloat();

  /* iso */
  try
    {
      gain = exifData["Exif.Photo.ISOSpeedRatings"].value().toLong() / 100.0f;
    }
  catch (Exiv2::Error ex)
    {
      // Assume ISO is set at 100. It's reasonably likely that the ISO is the
      // same across all images anyway, and for our purposes the relative
      // values can be sufficient.

      gain = 1.0f;
    }

  return log2f (aperture * aperture) + log2f (1 / time) + log2f (gain);
}


int
main (int    argc,
      char **argv)
{
  guint     cursor;
  GeglNode *gegl, *combiner, *sink;
  gchar    *all_evs = g_strdup ("");

  g_thread_init (NULL);
  gegl_init (&argc, &argv);
  check_usage (argc, argv);

  gegl     = gegl_node_new ();
  combiner = gegl_node_new_child (gegl,
                                  "operation", "gegl:exp-combine",
                                  NULL);

  for (cursor = ARG_PATH_0; cursor < argc; ++cursor)
    {
      const gchar *input_path;
      gchar        ev_string[G_ASCII_DTOSTR_BUF_SIZE + 1];
      gfloat       ev_val;

      gchar        combiner_pad[strlen (COMBINER_INPUT_PREFIX) +
                                G_ASCII_DTOSTR_BUF_SIZE + 1];
      gint         err;

      GeglNode    *img;
      
      input_path = argv[cursor];
      ev_val     = expcombine_get_file_ev (input_path);
      if (isnan (ev_val))
        {
          g_print ("Failed to calculate exposure value for '%s'\n",
                   input_path);
          exit (EXIT_FAILURE);
        }

      g_ascii_dtostr (ev_string, G_N_ELEMENTS (ev_string), ev_val);
      all_evs = g_strconcat (all_evs, " ", ev_string, NULL);

      /* Construct and link the input image into the combiner */
      img = gegl_node_new_child (gegl,
                                 "operation", "gegl:load",
                                 "path",      input_path,
                                  NULL);

      /* Create the exposure pad name */
      err = snprintf (combiner_pad,
                      G_N_ELEMENTS (combiner_pad),
                      "%s%u",
                      COMBINER_INPUT_PREFIX,
                      cursor - ARG_PATH_0);
      if (err < 1 || err >= G_N_ELEMENTS (combiner_pad))
        {
          g_warning ("Unable to construct input pad name for exposure %u\n",
                     cursor);
          return (EXIT_FAILURE);
        }

      gegl_node_connect_to (img, "output", combiner, combiner_pad);
    }

  g_return_val_if_fail (all_evs[0] == ' ', EXIT_FAILURE);
  gegl_node_set (combiner, "exposures", all_evs + 1, NULL);


  /* We should not have skipped past the last element of the arguments */
  g_return_val_if_fail (cursor == argc, EXIT_FAILURE);
  sink     = gegl_node_new_child (gegl,
                                  "operation", "gegl:save",
                                  "path", argv[ARG_OUTPUT],
                                   NULL);

  gegl_node_link_many (combiner, sink, NULL);
  gegl_node_process (sink);
  return (EXIT_SUCCESS);
}