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

   Fotoxx      edit photos and manage collections

   Copyright 2007 2008 2009 2010 2011 2012 Michael Cornelison
   Source URL: http://kornelix.squarespace.com/fotoxx
   Contact: kornelix2@googlemail.com
   
   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program. If not, see http://www.gnu.org/licenses/.

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

#define EX extern                                                          //  enable extern declarations
#include "fotoxx.h"


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

   Fotoxx image edit - transform functions

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


//  rotate image through any arbitrary angle

double      rotate_angle = 0;                                              //  E3 rotatation vs. F
double      rotate_delta = 0;
int         rotate_trim = 0;

editfunc    EFrotate;


void m_rotate(GtkWidget *, cchar *menu)                                    //  menu function
{
   int    rotate_dialog_event(zdialog *zd, cchar *event);
   void * rotate_thread(void *);
   void   rotate_mousefunc();
   
   cchar  *rotmess = ZTX("Use buttons or drag right edge with mouse");

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

   EFrotate.funcname = "rotate";
   EFrotate.Fprev = 1;                                                     //  use preview
   EFrotate.threadfunc = rotate_thread;                                    //  thread function
   EFrotate.mousefunc = rotate_mousefunc;                                  //  mouse function
   if (! edit_setup(EFrotate)) return;                                     //  setup edit

   zdialog *zd = zdialog_new(ZTX("Rotate Image"),mWin,Bdone,Bcancel,null);
   EFrotate.zd = zd;

   zdialog_add_widget(zd,"label","labrot","dialog",ZTX(rotmess),"space=5");
   zdialog_add_widget(zd,"label","labdeg","dialog",ZTX("degrees"),"space=5");
   zdialog_add_widget(zd,"hbox","hb1","dialog",0,"homog|space=5");
   zdialog_add_widget(zd,"vbox","vb1","hb1",0,"space=5");
   zdialog_add_widget(zd,"vbox","vb2","hb1",0,"space=5");
   zdialog_add_widget(zd,"vbox","vb3","hb1",0,"space=5");
   zdialog_add_widget(zd,"vbox","vb4","hb1",0,"space=5");
   zdialog_add_widget(zd,"button"," +0.1  ","vb1"," + 0.1 ");              //  button name is increment to use
   zdialog_add_widget(zd,"button"," -0.1  ","vb1"," - 0.1 ");
   zdialog_add_widget(zd,"button"," +1.0  ","vb2"," + 1   ");
   zdialog_add_widget(zd,"button"," -1.0  ","vb2"," - 1   ");
   zdialog_add_widget(zd,"button"," +10.0 ","vb3"," + 10  ");
   zdialog_add_widget(zd,"button"," -10.0 ","vb3"," - 10  ");
   zdialog_add_widget(zd,"button"," +90.0 ","vb4"," + 90  ");
   zdialog_add_widget(zd,"button"," -90.0 ","vb4"," - 90  ");
   zdialog_add_widget(zd,"hbox","hb2","dialog",0,"space=10");
   zdialog_add_widget(zd,"label","space","hb2",0,"expand");
   zdialog_add_widget(zd,"button","trim","hb2",ZTX("Trim"),"space=10");
   zdialog_add_widget(zd,"button","grid","hb2",ZTX("Grid"),"space=10");

   zdialog_help(zd,"rotate");                                              //  zdialog help topic              v.11.08
   zdialog_run(zd,rotate_dialog_event,"save");                             //  run dialog, parallel            v.11.07

   takeMouse(zd,rotate_mousefunc,dragcursor);                              //  connect mouse function          v.11.03
   rotate_angle = rotate_delta = rotate_trim = 0;

   load_grid(rotate_grid);                                                 //  load grid preferences           v.11.11

   return;
}


//  dialog event and completion callback function

int rotate_dialog_event(zdialog *zd, cchar * event)
{
   int         err, trim = 0;
   double      incr;
   char        text[40];
   
   if (zd->zstat)                                                          //  dialog complete
   {
      if (zd->zstat == 1) {
         rotate_delta = rotate_angle;                                      //  rotate main image
         rotate_angle = 0;
         edit_done(EFrotate);
      }
      else edit_cancel(EFrotate);                                          //  canceled

      rotate_angle = rotate_delta = rotate_trim = 0;
      
      save_grid(rotate_grid);                                              //  save grid preferences     v.11.11
      Fgrid = 0;                                                           //  grid off

      mwpaint2();
      return 0;
   }

   if (strEqu(event,"trim")) {
      rotate_trim = 1 - rotate_trim;                                       //  toggle trim button
      if (rotate_trim) zdialog_stuff(zd,"trim",ZTX("Undo Trim"));
      else zdialog_stuff(zd,"trim",ZTX("Trim"));
      trim = 1;                                                            //  v.10.3
   }
   
   if (strpbrk(event,"+-")) {
      err = convSD(event,incr);                                            //  button name is increment to use
      if (err) return 0;
      rotate_delta += incr;
   }

   if (rotate_delta || trim) {
      trim = 0;
      zdialog_stuff(zd,"labdeg","computing");
      signal_thread();                                                     //  do rotate in thread
      wait_thread_idle();
      snprintf(text,40,ZTX("degrees: %.1f"),rotate_angle);                 //  update dialog angle display  v.11.08
      zdialog_stuff(zd,"labdeg",text);
   }

   if (strEqu(event,"grid")) m_gridlines(0,0);                             //  grid dialog           v.11.11
   
   if (strstr("KB G KB g",event))                                          //  G key, toggle grid    v.11.11
      toggle_grid(2);

   return 1;
}


//  rotate mouse function - drag right edge of image up/down for rotation

void rotate_mousefunc()
{
   static int     mpx0 = 0, mpy0 = 0;
   static int     mpy1, mpy2, dist;
   zdialog        *zd = EFrotate.zd;

   if (! Mxdrag && ! Mydrag) return;                                       //  no drag underway
   if (Mxdrag < 0.8 * E3ww) return;                                        //  not right edge of image

   if (Mxdown != mpx0 || Mydown != mpy0) {
      mpx0 = Mxdown;                                                       //  new drag started
      mpy0 = mpy1 = Mydown;
   }
   
   mpy2 = Mydrag;
   dist = mpy2 - mpy1;                                                     //  drag distance
   mpy1 = mpy2;                                                            //  reset origin for next time
   if (! dist) return;

   rotate_delta = 30.0 * dist / E3ww;                                      //  convert to angle
   rotate_dialog_event(zd,"mouse");
   return;
}


//  rotate thread function

void * rotate_thread(void *)
{
   int         px3, py3, px9, py9;
   int         wwcut, hhcut, ww, hh;
   double      trim_angle, radians;
   uint16      *pix3, *pix9;
   
   while (true)
   {
      thread_idle_loop();                                                  //  wait for work or exit request

      mutex_lock(&Fpixmap_lock);

      rotate_angle += rotate_delta;                                        //  accum. net rotation   
      rotate_delta = 0;                                                    //    from dialog widget
      
      if (rotate_angle >= 360) rotate_angle -=360;
      if (rotate_angle <= -360) rotate_angle +=360;
      if (fabs(rotate_angle) < 0.01) rotate_angle = 0;

      if (! rotate_angle) {
         PXM_free(E3pxm16);                                                //  E1 >> E3
         E3pxm16 = PXM_copy(E1pxm16);
         E3ww = E1ww;
         E3hh = E1hh;
         CEF->Fmod = 0;
      }
      
      if (rotate_angle) {
         PXM_free(E3pxm16);
         E3pxm16 = PXM_rotate(E1pxm16,rotate_angle);                       //  E3 is rotated E1
         E3ww = E3pxm16->ww;
         E3hh = E3pxm16->hh;
         CEF->Fmod = 1;
      }

      if (rotate_trim)
      {                                                                    //  auto trim
         trim_angle = fabs(rotate_angle);
         while (trim_angle > 45) trim_angle -= 90;
         radians = fabs(trim_angle / 57.296);
         wwcut = int(E3pxm16->hh * sin(radians) + 1);                      //  amount to trim
         hhcut = int(E3pxm16->ww * sin(radians) + 1);
         ww = E3pxm16->ww - 2 * wwcut;
         hh = E3pxm16->hh - 2 * hhcut;
         if (ww > 0 && hh > 0) {
            E9pxm16 = PXM_make(ww,hh,16);
            
            for (py3 = hhcut; py3 < E3hh-hhcut; py3++)                     //  E9 = trimmed E3
            for (px3 = wwcut; px3 < E3ww-wwcut; px3++)
            {
               px9 = px3 - wwcut;
               py9 = py3 - hhcut;
               pix3 = PXMpix(E3pxm16,px3,py3);
               pix9 = PXMpix(E9pxm16,px9,py9);
               pix9[0] = pix3[0];
               pix9[1] = pix3[1];
               pix9[2] = pix3[2];
            }

            PXM_free(E3pxm16);                                             //  E3 = E9
            E3pxm16 = E9pxm16;
            E9pxm16 = 0;
            E3ww = ww;
            E3hh = hh;
         }
      }
      
      mutex_unlock(&Fpixmap_lock);
      mwpaint2();                                                          //  update window
   }

   return 0;                                                               //  not executed, stop g++ warning
}


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

//  trim image - use mouse to select image region to retain

int         trimx1, trimy1, trimx2, trimy2;                                //  current trim rectangle
int         trimpx1, trimpy1, trimpx2, trimpy2;                            //  prior trim rectangle
double      trimR;                                                         //  trim ratio, width/height

void  trim_mousefunc();                                                    //  edit trim margins with mouse
void  trim_dialog();
void  trim_trim(int mode);                                                 //  show trim area, trim image

editfunc    EFtrim;


void m_trim(GtkWidget *, cchar *menu)
{
   zfuncs::F1_help_topic = "trim_image";                                   //  v.10.8

   EFtrim.funcname = "trim";
   EFtrim.mousefunc = trim_mousefunc;
   if (! edit_setup(EFtrim)) return;                                       //  setup edit
   
   if (! trimbuttons[0] || strEqu(trimbuttons[0],"undefined")) {
      trimbuttons[0] = strdupz("1:1",8);                                   //  default trim buttons   v.10.10.2
      trimbuttons[1] = strdupz("2:1",8);
      trimbuttons[2] = strdupz("3:2",8);
      trimbuttons[3] = strdupz("4:3",8);
      trimbuttons[4] = strdupz("16:9",8);
      trimbuttons[5] = strdupz(ZTX("gold"),8);

      trimratios[0] = strdupz("1:1",8);                                    //  default trim ratios    v.10.10.2
      trimratios[1] = strdupz("2:1",8);
      trimratios[2] = strdupz("3:2",8);
      trimratios[3] = strdupz("4:3",8);
      trimratios[4] = strdupz("16:9",8);
      trimratios[5] = strdupz("1.618:1",8);                                //  fix inverted ratio     v.10.10.3
   }

   if (strNeq(menu,"autotrim"))                                            //  if not auto-trim       v.11.12
   {
      if (trimsize[0] < iww && trimsize[1] < ihh) {                        //  use last trim size if within limits
         trimx1 = Iorgx + 0.5 * (iww - trimsize[0]);                       //  v.11.12
         trimx2 = trimx1 + trimsize[0];
         trimy1 = Iorgy + 0.5 * (ihh - trimsize[1]);
         trimy2 = trimy1 + trimsize[1];
      }
      else {                                                               //  else use 90% current dimensions
         trimx1 = Iorgx + 0.05 * iww;
         trimx2 = Iorgx + 0.95 * iww;
         trimy1 = Iorgy + 0.05 * ihh;
         trimy2 = Iorgy + 0.95 * ihh;
      }
   }

   trimpx1 = Iorgx;                                                        //  prior trim rectangle
   trimpx2 = Iorgx + iww;                                                  //    = 100% of image
   trimpy1 = Iorgy;
   trimpy2 = Iorgy + ihh;

   trimsize[0] = (trimx2 - trimx1);
   trimsize[1] = (trimy2 - trimy1);
   trimR = 1.0 * trimsize[0] / trimsize[1];

   trim_trim(0);                                                           //  show trim area in image
   trim_dialog();                                                          //  start dialog

   return;
}


//  dialog function is called from two places

void trim_dialog()
{
   int    trim_dialog_event(zdialog *zd, cchar *event);

   cchar       *trim_message = ZTX("Drag middle to move, drag corners to resize.");
   char        text[20];
   int         ii;

/**
      Drag middle to move, drag corners to resize

      width [___]  height [___]  ratio [___]                               //  width/height inputs    v.11.05
      [1:1] [2:1] [3:2] [4:3] [16:9] [gold] [invert]
      [x] Lock Ratio

                     [customize] [Done] [Cancel]
**/

   zdialog *zd = zdialog_new(ZTX("Trim Image"),mWin,ZTX("customize"),Bdone,Bcancel,null);
   EFtrim.zd = zd;

   zdialog_add_widget(zd,"label","lab1","dialog",trim_message,"space=8");
   zdialog_add_widget(zd,"hbox","hb1","dialog",0,"space=4");
   zdialog_add_widget(zd,"label","labW","hb1",ZTX("width"),"space=3");
   zdialog_add_widget(zd,"spin","width","hb1","20|9999|1|1000");
   zdialog_add_widget(zd,"label","space","hb1",0,"space=5");
   zdialog_add_widget(zd,"label","labH","hb1",ZTX("height"),"space=3");
   zdialog_add_widget(zd,"spin","height","hb1","20|9999|1|600");
   zdialog_add_widget(zd,"label","space","hb1",0,"space=5");
   zdialog_add_widget(zd,"label","labR","hb1",ZTX("ratio"),"space=3");
   zdialog_add_widget(zd,"label","ratio","hb1","1.67   ");
   zdialog_add_widget(zd,"hbox","hb2","dialog",0,"space=4");               //  ratio buttons
   zdialog_add_widget(zd,"hbox","hb3","dialog",0,"space=4");
   zdialog_add_widget(zd,"check","lock","hb3",ZTX("Lock Ratio"),"space=3");

   for (ii = 0; ii < 6; ii++)                                              //  6 custom buttons    v.10.10.2
      zdialog_add_widget(zd,"button",trimbuttons[ii],"hb2",trimbuttons[ii]);

   zdialog_add_widget(zd,"button","invert","hb2",ZTX("invert"));           //  [invert] button

   zdialog_stuff(zd,"width",trimsize[0]);                                  //  stuff width, height, ratio   v.11.05
   zdialog_stuff(zd,"height",trimsize[1]);
   sprintf(text,"%.2f  ",trimR);
   zdialog_stuff(zd,"ratio",text);

   zdialog_help(zd,"trim_image");                                          //  zdialog help topic        v.11.08
   zdialog_run(zd,trim_dialog_event,"save");                               //  run dialog - parallel     v.11.07
   takeMouse(zd,trim_mousefunc,dragcursor);                                //  connect mouse function    v.11.12
   return;
}


//  dialog event and completion callback function

int trim_dialog_event(zdialog *zd, cchar *event)                           //  overhauled    v.11.05
{
   void  trim_customize();

   static int  flip = 0;
   int         width, height, delta;
   int         ii, rlock;
   double      r1, r2, ratio = 0;
   char        text[20];
   cchar       *pp;

   if (zd->zstat)                                                          //  dialog complete
   {
      paint_toplines(2);                                                   //  erase rectangle outline         v.11.05

      if (zd->zstat == 1) {                                                //  customize buttons
         freeMouse();                                                      //  bugfix                          v.11.12
         zdialog_free(zd);                                                 //  kill trim dialog
         trim_customize();                                                 //  do customize dialog
         trim_trim(0);                                                     //  show trim area in image         v.11.05
         trim_dialog();                                                    //  restart trim dialog
         return 0;
      }

      if (zd->zstat != 2) {                                                //  cancel
         edit_cancel(EFtrim);
         return 0;
      }

      trimsize[0] = trimx2 - trimx1;                                       //  apply
      trimsize[1] = trimy2 - trimy1;

      trim_trim(1);                                                        //  do trim on image
      Fzoom = 0;                                                           //  v.11.01
      edit_done(EFtrim);
      return 0;
   }

   if (strEqu(event,"focus")) {
      takeMouse(zd,trim_mousefunc,dragcursor);                             //  connect mouse function       v.12.01
      return 0;
   }
   
   if (strstr("width height",event))                                       //  get direct width/height inputs
   {                                                                       //                               v.11.05
      zdialog_fetch(zd,"width",width);
      zdialog_fetch(zd,"height",height);
      zdialog_fetch(zd,"lock",rlock);                                      //  lock ratio on/off

      
      if (strEqu(event,"width")) {                                         //  v.11.05
         if (width > iww) width = iww;
         if (rlock) {                                                      //  ratio locked
            height = width / trimR + 0.5;                                  //  try to keep ratio
            if (height > ihh) height = ihh;
            zdialog_stuff(zd,"height",height);
         }
      }

      if (strEqu(event,"height")) {
         if (height > ihh) height = ihh;
         if (rlock) {
            width = height * trimR + 0.5;
            if (width > iww) width = iww;
            zdialog_stuff(zd,"width",width);
         }
      }

      flip = 1 - flip;                                                     //  alternates 0, 1, 0, 1 ...

      delta = width - trimsize[0];

      if (delta > 0) {                                                     //  increase width
         trimx1 = trimx1 - delta / 2;                                      //  left and right sides equally
         trimx2 = trimx2 + delta / 2;
         if (delta % 2) {                                                  //  if increase is odd
            trimx1 = trimx1 - flip;                                        //    add 1 alternatively to each side
            trimx2 = trimx2 + 1 - flip;
         }
      }
      
      if (delta < 0) {                                                     //  decrease width
         trimx1 = trimx1 - delta / 2;
         trimx2 = trimx2 + delta / 2;
         if (delta % 2) {
            trimx1 = trimx1 + flip;
            trimx2 = trimx2 - 1 + flip;
         }
      }

      delta = height - trimsize[1];

      if (delta > 0) {                                                     //  increase width
         trimy1 = trimy1 - delta / 2;                                      //  left and right sides equally
         trimy2 = trimy2 + delta / 2;
         if (delta % 2) {                                                  //  if increase is odd
            trimy1 = trimy1 - flip;                                        //    add 1 alternatively to each side
            trimy2 = trimy2 + 1 - flip;
         }
      }
      
      if (delta < 0) {                                                     //  decrease width
         trimy1 = trimy1 - delta / 2;
         trimy2 = trimy2 + delta / 2;
         if (delta % 2) {
            trimy1 = trimy1 + flip;
            trimy2 = trimy2 - 1 + flip;
         }
      }

      if (trimx1 < Iorgx) trimx1 = Iorgx;
      if (trimx2 > Iorgx + iww) trimx2 = Iorgx + iww;
      if (trimy1 < Iorgy) trimy1 = Iorgy;
      if (trimy2 > Iorgy + ihh) trimy2 = Iorgy + ihh;
      
      width = trimx2 - trimx1;                                             //  new width and height
      height = trimy2 - trimy1;
      
      zdialog_stuff(zd,"width",width);                                     //  update dialog values
      zdialog_stuff(zd,"height",height);

      trimsize[0] = width;                                                 //  new rectangle dimensions
      trimsize[1] = height;

      if (! rlock)                                                         //  set new ratio if not locked
         trimR = 1.0 * trimsize[0] / trimsize[1];

      sprintf(text,"%.2f  ",trimR);                                        //  stuff new ratio
      zdialog_stuff(zd,"ratio",text);

      trim_trim(0);                                                        //  show trim area in image
      return 0;
   }

   for (ii = 0; ii < 6; ii++)                                              //  trim ratio buttons
      if (strEqu(event,trimbuttons[ii])) break;
   if (ii < 6) {
      r1 = r2 = ratio = 0;
      pp = strField(trimratios[ii],':',1);
      if (pp) r1 = atof(pp);
      pp = strField(trimratios[ii],':',2);
      if (pp) r2 = atof(pp);
      if (r1 > 0 && r2 > 0) ratio = r1/r2;
      if (ratio < 0.1 || ratio > 10) ratio = 1.0;
      if (! ratio) return 0;
      zdialog_stuff(zd,"lock",1);                                          //  assume lock is wanted
      trimR = ratio;
   }
   
   if (strEqu(event,"invert"))                                             //  invert ratio button
      if (trimR) ratio = 1.0 / trimR;
   
   if (ratio)                                                              //  ratio was changed
   {
      trimR = ratio;

      if (trimx2 - trimx1 > trimy2 - trimy1)
         trimy2 = trimy1 + (trimx2 - trimx1) / trimR;                      //  adjust smaller dimension
      else 
         trimx2 = trimx1 + (trimy2 - trimy1) * trimR;

      if (trimx2 > Iorgx + iww) {                                          //  if off the right edge,
         trimx2 = Iorgx + iww;                                             //  adjust height
         trimy2 = trimy1 + (trimx2 - trimx1) / trimR;
      }

      if (trimy2 > Iorgy + ihh) {                                          //  if off the bottom edge,
         trimy2 = Iorgy + ihh;                                             //  adjust width
         trimx2 = trimx1 + (trimy2 - trimy1) * trimR;
      }

      trimsize[0] = trimx2 - trimx1;                                       //  new rectangle dimensions
      trimsize[1] = trimy2 - trimy1;

      zdialog_stuff(zd,"width",trimsize[0]);                               //  stuff width, height, ratio   v.11.05
      zdialog_stuff(zd,"height",trimsize[1]);
      sprintf(text,"%.2f  ",trimR);
      zdialog_stuff(zd,"ratio",text);

      trim_trim(0);                                                        //  show trim area in image
      return 0;
   }
   
   return 0;
}


