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

   Fotocx - edit photos and manage collections

   Copyright 2007-2024 Michael Cornelison
   source code URL: https://kornelix.net
   contact: mkornelix@gmail.com

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version. See https://www.gnu.org/licenses

   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.

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

   Fotocx 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           sa_stat_none/edit/fini/disab
   sa_pixmap[*]      0/1/2+ = pixel outside/edge/inside (edge distance)
   sa_pixmap2[*]     mouse-selected pixels (without added matching pixels)
   sa_pixselc[*]     0/1 = pixel not/in current selection group

   sa_mode           current area selection method:
   sa_mode_rect         select rectangle by drag/click
   sa_mode_ellipse      select ellipse by drag
   sa_mode_draw         freehand draw by drag/click
   sa_mode_follow       follow edge indicated by clicks
   sa_mode_mouse        select area within mouse (radius)
   sa_mode_onecolor     select one matching color within mouse
   sa_mode_allcolors    select all matching colors within mouse
   sa_mode_sharp        select sharp pixels

   m_select_area        select area dialog
   sa_geom_mousefunc    select rectangle or ellipse
   sa_draw_mousefunc    draw area outline manually
   sa_mouse_select      select area mouse functions
   sa_nextseq           sequence number for selected pixels
   sa_unselect_pixels   unselect recently selected pixels
   sa_pixmap_create     setup area pixel maps
   sa_finish            finish selected areas
   sa_finish_auto       auto finish areas where possible
   sa_map_pixels        map area edge and interior pixels
   sa_unfinish          unfinish selected areas
   m_select_find_gap    find a gap in an area outline
   m_select_blend       blend area edits using the mouse
   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_clear       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_clear             delete area callable function
   sa_edgecalc          calculate area edge distances
   sa_edgecreep         adjust area edges +-1 pixel
   sa_postfix           copy E1 to E3 outside area, blend E3 area edges

   m_select_copy        save area to default PNG file with alpha channel
   m_select_save        save area to PNG file with alpha channel
   m_select_load        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 "fotocx.h"                                                              //  (variables in fotocx.h are refs)

using namespace zfuncs;

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


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

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

   ch       *title = "Select Area for Edits";
   zdialog  *zd;

   if (menu) F1_help_topic = "select area";

   Plog(1,"m_select_area \n");

   if (! curr_file) {
      zmessageACK(Mwin,"no current file");
      return;
   }

   if (FGM != 'F') return;

   if (zd_sela) return;                                                          //  already active

   if (CEF && CEF->Farea != 2) {                                                 //  active edit function
      zmessageACK(Mwin,"Select Area not supported \n"
                       "by this edit function");
      return;
   }

/***
       _______________________________________________
      |           Select Area for Edits               |
      |             Press F1 for help                 |
      |                                               |
      | [x] select rectangle  [x] ellipse             |
      | [x] freehand draw  [x] follow edge            |
      | - - - - - - - - - - - - - - - - - - - - - - - | 
      | [x] select area within mouse                  |
      | [x] select one color within mouse [####]      |
      |     [x] optional deselect color [####]        |
      | [x] select all colors within mouse (flood)    |
      |                                               |
      | color match level % [___]                     |
      | mouse radius [___]  search range [___]        |
      | - - - - - - - - - - - - - - - - - - - - - - - | 
      | area edge creep [+] [-]                       |      area edge blend removed
      | - - - - - - - - - - - - - - - - - - - - - - - | 
      | area outline color: (#) (#) (#) (#) (#)       |      color buttons red green blue black white
      |                                               |
      |   [Finish] [Invert] [Show] [Hide] [Clear] [X] |
      |_______________________________________________|

***/

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

   zdialog_add_widget(zd,"label","labhelp","dialog","Press F1 for help","space=3");

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

   zdialog_add_widget(zd,"hbox","hbdraw","dialog");
   zdialog_add_widget(zd,"check","ckdraw","hbdraw","freehand draw","space=3");
   zdialog_add_widget(zd,"check","ckfollow","hbdraw","follow edge","space=10");

   zdialog_add_widget(zd,"hsep","sep1","dialog",0,"space=4");

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

   zdialog_add_widget(zd,"hbox","hbm2","dialog");
   zdialog_add_widget(zd,"check","ckonecolor","hbm2","select one color within mouse","space=3");
   zdialog_add_widget(zd,"colorbutt","onecolor","hbm2","0|0|0","space=5");
   
   zdialog_add_widget(zd,"hbox","hbm3","dialog");
   zdialog_add_widget(zd,"label","space","hbm3",0,"space=20");
   zdialog_add_widget(zd,"check","ckdselcolor","hbm3","optional deselect color");
   zdialog_add_widget(zd,"colorbutt","dselcolor","hbm3","0|0|0","space=5");

   zdialog_add_widget(zd,"hbox","hbm4","dialog");
   zdialog_add_widget(zd,"check","ckallcolors","hbm4","select all colors within mouse (flood)","space=3");

   zdialog_add_widget(zd,"hsep","sep2","dialog",0,"space=4");

   zdialog_add_widget(zd,"hbox","hbml","dialog");
   zdialog_add_widget(zd,"label","labmatch","hbml","color match level %","space=3");
   zdialog_add_widget(zd,"zspin","colormatch","hbml","0|100|1|90","space=5|size=3");

   zdialog_add_widget(zd,"hbox","hbmm","dialog");
   zdialog_add_widget(zd,"label","labmr","hbmm","mouse radius","space=3");
   zdialog_add_widget(zd,"zspin","mouserad","hbmm","1|300|1|20","space=5|size=3");
   zdialog_add_widget(zd,"label","space","hbmm",0,"space=10");
   zdialog_add_widget(zd,"label","labrange","hbmm","search range","space=3");
   zdialog_add_widget(zd,"zspin","searchrange","hbmm","1|20|1|5","space=5|size=3");

   zdialog_add_widget(zd,"hsep","sep3","dialog",0,"space=5");

   zdialog_add_widget(zd,"hbox","hbbw","dialog");
   zdialog_add_widget(zd,"label","labcreep","hbbw","area edge creep","space=3");
   zdialog_add_widget(zd,"button","creep+","hbbw","+","space=5");
   zdialog_add_widget(zd,"button","creep-","hbbw","‒");

   zdialog_add_widget(zd,"hsep","sep4","dialog",0,"space=5");

   zdialog_add_widget(zd,"hbox","hbm5","dialog",0,"space=5");
   zdialog_add_widget(zd,"label","labcolor","hbm5","area outline color:","space=3");
   zdialog_add_widget(zd,"imagebutt","red","hbm5","redball.png","size=15|space=3");
   zdialog_add_widget(zd,"imagebutt","green","hbm5","greenball.png","size=15|space=3");
   zdialog_add_widget(zd,"imagebutt","blue","hbm5","blueball.png","size=15|space=3");
   zdialog_add_widget(zd,"imagebutt","black","hbm5","blackball.png","size=15|space=3");
   zdialog_add_widget(zd,"imagebutt","white","hbm5","whiteball.png","size=15|space=3");

   zdialog_add_widget(zd,"hbox","hbb2","dialog",0,"space=10");
   zdialog_add_widget(zd,"button","finish","hbb2","Finish","space=2");
   zdialog_add_widget(zd,"button","invert","hbb2","Invert","space=2");
   zdialog_add_widget(zd,"button","show","hbb2","Show","space=2");
   zdialog_add_widget(zd,"button","hide","hbb2","Hide","space=2");
   zdialog_add_widget(zd,"button","clear","hbb2","Clear","space=2");
   zdialog_add_widget(zd,"button","done","hbb2"," X ","space=2");

   zdialog_add_ttip(zd,"ckrect","drag mouse to select rectangular area");
   zdialog_add_ttip(zd,"ckelips","drag mouse to select circular or elliptical area");
   zdialog_add_ttip(zd,"ckdraw","drag mouse to outline an area");
   zdialog_add_ttip(zd,"ckfollow","drag mouse along an edge to follow the edge");
   zdialog_add_ttip(zd,"labcolor","select line color");
   zdialog_add_ttip(zd,"labmr","size of mouse selection circle");
   zdialog_add_ttip(zd,"labmatch","required match level to select by color");
   zdialog_add_ttip(zd,"labrange","select by color search range");
   zdialog_add_ttip(zd,"ckmouse","select area within mouse circle");
   zdialog_add_ttip(zd,"ckonecolor","left click on image for selected color");
   zdialog_add_ttip(zd,"ckdselcolor","right click on image for deselected color"); 
   zdialog_add_ttip(zd,"ckallcolors","select surrounding areas matching colors in mouse");
   zdialog_add_ttip(zd,"labblend","area edits fade away within edge distance");
   zdialog_add_ttip(zd,"labcreep","move area boundary in/out in 1-pixel steps");
   zdialog_add_ttip(zd,"finish","map selected areas and verify");
   zdialog_add_ttip(zd,"invert","invert area");
   zdialog_add_ttip(zd,"show","show area outlines");
   zdialog_add_ttip(zd,"hide","hide area outlines");
   zdialog_add_ttip(zd,"clear","clear area selections");

   sa_mouseradius = 30;                                                          //  initial values matching dialog
   sa_colormatch = 90;
   sa_searchrange = 5;
   sa_mode = 0;
   zdialog_stuff(zd,"ckrect",0);
   zdialog_stuff(zd,"ckelips",0);
   zdialog_stuff(zd,"ckdraw",0);
   zdialog_stuff(zd,"ckfollow",0);
   zdialog_stuff(zd,"ckmouse",0);
   zdialog_stuff(zd,"ckonecolor",0);
   zdialog_stuff(zd,"ckdselcolor",0); 
   zdialog_stuff(zd,"ckallcolors",0);

   zdialog_run(zd,select_dialog_event,"save");                                   //  run dialog - parallel

   if (sa_stat) sa_show(1,0);                                                    //  show existing area
   return;
}


