/**************************************************************************

   Fotoxx      edit photos and manage collections

   Copyright 2007 2008 2009 2010 2011 2012 Michael Cornelison
   Source URL: http://kornelix.squarespace.com/fotoxx
   Contact: kornelix2@googlemail.com
   
   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 http://www.gnu.org/licenses/.

***************************************************************************/

#define EX                                                                 //  disable extern declarations
#include "fotoxx.h"


char     *initial_file = 0;                                                //  initial file on command line

//  fotoxx main program

int main(int argc, char *argv[])
{
   char           lang[8] = "", *pp;
   int            err;
   struct stat    statb;
   int            Fclone=0, cloxx=0, cloyy=0, cloww=0, clohh=0;
   GdkScreen      *screen;
   int            Fsyncfiles = 0;
   int            Ftranslate = 0;
   
   if (argc > 1 && strEqu(argv[1],"-v")) {
      printf(fversion "\n");                                               //  print version and exit
      return 0;
   }

   //  initialize externals to default values 
   //  (saved parameters will override)

   Ffirsttime = 1;                                                         //  first time startup              v.11.11
   maxcolor = 0xffff;
   for (int ii = 0; ii < 8; ii++) wtnx[ii] = ii;
   strcpy(jpeg_quality,def_jpeg_quality);
   mwgeom[0] = mwgeom[1] = 100;                                            //  default main window geometry    v.11.07
   mwgeom[2] = 700; mwgeom[3] = 500;
   Mscale = 1.0;
   trimsize[0] = 1600;
   trimsize[1] = 1000;
   editresize[0] = 1600;
   editresize[1] = 1200;
   batchresize[0] = 1600;
   batchresize[1] = 1200;
   emailsize[0] = 1000;
   emailsize[1] = 800;
   lens_mm = lens_settings[0] = 35;                                        //  pano lens parameters            v.12.01
   lens_bow = lens_settings[1] = 0;
   gridcount[0] = gridcount[1] = 5;                                        //  set default grid line counts
   sa_pixRGB = &green;
   ss_funcs[0] = 1;
   fsync_lock = "fotoxx_syncfiles";                                        //  file sync lock file             v.11.12
   tbar_style = "both";                                                    //  default toolbar style           v.12.01
   startdisplay = "blank";                                                 //  start with blank window         v.12.01
   Fwarnoverwrite = 1;                                                     //  warn original file overwrite    v.12.01

   //  command line args

   gtk_init(&argc,&argv);                                                  //  initz. GTK & strip GTK command line params

   for (int ii = 1; ii < argc; ii++)                                       //  command line parameters
   {
      pp = argv[ii];
      if (strcmpv(pp,"-debug","-d",null))                                  //  -debug
         Fdebug = 1;
      else if (strcmpv(pp,"-lang","-l",null) && argc > ii+1)               //  -lang lc_RC      (language/region code)
         strncpy0(lang,argv[++ii],7);
      else if (strcmpv(pp,"-clone1","-c1",null))                           //  -clone1       (clone1/2 not user options)
         Fclone = 1;
      else if (strcmpv(pp,"-clone2","-c2",null)) {                         //  -clone2 xpos ypos               v.11.07
         Fclone = 2;
         cloxx = atoi(argv[ii+1]);                                         //  window position and size
         cloyy = atoi(argv[ii+2]);                                         //    passed from parent instance
         cloww = atoi(argv[ii+3]);
         clohh = atoi(argv[ii+4]);
         ii += 4;
      }
      else if (strcmpv(pp,"-slideshow","-ss",null) && argc > ii+1)         //  -slideshow /image/file.jpg      v.11.04
         SS_imagefile = strdupz(argv[++ii]);
      else if (strcmpv(pp,"-music","-m",null) && argc > ii+1)              //  -music /music/file.ogg          v.11.04
         SS_musicfile = strdupz(argv[++ii]);
      else if (strcmpv(pp,"-recent","-r",null))                            //  recent files gallery            v.11.07
         Frecent = 1;
      else if (strcmpv(pp,"-previous","-prev","-p",null))                  //  open previous file              v.11.07
         Fprev = 1;
      else if (strcmpv(pp,"-blank","-b",null))                             //  start with blank window         v.12.01
         Fblank = 1;
      else if (strcmpv(pp,"-syncfiles",null)) {                            //  spawned sync files process      v.11.11
         Fsyncfiles = 1;
         pp = argv[++ii];
         if (strEqu(pp,"auto")) Fautosync = 1;                             //  set auto or manual sync
         if (strEqu(pp,"manual")) Fmansync = 1;
      }
      else if (strcmpv(pp,"-translate","-t",null))                         //  start online translation        v.11.12
         Ftranslate = 1;
      else  initial_file = strdupz(pp);                                    //  must be initial file or directory
   }

   zinitapp("fotoxx",null);                                                //  get app directories
   ZTXinit(lang);                                                          //  setup locale and translations
   
   //  get files in user directory /home/<user>/.fotoxx/*

   *topdirk_file = 0;
   strncatv(topdirk_file,199,get_zuserdir(),"/top_directory",null);        //  top_directory

   *search_index_file = 0;
   strncatv(search_index_file,199,get_zuserdir(),"/search_index",null);    //  search_index

   *tags_defined_file = 0;
   strncatv(tags_defined_file,199,get_zuserdir(),"/tags_defined",null);    //  tags_defined

   *recentfiles_file = 0;
   strncatv(recentfiles_file,199,get_zuserdir(),"/recent_files",null);     //  recent_files

   *saved_areas_dirk = 0;
   strncatv(saved_areas_dirk,199,get_zuserdir(),"/saved_areas/",null);     //  saved_areas/
   err = stat(saved_areas_dirk,&statb);
   if (err) mkdir(saved_areas_dirk,0750);

   *collections_dirk = 0;
   strncatv(collections_dirk,199,get_zuserdir(),"/collections/",null);     //  collections/
   err = stat(collections_dirk,&statb);
   if (err) mkdir(collections_dirk,0750);

   *saved_curves_dirk = 0;
   strncatv(saved_curves_dirk,199,get_zuserdir(),"/saved_curves/",null);   //  saved_curves/
   err = stat(saved_curves_dirk,&statb);
   if (err) mkdir(saved_curves_dirk,0750);

   *annotations_dirk = 0;
   strncatv(annotations_dirk,199,get_zuserdir(),"/annotations/",null);     //  annotations/
   err = stat(annotations_dirk,&statb);
   if (err) mkdir(annotations_dirk,0750);

   load_params();                                                          //  restore parameters from last session
   
   if (Fsyncfiles) {                                                       //  this is the spawned sync process  v.11.11
      gtk_init_add((GtkFunction) syncfiles_func,0);                        //  do the sync function only
      gtk_main();
      return 0;
   }

  // if (! Fsyncfiles && ! Fclone) {                                         //  this is the user GUI process
  //    printf("spawn sync files subprocess \n");
  //    err = system("fotoxx -syncfiles auto &");                            //  spawn process for sync files
  //    if (err) {
  //       printf("error: %s \n",wstrerror(err));                            //  quit if failed
  //       return 1;
  //    }
  // }
   
   mWin = gtk_window_new(GTK_WINDOW_TOPLEVEL);                             //  create main window
   gtk_window_set_title(MWIN,fversion);
   
   mVbox = gtk_vbox_new(0,0);                                              //  add vert. packing box
   gtk_container_add(GTK_CONTAINER(mWin),mVbox);

   mMbar = create_menubar(mVbox);                                          //  add menus / sub-menus
   
   if (Ftranslate) {                                                       //  start online translation     v.11.12
      zfuncs::F1_help_topic = "translate";
      ZTX_translation_start(mWin);
   }

   GtkWidget *mFile = add_menubar_item(mMbar,ZTX("File"),topmenufunc);
      add_submenu_item(mFile,       ZTX("Image Gallery"),            m_gallery);
      add_submenu_item(mFile,       ZTX("Clone 50/50"),              m_clone1);
      add_submenu_item(mFile,       ZTX("Clone Overlay"),            m_clone2);
      add_submenu_item(mFile,       ZTX("Open Image File"),          m_open);
      add_submenu_item(mFile,       ZTX("Open in New Window"),       m_open_newin);
      add_submenu_item(mFile,       ZTX("Open Previous File"),       m_previous);
      add_submenu_item(mFile,       ZTX("Open Recent File"),         m_recent);
      add_submenu_item(mFile,       ZTX("Save to Same File"),        m_save);
      add_submenu_item(mFile,       ZTX("Save to New Version"),      m_savevers);
      add_submenu_item(mFile,       ZTX("Save to New File"),         m_saveas);
      add_submenu_item(mFile,       ZTX("Create Blank Image"),       m_create);
      add_submenu_item(mFile,       ZTX("Trash Image File"),         m_trash);
      add_submenu_item(mFile,       ZTX("Rename Image File"),        m_rename);
      add_submenu_item(mFile,       ZTX("Batch Rename Files"),       m_batchrename);
      add_submenu_item(mFile,       ZTX("Print Image File"),         m_print);
      add_submenu_item(mFile,       ZTX("Quit fotoxx"),              m_quit);

   GtkWidget *mTools = add_menubar_item(mMbar,ZTX("Tools"),topmenufunc);
      add_submenu_item(mTools,      ZTX("Manage Collections"),       m_manage_collections);
      add_submenu_item(mTools,      ZTX("Move Collections"),         m_move_collections);
      add_submenu_item(mTools,      ZTX("Batch Convert"),            m_batchconvert);
      add_submenu_item(mTools,      ZTX("Convert RAW files"),        m_conv_raw);
      add_submenu_item(mTools,      ZTX("Slide Show"),               m_slideshow);
      add_submenu_item(mTools,      ZTX("Synchronize Files"),        m_syncfiles);
      add_submenu_item(mTools,      ZTX("Show RGB"),                 m_show_RGB);
      add_submenu_item(mTools,      ZTX("Grid Lines"),               m_gridlines);
      add_submenu_item(mTools,      ZTX("Burn Images to CD/DVD"),    m_burn);
      add_submenu_item(mTools,      ZTX("E-mail Images"),            m_email);
      add_submenu_item(mTools,      ZTX("Check Monitor"),            m_moncheck);
      add_submenu_item(mTools,      ZTX("Monitor Gamma"),            m_mongamma);
      add_submenu_item(mTools,      ZTX("Brightness Distribution"),  m_histogram);
      add_submenu_item(mTools,      ZTX("Change Language"),          m_lang);
      add_submenu_item(mTools,          "Edit Translations",         m_translate);
      add_submenu_item(mTools,      ZTX("Menu and Launcher"),        m_menu_launcher);
      add_submenu_item(mTools,      ZTX("User Settings"),            m_settings);
      add_submenu_item(mTools,      ZTX("Memory Usage"),             m_memory_usage);

   GtkWidget *mInfo = add_menubar_item(mMbar,"Info",topmenufunc);
      add_submenu_item(mInfo,       ZTX("Edit Caption/Comments"),    m_edit_cctext);
      add_submenu_item(mInfo,       ZTX("Edit Tags"),                m_edit_tags);
      add_submenu_item(mInfo,       ZTX("Manage Tags"),              m_manage_tags);
      add_submenu_item(mInfo,       ZTX("Batch Add Tags"),           m_batchAddTags);
      add_submenu_item(mInfo,       ZTX("Batch Delete Tag"),         m_batchDelTag);
      add_submenu_item(mInfo,       ZTX("View Info (short)"),        m_info_view_short);
      add_submenu_item(mInfo,       ZTX("View Info (long)"),         m_info_view_long);
      add_submenu_item(mInfo,       ZTX("Edit Info"),                m_info_edit);
      add_submenu_item(mInfo,       ZTX("Delete Info"),              m_info_delete);
      add_submenu_item(mInfo,       ZTX("Search Images"),            m_search_images);
      add_submenu_item(mInfo,       ZTX("Search Metadata"),          m_search_metadata);

   GtkWidget *mSelect = add_menubar_item(mMbar,ZTX("Select"),topmenufunc);
      add_submenu_item(mSelect,     ZTX("Select"),                   m_select);
      add_submenu_item(mSelect,     ZTX("Show"),                     m_select_show);
      add_submenu_item(mSelect,     ZTX("Hide"),                     m_select_hide);
      add_submenu_item(mSelect,     ZTX("Enable"),                   m_select_enable);
      add_submenu_item(mSelect,     ZTX("Disable"),                  m_select_disable);
      add_submenu_item(mSelect,     ZTX("Invert"),                   m_select_invert);
      add_submenu_item(mSelect,     ZTX("Unselect"),                 m_select_unselect);
      add_submenu_item(mSelect,     ZTX("Copy"),                     m_select_copy);
      add_submenu_item(mSelect,     ZTX("Paste"),                    m_select_paste);
      add_submenu_item(mSelect,     ZTX("Open"),                     m_select_open);
      add_submenu_item(mSelect,     ZTX("Save"),                     m_select_save);
      add_submenu_item(mSelect,     ZTX("Select Whole Image"),       m_select_whole_image);
      add_submenu_item(mSelect,     ZTX("Select and Edit"),          m_select_edit);

   GtkWidget *mTransf = add_menubar_item(mMbar,ZTX("Transform"),topmenufunc);
      add_submenu_item(mTransf,     ZTX("Rotate Image"),             m_rotate);
      add_submenu_item(mTransf,     ZTX("Trim Image"),               m_trim);
      add_submenu_item(mTransf,     ZTX("Auto-Trim Image"),          m_autotrim);
      add_submenu_item(mTransf,     ZTX("Resize Image"),             m_resize);
      add_submenu_item(mTransf,     ZTX("Annotate Image"),           m_annotate);
      add_submenu_item(mTransf,     ZTX("Flip Image"),               m_flip);
      add_submenu_item(mTransf,     ZTX("Make Negative"),            m_negate);
      add_submenu_item(mTransf,     ZTX("Unbend Image"),             m_unbend);
      add_submenu_item(mTransf,     ZTX("Keystone Correction"),      m_keystone);
      add_submenu_item(mTransf,     ZTX("Warp Image (area)"),        m_warp_area);
      add_submenu_item(mTransf,     ZTX("Warp Image (curved)"),      m_warp_curved);
      add_submenu_item(mTransf,     ZTX("Warp Image (linear)"),      m_warp_linear);
      add_submenu_item(mTransf,     ZTX("Warp Image (affine)"),      m_warp_affine);

   GtkWidget *mRetouch = add_menubar_item(mMbar,ZTX("Retouch"),topmenufunc);
      add_submenu_item(mRetouch,    ZTX("Brightness/Color"),         m_tune);
      add_submenu_item(mRetouch,    ZTX("Gamma Curves"),             m_gamma);
      add_submenu_item(mRetouch,    ZTX("Expand Brightness"),        m_xbrange);
      add_submenu_item(mRetouch,    ZTX("Flatten Brightness"),       m_flatten);
      add_submenu_item(mRetouch,    ZTX("Brightness Ramp"),          m_brightramp);
      add_submenu_item(mRetouch,    ZTX("Tone Mapping"),             m_tonemap);
      add_submenu_item(mRetouch,    ZTX("White Balance"),            m_whitebal);
      add_submenu_item(mRetouch,    ZTX("Match Colors"),             m_match_color);
      add_submenu_item(mRetouch,        "DRGB",                      m_DRGB);
      add_submenu_item(mRetouch,    ZTX("Revise RGB"),               m_revise_RGB);
      add_submenu_item(mRetouch,    ZTX("Red Eyes"),                 m_redeye);
      add_submenu_item(mRetouch,    ZTX("Blur Image"),               m_blur);
      add_submenu_item(mRetouch,    ZTX("Sharpen Image"),            m_sharpen);
      add_submenu_item(mRetouch,    ZTX("Reduce Noise"),             m_denoise);
      add_submenu_item(mRetouch,    ZTX("Smart Erase"),              m_smart_erase);
      add_submenu_item(mRetouch,    ZTX("Remove Dust"),              m_dust);
      add_submenu_item(mRetouch,    ZTX("Fix Stuck Pixels"),         m_stuckpix);
      add_submenu_item(mRetouch,    ZTX("Edit Pixels"),              m_pixedit);

   GtkWidget *mArt = add_menubar_item(mMbar,ZTX("Art"),topmenufunc);
      add_submenu_item(mArt,        ZTX("Color Depth"),              m_colordep);
      add_submenu_item(mArt,        ZTX("Drawing"),                  m_draw);
      add_submenu_item(mArt,        ZTX("Outlines"),                 m_outlines);
      add_submenu_item(mArt,        ZTX("Embossing"),                m_emboss);
      add_submenu_item(mArt,        ZTX("Tiles"),                    m_tiles);
      add_submenu_item(mArt,        ZTX("Dots"),                     m_dots);
      add_submenu_item(mArt,        ZTX("Painting"),                 m_painting);

   GtkWidget *mComb = add_menubar_item(mMbar,ZTX("Combine"),topmenufunc);
      add_submenu_item(mComb,       ZTX("High Dynamic Range"),       m_HDR);
      add_submenu_item(mComb,       ZTX("High Depth of Field"),      m_HDF);
      add_submenu_item(mComb,       ZTX("Stack / Paint"),            m_STP);
      add_submenu_item(mComb,       ZTX("Stack / Noise"),            m_STN);
      add_submenu_item(mComb,       ZTX("Panorama"),                 m_pano);
      add_submenu_item(mComb,       ZTX("Vertical Panorama"),        m_vpano);

   GtkWidget *mPlugins = add_menubar_item(mMbar,"Plugins",topmenufunc);    //  build plugin menu     v.11.03
      add_submenu_item(mPlugins,ZTX("Edit Plugins"),m_edit_plugins);
      for (int ii = 0; ii < Nplugins; ii++) {
         pp = strstr(plugins[ii]," = ");
         if (! pp) continue;
         *pp = 0;
         add_submenu_item(mPlugins,plugins[ii],m_run_plugin);
         *pp = ' ';
      }

   GtkWidget *mHelp = add_menubar_item(mMbar,ZTX("Help"),topmenufunc);
      add_submenu_item(mHelp,       ZTX("About"),                    m_help);
      add_submenu_item(mHelp,       ZTX("User Guide"),               m_help);
      add_submenu_item(mHelp,       ZTX("User Guide Changes"),       m_help);
      add_submenu_item(mHelp,       ZTX("Edit Functions Summary"),   m_help);
      add_submenu_item(mHelp,       ZTX("Change Log"),               m_help);
      add_submenu_item(mHelp,       ZTX("Translations"),             m_help);
      add_submenu_item(mHelp,       ZTX("Home Page"),                m_help);
      add_submenu_item(mHelp,       "README",                        m_help);

   mTbar = create_toolbar(mVbox);                                          //  toolbar buttons
      add_toolbar_button(mTbar,  ZTX("Gallery"),  ZTX("Image Gallery"),        "gallery.png",  m_gallery);
      add_toolbar_button(mTbar,  ZTX("Open"),     ZTX("Open Image File"),      "open.png",     m_open);
      add_toolbar_button(mTbar,  ZTX("Prev"),     ZTX("Open Previous File"),   "prev.png",     m_prev);
      add_toolbar_button(mTbar,  ZTX("Next"),     ZTX("Open Next File"),       "next.png",     m_next);
      add_toolbar_button(mTbar,  "Zoom+",         ZTX("Zoom-in (bigger)"),     "zoom+.png",    m_zoom);
      add_toolbar_button(mTbar,  "Zoom-",         ZTX("Zoom-out (smaller)"),   "zoom-.png",    m_zoom);
      add_toolbar_button(mTbar,  ZTX("Undo"),     ZTX("Undo One Edit"),        "undo.png",     m_undo);
      add_toolbar_button(mTbar,  ZTX("Redo"),     ZTX("Redo One Edit"),        "redo.png",     m_redo);
      add_toolbar_button(mTbar,  "separator",     null,                        null,           null);
      add_toolbar_button(mTbar,  "separator",     null,                        null,           null);
      add_toolbar_button(mTbar,  ZTX("Save"),     ZTX("Save to Same File"),    "save.png",     m_save);
      add_toolbar_button(mTbar,  ZTX("Save+V"),   ZTX("Save to New Version"),  "save+V.png",   m_savevers);
      add_toolbar_button(mTbar,  ZTX("Save+F"),   ZTX("Save to New File"),     "save+F.png",   m_saveas);
      add_toolbar_button(mTbar,  ZTX("Trash"),    ZTX("Move Image to Trash"),  "trash.png",    m_trash);
      add_toolbar_button(mTbar,  "separator",     null,                        null,           null);
      add_toolbar_button(mTbar,  "separator",     null,                        null,           null);
      add_toolbar_button(mTbar,  ZTX("Quit"),     ZTX("Quit fotoxx"),          "quit.png",     m_quit);
      add_toolbar_button(mTbar,  ZTX("Help"),     ZTX("Fotoxx Essentials"),    "help.png",     m_help);
   
   if (strEqu(tbar_style,"icons"))                                         //  set toolbar style      v.12.01
      gtk_toolbar_set_style(GTK_TOOLBAR(mTbar),GTK_TOOLBAR_ICONS);
   else if (strEqu(tbar_style,"text"))
      gtk_toolbar_set_style(GTK_TOOLBAR(mTbar),GTK_TOOLBAR_TEXT);
   else gtk_toolbar_set_style(GTK_TOOLBAR(mTbar),GTK_TOOLBAR_BOTH);

   drWin = gtk_drawing_area_new();                                         //  add drawing window
   gtk_box_pack_start(GTK_BOX(mVbox),drWin,1,1,0);

   STbar = create_stbar(mVbox);                                            //  add status bar

   G_SIGNAL(mWin,"delete_event",delete_event,0);                           //  connect signals
   G_SIGNAL(mWin,"destroy",destroy_event,0);
   G_SIGNAL(drWin,"expose-event",mwpaint1,0);

   gtk_widget_add_events(drWin,GDK_BUTTON_PRESS_MASK);                     //  connect mouse events
   gtk_widget_add_events(drWin,GDK_BUTTON_RELEASE_MASK);
   gtk_widget_add_events(drWin,GDK_BUTTON_MOTION_MASK);                    //  motion with button pressed
   gtk_widget_add_events(drWin,GDK_POINTER_MOTION_MASK);                   //  pointer motion
   gtk_widget_add_events(drWin,GDK_SCROLL_MASK);                           //  scroll wheel              v.12.01
   G_SIGNAL(drWin,"button-press-event",mouse_event,0);
   G_SIGNAL(drWin,"button-release-event",mouse_event,0);
   G_SIGNAL(drWin,"motion-notify-event",mouse_event,0);
   G_SIGNAL(drWin,"scroll-event",mouse_event,0);

   G_SIGNAL(mWin,"key-press-event",KBpress,0);                             //  connect KB events
   G_SIGNAL(mWin,"key-release-event",KBrelease,0);
   
   drag_drop_connect(drWin,m_open_drag);                                   //  connect drag-drop event

   gtk_window_move(MWIN,mwgeom[0],mwgeom[1]);                              //  main window geometry
   gtk_window_set_default_size(MWIN,mwgeom[2],mwgeom[3]);                  //  (defaults or last session params)
   gtk_widget_show_all(mWin);                                              //  show all widgets

   if (Fclone == 1) {                                                      //  clone1: left half of desktop
      screen = gdk_screen_get_default();
      cloww = gdk_screen_get_width(screen);
      clohh = gdk_screen_get_height(screen);
      cloww = cloww / 2 - 20;
      clohh = clohh - 50;
      gtk_window_move(MWIN,10,10);                                         //  v.11.07
      gtk_window_resize(MWIN,cloww,clohh);
   }
   
   if (Fclone == 2) {                                                      //  clone2: open new window         v.11.07
      gtk_window_move(MWIN,cloxx+20,cloyy+30);                             //    slightly offset from old window
      gtk_window_resize(MWIN,cloww,clohh);
   }

   gdkgc = gdk_gc_new(drWin->window);                                      //  initz. graphics context

   black.red = black.green = black.blue = 0;                               //  set up colors
   white.red = white.green = white.blue = maxcolor;
   lgray.red = lgray.green = lgray.blue = 0.9 * maxcolor;
   dgray.red = dgray.green = dgray.blue = 0.5 * maxcolor;
   red.red = maxcolor;  red.green = red.blue = 0;
   green.green = maxcolor; green.red = green.blue = 0;

   colormap = gtk_widget_get_colormap(drWin);
   gdk_rgb_find_color(colormap,&black);
   gdk_rgb_find_color(colormap,&white);
   gdk_rgb_find_color(colormap,&lgray);
   gdk_rgb_find_color(colormap,&dgray);
   gdk_rgb_find_color(colormap,&red);
   gdk_rgb_find_color(colormap,&green);
   
   gdk_gc_set_foreground(gdkgc,&black);
   gdk_gc_set_background(gdkgc,&lgray);
   gdk_window_set_background(drWin->window,&lgray);                        //  v.11.01
   fg_color = black;

   gdk_gc_set_line_attributes(gdkgc,1,LINEATTRIBUTES);

   arrowcursor = gdk_cursor_new(GDK_TOP_LEFT_ARROW);                       //  cursor for selection
   dragcursor = gdk_cursor_new(GDK_CROSSHAIR);                             //  cursor for dragging
   drawcursor = gdk_cursor_new(GDK_PENCIL);                                //  cursor for drawing lines

   gtk_init_add((GtkFunction) gtkinitfunc,0);                              //  set initz. call from gtk_main()
   gtk_main();                                                             //  start processing window events
   return 0;
}


