Logo Search packages:      
Sourcecode: pandora version File versions  Download package

pandora_gen.c

/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; c-brace-offset: -2 -*- */
/* The GIMP -- an image manipulation program
 * 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 2 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

/*  This is a plugin intended to help with making panoramas.
 *        Copyright (C) 2003  Akkana Peck.
 *
 *  To build & install: gimptool --install-strip pandora_gen_gen.c
 */

#include "pandora.h"

#include <gtk/gtkfilesel.h>
#include <gtk/gtkclist.h>

#define MAXIMAGES 50

#define FILELIST_WIDTH   400
#define FILELIST_HEIGHT  120
#define SCALE_WIDTH       60
#define SPIN_WIDTH        60

typedef struct
{
  gint       run;
} Pandora_GenInterface;

typedef struct
{
  gdouble overlap;
  gboolean feather;
  gchar** files;
} Pandora_GenVals;

static Pandora_GenVals avals =
{
  .35,   /* initial overlap fraction */
  TRUE,  /* feather edges */
  0      /* file list */
};

/* Declare local functions.
 */
static void      query (void);
static void      run   (MAYBE_CONST gchar        *name,
                                    gint          nparams,
                        MAYBE_CONST GimpParam    *param,
                                    gint         *nreturn_vals,
                                    GimpParam   **return_vals);

/*  user interface functions  */
static gint      pandora_gen_dialog (void);
static gint      pandora_generate_panorama();

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

MAIN ()

static void
query (void)
{
  static GimpParamDef args[] =
  {
    { GIMP_PDB_INT32,       "run_mode",    "Interactive, non-interactive" },
    { GIMP_PDB_INT32,       "overlap",     "Overlap" },
    { GIMP_PDB_INT32,       "feather",     "Feather layers" },
    { GIMP_PDB_STRINGARRAY, "files",       "Input files" }
  };
  static GimpParamDef return_vals[] =
  {
    { GIMP_PDB_INT32,       "Status",      "Success = 0" },
    { GIMP_PDB_INT32,       "Pano Img id", "The new image." }
  }; 

#ifdef IN_GIMP_TREE
  INIT_I18N();
#endif

  gimp_install_procedure (
#ifdef GIMP_1_2
                          "extension_pandora_gen",
#else /* GIMP_1_3 */
                          "plugin_pandora_gen",
#endif /* GIMP_1_x */
                          "Collect images together to make a panorama",
                          "Help not yet written for this plug-in",
                          "Akkana Peck",
                          "Akkana Peck",
                          "2003",
                          N_("<Toolbox>/Xtns/Make Panorama..."),
                          "",
#ifdef GIMP_1_2
                          GIMP_EXTENSION,
#else /* GIMP_1_3 */
                          GIMP_PLUGIN,
#endif /* GIMP_1_x */
                          G_N_ELEMENTS (args), G_N_ELEMENTS (return_vals),
                          args, return_vals);
}

static void
run (MAYBE_CONST gchar      *name,
                 gint        nparams,
     MAYBE_CONST GimpParam  *param,
                 gint       *nreturn_vals,
                 GimpParam **return_vals)
{
  static GimpParam values[2];
  GimpRunMode  run_mode;
  GimpPDBStatusType status = GIMP_PDB_SUCCESS;

  run_mode = param[0].data.d_int32;

  *nreturn_vals = 2;
  *return_vals = values;
  values[0].type = GIMP_PDB_STATUS;
  values[0].data.d_status = status;
  values[1].type = GIMP_PDB_INT32;
  values[1].data.d_image = -1;

  if (run_mode == GIMP_RUN_INTERACTIVE)
  {
    INIT_I18N_UI();

    /*  Possibly retrieve data  */
    gimp_get_data ("plug_in_pandora_gen", &avals);

    /* Pop up the dialog, which will then call pandora_generate_panorama */
    (void)pandora_gen_dialog ();

    /*  Store avals data for next invocation */
    gimp_set_data ("plug_in_pandora_gen", &avals, sizeof (Pandora_GenVals));
  }

  else if (run_mode == GIMP_RUN_NONINTERACTIVE)
  {
    INIT_I18N();
    /*  Make sure all the arguments are there!  */
    if (nparams != 3)
      status = GIMP_PDB_CALLING_ERROR;
    if (status == GIMP_PDB_SUCCESS)
    {
      avals.files = param[1].data.d_stringarray;
      avals.overlap = param[1].data.d_int32;
      avals.feather = param[1].data.d_int32;
    }
    values[1].data.d_image = pandora_generate_panorama ();
  }

  else if (run_mode == GIMP_RUN_WITH_LAST_VALS)
  {
    /*  Possibly retrieve data  */
    gimp_get_data ("plug_in_pandora_gen", &avals);
    values[1].data.d_image = pandora_generate_panorama ();
  }

  values[0].data.d_status = status;
}

