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

   Fotoxx      edit photos and manage collections

   Copyright 2007-2016 Michael Cornelison
   Source URL: http://kornelix.net
   Contact: kornelix@posteo.de

   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/.

*********************************************************************************

   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/4 = none/edit/unused/complete/disabled
   sa_mode is current area selection method:
   mode_rect         select rectangle by drag/click
   mode_ellipse      select ellipse by drag
   mode_draw         freehand draw by drag/click
   mode_follow       follow edge indicated by clicks
   mode_replace      freehand draw and replace nearby pixels
   mode_mouse        select area within mouse (radius)
   mode_onecolor     select one matching color within mouse
   mode_allcolors    select all matching colors within mouse
   mode_image        select whole image

   m_select             select area dialog
   sa_finish            finish selected areas
   sa_unfinish          unfinish selected areas
   m_select_find_gap    find a gap in an area outline                            //  16.07
   m_select_hairy       hairy edge select function                               //  16.07
   m_select_show        show area outlines
   m_select_hide        hide area outlines
   m_select_enable      enable area
   m_select_disable     disable area (reversible)
   m_select_invert      invert selected area
   m_select_unselect    delete selected area
   sa_show              show area callable function
   sa_show_rect         show area within an image rectangle
   sa_validate          validate area for current image
   sa_enable            enable area callable function
   sa_disable           disable area callable function
   sa_invert            invert area callable function
   sa_unselect          delete area callable function
   sa_edgecalc          calculate area edge distances
   sa_edgecreep         adjust area edges +-1 pixel
   sa_blendfunc         compute edge blend coefficient
   m_select_save        save area to PNG file with alpha channel
   m_select_open        open PNG file and make a select area
   m_select_paste       paste area into image
   select_paste         paste area in memory onto image

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

#define EX extern                                                                //  enable extern declarations
#include "fotoxx.h"                                                              //  (variables in fotoxx.h are refs)

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


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

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;

   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"
                           "by this edit function"));
      return;
   }

/***
       _______________________________________________________________________
      |                 Select Area for Edits                                 |
      |                   Press F1 for help                                   |
      | [x] select rectangle    [x] select ellipse                            |
      | [x] freehand draw   [x] follow edge   [x] draw/replace                |
      | [x] select area within mouse circle                                   |
      | [x] select matching color within mouse circle: [####]                 |
      | [x] select all matching colors within mouse circle:                   |
      |     mouse radius [___+-]   match level % [___+-]                      |
      |     mouse search range [___+|-]                                       |
      | Area Edge Blend Width [___+-]   Edge Creep [ + ] [ - ]                |
      | Line Color: [red] [green] [blue] [black] [white]                      |
      |                                                                       |
      | [Show] [Hide] [Finish] [Unselect] [Enable] [Disable] [Invert] [Done]  |
      |_______________________________________________________________________|

***/

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

   zdialog_add_widget(zd,"label","labhelp","dialog",helptext,"space=3");

   zdialog_add_widget(zd,"hbox","hbshape","dialog");
   zdialog_add_widget(zd,"check","ckrect","hbshape",ZTX("select rectangle"),"space=5");
   zdialog_add_widget(zd,"check","ckelips","hbshape",ZTX("select ellipse"),"space=10");

   zdialog_add_widget(zd,"hbox","hbdraw","dialog");
   zdialog_add_widget(zd,"check","ckdraw","hbdraw",ZTX("freehand draw"),"space=5");
   zdialog_add_widget(zd,"check","ckfollow","hbdraw",ZTX("follow edge"),"space=10");
   zdialog_add_widget(zd,"check","ckrepl","hbdraw",ZTX("draw/replace"),"space=10");

   zdialog_add_widget(zd,"hbox","hbm1","dialog");
   zdialog_add_widget(zd,"check","ckmouse","hbm1",ZTX("select area within mouse circle"),"space=5");

   zdialog_add_widget(zd,"hbox","hbm2","dialog");
   zdialog_add_widget(zd,"check","ckonecolor","hbm2",ZTX("select one matching color within mouse circle:"),"space=5");
   zdialog_add_widget(zd,"colorbutt","onecolor","hbm2","0|0|255","space=5");
   zdialog_add_ttip(zd,"onecolor",ZTX("first select the checkbox, then \n"
                                      "shift+click on image to set the color"));

   zdialog_add_widget(zd,"hbox","hbm3","dialog");
   zdialog_add_widget(zd,"check","ckallcolors","hbm3",ZTX("select all matching colors within mouse circle"),"space=5");

   zdialog_add_widget(zd,"hbox","hbmm","dialog");
   zdialog_add_widget(zd,"label","space","hbmm",0,"space=15");
   zdialog_add_widget(zd,"label","labmr","hbmm",ZTX("mouse radius"));
   zdialog_add_widget(zd,"spin","mouserad","hbmm","1|300|1|20","space=5|size=2");
   zdialog_add_widget(zd,"label","space","hbmm",0,"space=10");
   zdialog_add_widget(zd,"label","labmatch","hbmm",ZTX("match level %"));
   zdialog_add_widget(zd,"spin","colormatch","hbmm","0|100|1|90","space=5|size=2");

   zdialog_add_widget(zd,"hbox","hbm4","dialog");
   zdialog_add_widget(zd,"label","space","hbm4",0,"space=15");
   zdialog_add_widget(zd,"label","labrange","hbm4",ZTX("search range"));
   zdialog_add_widget(zd,"spin","searchrange","hbm4","1|20|1|3","space=5|size=1");

   zdialog_add_widget(zd,"hbox","hbm5","dialog");
   zdialog_add_widget(zd,"label","labblend","hbm5",ZTX("Area Edge Blend Width"),"space=5");
   zdialog_add_widget(zd,"spin","blendwidth","hbm5","0|500|1|0","size=2");
   zdialog_add_widget(zd,"label","space","hbm5",0,"space=10");
   zdialog_add_widget(zd,"label","labcreep","hbm5",ZTX("Edge Creep"),"space=3");
   zdialog_add_widget(zd,"button","creep+","hbm5"," + ","space=3");
   zdialog_add_widget(zd,"button","creep-","hbm5"," ‒ ","space=3");
   
   zdialog_add_widget(zd,"hbox","hbm5","dialog");                                //  15.09
   zdialog_add_widget(zd,"label","labcolor","hbm5",ZTX("Line Color:"),"space=5");
   zdialog_add_widget(zd,"imagebutt","red","hbm5","redball.png","size=15|space=5");
   zdialog_add_widget(zd,"imagebutt","green","hbm5","greenball.png","size=15|space=5");
   zdialog_add_widget(zd,"imagebutt","blue","hbm5","blueball.png","size=15|space=5");
   zdialog_add_widget(zd,"imagebutt","black","hbm5","blackball.png","size=15|space=5");
   zdialog_add_widget(zd,"imagebutt","white","hbm5","whiteball.png","size=15|space=5");

   zdialog_add_widget(zd,"hsep","hsep3","dialog",0,"space=4");
   zdialog_add_widget(zd,"hbox","hbb2","dialog",0,"space=2");
   zdialog_add_widget(zd,"label","space","hbb2",0,"expand");
   zdialog_add_widget(zd,"button","show","hbb2",Bshow,"space=2");
   zdialog_add_widget(zd,"button","hide","hbb2",Bhide,"space=2");
   zdialog_add_widget(zd,"button","finish","hbb2",Bfinish,"space=2");
   zdialog_add_widget(zd,"button","unselect","hbb2",Bunselect,"space=2");
   zdialog_add_widget(zd,"button","enable","hbb2",Benable,"space=2");
   zdialog_add_widget(zd,"button","disable","hbb2",Bdisable,"space=2");
   zdialog_add_widget(zd,"button","invert","hbb2",Binvert,"space=2");
   zdialog_add_widget(zd,"button","done","hbb2",Bdone,"space=2");
   zdialog_add_widget(zd,"label","space","hbb2",0,"space=2");

   zdialog_add_ttip(zd,"labblend",ZTX("area edits fade away within edge distance"));
   zdialog_add_ttip(zd,"blendwidth",ZTX("area edits fade away within edge distance"));

   sa_mouseradius = 30;                                                          //  initial values matching dialog
   sa_colormatch = 90;
   sa_searchrange = 2;
   sa_blend = 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,"ckrepl",0);
   zdialog_stuff(zd,"ckmouse",0);
   zdialog_stuff(zd,"ckonecolor",0);
   zdialog_stuff(zd,"ckallcolors",0);

   zdialog_run(zd,select_dialog_event,"save");                                   //  run dialog - parallel
   if (sa_stat) sa_show(1);                                                      //  show existing area, if any

   return;
}


//  dialog event and completion callback function

int select_dialog_event(zdialog *zd, cchar *event)
{
   int      Nckevents = 8, ii, kk, cc;
   cchar    *ckevents[8] = { "ckrect", "ckelips", "ckdraw", "ckfollow",
                             "ckrepl", "ckmouse", "ckonecolor", "ckallcolors" };

   if (! curr_file) event = "done";                                              //  image went away
   if (FGWM != 'F') event = "done";
   if (strmatch(event,"escape")) event = "done";                                 //  15.07

   if (strmatch(event,"done") || zd->zstat) {                                    //  done or cancel
      freeMouse();                                                               //  disconnect mouse function
      zdialog_free(zdsela);                                                      //  kill dialog
      if (sa_stat) {
         cc = Fpxb->ww * Fpxb->hh;                                               //  check if any pixels mapped         15.11
         for (ii = 0; ii < cc; ii++)
            if (sa_pixmap[ii]) break;
         if (ii == cc) sa_unselect();                                            //  no, delete area                    15.12
      }
      return 0;
   }

   if (CEF && CEF->Fpreview && CEF->zd)                                          //  use full-size image
      zdialog_send_event(CEF->zd,"fullsize");

   sa_validate();                                                                //  validate area, remove if no good

   if (CEF && CEF->Farea != 2) {                                                 //  select area not supported
      printz("*** select area ignored for this edit function \n");
      return 0;
   }

   if (! sa_stat) {                                                              //  no area, create one
      cc = Fpxb->ww * Fpxb->hh * sizeof(uint16);                                 //  allocate sa_pixmap[] for new area
      sa_pixmap = (uint16 *) zmalloc(cc);                                        //  maps pixels in area
      memset(sa_pixmap,0,cc);
      sa_currseq = sa_Ncurrseq = 0;                                              //  reset selection sequence
      sa_Npixel = sa_blend = sa_calced = 0;
      sa_fww = Fpxb->ww;                                                         //  valid image size for area
      sa_fhh = Fpxb->hh;
      sa_stat = 1;                                                               //  status = edit
      sa_mode = 0;
      zdialog_stuff(zd,"blendwidth",0);                                          //  init. blend width = 0
   }

   for (ii = 0; ii < Nckevents; ii++)                                            //  look for checkbox event
   {
      if (strmatch(event,ckevents[ii])) {                                        //  checkbox was changed
         zdialog_fetch(zd,event,kk);                                             //  checkbox status
         if (kk) {                                                               //  ON
            sa_mode = ii+1;                                                      //  edit mode 1-8
            if (sa_mode == mode_replace) sa_unfinish();                          //  unmap internal pixels              15.08
            sa_stat = 1;                                                         //  active edit status
            sa_Npixel = sa_blend = sa_calced = 0;                                //  make area unfinished
            zdialog_stuff(zd,"blendwidth",0);
            sa_show(1);
         }
         else sa_mode = 0;                                                       //  OFF, no edit mode, edit paused

         for (kk = 0; kk < Nckevents; kk++)                                      //  other checkboxes >> off
            if (kk != ii)
               zdialog_stuff(zd,ckevents[kk],0);
      }
   }

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

   if (strmatch(event,"colormatch"))                                             //  mouse color match limit, 0 - 99.9
      zdialog_fetch(zdsela,"colormatch",sa_colormatch);

   if (strmatch(event,"searchrange"))                                            //  mouse color match search range, x radius
      zdialog_fetch(zdsela,"searchrange",sa_searchrange);

   if (strmatch(event,"show")) sa_show(1);                                       //  show area
   if (strmatch(event,"hide")) sa_show(0);                                       //  hide area
   if (strmatch(event,"finish")) sa_finish();                                    //  finish (finalize) area
   if (strmatch(event,"unselect")) sa_unselect();                                //  unselect area
   if (strmatch(event,"enable")) sa_enable();                                    //  enable area
   if (strmatch(event,"disable")) sa_disable();                                  //  disable area
   if (strmatch(event,"invert")) sa_invert();                                    //  invert area

   if (strmatch(event,"blendwidth") && sa_stat == 3) {                           //  blend width changed and area finished
      zdialog_fetch(zd,"blendwidth",sa_blend);                                   //  update sa_blend
      if (sa_blend > 0) sa_edgecalc();                                           //  do edge calc. if not already
      if (sa_calced && CEF && CEF->zd)                                           //  if edit is active
         zdialog_send_event(CEF->zd,event);                                      //    notify edit dialog
   }

   if (strmatchN(event,"creep",5) && sa_stat == 3) {                             //  edge creep changed and area finished
      if (event[5] == '+') sa_edgecreep(+1);
      else sa_edgecreep(-1);
      sa_blend = 0;
      zdialog_stuff(zd,"blendwidth",0);
   }
   
   if (strmatch(event,"red")) LINE_COLOR = RED;                                  //  15.07
   if (strmatch(event,"green")) LINE_COLOR = GREEN;
   if (strmatch(event,"blue")) LINE_COLOR = BLUE;
   if (strmatch(event,"black")) LINE_COLOR = BLACK;
   if (strmatch(event,"white")) LINE_COLOR = WHITE;
   if (strstr("red green blue black white",event)) Fpaint2();

   if (sa_stat == 1 && sa_mode && Fshowarea)                                     //  active edit mode
   {
      if (sa_mode == mode_rect)
         takeMouse(sa_geom_mousefunc,dragcursor);                                //  rectangle
      if (sa_mode == mode_ellipse)
         takeMouse(sa_geom_mousefunc,dragcursor);                                //  ellipse
      if (sa_mode == mode_draw)
         takeMouse(sa_draw_mousefunc,drawcursor);                                //  freehand draw
      if (sa_mode == mode_follow)
         takeMouse(sa_draw_mousefunc,drawcursor);                                //  follow edge
      if (sa_mode == mode_replace)
         takeMouse(sa_draw_mousefunc,drawcursor);                                //  replace nearby
      if (sa_mode == mode_mouse)
         takeMouse(sa_mouse_select,0);                                           //  mouse radius select
      if (sa_mode == mode_onecolor)
         takeMouse(sa_mouse_select,0);                                           //  mouse radius, one color select
      if (sa_mode == mode_allcolors)
         takeMouse(sa_mouse_select,0);                                           //  mouse radius, all colors select
   }
   else {                                                                        //  edit paused or done
      freeMouse();                                                               //  disconnect mouse
      gdk_window_set_cursor(gdkwin,null);                                        //  normal cursor
      for (kk = 0; kk < Nckevents; kk++)                                         //  all checkboxes off
         zdialog_stuff(zd,ckevents[kk],0);
      sa_mode = 0;
   }

   return 0;
}


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

