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

   Fotocx - edit photos and manage collections

   Copyright 2007-2026 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

   zdialog  *zd;

   if (menu) F1_help_topic = TX("SELECT AREA");

   printf("m_select_area \n");

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

   if (FGM != 'F') return;

   if (zd_sela) return;                                                                //  already active

   if (CEF && CEF->Farea != 2) {                                                       //  active edit function
      zmessageACK(Mwin,TX("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(TX("Select Area for Edits"),Mwin,null);
   zd = zd_sela;

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

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

   zdialog_add_widget(zd,"hbox","hbdraw","dialog");
   zdialog_add_widget(zd,"check","ckdraw","hbdraw",TX("freehand draw"),"space=3");
   zdialog_add_widget(zd,"check","ckfollow","hbdraw",TX("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",TX("select area within mouse"),"space=3");

   zdialog_add_widget(zd,"hbox","hbm2","dialog");
   zdialog_add_widget(zd,"check","ckonecolor","hbm2",TX("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",TX("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",TX("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",TX("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",TX("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",TX("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",TX("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",TX("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",TX("Finish"),"space=2");
   zdialog_add_widget(zd,"button","invert","hbb2",TX("Invert"),"space=2");
   zdialog_add_widget(zd,"button","show","hbb2",TX("Show"),"space=2");
   zdialog_add_widget(zd,"button","hide","hbb2",TX("Hide"),"space=2");
   zdialog_add_widget(zd,"button","clear","hbb2",TX("Clear"),"space=2");
   zdialog_add_widget(zd,"button","done","hbb2","X","space=2");

   zdialog_add_ttip(zd,"ckrect",TX("drag mouse to select rectangular area"));
   zdialog_add_ttip(zd,"ckelips",TX("drag mouse to select circular or elliptical area"));
   zdialog_add_ttip(zd,"ckdraw",TX("drag mouse to outline an area"));
   zdialog_add_ttip(zd,"ckfollow",TX("drag mouse along an edge to follow the edge"));
   zdialog_add_ttip(zd,"labcolor",TX("select line color"));
   zdialog_add_ttip(zd,"labmr",TX("size of mouse selection circle"));
   zdialog_add_ttip(zd,"labmatch",TX("select by color, required match level"));
   zdialog_add_ttip(zd,"labrange",TX("select by color, search range"));
   zdialog_add_ttip(zd,"ckmouse",TX("select area within mouse circle"));
   zdialog_add_ttip(zd,"ckonecolor",TX("left click on image for selected color"));
   zdialog_add_ttip(zd,"ckdselcolor",TX("right click on image for deselected color"));
   zdialog_add_ttip(zd,"ckallcolors",TX("select surrounding areas matching colors in mouse"));
   zdialog_add_ttip(zd,"labcreep",TX("move area boundary in/out in 1-pixel steps"));
   zdialog_add_ttip(zd,"finish",TX("map selected areas and verify"));
   zdialog_add_ttip(zd,"invert",TX("invert area"));
   zdialog_add_ttip(zd,"show",TX("show area outlines"));
   zdialog_add_ttip(zd,"hide",TX("hide area outlines"));
   zdialog_add_ttip(zd,"clear",TX("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
      printf("*** 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(Fgdkwin,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,TX("exceed %d edits"),sa_maxseq);                               //  cannot continue
      return;
   }

   cr = draw_context_create(Fgdkwin,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(Fgdkwin,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,TX("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(Fgdkwin,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(Fgdkwin,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(Fgdkwin,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, int dselect);
   void sa_mouse_selectall(int select);

   int         newdrag, dselect = 0;
   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 || RMclick)                                                             //  left or right mouse click
   {
      if (RMclick) dselect = 1;                                                        //  deselect color is active              25.1

      LMclick = RMclick = 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,dselect);                  //  set colors to match or not match      25.1
      if (sa_mode == sa_mode_allcolors) sa_mouse_selectall(1);                         //  select matching colors in or 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,dselect);                                             //  25.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,dselect);
         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 (match)
//  Right click - pixel at mouse position --> color to deselect (unmmatch)
//  Left/right drag - select/deselect matching pixels within mouse radius.

void sa_mouse_select1(int mode, int select, int dselect)                               //  25.1
{
   int      mrad, mrad2;
   int      rad2, ii, jj;
   int      px, py, rx, ry;
   int      xlo, xhi, ylo, yhi, newpix;
   int      Fdselect;                                                                  //  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 or right 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 select/match color button
      if (dselect) zdialog_stuff(zd_sela,"dselcolor",colorbutt);                       //   or deselect/unmatch color button     25.1
      else zdialog_stuff(zd_sela,"onecolor",colorbutt);

      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) {
      printf("*** color button error: select color");
      return;
   }

   zdialog_fetch(zd_sela,"ckdselcolor",Fdselect);                                      //  deselect option engaged

   if (Fdselect) {
      zdialog_fetch(zd_sela,"dselcolor",colorbutt,16);                                 //  get deselect color button color
      ii = sscanf(colorbutt,"%f|%f|%f",&Rdsel,&Gdsel,&Bdsel);
      if (ii != 3) {
         printf("*** 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
         if (! Fdselect) {                                                             //  select only
            match2 = RGBmatch(Rsel,Gsel,Bsel,pix1[0],pix1[1],pix1[2]);                 //  pixel match to select color
            if (match2 < match1) continue;                                             //  not a good enough match
         }
         else {                                                                        //  select and deselect                   25.1
            match2 = RGBmatch(Rsel,Gsel,Bsel,pix1[0],pix1[1],pix1[2]);                 //  pixel match to select color
            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;
   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
   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;

      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.05 * Ntab;                                                               //  exclude minority pixels
   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(Fgdkwin,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 =
            TX("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;

   printf("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(TX("Finish Area"),Mwin,                                      //  dialog for user to click inside
                           TX("Help"),TX("Keep"),TX("Undo"),null);
   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
   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,TX("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 = TX(
             "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;

   printf("sa_finish_mappix() \n");

   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
   }

   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;

   printf("sa_finish_color \n");

   cr = draw_context_create(Fgdkwin,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
   }

   draw_context_destroy(draw_context);
   return;
}


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

void sa_finish_auto()
{
   printf("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,TX("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;

   printf("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;

   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
   }

   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;
   
   printf("sa_unfinish \n");

   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 = TX("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 = TX("FIND AREA GAP");

   printf("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(TX("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
      return 1;
   }

   if (! zd->zstat) return 1;                                                          //  wait for completion

   if (Fbusy()) Fescape = 1;                                                           //  26.5

   zdialog_free(zd);
   freeMouse();                                                                        //  disconnect mouse
   return 1;
}


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

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

   if (Fbusy()) return;                                                                //  prevent re-entry

   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;

   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) {
      panelmessage(1,3,TX("cannot find area outline"));                                //  no edge pixels was found              25.1
      sa_show(1,0);                                                                    //  show area
      return;
   }

   m_zoom(0,"fit");                                                                    //  unzoom, fit window                    26.5

   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(Fgdkwin,draw_context);

   Fbusy(+1);

   delay = 0.001 + 2.0 / sa_Npixel;                                                    //  speed inverse of outline size         25.0

   sa_stack[0] = pix0;                                                                 //  put initial edge pixel into stack
   sa_Nstack = 1;                                                                      //  stack count

   while (sa_Nstack)                                                                   //  26.5
   {
      if (Fescape) break;                                                              //  26.0

      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(delay);                                                               //  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
         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;
         }
      }
   }
   
   Fbusy(-1);
   Fescape = 0;
   zfree(sa_stack);
   zfree(pixmark);
   sa_stack = 0;
   pixmark = 0;
   draw_context_destroy(draw_context);
   sa_show(1,0);
}


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

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

namespace selectblend_names
{
   editfunc    EFselectblend;                                                          //  edit function data
   int         mode = 0;                                                               //  1/2 = area/edge blend
   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 selectblend_names;

   int   selectblend_dialog_event(zdialog* zd, ch *event);
   void  selectblend_mousefunc();

   ch       *helptext = TX("Left-drag mouse within the area \n"
                           "to gradually undo area edits. \n"
                           "Right-drag to restore edits. \n"
                           "Drag along area edge to blend edge.");
   ch       text[40];

   F1_help_topic = TX("AREA BLEND");

   printf("m_select_blend \n");

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

   if (! E0pxm) {
      zmessageACK(Mwin,TX("no current edit image"));
      return;
   }

   if (URS_pos < 1) {
      zmessageACK(Mwin,TX("no prior edit image"));
      return;
   }

   E9pxm = load_undo(URS_pos - 1);                                                     //  image from prior edit
   if (! E9pxm) {
      printf("*** no prior edit image \n");                                            //  should not happen
      return;
   }

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

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

   EFselectblend.menufunc = m_select_blend;
   EFselectblend.menuname = "Area Blend";
   EFselectblend.mousefunc = selectblend_mousefunc;
   EFselectblend.Farea = 2;                                                            //  use area

   if (! edit_setup(EFselectblend)) return;                                            //  initz. edit

/***
             _________________________________________
            |              Area Blend                 |
            |                                         |
            | [x] 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
            |                                         |
            | [x] Area Edge Blend                     |
            | blend edges: blend width [___]          |
            | blur edges: blur width [___]            |
            |                                         |
            |                                [OK] [X] |
            |_________________________________________|

***/

   zdialog *zd = zdialog_new(TX("Area Blend"),Mwin,"OK","X",null);
   CEF->zd = zd;

   zdialog_add_widget(zd,"check","areablend","dialog",TX("Area Blend"),"space=3");                                                // 25.0
   zdialog_add_widget(zd,"hbox","hbtip","dialog",0,"space=1");
   zdialog_add_widget(zd,"label","labhelp","hbtip",helptext,"space=3");
   zdialog_add_widget(zd,"hbox","hbstep","dialog",0,"space=3");
   zdialog_add_widget(zd,"label","labstep","hbstep",TX("use prior edit step:"),"space=3");
   zdialog_add_widget(zd,"combo","editstep","hbstep",0,"space=3");
   zdialog_add_widget(zd,"hbox","hbrad","dialog",0,"space=1");
   zdialog_add_widget(zd,"label","labrad","hbrad",TX("mouse radius:"),"space=3");
   zdialog_add_widget(zd,"zspin","radius","hbrad","2|999|1|100","space=3");
   zdialog_add_widget(zd,"hbox","hbblend","dialog",0,"space=1");
   zdialog_add_widget(zd,"label","labcenter","hbblend",TX("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",TX("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=6");

   zdialog_add_widget(zd,"check","edgeblend","dialog",TX("Area Edge Blend"),"space=3");                                           // 25.0
   zdialog_add_widget(zd,"hbox","hbblendall","dialog",0,"space=1");
   zdialog_add_widget(zd,"label","labrad","hbblendall",TX("blend edges: blend width:"),"space=3");
   zdialog_add_widget(zd,"zspin","blendwidth","hbblendall","0|999|1|0","space=3");
   zdialog_add_widget(zd,"hbox","hbblurall","dialog",0,"space=1");
   zdialog_add_widget(zd,"label","labrad","hbblurall",TX("blur edges: blur width:"),"space=3");
   zdialog_add_widget(zd,"zspin","blurwidth","hbblurall","0|100|1|0","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
   }

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

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


//  selectblend dialog event and completion function

int selectblend_dialog_event(zdialog *zd, ch *event)                                   //  selectblend dialog event function
{
   using namespace selectblend_names;

   void selectblend_mousefunc();
   void selectblend_blendwidth();
   void selectblend_blurwidth();

   ch       text[40];
   int      nn;

   if (sa_stat < sa_stat_fini) {
      zmessageACK(Mwin,TX("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 (strstr("areablend editstep radius Bcent Bedge",event)) {                        //  toggle mode checkboxes depending      25.0
      mode = 1;                                                                        //    on selected widget edits
      zdialog_stuff(zd,"areablend",1);
      zdialog_stuff(zd,"edgeblend",0);
   }

   if (strstr("edgeblend blendwidth blurwidth",event)) {
      mode = 2;
      zdialog_stuff(zd,"areablend",0);
      zdialog_stuff(zd,"edgeblend",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,TX("Image dimensions have changed, \n"
                             "this prior image cannot be used"));
         PXM_free(E9pxm);                                                              //  free memory
         E9pxm = 0;
         return 1;
      }
   }

   zdialog_fetch(zd,"radius",radius);                                                  //  mouse radius
   zdialog_fetch(zd,"Bcent",Bcent);                                                    //  mouse blend rate center
   zdialog_fetch(zd,"Bedge",Bedge);                                                    //  mouse blend rate edge
   zdialog_fetch(zd,"blendwidth",blendwidth);                                          //  edge blend distance
   zdialog_fetch(zd,"blurwidth",blurwidth);                                            //  edge blur distance

   if (mode == 1)
      takeMouse(selectblend_mousefunc,0);                                              //  paint blend                           25.0

   if (mode == 2) {
      freeMouse();                                                                     //  process area edges                    25.0
      if (blendwidth) selectblend_blendwidth();
      if (blurwidth) selectblend_blurwidth();
      CEF->Fmods++;                                                                    //  25.0
      CEF->Fsaved = 0;
   }

   return 1;
}


//  mouse function - called for mouse events inside image

void selectblend_mousefunc()
{
   using namespace selectblend_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 selectblend_blendwidth()
{
   using namespace selectblend_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

   Fbusy(+1);

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

   Fbusy(-1);

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

   Fpaint2();                                                                          //  update window

   return;
}


//  apply edge blur width to all area edges

void selectblend_blurwidth()
{
   using namespace selectblend_names;

   void * selectblend_wthread1(void *);
   void * selectblend_wthread2(void *);

   int      cc;

   if (! CEF || sa_stat < sa_stat_fini || ! E9pxm) return;

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

   Fbusy(+1);

   cc = Eww * Ehh;
   distmap = (uint8 *) zmalloc(cc,"selectblend");                                      //  initz. max. pixel distance
   memset(distmap,255,cc);

   do_wthreads(selectblend_wthread1,NSMP);                                             //  do worker threads
   do_wthreads(selectblend_wthread2,NSMP);

   zfree(distmap);

   Fbusy(-1);

   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 * selectblend_wthread1(void *arg)
{
   using namespace selectblend_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 * selectblend_wthread2(void *arg)
{
   using namespace selectblend_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 = TX("SHOW/HIDE AREA");
   printf("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 = TX("SHOW/HIDE AREA");
   printf("m_select_hide \n");
   if (FGM != 'F') return;
   sa_show(0,0);
   return;
}


void m_select_enable(GtkWidget *, ch *menu)
{
   F1_help_topic = TX("ENABLE/DISABLE AREA");
   printf("m_select_enable \n");
   if (FGM != 'F') return;
   sa_enable();
   return;
}


void m_select_disable(GtkWidget *, ch *menu)
{
   F1_help_topic = TX("ENABLE/DISABLE AREA");
   printf("m_select_disable \n");
   if (FGM != 'F') return;
   sa_disable();
   return;
}


void m_select_invert(GtkWidget *, ch *menu)
{
   F1_help_topic = TX("INVERT AREA");
   printf("m_select_invert \n");
   if (FGM != 'F') return;
   sa_invert();
   return;
}


void m_select_clear(GtkWidget *, ch *menu)                                             //  delete the area
{
   F1_help_topic = TX("CLEAR AREA");
   printf("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(Fgdkwin,draw_context);
      crflag = 1;
   }
   
   for (ii = 0; ii < Fpxb->ww * Fpxb->hh; ii++)
   {
      if (! sa_pixmap[ii]) continue;

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

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

      if (Fpxb->nc > 3 && PXBpix(Fpxb,px,py)[3] < 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;
}


//  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(Fgdkwin,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,TX("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,TX("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,TX("area is not finished"));
      return;
   }

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

   npix = 0;

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

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

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

      if (px >= sa_maxx) sa_maxx = px + 1;
      if (px < sa_minx) sa_minx = px;
      if (py >= sa_maxy) sa_maxy = py + 1;
      if (py < sa_miny) sa_miny = py;
   }

   sa_minx -= 10;                                                                      //  add margins where possible
   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()
{
   if (Fbusy()) return;                                                                //  26.0
   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;
   int         edgecalc_threadbusy = 0;
}

void sa_edgecalc()
{
   using namespace sa_edgecalc_names;

   void * edgecalc_thread(void *arg);

   int      ii, nn, cc, px, py;

   if (edgecalc_threadbusy) return;                                                    //  should not happen                     26.0

   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;

   panelmessage(0,0,TX("edge distance calculation"));                                  //  25.1

   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

   Fescape = 0;

   edgecalc_threadbusy = 1;
   start_detached_thread(edgecalc_thread,0);
   while (edgecalc_threadbusy) zmainsleep(0.1);

   progress_setgoal(0);
   panelmessage(0);

   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
   }

   edgecalc_threadbusy = 0;
   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) {
            printf("*** 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) {
            printf("*** 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,TX("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
   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 *)
{
   printf("m_select_copy \n");
   select_copysave(0,"copy");
   return;
}

void m_select_save(GtkWidget *, ch *)
{
   printf("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 = TX("COPY/PASTE AREA");
   else if (strmatch(menu,"save"))
      F1_help_topic = TX("LOAD/SAVE AREA");
   else printf("*** 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,TX("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(TX("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 = TX("LOAD/SAVE AREA");

   printf("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 = TX("COPY/PASTE AREA");

   printf("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 = TX("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
   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(TX("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",TX("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",TX("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");
   zdialog_add_widget(CEF->zd,"label","labflip","hbflip",TX("Flip"),"space=3");
   zdialog_add_widget(CEF->zd,"button","horz","hbflip",TX("horizontal"),"space=5");
   zdialog_add_widget(CEF->zd,"button","vert","hbflip",TX("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"))
   {
      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
      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
         PXM_free(sacpR_pxm);
         sacpR_pxm = pxm_temp;
      }

      if (sacp_flipvert) {
         pxm_temp = PXM_upright(sacpR_pxm,0,2);                                        //  flip vertical
         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(Fgdkwin,0);                                                //  set normal cursor
   else
      gdk_window_set_cursor(Fgdkwin,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;
}