//  dialog event and completion callback function

int select_dialog_event(zdialog *zd, ch *event)
{
   int      Nckevents = 7, ii, kk, cc;
   ch       *ckevents[7] = { "ckrect", "ckelips", "ckdraw", "ckfollow",          //  mutually exclusive check boxes
                             "ckmouse", "ckonecolor", "ckallcolors" };
   
   if (zd->zstat == -2) zd->zstat = 0;                                           //  escape key (keep dialog) 

   if (sa_edgecalc_busy) return 1;                                               //  block until done 

   if (! curr_file) event = "done";                                              //  image went away
   if (FGM != 'F') event = "done";

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

   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
      Plog(0,"*** select area ignored for this edit function \n");
      return 1;
   }

   if (sa_stat == sa_stat_none) {                                                //  no area, create one
      sa_pixmap_create();                                                        //  allocate pixel maps
      sa_currseq = sa_Ncurrseq = 0;                                              //  reset selection sequence
      sa_Npixel = sa_edgecalc_done = 0;
      sa_fww = Fpxb->ww;                                                         //  valid image size for area
      sa_fhh = Fpxb->hh;
      sa_stat = sa_stat_edit;                                                    //  status = edit
      sa_mode = 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-9
            sa_stat = sa_stat_edit;                                              //  active edit status
            sa_Npixel = sa_edgecalc_done = 0;
            sa_show(1,0);                                                        //  show area
         }
         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);                      //  one checkbox >> on
      }
   }
   
   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(zd_sela,"colormatch",sa_colormatch);

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

   if (strmatch(event,"finish")) sa_finish();                                    //  finish (finalize) area
   if (strmatch(event,"invert")) sa_invert();                                    //  invert area (inside <--> outside)
   if (strmatch(event,"show")) sa_show(1,0);                                     //  show area
   if (strmatch(event,"hide")) sa_show(0,0);                                     //  hide area
   if (strmatch(event,"clear")) sa_clear();                                      //  clear area

   if (strmatchN(event,"creep",5) && sa_stat == sa_stat_fini) {                  //  edge creep changed and area finished
      if (event[5] == '+') sa_edgecreep(+1);
      else sa_edgecreep(-1);
   }
   
   if (strmatch(event,"red")) memcpy(LINE_COLOR,RED,3*sizeof(int));
   if (strmatch(event,"green")) memcpy(LINE_COLOR,GREEN,3*sizeof(int));
   if (strmatch(event,"blue")) memcpy(LINE_COLOR,BLUE,3*sizeof(int));
   if (strmatch(event,"black")) memcpy(LINE_COLOR,BLACK,3*sizeof(int));
   if (strmatch(event,"white")) memcpy(LINE_COLOR,WHITE,3*sizeof(int));
   if (zstrstr("red green blue black white",event)) Fpaint2();

   if (sa_stat == sa_stat_edit && sa_mode && Fshowarea)                          //  active edit mode
   {
      if (sa_mode == sa_mode_rect)
         takeMouse(sa_geom_mousefunc,dragcursor);                                //  rectangle
      if (sa_mode == sa_mode_ellipse)
         takeMouse(sa_geom_mousefunc,dragcursor);                                //  ellipse
      if (sa_mode == sa_mode_draw)
         takeMouse(sa_draw_mousefunc,drawcursor);                                //  freehand draw
      if (sa_mode == sa_mode_follow)
         takeMouse(sa_draw_mousefunc,drawcursor);                                //  follow edge

      if (sa_mode == sa_mode_mouse) {                                            //  mouse radius select
         sa_lastx = sa_lasty = 0;
         takeMouse(sa_mouse_select,0);
      }

      if (sa_mode == sa_mode_onecolor) {                                         //  mouse radius, one color select
         sa_lastx = sa_lasty = 0;
         takeMouse(sa_mouse_select,0);
      }
      
      if (sa_mode == sa_mode_allcolors) {                                        //  mouse radius, all colors select
         sa_lastx = sa_lasty = 0;
         takeMouse(sa_mouse_select,0);
      }
      
      if (sa_mode != sa_mode_onecolor)                                           //  not select one color
         zdialog_stuff(zd,"ckdselcolor",0);                                      //  remove check for deselect color
   }

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


//  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;
   cairo_t     *cr;

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

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

   cr = draw_context_create(gdkwin,draw_context);

   if (RMclick)                                                                  //  right mouse click
   {
      RMclick = 0;
      sa_unselect_pixels(cr);                                                    //  remove latest selection
      drag = 0;
      Fpaint2();
      draw_context_destroy(draw_context);
      return;
   }

   if (! Mxdrag && ! Mydrag) {                                                   //  no drag underway
      draw_context_destroy(draw_context);
      return;
   }

   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;
      draw_context_destroy(draw_context);
      return;
   }

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

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

   if (sa_mode == sa_mode_rect)                                                  //  draw rectangle
   {
      sa_draw_line(mx1,my1,mx2,my1,cr);                                          //  draw 4 lines
      sa_draw_line(mx2,my1,mx2,my2,cr);
      sa_draw_line(mx2,my2,mx1,my2,cr);
      sa_draw_line(mx1,my2,mx1,my1,cr);
   }

   if (sa_mode == sa_mode_ellipse)                                               //  draw ellipse
   {
      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,cr);                                                  //  draw 2 points on ellipse
         px = cx + x + 0.5;
         sa_draw1pix(px,py,cr);
      }

      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,cr);                                                  //  draw 2 points on ellipse
         py = cy + y + 0.5;
         sa_draw1pix(px,py,cr);
      }
   }

   draw_context_destroy(draw_context);
   return;
}


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

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

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

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

   cr = draw_context_create(gdkwin,draw_context);

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

   if (! (LMclick || RMclick || Mxdrag || Mydrag)) {                             //  no mouse action
      if (openend) {
         openend = 0;                                                            //  close gap after prior draw
         mx1 = sa_endpx[sa_currseq];
         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,cr);
      }
      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,cr);                                          //  erase pixel
            }
         }

         goto draw_exit;
      }

      if (sa_currseq > sa_maxseq-2) {
         zmessageACK(Mwin,"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,cr);                                       //  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) newseq = 1;                                                 //  incr. sequence each 50 pixels

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

      if (sa_mode == sa_mode_draw) sa_draw_line(mx1,my1,mx2,my2,cr);             //  draw line from end pixel to mouse
      if (sa_mode == sa_mode_follow) sa_follow_edge(mx1,my1,mx2,my2,cr);         //  follow edge from end pixel to mouse

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

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

draw_exit:

   draw_context_destroy(draw_context);
   LMclick = RMclick = 0;                                                        //  stop further mouse action
   Mxdrag = Mydrag = 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 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, cairo_t *cr)
{
   int      pxm, pym;
   float    slope;
   int      crflag = 0;

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

   if (! cr) {
      cr = draw_context_create(gdkwin,draw_context);
      crflag = 1;
   }

   if (px1 == px2 && py1 == py2) {                                               //  only one pixel
      sa_draw1pix(px1,py1,cr);
      if (crflag) draw_context_destroy(draw_context); 
      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,cr);
         }
      }
      else {
         for (pym = py1; pym >= py2; pym--) {
            pxm = round(px1 + slope * (pym - py1));
            sa_draw1pix(pxm,pym,cr);
         }
      }
   }
   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,cr);
         }
      }
      else {
         for (pxm = px1; pxm >= px2; pxm--) {
            pym = round(py1 + slope * (pxm - px1));
            sa_draw1pix(pxm,pym,cr);
         }
      }
   }

   if (crflag) draw_context_destroy(draw_context); 
   return;
}


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

void sa_draw1pix(int px, int py, cairo_t *cr)
{
   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,cr);                                                         //  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, cairo_t *cr)
{
   float    sa_get_contrast(int px, int py);

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

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

   if (! cr) {
      cr = draw_context_create(gdkwin,draw_context);
      crflag = 1;
   }
   
   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,cr);                                          //  draw p3 to p6

      px3 = px6;                                                                 //  next p3
      py3 = py6;
   }

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

   if (crflag) draw_context_destroy(draw_context); 
   return;
}


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

void sa_redraw(int mx1, int my1, int mx2, int my2, cairo_t *cr)
{
   int      ii, npx, npy;
   int      thresh, npdist, d1, d2;
   int      crflag = 0;

   if (! cr) {
      cr = draw_context_create(gdkwin,draw_context);
      crflag = 1;
   }
   
   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,cr);
      }
   }

   sa_draw_line(mx1,my1,mx2,my2,cr);                                             //  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) break;
      d1 = (npx-mx1)*(npx-mx1) + (npy-my1)*(npy-my1);
      d2 = (npx-mx2)*(npx-mx2) + (npy-my2)*(npy-my2);
      if (d2 >= d1) break;
      ii = npy * Fpxb->ww + npx;                                                 //  motion toward pixel
      sa_pixmap[ii] = 0;                                                         //  unmap pixel
      erase_pixel(npx,npy,cr);                                                   //  erase pixel
   }
   
   if (crflag) draw_context_destroy(draw_context); 
   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