void sa_geom_mousefunc()
{
   static int  mx1, my1, mx2, my2;
   static int  mdx0, mdy0, drag;
   float       a, b, a2, b2;
   float       x, y, x2, y2, cx, cy;
   int         px, py, crflag = 0;

   if (sa_stat != 1) return;                                                     //  area gone?

   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
      drag = 0;
      Fpaint2();
      return;
   }

   if (! Mxdrag && ! Mydrag) return;                                             //  no drag underway

   if (Mxdown != mdx0 || Mydown != mdy0) {                                       //  new drag initiated
      mdx0 = Mxdown;
      mdy0 = Mydown;
      mx1 = mdx0;                                                                //  drag start, one corner
      my1 = mdy0;
      drag = 0;
      Mxdrag = Mydrag = 0;
      return;
   }

   mx2 = Mxdrag;                                                                 //  drag continues, 2nd corner
   my2 = Mydrag;
   Mxdrag = Mydrag = 0;

   if (drag) sa_unselect_pixels();                                               //  remove prior drag result
   sa_nextseq();                                                                 //  next sequence number
   drag = 1;

   if (sa_mode == mode_rect)                                                     //  draw rectangle
   {
      if (! mwcr) {
         mwcr = gdk_cairo_create(gdkwin);                                        //  create cairo context if not already
         cairo_set_line_width(mwcr,1);
         crflag = 1;
      }

      sa_draw_line(mx1,my1,mx2,my1);                                             //  draw 4 lines
      sa_draw_line(mx2,my1,mx2,my2);                                             //  (makes own cairo_t)
      sa_draw_line(mx2,my2,mx1,my2);
      sa_draw_line(mx1,my2,mx1,my1);

      if (crflag) {
         cairo_destroy(mwcr);
         mwcr = 0;
      }
   }

   if (sa_mode == mode_ellipse)                                                  //  draw ellipse
   {
      if (! mwcr) {
         mwcr = gdk_cairo_create(gdkwin);                                        //  create cairo context if not already
         cairo_set_line_width(mwcr,1);
         crflag = 1;
      }

      a = abs(mx2 - mx1);                                                        //  ellipse constants from
      b = abs(my2 - my1);                                                        //    enclosing rectangle
      a2 = a * a;
      b2 = b * b;
      cx = mx1;                                                                  //  center at drag origin
      cy = my1;

      for (y = -b; y < b; y++)                                                   //  step through y values
      {
         y2 = y * y;
         x2 = a2 * (1 - y2 / b2);
         x = sqrtf(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 = sqrtf(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);
      }

      if (crflag) {
         cairo_destroy(mwcr);
         mwcr = 0;
      }
   }

   return;
}


//  select area mouse function - freehand draw, follow edge, replace nearby

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

   int         mx1, my1, mx2, my2;
   int         ii, npdist, npx, npy;
   int         click, newseq, thresh;
   int         crflag = 0;
   static int  drag = 0, openend = 0;
   static int  mdx0, mdy0, mdx1, mdy1;

   if (sa_stat != 1) return;                                                     //  area gone?

   sa_thresh = 4.0 / Mscale + 1;                                                 //  mouse pixel distance threshold

   if (! mwcr) {
      mwcr = gdk_cairo_create(gdkwin);                                           //  create cairo context if not already
      crflag = 1;
   }

   if (! (LMclick || RMclick || Mxdrag || Mydrag)) {                             //  no mouse action
      if (openend) {
         openend = 0;                                                            //  close pending gap after
         mx1 = sa_endpx[sa_currseq];                                             //    prior draw/replace
         my1 = sa_endpy[sa_currseq];
         thresh = 3 * sa_thresh;
         npdist = sa_nearpix(mx1,my1,thresh,mx2,my2,1);
         if (npdist) sa_draw_line(mx1,my1,mx2,my2);
      }
      goto draw_exit;
   }

   click = newseq = 0;

   if (LMclick || Mxdrag || Mydrag)                                              //  left mouse click or mouse drag
   {
      if (LMclick)                                                               //  left mouse click
      {
         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,0);                       //  find nearest pixel
            if (! npdist) break;
            ii = npy * Fpxb->ww + npx;
            if (sa_pixmap[ii]) {
               sa_pixmap[ii] = 0;                                                //  unmap pixel
               erase_pixel(npx,npy);                                             //  erase pixel
            }
         }

         goto draw_exit;
      }

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

      if (sa_currseq == 0 && newseq)                                             //  1st pixel(s) of 1st sequence
      {
         sa_nextseq();                                                           //  set next (1st) sequence no.
         sa_draw_line(mx1,my1,mx2,my2);                                          //  draw initial pixel or line
         sa_endpx[sa_currseq] = mx2;
         sa_endpy[sa_currseq] = my2;
         goto draw_exit;
      }

      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 (drag > 50 && sa_mode != mode_replace) newseq = 1;                      //  incr. sequence each 50 pixels

      if (newseq) {
         sa_nextseq();                                                           //  set next sequence no.
         drag = 1;                                                               //  drag length within sequence
      }

      if (sa_mode == mode_draw) sa_draw_line(mx1,my1,mx2,my2);                   //  draw line from end pixel to mouse
      if (sa_mode == mode_follow) sa_follow_edge(mx1,my1,mx2,my2);               //  follow edge from end pixel to mouse
      if (sa_mode == mode_replace) sa_redraw(mx1,my1,mx2,my2);                   //  tweak end pixel to mouse

      sa_endpx[sa_currseq] = mx2;                                                //  set end pixel for this sequence
      sa_endpy[sa_currseq] = my2;

      if (sa_mode == mode_replace) openend = 1;
      else openend = 0;
   }

   else if (RMclick)                                                             //  right mouse click
      sa_unselect_pixels();                                                      //  remove latest selection

draw_exit:

   LMclick = RMclick = 0;                                                        //  stop further mouse action
   Mxdrag = Mydrag = 0;

   if (crflag) {
      cairo_destroy(mwcr);
      mwcr = 0;
   }

   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.
//  fx flag: exclude current selection (sequence no.) from search.

int sa_nearpix(int mx, int my, int rad2, int &npx, int &npy, int fx)
{
   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 > Fpxb->ww-1) continue;
         if (qy < 0 || qy > Fpxb->hh-1) continue;
         ii = qy * Fpxb->ww + qx;
         if (! sa_pixmap[ii]) continue;
         if (fx && sa_pixmap[ii] == sa_currseq) continue;                        //  exclude curr. selection
         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)
{
   int      pxm, pym, crflag = 0;
   float    slope;

   if (sa_stat != 1) return;                                                     //  area gone?

   if (! mwcr) {
      mwcr = gdk_cairo_create(gdkwin);                                           //  create cairo context if not already
      cairo_set_line_width(mwcr,1);
      crflag = 1;
   }

   if (px1 == px2 && py1 == py2) {                                               //  only one pixel
      sa_draw1pix(px1,py1);
      if (crflag) {
         cairo_destroy(mwcr);
         mwcr = 0;
      }
      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);
         }
      }
   }

   if (crflag) {
      cairo_destroy(mwcr);
      mwcr = 0;
   }

   return;
}


//  add to select area and draw one pixel if not already
//  mwcr must be set by caller

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

   if (px < 0 || px > Fpxb->ww-1) return;
   if (py < 0 || py > Fpxb->hh-1) return;

   ii = Fpxb->ww * py + px;
   if (! sa_pixmap[ii]) {                                                        //  if not already selected,
      sa_pixmap[ii] = sa_currseq;                                                //    map pixel to curr. selection
      sa_Ncurrseq++;
   }

   draw_pixel(px,py);                                                            //  draw pixel
   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)
{
   float    sa_get_contrast(int px, int py);

   float    px3, py3, px4, py4, px5, py5, px6, py6;
   float    dx, dy, dist, contrast, maxcontrast;

   if (sa_stat != 1) return;                                                     //  area gone?

   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 < 5) break;                                                       //  line laggs mouse 5 pixels

      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
         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;
   }

   px2 = px3;                                                                    //  return lagging end point
   py2 = py3;
   return;
}


//  freehand draw while erasing nearby pixels, effectively replacing them

void sa_redraw(int mx1, int my1, int mx2, int my2)
{
   int         ii, npx, npy;
   int         thresh, npdist, d1, d2;

   thresh = 2 * sa_thresh;

   npdist = sa_nearpix(mx1,my1,thresh,npx,npy,0);                                //  nearest pixel to (mx1,my1)
   if (npdist) {
      ii = npy * Fpxb->ww + npx;
      if (sa_pixmap[ii] != sa_currseq) {                                         //  if not in current line,
         sa_pixmap[ii] = sa_currseq;                                             //    add to current line
         sa_draw_line(npx,npy,mx1,my1);
      }
   }

   sa_draw_line(mx1,my1,mx2,my2);                                                //  draw line from (mx1,my1) to (mx2,my2)

   while (true)
   {
      npdist = sa_nearpix(mx2,my2,thresh,npx,npy,1);                             //  nearest pixel to (mx2,my2) not in line
      if (! npdist) return;
      d1 = (npx-mx1)*(npx-mx1) + (npy-my1)*(npy-my1);
      d2 = (npx-mx2)*(npx-mx2) + (npy-my2)*(npy-my2);
      if (d2 >= d1) return;
      ii = npy * Fpxb->ww + npx;                                                 //  motion toward pixel
      sa_pixmap[ii] = 0;                                                         //  unmap pixel
      erase_pixel(npx,npy);                                                      //  erase pixel
   }

   return;
}


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

float sa_get_contrast(int px, int py)
{
   int         map[4][2] = { {1, 0}, {1, 1}, {0, 1}, {-1, 1} };
   int         ii, qx, qy;
   uint8       *pix1, *pix2;
   float       match, contrast, maxcontrast = 0;

   if (px < 1 || px > Fpxb->ww-2) return 0;                                      //  avoid edge pixels
   if (py < 1 || py > Fpxb->hh-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 = PXBpix(Fpxb,px+qx,py+qy);
      pix2 = PXBpix(Fpxb,px-qx,py-qy);
      match = PIXMATCH(pix1,pix2);                                               //  0..1 = zero..perfect match
      contrast = 1.0 - match;                                                    //  max. contrast = 1.0
      if (contrast > maxcontrast) maxcontrast = contrast;
   }

   return maxcontrast;
}


//  mouse function for edit modes with mouse / color selection
//  sa_mode = mouse = select area within mouse radius
//  sa_mode = onecolor = select one matching color within mouse radius
//  sa_mode = allcolors = select all matching colors within mouse radius
//                        and extend to contiguous matching colors
//  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_select()
{
   void sa_mouse_select1(int mode, int select);
   void sa_mouse_select2(int select);

   int         newdrag, crflag = 0;
   static int  pxcc, mxdown, mydown, dragseq;

   if (sa_stat != 1) return;                                                     //  area gone?

   if (sa_mode == mode_allcolors && ! sa_pixselc) {                              //  allocate memory for this mode
      pxcc = Fpxb->ww * Fpxb->hh;
      sa_stackdirec = (char *) zmalloc(pxcc);
      sa_stackii = (int *) zmalloc(4*pxcc);
      sa_maxstack = pxcc;
      sa_Nstack = 0;
      sa_pixselc = (char *) zmalloc(pxcc);
   }

   if (sa_mode != mode_allcolors && sa_pixselc) {                                //  free memory otherwise
      zfree(sa_stackdirec);
      zfree(sa_stackii);
      zfree(sa_pixselc);
      sa_stackdirec = 0;
      sa_stackii = 0;
      sa_pixselc = 0;
   }

   if (sa_pixselc) memset(sa_pixselc,0,pxcc);                                    //  do always

   if (! mwcr) {
      mwcr = gdk_cairo_create(gdkwin);                                           //  create cairo context if not already
      cairo_set_line_width(mwcr,1);
      crflag = 1;
   }

   if (LMclick) {                                                                //  left mouse click
      sa_mousex = Mxclick;
      sa_mousey = Myclick;
      LMclick = 0;
      sa_nextseq();                                                              //  set next sequence no.
      dragseq = 0;                                                               //  reset drag counter
      if (sa_mode == mode_onecolor)
         sa_mouse_select1(1,1);                                                  //  set color for matching within mouse
      else
         sa_mouse_select2(1);                                                    //  select in mouse + matching colors beyond
   }

   if (RMclick) {                                                                //  right mouse click
      RMclick = 0;
      sa_unselect_pixels();                                                      //  remove latest selection
      sa_show(1);
   }

   if (Mxdrag || Mydrag)                                                         //  drag is underway
   {
      sa_mousex = Mxdrag;                                                        //  new mouse position
      sa_mousey = Mydrag;
      Mxdrag = Mydrag = 0;
      newdrag = 0;

      if (Mxdown != mxdown || Mydown != mydown) {                                //  detect if new drag started
         newdrag = 1;                                                            //  yes
         mxdown = Mxdown;                                                        //  save drag start position
         mydown = Mydown;
      }

      if (Mbutton == 1) {                                                        //  left drag, select matching colors
         if (sa_mode == mode_onecolor)
            sa_mouse_select1(2,1);                                               //  select matching colors within mouse
         else
            sa_mouse_select2(1);                                                 //  select in mouse + matching colors beyond
         if (newdrag || ++dragseq > 30) {                                        //  limit work per sequence no.
            sa_nextseq();                                                        //  set next sequence no.
            dragseq = 0;                                                         //  reset drag counter
         }
      }

      if (Mbutton == 3) {                                                        //  right drag, find and unselect pixels
         if (sa_mode == mode_onecolor)
            sa_mouse_select1(2,0);
         else
            sa_mouse_select2(0);
         sa_map_pixels();                                                        //  re-establish area edge
      }
   }

   draw_mousecircle(Mxposn,Myposn,sa_mouseradius,0);                             //  move mouse circle with mouse

   if (crflag) {
      cairo_destroy(mwcr);
      mwcr = 0;
   }

   return;
}


