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

   Fotoxx      edit photos and manage collections  

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

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

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

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

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

   Fotoxx image edit - graphic image functions

   vpixel               get virtual pixel at any image location (float)

   PXM_audit            check PXM image for pixel values > 255.99...
   PXM_make             create new PXM pixmap image
   PXM_free             destroy PXM and free memory
   PXM_addalpha         add an alpha channel to a PXM pixmap
   PXM_copy             create a copy of a PXM image
   PXM_copy_area        create a copy of a rectangular area within a PXM
   PXM_rescale          create a rescaled copy of a PXM image
   PXM_rotate           create a rotated copy of a PXM image

   PXB_make             create a new PXB pixmap image, empty or from PIXBUF
   PXB_free             destroy PXB and free memory
   PXB_addalpha         add an alpha channel to a PXB pixmap
   PXB_copy             create a copy of a PXB image
   PXB_rescale          create a rescaled copy of a PXB image
   PXM_PXB_copy         create a PXB copy of a PXM image
   PXM_PXB_update       update a PXB area fom an updated PXM area
   PXB_PXB_update       update a PXB area from another PXB area
   pixbuf_rescale_fast  fast pixbuf rescale using threads

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

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

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


//  Get a virtual pixel at location (px,py) (real) in a PXB pixmap.
//  Get the overlapping integer pixels and build a composite.
//  Output vpix is uint8[4] supplied by caller.
//  Returns 1 if OK, 0 if px/py out of limits for pxm.

int vpixel(PXB *pxb, float px, float py, uint8 *vpix)
{
   int      ww, hh, rs, nc, ac, px0, py0;
   uint8    *pix0, *pix1, *pix2, *pix3;
   float    f0, f1, f2, f3;

   ww = pxb->ww;
   hh = pxb->hh;
   rs = pxb->rs;
   nc = pxb->nc;                                                                 //  15.08
   ac = nc - 3;

   px0 = px;                                                                     //  integer pixel containing (px,py)
   py0 = py;

   if (px0 < 0 || py0 < 0) return 0;
   if (px0 > ww-2 || py0 > hh-2) return 0;

   f0 = (px0+1 - px) * (py0+1 - py);                                             //  overlap of (px,py)
   f1 = (px0+1 - px) * (py - py0);                                               //   in each of the 4 pixels
   f2 = (px - px0) * (py0+1 - py);
   f3 = (px - px0) * (py - py0);

   pix0 = PXBpix(pxb,px0,py0);                                                   //  pixel (px0,py0)
   pix1 = pix0 + rs;                                                             //        (px0,py0+1)
   pix2 = pix0 + nc;                                                             //        (px0+1,py0)                  15.08
   pix3 = pix1 + nc;                                                             //        (px0+1,py0+1)

   vpix[0] = f0 * pix0[0] + f1 * pix1[0] + f2 * pix2[0] + f3 * pix3[0];          //  sum the weighted inputs
   vpix[1] = f0 * pix0[1] + f1 * pix1[1] + f2 * pix2[1] + f3 * pix3[1];
   vpix[2] = f0 * pix0[2] + f1 * pix1[2] + f2 * pix2[2] + f3 * pix3[2];
   if (ac) vpix[3] = f0 * pix0[3] + f1 * pix1[3] + f2 * pix2[3] + f3 * pix3[3];

   return 1;
}


//  Get a virtual pixel at location (px,py) (real) in a PXM pixmap.
//  Get the overlapping float pixels and build a composite.
//  Output vpix is float[4] supplied by caller.
//  Returns 1 if OK, 0 if px/py out of limits for pxm.

int vpixel(PXM *pxm, float px, float py, float *vpix)
{
   int      ww, hh, nc, ac, px0, py0;
   float    *pix0, *pix1, *pix2, *pix3;
   float    f0, f1, f2, f3;

   ww = pxm->ww;
   hh = pxm->hh;
   nc = pxm->nc;                                                                 //  15.08
   ac = nc - 3;

   px0 = px;                                                                     //  integer pixel containing (px,py)
   py0 = py;

   if (px0 < 1 || py0 < 1) return 0;                                             //  off-image or edge pixel
   if (px0 > ww-2 || py0 > hh-2) return 0;

   f0 = (px0+1 - px) * (py0+1 - py);                                             //  overlap of (px,py)
   f1 = (px0+1 - px) * (py - py0);                                               //   in each of the 4 pixels
   f2 = (px - px0) * (py0+1 - py);
   f3 = (px - px0) * (py - py0);

   pix0 = PXMpix(pxm,px0,py0);                                                   //  pixel (px0,py0)
   pix1 = pix0 + ww * nc;                                                        //        (px0,py0+1)
   pix2 = pix0 + nc;                                                             //        (px0+1,py0)
   pix3 = pix1 + nc;                                                             //        (px0+1,py0+1)

   vpix[0] = f0 * pix0[0] + f1 * pix1[0] + f2 * pix2[0] + f3 * pix3[0];          //  sum the weighted inputs
   vpix[1] = f0 * pix0[1] + f1 * pix1[1] + f2 * pix2[1] + f3 * pix3[1];
   vpix[2] = f0 * pix0[2] + f1 * pix1[2] + f2 * pix2[2] + f3 * pix3[2];

   if (ac)                                                                       //  alpha channel
      vpix[3] = f0 * pix0[3] + f1 * pix1[3] + f2 * pix2[3] + f3 * pix3[3];
                                                                                 //  remove test of vpix[2] < 1
   return 1;
}


/********************************************************************************
      PXM pixmap functions - RGB float pixel map
      pixel RGB values may range from 0.0 to 255.99
*********************************************************************************/

//  audit the contents of a PXM pixmap

