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

   Fotoxx      edit photos and manage collections

   Copyright 2007-2020 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.

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

   Fotoxx image editor - select area functions

   Select an area within the current image.
   Subsequent edit functions are carried out within the area.
   Otherwise, edit functions apply to the entire image.

   sa_stat           0/1/2/3/4 = none/edit/unused/complete/disabled
   sa_pixmap[*]      0/1/2+ = outside/edge/inside (edge distance)
   sa_pixselc[*]     tracks pixels in current mouse selection

   sa_mode is current area selection method:
   mode_rect         select rectangle by drag/click
   mode_ellipse      select ellipse by drag
   mode_draw         freehand draw by drag/click
   mode_follow       follow edge indicated by clicks
   mode_replace      freehand draw and replace nearby pixels
   mode_mouse        select area within mouse (radius)
   mode_onecolor     select one matching color within mouse
   mode_allcolors    select all matching colors within mouse
   mode_image        select whole image

   m_select             select area dialog
   sa_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_hairy       hairy edge select function
   m_select_find_gap    find a gap in an area outline
   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_blendfunc         compute edge blend coefficient

   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 "fotoxx.h"                                                              //  (variables in fotoxx.h are refs)

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


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

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

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

   if (menu) F1_help_topic = "select area";

   if (FGWM != 'F') return;                                                      //  19.0
   if (! curr_file) return;                                                      //  no image
   if (zd_sela) return;                                                          //  already active

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