//  Left click - set one color to match from pixel at mouse position.
//  Left/right drag - select/unselect matching pixels within mouse radius.

void sa_mouse_select1(int mode, int select)
{
   int            mrad, mrad2;
   int            rad2, ii, jj;
   int            px, py, rx, ry;
   int            xlo, xhi, ylo, yhi, newpix;
   uint8          *pix1;
   float          match1, match2;
   static float   red, green, blue;
   static char    colorbutt[16];

   px = sa_mousex;
   py = sa_mousey;
   if (px < 0 || px > Fpxb->ww-2) return;                                        //  mouse outside image
   if (py < 0 || py > Fpxb->hh-2) return;

   if (mode == 1)                                                                //  left click
   {
      red = green = blue = 0;

      for (ii = -1; ii <= +1; ii++)                                              //  get mean color for 3x3 pixels
      for (jj = -1; jj <= +1; jj++)                                              //  centered at px, py
      {
         pix1 = PXBpix(Fpxb,px+ii,py+jj);
         red += pix1[0];
         green += pix1[1];
         blue += pix1[2];
      }

      red = red / 9.0;                                                           //  color to match
      green = green / 9.0;
      blue = blue / 9.0;

      snprintf(colorbutt,16,"%.0f|%.0f|%.0f",red,green,blue);                    //  set button to new match color
      zdialog_stuff(zdsela,"onecolor",colorbutt);
      return;
   }

//  select = 1/0 for left/right drag                                             //  left or right drag
//  test all pixels within mouse, select/unselect matching colors

   zdialog_fetch(zdsela,"onecolor",colorbutt,16);                                //  get color button color
   ii = sscanf(colorbutt,"%f|%f|%f",&red,&green,&blue);
   if (ii != 3) {
      printz("color button error: %d %.0f|%.0f|%.0f \n",ii,red,green,blue);
      return;
   }

   match1 = 0.01 * sa_colormatch;                                                //  color match level, 0.01 to 1.0
   sa_Ncurrseq = 0;                                                              //  count newly selected pixels

   mrad = sa_mouseradius;
   mrad2 = mrad * mrad;

   xlo = px - mrad;                                                              //  track changed area
   xhi = px + mrad;
   ylo = py - mrad;
   yhi = py + mrad;

   for (rx = -mrad; rx <= mrad; rx++)                                            //  loop every pixel in radius of mouse
   for (ry = -mrad; ry <= mrad; ry++)
   {
      rad2 = rx * rx + ry * ry;
      if (rad2 > mrad2) continue;                                                //  outside radius
      px = sa_mousex + rx;
      py = sa_mousey + ry;
      if (px < 0 || px >= Fpxb->ww) continue;                                    //  off the image edge
      if (py < 0 || py >= Fpxb->hh) continue;

      pix1 = PXBpix(Fpxb,px,py);                                                 //  pixel to test
      match2 = RGBMATCH(red,green,blue,pix1[0],pix1[1],pix1[2]);                 //  0..1 = zero..perfect match
      if (match2 < match1) continue;                                             //  not a match

      ii = Fpxb->ww * py + px;
      newpix = 0;

      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
            newpix = 1;
         }
      }
      else if (sa_pixmap[ii]) {                                                  //  unselect mode
         sa_pixmap[ii] = 0;
         newpix = 1;
      }

      if (newpix) {
         if (px < xlo) xlo = px;                                                 //  range of changed pixels
         if (px > xhi) xhi = px;
         if (py < ylo) ylo = py;
         if (py > yhi) yhi = py;
      }
   }

   return;
}


//  Find all pixels within mouse radius and optionally extend selection
//  to all contiguous pixels matching colors within mouse and within range.
//  Select or unselect the matching pixels.

void sa_mouse_select2(int select)
{
   int         mrad, mrad2, srange2;
   int         rad1, rad2, ii;
   int         kk, px, py, rx, ry;
   int         ppx, ppy, npx, npy;
   int         xlo, xhi, ylo, yhi, newpix;
   uint8       *pix1;
   float       red, green, blue, ff1, ff2;
   float       match1, match2, match3;
   int         thresh, Npixels;
   char        direc;

   struct Ctab_t  {                                                              //  table of pixel colors in mouse circle
      int         count;                                                         //  count of pixels with this color
      float       rgb[3];                                                        //  RGB color
   };
   Ctab_t    Ctab[1000];                                                         //  table
   int         Ntab;                                                             //  table count

   px = sa_mousex;
   py = sa_mousey;
   if (px < 0 || px > Fpxb->ww-1) return;                                        //  mouse outside image
   if (py < 0 || py > Fpxb->hh-1) return;

   sa_Ncurrseq = 0;                                                              //  count newly selected pixels

   mrad = sa_mouseradius;
   mrad2 = mrad * mrad;

   for (rx = -mrad; rx <= mrad; rx++)                                            //  loop every pixel in radius of mouse
   for (ry = -mrad; ry <= mrad; ry++)
   {
      rad2 = rx * rx + ry * ry;
      if (rad2 > mrad2) continue;                                                //  outside radius
      px = sa_mousex + rx;
      py = sa_mousey + ry;
      if (px < 0 || px >= Fpxb->ww) continue;                                    //  off the image edge
      if (py < 0 || py >= Fpxb->hh) continue;

      ii = Fpxb->ww * py + px;

      if (select) {                                                              //  select pixel
         if (sa_pixmap[ii]) continue;                                            //  already selected
         sa_pixmap[ii] = sa_currseq;                                             //  map pixel to current sequence
         sa_Ncurrseq++;                                                          //  current sequence pixel count
      }
      else sa_pixmap[ii] = 0;                                                    //  unselect
   }

   if (sa_mode == mode_mouse) {                                                  //  no color matching, done
      if (! select) sa_map_pixels();                                             //  re-establish edge
      Fpaint4(px-mrad,py-mrad,2*mrad,2*mrad);                                    //  repaint changed area
      return;
   }

//  find all colors within mouse radius and build table of mouse colors
//    and counts of pixels (nearly) matching these colors

   match1 = 0.01 * sa_colormatch;                                                //  user color match level, 0.01 to 1.0
   match3 = match1 + 0.6 * (1.0 - match1);                                       //  level for combining colors
   Npixels = Ntab = 0;                                                           //  match color counts

   rad1 = mrad - 1;                                                              //  mouse radius - 1
   if (rad1 < 1) rad1 = 1;
   rad2 = rad1 * rad1;

   for (rx = -rad1; rx <= rad1; rx++)                                            //  find every pixel within mouse
   for (ry = -rad1; ry <= rad1; ry++)
   {
      if (rx * rx + ry * ry > rad2) continue;
      px = sa_mousex + rx;
      py = sa_mousey + ry;
      if (px < 1 || px > Fpxb->ww-2) continue;                                   //  off the image edge
      if (py < 1 || py > Fpxb->hh-2) continue;

      Npixels++;                                                                 //  count pixels inside mouse circle

      pix1 = PXBpix(Fpxb,px,py);
      red = pix1[0];                                                             //  average of 3x3 block removed       15.08
      green = pix1[1];
      blue = pix1[2];

      for (ii = 0; ii < Ntab; ii++) {                                            //  see if color is already included
         match2 = RGBMATCH(red,green,blue,                                       //  0..1 = zero..perfect match
            Ctab[ii].rgb[0],Ctab[ii].rgb[1],Ctab[ii].rgb[2]);
         if (match2 >= match3) break;                                            //  matches table color within margin
      }

      if (ii < Ntab) {                                                           //  average aggregated pixel color
         ff1 = Ctab[ii].count;
         ff2 = 1.0 / (ff1 + 1.0);
         Ctab[ii].rgb[0] = (Ctab[ii].rgb[0] * ff1 + red) * ff2;
         Ctab[ii].rgb[1] = (Ctab[ii].rgb[1] * ff1 + green) * ff2;
         Ctab[ii].rgb[2] = (Ctab[ii].rgb[2] * ff1 + blue) * ff2;
         Ctab[ii].count += 1;                                                    //  count of pixels matching color
      }
      else {
         Ctab[ii].rgb[0] = red;                                                  //  add unmatched pixel color to table
         Ctab[ii].rgb[1] = green;
         Ctab[ii].rgb[2] = blue;
         Ctab[ii].count = 1;
         Ntab++;
         if (Ntab == 1000) goto endmatch;                                        //  exit two nested loops
      }
   }        endmatch:

   int  keys[1][3] = { { 0, sizeof(int), 4 } };                                  //  sort position, length, descending
   MemSort((char *) Ctab, sizeof(Ctab_t), Ntab, keys, 1);                        //  sort descending count of matching pixels

   thresh = 0.03 * Ntab;                                                         //  exclude minority pixels            15.08
   if (Ntab < 100) thresh = 3;
   if (Ntab < 40) thresh = 2;
   for (ii = 0; ii < Ntab; ii++)
      if (Ctab[ii].count < thresh) break;
   Ntab = ii;

//  search pixels outside mouse radius but within range for matching colors

   srange2 = mrad * sa_searchrange;                                              //  search range (* mouse radius)
   srange2 = srange2 * srange2;                                                  //  squared

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

   xlo = px - mrad;                                                              //  track limits of changed area
   xhi = px + mrad;
   ylo = py - mrad;
   yhi = py + mrad;

   sa_stackii[0] = ii;                                                           //  put 1st pixel into stack
   sa_stackdirec[0] = 'a';                                                       //  direction = ahead
   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 / Fpxb->ww;                                                        //  reconstruct px, py
      px = ii - Fpxb->ww * 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 / Fpxb->ww;
         ppx = ii - ppy * Fpxb->ww;
      }
      else {
         ppx = px - 1;                                                           //  if only one, assume prior = left
         ppy = py;
      }

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

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

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

      if (npx < 0 || npx > Fpxb->ww-1) continue;                                 //  pixel off the edge
      if (npy < 0 || npy > Fpxb->hh-1) continue;

      ii = npy * Fpxb->ww + npx;
      if (sa_pixselc[ii]) continue;                                              //  already in current selection

      rx = npx - Mxposn;                                                         //  limit search to
      ry = npy - Myposn;                                                         //    mouse radius * search range
      rad2 = rx * rx + ry * ry;
      if (rad2 > srange2) continue;

      pix1 = PXBpix(Fpxb,npx,npy);                                               //  pixel at mouse

      for (kk = 0; kk < Ntab; kk++) {                                            //  compare pixel RGB to mouse colors
         match2 = PIXMATCH(pix1,Ctab[kk].rgb);                                   //  0..1 = zero..perfect match
         if (match2 >= match1) break;                                            //  good match, accept pixel
      }

      if (kk == Ntab) continue;                                                  //  not within range of any color

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

      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
            newpix = 1;
         }
      }
      else if (sa_pixmap[ii]) {                                                  //  unselect mode
         sa_pixmap[ii] = 0;
         newpix = 1;
      }

      if (newpix) {
         if (npx < xlo) xlo = npx;                                               //  range of changed pixels
         if (npx > xhi) xhi = npx;
         if (npy < ylo) ylo = npy;
         if (npy > yhi) yhi = npy;
      }

      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
   }

   Fpaint4(xlo,ylo,xhi-xlo+1,yhi-ylo+1);                                         //  repaint changed area
   return;
}


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