void PXM_audit(PXM *pxm)
{
   int      px, py;
   float    bright, *pix;

   for (py = 0; py < pxm->hh; py++)
   for (px = 0; px < pxm->ww; px++)
   {
      pix = PXMpix(pxm,px,py);
      bright = pixbright(pix);
      if (int(bright) < 0 || int(bright) > 255)
         zappcrash("PXM_audit: px/py %d/%d  RGB %.2f %.2f %.2f  bright %.2f %d \n",
                                px,py,pix[0],pix[1],pix[2],bright,int(bright));
   }
   return;
}


//  initialize PXM pixmap - allocate memory

PXM * PXM_make(int ww, int hh, int nc)                                           //  15.08
{
   uint     cc;
   double   dcc;

   if (ww < 5 || hh < 5) {                                                       //  impose reasonableness limits
      zmessageACK(Mwin,"image too small: %d x %d",ww,hh);
      return 0;
   }

   if (ww > wwhh_limit1 || hh > wwhh_limit1 || ww * hh > wwhh_limit2) {          //  avoid image dimensions too big     16.03
      zmessageACK(Mwin,"image too big: %dx%d",ww,hh);
      return 0;
   }

   PXM *pxm = (PXM *) zmalloc(sizeof(PXM));
   strcpy(pxm->wmi,"pxmpix");
   pxm->ww = ww;
   pxm->hh = hh;
   pxm->nc = nc;                                                                 //  nc = 3/4 for no/alpha channel      15.08

   dcc = 1.0 * ww * hh * nc * sizeof(float);                                     //  float[nc] per pixel
   if (dcc > 4095.0 * MEGA) {                                                    //  avoid image > 4 GB                 16.03
      zmessageACK(Mwin,"image too big: %dx%d",ww,hh);
      return 0;
   }
   cc = ww * hh * nc * sizeof(float); 
   pxm->pixels = (float *) zmalloc(cc);
   return pxm;
}


//  free PXM pixmap

void PXM_free(PXM *&pxm)
{
   if (! pxm) return;
   if (! strmatch(pxm->wmi,"pxmpix"))
      zappcrash("PXM_free(), bad PXM");
   strcpy(pxm->wmi,"xxxxxx");
   zfree(pxm->pixels);
   zfree(pxm);
   pxm = 0;
   return;
}


//  add an alpha channel to a PXM pixmap

void PXM_addalpha(PXM *pxm)                                                      //  15.08
{
   uint     cc;
   double   dcc;
   int      ww, hh, nc1, nc2, ii;
   float    *pixels1, *pixels2, *pix1, *pix2;
   
   ww = pxm->ww;
   hh = pxm->hh;
   nc1 = pxm->nc;
   if (nc1 > 3) return;
   pixels1 = pxm->pixels;

   nc2 = nc1 + 1;
   dcc = 1.0 * ww * hh * nc2 * sizeof(float);                                    //  float[nc] per pixel
   if (dcc > 4095.0 * MEGA) {                                                    //  avoid image > 4 GB                 16.03
      zmessageACK(Mwin,"image too big: %dx%d",ww,hh);
      exit(12);
   }
   cc = ww * hh * nc2 * sizeof(float);
   pixels2 = (float *) zmalloc(cc);

   cc = nc1 * sizeof(float);
   pix1 = pixels1;
   pix2 = pixels2;

   for (ii = 0; ii < ww * hh; ii++) {
      memcpy(pix2,pix1,cc);
      pix2[nc1] = 255.0;                                                         //  100% opaque = 255
      pix1 += nc1;
      pix2 += nc2;
   }
   
   zfree(pixels1);
   pxm->nc = nc2;
   pxm->pixels = pixels2;
   return;
}


//  create a copy of a PXM pixmap

PXM * PXM_copy(PXM *pxm1)
{
   uint     cc;
   int      ww, hh, nc;
   PXM      *pxm2;
   
   ww = pxm1->ww;
   hh = pxm1->hh;
   nc = pxm1->nc;
   cc = ww * hh * nc * sizeof(float);

   pxm2 = PXM_make(ww,hh,nc);                                                    //  15.08
   memcpy(pxm2->pixels,pxm1->pixels,cc);

   return pxm2;
}


//  create a copy of a PXM rectangular area

PXM * PXM_copy_area(PXM *pxm1, int orgx, int orgy, int ww2, int hh2)
{
   float    *pix1, *pix2;
   PXM      *pxm2 = 0;
   int      px1, py1, px2, py2;
   int      nc, pcc;
   
   nc = pxm1->nc;                                                                //  15.08
   pcc = nc * sizeof(float);

   pxm2 = PXM_make(ww2,hh2,nc);

   for (py1 = orgy, py2 = 0; py2 < hh2; py1++, py2++)
   {
      pix1 = PXMpix(pxm1,orgx,orgy);
      pix2 = PXMpix(pxm2,0,py2);

      for (px1 = orgx, px2 = 0; px2 < ww2; px1++, px2++)
      {
         memcpy(pix2,pix1,pcc);
         pix1 += nc;
         pix2 += nc;
      }
   }

   return pxm2;
}


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

   Rescale PXM image to new width and height.
   The scale ratios may be different for width and height.

   Method:
   The input and output images are overlayed, stretching or shrinking the
   output pixels as needed. The contribution of each input pixel overlapping
   an output pixel is proportional to the area of the output pixel covered by
   the input pixel. The contributions of all overlaping input pixels are added.
   The work is spread among NWT threads to reduce time on SMP processors.

   Example: if the output image is 40% of the input image, then:
     outpix[0,0] = 0.16 * inpix[0,0] + 0.16 * inpix[1,0] + 0.08 * inpix[2,0]
                 + 0.16 * inpix[0,1] + 0.16 * inpix[1,1] + 0.08 * inpix[2,1]
                 + 0.08 * inpix[0,2] + 0.08 * inpix[1,2] + 0.04 * inpix[2,2]

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

