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

   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 extern          //  enable extern declarations
#include "fotoxx.h"

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

   Fotoxx image editor - select area functions

   Select an area within the current image.
   Subsequent edit functions are carried out within the area.
   Otherwise, edit functions apply to the entire image.
   
   sa_stat  0/1/2/3 = none/edit/pause/complete
   sa_mode is current area selection method:
      1  select rectangle by drag or clicks
      2  select ellipse by drag
      3  freehand draw by drag
      4  follow edge indicated by clicks
      5  select pixels by mouse (radius, color, range, firewall)
      6  unused v.11.12
      7  select whole image

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

//  user select area dialog
//  line drawing and selection by color range are combined                 //  v.9.7

void m_select(GtkWidget *, cchar *)                                        //  menu function
{
   int   select_dialog_event(zdialog *, cchar *event);                     //  dialog event and completion funcs

   cchar    *title = ZTX("Select Area for Edits");
   cchar    *helptext = ZTX("Press F1 for help");
   zdialog  *zd;

   zfuncs::F1_help_topic = "select_area";

   if (! curr_file) return;                                                //  no image
   if (zdsela) return;                                                     //  already active
   
   if (CEF && CEF->Farea != 2) {                                           //  active edit function
      zmessageACK(mWin,ZTX("Select Area not supported \n"                  //  v.11.08
                           "by this edit function")); 
      return;
   }
   
   if (Fpreview) edit_fullsize();                                          //  use full-size image
   
   if (! Fpxm16) {                                                         //  create Fpxm16 if not already
      mutex_lock(&Fpixmap_lock);
      Fpxm16 = f_load(curr_file,16);
      mutex_unlock(&Fpixmap_lock);
      if (! Fpxm16) return;
   }

/***  v.11.12
    ____________________________________________________
   |                Press F1 for help                   |
   | [x] select rectangle    [x] select ellipse         |
   | [x] draw: freehand      [x] draw: follow edge      |
   | [x] select by mouse   mouse radius [___]           |
   |     [x] match mouse color [___]                    |
   |     search range [__]   [x] firewall               |
   | Blend Width [__]                    |
   |                                                    |
   | [Show] [Hide] [Color] [Finish] [Unfinish]          |
   | [Enable] [Disable] [Invert] [Unselect] [Done]      |
   |____________________________________________________|
   
***/

   zdsela = zdialog_new(title,mWin,null);
   zd = zdsela;

   zdialog_add_widget(zd,"label","labhelp","dialog",helptext);

   zdialog_add_widget(zd,"hbox","hbshape","dialog");
   zdialog_add_widget(zd,"vbox","vbsh1","hbshape");
   zdialog_add_widget(zd,"label","space","hbshape",0,"space=10");
   zdialog_add_widget(zd,"vbox","vbsh2","hbshape");
   zdialog_add_widget(zd,"check","ckrect","vbsh1",ZTX("rectangle"));
   zdialog_add_widget(zd,"check","ckelips","vbsh2",ZTX("ellipse"));

   zdialog_add_widget(zd,"hbox","hbdraw","dialog");
   zdialog_add_widget(zd,"vbox","vbdr1","hbdraw",0,"homog");
   zdialog_add_widget(zd,"label","space","hbdraw",0,"space=10");
   zdialog_add_widget(zd,"vbox","vbdr2","hbdraw",0,"homog");
   zdialog_add_widget(zd,"check","ckdraw","vbdr1",ZTX("draw: freehand"));
   zdialog_add_widget(zd,"check","ckfollow","vbdr2",ZTX("draw: follow edge"));

   zdialog_add_widget(zd,"hbox","hbm1","dialog");
   zdialog_add_widget(zd,"check","ckmouse","hbm1",ZTX("select by mouse"));
   zdialog_add_widget(zd,"label","space","hbm1",0,"space=10");
   zdialog_add_widget(zd,"label","labmr","hbm1",ZTX("mouse radius"));
   zdialog_add_widget(zd,"spin","mouserad","hbm1","1|300|1|20","space=5");
   zdialog_add_widget(zd,"hbox","hbm2","dialog");
   zdialog_add_widget(zd,"label","space","hbm2",0,"space=10");
   zdialog_add_widget(zd,"check","matchcolor","hbm2",ZTX("match mouse color"));
   zdialog_add_widget(zd,"spin","colormatch","hbm2","0|100|1|90","space=10");
   zdialog_add_widget(zd,"hbox","hbm3","dialog");
   zdialog_add_widget(zd,"label","space","hbm3",0,"space=10");
   zdialog_add_widget(zd,"label","labsr","hbm3",ZTX("search range"),"space=5");
   zdialog_add_widget(zd,"spin","searchrange","hbm3","1|20|1|3");
   zdialog_add_widget(zd,"check","firewall","hbm3",ZTX("firewall"),"space=10");

   zdialog_add_widget(zd,"hbox","hbbwfw","dialog",0,"space=5");
   zdialog_add_widget(zd,"label","labblend","hbbwfw",Bblendwidth,"space=5");
   zdialog_add_widget(zd,"spin","blendwidth","hbbwfw","0|500|1|0");

   zdialog_add_widget(zd,"hbox","hbb2","dialog",0,"space=5");
   zdialog_add_widget(zd,"button","show","hbb2",Bshow);
   zdialog_add_widget(zd,"button","hide","hbb2",Bhide);
   zdialog_add_widget(zd,"button","color","hbb2",Bcolor);
   zdialog_add_widget(zd,"button","finish","hbb2",Bfinish);
   zdialog_add_widget(zd,"button","unfinish","hbb2",Bunfinish);

   zdialog_add_widget(zd,"hbox","hbb3","dialog",0,"space=3");
   zdialog_add_widget(zd,"button","enable","hbb3",Benable);
   zdialog_add_widget(zd,"button","disable","hbb3",Bdisable);
   zdialog_add_widget(zd,"button","invert","hbb3",Binvert);
   zdialog_add_widget(zd,"button","unselect","hbb3",Bunselect);
   zdialog_add_widget(zd,"button","done","hbb3",Bdone);

   zdialog_help(zd,"select_area");                                         //  v.11.08
   zdialog_run(zd,select_dialog_event,"save");                             //  run dialog - parallel     v.11.07

   sa_mouseradius = 20;                                                    //  initial values matching dialog
   sa_colormatch = 90;
   sa_matchcolor = 0;
   sa_searchrange = 3;
   sa_blend = 0;
   sa_firewall = 0;
   sa_mode = 0;
   zdialog_stuff(zd,"ckrect",0);
   zdialog_stuff(zd,"ckelips",0);
   zdialog_stuff(zd,"ckdraw",0);
   zdialog_stuff(zd,"ckfollow",0);
   zdialog_stuff(zd,"ckmouse",0);
   sa_show(1);                                                             //  show existing area, if any

   return;
}


//  dialog event and completion callback function

int select_dialog_event(zdialog *zd, cchar *event)
{
   struct  {
      cchar    *event;
      int      stat;
   }  
   eventab[20] = {                                                         //  dialog events and corresp.
         { "ckrect",        1 },                                           //    edit status change
         { "ckelips",       1 },
         { "ckdraw",        1 },
         { "ckfollow",      1 },
         { "ckmouse",       1 },
         { "mouserad",      0 },
         { "matchcolor",    0 },
         { "colormatch",    0 },
         { "searchrange",   0 },
         { "firewall",      0 },
         { "blendwidth",    0 },
         { "show",          0 },
         { "hide",          0 },
         { "color",         0 },
         { "finish",        2 },
         { "unfinish",      2 },
         { "enable",        0 },
         { "disable",       2 },
         { "invert",        0 },
         { "unselect",      2 }  };

   int      Nevents = 20, ii, jj, kk, cc;
   
   if (strEqu(event,"done") || zd->zstat)                                  //  done or cancel
   {
      freeMouse();                                                         //  disconnect mouse function    v.10.12
      zdialog_free(zdsela);                                                //  kill dialog
      return 0;
   }

   if (CEF && CEF->Farea != 2) {                                           //  select area not supported    v.11.08
      printf("select area ignored \n");
      return 0;
   }
   
   for (ii = 0; ii < Nevents; ii++)
      if (strEqu(event,eventab[ii].event)) break;
   if (ii == Nevents) return 0;
   
   if (sa_fww != Fww || sa_fhh != Fhh)                                     //  area not valid for image, delete it
      sa_unselect();                                                       //  bugfix     v.11.08
   
   if (! sa_stat) {                                                        //  no area, create one          v.11.07
      cc = Fww * Fhh * sizeof(uint16);                                     //  allocate sa_pixmap[] for new area
      sa_pixmap = (uint16 *) zmalloc(cc,"sa_select");                      //  maps pixels in area
      memset(sa_pixmap,0,cc);
      sa_currseq = sa_Ncurrseq = 0;                                        //  reset selection sequence
      sa_Npixel = sa_blend = sa_calced = Factivearea = 0;
      sa_fww = Fww;                                                        //  valid image size for area    v.11.08
      sa_fhh = Fhh;
      sa_stat = 1;                                                         //  status = edit
      zdialog_stuff(zd,"blendwidth",0);                                    //  init. blend width = 0
   }

   if (ii < 5)                                                             //  one of the edit mode checkboxes
   {
      for (jj = 0; jj < 5; jj++)
      {
         if (jj == ii) {
            zdialog_fetch(zd,eventab[ii].event,kk);                        //  toggle checkbox on/off
            if (kk) {                                                      //  on: set edit mode
               sa_mode = jj+1;
               sa_stat = 1;
               sa_Npixel = sa_blend = sa_calced = Factivearea = 0;
               zdialog_stuff(zd,"blendwidth",0);
               sa_show(1);
            }
            else {                                                         //  off: no edit mode, edit paused
               sa_mode = 0;
               sa_stat = 2;
            }
         }
         else zdialog_stuff(zd,eventab[jj].event,0);                       //  all other checkboxes off
      }
   }

   if (eventab[ii].stat == 2) {
      for (jj = 0; jj < 5; jj++)                                           //  no edit mode active
         zdialog_stuff(zd,eventab[jj].event,0);                            //  all checkboxes off
      sa_stat = 2;                                                         //  edit is paused
   }

   if (strEqu(event,"mouserad"))                                           //  mouse selection radius
      zdialog_fetch(zd,"mouserad",sa_mouseradius);

   if (strEqu(event,"matchcolor"))                                         //    ""  color match enable           v.11.12
      zdialog_fetch(zdsela,"matchcolor",sa_matchcolor);

   if (strEqu(event,"colormatch"))                                         //    ""  color match limit, 0 - 99.9
      zdialog_fetch(zdsela,"colormatch",sa_colormatch);
   
   if (strEqu(event,"searchrange"))                                        //    ""  ""  search range, x radius   v.11.12
      zdialog_fetch(zdsela,"searchrange",sa_searchrange);
   
   if (strEqu(event,"firewall"))                                           //    ""  color match firewall on/off  v.10.11
      zdialog_fetch(zdsela,"firewall",sa_firewall);

   if (strEqu(event,"show")) sa_show(1);                                   //  show area
   if (strEqu(event,"hide")) sa_show(0);                                   //  hide area
   if (strEqu(event,"finish")) sa_finish();                                //  finish (finalize) area
   if (strEqu(event,"unfinish")) sa_unfinish();                            //  unfinish area                v.11.07
   if (strEqu(event,"unselect")) sa_unselect();                            //  unselect area
   if (strEqu(event,"enable")) sa_enable();                                //  enable area
   if (strEqu(event,"disable")) sa_disable();                              //  disable area
   if (strEqu(event,"invert")) sa_invert();                                //  invert area

   if (strEqu(event,"color")) {                                            //  change show area color
      if (sa_pixRGB == &red) sa_pixRGB = &green;
      else if (sa_pixRGB == &green) sa_pixRGB = &black;
      else sa_pixRGB = &red;
      sa_show(1);
   }

   if (strEqu(event,"blendwidth") && Factivearea) {                        //  blend width changed
      sa_edgecalc();                                                       //  do edge calc. if not already
      if (sa_calced && CEF && CEF->zd) {                                   //  edit is active
         zdialog_fetch(zd,"blendwidth",sa_blend);                          //  update sa_blend
         zdialog_send_event(CEF->zd,event);                                //  notify edit dialog
      }
   }                                                                       //  "ignore once" logic removed  v.11.01
   
   if (sa_stat == 1)                                                       //  active edit mode
   {
      if (sa_mode == 1) takeMouse(zd,sa_geom_mousefunc,dragcursor);        //  rectangle                    v.11.03
      if (sa_mode == 2) takeMouse(zd,sa_geom_mousefunc,dragcursor);        //  ellipse
      if (sa_mode == 3) takeMouse(zd,sa_draw_mousefunc,drawcursor);        //  freehand draw
      if (sa_mode == 4) takeMouse(zd,sa_draw_mousefunc,drawcursor);        //  follow edge
      if (sa_mode == 5) takeMouse(zd,sa_mouse_mousefunc,0);                //  mouse select
      if (sa_mode < 3) sa_geom1 = sa_geom2 = 0;                            //  new rectangle or ellipse
      if (sa_mode < 5) paint_toparc(2);                                    //  erase radius circle
   }
   else {                                                                  //  edit paused or done
      freeMouse();                                                         //  disconnect mouse
      gdk_window_set_cursor(drWin->window,null);                           //  normal cursor                v.12.01
   }

   return 0;
}


