/*
 * Cinelerra :: Blue Banana - color modification plugin for Cinelerra-CV
 * Copyright (C) 2012-2013 Monty <monty@xiph.org>
 *
 * 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 2 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#include <math.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>

#include "bluebanana.h"
#include "bluebananaconfig.h"
#include "loadbalance.h"
#include "bluebananacolor.c" // need the inlines

BluebananaPackage::BluebananaPackage(BluebananaEngine *engine) : LoadPackage(){
  this->engine=engine;
}

BluebananaUnit::BluebananaUnit(BluebananaEngine *server,
                               BluebananaMain *plugin) : LoadClient(server) {
  this->plugin = plugin;
  this->server = server;
}

BluebananaUnit::~BluebananaUnit(){}

void BluebananaUnit::process_package(LoadPackage *package){
  BluebananaPackage *pkg = (BluebananaPackage*)package;
  BluebananaEngine *engine = (BluebananaEngine*)pkg->engine;

  VFrame *frame = engine->data;
  int w = frame->get_w();
  int h = frame->get_h();
  int ant = plugin->ants_counter;
  int gui_open = plugin->gui_open();
  int show_ants = plugin->config.mark && gui_open;
  int j;

  int active = plugin->config.active;
  int op = plugin->config.op;
  int use_mask = plugin->config.use_mask;
  int capture_mask = plugin->config.capture_mask;
  int invert_selection = plugin->config.invert_selection;

  float *Hl = (plugin->config.Hsel_active &&
               (plugin->config.Hsel_lo!=0 ||
                plugin->config.Hsel_hi!=360)) ? plugin->hue_select_alpha_lookup : NULL;
  float *Sl = (plugin->config.Ssel_active &&
               (plugin->config.Ssel_lo!=0  ||
                plugin->config.Ssel_hi!=100)) ? plugin->sat_select_alpha_lookup : NULL;
  float *Vl = (plugin->config.Vsel_active &&
               (plugin->config.Vsel_lo!=0 ||
                plugin->config.Vsel_hi!=100)) ? plugin->val_select_alpha_lookup : NULL;

  float Hal = plugin->config.Hadj_active ? plugin->config.Hadj_val/60.f : 0.f;

  float *Sal = (plugin->config.Sadj_active &&
                (plugin->config.Sadj_lo!=0 ||
                 plugin->config.Sadj_hi!=100 ||
                 plugin->config.Sadj_gamma!=1)) ? plugin->sat_adj_lookup : NULL;
  float *Val = (plugin->config.Vadj_active &&
                (plugin->config.Vadj_lo!=0 ||
                 plugin->config.Vadj_hi!=100 ||
                 plugin->config.Vadj_gamma!=1)) ? plugin->val_adj_lookup : NULL;
  float *Ral = (plugin->config.Radj_active &&
                (plugin->config.Radj_lo!=0 ||
                 plugin->config.Radj_hi!=100 ||
                 plugin->config.Radj_gamma!=1)) ? plugin->red_adj_lookup : NULL;
  float *Gal = (plugin->config.Gadj_active &&
                (plugin->config.Gadj_lo!=0 ||
                 plugin->config.Gadj_hi!=100 ||
                 plugin->config.Gadj_gamma!=1)) ? plugin->green_adj_lookup : NULL;
  float *Bal = (plugin->config.Badj_active &&
                (plugin->config.Badj_lo!=0 ||
                 plugin->config.Badj_hi!=100 ||
                 plugin->config.Badj_gamma!=1)) ? plugin->blue_adj_lookup : NULL;

  float Sas = plugin->sat_adj_toe_slope;
  float Vas = plugin->val_adj_toe_slope;
  float Ras = plugin->red_adj_toe_slope;
  float Gas = plugin->green_adj_toe_slope;
  float Bas = plugin->blue_adj_toe_slope;

  float Oal = plugin->config.Oadj_active ? plugin->config.Oadj_val*.01 : 1.f;
  float Aal = plugin->config.Aadj_active ? plugin->config.Aadj_val*.01 : 1.f;

  float Vscale = (plugin->config.Vadj_hi-plugin->config.Vadj_lo) / 100.f;
  float Vshift = plugin->config.Vadj_lo / 100.f;
  float Vgamma = plugin->config.Vadj_gamma;

  int doRGB = Ral || Gal || Bal;

  int doHSV = (Hal!=0) || Sal || Val;

  int doSEL = ( Hl || Sl || Vl ) && (active || show_ants);

  int shaping = plugin->config.Fsel_active && doSEL &&
    (plugin->config.Fsel_lo || plugin->config.Fsel_hi || plugin->config.Fsel_over);

  int byte_advance=0;
  int have_alpha=1;

#define SPLIT 256

  int tasks = engine->get_total_packages()*16;
  int taski,rowi,coli;

  /* do as much work entirely local to thread memory as possible */
  unsigned char row_fragment[SPLIT*16];
  float *selection_fullframe=NULL;
  float  selection[SPLIT];

  float Rvec[SPLIT];
  float Gvec[SPLIT];
  float Bvec[SPLIT];

  float Avec[SPLIT];
  float Hvec[SPLIT];
  float Svec[SPLIT];
  float Vvec[SPLIT];

  float *Rhist = pkg->Rhist;
  float *Ghist = pkg->Ghist;
  float *Bhist = pkg->Bhist;
  float *Hhist = pkg->Hhist;
  float *Shist = pkg->Shist;
  float *Vhist = pkg->Vhist;
  float Htotal=0.f;
  float Hweight=0.f;

  float *Hhr = pkg->Hhr;
  float *Hhg = pkg->Hhg;
  float *Hhb = pkg->Hhb;
  float *Shr = pkg->Shr;
  float *Shg = pkg->Shg;
  float *Shb = pkg->Shb;
  float *Vhr = pkg->Vhr;
  float *Vhg = pkg->Vhg;
  float *Vhb = pkg->Vhb;

  memset(Rhist,0,sizeof(pkg->Rhist));
  memset(Ghist,0,sizeof(pkg->Ghist));
  memset(Bhist,0,sizeof(pkg->Bhist));
  memset(Hhist,0,sizeof(pkg->Hhist));
  memset(Shist,0,sizeof(pkg->Shist));
  memset(Vhist,0,sizeof(pkg->Vhist));

  memset(Hhr,0,sizeof(pkg->Hhr));
  memset(Hhg,0,sizeof(pkg->Hhg));
  memset(Hhb,0,sizeof(pkg->Hhb));
  memset(Shr,0,sizeof(pkg->Shr));
  memset(Shg,0,sizeof(pkg->Shg));
  memset(Shb,0,sizeof(pkg->Shb));
  memset(Vhr,0,sizeof(pkg->Vhr));
  memset(Vhg,0,sizeof(pkg->Vhg));
  memset(Vhb,0,sizeof(pkg->Vhb));

  /* If we're doing fill shaping, we need to compute base selection
     for the entire frame before shaping. */

  if(shaping){
    engine->set_task(tasks*2,"shaping_even");
    while ( (taski = engine->next_task()) >= 0){

    /* operate on discontinuous, interleaved sections of the source
       buffer in two passes.  Although we could take extra steps to
       make cache contention across cores completely impossible, the
       extra locking and central join required isn't worth it.  It's
       preferable to make contention merely highly unlikely. */

      int start_row, end_row;
      if(taski<tasks){
        start_row = (taski*2)*h/(tasks*2);
        end_row = (taski*2+1)*h/(tasks*2);
      }else{
        start_row = ((taski-tasks)*2+1)*h/(tasks*2);
        end_row = ((taski-tasks)*2+2)*h/(tasks*2);
      }

      for(rowi = start_row; rowi<end_row; rowi++){
        unsigned char *row = frame->get_rows()[rowi];

        for(coli=0;coli<w;coli+=SPLIT){

          int todo = (w-coli>SPLIT)?SPLIT:(w-coli);
          float *A = engine->selection_workA+w*rowi+coli;

          switch(frame->get_color_model()) {
          case BC_RGB888:
            rgb8_to_RGB((unsigned char *)row,Rvec,Gvec,Bvec,todo);
            row += todo*3;
            break;
          case BC_RGBA8888:
            rgba8_to_RGBA((unsigned char *)row,Rvec,Gvec,Bvec,Avec,todo);
            row += todo*4;
            break;
          case BC_RGB_FLOAT:
            rgbF_to_RGB((float *)row,Rvec,Gvec,Bvec,todo);
            row += todo*12;
            break;
          case BC_RGBA_FLOAT:
            rgbaF_to_RGBA((float *)row,Rvec,Gvec,Bvec,Avec,todo);
            row += todo*16;
            break;
          case BC_YUV888:
            yuv8_to_RGB((unsigned char *)row,Rvec,Gvec,Bvec,todo);
            row += todo*3;
            break;
          case BC_YUVA8888:
            yuva8_to_RGBA((unsigned char *)row,Rvec,Gvec,Bvec,Avec,todo);
            row += todo*4;
            break;
          }

          for(j = 0; j < todo; j++)
            RGB_to_HSpV(Rvec[j],Gvec[j],Bvec[j],Hvec[j],Svec[j],Vvec[j]);

          if(Hl)
            for(j = 0; j < todo; j++)
              selection[j]=sel_lookup(Hvec[j]*.166666667f,Hl);
          else
            for(j = 0; j < todo; j++)
              selection[j]=1.f;

          if(Sl)
            for(j = 0; j < todo; j++)
              selection[j]*=sel_lookup(Svec[j],Sl);

          if(Vl)
            for(j = 0; j < todo; j++)
              selection[j]*=sel_lookup(Vvec[j],Vl);

          /* lock the memcpy to prevent pessimal cache coherency
             interleave across cores. */
          pthread_mutex_lock(&engine->copylock);
          memcpy(A,selection,sizeof(*selection)*todo);
          pthread_mutex_unlock(&engine->copylock);
        }
      }
    }

    /* Perform fill shaping on the selection */
    selection_fullframe = plugin->fill_selection(engine->selection_workA,
                                                 engine->selection_workB, w, h, engine);
  }

  /* row-by-row color modification and histogram feedback */
  engine->set_task(tasks*2,"modification_even");
  while((taski = engine->next_task())>=0){

    /* operate on discontinuous, interleaved sections of the source
       buffer in two passes.  Although we could take extra steps to
       make cache contention across cores completely impossible, the
       extra locking and central join required isn't worth it.  It's
       preferable to make contention merely highly unlikely. */

    int start_row, end_row;
    if(taski<tasks){
      start_row = (taski*2)*h/(tasks*2);
      end_row = (taski*2+1)*h/(tasks*2);
    }else{
      start_row = ((taski-tasks)*2+1)*h/(tasks*2);
      end_row = ((taski-tasks)*2+2)*h/(tasks*2);
    }

    for(rowi = start_row; rowi<end_row; rowi++){
      unsigned char *row = frame->get_rows()[rowi];

      for(int coli=0;coli<w;coli+=SPLIT){
        int todo = (w-coli>SPLIT)?SPLIT:(w-coli);
        int have_selection = 0;

        /* convert from pipeline color format */
        if(active || show_ants || (use_mask && capture_mask && have_alpha)){

          switch(frame->get_color_model()) {
          case BC_RGB888:
            pthread_mutex_lock(&engine->copylock);
            memcpy(row_fragment,row,todo*3);
            pthread_mutex_unlock(&engine->copylock);
            rgb8_to_RGB(row_fragment,Rvec,Gvec,Bvec,todo);
            byte_advance = todo*3;
            have_alpha=0;
            break;

          case BC_RGBA8888:
            pthread_mutex_lock(&engine->copylock);
            memcpy(row_fragment,row,todo*4);
            pthread_mutex_unlock(&engine->copylock);
            rgba8_to_RGBA(row_fragment,Rvec,Gvec,Bvec,Avec,todo);
            byte_advance = todo*4;
            have_alpha=1;
            break;

          case BC_RGB_FLOAT:
            pthread_mutex_lock(&engine->copylock);
            memcpy(row_fragment,row,todo*12);
            pthread_mutex_unlock(&engine->copylock);
            rgbF_to_RGB((float *)row_fragment,Rvec,Gvec,Bvec,todo);
            byte_advance = todo*12;
            have_alpha=0;
            break;

          case BC_RGBA_FLOAT:
            pthread_mutex_lock(&engine->copylock);
            memcpy(row_fragment,row,todo*16);
            pthread_mutex_unlock(&engine->copylock);
            rgbaF_to_RGBA((float *)row_fragment,Rvec,Gvec,Bvec,Avec,todo);
            byte_advance = todo*16;
            have_alpha=1;
            break;

          case BC_YUV888:
            pthread_mutex_lock(&engine->copylock);
            memcpy(row_fragment,row,todo*3);
            pthread_mutex_unlock(&engine->copylock);
            yuv8_to_RGB(row_fragment,Rvec,Gvec,Bvec,todo);
            byte_advance = todo*3;
            have_alpha=0;
            break;

          case BC_YUVA8888:
            pthread_mutex_lock(&engine->copylock);
            memcpy(row_fragment,row,todo*4);
            pthread_mutex_unlock(&engine->copylock);
            yuva8_to_RGBA(row,Rvec,Gvec,Bvec,Avec,todo);
            byte_advance = todo*4;
            have_alpha=1;
            break;
          }

          if(doSEL)
            /* generate initial HSV values [if selection active] */
            for(j = 0; j < todo; j++)
              RGB_to_HSpV(Rvec[j],Gvec[j],Bvec[j],Hvec[j],Svec[j],Vvec[j]);


          float selection_test=todo;

          if(selection_fullframe){

            /* get the full-frame selection data we need into thread-local storage */
            /* the full-frame data is read-only at this point, no need to lock */
            float *sf = selection_fullframe + rowi*w + coli;
            selection_test=0.f;
            for(j = 0; j < todo; j++)
              selection_test += selection[j] = sf[j];
            have_selection=1;

          }else{

            /* selection computation when no full-frame shaping */
            if(Hl){
              selection_test=0.f;
              for(j = 0; j < todo; j++)
                selection_test += selection[j] = sel_lookup(Hvec[j]*.166666667f,Hl);
              have_selection=1;
            }

            if(Sl){
              if(have_selection){
                if(selection_test>SELECT_THRESH){
                  selection_test=0.f;
                  for(j = 0; j < todo; j++)
                    selection_test += selection[j] *= sel_lookup(Svec[j],Sl);
                }
              }else{
                selection_test=0.f;
                for(j = 0; j < todo; j++)
                  selection_test += selection[j] = sel_lookup(Svec[j],Sl);
                have_selection=1;
              }
            }

            if(Vl){
              if(have_selection){
                if(selection_test>SELECT_THRESH){
                  selection_test=0.f;
                  for(j = 0; j < todo; j++)
                    selection_test += selection[j] *= sel_lookup(Vvec[j],Vl);
                }
              }else{
                selection_test=0.f;
                for(j = 0; j < todo; j++)
                  selection_test += selection[j] = sel_lookup(Vvec[j],Vl);
                have_selection=1;
              }
            }
          }

          /* selection modification according to config */
          if(use_mask && have_alpha){
            if(!op){
              if(!have_selection){
                /* selection consists only of mask */
                selection_test=0.;
                for(j = 0; j < todo; j++)
                  selection_test += selection[j] = Avec[j];
                have_selection=1;
              }else{
                if(invert_selection){
                  if(selection_test < SELECT_THRESH){
                    /* fully selected after invert, clip to mask */
                    selection_test=0.f;
                    for(j = 0; j < todo; j++)
                      selection_test += selection[j] = Avec[j];
                  }else{
                    /* partial selection after invert, clip to mask */
                    selection_test=0.f;
                    for(j = 0; j < todo; j++)
                      selection_test += selection[j] = Avec[j]*(1.f-selection[j]);
                  }
                }else{
                  if(selection_test < SELECT_THRESH){
                    /* fully deselected */
                  }else if(selection_test >= todo-SELECT_THRESH){
                    /* fully selected, clip to mask */
                    selection_test=0.f;
                    for(j = 0; j < todo; j++)
                      selection_test += selection[j] = Avec[j];
                  }else{
                    /* partial selection, clip to mask */
                    selection_test=0.f;
                    for(j = 0; j < todo; j++)
                      selection_test += selection[j] *= Avec[j];
                  }
                }
              }
            }else{
              if(!have_selection){
                for(j = 0; j < todo; j++) selection[j] = 1;
              }else{
                if(invert_selection){
                  if(selection_test < SELECT_THRESH){
                    /* fully selected after invert */
                    for(j = 0; j < todo; j++) selection[j] = 1;
                  }else{
                    /* partial selection after invert, clip to mask */
                    selection_test=0.f;
                    for(j = 0; j < todo; j++)
                      selection_test += selection[j] =
                        Avec[j]+(1.f-selection[j])-Avec[j]*(1.f-selection[j]);
                  }
                }else{
                  if(selection_test < SELECT_THRESH){
                    /* fully deselected, clip to mask */
                    selection_test=0.f;
                    for(j = 0; j < todo; j++)
                      selection_test += selection[j] = Avec[j];
                  }else if(selection_test >= todo-SELECT_THRESH){
                    /* fully selected */
                  }else{
                    /* partial selection, clip to mask */
                    selection_test=0.f;
                    for(j = 0; j < todo; j++)
                      selection_test += selection[j] =
                        selection[j]+Avec[j]-selection[j]*Avec[j];
                  }
                }
              }
            }

            if(!use_mask && !invert_selection && selection_test < SELECT_THRESH){
              /* skip processing this fragment */
              row+=byte_advance;
              continue;
            }
            if(selection_test > todo-SELECT_THRESH)
              have_selection=0; // fully selected
          }else{
            if(have_selection){
              if(invert_selection){
                if(selection_test < SELECT_THRESH){
                  /* fully selected after inversion */
                  selection_test=todo;
                }else if (selection_test >= todo-SELECT_THRESH){
                  /* fully deselected after invert, skip fragment */
                  row+=byte_advance;
                  continue;
                }else{
                  /* partial selection */
                  selection_test=0.f;
                  for(j = 0; j < todo; j++)
                    selection_test += selection[j] = (1.f-selection[j]);
                }
              }else{
                if(selection_test < SELECT_THRESH){
                  /* fully deselected, skip fragment */
                  row+=byte_advance;
                  continue;
                }else if (selection_test >= todo-SELECT_THRESH){
                  /* fully selected */
                  selection_test=todo;
                }else{
                  /* partial selection; already calculated */
                }
              }
              if(selection_test < SELECT_THRESH){
                  row+=byte_advance;
                  continue; // inactive fragment
              }
              if(selection_test > todo-SELECT_THRESH)
                have_selection=0; // fully selected
            }else{
              if(invert_selection){
                  row+=byte_advance;
                  continue;
              }
            }
          }
        }

        if(active){

          /* red adjust */
          if(Ral) {
            if(have_selection){
              for(j = 0; j < todo; j++)
                if(selection[j]>SELECT_THRESH) Rvec[j]=adj_lookup(Rvec[j],Ral,Ras);
            }else{
              for(j = 0; j < todo; j++)
                Rvec[j] = adj_lookup(Rvec[j],Ral,Ras);
            }
          }
          /* red histogram */
          if(gui_open){
            if(have_selection){
              for(j = 0; j < todo; j++)
                if(selection[j]>SELECT_THRESH) Rhist[(int)CLAMP((Rvec[j]+HISTOFF)*HISTSCALE,0,HISTSIZE)] += selection[j];
            }else{
              for(j = 0; j < todo; j++)
                Rhist[(int)CLAMP((Rvec[j]+HISTOFF)*HISTSCALE,0,HISTSIZE)] += 1.f;
            }
          }

          /* green adjust */
          if(Gal) {
            if(have_selection){
              for(j = 0; j < todo; j++)
                if(selection[j]>SELECT_THRESH) Gvec[j]=adj_lookup(Gvec[j],Gal,Gas);
            }else{
              for(j = 0; j < todo; j++)
                Gvec[j] = adj_lookup(Gvec[j],Gal,Gas);
            }
          }
          /* green histogram */
          if(gui_open){
            if(have_selection){
              for(j = 0; j < todo; j++)
                if(selection[j]>SELECT_THRESH) Ghist[(int)CLAMP((Gvec[j]+HISTOFF)*HISTSCALE,0,HISTSIZE)] += selection[j];
            }else{
              for(j = 0; j < todo; j++)
                Ghist[(int)CLAMP((Gvec[j]+HISTOFF)*HISTSCALE,0,HISTSIZE)] += 1.f;
            }
          }

          /* blue adjust */
          if(Bal) {
            if(have_selection){
              for(j = 0; j < todo; j++)
                if(selection[j]>SELECT_THRESH) Bvec[j]=adj_lookup(Bvec[j],Bal,Bas);
            }else{
              for(j = 0; j < todo; j++)
                Bvec[j] = adj_lookup(Bvec[j],Bal,Bas);
            }
          }
          /* blue histogram */
          if(gui_open){
            if(have_selection){
              for(j = 0; j < todo; j++)
                if(selection[j]>SELECT_THRESH) Bhist[(int)CLAMP((Bvec[j]+HISTOFF)*HISTSCALE,0,HISTSIZE)] += selection[j];
            }else{
              for(j = 0; j < todo; j++)
                Bhist[(int)CLAMP((Bvec[j]+HISTOFF)*HISTSCALE,0,HISTSIZE)] += 1.f;
            }
          }

          /* compute HSV values or update values from earlier selection work if
             they've changed */
          if( (!doSEL || doRGB) && // not yet computed, or since modified
              (doHSV || gui_open) ) // needed for HSV mod and/or histograms
            for(j = 0; j < todo; j++)
              RGB_to_HSpV(Rvec[j],Gvec[j],Bvec[j],Hvec[j],Svec[j],Vvec[j]);

          if(doHSV){
            /* H modification */
            /* don't bother checking selection, H adj is lightweight */
            if(Hal){
              for(j = 0; j < todo; j++){
                Hvec[j] += Hal;
                if(Hvec[j]<0.f)Hvec[j]+=6.f;
                if(Hvec[j]>=6.f)Hvec[j]-=6.f;
              }
            }
            /* H histogram */
            /* this is pre-shift RGB data; shift hue later to save an HSV->RGB transform */
            if(gui_open){
              if(have_selection){
                for(j = 0; j < todo; j++){
                  if(selection[j]>SELECT_THRESH){
                    float weight = selection[j]*Svec[j];
                    int bin = CLAMP(Hvec[j]*HUESCALE,0,HISTSIZE);
                    Htotal += selection[j];
                    Hweight += weight;
                    Hhist[bin] += weight;
                    Hhr[bin>>HRGBSHIFT] += Rvec[j]*weight;
                    Hhg[bin>>HRGBSHIFT] += Gvec[j]*weight;
                    Hhb[bin>>HRGBSHIFT] += Bvec[j]*weight;
                  }
                }
              }else{
                for(j = 0; j < todo; j++){
                  float weight = Svec[j];
                  int bin = CLAMP(Hvec[j]*HUESCALE,0,HISTSIZE);
                  Htotal += 1.f;
                  Hweight += weight;
                  Hhist[bin] += weight;
                  Hhr[bin>>HRGBSHIFT] += Rvec[j]*weight;
                  Hhg[bin>>HRGBSHIFT] += Gvec[j]*weight;
                  Hhb[bin>>HRGBSHIFT] += Bvec[j]*weight;
                }
              }
            }

            /* S modification */
            if(Sal) {
              if(have_selection){
                for(j = 0; j < todo; j++)
                  if(selection[j]>SELECT_THRESH) Svec[j] = adj_lookup(Svec[j],Sal,Sas);
              }else{
                for(j = 0; j < todo; j++)
                  Svec[j] = adj_lookup(Svec[j],Sal,Sas);
              }
            }

            /* This is unrolled a few times below...
               Although we're using HSV, we don't want hue/saturation
               changes to have a strong effect on apparent brightness.
               Apply a correction to V (not Y!) based on luma change. */
            /* Calculate new RGB values at same time */
            if(Hal || Sal){
              if(have_selection){
                for(j = 0; j < todo; j++){
                  if(selection[j]>SELECT_THRESH){
                    HSpV_correct_RGB(Hvec[j],Svec[j],Vvec[j],Rvec[j],Gvec[j],Bvec[j]);
                    /* run S histogram at the same time as we've got
                       the RGB data */
                    if(gui_open){
                      int bin = CLAMP((Svec[j]+HISTOFF)*HISTSCALE,0,HISTSIZE);
                      Shist[bin] += selection[j];
                      Shr[bin>>HRGBSHIFT] += Rvec[j]*selection[j];
                      Shg[bin>>HRGBSHIFT] += Gvec[j]*selection[j];
                      Shb[bin>>HRGBSHIFT] += Bvec[j]*selection[j];
                    }
                  }
                }
              }else{
                for(j = 0; j < todo; j++){
                  HSpV_correct_RGB(Hvec[j],Svec[j],Vvec[j],Rvec[j],Gvec[j],Bvec[j]);
                  if(gui_open){
                    int bin = CLAMP((Svec[j]+HISTOFF)*HISTSCALE,0,HISTSIZE);
                    Shist[bin] += 1.f;
                    Shr[bin>>HRGBSHIFT] += Rvec[j];
                    Shg[bin>>HRGBSHIFT] += Gvec[j];
                    Shb[bin>>HRGBSHIFT] += Bvec[j];
                  }
                }
              }
            }else{
              if(gui_open){
                if(have_selection){
                  for(j = 0; j < todo; j++){
                    if(selection[j]>SELECT_THRESH){
                      int bin = CLAMP((Svec[j]+HISTOFF)*HISTSCALE,0,HISTSIZE);
                      Shist[bin] += selection[j];
                      Shr[bin>>HRGBSHIFT] += Rvec[j]*selection[j];
                      Shg[bin>>HRGBSHIFT] += Gvec[j]*selection[j];
                      Shb[bin>>HRGBSHIFT] += Bvec[j]*selection[j];
                    }
                  }
                }else{
                  for(j = 0; j < todo; j++){
                    int bin = CLAMP((Svec[j]+HISTOFF)*HISTSCALE,0,HISTSIZE);
                    Shist[bin] += 1.f;
                    Shr[bin>>HRGBSHIFT] += Rvec[j];
                    Shg[bin>>HRGBSHIFT] += Gvec[j];
                    Shb[bin>>HRGBSHIFT] += Bvec[j];
                  }
                }
              }
            }

            /* V modification */
            /* This one is a bit unlike the others; Value gamma
               behavior is conditional.  When < 1.0 (darkening the
               picture) gamma directly operates on Value as normally
               expected (thus scaling R G and B about zero).  When
               gamma > 1.0 (lightening the picture) it operates on the
               Value scale's zero point rather than the Value.  Both
               are done to get the most natural behavior out of
               saturation as gamma changes.  The only drawback: One
               can no longer reverse a gamma operation by applying the
               inverse gamma in a subsequent step. */
            if(Val){
              if(have_selection){
                if(Vgamma<1.0){
                  /* scale mode */
                  for(j = 0; j < todo; j++)
                    if(selection[j]>SELECT_THRESH){
                      float scale = adj_lookup(Vvec[j],Val,Vas);
                      Vvec[j] = Vvec[j] * scale + Vshift;
                      Rvec[j] = Rvec[j] * scale + Vshift;
                      Gvec[j] = Gvec[j] * scale + Vshift;
                      Bvec[j] = Bvec[j] * scale + Vshift;
                    }
                }else{
                  /* shift mode */
                  for(j = 0; j < todo; j++)
                    if(selection[j]>SELECT_THRESH){
                      float shift = adj_lookup(Vvec[j],Val,Vas);
                      Vvec[j] = Vvec[j] * Vscale + shift;
                      Rvec[j] = Rvec[j] * Vscale + shift;
                      Gvec[j] = Gvec[j] * Vscale + shift;
                      Bvec[j] = Bvec[j] * Vscale + shift;
                    }
                }
              }else{
                if(Vgamma<1.0){
                  /* scale mode */
                  for(j = 0; j < todo; j++){
                    float scale = adj_lookup(Vvec[j],Val,Vas);
                    Vvec[j] = Vvec[j] * scale + Vshift;
                    Rvec[j] = Rvec[j] * scale + Vshift;
                    Gvec[j] = Gvec[j] * scale + Vshift;
                    Bvec[j] = Bvec[j] * scale + Vshift;
                  }
                }else{
                  /* shift mode */
                  for(j = 0; j < todo; j++){
                    float shift = adj_lookup(Vvec[j],Val,Vas);
                    Vvec[j] = Vvec[j] * Vscale + shift;
                    Rvec[j] = Rvec[j] * Vscale + shift;
                    Gvec[j] = Gvec[j] * Vscale + shift;
                    Bvec[j] = Bvec[j] * Vscale + shift;
                  }
                }
              }
            }

            /* V histogram */
            if(gui_open){
              if(have_selection){
                for(j = 0; j < todo; j++){
                  if(selection[j]>SELECT_THRESH){
                    int bin = CLAMP((Vvec[j]+HISTOFF)*HISTSCALE,0,HISTSIZE);
                    Vhist[bin] += selection[j];
                    Vhr[bin>>HRGBSHIFT] += Rvec[j]*selection[j];
                    Vhg[bin>>HRGBSHIFT] += Gvec[j]*selection[j];
                    Vhb[bin>>HRGBSHIFT] += Bvec[j]*selection[j];
                  }
                }
              }else{
                for(j = 0; j < todo; j++){
                  int bin = CLAMP((Vvec[j]+HISTOFF)*HISTSCALE,0,HISTSIZE);
                  Vhist[bin] += 1.f;
                  Vhr[bin>>HRGBSHIFT] += Rvec[j];
                  Vhg[bin>>HRGBSHIFT] += Gvec[j];
                  Vhb[bin>>HRGBSHIFT] += Bvec[j];
                }
              }
            }

          }else if (gui_open){

            if(have_selection){
              /* H histogram */
              for(j = 0; j < todo; j++){
                if(selection[j]>SELECT_THRESH){
                  float weight = selection[j]*Svec[j];
                  int bin = CLAMP(Hvec[j]*HUESCALE,0,HISTSIZE);
                  Htotal += selection[j];
                  Hweight += weight;
                  Hhist[bin] += weight;
                  Hhr[bin>>HRGBSHIFT] += Rvec[j]*weight;
                  Hhg[bin>>HRGBSHIFT] += Gvec[j]*weight;
                  Hhb[bin>>HRGBSHIFT] += Bvec[j]*weight;
                }
              }
              /* S histogram */
              for(j = 0; j < todo; j++){
                if(selection[j]>SELECT_THRESH){
                  int bin = CLAMP((Svec[j]+HISTOFF)*HISTSCALE,0,HISTSIZE);
                  Shist[bin] += selection[j];
                  Shr[bin>>HRGBSHIFT] += Rvec[j]*selection[j];
                  Shg[bin>>HRGBSHIFT] += Gvec[j]*selection[j];
                  Shb[bin>>HRGBSHIFT] += Bvec[j]*selection[j];
                }
              }
              /* V histogram */
              for(j = 0; j < todo; j++){
                if(selection[j]>SELECT_THRESH){
                  int bin = CLAMP((Vvec[j]+HISTOFF)*HISTSCALE,0,HISTSIZE);
                  Vhist[bin] += selection[j];
                  Vhr[bin>>HRGBSHIFT] += Rvec[j]*selection[j];
                  Vhg[bin>>HRGBSHIFT] += Gvec[j]*selection[j];
                  Vhb[bin>>HRGBSHIFT] += Bvec[j]*selection[j];
                }
              }
            }else{

              /* H histogram */
              for(j = 0; j < todo; j++){
                float weight = Svec[j];
                int bin = CLAMP(Hvec[j]*HUESCALE,0,HISTSIZE);
                Htotal += 1.f;
                Hweight += weight;
                Hhist[bin] += weight;
                Hhr[bin>>HRGBSHIFT] += Rvec[j]*weight;
                Hhg[bin>>HRGBSHIFT] += Gvec[j]*weight;
                Hhb[bin>>HRGBSHIFT] += Bvec[j]*weight;
              }
              /* S histogram */
              for(j = 0; j < todo; j++){
                int bin = CLAMP((Svec[j]+HISTOFF)*HISTSCALE,0,HISTSIZE);
                Shist[bin] += 1.f;
                Shr[bin>>HRGBSHIFT] += Rvec[j];
                Shg[bin>>HRGBSHIFT] += Gvec[j];
                Shb[bin>>HRGBSHIFT] += Bvec[j];
              }
              /* V histogram */
              for(j = 0; j < todo; j++){
                int bin = CLAMP((Vvec[j]+HISTOFF)*HISTSCALE,0,HISTSIZE);
                Vhist[bin] += 1.f;
                Vhr[bin>>HRGBSHIFT] += Rvec[j];
                Vhg[bin>>HRGBSHIFT] += Gvec[j];
                Vhb[bin>>HRGBSHIFT] += Bvec[j];
              }
            }
          }

          /* layer back into pipeline color format; master fader applies here */
          switch(frame->get_color_model()) {
          case BC_RGB888:
            RGB_to_rgb8(Rvec,Gvec,Bvec,have_selection?selection:0,Oal,row_fragment,todo,3);
            break;
          case BC_RGBA8888:
            RGB_to_rgb8(Rvec,Gvec,Bvec,have_selection?selection:0,Oal,row_fragment,todo,4);
            break;
          case BC_RGB_FLOAT:
            RGB_to_rgbF(Rvec,Gvec,Bvec,have_selection?selection:0,Oal,(float *)row_fragment,todo,3);
            break;
          case BC_RGBA_FLOAT:
            RGB_to_rgbF(Rvec,Gvec,Bvec,have_selection?selection:0,Oal,(float *)row_fragment,todo,4);
            break;
          case BC_YUV888:
            RGB_to_yuv8(Rvec,Gvec,Bvec,have_selection?selection:0,Oal,row_fragment,todo,3);
            break;
          case BC_YUVA8888:
            RGB_to_yuv8(Rvec,Gvec,Bvec,have_selection?selection:0,Oal,row_fragment,todo,4);
            break;
          }
        }

        /* ants */
        if(show_ants){
          for(j = 0; j < todo; j++){
            float A = have_selection ? selection[j] : 1.f;
            if(A>.99999f){
              if( (ant+rowi-j)&0x4 ){
                /* magenta */
                Rvec[j] = 1;
                Gvec[j] = 0;
                Bvec[j] = 1;
              }else{
                /* green */
                Rvec[j] = 0;
                Gvec[j] = 1;
                Bvec[j] = 0;
              }
            }else{
              if( (ant+rowi+j)&0x4 ){
                /* black */
                Rvec[j] = 0;
                Gvec[j] = 0;
                Bvec[j] = 0;
              }else{
                /* white */
                Rvec[j] = 1;
                Gvec[j] = 1;
                Bvec[j] = 1;
              }
            }
          }
          float *s = have_selection?selection:0;
          /* re-layer back into pipeline color format */
          switch(frame->get_color_model()) {
          case BC_RGB888:
            RGB_to_rgb8(Rvec,Gvec,Bvec,s,1.f,row_fragment,todo,3);
            break;
          case BC_RGBA8888:
            RGB_to_rgb8(Rvec,Gvec,Bvec,s,1.f,row_fragment,todo,4);
            break;
          case BC_RGB_FLOAT:
            RGB_to_rgbF(Rvec,Gvec,Bvec,s,1.f,(float *)row_fragment,todo,3);
            break;
          case BC_RGBA_FLOAT:
            RGB_to_rgbF(Rvec,Gvec,Bvec,s,1.f,(float *)row_fragment,todo,4);
            break;
          case BC_YUV888:
            RGB_to_yuv8(Rvec,Gvec,Bvec,s,1.f,row_fragment,todo,3);
            break;
          case BC_YUVA8888:
            RGB_to_yuv8(Rvec,Gvec,Bvec,s,1.f,row_fragment,todo,4);
            break;
          }
        }

        if(active || show_ants || use_mask || capture_mask){
          if(use_mask && capture_mask){
            switch(frame->get_color_model()) {
            case BC_RGBA8888:
              unmask_rgba8(row_fragment,todo);
              break;
            case BC_RGBA_FLOAT:
              unmask_rgbaF((float *)row_fragment,todo);
              break;
            case BC_YUVA8888:
              unmask_yuva8(row_fragment,todo);
              break;
            }
          }
          else if(use_mask){
            float *s = !show_ants&&have_selection?selection:0;
            switch(frame->get_color_model()) {
            case BC_RGBA8888:
            case BC_YUVA8888:
              Aal_to_alp8(s,Aal,row_fragment,todo,4);
              break;
            case BC_RGBA_FLOAT:
              Aal_to_alpF(s,Aal,(float *)row_fragment,todo,4);
              break;
            }
          }

          /* lock to prevent interleaved cache coherency collisions
             across cores. Two memcpys touching the same cache line
             will cause the wings to fall off the processor. */

          pthread_mutex_lock(&engine->copylock);
          memcpy(row,row_fragment,byte_advance);
          pthread_mutex_unlock(&engine->copylock);
          row+=byte_advance;

        }
      }
    }
  }

  pthread_mutex_lock(&engine->copylock);

  plugin->hue_total+=Htotal;
  plugin->hue_weight+=Hweight;

  for(int i=0;i<HISTSIZE;i++){
    plugin->red_histogram[i] += Rhist[i];
    plugin->green_histogram[i] += Ghist[i];
    plugin->blue_histogram[i] += Bhist[i];

    plugin->hue_histogram[i] += Hhist[i];
    plugin->sat_histogram[i] += Shist[i];

    plugin->value_histogram[i] += Vhist[i];
  }

  for(int i=0;i<(HISTSIZE>>HRGBSHIFT);i++){
    plugin->hue_histogram_red[i] += Hhr[i];
    plugin->hue_histogram_green[i] += Hhg[i];
    plugin->hue_histogram_blue[i] += Hhb[i];

    plugin->sat_histogram_red[i] += Shr[i];
    plugin->sat_histogram_green[i] += Shg[i];
    plugin->sat_histogram_blue[i] += Shb[i];

    plugin->value_histogram_red[i] += Vhr[i];
    plugin->value_histogram_green[i] += Vhg[i];
    plugin->value_histogram_blue[i] += Vhb[i];
  }
  pthread_mutex_unlock(&engine->copylock);

}