static GtkWidget *pandora_gen_fsb = NULL;
static GtkWidget *pandora_gen_filelist = NULL;

static gboolean ok_pressed = FALSE;

/* Add a file: this is called from the fsb's ok button, or from doubleclick */
static void
pandora_gen_file_selection_add (GtkWidget *widget,
                                gpointer   data)
{
  gchar** multi_sel;
#ifdef GIMP_1_2
  gchar* sarr[2];
#endif /* GIMP 1_3 */
  gchar* tmp;

  /* If OK was pressed but we don't have any files in our list yet,
   * add any remaining selection to the list, before proceeding
   * to build the panorama. 
   */
  if (!ok_pressed ||
      gtk_clist_get_text(GTK_CLIST (pandora_gen_filelist), 0, 0, &tmp) == 0)
  {
#ifdef GIMP_1_2
    sarr[0] = gtk_file_selection_get_filename (GTK_FILE_SELECTION (data));
    sarr[1] = 0;
    multi_sel = sarr;
#else /* GIMP 1_3 */
    multi_sel = gtk_file_selection_get_selections (GTK_FILE_SELECTION (data));
#endif /* GIMP 1_3 */

    /* gtk_clist_append should append the whole array, but it doesn't.
     * So loop over it:
     */
    while (multi_sel && *multi_sel) {
      gtk_clist_append(GTK_CLIST (pandora_gen_filelist), multi_sel);
      ++multi_sel;
    }
  }

  if (ok_pressed) {
    gtk_widget_hide (GTK_WIDGET (data));
    pandora_generate_panorama();
    gtk_main_quit();
  }

}

/* Ick.  But we get both this and the file_selection_add callback */
static void
pandora_gen_file_selection_ok_pressed (GtkWidget *widget,
                                       gpointer   data)
{
  ok_pressed = TRUE;
}

/* Add a file: this is called from the fsb's ok button, or from doubleclick */
static void
pandora_gen_file_selection_rem (GtkWidget *widget,
                                gpointer   data)
{
  /* Wow, getting the selected items in a gtkclist is amazingly baroque! */
  GList *selection = GTK_CLIST(pandora_gen_filelist)->selection;
  GList *selection_end = GTK_CLIST(pandora_gen_filelist)->selection_end;
  if (selection) {
    while (selection && selection_end) {
      int i;
      int ss = (int)selection->data;
      int se = (int)selection_end->data;
      for (i = se; i >= ss; --i)
        gtk_clist_remove(GTK_CLIST (pandora_gen_filelist), i);

      selection = selection->next;
      selection_end = selection_end->next;

      /* Deletion confuses the clist and we would get several
       * extra zeroes deleted, so get the selection again:
       */
      selection = GTK_CLIST(pandora_gen_filelist)->selection;
      selection_end = GTK_CLIST(pandora_gen_filelist)->selection_end;
    }
  }
}

static void
pandora_gen_file_selection_cancel (GtkWidget *widget,
                                   gpointer   data)
{
  gtk_widget_hide (GTK_WIDGET (data));
}

#if 0
static void
rename_button (GtkWidget* button, const gchar* newname)
{
#ifdef GIMP_1_2
  gtk_label_set_text(GTK_LABEL (GTK_BIN(button)->child), newname);
#else
  button = GTK_BUTTON (button);
  gtk_button_set_label (button, newname);
  /* gtk_button_set_use_stock (button, TRUE); */
#endif
}
#endif

