/* Annular denoise, GIMP 2.X plug-in

   (C) Luis A. Florit, 2007

     ==== FILTER MANUAL ====  {{{1

    This filter applies noise reduction to an image (layer, selection, etc)
trying to eliminate, instead of smoothing, the obvious small noise. The filter
works on each channel independently. It can be used also before other noise
reductions filters for which luminance noise have undesired strong impact, like
Greycstoration or dcamnoise. As with any noise reduction filter, it is
recommended to mask fine detail affected by the filter.

    To deal with chroma noise, you can mix the annular denoise filter
with usual a-b channel blurring techniques (a la NoiseNinja):

1) Decompose in Lab mode
2) Apply annular denoise only to the L channel
3) Selectively blur a and b channels
4) Recompose in RGB


THE ANNULUS AND THE ANNULAR PARAMETERS  {{{2

    For each pixel p in the image, the filter computes the 'min' and 'max'
values of all pixels in the (square) annular region A(p) centered at p of
radius between 'Inner Radius' and 'Outer Radius' (this is where the name of the
filter came from). There are 3 ways the filter can process the annular region:

-   Solid: Takes the whole annulus in a run. This is what you would expect.
            You will get too weak results if Outer Radius is not small.
-   Onion: It computes a minmax (or maxmin) using each annuli of thickness one
            with radii between Inner Radius and Outer Radius. Much stronger
            than Solid, but not as strong as Iterate. Expect the best results
            with this method.
- Iterate: It runs the filter independently for each annuli of thickness one
             with radii between Inner Radius and Outer Radius.

    You have to choose the way the filter processes the annulus according with
the type of noise, and prior to tweaking the thresholds.


THE THRESHOLDS  {{{2

    The thresholds are the parameters used to decide when a pixel is considered
noise by the filter. For each channel, there are two thresholds, 'MIN' and
'MAX'. There is also a global 'Selectivity Threshold'.

    The idea is that, for each channel, a pixel p will be considered noise if
its value is out of the range of the values in the annulus A(p), but not if it
is very different or if there is strong variation of values in the annulus,
in which case it will be considered detail.

    More specifically, if the value of p belongs to the interval [max+MIN,
max+MAX] (respectively [min-MAX,min-MIN]), its value is changed to max+MIN
(respectively min-MIN). Otherwise, it remains unchanged. However, the value
of p is not changed if MAX-MIN is bigger than the Selectivity Threshold, to
protect regions with detail. Hence, set 'Selectivity Threshold = 255' to
disable this feature.

    The MAX Thresholds are essential to protect regions with fine detail.
In general, for RGB images the green channel is cleaner than the other two,
so the MAX Threshold for G will be, in general, the lowest.

    You choose the MAX Thresholds by panning in the preview window through
smooth regions with no detail (be sure the Preview box is checked), and looking
at the MAX Thresholds that appear in the progress bar in the bottom of the main
GIMP window. Choose the maximum values you see there. You can always select a
region with no detail prior to calling the filter, and then the preview window
will only show that region and its MAX Thresholds. NOTE: if you use the Iterate
method, set first in Onion to choose the MAX Thresholds and in Iterate for run.

    Choose nonzero MIN Thresholds if you get look that is too flat.


OTHER OPTIONS  {{{2

- If 'Exclude same row & col' is checked, the row and column where p lies is
    excluded from A(p). This is more useful for RAW files, or files which have
    horizontal or vertical fine noise structures (like JPEG artifacts).
    Use with care, specially if Inner Radius = 1.

- Include the borders of the image in the annulus, even if the annulus sits
    partially outside it. Set this if you get artifacts near the borders.

- To apply the filter weaker on highlights, check the corresponding box, and
    raise slightly the MAX and Selectivity Thresholds.

}}}1

 INSTALL  {{{1

     Just execute in a console:

             gimptool --install annulardenoise.c

    This will install the plugin under the Filters menu, and the compiled
    binary in the ~/.gimp-n.n/plug-ins/ directory


 BUGS  {{{1

  - First/last bvals.maxradius rows and columns are blurred with a box that is
    resized to enter in the drawable. In particular, the first and last are
    untouched when "Exclude same row & col" is unchecked, because the bounds of
    the box include the row or column where the boundary points are, and then
    the point is considered in the min/max. The correction of this implies in
    making the plug-in considerably slower.

  - Not really a bug: In the last maxradius lines, the filter does not take
    into account the sides, and could make a mess. Be sure "Exclude same row & col"
    is unchecked if this happens.

  - Iterative method not showing results in the Preview window. The MAX Thresholds
    that appear in the progress bar are also the Solid ones.


 TODO  {{{1
  - Mean instead of min-max

  - Zoom

  - COLOR NOISE

  + Hue/Saturation on Yellow channel: Hue = +10, Lightness = + 3, Saturation = -5
  But it should be selective...

  + a and b in LAB blurring

  + VARIATIONS OF "COLOR BLEND BLURRING"
  a)
    1) Duplicate layer. On this new layer do:
    2) Selective gaussian blur (enough only to blur the color noise)
    3) Blend to COLOR (!!!) and control the Opacity
    4) Merge both layers

  b)
    Blur the image, then immediately Edit > Fade Blur and set the mode to Color.

  c)
      1. Filter > Enhance > Despeckle (try with a radius of 10)
      2. Edit > Fade Despeckle > Color

  d)
     > Look what the median blur layer set to color mode does to small
     > areas of colour like an iris.

     Really :-) I think 'luminosity blend' is better. Or just take an image run CS2's grain
     texture or CS's film grain, which imparts color 'noise/grain' and then fade in luminosity.
     Color noise all gone. However, not that easy on an image that already has it. ;-)

}}}1*/

////////////////////////////////////////////////////////

// Includes  {{{1
#include <string.h>
#include <libgimp/gimp.h>
#include <libgimp/gimpui.h>

#define SCALE_WIDTH        70
#define SPIN_BUTTON_WIDTH  54
#define PKG_CONFIG_PATH /opt/gimp
// Declarations  {{{1
typedef struct
{
  gint     maxradius;
  gint     minradius;
  gboolean wborders;
  gboolean horver;
  gint     onion;
  gint     flip;
  gint     selective;
  gboolean nohigh;
  gboolean preview;
  gint     mint[3];
  gint     maxt[3];
  gboolean indepthres;
} annulardenoiseVals;