/***
       _________________________________________
      |        Select Area for Edits            |
      |          Press F1 for help              |                                //  19.0
      | [x] select rectangle  [x] ellipse       |
      | [x] freehand draw  [x] follow edge      |
      | [x] draw/replace                        |
      | Line Color: (0) (0) (0) (0) (0)         |
      | - - - - - - - - - - - - - - - - - - - - | 
      | mouse radius [___]                      |
      | match level % [___]                     |
      | search range [___]                      |
      | [x] select area in mouse                |
      | [x] select color in mouse: [####]       |
      | [x] select all colors in mouse (flood)  |
      | - - - - - - - - - - - - - - - - - - - - | 
      | Area Edge Blend Width [___]             |
      | Edge Creep [+] [-]                      |
      |                                         |
      | [Show] [Hide] [Finish] [Clear] [Done]   |
      |_________________________________________|

***/

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

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

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

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

   zdialog_add_widget(zd,"hbox","hbrepl","dialog");
   zdialog_add_widget(zd,"check","ckrepl","hbrepl",E2X("draw/replace"),"space=3");

   zdialog_add_widget(zd,"hbox","hbm5","dialog");
   zdialog_add_widget(zd,"label","labcolor","hbm5",E2X("Line 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,"hsep","sep1","dialog",0,"space=4");

   zdialog_add_widget(zd,"hbox","hbmm","dialog");
   zdialog_add_widget(zd,"label","labmr","hbmm",Bmouseradius,"space=3");
   zdialog_add_widget(zd,"zspin","mouserad","hbmm","1|300|1|20","space=5|size=3");

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

   zdialog_add_widget(zd,"hbox","hbsr","dialog");
   zdialog_add_widget(zd,"label","labrange","hbsr",E2X("search range"),"space=3");
   zdialog_add_widget(zd,"zspin","searchrange","hbsr","1|20|1|5","space=5|size=3");

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

   zdialog_add_widget(zd,"hbox","hbm2","dialog");
   zdialog_add_widget(zd,"check","ckonecolor","hbm2",E2X("select one color in mouse:"),"space=3");
   zdialog_add_widget(zd,"colorbutt","onecolor","hbm2","0|0|255","space=5");

   zdialog_add_widget(zd,"hbox","hbm3","dialog");
   zdialog_add_widget(zd,"check","ckallcolors","hbm3",E2X("select all colors in mouse (flood)"),"space=3");

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

   zdialog_add_widget(zd,"hbox","hbbw","dialog");
   zdialog_add_widget(zd,"label","labblend","hbbw",E2X("Area Edge Blend Width"),"space=3");
   zdialog_add_widget(zd,"zspin","blendwidth","hbbw","0|9999|1|0","size=4");

   zdialog_add_widget(zd,"hbox","hbec","dialog");
   zdialog_add_widget(zd,"label","labcreep","hbec",E2X("Edge Creep"),"space=3");
   zdialog_add_widget(zd,"zbutton","creep+","hbec","+");
   zdialog_add_widget(zd,"zbutton","creep-","hbec","‒");
   
   zdialog_add_widget(zd,"hbox","hbb2","dialog",0,"space=5");
   zdialog_add_widget(zd,"button","finish","hbb2",Bfinish,"space=2");
   zdialog_add_widget(zd,"button","invert","hbb2",Binvert,"space=2");
   zdialog_add_widget(zd,"button","show","hbb2",Bshow,"space=2");
   zdialog_add_widget(zd,"button","hide","hbb2",Bhide,"space=2");
   zdialog_add_widget(zd,"button","clear","hbb2",Bclear,"space=2");
   zdialog_add_widget(zd,"button","done","hbb2",Bdone,"space=2");

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

   sa_mouseradius = 30;                                                          //  initial values matching dialog
   sa_colormatch = 90;
   sa_searchrange = 5;
   if (sa_stat == 0) sa_blendwidth = 0;                                          //  preserve blend width
   zdialog_stuff(zd,"blendwidth",sa_blendwidth);
   sa_mode = 0;
   zdialog_stuff(zd,"ckrect",0);
   zdialog_stuff(zd,"ckelips",0);
   zdialog_stuff(zd,"ckdraw",0);
   zdialog_stuff(zd,"ckfollow",0);
   zdialog_stuff(zd,"ckrepl",0);
   zdialog_stuff(zd,"ckmouse",0);
   zdialog_stuff(zd,"ckonecolor",0);
   zdialog_stuff(zd,"ckallcolors",0);

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

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


//  dialog event and completion callback function

int select_dialog_event(zdialog *zd, cchar *event)
{
   int      Nckevents = 8, ii, kk, cc;
   cchar    *ckevents[8] = { "ckrect", "ckelips", "ckdraw", "ckfollow",
                             "ckrepl", "ckmouse", "ckonecolor", "ckallcolors" };
   
   if (! curr_file) event = "done";                                              //  image went away
   if (FGWM != 'F') event = "done";

   if (strmatch(event,"done") || zd->zstat) {                                    //  done or cancel
      freeMouse();                                                               //  disconnect mouse function
      zdialog_free(zd);                                                          //  kill dialog
      zd_sela = 0;                                                               //  bugfix                             19.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
      printz("*** select area ignored for this edit function \n");
      return 1;
   }

   if (! sa_stat) {                                                              //  no area, create one
      sa_pixmap_create();                                                        //  allocate pixel maps                19.0
      sa_currseq = sa_Ncurrseq = 0;                                              //  reset selection sequence
      sa_Npixel = sa_blendwidth = sa_calced = 0;
      sa_fww = Fpxb->ww;                                                         //  valid image size for area
      sa_fhh = Fpxb->hh;
      sa_stat = 1;                                                               //  status = edit
      sa_mode = 0;
      zdialog_stuff(zd,"blendwidth",0);                                          //  init. blend width = 0
   }

   for (ii = 0; ii < Nckevents; ii++)                                            //  look for checkbox event
   {
      if (strmatch(event,ckevents[ii])) {                                        //  checkbox was changed
         zdialog_fetch(zd,event,kk);                                             //  checkbox status
         if (kk) {                                                               //  ON
            sa_mode = ii+1;                                                      //  edit mode 1-8
            if (sa_mode == mode_replace) sa_unfinish();                          //  unmap internal pixels
            sa_stat = 1;                                                         //  active edit status
            sa_Npixel = sa_blendwidth = sa_calced = 0;                           //  make area unfinished
            zdialog_stuff(zd,"blendwidth",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);
      }
   }
   
   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)  20.0
   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 (strmatch(event,"blendwidth") && sa_stat == 3) {                           //  blend width changed and area finished
      zdialog_fetch(zd,"blendwidth",sa_blendwidth);                              //  update sa_blendwidth
      if (sa_blendwidth > 0) sa_edgecalc();                                      //  do edge calc. if not already
      if (sa_calced && CEF && CEF->zd)                                           //  if edit is active,
         zdialog_send_event(CEF->zd,event);                                      //    notify new blendwidth
      if (! zd_sela) return 1;                                                   //  dialog canceled                    19.0
      if (zd->zstat) {                                                           //  dialog completed                   19.0
         zdialog_send_event(zd,"zstat");
         return 1;
      }
   }
   
   if (strmatchN(event,"creep",5) && sa_stat == 3) {                             //  edge creep changed and area finished
      if (event[5] == '+') sa_edgecreep(+1);
      else sa_edgecreep(-1);
      sa_blendwidth = 0;
      zdialog_stuff(zd,"blendwidth",0);
   }
   
   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 == 1 && sa_mode && Fshowarea)                                     //  active edit mode
   {
      if (sa_mode == mode_rect)
         takeMouse(sa_geom_mousefunc,dragcursor);                                //  rectangle
      if (sa_mode == mode_ellipse)
         takeMouse(sa_geom_mousefunc,dragcursor);                                //  ellipse
      if (sa_mode == mode_draw)
         takeMouse(sa_draw_mousefunc,drawcursor);                                //  freehand draw
      if (sa_mode == mode_follow)
         takeMouse(sa_draw_mousefunc,drawcursor);                                //  follow edge
      if (sa_mode == mode_replace)
         takeMouse(sa_draw_mousefunc,drawcursor);                                //  replace nearby

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

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

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

   else {                                                                        //  edit paused or done
      freeMouse();                                                               //  disconnect mouse
      gdk_window_set_cursor(gdkwin,null);                                        //  normal cursor
      for (kk = 0; kk < Nckevents; kk++)                                         //  all checkboxes off
         zdialog_stuff(zd,ckevents[kk],0);
      sa_mode = 0;
   }

   return 1;
}


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

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

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

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

   cr = draw_context_create(gdkwin,draw_context);

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

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

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

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

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

   if (sa_mode == 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 == 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, follow edge, replace nearby

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 != 1) return;                                                     //  area gone?

   cr = draw_context_create(gdkwin,draw_context);

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

   if (! (LMclick || RMclick || Mxdrag || Mydrag)) {                             //  no mouse action
      if (openend) {
         openend = 0;                                                            //  close pending gap after
         mx1 = sa_endpx[sa_currseq];                                             //    prior draw/replace
         my1 = sa_endpy[sa_currseq];
         thresh = 3 * sa_thresh;
         npdist = sa_nearpix(mx1,my1,thresh,mx2,my2,1);
         if (npdist) sa_draw_line(mx1,my1,mx2,my2,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,E2X("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 && sa_mode != mode_replace) newseq = 1;                      //  incr. sequence each 50 pixels

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

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

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

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

   else if (RMclick)                                                             //  right mouse click
      sa_unselect_pixels(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 != 1) return;                                                     //  area gone?

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

   if (px1 == px2 && py1 == py2) {                                               //  only one pixel
      sa_draw1pix(px1,py1,cr);
      if (crflag) draw_context_destroy(draw_context); 
      return;
   }

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

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


//  add to select area and draw one pixel if not already
//  mwcr 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 != 1) return;                                                     //  area gone?

   if (! cr) {
      cr = draw_context_create(gdkwin,draw_context);
      crflag = 1;
   }
   
   px3 = px1;                                                                    //  p3 progresses from p1 to p2
   py3 = py1;

   while (true)
   {
      dx = px2 - px3;
      dy = py2 - py3;

      dist = sqrt(dx * dx + dy * dy);                                            //  last segment
      if (dist < 5) break;                                                       //  line laggs mouse 5 pixels

      px4 = px3 + dx / dist;                                                     //  p4 = p3 moved toward p2
      py4 = py3 + dy / dist;

      maxcontrast = 0;
      px6 = px4;
      py6 = py4;

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

      sa_draw_line(px3,py3,px6,py6,cr);                                          //  draw p3 to p6

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

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

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


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

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

   if (! cr) {
      cr = draw_context_create(gdkwin,draw_context);
      crflag = 1;
   }
   
   thresh = 2 * sa_thresh;

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

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

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


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

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

   if (px < 1 || px > Fpxb->ww-2) return 0;                                      //  avoid edge pixels
   if (py < 1 || py > Fpxb->hh-2) return 0;

   for (ii = 0; ii < 4; ii++)                                                    //  compare pixels around target
   {                                                                             //  e.g. (px-1,py) to (px+1,py)
      qx = map[ii][0];
      qy = map[ii][1];
      pix1 = PXBpix(Fpxb,px+qx,py+qy);
      pix2 = PXBpix(Fpxb,px-qx,py-qy);
      match = PIXMATCH(pix1,pix2);                                               //  0..1 = zero..perfect match
      contrast = 1.0 - match;                                                    //  max. contrast = 1.0
      if (contrast > maxcontrast) maxcontrast = contrast;
   }

   return maxcontrast;
}


//  mouse function for edit modes with mouse / color selection
//  sa_mode = mouse = select area within mouse radius
//  sa_mode = onecolor = select one matching color within mouse radius
//  sa_mode = allcolors = select all matching colors within mouse radius
//                        and extend to contiguous matching colors
//  if left click or drag, find and select matching pixels
//  if right click, unselect last selection
//  if right drag, find and unselect matching pixels

void sa_mouse_select()
{
   void sa_mouse_select1(int mode, int select);
   void sa_mouse_select2(int select);

   int         newdrag;
   static int  pxcc, mxdown, mydown, dragseq;
   int         startx, starty, endx, endy;
   float       slope;

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

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

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

   if (sa_pixselc) memset(sa_pixselc,0,pxcc);                                    //  do always
   
   if (LMclick)                                                                  //  left mouse click
   {                                                                             //  select along line from prior
      LMclick = 0;
      sa_nextseq();                                                              //  set next sequence no.
      dragseq = 0;                                                               //  reset drag counter
      
      if (sa_lastx == 0 && sa_lasty == 0) {                                      //  no prior selection
         sa_lastx = Mxclick;
         sa_lasty = Myclick;
      }

      sa_mousex = Mxclick;
      sa_mousey = Myclick;

      if (sa_mode == mode_onecolor) sa_mouse_select1(1,1);                       //  set color for matching within mouse
      if (sa_mode == mode_allcolors) sa_mouse_select2(1);                        //  select matching colors in/beyond mouse
      
      if (sa_mode == 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));
                  if (sa_mode == mode_onecolor) sa_mouse_select1(1,1);
                  else sa_mouse_select2(1);                                      //  select pixels within mouse
               }
            }
            else {
               for (sa_mousey = starty; sa_mousey >= endy; sa_mousey--) {
                  sa_mousex = round(startx + slope * (sa_mousey - starty));
                  if (sa_mode == mode_onecolor) sa_mouse_select1(1,1);
                  else sa_mouse_select2(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));
                  if (sa_mode == mode_onecolor) sa_mouse_select1(1,1);
                  else sa_mouse_select2(1);
               }
            }
            else {
               for (sa_mousex = startx; sa_mousex >= endx; sa_mousex--) {
                  sa_mousey = round(starty + slope * (sa_mousex - startx));
                  if (sa_mode == mode_onecolor) sa_mouse_select1(1,1);
                  else sa_mouse_select2(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 == mode_onecolor)
            sa_mouse_select1(2,1);                                               //  select matching colors within mouse
         else
            sa_mouse_select2(1);                                                 //  select in mouse + matching colors beyond
         if (newdrag || ++dragseq > 30) {                                        //  limit work per sequence no.
            sa_nextseq();                                                        //  set next sequence no.
            dragseq = 0;                                                         //  reset drag counter
         }
      }

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

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


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

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

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

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

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

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

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

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

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

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

   mrad = sa_mouseradius;
   mrad2 = mrad * mrad;

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

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

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

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

      if (select) {                                                              //  select mode
         if (! sa_pixmap[ii]) {                                                  //  if selected for the first time,
            sa_pixmap[ii] = sa_currseq;                                          //    map pixel to current sequence
            sa_Ncurrseq++;                                                       //  current sequence pixel count
            newpix = 1;
            sa_pixmap2[ii] = 1;                                                  //  pixel is mouse selected            19.0
         }
      }
      else if (sa_pixmap[ii]) {                                                  //  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;
}


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

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

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

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

   sa_Ncurrseq = 0;                                                              //  count newly selected pixels

   mrad = sa_mouseradius;
   mrad2 = mrad * mrad;

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

      ii = Fpxb->ww * py + px;

      if (select) {                                                              //  select pixel
         if (sa_pixmap[ii]) continue;                                            //  already selected
         sa_pixmap[ii] = sa_currseq;                                             //  map pixel to current sequence
         sa_Ncurrseq++;                                                          //  current sequence pixel count
         sa_pixmap2[ii] = 1;                                                     //  pixel is mouse selected            19.0
      }
      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 == mode_mouse) {                                                  //  no color matching, done
      Fpaint4(px-mrad,py-mrad,2*mrad,2*mrad,0);                                  //  repaint changed area
      return;
   }

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

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

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

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

      Npixels++;                                                                 //  count pixels inside mouse circle

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

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

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

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

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

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

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

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

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

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

   while (sa_Nstack)
   {
      kk = sa_Nstack - 1;                                                        //  get last pixel in stack
      ii = sa_stackii[kk];
      direc = sa_stackdirec[kk];

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

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

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

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

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

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

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

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

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

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

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

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

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

      if (select) {                                                              //  select mode
         if (! sa_pixmap[ii]) {                                                  //  if selected for the first time,
            sa_pixmap[ii] = sa_currseq;                                          //    map pixel to current sequence
            sa_Ncurrseq++;                                                       //  current sequence pixel count
            newpix = 1;
            sa_pixmap2[ii] = 1;                                                  //  pixel is mouse selected            19.0
         }
      }
      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_stackii[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 != 1) return;                                                     //  area gone?
   if (! sa_currseq) return;                                                     //  no pixels mapped

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

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

   if (sa_currseq > sa_initseq) {                                                //  reduce sequence no.
      sa_currseq--;
      sa_Ncurrseq = 1;                                                           //  unknown but > 0
   }
   else  sa_Ncurrseq = 0;                                                        //  initial sequence no. reached

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


//  allocate pixel maps for new select area

void sa_pixmap_create()                                                          //  19.0
{
   int      cc;
   cc = Fpxb->ww * Fpxb->hh * sizeof(uint16);
   sa_pixmap = (uint16 *) zmalloc(cc);                                           //  maps outside/edge/inside pixels
   memset(sa_pixmap,0,cc);
   cc = Fpxb->ww * Fpxb->hh * sizeof(uint8);
   sa_pixmap2 = (uint8 *) zmalloc(cc);                                           //  maps mouse-selected pixels
   memset(sa_pixmap2,0,cc);
   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, cchar *event);                          //  private functions
void sa_finish_mousefunc();
void sa_finish_mappix();
void sa_finish_fill();


void sa_finish()
{
   cchar  *fmess = E2X(
            "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;

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

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

   sa_show(1,0);                                                                 //  show area

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

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

   sa_fincancel = 0;
   sa_Nstack = 0;

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

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

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

   freeMouse();                                                                  //  disconnect mouse

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

   while (sa_Nstack) {                                                           //  wait for pixel search to complete
      zmainloop();
      zsleep(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,E2X("found %d pixels"),sa_Npixel);
      return;
   }

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


//  dialog event and completion function

int sa_finish_dialog_event(zdialog *zd, cchar *event)                            //  19.0
{
   cchar  *fmess = E2X(
             "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) return 1;                                                      //  [keep] or [undo]
   sa_finish_fill();
   return 1;
}


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

void sa_finish_mousefunc()
{
   int      ii, jj, px, py, npix;
   
   if (! sa_stat) return;                                                        //  area gone?
   if (sa_mode == mode_image) return;                                            //  a whole image area
   
   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_stackii[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_fill();                                                          //  fill with color for user verify
   }
   
   //  click outside all areas, select all pixels outside all outlines, then invert                                     19.0
   
   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_fill();                                                          //  fill with color for user verify

      sa_stat = 3;                                                               //  area now finished
      sa_Npixel = npix;                                                          //  new select area pixel count
      sa_calced = sa_blendwidth = 0;                                             //  edge calculation missing
      if (zd_sela) zdialog_stuff(zd_sela,"blendwidth",0);                        //  reset blend width
   }

   return;
}


//  function to map all pixels found within enclusure of starting pixel

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

   while (sa_Nstack)                                                             //  find all pixels within enclosed area(s)
   {
      kk = sa_Nstack - 1;                                                        //  get last pixel in stack
      ii = sa_stackii[kk];
      direc = sa_stackdirec[kk];

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

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

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

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

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

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

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

      jj = npy * Fpxb->ww + npx;

      if (Fpxb->nc > 3 && PXBpix(Fpxb,npx,npy)[3] < 254) {                       //  next pixel in transparent area
         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_stackii[kk] = jj;
      sa_stackdirec[kk] = 'a';                                                   //  direction = ahead
   }

   sa_Nstack = 0;                                                                //  done
   return;
}


//  fill selected areas with color for user verification

void sa_finish_fill()
{
   int      ii, px, py;
   int      npaint;
   cairo_t  *cr;

   cr = draw_context_create(gdkwin,draw_context);

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

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

   draw_context_destroy(draw_context);
   return;
}


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

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

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

   sa_map_pixels();                                                              //  map interior pixels

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

   sa_stat = 3;                                                                  //  area is finished
   areanumber++;                                                                 //  next sequential number
   sa_calced = sa_blendwidth = 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;

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

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

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

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

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

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

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

   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;                                              //  no unmap mouse selected pixels     19.0
      if (sa_pixmap[ii] == 2) sa_pixmap[ii] = 0;                                 //    by finish search function
   }

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


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

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

namespace select_hairy
{
   int         ww, hh, pcc;
   int         mradius = 30;                       //  mouse radius
   int         Fmode;                              //  1/2/3 = select/deselect/both
   int         selrad = 6;                         //  radius for match pixel selection (select)
   int         dselrad = 4;                        //  radius for match pixel selection (deselect)
   float       smatch = 80;                        //  pixel select match level 0-100
   float       dmatch = 80;                        //  pixel deselect match level 0-100
   float       selRGB[200][3];                     //  pixels to match & select
   float       dselRGB[200][3];                    //  pixels to match & deselect
   int         Nsel, Ndsel;                        //  pixel counts
   zdialog     *zdselhairy;
   GdkPixbuf   *pxbsel, *pxbdsel;                  //  pixbufs to show match colors
   int         pxbsize, pxbmid, rs;                //  size, middle pixel, row stride
   uint8       *pixels;
}


//  menu function

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

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

   cchar    *title = E2X("Select Hairy");
   cchar    *helptext = E2X("Press F1 for help");
   
   F1_help_topic = "select hairy";

   if (FGWM != 'F') return;                                                      //  19.0
   
   if (CEF) edit_done(0);                                                        //  if edit active, complete it

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

   if (sa_stat < 3) {
      zmessageACK(Mwin,Bareanotfinished);
      return;
   }

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

   PXM_addalpha(E0pxm);                                                          //  add alpha channel if not

   ww = E0pxm->ww;
   hh = E0pxm->hh;
   pcc = 4 * sizeof(float);

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

   Fpaint2();
   
   for (int ii = 0; ii < 200; ii++) {                                            //  initial match pixels
      selRGB[ii][0] = 0;                                                         //  default = black
      selRGB[ii][1] = 0;
      selRGB[ii][2] = 0;
      dselRGB[ii][0] = 0;
      dselRGB[ii][1] = 0;
      dselRGB[ii][2] = 0;
   }
   Nsel = Ndsel = 100;                                                           //  arbitrary at this point
   
   pxbsel = gdk_pixbuf_new(GDKRGB,0,8,40,40);                                    //  pixbuf showing select match colors
   rs = gdk_pixbuf_get_rowstride(pxbsel);
   pixels = gdk_pixbuf_get_pixels(pxbsel);
   memset(pixels,0,40*rs);                                                       //  initially all black

   pxbdsel = gdk_pixbuf_new(GDKRGB,0,8,40,40);                                   //  pixbuf showing deselect match colors
   rs = gdk_pixbuf_get_rowstride(pxbdsel);
   pixels = gdk_pixbuf_get_pixels(pxbdsel);
   memset(pixels,0,40*rs);                                                       //  initially all black
   
/***
       ____________________________________________
      |            Select Hairy                    |
      |                                            |
      | Press F1 for help                          |
      |                                            |
      | mouse radius ========[]============= [18]  |
      |                                            |
      |                                      ##### |
      | [X] Select  ===========[]===== [92]  ##### |
      |                                      ##### |
      |                                            |
      |                                      ##### |
      | [X] Deselect  =======[]======= [83]  ##### |
      |                                      ##### |
      |                                            |
      |                     [Copy] [Save] [Cancel] |  
      |____________________________________________|

***/

   zdselhairy = zdialog_new(title,Mwin,Bcopy,Bsave,Bcancel,null);
   zdialog *zd = zdselhairy;
   zdialog_add_widget(zd,"label","labhelp","dialog",helptext,"space=3");

   zdialog_add_widget(zd,"hbox","hbmrad","dialog",0,"space=5");
   zdialog_add_widget(zd,"label","labmrad","hbmrad",Bmouseradius,"space=3");
   zdialog_add_widget(zd,"hscale","mradius","hbmrad","3|300|1|40","expand");
   zdialog_add_widget(zd,"label","mradius2","hbmrad","20","space=3");

   zdialog_add_widget(zd,"hbox","hbsel","dialog",0,"space=3");
   zdialog_add_widget(zd,"check","select","hbsel",E2X("select"));
   zdialog_add_widget(zd,"hscale","smatch","hbsel","10|99|0.1|80","expand");
   zdialog_add_widget(zd,"label","smatch2","hbsel","80","space=3");
   zdialog_add_widget(zd,"image","scolors","hbsel",(cchar *) pxbsel);

   zdialog_add_widget(zd,"hbox","hbdes","dialog",0,"space=3");
   zdialog_add_widget(zd,"check","deselect","hbdes",E2X("deselect"));
   zdialog_add_widget(zd,"hscale","dmatch","hbdes","10|99|0.1|80","expand");
   zdialog_add_widget(zd,"label","dmatch2","hbdes","80","space=3");
   zdialog_add_widget(zd,"image","dcolors","hbdes",(cchar *) pxbdsel);

   zdialog_rescale(zd,"mradius",3,3,300);
   zdialog_rescale(zd,"smatch",10,99,99);
   zdialog_rescale(zd,"dmatch",10,99,99);
   
   smatch = dmatch = 80;                                                         //  defaults
   mradius = 20;
   
   zdialog_stuff(zd,"smatch",smatch);
   zdialog_stuff(zd,"dmatch",dmatch);
   zdialog_stuff(zd,"mradius",mradius);
   zdialog_stuff(zd,"select",0);
   zdialog_stuff(zd,"deselect",0);
   Fmode = 0;
   
   zdialog_resize(zd,400,0);                                                     //  run dialog
   zdialog_run(zd,select_hairy_dialog_event,"save");

   takeMouse(select_hairy_mousefunc,dragcursor);                                 //  capture mouse

   return;
}


//  dialog event and completion function

int select_hairy_dialog_event(zdialog *zd, cchar *event)
{
   using namespace select_hairy;
   
   int      ii;
   char     text[8];
   
   void  select_hairy_mousefunc();

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

   if (strmatch(event,"cancel")) zd->zstat = 3;                                  //  from f_open()
   
   if (strmatch(event,"focus")) 
      takeMouse(select_hairy_mousefunc,dragcursor);
   
   if (zd->zstat)                                                                //  done or cancel
   {
      if (zd->zstat == 1) {                                                      //  copy area
         m_select_copy(0,0);
         zd->zstat = 0;
         return 1;
      }

      if (zd->zstat == 2) {                                                      //  save area to file
         m_select_save(0,0);
         zd->zstat = 0;
         return 1;
      }

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

      zdialog_free(zd);                                                          //  kill dialog
      freeMouse();                                                               //  disconnect mouse
      g_object_unref(pxbsel);                                                    //  free pixbuf memory
      g_object_unref(pxbdsel);
      return 1;
   }
   
   Fmode = 0;                                                                    //  mode: 1/2/3 = select/deselect/both
   zdialog_fetch(zd,"select",ii);
   if (ii) Fmode = 1;
   zdialog_fetch(zd,"deselect",ii);
   if (ii) Fmode += 2;
   
   zdialog_fetch(zd,"mradius",mradius);                                          //  mouse radius
   zdialog_fetch(zd,"smatch",smatch);                                            //  select match level 0 - 100
   zdialog_fetch(zd,"dmatch",dmatch);                                            //  deselect match level 0 - 100
   
   snprintf(text,8,"%d",mradius);                                                //  stuff number values for sliders
   zdialog_stuff(zd,"mradius2",text);
   snprintf(text,8,"%.1f",smatch);
   zdialog_stuff(zd,"smatch2",text);
   snprintf(text,8,"%.1f",dmatch);
   zdialog_stuff(zd,"dmatch2",text);
   
   return 1;
}


//  mouse function

void select_hairy_mousefunc()
{
   using namespace select_hairy;
   int select_hairy_ucomp(cchar *, cchar*);
   
   int         ii, rx, ry, px, py;
   int         mrad, rrad;
   int         pcc3 = 3 * sizeof(float);
   uint8       *pix;
   float       *pix0, *pix1, *pixM;
   float       pixmatch, bestmatch;
   float       smlev, dmlev, psml, pdml;
   float       f1, f2, alpha;
   GdkPixbuf   *pxbtemp = 0;

   if (LMclick)                                                                  //  left mouse click,
   {                                                                             //    get colors to select
      mrad = selrad;                                                             //  area for pixel color select
      ii = 0;

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

      ii = 0;

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

      g_object_unref(pxbsel);                                                    //  show enlarged
      pxbsel = gdk_pixbuf_scale_simple(pxbtemp,40,40,BILINEAR);
      zdialog_set_image(zdselhairy,"scolors",pxbsel);
   }
   
   else if (RMclick)                                                             //  right mouse click,
   {                                                                             //    get colors to deselect
      mrad = dselrad;                                                            //  area for pixel color deselect
      ii = 0;

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

      ii = 0;

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

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

   cairo_t *cr = draw_context_create(gdkwin,draw_context);

   if ((Mxdrag || Mydrag) && Mbutton == 1)                                       //  left drag underway,
   {                                                                             //    select or deselect pixels
      mrad = mradius;
      smlev = 0.01 * smatch;                                                     //  select match level scaled 0-1
      dmlev = 0.01 * dmatch;                                                     //  deselect match level scaled 0-1
      psml = pdml = 0;                                                           //  stop GCC warnings

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

         pix1 = PXMpix(E1pxm,px,py);                                             //  input pixel
         pix0 = PXMpix(E0pxm,px,py);                                             //  output pixel
         
         bestmatch = -1;
         pixM = selRGB[0];
         for (ii = 0; ii < Nsel; ii++) {                                         //  compare to selected colors
            pixmatch = RGBMATCH(pix1[0], pix1[1], pix1[2],
                       selRGB[ii][0], selRGB[ii][1], selRGB[ii][2]);
            if (pixmatch > bestmatch) {
               bestmatch = pixmatch;                                             //  save best match level
               pixM = selRGB[ii];                                                //  save best match color
            }
         }
         psml = bestmatch;                                                       //  select colors best match level

         bestmatch = -1;
         for (ii = 0; ii < Ndsel; ii++) {                                        //  compare to deselect colors
            pixmatch = RGBMATCH(pix1[0], pix1[1], pix1[2],
                       dselRGB[ii][0], dselRGB[ii][1], dselRGB[ii][2]);
            if (pixmatch > bestmatch) bestmatch = pixmatch;                      //  save best match level
         }
         pdml = bestmatch;                                                       //  deselect colors best match level
         
         if (Fmode == 1)                                                         //  select only - increase opacity
         {
            if (psml > smlev) {
               alpha = (psml - smlev) / (1.0 - smlev);
               alpha = 255.0 * pow(alpha,0.3);
               if (pix0[3] < alpha) {
                  pix0[3] = alpha;
                  memcpy(pix0,pixM,pcc3);                                        //  color = match color
               }
            }
         }
         
         else if (Fmode == 2)                                                    //  deselect only - increase transparency
         {
            if (pdml >= dmlev) alpha = 0;
            else {
               alpha = 1.0 - pdml / dmlev;
               alpha = 255.0 * pow(alpha,0.3);
            }
            alpha = 255.0 * pow(alpha,0.3);
            if (alpha < 20) alpha = 0;
            if (pix0[3] > alpha) pix0[3] = alpha;
         }
         
         else  /*  Fmode == 3  */                                                //  parallel select and deselect
         {
            if (psml > pdml)                                                     //  pixel better matches select colors
               alpha = 1 - 0.5 * (1 - psml) / (1 - pdml);                        //  scale 0.5 to 1.0
            else if (pdml > psml)
               alpha = 0.5 * (1 - pdml) / (1 - psml);                            //  pixel better matches deselect colors
            else alpha = 0.5;                                                    //  scale 0 to 0.5
            alpha = 255 * alpha;
            if (alpha < 20) alpha = 0;
            pix0[3] = alpha;
            alpha = 255.0 * pow(alpha,0.3);
            memcpy(pix0,pixM,pcc3);                                              //  color = match color
         }
      }

      Fpaint0(Mxdrag-mrad,Mydrag-mrad,2*mrad+1,2*mrad+1,cr);                     //  update drawing window
   }

   if ((Mxdrag || Mydrag) && Mbutton == 3)                                       //  right drag underway,
   {                                                                             //    restore original pixels
      mrad = mradius;

      for (ry = -mrad; ry <= mrad; ry++)                                         //  loop pixels within mouse radius
      for (rx = -mrad; rx <= mrad; rx++)
      {
         rrad = sqrtf(rx * rx + ry * ry);
         if (rrad > mrad) continue;                                              //  outside radius
         px = Mxdrag + rx;
         py = Mydrag + ry;
         if (px < 0 || px > ww-1) continue;                                      //  outside image edge
         if (py < 0 || py > hh-1) continue;
         ii = py * ww + px;
         if (! sa_pixmap[ii]) continue;                                          //  outside area
         
         f1 = 1.0 - 1.0 * rrad / mrad;                                           //  center ... edge  >>  1 ... 0
         f1 = 0.3 * f1 * f1 + 0.03;                                              //  0.33 ... 0.03
         f2 = 1.0 - f1;                                                          //  restore slowly at edge
         pix0 = PXMpix(E0pxm,px,py);
         pix1 = PXMpix(E1pxm,px,py);
         pix0[0] = f2 * pix0[0] + f1 * pix1[0];
         pix0[1] = f2 * pix0[1] + f1 * pix1[1];
         pix0[2] = f2 * pix0[2] + f1 * pix1[2];
         pix0[3] = f2 * pix0[3] + f1 * pix1[3];
      }

      Fpaint0(Mxdrag-mrad,Mydrag-mrad,2*mrad+1,2*mrad+1,cr);                     //  update drawing window
   }

   draw_mousecircle(Mxposn,Myposn,mradius,0,cr);                                 //  refresh mouse circle

   draw_context_destroy(draw_context);

   LMclick = RMclick = Mxdrag = Mydrag = 0;
   return;
}


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

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

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

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

   F1_help_topic = "find area gap";

   if (FGWM != 'F') return;                                                      //  19.0

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

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

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

   sa_show(1,0);                                                                 //  show area

   zd = zdialog_new(E2X("find outline gap"),Mwin,Bdone,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, cchar *event)
{
   void sa_find_gap_mousefunc();

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


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

void sa_find_gap_mousefunc()
{
   int         rad, ii, jj, kk, nn, ff, np1, np2, npx;
   float       angle;
   int         ww, hh, cc;
   int         mx, my, px, py, rx, ry;
   char        *pixmark = 0;
   cairo_t     *cr = 0;

   if (! sa_stat) return;

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

   sa_map_pixels();                                                              //  find edge pixels
   if (! sa_Npixel) return;
   
   nn = np1 = np2 = -1;

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

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

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

   if (Ffuncbusy++) return;                                                      //  prevent re-entry                   f.20.02

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

   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
   {
      zmainloop(1000);
      if (! sa_stackii) goto cleanup;                                            //  user cancel                        19.0

      px = mx + rad * cosf(angle);                                               //  search for nearest edge pixel
      py = my + rad * sinf(angle);
      ii = py * ww + px;
      if (sa_pixmap[ii] != 1) continue;

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

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

   cr = draw_context_create(gdkwin,draw_context);
   pixmark = (char *) zmalloc(cc);                                               //  create pixel mark map
   
   for (ff = 0; ff < 2; ff++)
   {
      memset(pixmark,0,cc);                                                      //  clear all pixel marks
      pixmark[ii] = 1;                                                           //  mark edge pixel

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

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

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

         draw_pixel(px,py,cr,1);                                                 //  draw fat pixel
         zsleep(0.001);
         zmainloop();

         if (! sa_stackii) goto cleanup;                                         //  killed by user

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

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

            if (sa_pixmap[jj] == 1) {                                            //  neighbor is an unmarked edge pixel
               kk = sa_Nstack++;                                                 //  add to stack
               sa_stackii[kk] = jj;
            }
         }
      }
      
      if (ii == np2) break;                                                      //  no gap

      npx = np1;                                                                 //  np1 <--> np2
      np1 = np2;
      np2 = npx;
      zsleep(1);                                                                 //  pause and loop other direction
   }

cleanup:
   Ffuncbusy = 0;
   if (pixmark) zfree(pixmark);                                                  //  free memory
   if (cr) draw_context_destroy(draw_context);
   sa_show(1,0);                                                                 //  show area
   return;
}


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

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

void m_select_show(GtkWidget *, cchar *menu)
{
   F1_help_topic = "show/hide area";
   if (FGWM != 'F') return;                                                      //  19.0
   sa_show(1,0);                                                                 //  show area
   return;
}


void m_select_hide(GtkWidget *, cchar *menu)
{
   F1_help_topic = "show/hide area";
   if (FGWM != 'F') return;                                                      //  19.0
   sa_show(0,0);
   return;
}


void m_select_enable(GtkWidget *, cchar *menu)
{
   F1_help_topic = "enable/disable area";
   if (FGWM != 'F') return;                                                      //  19.0
   sa_enable();
   return;
}


void m_select_disable(GtkWidget *, cchar *menu)
{
   F1_help_topic = "enable/disable area";
   if (FGWM != 'F') return;                                                      //  19.0
   sa_disable();
   return;
}


void m_select_invert(GtkWidget *, cchar *menu)
{
   F1_help_topic = "invert area";
   if (FGWM != 'F') return;                                                      //  19.0
   sa_invert();
   return;
}


void m_select_clear(GtkWidget *, cchar *menu)                                    //  delete the area
{
   F1_help_topic = "clear area";
   if (FGWM != 'F') return;                                                      //  19.0
   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) return;                                                        //  no area
   if (! sa_validate()) return;                                                  //  invalid for current image
   if (sa_mode == mode_image) return;                                            //  a whole image area

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

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

   if (! cr) {
      cr = draw_context_create(gdkwin,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] < 254)                           //  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) return;                                                        //  no area
   if (! sa_validate()) return;                                                  //  invalid for current image
   if (sa_mode == mode_image) return;                                            //  a whole image area

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

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

   if (! cr) {
      cr = draw_context_create(gdkwin,draw_context);
      crflag = 1;
   }
   
   for (py = py1; py < py2; py++)                                                //  loop pixels in rectangle
   for (px = px1; px < px2; px++)
   {
      ii = Fpxb->ww * py + px;
      if (! sa_pixmap[ii]) continue;

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

      if (Fpxb->nc > 3 && PXBpix(Fpxb,px,py)[3] < 254)                           //  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) 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) return;                                                        //  no area
   if (! sa_validate()) return;                                                  //  invalid for current image
   if (sa_stat < 3) {                                                            //  finished or finished/disabled
      zmessageACK(Mwin,Bareanotfinished);
      return;
   }

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

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


//  disable select area

void sa_disable()
{
   if (! sa_stat) return;                                                        //  no area
   if (sa_stat < 3) {                                                            //  finished or */disabled
      zmessageACK(Mwin,Bareanotfinished);
      return;
   }

   sa_stat = 4;                                                                  //  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) return;                                                        //  no area
   if (! sa_validate()) return;                                                  //  invalid for current image
   if (sa_mode == mode_image) return;                                            //  a whole image area
   if (sa_stat < 3) {
      zmessageACK(Mwin,Bareanotfinished);
      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 = 3;                                                                  //  if disabled, now finished
   sa_Npixel = npix;                                                             //  new select area pixel count
   sa_calced = sa_blendwidth = 0;                                                //  edge calculation missing
   if (zd_sela) zdialog_stuff(zd_sela,"blendwidth",0);                           //  reset blend width

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


//  clear current area (delete the area)

void sa_clear()
{
   sa_stat = sa_Npixel = sa_blendwidth = sa_calced = 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_stackii) zfree(sa_stackii);
   if (sa_stackdirec) zfree(sa_stackdirec);
   if (sa_pixselc) zfree(sa_pixselc);
   sa_pixmap = 0;
   sa_pixmap2 = 0;
   sa_stackii = 0;
   sa_stackdirec = 0;
   sa_Nstack = 0;
   sa_pixselc = 0;
   Fpaint2();
   return;
}


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

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

void sa_edgecalc()
{
   using namespace sa_edgecalc_names;

   int    edgecalc_dialog_event(zdialog*, cchar *event);
   void * edgecalc_thread(void *arg);

   int         ii, nn, cc, px, py;

   if (! sa_stat) return;                                                        //  area gone?
   if (sa_mode == mode_image) return;                                            //  a whole image area
   if (sa_calced) return;                                                        //  done already

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

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

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

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

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

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

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

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

   sa_Nedge = nn;

   Fbusy_goal = sa_Npixel;
   Fbusy_done = 0;

   edgecalc_done = 0;
   start_detached_thread(edgecalc_thread,0);                                     //  use thread so that progress
   while (! edgecalc_done) zmainsleep(0.1);                                      //    indicator works                  19.0

   Fbusy_goal = 0;

   if (Fescape) {                                                                //  user killed edge calc              19.0
      Fescape = 0;
      sa_calced = 0;
      sa_blendwidth = 0;                                                         //  19.0
      if (zd_sela) zdialog_stuff(zd_sela,"blendwidth",0);                        //  reset blend width
   }

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

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

   Fpaint2();
   return;
}


//  thread function to calculate pixel edge distances

void * edgecalc_thread(void *arg)                                                //  19.0
{
   using namespace sa_edgecalc_names;
   void * edgecalc_wthread(void *arg);
   do_wthreads(edgecalc_wthread,NWT);                                            //  start worker threads
   edgecalc_done = 1;
   pthread_exit(0);
}


void * edgecalc_wthread(void *arg)                                               //  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 += NWT)
      {
         if (px < 0 || px > Fpxb->ww-1) continue;
         if (py < 0 || py > Fpxb->hh-1) continue;
         ii = py * Fpxb->ww + px;
         if (! sa_pixmap[ii]) continue;
         if (sa_edgedist[ii]) continue;
         edgecalc_func(px,py);
         if (Fescape) pthread_exit(0);                                           //  user cancel                        19.0
      }

      for (py = midy-rad; py <= midy+rad; py += 2 * rad)
      for (px = midx-rad+index; px <= midx+rad; px += NWT)
      {
         if (px < 0 || px > Fpxb->ww-1) continue;
         if (py < 0 || py > Fpxb->hh-1) continue;
         ii = py * Fpxb->ww + px;
         if (! sa_pixmap[ii]) continue;
         if (sa_edgedist[ii]) continue;
         edgecalc_func(px,py);
         if (Fescape) pthread_exit(0);                                           //  user cancel                        19.0
      }
   }

   pthread_exit(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) {
            printz("edgecalc() bug %d \n",ii);
            return;
         }
         if (sa_edgedist[ii]) return;
         dx = epx - pxm;                                                         //  calculate distance to edge
         dy = epy - pym;
         dist2 = sqrt(dx*dx + dy*dy) + 1;                                        //  minor bug fix
         sa_edgedist[ii] = dist2;                                                //  save
         Fbusy_done++;                                                           //  track progress
      }
   }

   else {
      slope = 1.0 * (epy - py1) / (epx - px1);
      if (epx > px1) inc = 1;
      else inc = -1;
      for (pxm = px1; pxm != epx; pxm += inc) {
         pym = py1 + slope * (pxm - px1);
         ii = pym * ww + pxm;
         if (ii < 0 || ii >= cc) {
            printz("edgecalc() bug %d \n",ii);
            return;
         }
         if (sa_edgedist[ii]) return;
         dx = epx - pxm;
         dy = epy - pym;
         dist2 = sqrt(dx*dx + dy*dy) + 1;
         sa_edgedist[ii] = dist2;
         Fbusy_done++;
      }
   }

   return;
}


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

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

   if (sa_stat != 3) {
      zmessageACK(Mwin,Bareanotfinished);
      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_calced = 0;                                                                //  invalidate prior edge calc.

   Fpaint2();
   return;
}


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

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

   Pblend = sa_blendwidth;

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


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

   float    sacp_scale;                                                          //  scale, 1.0 = original size
   float    sacp_angle;                                                          //  angle of rotation, -180 to +180
   int      sacp_orgx, sacp_orgy;                                                //  origin in target image
   float    sacp_blend;                                                          //  pasted area edge blend with image
   float    sacp_brite;                                                          //  pasted area brightness adjustment
   int      sacp_porg = 0;                                                       //  pasted area is present
   int      sacp_porgx, sacp_porgy;                                              //  pasted area origin in image
   int      sacp_pww, sacp_phh;                                                  //  pasted area dimensions

   editfunc    EFpaste;
}


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

void m_select_copysave(GtkWidget *, cchar *menu);

void m_select_copy(GtkWidget *, cchar *)
{
   m_select_copysave(0,"copy");
   return;
}

void m_select_save(GtkWidget *, cchar *)
{
   m_select_copysave(0,"save");
   return;
}

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

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

   if (strmatch(menu,"copy"))
      F1_help_topic = "copy/paste area";
   
   else if (strmatch(menu,"save"))
      F1_help_topic = "load/save area";
   
   else {
      zmessageACK(Mwin,"menu name bug: %s",menu);
      return;
   }

   if (FGWM != 'F') return;                                                      //  19.0

   if (! sa_stat) return;                                                        //  no selected area
   if (sa_mode == mode_image) return;                                            //  a whole image area
   if (sa_stat < 3) {
      zmessageACK(Mwin,Bareanotfinished);
      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
      if (dist == 0) pix2[3] = 0;                                                //  outside pixel, transparent
      else if (nc < 4) pix2[3] = 255;                                            //  inside, opaque if alpha added
   }

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

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

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

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

   return;
}


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

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

   void  select_paste(GtkWidget *, cchar *);

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

   F1_help_topic = "load/save area";

   if (FGWM != 'F') return;                                                      //  19.0

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

   file = gallery_select1(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,0);                                                            //  interactive move/resize area image
   return;
}


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

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

   void  select_paste(GtkWidget *, cchar *);

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

   F1_help_topic = "copy/paste area";

   if (FGWM != 'F') return;                                                      //  19.0

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

   snprintf(filename,100,"%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,0);                                                            //  interactive move/resize area image
   return;
}


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

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

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

   if (! sacp_pxm) return;                                                       //  nothing to paste
   sa_clear();                                                                   //  clear area if present

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

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

   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;
   }
   