static gint
pandora_gen_dialog (void)
{
  if (!pandora_gen_fsb)
  {
    GtkWidget* w;
    GtkWidget* table;
    GtkObject* scale;
    GtkWidget* scrolled_win;
    GtkWidget* frame;
    GtkWidget* vbox;
    GtkFileSelection* filesel;
    GtkWidget* button_box;

    gimp_ui_init ("pandora_gen", TRUE);
#ifdef GIMP_1_2
    gimp_help_init();
#endif

    pandora_gen_fsb = gtk_file_selection_new (_("Pandora: Select Images"));
    filesel = GTK_FILE_SELECTION (pandora_gen_fsb);
    gtk_file_selection_hide_fileop_buttons (filesel);

#ifdef GIMP_1_2
    /* This doesn't actually work: */
    gtk_clist_set_selection_mode (GTK_CLIST (filesel->file_list),
                                  GTK_SELECTION_MULTIPLE);
#else
    gtk_file_selection_set_select_multiple (filesel, TRUE);
#endif

    /* Add in some extra buttons */
    button_box = gtk_hbox_new (FALSE, 0);
    w = gtk_button_new_with_label (_("Remove File \\"));
    gtk_box_pack_start (GTK_BOX (button_box), w, FALSE, FALSE, 0);
    g_signal_connect (G_OBJECT (w), "clicked",
                      G_CALLBACK (pandora_gen_file_selection_rem),
                      G_OBJECT (pandora_gen_fsb));
    gtk_widget_show (w);
    w = gtk_button_new_with_label (_("\\ Add File"));
    gtk_box_pack_end (GTK_BOX (button_box), w, FALSE, FALSE, 0);
    g_signal_connect (G_OBJECT (w), "clicked",
                      G_CALLBACK (pandora_gen_file_selection_add),
                      G_OBJECT (pandora_gen_fsb));
    gtk_widget_show (w);
    gtk_widget_show (button_box);
    gtk_box_pack_start (GTK_BOX (filesel->main_vbox), button_box,
                        FALSE, FALSE, 0);

    /* Here's where the real stuff goes */
    frame = gtk_frame_new (NULL);
    gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
    gtk_box_pack_start (GTK_BOX (filesel->main_vbox), frame,
                        FALSE, FALSE, 0);
    vbox = gtk_vbox_new (FALSE, 0);
    gtk_container_add (GTK_CONTAINER (frame), vbox);

    w = gtk_label_new (_("File in panorama (left to right):"));
    gtk_box_pack_start (GTK_BOX (vbox), w, TRUE, TRUE, 0);
    gtk_widget_show(w);

    /* The list of files already selected */
    scrolled_win = gtk_scrolled_window_new (NULL, NULL);
    pandora_gen_filelist = gtk_clist_new (1);
    gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win),
                                    GTK_POLICY_AUTOMATIC,
                                    GTK_POLICY_AUTOMATIC);
    gtk_clist_set_selection_mode (GTK_CLIST (pandora_gen_filelist),
                                  GTK_SELECTION_MULTIPLE);
    gtk_widget_set_usize (scrolled_win, FILELIST_WIDTH, FILELIST_HEIGHT);
    gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW
                                           (scrolled_win),
                                           pandora_gen_filelist);
    gtk_box_pack_start (GTK_BOX (vbox), scrolled_win, TRUE, TRUE, 0);
    gtk_widget_show(pandora_gen_filelist);
    gtk_widget_show(scrolled_win);
    gtk_widget_show(frame);

    w = gtk_check_button_new_with_mnemonic (_("Feather Layers"));
    gtk_box_pack_start(GTK_BOX (vbox), w, TRUE, TRUE, 0);
    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (w), avals.feather);
    gtk_widget_show (w);
    g_signal_connect (G_OBJECT (w), "toggled",
                      G_CALLBACK (gimp_toggle_button_update),
                      &avals.feather);

    /* The scale widget (and its mandatory table container */
    table = gtk_table_new (1, 1, FALSE);
    /*
      gtk_table_set_col_spacings (GTK_TABLE (table2), 4);
      gtk_table_set_row_spacings (GTK_TABLE (table2), 2);
    */
    gtk_container_set_border_width (GTK_CONTAINER (table), 4);
    gtk_box_pack_start (GTK_BOX (vbox), table, TRUE, TRUE, 0);

    scale = gimp_scale_entry_new (GTK_TABLE (table), 0, 0,
                                  _("Overlap:"), SCALE_WIDTH, SPIN_WIDTH,
                                  avals.overlap, 0.0, 1.0, 0.01, 0.1, 3,
                                  TRUE, 0, 0,
                                  /*
                                    _("How much each image overlaps"),
                                    Why do tooltips always -> Gtk-CRITICAL?
                                  */
                                  NULL,
                                  NULL);
    g_signal_connect (G_OBJECT (scale), "value_changed",
                      G_CALLBACK (gimp_double_adjustment_update),
                      &avals.overlap);
    gtk_widget_show (table);
    gtk_widget_show(vbox);

    /* Connect all the signals */
    g_signal_connect (G_OBJECT (filesel), "destroy",
                      G_CALLBACK (gtk_widget_destroyed),
                      G_OBJECT (pandora_gen_fsb));

    g_signal_connect (G_OBJECT (filesel->ok_button),
                      "clicked",
                      G_CALLBACK (pandora_gen_file_selection_add),
                      G_OBJECT (pandora_gen_fsb));
    /* We want doubleclick to work, but we don't want it to dismiss
     * the dialog.  We want OK to dismiss the dialog and do the
     * function, without adding any more files.
     * Unfortunately, doubleclick works through the OK button callback.
     * Fortunately, it only sends clicked, not pressed.
     * So note when the pressed signal happens, and set a flag
     * so that the next clicked signal won't add a file but
     * will instead activate ok.
     */
    g_signal_connect (G_OBJECT (filesel->ok_button),
                      "pressed",
                      G_CALLBACK (pandora_gen_file_selection_ok_pressed),
                      G_OBJECT (pandora_gen_fsb));

    g_signal_connect (G_OBJECT (filesel->cancel_button),
                      "clicked",
                      G_CALLBACK (pandora_gen_file_selection_cancel),
                      G_OBJECT (pandora_gen_fsb));
    gimp_help_set_help_data (filesel->cancel_button,
                             _("Click here to cancel file selection"), NULL);
  }

  if (!GTK_WIDGET_VISIBLE (pandora_gen_fsb)) {
    gtk_widget_show (pandora_gen_fsb);
  }

  gtk_main ();    /* This won't return until after _generate has finished */
  gdk_flush ();

  return TRUE;
}