typedef struct
{
  gint       channels;
  GtkObject *channel_adj[4];
} NoisifyInterface;

static NoisifyInterface minthresholds_int =
{
  0,
  { NULL, NULL, NULL, NULL }
};
static NoisifyInterface maxthresholds_int =
{
  0,
  { NULL, NULL, NULL, NULL }
};

static void query       (void);
static void run         (const gchar      *name,
                         gint              nparams,
                         const GimpParam  *param,
                         gint             *nreturn_vals,
                         GimpParam       **return_vals);

static void denoise      (GimpDrawable     *drawable,
                         GimpPreview      *preview);
                         //GimpZoomPreview      *preview);

static void init_mem    (guchar         ***row,
                         guchar          **outrow,
                         gint              num_bytes);
static void process_row (GimpDrawable     *drawable,
                         guchar          **row,
                         guchar           *outrow,
                         gint              x1,
                         gint              y1,
                         gint              width,
                         gint              height,
                         gint              channels,
                         gint              i);
static void shuffle     (GimpPixelRgn     *rgn_in,
                         guchar          **row,
                         gint              x1,
                         gint              y1,
                         gint              width,
                         gint              height,
                         gint              totheight,
                         gint              ypos);

static gboolean denoise_dialog (GimpDrawable *drawable);

static void minthresholds_int_adjustment_update (GtkAdjustment *adjustment, gpointer data);
static void maxthresholds_int_adjustment_update (GtkAdjustment *adjustment, gpointer data);

/* Set up default values for options */
static annulardenoiseVals bvals =
{
  3,          // maxradius
  2,          // minradius
  1,          // Include borders
  0,          // Exclude same row and column
  1,          // onion: minmax of each radius
 -1,          // flip: add (raise), substract (sunken), or do nothing (flat) with MIN threshold
 30,          // selectivity threshold
  0,          // weaker in highlights?
  1,          // preview
  {1,1,1},    // RGB min Threshold
  {15,15,15}, // RGB max Threshold
  0,          // Unify RGB Threshold
};

GimpPlugInInfo PLUG_IN_INFO =
{
  NULL,
  NULL,
  query,
  run
};

//}}}1

MAIN()
gint         profile[4]={0,0,0,0};

// query function  {{{1
static void
query (void)
{
  static GimpParamDef args[] =
  {
    {
      GIMP_PDB_INT32,
      "run-mode",
      "Run mode"
    },
    {
      GIMP_PDB_IMAGE,
      "image",
      "Input image"
    },
    {
      GIMP_PDB_DRAWABLE,
      "drawable",
      "Input drawable"
    }
  };

  gimp_install_procedure (
    "plug-in-annulardenoise",
    "Annular denoise",
    "Annular denoises an image/layer/selection.",
    "Luis A. Florit",
    "Copyright Luis A. Florit",
    "2007",
    "_Annular denoise...",
    "RGB*, GRAY*",
    GIMP_PLUGIN,
    G_N_ELEMENTS (args), 0,
    args, NULL);

  gimp_plugin_menu_register ("plug-in-annulardenoise",
                             "<Image>/Script-Fu");
}

// run function  {{{1
static void
run (const gchar      *name,
     gint              nparams,
     const GimpParam  *param,
     gint             *nreturn_vals,
     GimpParam       **return_vals)
{
  static GimpParam  values[1];
  GimpPDBStatusType status = GIMP_PDB_SUCCESS;
  GimpRunMode       run_mode;
  GimpDrawable     *drawable;

  /* Setting mandatory output values */
  *nreturn_vals = 1;
  *return_vals  = values;

  values[0].type = GIMP_PDB_STATUS;
  values[0].data.d_status = status;

  /* Getting run_mode - we won't display a dialog if
   * we are in NONINTERACTIVE mode */
  run_mode = param[0].data.d_int32;

  /*  Get the specified drawable  */
  drawable = gimp_drawable_get (param[2].data.d_drawable);

  switch (run_mode)
    {
    case GIMP_RUN_INTERACTIVE:
      /* Get options last values if needed */
      gimp_get_data ("plug-in-annulardenoise", &bvals);

      /* Display the dialog */
      if (! denoise_dialog (drawable))
        return;
      break;

    case GIMP_RUN_NONINTERACTIVE:
      if (nparams != 17)
        status = GIMP_PDB_CALLING_ERROR;
      if (status == GIMP_PDB_SUCCESS)
      {
        bvals.maxradius = param[3].data.d_int32;
        bvals.minradius = param[4].data.d_int32;
        bvals.wborders  = param[5].data.d_int32;
        bvals.horver    = param[6].data.d_int32;
        bvals.onion     = param[7].data.d_int32;
        bvals.selective = param[8].data.d_int32;
        bvals.nohigh    = param[9].data.d_int32;
        bvals.preview   = 0;
        bvals.mint[0]   = param[11].data.d_int32;
        bvals.mint[1]   = param[12].data.d_int32;
        bvals.mint[2]   = param[13].data.d_int32;
        bvals.maxt[0]   = param[14].data.d_int32;
        bvals.maxt[1]   = param[15].data.d_int32;
        bvals.maxt[2]   = param[16].data.d_int32;
        bvals.indepthres  = param[17].data.d_int32;
      }
      break;

    case GIMP_RUN_WITH_LAST_VALS:
      /*  Get options last values if needed  */
      gimp_get_data ("plug-in-annulardenoise", &bvals);
      break;

    default:
      break;
    }

  // Iterativity (if here, it doesn't affect the preview)
  if ( bvals.onion == 2 && bvals.minradius < bvals.maxradius )
  {
      GimpParam *rreturn_vals;
      gint rad, nnreturn_vals;
      for (rad = bvals.minradius ; rad <= bvals.maxradius ; rad++)
      {
          rreturn_vals = gimp_run_procedure("plug-in-annulardenoise", &nnreturn_vals,
                  GIMP_PDB_INT32, GIMP_RUN_NONINTERACTIVE,
                  GIMP_PDB_IMAGE, param[1].data.d_image,
                  GIMP_PDB_DRAWABLE, param[2].data.d_drawable,
                  GIMP_PDB_INT32, rad,
                  GIMP_PDB_INT32, rad,
                  GIMP_PDB_INT32, bvals.wborders,
                  GIMP_PDB_INT32, bvals.horver,
                  GIMP_PDB_INT32, 0,
                  GIMP_PDB_INT32, bvals.selective,
                  GIMP_PDB_INT32, bvals.nohigh,
                  GIMP_PDB_INT32, 0,
                  GIMP_PDB_INT32, bvals.mint[0],
                  GIMP_PDB_INT32, bvals.mint[1],
                  GIMP_PDB_INT32, bvals.mint[2],
                  GIMP_PDB_INT32, bvals.maxt[0],
                  GIMP_PDB_INT32, bvals.maxt[1],
                  GIMP_PDB_INT32, bvals.maxt[2],
                  GIMP_PDB_INT32, bvals.indepthres,
                  GIMP_PDB_END);
      }
      gimp_destroy_params (rreturn_vals, nnreturn_vals);
      gimp_displays_flush ();
      gimp_drawable_detach (drawable);
      return;
  }

  denoise (drawable, NULL);

  gimp_displays_flush ();
  gimp_drawable_detach (drawable);

  /*  Finally, set options in the core  */
  if (run_mode == GIMP_RUN_INTERACTIVE)
    gimp_set_data ("plug-in-annulardenoise", &bvals, sizeof (annulardenoiseVals));

  return;
}