//  select area mouse function - select a rectangle or ellipse

void sa_geom_mousefunc()                                                   //  new v.11.03
{
   static int  mx1, my1, mx2, my2;
   static int  mdx0, mdy0;
   
   if (sa_stat != 1) return;                                               //  area gone?    v.11.07

   if (sa_currseq > sa_maxseq-2) {
      zmessageACK(mWin,ZTX("exceed %d edits"),sa_maxseq);                  //  cannot continue
      return;
   }

   if (RMclick)                                                            //  right mouse click
   {
      RMclick = 0;
      sa_unselect_pixels();                                                //  remove latest selection
      sa_geom1 = sa_geom2 = 0;
      mwpaint2();
      return;
   }
   
   if (! Mxdrag && ! Mydrag) return;                                       //  v.11.04
   
   if (Mxdown != mdx0 || Mydown != mdy0) {                                 //  new drag initiated
      mdx0 = Mxdown;
      mdy0 = Mydown;
      mx1 = mdx0;                                                          //  drag start, one corner
      my1 = mdy0;
      sa_geom1 = 1;
      sa_geom2 = 0;
      return;
   }

   if (sa_geom2) sa_unselect_pixels();                                     //  remove prior selection
   mx2 = Mxdrag;                                                           //  drag continues, 2nd corner
   my2 = Mydrag;
   sa_geom2 = 1;
   
   sa_nextseq();                                                           //  next sequence number
   
   if (sa_mode == 1)                                                       //  draw rectangle
   {
      sa_draw_line(mx1,my1,mx2,my1);
      sa_draw_line(mx2,my1,mx2,my2);
      sa_draw_line(mx2,my2,mx1,my2);
      sa_draw_line(mx1,my2,mx1,my1);
   }
   
   if (sa_mode == 2)                                                       //  draw ellipse
   {
      double   a, b, a2, b2;
      double   x, y, x2, y2, cx, cy;   
      int      px, py;

      a = abs(mx2 - mx1);                                                  //  ellipse constants from    v.11.04
      b = abs(my2 - my1);                                                  //    enclosing rectangle
      a2 = a * a;
      b2 = b * b;
      cx = mx1;                                                            //  center at drag origin     v.11.04
      cy = my1;
      
      for (y = -b; y < b; y++)                                             //  step through y values
      {
         y2 = y * y;
         x2 = a2 * (1 - y2 / b2);
         x = sqrt(x2);                                                     //  corresp. x values, + and -
         py = y + cy;
         px = cx - x + 0.5;
         sa_draw1pix(px,py);                                               //  draw 2 points on ellipse
         px = cx + x + 0.5;
         sa_draw1pix(px,py);
      }

      for (x = -a; x < a; x++)                                             //  step through x values
      {
         x2 = x * x;
         y2 = b2 * (1 - x2 / a2);
         y = sqrt(y2);                                                     //  corresp. y values, + and -
         px = cx + x;
         py = cy - y + 0.5;
         sa_draw1pix(px,py);                                               //  draw 2 points on ellipse
         py = cy + y + 0.5;
         sa_draw1pix(px,py);
      }
   }
   
   mwpaint2();
   return;
}


//  select area mouse function - freehand draw and follow edge

void sa_draw_mousefunc()
{
   void sa_follow_edge(int mx1, int my1, int mx2, int my2);

   int         mx1, my1, mx2, my2;
   int         npdist, npx, npy;
   int         ii, click, newseq, thresh;
   static int  drag = 0, mdx0, mdy0, mdx1, mdy1;
   
   if (sa_stat != 1) return;                                               //  area gone?    v.11.07

   sa_thresh = 4.0 / Mscale + 1;                                           //  mouse pixel distance threshold
   click = newseq = 0;
   
   if (LMclick || Mxdrag || Mydrag)                                        //  left mouse click or mouse drag
   {
      if (LMclick)                                                         //  left mouse click
      {
         LMclick = 0;
         mx1 = mx2 = Mxclick;                                              //  click position
         my1 = my2 = Myclick;
         newseq++;
         click++;
         drag = 0;
      }
      else                                                                 //  drag motion
      {
         if (Mxdown != mdx0 || Mydown != mdy0) {                           //  new drag initiated
            mdx0 = mdx1 = Mxdown;
            mdy0 = mdy1 = Mydown;
            newseq++;
         }
         mx1 = mdx1;                                                       //  drag start
         my1 = mdy1;
         mx2 = Mxdrag;                                                     //  drag position
         my2 = Mydrag;
         mdx1 = mx2;                                                       //  next drag start
         mdy1 = my2;
         drag++;
         click = 0;
      }
      
      if (Mbutton == 3)                                                    //  right mouse >> erase
      {
         while (true) {
            thresh = sa_thresh;
            npdist = sa_nearpix(mx2,my2,thresh,npx,npy);
            if (! npdist) break;
            ii = npy * Fww + npx;
            sa_pixmap[ii] = 0;
         }
         mwpaint2();
         return;
      }

      if (sa_currseq > sa_maxseq-2) {
         zmessageACK(mWin,ZTX("exceed %d edits"),sa_maxseq);               //  cannot continue
         return;
      }
      
      if (sa_currseq == 0 && newseq)                                       //  1st pixel(s) of 1st sequence
      {
         sa_nextseq();                                                     //  set next (1st) sequence no.  v.10.8
         sa_draw_line(mx1,my1,mx2,my2);                                    //  draw initial pixel or line
         sa_endpx[sa_currseq] = mx2;
         sa_endpy[sa_currseq] = my2;
         return;
      }
      
      if (click) {
         mx1 = sa_endpx[sa_currseq];                                       //  prior sequence end pixel
         my1 = sa_endpy[sa_currseq];                                       //  (before this click)
      }
      
      if (drag) {
         if (newseq) thresh = 2 * sa_thresh;                               //  new drag threshold
         else thresh = 5 * sa_thresh;                                      //  continuation drag threshold
         npx = sa_endpx[sa_currseq];                                       //  distance from prior end pixel
         npy = sa_endpy[sa_currseq];                                       //    (before this drag)
         if (abs(mx1-npx) < thresh && abs(my1-npy) < thresh) {
            mx1 = sa_endpx[sa_currseq];                                    //  if < threshold, connect this
            my1 = sa_endpy[sa_currseq];                                    //    drag to prior drag or click
         }
      }

      if (newseq || drag > 50) {
         sa_nextseq();                                                     //  set next sequence no.     v.10.8
         drag = 1;                                                         //  drag length within sequence
      }
      
      if (sa_mode == 4) sa_follow_edge(mx1,my1,mx2,my2);                   //  follow edge or draw line
      else sa_draw_line(mx1,my1,mx2,my2);                                  //    from end pixel to mouse
      
      sa_endpx[sa_currseq] = mx2;                                          //  set end pixel for this sequence
      sa_endpy[sa_currseq] = my2;
   }

   else if (RMclick)                                                       //  right mouse click
   {
      RMclick = 0;
      sa_unselect_pixels();                                                //  remove latest selection   v.10.8
      mwpaint2();
   }
   
   return;
}


//  Find the nearest drawn pixel within a radius of a given pixel.
//  Returns distance to pixel, or zero if nothing found.
//  Returns 1 for adjacent or diagonally adjacent pixel.

int sa_nearpix(int mx, int my, int rad2, int &npx, int &npy)
{
   int      ii, rad, qx, qy, dx, dy;
   int      mindist, dist;

   npx = npy = 0;
   mindist = (rad2+1) * (rad2+1);

   for (rad = 1; rad <= rad2; rad++)                                       //  seek neighbors within range
   {
      if (rad * rad > mindist) break;                                      //  can stop searching now

      for (qx = mx-rad; qx <= mx+rad; qx++)                                //  search within rad
      for (qy = my-rad; qy <= my+rad; qy++)
      {
         if (qx != mx-rad && qx != mx+rad &&                               //  exclude within rad-1
             qy != my-rad && qy != my+rad) continue;                       //  (already searched)
         if (qx < 0 || qx > Fww-1) continue;
         if (qy < 0 || qy > Fhh-1) continue;
         ii = qy * Fww + qx;
         if (! sa_pixmap[ii]) continue;
         dx = (mx - qx) * (mx - qx);                                       //  found pixel
         dy = (my - qy) * (my - qy);
         dist = dx + dy;                                                   //  distance**2
         if (dist < mindist) {
            mindist = dist;
            npx = qx;                                                      //  save nearest pixel found
            npy = qy;
         }
      }
   }
   
   if (npx + npy) return sqrt(mindist) + 0.5;
   return 0;
}


//  draw a line between two given pixels
//  add all in-line pixels to sa_pixmap[]
  
void sa_draw_line(int px1, int py1, int px2, int py2)
{
   void  sa_draw1pix(int px, int py);
 
   int      pxm, pym;
   double   slope;
   
   if (sa_stat != 1) return;                                               //  area gone?          v.11.07

   if (px1 == px2 && py1 == py2) {                                         //  only one pixel
      sa_draw1pix(px1,py1);
      return;
   }
   
   if (abs(py2 - py1) > abs(px2 - px1)) {
      slope = 1.0 * (px2 - px1) / (py2 - py1);
      if (py2 > py1) {
         for (pym = py1; pym <= py2; pym++) {
            pxm = round(px1 + slope * (pym - py1));
            sa_draw1pix(pxm,pym);
         }
      }
      else {
         for (pym = py1; pym >= py2; pym--) {
            pxm = round(px1 + slope * (pym - py1));
            sa_draw1pix(pxm,pym);
         }
      }
   }
   else {
      slope = 1.0 * (py2 - py1) / (px2 - px1);
      if (px2 > px1) {
         for (pxm = px1; pxm <= px2; pxm++) {
            pym = round(py1 + slope * (pxm - px1));
            sa_draw1pix(pxm,pym);
         }
      }
      else {
         for (pxm = px1; pxm >= px2; pxm--) {
            pym = round(py1 + slope * (pxm - px1));
            sa_draw1pix(pxm,pym);
         }
      }
   }

   return;
}


//  draw one pixel only if not already drawn

void sa_draw1pix(int px, int py)
{
   if (px < 0 || px > Fww-1) return;                                       //  bugfix              v.11.03.1
   if (py < 0 || py > Fhh-1) return;
   int ii = Fww * py + px;
   if (sa_pixmap[ii]) return;                                              //  map to curr. selection sequence
   sa_pixmap[ii] = sa_currseq;
   sa_Ncurrseq++;                                                          //  v.10.8
   draw_fat_pixel(px,py,sa_pixRGB);                                        //  v.11.04
   return;
}


//  Find series of edge pixels from px1/py1 to px2/py2 and connect them together.

void sa_follow_edge(int px1, int py1, int px2, int py2)                    //  v.10.8
{
   double   sa_get_contrast(int px, int py);
   
   double   px3, py3, px4, py4, px5, py5, px6, py6;
   double   dx, dy, dist, contrast, maxcontrast;
   
   if (sa_stat != 1) return;                                               //  area gone?       v.11.07
   
   px3 = px1;                                                              //  p3 progresses from p1 to p2
   py3 = py1;
   
   while (true)
   {
      dx = px2 - px3;
      dy = py2 - py3;
      
      dist = sqrt(dx * dx + dy * dy);                                      //  last segment
      if (dist < 3) break;
      
      px4 = px3 + dx / dist;                                               //  p4 = p3 moved toward p2
      py4 = py3 + dy / dist;
      
      maxcontrast = 0;
      px6 = px4;
      py6 = py4;

      for (int ii = -2; ii <= +2; ii++)                                    //  p5 points are in a line through p4
      {                                                                    //    and perpendicular to p4 - p2
         px5 = px4 + ii * dy / dist;
         py5 = py4 - ii * dx / dist;
         contrast = sa_get_contrast(px5,py5);
         contrast *= (7 - abs(ii));                                        //  favor points closer together   v.10.9
         if (contrast > maxcontrast) {
            px6 = px5;                                                     //  p6 = highest contrast point in p5
            py6 = py5;
            maxcontrast = contrast;
         }
      }
      
      sa_draw_line(px3,py3,px6,py6);                                       //  draw p3 to p6

      px3 = px6;                                                           //  next p3
      py3 = py6;
   }
   
   sa_draw_line(px3,py3,px2,py2);
   return;
}


//  Find max. contrast between neighbors on opposite sides of given pixel