static void
pandora_gen_add_layer_mask (gint32 pan_img, gint32 layer,
                            gint32 w, gint32 h, gdouble overlap)
{
#ifdef GIMP_1_2
  gdouble hgrad = (gdouble)h / 2.;
  gint32 mask;

  guchar oldFGr, oldFGg, oldFGb;
  guchar oldBGr, oldBGg, oldBGb;

  gimp_palette_get_foreground (&oldFGr, &oldFGg, &oldFGb);
  gimp_palette_get_background (&oldBGr, &oldBGg, &oldBGb);
  gimp_palette_set_foreground (0xff, 0xff, 0xff);
  gimp_palette_set_background (   0,    0,    0);

  /* Give it a layer mask */
  mask = gimp_layer_create_mask (layer, GIMP_WHITE_MASK);
  gimp_image_add_layer_mask (pan_img, layer, mask);

  gimp_blend (mask, GIMP_FG_BG_RGB, GIMP_NORMAL_MODE,
              GIMP_LINEAR, 100.0, 0.0, GIMP_REPEAT_NONE,
              FALSE, 0, 0.0,  /* supersampling stuff */
              w * overlap * .5, hgrad, 0.0, hgrad);

  gimp_palette_set_foreground (oldFGr, oldFGg, oldFGb);
  gimp_palette_set_background (oldBGr, oldBGg, oldBGb);

#else    /* GIMP_1_3 */
  gdouble hgrad = (gdouble)h / 2.;
  gint32 mask;

  GimpRGB black, white;
  GimpRGB oldFG, oldBG;

  gimp_palette_get_foreground (&oldFG);
  gimp_palette_get_background (&oldBG);
  gimp_rgb_set (&black, 0., 0., 0.);
  gimp_rgb_set (&white, 1., 1., 1.);
  gimp_palette_set_foreground (&white);
  gimp_palette_set_background (&black);

  /* Give it a layer mask */
  mask = gimp_layer_create_mask (layer, GIMP_ADD_WHITE_MASK);
  gimp_image_add_layer_mask (pan_img, layer, mask);

  gimp_blend (mask, GIMP_FG_BG_RGB_MODE, GIMP_NORMAL_MODE,
              GIMP_GRADIENT_LINEAR, 100.0, 0.0, GIMP_REPEAT_NONE, FALSE,
              FALSE, 0, 0.0, FALSE,  /* supersampling stuff */
              w * overlap * .5, hgrad, 0.0, hgrad);

  gimp_palette_set_background (&oldBG);
  gimp_palette_set_foreground (&oldFG);

  /* Set selection back to the layer; if it's on the mask,
   * then 2.0.1 and later will drag the mask, not the image!
   */
  mask = gimp_layer_set_edit_mask (layer, FALSE);

#endif    /* GIMP_1_2 | 1.3 */
}