// denoise function  {{{1
static void
denoise (GimpDrawable *drawable,
        GimpPreview *preview)
        //GimpZoomPreview *preview)
{
  gint         i, ii, channels;
  gint         x1, y1, x2, y2;
  GimpPixelRgn rgn_in, rgn_out;
  guchar     **row;
  guchar      *outrow;
  gint         width, height;

  // Gets upper left and lower right coordinates, and layers number in the image
  if (preview)
  {
    //guchar *s;
    //s = gimp_zoom_preview_get_source (GIMP_ZOOM_PREVIEW (preview), &width, &height, &channels);
    gimp_preview_get_position (preview, &x1, &y1);
    gimp_preview_get_size (preview, &width, &height);
    x2 = x1 + width;
    y2 = y1 + height;
    //g_free (s);
  }
  else
  {
    gimp_progress_init ("Annular denoising...");
    gimp_drawable_mask_bounds (drawable->drawable_id, &x1, &y1, &x2, &y2);
    width = x2 - x1;
    height = y2 - y1;
  }

  //printf("%i \n",bvals.onion);
  //printf("%i %i\n",gimp_tile_width(), gimp_tile_height());
  //printf("%i-%i %i-%i %i-%i\n",bvals.mint[0],bvals.maxt[0],bvals.mint[1],bvals.maxt[1],bvals.mint[2],bvals.maxt[2]);

  // Get number of channels
  channels = gimp_drawable_bpp (drawable->drawable_id);

  /* Allocate a big enough tile cache */
  gimp_tile_cache_ntiles (2 * (drawable->width / gimp_tile_width () + 1));

  /* Initialises two PixelRgns, one to read original data,
   * and the other to write output data.
   * That second one will be merged at the end by the call
   * to gimp_drawable_merge_shadow() */
  gimp_pixel_rgn_init (&rgn_in,
                       drawable,
                       x1, y1,
                       width, height,
                       FALSE, FALSE);
  gimp_pixel_rgn_init (&rgn_out,
                       drawable,
                       x1, y1,
                       width, height,
                       preview == NULL, TRUE);

  /* Allocate memory for input and output tile rows */
  init_mem (&row, &outrow, width * channels);

  // Initial load of row matrix: it only matters the last bvals.maxradius+1
  for (ii = -bvals.maxradius; ii <= bvals.maxradius; ii++)
    {
      gimp_pixel_rgn_get_row (&rgn_in,
                              row[bvals.maxradius + ii],
                              x1, y1 + CLAMP (ii, 0, height - 1),
                              width);
    }

  profile[0] = profile[1] = profile[2] = profile[3] = 0;
  // For each row...
  for (i = 0; i < height ; i++)
    {
      // Compute the new row
      process_row (drawable,
                   row,
                   outrow,
                   x1, y1,
                   width, height,
                   channels,
                   i);

      // Put it into rgn_out
      gimp_pixel_rgn_set_row (&rgn_out,
                              outrow,
                              x1, y1 + i,
                              width);

      // Shift tile rows and insert the new one at the end
      shuffle (&rgn_in,
               row,
               //x1, y1,
               //MIA
               x1, y1+1,
               width, height,
               drawable->height,
               i);

      // Progress bar (gives error in gimp 2.3)
      if ( i % 10 == 0 && !preview ) gimp_progress_update ((gdouble) i / (gdouble) height);
    }

  // Clone last line
  // gimp_pixel_rgn_get_row (&rgn_in,  row[0], x1, height -1, width);
  // gimp_pixel_rgn_set_row (&rgn_out, row[0], x1, height -1, width);

  // We could also put that in a separate function but it's rather simple
  // 
    for (ii = 0; ii <= 2*bvals.maxradius; ii++)
      g_free (row[ii]);

  g_free (row);
  g_free (outrow);

  //  Update the modified region
  if (preview)
    {
        gchar *prt = g_new (gchar, 150);
        //gimp_progress_init ("Put the mouse over this window to check here MAX noise thresholds in the preview window.");
        gimp_drawable_preview_draw_region (GIMP_DRAWABLE_PREVIEW (preview), &rgn_out);
        if ( gimp_drawable_is_rgb (drawable->drawable_id) == 1 )
            sprintf(prt,"MAX Thresholds in preview window: R: %i, G: %i, B: %i",profile[0],profile[1],profile[2]);
        else
        {
            if ( channels == 1 ) sprintf(prt,"MAX Thresholds in preview window: ch.1: %i",profile[0]);
            if ( channels == 2 ) sprintf(prt,"MAX Thresholds in preview window: ch.1: %i, ch.2: %i",profile[0],profile[1]);
            if ( channels == 3 ) sprintf(prt,"MAX Thresholds in preview window: ch.1: %i, ch.2: %i, ch.3: %i",profile[0],profile[1],profile[2]);
            if ( channels == 4 ) sprintf(prt,"MAX Thresholds in preview window: ch.1: %i, ch.2: %i, ch.3: %i, ch.4: %i",profile[0],profile[1],profile[2],profile[3]);
        }

        gimp_progress_init (prt);
        gimp_progress_update (0.403);
        gimp_progress_update (0);
        g_free(prt);
    }
  else
    {
      gimp_progress_init (" ");
      gimp_drawable_flush (drawable);
      gimp_drawable_merge_shadow (drawable->drawable_id, TRUE);
      gimp_drawable_update (drawable->drawable_id, x1, y1, width, height);
    }
  //printf("Radius: %i \nNoise threshold: %i \nSame col/row: %i\nAnnular Denoise: %i\n" , bvals.maxradius , bvals.minradius , bvals.horver, bvals.wborders);
}