namespace pxmrescale {
   float    *ppix1, *ppix2;
   int      ww1, hh1, ww2, hh2, nc;
   int      *px1L, *py1L;
   float    *pxmap, *pymap;
   int      maxmapx, maxmapy;
   int      busy[max_threads];
}

PXM * PXM_rescale(PXM *pxm1, int ww, int hh)
{
   using namespace pxmrescale;

   void * pxm_rescale_thread(void *arg);

   PXM         *pxm2;
   int         px1, py1, px2, py2;
   int         pxl, pyl, pxm, pym, ii;
   float       scalex, scaley;
   float       px1a, py1a, px1b, py1b;
   float       fx, fy;

   ww1 = pxm1->ww;                                                               //  input PXM
   hh1 = pxm1->hh;
   nc = pxm1->nc;                                                                //  15.08
   ppix1 = pxm1->pixels;

   pxm2 = PXM_make(ww,hh,nc);                                                    //  output PXM
   if (! pxm2) return 0;                                                         //  too big or no memory
   ww2 = ww;
   hh2 = hh;
   ppix2 = pxm2->pixels;

   memset(ppix2, 0, ww2 * hh2 * 3 * sizeof(float));                              //  clear output pixmap

   scalex = 1.0 * ww1 / ww2;                                                     //  compute x and y scales
   scaley = 1.0 * hh1 / hh2;

   if (scalex <= 1) maxmapx = 2;                                                 //  compute max input pixels
   else maxmapx = scalex + 2;                                                    //    mapping into output pixels
   maxmapx += 1;                                                                 //      for both dimensions
   if (scaley <= 1) maxmapy = 2;                                                 //  (pixels may not be square)
   else maxmapy = scaley + 2;
   maxmapy += 1;                                                                 //  (extra entry for -1 flag)

   pymap = (float *) zmalloc(hh2 * maxmapy * sizeof(float));                     //  maps overlap of < maxmap input
   pxmap = (float *) zmalloc(ww2 * maxmapx * sizeof(float));                     //    pixels per output pixel

   py1L = (int *) zmalloc(hh2 * sizeof(int));                                    //  maps first (lowest) input pixel
   px1L = (int *) zmalloc(ww2 * sizeof(int));                                    //    per output pixel

   for (py2 = 0; py2 < hh2; py2++)                                               //  loop output y-pixels
   {
      py1a = py2 * scaley;                                                       //  corresponding input y-pixels
      py1b = py1a + scaley;
      if (py1b >= hh1) py1b = hh1 - 0.001;                                       //  fix precision limitation
      pyl = py1a;
      py1L[py2] = pyl;                                                           //  1st overlapping input pixel

      for (py1 = pyl, pym = 0; py1 < py1b; py1++, pym++)                         //  loop overlapping input pixels
      {
         if (py1 < py1a) {                                                       //  compute amount of overlap
            if (py1+1 < py1b) fy = py1+1 - py1a;                                 //    0.0 to 1.0
            else fy = scaley;
         }
         else if (py1+1 > py1b) fy = py1b - py1;
         else fy = 1;

         ii = py2 * maxmapy + pym;                                               //  save it
         pymap[ii] = 0.9999 * fy / scaley;
      }
      ii = py2 * maxmapy + pym;                                                  //  set an end marker after
      pymap[ii] = -1;                                                            //    last overlapping pixel
   }

   for (px2 = 0; px2 < ww2; px2++)                                               //  do same for x-pixels
   {
      px1a = px2 * scalex;
      px1b = px1a + scalex;
      if (px1b >= ww1) px1b = ww1 - 0.001;
      pxl = px1a;
      px1L[px2] = pxl;

      for (px1 = pxl, pxm = 0; px1 < px1b; px1++, pxm++)
      {
         if (px1 < px1a) {
            if (px1+1 < px1b) fx = px1+1 - px1a;
            else fx = scalex;
         }
         else if (px1+1 > px1b) fx = px1b - px1;
         else fx = 1;

         ii = px2 * maxmapx + pxm;
         pxmap[ii] = 0.9999 * fx / scalex;
      }
      ii = px2 * maxmapx + pxm;
      pxmap[ii] = -1;
   }

   for (ii = 0; ii < NWT; ii++) {                                                //  start working threads
      busy[ii] = 1;
      start_detached_thread(pxm_rescale_thread,&Nval[ii]);
   }

   for (ii = 0; ii < NWT; ii++)                                                  //  wait for all done
      while (busy[ii]) zsleep(0.004);

   zfree(px1L);
   zfree(py1L);
   zfree(pxmap);
   zfree(pymap);

   return pxm2;
}