/**************************************************************************/

//  initial function called from gtk_main() at startup

int gtkinitfunc(void *data)
{
   int            err, flag, npid;
   int            ftype, fdirk = 0;
   char           procfile[20], *ppv;
   cchar          *pp, *pp2;
   struct stat    statb;

   menulock(1);                                                            //  block menus until initz. done   v.11.04

   snprintf(PIDstring,11,"%06d",getpid());                                 //  get process PID 6-digits        v.11.12

   printf(fversion "\n");                                                  //  print Fotoxx version
   printf("install location: %s \n",get_zprefix());                        //  install location
   printf("last Fotoxx session: %s \n",last_session);                      //  last session exit time
   printf("top image directory: %s \n",topdirk);
  
   if (Ffirsttime) {                                                       //  first time startup           v.11.11
      printf("%s \n",ZTX("first time startup"));
      zmessageACK(mWin,"User Guide is available in Help menu");            //  inform user of user guide
      Ffirsttime = 0;
      save_params();
   }

   Nwt = sysconf(_SC_NPROCESSORS_ONLN);                                    //  get SMP CPU count
   if (Nwt <= 0) Nwt = 1;
   if (Nwt > max_threads) Nwt = max_threads;                               //  compile time limit
   printf("using %d threads \n",Nwt);

   err = system("which exiftool");                                         //  check for exiftool     v.11.05
   if (! err) Fexiftool = 1;
   err = system("which xdg-open");                                         //  check for xdg-open
   if (! err) Fxdgopen = 1;
   err = system("which ufraw-batch");                                      //  check for ufraw-batch
   if (! err) Fufraw = 1;
   
   undo_files = zmalloc(200);
   *undo_files = 0;                                                        //  look for orphaned undo files
   strncatv(undo_files,199,get_zuserdir(),"/*_undo_*",null);               //  /home/user/.fotoxx/*_undo_*
   flag = 1;
   while ((pp = SearchWild(undo_files,flag)))
   {
      pp2 = strstr(pp,".fotoxx/");
      if (! pp2) continue;
      npid = atoi(pp2+8);                                                  //  pid of file owner
      snprintf(procfile,19,"/proc/%d",npid);
      err = stat(procfile,&statb);
      if (! err) continue;                                                 //  pid is active, keep file
      printf("orphaned undo file deleted: %s \n",pp);
      err = remove(pp);                                                    //  delete file         v.11.03
   }

   *undo_files = 0;                                                        //  setup undo filespec template
   strncatv(undo_files,199,get_zuserdir(),"/",PIDstring,"_undo_nn",null);  //  /home/user/.fotoxx/pppppp_undo_nn

   editlog = pvlist_create(maxedits);                                      //  history log of image edits done
   for (int ii = 0; ii < maxedits; ii++)                                   //  pre-load all pvlist entries        v.10.2
      pvlist_append(editlog,"nothing");

   mutex_init(&Fpixmap_lock,0);                                            //  setup lock for edit pixmaps
   zdialog_positions("load");                                              //  load saved dialog positions        v.11.07
   
//  set up current file and directory from saved parameters or command line

   if (topdirk) curr_dirk = strdupz(topdirk);                              //  use top directory if defined       v.11.07
   else {
      ppv = getcwd(command,ccc);                                           //  or use current directory           v.11.09
      if (ppv) curr_dirk = strdupz(ppv);
   }
   
   if (initial_file) {                                                     //  from command line or previous session
      ppv = realpath(initial_file,0);                                      //  prepend directory if needed
      if (ppv) {
         zfree(initial_file);
         initial_file = strdupz(ppv);
         free(ppv);
      }
      else {
         printf("invalid file: %s \n",initial_file);                       //  invalid file path
         zfree(initial_file);
         initial_file = 0;
      }
   }

   if (initial_file) {
      ftype = image_file_type(initial_file);
      if (ftype == 0) {                                                    //  non-existent file
         printf("invalid file: %s \n",initial_file);
         zfree(initial_file);
         initial_file = 0;
      }
      else if (ftype == 1) {                                               //  is a directory
         if (curr_dirk) zfree(curr_dirk);
         curr_dirk = initial_file;
         initial_file = 0;
         fdirk = 1;
      }
      else if (ftype == 2) {                                               //  supported image file type
         if (curr_dirk) zfree(curr_dirk);
         curr_dirk = strdupz(initial_file);                                //  set current directory from file
         ppv = strrchr(curr_dirk,'/');
         if (ppv) *ppv = 0;
      }
      else {
         printf("unsupported file type: %s \n",initial_file);              //  unsupported file type
         zfree(initial_file);
         initial_file = 0;
      }
   }

   if (curr_dirk) err = chdir(curr_dirk);                                  //  set current directory

   menulock(0);                                                            //  enable menus                       v.11.04

   g_timeout_add(200,gtimefunc,0);                                         //  start periodic function (200 ms)   v.11.06

   if (SS_imagefile) {                                                     //  start slide show if wanted         v.11.04
      f_open(SS_imagefile,1);
      if (SS_musicfile) {
         sprintf(command,"xdg-open \"%s\" ",SS_musicfile);                 //  start music if wanted
         printf("command: %s \n",command);
         err = system(command);
      }
      m_slideshow(0,0);
      return 0;
   }

   if (initial_file)                                                       //  open initial file
      f_open(initial_file,1);
   
   else if (fdirk) {                                                       //  open gallery for initial directory
      image_navi::thumbsize = 128;
      m_gallery(0,0);                                                      //  v.11.08
   }
   
   else if (Fprev)                                                         //  start with previous file  v.11.08
      m_previous(0,0);
   
   else if (Frecent) {
      image_navi::thumbsize = 180;                                         //  start with gallery of recent files
      m_recent(0,0);                                                       //  v.11.07
   }
   
   else if (Fblank) return 0;                                              //  start with blank window      v.12.01
   
   else if (strEqu(startdisplay,"recent")) {
      image_navi::thumbsize = 180;                                         //  start with gallery of recent files
      m_recent(0,0);                                                       //  v.11.07
   }

   else if (strEqu(startdisplay,"prev")) {                                 //  from user settings     v.12.01
      m_previous(0,0);
   }

   else if (strEqu(startdisplay,"dirk")) {                                 //  v.12.01
      if (! startdirk || *startdirk != '/') return 0;
      if (curr_dirk) zfree(curr_dirk);
      curr_dirk = strdupz(startdirk);
      err = chdir(curr_dirk); 
      image_navi::thumbsize = 128;
      m_gallery(0,0);
   }

   else if (strEqu(startdisplay,"file")) {                                 //  v.12.01
      f_open(startfile,1);
   }

   return 0;                                                               //  start with blank window
}


/**************************************************************************/

//  Periodic function
//  Avoid any thread usage of gtk/gdk functions.

int gtimefunc(void *arg)
{
   static zdialog    *zdmessage = 0;
   int               syncfd;

   if (Wrepaint) {                                                         //  window update needed
      Wrepaint = 0;
      mwpaint1();
   }

   update_statusbar();                                                     //  update every cycle           v.11.03
   
   if (Fslideshow) slideshow_next("timer");                                //  show next image if time is up

   if (Ffuncbusy)
      paint_text(Dww/2-50,Dhh-20,"BUSY","monospace 10");                   //  write BUSY on window         v.11.07

   if (threadmessage && ! zdmessage)                                       //  display message for thread process
      zdmessage = zmessage_post(mWin,0,threadmessage);                     //  (avoid use of GTK in threads)      v.11.03

   if (zdmessage && ! threadmessage)                                       //  message terminated by thread
      zdialog_free(zdmessage);
   
   if (zdmessage && zdmessage->sentinel != zdsentinel) {                   //  popup window closed by user
      zdmessage = 0;
      threadmessage = 0;
   }
   
   syncfd = global_lock(fsync_lock);                                       //  check for sync files process
   if (syncfd >= 0) {                                                      //  not busy
      global_unlock(syncfd);
      if (Fsyncbusy) threadmessage = 0;                                    //  it was busy, now done     v.11.11
      Fsyncbusy = 0;
   }
   else Fsyncbusy = 1;                                                     //  sync files still busy

   return 1;
}


/**************************************************************************/

//  update status bar with image data and status
//  called from timer function

void update_statusbar()
{
   static double  time1 = 0, time2, cpu1 = 0, cpu2, cpuload;
   char           text1[300], text2[100];
   int            ww, hh, scale, bpc, done;
   double         file_MB = 1.0 / mega * curr_file_size;
   
   *text1 = *text2 = 0;
   
   time2 = get_seconds();                                                  //  compute process cpu load
   if (time2 - time1 > 1.0) {                                              //    at 1 second intervals
      cpu2 = jobtime();                                                    //  v.11.09
      cpuload = 100.0 * (cpu2 - cpu1) / (time2 - time1);
      time1 = time2;
      cpu1 = cpu2;
   }

   sprintf(text1,"CPU %03.0f%c",cpuload,'%');                              //  CPU 023%
   
   if (curr_file)                                                          //  v.11.04
   {
      if (E3pxm16) {
         ww = E3ww;
         hh = E3hh;
         bpc = 16;
      }
      else if (Fpxm16) {
         ww = Fww;
         hh = Fhh;
         bpc = 16;
      }
      else {
         ww = Fww;
         hh = Fhh;
         bpc = curr_file_bpc;
      }

      snprintf(text2,99,"  %dx%dx%d",ww,hh,bpc);                           //  2345x1234x16 (preview) 1.56MB 45%
      strcat(text1,text2);
      if (Fpreview) strcat(text1," (preview)");
      sprintf(text2," %.2fMB",file_MB);
      strcat(text1,text2);
      scale = Mscale * 100 + 0.5;
      sprintf(text2," %d%c",scale,'%');
      strcat(text1,text2);
   
      if (Pundo) {                                                         //  edit undo stack depth 
         snprintf(text2,99,"  edits: %d",Pundo);
         strcat(text1,text2);
      }
   }

   if (Fmenulock) strcat(text1,"  menu locked");
   if (Fsyncbusy) strcat(text1,"  file sync busy");                        //  v.11.11
   if (Factivearea) strcat(text1,"  area active");                         //  v.11.01
   if (zfuncs::zdialog_busy) strcat(text1,"  dialog open");                //  v.11.11
   
   if (SB_goal) {                                                          //  progress monitor       v.9.6
      done = 100.0 * SB_done / SB_goal;                                    //                         v.11.06
      snprintf(text2,99,"  progress %d%c",done,'%');
      strcat(text1,text2);
   }

   if (*SB_text) {                                                         //  application text       v.10.7
      strcat(text1,"  ");
      strcat(text1,SB_text);
   }

   stbar_message(STbar,text1);                                             //  write to status bar

   return;
}


/**************************************************************************/

//  functions for main window event signals

int delete_event()                                                         //  main window closed
{
   printf("main window delete event \n");
   if (mod_keep()) return 1;                                               //  allow user bailout
   Fshutdown++;                                                            //  shutdown in progress
   save_params();                                                          //  save parameters for next session
   zdialog_positions("save");                                              //  save dialog positions too    v.11.07
   free_resources();                                                       //  delete undo files
   return 0;
}

void destroy_event()                                                       //  main window destroyed
{
   printf("main window destroy event \n");
   Fshutdown++;                                                            //  shutdown in progress
   free_resources();                                                       //  delete undo files            v.12.01
   exit(1);                                                                //  instead of gtk_main_quit();
   return;
}


//  process top-level menu entry

void topmenufunc(GtkWidget *, cchar *menu)
{
   topmenu = (char *) menu;                                                //  remember top-level menu in case
   return;                                                                 //    this is needed somewhere
}


/**************************************************************************/

//  Paint window when created, exposed, resized, or modified (edited).
//  This is a full window update. May NOT be called from threads.

int mwpaint1()
{
   GdkRectangle   wrect;
   int            incrx, incry;                                            //  mouse drag
   static int     pincrx = 0, pincry = 0;                                  //  prior mouse drag
   static int     pdww = 0, pdhh = 0;                                      //  prior window size
   static int     piorgx = 0, piorgy = 0;                                  //  prior image origin in window
   static double  pscale = 1;                                              //  prior scale
   double         wscale, hscale;
   int            refresh = 0;                                             //  flag, image refesh needed
   int            mousex, mousey;                                          //  mouse position after zoom 
   PXM            *pxmtemp;
   
   if (Fshutdown) return 1;                                                //  shutdown underway

   if (! Fpxm8) {
      gdk_window_clear(drWin->window);                                     //  no image            v.11.12
      return 1;  
   }

   Dww = drWin->allocation.width;                                          //  (new) drawing window size
   Dhh = drWin->allocation.height;
   if (Dww < 20 || Dhh < 20) return 1;                                     //  too small

   if (mutex_trylock(&Fpixmap_lock) != 0) {                                //  lock pixmaps
      Wrepaint++;                                                          //  cannot, come back later
      return 1;
   }
   
   if (Frefresh) {                                                         //  image was updated
      refresh++;
      Frefresh = 0;
   }

   if (Dww != pdww || Dhh != pdhh) {                                       //  window size changed
      refresh++;                                                           //  image refresh needed
      pdww = Dww; 
      pdhh = Dhh;
   }

   if (E3pxm16) {                                                          //  get image size
      Iww = E3ww;
      Ihh = E3hh;                                                          //  edit in progress
   }
   else {
      Iww = Fww;                                                           //  no edit
      Ihh = Fhh;
   }

   if (Fzoom == 0) {                                                       //  scale to fit window
      wscale = 1.0 * Dww / Iww;
      hscale = 1.0 * Dhh / Ihh;
      if (wscale < hscale) Mscale = wscale;                                //  use greatest ww/hh ratio
      else  Mscale = hscale;
      if (Iww < Dww && Ihh < Dhh && ! Fblowup) Mscale = 1.0;               //  small image 1x unless Fblowup
      zoomx = zoomy = 0;
   }
   else Mscale = Fzoom;                                                    //  scale to Fzoom level
   
   if (Mscale != pscale) refresh++;                                        //  scale changed, image refresh needed

   if (Mscale > pscale) {                                                  //  zoom increased
      Iorgx += iww * 0.5 * (1.0 - pscale / Mscale);                        //  keep current image center
      Iorgy += ihh * 0.5 * (1.0 - pscale / Mscale);
   }
   pscale = Mscale;
   
   iww = Dww / Mscale;                                                     //  image space fitting in window
   if (iww > Iww) iww = Iww;
   ihh = Dhh / Mscale;
   if (ihh > Ihh) ihh = Ihh;

   if (zoomx || zoomy) {                                                   //  req. zoom center
      Iorgx = zoomx - 0.5 * iww;                                           //  corresp. image origin
      Iorgy = zoomy - 0.5 * ihh;
      ///zoomx = zoomy = 0;                                                //                            v.12.01
   }

   if ((Mxdrag || Mydrag) && Fmousemain) {                                 //  scroll via mouse drag     v.12.01
      incrx = (Mxdrag - Mxdown) * 1.3 * Iww / iww;                         //  scale
      incry = (Mydrag - Mydown) * 1.3 * Ihh / ihh;
      if (pincrx > 0 && incrx < 0) incrx = 0;                              //  stop bounce at extremes
      if (pincrx < 0 && incrx > 0) incrx = 0;
      pincrx = incrx;
      if (pincry > 0 && incry < 0) incry = 0;
      if (pincry < 0 && incry > 0) incry = 0;
      pincry = incry;
      Iorgx += incrx;                                                      //  new image origin after scroll
      Iorgy += incry;
      Mxdown = Mxdrag + incrx;                                             //  new drag origin
      Mydown = Mydrag + incry;
      Mxdrag = Mydrag = 0;
      zoomx = zoomy = 0;                                                   //  no zoom target            v.12.01
   }

   if (iww == Iww) {                                                       //  scaled image <= window width
      Iorgx = 0;                                                           //  center image in window
      Dorgx = 0.5 * (Dww - Iww * Mscale);
   }
   else Dorgx = 0;                                                         //  image > window, use entire window

   if (ihh == Ihh) {                                                       //  same for image height
      Iorgy = 0;
      Dorgy = 0.5 * (Dhh - Ihh * Mscale);
   }
   else Dorgy = 0;
   
   if (Iorgx + iww > Iww) Iorgx = Iww - iww;                               //  set limits
   if (Iorgy + ihh > Ihh) Iorgy = Ihh - ihh;
   if (Iorgx < 0) Iorgx = 0;
   if (Iorgy < 0) Iorgy = 0;
   
   if (Iorgx != piorgx || Iorgy != piorgy) {                               //  image origin changed
      refresh++;                                                           //  image refresh needed
      piorgx = Iorgx;
      piorgy = Iorgy;
   }
   
   if (refresh)                                                            //  image refresh is needed
   {
      if (E3pxm16) {                                                       //  edit in progress
         PXM_free(Dpxm16);
         Dpxm16 = PXM_copy_area(E3pxm16,Iorgx,Iorgy,iww,ihh);              //  copy E3 image area, PXM-16
         pxmtemp = PXM_convbpc(Dpxm16);                                    //  convert to PXM-8
      }
      else pxmtemp = PXM_copy_area(Fpxm8,Iorgx,Iorgy,iww,ihh);             //  no edit, copy PXM-8 image area

      dww = iww * Mscale;                                                  //  scale to window
      dhh = ihh * Mscale;
      PXM_free(Dpxm8);
      Dpxm8 = PXM_rescale(pxmtemp,dww,dhh);
      PXM_free(pxmtemp);
   }
   
   if (zoomx || zoomy) {                                                   //  zoom target                  v.12.01
      mousex = (zoomx - Iorgx) * Mscale + Dorgx;
      mousey = (zoomy - Iorgy) * Mscale + Dorgy;
      gdk_window_move_pointer(drWin->window,mousex,mousey);                //  pointer follows target
   }

   wrect.x = wrect.y = 0;                                                  //  stop flicker
   wrect.width = Dww;
   wrect.height = Dhh;
   gdk_window_begin_paint_rect(drWin->window,&wrect);

   gdk_draw_rgb_image(drWin->window, gdkgc, Dorgx, Dorgy, dww, dhh,        //  draw scaled image to window
                      NODITHER, (uint8 *) Dpxm8->bmp, dww*3);

   if (Ntoplines) paint_toplines(1);                                       //  draw line overlays
   if (Ftoparc) paint_toparc(1);                                           //  draw arc overlay
   if (Fgrid) paint_gridlines();                                           //  draw grid lines
   if (Ntoptext) paint_toptext();                                          //  draw text strings            v.11.07
   if (Ntopcircles) paint_topcircles();                                    //  draw red circles             v.11.12
   if (Fshowarea && ! Fpreview) sa_show(1);                                //  draw select area outline

   gdk_window_end_paint(drWin->window);                                    //  release all window updates

   zoomx = zoomy = 0;                                                      //  reset zoom targetv.12.01     v.12.01
   mutex_unlock(&Fpixmap_lock);                                            //  unlock pixmaps
   Wpainted++;                                                             //  notify edit function of repaint
   histogram_paint();                                                      //  update brightness histogram if exists
   return 1;
}


/**************************************************************************/

//  Cause (modified) output image to get repainted soon.
//  The entire image is repainted (that part within the window).
//  MAY be called from threads.

void mwpaint2()
{
   Frefresh++;
   Wrepaint++;                                                             //  req. repaint by periodic function
   return;
}


//  Repaint a rectangular part of the image being edited.
//  px3, py3, ww3, hh3: modified area within E3pxm16 to be repainted
//  Dpxm16: 1x copy of E3pxm16 area currently visible in main window
//  px2, py2, ww2, hh2: E3pxm16 area copied into Dpxm16
//  Dpxm8: window image, Dpxm16 scaled to window, converted to 8 bits
//  May NOT be called from threads.

void mwpaint3(int px3, int py3, int ww3, int hh3)                          //  overhauled v.10.11
{
   int         px2, py2, ww2, hh2, dx, dy;
   uint16      *pix2, *pix3;
   uint8       *pxm8area;
   
   if (! Dpxm16) zappcrash("mwpaint3() no Dpxm16");                        //  v.10.12
   
   px2 = px3 - Iorgx;                                                      //  map modified area into Dpxm16
   py2 = py3 - Iorgy;
   ww2 = ww3;
   hh2 = hh3;

   if (px2 < 0) {                                                          //  if beyond Dpxm16 edge, reduce
      px3 -= px2;
      ww2 += px2;
      px2 = 0;
   }
   if (px2 + ww2 > iww) ww2 = iww - px2;

   if (py2 < 0) {
      py3 -= py2;
      hh2 += py2;
      py2 = 0;
   }
   if (py2 + hh2 > ihh) hh2 = ihh - py2;

   if (ww2 <= 0 || hh2 <= 0) return;                                       //  completely off Dpxm16 image

   for (dy = 0; dy < hh2; dy++)                                            //  copy pixels from E3pxm16 to Dpxm16
   for (dx = 0; dx < ww2; dx++)
   {
      pix3 = PXMpix(E3pxm16,px3+dx,py3+dy);
      pix2 = PXMpix(Dpxm16,px2+dx,py2+dy);
      pix2[0] = pix3[0];
      pix2[1] = pix3[1];
      pix2[2] = pix3[2];
   }

   PXM_update(Dpxm16,Dpxm8,px2,py2,ww2,hh2);                               //  copy/rescale Dpxm16 area into Dpxm8 area

   px2 = px2 * Mscale - 1;                                                 //  Dpxm8 area impacted   v.10.12
   py2 = py2 * Mscale - 1;
   ww2 = ww2 * Mscale + 1 / Mscale + 2;
   hh2 = hh2 * Mscale + 1 / Mscale + 2;
   
   if (px2 < 0) px2 = 0;                                                   //  stay within image edge
   if (py2 < 0) py2 = 0;
   if (px2 + ww2 > dww) ww2 = dww - px2;
   if (py2 + hh2 > dhh) hh2 = dhh - py2;
   if (ww2 <= 0 || hh2 <= 0) return;                                       //  completely off Dpxm8 image

   pxm8area = (uint8 *) Dpxm8->bmp + 3 * (dww * py2 + px2);                //  origin of area to copy

   gdk_draw_rgb_image(drWin->window, gdkgc, px2 + Dorgx, py2 + Dorgy,      //  copy into window
                                  ww2, hh2, NODITHER, pxm8area, dww*3);

   if (Fshowarea && ! Fpreview) sa_show(1);                                //  draw select area outline     v.12.01
   return;
}


/**************************************************************************/

//  mouse event function - capture buttons and drag movements