//  trim mouse function

void trim_mousefunc()
{
   int         mpx, mpy, xdrag, ydrag, rlock;
   int         corner, chop, moveall = 0;
   int         dx, dy, dd, d1, d2, d3, d4;
   char        text[20];
   double      drr;
   zdialog     *zd = EFtrim.zd;
   
   if (LMclick || Mxdrag || Mydrag)                                        //  mouse click or drag
   {
      if (LMclick) {
         mpx = Mxclick;                                                    //  click
         mpy = Myclick;
         xdrag = ydrag = 0;
         LMclick = 0;
      }
      else {
         mpx = Mxdrag;                                                     //  drag
         mpy = Mydrag;
         xdrag = Mxdrag - Mxdown;
         ydrag = Mydrag - Mydown;
         Mxdown = Mxdrag;                                                  //  reset drag origin
         Mydown = Mydrag;
      }
      
      if (Mxdrag || Mydrag) {
         moveall = 1;
         dd = 0.1 * (trimx2 - trimx1);                                     //  test if mouse is in the broad
         if (mpx < trimx1 + dd) moveall = 0;                               //    middle of the rectangle
         if (mpx > trimx2 - dd) moveall = 0;
         dd = 0.1 * (trimy2 - trimy1);
         if (mpy < trimy1 + dd) moveall = 0;
         if (mpy > trimy2 - dd) moveall = 0;
      }

      if (moveall) {                                                       //  yes, move the whole rectangle
         trimx1 += xdrag;
         trimx2 += xdrag;
         trimy1 += ydrag;
         trimy2 += ydrag;
         corner = 0;
      }

      else {                                                               //  no, find closest corner
         dx = mpx - trimx1;
         dy = mpy - trimy1;
         d1 = sqrt(dx*dx + dy*dy);                                         //  distance from NW corner
         
         dx = mpx - trimx2;
         dy = mpy - trimy1;
         d2 = sqrt(dx*dx + dy*dy);
         
         dx = mpx - trimx2;
         dy = mpy - trimy2;
         d3 = sqrt(dx*dx + dy*dy);
         
         dx = mpx - trimx1;
         dy = mpy - trimy2;
         d4 = sqrt(dx*dx + dy*dy);
         
         corner = 1;                                                       //  NW
         dd = d1;
         if (d2 < dd) { corner = 2; dd = d2; }                             //  NE
         if (d3 < dd) { corner = 3; dd = d3; }                             //  SE
         if (d4 < dd) { corner = 4; dd = d4; }                             //  SW
         
         if (corner == 1) { trimx1 = mpx; trimy1 = mpy; }                  //  move this corner to mouse
         if (corner == 2) { trimx2 = mpx; trimy1 = mpy; }
         if (corner == 3) { trimx2 = mpx; trimy2 = mpy; }
         if (corner == 4) { trimx1 = mpx; trimy2 = mpy; }
      }
      
      if (trimx1 < Iorgx) trimx1 = Iorgx;                                  //  keep within visible area     v.11.05
      if (trimx2 > Iorgx + iww) trimx2 = Iorgx + iww;
      if (trimy1 < Iorgy) trimy1 = Iorgy;
      if (trimy2 > Iorgy + ihh) trimy2 = Iorgy + ihh;

      if (trimx1 > trimx2-10) trimx1 = trimx2-10;                          //  sanity limits
      if (trimy1 > trimy2-10) trimy1 = trimy2-10;

      zdialog_fetch(zd,"lock",rlock);                                      //  w/h ratio locked
      if (rlock && corner) {
         if (corner < 3)                                                   //  bugfix   v.10.3.1
            trimy2 = trimy1 + 1.0 * (trimx2 - trimx1) / trimR;
         else
            trimy1 = trimy2 - 1.0 * (trimx2 - trimx1) / trimR;
      }

      chop = 0;
      if (trimx1 < Iorgx) {                                                //  look for off the edge   v.10.1
         trimx1 = Iorgx; 
         chop = 1; 
      }

      if (trimx2 > Iorgx + iww) {                                          //  after corner move    v.10.3.1
         trimx2 = Iorgx + iww;
         chop = 2; 
      }

      if (trimy1 < Iorgy) { 
         trimy1 = Iorgy; 
         chop = 3; 
      }

      if (trimy2 > Iorgy + ihh) { 
         trimy2 = Iorgy + ihh;
         chop = 4; 
      }

      if (rlock && chop) {                                                 //  keep ratio if off edge   v.10.1
         if (chop < 3)
            trimy2 = trimy1 + 1.0 * (trimx2 - trimx1) / trimR;
         else
            trimx2 = trimx1 + 1.0 * (trimy2 - trimy1) * trimR;
      }

      if (trimx1 > trimx2-10) trimx1 = trimx2-10;                          //  sanity limits
      if (trimy1 > trimy2-10) trimy1 = trimy2-10;

      if (trimx1 < Iorgx) trimx1 = Iorgx;                                  //  keep within visible area     v.11.05
      if (trimx2 > Iorgx + iww) trimx2 = Iorgx + iww;
      if (trimy1 < Iorgy) trimy1 = Iorgy;
      if (trimy2 > Iorgy + ihh) trimy2 = Iorgy + ihh;

      trimsize[0] = trimx2 - trimx1;                                       //  new rectangle dimensions
      trimsize[1] = trimy2 - trimy1;

      drr = 1.0 * trimsize[0] / trimsize[1];                               //  new w/h ratio
      if (! rlock) trimR = drr;

      zdialog_stuff(zd,"width",trimsize[0]);                               //  stuff width, height, ratio   v.11.05
      zdialog_stuff(zd,"height",trimsize[1]);
      sprintf(text,"%.2f  ",trimR);
      zdialog_stuff(zd,"ratio",text);
      
      trim_trim(0);                                                        //  show trim area in image
   }

   return;
}


//  darken image pixels outside of current trim margins
//  messy logic: update pixmaps only for changed pixels to increase speed

void trim_trim(int mode)
{
   int      ox1, oy1, ox2, oy2;                                            //  outer trim rectangle
   int      nx1, ny1, nx2, ny2;                                            //  inner trim rectangle
   int      px, py, px1, py1, px2, py2;
   uint16   *pix1, *pix3;
 
   if (mode == 1)                                                          //  do the final trim
   {
      mutex_lock(&Fpixmap_lock);
      PXM_free(E3pxm16);
      E3pxm16 = PXM_make(trimsize[0],trimsize[1],16);                      //  new pixmap with requested size
      E3ww = trimsize[0];
      E3hh = trimsize[1];
      
      for (py1 = trimy1; py1 < trimy2; py1++)                              //  copy pixels
      for (px1 = trimx1; px1 < trimx2; px1++)
      {
         px2 = px1 - trimx1;
         py2 = py1 - trimy1;
         pix1 = PXMpix(E1pxm16,px1,py1);
         pix3 = PXMpix(E3pxm16,px2,py2);
         pix3[0] = pix1[0];
         pix3[1] = pix1[1];
         pix3[2] = pix1[2];
      }

      CEF->Fmod = 1;
      mutex_unlock(&Fpixmap_lock);
      mwpaint2();                                                          //  update window
      return;
   }

   if (trimx1 < Iorgx) trimx1 = Iorgx;                                     //  keep within visible area     v.11.05
   if (trimx2 > Iorgx + iww) trimx2 = Iorgx + iww;
   if (trimy1 < Iorgy) trimy1 = Iorgy;
   if (trimy2 > Iorgy + ihh) trimy2 = Iorgy + ihh;

   if (trimpx1 < trimx1) ox1 = trimpx1;                                    //  outer rectangle
   else ox1 = trimx1;
   if (trimpx2 > trimx2) ox2 = trimpx2;
   else ox2 = trimx2;
   if (trimpy1 < trimy1) oy1 = trimpy1;
   else oy1 = trimy1;
   if (trimpy2 > trimy2) oy2 = trimpy2;
   else oy2 = trimy2;

   if (trimpx1 > trimx1) nx1 = trimpx1;                                    //  inner rectangle
   else nx1 = trimx1;
   if (trimpx2 < trimx2) nx2 = trimpx2;
   else nx2 = trimx2;
   if (trimpy1 > trimy1) ny1 = trimpy1;
   else ny1 = trimy1;
   if (trimpy2 < trimy2) ny2 = trimpy2;
   else ny2 = trimy2;
   
   trimpx1 = trimx1;                                                       //  set prior trim rectangle 
   trimpx2 = trimx2;                                                       //    from current trim rectangle
   trimpy1 = trimy1;
   trimpy2 = trimy2;
   
   if (ox1 > 0) ox1--;
   if (ox2 < E3ww-1) ox2++;
   if (oy1 > 0) oy1--;
   if (oy2 < E3hh-1) oy2++;

   for (py = oy1; py < ny1; py++)                                          //  top band of pixels
   for (px = ox1; px < ox2; px++) 
   {
      pix1 = PXMpix(E1pxm16,px,py);
      pix3 = PXMpix(E3pxm16,px,py);
      if (px < trimx1 || px > trimx2 || py < trimy1 || py > trimy2) {
         pix3[0] = pix1[0] / 2;                                            //  outside trim margins
         pix3[1] = pix1[1] / 2;                                            //  50% brightness
         pix3[2] = pix1[2] / 2;
      }
      else {
         pix3[0] = pix1[0];                                                //  inside trim margins
         pix3[1] = pix1[1];                                                //  100% brightness
         pix3[2] = pix1[2];
      }
   }

   for (py = oy1; py < oy2; py++)                                          //  right band
   for (px = nx2; px < ox2; px++)
   {
      pix1 = PXMpix(E1pxm16,px,py);
      pix3 = PXMpix(E3pxm16,px,py);
      if (px < trimx1 || px > trimx2 || py < trimy1 || py > trimy2) {
         pix3[0] = pix1[0] / 2;
         pix3[1] = pix1[1] / 2;
         pix3[2] = pix1[2] / 2;
      }
      else {
         pix3[0] = pix1[0];
         pix3[1] = pix1[1];
         pix3[2] = pix1[2];
      }
   }

   for (py = ny2; py < oy2; py++)                                          //  bottom band
   for (px = ox1; px < ox2; px++)
   {
      pix1 = PXMpix(E1pxm16,px,py);
      pix3 = PXMpix(E3pxm16,px,py);
      if (px < trimx1 || px > trimx2 || py < trimy1 || py > trimy2) {
         pix3[0] = pix1[0] / 2;
         pix3[1] = pix1[1] / 2;
         pix3[2] = pix1[2] / 2;
      }
      else {
         pix3[0] = pix1[0];
         pix3[1] = pix1[1];
         pix3[2] = pix1[2];
      }
   }

   for (py = oy1; py < oy2; py++)                                          //  left band
   for (px = ox1; px < nx1; px++)
   {
      pix1 = PXMpix(E1pxm16,px,py);
      pix3 = PXMpix(E3pxm16,px,py);
      if (px < trimx1 || px > trimx2 || py < trimy1 || py > trimy2) {
         pix3[0] = pix1[0] / 2;
         pix3[1] = pix1[1] / 2;
         pix3[2] = pix1[2] / 2;
      }
      else {
         pix3[0] = pix1[0];
         pix3[1] = pix1[1];
         pix3[2] = pix1[2];
      }
   }

   mwpaint3(ox1,oy1,ox2-ox1+1,ny1-oy1+1);                                  //  4 updated rectangles
   mwpaint3(nx2,oy1,ox2-nx2+1,oy2-oy1+1);                                  //  ww+1, hh+1                v.11.05
   mwpaint3(ox1,ny2,ox2-ox1+1,oy2-ny2+1);
   mwpaint3(ox1,oy1,nx1-ox1+1,oy2-oy1+1);

   Ntoplines = 4;                                                          //  outline trim rectangle    v.11.05
   toplinex1[0] = trimx1;
   topliney1[0] = trimy1;
   toplinex2[0] = trimx2;
   topliney2[0] = trimy1;
   toplinex1[1] = trimx2;
   topliney1[1] = trimy1;
   toplinex2[1] = trimx2;
   topliney2[1] = trimy2;
   toplinex1[2] = trimx2;
   topliney1[2] = trimy2;
   toplinex2[2] = trimx1;
   topliney2[2] = trimy2;
   toplinex1[3] = trimx1;
   topliney1[3] = trimy2;
   toplinex2[3] = trimx1;
   topliney2[3] = trimy1;
   paint_toplines(1);

   return;
}


//  dialog to get custom trim button names and corresponding ratios

void trim_customize()
{
   char        text[20], blab[8], rlab[8];
   double      r1, r2, ratio;
   cchar       *pp;
   int         ii, zstat;
   
   zdialog *zd = zdialog_new(ZTX("Trim Buttons"),mWin,Bdone,Bcancel,null); //  start dialog
   zdialog_add_widget(zd,"hbox","hbb","dialog",0,"homog|space=5");
   zdialog_add_widget(zd,"hbox","hbr","dialog",0,"homog|space=5");
   
   strcpy(blab,"butt-0");
   strcpy(rlab,"ratio-0");
   
   for (ii = 0; ii < 6; ii++) 
   {
      blab[5] = '0' + ii;
      rlab[6] = '0' + ii;
      zdialog_add_widget(zd,"entry",blab,"hbb",trimbuttons[ii],"scc=6");
      zdialog_add_widget(zd,"entry",rlab,"hbr",trimratios[ii],"scc=6");
   }
   
   zdialog_run(zd,0,"mouse");                                              //  run dialog       v.11.07
   zstat = zdialog_wait(zd);

   if (zstat == 1)                                                         //  apply
   {
      for (ii = 0; ii < 6; ii++)                                           //  get custom button names
      {
         blab[5] = '0' + ii;
         zdialog_fetch(zd,blab,text,12);
         strTrim2(text);
         if (! *text) continue;
         zfree(trimbuttons[ii]);
         trimbuttons[ii] = strdupz(text,0,"trim");
      }

      for (ii = 0; ii < 6; ii++)                                           //  get custom ratios
      {
         rlab[6] = '0' + ii;
         zdialog_fetch(zd,rlab,text,12);
         strTrim2(text);
         r1 = r2 = ratio = 0;
         pp = strField(text,':',1);
         if (! pp) continue;
         r1 = atof(pp);
         pp = strField(text,':',2);
         if (! pp) continue;
         r2 = atof(pp);
         if (r1 > 0 && r2 > 0) ratio = r1/r2;
         if (ratio < 0.1 || ratio > 10) continue;
         zfree(trimratios[ii]);
         trimratios[ii] = strdupz(text,0,"trim");
      }
   }

   zdialog_free(zd);                                                       //  kill dialog
   return;
}


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

//  auto-trim image - set trim rectangle to exclude black margins
//                    left over from rotate or warp functions