void * pxm_rescale_thread(void *arg)                                             //  worker thread function
{
   using namespace pxmrescale;

   int         index = *((int *) arg);
   int         px1, py1, px2, py2;
   int         pxl, pyl, pxm, pym, ii;
   float       *pixel1, *pixel2;
   float       fx, fy, ftot;
   float       chan[6];
   int         pcc = nc * sizeof(float);

   for (py2 = index; py2 < hh2; py2 += NWT)                                      //  loop output y-pixels
   {
      pyl = py1L[py2];                                                           //  corresp. 1st input y-pixel

      for (px2 = 0; px2 < ww2; px2++)                                            //  loop output x-pixels
      {
         pxl = px1L[px2];                                                        //  corresp. 1st input x-pixel

         memset(chan,0,pcc);                                                     //  initz. output pixel

         for (py1 = pyl, pym = 0; ; py1++, pym++)                                //  loop overlapping input y-pixels
         {
            ii = py2 * maxmapy + pym;                                            //  get y-overlap
            fy = pymap[ii];
            if (fy < 0) break;                                                   //  no more pixels

            for (px1 = pxl, pxm = 0; ; px1++, pxm++)                             //  loop overlapping input x-pixels
            {
               ii = px2 * maxmapx + pxm;                                         //  get x-overlap
               fx = pxmap[ii];
               if (fx < 0) break;                                                //  no more pixels

               ftot = fx * fy;                                                   //  area overlap = x * y overlap
               pixel1 = ppix1 + (py1 * ww1 + px1) * nc;
               for (ii = 0; ii < nc; ii++)
                  chan[ii] += pixel1[ii] * ftot;                                 //  add input pixel * overlap
            }
         }

         pixel2 = ppix2 + (py2 * ww2 + px2) * nc;                                //  save output pixel                  15.08
         memcpy(pixel2,chan,pcc);
      }
   }

   busy[index] = 0;
   return 0;
}


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

      PXM *pxm2 = PXM_rotate(PXM *pxm1, float angle)

      Rotate PXM pixmap through an arbitrary angle (degrees).

      The returned image has the same size as the original, but the
      pixmap size is increased to accomodate the rotated image.
      (e.g. a 100x100 image rotated 45 deg. needs a 142x142 pixmap).

      The space added around the rotated image is black (RGB 0,0,0).
      Angle is in degrees. Positive direction is clockwise.
      Speed is about 28 million pixels/sec/thread for a 3.3 GHz CPU.
      Loss of resolution is less than 1 pixel.

      Work is divided among NWT threads to gain speed.

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

namespace pxmrotate {
   int      busy = 0;
   float    *ppix1, *ppix2;
   int      ww1, hh1, ww2, hh2, nc;
   float    angle;
}

PXM * PXM_rotate(PXM *pxm1, float anglex)
{
   using namespace pxmrotate;

   PXM *PXM_rotate2(PXM *pxm, int angle);
   void *PXM_rotate_thread(void *);

   int      ii;
   PXM      *pxm2;

   ww1 = pxm1->ww;                                                               //  input PXM
   hh1 = pxm1->hh;
   nc = pxm1->nc;                                                                //  15.08
   ppix1 = pxm1->pixels;
   angle = anglex;

   while (angle < -180) angle += 360;                                            //  normalize, -180 to +180
   while (angle > 180) angle -= 360;

   if (angle >= -180.0 && angle < -179.99)                                       //  use lossless version for angles
      return PXM_rotate2(pxm1,180);                                              //    of -180, -90, 0, 90, 180
   if (angle > -90.01 && angle < -89.99)
      return PXM_rotate2(pxm1,-90);
   if (angle > -0.01 && angle < 0.01)
      return PXM_copy(pxm1);
   if (angle > 89.99 && angle < 90.01)
      return PXM_rotate2(pxm1,90);
   if (angle > 179.99 && angle <= 180.0)
      return PXM_rotate2(pxm1,180);

   angle = angle * PI / 180;                                                     //  radians, -PI to +PI

   ww2 = ww1*fabsf(cosf(angle)) + hh1*fabsf(sinf(angle));                        //  rectangle containing rotated image
   hh2 = ww1*fabsf(sinf(angle)) + hh1*fabsf(cosf(angle));

   pxm2 = PXM_make(ww2,hh2,nc);                                                  //  output PXM
   if (! pxm2) return 0;
   ppix2 = pxm2->pixels;

   for (ii = 0; ii < NWT; ii++)                                                  //  start worker threads
      start_detached_thread(PXM_rotate_thread,&Nval[ii]);
   zadd_locked(busy,+NWT);

   while (busy) zsleep(0.004);                                                   //  wait for completion
   return pxm2;
}

void * PXM_rotate_thread(void *arg)
{
   using namespace pxmrotate;

   int      index = *((int *) (arg));
   int      px2, py2, px0, py0;
   float    *pix0, *pix1, *pix2, *pix3;
   float    px1, py1;
   float    f0, f1, f2, f3, chan[6];
   float    a, b, ww15, hh15, ww25, hh25;
   int      pcc = nc * sizeof(float);                                            //  15.08

   ww15 = 0.5 * ww1;
   hh15 = 0.5 * hh1;
   ww25 = 0.5 * ww2;
   hh25 = 0.5 * hh2;

   a = cosf(angle);
   b = sinf(angle);

   for (py2 = index; py2 < hh2; py2 += NWT)                                      //  loop through output pixels
   for (px2 = 0; px2 < ww2; px2++)
   {
      px1 = a * (px2 - ww25) + b * (py2 - hh25) + ww15;                          //  (px1,py1) = corresponding
      py1 = -b * (px2 - ww25) + a * (py2 - hh25) + hh15;                         //    point within input pixels

      px0 = px1;                                                                 //  pixel containing (px1,py1)
      py0 = py1;

      if (px0 < 0 || px0 > ww1-2 || py0 < 0 || py0 > hh1-2) {                    //  if outside input pixel array
         pix2 = ppix2 + (py2 * ww2 + px2) * nc;                                  //    output is black
         memset(pix2,0,pcc);
         continue;
      }

      pix0 = ppix1 + (py0 * ww1 + px0) * nc;                                     //  4 input pixels based at (px0,py0)
      pix1 = pix0 + ww1 * nc;
      pix2 = pix0 + nc;
      pix3 = pix1 + nc;

      f0 = (px0+1 - px1) * (py0+1 - py1);                                        //  overlap of (px1,py1)
      f1 = (px0+1 - px1) * (py1 - py0);                                          //    in each of the 4 pixels
      f2 = (px1 - px0) * (py0+1 - py1);
      f3 = (px1 - px0) * (py1 - py0);
      
      for (int ii = 0; ii < nc; ii++)                                            //  sum the weighted inputs
         chan[ii] = f0 * pix0[ii] + f1 * pix1[ii] 
                  + f2 * pix2[ii] + f3 * pix3[ii];

      pix2 = ppix2 + (py2 * ww2 + px2) * nc;                                     //  output pixel
      memcpy(pix2,chan,pcc);
   }

   zadd_locked(busy,-1);
   return 0;
}