void mouse_event(GtkWidget *, GdkEventButton *event, void *)
{
   void mouse_convert(int &xpos, int &ypos);

   static int     bdtime = 0, butime = 0, mbusy = 0;
   int            button, time, type, scroll;

   type = event->type;
   button = event->button;                                                 //  button, 1/2/3 = left/center/right
   time = event->time;
   Mxposn = event->x;                                                      //  mouse position in window
   Myposn = event->y;
   scroll = ((GdkEventScroll *) event)->direction;                         //  scroll wheel event
   
   mouse_convert(Mxposn,Myposn);                                           //  convert to image space
   
   if (type == GDK_SCROLL) {                                               //  scroll wheel = zoom          v.12.01
      zoomx = Mxposn;                                                      //  zoom center = mouse position (doriano)
      zoomy = Myposn;
      if (scroll == GDK_SCROLL_UP) m_zoom(null,"in");
      if (scroll == GDK_SCROLL_DOWN) m_zoom(null,"out");
      return;
   }

   if (type == GDK_MOTION_NOTIFY) {
      if (mbusy) return;                                                   //  discard excess motion events
      mbusy++;
      zmainloop();
      mbusy = 0;
   }

   if (type == GDK_BUTTON_PRESS) {                                         //  button down
      bdtime = time;                                                       //  time of button down
      Mxdown = Mxposn;                                                     //  position at button down time
      Mydown = Myposn;
      if (button) {
         Mdrag++;                                                          //  possible drag start
         Mbutton = button;
      }
      Mxdrag = Mydrag = 0;
   }

   if (type == GDK_BUTTON_RELEASE) {                                       //  button up
      Mxclick = Myclick  = 0;                                              //  reset click status
      butime = time;                                                       //  time of button up
      if (butime - bdtime < 400)                                           //  less than 0.4 secs down
         if (Mxposn == Mxdown && Myposn == Mydown) {                       //       and not moving
            if (Mbutton == 1) LMclick++;                                   //  left mouse click
            if (Mbutton == 3) RMclick++;                                   //  right mouse click
            Mxclick = Mxdown;                                              //  click = button down position
            Myclick = Mydown;
         }
         if (button == 2) {                                                //  center button click          v.12.01
            zoomx = Mxposn;                                                //  re-center at mouse (doriano)
            zoomy = Myposn;
            mwpaint2();
         }
      Mxdown = Mydown = Mxdrag = Mydrag = Mdrag = Mbutton = 0;             //  forget buttons and drag
   }
   
   if (type == GDK_MOTION_NOTIFY && Mdrag) {                               //  drag underway
      Mxdrag = Mxposn;
      Mydrag = Myposn;
   }
   
   Fmousemain = 1;                                                         //  mouse acts on main window   v.12.01
   if (Mcapture) Fmousemain = 0;
   if (mouseCBfunc) Fmousemain = 0;
   if (KBcontrolkey) Fmousemain = 1;                                       //  Ctrl key >> mouse acts on main window
   
   if (mouseCBfunc && ! Fmousemain) {                                      //  pass to handler function
      if (mbusy) return;
      mbusy++;                                                             //  stop re-entrance       v.10.8
      (* mouseCBfunc)();
      mbusy = 0;
      return;
   }
   
   if (LMclick && Fmousemain) {                                            //  left click = zoom request
      LMclick = 0;
      zoomx = Mxclick;                                                     //  zoom center = mouse
      zoomy = Myclick;
      m_zoom(null,"in");
   }

   if (RMclick && Fmousemain) {                                            //  right click
      RMclick = 0;
      if (Fzoom) {
         zoomx = zoomy = 0;                                                //  reset zoom to fit window
         m_zoom(null,"fit");
      }
      else if (edit_coll_name && curr_file)                                //  pass file to edit collection
         edit_coll_popmenu(null,curr_file);                                //  v.11.11
   }

   if ((Mxdrag || Mydrag) && Fmousemain) mwpaint1();                       //  drag = scroll

   return;
}


//  convert mouse position from window space to image space

void mouse_convert(int &xpos, int &ypos)
{
   xpos = (xpos - Dorgx) / Mscale + Iorgx + 0.5;
   ypos = (ypos - Dorgy) / Mscale + Iorgy + 0.5;

   if (xpos < 0) xpos = 0;                                                 //  if outside image put at edge
   if (ypos < 0) ypos = 0;

   if (E3pxm16) { 
      if (xpos >= E3ww) xpos = E3ww-1;
      if (ypos >= E3hh) ypos = E3hh-1;
   }
   else {
      if (xpos >= Fww) xpos = Fww-1;
      if (ypos >= Fhh) ypos = Fhh-1;
   }
   
   return;
}


/**************************************************************************/

//  keyboard event function - some toolbar buttons have KB equivalents
//  GDK key symbols: /usr/include/gtk-2.0/gdk/gdkkeysyms.h

int KBpress(GtkWidget *win, GdkEventKey *event, void *)                    //  prevent propagation of key-press
{                                                                          //    events to toolbar buttons
   KBkey = event->keyval;

   if (KBkey == GDK_Control_L || KBkey == GDK_Control_R)                   //  Ctrl key is pressed    v.11.07
       KBcontrolkey = 1;

   if (KBkey == GDK_Shift_L || KBkey == GDK_Shift_R)                       //  Shift key is pressed   v.11.07
       KBshiftkey = 1;
   
   if (KBkey == GDK_a || KBkey == GDK_A)                                   //  'A' key is pressed     v.11.11
       KB_A_key = 1;                                                       //  (undo/redo --> undo/redo ALL)

   return 1;
}

int KBrelease(GtkWidget *win, GdkEventKey *event, void *)
{
   int      angle = 0;
   PXM      *pxm;
   
   KBkey = event->keyval;
   
   if (KBcapture) return 1;                                                //  let function handle it

   if (KBkey == GDK_Control_L   ||                                         //  Ctrl key released      v.11.07
       KBkey == GDK_Control_R) KBcontrolkey = 0;
   
   if (KBkey == GDK_Shift_L   ||                                           //  Shift key released     v.11.07
       KBkey == GDK_Shift_R) KBshiftkey = 0;

   if (KBkey == GDK_a   ||                                                 //  'A' key is released    v.11.11
       KBkey == GDK_A) KB_A_key = 0;
   
   if (KBcontrolkey) {
      if (KBkey == GDK_s) m_save(0,0);                                     //  Ctrl-* shortcuts
      if (KBkey == GDK_S) m_saveas(0,0);
      if (KBkey == GDK_v) m_savevers(0,0);
      if (KBkey == GDK_V) m_savevers(0,0);
      if (KBkey == GDK_q) m_quit(0,0);  
      if (KBkey == GDK_Q) m_quit(0,0);
   }

   if (KBkey == GDK_G || KBkey == GDK_g)                                   //  toggle grid lines      v.11.11
      toggle_grid(2);

   if (Fslideshow) {                                                       //  v.11.10
      if (KBkey == GDK_Left) slideshow_next("prev");                       //  arrow keys = prev/next image
      if (KBkey == GDK_Right) slideshow_next("next");
      if (KBkey == GDK_space) slideshow_next("pause");                     //  spacebar = pause/resume
      if (KBkey == GDK_Escape) m_slideshow(0,0);                           //  escape = exit slideshow
   }
   else {
      if (KBkey == GDK_Left) m_prev(0,0);                                  //  arrow keys = prev/next image
      if (KBkey == GDK_Right) m_next(0,0);
   }

   if (KBkey == GDK_plus) m_zoom(null,"in");                               //  + key  >>  zoom in
   if (KBkey == GDK_minus) m_zoom(null,"fit");                             //  - key  >>  fit to window
   if (KBkey == GDK_equal) m_zoom(null,"in");                              //  = key: same as +
   if (KBkey == GDK_KP_Add) m_zoom(null,"in");                             //  keypad +
   if (KBkey == GDK_KP_Subtract) m_zoom(null,"fit");                       //  keypad -
   
   if (KBkey == GDK_Delete) m_trash(0,0);                                  //  delete  >>  trash

   if (KBkey == GDK_F1)                                                    //  F1  >>  user guide
      showz_userguide(zfuncs::F1_help_topic);                              //  show topic if there, or page 1

   if (! E1pxm16) {                                                        //  if no edit in progress,
      if (KBkey == GDK_R || KBkey == GDK_r) angle = 90;                    //    allow manual rotations
      if (KBkey == GDK_L || KBkey == GDK_l) angle = -90;
      if (angle) {
         mutex_lock(&Fpixmap_lock);                                        //  v.10.5
         pxm = PXM_rotate(Fpxm8,angle);
         PXM_free(Fpxm8);
         Fpxm8 = pxm;
         Fww = pxm->ww;
         Fhh = pxm->hh;
         mutex_unlock(&Fpixmap_lock);
         mwpaint2();
         m_save(0,"nowarn");                                               //  auto save uprighted image   v.10.12
      }
   }

   if (KBcontrolkey && KBkey == GDK_m) {                                   //  zmalloc() activity log
      KBzmalloclog = 1 - KBzmalloclog;                                     //  Ctrl+M = toggle on/off
      if (KBzmalloclog) zmalloc_log(999999);
      else zmalloc_log(0);
      zmalloc_report();                                                    //  v.10.8
   }
   
   if (KBkey == GDK_T || KBkey == GDK_t) m_trim(0,0);                      //  Key T = Trim function        v.10.3.1
   if (KBkey == GDK_B || KBkey == GDK_b) m_tune(0,0);                      //  Key B = brightness/color     v.10.3.1
   if (KBkey == GDK_N || KBkey == GDK_n) m_rename(0,0);                    //  Key N = rename file          v.11.11
   
   if (KBkey == GDK_z || KBkey == GDK_Z) {                                 //  Z key
      if (! KBcontrolkey) m_zoom(null,"100");                              //  if no CTRL, toggle zoom 1x
      else {                                                               //  if CTRL,                     v.11.11
         if (KBshiftkey) m_redo(0,0);                                      //  with shift: redo edit
         else m_undo(0,0);                                                 //  without shift: undo edit
      }
   }
   
   KBkey = 0;
   return 1;
}


/**************************************************************************/

//  paint a grid of horizontal and vertical lines

void paint_gridlines()                                                     //  revised  v.10.10.2
{
   int      px, py, stepx, stepy, startx, starty;

   if (! Fpxm8) return;                                                    //  no image
   if (! Fgrid) return;                                                    //  grid lines off               v.11.11
   
   stepx = gridspace[0];                                                   //  space between grid lines
   stepy = gridspace[1];

   if (gridcount[0]) stepx = dww / (1 + gridcount[0]);                     //  if line counts specified,    v.10.11
   if (gridcount[1]) stepy = dhh / ( 1 + gridcount[1]);                    //    set spacing accordingly
   
   startx = stepx * gridoffset[0] / 100;                                   //  variable offsets             v.11.11
   if (startx < 0) startx += stepx;

   starty = stepy * gridoffset[1] / 100;
   if (starty < 0) starty += stepy;
   
   gdk_gc_set_foreground(gdkgc,&white);                                    //  white lines

   if (gridon[0] && stepx)
      for (px = Dorgx+startx; px < Dorgx+dww; px += stepx)
         gdk_draw_line(drWin->window,gdkgc,px,Dorgy,px,Dorgy+dhh);

   if (gridon[1] && stepy)
      for (py = Dorgy+starty; py < Dorgy+dhh; py += stepy)
         gdk_draw_line(drWin->window,gdkgc,Dorgx,py,Dorgx+dww,py);

   gdk_gc_set_foreground(gdkgc,&black);                                    //  adjacent black lines
   fg_color = black;                                                       //  v.10.12

   if (gridon[0] && stepx) 
      for (px = Dorgx+startx+1; px < Dorgx+dww; px += stepx)
         gdk_draw_line(drWin->window,gdkgc,px,Dorgy,px,Dorgy+dhh);

   if (gridon[1] && stepy)
      for (py = Dorgy+starty+1; py < Dorgy+dhh; py += stepy)
         gdk_draw_line(drWin->window,gdkgc,Dorgx,py,Dorgx+dww,py);
   
   return;
}


/**************************************************************************/

//  refresh overlay lines on top of image
//  arg = 1:   paint lines only (because window repainted)
//        2:   erase lines and forget them
//        3:   erase old lines, paint new lines, save new in old

void paint_toplines(int arg)
{
   int      ii;

   if (arg == 2 || arg == 3)                                               //  erase old lines
      for (ii = 0; ii < Nptoplines; ii++)
         erase_line(ptoplinex1[ii],ptopliney1[ii],ptoplinex2[ii],ptopliney2[ii]);
   
   if (arg == 1 || arg == 3)                                               //  draw new lines
      for (ii = 0; ii < Ntoplines; ii++)
         draw_line(toplinex1[ii],topliney1[ii],toplinex2[ii],topliney2[ii]);

   if (arg == 2) {
      Nptoplines = Ntoplines = 0;                                          //  forget lines
      return;
   }

   for (ii = 0; ii < Ntoplines; ii++)                                      //  save for future erase
   {
      ptoplinex1[ii] = toplinex1[ii];
      ptopliney1[ii] = topliney1[ii];
      ptoplinex2[ii] = toplinex2[ii];
      ptopliney2[ii] = topliney2[ii];
   }

   Nptoplines = Ntoplines;

   return;
}


/**************************************************************************/

//  refresh overlay arc (circle/ellipse) on top of image
//  arg = 1:   paint arc only (because window repainted)
//        2:   erase arc and forget it
//        3:   erase old arc, paint new arc, save new in old

void paint_toparc(int arg)
{
   int      arcx, arcy, arcw, arch;

   if (ptoparc && (arg == 2 || arg == 3)) {                                //  erase old arc
      arcx = (ptoparcx-Iorgx) * Mscale + Dorgx + 0.5;                      //  image to window space
      arcy = (ptoparcy-Iorgy) * Mscale + Dorgy + 0.5;
      arcw = ptoparcw * Mscale;
      arch = ptoparch * Mscale;

      gdk_gc_set_function(gdkgc,GDK_INVERT);                               //  invert pixels
      gdk_draw_arc(drWin->window,gdkgc,0,arcx,arcy,arcw,arch,0,64*360);    //  draw arc
      gdk_gc_set_function(gdkgc,GDK_COPY);
   }
   
   if (Ftoparc && (arg == 1 || arg == 3)) {                                //  draw new arc
      arcx = (toparcx-Iorgx) * Mscale + Dorgx + 0.5;                       //  image to window space
      arcy = (toparcy-Iorgy) * Mscale + Dorgy + 0.5;
      arcw = toparcw * Mscale;
      arch = toparch * Mscale;

      gdk_gc_set_function(gdkgc,GDK_INVERT);                               //  invert pixels
      gdk_draw_arc(drWin->window,gdkgc,0,arcx,arcy,arcw,arch,0,64*360);    //  draw arc
      gdk_gc_set_function(gdkgc,GDK_COPY);
   }

   if (arg == 2) {
      Ftoparc = ptoparc = 0;                                               //  forget arcs
      return;
   }
   
   ptoparc = Ftoparc;                                                      //  save for future erase
   ptoparcx = toparcx;
   ptoparcy = toparcy;
   ptoparcw = toparcw;
   ptoparch = toparch;

   return;
}


/**************************************************************************/

//  draw dotted line. coordinates are in image space.

void draw_line(int ix1, int iy1, int ix2, int iy2)
{
   void draw_line_pixel(double pxm, double pym);

   double      x1, y1, x2, y2;   
   double      pxm, pym, slope;
   
   x1 = Mscale * (ix1-Iorgx);                                              //  image to window space
   y1 = Mscale * (iy1-Iorgy);
   x2 = Mscale * (ix2-Iorgx);
   y2 = Mscale * (iy2-Iorgy);
   
   if (abs(y2 - y1) > abs(x2 - x1)) {
      slope = 1.0 * (x2 - x1) / (y2 - y1);
      if (y2 > y1) {
         for (pym = y1; pym <= y2; pym++) {
            pxm = round(x1 + slope * (pym - y1));
            draw_line_pixel(pxm,pym);
         }
      }
      else {
         for (pym = y1; pym >= y2; pym--) {
            pxm = round(x1 + slope * (pym - y1));
            draw_line_pixel(pxm,pym);
         }
      }
   }
   else {
      slope = 1.0 * (y2 - y1) / (x2 - x1);
      if (x2 > x1) {
         for (pxm = x1; pxm <= x2; pxm++) {
            pym = round(y1 + slope * (pxm - x1));
            draw_line_pixel(pxm,pym);
         }
      }
      else {
         for (pxm = x1; pxm >= x2; pxm--) {
            pym = round(y1 + slope * (pxm - x1));
            draw_line_pixel(pxm,pym);
         }
      }
   }

   gdk_gc_set_foreground(gdkgc,&black);
   fg_color = black;

   return;
}

void draw_line_pixel(double px, double py)
{
   int            pxn, pyn;
   static int     flip = 0;
   
   pxn = px;
   pyn = py;
   
   if (pxn < 0 || pxn > dww-1) return;
   if (pyn < 0 || pyn > dhh-1) return;
   
   if (++flip > 4) flip = -5;                                              //  black/white line     v.10.10
   if (flip < 0) gdk_gc_set_foreground(gdkgc,&black);
   else gdk_gc_set_foreground(gdkgc,&white);
   gdk_draw_point(drWin->window, gdkgc, pxn + Dorgx, pyn + Dorgy);
   
   return;
}


//  erase line. refresh line path from Dpxm8 pixels.

void erase_line(int ix1, int iy1, int ix2, int iy2)
{
   void erase_line_pixel(double pxm, double pym);

   double      x1, y1, x2, y2;   
   double      pxm, pym, slope;
   
   if (! Dpxm8) zappcrash("Dpxm8 = 0");                                    //  v.10.3

   x1 = Mscale * (ix1-Iorgx);
   y1 = Mscale * (iy1-Iorgy);
   x2 = Mscale * (ix2-Iorgx);
   y2 = Mscale * (iy2-Iorgy);
   
   if (abs(y2 - y1) > abs(x2 - x1)) {
      slope = 1.0 * (x2 - x1) / (y2 - y1);
      if (y2 > y1) {
         for (pym = y1; pym <= y2; pym++) {
            pxm = x1 + slope * (pym - y1);
            erase_line_pixel(pxm,pym);
         }
      }
      else {
         for (pym = y1; pym >= y2; pym--) {
            pxm = x1 + slope * (pym - y1);
            erase_line_pixel(pxm,pym);
         }
      }
   }
   else {
      slope = 1.0 * (y2 - y1) / (x2 - x1);
      if (x2 > x1) {
         for (pxm = x1; pxm <= x2; pxm++) {
            pym = y1 + slope * (pxm - x1);
            erase_line_pixel(pxm,pym);
         }
      }
      else {
         for (pxm = x1; pxm >= x2; pxm--) {
            pym = y1 + slope * (pxm - x1);
            erase_line_pixel(pxm,pym);
         }
      }
   }

   return;
}

void erase_line_pixel(double px, double py)
{
   int            pxn, pyn;
   
   pxn = px;
   pyn = py;
   
   if (pxn < 0 || pxn > dww-1) return;
   if (pyn < 0 || pyn > dhh-1) return;

   uint8 *pixel = (uint8 *) Dpxm8->bmp + (pyn * dww + pxn) * 3;
   gdk_draw_rgb_image(drWin->window, gdkgc, pxn + Dorgx, pyn + Dorgy, 
                                 1, 1, NODITHER, pixel, dww * 3);         
   return;
}


//  draw one pixel using given color, R/G/B = red/green/black

void draw_pixel(int px, int py, GdkColor *color)                           //  v.10.12
{
   int            qx, qy;
   static int     pqx, pqy;
   
   qx = Mscale * (px-Iorgx) + 0.5;                                         //  image to window space
   qy = Mscale * (py-Iorgy) + 0.5;

   if (qx == pqx && qy == pqy) return;                                     //  avoid redundant points   v.9.7

   pqx = qx;
   pqy = qy;
   
   qx = qx + Dorgx;                                                        //  image origin in drawing window
   qy = qy + Dorgy;

   if (qx < 0 || qx > Dww-1) return;                                       //  bugfix v.11.03.1
   if (qy < 0 || qy > Dhh-1) return;

   if (color != &fg_color) {
      fg_color = *color;
      gdk_gc_set_foreground(gdkgc,&fg_color);
   }
   
   gdk_draw_point(drWin->window,gdkgc,qx,qy);                              //  draw pixel

   return;
}


//  draw a fat pixel using given color, R/G/B = red/green/black

void draw_fat_pixel(int px, int py, GdkColor *color)                       //  speedup    v.11.04
{
   int            qx, qy;
   static int     pqx, pqy;
   
   qx = Mscale * (px-Iorgx) + 0.5;                                         //  image to window space
   qy = Mscale * (py-Iorgy) + 0.5;

   if (qx == pqx && qy == pqy) return;                                     //  avoid redundant points   v.9.7

   pqx = qx;
   pqy = qy;
   
   qx = qx + Dorgx;                                                        //  image origin in drawing window
   qy = qy + Dorgy;
   
   if (qx < 0 || qy < 0) return;
   
   if (color != &fg_color) {
      fg_color = *color;
      gdk_gc_set_foreground(gdkgc,&fg_color);
   }
   
   if (qx < Dww-1 && qy < Dhh-1 && Mscale > 1.0)                     
      gdk_draw_rectangle(drWin->window,gdkgc,0,qx,qy,2,2);                 //  draw fat pixel 2x2

   else if (qx < Dww && qy < Dhh)
      gdk_draw_point(drWin->window,gdkgc,qx,qy);                           //  on edge, draw pixel

   return;
}


/**************************************************************************/

//  maintain a set of text strings written whenever the window is painted

//  add a new text string to the list
//  multiple text strings can be added with the same ID
//  px and py are image coordinates

void add_toptext(int ID, int px, int py, cchar *text, cchar *font)         //  new v.11.07
{
   if (Ntoptext == maxtoptext) {
      printf("maxtoptext exceeded \n");
      return;
   }
   
   int ii = Ntoptext++;
   toptext[ii].ID = ID;
   toptext[ii].px = px;
   toptext[ii].py = py;
   toptext[ii].text = text;
   toptext[ii].font = font;
   
   return;
}


//  delete text strings having the given ID from the list

void erase_toptext(int ID)                                                 //  new v.11.07
{
   int      ii, jj;

   for (ii = jj = 0; ii < Ntoptext; ii++)
   {
      if (toptext[ii].ID == ID) continue;
      else toptext[jj++] = toptext[ii];
   }
   
   Ntoptext = jj;

   return;
}


//  paint current text strings on window whenever it is repainted
//  called from mwpaint()

void paint_toptext()                                                       //  new v.11.07
{
   int      ii, px, py;

   for (ii = 0; ii < Ntoptext; ii++) {
      px = toptext[ii].px;
      py = toptext[ii].py;
      px = Mscale * (px - Iorgx) + Dorgx;                                  //  image to window space
      py = Mscale * (py - Iorgy) + Dorgy;
      paint_text(px,py,toptext[ii].text,toptext[ii].font);
   }

   return;
}


//  paint text on window, black on white background
//  px and py are window coordinates

void paint_text(int px, int py, cchar *text, cchar *font)                  //  new v.11.07
{
   static PangoFontDescription   *pangofont = null;
   static PangoLayout            *pangolayout = null;
   static char                   priorfont[40] = "";
   
   if (strNeq(font,priorfont)) {                                           //  change font
      strncpy0(priorfont,font,40);
      if (pangofont) pango_font_description_free(pangofont);
      if (pangolayout) g_object_unref(pangolayout);
      pangofont = pango_font_description_from_string(font);
      pangolayout = gtk_widget_create_pango_layout(drWin,0);
      pango_layout_set_font_description(pangolayout,pangofont);
   }
   
   pango_layout_set_text(pangolayout,text,-1);
   gdk_draw_layout_with_colors(drWin->window,gdkgc,px,py,pangolayout,&black,&white);

   return;
}


/**************************************************************************/

//  maintain a set of circles written whenever the window is painted

void add_topcircle(int px, int py, int radius, int color)
{
   if (Ntopcircles == maxtopcircles) {
      printf("maxtopcircles exceeded \n");
      return;
   }
   
   int ii = Ntopcircles++;
   topcircles[ii].px = px;
   topcircles[ii].py = py;
   topcircles[ii].radius = radius;
   topcircles[ii].color = color;
   
   return;
}


void erase_topcircles()
{
   Ntopcircles = 0;
   return;
}


void paint_topcircles()
{
   int         ii, px, py, rad, color;
   
   for (ii = 0; ii < Ntopcircles; ii++)
   {
      px = (topcircles[ii].px - Iorgx) * Mscale + Dorgx + 0.5;             //  image to window space
      py = (topcircles[ii].py - Iorgy) * Mscale + Dorgy + 0.5;
      rad = topcircles[ii].radius;                                         //  radius is window space
      color = topcircles[ii].color;                                        //  1/2/3 = white/black/red

      px -= rad ;                                                          //  NW corner of enclosing square
      py -= rad ;
      rad = 2 * rad;                                                       //  size of enclosing square
      
      if (color == 1) gdk_gc_set_foreground(gdkgc,&white);                 //  set foreground color
      else if (color == 2) gdk_gc_set_foreground(gdkgc,&black);
      else gdk_gc_set_foreground(gdkgc,&red);

      gdk_draw_arc(drWin->window,gdkgc,0,px,py,rad,rad,0,64*360);          //  draw 360 deg. arc
   }
   
   return;
}