void m_autotrim(GtkWidget *, cchar *)                                      //  new v.11.12
{
   int      px1, py1, px3, py3;
   int      qx1, qy1, qx3, qy3;
   int      qx, qy, step1, step2;
   int      area1, area2, Fgrow = 0;
   uint8    *ppix;

   if (! Fpxm8) return;
   
   px1 = 0.4 * Fww;                                                        //  select small rectangle in the middle
   py1 = 0.4 * Fhh;
   px3 = 0.6 * Fww;
   py3 = 0.6 * Fhh;
   
   step1 = 0.05 * (Fww + Fhh);                                             //  start with big search steps
   step2 = 0.2 * step1;
   
   while (true)
   {
      while (true)
      {
         Fgrow = 0;

         area1 = (px3 - px1) * (py3 - py1);                                //  area of current selection rectangle
         area2 = area1;

         for (qx1 = px1-step1; qx1 <= px1+step1; qx1 += step2)             //  loop, vary NW and SE corners
         for (qy1 = py1-step1; qy1 <= py1+step1; qy1 += step2) 
         for (qx3 = px3-step1; qx3 <= px3+step1; qx3 += step2) 
         for (qy3 = py3-step1; qy3 <= py3+step1; qy3 += step2) 
         {
            if (qx1 < 0) continue;                                         //  check image limits
            if (qy1 < 0) continue;
            if (qx1 > 0.5 * Fww) continue;
            if (qy1 > 0.5 * Fhh) continue;
            if (qx3 > Fww-1) continue;
            if (qy3 > Fhh-1) continue;
            if (qx3 < 0.5 * Fww) continue;
            if (qy3 < 0.5 * Fhh) continue;
            
            ppix = (uint8 *) Fpxm8->bmp + (qy1 * Fww + qx1) * 3;           //  check 4 corners are not 
            if (ppix[0] + ppix[1] + ppix[2] == 0) continue;                //    in the black margin zones
            ppix = (uint8 *) Fpxm8->bmp + (qy1 * Fww + qx3) * 3;
            if (ppix[0] + ppix[1] + ppix[2] == 0) continue;
            ppix = (uint8 *) Fpxm8->bmp + (qy3 * Fww + qx3) * 3;
            if (ppix[0] + ppix[1] + ppix[2] == 0) continue;
            ppix = (uint8 *) Fpxm8->bmp + (qy3 * Fww + qx1) * 3;
            if (ppix[0] + ppix[1] + ppix[2] == 0) continue;
            
            area2 = (qx3 - qx1) * (qy3 - qy1);                             //  look for larger enclosed area
            if (area2 <= area1) continue;
            
            for (qx = qx1; qx < qx3; qx++) {                               //  check 4 sides don't intersect
               ppix = (uint8 *) Fpxm8->bmp + (qy1 * Fww + qx) * 3;         //    the black margin zones
               if (ppix[0] + ppix[1] + ppix[2] == 0) break;
               ppix = (uint8 *) Fpxm8->bmp + (qy3 * Fww + qx) * 3;
               if (ppix[0] + ppix[1] + ppix[2] == 0) break;
            }
            if (qx < qx3) continue;

            for (qy = qy1; qy < qy3; qy++) {
               ppix = (uint8 *) Fpxm8->bmp + (qy * Fww + qx1) * 3;
               if (ppix[0] + ppix[1] + ppix[2] == 0) break;
               ppix = (uint8 *) Fpxm8->bmp + (qy * Fww + qx3) * 3;
               if (ppix[0] + ppix[1] + ppix[2] == 0) break;
            }
            if (qy < qy3) continue;

            Fgrow = 1;                                                     //  successfully grew the rectangle
            px1 = qx1;                                                     //  new bigger rectangle coordinates
            py1 = qy1;                                                     //    for the next search iteration
            px3 = qx3;
            py3 = qy3;
            break;
         }
         
         if (! Fgrow) break;
      }
      
      if (step2 == 1) break;                                               //  done

      step1 = 0.6 * step1;                                                 //  reduce search step size
      step2 = 0.2 * step1;
      if (step2 < 1) step2 = 1;
   }

   trimx1 = px1;                                                           //  set parameters for trim function
   trimx2 = px3;
   trimy1 = py1;
   trimy2 = py3;

   m_trim(0,"autotrim");                                                   //  perform regular trim with
   return;                                                                 //    pre-set margins
}


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

//  Resize (rescale) image
//
//  Output pixels are composites of input pixels, e.g. 2/3 size means 
//  that 3x3 input pixels are mapped into 2x2 output pixels, and an 
//  image size of 1000 x 600 becomes 667 x 400.

int      resize_ww0, resize_hh0;                                           //  original size
int      resize_ww1, resize_hh1;                                           //  new size

editfunc    EFresize;

void m_resize(GtkWidget *, cchar *)
{
   int    resize_dialog_event(zdialog *zd, cchar *event);
   void * resize_thread(void *);

   cchar  *lockmess = ZTX("Lock aspect ratio");

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

   EFresize.funcname = "resize";
   EFresize.threadfunc = resize_thread;                                    //  thread function
   if (! edit_setup(EFresize)) return;                                     //  setup edit

   zdialog *zd = zdialog_new(ZTX("Resize Image"),mWin,Bdone,Bcancel,null);
   EFresize.zd = zd;

   zdialog_add_widget(zd,"hbox","hb1","dialog",0,"space=10");
   zdialog_add_widget(zd,"vbox","vb11","hb1",0,"homog|space=5");
   zdialog_add_widget(zd,"vbox","vb12","hb1",0,"homog|space=5");
   zdialog_add_widget(zd,"vbox","vb13","hb1",0,"homog|space=5");
   zdialog_add_widget(zd,"label","placeholder","vb11",0);             //            pixels     percent
   zdialog_add_widget(zd,"label","labw","vb11",Bwidth);               //    width   [______]   [______]
   zdialog_add_widget(zd,"label","labh","vb11",Bheight);              //    height  [______]   [______]
   zdialog_add_widget(zd,"label","labpix","vb12","pixels");           //
   zdialog_add_widget(zd,"spin","wpix","vb12","20|9999|1|20");        //    presets [2/3] [1/2] [1/3] [1/4] [Prev]
   zdialog_add_widget(zd,"spin","hpix","vb12","20|9999|1|20");        //
   zdialog_add_widget(zd,"label","labpct","vb13",Bpercent);           //    [_] lock aspect ratio
   zdialog_add_widget(zd,"spin","wpct","vb13","1|500|0.1|100");       //
   zdialog_add_widget(zd,"spin","hpct","vb13","1|500|0.1|100");       //                      [done] [cancel]
   zdialog_add_widget(zd,"hbox","hb2","dialog",0,"space=5");
   zdialog_add_widget(zd,"label","preset","hb2",Bpresets,"space=5");
   zdialog_add_widget(zd,"button","b 3/4","hb2","3/4");
   zdialog_add_widget(zd,"button","b 2/3","hb2","2/3");
   zdialog_add_widget(zd,"button","b 1/2","hb2","1/2");
   zdialog_add_widget(zd,"button","b 1/3","hb2","1/3");
   zdialog_add_widget(zd,"button","b 1/4","hb2","1/4");
   zdialog_add_widget(zd,"button","prev","hb2",ZTX("Prev"));
   zdialog_add_widget(zd,"hbox","hb3","dialog",0,"space=5");
   zdialog_add_widget(zd,"check","lock","hb3",lockmess,"space=5");

   resize_ww0 = Fpxm16->ww;                                                //  original width, height
   resize_hh0 = Fpxm16->hh;

   zdialog_stuff(zd,"wpix",resize_ww0);
   zdialog_stuff(zd,"hpix",resize_hh0);
   zdialog_stuff(zd,"lock",1);

   zdialog_help(zd,"resize");                                              //  zdialog help topic        v.11.08
   zdialog_run(zd,resize_dialog_event,"save");                             //  run dialog                v.11.07
   zdialog_wait(zd);                                                       //  wait for completion
   return;
}


//  dialog event and completion callback function

int resize_dialog_event(zdialog *zd, cchar * event)
{
   int         lock;
   double      wpct1, hpct1;
   
   if (zd->zstat)                                                          //  dialog complete
   {
      if (zd->zstat != 1) {                                                //  user cancel
         edit_cancel(EFresize);
         return 0;
      }

      editresize[0] = resize_ww1;                                          //  remember size used     v.10.10
      editresize[1] = resize_hh1;

      Fzoom = 0;                                                           //  v.11.01
      edit_done(EFresize);
      return 0;
   }
   
   if (strnEqu(event,"KB",2)) return 0;                                    //  ignore KB inputs       v.11.11

   zdialog_fetch(zd,"wpix",resize_ww1);                                    //  get all widget values
   zdialog_fetch(zd,"hpix",resize_hh1);
   zdialog_fetch(zd,"wpct",wpct1);
   zdialog_fetch(zd,"hpct",hpct1);
   zdialog_fetch(zd,"lock",lock);
   
   if (strEqu(event,"b 3/4")) {
      resize_ww1 = (3 * resize_ww0 + 3) / 4;
      resize_hh1 = (3 * resize_hh0 + 3) / 4;
   }

   if (strEqu(event,"b 2/3")) {
      resize_ww1 = (2 * resize_ww0 + 2) / 3;
      resize_hh1 = (2 * resize_hh0 + 2) / 3;
   }

   if (strEqu(event,"b 1/2")) {
      resize_ww1 = (resize_ww0 + 1) / 2;
      resize_hh1 = (resize_hh0 + 1) / 2;
   }

   if (strEqu(event,"b 1/3")) {
      resize_ww1 = (resize_ww0 + 2) / 3;
      resize_hh1 = (resize_hh0 + 2) / 3;
   }

   if (strEqu(event,"b 1/4")) {
      resize_ww1 = (resize_ww0 + 3) / 4;
      resize_hh1 = (resize_hh0 + 3) / 4;
   }

   if (strEqu(event,"prev")) {                                             //  use previous resize values   v.10.10
      resize_ww1 = editresize[0];
      resize_hh1 = editresize[1];
   }

   if (strEqu(event,"wpct"))                                               //  width % - set pixel width
      resize_ww1 = int(wpct1 / 100.0 * resize_ww0 + 0.5);

   if (strEqu(event,"hpct"))                                               //  height % - set pixel height
      resize_hh1 = int(hpct1 / 100.0 * resize_hh0 + 0.5);
   
   if (lock && event[0] == 'w')                                            //  preserve width/height ratio
      resize_hh1 = int(resize_ww1 * (1.0 * resize_hh0 / resize_ww0) + 0.5);
   if (lock && event[0] == 'h') 
      resize_ww1 = int(resize_hh1 * (1.0 * resize_ww0 / resize_hh0) + 0.5);
   
   hpct1 = 100.0 * resize_hh1 / resize_hh0;                                //  set percents to match pixels
   wpct1 = 100.0 * resize_ww1 / resize_ww0;
   
   zdialog_stuff(zd,"wpix",resize_ww1);                                    //  index all widget values
   zdialog_stuff(zd,"hpix",resize_hh1);
   zdialog_stuff(zd,"wpct",wpct1);
   zdialog_stuff(zd,"hpct",hpct1);

   signal_thread();                                                        //  do the update, don't wait for idle
   return 1;
}


//  do the resize job

void * resize_thread(void *)                                               //  v.10.12
{
   CEF->Fmod = 0;
   
   while (true)
   {
      thread_idle_loop();                                                  //  wait for signal
      
      mutex_lock(&Fpixmap_lock);
      PXM_free(E3pxm16);
      E3pxm16 = PXM_rescale(Fpxm16,resize_ww1,resize_hh1);                 //  rescale the edit image
      E3ww = resize_ww1;
      E3hh = resize_hh1;
      CEF->Fmod = 1;
      mutex_unlock(&Fpixmap_lock);
      mwpaint2();
   }

   return 0;
}


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

//  edit image annotation - write text on the image itself

char     annotate_file[1000] = "";                                         //  file for annotation data
int      annotate_mode = 0;                                                //  0/1/2 = 1st write / re-write / erase
int      annotate_px, annotate_py;                                         //  text position on image
PXM      *annotate_pxm = 0;                                                //  buffer for rendered annotation text
PXM      *annotate_pxm_transp = 0;                                         //  buffer for transparency data

void  annotate_dialog_stuff(zdialog *zd);                                  //  stuff dialog widgets from memory data
int   annotate_dialog_event(zdialog *zd, cchar *event);                    //  dialog event function
void  annotate_mousefunc();                                                //  mouse event function
void  annotate_gettext();                                                  //  render text into graphic image
void  annotate_write();                                                    //  write text on image
void  annotate_load();                                                     //  load annotation data from file
void  annotate_save();                                                     //  save annotation data to file

editfunc    EFannotate;


void m_annotate(GtkWidget *, cchar *menu)                                  //  overhauled  v.10.11
{
   char     *pp;
   cchar    *intro = ZTX("Enter text, click/drag on image.\n"
                         "Right click to remove");

   zfuncs::F1_help_topic = "annotate";                                     //  user guide topic

   EFannotate.funcname = "annotate";
   EFannotate.Farea = 1;                                                   //  select area ignored
   if (! edit_setup(EFannotate)) return;                                   //  setup edit

   if (! annotate_text) 
      annotate_text = strdupz("enter text",0,"annotate");

   if (! annotate_font || strEqu(annotate_font,"undefined"))
      annotate_font = strdupz("FreeMono Bold Italic 44",4,"annotate");     //  default font and size
   else annotate_font = strdupz(annotate_font,4,"annotate");               //  add extra space      bugfix  v.11.02
   

   if (! annotate_color[0] || strEqu(annotate_color[0],"undefined")) {     //  default colors
      annotate_color[0] = strdupz("255|0|0",0,"annotate");                 //  text
      annotate_color[1] = strdupz("0|0|255",0,"annotate");                 //  background
      annotate_color[2] = strdupz("0|255|0",0,"annotate");                 //  text outline
      annotate_outline = 6;                                                //  outline width
   }
   
   pp = annotate_color[0];                                                 //  insure at least 20 bytes
   annotate_color[0] = strdupz(pp,20,"annotate");
   zfree(pp);
   pp = annotate_color[1];
   annotate_color[1] = strdupz(pp,20,"annotate");
   zfree(pp);
   pp = annotate_color[2];
   annotate_color[2] = strdupz(pp,20,"annotate");
   zfree(pp);

//                Annotate Image
//
//      Text [__________________________________]                          //  dialog
//
//      [font]    size [___|v]    angle [___|v]
//
//                     text     backing   outline                          //  v.11.04
//      Color         [color]   [color]   [color]
//      Transparency  [___|v]   [___|v]   [___|v]
//                               Width    [___|v]
//
//      Annotation File: [Open] [Save]
//
//                                [Done] [Cancel]

   zdialog *zd = zdialog_new(ZTX("Annotate Image"),mWin,Bdone,Bcancel,null);
   EFannotate.zd = zd;
   EFannotate.mousefunc = annotate_mousefunc;
 
   zdialog_add_widget(zd,"label","intro","dialog",intro,"space=3");
   zdialog_add_widget(zd,"hbox","hbtext","dialog",0,"space=3");
   zdialog_add_widget(zd,"label","labtext","hbtext",ZTX("Text"),"space=5");
   zdialog_add_widget(zd,"frame","frtext","hbtext",0,"expand");
   zdialog_add_widget(zd,"edit","text","frtext",0,"expand");

   zdialog_add_widget(zd,"hbox","hbfont","dialog",0,"space=8");
   zdialog_add_widget(zd,"button","font","hbfont",Bfont,"space=5");
   zdialog_add_widget(zd,"label","space","hbfont","","space=5");
   zdialog_add_widget(zd,"label","labsize","hbfont",ZTX("Size"),"space=3");
   zdialog_add_widget(zd,"spin","size","hbfont","6|99|1|20","space=3");
   zdialog_add_widget(zd,"label","space","hbfont","","space=5");
   zdialog_add_widget(zd,"label","labangle","hbfont",ZTX("Angle"),"space=3");
   zdialog_add_widget(zd,"spin","angle","hbfont","-180|180|0.2|0","space=3");
   
   zdialog_add_widget(zd,"hsep","hs1","dialog",0,"space=3");
   zdialog_add_widget(zd,"hbox","hbcol","dialog",0,"space=3");
   zdialog_add_widget(zd,"vbox","vbcol1","hbcol",0,"homog|space=5");
   zdialog_add_widget(zd,"vbox","vbcol2","hbcol",0,"homog|space=5");
   zdialog_add_widget(zd,"vbox","vbcol3","hbcol",0,"homog|space=5");
   zdialog_add_widget(zd,"vbox","vbcol4","hbcol",0,"homog|space=5");
   
   zdialog_add_widget(zd,"label","space","vbcol1");
   zdialog_add_widget(zd,"label","labcol","vbcol1",ZTX("Color"));
   zdialog_add_widget(zd,"label","labcol","vbcol1",ZTX("Transparency"));
   zdialog_add_widget(zd,"label","space","vbcol1");
   
   zdialog_add_widget(zd,"label","labtext","vbcol2",ZTX("text"));
   zdialog_add_widget(zd,"colorbutt","fgcolor","vbcol2","0|0|0");
   zdialog_add_widget(zd,"spin","fgtrans","vbcol2","0|100|1|0");
   zdialog_add_widget(zd,"label","space","vbcol2");
   
   zdialog_add_widget(zd,"label","labback","vbcol3",ZTX("backing"));
   zdialog_add_widget(zd,"colorbutt","bgcolor","vbcol3","255|255|255");
   zdialog_add_widget(zd,"spin","bgtrans","vbcol3","0|100|1|0");
   zdialog_add_widget(zd,"label","labw","vbcol3",ZTX("Outline\n Width"));
   
   zdialog_add_widget(zd,"label","laboutln","vbcol4",ZTX("outline"));
   zdialog_add_widget(zd,"colorbutt","tocolor","vbcol4","255|0|0");
   zdialog_add_widget(zd,"spin","totrans","vbcol4","0|100|1|0");
   zdialog_add_widget(zd,"spin","outline","vbcol4","0|9|1|0");

   zdialog_add_widget(zd,"hsep","hs1","dialog",0,"space=3");
   zdialog_add_widget(zd,"hbox","hbaf","dialog",0,"space=8");
   zdialog_add_widget(zd,"label","labbg","hbaf",ZTX("Annotation File:"),"space=3");
   zdialog_add_widget(zd,"button","load","hbaf",Bopen,"space=5");
   zdialog_add_widget(zd,"button","save","hbaf",Bsave,"space=5");

   annotate_dialog_stuff(zd);                                              //  stuff dialog widgets from memory data

   zdialog_help(zd,"annotate");                                            //  zdialog help topic        v.11.08
   zdialog_run(zd,annotate_dialog_event,"save");                           //  run dialog, parallel            v.11.07

   takeMouse(zd,annotate_mousefunc,dragcursor);                            //  connect mouse function          v.10.12

   annotate_mode = 0;                                                      //  no write on image (yet)
   annotate_px = annotate_py = -1;                                         //  no location on image (yet)
   return;
}


//  stuff all dialog widgets from annotation data in memory

void  annotate_dialog_stuff(zdialog *zd)
{
   int      size;
   char     *pp;

   zdialog_stuff(zd,"text",annotate_text);
   zdialog_stuff(zd,"angle",annotate_angle);
   zdialog_stuff(zd,"fgtrans",annotate_trans[0]);
   zdialog_stuff(zd,"bgtrans",annotate_trans[1]);
   zdialog_stuff(zd,"totrans",annotate_trans[2]);
   zdialog_stuff(zd,"fgcolor",annotate_color[0]);
   zdialog_stuff(zd,"bgcolor",annotate_color[1]);
   zdialog_stuff(zd,"tocolor",annotate_color[2]);
   zdialog_stuff(zd,"outline",annotate_outline);

   pp = annotate_font + strlen(annotate_font);
   while (*pp != ' ') pp--;
   if (pp > annotate_font) {
      size = atoi(pp);
      if (size >= 6 && size <= 99) 
         zdialog_stuff(zd,"size",size);
   }
   
   annotate_gettext();                                                     //  build text buffer from annotation data
   
   return;
}