BluebananaEngine::BluebananaEngine(BluebananaMain *plugin, int total_clients,
                                   int total_packages) : LoadServer(total_clients, total_packages){
  this->plugin = plugin;
  selection_workA=0;
  selection_workB=0;
  task_init_serial=0;
  pthread_mutex_init(&copylock,NULL);
  pthread_mutex_init(&tasklock,NULL);
  pthread_cond_init(&taskcond,NULL);
}
BluebananaEngine::~BluebananaEngine(){
  pthread_cond_destroy(&taskcond);
  pthread_mutex_destroy(&tasklock);
  pthread_mutex_destroy(&copylock);
  if(selection_workA) delete[] selection_workA;
  if(selection_workB) delete[] selection_workB;
}

void BluebananaEngine::init_packages(){}

LoadClient* BluebananaEngine::new_client(){
  return new BluebananaUnit(this, plugin);
}

LoadPackage* BluebananaEngine::new_package(){
  return new BluebananaPackage(this);
}

static float lt(float *data,float pos, int n){
  if(pos<0)pos+=n;
  if(pos>=n)pos-=n;
  int i = (int)pos;
  float del = pos-i;
  return data[i]*(1.f-del) + data[i+1]*del;
}

static float lt_shift(float *data, float pos, int n, int over){
  float s0=0, s1=0;
  if(pos<0)pos+=n;
  if(pos>=n)pos-=n;
  int i = (int)pos;
  float del = pos-i;
  i *= over;
  data += i;
  for(int j=0; j<over; j++)
    s0 += *data++;
  for(int j=0; j<over; j++)
    s1 += *data++;

  float temp=(s0*(1.f-del) + s1*del);
  if(temp>0)
    return 1./temp;
  else
    return 0;
}