void sa_nextseq()
{
   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()
{
   int      px, py, xlo, xhi, ylo, yhi;

   xlo = Fpxb->ww;
   xhi = 0;
   ylo = Fpxb->hh;
   yhi = 0;

   if (sa_stat != 1) return;                                                     //  area gone?
   if (! sa_currseq) return;                                                     //  no pixels mapped

   for (int ii = 0; ii < Fpxb->ww * Fpxb->hh; ii++) {
      if (sa_pixmap[ii] == sa_currseq) {
         sa_pixmap[ii] = 0;                                                      //  unmap current selection
         py = ii / Fpxb->ww;
         px = ii - Fpxb->ww * py;
         if (px < xlo) xlo = px;                                                 //  range of changed pixels
         if (px > xhi) xhi = px;
         if (py < ylo) ylo = py;
         if (py > yhi) yhi = py;
      }
   }

   if (xhi >= xlo)
      Fpaint4(xlo,ylo,xhi-xlo+1,yhi-ylo+1);                                      //  repaint changed area

   if (sa_currseq > sa_initseq) {                                                //  reduce sequence no.
      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  *sa_finzd = 0;                                                          //  finish area zdialog
VOL int  sa_fincancel;                                                           //  finish area - user cancel

void sa_finish()                                                                 //  overhauled
{
   void sa_finish_mousefunc();

   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         zstat, cc;

   if (! sa_stat) return;                                                        //  no area?
   if (! sa_validate()) return;                                                  //  invalid for current image
   if (sa_mode == mode_image) return;                                            //  a whole image area

   sa_map_pixels();                                                              //  find edge pixels
   if (! sa_Npixel) return;

   sa_finish_auto();                                                             //  auto-finish mouse-selected areas
   sa_show(1);

   cc = Fpxb->ww * Fpxb->hh;
   if (sa_stackdirec) zfree(sa_stackdirec);
   sa_stackdirec = (char *) zmalloc(cc);
   if (sa_stackii) zfree(sa_stackii);
   sa_stackii = (int *) zmalloc(cc * sizeof(int));
   sa_maxstack = cc;

   sa_fincancel = 0;
   sa_Nstack = 0;

   sa_finzd = zdialog_new(ZTX("finish area"),pwin,Bkeep,Bundo,null);             //  dialog for user to click inside
   zdialog_add_widget(sa_finzd,"hbox","hbmess","dialog",0,"space=3");            //    each enclosed area
   zdialog_add_widget(sa_finzd,"label","fmess","hbmess",fmess,"space=5");

   takeMouse(sa_finish_mousefunc,dragcursor);                                    //  connect mouse function

   zdialog_run(sa_finzd,0,"parent");                                             //  run dialog, wait for completion
   zstat = zdialog_wait(sa_finzd);
   zdialog_free(sa_finzd);
   sa_finzd = 0;

   freeMouse();                                                                  //  disconnect mouse

   if (! sa_stat) return;                                                        //  area gone?
   if (zstat != 1) sa_fincancel = 1;                                             //  user cancel

   while (sa_Nstack) zmainloop();                                                //  wait for pixel search to complete
   
   if (sa_fincancel) {                                                           //  user cancel
      sa_unfinish();                                                             //  unmap interior pixels, set edit mode
      return;
   }

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

   if (sa_Npixel < 10) {                                                         //  ignore tiny area
      zmessageACK(Mwin,ZTX("found %d pixels"),sa_Npixel);
      return;
   }

   sa_stat = 3;                                                                  //  area is finished
   areanumber++;                                                                 //  next sequential number
   sa_calced = sa_blend = 0;                                                     //  edge calculation is missing
   if (zdsela) zdialog_stuff(zdsela,"blendwidth",0);
   Fpaint2();
   return;
}


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

void sa_finish_mousefunc()                                                       //  overhauled
{
   void sa_finish_mappix();

   int      ii, px, py;
   int      npaint, crflag = 0;
   
   if (! LMclick) return;
   LMclick = 0;

   if (! sa_stat) return;                                                        //  area gone?
   if (sa_Nstack) return;                                                        //  prior search still busy            16.07

   ii = Fpxb->ww * Myclick + Mxclick;                                            //  seed pixel from mouse click
   if (sa_pixmap[ii] == 1) return;                                               //  ignore if edge pixel
   sa_pixmap[ii] = 2;                                                            //  map the pixel, inside area
   sa_stackii[0] = ii;                                                           //  put seed pixel into stack
   sa_stackdirec[0] = 'a';                                                       //  direction = ahead
   sa_Nstack = 1;                                                                //  stack count

   sa_finish_mappix();                                                           //  do pixel search

   if (! mwcr) {
      mwcr = gdk_cairo_create(gdkwin);                                           //  create cairo context if not already
      cairo_set_line_width(mwcr,1);
      crflag = 1;
   }

   npaint = 2.0 / Mscale + 1;                                                    //  area fill, sparse

   for (py = 0; py < Fpxb->hh; py += npaint)                                     //  mark pixels inside area
   for (px = 0; px < Fpxb->ww; px += npaint)
   {
      ii = py * Fpxb->ww + px;
      if (sa_pixmap[ii]) draw_pixel(px,py);
   }

   if (crflag) {
      cairo_destroy(mwcr);
      mwcr = 0;
   }

   return;
}


//  function to map all pixels found within defined edge pixels.

void sa_finish_mappix()                                                          //  16.07
{
   int         px, py, ii, jj, kk;
   int         ppx, ppy, npx, npy;
   char        direc;

   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 / Fpxb->ww;                                                        //  reconstruct px, py
      px = ii - Fpxb->ww * py;

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

      if (sa_Nstack > 1) {
         jj = sa_Nstack - 2;                                                     //  get prior pixel in stack
         jj = sa_stackii[jj];
         ppy = jj / Fpxb->ww;
         ppx = jj - ppy * Fpxb->ww;
      }
      else {
         ppx = px - 1;                                                           //  if only one, assume prior = left
         ppy = py;
      }

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

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

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

      if (npx < 0 || npx >= Fpxb->ww || npy < 0 || npy >= Fpxb->hh) {            //  next pixel off image edge          15.09
         sa_pixmap[ii] = 1;                                                      //  this pixel is edge pixel
         continue;
      }

      jj = npy * Fpxb->ww + npx;

      if (Fpxb->nc > 3 && PXBpix(Fpxb,npx,npy)[3] < 254) {                       //  next pixel in transparent area     15.09
         sa_pixmap[ii] = 1;                                                      //  this pixel is edge pixel
         sa_pixmap[jj] = 1;                                                      //  next pixel is edge pixel
         continue;
      }

      if (sa_pixmap[jj]) continue;                                               //  next pixel already mapped

      sa_pixmap[jj] = 2;                                                         //  map next pixel, inside area
      kk = sa_Nstack++;                                                          //  put pixel into stack
      sa_stackii[kk] = jj;
      sa_stackdirec[kk] = 'a';                                                   //  direction = ahead
   }

   sa_Nstack = 0;                                                                //  thread is done
   return;
}


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

void sa_finish_auto()
{
   if (! sa_stat) return;                                                        //  no area?
   if (! sa_validate()) return;                                                  //  invalid for current image

   sa_stat = 1;                                                                  //  area is unfinished
   sa_Npixel = 0;

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

   if (sa_Npixel < 10) {
      zmessageACK(Mwin,ZTX("found %d pixels"),sa_Npixel);
      sa_unselect();                                                             //  16.07
      return;
   }

   sa_stat = 3;                                                                  //  area is finished
   areanumber++;                                                                 //  next sequential number
   sa_calced = sa_blend = 0;                                                     //  edge calculation is missing
   Fpaint2();
   return;
}


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

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

   for (py = 0; py < Fpxb->hh; py++)                                             //  loop all pixels
   for (px = 0; px < Fpxb->ww; px++)
   {
      ii = py * Fpxb->ww + px;                                                   //  clear interior pixels found
      if (sa_pixmap[ii] == 2) sa_pixmap[ii] = 0;                                 //    by finish search function
   }

   sa_stat = 1;                                                                  //  resume edit mode
   sa_calced = sa_blend = 0;
   if (zdsela) zdialog_stuff(zdsela,"blendwidth",0);
   Fpaint2();
   return;
}


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

void sa_map_pixels()
{
   int      npix, px, py, ii, kk;

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

   sa_minx = Fpxb->ww;
   sa_maxx = 0;
   sa_miny = Fpxb->hh;
   sa_maxy = 0;
   npix = 0;

   for (ii = 0; ii < Fpxb->ww * Fpxb->hh; ii++)
   {
      if (! sa_pixmap[ii]) continue;                                             //  outside area
      npix++;                                                                    //  count pixels inside area

      py = ii / Fpxb->ww;                                                        //  simplify
      px = ii - Fpxb->ww * py;

      if (px >= sa_maxx) sa_maxx = px+1;                                         //  get enclosing rectangle
      if (px < sa_minx) sa_minx = px;                                            //  (sa_maxx = last pixel + 1)
      if (py >= sa_maxy) sa_maxy = py+1;
      if (py < sa_miny) sa_miny = py;

      if (px == 0 || px == Fpxb->ww-1 || py == 0 || py == Fpxb->hh-1)            //  image edge
         goto edgepix;

      if (Fpxb->nc > 3 && PXBpix(Fpxb,px,py)[3] < 254)                           //  transparency edge                  15.09
         goto edgepix;
      
      if (! sa_pixmap[ii-1] || ! sa_pixmap[ii+1]) goto edgepix;                  //  check 8 neighbor pixels
      kk = ii - Fpxb->ww;
      if (! sa_pixmap[kk-1] || ! sa_pixmap[kk] || ! sa_pixmap[kk+1]) goto edgepix;
      kk = ii + Fpxb->ww;
      if (! sa_pixmap[kk-1] || ! sa_pixmap[kk] || ! 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

   sa_minx -= 10;                                                                //  add margins where possible
   if (sa_minx < 0) sa_minx = 0;
   sa_maxx += 10;
   if (sa_maxx > Fpxb->ww) sa_maxx = Fpxb->ww;
   sa_miny -= 10;
   if (sa_miny < 0) sa_miny = 0;
   sa_maxy += 10;
   if (sa_maxy > Fpxb->hh) sa_maxy = Fpxb->hh;

   return;
}


//  Find the edge pixels surrounding the clicked pixel location.
//  Trace around the edge outline to see if there is a gap.

void m_select_find_gap(GtkWidget *, cchar *menu)                                 //  16.07
{
   int  sa_find_gap_dialogfunc(zdialog *zd, cchar *event);
   void sa_find_gap_mousefunc();

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

   F1_help_topic = "find_area_gap";
   
   if (! sa_stat) return;                                                        //  no area?
   if (! sa_validate()) return;                                                  //  invalid for current image
   if (sa_mode == mode_image) return;                                            //  a whole image area

   if (sa_finzd) zdialog_destroy(sa_finzd);                                      //  terminate finish dialog

   sa_map_pixels();                                                              //  find edge pixels
   if (! sa_Npixel) return;

   sa_show(1);

   cc = Fpxb->ww * Fpxb->hh;
   if (sa_stackii) zfree(sa_stackii);
   sa_stackii = (int *) zmalloc(cc * sizeof(int));
   sa_Nstack = 0;
   sa_maxstack = cc;

   zd = zdialog_new(ZTX("find outline gap"),Mwin,BOK,null);                      //  dialog for user to click inside
   zdialog_add_widget(zd,"hbox","hbmess","dialog",0,"space=3");                  //    each enclosed area
   zdialog_add_widget(zd,"label","fmess","hbmess",fmess,"space=5");

   zdialog_run(zd,sa_find_gap_dialogfunc,"save");                                //  run dialog
   takeMouse(sa_find_gap_mousefunc,dragcursor);                                  //  connect mouse function
   return;
}


//  dialog event and completion function

int sa_find_gap_dialogfunc(zdialog *zd, cchar *event)
{
   void sa_find_gap_mousefunc();

   if (strmatch(event,"focus"))    
      takeMouse(sa_find_gap_mousefunc,dragcursor);                               //  connect mouse function
   
   if (! zd->zstat) return 1;                                                    //  wait for completion
   zdialog_free(zd);
   freeMouse();                                                                  //  disconnect mouse
   zfree(sa_stackii);                                                            //  free memory
   sa_stackii = 0;
   return 1;
}


//  mouse function - search area outline surrounding mouse click position

void sa_find_gap_mousefunc()                                                     //  16.07
{
   int         rad, ii, jj, kk, nn, np1, np2, npx;
   float       angle;
   int         ww, hh, cc;
   int         mx, my, px, py, rx, ry;
   int         crflag = 0;
   char        *pixmark = 0;
   static int  flip = 0;
   
   if (! LMclick) return;                                                        //  no left mouse click data
   LMclick = 0;

   sa_map_pixels();                                                              //  find edge pixels
   if (! sa_Npixel) return;

   ww = Fpxb->ww;                                                                //  image size
   hh = Fpxb->hh;
   cc = ww * hh;

   mx = Mxclick;                                                                 //  mouse click position
   my = Myclick;

   if (mx == 0 || mx >= ww) return;                                              //  reject if image edge
   if (my == 0 || my >= hh) return;

   nn = np1 = np2 = 0;

   for (rad = 1; rad < 100; rad++)                                               //  loop radius = 1 - 100 pixels
   for (angle = 0; angle < 2*PI; angle += 0.9/rad)                               //  loop angle = 0 - 360 degrees
   {
      px = mx + rad * cosf(angle);                                               //  search for nearest edge pixel
      py = my + rad * sinf(angle);
      ii = py * ww + px;
      if (sa_pixmap[ii] != 1) continue;

      nn = np1 = np2 = 0;                                                        //  test if edge pixel has exactly
      for (ry = -1; ry <= +1; ry++)                                              //    two neighbor edge pixels
      for (rx = -1; rx <= +1; rx++)
      {
         if (ry == 0 && rx == 0) continue;                                       //  skip self
         jj = ii + ww * ry + rx;
         if (sa_pixmap[jj] == 1) {                                               //  neighbor is an edge pixel
            if (++nn > 2) break;                                                 //  > 2 edge neighbors, reject
            if (! np1) np1 = jj;                                                 //  edge neighbor 1
            else if (! np2) np2 = jj;                                            //  edge neighbor 2
         }
      }
      if (nn == 2) break;                                                        //  found suitable edge pixel
   }
   
   if (nn != 2) {                                                                //  no edge pixel with 2 neighbor
      zmessage_post(Mwin,3,ZTX("cannot find area outline"));                     //    edge pixels was found
      return;
   }

   sa_show(0);                                                                   //  hide area
   Fpaintnow();

   if (! mwcr) {
      mwcr = gdk_cairo_create(gdkwin);                                           //  create cairo context if not already
      cairo_set_line_width(mwcr,1);
      crflag = 1;
   }

   pixmark = (char *) zmalloc(cc);                                               //  create pixel mark map
   memset(pixmark,0,cc);                                                         //  clear all pixel marks

   pixmark[ii] = 1;                                                              //  mark edge pixel
   draw_pixel(px,py);                                                            //  draw pixel
   
   flip = 1 - flip;                                                              //  alternate direction of search
   if (flip) {
      npx = np1;                                                                 //  np1 <--> np2
      np1 = np2;
      np2 = npx;
   }

   sa_stackii[0] = np1;                                                          //  put neighbor 1 pixel into stack
   sa_Nstack = 1;                                                                //  stack count

   while (sa_Nstack)   
   {
      kk = --sa_Nstack;                                                          //  pull pixel from stack
      ii = sa_stackii[kk];
      if (ii == np2) break;                                                      //  = neighbor 2 pixel, no outline gap

      pixmark[ii] = 1;                                                           //  mark pixel
      py = ii / ww;
      px = ii - ww * py;

      draw_pixel(px,py);                                                         //  draw pixel
      zsleep(0.0003);
      zmainloop();

      for (ry = -1; ry <= +1; ry++)                                              //  find unmarked edge neighbor pixels
      for (rx = -1; rx <= +1; rx++)
      {
         if (py+ry < 0 || py+ry > hh-1) continue;                                //  off the image edge
         if (px+rx < 0 || px+rx > ww-1) continue;

         jj = ii + ww * ry + rx;
         if (pixmark[jj]) continue;                                              //  pixel already marked

         if (sa_pixmap[jj] == 1) {                                               //  neighbor is an unmarked edge pixel
            kk = sa_Nstack++;
            sa_stackii[kk] = jj;
         }
      }
   }
   
   if (pixmark) zfree(pixmark);                                                  //  free memory

   if (crflag) {
      cairo_destroy(mwcr);                                                       //  free cairo context
      mwcr = 0;
   }

   if (ii == np2) {                                                              //  success, no gap
      sa_show(1);                                                                //  show area
      return;
   }

   poptext_mouse(ZTX("outline has a gap"),0,0,0,3);                              //  outline has a gap
   zsleep(2);                                                                    //  leave gap end showing for a moment
   sa_show(1);
   return;
}


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

//  Hairy Edge selection function.
//  Set transparencies for hairy edge pixels based on color matching
//    with a set of selected pixels well within the hairy edge.

namespace select_hairy
{
   int         ww, hh, pcc;
   int         selev = 60;                         //  pixel select match level
   int         deselev = 40;                       //  pixel deselect match level
   int         mouserad = 100;                     //  mouse radius for selecting or deselecting pixels
   int         selrad = 8;                         //  radius of area for match pixel selection (select)
   int         deselrad = 3;                       //  radius of area for match pixel selection (deselect)
   float       selRGB[400][3];                     //  pixels to match & select
   float       deselRGB[400][3];                   //  pixels to match & deselect
   int         Nsel, Ndesel;                       //  pixel counts
   int         Fselect;
   zdialog     *zdselhairy;
   GdkPixbuf   *pxbsel, *pxbdesel;                 //  pixbufs to show match colors
   int         pxbsize, pxbmid, rs;                //  size, middle pixel, row stride
   uint8       *pxbpixels, *pxbpix;
}


//  menu function

void m_select_hairy(GtkWidget *, cchar *)                                        //  16.07
{
   using namespace select_hairy;

   int   select_hairy_dialog_event(zdialog *zd, cchar *event);
   void  select_hairy_mousefunc();

   cchar    *title = ZTX("Select Hairy Edge");
   cchar    *helptext = ZTX("Click on colors to match. \n"
                            "Drag along edge to select. \n"
                            "Adjust match level as needed. \n"
                            "Copy or Save area when done.");
   
   F1_help_topic = "select_hairy";
   
   if (CEF) edit_done(0);                                                        //  if edit active, complete it

   if (! sa_stat) {                                                              //  no selected area
      zmessageACK(Mwin,ZTX("select the area first"));
      return;
   }

   if (sa_stat < 3) {
      zmessageACK(Mwin,ZTX("the area is not finished"));
      return;
   }

   if (! E0pxm) {                                                                //  get poss. 16-bit image
      paintlock(1);                                                              //  block window updates
      E0pxm = PXM_load(curr_file,1);
      paintlock(0);                                                              //  unblock window updates
      if (! E0pxm) return;
   }

   PXM_addalpha(E0pxm);                                                          //  add an alpha channel if req.

   if (E1pxm) PXM_free(E1pxm);                                                   //  make reference copy of original
   E1pxm = PXM_copy(E0pxm);

   Fpaint2();
   
   ww = E0pxm->ww;
   hh = E0pxm->hh;
   pcc = 4 * sizeof(float);
   
   for (int ii = 0; ii < 400; ii++) {                                            //  initial match pixels
      selRGB[ii][0] = 0;                                                         //  default = black
      selRGB[ii][1] = 0;
      selRGB[ii][2] = 0;
      deselRGB[ii][0] = 0;
      deselRGB[ii][1] = 0;
      deselRGB[ii][2] = 0;
   }
   Nsel = Ndesel = 100;
   
   pxbsel = gdk_pixbuf_new(GDKRGB,0,8,40,40);                                    //  pixbuf to show select match colors
   rs = gdk_pixbuf_get_rowstride(pxbsel);
   pxbpixels = gdk_pixbuf_get_pixels(pxbsel);
   memset(pxbpixels,0,40*rs);                                                    //  initially all black

   pxbdesel = gdk_pixbuf_new(GDKRGB,0,8,40,40);                                  //  pixbuf to show deselect match colors
   rs = gdk_pixbuf_get_rowstride(pxbdesel);
   pxbpixels = gdk_pixbuf_get_pixels(pxbdesel);
   memset(pxbpixels,0,40*rs);                                                    //  initially all black

/***
       ___________________________________
      |      Select Hairy Edge            |
      |                                   |
      |  Click on colors to match.        |
      |  Drag along edge to select.       |
      |  Adjust match level as needed.    |
      |  Copy or Save area when done.     |
      |                                   |
      | Mouse Radius  [____|-+]           |
      |                                   |
      | Match Level                       |
      | (o) select   ===========[]======= |
      | (o) deselect =========[]========= |
      |                                   |
      |                  |#####|          |                                      //  16.08
      | Colors to match  |#####|          |
      |                  |#####|          |
      |                                   |
      |            [copy] [save] [cancel] |
      |___________________________________|
      
***/

   zdselhairy = zdialog_new(title,Mwin,Bcopy,Bsave,Bcancel,null);
   zdialog *zd = zdselhairy;

   zdialog_add_widget(zd,"label","labhelp","dialog",helptext,"space=3");
   zdialog_add_widget(zd,"hbox","hbradius","dialog",0,"space=5");
   zdialog_add_widget(zd,"label","labradius","hbradius",ZTX("Mouse Radius"),"space=3");
   zdialog_add_widget(zd,"spin","mouserad","hbradius","10|300|1|100","space=3");
   zdialog_add_widget(zd,"hbox","hbsel","dialog",0,"space=5");
   zdialog_add_widget(zd,"vbox","vbsel","hbsel",0,"space=3|homog");
   zdialog_add_widget(zd,"vbox","vblev","hbsel",0,"space=3|homog|expand");
   zdialog_add_widget(zd,"label","lablevel","vbsel",ZTX("Match Level"));
   zdialog_add_widget(zd,"radio","select","vbsel",ZTX("select"));
   zdialog_add_widget(zd,"radio","deselect","vbsel",ZTX("deselect"));
   zdialog_add_widget(zd,"label","space","vblev");
   zdialog_add_widget(zd,"hscale","selev","vblev","0|1000|1|400");
   zdialog_add_widget(zd,"hscale","deselev","vblev","0|1000|1|600");
   zdialog_add_widget(zd,"hbox","hbcolors","dialog",0,"space=3");
   zdialog_add_widget(zd,"label","labcolors","hbcolors",ZTX("Colors to match"),"space=3");
   zdialog_add_widget(zd,"image","colors","hbcolors",(cchar *) pxbsel,"space=5");
   
   zdialog_rescale(zd,"selev",0,0,1000);                                         //  expand scale at min.
   zdialog_rescale(zd,"deselev",0,1000,1000);                                    //  expand scale at max.
   
   zdialog_restore_inputs(zd);                                                   //  restore prior settings
   zdialog_fetch(zd,"selev",selev);
   zdialog_fetch(zd,"deselev",deselev);
   zdialog_fetch(zd,"mouserad",mouserad);

   zdialog_stuff(zd,"select",1);                                                 //  default is select mode
   zdialog_stuff(zd,"deselect",0);
   Fselect = 1;
   
   zdialog_resize(zd,300,0);                                                     //  run dialog
   zdialog_run(zd,select_hairy_dialog_event,"save");

   takeMouse(select_hairy_mousefunc,dragcursor);                                 //  capture mouse

   return;
}


//  dialog event and completion function

int select_hairy_dialog_event(zdialog *zd, cchar *event)
{
   using namespace select_hairy;
   
   void  select_hairy_mousefunc();

   if (! curr_file) zd->zstat = 3;                                               //  image went away
   if (sa_stat < 3) zd->zstat = 3;                                               //  area gone

   if (strmatch(event,"cancel")) zd->zstat = 3;                                  //  from f_open()
   if (strmatch(event,"escape")) zd->zstat = 3;                                  //  escape = cancel
   
   if (strmatch(event,"focus")) 
      takeMouse(select_hairy_mousefunc,dragcursor);
   
   if (zd->zstat)                                                                //  done or cancel
   {
      if (zd->zstat == 1)                                                        //  copy area
         m_select_save(null,"copy");
      if (zd->zstat == 2)                                                        //  save area to file
         m_select_save(null,"save");

      paintlock(1);
      PXM_free(E0pxm);
      E0pxm = E1pxm;                                                             //  refresh image, remove transparency
      E1pxm = 0;
      paintlock(0);
      Fpaint2();

      zdialog_free(zd);                                                          //  kill dialog
      freeMouse();                                                               //  disconnect mouse
      g_object_unref(pxbsel);                                                    //  free pixbuf memory
      g_object_unref(pxbdesel);
      return 1;
   }

   zdialog_fetch(zd,"mouserad",mouserad);                                        //  get dialog inputs
   zdialog_fetch(zd,"selev",selev);
   zdialog_fetch(zd,"deselev",deselev);
   zdialog_fetch(zd,"select",Fselect);

   if (Fselect) zdialog_set_image(zd,"colors",pxbsel);
   else zdialog_set_image(zd,"colors",pxbdesel);

   return 1;
}


//  mouse function

void select_hairy_mousefunc()
{
   using namespace select_hairy;
   int select_hairy_ucomp(cchar *, cchar*);
   
   int         ii, bestii, rx, ry, px, py;
   int         mrad = mouserad, srad, rrad;
   float       *pix0, *pix1;
   float       pixmatch, bestmatch, alpha;
   float       fselev = 0.001 * selev;                                           //  scale to range 0.0 - 1.0
   float       fdeselev = 0.001 * deselev;
   GdkPixbuf   *pxbtemp = 0;

   if (LMclick)                                                                  //  left mouse click
   {
      if (Fselect)                                                               //  get select match pixels
      {
         srad = selrad;                                                          //  image radius to scan

         ii = 0;
         for (ry = -srad; ry <= srad; ry++)                                      //  loop pixels within selection radius
         for (rx = -srad; rx <= srad; rx++)
         {
            rrad = sqrtf(rx * rx + ry * ry);
            if (rrad > srad) continue;                                           //  outside radius
            px = Mxclick + rx;
            py = Myclick + ry;
            if (px < 0 || px > ww-1) continue;                                   //  off the image edge
            if (py < 0 || py > hh-1) continue;
            pix1 = PXMpix(E1pxm,px,py);
            selRGB[ii][0] = pix1[0];                                             //  pixels to match and select
            selRGB[ii][1] = pix1[1];
            selRGB[ii][2] = pix1[2];
            ii++;
         }
         Nsel = ii;                                                              //  pixel count
         
         pxbsize = 2 * srad + 3;                                                 //  pixbuf size (odd number)           16.08
         pxbmid = pxbsize / 2;                                                   //  middle pixel
         pxbtemp = gdk_pixbuf_new(GDKRGB,0,8,pxbsize,pxbsize);
         rs = gdk_pixbuf_get_rowstride(pxbtemp);
         pxbpixels = gdk_pixbuf_get_pixels(pxbtemp);                             //  pixbuf to show match colors
         memset(pxbpixels,0,pxbsize*rs);

         ii = 0;
         for (ry = -srad; ry <= srad; ry++)                                      //  loop pixels within selection radius
         for (rx = -srad; rx <= srad; rx++)
         {
            rrad = sqrtf(rx * rx + ry * ry);
            if (rrad > srad) continue;                                           //  outside radius
            px = Mxclick + rx;
            py = Myclick + ry;
            if (px < 0 || px > ww-1) continue;                                   //  off the image edge
            if (py < 0 || py > hh-1) continue;
            px = pxbmid + rx;                                                    //  corresp. pixbuf pixel
            py = pxbmid + ry;
            pxbpix = pxbpixels + py * rs + px * 3;
            pxbpix[0] = selRGB[ii][0];                                           //  fill pixbuf with colors
            pxbpix[1] = selRGB[ii][1];
            pxbpix[2] = selRGB[ii][2];
            ii++;
         }

         g_object_unref(pxbsel);                                                 //  show enlarged
         pxbsel = gdk_pixbuf_scale_simple(pxbtemp,40,40,BILINEAR);
         zdialog_set_image(zdselhairy,"colors",pxbsel);
      }

      else                                                                       //  get deselect match pixels
      {
         srad = deselrad;                                                        //  image radius to scan

         ii = 0;
         for (ry = -srad; ry <= srad; ry++)                                      //  loop pixels within selection radius
         for (rx = -srad; rx <= srad; rx++)
         {
            rrad = sqrtf(rx * rx + ry * ry);
            if (rrad > srad) continue;                                           //  outside radius
            px = Mxclick + rx;
            py = Myclick + ry;
            if (px < 0 || px > ww-1) continue;                                   //  off the image edge
            if (py < 0 || py > hh-1) continue;
            pix1 = PXMpix(E1pxm,px,py);
            deselRGB[ii][0] = pix1[0];                                           //  pixels to match and deselect
            deselRGB[ii][1] = pix1[1];
            deselRGB[ii][2] = pix1[2];
            ii++;
         }
         Ndesel = ii;                                                            //  pixel count
         
         pxbsize = 2 * srad + 3;                                                 //  pixbuf size (odd number)           16.08
         pxbmid = pxbsize / 2;                                                   //  middle pixel
         pxbtemp = gdk_pixbuf_new(GDKRGB,0,8,pxbsize,pxbsize);
         rs = gdk_pixbuf_get_rowstride(pxbtemp);
         pxbpixels = gdk_pixbuf_get_pixels(pxbtemp);                             //  pixbuf to show match colors
         memset(pxbpixels,0,pxbsize*rs);

         ii = 0;
         for (ry = -srad; ry <= srad; ry++)                                      //  loop pixels within selection radius
         for (rx = -srad; rx <= srad; rx++)
         {
            rrad = sqrtf(rx * rx + ry * ry);
            if (rrad > srad) continue;                                           //  outside radius
            px = Mxclick + rx;
            py = Myclick + ry;
            if (px < 0 || px > ww-1) continue;                                   //  off the image edge
            if (py < 0 || py > hh-1) continue;
            px = pxbmid + rx;                                                    //  corresp. pixbuf pixel
            py = pxbmid + ry;
            pxbpix = pxbpixels + py * rs + px * 3;
            pxbpix[0] = deselRGB[ii][0];                                         //  fill pixbuf with colors
            pxbpix[1] = deselRGB[ii][1];
            pxbpix[2] = deselRGB[ii][2];
            ii++;
         }

         g_object_unref(pxbdesel);                                               //  show enlarged
         pxbdesel = gdk_pixbuf_scale_simple(pxbtemp,40,40,BILINEAR);
         zdialog_set_image(zdselhairy,"colors",pxbdesel);
      }
   }

   if (Mxdrag || Mydrag)                                                         //  drag is underway
   {
      if (Mbutton == 1)                                                          //  left drag, set pixel transparencies
      {
         for (ry = -mrad; ry <= mrad; ry++)                                      //  loop pixels within mouse radius
         for (rx = -mrad; rx <= mrad; rx++)
         {
            rrad = sqrtf(rx * rx + ry * ry);
            if (rrad > mrad) continue;                                           //  outside radius
            px = Mxdrag + rx;
            py = Mydrag + ry;
            if (px < 0 || px > ww-1) continue;                                   //  outside image edge
            if (py < 0 || py > hh-1) continue;
            ii = py * ww + px;
            if (! sa_pixmap[ii]) continue;                                       //  outside area

            pix1 = PXMpix(E1pxm,px,py);                                          //  input pixel
            pix0 = PXMpix(E0pxm,px,py);                                          //  output pixel
            bestmatch = -1;
            bestii = 0;

            if (Fselect)                                                         //  test pixel for selection (opaque)
            {
               for (ii = 0; ii < Nsel; ii++) {                                   //  compare to select match pixels
                  pixmatch = RGBMATCH(pix1[0], pix1[1], pix1[2],
                             selRGB[ii][0], selRGB[ii][1], selRGB[ii][2]);
                  if (pixmatch > bestmatch) {
                     bestmatch = pixmatch;
                     bestii = ii;
                  }
               }

               if (bestmatch < fselev) pix0[3] = 0;                              //  < select match level, make transparent
               else {
                  pixmatch = (bestmatch - fselev) / (1 - fselev);                //  opacity ramps with match level
                  alpha = 255 * pow(pixmatch,0.4);                               //  16.08
                  ii = bestii;
                  pix0[0] = selRGB[ii][0];                                       //  set to matching color
                  pix0[1] = selRGB[ii][1];
                  pix0[2] = selRGB[ii][2];
                  pix0[3] = alpha;
               }
            }

            else                                                                 //  test pixel for deselection (transparent)
            {
               for (ii = 0; ii < Ndesel; ii++) {                                 //  compare to deselect match pixels
                  pixmatch = RGBMATCH(pix1[0], pix1[1], pix1[2],
                             deselRGB[ii][0], deselRGB[ii][1], deselRGB[ii][2]);
                  if (pixmatch > bestmatch) {
                     bestmatch = pixmatch;
                     bestii = ii;
                  }
               }

               if (bestmatch >= fdeselev) alpha = 0;                             //  > deselect match level, make transparent
               else {
                  bestmatch = 1.0 - bestmatch / fdeselev;                        //  opacity ramps with match level     16.08
                  alpha = 255 * pow(bestmatch,0.4);
               }
               if (pix0[3] > alpha) pix0[3] = alpha;                             //  accumulate transparency            16.08
            }
         }
      }

      if (Mbutton == 3)                                                          //  right drag, restore pixels
      {
         for (ry = -mrad; ry <= mrad; ry++)                                      //  loop pixels within mouse radius
         for (rx = -mrad; rx <= mrad; rx++)
         {
            rrad = sqrtf(rx * rx + ry * ry);
            if (rrad > mrad) continue;                                           //  outside radius
            px = Mxdrag + rx;
            py = Mydrag + ry;
            if (px < 0 || px > ww-1) continue;                                   //  outside image edge
            if (py < 0 || py > hh-1) continue;
            ii = py * ww + px;
            if (! sa_pixmap[ii]) continue;                                       //  outside area

            pix0 = PXMpix(E0pxm,px,py);
            pix1 = PXMpix(E1pxm,px,py);
            memcpy(pix0,pix1,pcc);                                               //  output pixel = input pixel
         }
      }

      Fpaint0(Mxdrag-mrad,Mydrag-mrad,2*mrad+1,2*mrad+1);                        //  update drawing window
   }
   
   draw_mousecircle(Mxposn,Myposn,mouserad,0);                                   //  refresh mouse circle

   LMclick = RMclick = Mxdrag = Mydrag = 0;
   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)
{
   F1_help_topic = "area_show_hide";
   sa_show(1);
   return;
}


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


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


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


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


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


//  show or hide outline of select area
//  also called from Fpaint() if Fshowarea = 1
//  edges are detected independently of sa_pixmap[]

void sa_show(int flag)
{
   int      px, py, ii, kk;
   int      crflag = 0;

   if (! sa_stat) return;                                                        //  no area
   if (! sa_validate()) return;                                                  //  invalid for current image
   if (sa_mode == mode_image) return;                                            //  a whole image area

   Fshowarea = flag;                                                             //  flag for Fpaint*()

   if (! flag) {
      Fpaint2();                                                                 //  erase area outline
      return;
   }

   if (! mwcr) {
      mwcr = gdk_cairo_create(gdkwin);                                           //  create cairo context if not already
      crflag = 1;
   }

   for (ii = 0; ii < Fpxb->ww * Fpxb->hh; ii++)
   {
      if (! sa_pixmap[ii]) continue;

      py = ii / Fpxb->ww;
      px = ii - Fpxb->ww * py;

      if (px == 0 || px == Fpxb->ww-1 || py == 0 || py == Fpxb->hh-1)            //  edge of image
         continue;

      if (Fpxb->nc > 3 && PXBpix(Fpxb,px,py)[3] < 254)                           //  transparency edge                  15.09
         continue;

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

   edgepix:
      draw_pixel(px,py);
   }

   if (crflag) {
      cairo_destroy(mwcr);
      mwcr = 0;
   }

   return;
}


//  Show the area outline only within a rectangular section.
//  Improve responsiveness during user mouse-driven updates.
//  Also called by Fpaint4() after a sectional edit is applied.

void sa_show_rect(int px1, int py1, int ww, int hh)
{
   int      px, py, px2, py2, ii, kk;

   if (! Fshowarea) return;
   if (! sa_stat) return;                                                        //  no area
   if (! sa_validate()) return;                                                  //  invalid for current image
   if (sa_mode == mode_image) return;                                            //  a whole image area

   px2 = px1 + ww;
   py2 = py1 + hh;

   if (px1 < 0) px1 = 0;
   if (py1 < 0) py1 = 0;
   if (px2 > Fpxb->ww) px2 = Fpxb->ww;
   if (py2 > Fpxb->hh) py2 = Fpxb->hh;

   for (py = py1; py < py2; py++)                                                //  loop pixels in rectangle
   for (px = px1; px < px2; px++)
   {
      ii = Fpxb->ww * py + px;
      if (! sa_pixmap[ii]) continue;

      if (px == 0 || px == Fpxb->ww-1 || py == 0 || py == Fpxb->hh-1)            //  edge of image
         continue;

      if (Fpxb->nc > 3 && PXBpix(Fpxb,px,py)[3] < 254)                           //  transparency edge                  15.09
         continue;

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

   edgepix:
      draw_pixel(px,py);
   }

   return;
}


//  validate an area for use in the current image.
//  returns 1 if OK, 0 if not (area will have been deleted).

int sa_validate()
{
   int      ww = 0, hh = 0;

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

   if (! curr_file || (CEF && CEF->Fpreview)) {                                  //  no image file or edit preview image
      sa_unselect();
      return 0;
   }

   if (E1pxm) {
      ww = E1pxm->ww;
      hh = E1pxm->hh;
   }

   else if (E0pxm) {
      ww = E0pxm->ww;
      hh = E0pxm->hh;
   }

   else {
      ww = Fpxb->ww;
      hh = Fpxb->hh;
   }

   if (sa_fww == ww && sa_fhh == hh) return 1;
   sa_unselect();
   return 0;
}


//  enable select area that was disabled

void sa_enable()
{
   if (! sa_stat) return;                                                        //  no area
   if (! sa_validate()) return;                                                  //  invalid for current image
   if (sa_stat < 3) {                                                            //  finished or finished/disabled
      zmessageACK(Mwin,ZTX("the area is not finished"));
      return;
   }

   sa_stat = 3;                                                                  //  finished/enabled
   areanumber++;                                                                 //  next sequential number
   sa_show(1);
   return;
}


//  disable select area

void sa_disable()
{
   if (! sa_stat) return;                                                        //  no area
   if (sa_stat < 3) {                                                            //  finished or */disabled
      zmessageACK(Mwin,ZTX("the area is not finished"));
      return;
   }

   sa_stat = 4;                                                                  //  finished/disabled
   sa_show(0);
   return;
}


//  invert a selected area

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

   if (! sa_stat) return;                                                        //  no area
   if (! sa_validate()) return;                                                  //  invalid for current image
   if (sa_mode == mode_image) return;                                            //  a whole image area
   if (sa_stat < 3) {
      zmessageACK(Mwin,ZTX("the area is not finished"));
      return;
   }

   sa_minx = Fpxb->ww;                                                           //  get new enclosing rectangle
   sa_maxx = 0;
   sa_miny = Fpxb->hh;
   sa_maxy = 0;

   npix = 0;

   for (py = 0; py < Fpxb->hh; py++)                                             //  loop all pixels
   for (px = 0; px < Fpxb->ww; px++)
   {
      ii = py * Fpxb->ww + px;
      jj = sa_pixmap[ii];                                                        //  0/1/2+ = outside/edge/inside

      if (jj > 1) {                                                              //  inside pixel (2+)
         sa_pixmap[ii] = 0;                                                      //    is now outside (0)
         continue;
      }

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

      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;
   }

   sa_minx -= 10;                                                                //  add margins where possible         15.09
   if (sa_minx < 0) sa_minx = 0;
   sa_maxx += 10;
   if (sa_maxx > Fpxb->ww) sa_maxx = Fpxb->ww;
   sa_miny -= 10;
   if (sa_miny < 0) sa_miny = 0;
   sa_maxy += 10;
   if (sa_maxy > Fpxb->hh) sa_maxy = Fpxb->hh;

   sa_stat = 3;                                                                  //  if disabled, now finished
   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

   sa_show(1);
   return;
}


//  unselect current area (delete the area)

void sa_unselect()
{
   sa_stat = sa_Npixel = sa_blend = sa_calced = 0;
   sa_currseq = sa_Ncurrseq = 0;
   sa_fww = sa_fhh = 0;
   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_Nstack = 0;
   sa_pixselc = 0;
   Fpaint2();
   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?
   if (sa_mode == mode_image) return;                                            //  a whole image area
   if (sa_calced) return;                                                        //  done already

   if (sa_stat < 3) sa_finish();                                                 //  finish if needed
   if (sa_stat != 3) return;                                                     //  failed or canceled

   zd = zdialog_new(ZTX("Area Edge Calc"),Mwin,Bcancel,null);                    //  start dialog for user cancel
   zdialog_add_widget(zd,"hbox","hb1","dialog",0,"space=5");
   zdialog_add_widget(zd,"label","lab1","hb1",zectext,"space=10");
   zdialog_run(zd,edgecalc_dialog_event);
   Fkillfunc = 0;

   cc = Fpxb->ww * Fpxb->hh * sizeof(uint16);                                    //  allocate memory for calculations
   sa_edgedist = (uint16 *) zmalloc(cc);
   memset(sa_edgedist,0,cc);

   for (ii = nn = 0; ii < Fpxb->ww * Fpxb->hh; ii++)                             //  count edge pixels in select area
      if (sa_pixmap[ii] == 1) nn++;

   cc = nn * sizeof(uint16);
   sa_edgepx = (uint16 *) zmalloc(cc);                                           //  allocate memory
   sa_edgepy = (uint16 *) zmalloc(cc);

   for (ii = nn = 0; ii < Fpxb->ww * Fpxb->hh; ii++)                             //  build list of edge pixels
   {
      if (sa_pixmap[ii] != 1) continue;
      
      py = ii / Fpxb->ww;
      px = ii - py * Fpxb->ww;

      if (px == 0 || px == Fpxb->ww-1) continue;                                 //  omit edge pixels
      if (py == 0 || py == Fpxb->hh-1) continue;

      if (Fpxb->nc > 3) {                                                        //  omit pixels at transparency edge   15.09
         if (PXBpix(Fpxb,px-1,py)[3] < 254) continue;
         if (PXBpix(Fpxb,px,  py)[3] < 254) continue;
         if (PXBpix(Fpxb,px+1,py)[3] < 254) continue;
         if (PXBpix(Fpxb,px-1,py-1)[3] < 254) continue;
         if (PXBpix(Fpxb,px,  py-1)[3] < 254) continue;
         if (PXBpix(Fpxb,px+1,py-1)[3] < 254) continue;
         if (PXBpix(Fpxb,px-1,py+1)[3] < 254) continue;
         if (PXBpix(Fpxb,px,  py+1)[3] < 254) continue;
         if (PXBpix(Fpxb,px+1,py+1)[3] < 254) continue;
      }

      sa_edgepx[nn] = px;
      sa_edgepy[nn] = py;
      nn++;
   }

   sa_Nedge = nn;
   Fbusy_goal = sa_Npixel;
   Fbusy_done = 0;

   for (ii = 0; ii < NWT; ii++)                                                  //  start worker threads to calculate
      start_wthread(edgecalc_thread,&Nval[ii]);                                  //    sa_pixmap[] edge distances
   wait_wthreads(0);                                                             //  wait for completion

   Fbusy_goal = 0;

   zdialog_free(zd);                                                             //  kill dialog

   if (Fkillfunc) {                                                              //  user killed edge calc
      Fkillfunc = 0;
      sa_calced = 0;
      if (zdsela) zdialog_stuff(zdsela,"blendwidth",0);                          //  reset blend width
   }

   else {
      for (ii = 0; ii < Fpxb->ww * Fpxb->hh; ii++) {                             //  copy sa_edgedist[] to sa_pixmap[]
         if (sa_pixmap[ii] <= 1) continue;                                       //  skip outside and edge pixels
         sa_pixmap[ii] = sa_edgedist[ii];                                        //  interior pixel edge distance
      }
      sa_calced = 1;                                                             //  edge calculation available
   }

   zfree(sa_edgedist);                                                           //  free memory
   zfree(sa_edgepx);
   zfree(sa_edgepy);

   Fpaint2();
   return;
}


//  dialog event and completion callback function

int edgecalc_dialog_event(zdialog *zd, cchar *event)                             //  respond to user cancel
{
   if (strmatch(event,"escape")) zd->zstat = 1;                                  //  escape = cancel                    15.07
   if (! zd->zstat) return 0;
   Fkillfunc = 1;
   return 0;
}


void * edgecalc_thread(void *arg)                                                //  worker thread function
{                                                                                //  new algorithm
   using namespace sa_edgecalc_names;

   void  edgecalc_func(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 * Fpxb->ww + px;
   if (sa_pixmap[ii]) edgecalc_func(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)                         //  edges only, interior already done
      for (py = midy-rad+index; py <= midy+rad; py += NWT)
      {
         if (px < 0 || px > Fpxb->ww-1) continue;
         if (py < 0 || py > Fpxb->hh-1) continue;
         ii = py * Fpxb->ww + px;
         if (! sa_pixmap[ii]) continue;
         if (sa_edgedist[ii]) continue;
         edgecalc_func(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 > Fpxb->ww-1) continue;
         if (py < 0 || py > Fpxb->hh-1) continue;
         ii = py * Fpxb->ww + px;
         if (! sa_pixmap[ii]) continue;
         if (sa_edgedist[ii]) continue;
         edgecalc_func(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_func(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;
   int      ww = Fpxb->ww, hh = Fpxb->hh;
   int      cc = ww * hh;
   float    slope;

   mindist = 9999;
   mindist2 = mindist * mindist;
   epx = epy = 0;

   for (ii = 0; ii < sa_Nedge; ii++)                                             //  loop all edge pixels
   {                                                                             //  (ii += 2 tried, buggy)
      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
         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 * ww + pxm;
         if (ii < 0 || ii >= cc) {                                               //  16.03
            printz("edgecalc() bug %d \n",ii);
            return;
         }
         if (sa_edgedist[ii]) return;
         dx = epx - pxm;                                                         //  calculate distance to edge
         dy = epy - pym;
         dist2 = sqrt(dx*dx + dy*dy) + 1;                                        //  minor bug fix
         sa_edgedist[ii] = dist2;                                                //  save
         Fbusy_done++;                                                           //  track progress
      }
   }

   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 * ww + pxm;
         if (ii < 0 || ii >= cc) {                                               //  16.03
            printz("edgecalc() bug %d \n",ii);
            return;
         }
         if (sa_edgedist[ii]) return;
         dx = epx - pxm;
         dy = epy - pym;
         dist2 = sqrt(dx*dx + dy*dy) + 1;
         sa_edgedist[ii] = dist2;
         Fbusy_done++;
      }
   }

   return;
}


//  adjust area edge 1 pixel out (+) or in (-)

void sa_edgecreep(int kk)
{
   int      px, py, ii, jj;

   if (sa_stat != 3) {
      zmessageACK(Mwin,ZTX("the area is not finished"));
      return;
   }

   for (py = sa_miny; py < sa_maxy; py++)                                        //  find all area edge pixels
   for (px = sa_minx; px < sa_maxx; px++)
   {
      ii = Fpxb->ww * py + px;
      if (sa_pixmap[ii] != 1) continue;

      if (px == 0 || px == Fpxb->ww-1 || py == 0 || py == Fpxb->hh-1)            //  edge of image, no change
         continue;

      if (kk < 0) {                                                              //  shrink area
         sa_pixmap[ii] = 0;
         continue;
      }

      sa_pixmap[ii-1] = sa_pixmap[ii+1] = 2;                                     //  expand area
      jj = ii - Fpxb->ww;
      sa_pixmap[jj] = sa_pixmap[jj-1] = sa_pixmap[jj+1] = 2;                     //  mark 8 neighbor pixels in area
      jj = ii + Fpxb->ww;
      sa_pixmap[jj] = sa_pixmap[jj-1] = sa_pixmap[jj+1] = 2;
   }

   sa_map_pixels();                                                              //  remap edge and interior pixels

   sa_finish_auto();                                                             //  finish area again
   sa_show(1);
   sa_calced = 0;                                                                //  invalidate prior edge calc.

   Fpaint2();
   return;
}


//  Compute edge blend coefficient for given edge distance.
//  Returned coefficient: 0.0 to 1.0 for edge distance from 0 to sa_blend.

float sa_blendfunc(int edgedist)                                                 //  16.08
{
   static float   coeff[5000];                                                   //  edge distance limit
   static int     Pblend = -1;
   float          ff;
   int            ii;
   
   if (sa_mode == mode_image) {                                                  //  whole image area (see m_paint_edits())
      ff = 1.0 * edgedist / sa_blend;
      return ff;
   }
   
   if (edgedist >= sa_blend) return 1.0;
   if (sa_blend == Pblend) return coeff[edgedist];
   if (sa_blend < 1 || sa_blend > 4999) return 1.0;

   Pblend = sa_blend;

   for (ii = 0; ii <= sa_blend; ii++) {
      ff = ii;
      ff = PI * (ff / sa_blend - 0.5);                                           //  -PI/2 ... +PI/2
      coeff[ii] = 0.5 * (sinf(ff) + 1.0);                                        //  0.0 ... 1.0
   }
   
   return coeff[edgedist];
}


/********************************************************************************
   select area copy/paste and open/save - select area <--> disk file
*********************************************************************************/

namespace sa_diskfile
{
   PXM      *sacp_pxm = 0;                                                       //  select area pixmap image
   int      sacp_ww, sacp_hh;                                                    //  original dimensions
   PXM      *sacpR_pxm = 0;                                                      //  resized/rotated image
   int      sacpR_ww, sacpR_hh;                                                  //  resized/rotated dimensions

   float    sacp_scale;                                                          //  scale, 1.0 = original size
   float    sacp_angle;                                                          //  angle of rotation, -180 to +180
   int      sacp_orgx, sacp_orgy;                                                //  origin in target image
   float    sacp_blend;                                                          //  pasted area edge blend with image
   float    sacp_brite;                                                          //  pasted area brightness adjustment
   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
   int      sacp_needcalc = 1;                                                   //  edge distance calculation needed

   editfunc    EFpaste;
}


//  Save a select area in memory to a disk PNG file.
//  For menu "Copy Area" use default file:  ~/.fotoxx/saved_areas/copied_area.png
//  For menu "Save Area File" use file name from user input.

void m_select_save(GtkWidget *, cchar *menu)                                     //  consolidate 2 menus                16.07 
{
   using namespace sa_diskfile;

   int      ii, px1, py1, px2, py2, dist;
   int      ww, nc, pcc, alpha;
   float    *pix1, *pix2;
   char     *pp, *file;
   char     filename[100];

   if (strmatch(menu,ZTX("Copy Area")) || strmatch(menu,"copy"))
      F1_help_topic = "area_copy_paste";
   
   else if (strmatch(menu,ZTX("Save Area File")) || strmatch(menu,"save"))
      F1_help_topic = "area_open_save";
   
   else {
      zmessageACK(Mwin,"menu name bug: %s",menu);
      return;
   }

   if (! sa_stat) return;                                                        //  no selected area
   if (sa_mode == mode_image) return;                                            //  a whole image area
   if (sa_stat < 3) {
      zmessageACK(Mwin,ZTX("the area is not finished"));
      return;
   }

   if (! E0pxm) {                                                                //  get poss. 16-bit image
      paintlock(1);                                                              //  block window updates               16.02
      E0pxm = PXM_load(curr_file,1);
      paintlock(0);                                                              //  unblock window updates             16.02
      if (! E0pxm) return;
   }

   ww = E0pxm->ww;
   nc = E0pxm->nc;                                                               //  16.03
   pcc = nc * sizeof(float);

   PXM_free(sacp_pxm);                                                           //  free prior if any
   PXM_free(sacpR_pxm);

   sacp_ww = sa_maxx - sa_minx;                                                  //  new area image PXM          bugfix 16.07
   sacp_hh = sa_maxy - sa_miny;
   sacp_pxm = PXM_make(sacp_ww,sacp_hh,4);                                       //  alpha channel                      16.03
   
   for (py2 = 0; py2 < sacp_hh; py2++)                                           //  loop area pixels
   for (px2 = 0; px2 < sacp_ww; px2++)
   {
      px1 = px2 + sa_minx;
      py1 = py2 + sa_miny;
      pix1 = PXMpix(E0pxm,px1,py1);                                              //  copy to area PXM
      pix2 = PXMpix(sacp_pxm,px2,py2);
      memcpy(pix2,pix1,pcc);                                                     //  copy RGB(A) data
      ii = py1 * ww + px1;
      dist = sa_pixmap[ii];                                                      //  0/1/2+ = outside/edge/inside       16.03
      if (dist == 0) pix2[3] = 0;                                                //  outside pixel, transparent
      else if (nc < 4) pix2[3] = 255;                                            //  inside, opaque if alpha added
   }

   for (py2 = 1; py2 < sacp_hh-1; py2++)                                         //  loop area edge pixels              16.08
   for (px2 = 1; px2 < sacp_ww-1; px2++)
   {
      px1 = px2 + sa_minx;
      py1 = py2 + sa_miny;
      ii = py1 * ww + px1;
      if (sa_pixmap[ii] != 1) continue;                                          //  not an edge pixel

      alpha = 0;
      if (sa_pixmap[ii-1] == 1 && sa_pixmap[ii+1] == 1) alpha += 128;            //  add transparency to edge pixels
      else if (sa_pixmap[ii-1] == 1 || sa_pixmap[ii+1] == 1) alpha += 32;        //    depending on neighboring
      if (sa_pixmap[ii-ww] == 1 && sa_pixmap[ii+ww] == 1) alpha += 128;          //      edge pixels
      else if (sa_pixmap[ii-ww] == 1 || sa_pixmap[ii+ww] == 1) alpha += 32;
      if (sa_pixmap[ii-ww-1] == 1 && sa_pixmap[ii+ww+1] == 1) alpha += 128;
      else if (sa_pixmap[ii-ww-1] == 1 || sa_pixmap[ii+ww+1] == 1) alpha += 32;
      if (sa_pixmap[ii-ww-1] == 1 && sa_pixmap[ii+ww+1] == 1) alpha += 128;
      else if (sa_pixmap[ii-ww-1] == 1 || sa_pixmap[ii+ww+1] == 1) alpha += 32;
      if (alpha > 255) alpha = 255;
      pix2 = PXMpix(sacp_pxm,px2,py2);
      if (pix2[3] > alpha) pix2[3] = alpha;                                      //  only reduce opacity
   }

   if (strmatch(menu,ZTX("Copy Area")) || strmatch(menu,"copy")) {
      snprintf(filename,100,"%s/copied_area.png",saved_areas_dirk);              //  save to default PNG file           16.07
      PXM_PNG_save(sacp_pxm,filename,16);
      return;
   }

   pp = zgetfile(ZTX("save area as a PNG file"),MWIN,"save",saved_areas_dirk);   //  get file name from user            16.07
   if (! pp) return;
   file = zstrdup(pp,8);
   zfree(pp);
   pp = strrchr(file,'/');
   pp = strcasestr(pp,".png");
   if (! pp) strcat(file,".png");
   PXM_PNG_save(sacp_pxm,file,16);                                               //  use PNG-16 file
   zfree(file);

   return;
}


//  Read a select area from a disk PNG file.

void m_select_open(GtkWidget *, cchar *)                                         //  15.08
{
   using namespace sa_diskfile;

   void  select_paste(GtkWidget *, cchar *);

   PXM      *pxmtemp;
   char     *file;
   float    *pix1, *pix2;
   int      px, py, nc, pcc;

   F1_help_topic = "area_open_save";

   PXM_free(sacp_pxm);                                                           //  free prior if any
   PXM_free(sacpR_pxm);

   file = zgetfile(ZTX("load area from a file"),MWIN,"file",saved_areas_dirk);
   if (! file) return;

   pxmtemp = ANY_PXM_load(file);                                                 //  load image file
   zfree(file);
   if (! pxmtemp) {
      if (*file_errmess) zmessageACK(Mwin,file_errmess);
      return;
   }

   nc = pxmtemp->nc;
   pcc = nc * sizeof(float);                                                     //  3 or 4 channels, RGB or RGBA

   sacp_ww = pxmtemp->ww;                                                        //  image dimensiona
   sacp_hh = pxmtemp->hh;
   sacp_pxm = PXM_make(sacp_ww,sacp_hh,4);                                       //  alpha channel                      16.03

   for (py = 0; py < sacp_hh; py++)
   for (px = 0; px < sacp_ww; px++)
   {
      pix1 = PXMpix(pxmtemp,px,py);
      pix2 = PXMpix(sacp_pxm,px,py);
      memcpy(pix2,pix1,pcc);                                                     //  copy all channels
      if (nc < 4) pix2[3] = 255;                                                 //  no alpha, all pixels opaque        16.03
   }
   
   PXM_free(pxmtemp);
   select_paste(0,0);                                                            //  interactive move/resize area image
   return;
}


//  paste the area last copied on to the current image
//  uses the default file from "copy area" menu (above)

void m_select_paste(GtkWidget *, cchar *)                                        //  15.09
{
   using namespace sa_diskfile;

   void  select_paste(GtkWidget *, cchar *);

   PXM      *pxmtemp;
   char     filename[100];
   float    *pix1, *pix2;
   int      px, py, nc, pcc;

   F1_help_topic = "area_copy_paste";

   PXM_free(sacp_pxm);                                                           //  free prior if any
   PXM_free(sacpR_pxm);

   snprintf(filename,100,"%s/copied_area.png",saved_areas_dirk);

   pxmtemp = ANY_PXM_load(filename);                                             //  load image file
   if (! pxmtemp) {
      if (*file_errmess) zmessageACK(Mwin,file_errmess);
      return;
   }

   nc = pxmtemp->nc;                                                             //  16.03
   pcc = nc * sizeof(float);

   sacp_ww = pxmtemp->ww;                                                        //  image dimensiona
   sacp_hh = pxmtemp->hh;
   sacp_pxm = PXM_make(sacp_ww,sacp_hh,4);                                       //  alpha channel                      16.03

   for (py = 0; py < sacp_hh; py++)
   for (px = 0; px < sacp_ww; px++)
   {
      pix1 = PXMpix(pxmtemp,px,py);
      pix2 = PXMpix(sacp_pxm,px,py);
      memcpy(pix2,pix1,pcc);                                                     //  copy all channels
      if (nc < 4) pix2[3] = 255;                                                 //  no alpha, all pixels opaque        16.03
   }
   
   PXM_free(pxmtemp);
   select_paste(0,0);                                                            //  interactive move/resize area image
   return;
}


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

void select_paste(GtkWidget *, cchar *)
{
   using namespace sa_diskfile;

   void  select_paste_image();
   int   select_paste_dialog_event(zdialog *, cchar *event);
   void  select_paste_mousefunc();
   
   cchar    *dragmess = ZTX("position with mouse click/drag");

   if (! sacp_pxm) return;                                                       //  nothing to paste
   sa_unselect();                                                                //  unselect area if present

   EFpaste.menufunc = select_paste;
   EFpaste.funcname = "paste area";
   EFpaste.Frestart = 1;                                                         //  make restartable
   if (! edit_setup(EFpaste)) return;                                            //  setup edit for paste

   sacp_scale = 1.0;                                                             //  size = 1x
   sacp_blend = 0;                                                               //  edge blend = 0
   sacp_angle = 0;                                                               //  angle = 0
   sacp_brite = 1.0;                                                             //  no brightness adjustment
   sacp_porg = 0;                                                                //  no image paste location yet
   sacp_needcalc = 1;                                                            //  edge distance calculation needed   16.03

   PXM_free(sacpR_pxm);                                                          //  free prior if any

   if (sacp_ww > E0pxm->ww)                                                      //  if paste image > current image     15.10
      sacp_scale = 1.0 * E0pxm->ww / sacp_ww;                                    //    scale down to fit
   if (sacp_hh * sacp_scale > E0pxm->hh)
      sacp_scale = 1.0 * E0pxm->hh / sacp_hh;
   
   if (sacp_scale < 1.0) {
      sacp_ww *= sacp_scale;
      sacp_hh *= sacp_scale;
      sacpR_pxm = PXM_rescale(sacp_pxm,sacp_ww,sacp_hh);
      PXM_free(sacp_pxm);
      sacp_pxm = sacpR_pxm;
      sacp_scale = 1.0;
   }
   
   sacpR_pxm = PXM_copy(sacp_pxm);                                               //  initial paste image
   sacpR_ww = sacp_ww;                                                           //  size = 1.0, no rotation
   sacpR_hh = sacp_hh;
   
   sacp_orgx = sacp_orgy = 0;                                                    //  initial position
   select_paste_image();                                                         //  paste area image

   if (! sa_Npixel) {                                                            //  failed                             16.07
      edit_cancel(0);                                                            //  cancel edit, restore image
      PXM_free(sacpR_pxm);                                                       //  free memory
      return;
   }

   CEF->zd = zdialog_new(ZTX("Paste Image"),Mwin,Bdone,Bcancel,null);
   zdialog_add_widget(CEF->zd,"hbox","hb0","dialog",0,"space=3");
   zdialog_add_widget(CEF->zd,"label","lab1","hb0",dragmess,"space=5");

   zdialog_add_widget(CEF->zd,"hbox","hbres","dialog",0,"space=3");
   zdialog_add_widget(CEF->zd,"label","labres","hbres",ZTX("resize"),"space=3");
   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","+.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=3");
   zdialog_add_widget(CEF->zd,"label","labang","hbang",Bangle,"space=3");
   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","+.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","hbbr","dialog",0,"space=3");
   zdialog_add_widget(CEF->zd,"label","labbr","hbbr","brightness","space=3");
   zdialog_add_widget(CEF->zd,"hscale","brite","hbbr","0.3|3.0|0.001|1.0","expand|space=3");

   zdialog_add_widget(CEF->zd,"hbox","hbbl","dialog",0,"space=3");
   zdialog_add_widget(CEF->zd,"label","labbl","hbbl","edge blend","space=3");
   zdialog_add_widget(CEF->zd,"hscale","blend","hbbl","0|50|0.1|0","expand|space=3");

   zdialog_run(CEF->zd,select_paste_dialog_event,"80/20");                       //  start dialog
   takeMouse(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)
{
   using namespace sa_diskfile;

   void  select_paste_mousefunc();
   void  select_paste_image();
   void  select_paste_adjust();

   int      ww, hh;
   PXM      *pxm_temp;

   if (strmatch(event,"done")) zd->zstat = 1;                                    //  apply and quit
   if (strmatch(event,"cancel")) zd->zstat = 2;                                  //  cancel
   if (strmatch(event,"escape")) zd->zstat = 2;                                  //  escape = cancel                    15.07

   if (zd->zstat)                                                                //  dialog completed
   {
      freeMouse();                                                               //  disconnect mouse

      if (zd->zstat != 1 || ! sacp_porg) {                                       //  cancel paste
         edit_cancel(0);                                                         //  cancel edit, restore image
         sa_unselect();
         PXM_free(sacpR_pxm);                                                    //  free memory
         return 1;
      }

      if (sacp_needcalc) sa_edgecalc();                                          //  update edge distance data          16.03
      sacp_needcalc = 0;
      sa_show(1);
      edit_done(0);                                                              //  commit the edit (pasted image)
      PXM_free(sacpR_pxm);                                                       //  free memory
      return 1;
   }

   if (strmatch(event,"focus"))                                                  //  toggle mouse capture
      takeMouse(select_paste_mousefunc,0);

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

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

      PXM_free(sacpR_pxm);                                                       //  free prior if any

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

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

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

      select_paste_image();                                                      //  copy onto target image
      sacp_needcalc = 1;                                                         //  edge distance calculation needed   16.03
   }

   if (strmatch(event,"blend") && sacp_porg) {                                   //  edge blend width adjustment        16.03
      zdialog_fetch(zd,"blend",sacp_blend);
      select_paste_adjust();
   }

   if (strmatch(event,"brite") && sacp_porg) {                                   //  area brightness adjustment         16.03
      zdialog_fetch(zd,"brite",sacp_brite);
      select_paste_adjust();
   }

   Fpaint2();
   return 1;
}


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

void select_paste_mousefunc()
{
   using namespace sa_diskfile;

   void  select_paste_image();

   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
      sacp_orgy = Myclick - sacpR_hh / 2;
      select_paste_image();
   }

   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(gdkwin,0);                                           //  set normal cursor
   else
      gdk_window_set_cursor(gdkwin,dragcursor);                                  //  set drag cursor

   if (! Mxdrag && ! Mydrag) 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;
   Mxdrag = Mydrag = 0;

   sacp_orgx += (mx2 - mx1);                                                     //  move position of select area
   sacp_orgy += (my2 - my1);                                                     //    by mouse drag amount
   select_paste_image();                                                         //  re-copy area to new position

   sacp_needcalc = 1;                                                            //  edge distance calculation needed   16.03
   return;
}


//  copy select area into edit image, starting at sacp_orgx/y
//  called when area moved, resized, rotated

void select_paste_image()
{
   using namespace sa_diskfile;
   
   void select_paste_makearea();

   int      px1, py1, px2, py2, nc, pcc;
   float    *pix1, *pix2, *pix3;
   float    f1, f2;
   float    red, green, blue;
   
   if (sacp_porg)                                                                //  prior area overlap rectangle
   {
      nc = E1pxm->nc;
      pcc = nc * sizeof(float);

      for (py2 = 0; py2 < sacp_phh; py2++)                                       //  loop area pixels
      for (px2 = 0; px2 < sacp_pww; px2++)
      {
         px1 = px2 + sacp_porgx;                                                 //  coresp. E1/E3 image pixel
         py1 = py2 + sacp_porgy;
         if (px1 < 0 || px1 >= Fpxb->ww) continue;                               //  parts may be beyond edges
         if (py1 < 0 || py1 >= Fpxb->hh) continue;
         pix1 = PXMpix(E1pxm,px1,py1);                                           //  restore E1 pixels to E3
         pix3 = PXMpix(E3pxm,px1,py1);
         memcpy(pix3,pix1,pcc);
      }
   }
   
   for (py2 = 0; py2 < sacpR_hh; py2++)                                          //  copy paste area pixels to new
   for (px2 = 0; px2 < sacpR_ww; px2++)                                          //    image overlap rectangle
   {
      px1 = px2 + sacp_orgx;
      py1 = py2 + sacp_orgy;
      if (px1 < 0 || px1 >= E3pxm->ww) continue;                                 //  parts may be beyond edges
      if (py1 < 0 || py1 >= E3pxm->hh) continue;

      pix2 = PXMpix(sacpR_pxm,px2,py2);                                          //  area pixel
      red = pix2[0];
      green = pix2[1];
      blue = pix2[2];

      pix3 = PXMpix(E3pxm,px1,py1);                                              //  corresp. image pixel

      if (pix2[3] < 255) {                                                       //  opacity of area pixel              16.03
         f1 = pix2[3] / 255.0;
         f2 = 1.0 - f1;
         red = f1 * red + f2 * pix3[0];                                          //  blend area and image pixels
         green = f1 * green + f2 * pix3[1];
         blue = f1 * blue + f2 * pix3[2];
      }
      
      pix3[0] = red;
      pix3[1] = green;
      pix3[2] = blue;
   }

   if (sacp_porg)                                                                //  update window for old overlap area
      Fpaint3(sacp_porgx,sacp_porgy,sacp_pww,sacp_phh);

   Fpaint3(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;
   
   select_paste_makearea();                                                      //  16.03
   if (! sa_Npixel) return;                                                      //  16.07
   
   CEF->Fmods++;                                                                 //  image is modified
   CEF->Fsaved = 0;
   return;
}


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

void select_paste_makearea()
{
   using namespace sa_diskfile;

   int      cc, ii;
   int      px1, py1, px2, py2;
   float    *pix2, opac;
   
   sa_unselect();                                                                //  unselect old area

   cc = Fpxb->ww * Fpxb->hh * sizeof(uint16);
   sa_pixmap = (uint16 *) zmalloc(cc);                                           //  pixel map for new area
   memset(sa_pixmap,0,cc);

   for (py2 = 0; py2 < sacpR_hh; py2++)                                          //  loop area pixels
   for (px2 = 0; px2 < sacpR_ww; px2++)
   {
      px1 = px2 + sacp_orgx;                                                     //  corresp. E0 image pixel
      py1 = py2 + sacp_orgy;
      if (px1 < 0 || px1 >= Fpxb->ww) continue;                                  //  parts may be beyond edges
      if (py1 < 0 || py1 >= Fpxb->hh) continue;
      pix2 = PXMpix(sacpR_pxm,px2,py2);                                          //  get opacity
      opac = pix2[3];
      ii = py1 * Fpxb->ww + px1;                                                 //  set sa_pixmap[] from opacity
      if (opac== 0) sa_pixmap[ii] = 0;                                           //  pixel outside area
      else sa_pixmap[ii] = 2;                                                    //  interior pixel
   }

   sa_stat = 1;
   sa_mode = mode_mouse;                                                         //  equivalent select by mouse area
   sa_fww = Fpxb->ww;
   sa_fhh = Fpxb->hh;
   sa_finish_auto();                                                             //  will find new edge pixels
   sa_show(0);
   return;
}


//  make area brightness and edge blend adjustments.

void select_paste_adjust()                                                       //  16.09
{
   using namespace sa_diskfile;

   int      px1, py1, px2, py2;
   float    *pix1, *pix2, *pix3;
   float    red, green, blue;
   float    maxrgb, f1, f2;
   int      ii, dist;

   if (sacp_needcalc) sa_edgecalc();                                             //  update edge distance data
   sacp_needcalc = 0;

   for (py2 = 0; py2 < sacpR_hh; py2++)                                          //  copy paste area pixels to new
   for (px2 = 0; px2 < sacpR_ww; px2++)                                          //    image overlap rectangle
   {
      px1 = px2 + sacp_orgx;
      py1 = py2 + sacp_orgy;
      if (px1 < 0 || px1 >= E3pxm->ww) continue;                                 //  parts may be beyond edges
      if (py1 < 0 || py1 >= E3pxm->hh) continue;

      pix2 = PXMpix(sacpR_pxm,px2,py2);                                          //  area pixel
      pix1 = PXMpix(E1pxm,px1,py1);                                              //  corresp. E1 image pixel
      pix3 = PXMpix(E3pxm,px1,py1);                                              //  corresp. E3 image pixel

      red = pix2[0];                                                             //  area pixel
      green = pix2[1];
      blue = pix2[2];

      red *= sacp_brite;                                                         //  adjust brightness
      green *= sacp_brite;
      blue *= sacp_brite;
      maxrgb = red;                                                              //  prevent RGB limit overflow
      if (green > maxrgb) maxrgb = green;
      if (blue > maxrgb) maxrgb = blue;
      if (maxrgb > 255.9) {
         f1 = 255.9 / maxrgb;
         red *= f1;
         green *= f1;
         blue *= f1;
      }

      f1 = pix2[3] / 255.0;                                                      //  area pixel opacity

      if (f1 < 1.0) {
         f2 = 1.0 - f1;
         red = f1 * red + f2 * pix1[0];                                          //  blend area and image pixels
         green = f1 * green + f2 * pix1[1];
         blue = f1 * blue + f2 * pix1[2];
      }

      ii = py1 * Fpxb->ww + px1;
      dist = sa_pixmap[ii];                                                      //  edge distance
      if (dist == 0) f1 = 0;
      else if (dist < sacp_blend) f1 = f1 * dist / sacp_blend;                   //  ramp opacity within blend distance
      f2 = 1.0 - f1;
      red = f1 * red + f2 * pix1[0];
      green = f1 * green + f2 * pix1[1];
      blue = f1 * blue + f2 * pix1[2];

      pix3[0] = red;
      pix3[1] = green;
      pix3[2] = blue;
   }

   return;
}