//  dialog event and completion callback function

int annotate_dialog_event(zdialog *zd, cchar *event)
{
   GtkWidget   *font_dialog;
   int         size;
   char        *pp, text[1000];
   
   if (zd->zstat)
   {
      if (zd->zstat == 1 && CEF->Fmod) edit_done(EFannotate);              //  Done, complete pending edit
      else edit_cancel(EFannotate);                                        //  Cancel or kill
      return 0;
   }

   if (strEqu(event,"focus"))                                              //  toggle mouse capture
      takeMouse(zd,annotate_mousefunc,dragcursor);                         //  connect mouse function       v.12.01

   if (strEqu(event,"text")) {
      zdialog_fetch(zd,"text",text,999);                                   //  get text from dialog
      if (annotate_text) zfree(annotate_text);
      annotate_text = 0;
      if (*text) annotate_text = strdupz(text,0,"annotate");
   }
   
   if (strEqu(event,"font")) {                                             //  new font
      font_dialog = gtk_font_selection_dialog_new(ZTX("select font"));
      gtk_font_selection_dialog_set_font_name(GTK_FONT_SELECTION_DIALOG(font_dialog),annotate_font);
      gtk_dialog_run(GTK_DIALOG(font_dialog));
      pp = gtk_font_selection_dialog_get_font_name(GTK_FONT_SELECTION_DIALOG(font_dialog));
      gtk_widget_destroy(GTK_WIDGET(font_dialog));

      if (pp) {
         zfree(annotate_font);
         annotate_font = strdupz(pp,6,"annotate");                         //  update font and size
         g_free(pp);
         pp = annotate_font + strlen(annotate_font);
         while (*pp != ' ') pp--;
         if (pp > annotate_font) {
            size = atoi(pp);
            if (size >= 6 && size <= 99) zdialog_stuff(zd,"size",size);
         }
      }
   }

   if (strEqu(event,"load")) {                                             //  load annotate data from file
      annotate_load();
      annotate_dialog_stuff(zd);
      return 1;
   }

   if (strEqu(event,"save")) {                                             //  save annotate data to file
      annotate_save();
      return 1;
   }
   
   if (strEqu(event,"size")) {                                             //  new font size
      zdialog_fetch(zd,"size",size);
      pp = annotate_font + strlen(annotate_font);                          //  "fontname NN"
      while (*pp != ' ') pp--;                                             //  back-up to " NN"
      if (pp > annotate_font) sprintf(pp," %d",size);                      //  replace NN with new size
   }

   if (strEqu(event,"angle"))
      zdialog_fetch(zd,"angle",annotate_angle);
   
   if (strEqu(event,"fgcolor"))                                            //  foreground (text) color
      zdialog_fetch(zd,"fgcolor",annotate_color[0],20);
   
   if (strEqu(event,"bgcolor"))                                            //  background color
      zdialog_fetch(zd,"bgcolor",annotate_color[1],20);
   
   if (strEqu(event,"tocolor"))                                            //  text outline color
      zdialog_fetch(zd,"tocolor",annotate_color[2],20);
   
   if (strEqu(event,"fgtrans"))                                            //  foreground transparency
      zdialog_fetch(zd,"fgtrans",annotate_trans[0]);
   
   if (strEqu(event,"bgtrans"))                                            //  background transparency
      zdialog_fetch(zd,"bgtrans",annotate_trans[1]);

   if (strEqu(event,"totrans"))                                            //  text outline transparency
      zdialog_fetch(zd,"totrans",annotate_trans[2]);
   
   if (strEqu(event,"outline"))                                            //  text outline width
      zdialog_fetch(zd,"outline",annotate_outline);

   annotate_gettext();                                                     //  rebuild text buffer
   annotate_write();                                                       //  write on image
   zmainloop();          

   return 1;
}


//  mouse function, set position for annotation text on image

void annotate_mousefunc()
{
   static int     dragging = 0;

   if (LMclick) {                                                          //  left mouse click
      LMclick = 0;
      annotate_px = Mxclick;                                               //  new text position on image
      annotate_py = Myclick;
      annotate_write();                                                    //  write text on image
   }
   
   if (RMclick) {                                                          //  right mouse click
      RMclick = 0;
      annotate_mode = 2;                                                   //  erase text on image
      annotate_write();
      annotate_px = annotate_py = -1;                                      //  no location on image
   }
   
   if (Mxdrag || Mydrag)                                                   //  mouse dragged
   {
      if (! dragging && annotate_mode) dragging = 1;

      if (dragging) {
         annotate_px = Mxdrag;                                             //  new text position on image
         annotate_py = Mydrag;
         annotate_write();                                                 //  write text on image
      }
   }
   else  dragging = 0;                                                     //  no drag underway

   return;
}


//  write annotation text on image at designated place, if wanted
//  annotate_mode: 0/1/2 = 1st write / re-write / erase

void annotate_write()
{
   uint8       *pix1, *pix2;
   uint16      *pix3, *pix31, *pix33;
   int         px1, py1, px3, py3;
   double      e3part, f256 = 1.0 / 256.0;

   static int  orgx1 = 0, orgy1 = 0, ww1 = 0, hh1 = 0;                     //  old text image overlap rectangle
   int         orgx2, orgy2, ww2, hh2;                                     //  new overlap rectangle
   
   if (annotate_mode)                                                      //  re-write or erase
   {
      for (py3 = orgy1; py3 < orgy1 + hh1; py3++)                          //  erase prior text image
      for (px3 = orgx1; px3 < orgx1 + ww1; px3++)                          //  replace E3 pixels with E1 pixels
      {                                                                    //    in prior overlap rectangle
         if (px3 < 0 || px3 >= E3ww) continue;
         if (py3 < 0 || py3 >= E3hh) continue;
         pix31 = PXMpix(E1pxm16,px3,py3);
         pix33 = PXMpix(E3pxm16,px3,py3);
         pix33[0] = pix31[0];
         pix33[1] = pix31[1];
         pix33[2] = pix31[2];
      }
      
      CEF->Fmod = 0;
   }

   if (annotate_mode == 2) {                                               //  erase only
      annotate_mode = 0;                                                   //  next time is a 1st write
      mwpaint3(orgx1,orgy1,ww1,hh1);                                       //  update window
      return;
   }   

   if (! annotate_text) {                                                  //  no text to write
      annotate_mode = 0;                                                   //  next time is a 1st write
      mwpaint3(orgx1,orgy1,ww1,hh1);                                       //  update window
      return;
   }
   
   if (annotate_px < 0 || annotate_py < 0) {                               //  no image position to write
      annotate_mode = 0;                                                   //  next time is a 1st write
      mwpaint3(orgx1,orgy1,ww1,hh1);                                       //  update window
      return;
   }

   annotate_mode = 1;                                                      //  next time is a re-write

   ww2 = annotate_pxm->ww;                                                 //  text image buffer
   hh2 = annotate_pxm->hh;

   orgx2 = annotate_px - ww2/2;                                            //  copy-to image3 location
   orgy2 = annotate_py - hh2/2;

   for (py1 = 0; py1 < hh2; py1++)                                         //  loop all pixels in text image
   for (px1 = 0; px1 < ww2; px1++)
   {
      px3 = orgx2 + px1;                                                   //  copy-to image3 pixel
      py3 = orgy2 + py1;

      if (px3 < 0 || px3 >= E3ww) continue;                                //  omit parts beyond edges
      if (py3 < 0 || py3 >= E3hh) continue;

      pix1 = PXMpix8(annotate_pxm,px1,py1);                                //  copy-from text pixel
      pix2 = PXMpix8(annotate_pxm_transp,px1,py1);                         //  copy-from transparency
      pix3 = PXMpix(E3pxm16,px3,py3);                                      //  copy-to image pixel
      
      e3part = pix2[0] * f256;                                             //  image part visible through text
      
      pix3[0] = (pix1[0] << 8) + e3part * pix3[0];                         //  combine text part + image part
      pix3[1] = (pix1[1] << 8) + e3part * pix3[1];
      pix3[2] = (pix1[2] << 8) + e3part * pix3[2];
   }
   
   mwpaint3(orgx1,orgy1,ww1,hh1);                                          //  restore prior overlap rectangle
   mwpaint3(orgx2,orgy2,ww2,hh2);                                          //  update new overlap rectangle

   CEF->Fmod = 1;
   
   orgx1 = orgx2;                                                          //  remember overlap rectangle
   orgy1 = orgy2;                                                          //    for next call

   ww1 = ww2;
   hh1 = hh2;
   return;
}


//  Create a graphic image with text, white on black, any font, any angle.

void annotate_gettext()                                                    //  revised   v.10.12
{
   PXM * annotate_addoutline(PXM *);

   PangoFontDescription    *pfont;
   static GdkColormap      *colormap = 0;
   static GdkColor         black, white;
   PangoLayout             *playout;
   GdkPixmap               *pixmap;
   GdkGC                   *gdkgc;
   GdkPixbuf               *pixbuf;

   char           *text = annotate_text;
   double         angle = annotate_angle;
   PXM            *pxm_temp1, *pxm_temp2, *pxm_temp3;
   uint8          *pix1, *pix2, *pix3, *ppix;
   int            px, py, ww, hh, rs, fontsize;
   char           *pp, font[100];
   cchar          *ppc;
   int            fgred, fggreen, fgblue;
   int            bgred, bggreen, bgblue;
   int            tored, togreen, toblue;
   int            red, green, blue;
   double         fgtrans, bgtrans, totrans, fgpart, bgpart;
   double         f256 = 1.0 / 256.0;

   if (! annotate_text || ! *annotate_text) return;                        //  no annotation text

   strncpy0(font,annotate_font,99);
   pp = font + strlen(font);                                               //  save current font size
   while (*pp != ' ') pp--;
   fontsize = atoi(pp);
   if (fontsize < 6 || fontsize > 99) fontsize = 20;

   strcpy(pp+1,"99");                                                      //  use large size for text generation

   if (! colormap) {
      colormap = gtk_widget_get_colormap(drWin);
      black.red = black.green = black.blue = 0;
      white.red = white.green = white.blue = 0xffff;
      gdk_rgb_find_color(colormap,&black);
      gdk_rgb_find_color(colormap,&white);
   }
   
   pfont = pango_font_description_from_string(font);                       //  make layout with text
   playout = gtk_widget_create_pango_layout(drWin,null);
   pango_layout_set_font_description(playout,pfont);
   pango_layout_set_text(playout,text,-1);
   pango_layout_get_pixel_size(playout,&ww,&hh);

   ww += 10;                                                               //  sometimes it is a little too small
   hh += 2;
   pixmap = gdk_pixmap_new(drWin->window,ww,hh,-1);                        //  then make pixmap
   gdkgc = gdk_gc_new(pixmap);
   gdk_draw_rectangle(pixmap,gdkgc,1,0,0,ww,hh);
   gdk_draw_layout_with_colors(pixmap,gdkgc,0,0,playout,&white,&black);

   pixbuf = gdk_pixbuf_get_from_drawable(null,pixmap,0,0,0,0,0,ww,hh);     //  then make pixbuf

   ww = gdk_pixbuf_get_width(pixbuf);                                      //  pixbuf dimensions
   hh = gdk_pixbuf_get_height(pixbuf);
   rs = gdk_pixbuf_get_rowstride(pixbuf);
   ppix = gdk_pixbuf_get_pixels(pixbuf);
   
   pxm_temp1 = PXM_make(ww,hh,8);

   for (py = 0; py < hh; py++)                                             //  copy pixbuf to PXM
   for (px = 0; px < ww; px++)
   {                                                                       //  text color is gray/white
      pix1 = ppix + rs * py + 3 * px;                                      //  can erase all but one color
      pix2 = PXMpix8(pxm_temp1,px,py);
      pix2[0] = pix1[0];
      pix2[1] = pix2[2] = 0;
   }

   g_object_unref(playout);
   g_object_unref(pixmap);
   g_object_unref(gdkgc);
   g_object_unref(pixbuf);
   
   pxm_temp2 = annotate_addoutline(pxm_temp1);                             //  add text outline color if any
   if (pxm_temp2) {
      PXM_free(pxm_temp1);
      pxm_temp1 = pxm_temp2;
   }

   if (fabs(angle) > 0.1) {                                                //  rotate text if wanted
      pxm_temp2 = PXM_rotate(pxm_temp1,angle);
      PXM_free(pxm_temp1);
      pxm_temp1 = pxm_temp2;
   }

   fgred = fggreen = fgblue = 0;
   bgred = bggreen = bgblue = 255;
   tored = togreen = toblue = 0;

   ppc = strField(annotate_color[0],'|',1);                                //  get text foreground color
   if (ppc) fgred = atoi(ppc);                                             //  0 - 255 per RGB color
   ppc = strField(annotate_color[0],'|',2);
   if (ppc) fggreen = atoi(ppc);
   ppc = strField(annotate_color[0],'|',3);
   if (ppc) fgblue = atoi(ppc);

   ppc = strField(annotate_color[1],'|',1);                                //  get text background color
   if (ppc) bgred = atoi(ppc);
   ppc = strField(annotate_color[1],'|',2);
   if (ppc) bggreen = atoi(ppc);
   ppc = strField(annotate_color[1],'|',3);
   if (ppc) bgblue = atoi(ppc);

   ppc = strField(annotate_color[2],'|',1);                                //  get text outline color
   if (ppc) tored = atoi(ppc);
   ppc = strField(annotate_color[2],'|',2);
   if (ppc) togreen = atoi(ppc);
   ppc = strField(annotate_color[2],'|',3);
   if (ppc) toblue = atoi(ppc);

   fgtrans = 0.01 * annotate_trans[0];                                     //  get transparencies
   bgtrans = 0.01 * annotate_trans[1];                                     //  text, background, text outline
   totrans = 0.01 * annotate_trans[2];

   ww = pxm_temp1->ww;                                                     //  text image input pixmap
   hh = pxm_temp1->hh;

   pxm_temp2 = PXM_make(ww,hh,8);                                          //  text image output pixmap
   pxm_temp3 = PXM_make(ww,hh,8);                                          //  output transparency map

   for (py = 0; py < hh; py++)                                             //  loop all pixels in text image
   for (px = 0; px < ww; px++)
   {
      pix1 = PXMpix8(pxm_temp1,px,py);                                     //  copy-from pixel
      pix2 = PXMpix8(pxm_temp2,px,py);                                     //  copy-to pixel
      pix3 = PXMpix8(pxm_temp3,px,py);                                     //  copy-to transparency

      fgpart = pix1[0] * f256;                                             //  white part = text foreground, 0 - 1
      bgpart = 1.0 - fgpart;                                               //  rest = text background part, 1 - 0
      
      if (pix1[1])                                                         //  use text outline color
      {
         fgpart = fgpart * (1.0 - totrans);                                //  reduce for transparencies
         bgpart = bgpart * (1.0 - bgtrans);
         red = tored * fgpart + bgred * bgpart;                            //  red part for text outline + background
         green = togreen * fgpart + bggreen * bgpart;                      //  same for green
         blue = toblue * fgpart + bgblue * bgpart;                         //  same for blue
      }
      
      else                                                                 //  use text foreground color
      {
         fgpart = fgpart * (1.0 - fgtrans);                                //  reduce for transparencies
         bgpart = bgpart * (1.0 - bgtrans);
         red = fgred * fgpart + bgred * bgpart;                            //  red part for text + text background
         green = fggreen * fgpart + bggreen * bgpart;                      //  same for green
         blue = fgblue * fgpart + bgblue * bgpart;                         //  same for blue
      }

      pix2[0] = red;                                                       //  output total red, green blue
      pix2[1] = green;
      pix2[2] = blue;

      pix3[0] = 255 * (1.0 - fgpart - bgpart);                             //  image part visible through text
   }
   
   ww = ww * fontsize / 99.0 + 0.5;                                        //  resize from size 99 font
   hh = hh * fontsize / 99.0 + 0.5;                                        //    to requested font size
   PXM_free(pxm_temp1);
   pxm_temp1 = PXM_rescale(pxm_temp2,ww,hh);
   PXM_free(pxm_temp2);
   PXM_free(annotate_pxm);
   annotate_pxm = pxm_temp1;
   
   pxm_temp1 = PXM_rescale(pxm_temp3,ww,hh);                               //  resize transparency map
   PXM_free(pxm_temp3);
   PXM_free(annotate_pxm_transp);
   annotate_pxm_transp = pxm_temp1;

   return;
}


//  add an outline color to the text character edges