void BluebananaEngine::process_packages(VFrame *data){
  int w = data->get_w();
  int h = data->get_h();
  this->data = data;
  task_init_state=0;
  task_n=0;
  task_finish_count=0;

  /* If we're doing any spatial modification of the selection, we'll
     need to operate on a temporary selection array that covers the
     complete frame */
  if(plugin->config.Fsel_active){
    if(plugin->config.Fsel_lo ||
       plugin->config.Fsel_mid ||
       plugin->config.Fsel_hi ||
       plugin->config.Fsel_over){
      selection_workA = new float[w*h];
      selection_workB = new float[w*h];
    }
  }

  memset(plugin->red_histogram,0,sizeof(plugin->red_histogram));
  memset(plugin->green_histogram,0,sizeof(plugin->green_histogram));
  memset(plugin->blue_histogram,0,sizeof(plugin->blue_histogram));
  memset(plugin->hue_histogram,0,sizeof(plugin->hue_histogram));
  plugin->hue_total=0.f;
  plugin->hue_weight=0.f;
  memset(plugin->sat_histogram,0,sizeof(plugin->sat_histogram));
  memset(plugin->value_histogram,0,sizeof(plugin->value_histogram));

  memset(plugin->hue_histogram_red,0,sizeof(plugin->hue_histogram_red));
  memset(plugin->hue_histogram_green,0,sizeof(plugin->hue_histogram_green));
  memset(plugin->hue_histogram_blue,0,sizeof(plugin->hue_histogram_blue));

  memset(plugin->sat_histogram_red,0,sizeof(plugin->sat_histogram_red));
  memset(plugin->sat_histogram_green,0,sizeof(plugin->sat_histogram_green));
  memset(plugin->sat_histogram_blue,0,sizeof(plugin->sat_histogram_blue));

  memset(plugin->value_histogram_red,0,sizeof(plugin->value_histogram_red));
  memset(plugin->value_histogram_green,0,sizeof(plugin->value_histogram_green));
  memset(plugin->value_histogram_blue,0,sizeof(plugin->value_histogram_blue));

  LoadServer::process_packages();

  /* The Hue histogram needs to be adjusted/shifted */
  if(plugin->config.active && plugin->hue_weight){
    int i,j;
    float scale = plugin->hue_total/plugin->hue_weight;
    float Hshift = plugin->config.Hsel_active ? (plugin->config.Hsel_lo + plugin->config.Hsel_hi)/720.f-.5f : 0.f;
    float Hal = plugin->config.Hadj_active ? plugin->config.Hadj_val/60.f : 0.f;
    float hist[HISTSIZE + (1<<HRGBSHIFT)];
    float red[(HISTSIZE>>HRGBSHIFT)+1];
    float green[(HISTSIZE>>HRGBSHIFT)+1];
    float blue[(HISTSIZE>>HRGBSHIFT)+1];

    for(i=0;i<(1<<HRGBSHIFT);i++){
      plugin->hue_histogram[i]+=plugin->hue_histogram[HISTSIZE+i];
      plugin->hue_histogram[HISTSIZE+i]=plugin->hue_histogram[i];
    }
    plugin->hue_histogram_red[0]+=plugin->hue_histogram_red[HISTSIZE>>HRGBSHIFT];
    plugin->hue_histogram_red[HISTSIZE>>HRGBSHIFT]=plugin->hue_histogram_red[0];
    plugin->hue_histogram_green[0]+=plugin->hue_histogram_green[HISTSIZE>>HRGBSHIFT];
    plugin->hue_histogram_green[HISTSIZE>>HRGBSHIFT]=plugin->hue_histogram_green[0];
    plugin->hue_histogram_blue[0]+=plugin->hue_histogram_blue[HISTSIZE>>HRGBSHIFT];
    plugin->hue_histogram_blue[HISTSIZE>>HRGBSHIFT]=plugin->hue_histogram_blue[0];

    for(i=0; i<(HISTSIZE>>HRGBSHIFT); i++){
      float pos = i+Hshift*(HISTSIZE>>HRGBSHIFT);
      if(pos<0)pos+=(HISTSIZE>>HRGBSHIFT);
      if(pos>=(HISTSIZE>>HRGBSHIFT))pos-=(HISTSIZE>>HRGBSHIFT);

      float div = lt_shift(plugin->hue_histogram,i+Hshift*(HISTSIZE>>HRGBSHIFT),
                           (HISTSIZE>>HRGBSHIFT), (1<<HRGBSHIFT));
      red[i] = lt(plugin->hue_histogram_red,i+Hshift*(HISTSIZE>>HRGBSHIFT),
                  (HISTSIZE>>HRGBSHIFT))*div;
      green[i] = lt(plugin->hue_histogram_green,i+Hshift*(HISTSIZE>>HRGBSHIFT),
                    (HISTSIZE>>HRGBSHIFT))*div;
      blue[i] = lt(plugin->hue_histogram_blue,i+Hshift*(HISTSIZE>>HRGBSHIFT),
                   (HISTSIZE>>HRGBSHIFT))*div;
    }

    for(int i=0; i<HISTSIZE; i++)
      hist[i] = lt(plugin->hue_histogram,i+Hshift*HISTSIZE,HISTSIZE)*scale;

    memcpy(hist+HISTSIZE,hist,sizeof(*hist)*(1<<HRGBSHIFT));
    memcpy(plugin->hue_histogram,hist,sizeof(hist));

    red[HISTSIZE>>HRGBSHIFT]=red[0];
    green[HISTSIZE>>HRGBSHIFT]=green[0];
    blue[HISTSIZE>>HRGBSHIFT]=blue[0];

    for(i=0,j=0; i<=(HISTSIZE>>HRGBSHIFT); i++){
      float sum=0.f,H,S,V;
      for(int k=0; k<(1<<HRGBSHIFT); j++,k++)
        sum+=hist[j];

      RGB_to_HSpV(red[i],green[i],blue[i],H,S,V);
      H+=Hal;
      if(H<0)H+=6;
      if(H>=6)H-=6;
      HSpV_to_RGB(H,S,V,red[i],green[i],blue[i]);

      plugin->hue_histogram_red[i] = red[i] * sum;
      plugin->hue_histogram_green[i] = green[i] * sum;
      plugin->hue_histogram_blue[i] =  blue[i] * sum;
    }
  }

  if(selection_workA){
    delete[] selection_workA;
    selection_workA=0;
  }
  if(selection_workB){
    delete[] selection_workB;
    selection_workB=0;
  }
}