/**************************************************************************

   spline curve setup and edit functions

   Support multiple frames with curves in parallel                         //  v.11.01
   (edit curve(s) and image mask curve)

   Usage:
   Add frame widget to dialog or zdialog.
   Set up drawing in frame: 
      sd = curve_init(frame, callback_func)
   Initialize no. of curves in frame (1-10): 
      sd->Nspc = n
   Initialize vert/horz flag for curve spc: 
      sd->vert[spc] = hv
   Initialize anchor points for curve spc: 
      sd->nap[spc], sd->apx[spc][xx], sd->apy[spc][yy]
   Generate data for curve spc:  
      splcurve_generate(sd,spc)
   Curves will now be shown and edited inside the frame when window is realized.
   The callback_func(spc) will be called when curve spc is edited.
   Change curve in program: 
      set anchor points, call splcurve_generate(sd,spc)
   Get y-value (0-1) for curve spc and given x-value (0-1):
      yval = splcurve_yval(sd,spc,xval)
   If faster access to curve is needed (no interpolation):
      kk = 1000 * xval;
      if (kk > 999) kk = 999;
      yval = sd->yval[spc][kk];

***/

//  initialize for spline curve editing
//  initial anchor points are pre-loaded into spldat before window is realized

spldat * splcurve_init(GtkWidget *frame, void func(int spc))
{
   int      cc = sizeof(spldat);                                           //  allocate spc curve data area
   spldat * sd = (spldat *) zmalloc(cc,"curvedat");
   memset(sd,0,cc);

   sd->drawarea = gtk_drawing_area_new();                                  //  drawing area for curves
   gtk_container_add(GTK_CONTAINER(frame),sd->drawarea);
   sd->spcfunc = func;                                                     //  user callback function

   gtk_widget_add_events(sd->drawarea,GDK_BUTTON_PRESS_MASK);              //  connect mouse events to drawing area
   gtk_widget_add_events(sd->drawarea,GDK_BUTTON_RELEASE_MASK);
   gtk_widget_add_events(sd->drawarea,GDK_BUTTON1_MOTION_MASK); 
   G_SIGNAL(sd->drawarea,"motion-notify-event",splcurve_adjust,sd);
   G_SIGNAL(sd->drawarea,"button-press-event",splcurve_adjust,sd);
   G_SIGNAL(sd->drawarea,"expose-event",splcurve_draw,sd);
   
   return sd;
}


//  modify anchor points in curve using mouse

int splcurve_adjust(void *, GdkEventButton *event, spldat *sd)
{
   int            ww, hh, kk;
   int            mx, my, button, evtype;
   static int     spc, ap, mbusy = 0, Fdrag = 0;                           //  drag continuation logic   v.9.7
   int            minspc, minap;
   double         mxval, myval, cxval, cyval;
   double         dist2, mindist2 = 0;
   
   mx = event->x;                                                          //  mouse position in drawing area
   my = event->y;
   evtype = event->type;
   button = event->button;

   if (evtype == GDK_MOTION_NOTIFY) {
      if (mbusy) return 0;                                                 //  discard excess motion events    v.11.01
      mbusy++;
      zmainloop();
      mbusy = 0;
   }

   if (evtype == GDK_BUTTON_RELEASE) {
      Fdrag = 0;
      return 0;
   }
   
   ww = sd->drawarea->allocation.width;                                    //  drawing area size
   hh = sd->drawarea->allocation.height;
   
   if (mx < 0) mx = 0;                                                     //  limit edge excursions
   if (mx > ww) mx = ww;
   if (my < 0) my = 0;
   if (my > hh) my = hh;
   
   if (evtype == GDK_BUTTON_PRESS) Fdrag = 0;                              //  left or right click
   
   if (Fdrag)                                                              //  continuation of drag
   {
      if (sd->vert[spc]) {
         mxval = 1.0 * my / hh;                                            //  mouse position in curve space
         myval = 1.0 * mx / ww;
      }
      else {
         mxval = 1.0 * mx / ww;
         myval = 1.0 * (hh - my) / hh;
      }

      if (ap < sd->nap[spc]-1 && sd->apx[spc][ap+1] - mxval < 0.05)        //  disallow < 0.05 from next
            return 0;
      if (ap > 0 && mxval - sd->apx[spc][ap-1] < 0.05) return 0;           //    or prior anchor point 
   }
   
   else                                                                    //  mouse click or new drag begin
   {
      minspc = minap = -1;                                                 //  find closest curve/anchor point
      mindist2 = 999999;

      for (spc = 0; spc < sd->Nspc; spc++)                                 //  loop curves
      {
         if (sd->vert[spc]) {
            mxval = 1.0 * my / hh;                                         //  mouse position in curve space
            myval = 1.0 * mx / ww;
         }
         else {
            mxval = 1.0 * mx / ww;
            myval = 1.0 * (hh - my) / hh;
         }

         for (ap = 0; ap < sd->nap[spc]; ap++)                             //  loop anchor points
         {
            cxval = sd->apx[spc][ap];
            cyval = sd->apy[spc][ap];
            dist2 = (mxval-cxval)*(mxval-cxval) 
                  + (myval-cyval)*(myval-cyval);
            if (dist2 < mindist2) {
               mindist2 = dist2;                                           //  remember closest anchor point
               minspc = spc;
               minap = ap;
            }
         }
      }

      if (minspc < 0) return 0;                                            //  impossible
      spc = minspc;
      ap = minap;
   }
   
   if (evtype == GDK_BUTTON_PRESS && button == 3)                          //  right click, remove anchor point
   {
      if (sqrt(mindist2) > 0.05) return 0;                                 //  not close enough
      if (sd->nap[spc] < 3) return 0;                                      //  < 2 anchor points would remain
      sd->nap[spc]--;                                                      //  decr. before loop      v.11.11
      for (kk = ap; kk < sd->nap[spc]; kk++) {
         if (! kk) printf("meaningless reference %d",kk);                  //  stop gcc optimization bug    v.11.11  /////
         sd->apx[spc][kk] = sd->apx[spc][kk+1];
         sd->apy[spc][kk] = sd->apy[spc][kk+1];
      }
      splcurve_generate(sd,spc);                                           //  regenerate data for modified curve
      splcurve_draw(0,0,sd);                                               //  regen and redraw all curves
      sd->spcfunc(spc);                                                    //  call user function
      return 0;
   }

   if (! Fdrag)                                                            //  new drag or left click
   {
      if (sd->vert[spc]) {
         mxval = 1.0 * my / hh;                                            //  mouse position in curve space
         myval = 1.0 * mx / ww;
      }
      else {
         mxval = 1.0 * mx / ww;
         myval = 1.0 * (hh - my) / hh;
      }

      if (sqrt(mindist2) < 0.05)                                           //  existing point close enough,
      {                                                                    //    move this anchor point to mouse
         if (ap < sd->nap[spc]-1 && sd->apx[spc][ap+1] - mxval < 0.05)
               return 0;                                                   //  disallow < 0.05 from next
         if (ap > 0 && mxval - sd->apx[spc][ap-1] < 0.05) return 0;        //    or prior anchor point 
      }
   
      else                                                                 //  none close, add new anchor point
      {
         minspc = -1;                                                      //  find closest curve to mouse
         mindist2 = 999999;

         for (spc = 0; spc < sd->Nspc; spc++)                              //  loop curves
         {
            if (sd->vert[spc]) {
               mxval = 1.0 * my / hh;                                      //  mouse position in curve space
               myval = 1.0 * mx / ww;
            }
            else {
               mxval = 1.0 * mx / ww;
               myval = 1.0 * (hh - my) / hh;
            }

            cyval = splcurve_yval(sd,spc,mxval);
            dist2 = fabs(myval - cyval);
            if (dist2 < mindist2) {
               mindist2 = dist2;                                           //  remember closest curve
               minspc = spc;
            }
         }

         if (minspc < 0) return 0;                                         //  impossible
         if (mindist2 > 0.05) return 0;                                    //  not close enough to any curve
         spc = minspc;

         if (sd->nap[spc] > 49) {
            zmessageACK(mWin,ZTX("Exceed 50 anchor points"));
            return 0;
         }

         if (sd->vert[spc]) {
            mxval = 1.0 * my / hh;                                         //  mouse position in curve space
            myval = 1.0 * mx / ww;
         }
         else {
            mxval = 1.0 * mx / ww;
            myval = 1.0 * (hh - my) / hh;
         }

         for (ap = 0; ap < sd->nap[spc]; ap++)                             //  find anchor point with next higher x
            if (mxval <= sd->apx[spc][ap]) break;                          //    (ap may come out 0 or nap)

         if (ap < sd->nap[spc] && sd->apx[spc][ap] - mxval < 0.05)         //  disallow < 0.05 from next
               return 0;                                                   //    or prior anchor point
         if (ap > 0 && mxval - sd->apx[spc][ap-1] < 0.05) return 0;

         for (kk = sd->nap[spc]; kk > ap; kk--) {                          //  make hole for new point
            sd->apx[spc][kk] = sd->apx[spc][kk-1];
            sd->apy[spc][kk] = sd->apy[spc][kk-1];
         }

         sd->nap[spc]++;                                                   //  up point count
      }
   }

   sd->apx[spc][ap] = mxval;                                               //  new or moved anchor point
   sd->apy[spc][ap] = myval;                                               //    at mouse position

   splcurve_generate(sd,spc);                                              //  regenerate data for modified curve
   splcurve_draw(0,0,sd);                                                  //  regen and redraw all curves
   sd->spcfunc(spc);                                                       //  call user function
   
   if (evtype == GDK_MOTION_NOTIFY) Fdrag = 1;                             //  remember drag is underway
   return 0;
}


//  for expose event or when a curve is changed
//  draw all curves based on current anchor points

int splcurve_draw(void *, void *, spldat *sd)
{
   int         ww, hh, px, py, qx, qy, spc, ap;
   double      xval, yval;
   
   ww = sd->drawarea->allocation.width;                                    //  drawing area size
   hh = sd->drawarea->allocation.height;
   if (ww < 50 || hh < 50) return 0;

   gdk_window_clear(sd->drawarea->window);                                 //  clear window
   
   gdk_gc_set_foreground(gdkgc,&dgray);

   for (int ii = 0; ii < sd->Nscale; ii++)                                 //  draw y-scale lines if any    v.11.07
   {
      px = ww * sd->xscale[0][ii] + 0.5;                                   //  any line, not only horizontal   v.11.10
      py = hh - hh * sd->yscale[0][ii] + 0.5;
      qx = ww * sd->xscale[1][ii] + 0.5;
      qy = hh - hh * sd->yscale[1][ii] + 0.5;
      gdk_draw_line(sd->drawarea->window,gdkgc,px,py,qx,qy);
   }

   gdk_gc_set_foreground(gdkgc,&black);
   fg_color = black;

   for (spc = 0; spc < sd->Nspc; spc++)
   {
      if (sd->vert[spc])                                                   //  vert. curve
      {
         for (py = 0; py < hh; py++)                                       //  generate all points for curve
         {
            xval = 1.0 * py / hh;                                          //  remove anchor point limits   v.10.9
            yval = splcurve_yval(sd,spc,xval);
            px = ww * yval + 0.49;                                         //  "almost" round - erratic floating point
            gdk_draw_point(sd->drawarea->window,gdkgc,px,py);              //    causes "bumps" in a flat curve
         }
         
         for (ap = 0; ap < sd->nap[spc]; ap++)                             //  draw boxes at anchor points
         {
            xval = sd->apx[spc][ap];
            yval = sd->apy[spc][ap];
            px = ww * yval;
            py = hh * xval;
            for (qx = -2; qx < 3; qx++)
            for (qy = -2; qy < 3; qy++) {
               if (px+qx < 0 || px+qx >= ww) continue;
               if (py+qy < 0 || py+qy >= hh) continue;
               gdk_draw_point(sd->drawarea->window,gdkgc,px+qx,py+qy);
            }
         }
      }
      else                                                                 //  horz. curve
      {
         for (px = 0; px < ww; px++)                                       //  generate all points for curve
         {
            xval = 1.0 * px / ww;                                          //  remove anchor point limits   v.10.9
            yval = splcurve_yval(sd,spc,xval);
            py = hh - hh * yval + 0.49;                                    //  almost round - erratic FP in Intel CPUs
            gdk_draw_point(sd->drawarea->window,gdkgc,px,py);              //    causes "bumps" in a flat curve
         }
         
         for (ap = 0; ap < sd->nap[spc]; ap++)                             //  draw boxes at anchor points
         {
            xval = sd->apx[spc][ap];
            yval = sd->apy[spc][ap];
            px = ww * xval;
            py = hh - hh * yval;
            for (qx = -2; qx < 3; qx++)
            for (qy = -2; qy < 3; qy++) {
               if (px+qx < 0 || px+qx >= ww) continue;
               if (py+qy < 0 || py+qy >= hh) continue;
               gdk_draw_point(sd->drawarea->window,gdkgc,px+qx,py+qy);
            }
         }
      }
   }

   return 0;
}


//  generate all curve data points when anchor points are modified

int splcurve_generate(spldat *sd, int spc)
{
   int      kk, kklo, kkhi;
   double   xval, yvalx;

   spline1(sd->nap[spc],sd->apx[spc],sd->apy[spc]);                        //  compute curve fitting anchor points

   kklo = 1000 * sd->apx[spc][0] - 30;                                     //  xval range = anchor point range
   if (kklo < 0) kklo = 0;                                                 //    + 0.03 extra below/above      v.9.5
   kkhi = 1000 * sd->apx[spc][sd->nap[spc]-1] + 30;
   if (kkhi > 1000) kkhi = 1000;

   for (kk = 0; kk < 1000; kk++)                                           //  generate all points for curve
   {
      xval = 0.001 * kk;                                                   //  remove anchor point limits   v.10.9
      yvalx = spline2(xval);
      if (yvalx < 0) yvalx = 0;                                            //  yval < 0 not allowed, > 1 OK    v.9.5
      sd->yval[spc][kk] = yvalx;
   }

   return 0;
}


//  Retrieve curve data using interpolation of saved table of values

double splcurve_yval(spldat *sd, int spc, double xval)
{
   int      ii;
   double   x1, x2, y1, y2, y3;
   
   if (xval <= 0) return sd->yval[spc][0];
   if (xval >= 0.999) return sd->yval[spc][999];
   
   x2 = 1000.0 * xval;
   ii = x2;
   x1 = ii;
   y1 = sd->yval[spc][ii];
   y2 = sd->yval[spc][ii+1];
   y3 = y1 + (y2 - y1) * (x2 - x1);
   return y3;
}


//  load curve data from a file
//  returns 0 if fail (invalid file data), sd not modified
//  returns 1 if succcess, sd is initialized from file data

int splcurve_load(spldat *sd)                                              //  v.11.02
{
   char        *pfile;
   int         nn, ii, jj;
   FILE        *fid = 0;
   int         Nspc, vert[10], nap[10];
   double      apx[10][50], apy[10][50];

   zfuncs::F1_help_topic = "curve_edit";

   pfile = zgetfile1(ZTX("load curve from a file"),"open",saved_curves_dirk);
   if (! pfile) return 0;

   fid = fopen(pfile,"r");
   zfree(pfile);
   if (! fid) goto fail;
   
   nn = fscanf(fid,"%d ",&Nspc);                                           //  no. of curves
   if (nn != 1) goto fail;
   if (Nspc < 1 || Nspc > 10) goto fail;
   if (Nspc != sd->Nspc) goto fail2;

   for (ii = 0; ii < Nspc; ii++)                                           //  loop each curve
   {
      nn = fscanf(fid,"%d  %d ",&vert[ii],&nap[ii]);                       //  vertical flag, no. anchor points
      if (nn != 2) goto fail;
      if (vert[ii] < 0 || vert[ii] > 1) goto fail;
      if (nap[ii] < 2 || nap[ii] > 50) goto fail;

      for (jj = 0; jj < nap[ii]; jj++)                                     //  anchor point values 
      {
         nn = fscanf(fid,"%lf/%lf  ",&apx[ii][jj],&apy[ii][jj]);
         if (nn != 2) goto fail;
         if (apx[ii][jj] < 0 || apx[ii][jj] > 1) goto fail;
         if (apy[ii][jj] < 0 || apy[ii][jj] > 1) goto fail;
      }
   }
   
   fclose(fid);
   
   sd->Nspc = Nspc;                                                        //  copy curve data to caller's arg

   for (ii = 0; ii < Nspc; ii++)
   {
      sd->vert[ii] = vert[ii];
      sd->nap[ii] = nap[ii];
      
      for (jj = 0; jj < nap[ii]; jj++)
      {
         sd->apx[ii][jj] = apx[ii][jj];
         sd->apy[ii][jj] = apy[ii][jj];
      }
   }

   for (ii = 0; ii < Nspc; ii++)                                           //  generate curve data from anchor points
      splcurve_generate(sd,ii);
   
   if (sd->drawarea) splcurve_draw(0,0,sd);                                //  regen and redraw all curves

   return 1;
   
fail:
   if (fid) fclose(fid);
   zmessageACK(mWin,ZTX("curve file is invalid"));
   return 0;

fail2:
   if (fid) fclose(fid);
   zmessageACK(mWin,ZTX("curve file has different no. of curves"));
   return 0;
}


//  save curve data to a file

int splcurve_save(spldat *sd)                                              //  v.11.02
{
   char        *pfile, *pp;
   int         ii, jj;
   FILE        *fid;

   zfuncs::F1_help_topic = "curve_edit";

   pp = zgetfile1(ZTX("save curve to a file"),"save",saved_curves_dirk);
   if (! pp) return 0;

   pfile = strdupz(pp,8);
   zfree(pp);

   pp = strrchr(pfile,'/');                                                //  force .curve extension
   if (pp) pp = strrchr(pp,'.');
   if (pp) strcpy(pp,".curve");
   else strcat(pfile,".curve");

   fid = fopen(pfile,"w");
   zfree(pfile);
   if (! fid) return 0;
   
   fprintf(fid,"%d \n",sd->Nspc);                                          //  no. of curves

   for (ii = 0; ii < sd->Nspc; ii++)                                       //  loop each curve
   {
      fprintf(fid,"%d  %d \n",sd->vert[ii],sd->nap[ii]);                   //  vertical flag, no. anchor points
      for (jj = 0; jj < sd->nap[ii]; jj++)                                 //  anchor point values 
         fprintf(fid,"%.4f/%.4f  ",sd->apx[ii][jj],sd->apy[ii][jj]);
      fprintf(fid,"\n");
   }
   
   fclose(fid);
   return 0;
}


/**************************************************************************
      zdialog mouse capture and release
***************************************************************************/

void takeMouse(zdialog *zd, CBfunc func, GdkCursor *cursor)                //  capture mouse for dialog     v.11.03
{
   freeMouse();
   mouseCBfunc = func;
   Mcapture = 1;
   gdk_window_set_cursor(drWin->window,cursor);                            //  v.11.03
   return;
}


void freeMouse()                                                           //  free mouse for main window   v.10.12
{
   mouseCBfunc = 0;
   Mcapture = 0;
   paint_toparc(2);                                                        //  remove mouse circle          v.11.04
   gdk_window_set_cursor(drWin->window,0);                                 //  set normal cursor            v.11.03
   return;
}


/**************************************************************************

   edit transaction and thread support functions

   edit transaction management
      edit_setup()                     start new edit - copy E3 > E1
      edit_cancel()                    cancel edit - E1 > E3, delete E1
      edit_done()                      commit edit - add to undo stack
      edit_undo()                      undo edit - E1 > E3
      edit_redo()                      redo edit - run thread again
      edit_fullsize()                  convert preview to full-size pixmaps

   main level thread management
      start_thread(func,arg)           start thread running
      signal_thread()                  signal thread that work is pending
      wait_thread_idle()               wait for pending work complete
      wrapup_thread(command)           wait for exit or command thread exit

   thread function
      thread_idle_loop()               wait for pending work, exit if commanded
      thread_exit()                    exit thread unconditionally

   thread_status (thread ownership
      0     no thread is running
      1     thread is running and idle (no work)
      2     thread is working
      0     thread has exited

   thread_command (main program ownership)
      0     idle, no work pending
      8     exit when pending work is done
      9     exit now, unconditionally

   thread_pend       work requested counter
   thread_done       work done counter
   thread_hiwater    high water mark
   edit_action       done/cancel/undo/redo in progress

***************************************************************************/

int      thread_command = 0, thread_status = 0;
int      thread_pend = 0, thread_done = 0, thread_hiwater = 0;
int      edit_action = 0;


/**************************************************************************

  Setup for a new edit transaction
  Create E1 (edit input) and E3 (edit output) pixmaps from
  previous edit output (Fpxm16) or image file (new Fpxm16).

  Fprev      0     edit full-size image
             1     edit preview image unless select area exists

  Farea      0     select_area is invalid and will be deleted (e.g. rotate)
             1     select_area not used but remains valid (e.g. red-eye)
             2     select_area can be used and remains valid (e.g. flatten)

***************************************************************************/

int edit_setup(editfunc &EF)                                               //  support parallel edits          v.11.07
{
   int      yn;
   PXM      *pxmtemp = 0;

   if (! curr_file) return 0;                                              //  no image file
   if (is_syncbusy()) return 0;                                            //  must wait for file sync         v.11.11
   
   if (! Fpxm16) {                                                         //  no 16-bit image from prior edit
      pxmtemp = f_load(curr_file,16);                                      //  check that file can be loaded
      if (! pxmtemp) {
         printf("f_load (16) failed: %s \n",curr_file);
         return 0;
      }
   }

   if (CEF) {                                                              //  prior edit function active      v.11.07
      if (&EF == CEF) return 0;                                            //  2nd instance of one function
      if (EF.Fpara && CEF->Fpara)                                          //  check if parallel edits are OK
         edit_suspend();                                                   //  suspend prior edit
      else {
         zmessageACK(mWin,ZTX("cannot parallel edit"));
         return 0;
      }
   }
   else {                                                                  //  if this is 1st active edit
      if (! menulock(1)) return 0;                                         //    test menu lock is allowed
      menulock(0);                                                         //      but don't lock yet          v.11.07
   }

   if (! Fexiftool && ! Fexifwarned) {
      zmessageACK(mWin,ZTX("exiftool is not installed \n"                  //  warn if starting to edit
                           "edited images will lose EXIF data"));          //    and exiftool is missing
      Fexifwarned = 1;
   }

   if (Pundo > maxedits-2) {                                               //  undo capacity reached
      zmessageACK(mWin,ZTX("Too many edits, please save image"));
      return 0;
   }

   if (EF.Farea == 0 && sa_stat) {                                         //  select area will be lost, warn user
      yn = zmessageYN(mWin,ZTX("Select area cannot be kept.\n"
                               "Continue?"));
      if (! yn) return 0;
      sa_unselect();                                                       //  unselect area
      zdialog_free(zdsela);
   }
   
   if (EF.Farea == 2 && sa_stat && ! Factivearea) {                        //  select area exists and can be used,
      yn = zmessageYN(mWin,ZTX("Select area not active.\n"                 //    but not active, ask user
                               "Continue?"));
      if (! yn) return 0;
   }

   if (! menulock(1)) return 0;                                            //  can now commit, lock menu       v.11.07

   Fpreview = 0;                                                           //  use preview image if supported
   if (EF.Fprev && ! (EF.Farea == 2 && Factivearea)) {                     //    and select area will not be used
      Fpreview = 1;                                                        //  use preview image (smaller)
      sa_show(0);                                                          //  hide area if present
      curr_image_time = get_seconds();                                     //  mark time of file load          v.11.07
      if (Fzoom) {
         Fzoom = 0;                                                        //  bugfix, mwpaint() req.          v.11.01
         mwpaint1();
      }
   }

   mutex_lock(&Fpixmap_lock);                                              //  lock pixmaps

   if (! Fpxm16) Fpxm16 = pxmtemp;                                         //  create Fpxm16 if not already

   PXM_fixblue(Fpxm16);                                                    //  blue=0 >> blue=2 for vpixel()   v.11.07

   if (Fpreview)                                                           //  edit pixmaps are window-size
      E1pxm16 = PXM_rescale(Fpxm16,dww,dhh);                               //  E1pxm16 = Fpxm16 scaled to window
   else E1pxm16 = PXM_copy(Fpxm16);                                        //  edit pixmaps are full-size
   E3pxm16 = PXM_copy(E1pxm16);                                            //  E1 >> E3
   
   E1ww = E3ww = E1pxm16->ww;                                              //  E1 and E3 pixmap dimensions
   E1hh = E3hh = E1pxm16->hh;

   mutex_unlock(&Fpixmap_lock);
   
   if (Pundo == 0) save_undo("initial");                                   //  initial image >> undo stack

   CEF = &EF;                                                              //  set current edit function
   CEF->Fmod = 0;                                                          //  image not modified yet

   thread_command = thread_status = 0;                                     //  no thread running
   thread_pend = thread_done = thread_hiwater = 0;                         //  no work pending or done
   if (EF.threadfunc) start_thread(EF.threadfunc,0);                       //  start edit thread
   
   Frefresh++;
   mwpaint1();                                                             //  mwpaint1() not mwpaint2()
   return 1;
}