//                       optionally deselect matching color within mouse
//  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_selectall(int select);

   int         newdrag;
   static int  pxcc, mxdown, mydown, dragseq;
   int         startx, starty, endx, endy;
   float       slope;
   
   if (sa_stat != sa_stat_edit) return;                                          //  area gone?

   if (sa_mode == sa_mode_allcolors && ! sa_pixselc) {                           //  allocate memory for this mode
      pxcc = Fpxb->ww * Fpxb->hh;
      sa_stackdirec = (ch *) zmalloc(pxcc,"select-area");
      sa_stack = (int *) zmalloc(pxcc * sizeof(int),"select-area");
      sa_maxstack = pxcc;
      sa_Nstack = 0;
      sa_pixselc = (uint8 *) zmalloc(pxcc,"select-area");
   }

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

   if (sa_pixselc) memset(sa_pixselc,0,pxcc);                                    //  clear list of mouse-selected pixels
   
   if (LMclick)                                                                  //  left mouse click
   {
      LMclick = 0;
      sa_nextseq();                                                              //  set next sequence no.
      dragseq = 0;                                                               //  reset drag counter
      
      if (sa_lastx == 0 && sa_lasty == 0) {                                      //  no prior selection
         sa_lastx = Mxclick;
         sa_lasty = Myclick;
      }

      sa_mousex = Mxclick;
      sa_mousey = Myclick;

      if (sa_mode == sa_mode_onecolor) sa_mouse_select1(1,1);                    //  set color for matching within mouse
      if (sa_mode == sa_mode_allcolors) sa_mouse_selectall(1);                   //  select matching colors in/beyond mouse
      
      if (sa_mode == sa_mode_mouse)                                              //  select along line from last
      {                                                                          //    selection to current mouse
         startx = sa_lastx;
         starty = sa_lasty;
         endx = Mxclick;
         endy = Myclick;

         if (abs(endy - starty) > abs(endx - startx)) {
            slope = 1.0 * (endx - startx) / (endy - starty);
            if (endy > starty) {
               for (sa_mousey = starty; sa_mousey <= endy; sa_mousey++) {
                  sa_mousex = round(startx + slope * (sa_mousey - starty));
                  sa_mouse_selectall(1);                                         //  select pixels within mouse
               }
            }
            else {
               for (sa_mousey = starty; sa_mousey >= endy; sa_mousey--) {
                  sa_mousex = round(startx + slope * (sa_mousey - starty));
                  sa_mouse_selectall(1);
               }
            }
         }
         else {
            slope = 1.0 * (endy - starty) / (endx - startx);
            if (endx > startx) {
               for (sa_mousex = startx; sa_mousex <= endx; sa_mousex++) {
                  sa_mousey = round(starty + slope * (sa_mousex - startx));
                  sa_mouse_selectall(1);
               }
            }
            else {
               for (sa_mousex = startx; sa_mousex >= endx; sa_mousex--) {
                  sa_mousey = round(starty + slope * (sa_mousex - startx));
                  sa_mouse_selectall(1);
               }
            }
         }
      }
      
      Fpaintnow();
   }

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

   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 == sa_mode_mouse)
            sa_mouse_selectall(1);
         if (sa_mode == sa_mode_onecolor)                                        //  select matching colors within mouse
            sa_mouse_select1(2,1); 
         if (sa_mode == sa_mode_allcolors)                                       //  select in mouse + matching colors beyond
            sa_mouse_selectall(1);
         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 == sa_mode_mouse)
            sa_mouse_selectall(0);
         if (sa_mode == sa_mode_onecolor)
            sa_mouse_select1(2,0);
         if (sa_mode == sa_mode_allcolors)
            sa_mouse_selectall(0);
      }
   }

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


//  Left click - pixel at mouse position --> color to select
//     + shift - pixel at mouse position --> color to deselect 
//  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;
   int      Fdeselect;                                                           //  deselect option engaged 
   uint8    *pix1;
   float    match1, match2, match3;
   float    red, green, blue;
   float    Rsel, Gsel, Bsel;
   float    Rdsel, Gdsel, Bdsel;
   ch       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
      if (KBshiftkey) zdialog_stuff(zd_sela,"dselcolor",colorbutt);              //    deselect color 
      else zdialog_stuff(zd_sela,"onecolor",colorbutt);                          //    select color

      return;
   }

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

   zdialog_fetch(zd_sela,"onecolor",colorbutt,16);                               //  get select color button color
   ii = sscanf(colorbutt,"%f|%f|%f",&Rsel,&Gsel,&Bsel);
   if (ii != 3) {
      Plog(0,"color button error: select color");
      return;
   }
   
   zdialog_fetch(zd_sela,"ckdselcolor",Fdeselect);                               //  deselect option engaged

   if (Fdeselect) {
      zdialog_fetch(zd_sela,"dselcolor",colorbutt,16);                           //  get deselect color button color
      ii = sscanf(colorbutt,"%f|%f|%f",&Rdsel,&Gdsel,&Bdsel);
      if (ii != 3) {
         Plog(0,"color button error: deselect color");
         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
      ii = Fpxb->ww * py + px;
      newpix = 0;

      if (select)                                                                //  left drag - select mode
      {
         match2 = RGBmatch(Rsel,Gsel,Bsel,pix1[0],pix1[1],pix1[2]);              //  0...1 = zero...perfect match
         if (match2 < match1) continue;                                          //  not a good enough match
         
         if (Fdeselect) {                                                        //  deselect option engaged
            match3 = RGBmatch(Rdsel,Gdsel,Bdsel,pix1[0],pix1[1],pix1[2]);        //  pixel match to deselect color
            if (match3 > match2) continue;                                       //  if better match, don't select
         }

         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;
            sa_pixmap2[ii] = 1;                                                  //  pixel is mouse selected
         }
      }

      else if (sa_pixmap[ii])                                                    //  right drag - unselect mode
      {
         sa_pixmap[ii] = 0;
         newpix = 1;
         sa_pixmap2[ii] = 0;
      }

      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;
      }
   }
   
   if (select) {                                                                 //  remember last select location
      sa_lastx = sa_mousex;
      sa_lasty = sa_mousey;
   }

   return;
}


//  Select 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_selectall(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;
   ch          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
         sa_pixmap2[ii] = 1;                                                     //  pixel is mouse selected
      }
      else sa_pixmap[ii] = sa_pixmap2[ii] = 0;                                   //  unselect
   }

   if (select) {                                                                 //  remember last select location
      sa_lastx = sa_mousex;
      sa_lasty = sa_mousey;
   }

   if (sa_mode == sa_mode_mouse) {                                               //  no color matching, done
      Fpaint4(px-mrad,py-mrad,2*mrad,2*mrad,0);                                  //  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;                                    //  outside mouse circle
      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];
      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((ch *) Ctab, sizeof(Ctab_t), Ntab, keys, 1);                          //  sort descending count of matching pixels