PXM * annotate_addoutline(PXM *pxm1)                                       //  new  v.10.12
{
   PXM         *pxm2;
   int         toww, toww2;
   int         ww1, hh1, ww2, hh2;
   int         px1, py1, px2, py2, ii, diff;
   uint8       *pix1, *pix2;

   toww = annotate_outline;                                                //  text outline color width
   if (toww == 0) return 0;                                                //  zero
   toww2 = 2 * toww;
   
   ww1 = pxm1->ww;                                                         //  input PXM dimensions
   hh1 = pxm1->hh;
   
   ww2 = ww1 + toww2;                                                      //  output PXM with added margins
   hh2 = hh1 + toww2;
   pxm2 = PXM_make(ww2,hh2,8);
   
   memset(pxm2->bmp,0,ww2*hh2*3);                                          //  clear output to black
   
   for (py1 = 0; py1 < hh1; py1++)                                         //  copy input to output,
   for (px1 = 0; px1 < ww1; px1++)                                         //    displaced for margins
   {
      pix1 = PXMpix8(pxm1,px1,py1);
      pix2 = PXMpix8(pxm2,px1+toww,py1+toww);
      pix2[0] = pix1[0];
      pix2[1] = pix1[1];
      pix2[2] = pix1[2];
   }
   
   for (py1 = 0; py1 < hh1; py1++)
   for (px1 = 0; px1 < ww1-toww-2; px1++)                                  //  horizontal forward scan
   {
      pix1 = PXMpix8(pxm1,px1,py1);
      diff = (pix1+6)[0] - pix1[0];
      if (diff < 200) continue;
      
      px2 = px1 + toww;
      py2 = py1 + toww;
      pix2 = PXMpix8(pxm2,px2-toww/2,py2);
      
      for (ii = 0; ii < toww + 1; ii++)
      {
         pix2[0] = pix1[0];
         pix2[1] = 1;
         pix1 += 3;
         pix2 += 3;
      }
   }

   for (py1 = 0; py1 < hh1; py1++)
   for (px1 = ww1-1; px1 > toww+2; px1--)                                  //  horizontal reverse scan
   {
      pix1 = PXMpix8(pxm1,px1,py1);
      diff = (pix1-6)[0] - pix1[0];
      if (diff < 200) continue;
      
      px2 = px1 + toww;
      py2 = py1 + toww;
      pix2 = PXMpix8(pxm2,px2+toww/2,py2);
      
      for (ii = 0; ii < toww + 1; ii++)
      {
         pix2[0] = pix1[0];
         pix2[1] = 1;
         pix1 -= 3;
         pix2 -= 3;
      }
   }

   for (px1 = 0; px1 < ww1; px1++)
   for (py1 = 0; py1 < hh1-toww-2; py1++)                                  //  vertical forward scan
   {
      pix1 = PXMpix8(pxm1,px1,py1);
      diff = (pix1+6*ww1)[0] - pix1[0];
      if (diff < 200) continue;
      
      px2 = px1 + toww;
      py2 = py1 + toww;
      pix2 = PXMpix8(pxm2,px2,py2-toww/2);
      
      for (ii = 0; ii < toww + 1; ii++)
      {
         if (pix2[0] < pix1[0]) pix2[0] = pix1[0];
         pix2[1] = 1;
         pix1 += 3*ww1;
         pix2 += 3*ww2;
      }
   }

   for (px1 = 0; px1 < ww1; px1++)
   for (py1 = hh1-1; py1 > toww+2; py1--)                                  //  vertical reverse scan
   {
      pix1 = PXMpix8(pxm1,px1,py1);
      diff = (pix1-6*ww1)[0] - pix1[0];
      if (diff < 200) continue;
      
      px2 = px1 + toww;
      py2 = py1 + toww;
      pix2 = PXMpix8(pxm2,px2,py2+toww/2);
      
      for (ii = 0; ii < toww + 1; ii++)
      {
         if (pix2[0] < pix1[0]) pix2[0] = pix1[0];
         pix2[1] = 1;
         pix1 -= 3*ww1;
         pix2 -= 3*ww2;
      }
   }

   return pxm2;
}


//  load annotation data from a file

void annotate_load()                                                       //  v.10.11
{
   FILE     *fid;
   int      cc, err;
   char     *pp, *file, buff[1200];
   cchar    *dialogtitle = "load annotation data from a file";

   file = zgetfile1(dialogtitle,"open",annotations_dirk);                  //  get input file from user
   if (! file) return;
   
   fid = fopen(file,"r");                                                  //  open for read
   if (! fid) {
      zmessageACK(mWin,"%s",strerror(errno));
      zfree(file);
      return;
   }

   while (true)
   {
      pp = fgets_trim(buff,1200,fid,1);
      if (! pp) break;
      
      if (strnEqu(pp,"annotate_text  ",15)) {
         if (annotate_text) zfree(annotate_text);
         cc = strlen(pp+15) + 100;
         annotate_text = zmalloc(cc,"annotate");
         repl_1str(pp+15,annotate_text,"\\n","\n");                        //  replace "\n" with real newline char.
      }
      
      if (strnEqu(pp,"annotate_font  ",15))
         strcpy(annotate_font,pp+15);
         
      if (strnEqu(pp,"annotate_angle  ",16))
         convSD(pp+16,annotate_angle,-180,+180);
      
      if (strnEqu(pp,"annotate_fgcolor  ",18))
         strcpy(annotate_color[0],pp+18);

      if (strnEqu(pp,"annotate_bgcolor  ",18))
         strcpy(annotate_color[1],pp+18);

      if (strnEqu(pp,"annotate_tocolor  ",18))
         strcpy(annotate_color[2],pp+18);

      if (strnEqu(pp,"annotate_fgtrans  ",18))
         convSI(pp+18,annotate_trans[0],0,100);

      if (strnEqu(pp,"annotate_bgtrans  ",18))
         convSI(pp+18,annotate_trans[1],0,100);

      if (strnEqu(pp,"annotate_totrans  ",18))
         convSI(pp+18,annotate_trans[2],0,100);

      if (strnEqu(pp,"annotate_outline  ",18))
         convSI(pp+18,annotate_outline,0,9);
   }

   err = fclose(fid);
   if (err) {
      zmessageACK(mWin,"%s",strerror(errno));
      zfree(file);
      return;
   }
   
   strcpy(annotate_file,file);                                             //  update current file
   zfree(file);

   return;
}


//  save annotation data to a file

void annotate_save()                                                       //  v.10.11
{
   FILE     *fid;
   char     *file, text[1100];
   cchar    *dialogtitle = "save annotation data to a file";
   int      err;

   file = zgetfile1(dialogtitle,"save",annotations_dirk);                  //  get output file from user
   if (! file) return;
   
   fid = fopen(file,"w");                                                  //  open for write
   if (! fid) {
      zmessageACK(mWin,"%s",strerror(errno));
      zfree(file);
      return;
   }
   
   repl_1str(annotate_text,text,"\n","\\n");                               //  replace newlines with "\n"

   fprintf(fid,"annotate_text  %s \n", text);
   fprintf(fid,"annotate_font  %s \n", annotate_font);
   fprintf(fid,"annotate_angle  %.1f \n", annotate_angle);
   fprintf(fid,"annotate_fgcolor  %s \n", annotate_color[0]);
   fprintf(fid,"annotate_bgcolor  %s \n", annotate_color[1]);
   fprintf(fid,"annotate_tocolor  %s \n", annotate_color[2]);
   fprintf(fid,"annotate_fgtrans  %d \n", annotate_trans[0]);
   fprintf(fid,"annotate_bgtrans  %d \n", annotate_trans[1]);
   fprintf(fid,"annotate_totrans  %d \n", annotate_trans[2]);
   fprintf(fid,"annotate_outline  %d \n", annotate_outline);

   fprintf(fid,"\n");
   
   err = fclose(fid);
   if (err) {
      zmessageACK(mWin,"file I/O error %s",file);
      zfree(file);
      return;
   }
   
   strcpy(annotate_file,file);                                             //  update current file
   zfree(file);

   return;
}


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

//  flip an image horizontally or vertically

editfunc    EFflip;

void m_flip(GtkWidget *, cchar *)
{
   int flip_dialog_event(zdialog *zd, cchar *event);

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

   EFflip.funcname = "flip";
   if (! edit_setup(EFflip)) return;                                       //  setup edit

   zdialog *zd = zdialog_new(ZTX("Flip Image"),mWin,Bdone,Bcancel,null);
   EFflip.zd = zd;

   zdialog_add_widget(zd,"hbox","hb1","dialog",0,"space=10");
   zdialog_add_widget(zd,"button","horz","hb1",ZTX("horizontal"),"space=5");
   zdialog_add_widget(zd,"button","vert","hb1",ZTX("vertical"),"space=5");

   zdialog_help(zd,"flip_image");                                          //  zdialog help topic        v.11.08
   zdialog_run(zd,flip_dialog_event,"save");                               //  run dialog, parallel            v.11.07
   return;
}


//  dialog event and completion callback function

int flip_dialog_event(zdialog *zd, cchar *event)
{
   int flip_horz();
   int flip_vert();

   if (zd->zstat)                                                          //  dialog complete
   {
      if (zd->zstat == 1) edit_done(EFflip);
      else edit_cancel(EFflip);
      return 0;
   }

   if (strEqu(event,"horz")) flip_horz();
   if (strEqu(event,"vert")) flip_vert();
   return 0;
}


int flip_horz()
{
   int      px, py;
   uint16   *pix3, *pix9;
   
   edit_zapredo();                                                         //  delete redo copy    v.10.3

   E9pxm16 = PXM_copy(E3pxm16);
   
   for (py = 0; py < E3hh; py++)
   for (px = 0; px < E3ww; px++)
   {
      pix3 = PXMpix(E3pxm16,px,py);                                        //  image9 = flipped image3
      pix9 = PXMpix(E9pxm16,E3ww-1-px,py);
      pix9[0] = pix3[0];
      pix9[1] = pix3[1];
      pix9[2] = pix3[2];
   }
   
   mutex_lock(&Fpixmap_lock);
   PXM_free(E3pxm16);                                                      //  image9 >> image3
   E3pxm16 = E9pxm16;
   E9pxm16 = 0;
   mutex_unlock(&Fpixmap_lock);

   CEF->Fmod = 1;
   mwpaint2();
   return 0;
}


int flip_vert()
{
   int      px, py;
   uint16   *pix3, *pix9;
   
   edit_zapredo();                                                         //  delete redo copy    v.10.3

   E9pxm16 = PXM_copy(E3pxm16);
   
   for (py = 0; py < E3hh; py++)
   for (px = 0; px < E3ww; px++)
   {
      pix3 = PXMpix(E3pxm16,px,py);                                        //  image9 = flipped image3
      pix9 = PXMpix(E9pxm16,px,E3hh-1-py);
      pix9[0] = pix3[0];
      pix9[1] = pix3[1];
      pix9[2] = pix3[2];
   }
   
   mutex_lock(&Fpixmap_lock);
   PXM_free(E3pxm16);                                                      //  image9 >> image3
   E3pxm16 = E9pxm16;
   E9pxm16 = 0;
   mutex_unlock(&Fpixmap_lock);

   CEF->Fmod = 1;
   mwpaint2();
   return 0;
}


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

//  make a black & white or color negative image

editfunc    EFnegate;

void m_negate(GtkWidget *, cchar *)                                        //  v.10.9
{
   int negate_dialog_event(zdialog *zd, cchar *event);

   zfuncs::F1_help_topic = "make_negative";
   
   EFnegate.funcname = "negate";
   if (! edit_setup(EFnegate)) return;                                     //  setup edit: no preview

   zdialog *zd = zdialog_new(ZTX("Make Negative"),mWin,Bdone,Bcancel,null);
   EFnegate.zd = zd;

   zdialog_add_widget(zd,"radio","b&wpos","dialog",ZTX("black/white positive"));
   zdialog_add_widget(zd,"radio","b&wneg","dialog",ZTX("black/white negative"));
   zdialog_add_widget(zd,"radio","colpos","dialog",ZTX("color positive"));
   zdialog_add_widget(zd,"radio","colneg","dialog",ZTX("color negative"));

   zdialog_stuff(zd,"colpos",1);

   zdialog_resize(zd,200,0);
   zdialog_help(zd,"make_negative");                                       //  zdialog help topic        v.11.08
   zdialog_run(zd,negate_dialog_event,"save");                             //  run dialog - parallel     v.11.07
   return;
}


//  dialog event and completion callback function

int negate_dialog_event(zdialog *zd, cchar *event)
{
   int         mode, px, py;
   int         red, green, blue;
   uint16      *pix1, *pix3;

   if (zd->zstat)                                                          //  dialog complete
   {
      if (zd->zstat == 1) edit_done(EFnegate);
      else edit_cancel(EFnegate);
      return 0;
   }

   if (strEqu(event,"b&wpos")) mode = 1;
   if (strEqu(event,"b&wneg")) mode = 2;
   if (strEqu(event,"colpos")) mode = 3;
   if (strEqu(event,"colneg")) mode = 4;
   
   edit_zapredo();                                                         //  delete redo copy

   for (py = 0; py < E3hh; py++)
   for (px = 0; px < E3ww; px++)
   {
      pix1 = PXMpix(E1pxm16,px,py);

      red = pix1[0];
      green = pix1[1];
      blue = pix1[2];
      
      if (mode == 1)                                                       //  black and white positive
         red = green = blue = (red + green + blue) / 3;
      
      else if (mode == 2)                                                  //  black and white negative
         red = green = blue = 65535 - (red + green + blue) / 3;
      
      if (mode == 3) {  /** do nothing  **/  }                             //  color positive
      
      if (mode == 4) {                                                     //  color negative
         red = 65535 - red;
         green = 65535 - green;
         blue = 65535 - blue; 
      }
      
      pix3 = PXMpix(E3pxm16,px,py);

      pix3[0] = red;
      pix3[1] = green;
      pix3[2] = blue;
   }
   
   CEF->Fmod = 1;
   mwpaint2();

   return 0;
}


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

//  unbend an image
//  straighten curvature added by pano or improve perspective

double   unbend_lin_horz, unbend_lin_vert;                                 //  unbend values from dialog
double   unbend_cur_horz, unbend_cur_vert;
double   unbend_x1, unbend_x2, unbend_y1, unbend_y2;                       //  unbend axes scaled 0 to 1
int      unbend_hx1, unbend_hy1, unbend_hx2, unbend_hy2;
int      unbend_vx1, unbend_vy1, unbend_vx2, unbend_vy2;

editfunc    EFunbend;


void m_unbend(GtkWidget *, cchar *)                                        //  overhauled    v.11.04
{
   int    unbend_dialog_event(zdialog* zd, cchar *event);
   void * unbend_thread(void *);
   void   unbend_mousefunc();

   zfuncs::F1_help_topic = "unbend";

   EFunbend.funcname = "unbend";
   EFunbend.Fprev = 1;                                                     //  use preview
   EFunbend.threadfunc = unbend_thread;                                    //  thread function
   EFunbend.mousefunc = unbend_mousefunc;                                  //  mouse function
   if (! edit_setup(EFunbend)) return;                                     //  setup edit

/***         ___________________________________
            |                                   |
            |           Unbend Image            |
            |                                   |
            |                linear    curved   |
            |  vertical      [__|v]    [__|v]   |
            |  horizontal    [__|v]    [__|v]   |
            |                                   |
            |  [grid]                           |
            |                [done]  [cancel]   |
            |___________________________________|
***/

   zdialog *zd = zdialog_new(ZTX("Unbend Image"),mWin,Bdone,Bcancel,null);
   EFunbend.zd = zd;

   zdialog_add_widget(zd,"hbox","hb1","dialog",0,"space=8");
   zdialog_add_widget(zd,"vbox","vb1","hb1",0,"homog|space=5");
   zdialog_add_widget(zd,"vbox","vb2","hb1",0,"homog|space=5");
   zdialog_add_widget(zd,"vbox","vb3","hb1",0,"homog|space=5");
   zdialog_add_widget(zd,"label","labspace","vb1","");
   zdialog_add_widget(zd,"label","labvert","vb1",ZTX("vertical"));
   zdialog_add_widget(zd,"label","labhorz","vb1",ZTX("horizontal"));
   zdialog_add_widget(zd,"label","lablin","vb2",ZTX("linear"));
   zdialog_add_widget(zd,"spin","splinvert","vb2","-99|99|1|0");
   zdialog_add_widget(zd,"spin","splinhorz","vb2","-99|99|1|0");
   zdialog_add_widget(zd,"label","labhorz","vb3",ZTX("curved"));
   zdialog_add_widget(zd,"spin","spcurvert","vb3","-99|99|1|0");
   zdialog_add_widget(zd,"spin","spcurhorz","vb3","-99|99|1|0");
   zdialog_add_widget(zd,"hbox","hb2","dialog",0,"space=5");
   zdialog_add_widget(zd,"button","grid","hb2",ZTX("Grid"),"space=10");

   zdialog_help(zd,"unbend");                                              //  zdialog help topic        v.11.08
   zdialog_run(zd,unbend_dialog_event,"save");                             //  run dialog, parallel            v.11.07

   unbend_x1 = unbend_x2 = unbend_y1 = unbend_y2 = 0.5;                    //  initial axes thru image middle
   unbend_lin_horz = unbend_lin_vert = 0;
   unbend_cur_horz = unbend_cur_vert = 0;

   load_grid(unbend_grid);                                                 //  load grid preferences           v.11.11

   takeMouse(zd,unbend_mousefunc,dragcursor);                              //  connect mouse function          v.11.03
   signal_thread();
   return;
}


//  dialog event and completion callback function

int unbend_dialog_event(zdialog *zd, cchar *event)
{
   if (zd->zstat)                                                          //  dialog complete
   {
      paint_toplines(2);                                                   //  erase axes-lines

      save_grid(unbend_grid);                                              //  save grid preferences     v.11.11
      Fgrid = 0;                                                           //  grid off

      if (zd->zstat != 1) {
         edit_cancel(EFunbend);                                            //  canceled
         return 0;
      }

      if (unbend_cur_vert || unbend_cur_horz ||                            //  image3 modified
          unbend_lin_vert || unbend_lin_horz) CEF->Fmod = 1;
      else CEF->Fmod = 0;

      edit_done(EFunbend);                                                 //  commit changes to image3
      return 1;
   }

   if (strstr(event,"splinvert")) {                                        //  get new unbend value
      zdialog_fetch(zd,"splinvert",unbend_lin_vert);
      signal_thread();                                                     //  trigger thread
   }

   if (strstr(event,"splinhorz")) {
      zdialog_fetch(zd,"splinhorz",unbend_lin_horz);
      signal_thread();
   }

   if (strstr(event,"spcurvert")) {
      zdialog_fetch(zd,"spcurvert",unbend_cur_vert);
      signal_thread();
   }

   if (strstr(event,"spcurhorz")) {
      zdialog_fetch(zd,"spcurhorz",unbend_cur_horz);
      signal_thread();
   }

   if (strEqu(event,"grid")) m_gridlines(0,0);                             //  grid dialog           v.11.11
   
   if (strstr("KB G KB g",event))                                          //  G key, toggle grid    v.11.11
      toggle_grid(2);


   return 1;
}


//  unbend mouse function                                                  //  adjustable axes

void unbend_mousefunc()   
{
   cchar       *close;
   double      dist1, dist2;
   double      mpx = 0, mpy = 0;
   
   if (LMclick) {                                                          //  left mouse click
      LMclick = 0;
      mpx = Mxclick;
      mpy = Myclick;
   }
   
   if (Mxdrag || Mydrag) {                                                 //  mouse dragged
      mpx = Mxdrag;
      mpy = Mydrag;
   }
   
   if (! mpx && ! mpy) return;

   mpx = 1.0 * mpx / E3ww;                                                 //  scale mouse position 0 to 1
   mpy = 1.0 * mpy / E3hh;

   if (mpx < 0.2 || mpx > 0.8 ) {                                          //  check reasonable position
      if (mpy < 0.1 || mpy > 0.9) return;
   }
   else if (mpy < 0.2 || mpy > 0.8) {
      if (mpx < 0.1 || mpx > 0.9) return;
   }
   else return;

   close = "?";                                                            //  find closest axis end-point
   dist1 = 2;

   dist2 = mpx * mpx + (mpy-unbend_y1) * (mpy-unbend_y1);
   if (dist2 < dist1) {
      dist1 = dist2;
      close = "left";
   }

   dist2 = (1-mpx) * (1-mpx) + (mpy-unbend_y2) * (mpy-unbend_y2);
   if (dist2 < dist1) {
      dist1 = dist2;
      close = "right";
   }

   dist2 = (mpx-unbend_x1) * (mpx-unbend_x1) + mpy * mpy;
   if (dist2 < dist1) {
      dist1 = dist2;
      close = "top";
   }

   dist2 = (mpx-unbend_x2) * (mpx-unbend_x2) + (1-mpy) * (1-mpy);
   if (dist2 < dist1) {
      dist1 = dist2;
      close = "bottom";
   }
   
   if (strEqu(close,"left")) unbend_y1 = mpy;                              //  set new axis end-point
   if (strEqu(close,"right")) unbend_y2 = mpy;
   if (strEqu(close,"top")) unbend_x1 = mpx;
   if (strEqu(close,"bottom")) unbend_x2 = mpx;

   signal_thread();                                                        //  trigger thread 

   return;
}