PXM * PXM_rotate2(PXM *pxm1, int angle)                                          //  angle = -90, 90, 180
{
   using namespace pxmrotate;

   int      px1, py1, px2, py2, nc, pcc;
   float    *pix1, *pix2;
   PXM      *pxm2;

   if (angle == 0) return PXM_copy(pxm1);

   ww1 = pxm1->ww;                                                               //  input PXM
   hh1 = pxm1->hh;
   nc = pxm1->nc;                                                                //  15.08
   pcc = nc * sizeof(float);

   if (angle == -90) {
      ww2 = hh1;
      hh2 = ww1;
   }

   else if (angle == 90) {
      ww2 = hh1;
      hh2 = ww1;
   }

   else if (angle == 180) {
      ww2 = ww1;
      hh2 = hh1;
   }

   else zappcrash("PXM_rotate2() bad angle %d",angle);

   pxm2 = PXM_make(ww2,hh2,nc);                                                  //  output PXM
   ppix2 = pxm2->pixels;

   for (py1 = 0; py1 < hh1; py1++)                                               //  loop all input pixels
   for (px1 = 0; px1 < ww1; px1++)
   {
      if (angle == -90) {
         px2 = py1;
         py2 = hh2 - px1 - 1;
      }

      else if (angle == 90) {
         px2 = ww2 - py1 - 1;
         py2 = px1;
      }

      else /* angle = 180 */ {
         px2 = ww2 - px1 - 1;
         py2 = hh2 - py1 - 1;
      }

      pix1 = ppix1 + (py1 * ww1 + px1) * nc;
      pix2 = ppix2 + (py2 * ww2 + px2) * nc;
      memcpy(pix2,pix1,pcc);
   }

   return pxm2;
}


/********************************************************************************
      PXB pixmap functions - RGB uint8 pixel map and PIXBUF wrapper
*********************************************************************************/

//  Create PXB pixmap with pixels cleared to zero

PXB * PXB_make(int ww, int hh, int ac)                                           //  15.08
{
   uint     cc;

   zthreadcrash();

   PXB *pxb = (PXB *) zmalloc(sizeof(PXB));
   strcpy(pxb->wmi,"pxbpix");
   pxb->pixbuf = gdk_pixbuf_new(GDKRGB,ac,8,ww,hh);
   if (! pxb->pixbuf) {
      zmessageACK(Mwin,"memory allocation failure");
      exit(12);
   }
   pxb->pixels = gdk_pixbuf_get_pixels(pxb->pixbuf);
   pxb->ww = ww;
   pxb->hh = hh;
   pxb->rs = gdk_pixbuf_get_rowstride(pxb->pixbuf);
   pxb->nc = 3 + ac;                                                             //  nc = 3/4 for no/alpha channel      15.08
   cc = hh * pxb->rs;
   memset(pxb->pixels,0,cc);
   return pxb;
}


//  Create a PXB pixmap from a PIXBUF
//  The pixbuf is used directly and is not duplicated

PXB * PXB_make(PIXBUF *pixbuf1)
{
   zthreadcrash();

   PXB *pxb2 = (PXB *) zmalloc(sizeof(PXB));
   strcpy(pxb2->wmi,"pxbpix");
   pxb2->pixbuf = pixbuf1;
   pxb2->ww = gdk_pixbuf_get_width(pixbuf1);
   pxb2->hh = gdk_pixbuf_get_height(pixbuf1);
   pxb2->rs = gdk_pixbuf_get_rowstride(pixbuf1);
   pxb2->nc = 3 + gdk_pixbuf_get_has_alpha(pixbuf1);                             //  15.08
   pxb2->pixels = gdk_pixbuf_get_pixels(pixbuf1);
   return pxb2;
}


//  Free PXB pixmap - release memory

void PXB_free(PXB *&pxb)
{
   zthreadcrash();

   if (! pxb) return;
   if (! strmatch(pxb->wmi,"pxbpix"))
      zappcrash("PXB_free(), bad PXB");
   strcpy(pxb->wmi,"xxxxxx");
   g_object_unref(pxb->pixbuf);
   zfree(pxb);
   pxb = 0;
   return;
}


//  add an alpha channel to a PXB pixmap

void PXB_addalpha(PXB *pxb)
{
   GdkPixbuf   *pixbuf1, *pixbuf2;                                               //  15.08
   uint8       *pixels1, *pixels2, *pix1, *pix2;
   int         nc, ww, hh, rs1, rs2, px, py;

   nc = pxb->nc;
   if (nc > 3) return;
   
   ww = pxb->ww;
   hh = pxb->hh;
   
   pixbuf1 = pxb->pixbuf;                                                        //  old pixbuf
   pixbuf2 = gdk_pixbuf_new(GDKRGB,1,8,ww,hh);                                   //  new pixbuf with alpha channel
   if (! pixbuf2) {
      zmessageACK(Mwin,"memory allocation failure");                             //  16.03
      exit(12);
   }

   pixels1 = gdk_pixbuf_get_pixels(pixbuf1);
   pixels2 = gdk_pixbuf_get_pixels(pixbuf2);
   rs1 = gdk_pixbuf_get_rowstride(pixbuf1);
   rs2 = gdk_pixbuf_get_rowstride(pixbuf2);
   
   for (py = 0; py < hh; py++)                                                   //  copy RGB data
   {
      pix1 = pixels1 + rs1 * py;
      pix2 = pixels2 + rs2 * py;

      for (px = 0; px < ww; px++)
      {
         memcpy(pix2,pix1,3);
         pix2[3] = 255;                                                          //  set opacity = max. for new pixbuf
         pix1 += 3;
         pix2 += 4;
      }
   }
   
   g_object_unref(pixbuf1);
   pxb->pixbuf = pixbuf2;
   pxb->pixels = pixels2;
   pxb->rs = rs2;
   pxb->nc = 4;

   return;
}