double sa_get_contrast(int px, int py)                                     //  v.10.8
{
   int         map[4][2] = { {1, 0}, {1, 1}, {0, 1}, {-1, 1} };
   int         ii, qx, qy;
   uint16      *pix1, *pix2;
   double      red, green, blue;
   double      contrast, maxcontrast = 0;
   double      f65k = 1.0 / 65535.0;
   
   if (px < 1 || px > Fww-2) return 0;                                     //  avoid edge pixels
   if (py < 1 || py > Fhh-2) return 0;
   
   for (ii = 0; ii < 4; ii++)                                              //  compare pixels around target
   {                                                                       //  e.g. (px-1,py) to (px+1,py)
      qx = map[ii][0];
      qy = map[ii][1];
      pix1 = PXMpix(Fpxm16,px+qx,py+qy);
      pix2 = PXMpix(Fpxm16,px-qx,py-qy);
      red = f65k * abs(pix1[0] - pix2[0]);
      green = f65k * abs(pix1[1] - pix2[1]);
      blue = f65k * abs(pix1[2] - pix2[2]);
      contrast = (1.0 - red) * (1.0 - green) * (1.0 - blue);               //  no contrast = 1.0
      contrast = 1.0 - contrast;                                           //  max. contrast = 1.0
      if (contrast > maxcontrast) maxcontrast = contrast;
   }
   
   return maxcontrast;
}


//  mouse function - select area by mouse and matching color range
//  if left click or drag, find and select matching pixels
//  if right click, unselect last selection
//  if right drag, find and unselect matching pixels

void sa_mouse_mousefunc()                                                  //  revised v.11.12
{
   void sa_find_color_pixels(int select);

   static int  mxdown, mydown, drag = 0;
   
   if (sa_stat != 1) return;                                               //  area gone?          v.11.07

   sa_radius = sa_mouseradius;                                             //  mouse radius
   sa_radius2 = sa_radius * sa_radius;                                     //  squared

   sa_radius3 = sa_radius * sa_searchrange;                                //  search range (* mouse radius)
   sa_radius3 = sa_radius3 * sa_radius3;                                   //  squared

   toparcx = Mxposn - sa_radius;                                           //  draw radius outline circle
   toparcy = Myposn - sa_radius;
   toparcw = toparch = 2 * sa_radius;
   Ftoparc = 1;
   paint_toparc(3);

   if (RMclick) {                                                          //  right mouse click
      RMclick = 0;
      sa_unselect_pixels();                                                //  remove latest selection   v.10.8
      mwpaint2();
      return;
   }

   if ((Mxdrag || Mydrag) && Mbutton == 3) {                               //  right drag, find and unselect pixels
      sa_mousex = Mxdrag;
      sa_mousey = Mydrag;
      Mxdrag = Mydrag = 0;
      sa_find_color_pixels(0);
      mwpaint2();
      return;
   }

   sa_mousex = sa_mousey = 0;

   if (LMclick) {                                                          //  left mouse click
      sa_mousex = Mxclick;
      sa_mousey = Myclick;
      LMclick = 0;
      sa_nextseq();                                                        //  set next sequence no.     v.10.8
      drag = 1;                                                            //  reset drag counter
   }

   if ((Mxdrag || Mydrag) && Mbutton == 1) {                               //  left drag, select matching colors
      sa_mousex = Mxdrag;
      sa_mousey = Mydrag;
      Mxdrag = Mydrag = 0;

      if (Mxdown != mxdown || Mydown != mydown) {                          //  detect if new drag started
         mxdown = Mxdown;
         mydown = Mydown;
         sa_nextseq();                                                     //  set next sequence no.     v.10.8
         drag = 1;                                                         //  reset drag counter
      }
      else if (++drag > 30) {                                              //  limit work per sequence no.
         sa_nextseq();                                                     //  set next sequence no.     v.10.8
         drag = 1;                                                         //  reset drag counter
      }
   }

   if (sa_mousex || sa_mousey) {
      sa_find_color_pixels(1);                                             //  find and select pixels
      mwpaint2();
   }

   return;
}


//  find all contiguous pixels within the specified range of colors to match
//  select or unselect the matching pixels

void sa_find_color_pixels(int select)                                      //  revised       v.11.12
{
   int         ii, kk, cc, px, py, rx, ry, rad2;
   int         ppx, ppy, npx, npy;
   uint16      *matchpix;
   double      match1, match2, ff = 1.0 / 65536.0;
   double      dred, dgreen, dblue;
   char        direc;
   
   px = sa_mousex;
   py = sa_mousey;
   if (px < 0 || px > Fww-1) return;                                       //  mouse outside image
   if (py < 0 || py > Fhh-1) return;

   sa_Ncurrseq = 0;                                                        //  count newly selected pixels

   for (rx = -sa_radius; rx <= sa_radius; rx++)                            //  loop every pixel in radius of mouse
   for (ry = -sa_radius; ry <= sa_radius; ry++)                            //  v.11.12
   {
      rad2 = rx * rx + ry * ry;
      if (rad2 > sa_radius2) continue;                                     //  outside radius
      px = sa_mousex + rx;
      py = sa_mousey + ry;
      if (px < 0 || px > Fww-1) continue;                                  //  off the image edge
      if (py < 0 || py > Fhh-1) continue;

      ii = Fww * py + px;
      
      if (select) {                                                        //  select pixel
         if (sa_pixmap[ii]) continue;
         sa_pixmap[ii] = sa_currseq;                                       //  map pixel to current sequence
         sa_Ncurrseq++;                                                    //  current sequence pixel count
      }
      else sa_pixmap[ii] = 0;                                              //  unselect
   }
   
   if (! sa_matchcolor) return;                                            //  no color matching, done      v.11.12

   match1 = 0.01 * sa_colormatch;                                          //  color match level, 0.01 to 1.0
   sa_Nmatch = 0;                                                          //  match color count

   for (rx = -sa_radius; rx <= sa_radius; rx++)                            //  loop every pixel in radius of mouse
   for (ry = -sa_radius; ry <= sa_radius; ry++)
   {
      rad2 = rx * rx + ry * ry;
      if (rad2 > sa_radius2) continue;                                     //  outside radius
      px = sa_mousex + rx;
      py = sa_mousey + ry;
      if (px < 0 || px > Fww-1) continue;                                  //  off the image edge
      if (py < 0 || py > Fhh-1) continue;
      
      matchpix = PXMpix(Fpxm16,px,py);                                     //  get color at mouse position
      
      for (ii = 0; ii < sa_Nmatch; ii++)                                   //  see if color is already included
      {
         dred =   ff * abs(sa_matchRGB[ii][0] - matchpix[0]);              //  0 = perfect match
         dgreen = ff * abs(sa_matchRGB[ii][1] - matchpix[1]);
         dblue =  ff * abs(sa_matchRGB[ii][2] - matchpix[2]);
         match2 = (1.0 - dred) * (1.0 - dgreen) * (1.0 - dblue);           //  1 = perfect match
         if (match2 >= match1) break;                                      //  matches close enough
      }
      
      if (ii == sa_Nmatch) {                                               //  no close match 
         sa_matchRGB[ii][0] = matchpix[0];                                 //  add new match color to list
         sa_matchRGB[ii][1] = matchpix[1];
         sa_matchRGB[ii][2] = matchpix[2];
         sa_Nmatch++;
         if (sa_Nmatch == 1000) goto startsearch;                          //  capacity limit
      }
   }

startsearch:

   if (sa_stackdirec) zfree(sa_stackdirec);                                //  get memory
   if (sa_stackii) zfree(sa_stackii);
   if (sa_pixselc) zfree(sa_pixselc);   
   cc = Fww * Fhh;   
   sa_stackdirec = zmalloc(cc,"sa_color");
   sa_stackii = (int *) zmalloc(4*cc,"sa_color");
   sa_maxstack = cc;
   sa_Nstack = 0;
   sa_pixselc = zmalloc(cc,"sa_color");
   memset(sa_pixselc,0,cc);

   px = sa_mousex;                                                         //  pixel at mouse
   py = sa_mousey;
   ii = Fww * py + px;
   sa_pixselc[ii] = 1;                                                     //  pixel is in current selection

   if (! sa_pixmap[ii]) {                                                  //  if selected for the first time,  v.10.12
      sa_pixmap[ii] = sa_currseq;                                          //    map pixel to current sequence
      sa_Ncurrseq++;                                                       //  current sequence pixel count
   }

   sa_stackii[0] = ii;                                                     //  put 1st pixel into stack
   sa_stackdirec[0] = 'a';                                                 //  direction = ahead               v.11.04
   sa_Nstack = 1;                                                          //  stack count

   while (sa_Nstack)
   {
      kk = sa_Nstack - 1;                                                  //  get last pixel in stack
      ii = sa_stackii[kk];
      direc = sa_stackdirec[kk];
      
      py = ii / Fww;                                                       //  reconstruct px, py
      px = ii - Fww * py;

      if (direc == 'x') {                                                  //  no neighbors left to check
         sa_Nstack--;
         continue;
      }
      
      if (sa_Nstack > 1) {
         ii = sa_Nstack - 2;                                               //  get prior pixel in stack
         ii = sa_stackii[ii];
         ppy = ii / Fww;
         ppx = ii - ppy * Fww;
      }
      else {
         ppx = px - 1;                                                     //  if only one, assume prior = left
         ppy = py;
      }

      if (direc == 'a') {                                                  //  next ahead pixel             v.11.04
         npx = px + px - ppx;
         npy = py + py - ppy;
         sa_stackdirec[kk] = 'r';                                          //  next search direction
      }

      else if (direc == 'r') {                                             //  next right pixel             v.11.04
         npx = px + py - ppy;
         npy = py + px - ppx;
         sa_stackdirec[kk] = 'l';
      }

      else { /*  direc = 'l'  */                                           //  next left pixel              v.11.04
         npx = px + ppy - py;
         npy = py + ppx - px;
         sa_stackdirec[kk] = 'x';
      }

      if (npx < 0 || npx > Fww-1) continue;                                //  pixel off the edge           v.11.04
      if (npy < 0 || npy > Fhh-1) continue;
      
      rx = npx - Mxposn;                                                   //  limit search to 
      ry = npy - Myposn;                                                   //    mouse radius * search range
      rad2 = rx * rx + ry * ry;
      if (rad2 > sa_radius3) continue;                                     //  v.11.12

      ii = npy * Fww + npx;
      
      if (sa_pixselc[ii]) continue;                                        //  already in current selection

      if (select && sa_firewall && sa_pixmap[ii])                          //  aleady selected, firewall mode
         if (rad2 > sa_radius2) continue;                                  //  and pixel outside mouse radius

      matchpix = PXMpix(Fpxm16,npx,npy);
      for (kk = 0; kk < sa_Nmatch; kk++) {                                 //  compare pixel RGB to match colors
         dred =   ff * abs(sa_matchRGB[kk][0] - matchpix[0]);              //  v.10.8
         dgreen = ff * abs(sa_matchRGB[kk][1] - matchpix[1]);
         dblue =  ff * abs(sa_matchRGB[kk][2] - matchpix[2]);
         match2 = (1.0 - dred) * (1.0 - dgreen) * (1.0 - dblue);           //  1 = perfect match
         if (match2 >= match1) break;                                      //  within range
      }
      if (kk == sa_Nmatch) continue;                                       //  not within range of any color

      sa_pixselc[ii] = 1;                                                  //  map pixel to current selection

      if (select) {                                                        //  select mode
         if (! sa_pixmap[ii]) {                                            //  if selected for the first time,
            sa_pixmap[ii] = sa_currseq;                                    //    map pixel to current sequence
            sa_Ncurrseq++;                                                 //  current sequence pixel count
         }
      }
      else sa_pixmap[ii] = 0;                                              //  unselect mode                v.11.12

      if (sa_Nstack == sa_maxstack) continue;                              //  stack is full
      kk = sa_Nstack++;                                                    //  push pixel into stack
      sa_stackii[kk] = ii;
      sa_stackdirec[kk] = 'a';                                             //  direction = ahead            v.11.04
   }

   return;
}


//  set next sequence number for pixels about to be selected

void sa_nextseq()                                                          //  v.10.8
{
   if (sa_Ncurrseq > 0) sa_currseq++;                                      //  increase only if some pixels mapped
   if (sa_currseq < sa_initseq) sa_currseq = sa_initseq;                   //  start at initial value
   sa_Ncurrseq = 0;
   return;
}


//  un-select all pixels mapped to current sequence number
//  reduce sequence number and set pixel count = 1

void sa_unselect_pixels()
{
   if (sa_stat != 1) return;                                               //  area gone?             v.11.07
   if (! sa_currseq) return;                                               //  no pixels mapped

   for (int ii = 0; ii < Fww * Fhh; ii++)
      if (sa_pixmap[ii] == sa_currseq) sa_pixmap[ii] = 0;                  //  unmap current selection
   
   if (sa_currseq > sa_initseq) {                                          //  reduce sequence no.    v.10.8
      sa_currseq--;
      sa_Ncurrseq = 1;                                                     //  unknown but > 0
   }
   else  sa_Ncurrseq = 0;                                                  //  initial sequence no. reached
   
   return;
}


//  Finish select area - map pixels enclosed by edge pixels 
//  into sa_pixmap[ii]: 0/1/2 = outside/edge/inside (ii=py*Fww+px)
//  total count = sa_Npixel

zdialog  *safinzd = 0;