// init_mem function  {{{1
static void
init_mem (guchar ***row,
          guchar  **outrow,
          gint      num_bytes)
{
  gint i;

  /* Allocate enough memory for row and outrow */
  *row = g_new (guchar *, (2 * bvals.maxradius + 1));

  for (i = 0; i <= 2*bvals.maxradius; i++)
    (*row)[i] = g_new (guchar, num_bytes);

  *outrow = g_new (guchar, num_bytes);
}

// process_row function  {{{1
static void
process_row (GimpDrawable *drawable,
             guchar **row,
             guchar  *outrow,
             gint     x1,
             gint     y1,
             gint     width,
             gint     height,
             gint     channels,
             gint     i)
{
  gint j,s,minaux,maxaux,channelsvis;
  gint k, rad, jj, ii, left, right, bot, top, max, min, sl, sr;
  gint MINT, MAXT;

  // Copy the noiseless alpha channel
  // Be sure the last one is the alpha channel!! (TIFF)
  if ( gimp_drawable_has_alpha (drawable->drawable_id) )
     {
         for (j = 1; j <= width; j++)
            outrow[channels * j - 1] = row[bvals.maxradius][channels * j - 1];

         channelsvis = channels-1;
     }
  else
      channelsvis = channels;

  // ...and for each column...
  for (j = 0; j < width; j++)
  {
      // For each channel..., (R=0,G=1,B=2,alpha=3 excluded)
      for (k = 0; k < channelsvis; k++)
      {
          // ...compute the min/max of the box/annulus...
          max=minaux=0;
          min=maxaux=255;
          // ...by looping from 1 to bvals.maxradius if non-annular, or a
          // single loop from bvals.maxradius to bvals.maxradius if annular
          for (rad = bvals.minradius ; rad <= bvals.maxradius ; rad++)
          {
              left  = j - MIN (rad,j);                              // >= 0
              right = j + MIN (rad, width - 1 - j);                 // <= width - 1
              top = bvals.maxradius - MIN (rad, i);                 // >= bvals.maxradius - i
              bot = bvals.maxradius + MIN (rad, height - 1 - i);    // <= bvals.maxradius - i + (height - 1)
              // top and bottom
              for (jj = left; jj <= right; jj++)
              {
                  if ( bvals.horver && jj == j ) continue;
                  s = channels * jj + k;
                  if ( bvals.wborders || i >= rad )                // top
                  {
                    min = MIN (row[top][s],min);
                    max = MAX (row[top][s],max);
                  }
                  if ( bvals.wborders || i <= height - 1 - rad )   // bottom
                  {
                    min = MIN (row[bot][s],min);
                    max = MAX (row[bot][s],max);
                  }
              }
              // left and right
              sl = channels * left + k;
              sr = channels * right + k;
              // The 4 corners are counted twice to ensure the first/last line
              // influence. Replace with top+1 and bot-1 if you don't like/need this.
              for (ii = top ; ii <= bot ; ii++)
              {
                  if ( bvals.horver && ii == bvals.maxradius ) continue;
                  if ( bvals.wborders || j >= rad )                // left
                  {
                    min = MIN (row[ii][sl],min);
                    max = MAX (row[ii][sl],max);
                  }
                  if ( bvals.wborders || j <= width - 1 - rad )    // right
                  {
                    min = MIN (row[ii][sr],min);
                    max = MAX (row[ii][sr],max);
                  }
              }
              if (bvals.onion == 1)
              {
                  if (min > minaux) minaux=min;
                  if (max < maxaux) maxaux=max;
                  max=0;
                  min=255;
              }
          }

          if (bvals.onion == 1)
          {
              min=minaux;
              max=maxaux;
          }

          // Set new value  {{{2
          // Change the pixel value according with min-max of the annulus:
          s = channels * j + k;
          outrow[s] = row[bvals.maxradius][s];
          profile[k] = MAX (profile[k], row[bvals.maxradius][s] - max);
          profile[k] = MAX (profile[k], min - row[bvals.maxradius][s]);
          // WARNING!!!!!!!!!!!!!!
          // onion mode may give max < min, since the biggest of the min may be bigger than
          // the smallest of the max, since they refer to different radii inside the annulus.
          // And sometimes this introduces color noise.
          // That's why I used 'min > max' instead of 'min - max == 255': conservative.
          if ( (min > max ) || (max - min > bvals.selective) ) continue ;

          /* This is the (linearly interpolated... should be logarithmic??) rule
           * when bvals.nohigh = 1 (otherwise, MINT=bvals.mint, MAXT=bvals.maxt):

                               |------|----------|
                               | MINT |   MAXT   |
                   |-----------|------|----------|
                   | value=0   |  0   |   maxt   |
                   | value=255 | mint | mint - 1 |
                   |-----------|------|----------|

          */
          MINT = bvals.mint[k] + bvals.nohigh * ( (int)(( row[bvals.maxradius][s] - 255 ) * bvals.mint[k] / 255) );
          MAXT = bvals.maxt[k] + bvals.nohigh * ( (int)( row[bvals.maxradius][s] * (bvals.mint[k]-bvals.maxt[k]-1) / 255) );

          // Actual algorithm
          if ( row[bvals.maxradius][s] >= max + MINT && row[bvals.maxradius][s] <= max + MAXT ) outrow[s] = MAX (0,  max + bvals.flip*MINT);
          if ( row[bvals.maxradius][s] <= min - MINT && row[bvals.maxradius][s] >= min - MAXT ) outrow[s] = MIN (255,min - bvals.flip*MINT);

          //}}}2

      }
  }
}