//  Copy a PXB pixmap to a new PXB pixmap

PXB * PXB_copy(PXB *pxb1)
{
   zthreadcrash();

   PXB *pxb2 = (PXB *) zmalloc(sizeof(PXB));
   strcpy(pxb2->wmi,"pxbpix");
   pxb2->pixbuf = gdk_pixbuf_copy(pxb1->pixbuf);
   if (! pxb2->pixbuf) {
      zmessageACK(Mwin,"memory allocation failure");
      exit(12);
   }
   pxb2->ww = pxb1->ww;
   pxb2->hh = pxb1->hh;
   pxb2->rs = pxb1->rs;
   pxb2->nc = pxb1->nc;                                                          //  15.08
   pxb2->pixels = gdk_pixbuf_get_pixels(pxb2->pixbuf);
   return pxb2;
}


//  Rescale PXB pixmap to given width and height.

PXB * PXB_rescale(PXB *pxb1, int ww2, int hh2)
{
   PIXBUF      *pixbuf = 0;

   zthreadcrash();
   pixbuf = gdk_pixbuf_scale_simple(pxb1->pixbuf,ww2,hh2,GDK_INTERP_BILINEAR);
   if (! pixbuf) {
      zmessageACK(Mwin,"memory allocation failure");
      exit(12);
   }
   PXB *pxb2 = PXB_make(pixbuf);
   return pxb2;
}


//  Rotate PXB pixmap by given angle.

PXB * PXB_rotate(PXB *pxb1, float angle)                                         //  15.04
{
   PIXBUF   *pixbuf = 0;

   zthreadcrash();
   pixbuf = gdk_pixbuf_rotate(pxb1->pixbuf,angle);
   if (! pixbuf) {
      zmessageACK(Mwin,"memory allocation failure");
      exit(12);
   }
   PXB *pxb2 = PXB_make(pixbuf);
   return pxb2;
}



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

//  Copy a PXM image (RGB float) to a PXB image (RGB uint8).

PXB * PXM_PXB_copy(PXM *pxm1)
{
   float    *pix1;
   uint8    *pix2;
   PXB      *pxb2;
   int      ww, hh, nc, ac, px, py;

   ww = pxm1->ww;
   hh = pxm1->hh;
   nc = pxm1->nc;
   ac = nc - 3;

   pxb2 = PXB_make(ww,hh,ac);
   if (! pxb2) {
      zmessageACK(Mwin,"memory allocation failure");
      exit(12);
   }

   for (py = 0; py < hh; py++)
   {
      pix1 = PXMpix(pxm1,0,py);
      pix2 = PXBpix(pxb2,0,py);

      for (px = 0; px < ww; px++)
      {
         pix2[0] = pix1[0];
         pix2[1] = pix1[1];
         pix2[2] = pix1[2];
         if (ac) pix2[3] = pix1[3];                                              //  15.08
         pix1 += nc;
         pix2 += nc;
      }
   }

   return pxb2;
}


//  Update a PXB section from an updated PXM section.
//  PXM and PXB must have the same dimensions.
//  px3, py3, ww3, hh3: modified section within pxm1 to propagate to pxb2;

void PXM_PXB_update(PXM *pxm1, PXB *pxb2, int px3, int py3, int ww3, int hh3)
{
   float    *pix1;
   uint8    *pix2;
   int      px, py;
   int      lox, hix, loy, hiy;
   int      nc1, nc2, ac;
   
   if (pxm1->ww + pxm1->hh != pxb2->ww + pxb2->hh)                               //  15.12
      zappcrash("PXM_PXB_update() call error");

   lox = px3;
   hix = px3 + ww3;
   if (lox < 0) lox = 0;
   if (hix > pxb2->ww) hix = pxb2->ww;

   loy = py3;
   hiy = py3 + hh3;
   if (loy < 0) loy = 0;
   if (hiy > pxb2->hh) hiy = pxb2->hh;
   
   nc1 = pxm1->nc;
   nc2 = pxb2->nc;
   
   if (nc1 > 3 && nc2 > 3) ac = 1;                                               //  alpha channel    15.08
   else ac = 0;
   
   for (py = loy; py < hiy; py++)
   {
      pix1 = PXMpix(pxm1,lox,py);
      pix2 = PXBpix(pxb2,lox,py);

      for (px = lox; px < hix; px++)
      {
         pix2[0] = pix1[0];                                                      //  0.0 - 255.99  >>  0 - 255
         pix2[1] = pix1[1];
         pix2[2] = pix1[2];
         if (ac) pix2[3] = pix1[3];
         pix1 += nc1;
         pix2 += nc2;                                                            //  15.08
      }
   }

   return;
}


//  Update an output PXB section from a corresponding input PXB section.
//  The two PXBs represent the same image at different scales (width/height).
//  pxb1 must be >= pxb2.  
//  px3, py3, ww3, hh3: modified section within pxb1 to propagate to pxb2;