/**************************************************************************/

//  suspend a parallel edit function so it can be resumed
//  edit dialog and curve edit are left active
//  logically the equivalent of edit_done()

void edit_suspend()
{
   wait_thread_idle();                                                     //  wait for thread to finish
   
   if (Fpreview && CEF->Fmod) {                                            //  preview image was edited
      Fzoom = 0;
      edit_fullsize();                                                     //  update full image
   }

   wrapup_thread(8);                                                       //  tell thread to finish and exit

   mutex_lock(&Fpixmap_lock);                                              //  lock pixmaps

   if (CEF->Fmod) {                                                        //  image was modified
      Fmodified++;                                                         //  overall mod counter
      PXM_free(Fpxm16);
      Fpxm16 = E3pxm16;                                                    //  E3 >> Fpxm16           v.11.07
      E3pxm16 = 0;
      PXM_free(Fpxm8);
      Fpxm8 = PXM_convbpc(Fpxm16);                                         //  Fpxm16 >> Fpxm8
      Fww = Fpxm8->ww;
      Fhh = Fpxm8->hh;
      Pundo++;                                                             //  save next undo state
      Pumax = Pundo;
      save_undo(CEF->funcname);
   }

   PXM_free(E1pxm16);                                                      //  free edit pixmaps
   PXM_free(E3pxm16);
   PXM_free(ERpxm16);                                                      //  free redo copy
   E1ww = E3ww = ERww = 0;

   mutex_unlock(&Fpixmap_lock);
   
   CEF = 0;                                                                //  no current edit func
   menulock(0);
   mwpaint2();
   return;
}


/**************************************************************************/

//  process edit cancel

void edit_cancel(editfunc &EF)
{
   if (edit_action) return;
   edit_action++;                                                          //  stop reentry
   
   if (&EF == CEF)                                                         //  current edit is canceled     v.11.07
      wrapup_thread(9);                                                    //  tell thread to quit, wait
   
   if (EF.mousefunc == mouseCBfunc)                                        //  if my mouse, free mouse
      freeMouse();
   if (EF.zd) zdialog_free(EF.zd);                                         //  kill dialog
   if (EF.curves) zfree(EF.curves);                                        //  free curves data
   EF.zd = 0;
   EF.curves = 0;

   if (&EF != CEF) {                                                       //  inactive edit canceled       v.11.07
      edit_action = 0;
      return;
   }
   
   mutex_lock(&Fpixmap_lock);
   PXM_free(E1pxm16);                                                      //  free edit pixmaps E1, E3
   PXM_free(E3pxm16);
   PXM_free(ERpxm16);                                                      //  free redo copy    v.10.3
   E1ww = E3ww = ERww = 0;
   mutex_unlock(&Fpixmap_lock);
   
   if (Fpreview) curr_image_time = get_seconds();                          //  mark time of file load       v.11.07
   Fzoom = 0;                                                              //  v.11.07
   CEF = 0;                                                                //  no current edit func
   Fpreview = 0;                                                           //  no preview mode
   menulock(0);                                                            //  unlock menu
   mwpaint2();                                                             //  refresh window
   edit_action = 0;
   return;
}   


/**************************************************************************/

//  process edit dialog [done]  
//  E3pxm16 >> Fpxm16 >> Fpxm8

void edit_done(editfunc &EF)
{
   if (edit_action) return;
   edit_action++;                                                          //  stop reentry

   if (&EF == CEF) {                                                       //  current edit is done      v.11.07
      wait_thread_idle();                                                  //  wait for thread to finish
      if (Fpreview && CEF->Fmod) {                                         //  preview image was edited
         Fzoom = 0;
         edit_fullsize();                                                  //  update full image
      }
      wrapup_thread(8);                                                    //  tell thread to finish and exit
   }

   if (EF.mousefunc == mouseCBfunc)                                        //  if my mouse, free mouse
      freeMouse();
   if (EF.zd) zdialog_free(EF.zd);                                         //  kill dialog
   if (EF.curves) zfree(EF.curves);                                        //  free curves data
   EF.zd = 0;
   EF.curves = 0;

   if (&EF != CEF) {                                                       //  inactive edit is done     v.11.07
      edit_action = 0;
      return;
   }

   mutex_lock(&Fpixmap_lock);

   if (CEF->Fmod) {                                                        //  image was modified
      Fmodified++;                                                         //  overall mod counter
      PXM_free(Fpxm16);
      Fpxm16 = E3pxm16;                                                    //  E3 >> Fpxm16              v.11.07
      E3pxm16 = 0;
      PXM_free(Fpxm8);
      Fpxm8 = PXM_convbpc(Fpxm16);                                         //  Fpxm16 >> Fpxm8
      Fww = Fpxm8->ww;
      Fhh = Fpxm8->hh;
      Pundo++;
      Pumax = Pundo;
      save_undo(CEF->funcname);                                            //  save next undo state
   }

   PXM_free(E1pxm16);                                                      //  free edit pixmaps
   PXM_free(E3pxm16);
   PXM_free(ERpxm16);                                                      //  free redo copy    v.10.3
   E1ww = E3ww = ERww = 0;

   mutex_unlock(&Fpixmap_lock);
   
   CEF = 0;                                                                //  no current edit func
   Fpreview = 0;                                                           //  no preview mode
   menulock(0);                                                            //  unlock menu
   mwpaint2();                                                             //  update window
   edit_action = 0;
   return;
}


/**************************************************************************/

//  Setup for edit dialog event or curve edit.
//  Call this function before performing any image edit
//  (switch current edit function to caller's edit function).

void edit_takeover(editfunc &EF)                                           //  v.11.07
{
   static int  busy = 0;
   
   if (busy++) return;                                                     //  KB key bounce
   if (CEF && &EF != CEF) {                                                //  edit function changed
      edit_setup(EF);                                                      //  close old and setup new edit
      if (EF.zd) zdialog_send_event(EF.zd,"reset");                        //  reset dialog controls           v.11.08
      freeMouse();                                                         //  free mouse from old dialog      v.11.08
   }
   busy = 0;
   return;
}


/**************************************************************************/

//  edit undo, redo, reset functions
//  these apply within an active edit function

void edit_undo()
{
   if (thread_status == 2) return;                                         //  thread busy
   if (! CEF->Fmod) return;                                                //  not modified
   if (edit_action) return;
   edit_action++;                                                          //  stop reentry
   
   mutex_lock(&Fpixmap_lock);
   PXM_free(ERpxm16);                                                      //  E3 >> redo copy
   ERpxm16 = E3pxm16;
   ERww = E3ww;
   ERhh = E3hh;
   E3pxm16 = PXM_copy(E1pxm16);                                            //  E1 >> E3
   E3ww = E1ww;
   E3hh = E1hh;
   mutex_unlock(&Fpixmap_lock);

   CEF->Fmod = 0;                                                          //  reset image modified status
   mwpaint2();                                                             //  refresh window
   edit_action = 0;
   return;
}


void edit_redo()
{
   if (thread_status == 2) return;                                         //  thread busy
   if (! ERpxm16) return;                                                  //  no prior undo
   if (edit_action) return;
   edit_action++;                                                          //  stop reentry

   mutex_lock(&Fpixmap_lock);
   PXM_free(E3pxm16);                                                      //  redo copy >> E3
   E3pxm16 = ERpxm16;
   E3ww = ERww;
   E3hh = ERhh;
   ERpxm16 = 0;
   mutex_unlock(&Fpixmap_lock);

   CEF->Fmod = 1;                                                          //  image modified
   Fmodified++;                                                            //  overall mod counter
   mwpaint2();
   edit_action = 0;
   return;
}


void edit_reset()
{
   if (thread_status == 2) return;                                         //  thread busy
   if (! CEF->Fmod) return;                                                //  not modified
   if (edit_action) return;
   edit_action++;                                                          //  stop reentry

   mutex_lock(&Fpixmap_lock);
   PXM_free(ERpxm16);                                                      //  delete redo copy
   PXM_free(E3pxm16);
   E3pxm16 = PXM_copy(E1pxm16);                                            //  E1 >> E3
   E3ww = E1ww;
   E3hh = E1hh;
   mutex_unlock(&Fpixmap_lock);

   CEF->Fmod = 0;                                                          //  reset image modified status
   mwpaint2();                                                             //  refresh window
   edit_action = 0;
   return;
}


void edit_zapredo()
{
   PXM_free(ERpxm16);                                                      //  no redo copy
   return;
}


/**************************************************************************/

//  Convert from preview mode (window-size pixmaps) to full-size pixmaps.
//  Can only be used when edit function is in an edit thread with idle_loop
//  This function is called directly from some edit functions

void edit_fullsize()
{
   mutex_lock(&Fpixmap_lock);
   PXM_free(E1pxm16);                                                      //  free preview pixmaps
   PXM_free(E3pxm16);
   E1pxm16 = PXM_copy(Fpxm16);                                             //  make full-size pixmaps
   E3pxm16 = PXM_copy(Fpxm16);
   E1ww = E3ww = Fww;
   E1hh = E3hh = Fhh;
   mutex_unlock(&Fpixmap_lock);

   Fpreview = 0;
   Fzoom = 0;                                                              //  v.11.07
   curr_image_time = get_seconds();                                        //  mark time of file load    v.11.07

   if (! CEF->Fmod) return;                                                //  no change
   signal_thread();                                                        //  signal thread, repeat edit
   wait_thread_idle();
   return;
}


/**************************************************************************
      undo / redo toolbar buttons
***************************************************************************/

//  [undo] menu function - reinstate previous edit in undo/redo stack

void m_undo(GtkWidget *, cchar *)
{
   if (CEF) {                                                              //  undo active edit
      edit_undo();                                                         //  v.11.08
      return;
   }

   if (Pundo == 0) return;                                                 //  undo past edit
   if (! menulock(1)) return;
   Pundo--;
   if (KB_A_key) Pundo = 0;                                                //  if KB 'A' key, undo all      v.11.11
   load_undo();
   Fmodified = Pundo;                                                      //  v.11.08
   menulock(0);
   return;
}


//  [redo] menu function - reinstate next edit in undo/redo stack

void m_redo(GtkWidget *, cchar *)
{
   if (CEF) {                                                              //  redo active edit
      edit_redo();                                                         //  v.11.08
      return;
   }

   if (Pundo == Pumax) return;
   if (! menulock(1)) return;
   Pundo++;
   if (KB_A_key) Pundo = Pumax;                                            //  if KB 'A' key, redo all      v.11.11
   load_undo();
   Fmodified = Pundo;                                                      //  v.11.08
   menulock(0);
   return;
}


//  undo all edits of the current image
//  (discard modifications)

void undo_all()                                                            //  v.11.04
{
   if (! menulock(1)) return;
   Pundo = 0;                                                              //  v.11.08
   load_undo();
   Fmodified = 0;                                                          //  v.11.07
   menulock(0);
   return;
}


//  Save Fpxm16 to undo/redo file stack
//  stack position = Pundo

void save_undo(cchar *funcname)
{
   char     *pp, buff[24];
   int      fid, cc, cc2;
   
   pp = strstr(undo_files,"_undo_");
   if (! pp) zappcrash("undo/redo stack corrupted 1");
   snprintf(pp+6,3,"%02d",Pundo);
   
   fid = open(undo_files,O_WRONLY|O_CREAT|O_TRUNC,0640);
   if (! fid) zappcrash("undo/redo stack corrupted 2");

   snprintf(buff,24," %05d %05d fotoxx ",Fww,Fhh);
   cc = write(fid,buff,20);
   if (cc != 20) zappcrash("undo/redo stack corrupted 3");
   
   cc = Fww * Fhh * 6;
   cc2 = write(fid,Fpxm16->bmp,cc);
   if (cc2 != cc) zappcrash("undo/redo stack corrupted 4");

   close(fid);

   pvlist_replace(editlog,Pundo,funcname);                                 //  save log of edits done
   return;
}


//  Load Fpxm16 from undo/redo file stack
//  stack position = Pundo

void load_undo()
{
   char     *pp, buff[24], fotoxx[8];
   int      fid, ww, hh, cc, cc2;

   pp = strstr(undo_files,"_undo_");
   if (! pp) zappcrash("undo/redo stack corrupted 1");
   snprintf(pp+6,3,"%02d",Pundo);
   
   fid = open(undo_files,O_RDONLY);
   if (! fid) zappcrash("undo/redo stack corrupted 2");
   
   *fotoxx = 0;
   cc = read(fid,buff,20);
   sscanf(buff," %d %d %8s ",&ww, &hh, fotoxx);
   if (! strEqu(fotoxx,"fotoxx")) zappcrash("undo/redo stack corrupted 4");

   mutex_lock(&Fpixmap_lock);

   PXM_free(Fpxm16);
   Fpxm16 = PXM_make(ww,hh,16);
   cc = ww * hh * 6;
   cc2 = read(fid,Fpxm16->bmp,cc);
   if (cc2 != cc) zappcrash("undo/redo stack corrupted 5");
   close(fid);
   
   PXM_free(Fpxm8);
   Fpxm8 = PXM_convbpc(Fpxm16);

   Fww = ww;
   Fhh = hh;
   
   if (sa_fww != Fww || sa_fhh != Fhh)                                     //  disable area if image size changes
      sa_disable();                                                        //  bugfix  v.11.08

   mutex_unlock(&Fpixmap_lock);
   mwpaint2();
   return;
}


/**************************************************************************/

//  ask user if modified image should be kept or discarded
//  returns: 0 = mods discarded,  1 = do not discard mods

int mod_keep()
{
   int      keep = 0, choice = 0;
   cchar    *title = ZTX("Discard edits?");
   cchar    *message = ZTX("This action will discard current edits.\n"     //  more clarity  v.11.10
                           "Continue to discard edits.\n"
                           "Go Back to keep edits.");
   cchar    *continu = ZTX("Continue");
   cchar    *goback = ZTX("Go Back");

   if (Fsaved < Pundo) keep = 1;                                           //  curr. edits not saved
   if (keep == 0) return 0;                                                //  no mods
   choice = zdialog_choose(title,mWin,message,continu,goback,null);
   if (choice == 2) return 1;                                              //  keep mods
   undo_all();                                                             //  discard mods
   return 0;
}


/**************************************************************************/

//  menu lock/unlock - some functions must not run concurrently
//  returns 1 if success, else 0

int menulock(int lock)
{
   if (! lock && ! Fmenulock) zappcrash("menu lock error");
   if (lock && Fmenulock) {
      zmessageACK(mWin,ZTX("prior function still active"));                //  v.11.06
      return 0;
   }
   if (lock) Fmenulock++;
   else Fmenulock--;
   return 1;
}


/**************************************************************************/

//  check if Fsyncbusy is active, return 0 if not,
//  otherwise message user and return 1

int is_syncbusy()
{
   if (! Fsyncbusy) return 0;
   threadmessage = "Synchronize files is running. Edits are blocked. \n"
                   "You can still navigate and view images normally.";
   return 1;
}


/**************************************************************************/

//  start thread that does the edit work

void start_thread(threadfunc func, void *arg)
{
   thread_status = 1;                                                      //  thread is running
   thread_command = thread_pend = thread_done = thread_hiwater = 0;        //  nothing pending
   start_detached_thread(func,arg);
   return;
}


//  signal thread that work is pending

void signal_thread()
{
   edit_zapredo();                                                         //  reset redo copy
   if (thread_status > 0) thread_pend++;
   return;
}


//  wait for edit thread to complete pending work and become idle

void wait_thread_idle()
{
   while (thread_status && thread_pend > thread_done)
   {
      zmainloop();
      zsleep(0.01);
   }
   
   return;
}


//  wait for thread exit or command thread exit
//  command = 0    wait for normal completion
//            8    finish pending work and exit
//            9    quit, exit now

void wrapup_thread(int command)
{
   thread_command = command;                                               //  tell thread to quit or finish

   while (thread_status > 0)                                               //  wait for thread to finish
   {                                                                       //    pending work and exit
      zmainloop();
      zsleep(0.01);
   }

   return;
}


//  called only from edit threads
//  idle loop - wait for work request or exit command

void thread_idle_loop()
{
   if (thread_status == 2) Ffuncbusy--;                                    //  v.11.05
   thread_status = 1;                                                      //  status = idle
   thread_done = thread_hiwater;                                           //  work done = high-water mark

   while (true)
   {
      if (thread_command == 9) thread_exit();                              //  quit now command
      if (thread_command == 8)                                             //  finish work and exit
         if (thread_pend <= thread_done) thread_exit();
      if (thread_pend > thread_done) break;                                //  wait for work request
      zsleep(0.01);
   }
   
   thread_hiwater = thread_pend;                                           //  set high-water mark
   thread_status = 2;                                                      //  thread is working
   Ffuncbusy++;                                                            //  v.11.05
   return;                                                                 //  perform edit
}


//  exit thread unconditionally, called only from edit threads

void thread_exit()
{
   if (thread_status == 2) Ffuncbusy--;                                    //  v.11.05
   thread_pend = thread_done = thread_hiwater = 0;
   thread_status = 0;
   pthread_exit(0);                                                        //  "return" cannot be used here
}


/**************************************************************************/

//  edit support functions for working threads (per processor core)

void start_wthread(threadfunc func, void *arg)                             //  start and increment busy count
{
   zadd_locked(wthreads_busy,+1);
   zadd_locked(Ffuncbusy,+1);                                              //  v.11.01
   start_detached_thread(func,arg);
   return;
}


void exit_wthread()                                                        //  decrement busy count and exit
{
   zadd_locked(Ffuncbusy,-1);                                              //  v.11.01
   zadd_locked(wthreads_busy,-1);
   pthread_exit(0);                                                        //  "return" cannot be used here  v.9.4
}


void wait_wthreads()                                                       //  wait for all working threads done
{                                                                          //  caller can be main() or a thread
   while (wthreads_busy) {
      zsleep(0.01);
      zmainloop();                                                         //  v.11.11.1
   }
   return;
}


/**************************************************************************
      other support functions
***************************************************************************/

//  help menu function

void m_help(GtkWidget *, cchar *menu)
{
   if (strEqu(menu,ZTX("About"))) 
      zmessageACK(mWin,"%s \n%s \n%s \n%s \n%s \n%s",
        fversion,flicense,fhomepage,fcontact,fcredits,ftranslators);
      
   if (strEqu(menu,ZTX("User Guide"))) 
      showz_userguide();
   
   if (strEqu(menu,ZTX("User Guide Changes")))
      showz_userguide("changes");

   if (strEqu(menu,ZTX("Help")))                                           //  toolbar button
      showz_userguide(zfuncs::F1_help_topic);                              //  show topic if there, or page 1

   if (strEqu(menu,"README"))
      showz_readme();

   if (strEqu(menu,ZTX("Edit Functions Summary")))                         //  v.11.11
      showz_doctext("edit-menus");

   if (strEqu(menu,ZTX("Change Log")))
      showz_changelog();

   if (strEqu(menu,ZTX("Translations")))
      showz_translations();
      
   if (strEqu(menu,ZTX("Home Page")))
      showz_html(fhomepage);

   return;
}


/**************************************************************************/

//  table for loading and saving adjustable parameters between sessions

typedef struct {
   char     name[20];
   char     type[12];
   int      count;
   void     *location;
}  param;

#define Nparms 33

param paramTab[Nparms] = {
//     name                 type        count      location
{     "fotoxx version",    "char",        1,       &pversion               },
{     "last session",      "char",        1,       &last_session           },
{     "first time",        "int",         1,       &Ffirsttime             },
{     "new files found",   "int",         1,       &newfiles               },
{     "window geometry",   "int",         4,       &mwgeom                 },
{     "toolbar style",     "char",        1,       &tbar_style             },
{     "warn overwrite",    "int",         1,       &Fwarnoverwrite         },
{     "zoom ratio",        "char",        1,       &zoomratio              },
{     "startup display",   "char",        1,       &startdisplay           },
{     "start image file",  "char",        1,       &startfile              },
{     "start directory",   "char",        1,       &startdirk              },
{     "lens mm and bow",   "double",      2,       &lens_settings          },
{     "trim size",         "int",         2,       &trimsize               },
{     "trim buttons",      "char",        6,       &trimbuttons            },
{     "trim ratios",       "char",        6,       &trimratios             },
{     "edit resize",       "int",         2,       &editresize             },
{     "batch resize",      "int",         2,       &batchresize            },
{     "e-mail resize",     "int",         2,       &emailsize              },
{     "annotate font",     "char",        1,       &annotate_font          },
{     "annotate text",     "char",        1,       &annotate_text          },
{     "annotate color",    "char",        3,       &annotate_color         },
{     "annotate trans",    "int",         3,       &annotate_trans         },
{     "annotate outline",  "int",         1,       &annotate_outline       },
{     "annotate angle",    "double",      1,       &annotate_angle         },
{     "grid spacing",      "int",         2,       &gridspace              },
{     "grid counts",       "int",         2,       &gridcount              },
{     "rotate grid",       "int",         8,       &rotate_grid            },
{     "unbend grid",       "int",         8,       &unbend_grid            },
{     "warp-curved grid",  "int",         8,       &warpC_grid             },
{     "warp-linear grid",  "int",         8,       &warpL_grid             },
{     "warp-affine grid",  "int",         8,       &warpF_grid             },
{     "slideshow trans",   "int",         SSNF,    &ss_funcs               },
{     "slideshow music",   "char",        1,       &ss_musicfile           }   };


//  save parameters to file /home/<user>/.fotoxx/parameters

void save_params()                                                         //  new v.10.9
{
   FILE        *fid;
   char        buff[1050], text[1000];                                     //  limit for character data cc
   char        *pp, *name, *type;
   int         count;
   void        *location;
   char        **charloc;
   int         *intloc;
   double      *doubleloc;
   time_t      currtime;
   
   pversion = strdupz(fversion);                                           //  this fotoxx version       v.11.10
   
   currtime = time(0);
   last_session = ctime(&currtime) + 4;                                    //  fotoxx session exit time  v.11.11
   if (last_session[4] == ' ') last_session[4] = '0';                      //  format  mmm dd hh:mm:ss yyyy
   pp = strchr(last_session,'\n');
   if (pp) *pp = 0;

   snprintf(buff,199,"%s/parameters",get_zuserdir());                      //  open output file
   fid = fopen(buff,"w");
   if (! fid) return;
   
   for (int ii = 0; ii < Nparms; ii++)                                     //  write table of state data
   {
      name = paramTab[ii].name;
      type = paramTab[ii].type;
      count = paramTab[ii].count;
      location = paramTab[ii].location;
      charloc = (char **) location;
      intloc = (int *) location;
      doubleloc = (double *) location;

      fprintf(fid,"%-20s  %-8s  %02d  ",name,type,count);                  //  write parm name, type, count

      for (int kk = 0; kk < count; kk++)                                   //  write "value" "value" ...
      {
         if (strEqu(type,"char")) {
            if (! *charloc) printf("bad param data %s %d \n",name,kk);     //  bugfix v.10.11.1
            if (! *charloc) break;
            repl_1str(*charloc++,text,"\n","\\n");                         //  replace newlines with "\n"   v.10.11
            fprintf(fid,"  \"%s\"",text);
         }
         if (strEqu(type,"int"))
            fprintf(fid,"  \"%d\"",*intloc++);

         if (strEqu(type,"double"))
            fprintf(fid,"  \"%.2f\"",*doubleloc++);
      }

      fprintf(fid,"\n");                                                   //  write EOR
   }
   
   fprintf(fid,"\n");
   fclose(fid);                                                            //  close file

   fid = fopen(recentfiles_file,"w");                                      //  recent files file
   if (! fid) return;
   
   for (int ii = 0; ii < Nrecentfiles; ii++)                               //  save list of recent files
      if (recentfiles[ii])
         fprintf(fid,"%s \n",recentfiles[ii]);
   
   fclose(fid);

   snprintf(buff,199,"%s/plugins",get_zuserdir());                         //  open file for plugins     v.11.03
   fid = fopen(buff,"w");
   if (! fid) return;
   
   for (int ii = 0; ii < Nplugins; ii++)                                   //  save plugins
      if (plugins[ii])
         fprintf(fid,"%s \n",plugins[ii]);
   
   fclose(fid);

   return;
}