//shuffle function  {{{1
static void
shuffle (GimpPixelRgn *rgn_in,
         guchar      **row,
         gint          x1,
         gint          y1,
         gint          width,
         gint          height,
         gint          totheight,
         gint          i)
{
  /* MIA 2  {{{2
  gint ii;
  gint lastinthisbox = bvals.maxradius + MIN (bvals.maxradius, height - i - 1);
  //gint lastinnextbox = bvals.maxradius + MIN (bvals.maxradius, height - i - 2);

  //for (ii = 0; ii < lastinthisbox; ii++)
  for (ii = 0; ii < lastinthisbox; ii++)
    row[ii] = row[ii+1];

  //if ( lastinnextbox == 2*bvals.maxradius )
  if ( bvals.maxradius < height - i - 1 )
      gimp_pixel_rgn_get_row (rgn_in, row[2*bvals.maxradius], x1, y1 + i + 1 + bvals.maxradius, width);

  //printf("%s","\n");
  //for (ii = 0; ii <= lastinthisbox; ii++) printf("%i ",ii);
  }}}2*/
  //
  gint    ii,y1p;
  guchar *tmp_row;

  // Get tile row (i + radius + 1) into row[0]
  y1p = y1 + MIN (i + bvals.maxradius , height - 1);
  //gimp_pixel_rgn_get_row (rgn_in, row[0], x1, y1p, width);
  // These **3** lines exist to take care of a 'LibGimp-CRITICAL **: gimp_pixel_rgn_get_row: assertion' error message
  if ( y1p >= totheight )
      row[0] = 0;
  else
      gimp_pixel_rgn_get_row (rgn_in, row[0], x1, y1p, width);

  // Permute row[i] with row[i-1] and row[0] with row[2r]
  tmp_row = row[0];
  for (ii = 0; ii < 2 * bvals.maxradius; ii++)
    row[ii] = row[ii+1];
  //MIA: ESTO ESTA MAL!! Hay que calcular cual es la fila para cambiar,
  //y no la 2 * bvals.maxradius. Ademas, totheight no es necesaria!
  row[2 * bvals.maxradius] = tmp_row;
  

  /* MIA  {{{2
  // Permute row[i] with row[i-1] and row[0] with row[2r]
  for (i = 1; i <= 2 * bvals.maxradius; i++)
    row[i - 1] = row[i];

  // Get tile row (i + radius + 1) into row[0]
  gimp_pixel_rgn_get_row (rgn_in,
                          row[2 * bvals.maxradius],
                          x1, MIN (ypos + bvals.maxradius + y1, y1 + height - 1),
                          width);
  }}}2*/
}

/* My invalidate check
static gboolean
invalidate (GimpDrawable *drawable, GtkWidget *preview)
{
    gimp_preview_invalidate(preview);
}*/
//}}}1