void PXB_PXB_update(PXB *pxb1, PXB *pxb2, int px3, int py3, int ww3, int hh3)
{
   static int     pww1 = 0, phh1 = 0, pww2 = 0, phh2 = 0;
   static int     *px1L = 0, *py1L = 0;
   static float   scalex = 1, scaley = 1;
   static float   *pxmap = 0, *pymap = 0;
   static int     maxmapx = 0, maxmapy = 0;

   uint8       *ppix1, *ppix2;
   int         ww1, hh1, ww2, hh2, rs1, rs2, nc1, nc2, ac;
   int         px1, py1, px2, py2;
   int         pxl, pyl, pxm, pym, ii;
   float       px1a, py1a, px1b, py1b;
   float       fx, fy, ftot;
   uint8       *pixel1, *pixel2;
   float       red, green, blue, alpha;
   int         lox, hix, loy, hiy;

   ww1 = pxb1->ww;
   hh1 = pxb1->hh;
   rs1 = pxb1->rs;
   nc1 = pxb1->nc;                                                               //  15.08
   ppix1 = pxb1->pixels;

   ww2 = pxb2->ww;
   hh2 = pxb2->hh;
   rs2 = pxb2->rs;
   nc2 = pxb2->nc;                                                               //  15.08
   ppix2 = pxb2->pixels;
   
   if (nc1 > 3 && nc2 > 3) ac = 1;                                               //  alpha channel    15.08
   else ac = 0;

   if (ww1 == pww1 && hh1 == phh1 && ww2 == pww2 && hh2 == phh2)                 //  if the sizes are the same as before,
      goto copy_pixels;                                                          //    the pixel mapping math can be avoided.

   pww1 = ww1;
   phh1 = hh1;
   pww2 = ww2;
   phh2 = hh2;

   if (px1L) {                                                                   //  unless this is the first call,
      zfree(px1L);                                                               //    free prior map memory
      zfree(py1L);
      zfree(pxmap);
      zfree(pymap);
   }

   scalex = 1.0 * ww1 / ww2;                                                     //  compute x and y scales
   scaley = 1.0 * hh1 / hh2;

   if (scalex <= 1) maxmapx = 2;                                                 //  compute max input pixels
   else maxmapx = scalex + 2;                                                    //    mapping into output pixels
   maxmapx += 1;                                                                 //      for both dimensions
   if (scaley <= 1) maxmapy = 2;                                                 //  (pixels may not be square)
   else maxmapy = scaley + 2;
   maxmapy += 1;                                                                 //  (extra entry for -1 flag)

   pymap = (float *) zmalloc(hh2 * maxmapy * sizeof(float));                     //  maps overlap of < maxmap input
   pxmap = (float *) zmalloc(ww2 * maxmapx * sizeof(float));                     //    pixels per output pixel

   py1L = (int *) zmalloc(hh2 * sizeof(int));                                    //  maps first (lowest) input pixel
   px1L = (int *) zmalloc(ww2 * sizeof(int));                                    //    per output pixel

   for (py2 = 0; py2 < hh2; py2++)                                               //  loop output y-pixels
   {
      py1a = py2 * scaley;                                                       //  corresponding input y-pixels
      py1b = py1a + scaley;
      if (py1b >= hh1) py1b = hh1 - 0.001;                                       //  fix precision limitation
      pyl = py1a;
      py1L[py2] = pyl;                                                           //  1st overlapping input pixel

      for (py1 = pyl, pym = 0; py1 < py1b; py1++, pym++)                         //  loop overlapping input pixels
      {
         if (py1 < py1a) {                                                       //  compute amount of overlap
            if (py1+1 < py1b) fy = py1+1 - py1a;                                 //    0.0 to 1.0
            else fy = scaley;
         }
         else if (py1+1 > py1b) fy = py1b - py1;
         else fy = 1;

         ii = py2 * maxmapy + pym;                                               //  save it
         pymap[ii] = 0.9999 * fy / scaley;
      }
      ii = py2 * maxmapy + pym;                                                  //  set an end marker after
      pymap[ii] = -1;                                                            //    last overlapping pixel
   }

   for (px2 = 0; px2 < ww2; px2++)                                               //  do same for x-pixels
   {
      px1a = px2 * scalex;
      px1b = px1a + scalex;
      if (px1b >= ww1) px1b = ww1 - 0.001;
      pxl = px1a;
      px1L[px2] = pxl;

      for (px1 = pxl, pxm = 0; px1 < px1b; px1++, pxm++)
      {
         if (px1 < px1a) {
            if (px1+1 < px1b) fx = px1+1 - px1a;
            else fx = scalex;
         }
         else if (px1+1 > px1b) fx = px1b - px1;
         else fx = 1;

         ii = px2 * maxmapx + pxm;
         pxmap[ii] = 0.9999 * fx / scalex;
      }
      ii = px2 * maxmapx + pxm;
      pxmap[ii] = -1;
   }

copy_pixels:

   px3 = px3 / scalex;                                                           //  convert input area to output area
   py3 = py3 / scaley;
   ww3 = ww3 / scalex + 2;
   hh3 = hh3 / scaley + 2;

   lox = px3;
   hix = px3 + ww3;
   if (lox < 0) lox = 0;
   if (hix > ww2) hix = ww2;

   loy = py3;
   hiy = py3 + hh3;
   if (loy < 0) loy = 0;
   if (hiy > hh2) hiy = hh2;

   for (py2 = loy; py2 < hiy; py2++)                                             //  loop output y-pixels
   {
      pyl = py1L[py2];                                                           //  corresp. 1st input y-pixel

      for (px2 = lox; px2 < hix; px2++)                                          //  loop output x-pixels
      {
         pxl = px1L[px2];                                                        //  corresp. 1st input x-pixel

         red = green = blue = alpha = 0;                                         //  initz. output pixel

         for (py1 = pyl, pym = 0; ; py1++, pym++)                                //  loop overlapping input y-pixels
         {
            ii = py2 * maxmapy + pym;                                            //  get y-overlap
            fy = pymap[ii];
            if (fy < 0) break;                                                   //  no more pixels

            for (px1 = pxl, pxm = 0; ; px1++, pxm++)                             //  loop overlapping input x-pixels
            {
               ii = px2 * maxmapx + pxm;                                         //  get x-overlap
               fx = pxmap[ii];
               if (fx < 0) break;                                                //  no more pixels

               ftot = fx * fy;                                                   //  area overlap = x * y overlap
               pixel1 = ppix1 + py1 * rs1 + px1 * nc1;
               red += pixel1[0] * ftot;                                          //  add input pixel * overlap
               green += pixel1[1] * ftot;
               blue += pixel1[2] * ftot;
               if (ac) alpha += pixel1[3] * ftot;
            }
         }

         pixel2 = ppix2 + py2 * rs2 + px2 * nc2;                                 //  save output pixel                  15.08
         pixel2[0] = red;                                                        //  0.0 - 255.996  >>  0 - 255
         pixel2[1] = green;
         pixel2[2] = blue;
         if (ac) pixel2[3] = alpha;
      }
   }

   return;
}


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