void sa_finish()                                                           //  overhauled    v.11.02
{
   void sa_finish_mousefunc();
   int  sa_finish_dialog_event(zdialog *, cchar *event);

   cchar  *fmess = ZTX("Click one time inside each enclosed area \n"
                       "(possible gaps in the outline will be found). \n"
                       "Press F1 for help.");

   GtkWidget   *pwin = zdialog_widget(zdsela,"dialog");
   int         ii, cc, px, py;
   
   if (! sa_stat) return;                                                  //  no area?                  v.11.07
   if (sa_fww != Fww || sa_fhh != Fhh) return;                             //  area not valid for image  v.11.08
   if (sa_mode == 7) return;                                               //  a whole image area

   sa_Npixel = Factivearea = 0;                                            //  area disabled, unfinished
   sa_finOK = sa_finhole = 0;                                              //  count areas OK and with holes
   sa_show(1);                                                             //  show outline

   sa_minx = Fww;
   sa_maxx = 0;
   sa_miny = Fhh;
   sa_maxy = 0;

   for (ii = 0; ii < Fww * Fhh; ii++)                                      //  get enclosing rectangle
   {                                                                       //    for selected area(s)
      if (! sa_pixmap[ii]) continue;
      py = ii / Fww;
      px = ii - Fww * py;
      if (px >= sa_maxx) sa_maxx = px+1;                                   //  like Fww, sa_maxx = last + 1
      if (px < sa_minx) sa_minx = px;
      if (py >= sa_maxy) sa_maxy = py+1;
      if (py < sa_miny) sa_miny = py;
   }

   sa_map_pixels();                                                        //  map edge and interior pixels
   if (sa_Npixel < 10) return;                                             //  ridiculous
   
   sa_minx -= 10;                                                          //  add margins where possible
   if (sa_minx < 0) sa_minx = 0;
   sa_maxx += 10;
   if (sa_maxx > Fww) sa_maxx = Fww;
   sa_miny -= 10;
   if (sa_miny < 0) sa_miny = 0;
   sa_maxy += 10;
   if (sa_maxy > Fhh) sa_maxy = Fhh;
   
   for (py = sa_miny; py < sa_maxy; py++)                                  //  loop pixels in rectangle     v.10.12
   for (px = sa_minx; px < sa_maxx; px++)
   {
      ii = Fww * py + px;                                                  //  eliminate interior pixels
      if (sa_pixmap[ii] == 2) sa_pixmap[ii] = 0;                           //    from prior finish functions
   }

   cc = (sa_maxx-sa_minx) * (sa_maxy-sa_miny);                             //  allocate stack memory
   if (sa_stackdirec) zfree(sa_stackdirec);
   sa_stackdirec = zmalloc(cc,"sa_finish");
   if (sa_stackii) zfree(sa_stackii);
   sa_stackii = (int *) zmalloc(cc * 4,"sa_finish");
   sa_maxstack = cc;
   
   safinzd = zdialog_new(ZTX("finish area"),pwin,Bdone,Bcancel,null);      //  dialog for user to click inside
   zdialog_add_widget(safinzd,"label","fmess","dialog",fmess,"space=5");   //    each enclosed area
   zdialog_add_widget(safinzd,"hbox","hbstat","dialog");
   zdialog_add_widget(safinzd,"label","labstat","hbstat","status:","space=5");
   zdialog_add_widget(safinzd,"label","statmess","hbstat",0);
   
   takeMouse(safinzd,sa_finish_mousefunc,dragcursor);                      //  connect mouse function       v.11.03

   zdialog_run(safinzd,sa_finish_dialog_event,"save");                     //  run dialog, parallel         v.11.07
   zdialog_wait(safinzd);
   return;
}


//  mouse function - get user clicks and perform pixel searches

void sa_finish_mousefunc()                                                 //  overhauled    v.11.02
{
   int         px, py, ii, kk;
   int         ppx, ppy, npx, npy;
   int         finhole = 0;
   char        direc;
   
   if (! LMclick) return;
   LMclick = 0;
   
   if (! sa_stat) return;                                                  //  area gone?                   v.11.07

   ii = Fww * Myclick + Mxclick;                                           //  seed pixel from mouse click
   if (sa_pixmap[ii] == 1) return;                                         //  ignore if edge pixel         v.11.07
   sa_pixmap[ii] = 2;                                                      //  map the pixel, inside area
   sa_stackii[0] = ii;                                                     //  put seed pixel into stack
   sa_stackdirec[0] = 'a';                                                 //  direction = ahead            v.11.04
   sa_Nstack = 1;                                                          //  stack count
   
   zdialog_stuff(safinzd,"statmess",ZTX("searching"));
   zmainloop();
   zsleep(0.2);
   
   Ffuncbusy++;

   while (sa_Nstack)                                                       //  find all pixels outside enclosed area(s)
   {
      kk = sa_Nstack - 1;                                                  //  get last pixel in stack
      ii = sa_stackii[kk];
      direc = sa_stackdirec[kk];
      
      py = ii / Fww;                                                       //  reconstruct px, py
      px = ii - Fww * py;

      if (px < sa_minx || px >= sa_maxx || py < sa_miny || py >= sa_maxy)  //  moved v.11.08
      {
         finhole++;                                                        //  ran off the edge, seed pixel was
         break;                                                            //    not inside area or area has a hole
      }

      if (direc == 'x') {                                                  //  no neighbors left to check
         sa_Nstack--;
         continue;
      }

      if (sa_Nstack > 1) {
         ii = sa_Nstack - 2;                                               //  get prior pixel in stack
         ii = sa_stackii[ii];
         ppy = ii / Fww;
         ppx = ii - ppy * Fww;
      }
      else {
         ppx = px - 1;                                                     //  if only one, assume prior = left
         ppy = py;
      }
      
      if (direc == 'a') {                                                  //  next ahead pixel             v.11.04
         npx = px + px - ppx;
         npy = py + py - ppy;
         sa_stackdirec[kk] = 'r';                                          //  next search direction
      }

      else if (direc == 'r') {                                             //  next right pixel             v.11.04
         npx = px + py - ppy;
         npy = py + px - ppx;
         sa_stackdirec[kk] = 'l';
      }

      else { /*  direc = 'l'  */                                           //  next left pixel              v.11.04
         npx = px + ppy - py;
         npy = py + ppx - px;
         sa_stackdirec[kk] = 'x';
      }
      
      if (npx < 0 || npx > Fww-1) continue;                                //  next pixel off the image edge
      if (npy < 0 || npy > Fhh-1) continue;                                //  bugfix        v.11.04
      
      ii = npy * Fww + npx;
      if (sa_pixmap[ii]) continue;                                         //  pixel already mapped

      sa_pixmap[ii] = 2;                                                   //  map the pixel, inside area
      kk = sa_Nstack++;                                                    //  put pixel into stack
      sa_stackii[kk] = ii;
      sa_stackdirec[kk] = 'a';                                             //  direction = ahead            v.11.04
      draw_pixel(npx,npy,sa_pixRGB);                                       //  color mapped pixels
      zmainloop(10);                                                       //  let window  update           v.11.07
   }

   Ffuncbusy--;

   if (finhole) {                                                          //  this area has a hole
      zdialog_stuff(safinzd,"statmess",ZTX("outline has a gap"));
      sa_finhole++;
   }
   else {
      zdialog_stuff(safinzd,"statmess",ZTX("success"));                    //  all pixels found and mapped
      sa_finOK++;
   }
   return;
}


//  dialog event and completion callback function

int sa_finish_dialog_event(zdialog *zd, cchar *event)
{
   int         zstat;
   
   zstat = zd->zstat;
   if (! zstat) return 0;

   freeMouse();                                                            //  disconnect mouse
   zdialog_free(safinzd);                                                  //  kill dialog

   if (! sa_stat) return 0;                                                //  area gone?       v.11.07

   if (zstat != 1 || sa_finOK == 0 || sa_finhole) {                        //  user cancel or pixel search failure
      sa_unfinish();                                                       //  unmap interior pixels, set edit mode
      return 0;                                                            //  v.11.07
   }

   sa_map_pixels();                                                        //  count pixels, map interior pixels

   sa_stat = 3;                                                            //  area is finished
   Factivearea = 1;                                                        //  area is active by default
   areanumber++;                                                           //  next sequential number
   sa_calced = sa_blend = 0;                                               //  edge calculation is missing
   if (zdsela) zdialog_stuff(zdsela,"blendwidth",0);                       //  v.11.12
   mwpaint2();
   return 0;
}


//  Finish select area automatically when the 
//  interior selected pixels are already known.

void sa_finish_auto()
{
   int      ii, px, py;

   if (! sa_stat) return;                                                  //  no area?         v.11.07
   if (sa_fww != Fww || sa_fhh != Fhh) return;                             //  area not valid for image  v.11.08

   sa_Npixel = Factivearea = 0;                                            //  area disabled, unfinished
   
   sa_minx = Fww;
   sa_maxx = 0;
   sa_miny = Fhh;
   sa_maxy = 0;

   for (ii = 0; ii < Fww * Fhh; ii++)                                      //  get enclosing rectangle
   {                                                                       //    for selected area
      if (! sa_pixmap[ii]) continue;
      py = ii / Fww;
      px = ii - Fww * py;
      if (px >= sa_maxx) sa_maxx = px+1;                                   //  like Fww, sa_maxx = last + 1
      if (px < sa_minx) sa_minx = px;
      if (py >= sa_maxy) sa_maxy = py+1;
      if (py < sa_miny) sa_miny = py;
   }

   sa_minx -= 10;                                                          //  add margins where possible
   if (sa_minx < 0) sa_minx = 0;
   sa_maxx += 10;
   if (sa_maxx > Fww) sa_maxx = Fww;
   sa_miny -= 10;
   if (sa_miny < 0) sa_miny = 0;
   sa_maxy += 10;
   if (sa_maxy > Fhh) sa_maxy = Fhh;
   
   sa_map_pixels();                                                        //  count pixels, map interior pixels
   
   sa_stat = 3;                                                            //  area is finished
   Factivearea = 1;                                                        //  area is active by default
   areanumber++;                                                           //  next sequential number
   sa_calced = sa_blend = 0;                                               //  edge calculation is missing
   mwpaint2();
   return;
}


//  private function
//  map edge and interior pixels (sa_pixmap[*] = 1 or 2)
//  set sa_Npixel = total pixel count

void sa_map_pixels()                                                       //  v.11.07
{
   int      npix, px, py, ii, kk;

   if (! sa_stat) return;                                                  //  no area?

   npix = 0;

   for (py = sa_miny; py < sa_maxy; py++)                                  //  find edge pixels
   for (px = sa_minx; px < sa_maxx; px++)
   {
      ii = py * Fww + px;
      if (! sa_pixmap[ii]) continue;
      npix++;
      
      if (px == 0 || px == Fww-1 || py == 0 || py == Fhh-1)                //  edge of image
         goto edgepix;

      if (! sa_pixmap[ii-1] || ! sa_pixmap[ii+1]) goto edgepix;            //  check 8 neighbor pixels
      kk = ii - Fww;
      if (! sa_pixmap[kk] || ! sa_pixmap[kk-1] || ! sa_pixmap[kk+1]) goto edgepix;
      kk = ii + Fww;
      if (! sa_pixmap[kk] || ! sa_pixmap[kk-1] || ! sa_pixmap[kk+1]) goto edgepix;

      sa_pixmap[ii] = 2;                                                   //  interior pixel
      continue;

   edgepix:
      sa_pixmap[ii] = 1;                                                   //  edge pixel
   }

   sa_Npixel = npix;                                                       //  total pixel count
   return;
}


//  unfinish an area - unmap interior pixels and put back in edit mode

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

   if (! sa_stat) return;                                                  //  no area?

   for (py = sa_miny; py < sa_maxy; py++)                                  //  loop pixels in rectangle
   for (px = sa_minx; px < sa_maxx; px++)
   {
      ii = py * Fww + px;                                                  //  clear interior pixels found
      if (sa_pixmap[ii] == 2) sa_pixmap[ii] = 0;                           //    by finish search function
   }

   sa_stat = 1;                                                            //  resume edit mode
   Factivearea = 0;
   sa_calced = sa_blend = 0;
   if (zdsela) zdialog_stuff(zdsela,"blendwidth",0);                       //  v.11.12
   mwpaint2();
   return;
}


//  menu function for show, hide, enable, disable, invert, unselect
//  (also implemented as buttons in select area dialog)

void m_select_show(GtkWidget *, cchar *menu)
{
   zfuncs::F1_help_topic = "area_show_hide";
   sa_show(1);
   return;
}


void m_select_hide(GtkWidget *, cchar *menu)
{
   zfuncs::F1_help_topic = "area_show_hide";
   sa_show(0);
   return;
}


void m_select_enable(GtkWidget *, cchar *menu)
{
   zfuncs::F1_help_topic = "area_enable_disable";
   sa_enable();
   return;
}


void m_select_disable(GtkWidget *, cchar *menu)
{
   zfuncs::F1_help_topic = "area_enable_disable";
   sa_disable();
   return;
}


void m_select_invert(GtkWidget *, cchar *menu)
{
   zfuncs::F1_help_topic = "area_invert";
   sa_invert();
   return;
}


void m_select_unselect(GtkWidget *, cchar *menu)                           //  delete the area
{
   zfuncs::F1_help_topic = "area_unselect";
   sa_unselect();
   return;
}


//  show or hide outline of select area
//  also called from mwpaint1() if Fshowarea = 1