// Dialog  {{{1
static gboolean
denoise_dialog (GimpDrawable *drawable)
{
  GtkWidget *dialog;
  GtkWidget *main_vbox;
  GtkWidget *main_hbox;
  GtkWidget *table;
  GtkWidget *preview;
  GtkWidget *frame;
  GtkWidget *label;
//  GtkWidget *radii;
  GtkWidget *auxs;
  GtkWidget *auxo;
  GtkWidget *auxi;
  GtkWidget *toggle;
  GtkWidget *spinbutton;
  GtkObject *adj;
  GtkObject *spinbutton_adj;
  gboolean   run;

  gint rr,chan;
  char *cha;
  gchar pepemin[15],pepemax[15];

  gimp_ui_init ("annulardenoise", FALSE);

  // get number of channels and check if RGB
  chan = gimp_drawable_bpp (drawable->drawable_id);
  if ( gimp_drawable_has_alpha (drawable->drawable_id) ) chan--;
  minthresholds_int.channels = chan;
  cha = "RGB";
  if ( gimp_drawable_is_rgb (drawable->drawable_id) == 0 ) cha = "12345";

  // Init  {{{2
  dialog = gimp_dialog_new ("Annular denoise", "annulardenoise",
                            NULL, 0,
                            gimp_standard_help_func, "plug-in-annulardenoise",

                            GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
                            GTK_STOCK_OK,     GTK_RESPONSE_OK,

                            NULL);

  // vbox  {{{2
  main_vbox = gtk_vbox_new (FALSE, 12);
  gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), main_vbox);
  gtk_widget_show (main_vbox);

  // Preview  {{{2
  preview = gimp_drawable_preview_new (drawable, &bvals.preview);
  gtk_box_pack_start (GTK_BOX (main_vbox), preview, TRUE, TRUE, 0);
  gtk_widget_show (preview);

  g_signal_connect_swapped (preview, "invalidated", G_CALLBACK (denoise), drawable);

  // Frame 2  {{{2
  frame = gtk_frame_new (NULL);
  gtk_widget_show (frame);
  gtk_box_pack_start (GTK_BOX (main_vbox), frame, TRUE, TRUE, 0);
  gtk_container_set_border_width (GTK_CONTAINER (frame), 6);

  label = gtk_label_new ("<b>Annular parameters</b>");
  gtk_widget_show (label);
  gtk_frame_set_label_widget (GTK_FRAME (frame), label);
  gtk_label_set_use_markup (GTK_LABEL (label), TRUE);

  main_hbox = gtk_hbox_new (FALSE, 0);
  gtk_widget_show (main_hbox);
  gtk_container_add (GTK_CONTAINER (frame), main_hbox);

  table = gtk_table_new (5, 6, FALSE);
  gtk_table_set_col_spacings (GTK_TABLE (table), 6);
  gtk_table_set_row_spacings (GTK_TABLE (table), 2);
  gtk_container_add (GTK_CONTAINER (main_hbox), table);
  gtk_container_set_border_width (GTK_CONTAINER (main_hbox), 10);
  gtk_widget_show (table);

  // Inner Radius  {{{2
  label = gtk_label_new_with_mnemonic ("Inner Radius:");
  gtk_table_attach (GTK_TABLE (table), label, 0,1,0,1, GTK_SHRINK, GTK_SHRINK, 0, 0);
  gtk_widget_show (label);

  spinbutton = gimp_spin_button_new (&spinbutton_adj, bvals.minradius, 1, 20, 1, 1, 1, 5, 0);
  gtk_table_attach (GTK_TABLE (table), spinbutton, 2,3,0,1, GTK_EXPAND ,GTK_EXPAND, 0, 0);
  gtk_widget_show (spinbutton);
  gimp_help_set_help_data(spinbutton,"Inner radius of the annulus.",NULL);

  g_signal_connect (spinbutton_adj, "value_changed", G_CALLBACK (gimp_int_adjustment_update), &bvals.minradius);
  g_signal_connect_swapped (spinbutton_adj, "value_changed", G_CALLBACK (gimp_preview_invalidate), preview);

  // Outer Radius  {{{2
  label = gtk_label_new_with_mnemonic ("Outer Radius:");
  gtk_table_attach (GTK_TABLE (table), label, 0,1,2,3, GTK_SHRINK, GTK_SHRINK, 0, 0);
  gtk_widget_show (label);

  spinbutton = gimp_spin_button_new (&spinbutton_adj, bvals.maxradius, 1, 20, 1, 1, 1, 5, 0);
  gtk_table_attach (GTK_TABLE (table), spinbutton, 2,3,2,3, GTK_EXPAND, GTK_EXPAND, 0, 0);
  gtk_widget_show (spinbutton);
  gimp_help_set_help_data(spinbutton,"Outer radius of the annulus.",NULL);

  g_signal_connect (spinbutton_adj, "value_changed", G_CALLBACK (gimp_int_adjustment_update), &bvals.maxradius);
  g_signal_connect_swapped (spinbutton_adj, "value_changed", G_CALLBACK (gimp_preview_invalidate), preview);

  /*
  // No funca  {{{3

  radii = gimp_coordinates_new (GIMP_UNIT_PIXEL, "%p", FALSE, FALSE, 50,
                               GIMP_SIZE_ENTRY_UPDATE_SIZE,

                               (bvals.minradius == bvals.maxradius),
                               FALSE,

                               "Inner Radius:", bvals.minradius, 72,
                               1, 20,
                               0, 0,

                               "Max radius:", bvals.maxradius, 72,
                               1, 20,
                               0, 0);

  gtk_box_pack_start (GTK_BOX (main_hbox), radii, FALSE, FALSE, 6);
  gtk_widget_show (radii);

  g_signal_connect_swapped (radii, "value-changed", G_CALLBACK (gimp_preview_invalidate), preview);
  g_signal_connect_swapped (radii, "refval-changed", G_CALLBACK (gimp_preview_invalidate), preview);
  g_signal_connect (preview, "invalidated", G_CALLBACK (gimp_int_adjustment_update), radii);
  //}}}3*/

  // Onionicity  {{{2
  toggle = gimp_int_radio_group_new (FALSE, NULL,
                                    G_CALLBACK (gimp_radio_button_update),
                                    &bvals.onion, bvals.onion,
                                    "Solid",   0, &auxs,
                                    "Onion",   1, &auxo,
                                    "Iterate", 2, &auxi,
                                    NULL);

  gtk_table_attach (GTK_TABLE (table), toggle, 4,5,0,3, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
  gtk_widget_show (toggle);

  //g_signal_connect (GTK_OBJECT (toggle), "pressed", G_CALLBACK (gimp_radio_button_update), &bvals.onion);
  g_signal_connect_swapped (auxs, "toggled", G_CALLBACK (gimp_preview_invalidate), preview);
  g_signal_connect_swapped (auxo, "toggled", G_CALLBACK (gimp_preview_invalidate), preview);

  gimp_help_set_help_data(auxo,"Perform a minmax (maxmin) among the radius between Inner Radius and Outer Radius, instead of an absolute min (max) in the solid annulus.\n  Therefore, this is much stronger than 'Solid', almost as strong as Iterate: mask detail smaller than Max radius.",NULL);
  gimp_help_set_help_data(auxi,"For each value between Inner Radius and Outer Radius,\nrun the plugin for an annulus of thickness one.\n(Does not update the preview)",NULL);

  /*  {{{3
  // Iterate
  toggle = gtk_check_button_new_with_label ("Iterate");
  gtk_table_attach (GTK_TABLE (table), toggle, 4,5,4,5, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
  gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (toggle), bvals.iterate);
  gtk_widget_show (toggle);

  g_signal_connect (GTK_OBJECT (toggle), "toggled", G_CALLBACK (gimp_toggle_button_update), &bvals.iterate);
  g_signal_connect_swapped (GTK_OBJECT (toggle), "toggled", G_CALLBACK (gimp_preview_invalidate), preview);

  // Onionicity
  toggle = gtk_check_button_new_with_label ("Onionicity");
  gtk_table_attach (GTK_TABLE (table), toggle, 0,1,4,5, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
  gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (toggle), bvals.onion);
  gtk_widget_show (toggle);

  g_signal_connect (GTK_OBJECT (toggle), "toggled", G_CALLBACK (gimp_toggle_button_update), &bvals.onion);
  g_signal_connect_swapped (GTK_OBJECT (toggle), "toggled", G_CALLBACK (gimp_preview_invalidate), preview);
  }}}3*/

  // Exclude same row/col  {{{2
  toggle = gtk_check_button_new_with_label ("Exclude same row & col");
  gtk_table_attach (GTK_TABLE (table), toggle, 0,4,4,5, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), bvals.horver);
  gtk_widget_show (toggle);
  gimp_help_set_help_data(toggle,"Exclude from the annular region the row and column to which the pixel belongs.\n  If set, the result is much more aggressive, specially for small radii. This should be unset if your image has vertical and/or horizontal detail with thickness smaller than Inner Radius.\n  Try enabling this for noisy RAW pictures. Use with care.",NULL);

  g_signal_connect (GTK_OBJECT (toggle), "toggled", G_CALLBACK (gimp_toggle_button_update), &bvals.horver);
  g_signal_connect_swapped (GTK_OBJECT (toggle), "toggled", G_CALLBACK (gimp_preview_invalidate), preview);

  // Include borders  {{{2
  toggle = gtk_check_button_new_with_label ("Include borders");
  gtk_table_attach (GTK_TABLE (table), toggle, 0,4,5,6, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
  gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (toggle), bvals.wborders);
  gtk_widget_show (toggle);
  gimp_help_set_help_data(toggle,"Include the borders of the drawable in the annulus, even if the annulus sits partially outside it.\n  Set this if you get artifacts near the borders.",NULL);

  g_signal_connect (GTK_OBJECT (toggle), "toggled", G_CALLBACK (gimp_toggle_button_update), &bvals.wborders);
  g_signal_connect_swapped (GTK_OBJECT (toggle), "toggled", G_CALLBACK (gimp_preview_invalidate), preview);

  //}}}2
  // Frame 1  {{{2
  frame = gtk_frame_new (NULL);
  gtk_widget_show (frame);
  gtk_box_pack_start (GTK_BOX (main_vbox), frame, TRUE, TRUE, 0);
  gtk_container_set_border_width (GTK_CONTAINER (frame), 6);

  label = gtk_label_new ("<b>Noise Thresholds per channel</b>");
  gtk_widget_show (label);
  gtk_frame_set_label_widget (GTK_FRAME (frame), label);
  gtk_label_set_use_markup (GTK_LABEL (label), TRUE);

  main_hbox = gtk_hbox_new (FALSE, 0);
  gtk_widget_show (main_hbox);
  gtk_container_add (GTK_CONTAINER (frame), main_hbox);

  table = gtk_table_new (chan + 1, 2*chan, FALSE);
  gtk_table_set_col_spacings (GTK_TABLE (table), 6);
  gtk_table_set_row_spacings (GTK_TABLE (table), 3);
  gtk_container_add (GTK_CONTAINER (main_hbox), table);
  gtk_container_set_border_width (GTK_CONTAINER (main_hbox), 15);
  gtk_widget_show (table);

  // MIN/MAX noise thresholds  {{{2

  // Weaker in highlights  {{{2
  toggle = gtk_check_button_new_with_label ("Weaker in highlights");
  //gtk_table_attach (GTK_TABLE (table), toggle, 1,3,4,5, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
  gtk_table_attach (GTK_TABLE (table), toggle, 3,7,0,1, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
  gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (toggle), bvals.nohigh);
  gtk_widget_show (toggle);
  gimp_help_set_help_data(toggle,"Stronger effect in shadows, weaker in highlights. This allows to raise a little bit MAX Thresholds.\n  Sometimes it can create some color pixels.",NULL);

  g_signal_connect (GTK_OBJECT (toggle), "toggled", G_CALLBACK (gimp_toggle_button_update), &bvals.nohigh);
  g_signal_connect_swapped (GTK_OBJECT (toggle), "toggled", G_CALLBACK (gimp_preview_invalidate), preview);

  // Independent Thresholds  {{{2
  toggle = gtk_check_button_new_with_label ("Independent Thres.");
  gtk_table_attach (GTK_TABLE (table), toggle, 3,7,2,3, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
  gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (toggle), bvals.indepthres);
  gtk_widget_show (toggle);
  gimp_help_set_help_data(toggle,"Check this to adjust each channel threshold independently.",NULL);

  g_signal_connect (GTK_OBJECT (toggle), "toggled", G_CALLBACK (gimp_toggle_button_update), &bvals.indepthres);
  g_signal_connect_swapped (GTK_OBJECT (toggle), "toggled", G_CALLBACK (gimp_preview_invalidate), preview);

  // Thresholds sliders
  label = gtk_label_new ("MIN");
  gtk_table_attach (GTK_TABLE (table), label, 0,3,3,4, GTK_SHRINK, GTK_SHRINK, 0, 0);
  gtk_widget_show (label);

  label = gtk_label_new ("MAX");
  gtk_table_attach (GTK_TABLE (table), label, 4,7,3,4, GTK_SHRINK, GTK_SHRINK, 0, 0);
  gtk_widget_show (label);

  for ( rr = 0 ; rr < chan ; rr++ )
  {
      sprintf(pepemin,"%c",cha[rr]);
      sprintf(pepemax,"%c",cha[rr]);

      adj = gimp_scale_entry_new (GTK_TABLE (table), 0, rr+4,
                      pepemin, SCALE_WIDTH, SPIN_BUTTON_WIDTH,
                      bvals.mint[rr], 0, 20, 1, 1, 0,
                      TRUE, 0, 0,
                      "The center of the annulus will be considered noise (in this channel) if the difference between its value and the values in the annulus is bigger than MIN Threshold, and less than MAX Threshold. Hence, the closer to 0 the MIN Thresholds are, the stronger the results.\n  These MIN controls are also provided to get less flat look.", NULL);

      //g_signal_connect (adj, "value_changed", G_CALLBACK (gimp_int_adjustment_update), &bvals.mint[rr]);
      g_signal_connect (adj, "value_changed", G_CALLBACK (minthresholds_int_adjustment_update), &bvals.mint[rr]);
      g_signal_connect_swapped (adj, "value_changed", G_CALLBACK (gimp_preview_invalidate), preview);

      minthresholds_int.channel_adj[rr] = adj;

      adj = gimp_scale_entry_new (GTK_TABLE (table), 4, rr+4,
                      pepemax, SCALE_WIDTH, SPIN_BUTTON_WIDTH,
                      //profile[rr], 0, 255, 1, 10, 0,
                      bvals.maxt[rr], 0, 120, 1, 5, 0,
                      TRUE, 0, 0,
                      "The center of the annulus will be considered noise (in this channel) if the difference between its value and the values in the annulus is bigger than MIN Threshold, and less than MAX Threshold. Hence, the closer to 255 MAX Thresholds are, the stronger the results.\n\n  HOW TO SET THESE VALUES: Pan in the preview window through different regions of LOW DETAIL, watching the corresponding profiled noise values in the progress bar of the main window, and choose the maximum values.", NULL);

      g_signal_connect (adj, "value_changed", G_CALLBACK (maxthresholds_int_adjustment_update), &bvals.maxt[rr]);
      g_signal_connect_swapped (adj, "value_changed", G_CALLBACK (gimp_preview_invalidate), preview);

      maxthresholds_int.channel_adj[rr] = adj;
  }