/* create a coordinated work-division list and join point */
void BluebananaEngine::set_task(int n, const char *task){
  pthread_mutex_lock(&this->tasklock);
  if(task_init_state==0){
    //fprintf(stderr,"New task!: %s",task);
    task_n=n;
    task_finish_count=get_total_packages();
    task_init_state=1;
    task_init_serial++;
  }
  pthread_mutex_unlock(&this->tasklock);
}

/* fetch the next task ticket.  If the task is complete, wait here for
   all package threads to complete before returning <0 */
int BluebananaEngine::next_task(){
  pthread_mutex_lock(&tasklock);
  if(task_n){
    int ret = --task_n;
    //fprintf(stderr,".");
    pthread_mutex_unlock(&tasklock);
    return ret;
  }else{
    task_finish_count--;
    if(task_finish_count==0){
      task_init_state=0;
      //fprintf(stderr,"done\n");
      pthread_cond_broadcast(&taskcond);
    }else{
      int serial = task_init_serial;
      while(task_finish_count && serial == task_init_serial){
        //fprintf(stderr,"+");
        pthread_cond_wait(&taskcond,&tasklock);
      }
    }
    pthread_mutex_unlock(&tasklock);
    //fprintf(stderr,"-");
    return -1;
  }
}

/* same as above, but waits for join without fetching a task slot */
void BluebananaEngine::wait_task(){
  pthread_mutex_lock(&tasklock);
  task_finish_count--;
  if(task_finish_count==0){
    task_init_state=0;
    //fprintf(stderr,"done\n");
    pthread_cond_broadcast(&taskcond);
  }else{
    int serial = task_init_serial;
    while(task_finish_count && serial == task_init_serial){
      //fprintf(stderr,"+");
      pthread_cond_wait(&taskcond,&tasklock);
    }
  }
  pthread_mutex_unlock(&tasklock);
  //fprintf(stderr,"-");
}
