#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) { GError *error = NULL; GExiv2Metadata *e2m = gexiv2_metadata_new (); gfloat time, aperture, gain = 1.0f; gexiv2_metadata_open_path (e2m, path, &error); if (error) { g_warning ("%s", error->message); exit (EXIT_FAILURE); } /* Calculate the APEX brightness / EV */ { gint nom, den; gexiv2_metadata_get_exposure_time (e2m, &nom, &den); time = nom * 1.0f / den; } aperture = gexiv2_metadata_get_fnumber (e2m); /* iso */ if (gexiv2_metadata_has_tag (e2m, "Exif.Image.ISOSpeedRatings")) { gain = gexiv2_metadata_get_iso_speed (e2m) / 100.0f; } else { // 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); }