void sa_show(int flag)
{
   int      px, py, ii, kk;
   
   if (! sa_stat) return;                                                  //  no area
   if (sa_fww != Fww || sa_fhh != Fhh) return;                             //  area not valid for image
   if (sa_mode == 7) return;                                               //  a whole image area
   if (Fpreview) return;                                                   //  preview mode, area ignored

   Fshowarea = flag;                                                       //  flag for mwpaint1()

   if (! flag) {
      mwpaint2();                                                          //  erase area outline     v.10.8
      return;
   }
   
   for (py = 0; py < Fhh; py++)                                            //  find pixels in area    bugfix  v.10.9
   for (px = 0; px < Fww; px++)
   {
      ii = py * Fww + px;
      if (! sa_pixmap[ii]) continue;                                       //  outside of area

      if (px == 0 || px == Fww-1 || py == 0 || py == Fhh-1)                //  edge of image
         goto edgepix;

      if (! sa_pixmap[ii-1] || ! sa_pixmap[ii+1]) goto edgepix;            //  check 8 neighbor pixels
      kk = ii - Fww;
      if (! sa_pixmap[kk] || ! sa_pixmap[kk-1] || ! sa_pixmap[kk+1]) goto edgepix;
      kk = ii + Fww;
      if (! sa_pixmap[kk] || ! sa_pixmap[kk-1] || ! sa_pixmap[kk+1]) goto edgepix;
      continue;

   edgepix:
      draw_fat_pixel(px,py,sa_pixRGB);                                     //  draw fat pixels           v.11.04
   }

   return;
}


//  enable select area that was disabled

void sa_enable()
{
   if (sa_fww != Fww || sa_fhh != Fhh) return;                             //  area not valid for image     v.11.08

   if (sa_stat != 3) {
      zmessageACK(mWin,ZTX("the area is not finished"));                   //  v.11.08
      return;                                                              //  v.11.06.1
   }

   Factivearea = 1;
   areanumber++;                                                           //  next sequential number
   sa_show(1);                                                             //  v.10.11
   return;
}


//  disable select area

void sa_disable()
{
   Factivearea = 0;                                                        //  v.9.7
   sa_show(0);                                                             //  v.10.11
   return;
}


//  invert a selected area

void sa_invert()
{
   int      ii, jj, px, py, npix;

   if (sa_fww != Fww || sa_fhh != Fhh) return;                             //  area not valid for image     v.11.08

   if (sa_stat != 3) {                                                     //  v.11.06.1
      zmessageACK(mWin,ZTX("the area is not finished"));
      return;
   }

   if (sa_mode == 7) return;                                               //  a whole image area
   
   npix = 0;

   for (py = 0; py < Fhh; py++)                                            //  loop all pixels
   for (px = 0; px < Fww; px++)
   {
      ii = py * Fww + px;
      jj = sa_pixmap[ii];                                                  //  0/1/2+ = outside/edge/inside

      if (px == 0 || px == Fww-1 || py == 0 || py == Fhh-1)                //  pixel on image edge    v.10.12
      {
         if (jj == 0) {
            sa_pixmap[ii] = 1;                                             //  outside pixel >> edge pixel
            npix++;                                                        //  count
         }
         else sa_pixmap[ii] = 0;                                           //  edge pixel >> outside
         continue;
      }

      if (jj > 1) sa_pixmap[ii] = 0;                                       //  inside pixel (2+) >> outside (0)
      else {
         sa_pixmap[ii] = 2 - jj;                                           //  edge/outside (1/0) >> edge/inside (1/2)
         npix++;                                                           //  count
      }
   }

   sa_Npixel = npix;                                                       //  new select area pixel count
   sa_calced = sa_blend = 0;                                               //  edge calculation missing
   if (zdsela) zdialog_stuff(zdsela,"blendwidth",0);                       //  reset blend width               v.11.01
   
   sa_minx = Fww;                                                          //  new enclosing rectangle    bugfix v.11.01
   sa_maxx = 0;
   sa_miny = Fhh;
   sa_maxy = 0;

   for (ii = 0; ii < Fww * Fhh; ii++)
   {
      if (! sa_pixmap[ii]) continue;
      py = ii / Fww;
      px = ii - Fww * py;
      if (px >= sa_maxx) sa_maxx = px + 1;
      if (px < sa_minx) sa_minx = px;
      if (py >= sa_maxy) sa_maxy = py + 1;
      if (py < sa_miny) sa_miny = py;
   }

   return;
}


//  unselect current area (delete the area)

void sa_unselect()
{
   sa_stat = sa_Npixel = sa_blend = sa_calced = Factivearea = 0;
   sa_currseq = sa_Ncurrseq = 0;
   sa_fww = sa_fhh = 0;                                                    //  v.11.08
   if (sa_pixmap) zfree(sa_pixmap);
   if (sa_stackii) zfree(sa_stackii);
   if (sa_stackdirec) zfree(sa_stackdirec);
   if (sa_pixselc) zfree(sa_pixselc);
   sa_pixmap = 0;
   sa_stackii = 0;
   sa_stackdirec = 0;
   sa_pixselc = 0;
   mwpaint2();
   return;
}


//  compute distance from all pixels in area to nearest edge
//  output: sa_pixmap[*] = 0/1/2+ = outside, edge, inside distance from edge

namespace sa_edgecalc_names
{
   uint16      *sa_edgepx, *sa_edgepy, *sa_edgedist;
   int         sa_Nedge;
}

void sa_edgecalc()
{
   using namespace sa_edgecalc_names;
   
   int    edgecalc_dialog_event(zdialog*, cchar *event);
   void * edgecalc_thread(void *);

   int         ii, nn, cc, px, py;
   zdialog     *zd = 0;
   cchar       *zectext = ZTX("Edge calculation in progress");
   
   if (! sa_stat) return;                                                  //  area gone?                   v.11.07
   if (sa_mode == 7) return;                                               //  a whole image area

   if (sa_calced) return;                                                  //  done already
   if (! Factivearea) sa_finish();                                         //  finish if needed
   if (! Factivearea) return;                                              //  no finished area
   
   zd = zdialog_new(ZTX("Area Edge Calc"),mWin,Bcancel,null);              //  start dialog for user cancel
   zdialog_add_widget(zd,"label","lab1","dialog",zectext,"space=10");
   zdialog_run(zd,edgecalc_dialog_event);
   Fkillfunc = 0;

   cc = Fww * Fhh * sizeof(uint16);                                        //  allocate memory for calculations
   sa_edgedist = (uint16 *) zmalloc(cc,"sa_edgecalc");
   memset(sa_edgedist,0,cc);
   
   for (ii = nn = 0; ii < Fww * Fhh; ii++)                                 //  count edge pixels in select area
      if (sa_pixmap[ii] == 1) nn++;
   
   cc = nn * sizeof(uint16);
   sa_edgepx = (uint16 *) zmalloc(cc,"sa_edgecalc");                       //  allocate memory
   sa_edgepy = (uint16 *) zmalloc(cc,"sa_edgecalc");

   for (ii = nn = 0; ii < Fww * Fhh; ii++)                                 //  build list of edge pixels
   {                                                                       //  v.9.6
      if (sa_pixmap[ii] != 1) continue;
      py = ii / Fww;
      px = ii - py * Fww;
      if (px < 3 || px > Fww-4) continue;                                  //  omit pixels < 3 from image edge
      if (py < 3 || py > Fhh-4) continue;
      sa_edgepx[nn] = px;
      sa_edgepy[nn] = py;
      nn++;
   }

   sa_Nedge = nn;
   SB_goal = sa_Npixel;
   SB_done = 0;
   
   for (ii = 0; ii < Nwt; ii++)                                            //  start worker threads to calculate
      start_wthread(edgecalc_thread,&wtnx[ii]);                            //    sa_pixmap[] edge distances
   wait_wthreads();                                                        //  wait for completion

   SB_goal = 0;
   
   if (! Fkillfunc) {                                                      //  v.11.07
      for (ii = 0; ii < Fww * Fhh; ii++)                                   //  copy data from sa_edgedist[]
      {                                                                    //    to sa_pixmap[]
         if (sa_pixmap[ii] < 2) continue;                                  //  skip outside and edge pixels
         sa_pixmap[ii] = sa_edgedist[ii];                                  //  interior pixel edge distance
      }
   }

   zdialog_free(zd);                                                       //  kill dialog
   zfree(sa_edgedist);                                                     //  free memory
   zfree(sa_edgepx);
   zfree(sa_edgepy);

   if (Fkillfunc) {
      Fkillfunc = 0;
      sa_calced = 0;
      if (zdsela) zdialog_stuff(zdsela,"blendwidth",0);                    //  reset blend width    v.11.01
   }

   sa_calced = 1;                                                          //  edge calculation available

   mwpaint2();
   return;
}


//  dialog event and completion callback function

int edgecalc_dialog_event(zdialog *zd, cchar *event)                       //  respond to user cancel
{
   if (! zd->zstat) return 0;
   Fkillfunc = 1;
   printf("edge calc killed \n");
   return 0;
}


void * edgecalc_thread(void *arg)                                          //  worker thread function
{                                                                          //  new algorithm    v.11.05
   using namespace sa_edgecalc_names;
   
   void  edgecalc_f1(int px, int py);

   int      index = *((int *) (arg));
   int      midx, midy, radx, rady, rad;
   int      ii, px, py;
   
   midx = (sa_maxx + sa_minx) / 2;
   midy = (sa_maxy + sa_miny) / 2;
   radx = (sa_maxx - sa_minx) / 2 + 1;
   rady = (sa_maxy - sa_miny) / 2 + 1;
   px = midx;                                                              //  center of enclosing rectangle
   py = midy;   

   ii = py * Fww + px;
   if (sa_pixmap[ii]) edgecalc_f1(px,py);                                  //  do center pixel first

   for (rad = 1; rad < radx || rad < rady; rad++)                          //  expanding square from the center
   {
      for (px = midx-rad; px <= midx+rad; px += 2 * rad)                   //  process edges only, interior already done
      for (py = midy-rad+index; py <= midy+rad; py += Nwt)
      {
         if (px < 0 || px > Fww-1) continue;
         if (py < 0 || py > Fhh-1) continue;
         ii = py * Fww + px;
         if (! sa_pixmap[ii]) continue;
         if (sa_edgedist[ii]) continue;
         edgecalc_f1(px,py);
         if (Fkillfunc) exit_wthread();
      }

      for (py = midy-rad; py <= midy+rad; py += 2 * rad)
      for (px = midx-rad+index; px <= midx+rad; px += Nwt)
      {
         if (px < 0 || px > Fww-1) continue;
         if (py < 0 || py > Fhh-1) continue;
         ii = py * Fww + px;
         if (! sa_pixmap[ii]) continue;
         if (sa_edgedist[ii]) continue;
         edgecalc_f1(px,py);
         if (Fkillfunc) exit_wthread();
      }
   }

   exit_wthread();
   return 0;                                                               //  not executed, stop gcc warning
}


//  Find the nearest edge pixel for a given pixel.
//  For all pixels in a line from the given pixel to the edge pixel, 
//  the same edge pixel is used to compute edge distance.

void edgecalc_f1(int px1, int py1)
{
   using namespace sa_edgecalc_names;
   
   int      ii, px2, py2, mindist;
   uint     dist2, mindist2;
   int      epx, epy, pxm, pym, dx, dy, inc;
   double   slope;
   
   mindist = 9999;
   mindist2 = mindist * mindist;
   epx = epy = 0;

   for (ii = 0; ii < sa_Nedge; ii++)                                       //  loop all edge pixels
   {
      px2 = sa_edgepx[ii];
      py2 = sa_edgepy[ii];
      dx = px2 - px1;
      dy = py2 - py1;
      dist2 = dx*dx + dy*dy;                                               //  avoid sqrt()
      if (dist2 < mindist2) {
         mindist2 = dist2;                                                 //  remember minimum
         epx = px2;                                                        //  remember nearest edge pixel
         mindist = sqrt(dist2);
         epy = py2;
      }
   }
   
   if (abs(epy - py1) > abs(epx - px1)) {                                  //  find all pixels along a line
      slope = 1.0 * (epx - px1) / (epy - py1);                             //    to the edge pixel
      if (epy > py1) inc = 1;
      else inc = -1;
      for (pym = py1; pym != epy; pym += inc) {
         pxm = px1 + slope * (pym - py1);
         ii = pym * Fww + pxm;
         if (sa_edgedist[ii]) break;
         dx = epx - pxm;                                                   //  calculate distance to edge
         dy = epy - pym;
         dist2 = sqrt(dx*dx + dy*dy) + 2;
         sa_edgedist[ii] = dist2;                                          //  save
         SB_done++;                                                        //  track progress            v.11.06
      }
   }

   else {
      slope = 1.0 * (epy - py1) / (epx - px1);
      if (epx > px1) inc = 1;
      else inc = -1;
      for (pxm = px1; pxm != epx; pxm += inc) {
         pym = py1 + slope * (pxm - px1);
         ii = pym * Fww + pxm;
         if (sa_edgedist[ii]) break;
         dx = epx - pxm;
         dy = epy - pym;
         dist2 = sqrt(dx*dx + dy*dy) + 2;
         sa_edgedist[ii] = dist2;
         SB_done++;
      }
   }

   return;
}