//  button = gtk_button_new ();
//  g_signal_connect (button, "clicked", G_CALLBACK (gtk_adjustment_set_value(*adjj[1],profile[1])), NULL);
//  gtk_table_attach (GTK_TABLE (table), button, 1, 4, 4, 5, GTK_FILL, GTK_FILL, 0, 0);
//  gtk_widget_show (button);

  // Flip MIN value  {{{2
  toggle = gimp_int_radio_group_new (FALSE, NULL,
                                    G_CALLBACK (gimp_radio_button_update),
                                    &bvals.flip, bvals.flip,
                                    "raised",  1, &auxs,
                                    "flat",    0, &auxo,
                                    "sunken", -1, &auxi,
                                    NULL);

  gtk_table_attach (GTK_TABLE (table), toggle, 1,2,0,3, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
  gtk_widget_show (toggle);

  //g_signal_connect (GTK_OBJECT (toggle), "pressed", G_CALLBACK (gimp_radio_button_update), &bvals.onion);
  g_signal_connect_swapped (auxs, "toggled", G_CALLBACK (gimp_preview_invalidate), preview);
  g_signal_connect_swapped (auxo, "toggled", G_CALLBACK (gimp_preview_invalidate), preview);
  g_signal_connect_swapped (auxi, "toggled", G_CALLBACK (gimp_preview_invalidate), preview);

  gimp_help_set_help_data(auxs,"When noise is found, MIN Threshold is added to the max value in the annulus (or substracted from the min). Hence, weaker output.",NULL);
  gimp_help_set_help_data(auxi,"When noise is found, MIN Threshold is substracted to the max value in the annulus (or added from the min). Hence, stronger output. Use small values for MIN, since otherwise noise will be added.",NULL);

  // Frame 2  {{{2
  table = gtk_table_new (2, 4, FALSE);
  gtk_table_set_col_spacings (GTK_TABLE (table), 6);
  gtk_table_set_row_spacings (GTK_TABLE (table), 3);
  gtk_container_add (GTK_CONTAINER (main_vbox), table);
  gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 15);
  gtk_widget_show (table);

  // Selectivity threshold  {{{2
  adj = gimp_scale_entry_new (GTK_TABLE (table), 1, 1,
                  "Selectivity threshold:", SCALE_WIDTH, SPIN_BUTTON_WIDTH,
                  bvals.selective, 0, 120, 1, 5, 0,
                  TRUE, 0, 0,
                  "Lower this value to preserve regions with fine detail.\n The center of an annulus will NOT be considered noise\n if the difference between the max values and min values\n in the annulus is greater than this Selectivity Threshold.\n Hence, set this between MAX and 2*MAX.", NULL);

  g_signal_connect (adj, "value_changed", G_CALLBACK (gimp_int_adjustment_update), &bvals.selective);
  g_signal_connect_swapped (adj, "value_changed", G_CALLBACK (gimp_preview_invalidate), preview);


  //Poner en funcion (ver /home/gato/nosync/aux/gimp-2.3.15/plug-ins/common/vpropagate.c)
  //gimp_toggle_button_update (widget, data);
  //gimp_preview_invalidate(GIMP_PREVIEW(preview));
  //gtk_widget_hide  ??

  denoise (drawable, GIMP_PREVIEW (preview));

  gimp_displays_flush ();

  gtk_widget_show (dialog);

  run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK);

  gtk_widget_destroy (dialog);

  return run;
}
//}}}1