// thresh = 0.03 * Ntab;                                                         //  exclude minority pixels
   thresh = 0.05 * Ntab;                                                         //  exclude minority pixels               24.60
   if (thresh < 2) 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_stack[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_stack[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_stack[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(Ctab[kk].rgb,pix1);                                   //  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;
            sa_pixmap2[ii] = 1;                                                  //  pixel is mouse selected
         }
      }
      else if (sa_pixmap[ii]) {                                                  //  unselect mode
         sa_pixmap[ii] = sa_pixmap2[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_stack[kk] = ii;
      sa_stackdirec[kk] = 'a';                                                   //  direction = ahead
   }

   Fpaint4(xlo,ylo,xhi-xlo+1,yhi-ylo+1,0);                                       //  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(cairo_t *cr)
{
   int      px, py, xlo, xhi, ylo, yhi;
   int      crflag = 0;

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

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

   if (! cr) {
      cr = draw_context_create(gdkwin,draw_context);
      crflag = 1;
   }
   
   for (int ii = 0; ii < Fpxb->ww * Fpxb->hh; ii++) {
      if (sa_pixmap[ii] == sa_currseq) {
         sa_pixmap[ii] = sa_pixmap2[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,cr);                      //  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

   if (crflag) draw_context_destroy(draw_context); 
   return;
}


//  allocate pixel maps for new select area

void sa_pixmap_create()
{
   int      cc;
   cc = Fpxb->ww * Fpxb->hh * sizeof(uint16);
   sa_pixmap = (uint16 *) zmalloc(cc,"select-area");                             //  maps outside/edge/inside pixels
   cc = Fpxb->ww * Fpxb->hh * sizeof(uint8);
   sa_pixmap2 = (uint8 *) zmalloc(cc,"select-area");                             //  maps mouse-selected pixels
   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
int      sa_fincancel;                                                           //  finish area - user cancel

int  sa_finish_dialog_event(zdialog *zd, ch *event);                             //  private functions
void sa_finish_mousefunc();
void sa_finish_mappix();
void sa_finish_color();


void sa_finish()
{
   ch     *fmess = 
            "Fill selected areas with color for visual verification. \n"
            "Method 1: left-click in each selected area not already filled. \n"
            "Method 2: right-click OUTSIDE all selected areas. \n"
            "Press [help] button for clarification";

   int      zstat, cc;
   
   Plog(1,"sa_finish() \n");

   if (sa_stat == sa_stat_none) return;                                          //  no area?
   if (! sa_validate()) return;                                                  //  invalid for current image

   sa_finish_auto();                                                             //  auto-finish mouse-selected areas
   if (sa_stat != sa_stat_fini) return;

   cc = Fpxb->ww * Fpxb->hh;
   if (sa_stackdirec) zfree(sa_stackdirec);
   sa_stackdirec = (ch *) zmalloc(cc,"select-area");
   if (sa_stack) zfree(sa_stack);
   sa_stack = (int *) zmalloc(cc * sizeof(int),"select-area");
   sa_maxstack = cc;

   sa_fincancel = 0;
   sa_Nstack = 0;

/***
          _________________________________________________________________
         |                     Finish Area                                 |
         |                                                                 |
         | Fill selected areas with color for visual verification.         |
         | Method 1: left-click in each selected area not already filled.  |
         | Method 2: right-click OUTSIDE all selected areas.               |
         | Press [help] button for clarification.                          |
         |                                                                 |
         |                                            [Help] [Keep] [Undo] |
         |_________________________________________________________________|
         
***/

   sa_finzd = zdialog_new("Finish Area",Mwin,"Help","Keep","Undo",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,sa_finish_dialog_event,"mouse");                         //  run dialog, wait for completion       24.70
   zstat = zdialog_wait(sa_finzd);
   zdialog_free(sa_finzd);
   sa_finzd = 0;

   freeMouse();                                                                  //  disconnect mouse

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

   while (sa_Nstack)                                                             //  wait for pixel search to complete
      zmainsleep(0.01);

   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,"found %d pixels",sa_Npixel);
      return;
   }

   sa_stat = sa_stat_fini;                                                       //  area is finished
   areanumber++;                                                                 //  next sequential number
   sa_edgecalc_done = 0;                                                         //  edge calculation is missing
   Fpaintnow();
   return;
}


//  dialog event and completion function

int sa_finish_dialog_event(zdialog *zd, ch *event)
{
   ch     *fmess = 
             "Method 1: \n"
             "  Left-click inside an outlined area that is not already filled. \n"
             "  Area will be filled with color for visible verification. \n"
             "  Gaps in the outline will cause overflow outside the area. \n"
             "  Repeat for each outlined area that is not already filled. \n"
             "Method 2: \n"
             "  Right-click outside ALL outlined areas. \n"
             "  All areas will be filled with color for visible verification. \n"
             "  Gaps in an area outline will cause that area not to be filled. \n"
             "Gaps in an area outline: \n"
             "  Gaps must be closed before proceeding with edits. \n"
             "  The Find Gap function can be used for this.";

   if (zd->zstat == 1) {                                                         //  [help]
      zd->zstat = 0;                                                             //  keep dialog active
      zmessageACK(Mwin,fmess);
      return 1;
   }
   
   if (zd->zstat == 2) return 0;                                                 //  [keep]
   if (zd->zstat == 3)  sa_unfinish();                                           //  [undo] 
   return 0;                                                                     //  [x]
}


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

void sa_finish_mousefunc()
{
   int      ii, jj, px, py, npix;
   
   if (sa_stat == sa_stat_none) return;                                          //  area gone?
   
   if (! LMclick && ! RMclick) return;

   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_stack[0] = ii;                                                             //  put seed pixel into stack
   sa_stackdirec[0] = 'a';                                                       //  direction = ahead
   sa_Nstack = 1;                                                                //  stack count

   //  click inside one outlined area, select all pixels within outline

   if (LMclick)
   {
      LMclick = 0;
      sa_finish_mappix();                                                        //  do pixel search
      sa_finish_color();                                                         //  fill with color for user verify
   }
   
   //  click outside all areas, select all pixels outside all outlines, then invert
   
   if (RMclick)
   {
      RMclick = 0;

      sa_finish_mappix();                                                        //  do pixel search

      npix = 0;

      for (py = 0; py < Fpxb->hh; py++)                                          //  loop all pixels
      for (px = 0; px < Fpxb->ww; px++)
      {
         ii = py * Fpxb->ww + px;

         if (sa_pixmap2[ii]) {                                                   //  if mouse-selected pixel
            npix++;                                                              //    no change
            continue;
         }

         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
      }

      sa_finish_color();                                                         //  fill with color for user verify

      sa_stat = sa_stat_fini;                                                    //  area now finished
      sa_Npixel = npix;                                                          //  new select area pixel count
      sa_edgecalc_done = 0;                                                      //  edge calculation missing
   }

   return;
}


//  function to map all pixels found within enclosure containing starting pixel

void sa_finish_mappix()
{
   int         px, py, ii, jj, kk;
   int         ppx, ppy, npx, npy;
   ch          direc;
   
   Plog(1,"sa_finish_mappix() \n");
   
   Funcbusy(+1);                                                                 //  24.40

   while (sa_Nstack)                                                             //  find all pixels within enclosed area(s)
   {
      kk = sa_Nstack - 1;                                                        //  get last pixel in stack
      ii = sa_stack[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_stack[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
         sa_pixmap[ii] = 1;                                                      //  this pixel is edge pixel
         continue;
      }

      jj = npy * Fpxb->ww + npx;

      if (Fpxb->nc > 3 && PXBpix(Fpxb,npx,npy)[3] < 250) {                       //  next pixel is in transparent area
         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;                                                         //  this pixel is interior pixel
      kk = sa_Nstack++;                                                          //  put pixel into stack
      sa_stack[kk] = jj;
      sa_stackdirec[kk] = 'a';                                                   //  direction = ahead
   }

   Funcbusy(-1);                                                                 //  24.40
   
   sa_Nstack = 0;                                                                //  done
   return;
}


//  fill selected areas with color for user verification

void sa_finish_color()
{
   int      ii, px, py;
   int      npaint;
   cairo_t  *cr;
   
   Plog(1,"sa_finish_color \n");

   cr = draw_context_create(gdkwin,draw_context);

   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,cr,1);                                 //  thin pixel                            24.60
   }

   draw_context_destroy(draw_context);
   return;
}


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

void sa_finish_auto()
{
   Plog(2,"sa_finish_auto \n");

   if (sa_stat == sa_stat_none) return;                                          //  no area?
   if (! sa_validate()) return;                                                  //  invalid for current image
   
   sa_stat = sa_stat_edit;                                                       //  area is unfinished
   sa_Npixel = 0;

   sa_map_pixels();                                                              //  map interior pixels

   if (sa_Npixel < 10) {
      zmessageACK(Mwin,"found %d pixels",sa_Npixel);
      sa_clear();
      return;
   }

   sa_stat = sa_stat_fini;                                                       //  area is finished
   areanumber++;                                                                 //  next sequential number
   sa_edgecalc_done = 0;                                                         //  edge calculation is missing
   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;
   
   Plog(2,"sa_map_pixels \n");

   if (sa_stat == sa_stat_none) return;                                          //  no area?

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

   Funcbusy(+1);                                                                 //  24.40
   
   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] < 250)                           //  transparency edge
         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
   }

   Funcbusy(-1);                                                                 //  24.40

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


//  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_pixmap2[ii]) continue;                                              //   (omit mouse selected pixels)
      if (sa_pixmap[ii] > 1) sa_pixmap[ii] = 0;                                  //     by finish search function
   }

   sa_stat = sa_stat_edit;                                                       //  resume edit mode
   sa_edgecalc_done = 0;
   Fpaint2();
   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 *, ch *menu)
{
   int  sa_find_gap_dialog_event(zdialog *zd, ch *event);
   void sa_find_gap_mousefunc();

   ch     *fmess = "Click near any position on the area outline. \n"
                   "Possible gaps in the outline will be found. \n"
                    "Press F1 for help";
   zdialog     *zd;

   F1_help_topic = "find area gap";

   Plog(1,"m_select_find_gap \n");

   if (FGM != 'F') return;

   if (sa_stat == sa_stat_none) return;                                          //  no area?
   if (! sa_validate()) return;                                                  //  invalid for current image

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

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

   sa_show(1,0);                                                                 //  show area

   zd = zdialog_new("find outline gap",Mwin," X ",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_dialog_event,"save");                              //  run dialog
   takeMouse(sa_find_gap_mousefunc,dragcursor);                                  //  connect mouse function
   return;
}


//  dialog event and completion function

int sa_find_gap_dialog_event(zdialog *zd, ch *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
   if (sa_stack) zfree(sa_stack);                                                //  free memory
   sa_stack = 0;
   return 1;
}


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

