#include #include #include #include #include #include #include #include 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 [ ...]\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); }