//  load parameters from file /home/<user>/.fotoxx/parameters

void load_params()                                                         //  new v.10.9
{
   FILE        *fid;
   int         ii, err, cc, pcount, Idata;
   double      Rdata;
   char        buff[1000], text[1000], *pp;
   char        name[20], type[12], count[8], data[1000];
   void        *location;
   char        **charloc;
   int         *intloc;
   double      *doubleloc;
   struct stat statb;
   
   for (ii = 0; ii < Nparms; ii++)                                         //  set string parameters to "undefined"
   {                                                                       //  v.10.11
      if (strNeq(paramTab[ii].type,"char")) continue;
      charloc = (char **) paramTab[ii].location;
      pcount = paramTab[ii].count;
      for (int jj = 0; jj < pcount; jj++)
         *charloc++ = strdupz("undefined",20);
   }
   
   snprintf(buff,499,"%s/parameters",get_zuserdir());                      //  open parameters file
   fid = fopen(buff,"r");
   if (! fid) return;                                                      //  none, defaults are used

   while (true)                                                            //  read parameters
   {
      pp = fgets_trim(buff,999,fid,1);
      if (! pp) break;
      if (*pp == '#') continue;                                            //  comment          v.10.10
      if (strlen(pp) < 40) continue;                                       //  rubbish          v.10.10

      err = 0;

      strncpy0(name,pp,20);                                                //  parm name
      strTrim2(name);
      
      strncpy0(type,pp+22,8);                                              //  parm type
      strTrim2(type);
      
      strncpy0(count,pp+32,4);                                             //  parm count
      strTrim2(count);
      err = convSI(count,pcount);
      
      strncpy0(data,pp+38,1000);                                           //  parm value(s)
      strTrim2(data);
      
      for (ii = 0; ii < Nparms; ii++)                                      //  match file record to param table
      {
         if (strNeq(name,paramTab[ii].name)) continue;                     //  parm name
         if (strNeq(type,paramTab[ii].type)) continue;                     //  parm type
         if (paramTab[ii].count != pcount) continue;                       //  parm count

         location = paramTab[ii].location;
         charloc = (char **) location;
         intloc = (int *) location;
         doubleloc = (double *) location;
         
         for (int kk = 1; kk <= pcount; kk++)
         {
            pp = (char *) strField(data,' ',kk);
            if (! pp) break;

            if (strEqu(type,"char")) {
               repl_1str(pp,text,"\\n","\n");                              //  replace "\n" with real newlines  v.10.11
               *charloc++ = strdupz(text,0,"parameters");                  //  v.11.04
            }

            if (strEqu(type,"int")) {
               err = convSI(pp,Idata);
               if (err) continue;
               *intloc++ = Idata;
            }

            if (strEqu(type,"double")) {
               err = convSD(pp,Rdata);
               if (err) continue;
               *doubleloc++ = Rdata;
            }
         }
      }
   }

   fclose(fid);
   
   if (strNeq(pversion,fversion))                                          //  fotoxx version change        v.11.10
      printf("new Fotoxx version %s %s \n",pversion,fversion);

   topdirk = 0;

   fid = fopen(topdirk_file,"r");                                          //  set top directory from       v.11.11
   if (fid) {                                                              //    from top directory file
      pp = fgets_trim(buff,499,fid,1);
      if (pp && *pp == '/') 
         topdirk = strdupz(buff,0,"top_dirk");
      fclose(fid);
   }
   
   if (topdirk) {                                                          //  v.11.12.1
      err = stat(topdirk,&statb);
      if (! err && S_ISDIR(statb.st_mode)) {
         cc = strlen(topdirk);                                             //  remove trailing '/' if present
         if (topdirk[cc-1] == '/') topdirk[cc-1] = 0;                      //  v.11.12
      }
      else topdirk = 0;                                                    //  v.11.12.1
   }

   for (ii = 0; ii < Nrecentfiles; ii++)                                   //  recent image file list = empty
      recentfiles[ii] = 0;

   fid = fopen(recentfiles_file,"r");                                      //  open recent files file
   if (fid) {
      for (ii = 0; ii < Nrecentfiles; ii++)                                //  read list of recent files
      {
         pp = fgets_trim(buff,499,fid,1);
         if (! pp) break;
         if (*pp == '/') 
            recentfiles[ii] = strdupz(buff,0,"recentfile");
         else ii--;                                                        //  v.11.11
      }

      fclose(fid);
   }

   for (ii = 0; ii < maxplugins; ii++)                                     //  plugins list = empty      v.11.03
      plugins[ii] = 0;
   
   plugins[0] = strdupz("Gimp = gimp");                                    //  if empty, default Gimp plugin
   Nplugins = 1;

   snprintf(buff,499,"%s/plugins",get_zuserdir());                         //  open plugins file         v.11.03
   fid = fopen(buff,"r");
   if (fid)
   {   
      for (ii = 0; ii < maxplugins; ii++)                                  //  read list of plugins
      {
         pp = fgets_trim(buff,499,fid,1);
         if (! pp) break;
         plugins[ii] = strdupz(buff,0,"plugins");
      }

      fclose(fid);
      Nplugins = ii;
   }

   return;
}


/**************************************************************************/

//  Compute the mean brightness of all pixel neighborhoods,                //  new v.9.6
//  using a Guassian or a flat distribution for the weightings. 
//  If a select area is active, only inside pixels are calculated.
//  The flat method is 10-100x faster.

int         brhood_radius;
double      brhood_kernel[200][200];                                       //  up to radius = 99
float       *brhood_brightness = 0;                                        //  neighborhood brightness per pixel
char        brhood_method;                                                 //  g = gaussian, f = flat distribution


void brhood_calc(int radius, char method)
{
   void * brhood_wthread(void *arg);

   int      rad, rad2, dx, dy, cc, ii;
   double   kern;
   
   brhood_radius = radius;
   brhood_method = method;

   if (brhood_method == 'g')                                               //  compute Gaussian kernel
   {                                                                       //  (not currently used   v.11.02)
      rad = brhood_radius;
      rad2 = rad * rad;

      for (dy = -rad; dy <= rad; dy++)
      for (dx = -rad; dx <= rad; dx++)
      {
         if (dx*dx + dy*dy <= rad2)                                        //  cells within radius
            kern = exp( - (dx*dx + dy*dy) / rad2);
         else kern = 0;                                                    //  outside radius
         brhood_kernel[dy+rad][dx+rad] = kern;
      }
   }

   if (brhood_brightness) zfree(brhood_brightness);                        //  allocate memory for pixel map
   cc = E1ww * E1hh * sizeof(float);
   brhood_brightness = (float *) zmalloc(cc,"brhood");
   memset(brhood_brightness,0,cc);

   if (Factivearea) SB_goal = sa_Npixel;                                   //  set up progress tracking
   else SB_goal = E1ww * E1hh;
   SB_done = 0;                                                            //  v.11.06

   for (ii = 0; ii < Nwt; ii++)                                            //  start worker threads
      start_wthread(brhood_wthread,&wtnx[ii]);
   wait_wthreads();                                                        //  wait for completion

   SB_goal = 0;
   return;
}


//  worker thread function

void * brhood_wthread(void *arg)
{
   int      index = *((int *) arg);
   int      rad = brhood_radius;
   int      ii, px, py, qx, qy, Fstart;
   double   kern, bsum, bsamp, bmean;
   uint16   *pixel;

   if (brhood_method == 'g')                                               //  use round gaussian distribution
   {
      for (py = index; py < E1hh; py += Nwt)
      for (px = 0; px < E1ww; px++)
      {
         if (Factivearea && sa_mode != 7) {                                //  select area, not whole image    v.11.02
            ii = py * E1ww + px;                                           //    use only inside pixels
            if (! sa_pixmap[ii]) continue;
         }
         
         bsum = bsamp = 0;

         for (qy = py-rad; qy <= py+rad; qy++)                             //  computed weighted sum of brightness
         for (qx = px-rad; qx <= px+rad; qx++)                             //    for pixels in neighborhood
         {
            if (qy < 0 || qy > E1hh-1) continue;
            if (qx < 0 || qx > E1ww-1) continue;
            kern = brhood_kernel[qy+rad-py][qx+rad-px];
            pixel = PXMpix(E1pxm16,qx,qy);
            bsum += pixbright(pixel) * kern;                               //  sum brightness * weight
            bsamp += kern;                                                 //  sum weights
         }

         bmean = bsum / bsamp;                                             //  mean brightness
         ii = py * E1ww + px;
         brhood_brightness[ii] = bmean;                                    //  pixel value

         SB_done++;                                                        //  track progress                  v.11.06
         if (drandz() < 0.0001) zsleep(0.001);                             //  trigger sorry kernel scheduler
      }
   }

   if (brhood_method == 'f')                                               //  use square flat distribution
   {
      Fstart = 1;
      bsum = bsamp = 0;

      for (py = index; py < E1hh; py += Nwt)
      for (px = 0; px < E1ww; px++)
      {
         if (Factivearea && sa_mode != 7) {                                //  select area, not whole image    v.11.02
            ii = py * E1ww + px;                                           //     compute only inside pixels
            if (! sa_pixmap[ii]) {
               Fstart = 1;
               continue;
            }
         }
         
         if (px == 0) Fstart = 1;
         
         if (Fstart) 
         {
            Fstart = 0;
            bsum = bsamp = 0;

            for (qy = py-rad; qy <= py+rad; qy++)                          //  add up all columns
            for (qx = px-rad; qx <= px+rad; qx++)
            {
               if (qy < 0 || qy > E1hh-1) continue;
               if (qx < 0 || qx > E1ww-1) continue;
               pixel = PXMpix(E1pxm16,qx,qy);
               bsum += pixbright(pixel);
               bsamp += 1;
            }
         }
         else
         {
            qx = px-rad-1;                                                 //  subtract first-1 column
            if (qx >= 0) {
               for (qy = py-rad; qy <= py+rad; qy++)
               {
                  if (qy < 0 || qy > E1hh-1) continue;
                  pixel = PXMpix(E1pxm16,qx,qy);
                  bsum -= pixbright(pixel);
                  bsamp -= 1;
               }
            }
            qx = px+rad;                                                   //  add last column
            if (qx < E1ww) {
               for (qy = py-rad; qy <= py+rad; qy++)
               {
                  if (qy < 0 || qy > E1hh-1) continue;
                  pixel = PXMpix(E1pxm16,qx,qy);
                  bsum += pixbright(pixel);
                  bsamp += 1;
               }
            }
         }

         bmean = bsum / bsamp;                                             //  mean brightness
         ii = py * E1ww + px;
         brhood_brightness[ii] = bmean;

         SB_done++;                                                        //  track progress                  v.11.06
         if (drandz() < 0.0001) zsleep(0.001);                             //  trigger sorry kernel scheduler
      }
   }
         
   exit_wthread();
   return 0;                                                               //  not executed, avoid gcc warning
}


//  get the neighborhood brightness for given pixel

float get_brhood(int px, int py)
{
   int ii = py * E1ww + px;
   return brhood_brightness[ii];
}


/**************************************************************************/

//  free all resources associated with the current image file

void free_resources(int fkeepundo)
{
   char        *pp;
   int         err;

   mutex_lock(&Fpixmap_lock);                                              //  lock pixmaps
   
   if (! fkeepundo) {
      strcpy(command,"rm -f ");                                            //  delete all undo files
      strcat(command,undo_files);
      pp = strstr(command,"_undo_");                                       //  /home/user/.fotoxx/pppppp_undo_*
      strcpy(pp + 6,"*");
      err = system(command);
      if (err) printf("error: %s \n",wstrerror(err));
      Fmodified = Pundo = Pumax = Fsaved = 0;                              //  reset undo/redo stack
   }

   if (Fshutdown) {                                                        //  stop here if shutdown mode
      mutex_unlock(&Fpixmap_lock);
      return;
   }
   
   Ntoplines = Nptoplines;                                                 //  no image overlay lines
   freeMouse();                                                            //  free mouse                v.11.04
   
   if (curr_file) {
      sa_unselect();                                                       //  unselect select area
      zdialog_free(zdsela);                                                //  kill dialogs if active
      zfree(curr_file);                                                    //  free image file
      curr_file = 0;
      SB_goal = 0;                                                         //  v.9.2
      *SB_text = 0;                                                        //  v.10.7
   }

   if (brhood_brightness) zfree(brhood_brightness);                        //  free brightness map       v.9.6
   brhood_brightness = 0;

   PXM_free(Fpxm8);
   PXM_free(Dpxm8);
   PXM_free(Fpxm16);
   PXM_free(E1pxm16);
   PXM_free(E3pxm16);
   PXM_free(ERpxm16);                                                      //  v.10.3

   Fww = E1ww = E3ww = Dww = 0;                                            //  make unusable (crash)

   mutex_unlock(&Fpixmap_lock);
   return;
}


//  Get a virtual pixel at location (px,py) (real) in an PXM-16 pixmap.
//  Get the overlapping real pixels and build a composite.

int vpixel(PXM *pxm, double px, double py, uint16 *vpix)
{
   int            ww, hh, px0, py0;
   uint16         *ppix, *pix0, *pix1, *pix2, *pix3;
   double         f0, f1, f2, f3;
   double         red, green, blue;
   
   ww = pxm->ww;
   hh = pxm->hh;
   ppix = (uint16 *) pxm->bmp;

   px0 = px;                                                               //  pixel containing (px,py)
   py0 = py;

   if (px0 < 1 || py0 < 1) return 0;                                       //  void edge pixels
   if (px0 > ww-3 || py0 > hh-3) return 0;
   
   pix0 = ppix + (py0 * ww + px0) * 3;                                     //  4 pixels based at (px0,py0)
   pix1 = pix0 + ww * 3;
   pix2 = pix0 + 3;
   pix3 = pix0 + ww * 3 + 3;

   f0 = (px0+1 - px) * (py0+1 - py);                                       //  overlap of (px,py)
   f1 = (px0+1 - px) * (py - py0);                                         //   in each of the 4 pixels
   f2 = (px - px0) * (py0+1 - py);
   f3 = (px - px0) * (py - py0);
   
   red =   f0 * pix0[0] + f1 * pix1[0] + f2 * pix2[0] + f3 * pix3[0];      //  sum the weighted inputs
   green = f0 * pix0[1] + f1 * pix1[1] + f2 * pix2[1] + f3 * pix3[1];
   blue =  f0 * pix0[2] + f1 * pix1[2] + f2 * pix2[2] + f3 * pix3[2];
   
   vpix[0] = red;
   vpix[1] = green;
   vpix[2] = blue;
   
   if (blue < 1) {                                                         //  v.10.7 
      if (blue < 0.9) return 0;                                            //  near edge or voided area
      vpix[2] = 1;                                                         //  avoid 0.999 to integer 0
   }

   return 1;
}


//  compare two doubles for significant difference
//  return:  0  difference not significant
//          +1  d1 > d2
//          -1  d1 < d2

int sigdiff(double d1, double d2, double signf)
{
   double diff = fabs(d1-d2);
   if (diff == 0.0) return 0;
   diff = diff / (fabs(d1) + fabs(d2));
   if (diff < signf) return 0;
   if (d1 > d2) return 1;
   else return -1;
}


/**************************************************************************
   file read and write utilities
   PXM pixmap <--> file on disk
***************************************************************************/

//  Load an image file into an PXM pixmap of 8 or 16 bits/color
//  Also sets the following file scope variables:
//    f_load_type = "jpg" "tif" "png" or "other"
//    f_load_bpc = disk file bits per color = 1/8/16
//    f_load_size = disk file size

PXM * f_load(cchar *filespec, int bpc)                                     //  use pixbuf or tiff library   v.9.8
{
   int            err;
   cchar          *pext;
   PXM            *pxm1, *pxm2;
   struct stat    fstat;

   if (bpc != 8 && bpc != 16)                                              //  requested bpc must be 8 or 16
      zappcrash("image_load bpc: %d",bpc);

   err = stat(filespec,&fstat);
   if (err) return 0;                                                      //  file not found
   if (! S_ISREG(fstat.st_mode)) return 0;                                 //  not a regular file
   if (image_file_type(filespec) != 2) return 0;                           //  not a supported image type
   f_load_size = fstat.st_size;                                            //  disk file bytes
   
   pext = strrchr(filespec,'/');
   if (! pext) pext = filespec;

   if (pext > filespec+12 && strnEqu(pext-12,"/.thumbnails/",13)) {        //  refuse thumbnail files    v.10.0
      zmessageACK(mWin,ZTX("cannot open thumbnail file"));
      return 0;
   }

   pext = strrchr(pext,'.');
   if (! pext) pext = "";
   
   if (strstr(".jpg .jpeg .JPG .JPEG",pext)) strcpy(f_load_type,"jpg");
   else if (strstr(".tif .tiff .TIF .TIFF",pext)) strcpy(f_load_type,"tif");
   else if (strstr(".png .PNG",pext)) strcpy(f_load_type,"png");
   else strcpy(f_load_type,"other");
   
   if (strEqu(f_load_type,"tif"))                                          //  use tiff lib to read tiff file
      pxm1 = TIFFread(filespec);
   else  pxm1 = PXBread(filespec);                                         //  use pixbuf lib for others
   if (! pxm1) return 0;                                                   //  both set f_load_bpc = file bpc
   
   if (pxm1->bpc != bpc) {
      pxm2 = PXM_convbpc(pxm1);                                            //  convert to requested bpc
      PXM_free(pxm1);                                                      //    8 <--> 16
      pxm1 = pxm2;
   }
                                                                           //  auto upright image removed      v.10.12
   return pxm1;
}


/**************************************************************************/

//  save current image to specified disk file (same or new).
//  set f_save_type, f_save_bpc, f_save_size
//  update search index file
//  return 0 if OK, else +N

int f_save(char *outfile, cchar *type, int bpc)                            //  use pixbuf or tiff library   v.9.8
{
   PXM            *pxm16;
   cchar          *exifkey[2] = { exif_orientation_key, iptc_editlog_key };
   cchar          *exifdata[2];
   char           **ppv, funcslist[1000];
   char           *pp, *tempfile, *pext;
   int            nkeys, err = 1, cc1, cc2;
   struct stat    fstat;
   
   if ((bpc != 8 && bpc != 16) || ! strstr("jpg tif png",type))            //  check args
      zappcrash("f_save: %s %d",type,bpc);
   
   Ffuncbusy++;                                                            //  v.11.01

   pext = strrchr(outfile,'/');                                            //  force compatible file extension
   if (pext) pext = strrchr(pext,'.');                                     //    if not already
   if (! pext) pext = outfile + strlen(outfile);
   if (strEqu(type,"jpg") && ! strstr(".jpg .JPG .jpeg .JPEG",pext))
      strcpy(pext,".jpg");
   if (strEqu(type,"png") && ! strstr(".png .PNG",pext))
      strcpy(pext,".png");
   if (strEqu(type,"tif") && ! strstr(".tif .TIF .tiff .TIFF",pext))
      strcpy(pext,".tif");

   tempfile = strdupz(get_zuserdir(),24,"f_save");                         //  use temp output file
   strcat(tempfile,"/temp_");                                              //    ~/.fotoxx/temp_ppppp.ext
   strcat(tempfile,PIDstring);
   strcat(tempfile,".");
   strcat(tempfile,type);

   if (strEqu(type,"png")) {                                               //  save as PNG file (bpc = 8 always)
      if (Fpxm16) err = PXBwrite(Fpxm16,tempfile);
      else  err = PXBwrite(Fpxm8,tempfile);
      bpc = 8;
   }

   else if (strEqu(type,"tif")) {                                          //  save as tiff file
      if (bpc == 8) {
         if (Fpxm16) {
            PXM_free(Fpxm8);                                               //  bugfix     v.10.8
            Fpxm8 = PXM_convbpc(Fpxm16);
         }
         err = TIFFwrite(Fpxm8,tempfile);
      }
      else if (bpc == 16) {
         if (Fpxm16) err = TIFFwrite(Fpxm16,tempfile);                     //  edit file exists, use it
         else {
            if (curr_file_bpc == 16) pxm16 = TIFFread(curr_file);          //  use original 16 bpc file
            else  pxm16 = PXM_convbpc(Fpxm8);                              //  or convert 8 to 16 bpc
            if (! pxm16) err = 1;
            else {
               err = TIFFwrite(pxm16,tempfile);
               PXM_free(pxm16);
            }
         }
      }
   }

   else {                                                                  //  save as JPEG file (bpc = 8 always)
      if (Fpxm16) err = PXBwrite(Fpxm16,tempfile);
      else  err = PXBwrite(Fpxm8,tempfile);
      bpc = 8;
   }

   if (err) {
      remove(tempfile);                                                    //  failure, clean up   v.11.02
      zfree(tempfile);
      Ffuncbusy--;
      return 1;
   }

   exifdata[0] = "";                                                       //  EXIF orientation = upright
   nkeys = 1;                                                              //  remove old width, height        v.10.8

   if (Pundo > 0)                                                          //  if edits were made,
   {                                                                       //    update relevant EXIF key      v.10.2
      *funcslist = 0;
      ppv = info_get(curr_file,&exifkey[1],1);                             //  get existing list of edits
      if (ppv[0]) {
         strncpy0(funcslist,ppv[0],998);
         zfree(ppv[0]);
      }
      cc1 = strlen(funcslist);                                             //  add blank
      if (cc1 && funcslist[cc1-1] != ' ') {
         strcpy(funcslist+cc1," ");
         cc1++;
      }

      for (int ii = 1; ii <= Pundo; ii++)                                  //  append new list of edits
      {                                                                    //  (omit index 0 = initial)
         pp = pvlist_get(editlog,ii);
         cc2 = strlen(pp);
         if (cc1 + cc2 > 998) break;
         strcpy(funcslist+cc1,pp);
         strcpy(funcslist+cc1+cc2," ");
         cc1 += cc2 + 1;
      }
      
      exifdata[1] = funcslist;                                             //  IPTC edit history key, fotoxx edits done
      nkeys = 2;
   }

   err = stat(curr_file,&fstat);
   if (! err && S_ISREG(fstat.st_mode)) {                                  //  if current file exists,         v.11.05
      err = info_copy(curr_file,tempfile,exifkey,exifdata,nkeys);          //    copy all EXIF/IPTC data to
      if (err) zmessageACK(mWin,"Unable to copy EXIF/IPTC");               //      temp file with above revisions
   }
   
   snprintf(command,ccc,"cp -f \"%s\" \"%s\" ",tempfile,outfile);          //  copy temp file to output file
   err = system(command);
   if (err) zmessageACK(mWin,"Unable to save image: %s",wstrerror(err));

   remove(tempfile);                                                       //  delete temp file                v.11.02
   zfree(tempfile);
   
   save_fileinfo(outfile);                                                 //  save tag changes if any
   if (! curr_file || strNeq(outfile,curr_file))                           //  if save to new file,            v.11.08
      update_search_index(outfile);                                        //    update search index file

   Fsaved = Pundo;                                                         //  update which mods are saved to disk

   add_recent_file(outfile);                                               //  first in recent files list
   
   strcpy(f_save_type,type);                                               //  update f_save_xxx data
   f_save_bpc = bpc;

   err = stat(outfile,&fstat);
   if (err) {
      Ffuncbusy--;
      return 1;
   }

   f_save_size = fstat.st_size;
   
   Ffuncbusy--;
   mwpaint2();                                                             //  v.11.03
   return 0;
}


/**************************************************************************/

//  intercept TIFF warning messages (many)                                 //  new v.9.8

void tiffwarninghandler(cchar *module, cchar *format, va_list ap)
{
   return;                                                                 //  stop flood of crap
   char  message[200];
   vsnprintf(message,199,format,ap);
   printf("TIFF warning: %s %s \n",module,message);
   return;
}


//  Read from TIFF file using TIFF library. 
//  Use native TIFF file bits/pixel.