/***
          __________________________________________________
         | position with mouse click/drag                   |
         |                                                  |
         | resize [-10%] [-1%] [-.1%] [+.1%] [+1%] [+10%]   |
         | angle [-10°] [-1°] [-.1°] [+.1°] [+1°] [+10°]    |
         | brightness ==============[]====================  |
         | edge blend ===[]===============================  |
         |                                                  |
         |                                 [Done] [Cancel]  |
         |__________________________________________________|

***/

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

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

   zdialog_add_widget(CEF->zd,"hbox","hbang","dialog",0,"space=3");
   zdialog_add_widget(CEF->zd,"label","labang","hbang",Bangle,"space=3");
   zdialog_add_widget(CEF->zd,"button","-10°","hbang","-10°");
   zdialog_add_widget(CEF->zd,"button","-1°","hbang","-1°");
   zdialog_add_widget(CEF->zd,"button","-.1°","hbang","-.1°");
   zdialog_add_widget(CEF->zd,"button","+.1°","hbang","+.1°");
   zdialog_add_widget(CEF->zd,"button","+1°","hbang","+1°");
   zdialog_add_widget(CEF->zd,"button","+10°","hbang","+10°");

   zdialog_add_widget(CEF->zd,"hbox","hbbr","dialog",0,"space=3");
   zdialog_add_widget(CEF->zd,"label","labbr","hbbr","brightness","space=3");
   zdialog_add_widget(CEF->zd,"hscale","brite","hbbr","0.3|3.0|0.001|1.0","expand|space=3");

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

   zdialog_run(CEF->zd,select_paste_dialog_event,"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, cchar *event)
{
   using namespace sa_diskfile;

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

   int      ww, hh;
   PXM      *pxm_temp;

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

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

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

      if (! sa_calced) sa_edgecalc();                                            //  update edge distance data

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

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

      PXM_free(sacpR_pxm);                                                       //  free prior if any

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

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

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

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

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

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

   Fpaint2();
   return 1;
}


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

void select_paste_mousefunc()
{
   using namespace sa_diskfile;

   void  select_paste_image();

   int            mx1, my1, mx2, my2;
   static int     mdx0, mdy0, mdx1, mdy1;

   if (LMclick) {                                                                //  left mouse click
      LMclick = 0;
      sacp_orgx = Mxclick - sacpR_ww / 2;                                        //  position image at mouse
      sacp_orgy = Myclick - sacpR_hh / 2;
      select_paste_image();
   }

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

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

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

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

   mx1 = mdx1;                                                                   //  drag start
   my1 = mdy1;
   mx2 = Mxdrag;                                                                 //  drag position
   my2 = Mydrag;
   mdx1 = mx2;                                                                   //  next drag start
   mdy1 = my2;
   Mxdrag = Mydrag = 0;

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

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


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

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

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

   if (sacp_porg)                                                                //  prior area overlap rectangle
   {
      nc = E1pxm->nc;
      pcc = nc * sizeof(float);

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

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

   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                19.0

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

   sa_stat = 1;
   sa_mode = mode_mouse;                                                         //  equivalent select by mouse area
   sa_fww = Fpxb->ww;
   sa_fhh = Fpxb->hh;
   sa_finish_auto();                                                             //  will find new edge pixels
   sa_show(0,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    maxrgb, f1, f2;
   int      ii, dist;

   if (! sa_calced) sa_edgecalc();                                               //  update edge distance data

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

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

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

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

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

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

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

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

   return;
}