/**************************************************************************
   select area copy and paste menu functions
***************************************************************************/

PXM      *sacp_image16 = 0;                                                //  select area pixmap image
PXM      *sacp_info16 = 0;                                                 //  opacity and edge distance
int      sacp_ww, sacp_hh;                                                 //  original dimensions

PXM      *sacpR_image16 = 0;                                               //  resized/rotated image
PXM      *sacpR_info16 = 0;                                                //  resized/rotated info
int      sacpR_ww, sacpR_hh;                                               //  resized/rotated dimensions

double   sacp_resize;                                                      //  size, 1.0 = original size
double   sacp_angle;                                                       //  angle of rotation, -180 to +180
int      sacp_orgx, sacp_orgy;                                             //  origin in target image
double   sacp_blend;                                                       //  edge blend with target image


//  copy selected area, save in memory

void m_select_copy(GtkWidget *, cchar *menu)                               //  overhauled    v.11.02
{
   int      ii, px, py;
   int      pxmin, pxmax, pymin, pymax;
   uint16   *pix1, *pix2;

   if (menu) zfuncs::F1_help_topic = "area_copy_paste";

   if (sa_mode == 7) return;                                               //  a whole image area         v.11.01
   if (! Factivearea) sa_finish();                                         //  finish area if not already
   if (! Factivearea) return;
   sa_edgecalc();                                                          //  do edge calc if not already
   sa_show(0);
   
   pxmin = Fww;
   pxmax = 0;
   pymin = Fhh;
   pymax = 0;

   for (ii = 0; ii < Fww * Fhh; ii++)                                      //  find pixels in select area
   {                                                                       //  v.9.6
      if (! sa_pixmap[ii]) continue;
      py = ii / Fww;
      px = ii - py * Fww;
      if (px > pxmax) pxmax = px;                                          //  find enclosing rectangle
      if (px < pxmin) pxmin = px;
      if (py > pymax) pymax = py;
      if (py < pymin) pymin = py;
   }
   
   PXM_free(sacp_image16);                                                 //  free prior if any
   PXM_free(sacp_info16);
   PXM_free(sacpR_image16);
   PXM_free(sacpR_info16);
   
   sacp_ww = pxmax - pxmin + 1;                                            //  new area image PXM
   sacp_hh = pymax - pymin + 1;
   sacp_image16 = PXM_make(sacp_ww,sacp_hh,16);
   sacp_info16 = PXM_make(sacp_ww,sacp_hh,16);                             //  new info PXM
   
   for (ii = 0; ii < Fww * Fhh; ii++)                                      //  find pixels in select area
   {
      if (! sa_pixmap[ii]) continue;                                       //  0/1/2+ = outside/edge/inside edge distance
      py = ii / Fww;
      px = ii - py * Fww;
      pix1 = PXMpix(Fpxm16,px,py);                                         //  copy pixels into image PXM
      px = px - pxmin;
      py = py - pymin;
      pix2 = PXMpix(sacp_image16,px,py);
      pix2[0] = pix1[0];
      pix2[1] = pix1[1];
      pix2[2] = pix1[2];
      pix2 = PXMpix(sacp_info16,px,py);                                    //  build info PXM      v.11.02
      pix2[0] = 65535;                                                     //  opacity
      pix2[1] = sa_pixmap[ii];                                             //  edge distance
      pix2[2] = 0;
   }

   return;
}


//  paste selected area into current image
//  this is an edit function - select area image is copied into main image

int      sacp_porg = 0;                                                    //  pasted area is present
int      sacp_porgx, sacp_porgy;                                           //  pasted area origin in image
int      sacp_pww, sacp_phh;                                               //  pasted area dimensions

editfunc    EFpaste;

void m_select_paste(GtkWidget *, cchar *menu)                              //  menu function
{
   int   select_paste_dialog_event(zdialog *, cchar *event);
   void  select_paste_mousefunc();

   cchar  *dragmess = ZTX("position with mouse click/drag");

   if (menu) zfuncs::F1_help_topic = "area_copy_paste";

   if (! sacp_image16) return;                                             //  nothing to paste
   sa_unselect();                                                          //  unselect area if present
   
   EFpaste.funcname = "paste";
   if (! edit_setup(EFpaste)) return;                                      //  setup edit for paste
   
   sacp_resize = 1.0;                                                      //  size = 1x
   sacp_blend = 1;                                                         //  edge blend = 1
   sacp_angle = 0;                                                         //  angle = 0

   PXM_free(sacpR_image16);                                                //  free prior if any
   PXM_free(sacpR_info16);

   sacpR_ww = sacp_ww;                                                     //  setup resized paste image
   sacpR_hh = sacp_hh;                                                     //  (initially 1x, 0 rotation)
   sacpR_image16 = PXM_copy(sacp_image16);
   sacpR_info16 = PXM_copy(sacp_info16);
   
   sacp_porg = 0;                                                          //  no image paste location yet
   
   CEF->zd = zdialog_new(ZTX("Paste Image"),mWin,Bdone,Bcancel,null);
   zdialog_add_widget(CEF->zd,"hbox","hb0","dialog",0,"space=8");
   zdialog_add_widget(CEF->zd,"label","lab1","hb0",dragmess,"space=8");

   zdialog_add_widget(CEF->zd,"hbox","hbres","dialog",0,"space=5");
   zdialog_add_widget(CEF->zd,"label","labres","hbres","resize");
   zdialog_add_widget(CEF->zd,"button","+.1%","hbres","+.1%");
   zdialog_add_widget(CEF->zd,"button","+1%","hbres","+1%");
   zdialog_add_widget(CEF->zd,"button","+10%","hbres","+10%");
   zdialog_add_widget(CEF->zd,"button","-.1%","hbres","-.1%");
   zdialog_add_widget(CEF->zd,"button","-1%","hbres","-1%");
   zdialog_add_widget(CEF->zd,"button","-10%","hbres","-10%");

   zdialog_add_widget(CEF->zd,"hbox","hbang","dialog",0,"space=5");        //  rotation  v.11.02
   zdialog_add_widget(CEF->zd,"label","labang","hbang",ZTX("angle"));
   zdialog_add_widget(CEF->zd,"button","+.1°","hbang","+.1°");
   zdialog_add_widget(CEF->zd,"button","+1°","hbang","+1°");
   zdialog_add_widget(CEF->zd,"button","+10°","hbang","+10°");
   zdialog_add_widget(CEF->zd,"button","-.1°","hbang","-.1°");
   zdialog_add_widget(CEF->zd,"button","-1°","hbang","-1°");
   zdialog_add_widget(CEF->zd,"button","-10°","hbang","-10°");

   zdialog_add_widget(CEF->zd,"hbox","hbbl","dialog",0,"space=5");
   zdialog_add_widget(CEF->zd,"label","lab2","hbbl","edge blend");
   zdialog_add_widget(CEF->zd,"hscale","blend","hbbl","0|20|0.3|0","expand");     //  v.11.06

   zdialog_help(CEF->zd,"area_copy_paste");                                //  zdialog help topic        v.11.08
   zdialog_run(CEF->zd,select_paste_dialog_event,"80/20");                 //  v.11.07

   takeMouse(CEF->zd,select_paste_mousefunc,0);                            //  connect mouse function

   return;
}


//  Dialog event and completion callback function
//  Get dialog values and convert image. When done, commit edited image 
//  (with pasted area) and set up a new select area for the pasted area,
//  allowing further editing of the area.

int select_paste_dialog_event(zdialog *zd, cchar *event)
{
   void  select_paste_mousefunc();
   void  select_paste_pixmap();
   void  select_paste_makearea();

   int      ww, hh;
   PXM      *pxm_temp;
   
   if (zd->zstat)                                                          //  dialog completed
   {
      freeMouse();                                                         //  disconnect mouse

      if (zd->zstat != 1 || ! sacp_porg) {                                 //  cancel paste
         edit_cancel(EFpaste);                                             //  cancel edit, restore image
         sa_unselect();
         return 0;
      }
      
      edit_done(EFpaste);                                                  //  commit the edit (pasted image)
      select_paste_makearea();                                             //  make equivalent select area
      PXM_free(sacpR_image16);                                             //  free memory
      PXM_free(sacpR_info16);
      return 0;
   }

   if (strEqu(event,"focus"))                                              //  toggle mouse capture      v.12.01
      takeMouse(zd,select_paste_mousefunc,0);

   if (strstr(event,"%") || strstr(event,"°"))                             //  new size or angle
   {
      if (strEqu(event,"+.1%")) sacp_resize *= 1.001;                      
      if (strEqu(event,"+1%")) sacp_resize *= 1.01;
      if (strEqu(event,"+10%")) sacp_resize *= 1.10;
      if (strEqu(event,"-.1%")) sacp_resize *= 0.999001;
      if (strEqu(event,"-1%")) sacp_resize *= 0.990099;
      if (strEqu(event,"-10%")) sacp_resize *= 0.909091;                   //  -10% is really 1.0/1.10

      if (strEqu(event,"+.1°")) sacp_angle += 0.1;                         //  rotation   v.11.02
      if (strEqu(event,"+1°")) sacp_angle += 1.0;
      if (strEqu(event,"+10°")) sacp_angle += 10.0;
      if (strEqu(event,"-.1°")) sacp_angle -= 0.1;
      if (strEqu(event,"-1°")) sacp_angle -= 1.0;
      if (strEqu(event,"-10°")) sacp_angle -= 10.0;

      PXM_free(sacpR_image16);                                             //  free prior if any
      PXM_free(sacpR_info16);

      ww = sacp_resize * sacp_ww;                                          //  new size
      hh = sacp_resize * sacp_hh;

      pxm_temp = PXM_rescale(sacp_image16,ww,hh);                          //  resized area image
      sacpR_image16 = PXM_rotate(pxm_temp,sacp_angle);                     //  rotated area image
      PXM_free(pxm_temp);

      pxm_temp = PXM_rescale(sacp_info16,ww,hh);                           //  resized area info
      sacpR_info16 = PXM_rotate(pxm_temp,sacp_angle);                      //  rotated/resized area info
      PXM_free(pxm_temp);

      sacpR_ww = sacpR_image16->ww;                                        //  size after resize/rotate
      sacpR_hh = sacpR_image16->hh;

      select_paste_pixmap();                                               //  copy onto target image
   }

   if (strEqu(event,"blend") && sacp_porg) {
      zdialog_fetch(zd,"blend",sacp_blend);                                //  new edge blend distance
      select_paste_pixmap();                                               //  copy onto target image
   }

   CEF->Fmod = 1;                                                          //  image is modified
   mwpaint2();
   return 0;
}


//  convert the pasted image area into an equivalent select area by mouse

void select_paste_makearea()
{
   int      cc, ii;
   int      px1, py1, px2, py2;
   uint16   *pix2;
   
   sa_unselect();                                                          //  unselect old area

   cc = Fww * Fhh * sizeof(uint16);
   sa_pixmap = (uint16 *) zmalloc(cc,"sa_paste");                          //  pixel map for new area
   memset(sa_pixmap,0,cc);
   
   for (py1 = 0; py1 < sacpR_hh; py1++)                                    //  map non-transparent pixels
   for (px1 = 0; px1 < sacpR_ww; px1++)                                    //    into sa_pixmap[]
   {
      pix2 = PXMpix(sacpR_info16,px1,py1);                                 //  opacity and edge distance
      if (pix2[0] == 0) continue;                                          //  transparent
      px2 = px1 + sacp_orgx;
      py2 = py1 + sacp_orgy;
      if (px2 < 0 || px2 > Fww-1) continue;                                //  parts may be beyond edges
      if (py2 < 0 || py2 > Fhh-1) continue;
      ii = py2 * Fww + px2;
      sa_pixmap[ii] = 1;
   }

   sa_stat = 1;
   sa_mode = 5;                                                            //  equivalent select/mouse area
   sa_fww = Fww;                                                           //  v.11.08
   sa_fhh = Fhh;
   sa_finish_auto();                                                       //  v.11.04
   sa_show(1);
   return;
}


//  mouse function - follow mouse drags and move pasted area accordingly

void select_paste_mousefunc()
{
   void  select_paste_pixmap();

   int            mx1, my1, mx2, my2;
   static int     mdx0, mdy0, mdx1, mdy1;
   
   if (LMclick) {                                                          //  left mouse click
      LMclick = 0;
      sacp_orgx = Mxclick - sacpR_ww / 2;                                  //  position image at mouse   v.10.11
      sacp_orgy = Myclick - sacpR_hh / 2;
      select_paste_pixmap();
      CEF->Fmod = 1;                                                       //  image is modified
   }

   if (! sacp_porg) return;                                                //  no select area paste yet

   if (Mxposn < sacp_orgx || Mxposn > sacp_orgx + sacpR_ww ||              //  mouse outside select area
      Myposn < sacp_orgy || Myposn > sacp_orgy + sacpR_hh)
      gdk_window_set_cursor(drWin->window,0);                              //  set normal cursor         v.11.03
   else 
      gdk_window_set_cursor(drWin->window,dragcursor);                     //  set drag cursor           v.11.03

   if (Mxdrag + Mydrag == 0) return;                                       //  no drag underway

   if (Mxdown != mdx0 || Mydown != mdy0) {                                 //  new drag initiated
      mdx0 = mdx1 = Mxdown;
      mdy0 = mdy1 = Mydown;
   }

   mx1 = mdx1;                                                             //  drag start
   my1 = mdy1;
   mx2 = Mxdrag;                                                           //  drag position
   my2 = Mydrag;
   mdx1 = mx2;                                                             //  next drag start
   mdy1 = my2;
   
   sacp_orgx += (mx2 - mx1);                                               //  move position of select area
   sacp_orgy += (my2 - my1);                                               //    by mouse drag amount
   select_paste_pixmap();                                                  //  re-copy area to new position
   CEF->Fmod = 1;                                                          //  image is modified

   return;      
}