PXM * TIFFread(cchar *filespec)                                            //  overhauled  v.10.8.1
{
   static int  ftf = 1;
   TIFF        *tiff;
   PXM         *pxm;
   char        *tiffbuff;
   uint8       *tiff8, *pxm8;
   uint16      *tiff16, *pxm16;
   uint16      bpc, nch, fmt; 
   int         ww, hh, rps, stb, nst;                                      //  int not uint        v.11.03
   int         tiffstat, row, col, strip, cc;
   
   if (ftf) {
      TIFFSetWarningHandler(tiffwarninghandler);                           //  intercept TIFF warning messages
      ftf = 0;
   }

   tiff = TIFFOpen(filespec,"r");
   if (! tiff) {
      zmessageACK(mWin,ZTX("TIFF open failure"));
      return 0;
   }

   TIFFGetField(tiff, TIFFTAG_IMAGEWIDTH, &ww);                            //  width
   TIFFGetField(tiff, TIFFTAG_IMAGELENGTH, &hh);                           //  height
   TIFFGetField(tiff, TIFFTAG_BITSPERSAMPLE, &bpc);                        //  bits per color, 1/8/16
   TIFFGetField(tiff, TIFFTAG_SAMPLESPERPIXEL, &nch);                      //  no. channels (colors), 1/3/4
   TIFFGetField(tiff, TIFFTAG_ROWSPERSTRIP, &rps);                         //  rows per strip
   TIFFGetField(tiff, TIFFTAG_SAMPLEFORMAT, &fmt);                         //  data format
   stb = TIFFStripSize(tiff);                                              //  strip size
   nst = TIFFNumberOfStrips(tiff);                                         //  number of strips

// printf("ww %d  hh %d  nch %d  bpc %d  rps %d  stb %d  nst %d  fmt %d \n",ww,hh,nch,bpc,rps,stb,nst,fmt);
   
   if (! (bpc <= 8 || bpc == 16)) {                                        //  check for supported bits/color
      zmessageACK(mWin,ZTX("TIFF bits/color=%d not supported"),bpc);
      TIFFClose(tiff);
      return 0;
   }
   
   f_load_bpc = bpc;                                                       //  for f_load(), file bpc 1/8/16   v.10.12

   if (bpc <= 8)                                                           //  use universal TIFF reader
   {                                                                       //    if bits/color <= 8
      tiffbuff = zmalloc(ww*hh*4,"tiffbuff");

      tiffstat = TIFFReadRGBAImage(tiff, ww, hh, (uint *) tiffbuff, 0);
      TIFFClose(tiff);

      if (tiffstat != 1) {
         zmessageACK(mWin,ZTX("TIFF read failure"));
         zfree(tiffbuff);
         return 0;
      }

      pxm = PXM_make(ww,hh,8);

      tiff8 = (uint8 *) tiffbuff;

      for (row = 0; row < hh; row++)                                       //  convert RGBA to RGB
      {
         pxm8 = (uint8 *) pxm->bmp;
         pxm8 += (hh-1 - row) * ww * 3;

         for (col = 0; col < ww; col++)
         {
            pxm8[0] = tiff8[0];
            pxm8[1] = tiff8[1];
            pxm8[2] = tiff8[2];
            pxm8 += 3;
            tiff8 += 4;
         }
      }

      zfree(tiffbuff);
      return pxm;
   }
                                                                           //  16 bits per color

   stb += 1000000;                                                         //  reduce risk of crash   v.10.8.2
   tiffbuff = zmalloc(stb,"tiffbuff");                                     //  read encoded strips 

   pxm = PXM_make(ww,hh,16);
   
   for (strip = 0; strip < nst; strip++)
   {
      cc = TIFFReadEncodedStrip(tiff,strip,tiffbuff,stb);
      if (cc < 0) {
         zmessageACK(mWin,ZTX("TIFF read failure"));
         TIFFClose(tiff);
         zfree(tiffbuff);
         PXM_free(pxm);
         return 0;
      }
      
      if (cc == 0) break;
      
      tiff16 = (uint16 *) tiffbuff;
      pxm16 = (uint16 *) pxm->bmp;
      row = strip * rps;
      pxm16 += row * ww * 3;

      while (cc >= 6)                                                      //  bugfix  v.11.03
      {
         pxm16[0] = tiff16[0];
         pxm16[1] = tiff16[1];
         pxm16[2] = tiff16[2];
         pxm16 += 3;
         tiff16 += nch;
         cc -= nch * 2;
      }
   }

   TIFFClose(tiff);
   zfree(tiffbuff);
   return pxm;
}


//  Write to TIFF file using TIFF library. 
//  File bpc is taken from PXM (8 or 16).
//  returns 0 if OK, +N if error.

int TIFFwrite(PXM *pxm, cchar *filespec)                                   //  new v.9.8
{
   static int  ftf = 1;
   TIFF        *tiff;
   uint8       *tiff8, *pxm8;
   uint16      *tiff16, *pxm16;
   int         tiffstat = 0;
   int         ww, hh, row, col, rowcc;                                    //  int not uint        v.11.03
   int         bpc, nch, pm = 2, pc = 1, comp = 5; 
   char        *tiffbuff;
   
   if (ftf) {
      TIFFSetWarningHandler(tiffwarninghandler);                           //  intercept TIFF warning messages
      ftf = 0;
   }

   tiff = TIFFOpen(filespec,"w");
   if (! tiff) {
      zmessageACK(mWin,ZTX("TIFF open failure"));
      return 1;
   }
   
   ww = pxm->ww;
   hh = pxm->hh;
   bpc = pxm->bpc;
   nch = 3;                                                                //  alpha channel removed

   TIFFSetField(tiff, TIFFTAG_IMAGEWIDTH, ww);
   TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, bpc);
   TIFFSetField(tiff, TIFFTAG_SAMPLESPERPIXEL, nch);
   TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, pm);                            //  RGB
   TIFFSetField(tiff, TIFFTAG_PLANARCONFIG, pc);
   TIFFSetField(tiff, TIFFTAG_COMPRESSION, comp);                          //  LZW
   
   rowcc = TIFFScanlineSize(tiff);
   tiffbuff = (char*) zmalloc(rowcc,"tiffbuff");

   for (row = 0; row < hh; row++)
   {
      if (bpc == 8)
      {
         tiff8 = (uint8 *) tiffbuff;
         pxm8 = (uint8 *) pxm->bmp + row * ww * 3;

         for (col = 0; col < ww; col++)
         {
            tiff8[0] = pxm8[0];
            tiff8[1] = pxm8[1];
            tiff8[2] = pxm8[2];
            pxm8 += 3;
            tiff8 += nch;
         }
      }

      if (bpc == 16) 
      {
         tiff16 = (uint16 *) tiffbuff;
         pxm16 = (uint16 *) pxm->bmp + row * ww * 3;

         for (col = 0; col < ww; col++)
         {
            tiff16[0] = pxm16[0];
            tiff16[1] = pxm16[1];
            tiff16[2] = pxm16[2];
            pxm16 += 3;
            tiff16 += nch;
         }
      }

      tiffstat = TIFFWriteScanline(tiff,tiffbuff,row,0);
      if (tiffstat != 1) break;
   }

   TIFFClose(tiff);
   zfree(tiffbuff);
   
   if (tiffstat == 1) return 0;
   zmessageACK(mWin,ZTX("TIFF write failure"));
   return 2;
}


/**************************************************************************/

//  Read from jpeg/png file using pixbuf library. bpc = 8.

PXM * PXBread(cchar *filespec)                                             //  new v.9.8
{
   GError      *gerror = 0;
   PXB         *pxb;
   PXM         *pxm;
   int         ww, hh, px, py, nch, rowst;
   uint8       *bmp1, *bmp2, *pix1, *pix2;
   
   pxb = gdk_pixbuf_new_from_file(filespec,&gerror);
   if (! pxb) {
      printf("%s \n",gerror->message);                                     //  v.10.8
      zmessageACK(mWin,ZTX("file type not supported"));
      return 0;
   }

   ww = gdk_pixbuf_get_width(pxb);
   hh = gdk_pixbuf_get_height(pxb);
   nch = gdk_pixbuf_get_n_channels(pxb);
   rowst = gdk_pixbuf_get_rowstride(pxb);
   bmp1 = gdk_pixbuf_get_pixels(pxb);
   
   pxm = PXM_make(ww,hh,8);
   bmp2 = (uint8 *) pxm->bmp;

   for (py = 0; py < hh; py++)
   {
      pix1 = bmp1 + rowst * py;
      pix2 = bmp2 + py * ww * 3;

      for (px = 0; px < ww; px++)
      {
         pix2[0] = pix1[0];
         pix2[1] = pix1[1];
         pix2[2] = pix1[2];
         pix1 += nch;
         pix2 += 3;
      }
   }
   
   f_load_bpc = 8;                                                         //  file bits per color for f_load()
   g_object_unref(pxb);                                                    //  (any value on disk becomes 8)
   return pxm;
}


//  Write to jpeg/png file using pixbuf library. bpc = 8.
//  returns 0 if OK, +N if error.

int PXBwrite(PXM *pxm, cchar *filespec)                                    //  new v.9.8
{
   int         ww, hh, bpc, px, py, rowst;
   uint8       *bmp8, *pix8, *bmp2, *pix2;
   uint16      *bmp16, *pix16;
   PXB         *pxb;
   cchar       *pext;
   GError      *gerror = 0;
   int         pxbstat;

   ww = pxm->ww;
   hh = pxm->hh;
   bpc = pxm->bpc;
   
   pxb = gdk_pixbuf_new(RGBCOLOR,0,8,ww,hh);
   if (! pxb) zappcrash("pixbuf allocation failure");

   bmp8 = (uint8 *) pxm->bmp;
   bmp16 = (uint16 *) pxm->bmp;
   bmp2 = gdk_pixbuf_get_pixels(pxb);
   rowst = gdk_pixbuf_get_rowstride(pxb);

   if (bpc == 8)
   {
      for (py = 0; py < hh; py++)
      {
         pix8 = bmp8 + py * ww * 3;
         pix2 = bmp2 + rowst * py;

         for (px = 0; px < ww; px++)
         {
            pix2[0] = pix8[0];
            pix2[1] = pix8[1];
            pix2[2] = pix8[2];
            pix2 += 3;
            pix8 += 3;
         }
      }
   }
   
   if (bpc == 16)
   {
      for (py = 0; py < hh; py++)
      {
         pix16 = bmp16 + py * ww * 3;
         pix2 = bmp2 + rowst * py;

         for (px = 0; px < ww; px++)
         {
            pix2[0] = pix16[0] >> 8;
            pix2[1] = pix16[1] >> 8;
            pix2[2] = pix16[2] >> 8;
            pix2 += 3;
            pix16 += 3;
         }
      }
   }
   
   pext = strrchr(filespec,'/');
   if (! pext) pext = filespec;
   pext = strrchr(pext,'.');
   if (! pext) pext = "";
   
   if (strstr(".png .PNG",pext))
      pxbstat = gdk_pixbuf_save(pxb,filespec,"png",&gerror,null);
   else pxbstat = gdk_pixbuf_save(pxb,filespec,"jpeg",&gerror,"quality",jpeg_quality,null);
   g_object_unref(pxb);
   if (pxbstat) return 0;

   printf("%s \n",gerror->message);                                        //  v.10.8
   zmessageACK(mWin,ZTX("pixbuf write failure"));
   return 1;
}


/**************************************************************************
      PXM pixmap conversion and rescale functions
***************************************************************************/

//  initialize PXM pixmap - allocate memory

PXM * PXM_make(int ww, int hh, int bpc)
{
   if (ww < 1 || hh < 1 || (bpc != 8 && bpc != 16))
      zappcrash("PXM_make() %d %d %d",ww,hh,bpc);
   
   PXM *pxm = (PXM *) zmalloc(sizeof(PXM),"PXM");
   pxm->ww = ww;
   pxm->hh = hh;
   pxm->bpc = bpc;
   if (bpc == 8) pxm->bmp = zmalloc(ww*hh*3,"PXM.bmp");
   if (bpc == 16) pxm->bmp = zmalloc(ww*hh*6,"PXM.bmp");
   strcpy(pxm->wmi,"rgbrgb");
   return pxm;
}


//  free PXM pixmap

void PXM_free(PXM *&pxm)
{
   if (! pxm) return;
   if (! strEqu(pxm->wmi,"rgbrgb")) 
      zappcrash("PXM_free(), bad PXM");
   strcpy(pxm->wmi,"xxxxxx");
   zfree(pxm->bmp);
   zfree(pxm);
   pxm = 0;                                                                //  v.11.01
   return;
}


//  create a copy of an PXM pixmap

PXM * PXM_copy(PXM *pxm1)
{
   int      cc;
   PXM      *pxm2;

   pxm2 = PXM_make(pxm1->ww, pxm1->hh, pxm1->bpc);
   cc = pxm1->ww * pxm1->hh * (pxm1->bpc / 8 * 3);
   memcpy(pxm2->bmp,pxm1->bmp,cc);
   return pxm2;
}


//  create a copy of an PXM area

PXM * PXM_copy_area(PXM *pxm1, int orgx, int orgy, int ww2, int hh2)
{
   uint8          *bmp1, *pix1, *bmp2, *pix2;
   uint16         *bmp3, *pix3, *bmp4, *pix4;
   PXM            *pxm2 = 0;
   int            ww1, bpc, px1, py1, px2, py2;

   ww1 = pxm1->ww;
   bpc = pxm1->bpc;

   if (bpc == 8)
   {
      pxm2 = PXM_make(ww2,hh2,8);
      bmp1 = (uint8 *) pxm1->bmp;
      bmp2 = (uint8 *) pxm2->bmp;
     
      for (py1 = orgy, py2 = 0; py2 < hh2; py1++, py2++) 
      {
         for (px1 = orgx, px2 = 0; px2 < ww2; px1++, px2++)
         {
            pix1 = bmp1 + (py1 * ww1 + px1) * 3;
            pix2 = bmp2 + (py2 * ww2 + px2) * 3;

            pix2[0] = pix1[0];
            pix2[1] = pix1[1];
            pix2[2] = pix1[2];
            pix1 += 3;
            pix2 += 3;
         }
      }
   }

   if (bpc == 16)
   {
      pxm2 = PXM_make(ww2,hh2,16);
      bmp3 = (uint16 *) pxm1->bmp;
      bmp4 = (uint16 *) pxm2->bmp;
     
      for (py1 = orgy, py2 = 0; py2 < hh2; py1++, py2++) 
      {
         for (px1 = orgx, px2 = 0; px2 < ww2; px1++, px2++)
         {
            pix3 = bmp3 + (py1 * ww1 + px1) * 3;
            pix4 = bmp4 + (py2 * ww2 + px2) * 3;

            pix4[0] = pix3[0];
            pix4[1] = pix3[1];
            pix4[2] = pix3[2];
            pix3 += 3;
            pix4 += 3;
         }
      }
   }
   
   return pxm2;
}


//   convert PXM pixmap from 8/16 to 16/8 bits per color

PXM * PXM_convbpc(PXM *pxm1)
{
   uint8          *bmp8, *pix8;
   uint16         *bmp16, *pix16;
   PXM            *pxm2 = 0;
   int            ww, hh, bpc, px, py;

   ww = pxm1->ww;
   hh = pxm1->hh;
   bpc = pxm1->bpc;

   if (bpc == 8)                                                           //  8 > 16
   {
      pxm2 = PXM_make(ww,hh,16);
      bmp8 = (uint8 *) pxm1->bmp;
      bmp16 = (uint16 *) pxm2->bmp;
     
      for (py = 0; py < hh; py++) 
      {
         pix8 = bmp8 + py * ww * 3;
         pix16 = bmp16 + py * ww * 3;

         for (px = 0; px < ww; px++)
         {
            pix16[0] = pix8[0] << 8;
            pix16[1] = pix8[1] << 8;
            pix16[2] = pix8[2] << 8;
            pix8 += 3;
            pix16 += 3;
         }
      }
   }

   if (bpc == 16)                                                          //  16 > 8
   {
      pxm2 = PXM_make(ww,hh,8);
      bmp8 = (uint8 *) pxm2->bmp;
      bmp16 = (uint16 *) pxm1->bmp;
     
      for (py = 0; py < hh; py++) 
      {
         pix8 = bmp8 + py * ww * 3;
         pix16 = bmp16 + py * ww * 3;

         for (px = 0; px < ww; px++)
         {
            pix8[0] = pix16[0] >> 8;
            pix8[1] = pix16[1] >> 8;
            pix8[2] = pix16[2] >> 8;
            pix8 += 3;
            pix16 += 3;
         }
      }
   }

   return pxm2;
}


//  replace blue = 0 pixels with blue = 2 in a 16-bit pixmap
//  (blue = 0 reserved for pixels voided by warp or overlay offsets)
//  all callers of vpixel() need to do this
//  needs about 0.013 seconds for 10 megapixel image and 3.3 GHz processor

void PXM_fixblue(PXM *pxm)                                                 //  v.11.07
{
   int      size;
   uint16   *pixel, *pixel0, *pixelN;

   size = pxm->ww * pxm->hh * 3;
   pixel0 = (uint16 *) pxm->bmp + 2;
   pixelN = (uint16 *) pixel0 + size;

   for (pixel = pixel0; pixel < pixelN; pixel += 3)                        //  +3 is +6 bytes
      if (! *pixel) *pixel = 2;

   return;
}


//  rescale PXM pixmap to size ww2 x hh2

PXM * PXM_rescale(PXM *pxm1, int ww2, int hh2)
{
   void bmp8_rescale(uint8*, uint8*, int, int, int, int);
   void bmp16_rescale(uint16*, uint16*, int, int, int, int);

   PXM      *pxm2;
   int      ww1, hh1, bpc;
   uint8    *bmp1, *bmp2;
   uint16   *bmp3, *bmp4;

   ww1 = pxm1->ww;
   hh1 = pxm1->hh;
   bpc = pxm1->bpc;
   
   pxm2 = PXM_make(ww2,hh2,bpc);
   
   if (bpc == 8) {
      bmp1 = (uint8 *) pxm1->bmp;
      bmp2 = (uint8 *) pxm2->bmp;
      bmp8_rescale(bmp1,bmp2,ww1,hh1,ww2,hh2);
   }

   if (bpc == 16) {
      bmp3 = (uint16 *) pxm1->bmp;
      bmp4 = (uint16 *) pxm2->bmp;
      bmp16_rescale(bmp3,bmp4,ww1,hh1,ww2,hh2);
   }
   
   return pxm2;
}


/**************************************************************************

   Rescale 8 bpc image (3 x 8 bits per color) to new width and height.
   The scale ratios may be different for width and height.

   Method: 
   The input and output images are overlayed, stretching or shrinking the
   output pixels as needed. The contribution of each input pixel overlapping
   an output pixel is proportional to the area of the output pixel covered by
   the input pixel. The contributions of all overlaping input pixels are added.
   The work is spread among Nwt threads to reduce the elapsed time on modern 
   computers having multiple SMP processors.

   Example: if the output image is 40% of the input image, then:
     outpix[0,0] = 0.16 * inpix[0,0] + 0.16 * inpix[1,0] + 0.08 * inpix[2,0]
                 + 0.16 * inpix[0,1] + 0.16 * inpix[1,1] + 0.08 * inpix[2,1]
                 + 0.08 * inpix[0,2] + 0.08 * inpix[1,2] + 0.04 * inpix[2,2]

*********/

namespace bmp8rescale {                                                    //  data for threads
   uint8    *pixmap1;
   uint8    *pixmap2;
   int      ww1;
   int      hh1;
   int      ww2;
   int      hh2;
   int      *px1L;
   int      *py1L;
   double   *pxmap;
   double   *pymap;
   int      maxmapx;
   int      maxmapy;
   int      busy[max_threads];
}


void bmp8_rescale(uint8 *pixmap1x, uint8 *pixmap2x, int ww1x, int hh1x, int ww2x, int hh2x)
{
   using namespace bmp8rescale;

   void * bmp8_rescale_thread(void *arg);

   int         px1, py1, px2, py2;
   int         pxl, pyl, pxm, pym, ii;
   double      scalex, scaley;
   double      px1a, py1a, px1b, py1b;
   double      fx, fy;
   
   pixmap1 = pixmap1x;
   pixmap2 = pixmap2x;
   ww1 = ww1x;
   hh1 = hh1x;
   ww2 = ww2x;
   hh2 = hh2x;

   memset(pixmap2, 0, ww2 * hh2 * 3 * sizeof(uint8));                      //  clear output pixmap

   scalex = 1.0 * ww1 / ww2;                                               //  compute x and y scales
   scaley = 1.0 * hh1 / hh2;
   
   if (scalex <= 1) maxmapx = 2;                                           //  compute max input pixels
   else maxmapx = scalex + 2;                                              //    mapping into output pixels
   maxmapx += 1;                                                           //      for both dimensions
   if (scaley <= 1) maxmapy = 2;                                           //  (pixels may not be square)
   else maxmapy = scaley + 2;
   maxmapy += 1;
   
   pymap = (double *) zmalloc(hh2 * maxmapy * sizeof(double));             //  maps overlap of < maxmap input
   pxmap = (double *) zmalloc(ww2 * maxmapx * sizeof(double));             //    pixels per output pixel

   py1L = (int *) zmalloc(hh2 * sizeof(int));                              //  maps first (lowest) input pixel
   px1L = (int *) zmalloc(ww2 * sizeof(int));                              //    per output pixel

   for (py2 = 0; py2 < hh2; py2++)                                         //  loop output y-pixels
   {
      py1a = py2 * scaley;                                                 //  corresponding input y-pixels
      py1b = py1a + scaley;
      if (py1b >= hh1) py1b = hh1 - 0.001;                                 //  fix precision limitation
      pyl = py1a;
      py1L[py2] = pyl;                                                     //  1st overlapping input pixel

      for (py1 = pyl, pym = 0; py1 < py1b; py1++, pym++)                   //  loop overlapping input pixels
      {
         if (py1 < py1a) {                                                 //  compute amount of overlap
            if (py1+1 < py1b) fy = py1+1 - py1a;                           //    0.0 to 1.0 
            else fy = scaley;
         }
         else if (py1+1 > py1b) fy = py1b - py1;
         else fy = 1;

         ii = py2 * maxmapy + pym;                                         //  save it
         pymap[ii] = 0.9999 * fy / scaley;
      }
      ii = py2 * maxmapy + pym;                                            //  set an end marker after
      pymap[ii] = -1;                                                      //    last overlapping pixel
   }
   
   for (px2 = 0; px2 < ww2; px2++)                                         //  do same for x-pixels
   {
      px1a = px2 * scalex;
      px1b = px1a + scalex;
      if (px1b >= ww1) px1b = ww1 - 0.001;
      pxl = px1a;
      px1L[px2] = pxl;

      for (px1 = pxl, pxm = 0; px1 < px1b; px1++, pxm++)
      {
         if (px1 < px1a) {
            if (px1+1 < px1b) fx = px1+1 - px1a;
            else fx = scalex;
         }
         else if (px1+1 > px1b) fx = px1b - px1;
         else fx = 1;

         ii = px2 * maxmapx + pxm;
         pxmap[ii] = 0.9999 * fx / scalex;
      }
      ii = px2 * maxmapx + pxm;
      pxmap[ii] = -1;
   }

   for (ii = 0; ii < Nwt; ii++) {                                          //  start working threads
      busy[ii] = 1;
      start_detached_thread(bmp8_rescale_thread,&wtnx[ii]);
   }
   
   for (ii = 0; ii < Nwt; ii++)                                            //  wait for all done
      while (busy[ii]) zsleep(0.004);

   zfree(px1L);
   zfree(py1L);
   zfree(pxmap);
   zfree(pymap);
   return;
}