//  fast pixbuf rescale, bilinear interpolation, threaded
//  works best when the rescale ratio is between 0.5 and 2.0
//  about 4x faster than gdk_pixbuf_scale_simple() if 4 processor cores avail. 
//  alpha channel is not copied - used only in slide show for now

namespace pixbuf_rescale_fast_names {
   int      ww1, hh1, ww2, hh2, rs1, rs2, nc1, nc2;
   uint8    *pixels1, *pixels2;
   int      busy[max_threads];
   int      nwt;
}

PIXBUF * pixbuf_rescale_fast(PIXBUF *pixbuf1, int ww, int hh)                    //  15.02
{
   using namespace pixbuf_rescale_fast_names;

   void * pixbuf_rescale_fast_thread(void *arg);

   PIXBUF      *pixbuf2;
   uint8       *pix2;

   nwt = NWT - 1;                                                                //  threads to use                     16.07   

   pixbuf2 = gdk_pixbuf_new(GDKRGB,0,8,ww,hh);
   if (! pixbuf2) {
      zmessageACK(Mwin,"memory allocation failure");
      exit(12);
   }

   ww1 = gdk_pixbuf_get_width(pixbuf1);
   hh1 = gdk_pixbuf_get_height(pixbuf1);
   rs1 = gdk_pixbuf_get_rowstride(pixbuf1);
   nc1 = gdk_pixbuf_get_n_channels(pixbuf1);                                     //  15.08
   pixels1 = gdk_pixbuf_get_pixels(pixbuf1);

   ww2 = ww;
   hh2 = hh;
   rs2 = gdk_pixbuf_get_rowstride(pixbuf2);
   nc2 = gdk_pixbuf_get_n_channels(pixbuf2);                                     //  15.08
   pixels2 = gdk_pixbuf_get_pixels(pixbuf2);

   for (int ii = 0; ii < nwt; ii++) {                                            //  start working threads
      busy[ii] = 1;
      start_detached_thread(pixbuf_rescale_fast_thread,&Nval[ii]);
   }

   for (int ii = 0; ii < nwt; ii++)                                              //  wait for all done
      while (busy[ii]) zsleep(0.001);

   for (int py2 = 0; py2 < hh2; py2++) {                                         //  set last column of pixels black
      pix2 = pixels2 + py2 * rs2 + (ww2-1) * 3;
      memset(pix2,0,3);
   }

   pix2 = pixels2 + (hh2-1) * rs2;                                               //  set last row of pixels black
   memset(pix2,0,rs2);

   return pixbuf2;
}

void * pixbuf_rescale_fast_thread(void *arg)
{
   using namespace pixbuf_rescale_fast_names;

   int      index = *((int *) arg);
   uint8    *pix1, *pix2;
   int      px1, py1, px2, py2;
   float    fww, fhh, fx1, fy1;
   float    dx1, dy1, dx2, dy2;

   fww = 1.0 * ww1 / ww2;
   fhh = 1.0 * hh1 / hh2;

   for (py2 = index; py2 < hh2-1; py2 += nwt)                                    //  loop output pixel rows
   for (px2 = 0; px2 < ww2-1; px2++)                                             //  loop output pixel cols
   {
      fx1 = px2 * fww;                                                           //  real position in input image
      fy1 = py2 * fhh;
      px1 = int(fx1);                                                            //  corresp. input pixel
      py1 = int(fy1);
      dx1 = fx1 - px1;                                                           //  distance from pixel left
      dx2 = 1.0 - dx1;                                                           //  distance from pixel right
      dy1 = fy1 - py1;                                                           //  distance from pixel top
      dy2 = 1.0 - dy1;                                                           //  distance from pixel bottom

      pix1 = pixels1 + py1 * rs1 + px1 * nc1;                                    //  input pixel group (4)
      pix2 = pixels2 + py2 * rs2 + px2 * nc2;                                    //  output pixel

      pix2[0] = (dx2 * pix1[0] + dx1 * pix1[3] + dy2 * pix1[0] + dy1 * pix1[rs1+0]) / 2;
      pix2[1] = (dx2 * pix1[1] + dx1 * pix1[4] + dy2 * pix1[1] + dy1 * pix1[rs1+1]) / 2;
      pix2[2] = (dx2 * pix1[2] + dx1 * pix1[5] + dy2 * pix1[2] + dy1 * pix1[rs1+2]) / 2;
   }

   busy[index] = 0;
   return 0;
}