void sa_find_gap_mousefunc()                                                     //  overhauled 24.40
{
   int         rad, ii, jj, kk, pix0;
   static int  Fbusy = 0;
   float       angle;
   int         ww, hh, cc;
   int         mx, my, px, py, rx, ry;
   ch          *pixmark = 0;
   cairo_t     *cr = 0;

   if (sa_stat == sa_stat_none) return;

   if (! LMclick) return;                                                        //  no left mouse click
   LMclick = 0;

   sa_map_pixels();                                                              //  find edge pixels
   if (! sa_Npixel) return;
   
   ww = Fpxb->ww;                                                                //  image size
   hh = Fpxb->hh;

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

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

   if (Fbusy) return;                                                            //  prevent re-entry
   Fbusy++;
   
   pix0 = -1;

   for (rad = 1; rad < 100; rad++)                                               //  loop radius = 1 - 100 pixels
   for (angle = 0; angle < 2*PI; angle += 0.7/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) {
         pix0 = ii;
         goto break2;
      }
   }
   break2:
   
   if (pix0 == -1) {
      zmessage_post(Mwin,"20/20",3,"cannot find area outline");                  //  no edge pixels was found
      goto cleanup;
   }
   
   sa_show(0,0);                                                                 //  hide area
   Fpaintnow();

   if (sa_stack) zfree(sa_stack);                                                //  allocate pixel stack memory
   cc = ww * hh;
   sa_stack = (int *) zmalloc(cc * sizeof(int),"select_area");
   sa_Nstack = 0;
   sa_maxstack = cc;

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

   cr = draw_context_create(gdkwin,draw_context);

   Funcbusy(+1);                                                                 //  24.40
   
   sa_stack[0] = pix0;                                                           //  put initial edge pixel into stack
   sa_Nstack = 1;                                                                //  stack count

   while (sa_Nstack)   
   {
      kk = --sa_Nstack;                                                          //  pull pixel from stack
      ii = sa_stack[kk];
      pixmark[ii] = 1;                                                           //  mark pixel

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

      draw_pixel(px,py,cr,2);                                                    //  draw fat pixel

      zmainsleep(0.0005);                                                        //  slow down for visibility

      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                    24.40
         if (px+rx <= 0 || px+rx >= ww-1) continue;

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

         if (sa_pixmap[jj] == 1) {                                               //  neighbor is an unmarked edge pixel
            kk = sa_Nstack++;                                                    //  add to stack
            sa_stack[kk] = jj;
         }
      }
   }
   
   Funcbusy(-1);                                                                 //  24.40
cleanup:
   Fbusy = 0;
   if (pixmark) zfree(pixmark);                                                  //  free memory
   if (cr) draw_context_destroy(draw_context);
   sa_show(1,0);                                                                 //  show area
   return;
}


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

//  Manually blend selected parts of an edited area using the mouse.
//  Change pixels from E3 values to E1 values, gradually.

namespace areablend_names
{
   editfunc    EFareablend;                                                      //  edit function data
   int         blendwidth = 0;                                                   //  area edge blend width
   int         blurwidth = 0;                                                    //  area edge blur width
   int         radius = 50;                                                      //  mouse radius
   int         Bcent = 50, Bedge = 0;                                            //  blend rates, mouse center and edge
   uint8       *distmap;                                                         //  maps pixel distance to area edge
}


//  menu function

void m_select_blend(GtkWidget *, ch *menu)
{
   using namespace areablend_names;
   
   int   areablend_dialog_event(zdialog* zd, ch *event);
   void  areablend_mousefunc();
   
   ch       *helptext = "Left-drag mouse within the area to \n"
                        "  gradually undo area edits. \n"
                        "Right-drag to restore edits. \n"
                        "Drag along area edge to blend edge.";
   ch       text[40];

   F1_help_topic = "area blend";
   
   Plog(1,"m_select_blend \n");

   if (CEF) {
      zmessageACK(Mwin,"finish current edit first");
      return;
   }

   if (! E0pxm) {
      zmessageACK(Mwin,"no current edit image");
      return;
   }
   
   if (URS_pos < 1) {
      zmessageACK(Mwin,"no prior edit image");
      return;
   }
   
   E9pxm = load_undo(URS_pos - 1);                                               //  image from prior edit
   if (! E9pxm) {
      Plog(0,"no prior edit image \n");                                          //  should not happen
      return;
   }

   if (E9pxm->ww != E0pxm->ww || E9pxm->hh != E0pxm->hh) {                       //  prior edit must be same size          24.60
      zmessageACK(Mwin,"Image dimensions have changed, \n"
                       "Area Blend cannot be used");
      PXM_free(E9pxm);
      return;
   }

   if (sa_stat < sa_stat_fini) {
      zmessageACK(Mwin,"select area missing or not finished");
      return;
   }

   EFareablend.menufunc = m_select_blend;
   EFareablend.menuname = "Area Blend";
   EFareablend.mousefunc = areablend_mousefunc;
   EFareablend.Farea = 2;                                                        //  use area
   
   if (! edit_setup(EFareablend)) return;                                        //  initz. edit

/***
             _________________________________________
            |              Area Blend                 |
            |                                         |
            |  Left-drag mouse within the area to     |
            |    gradually undo area edits.           |
            |  Right-drag to restore edits.           |
            |  Drag along area edge to blend edge.    |
            |                                         |
            | use prior edit step [_______________|v] |
            |                                         |
            | mouse radius [___]                      |                          //  2-999
            | blend rate:  center [___]  edge [___]   |                          //  0-100
            |                                         |
            | Process all area edges at once:         |
            | blend area edges: blend width [___]     |
            | blur area edges: blur width [___]       |                          //  24.60
            |                                         | 
            |                                [OK] [X] |
            |_________________________________________|

***/

   zdialog *zd = zdialog_new("Area Blend",Mwin,"OK"," X ",null);
   CEF->zd = zd;
   
   zdialog_add_widget(zd,"hbox","hbtip","dialog",0,"space=3");
   zdialog_add_widget(zd,"label","labhelp","hbtip",helptext,"space=3");

   zdialog_add_widget(zd,"hbox","space","dialog",0,"space=3");

   zdialog_add_widget(zd,"hbox","hbstep","dialog");
   zdialog_add_widget(zd,"label","labstep","hbstep","use prior edit step:","space=3");
   zdialog_add_widget(zd,"combo","editstep","hbstep");

   zdialog_add_widget(zd,"hbox","space","dialog",0,"space=5");

   zdialog_add_widget(zd,"hbox","hbrad","dialog");
   zdialog_add_widget(zd,"label","labrad","hbrad","mouse radius:","space=3");
   zdialog_add_widget(zd,"zspin","radius","hbrad","2|999|1|100","space=3");
   zdialog_add_widget(zd,"hbox","hbblend","dialog");
   zdialog_add_widget(zd,"label","labcenter","hbblend","blend rate: center:","space=3");
   zdialog_add_widget(zd,"zspin","Bcent","hbblend","0|100|1|50","space=3");
   zdialog_add_widget(zd,"label","labedge","hbblend","edge:","space=3");
   zdialog_add_widget(zd,"zspin","Bedge","hbblend","0|100|1|0","space=3");

   zdialog_add_widget(zd,"hbox","space","dialog",0,"space=8");

   zdialog_add_widget(zd,"hbox","hbdoall","dialog");
   zdialog_add_widget(zd,"label","labdoall","hbdoall","Process all area edges at once:","space=3");
   zdialog_add_widget(zd,"hbox","hbblendall","dialog");
   zdialog_add_widget(zd,"label","labrad","hbblendall","blend area edges: blend width:","space=3");
   zdialog_add_widget(zd,"zspin","blendwidth","hbblendall","1|999|1|1","space=3");
   zdialog_add_widget(zd,"hbox","hbblurall","dialog");
   zdialog_add_widget(zd,"label","labrad","hbblurall","blur area edges: blur width:","space=3");
   zdialog_add_widget(zd,"zspin","blurwidth","hbblurall","1|100|1|1","space=3");

   for (int ii = 0; ii <= URS_pos - 1; ii++) {                                   //  prior edit steps > dropdown list
      snprintf(text,40,"%d %s",ii,URS_menu[ii]);                                 //  includes original image
      zdialog_stuff(zd,"editstep",text);                                         //  current = status before current edit
   }

   blendwidth = 0;                                                               //  initial defaults
   radius = 100;
   Bcent = 50;
   Bedge = 0;

   takeMouse(areablend_mousefunc,0);                                             //  take mouse 

   zdialog_run(zd,areablend_dialog_event,"save");                                //  run dialog - parallel
   return;
}


//  areablend dialog event and completion function

int areablend_dialog_event(zdialog *zd, ch *event)                               //  areablend dialog event function
{
   using namespace areablend_names;
   
   void areablend_mousefunc();
   void areablend_blendwidth();
   void areablend_blurwidth();

   ch       text[40];
   int      nn;

   if (sa_stat < sa_stat_fini) {
      zmessageACK(Mwin,"select area missing or not finished");
      zd->zstat = -2;
   }
   
   if (strmatch(event,"done")) zd->zstat = 1;                                    //  apply and quit
   if (strmatch(event,"cancel")) zd->zstat = 2;                                  //  cancel

   if (zd->zstat)
   {
      if (zd->zstat == 1) edit_done(0);                                          //  complete edit
      else edit_cancel(0);
      freeMouse();                                                               //  disconnect mouse function
      return 1;
   }

   if (strmatch(event,"editstep"))                                               //  select prior edit step
   {
      zdialog_fetch(zd,"editstep",text,40);                                      //  "nn menuname"
      nn = atoi(text);
      if (nn < 0 || nn > URS_max) return 1;
      if (E9pxm) PXM_free(E9pxm);
      E9pxm = load_undo(nn);                                                     //  load prior edit output image
      if (! E9pxm) return 1;
      if (E9pxm->ww != Eww || E9pxm->hh != Ehh) {
         zmessageACK(Mwin,"Image dimensions have changed, \n"
                          "this prior image cannot be used");
         PXM_free(E9pxm);                                                        //  free memory
         E9pxm = 0;
      }
   }

   takeMouse(areablend_mousefunc,0);

   if (strmatch(event,"blendwidth")) {                                           //  process area edge blend
      zdialog_fetch(zd,"blendwidth",blendwidth);                                 //  edge blend distance
      areablend_blendwidth();
      return 1;
   }

   if (strmatch(event,"blurwidth")) {                                            //  process area edge blend
      zdialog_fetch(zd,"blurwidth",blurwidth);                                   //  edge blend distance
      areablend_blurwidth();
      return 1;
   }
   
   if (strmatch(event,"radius"))
      zdialog_fetch(zd,"radius",radius);

   if (strmatch(event,"Bcent"))
      zdialog_fetch(zd,"Bcent",Bcent);

   if (strmatch(event,"Bedge"))
      zdialog_fetch(zd,"Bedge",Bedge);

   return 1;
}