void * bmp8_rescale_thread(void *arg)                                      //  worker thread function
{
   using namespace bmp8rescale;

   int         index = *((int *) arg);
   int         px1, py1, px2, py2;
   int         pxl, pyl, pxm, pym, ii;
   uint8       *pixel1, *pixel2;
   double      fx, fy, ftot;
   double      red, green, blue;

   for (py2 = index; py2 < hh2; py2 += Nwt)                                //  loop output y-pixels
   {
      pyl = py1L[py2];                                                     //  corresp. 1st input y-pixel

      for (px2 = 0; px2 < ww2; px2++)                                      //  loop output x-pixels
      {
         pxl = px1L[px2];                                                  //  corresp. 1st input x-pixel

         red = green = blue = 0;                                           //  initz. output pixel

         for (py1 = pyl, pym = 0; ; py1++, pym++)                          //  loop overlapping input y-pixels
         {
            ii = py2 * maxmapy + pym;                                      //  get y-overlap
            fy = pymap[ii];
            if (fy < 0) break;                                             //  no more pixels

            for (px1 = pxl, pxm = 0; ; px1++, pxm++)                       //  loop overlapping input x-pixels
            {
               ii = px2 * maxmapx + pxm;                                   //  get x-overlap
               fx = pxmap[ii];
               if (fx < 0) break;                                          //  no more pixels

               ftot = fx * fy;                                             //  area overlap = x * y overlap
               pixel1 = pixmap1 + (py1 * ww1 + px1) * 3;
               red += pixel1[0] * ftot;                                    //  add input pixel * overlap
               green += pixel1[1] * ftot;
               blue += pixel1[2] * ftot;
            }

            pixel2 = pixmap2 + (py2 * ww2 + px2) * 3;                      //  save output pixel
            pixel2[0] = red;
            pixel2[1] = green;
            pixel2[2] = blue;
         }
      }
   }

   busy[index] = 0;
   return 0;
}


/**************************************************************************

   Rescale 16 bpc image (3 x 16 bits per color) to new width and height.
   Identical to bmp8_rescale except for the following: 
      uint8 >> uint16
      xxx8 >> xxx16

*******/

namespace bmp16rescale {                                                   //  data for threads
   uint16   *pixmap1;
   uint16   *pixmap2;
   int      ww1;
   int      hh1;
   int      ww2;
   int      hh2;
   int      *px1L;
   int      *py1L;
   double   *pxmap;
   double   *pymap;
   int      maxmapx;
   int      maxmapy;
   int      busy[max_threads];
}


void bmp16_rescale(uint16 *pixmap1x, uint16 *pixmap2x, int ww1x, int hh1x, int ww2x, int hh2x)
{
   using namespace bmp16rescale;

   void * bmp16_rescale_thread(void *arg);

   int         px1, py1, px2, py2;
   int         pxl, pyl, pxm, pym, ii;
   double      scalex, scaley;
   double      px1a, py1a, px1b, py1b;
   double      fx, fy;
   
   pixmap1 = pixmap1x;
   pixmap2 = pixmap2x;
   ww1 = ww1x;
   hh1 = hh1x;
   ww2 = ww2x;
   hh2 = hh2x;

   memset(pixmap2, 0, ww2 * hh2 * 3 * sizeof(uint16));                     //  clear output pixmap

   scalex = 1.0 * ww1 / ww2;                                               //  compute x and y scales
   scaley = 1.0 * hh1 / hh2;
   
   if (scalex <= 1) maxmapx = 2;                                           //  compute max input pixels
   else maxmapx = scalex + 2;                                              //    mapping into output pixels
   maxmapx += 1;                                                           //      for both dimensions
   if (scaley <= 1) maxmapy = 2;                                           //  (pixels may not be square)
   else maxmapy = scaley + 2;
   maxmapy += 1;
   
   pymap = (double *) zmalloc(hh2 * maxmapy * sizeof(double));             //  maps overlap of < maxmap input
   pxmap = (double *) zmalloc(ww2 * maxmapx * sizeof(double));             //    pixels per output pixel

   py1L = (int *) zmalloc(hh2 * sizeof(int));                              //  maps first (lowest) input pixel
   px1L = (int *) zmalloc(ww2 * sizeof(int));                              //    per output pixel

   for (py2 = 0; py2 < hh2; py2++)                                         //  loop output y-pixels
   {
      py1a = py2 * scaley;                                                 //  corresponding input y-pixels
      py1b = py1a + scaley;
      if (py1b >= hh1) py1b = hh1 - 0.001;                                 //  fix precision limitation
      pyl = py1a;
      py1L[py2] = pyl;                                                     //  1st overlapping input pixel

      for (py1 = pyl, pym = 0; py1 < py1b; py1++, pym++)                   //  loop overlapping input pixels
      {
         if (py1 < py1a) {                                                 //  compute amount of overlap
            if (py1+1 < py1b) fy = py1+1 - py1a;                           //    0.0 to 1.0 
            else fy = scaley;
         }
         else if (py1+1 > py1b) fy = py1b - py1;
         else fy = 1;

         ii = py2 * maxmapy + pym;                                         //  save it
         pymap[ii] = 0.9999 * fy / scaley;
      }
      ii = py2 * maxmapy + pym;                                            //  set an end marker after
      pymap[ii] = -1;                                                      //    last overlapping pixel
   }
   
   for (px2 = 0; px2 < ww2; px2++)                                         //  do same for x-pixels
   {
      px1a = px2 * scalex;
      px1b = px1a + scalex;
      if (px1b >= ww1) px1b = ww1 - 0.001;
      pxl = px1a;
      px1L[px2] = pxl;

      for (px1 = pxl, pxm = 0; px1 < px1b; px1++, pxm++)
      {
         if (px1 < px1a) {
            if (px1+1 < px1b) fx = px1+1 - px1a;
            else fx = scalex;
         }
         else if (px1+1 > px1b) fx = px1b - px1;
         else fx = 1;

         ii = px2 * maxmapx + pxm;
         pxmap[ii] = 0.9999 * fx / scalex;
      }
      ii = px2 * maxmapx + pxm;
      pxmap[ii] = -1;
   }

   for (ii = 0; ii < Nwt; ii++) {                                          //  start working threads
      busy[ii] = 1;
      start_detached_thread(bmp16_rescale_thread,&wtnx[ii]);
   }
   
   for (ii = 0; ii < Nwt; ii++)                                            //  wait for all done
      while (busy[ii]) zsleep(0.004);

   zfree(px1L);
   zfree(py1L);
   zfree(pxmap);
   zfree(pymap);
   return;
}


void * bmp16_rescale_thread(void *arg)                                     //  worker thread function
{
   using namespace bmp16rescale;

   int         index = *((int *) arg);
   int         px1, py1, px2, py2;
   int         pxl, pyl, pxm, pym, ii;
   uint16      *pixel1, *pixel2;
   double      fx, fy, ftot;
   double      red, green, blue;

   for (py2 = index; py2 < hh2; py2 += Nwt)                                //  loop output y-pixels
   {
      pyl = py1L[py2];                                                     //  corresp. 1st input y-pixel

      for (px2 = 0; px2 < ww2; px2++)                                      //  loop output x-pixels
      {
         pxl = px1L[px2];                                                  //  corresp. 1st input x-pixel

         red = green = blue = 0;                                           //  initz. output pixel

         for (py1 = pyl, pym = 0; ; py1++, pym++)                          //  loop overlapping input y-pixels
         {
            ii = py2 * maxmapy + pym;                                      //  get y-overlap
            fy = pymap[ii];
            if (fy < 0) break;                                             //  no more pixels

            for (px1 = pxl, pxm = 0; ; px1++, pxm++)                       //  loop overlapping input x-pixels
            {
               ii = px2 * maxmapx + pxm;                                   //  get x-overlap
               fx = pxmap[ii];
               if (fx < 0) break;                                          //  no more pixels

               ftot = fx * fy;                                             //  area overlap = x * y overlap
               pixel1 = pixmap1 + (py1 * ww1 + px1) * 3;
               red += pixel1[0] * ftot;                                    //  add input pixel * overlap
               green += pixel1[1] * ftot;
               blue += pixel1[2] * ftot;
            }

            pixel2 = pixmap2 + (py2 * ww2 + px2) * 3;                      //  save output pixel
            pixel2[0] = red;
            pixel2[1] = green;
            pixel2[2] = blue;
         }
      }
   }

   busy[index] = 0;
   return 0;
}


//  Copy and rescale a modified area within a PXM-16 image into the
//  corresponding area of a PXM-8 image previously rescaled from the 
//  PXM-16 image. Keep the same mapping of input to output pixels, so 
//  that the modified area fits seamlessly into the PXM-8 image. Used 
//  when a section of an image is edited and the window image is updated.
//   
//  pxm1           PXM-16 image with changed area to rescale and copy
//  pxm2           existing rescaled PXM-8 copy of pxm1
//  org1x, org1y   pxm1 origin of area to copy (top left corner)
//  ww1a, hh1a     width and height of area to copy                        //  new v.10.11

void PXM_update(PXM *pxm1, PXM *pxm2, int org1x, int org1y, int ww1a, int hh1a)
{
   uint16      *bmp1, *pix1;
   uint8       *bmp2, *pix2;
   int         ww1, hh1, ww2, hh2;
   int         px1, py1, px2, py2;
   int         pxl, pyl, pxm, pym, ii;
   int         *px1L, *py1L;
   int         maxmapx, maxmapy;
   int         org2x, end2x, org2y, end2y;
   float       scalex, scaley;
   float       px1a, py1a, px1b, py1b;
   float       fx, fy, ftot;
   float       red, green, blue;
   float       *pxmap, *pymap;

   bmp1 = (uint16 *) pxm1->bmp;   
   bmp2 = (uint8 *) pxm2->bmp;   
   ww1 = pxm1->ww;
   hh1 = pxm1->hh;
   ww2 = pxm2->ww;
   hh2 = pxm2->hh;

   scalex = 1.0 * ww1 / ww2;                                               //  compute x and y scales
   scaley = 1.0 * hh1 / hh2;
   
   if (scalex <= 1) maxmapx = 2;                                           //  compute max input pixels
   else maxmapx = scalex + 2;                                              //    mapping into output pixels
   maxmapx += 1;                                                           //      for both dimensions
   if (scaley <= 1) maxmapy = 2;                                           //  (pixels may not be square)
   else maxmapy = scaley + 2;
   maxmapy += 1;
   
   pymap = (float *) zmalloc(hh2 * maxmapy * sizeof(float));               //  maps overlap of < maxmap input
   pxmap = (float *) zmalloc(ww2 * maxmapx * sizeof(float));               //    pixels per output pixel

   py1L = (int *) zmalloc(hh2 * sizeof(int));                              //  maps first (lowest) input pixel
   px1L = (int *) zmalloc(ww2 * sizeof(int));                              //    per output pixel

   for (py2 = 0; py2 < hh2; py2++)                                         //  loop output y-pixels
   {
      py1a = py2 * scaley;                                                 //  corresponding input y-pixels
      py1b = py1a + scaley;
      if (py1b >= hh1) py1b = hh1 - 0.001;                                 //  fix precision limitation
      pyl = py1a;
      py1L[py2] = pyl;                                                     //  1st overlapping input pixel

      for (py1 = pyl, pym = 0; py1 < py1b; py1++, pym++)                   //  loop overlapping input pixels
      {
         if (py1 < py1a) {                                                 //  compute amount of overlap
            if (py1+1 < py1b) fy = py1+1 - py1a;                           //    0.0 to 1.0 
            else fy = scaley;
         }
         else if (py1+1 > py1b) fy = py1b - py1;
         else fy = 1;

         ii = py2 * maxmapy + pym;                                         //  save it
         pymap[ii] = 0.9999 * fy / scaley;
      }
      ii = py2 * maxmapy + pym;                                            //  set an end marker after
      pymap[ii] = -1;                                                      //    last overlapping pixel
   }

   for (px2 = 0; px2 < ww2; px2++)                                         //  do same for x-pixels
   {
      px1a = px2 * scalex;
      px1b = px1a + scalex;
      if (px1b >= ww1) px1b = ww1 - 0.001;
      pxl = px1a;
      px1L[px2] = pxl;

      for (px1 = pxl, pxm = 0; px1 < px1b; px1++, pxm++)
      {
         if (px1 < px1a) {
            if (px1+1 < px1b) fx = px1+1 - px1a;
            else fx = scalex;
         }
         else if (px1+1 > px1b) fx = px1b - px1;
         else fx = 1;

         ii = px2 * maxmapx + pxm;
         pxmap[ii] = 0.9999 * fx / scalex;
      }
      ii = px2 * maxmapx + pxm;
      pxmap[ii] = -1;
   }

   org2x = org1x / scalex;                                                 //  compute output image rectangle
   end2x = (org1x + ww1a) / scalex + 1;                                    //    containing any part of input area
   if (org2x < 0) org2x = 0;                                               //       revised  v.10.12
   if (end2x > ww2) end2x = ww2;

   org2y = org1y / scaley;
   end2y = (org1y + hh1a) / scaley + 1;
   if (org2y < 0) org2y = 0;
   if (end2y > hh2) end2y = hh2;

   for (py2 = org2y; py2 < end2y; py2++)                                   //  loop output y-pixels
   {
      pyl = py1L[py2];                                                     //  corresp. 1st input y-pixel

      for (px2 = org2x; px2 < end2x; px2++)                                //  loop output x-pixels
      {
         pxl = px1L[px2];                                                  //  corresp. 1st input x-pixel

         red = green = blue = 0;                                           //  initz. output pixel

         for (py1 = pyl, pym = 0; ; py1++, pym++)                          //  loop overlapping input y-pixels
         {
            ii = py2 * maxmapy + pym;                                      //  get y-overlap
            fy = pymap[ii];
            if (fy < 0) break;                                             //  no more pixels

            for (px1 = pxl, pxm = 0; ; px1++, pxm++)                       //  loop overlapping input x-pixels
            {
               ii = px2 * maxmapx + pxm;                                   //  get x-overlap
               fx = pxmap[ii];
               if (fx < 0) break;                                          //  no more pixels

               ftot = fx * fy;                                             //  area overlap = x * y overlap
               pix1 = bmp1 + (py1 * ww1 + px1) * 3;
               red += pix1[0] * ftot;                                      //  add input pixel * overlap
               green += pix1[1] * ftot;
               blue += pix1[2] * ftot;
            }

            pix2 = bmp2 + (py2 * ww2 + px2) * 3;                           //  save output pixel
            pix2[0] = int(red) >> 8;
            pix2[1] = int(green) >> 8;
            pix2[2] = int(blue) >> 8;
         }
      }
   }

   zfree(px1L);
   zfree(py1L);
   zfree(pxmap);
   zfree(pymap);
   return;
}


//  rotate PXM pixmap through given angle in degrees (+ = clockwise)

PXM * PXM_rotate(PXM *pxm1, double angle)
{
   PXM * PXM_rotate8(PXM *, double);
   PXM * PXM_rotate16(PXM *, double);

   PXM      *pxm2 = 0;
   int      bpc;
   
   bpc = pxm1->bpc;
   if (bpc == 8) pxm2 = PXM_rotate8(pxm1,angle);
   if (bpc == 16) pxm2 = PXM_rotate16(pxm1,angle);
   return pxm2;
}


/**************************************************************************

      PXM *pxm2 = PXM_rotate8(PXM *pxm1, double angle)

      Rotate PXM-8 pixmap through an arbitrary angle (degrees).

      The returned image has the same size as the original, but the
      pixmap size is increased to accomodate the rotated image.
      (e.g. a 100x100 image rotated 45 deg. needs a 142x142 pixmap).
      The parameters ww and hh are the dimensions of the input
      pixmap, and are updated to the dimensions of the output pixmap.

      The space added around the rotated image is black (RGB 0,0,0).
      Angle is in degrees. Positive direction is clockwise.
      Speed is about 3 million pixels/sec/thread for a 2.4 GHz CPU.
      Loss of resolution is less than 1 pixel.
      
      Work is divided among Nwt threads to gain speed.
      
      v.9.3: affine transform instead of trig functions, for speed

***************************************************************************/

namespace rotpxm8 {
   int      busy = 0;
   uint8    *pixmap1;
   uint8    *pixmap2;
   int      ww1;
   int      hh1;
   int      ww2;
   int      hh2;
   double   angle;
}


PXM * PXM_rotate8(PXM *pxm1, double anglex)
{
   using namespace rotpxm8;

   void     *PXM_rotate8_thread(void *);

   int      cc, ii;
   PXM      *pxm2;
   
   ww1 = pxm1->ww;
   hh1 = pxm1->hh;
   pixmap1 = (uint8 *) pxm1->bmp;
   angle = anglex;

   while (angle < -180) angle += 360;                                      //  normalize, -180 to +180
   while (angle > 180) angle -= 360;
   angle = angle * pi / 180;                                               //  radians, -pi to +pi
   
   if (fabs(angle) < 0.001) {                                              //  angle = 0 within my precision
      pxm2 = PXM_make(ww1,hh1,8);                                          //  return a copy of the input
      pixmap2 = (uint8 *) pxm2->bmp;
      cc = ww1 * hh1 * 3 * sizeof(uint8);
      memcpy(pixmap2,pixmap1,cc);
      return pxm2;
   }

   ww2 = ww1*fabs(cos(angle)) + hh1*fabs(sin(angle));                      //  rectangle containing rotated image
   hh2 = ww1*fabs(sin(angle)) + hh1*fabs(cos(angle));
   
   pxm2 = PXM_make(ww2,hh2,8);
   pixmap2 = (uint8 *) pxm2->bmp;

   for (ii = 0; ii < Nwt; ii++)                                            //  start worker threads
      start_detached_thread(PXM_rotate8_thread,&wtnx[ii]);
   zadd_locked(busy,+Nwt);

   while (busy) zsleep(0.004);                                             //  wait for completion
   return pxm2;
}


void * PXM_rotate8_thread(void *arg)
{
   using namespace rotpxm8;

   int      index = *((int *) (arg));
   int      px2, py2, px0, py0;
   uint8    *pix0, *pix1, *pix2, *pix3;
   double   px1, py1;
   double   f0, f1, f2, f3, red, green, blue;
   double   a, b, d, e, ww15, hh15, ww25, hh25;

   ww15 = 0.5 * ww1;
   hh15 = 0.5 * hh1;
   ww25 = 0.5 * ww2;
   hh25 = 0.5 * hh2;

   a = cos(angle);
   b = sin(angle);
   d = - sin(angle);
   e = cos(angle);
   
   for (py2 = index; py2 < hh2; py2 += Nwt)                                //  loop through output pixels
   for (px2 = 0; px2 < ww2; px2++)                                         //  outer loop y
   {
      px1 = a * (px2 - ww25) + b * (py2 - hh25) + ww15;                    //  (px1,py1) = corresponding    v.9.3
      py1 = d * (px2 - ww25) + e * (py2 - hh25) + hh15;                    //    point within input pixels

      px0 = px1;                                                           //  pixel containing (px1,py1)
      py0 = py1;
      
      if (px1 < 0 || px0 >= ww1-1 || py1 < 0 || py0 >= hh1-1) {            //  if outside input pixel array
         pix2 = pixmap2 + (py2 * ww2 + px2) * 3;                           //    output is black
         pix2[0] = pix2[1] = pix2[2] = 0;
         continue;
      }

      pix0 = pixmap1 + (py0 * ww1 + px0) * 3;                              //  4 input pixels based at (px0,py0)
      pix1 = pix0 + ww1 * 3;
      pix2 = pix0 + 3;
      pix3 = pix1 + 3;

      f0 = (px0+1 - px1) * (py0+1 - py1);                                  //  overlap of (px1,py1)
      f1 = (px0+1 - px1) * (py1 - py0);                                    //    in each of the 4 pixels
      f2 = (px1 - px0) * (py0+1 - py1);
      f3 = (px1 - px0) * (py1 - py0);
   
      red =   f0 * pix0[0] + f1 * pix1[0] + f2 * pix2[0] + f3 * pix3[0];   //  sum the weighted inputs
      green = f0 * pix0[1] + f1 * pix1[1] + f2 * pix2[1] + f3 * pix3[1];
      blue =  f0 * pix0[2] + f1 * pix1[2] + f2 * pix2[2] + f3 * pix3[2];
      
      pix2 = pixmap2 + (py2 * ww2 + px2) * 3;                              //  output pixel
      pix2[0] = red;
      pix2[1] = green;
      pix2[2] = blue;
   }
   
   zadd_locked(busy,-1);
   return 0;
}


/**************************************************************************

   PXM *pxm2 = PXM_rotate16(PXM *pxm1, double angle)
   Rotate PXM-16 pixmap through an arbitrary angle (degrees).
   Identical to PXM_rotate8() except for:
      uint8 >> uint16
      rotpxm8 >> rotpxm16
      8 >> 16   

**********/

namespace rotpxm16 {
   int      busy = 0;
   uint16   *pixmap1;
   uint16   *pixmap2;
   int      ww1;
   int      hh1;
   int      ww2;
   int      hh2;
   double   angle;
}


PXM * PXM_rotate16(PXM *pxm1, double anglex)
{
   using namespace rotpxm16;

   void     *PXM_rotate16_thread(void *);

   int      cc, ii;
   PXM      *pxm2;
   
   ww1 = pxm1->ww;
   hh1 = pxm1->hh;
   pixmap1 = (uint16 *) pxm1->bmp;
   angle = anglex;

   while (angle < -180) angle += 360;                                      //  normalize, -180 to +180
   while (angle > 180) angle -= 360;
   angle = angle * pi / 180;                                               //  radians, -pi to +pi
   
   if (fabs(angle) < 0.001) {                                              //  angle = 0 within my precision
      pxm2 = PXM_make(ww1,hh1,16);                                         //  return a copy of the input
      pixmap2 = (uint16 *) pxm2->bmp;
      cc = ww1 * hh1 * 3 * sizeof(uint16);
      memcpy(pixmap2,pixmap1,cc);
      return pxm2;
   }

   ww2 = ww1*fabs(cos(angle)) + hh1*fabs(sin(angle));                      //  rectangle containing rotated image
   hh2 = ww1*fabs(sin(angle)) + hh1*fabs(cos(angle));
   
   pxm2 = PXM_make(ww2,hh2,16);
   pixmap2 = (uint16 *) pxm2->bmp;

   for (ii = 0; ii < Nwt; ii++)                                            //  start worker threads
      start_detached_thread(PXM_rotate16_thread,&wtnx[ii]);
   zadd_locked(busy,+Nwt);

   while (busy) zsleep(0.004);                                             //  wait for completion
   return pxm2;
}


void * PXM_rotate16_thread(void *arg)
{
   using namespace rotpxm16;

   int      index = *((int *) (arg));
   int      px2, py2, px0, py0;
   uint16   *pix0, *pix1, *pix2, *pix3;
   double   px1, py1;
   double   f0, f1, f2, f3, red, green, blue;
   double   a, b, d, e, ww15, hh15, ww25, hh25;

   ww15 = 0.5 * ww1;
   hh15 = 0.5 * hh1;
   ww25 = 0.5 * ww2;
   hh25 = 0.5 * hh2;

   a = cos(angle);
   b = sin(angle);
   d = - sin(angle);
   e = cos(angle);
   
   for (py2 = index; py2 < hh2; py2 += Nwt)                                //  loop through output pixels
   for (px2 = 0; px2 < ww2; px2++)                                         //  outer loop y
   {
      px1 = a * (px2 - ww25) + b * (py2 - hh25) + ww15;                    //  (px1,py1) = corresponding    v.9.3
      py1 = d * (px2 - ww25) + e * (py2 - hh25) + hh15;                    //    point within input pixels

      px0 = px1;                                                           //  pixel containing (px1,py1)
      py0 = py1;
      
      if (px1 < 0 || px0 >= ww1-1 || py1 < 0 || py0 >= hh1-1) {            //  if outside input pixel array
         pix2 = pixmap2 + (py2 * ww2 + px2) * 3;                           //    output is black
         pix2[0] = pix2[1] = pix2[2] = 0;
         continue;
      }

      pix0 = pixmap1 + (py0 * ww1 + px0) * 3;                              //  4 input pixels based at (px0,py0)
      pix1 = pix0 + ww1 * 3;
      pix2 = pix0 + 3;
      pix3 = pix1 + 3;

      f0 = (px0+1 - px1) * (py0+1 - py1);                                  //  overlap of (px1,py1)
      f1 = (px0+1 - px1) * (py1 - py0);                                    //    in each of the 4 pixels
      f2 = (px1 - px0) * (py0+1 - py1);
      f3 = (px1 - px0) * (py1 - py0);
   
      red =   f0 * pix0[0] + f1 * pix1[0] + f2 * pix2[0] + f3 * pix3[0];   //  sum the weighted inputs
      green = f0 * pix0[1] + f1 * pix1[1] + f2 * pix2[1] + f3 * pix3[1];
      blue =  f0 * pix0[2] + f1 * pix1[2] + f2 * pix2[2] + f3 * pix3[2];
      
      pix2 = pixmap2 + (py2 * ww2 + px2) * 3;                              //  output pixel
      pix2[0] = red;
      pix2[1] = green;
      pix2[2] = blue;
   }
   
   zadd_locked(busy,-1);
   return 0;
}