//  copy select area into edit image, starting at sacp_orgx/y

void select_paste_pixmap()                                                 //  overhauled    v.11.02
{
   int      px1, py1, px3, py3, opac, dist;
   uint16   *pix1, *pix3;
   double   f1, f2;
   
   if (sacp_porg)                                                          //  prior area overlap rectangle
   {
      for (py1 = 0; py1 < sacp_phh; py1++)                                 //  restore original image pixels   v.10.11
      for (px1 = 0; px1 < sacp_pww; px1++)
      {
         px3 = px1 + sacp_porgx;
         py3 = py1 + sacp_porgy;
         if (px3 < 0 || px3 >= E3ww) continue;                             //  parts may be beyond edges
         if (py3 < 0 || py3 >= E3hh) continue;
         pix1 = PXMpix(E1pxm16,px3,py3);
         pix3 = PXMpix(E3pxm16,px3,py3);
         pix3[0] = pix1[0];
         pix3[1] = pix1[1];
         pix3[2] = pix1[2];
      }
   }
   
   for (py1 = 0; py1 < sacpR_hh; py1++)                                    //  copy paste area pixels to new 
   for (px1 = 0; px1 < sacpR_ww; px1++)                                    //    image overlap rectangle
   {
      pix1 = PXMpix(sacpR_info16,px1,py1);                                 //  opacity and edge distance
      opac = pix1[0];
      dist = pix1[1];
      if (opac == 0) continue;                                             //  skip transparent pixel
      px3 = px1 + sacp_orgx;
      py3 = py1 + sacp_orgy;
      if (px3 < 0 || px3 >= E3ww) continue;                                //  parts may be beyond edges
      if (py3 < 0 || py3 >= E3hh) continue;

      pix1 = PXMpix(sacpR_image16,px1,py1);                                //  paste image pixel
      pix3 = PXMpix(E3pxm16,px3,py3);                                      //  target image pixel
      if (dist > sacp_blend) f1 = 1.0;                                     //  minor bugfix             v.11.06
      else f1 = 1.0 * dist / sacp_blend;                                   //  opacity reduction from edge blend
      f1 = f1 * opac / 65535.0;                                            //  opacity reduction from resize/rescale
      f2 = 1.0 - f1;
      pix3[0] = f1 * pix1[0] + f2 * pix3[0];                               //  blend paste and target images
      pix3[1] = f1 * pix1[1] + f2 * pix3[1];
      pix3[2] = f1 * pix1[2] + f2 * pix3[2];
   }

   mwpaint3(sacp_porgx,sacp_porgy,sacp_pww,sacp_phh);                      //  update window for old overlap area
   mwpaint3(sacp_orgx,sacp_orgy,sacpR_ww,sacpR_hh);                        //  update window for new overlap area

   sacp_porgx = sacp_orgx;                                                 //  remember location for next call
   sacp_porgy = sacp_orgy;
   sacp_pww = sacpR_ww;
   sacp_phh = sacpR_hh;
   sacp_porg = 1;
   return;
}


/**************************************************************************
   select area load from file and save to file functions           v.9.9
***************************************************************************/

//  Load a select area from a disk file and paste into current image.

void m_select_open(GtkWidget *, cchar *)
{
   char     *pfile1, *pfile2, *pp;

   zfuncs::F1_help_topic = "area_open_save";                               //  v.10.8

   pp = zgetfile1(ZTX("load select area from a file"),"open",saved_areas_dirk);
   if (! pp) return;

   pfile1 = strdupz(pp,8,"select_open");
   zfree(pp);
   pp = strrchr(pfile1,'/');
   if (pp) pp = strrchr(pp,'.');
   if (pp) strcpy(pp,".tiff");
   else strcat(pfile1,".tiff");

   pfile2 = strdupz(pfile1,0,"select_open");
   pp = strrchr(pfile2,'.');
   strcpy(pp,".info");
   
   PXM_free(sacp_image16);                                                 //  free prior if any
   PXM_free(sacp_info16);

   sacp_image16 = TIFFread(pfile1);                                        //  .tiff file --> image PXM
   if (! sacp_image16) goto fail;

   sacp_info16 = TIFFread(pfile2);                                         //  .info file --> info PXM
   if (! sacp_info16) goto fail;

   zfree(pfile1);
   zfree(pfile2);

   sacp_ww = sacp_image16->ww;                                             //  paste into current image
   sacp_hh = sacp_image16->hh;
   m_select_paste(0,0);
   return;

fail:
   zfree(pfile1);
   zfree(pfile2);
   zmessageACK(mWin,ZTX("cannot open .tiff and .info files"));
   return;
}


//  save a select area as a disk file

void m_select_save(GtkWidget *, cchar *)
{
   char        *pfile1, *pfile2, *pp;

   zfuncs::F1_help_topic = "area_open_save";                               //  v.10.11

   if (sa_mode == 7) return;                                               //  a whole image area         v.11.01
   if (! Factivearea) sa_finish();                                         //  finish select area if not already
   if (! Factivearea) return;                                              //  v.11.06.1
   m_select_copy(0,0);                                                     //  copy select area to memory
   if (! sacp_image16) return;
   
   pp = zgetfile1(ZTX("save select area to a file"),"save",saved_areas_dirk);
   if (! pp) return;

   pfile1 = strdupz(pp,8,"select_save");
   zfree(pp);
   pp = strrchr(pfile1,'/');
   if (pp) pp = strrchr(pp,'.');
   if (pp) strcpy(pp,".tiff");
   else strcat(pfile1,".tiff");

   pfile2 = strdupz(pfile1,0,"select_save");
   pp = strrchr(pfile2,'.');
   strcpy(pp,".info");

   TIFFwrite(sacp_image16,pfile1);                                         //  image PXM --> .tiff file
   TIFFwrite(sacp_info16,pfile2);                                          //  info PXM --> .info file

   return;
}


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

//  Select the whole image as an area.
//  Set "edge distance" 1 to 255 from pixel or RGB color brightness.
//  Set "blend width" to 255
//  Edit function coefficient = edge distance / blend width.

spldat   * select_whole_image_curve;

void m_select_whole_image(GtkWidget *, cchar *)                            //  new  v.11.01
{
   int    select_whole_image_event(zdialog *, cchar *event);               //  dialog event and completion func
   void   select_whole_image_curve_update(int spc);                        //  curve update callback function

   cchar    *title = ZTX("Select Whole Image");
   cchar    *legend = ZTX("Edit Function Amplifier");

   zfuncs::F1_help_topic = "select_whole_image";

   if (! curr_file) return;                                                //  no image
   if (zdsela) return;                                                     //  select dialog already active
   if (Fpreview) edit_fullsize();                                          //  use full-size image

/***
             Edit Function Amplifier
             ------------------------------------------
            |                                          |
            |                                          |
            |           curve drawing area             |
            |                                          |
            |                                          |
             ------------------------------------------
             darker areas                 lighter areas

             [+++]  [---]  [+ -]  [- +]  [+-+]  [-+-]
             (o) Brightness  (o) Red  (o) Green  (o) Blue
             Curve File: [ Open ] [ Save ]
                                              [ Done ]
***/

   zdsela = zdialog_new(title,mWin,Bdone,null);

   zdialog_add_widget(zdsela,"label","labt","dialog",legend);
   zdialog_add_widget(zdsela,"frame","fr1","dialog",0,"expand");
   zdialog_add_widget(zdsela,"hbox","hba","dialog");
   zdialog_add_widget(zdsela,"label","labda","hba",Bdarker,"space=5");
   zdialog_add_widget(zdsela,"label","space","hba",0,"expand");
   zdialog_add_widget(zdsela,"label","labba","hba",Blighter,"space=5");
   zdialog_add_widget(zdsela,"hbox","hbb","dialog",0,"space=5");
   zdialog_add_widget(zdsela,"button","b +++","hbb","+++");
   zdialog_add_widget(zdsela,"button","b ---","hbb","‒ ‒ ‒");
   zdialog_add_widget(zdsela,"button","b +-", "hbb"," + ‒ ");
   zdialog_add_widget(zdsela,"button","b -+", "hbb"," ‒ + ");
   zdialog_add_widget(zdsela,"button","b +-+","hbb","+ ‒ +");
   zdialog_add_widget(zdsela,"button","b -+-","hbb","‒ + ‒");

   zdialog_add_widget(zdsela,"hbox","hbbr","dialog",0,"space=5");
   zdialog_add_widget(zdsela,"radio","bright","hbbr",Bbrightness,"space=5");
   zdialog_add_widget(zdsela,"radio","red","hbbr",Bred,"space=5");
   zdialog_add_widget(zdsela,"radio","green","hbbr",Bgreen,"space=5");
   zdialog_add_widget(zdsela,"radio","blue","hbbr",Bblue,"space=5");

   zdialog_add_widget(zdsela,"hbox","hbcf","dialog",0,"space=5");
   zdialog_add_widget(zdsela,"label","labcf","hbcf",Bcurvefile,"space=5");
   zdialog_add_widget(zdsela,"button","load","hbcf",Bopen,"space=5");
   zdialog_add_widget(zdsela,"button","save","hbcf",Bsave,"space=5");

   GtkWidget *frame = zdialog_widget(zdsela,"fr1");                        //  setup for curve editing
   spldat *sd = splcurve_init(frame,select_whole_image_curve_update);      //  v.11.01
   select_whole_image_curve = sd;

   sd->Nspc = 1;
   sd->vert[0] = 0;
   sd->nap[0] = 3;                                                         //  initial curve anchor points
   sd->apx[0][0] = 0.01;
   sd->apy[0][0] = 0.5;
   sd->apx[0][1] = 0.50;
   sd->apy[0][1] = 0.5;
   sd->apx[0][2] = 0.99;
   sd->apy[0][2] = 0.5;
   splcurve_generate(sd,0);                                                //  generate curve data

   zdialog_stuff(zdsela,"bright",1);
   zdialog_stuff(zdsela,"red",0);
   zdialog_stuff(zdsela,"green",0);
   zdialog_stuff(zdsela,"blue",0);

   sa_unselect();                                                          //  unselect current area if any

   zdialog_resize(zdsela,0,360);
   zdialog_help(zdsela,"select_whole_image");                              //  zdialog help topic        v.11.08
   zdialog_run(zdsela,select_whole_image_event,"80/20");                   //  run dialog - parallel        v.11.07
   select_whole_image_event(zdsela,"init");                                //  initialize default params

   return;
}


//  dialog event and completion function