//  mouse function - called for mouse events inside image

void areablend_mousefunc()
{
   using namespace areablend_names;

   int      ii, rx, ry;
   int      radius2, rad2;
   int      px, py, ww, hh;
   float    *pix1, *pix3, *pix9;
   float    Fdist, Pcenter, Pedge, power;
   float    f1, f3, f9;

   Pcenter = 0.01 * Bcent;                                                       //  blend power at mouse center, 0...1.0
   Pedge = 0.01 * Bedge;                                                         //  blend power at mouse edge, 0...1.0
      
   if (! CEF || sa_stat < sa_stat_fini || ! E9pxm) return;

   draw_mousecircle(Mxposn,Myposn,radius,0,0);                                   //  show mouse selection circle

   if (! Mxdrag && ! Mydrag) return;                                             //  wait for drag event
   Mxdrag = Mydrag = 0;                                                          //  neutralize drag
   
   radius2 = radius * radius;
   
   for (rx = -radius; rx <= radius; rx++)                                        //  loop every pixel in mouse
   for (ry = -radius; ry <= radius; ry++)
   {
      rad2 = rx * rx + ry * ry;
      if (rad2 > radius2) continue;                                              //  outside radius
      px = Mxposn + rx;
      py = Myposn + ry;
      if (px < 0 || px > Eww-1) continue;                                        //  off the image edge
      if (py < 0 || py > Ehh-1) continue;

      ii = py * Eww + px;
      if (! sa_pixmap[ii]) continue;                                             //  not within area
      
      Fdist = sqrt(rad2);                                                        //  px/py distance from mouse center
      Fdist = Fdist / radius;                                                    //  scale 0...1.0
      power = Pcenter + (Pedge - Pcenter) * Fdist;                               //  blend power at this pixel, 0...1.0
      power = pow(power,1.5);                                                    //  curve it

      if (Mbutton == 1) {                                                        //  left drag, blend
         f9 = 0.1 * power;                                                       //  reduce rate of change
         f3 = 1.0 - f9;
         pix9 = PXMpix(E9pxm,px,py);
         pix3 = PXMpix(E3pxm,px,py);
         pix3[0] = f9 * pix9[0] + f3 * pix3[0];                                  //  edit image tends to prior edit image
         pix3[1] = f9 * pix9[1] + f3 * pix3[1];
         pix3[2] = f9 * pix9[2] + f3 * pix3[2];
      }
      
      if (Mbutton == 3) {                                                        //  right drag, undo blend
         f1 = 0.2 * power;
         f3 = 1.0 - f1;
         pix1 = PXMpix(E1pxm,px,py);
         pix3 = PXMpix(E3pxm,px,py);
         pix3[0] = f1 * pix1[0] + f3 * pix3[0];                                  //  edit image tends to input image
         pix3[1] = f1 * pix1[1] + f3 * pix3[1];
         pix3[2] = f1 * pix1[2] + f3 * pix3[2];
      }
   }   
   
   px = Mxposn - radius - 1;                                                     //  repaint modified area
   py = Myposn - radius - 1;
   ww = hh = 2 * radius + 3;
   Fpaint3(px,py,ww,hh,0);

   draw_mousecircle(Mxposn,Myposn,radius,0,0);                                   //  show mouse selection circle

   CEF->Fmods++;
   CEF->Fsaved = 0;

   return;
}


//  apply edge blend width to all area edges

void areablend_blendwidth()
{   
   using namespace areablend_names;
   
   int      px, py, ii, dist;
   float    *pix1, *pix3, *pix9;
   float    f1, f9, blend;
   
   if (! CEF || sa_stat < sa_stat_fini || ! E9pxm) return;

   sa_edgecalc();                                                                //  calc. area pixel edge distances

   Funcbusy(+1,1);                                                               //  24.40

   for (py = sa_miny; py < sa_maxy; py++)
   for (px = sa_minx; px < sa_maxx; px++)
   {
      ii = py * Eww + px;
      dist = sa_pixmap[ii];
      if (! dist) continue;                                                      //  outside area
      blend = 1.0 * (blendwidth - dist) / blendwidth;                            //  edge pixel --> blend = 1.0
      if (blend <= 0) continue;                                                  //  edge distance > blend width

      f9 = blend;
      f1 = 1.0 - f9;
      pix1 = PXMpix(E1pxm,px,py);                                                //  input pixel
      pix3 = PXMpix(E3pxm,px,py);                                                //  output pixel
      pix9 = PXMpix(E9pxm,px,py);                                                //  blend pixel
      pix3[0] = f1 * pix1[0] + f9 * pix9[0];
      pix3[1] = f1 * pix1[1] + f9 * pix9[1];
      pix3[2] = f1 * pix1[2] + f9 * pix9[2];
   }

   Funcbusy(-1);                                                                 //  24.40

   CEF->Fmods++;
   CEF->Fsaved = 0;

   Fpaint2();                                                                    //  update window

   return;
}


//  apply edge blur width to all area edges

void areablend_blurwidth()                                                       //  24.60
{   
   using namespace areablend_names;
   
   void * areablend_wthread1(void *);
   void * areablend_wthread2(void *);
   
   int      cc;
   
   if (! CEF || sa_stat < sa_stat_fini || ! E9pxm) return;
   
   sa_edgecalc();                                                                //  calc. area pixel edge distances
   
   Funcbusy(+1,1);

   cc = Eww * Ehh;
   distmap = (uint8 *) zmalloc(cc,"areablend");                                  //  initz. max. pixel distance
   memset(distmap,255,cc);
  
   Funcbusy(-1);
   
   do_wthreads(areablend_wthread1,NSMP);                                         //  do worker threads
   do_wthreads(areablend_wthread2,NSMP);
   
   zfree(distmap);

   CEF->Fmods++;
   CEF->Fsaved = 0;

   Fpaint2();                                                                    //  update window

   return;
}


//  worker thread 1 - map all pixels within blur width of area edge
//  threads collide on updates to distmap, but seems to work fine

void * areablend_wthread1(void *arg)
{
   using namespace areablend_names;
   
   int      index = *((int *) arg);
   int      px, py, qx, qy, ii;
   int      dist, bw;
   float    rad;

   bw = blurwidth;

   for (py = sa_miny + index; py < sa_maxy; py += NSMP)                          //  loop all area edge pixels px/py
   for (px = sa_minx; px < sa_maxx; px++)
   {
      ii = py * Eww + px;
      dist = sa_pixmap[ii];
      if (dist != 1) continue;                                                   //  not edge pixel
      
      for (qy = py-bw; qy <= py+bw; qy++)                                        //  loop all pixels qx/qy
      for (qx = px-bw; qx <= px+bw; qx++)                                        //    within blur width of px/py
      {
         if (qy < 0 || qy > Ehh-1) continue;                                     //  skip if off image
         if (qx < 0 || qx > Eww-1) continue;
         
         rad = sqrtf((qx-px) * (qx-px) + (qy-py) * (qy-py));                     //  qx/qy distance from px/py
         
         ii = qy * Eww + qx;
         dist = distmap[ii];
         if (rad < dist) distmap[ii] = rad + 0.5;                                //  save shortest distance to edge
      }
   }
   
   return 0;
}


//  worker thread 2 - compute blur for all mapped pixels

void * areablend_wthread2(void *arg)
{
   using namespace areablend_names;
   
   int      index = *((int *) arg);
   int      px, py, qx, qy, ii;
   int      dist, bw, npix;
   float    F1, F2;
   float    R, G, B;
   float    *pix1, *pix3;

   bw = blurwidth;

   for (py = index; py < Ehh; py += NSMP)                                        //  loop all pixels px/py 
   for (px = 0; px < Eww; px++)                                                  //    < blur width from edge
   {
      ii = py * Eww + px;
      dist = distmap[ii];
      if (dist > bw) continue;                                                   //  not within blur width

      npix = 0;
      R = G = B = 0;
      
      for (qy = py-bw; qy <= py+bw; qy++)                                        //  loop all pixels qx/qy
      for (qx = px-bw; qx <= px+bw; qx++)                                        //    within blur width of px/py
      {
         if (qy < 0 || qy > Ehh-1) continue;                                     //  skip if off image
         if (qx < 0 || qx > Eww-1) continue;

         pix1 = PXMpix(E1pxm,qx,qy);                                             //  sum RGB for all pixels
         R += pix1[0];
         G += pix1[1];
         B += pix1[2];
         npix++;
      }
   
      R = R / npix;                                                              //  average RGB within blur width
      G = G / npix;
      B = B / npix;
   
      pix1 = PXMpix(E1pxm,px,py);                                                //  input pixel
      pix3 = PXMpix(E3pxm,px,py);                                                //  output pixel to blur
      
      F1 = 1.0 * dist / bw;                                                      //  edge distance, 0 .. 1 = blur width
      F2 = 1 - F1;
      pix3[0] = F1 * pix1[0] + F2 * R;                                           //  blur = max at edge, 0 at blur width
      pix3[1] = F1 * pix1[1] + F2 * G;
      pix3[2] = F1 * pix1[2] + F2 * B;
   }
   
   return 0;
}


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

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