//  unbend thread function

void * unbend_thread(void *arg)
{
   void * unbend_wthread(void *);

   while (true)
   {
      thread_idle_loop();                                                  //  wait for work or exit request

      unbend_hx1 = 0;                                                      //  scale axes to E3ww/hh
      unbend_hy1 = unbend_y1 * E3hh;
      unbend_hx2 = E3ww;
      unbend_hy2 = unbend_y2 * E3hh;

      unbend_vx1 = unbend_x1 * E3ww;
      unbend_vy1 = 0;
      unbend_vx2 = unbend_x2 * E3ww;
      unbend_vy2 = E3hh;

      if (Fpreview) {                                                      //  omit for final unbend
         Ntoplines = 2;
         toplinex1[0] = unbend_hx1;                                        //  lines on window
         topliney1[0] = unbend_hy1;
         toplinex2[0] = unbend_hx2;
         topliney2[0] = unbend_hy2;
         toplinex1[1] = unbend_vx1;
         topliney1[1] = unbend_vy1;
         toplinex2[1] = unbend_vx2;
         topliney2[1] = unbend_vy2;
      }
      
      for (int ii = 0; ii < Nwt; ii++)                                     //  start worker threads
         start_wthread(unbend_wthread,&wtnx[ii]);
      wait_wthreads();                                                     //  wait for completion

      mwpaint2();                                                          //  update window
   }

   return 0;                                                               //  not executed, stop g++ warning
}


void * unbend_wthread(void *arg)                                           //  worker thread function
{
   int         index = *((int *) arg);
   int         vstat, px3, py3, cx3, cy3;
   double      dispx, dispy, dispx2, dispy2;
   double      px1, py1, vx1, vx2, hy1, hy2;
   double      curvert, curhorz, linvert, linhorz;
   uint16      vpix[3], *pix3;

   curvert = int(unbend_cur_vert * 0.01 * E3hh);                           //  -0.99 to +0.99
   curhorz = int(unbend_cur_horz * 0.01 * E3ww);
   linvert = int(unbend_lin_vert * 0.0013 * E3hh);                         //  -0.13 to +0.13
   linhorz = int(unbend_lin_horz * 0.0013 * E3ww);
   
   vx1 = unbend_vx1;
   vx2 = unbend_vx2;
   hy1 = unbend_hy1;
   hy2 = unbend_hy2;

   for (py3 = index; py3 < E3hh; py3 += Nwt)                               //  step through F3 pixels
   for (px3 = 0; px3 < E3ww; px3++)
   {
      cx3 = vx1 + (vx2 - vx1) * py3 / E3hh;                                //  center of unbend
      cy3 = hy1 + (hy2 - hy1) * px3 / E3ww;
      dispx = 2.0 * (px3 - cx3) / E3ww;                                    //  -1.0 ..  0.0 .. +1.0  (roughly)
      dispy = 2.0 * (py3 - cy3) / E3hh;                                    //  -1.0 ..  0.0 .. +1.0
      dispx2 = -cos(0.8 * dispx) + 1;                                      //   curved                v.11.03
      dispy2 = -cos(0.8 * dispy) + 1;                                      //                         v.11.04
      
      pix3 = PXMpix(E3pxm16,px3,py3);                                      //  output pixel
      px1 = px3;                                                           //  input pixel = output
      py1 = py3;
      
      px1 += dispx * dispy * linhorz;                                      //  move input pixel
      py1 += dispy * dispx * linvert;
      px1 += dispx * dispy2 * curhorz;
      py1 += dispy * dispx2 * curvert;

      vstat = vpixel(E1pxm16,px1,py1,vpix);
      if (vstat) {
         pix3[0] = vpix[0];                                                //  input pixel >> output pixel
         pix3[1] = vpix[1];
         pix3[2] = vpix[2];
      }
      else pix3[0] = pix3[1] = pix3[2] = 0;
   }
      
   exit_wthread();
   return 0;                                                               //  not executed, avoid gcc warning
}


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

//  Convert a selected tetragon area into a rectangle, converting the
//  rest of the image to match and keeping straight lines straight.

int         KST_pixel[4][2];                                               //  last 0-4 pixels clicked
int         KST_npix;                                                      //  count of pixels
char        KST_pixlab[4][4] = { " A ", " B ", " C ", " D " };

editfunc    EFkeystone;

int   KST_dialog_event(zdialog *zd, cchar *event);
void  KST_mousefunc(void);
void  KST_warpfunc(void);


void m_keystone(GtkWidget *, cchar *)                                      //   new v.11.10
{
   cchar  *KST_message = ZTX(
          " Click the four corners of a tetragon area. Press [apply]. \n"
          " The image is warped to make the tetragon into a rectangle.");
   
   zfuncs::F1_help_topic = "keystone";

   EFkeystone.funcname = "keystone";
   EFkeystone.Fprev = 0;                                                   //  no preview
   EFkeystone.mousefunc = KST_mousefunc;                                   //  mouse function
   if (! edit_setup(EFkeystone)) return;                                   //  setup edit
   
   KST_npix = 0;                                                           //  no pixels yet

   zdialog *zd = zdialog_new(ZTX("Keystone Correction"),mWin,Bapply,Breset,Bdone,null);
   zdialog_add_widget(zd,"label","lab1","dialog",KST_message,"space=5");
   zdialog_help(zd,"keystone");                                            //  zdialog help topic

   EFkeystone.zd = zd;
   zdialog_run(zd,KST_dialog_event,"save");                                //  run dialog, parallel

   takeMouse(zd,KST_mousefunc,dragcursor);                                 //  connect mouse function
   return;
}


//  dialog completion callback function

int KST_dialog_event(zdialog *zd, cchar *event)
{
   int      ii, px, py;

   if (strEqu(event,"focus"))                                              //  toggle mouse capture   12.01
      takeMouse(zd,KST_mousefunc,dragcursor);

   if (! zd->zstat) return 1;                                              //  wait for completion
   
   if (zd->zstat == 1)                                                     //  apply
   {
      erase_toptext(102);                                                  //  erase points
      KST_warpfunc();                                                      //  do the warp
      zd->zstat = 0;                                                       //  keep dialog active
   }
   
   else if (zd->zstat == 2)                                                //  reset
   {
      edit_reset();
      zd->zstat = 0;

      for (ii = 0; ii < KST_npix; ii++)                                    //  show pixel labels on image
      {
         px = KST_pixel[ii][0];
         py = KST_pixel[ii][1];
         add_toptext(102,px,py,KST_pixlab[ii],"Sans 8");
      }
      mwpaint2();
   }
   
   else if (zd->zstat == 3)                                                //  done
   {
      erase_toptext(102);                                                  //  erase points
      edit_done(EFkeystone);
   }
      
   else {                                                                  //  cancel
      erase_toptext(102);                                                  //  erase points
      edit_cancel(EFkeystone);
   }
   
   return 0;
}


//  mouse function - click on 4 corners of tetragon

void  KST_mousefunc(void)
{
   int            ii, minii, jj, px, py;
   double         dist, distx, disty, mindist;
   
   if (LMclick)                                                            //  left click
   {
      LMclick = 0;
      
      for (ii = 0; ii < KST_npix; ii++)                                    //  check if very near a previous corner
      {
         if (abs(KST_pixel[ii][0] - Mxclick) < 0.07 * E3ww 
          && abs(KST_pixel[ii][1] - Myclick) < 0.07 * E3hh)
         {
            KST_pixel[ii][0] = Mxclick;                                    //  yes, set new corner position
            KST_pixel[ii][1] = Myclick;
            goto showcorners;
         }
      }

      if (KST_npix < 4)                                                    //  if < 4 corners, add a new one
      {
         ii = KST_npix;                                                    //  next corner to fill
         KST_pixel[ii][0] = Mxclick;                                       //  save newest corner position
         KST_pixel[ii][1] = Myclick;
         KST_npix++;
         goto showcorners;
      }

      mindist = 99999;                                                     //  all 4 corners already specified
      minii = -1;
      
      for (ii = 0; ii < 4; ii++)                                           //  find closest corner to click position
      {
         distx = (Mxclick - KST_pixel[ii][0]);
         disty = (Myclick - KST_pixel[ii][1]);
         dist = sqrt(distx*distx + disty*disty);
         if (dist < mindist) {
            mindist = dist;
            minii = ii;
         }
      }
      
      if (minii >= 0) {                                                    //  set new corner position
         ii = minii;
         KST_pixel[ii][0] = Mxclick;
         KST_pixel[ii][1] = Myclick;
         goto showcorners;
      }
   }

   else if (RMclick)                                                       //  right click
   {
      RMclick = 0;
      mindist = 99999;
      minii = -1;
      
      for (ii = 0; ii < KST_npix; ii++)                                    //  find closest corner to click position
      {
         distx = (Mxclick - KST_pixel[ii][0]);
         disty = (Myclick - KST_pixel[ii][1]);
         dist = sqrt(distx*distx + disty*disty);
         if (dist < mindist) {
            mindist = dist;
            minii = ii;
         }
      }
      
      if (minii >= 0) {                                                    //  replace deleted corner with
         ii = minii;                                                       //    last corner
         jj = KST_npix - 1;
         KST_pixel[ii][0] = KST_pixel[jj][0];
         KST_pixel[ii][1] = KST_pixel[jj][1];
         --KST_npix;                                                       //  reduce corner count
         goto showcorners;
      }
   }

showcorners:
                                                                           //  show corner labels on image
   erase_toptext(102);
   
   for (ii = 0; ii < KST_npix; ii++)
   {
      px = KST_pixel[ii][0];
      py = KST_pixel[ii][1];
      add_toptext(102,px,py,KST_pixlab[ii],"Sans 8");
   }
   
   mwpaint2();
   return;
}


//  keystone warp function - make input tetragon into a rectangle 