//MIN Thresholds adjustments updates {{{1
static void
minthresholds_int_adjustment_update (GtkAdjustment *adjustment, gpointer data)
{
    GimpDrawable *drawable;
    gint n;

    gimp_int_adjustment_update (adjustment, data);
    drawable = g_object_get_data (G_OBJECT (adjustment), "drawable");

    if (! bvals.indepthres)
    {
      gint i;
      switch (minthresholds_int.channels)
        {
        case 1:  n = 1;  break;
        case 2:  n = 1;  break;
        case 3:  n = 3;  break;
        default: n = 3;  break;
        }

      for (i = 0; i < n; i++)
      {
        if (adjustment != GTK_ADJUSTMENT (minthresholds_int.channel_adj[i]))
          gtk_adjustment_set_value (GTK_ADJUSTMENT (minthresholds_int.channel_adj[i]), (int) adjustment->value);
      }
    }
}
//MAX Thresholds adjustments updates {{{1
static void
maxthresholds_int_adjustment_update (GtkAdjustment *adjustment, gpointer data)
{
    GimpDrawable *drawable;
    gint n;

    gimp_int_adjustment_update (adjustment, data);
    drawable = g_object_get_data (G_OBJECT (adjustment), "drawable");

    if (! bvals.indepthres)
    {
      gint i;
      switch (minthresholds_int.channels)
        {
        case 1:  n = 1;  break;
        case 2:  n = 1;  break;
        case 3:  n = 3;  break;
        default: n = 3;  break;
        }

      for (i = 0; i < n; i++)
      {
        if (adjustment != GTK_ADJUSTMENT (maxthresholds_int.channel_adj[i]))
          gtk_adjustment_set_value (GTK_ADJUSTMENT (maxthresholds_int.channel_adj[i]), (int) adjustment->value);
      }
    }
}
//}}}1

// Otros algoritmos {{{1
          // CLAMP  {{{2
          // clamp(val,minv,maxv): same as min(maxv,max(minv,val))
          //outrow[s] = CLAMP (row[bvals.maxradius][s], min - bvals.minradius, max + bvals.minradius) ;

          // promedio  {{{2
          //outrow[channels * j + k] = (int) (max + min)/2 ;

          // Promedio del min y el max: HORRIBLE (intentar con el promedio real?)  {{{2
          //if ( row[bvals.maxradius][s] < min - bvals.minradius || row[bvals.maxradius][s] > max + bvals.minradius )
          //    outrow[s] = (int) (max + min)/2 ;
          //else
          //    outrow[s] = row[bvals.maxradius][s];
          // }}}2