void m_select_show(GtkWidget *, ch *menu)
{
   F1_help_topic = "show/hide area";
   Plog(1,"m_select_show \n");
   if (FGM != 'F') return;
   sa_show(1,0);                                                                 //  show area
   return;
}


void m_select_hide(GtkWidget *, ch *menu)
{
   F1_help_topic = "show/hide area";
   Plog(1,"m_select_hide \n");
   if (FGM != 'F') return;
   sa_show(0,0);
   return;
}


void m_select_enable(GtkWidget *, ch *menu)
{
   F1_help_topic = "enable/disable area";
   Plog(1,"m_select_enable \n");
   if (FGM != 'F') return;
   sa_enable();
   return;
}


void m_select_disable(GtkWidget *, ch *menu)
{
   F1_help_topic = "enable/disable area";
   Plog(1,"m_select_disable \n");
   if (FGM != 'F') return;
   sa_disable();
   return;
}


void m_select_invert(GtkWidget *, ch *menu)
{
   F1_help_topic = "invert area";
   Plog(1,"m_select_invert \n");
   if (FGM != 'F') return;
   sa_invert();
   return;
}


void m_select_clear(GtkWidget *, ch *menu)                                       //  delete the area
{
   F1_help_topic = "clear area";
   Plog(1,"m_select_clear \n");
   if (FGM != 'F') return;
   sa_clear();
   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, cairo_t *cr)
{
   int      px, py, ii, kk;
   int      crflag = 0;
   
   if (sa_stat == sa_stat_none) return;                                          //  no area
   if (! sa_validate()) return;                                                  //  invalid for current image
   
   Fshowarea = flag;                                                             //  flag for Fpaint*()

   if (! flag) {
      Fpaint2();                                                                 //  erase area outline
      return;
   }
   
   if (! cr) {
      cr = draw_context_create(gdkwin,draw_context);
      crflag = 1;
   }
   
   Funcbusy(+1);                                                                 //  24.40

   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] < 250)                           //  transparency edge
         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,cr);
   }

   Funcbusy(-1);                                                                 //  24.40

   if (crflag) draw_context_destroy(draw_context); 
   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, cairo_t *cr)
{
   int      px, py, px2, py2, ii, kk;
   int      crflag = 0;

   if (! Fshowarea) return;
   if (sa_stat == sa_stat_none) return;                                          //  no area
   if (! sa_validate()) return;                                                  //  invalid for current image

   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;

   if (! cr) {
      cr = draw_context_create(gdkwin,draw_context);
      crflag = 1;
   }
   
   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] < 250)                           //  transparency edge 
         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,cr);
   }

   if (crflag) draw_context_destroy(draw_context); 
   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 == sa_stat_none) return 0;                                        //  no area

   if (! curr_file || (CEF && CEF->Fpreview)) {                                  //  no image file or edit preview image
      sa_clear();
      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_clear();
   return 0;
}


//  enable select area that was disabled

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

   sa_stat = sa_stat_fini;                                                       //  finished/enabled
   areanumber++;                                                                 //  next sequential number

   sa_show(1,0);                                                                 //  show area
   return;
}


//  disable select area

void sa_disable()
{
   if (sa_stat == sa_stat_none) return;                                          //  no area
   if (sa_stat < sa_stat_fini) {
      zmessageACK(Mwin,"area is not finished");
      return;
   }

   sa_stat = sa_stat_disab;                                                      //  finished/disabled
   sa_show(0,0);                                                                 //  hide area
   return;
}


//  invert a selected area

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

   if (sa_stat == sa_stat_none) return;                                          //  no area
   if (! sa_validate()) return;                                                  //  invalid for current image
   if (sa_stat < sa_stat_fini) {
      zmessageACK(Mwin,"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;

   Funcbusy(+1);                                                                 //  24.40

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

   Funcbusy(-1);                                                                 //  24.40

   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;

   sa_stat = sa_stat_fini;                                                       //  if disabled, now finished
   sa_Npixel = npix;                                                             //  new select area pixel count
   sa_edgecalc_done = 0;                                                         //  edge calculation missing

   sa_show(1,0);                                                                 //  show area
   return;
}


//  clear current area (delete the area)

void sa_clear()
{
   sa_stat = sa_Npixel = sa_edgecalc_done = 0;
   sa_currseq = sa_Ncurrseq = 0;
   sa_fww = sa_fhh = 0;
   if (sa_pixmap) zfree(sa_pixmap);
   if (sa_pixmap2) zfree(sa_pixmap2);
   if (sa_stack) zfree(sa_stack);
   if (sa_stackdirec) zfree(sa_stackdirec);
   if (sa_pixselc) zfree(sa_pixselc);
   sa_pixmap = 0;
   sa_pixmap2 = 0;
   sa_stack = 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;

   void * edgecalc_thread(void *arg);

   int      ii, nn, cc, px, py;
   zdialog  *zdp;
   
   if (Nfuncbusy) zappcrash("sa_edgecalc() Funcbusy conflict");                  //  24.60

   if (sa_stat == sa_stat_none) return;                                          //  area gone?
   if (sa_edgecalc_done) return;                                                 //  done already
   if (sa_stat != sa_stat_fini) return;                                          //  area must be finished/enabled

   if (sa_edgecalc_busy) return;                                                 //  stop re-entry 
   sa_edgecalc_busy = 1;
   
   zdp = zmessage_post(Mwin,"mouse",3,"edge distance calculation");

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

   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,"select_area");                             //  allocate memory
   sa_edgepy = (uint16 *) zmalloc(cc,"select_area");

   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
         if (PXBpix(Fpxb,px-1,py)[3] < 250) continue;
         if (PXBpix(Fpxb,px,  py)[3] < 250) continue;
         if (PXBpix(Fpxb,px+1,py)[3] < 250) continue;
         if (PXBpix(Fpxb,px-1,py-1)[3] < 250) continue;
         if (PXBpix(Fpxb,px,  py-1)[3] < 250) continue;
         if (PXBpix(Fpxb,px+1,py-1)[3] < 250) continue;
         if (PXBpix(Fpxb,px-1,py+1)[3] < 250) continue;
         if (PXBpix(Fpxb,px,  py+1)[3] < 250) continue;
         if (PXBpix(Fpxb,px+1,py+1)[3] < 250) continue;
      }

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

   sa_Nedge = nn;

   progress_setgoal(sa_Npixel);                                                  //  progress counter goal

   Funcbusy(+1,1);                                                               //  enable escape key kill 
   Fescape = 0;
   
   start_detached_thread(edgecalc_thread,0);
   while (Nfuncbusy) zmainsleep(0.1);
   
   progress_setgoal(0);
   
   zdialog_free(zdp);

   zfree(sa_edgedist);                                                           //  free memory
   zfree(sa_edgepx);
   zfree(sa_edgepy);
   
   sa_edgecalc_busy = 0;
   Fescape = 0;
   return;
}


void * edgecalc_thread(void *arg)                                                //  edgecalc thread function
{
   using namespace sa_edgecalc_names;
   
   void * edgecalc_wthread(void *arg);
   
   do_wthreads(edgecalc_wthread,NSMP);                                           //  do worker threads
   
   if (! Fescape)                                                                //  not canceled 
   {
      for (int 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_edgecalc_done = 1;                                                      //  edge calc. done
   }
   
   Funcbusy(-1);                                                                 //  must be done here

   return 0;
}


void * edgecalc_wthread(void *arg)                                               //  edgecalc worker thread function
{
   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 += NSMP)
      {
         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 (Fescape) return 0;                                                  //  killed
      }

      for (py = midy-rad; py <= midy+rad; py += 2 * rad)
      for (px = midx-rad+index; px <= midx+rad; px += NSMP)
      {
         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 (Fescape) return 0;                                                  //  killed
      }
   }

   return 0;
}