void  KST_warpfunc(void)
{
   int         ii, jj, tempx, tempy, vstat;
   double      px3, py3, trpx[4], trpy[4];
   double      sqpx0, sqpy0, sqpx1, sqpy1, sqpx2, sqpy2, sqpx3, sqpy3;
   double      cdx0, cdy0, cdx1, cdy1, cdx2, cdy2, cdx3, cdy3;
   double      px1, py1, dispx, dispy, sqww, sqhh;
   double      f0, f1, f2, f3;
   uint16      vpix1[3], *pix3;

   if (KST_npix != 4) {
      zmessageACK(mWin,ZTX("must have 4 corners"));
      return;
   }

   for (ii = 0; ii < 4; ii++) {                                            //  get 4 selected tetragon points
      trpx[ii] = KST_pixel[ii][0];   
      trpy[ii] = KST_pixel[ii][1];   
   }
   
   //  sort 4 points in clockwise order NW, NE, SE, SW
   
   for (ii = 0; ii < 4; ii++) {                                            //  sort top to bottom (y order)
      for (jj = ii; jj < 4; jj++) {
         if (trpy[jj] < trpy[ii]) {
            tempx = trpx[ii];
            tempy = trpy[ii];
            trpx[ii] = trpx[jj];
            trpy[ii] = trpy[jj];
            trpx[jj] = tempx;
            trpy[jj] = tempy;
         }
      }
   }
   
   if (trpx[1] < trpx[0]) {                                                //  sort upper two left, right
      tempx = trpx[0];
      tempy = trpy[0];
      trpx[0] = trpx[1];
      trpy[0] = trpy[1];
      trpx[1] = tempx;
      trpy[1] = tempy;
   }

   if (trpx[2] < trpx[3]) {                                                //  sort lower two right, left
      tempx = trpx[2];
      tempy = trpy[2];
      trpx[2] = trpx[3];
      trpy[2] = trpy[3];
      trpx[3] = tempx;
      trpy[3] = tempy;
   }
   
   if (trpx[0] < trpx[3]) sqpx0 = sqpx3 = trpx[0];                         //  rectangle enclosing tetragon
   else  sqpx0 = sqpx3 = trpx[3];
   if (trpx[1] > trpx[2]) sqpx1 = sqpx2 = trpx[1];
   else  sqpx1 = sqpx2 = trpx[2];
   if (trpy[0] < trpy[1]) sqpy0 = sqpy1 = trpy[0];
   else  sqpy0 = sqpy1 = trpy[1];
   if (trpy[2] > trpy[3]) sqpy2 = sqpy3 = trpy[2];
   else  sqpy2 = sqpy3 = trpy[3];

/***   
   sqpx0 = sqpx3 = 0.5 * (trpx[0] + trpx[3]);                              //  rectangle bisecting tetragon sides
   sqpx1 = sqpx2 = 0.5 * (trpx[1] + trpx[2]);
   sqpy0 = sqpy1 = 0.5 * (trpy[0] + trpy[1]);
   sqpy2 = sqpy3 = 0.5 * (trpy[2] + trpy[3]);
***/
   
   cdx0 = sqpx0 - trpx[0];                                                 //  displavement of tetragon corner
   cdy0 = sqpy0 - trpy[0];                                                 //    to corresponding rectangle corner
   cdx1 = sqpx1 - trpx[1];
   cdy1 = sqpy1 - trpy[1];
   cdx2 = sqpx2 - trpx[2];
   cdy2 = sqpy2 - trpy[2];
   cdx3 = sqpx3 - trpx[3];
   cdy3 = sqpy3 - trpy[3];

   sqww = 1.0 / (sqpx1 - sqpx0);                                           //  rectangle width and height
   sqhh = 1.0 / (sqpy3 - sqpy0);
   
   for (py3 = 0; py3 < E3hh; py3++)                                        //  loop all output pixels
   for (px3 = 0; px3 < E3ww; px3++)
   {
      f0 = (1.0 - (px3 - sqpx0) * sqww) * (1.0 - (py3 - sqpy0) * sqhh);
      f1 = (px3 - sqpx0) * sqww * (1.0 - (py3 - sqpy0) * sqhh);
      f2 = (px3 - sqpx0) * sqww * (py3 - sqpy0) * sqhh;
      f3 = (1.0 - (px3 - sqpx0) * sqww) * (py3 - sqpy0) * sqhh;

      dispx = cdx0 * f0 + cdx1 * f1 + cdx2 * f2 + cdx3 * f3;
      dispy = cdy0 * f0 + cdy1 * f1 + cdy2 * f2 + cdy3 * f3;

      px1 = px3 - dispx;                                                   //  input virtual pixel for px3/py3
      py1 = py3 - dispy;

      pix3 = PXMpix(E3pxm16,int(px3),int(py3));                            //  output pixel

      vstat = vpixel(E1pxm16,px1,py1,vpix1);                               //  output pixel = input virtual pixel
      if (vstat) {
         pix3[0] = vpix1[0];
         pix3[1] = vpix1[1];
         pix3[2] = vpix1[2];
      }
      else pix3[0] = pix3[1] = pix3[2] = 0;
   }

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


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

//  warp/distort area - select image area and pull with mouse

float       *WarpAx, *WarpAy;                                              //  memory of all displaced pixels
float       WarpAmem[4][100];                                              //  undo memory, last 100 warps
int         NWarpA;                                                        //  WarpA mem count
int         WarpA_started;
int         WarpA_areanumber;

editfunc    EFwarpA;

void  WarpA_init();
void  WarpA_warpfunc(float wdx, float wdy, float wdw, float wdh, int acc);
void  WarpA_mousefunc();


void m_warp_area(GtkWidget *, cchar *)
{
   int      WarpA_dialog_event(zdialog *zd, cchar *event);
   
   cchar  *WarpA_message = ZTX(
             " Select an area to warp using select area function. \n"
             " Press [start warp] and pull area with mouse. \n"
             " Make multiple mouse pulls until satisfied. \n"
             " When finished, select another area or press [done]."); 
   
   zfuncs::F1_help_topic = "warp_area";

   EFwarpA.funcname = "warp-area";
   EFwarpA.Farea = 2;                                                      //  select area usable
   EFwarpA.mousefunc = WarpA_mousefunc;                                    //  mouse function
   if (! edit_setup(EFwarpA)) return;                                      //  setup edit

   zdialog *zd = zdialog_new(ZTX("Warp Image (area)"),mWin,Bdone,Bcancel,null);
   EFwarpA.zd = zd;

   zdialog_add_widget(zd,"label","lab1","dialog",WarpA_message,"space=5");
   zdialog_add_widget(zd,"hbox","hb1","dialog",0,"space=5");
   zdialog_add_widget(zd,"button","start","hb1",ZTX("start warp"),"space=5");
   zdialog_add_widget(zd,"button","undolast","hb1",Bundolast,"space=5");
   zdialog_add_widget(zd,"button","undoall","hb1",Bundoall,"space=5");

   zdialog_help(zd,"warp_area");                                           //  zdialog help topic        v.11.08
   zdialog_run(zd,WarpA_dialog_event,"save");                              //  run dialog, parallel            v.11.07

   WarpAx = (float *) zmalloc(E3ww * E3hh * sizeof(float),"warpA");        //  get memory for pixel displacements
   WarpAy = (float *) zmalloc(E3ww * E3hh * sizeof(float),"warpA");
   
   WarpA_init();
   
   return;
}


//  clear warp data when warp started or re-started after new select area

void WarpA_init()                                                          //  v.11.12
{
   int         px, py, ii;

   NWarpA = 0;                                                             //  no warp data
   WarpA_started = 0;
   WarpA_areanumber = 0;

   for (py = 0; py < E3hh; py++)                                           //  no pixel displacements
   for (px = 0; px < E3ww; px++)
   {
      ii = py * E3ww + px;
      WarpAx[ii] = WarpAy[ii] = 0.0;
   }
   
   mwpaint2();
   return;
}


//  dialog event and completion callback function

int WarpA_dialog_event(zdialog * zd, cchar *event)
{
   int         px, py, ii;
   float       wdx, wdy, wdw, wdh;

   if (zd->zstat)                                                          //  dialog complete
   {
      if (NWarpA) CEF->Fmod = 1;
      else CEF->Fmod = 0;

      if (zd->zstat == 1) edit_done(EFwarpA);
      else edit_cancel(EFwarpA);

      zfree(WarpAx);                                                       //  release undo memory
      zfree(WarpAy);
      return 0;
   }

   if (! Factivearea || sa_mode == 7)                                      //  no select area active        v.11.12
      WarpA_init();
   
   if (WarpA_started && WarpA_areanumber != areanumber)                    //  select area changed          v.11.12
      WarpA_init();

   if (strEqu(event,"start"))                                              //  start warp
   {
      if (! Factivearea || sa_mode == 7) {                                 //  no select area active        v.11.01
         zmessageACK(mWin,ZTX("no active Select Area"));
         return 0;
      }
      sa_edgecalc();                                                       //  calculate area edge distances
      takeMouse(zd,WarpA_mousefunc,dragcursor);                            //  connect mouse function          v.11.03
      WarpA_started = 1;
      WarpA_areanumber = areanumber;
   }
   
   if (strEqu(event,"focus"))                                              //  toggle mouse capture         v.12.01
      takeMouse(zd,WarpA_mousefunc,dragcursor);

   if (strEqu(event,"undolast")) {
      if (NWarpA) {                                                        //  undo most recent warp
         ii = --NWarpA;
         wdx = WarpAmem[0][ii];
         wdy = WarpAmem[1][ii];
         wdw = WarpAmem[2][ii];
         wdh = WarpAmem[3][ii];
         WarpA_warpfunc(wdx,wdy,-wdw,-wdh,0);                              //  unwarp image
         WarpA_warpfunc(wdx,wdy,-wdw,-wdh,1);                              //  unwarp memory
      }
   }

   if (strEqu(event,"undoall"))                                            //  undo all warps
   {
      edit_reset();                                                        //  v.10.3

      for (py = 0; py < E3hh; py++)                                        //  reset pixel displacements
      for (px = 0; px < E3ww; px++)
      {
         ii = py * E3ww + px;
         WarpAx[ii] = WarpAy[ii] = 0.0;
      }

      NWarpA = 0;                                                          //  erase undo memory
      mwpaint2(); 
   }

   return 1;
}


//  warp mouse function

void  WarpA_mousefunc()
{
   static float   wdx, wdy, wdw, wdh;
   static int     ii, warped = 0;

   if (Mxdrag || Mydrag)                                                   //  mouse drag underway
   {
      wdx = Mxdown;                                                        //  drag origin, image coordinates
      wdy = Mydown;
      wdw = Mxdrag - Mxdown;                                               //  drag increment
      wdh = Mydrag - Mydown;
      WarpA_warpfunc(wdx,wdy,wdw,wdh,0);                                   //  warp image
      warped = 1;
      return;
   }

   else if (warped) 
   {
      warped = 0;
      WarpA_warpfunc(wdx,wdy,wdw,wdh,1);                                   //  drag done, add to warp memory

      if (NWarpA == 100)                                                   //  if full, throw away oldest
      {
         NWarpA = 99;
         for (ii = 0; ii < NWarpA; ii++)
         {
            WarpAmem[0][ii] = WarpAmem[0][ii+1];
            WarpAmem[1][ii] = WarpAmem[1][ii+1];
            WarpAmem[2][ii] = WarpAmem[2][ii+1];
            WarpAmem[3][ii] = WarpAmem[3][ii+1];
         }
      }

      ii = NWarpA;
      WarpAmem[0][ii] = wdx;                                               //  save warp for undo
      WarpAmem[1][ii] = wdy;
      WarpAmem[2][ii] = wdw;
      WarpAmem[3][ii] = wdh;
      NWarpA++;
   }

   return;
}


//  warp image and accumulate warp memory

void  WarpA_warpfunc(float wdx, float wdy, float wdw, float wdh, int acc)
{
   int            ii, px, py, ww, hh, vstat;
   double         ddx, ddy, dpe, dpm, mag, dispx, dispy;
   uint16         vpix[3], *pix3;

   edit_zapredo();                                                         //  delete redo copy          v.10.3
   
   if (! Factivearea) return;                                              //  area erased               v.11.06.1
   
   for (py = sa_miny; py < sa_maxy; py++)                                  //  loop all pixels in area   v.10.11
   for (px = sa_minx; px < sa_maxx; px++)
   {
      ii = py * Fww + px;
      dpe = sa_pixmap[ii];                                                 //  distance from area edge
      if (dpe < 1) continue;
      dpe -= 1;                                                            //  v.11.12

      ddx = (px - wdx);
      ddy = (py - wdy);
      dpm = sqrt(ddx*ddx + ddy*ddy);                                       //  distance from drag origin
      
      mag = (dpe / (dpm + dpe)) * (1.0 - dpm / (dpm + dpe));               //  v.11.12
      
      dispx = -wdw * mag;                                                  //  pixel movement from drag movement
      dispy = -wdh * mag;
      
      if (acc) {                                                           //  mouse drag done,
         WarpAx[ii] += dispx;                                              //    accumulate warp memory
         WarpAy[ii] += dispy;
         continue;
      }

      dispx += WarpAx[ii];                                                 //  add this warp to prior
      dispy += WarpAy[ii];
      
      vstat = vpixel(E1pxm16,px+dispx,py+dispy,vpix);                      //  input virtual pixel
      if (vstat) {
         pix3 = PXMpix(E3pxm16,px,py);                                     //  output pixel
         pix3[0] = vpix[0];
         pix3[1] = vpix[1];
         pix3[2] = vpix[2];
      }
   }

   ww = sa_maxx - sa_minx;                                                 //  update window    v.10.11
   hh = sa_maxy - sa_miny;
   mwpaint3(sa_minx,sa_miny,ww,hh);

   CEF->Fmod = 1;                                                          //  v.10.2
   return;
}


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

//  warp/distort whole image with a curved transform
//  fix perspective problems (e.g. curved walls, leaning buildings)

float       *WarpCx, *WarpCy;                                              //  memory of all dragged pixels
float       WarpCmem[4][100];                                              //  undo memory, last 100 drags
int         NWarpC;                                                        //  WarpCmem count
int         WarpCdrag;
int         WarpCww, WarpChh;

editfunc    EFwarpC;

void  WarpC_warpfunc(float wdx, float wdy, float wdw, float wdh, int acc);
void  WarpC_mousefunc(void);


void m_warp_curved(GtkWidget *, cchar *)
{
   int      WarpC_dialog_event(zdialog *zd, cchar *event);

   cchar  *WarpC_message = ZTX(
             " Pull an image position using the mouse. \n"
             " Make multiple mouse pulls until satisfied. \n"
             " When finished, press [done]."); 
   
   int         px, py, ii;

   zfuncs::F1_help_topic = "warp_curved";

   EFwarpC.funcname = "warp-curved";
   EFwarpC.Fprev = 1;                                                      //  use preview
   EFwarpC.mousefunc = WarpC_mousefunc;                                    //  mouse function
   if (! edit_setup(EFwarpC)) return;                                      //  setup edit

   zdialog *zd = zdialog_new(ZTX("Warp Image (curved)"),mWin,Bdone,Bcancel,null);
   EFwarpC.zd = zd;

   zdialog_add_widget(zd,"label","lab1","dialog",WarpC_message,"space=5");
   zdialog_add_widget(zd,"hbox","hb1","dialog",0,"space=10");
   zdialog_add_widget(zd,"button","undolast","hb1",Bundolast,"space=5");
   zdialog_add_widget(zd,"button","undoall","hb1",Bundoall,"space=5");
   zdialog_add_widget(zd,"hbox","hb2","dialog",0,"space=10");
   zdialog_add_widget(zd,"button","grid","hb2",ZTX("Grid"),"space=10");

   load_grid(warpC_grid);                                                  //  load grid preferences           v.11.11

   zdialog_help(zd,"warp_curved");                                         //  zdialog help topic
   zdialog_run(zd,WarpC_dialog_event,"save");                              //  run dialog, parallel            v.11.07

   NWarpC = WarpCdrag = 0;                                                 //  no drag data

   WarpCx = (float *) zmalloc(E3ww * E3hh * sizeof(float),"warpC");        //  get memory for pixel displacements
   WarpCy = (float *) zmalloc(E3ww * E3hh * sizeof(float),"warpC");
   
   for (py = 0; py < E3hh; py++)                                           //  no pixel displacements
   for (px = 0; px < E3ww; px++)
   {
      ii = py * E3ww + px;
      WarpCx[ii] = WarpCy[ii] = 0.0;
   }
   
   WarpCww = E3ww;                                                         //  preview dimensions
   WarpChh = E3hh;
   
   takeMouse(zd,WarpC_mousefunc,dragcursor);                               //  connect mouse function          v.11.03
   return;
}


//  dialog event and completion callback function

int WarpC_dialog_event(zdialog * zd, cchar *event)
{
   int         px, py, ii;
   float       wdx, wdy, wdw, wdh;
   int         fpx, fpy, epx, epy, vstat;
   double      scale, dispx, dispy;
   uint16      vpix[3], *pix3;

   if (zd->zstat) goto complete;

   if (strEqu(event,"undolast")) 
   {
      if (NWarpC == 1) event = "undoall";
      else if (NWarpC) {                                                   //  undo most recent drag
         ii = --NWarpC;
         wdx = WarpCmem[0][ii];
         wdy = WarpCmem[1][ii];
         wdw = WarpCmem[2][ii];
         wdh = WarpCmem[3][ii];
         WarpC_warpfunc(wdx,wdy,-wdw,-wdh,0);                              //  undrag image
         WarpC_warpfunc(wdx,wdy,-wdw,-wdh,1);                              //  undrag memory
      }
   }

   if (strEqu(event,"undoall"))                                            //  undo all drags
   {
      NWarpC = 0;                                                          //  erase undo memory

      for (py = 0; py < E3hh; py++)                                        //  reset pixel displacements
      for (px = 0; px < E3ww; px++)
      {
         ii = py * E3ww + px;
         WarpCx[ii] = WarpCy[ii] = 0.0;
      }
      edit_reset();                                                        //  restore image 1  v.10.3
   }
   
   if (strEqu(event,"grid")) m_gridlines(0,0);                             //  grid dialog           v.11.11
   
   if (strstr("KB G KB g",event))                                          //  G key, toggle grid    v.11.11
      toggle_grid(2);

   return 1;

complete:

   save_grid(warpC_grid);                                                  //  save grid preferences     v.11.11
   Fgrid = 0;                                                              //  grid off

   if (zd->zstat != 1) edit_cancel(EFwarpC);
   else if (NWarpC == 0) edit_cancel(EFwarpC);
   else 
   {
      edit_fullsize();                                                     //  get full-size E1/E3

      scale = 1.0 * (E3ww + E3hh) / (WarpCww + WarpChh);

      for (fpy = 0; fpy < E3hh; fpy++)                                     //  scale net pixel displacements
      for (fpx = 0; fpx < E3ww; fpx++)                                     //    to full image size
      {
         epx = WarpCww * fpx / E3ww;
         epy = WarpChh * fpy / E3hh;
         ii = epy * WarpCww + epx;
         dispx = WarpCx[ii] * scale;
         dispy = WarpCy[ii] * scale;

         vstat = vpixel(E1pxm16,fpx+dispx,fpy+dispy,vpix);                 //  input virtual pixel
         pix3 = PXMpix(E3pxm16,fpx,fpy);                                   //  output pixel
         if (vstat) {
            pix3[0] = vpix[0];
            pix3[1] = vpix[1];
            pix3[2] = vpix[2];
         }
         else pix3[0] = pix3[1] = pix3[2] = 0;
      }

      edit_done(EFwarpC);
   }

   zfree(WarpCx);                                                          //  release memory
   zfree(WarpCy);
   return 0;
}


//  WarpC mouse function

void  WarpC_mousefunc(void)
{
   static float   wdx, wdy, wdw, wdh;
   int            ii;

   if (Mxdrag || Mydrag)                                                   //  mouse drag underway
   {
      wdx = Mxdown;                                                        //  drag origin, window coordinates
      wdy = Mydown;
      wdw = Mxdrag - Mxdown;                                               //  drag increment
      wdh = Mydrag - Mydown;
      WarpC_warpfunc(wdx,wdy,wdw,wdh,0);                                   //  drag image
      WarpCdrag = 1;
      return;
   }
   
   else if (WarpCdrag) 
   {
      WarpCdrag = 0;
      WarpC_warpfunc(wdx,wdy,wdw,wdh,1);                                   //  drag done, add to memory

      if (NWarpC == 100)                                                   //  if full, throw away oldest
      {
         NWarpC = 99;
         for (ii = 0; ii < NWarpC; ii++)
         {
            WarpCmem[0][ii] = WarpCmem[0][ii+1];
            WarpCmem[1][ii] = WarpCmem[1][ii+1];
            WarpCmem[2][ii] = WarpCmem[2][ii+1];
            WarpCmem[3][ii] = WarpCmem[3][ii+1];
         }
      }

      ii = NWarpC;
      WarpCmem[0][ii] = wdx;                                               //  save drag for undo
      WarpCmem[1][ii] = wdy;
      WarpCmem[2][ii] = wdw;
      WarpCmem[3][ii] = wdh;
      NWarpC++;
   }
   
   return;
}


//  warp image and accumulate warp memory
//  mouse at (mx,my) is moved (mw,mh) pixels

void  WarpC_warpfunc(float mx, float my, float mw, float mh, int acc)
{
   int         ii, px, py, vstat;
   double      mag, dispx, dispy;
   double      d1, d2, d3, d4;
   uint16      vpix[3], *pix3;
   
   edit_zapredo();                                                         //  delete redo copy    v.10.3
   
   d1 = (mx-0) * (mx-0) + (my-0) * (my-0);                                 //  distance, mouse to 4 corners
   d2 = (E3ww-mx) * (E3ww-mx) + (my-0) * (my-0);
   d3 = (E3ww-mx) * (E3ww-mx) + (E3hh-my) * (E3hh-my);
   d4 = (mx-0) * (mx-0) + (E3hh-my) * (E3hh-my);
   
   if (d2 > d1) d1 = d2;                                                   //  d1 = greatest       v.10.11
   if (d3 > d1) d1 = d3;
   if (d4 > d1) d1 = d4;

   for (py = 0; py < E3hh; py++)                                           //  process all pixels
   for (px = 0; px < E3ww; px++)
   {
      d2 = (px-mx)*(px-mx) + (py-my)*(py-my);
      mag = (1.0 - d2 / d1);
      mag = mag * mag;                                                     //  faster than pow(mag,16);
      mag = mag * mag;

      dispx = -mw * mag;                                                   //  displacement = drag * mag
      dispy = -mh * mag;
      
      ii = py * E3ww + px;

      if (acc) {                                                           //  drag done, accumulate drag sum
         WarpCx[ii] += dispx;
         WarpCy[ii] += dispy;
         continue;
      }

      dispx += WarpCx[ii];                                                 //  add this drag to prior sum
      dispy += WarpCy[ii];

      vstat = vpixel(E1pxm16,px+dispx,py+dispy,vpix);                      //  input virtual pixel
      pix3 = PXMpix(E3pxm16,px,py);                                        //  output pixel
      if (vstat) {
         pix3[0] = vpix[0];
         pix3[1] = vpix[1];
         pix3[2] = vpix[2];
      }
      else pix3[0] = pix3[1] = pix3[2] = 0;
   }

   CEF->Fmod = 1;
   mwpaint2();                                                             //  update window
   return;
}


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

//  warp/distort whole image with a linear transform
//  fix perspective problems (e.g. curved walls, leaning buildings)

float       *WarpLx, *WarpLy;                                              //  memory of all dragged pixels
float       WarpLmem[4][100];                                              //  undo memory, last 100 drags
int         NWarpL;                                                        //  WarpLmem count
int         WarpLdrag;
int         WarpLww, WarpLhh;

editfunc    EFwarpL;

void  WarpL_warpfunc(float wdx, float wdy, float wdw, float wdh, int acc);
void  WarpL_mousefunc(void);


void m_warp_linear(GtkWidget *, cchar *)                                   //  new v.10.11
{
   int      WarpL_dialog_event(zdialog *zd, cchar *event);

   cchar  *WarpL_message = ZTX(
             " Pull an image position using the mouse. \n"
             " Make multiple mouse pulls until satisfied. \n"
             " When finished, press [done]."); 
   
   int         px, py, ii;

   zfuncs::F1_help_topic = "warp_linear";

   EFwarpL.funcname = "warp-linear";
   EFwarpL.Fprev = 1;                                                      //  use preview
   EFwarpL.mousefunc = WarpL_mousefunc;                                    //  mouse function
   if (! edit_setup(EFwarpL)) return;                                      //  setup edit

   zdialog *zd = zdialog_new(ZTX("Warp Image (linear)"),mWin,Bdone,Bcancel,null);
   EFwarpL.zd = zd;

   zdialog_add_widget(zd,"label","lab1","dialog",WarpL_message,"space=5");
   zdialog_add_widget(zd,"hbox","hb1","dialog",0,"space=10");
   zdialog_add_widget(zd,"button","undolast","hb1",Bundolast,"space=5");
   zdialog_add_widget(zd,"button","undoall","hb1",Bundoall,"space=5");
   zdialog_add_widget(zd,"hbox","hb2","dialog",0,"space=10");
   zdialog_add_widget(zd,"button","grid","hb2",ZTX("Grid"),"space=10");

   load_grid(warpL_grid);                                                  //  load grid preferences           v.11.11

   zdialog_help(zd,"warp_linear");                                         //  zdialog help topic           v.11.08
   zdialog_run(zd,WarpL_dialog_event,"save");                              //  run dialog, parallel         v.11.07

   NWarpL = WarpLdrag = 0;                                                 //  no drag data

   WarpLx = (float *) zmalloc(E3ww * E3hh * sizeof(float),"warpL");        //  get memory for pixel displacements
   WarpLy = (float *) zmalloc(E3ww * E3hh * sizeof(float),"warpL");
   
   for (py = 0; py < E3hh; py++)                                           //  no pixel displacements
   for (px = 0; px < E3ww; px++)
   {
      ii = py * E3ww + px;
      WarpLx[ii] = WarpLy[ii] = 0.0;
   }
   
   WarpLww = E3ww;                                                         //  preview dimensions
   WarpLhh = E3hh;
   
   takeMouse(zd,WarpL_mousefunc,dragcursor);                               //  connect mouse function          v.11.03
   return;
}


//  dialog event and completion callback function

int WarpL_dialog_event(zdialog * zd, cchar *event)
{
   int         px, py, ii;
   float       wdx, wdy, wdw, wdh;
   int         fpx, fpy, epx, epy, vstat;
   double      scale, dispx, dispy;
   uint16      vpix[3], *pix3;

   if (zd->zstat) goto complete;

   if (strEqu(event,"undolast")) 
   {
      if (NWarpL == 1) event = "undoall";
      else if (NWarpL) {                                                   //  undo most recent drag
         ii = --NWarpL;
         wdx = WarpLmem[0][ii];
         wdy = WarpLmem[1][ii];
         wdw = WarpLmem[2][ii];
         wdh = WarpLmem[3][ii];
         WarpL_warpfunc(wdx,wdy,-wdw,-wdh,0);                              //  undrag image
         WarpL_warpfunc(wdx,wdy,-wdw,-wdh,1);                              //  undrag memory
      }
   }

   if (strEqu(event,"undoall"))                                            //  undo all drags
   {
      NWarpL = 0;                                                          //  erase undo memory

      for (py = 0; py < E3hh; py++)                                        //  reset pixel displacements
      for (px = 0; px < E3ww; px++)
      {
         ii = py * E3ww + px;
         WarpLx[ii] = WarpLy[ii] = 0.0;
      }
      edit_reset();                                                        //  restore image 1  v.10.3
   }

   if (strEqu(event,"grid")) m_gridlines(0,0);                             //  grid dialog           v.11.11
   
   if (strstr("KB G KB g",event))                                          //  G key, toggle grid    v.11.11
      toggle_grid(2);

   return 1;

complete:

   save_grid(warpL_grid);                                                  //  save grid preferences     v.11.11
   Fgrid = 0;                                                              //  grid off

   if (zd->zstat != 1) edit_cancel(EFwarpL);
   else if (NWarpL == 0) edit_cancel(EFwarpL);
   else 
   {
      edit_fullsize();                                                     //  get full-size E1/E3

      scale = 1.0 * (E3ww + E3hh) / (WarpLww + WarpLhh);

      for (fpy = 0; fpy < E3hh; fpy++)                                     //  scale net pixel displacements
      for (fpx = 0; fpx < E3ww; fpx++)                                     //    to full image size
      {
         epx = WarpLww * fpx / E3ww;
         epy = WarpLhh * fpy / E3hh;
         ii = epy * WarpLww + epx;
         dispx = WarpLx[ii] * scale;
         dispy = WarpLy[ii] * scale;

         vstat = vpixel(E1pxm16,fpx+dispx,fpy+dispy,vpix);                 //  input virtual pixel
         pix3 = PXMpix(E3pxm16,fpx,fpy);                                   //  output pixel
         if (vstat) {
            pix3[0] = vpix[0];
            pix3[1] = vpix[1];
            pix3[2] = vpix[2];
         }
         else pix3[0] = pix3[1] = pix3[2] = 0;
      }

      edit_done(EFwarpL);
   }

   zfree(WarpLx);                                                          //  release memory
   zfree(WarpLy);
   return 0;
}


//  WarpL mouse function

void  WarpL_mousefunc(void)
{
   static float   wdx, wdy, wdw, wdh;
   int            ii;

   if (Mxdrag || Mydrag)                                                   //  mouse drag underway
   {
      wdx = Mxdown;                                                        //  drag origin, window coordinates
      wdy = Mydown;
      wdw = Mxdrag - Mxdown;                                               //  drag increment
      wdh = Mydrag - Mydown;
      WarpL_warpfunc(wdx,wdy,wdw,wdh,0);                                   //  drag image
      WarpLdrag = 1;
      return;
   }
   
   else if (WarpLdrag) 
   {
      WarpLdrag = 0;
      WarpL_warpfunc(wdx,wdy,wdw,wdh,1);                                   //  drag done, add to memory

      if (NWarpL == 100)                                                   //  if full, throw away oldest
      {
         NWarpL = 99;
         for (ii = 0; ii < NWarpL; ii++)
         {
            WarpLmem[0][ii] = WarpLmem[0][ii+1];
            WarpLmem[1][ii] = WarpLmem[1][ii+1];
            WarpLmem[2][ii] = WarpLmem[2][ii+1];
            WarpLmem[3][ii] = WarpLmem[3][ii+1];
         }
      }

      ii = NWarpL;
      WarpLmem[0][ii] = wdx;                                               //  save drag for undo
      WarpLmem[1][ii] = wdy;
      WarpLmem[2][ii] = wdw;
      WarpLmem[3][ii] = wdh;
      NWarpL++;
   }
   
   return;
}


//  warp image and accumulate warp memory
//  mouse at (mx,my) is moved (mw,mh) pixels

void  WarpL_warpfunc(float mx, float my, float mw, float mh, int acc)
{
   int         ii, px, py, vstat;
   double      mag, dispx, dispy;
   double      d1, d2, d3, d4;
   uint16      vpix[3], *pix3;
   
   edit_zapredo();                                                         //  delete redo copy    v.10.3
   
   d1 = (mx-0) * (mx-0) + (my-0) * (my-0);                                 //  distance, mouse to 4 corners
   d2 = (E3ww-mx) * (E3ww-mx) + (my-0) * (my-0);
   d3 = (E3ww-mx) * (E3ww-mx) + (E3hh-my) * (E3hh-my);
   d4 = (mx-0) * (mx-0) + (E3hh-my) * (E3hh-my);
   
   if (d2 > d1) d1 = d2;                                                   //  d1 = greatest       v.10.11
   if (d3 > d1) d1 = d3;
   if (d4 > d1) d1 = d4;
   
   d1 = sqrt(d1);

   for (py = 0; py < E3hh; py++)                                           //  process all pixels
   for (px = 0; px < E3ww; px++)
   {
      d2 = (px-mx)*(px-mx) + (py-my)*(py-my);
      d2 = sqrt(d2);
      mag = (1.0 - d2 / d1);

      dispx = -mw * mag;                                                   //  displacement = drag * mag
      dispy = -mh * mag;
      
      ii = py * E3ww + px;

      if (acc) {                                                           //  drag done, accumulate drag sum
         WarpLx[ii] += dispx;
         WarpLy[ii] += dispy;
         continue;
      }

      dispx += WarpLx[ii];                                                 //  add this drag to prior sum
      dispy += WarpLy[ii];

      vstat = vpixel(E1pxm16,px+dispx,py+dispy,vpix);                      //  input virtual pixel
      pix3 = PXMpix(E3pxm16,px,py);                                        //  output pixel
      if (vstat) {
         pix3[0] = vpix[0];
         pix3[1] = vpix[1];
         pix3[2] = vpix[2];
      }
      else pix3[0] = pix3[1] = pix3[2] = 0;
   }

   CEF->Fmod = 1;
   mwpaint2();                                                             //  update window
   return;
}


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

//  warp/distort whole image using affine transform 
//  (straight lines remain straight)

double      WarpF_old[3][2];                                               //  3 original image points 
double      WarpF_new[3][2];                                               //  corresponding warped points
double      WarpF_coeff[6];                                                //  transform coefficients
double      WarpF_Icoeff[6];                                               //  inverse transform coefficients
int         WarpF_ftf;                                                     //  first time flag

editfunc    EFwarpF;

void  WarpF_warpfunc();                                                    //  image warp function
void  WarpF_mousefunc(void);
void  WarpF_affine(double po[3][2], double pn[3][2], double coeff[6]);     //  compute affine transform coefficients
void  WarpF_invert(double coeff[6], double Icoeff[6]);                     //  compute reverse transform coefficients


void m_warp_affine(GtkWidget *, cchar *)
{
   int      WarpF_dialog_event(zdialog *zd, cchar *event);

   cchar  *WarpF_message = ZTX(
             " Pull on an image corner using the mouse. \n"
             " Make multiple mouse pulls until satisfied. \n"
             " When finished, press [done]."); 
   
   zfuncs::F1_help_topic = "warp_affine";

   EFwarpF.funcname = "warp-affine";
   EFwarpF.Fprev = 1;                                                      //  use preview
   EFwarpF.mousefunc = WarpF_mousefunc;                                    //  mouse function
   if (! edit_setup(EFwarpF)) return;                                      //  setup edit

   zdialog *zd = zdialog_new(ZTX("Warp Image (affine)"),mWin,Bdone,Bcancel,null);
   EFwarpF.zd = zd;

   zdialog_add_widget(zd,"label","lab1","dialog",WarpF_message,"space=5");
   zdialog_add_widget(zd,"hbox","hb2","dialog",0,"space=10");
   zdialog_add_widget(zd,"button","grid","hb2",ZTX("Grid"),"space=10");

   load_grid(warpF_grid);                                                  //  load grid preferences           v.11.11

   zdialog_help(zd,"warp_affine");                                         //  zdialog help topic              v.11.08
   zdialog_run(zd,WarpF_dialog_event,"save");                              //  run dialog, parallel            v.11.07

   WarpF_ftf = 1;                                                          //  1st warp flag

   takeMouse(zd,WarpF_mousefunc,dragcursor);                               //  connect mouse function          v.11.03
   return;
}


//  dialog event and completion callback function

int WarpF_dialog_event(zdialog *zd, cchar *event)
{
   double      scale;
   int         ww, hh;

   if (strEqu(event,"grid")) m_gridlines(0,0);                             //  grid dialog           v.11.11
   
   if (strstr("KB G KB g",event))                                          //  G key, toggle grid    v.11.11
      toggle_grid(2);

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

   save_grid(warpF_grid);                                                  //  save grid preferences     v.11.11
   Fgrid = 0;                                                              //  grid off

   if (zd->zstat != 1 || ! CEF->Fmod) {
      edit_cancel(EFwarpF);                                                //  bugfix     v.11.11
      return 0;
   }

   ww = E3ww;                                                              //  preview image dimensions
   hh = E3hh;

   edit_fullsize();                                                        //  get full-size images

   scale = 1.0 * (E3ww + E3hh) / (ww + hh);                                //  preview to full-size scale factor
   
   WarpF_old[0][0] = WarpF_old[0][0] * scale;                              //  re-scale new and old points
   WarpF_old[0][1] = WarpF_old[0][1] * scale;
   WarpF_old[1][0] = WarpF_old[1][0] * scale;
   WarpF_old[1][1] = WarpF_old[1][1] * scale;
   WarpF_old[2][0] = WarpF_old[2][0] * scale;
   WarpF_old[2][1] = WarpF_old[2][1] * scale;

   WarpF_new[0][0] = WarpF_new[0][0] * scale;
   WarpF_new[0][1] = WarpF_new[0][1] * scale;
   WarpF_new[1][0] = WarpF_new[1][0] * scale;
   WarpF_new[1][1] = WarpF_new[1][1] * scale;
   WarpF_new[2][0] = WarpF_new[2][0] * scale;
   WarpF_new[2][1] = WarpF_new[2][1] * scale;
   
   WarpF_warpfunc();                                                       //  warp full-size image
   edit_done(EFwarpF);

   return 0;
}


//  WarpF mouse function

void  WarpF_mousefunc(void)
{
   int      mdx1, mdy1, mdx2, mdy2;
   double   x1o, y1o, x2o, y2o, x3o, y3o;
   double   x1n, y1n, x2n, y2n, x3n, y3n;
   double   a, b, c, d, e, f;
   
   if (Mxdrag + Mydrag == 0) return;

   mdx1 = Mxdown;                                                          //  mouse drag origin
   mdy1 = Mydown;
   mdx2 = Mxdrag;                                                          //  mouse drag position
   mdy2 = Mydrag;

   Mxdown = Mxdrag;                                                        //  reset origin for next time
   Mydown = Mydrag;
   
   x1n = mdx1;                                                             //  point 1 = drag origin
   y1n = mdy1;
   x2n = E3ww - x1n;                                                       //  point 2 = mirror of point1
   y2n = E3hh - y1n;
   x3n = E3ww * (y2n / E3hh);
   y3n = E3hh * (1.0 - (x2n / E3ww));

   if (WarpF_ftf)                                                          //  first warp
   {
      WarpF_ftf = 0;
      x1o = x1n;                                                           //  old = current positions
      y1o = y1n;
      x2o = x2n;
      y2o = y2n;
      x3o = x3n;
      y3o = y3n;
   }
   else
   {
      WarpF_invert(WarpF_coeff,WarpF_Icoeff);                              //  get inverse coefficients
      a = WarpF_Icoeff[0];
      b = WarpF_Icoeff[1];
      c = WarpF_Icoeff[2];
      d = WarpF_Icoeff[3];
      e = WarpF_Icoeff[4];
      f = WarpF_Icoeff[5];
      
      x1o = a * x1n + b * y1n + c;                                         //  compute old from current positions
      y1o = d * x1n + e * y1n + f;
      x2o = a * x2n + b * y2n + c;
      y2o = d * x2n + e * y2n + f;
      x3o = a * x3n + b * y3n + c;
      y3o = d * x3n + e * y3n + f;
   }
      
   WarpF_old[0][0] = x1o;                                                  //  set up 3 old points and corresponding
   WarpF_old[0][1] = y1o;                                                  //    new points for affine translation
   WarpF_old[1][0] = x2o;
   WarpF_old[1][1] = y2o;
   WarpF_old[2][0] = x3o;
   WarpF_old[2][1] = y3o;

   x1n = mdx2;                                                             //  point 1 new position = drag position
   y1n = mdy2;
   x2n = E3ww - x1n;                                                       //  point 2 new = mirror of point1 new
   y2n = E3hh - y1n;

   WarpF_new[0][0] = x1n;                                                  //  3 new points 
   WarpF_new[0][1] = y1n;
   WarpF_new[1][0] = x2n;
   WarpF_new[1][1] = y2n;
   WarpF_new[2][0] = x3n;
   WarpF_new[2][1] = y3n;
   
   WarpF_warpfunc();                                                       //  do the warp

   return;
}


//  warp image and accumulate warp memory

void  WarpF_warpfunc()
{
   double      a, b, c, d, e, f;
   int         px3, py3, vstat;
   double      px1, py1;
   uint16      vpix1[3], *pix3;
   
   edit_zapredo();                                                         //  delete redo copy    v.10.3

   WarpF_affine(WarpF_old, WarpF_new, WarpF_coeff);                        //  get coefficients for forward transform
   WarpF_invert(WarpF_coeff, WarpF_Icoeff);                                //  get coefficients for reverse transform
   
   a = WarpF_Icoeff[0];                                                    //  coefficients to map output pixels
   b = WarpF_Icoeff[1];                                                    //    to corresponding input pixels
   c = WarpF_Icoeff[2];
   d = WarpF_Icoeff[3];
   e = WarpF_Icoeff[4];
   f = WarpF_Icoeff[5];
   
   for (py3 = 0; py3 < E3hh; py3++)                                        //  loop all output pixels
   for (px3 = 0; px3 < E3ww; px3++)
   {
      px1 = a * px3 + b * py3 + c;                                         //  corresponding input pixel
      py1 = d * px3 + e * py3 + f;

      vstat = vpixel(E1pxm16,px1,py1,vpix1);                               //  input virtual pixel
      pix3 = PXMpix(E3pxm16,px3,py3);                                      //  output pixel

      if (vstat) {
         pix3[0] = vpix1[0];
         pix3[1] = vpix1[1];
         pix3[2] = vpix1[2];
      }
      else pix3[0] = pix3[1] = pix3[2] = 0;
   }

   CEF->Fmod = 1;
   mwpaint2();                                                             //  update window
   return;
}


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

   Compute affine transformation of an image (warp image).

   Given 3 new (warped) positions for 3 image points, derive the 
   coefficients of the translation function to warp the entire image.

   Inputs:
      pold[3][2]  (x,y) coordinates for 3 points in original image
      pnew[3][2]  (x,y) coordinates for same points in warped image
   
   Output: 
      coeff[6]  coefficients of translation function which can be used
                to convert all image points to their warped positions

   If coeff[6] = (a, b, c, d, e, f) then the following formula
   can be used to convert an image point to its warped position:

      Xnew = a * Xold + b * Yold + c
      Ynew = d * Xold + e * Yold + f

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

void WarpF_affine(double pold[3][2], double pnew[3][2], double coeff[6])
{
   double   x11, y11, x12, y12, x13, y13;                                  //  original points
   double   x21, y21, x22, y22, x23, y23;                                  //  moved points
   double   a, b, c, d, e, f;                                              //  coefficients
   double   A1, A2, B1, B2, C1, C2;
   
   x11 = pold[0][0];
   y11 = pold[0][1];
   x12 = pold[1][0];
   y12 = pold[1][1];
   x13 = pold[2][0];
   y13 = pold[2][1];

   x21 = pnew[0][0];
   y21 = pnew[0][1];
   x22 = pnew[1][0];
   y22 = pnew[1][1];
   x23 = pnew[2][0];
   y23 = pnew[2][1];
   
   A1 = x11 - x12;
   A2 = x12 - x13;
   B1 = y11 - y12;
   B2 = y12 - y13;
   C1 = x21 - x22;
   C2 = x22 - x23;
   
   a = (B1 * C2 - B2 * C1) / (A2 * B1 - A1 * B2);
   b = (A1 * C2 - A2 * C1) / (A1 * B2 - A2 * B1);
   c = x23 - a * x13 - b * y13;
   
   C1 = y21 - y22;
   C2 = y22 - y23;
   
   d = (B1 * C2 - B2 * C1) / (A2 * B1 - A1 * B2);
   e = (A1 * C2 - A2 * C1) / (A1 * B2 - A2 * B1);
   f = y23 - d * x13 - e * y13;
   
   coeff[0] = a;
   coeff[1] = b;
   coeff[2] = c;
   coeff[3] = d;
   coeff[4] = e;
   coeff[5] = f;

   return;
}   


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

   Invert affine transform

   Input:
      coeff[6]  coefficients of translation function to convert
                image points to their warped positions
   Output: 
      Icoeff[6]  coefficients of translation function to convert
                 warped image points to their original positions

   If Icoeff[6] = (a, b, c, d, e, f) then the following formula can be
      used to translate a warped image point to its original position:

      Xold = a * Xnew + b * Ynew + c
      Yold = d * Xnew + e * Ynew + f

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

void WarpF_invert(double coeff[6], double Icoeff[6])
{
   double   a, b, c, d, e, f, Z;
   
   a = coeff[0];
   b = coeff[1];
   c = coeff[2];
   d = coeff[3];
   e = coeff[4];
   f = coeff[5];
   
   Z = 1.0 / (a * e - b * d);
   
   Icoeff[0] = e * Z;
   Icoeff[1] = - b * Z;
   Icoeff[2] = Z * (b * f - c * e);
   Icoeff[3] = - d * Z;
   Icoeff[4] = a * Z;
   Icoeff[5] = Z * (c * d - a * f);
   
   return;
}