int select_whole_image_event(zdialog *zd, cchar *event)
{
   int         ii, kk, cc, base, pixbright, pixdist;
   double      px, py, xval, yval;
   uint8       *pixel;
   spldat      *sd = select_whole_image_curve;
   
   if (zd->zstat) {                                                        //  done, kill dialog
      zdialog_free(zdsela);
      zfree(sd);                                                           //  free curve edit memory
      return 0;
   }

   if (! sa_stat)                                                          //  if no area exists
   {
      cc = Fww * Fhh * sizeof(uint16);                                     //  allocate sa_pixmap[] for new area
      sa_pixmap = (uint16 *) zmalloc(cc,"sa_select_image");

      sa_minx = 0;                                                         //  enclosing rectangle
      sa_maxx = Fww;
      sa_miny = 0;
      sa_maxy = Fhh;

      sa_Npixel = Fww * Fhh;
      sa_stat = 3;                                                         //  area status = complete
      sa_mode = 7;                                                         //  area mode = whole image
      sa_calced = 1;                                                       //  edge calculation complete
      sa_blend = 255;                                                      //  "blend width" = 255
      sa_fww = Fww;                                                        //  valid image dimensions    v.11.08
      sa_fhh = Fhh;
      Factivearea = 1;                                                     //  area is active
      areanumber++;                                                        //  next sequential number
   }
   
   if (strEqu(event,"load")) {                                             //  load saved curve    v.11.02
      splcurve_load(sd);
      if (CEF && CEF->zd) zdialog_send_event(CEF->zd,"blendwidth");        //  notify edit dialog
      return 0;
   }

   if (strEqu(event,"save")) {                                             //  save curve to file  v.11.02
      splcurve_save(sd);
      return 0;
   }

   base = 0;
   zdialog_fetch(zd,"bright",ii);                                          //  set brightness or color to be used
   if (ii) base = 1;
   zdialog_fetch(zd,"red",ii);
   if (ii) base = 2;
   zdialog_fetch(zd,"green",ii);
   if (ii) base = 3;
   zdialog_fetch(zd,"blue",ii);
   if (ii) base = 4;
   if (! base) return 0;

   if (strnEqu(event,"b ",2)) {                                            //  button to move entire curve
      for (ii = 0; ii < sd->nap[0]; ii++) {
         px = sd->apx[0][ii];
         py = sd->apy[0][ii];
         if (strEqu(event,"b +++")) py += 0.1;
         if (strEqu(event,"b ---")) py -= 0.1;
         if (strEqu(event,"b +-"))  py += 0.1 - 0.2 * px;
         if (strEqu(event,"b -+"))  py -= 0.1 - 0.2 * px;
         if (strEqu(event,"b +-+")) py -= 0.05 - 0.2 * fabs(px-0.5);
         if (strEqu(event,"b -+-")) py += 0.05 - 0.2 * fabs(px-0.5);
         if (py > 1) py = 1;
         if (py < 0) py = 0;
         sd->apy[0][ii] = py;
      }
   }

   splcurve_generate(sd,0);                                                //  regenerate the curve
   splcurve_draw(0,0,sd);

   pixel = (uint8 *) Fpxm8->bmp;

   for (ii = 0; ii < Fww * Fhh; ii++)                                      //  loop all pixels
   {
      pixbright = pixel[0] + pixel[1] + pixel[2] + 1;                      //  brightness of pixel, 1 - 766
      if (base == 1) pixbright = pixbright / 3;                            //  brightness, 0 - 255       bugfix v.11.01.2
      else if (base == 2) pixbright = 255 * pixel[0] / pixbright;          //  red part: 0 - 255 = 100% red
      else if (base == 3) pixbright = 255 * pixel[1] / pixbright;          //  green part
      else if (base == 4) pixbright = 255 * pixel[2] / pixbright;          //  blue part
      xval = pixbright / 256.0;                                            //  curve x-value, 0 to 0.999
      kk = 1000 * xval;                                                    //  speedup    v.11.06
      if (kk > 999) kk = 999;
      yval = sd->yval[0][kk];
      pixdist = 255 * yval;                                                //  pixel "edge distance" 0 to 255
      sa_pixmap[ii] = pixdist | 1;                                         //  avoid 0 (pixel outside area)
      pixel += 3;
   }

   if (CEF && CEF->zd) zdialog_send_event(CEF->zd,"blendwidth");           //  notify edit dialog

   return 0;
}


//  this function is called when curve is edited using mouse

void  select_whole_image_curve_update(int)
{
   select_whole_image_event(zdsela,"edit");
   return;
}


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

//  Select area and edit in parallel
//  Current edit function is applied to areas painted with the mouse.
//  Mouse can be weak or strong, and edits are applied incrementally.
//  method: 
//    entire image is a select area with all pixel edge distance = 0 (outside area)
//    blendwidth = 10000
//    pixels painted with mouse have increasing edge distance to amplify edits

int   select_edit_radius;
int   select_edit_cpower;
int   select_edit_epower;

void m_select_edit(GtkWidget *, cchar *)                                   //  menu function    v.11.02
{
   int   select_edit_dialog_event(zdialog *, cchar *event);                //  dialog event function
   void  select_edit_mousefunc();                                          //  mouse function

   cchar    *title = ZTX("Select Area for Edits");
   cchar    *helptext1 = ZTX("Press F1 for help");
   cchar    *helptext2 = ZTX("Edit function must be active");
   int      cc;

   zfuncs::F1_help_topic = "select_edit";

   if (! curr_file) return;                                                //  no image
   if (zdsela) return;                                                     //  select area already active
   if (Fpreview) edit_fullsize();                                          //  use full-size image
   
   if (! Fpxm16) {                                                         //  create Fpxm16 if not already
      mutex_lock(&Fpixmap_lock);
      Fpxm16 = f_load(curr_file,16);
      mutex_unlock(&Fpixmap_lock);
      if (! Fpxm16) return;
   }

/***
    ____________________________________________
   |           Press F1 for help                |
   |       Edit Function must be active         |
   | mouse radius [___|v]                       |
   | power:  center [___|v]  edge [___|v]       |
   | [reset area]                               |
   |                                  [done]    |
   |____________________________________________|
   
***/

   zdsela = zdialog_new(title,mWin,Bdone,null);
   zdialog_add_widget(zdsela,"label","labhelp1","dialog",helptext1,"space=5");
   zdialog_add_widget(zdsela,"label","labhelp2","dialog",helptext2);
   zdialog_add_widget(zdsela,"label","labspace","dialog");
   zdialog_add_widget(zdsela,"hbox","hbr","dialog",0,"space=3");
   zdialog_add_widget(zdsela,"label","labr","hbr",ZTX("mouse radius"),"space=5");
   zdialog_add_widget(zdsela,"spin","radius","hbr","2|500|1|50");
   zdialog_add_widget(zdsela,"hbox","hbt","dialog",0,"space=3");
   zdialog_add_widget(zdsela,"label","labtc","hbt",ZTX("power:  center"),"space=5");
   zdialog_add_widget(zdsela,"spin","center","hbt","0|100|1|50");
   zdialog_add_widget(zdsela,"label","labte","hbt",ZTX("edge"),"space=5");
   zdialog_add_widget(zdsela,"spin","edge","hbt","0|100|1|0");
   zdialog_add_widget(zdsela,"hbox","hbr","dialog",0,"space=5");
   zdialog_add_widget(zdsela,"button","reset","hbr",ZTX("reset area"),"space=5");
   
   select_edit_radius = 50;
   select_edit_cpower = 50;
   select_edit_epower = 0;

   sa_unselect();                                                          //  unselect current area if any
   cc = Fww * Fhh * sizeof(uint16);                                        //  allocate sa_pixmap[] for new area
   sa_pixmap = (uint16 *) zmalloc(cc,"sa_select_edit");
   memset(sa_pixmap,0,cc);                                                 //  edge distance = 0 for all pixels

   sa_minx = 0;                                                            //  enclosing rectangle
   sa_maxx = Fww;
   sa_miny = 0;
   sa_maxy = Fhh;

   sa_Npixel = Fww * Fhh;
   sa_stat = 3;                                                            //  area status = complete
   sa_mode = 7;                                                            //  area mode = whole image
   sa_calced = 1;                                                          //  edge calculation complete
   sa_blend = 10000;                                                       //  "blend width"
   sa_fww = Fww;                                                           //  valid image dimensions    v.11.08
   sa_fhh = Fhh;
   Factivearea = 1;                                                        //  area is active
   areanumber++;                                                           //  next sequential number

   zdialog_help(zdsela,"select_edit");                                     //  zdialog help topic        v.11.08
   zdialog_run(zdsela,select_edit_dialog_event,"80/20");                   //  run dialog - parallel     v.11.07
   return;
}


//  tailor whole image area to increase edit power for pixels within the mouse radius
//  sa_pixmap[*]  = 0 = never touched by mouse
//                = 1 = minimum edit power (barely painted)
//                = sa_blend = maximum edit power (edit fully applied)

int select_edit_dialog_event(zdialog *zd, cchar *event)
{
   void  select_edit_mousefunc();                                          //  mouse function
   int      cc;
   
   if (! Factivearea) return 1;                                            //  area gone    v.11.06.1

   if (zd->zstat)                                                          //  done or cancel
   {
      freeMouse();                                                         //  disconnect mouse function
      zdialog_free(zdsela);                                                //  kill dialog
      sa_unselect();                                                       //  unselect area
      return 0;
   }

   if (strEqu(event,"focus")) {                                            //  toggle mouse capture      v.12.01
      if (CEF) takeMouse(zd,select_edit_mousefunc,0);
      else freeMouse();                                                    //  disconnect mouse
   }

   if (strEqu(event,"radius"))
      zdialog_fetch(zd,"radius",select_edit_radius);                       //  set mouse radius

   if (strEqu(event,"center"))
      zdialog_fetch(zd,"center",select_edit_cpower);                       //  set mouse center power

   if (strEqu(event,"edge"))
      zdialog_fetch(zd,"edge",select_edit_epower);                         //  set mouse edge power
   
   if (strEqu(event,"reset")) {
      sa_unselect();                                                       //  unselect current area if any
      cc = Fww * Fhh * sizeof(uint16);                                     //  allocate sa_pixmap[] for new area
      sa_pixmap = (uint16 *) zmalloc(cc,"sa_select_edit");
      memset(sa_pixmap,0,cc);                                              //  edge distance = 0 for all pixels

      sa_minx = 0;                                                         //  enclosing rectangle
      sa_maxx = Fww;
      sa_miny = 0;
      sa_maxy = Fhh;

      sa_Npixel = Fww * Fhh;
      sa_stat = 3;                                                         //  area status = complete
      sa_mode = 7;                                                         //  area mode = whole image
      sa_calced = 1;                                                       //  edge calculation complete
      sa_blend = 10000;                                                    //  "blend width"
      sa_fww = Fww;                                                        //  valid image dimensions    v.11.08
      sa_fhh = Fhh;
      Factivearea = 1;                                                     //  area is active
      areanumber++;                                                        //  next sequential number
   }
   
   return 1;
}


//  mouse function - adjust edit strength for areas within mouse radius
//  "edge distance" is increased for more strength, decreased for less

void select_edit_mousefunc()
{
   int      ii, cc, px, py, rx, ry;
   int      radius, radius2, cpower, epower;
   double   rad, rad2, power;                                              //  v.11.06
   
   if (! CEF) {                                                            //  no active edit
      freeMouse();
      return;
   }
   
   if (! sa_stat)                                                          //  area gone?       v.11.07
   {
      cc = Fww * Fhh * sizeof(uint16);                                     //  allocate sa_pixmap[] for new area
      sa_pixmap = (uint16 *) zmalloc(cc,"sa_select_edit");
      memset(sa_pixmap,0,cc);                                              //  clear to zeros

      sa_minx = 0;                                                         //  enclosing rectangle
      sa_maxx = Fww;
      sa_miny = 0;
      sa_maxy = Fhh;

      sa_Npixel = Fww * Fhh;
      sa_stat = 3;                                                         //  area status = complete
      sa_mode = 7;                                                         //  area mode = whole image
      sa_calced = 1;                                                       //  edge calculation complete
      sa_blend = 10000;                                                    //  "blend width"
      sa_fww = Fww;                                                        //  valid image dimensions    v.11.08
      sa_fhh = Fhh;
      Factivearea = 1;                                                     //  area is active
      areanumber++;                                                        //  next sequential number
   }

   radius = select_edit_radius;                                            //  pixel selection radius
   radius2 = radius * radius;
   cpower = select_edit_cpower;
   epower = select_edit_epower;

   toparcx = Mxposn - radius;                                              //  draw mouse outline circle
   toparcy = Myposn - radius;
   toparcw = toparch = 2 * radius;
   Ftoparc = 1;
   paint_toparc(3);

   if (LMclick || RMclick)                                                 //  mouse click
      LMclick = RMclick = 0;

   if (Mbutton != 1 && Mbutton != 3)                                       //  button released
      return;
   
   for (rx = -radius; rx <= radius; rx++)                                  //  loop every pixel in radius
   for (ry = -radius; ry <= radius; ry++)
   {
      rad2 = rx * rx + ry * ry;
      if (rad2 > radius2) continue;                                        //  outside radius
      px = Mxposn + rx;
      py = Myposn + ry;
      if (px < 0 || px > Fww-1) continue;                                  //  off the image edge
      if (py < 0 || py > Fhh-1) continue;

      ii = Fww * py + px;
      rad = sqrt(rad2);
      power = cpower + rad / radius * (epower - cpower);                   //  power at pixel radius     v.11.06
      
      if (Mbutton == 1)                                                    //  left mouse button
      {                                                                    //  increase edit power
         sa_pixmap[ii] += power;
         if (sa_pixmap[ii] > sa_blend) sa_pixmap[ii] = sa_blend;
      }

      if (Mbutton == 3)                                                    //  right mouse button
      {                                                                    //  weaken edit power
         if (sa_pixmap[ii] <= power) sa_pixmap[ii] = 0;
         else sa_pixmap[ii] -= power;
      }
   }

   sa_minx = Mxposn - radius;                                              //  set temp. smaller area around mouse
   if (sa_minx < 0) sa_minx = 0;                                           //    speedup  v.11.06
   sa_maxx = Mxposn + radius;
   if (sa_maxx > Fww) sa_maxx = Fww;
   sa_miny = Myposn - radius;
   if (sa_miny < 0) sa_miny = 0;
   sa_maxy = Myposn + radius;
   if (sa_maxy > Fhh) sa_maxy = Fhh;
   sa_Npixel = (sa_maxx - sa_minx) * (sa_maxy - sa_miny);

   paint_toparc(2);                                                        //  remove mouse outline

   zdialog_send_event(CEF->zd,"blendwidth");                               //  notify edit dialog

   Ftoparc = 1;                                                            //  restore mouse outline
   paint_toparc(3);

   sa_minx = 0;                                                            //  restore whole image area     v.11.06
   sa_maxx = Fww;
   sa_miny = 0;
   sa_maxy = Fhh;
   sa_Npixel = Fww * Fhh;

   return;
}