//  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) {
            Plog(0,"edgecalc() error %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;
         sa_edgedist[ii] = dist2;                                                //  save
         progress_addvalue(1);                                                   //  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) {
            Plog(0,"edgecalc() error %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;
         progress_addvalue(1);
      }
   }

   return;
}


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

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

   if (sa_stat != sa_stat_fini) {
      zmessageACK(Mwin,"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,0);                                                                 //  show area
   sa_edgecalc_done = 0;                                                         //  invalidate prior edge calc.

   Fpaint2();
   return;
}


//  finish select area after edits on entire image but only select area is wanted
//  pixels outside select area are copied from E1 to E3

void sa_postfix() 
{
   int         ii, px, py, dist, pcc;
   float       *pix1, *pix3;
   
   if (sa_stat != sa_stat_fini) return;
   
   pcc = E3pxm->nc * sizeof(float);
   
   for (py = 0; py < Ehh; py++)
   for (px = 0; px < Eww; px++)
   {
      if (Fescape) break;

      pix1 = PXMpix(E1pxm,px,py);                                                //  E1 input pixel
      pix3 = PXMpix(E3pxm,px,py);                                                //  E3 output pixel

      ii = py * Eww + px;
      dist = sa_pixmap[ii];                                                      //  pixel distance from area edge

      if (dist == 0) {                                                           //  pixel is outside area
         memcpy(pix3,pix1,pcc);                                                  //  use E1 pixel
         continue;
      }
   }

   return;
}


/********************************************************************************
   select area copy/paste and load/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;                                                      //  rescaled/rotated image
   int      sacpR_ww, sacpR_hh;                                                  //  rescaled/rotated dimensions

   float    sacp_scale;                                                          //  scale, 1.0 = original size
   float    sacp_angle;                                                          //  angle of rotation, -180 to +180
   int      sacp_fliphorz;                                                       //  flip horizontal                       24.20
   int      sacp_flipvert;                                                       //  flip vertical
   int      sacp_orgx, sacp_orgy;                                                //  origin in target image
   int      sacp_porg = 0;                                                       //  pasted area is present
   int      sacp_porgx, sacp_porgy;                                              //  pasted area origin in image
   int      sacp_pww, sacp_phh;                                                  //  pasted area dimensions

   editfunc    EFpaste;
}


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

void m_select_copy(GtkWidget *, ch *)
{
   Plog(1,"m_select_copy \n");
   select_copysave(0,"copy");
   return;
}

void m_select_save(GtkWidget *, ch *)
{
   Plog(1,"m_select_save \n");
   select_copysave(0,"save");
   return;
}

void select_copysave(GtkWidget *, ch *menu)
{
   using namespace sa_diskfile;

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

   if (strmatch(menu,"copy"))
      F1_help_topic = "copy/paste area";
   else if (strmatch(menu,"save"))
      F1_help_topic = "load/save area";
   else Plog(0,"F1 topic error \n");     

   if (FGM != 'F') return;

   if (sa_stat == sa_stat_none) return;                                          //  no selected area
   if (sa_stat < sa_stat_fini) {
      zmessageACK(Mwin,"area is not finished");
      return;
   }

   if (! E0pxm) {                                                                //  get poss. 16-bit image
      E0pxm = PXM_load(curr_file,1);
      if (! E0pxm) return;
   }

   ww = E0pxm->ww;
   nc = E0pxm->nc;
   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
   sacp_hh = sa_maxy - sa_miny;
   sacp_pxm = PXM_make(sacp_ww,sacp_hh,4);                                       //  alpha channel
   
   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 edge dist.
      if (dist == 0) pix2[3] = 0;                                                //  outside pixel, transparent
      else pix2[3] = 255;                                                        //  edge or inside, opaque
   }

   if (strmatch(menu,"copy")) {
      snprintf(filename,200,"%s/copied_area.png",saved_areas_folder);            //  save to default PNG file
      PXM_PNG_save(sacp_pxm,filename,16);
      return;
   }

   pp = zgetfile("save area as a PNG file",MWIN,"save",saved_areas_folder);      //  get file name from user
   if (! pp) return;
   file = zstrdup(pp,"select_area",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_load(GtkWidget *, ch *menu)
{
   using namespace sa_diskfile;

   void  select_paste(GtkWidget *, ch *);

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

   F1_help_topic = "load/save area";

   Plog(1,"m_select_load \n");

   if (FGM != 'F') return;

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

   file = select_files1(saved_areas_folder);                                     //  use thumbnail selection
   if (! file) return;

   pxmtemp = PXM_load(file,1);                                                   //  load image file
   zfree(file);
   if (! pxmtemp) 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

   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
   }
   
   PXM_free(pxmtemp);
   select_paste(0,"paste area");                                                 //  interactive move/rescale 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 *, ch *menu)
{
   using namespace sa_diskfile;

   void  select_paste(GtkWidget *, ch *);

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

   F1_help_topic = "copy/paste area";

   Plog(1,"m_select_paste \n");

   if (FGM != 'F') return;

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

   snprintf(filename,200,"%s/copied_area.png",saved_areas_folder);

   pxmtemp = PXM_load(filename,1);                                               //  load image file
   if (! pxmtemp) return;

   nc = pxmtemp->nc;
   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

   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
   }
   
   PXM_free(pxmtemp);
   select_paste(0,"paste area");                                                 //  interactive move/rescale 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 *, ch *menu)
{
   using namespace sa_diskfile;

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

   if (! sacp_pxm) return;                                                       //  nothing to paste

   sa_clear();                                                                   //  clear area if present

   EFpaste.menufunc = select_paste;
   EFpaste.menuname = "paste area";

   if (! edit_setup(EFpaste)) return;                                            //  setup edit for paste

   sacp_scale = 1.0;                                                             //  size = 1x
   sacp_angle = 0;                                                               //  angle = 0
   sacp_fliphorz = sacp_flipvert = 0;                                            //  not flipped                           24.20
   sacp_porg = 0;                                                                //  no image paste location yet

   PXM_free(sacpR_pxm);                                                          //  free prior if any

   if (sacp_ww > E0pxm->ww)                                                      //  if paste image > current image
      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
      edit_cancel(0);                                                            //  cancel edit, restore image
      PXM_free(sacpR_pxm);                                                       //  free memory
      return;
   }
   
/***
          __________________________________________________
         |               Paste Image                        |
         |                                                  |
         | position with mouse click/drag                   |
         |                                                  |
         | Rescale [-10%] [-1%] [-.1%] [+.1%] [+1%] [+10%]  |
         | Angle [-10°] [-1°] [-.1°] [+.1°] [+1°] [+10°]    |
         | Flip [horizontal]   [vertical]                   |
         |                                                  |
         |                                         [OK] [X] |
         |__________________________________________________|

***/

   CEF->zd = zdialog_new("Paste Image",Mwin,"OK"," X ",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","Rescale","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","Angle","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","hbflip","dialog",0,"space=3");             //  24.20
   zdialog_add_widget(CEF->zd,"label","labflip","hbflip","Flip","space=3");
   zdialog_add_widget(CEF->zd,"button","horz","hbflip","horizontal","space=5");
   zdialog_add_widget(CEF->zd,"button","vert","hbflip","vertical","space=10");

   zdialog_run(CEF->zd,select_paste_dialog_event,"save");                        //  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, ch *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 (zd->zstat)                                                                //  dialog completed
   {
      freeMouse();                                                               //  disconnect mouse

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

      sa_show(1,0);                                                              //  show area
      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 (zstrstr(event,"%") || zstrstr(event,"°") 
         || zstrstr(event,"horz") || zstrstr(event,"vert"))                      //  24.20
   {
      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;
      
      if (strmatch(event,"horz")) sacp_fliphorz = 1 - sacp_fliphorz;             //  flip status                           24.20
      if (strmatch(event,"vert")) sacp_flipvert = 1 - sacp_flipvert;

      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);                                    //  rescaled area image
      sacpR_pxm = PXM_rotate(pxm_temp,sacp_angle);                               //  rotated area image
      PXM_free(pxm_temp);
      
      if (sacp_fliphorz) {
         pxm_temp = PXM_upright(sacpR_pxm,0,1);                                  //  flip horizontal                       24.20
         PXM_free(sacpR_pxm);
         sacpR_pxm = pxm_temp;
      } 

      if (sacp_flipvert) {
         pxm_temp = PXM_upright(sacpR_pxm,0,2);                                  //  flip vertical                         24.20
         PXM_free(sacpR_pxm);
         sacpR_pxm = pxm_temp;
      } 

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

      select_paste_image();                                                      //  copy onto target image
      sa_edgecalc_done = 0;                                                      //  edge distance calculation needed
   }

   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

   sa_edgecalc_done = 0;                                                         //  edge distance calculation needed
   return;
}


//  copy select area into edit image, starting at sacp_orgx/y
//  called when area moved, rescaled, 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;

   nc = E1pxm->nc;
   pcc = nc * sizeof(float);

   if (sacp_porg)                                                                //  prior area overlap rectangle
   {
      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);
      }

      Fpaint3(sacp_porgx,sacp_porgy,sacp_pww,sacp_phh,0);                        //  update window
   }
   
   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
         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 (nc > 3) {                                                              //  combine alpha channels 
         pix3[3] = 255 - (255 - pix2[3]) * (255 - pix3[3]) / 255;
         if (pix3[3] > 255) pix3[3] = 255;
         if (pix3[3] < 0) pix3[3] = 0;
      }
   }

   Fpaint3(sacp_orgx,sacp_orgy,sacpR_ww,sacpR_hh,0);                             //  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();
   if (! sa_Npixel) return;
   
   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      ii, px1, py1, px2, py2;
   float    *pix2, opac;
   
   sa_clear();                                                                   //  clear old area
   sa_pixmap_create();                                                           //  allocate pixel maps

   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 = sa_stat_edit;
   sa_mode = sa_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,0);                                                                 //  hide area
   return;
}


//  make area brightness and edge blend adjustments.

void select_paste_adjust()
{
   using namespace sa_diskfile;

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

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

      RGBfix(red,green,blue);

      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) {                                                           //  pixel outside area
         red = pix1[0];
         green = pix1[1];
         blue = pix1[2];
      }

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

   return;
}