static gint
pandora_generate_panorama()
{
  int i;
  int pan_img = 0;
  int pan_img_w = 0, pan_img_h = 0;
  int pan_num_layers = 0;
  gint32 pan_layers[MAXIMAGES];
  int layerXoff[MAXIMAGES];
  int w = 0, h;

  layerXoff[0] = 0;

  /* Get the list of files */
  for (i=0; ; ++i)
  {
    gchar* fnam;
    gchar* bnam;
    int cur_img;
    int numlayers;
    int *layers;
    int j;

    if (gtk_clist_get_text(GTK_CLIST (pandora_gen_filelist), i, 0, &fnam) == 0)
      break;
    bnam = basename(fnam);
    cur_img = gimp_file_load (GIMP_RUN_INTERACTIVE, fnam, bnam);
    /* should pass the real run mode */
    if (cur_img == -1) {
      printf("Couldn't open %s\n", fnam);
      continue;
    }

    gimp_image_set_filename (cur_img, bnam);
    w = gimp_image_width (cur_img);
    h = gimp_image_height (cur_img);
    if (pan_img)
      {
        /* Resize the image bigger if need be */
        if (w > pan_img_w)
          pan_img_w = w*2;
        if (h > pan_img_h)
          pan_img_h = h*2;
        gimp_image_resize(pan_img, pan_img_w, pan_img_h, 0, 0);
      }
    else
      {
        /* Make a new image, twice the size of this first component image */
        pan_img_w = w*2;
        pan_img_h = h*2;
        pan_img = gimp_image_new(pan_img_w, pan_img_h, GIMP_RGB);
      }

    /* Now it's safe, so stick the image in.
     * Loop over layers in the image to be inserted.
     */
    layers = gimp_image_get_layers(cur_img, &numlayers);
    for (j = 0; j < numlayers && pan_num_layers < MAXIMAGES; ++j)
      {
        /* Make a new layer in pan_img and paste the image's layer into it. */
#ifdef GIMP_1_2
        pan_layers[pan_num_layers] = gimp_layer_new(pan_img, bnam, w, h,
                                                    GIMP_RGBA_IMAGE, 100.0,
                                                    GIMP_NORMAL_MODE);
        gimp_image_add_layer(pan_img, pan_layers[pan_num_layers], 0);
        gimp_edit_copy(layers[j]);
        gimp_edit_paste(pan_layers[pan_num_layers], FALSE);
#else
        /* gimp 1.3 has this great new call: */
        pan_layers[pan_num_layers] = gimp_layer_new_from_drawable(layers[0],
                                                                  pan_img);
        /* but it doesn't copy the drawable's name, so set it: */
        gimp_layer_set_name(pan_layers[pan_num_layers], bnam);
        gimp_image_add_layer(pan_img, pan_layers[pan_num_layers], 0);
#endif /* GIMP_1.2 */

        /* Give it a layer mask with a gradient on the left,
         * except the first image.
         */
        if (pan_num_layers > 0 && avals.feather)
          pandora_gen_add_layer_mask (pan_img, pan_layers[pan_num_layers],
                                  w, h, avals.overlap);

        /* Calculate the offset of the next image
         * based on the size of this one.
         * Don't try to set it now -- the image isn't big enough yet.
         */
        layerXoff[pan_num_layers+1] = layerXoff[pan_num_layers]
          + (w * (1. - avals.overlap));
        ++pan_num_layers;
      }

    /* Now we're done with the temp image. */
    gimp_image_delete(cur_img);

    if (pan_num_layers >= MAXIMAGES)
      break;

  } /* end of loop over files */

  /* Now we're probably left with a floating selection from the
   * last paste, and we need to get rid of it somehow.
   */
  i = gimp_image_floating_selection (pan_img);
  if (i != -1)
    gimp_floating_sel_anchor (i);

  /* Now, resize the image to the right size, and move all the layers.
   * Allow some extra room on the right in case the user was conservative
   * with estimating overlap.
   */
  printf("Final image should have %d layers and width %d\n",
         pan_num_layers, layerXoff[pan_num_layers]);
  gimp_image_resize(pan_img,
                    layerXoff[pan_num_layers] + w * .6, pan_img_h,
                    0, 0);

  /* Loop over all layers in the final image,
   * moving them to their final resting place.
   */
  for (i=0; i<pan_num_layers; ++i)
    {
      GimpDrawable* drawable;

      gimp_layer_translate (pan_layers[i], layerXoff[i],
                            (pan_img_h -
                             gimp_drawable_height (pan_layers[i])) / 2);

      /* Try to flush everything.
         Is all this really needed?  I have no idea!
      */
      drawable = gimp_drawable_get (pan_layers[i]);
      gimp_drawable_flush (drawable);
      /*
      gimp_drawable_merge_shadow (pan_layers[i], TRUE);
      gimp_drawable_update (pan_layers[i], 0, 0, new_width, new_height);
      */
      gimp_drawable_detach (drawable);
    }

  gimp_display_new (pan_img);
  gimp_displays_flush ();

  return TRUE;
}


Generated by  Doxygen 1.6.0   Back to index