/*
 * GIMP CVS: "|Id: sharpen.c,v 1.23.2.1 2001/01/12 13:15:10 neo Exp |"
 *
 *   Sharpen filters for The GIMP -- an image manipulation program
 *
 *   Copyright 1997-1998 Michael Sweet (mike@easysw.com)
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 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., 51 Franklin Street, Fifth Floor,
 *   Boston, MA  02110-1301  USA
 *
 * Contents:
 *
 *   sharpen()                 - Sharpen an image using a median filter.
 *   rgb_filter()              - Sharpen RGB pixels.
 *
 * Revision History:
 *
 *   See GIMP ChangeLog
 */

#include <stdlib.h>
#include <string.h>

#include "sharpen.h"

#define new(ctype, ccount)   calloc(ccount, sizeof(ctype))

static void
compute_luts (int sharpen_percent, int *pos_lut, int *neg_lut )
{
  int i;	/* Looping var */
  int fact;	/* 1 - sharpness */

  fact = 100 - sharpen_percent;
  if (fact < 1)
    fact = 1;

  for (i = 0; i < 256; i ++)
    {
      pos_lut[i] = (800 * i) / fact;
      neg_lut[i] = (4 + pos_lut[i] - (i << 3)) >> 3;
    };
}

/** 'rgb_filter()' - Sharpen RGB pixels.  **/

static void rgb_filter (int    width,	/* I - Width of line in pixels */
		 int    *pos_lut,
		 int    *neg_lut,
 	         unsigned char *src,	/* I - Source line */
	         unsigned char *dst,	/* O - Destination line */
		long int *neg0,	/* I - Top negative coefficient line */
		long int *neg1,	/* I - Middle negative coefficient line */
		long int *neg2)	/* I - Bottom negative coefficient line */
{
  long int pixel;		/* New pixel value */

  *dst++ = *src++;
  *dst++ = *src++;
  *dst++ = *src++;
  width -= 2;

  while (width > 0)
    {
      pixel = (pos_lut[*src++] - neg0[-3] - neg0[0] - neg0[3] -
	       neg1[-3] - neg1[3] -
	       neg2[-3] - neg2[0] - neg2[3]);
      pixel = (pixel + 4) >> 3;
      if (pixel < 0)
	*dst++ = 0;
      else if (pixel < 255)
	*dst++ = pixel;
      else
	*dst++ = 255;

      pixel = (pos_lut[*src++] - neg0[-2] - neg0[1] - neg0[4] -
	       neg1[-2] - neg1[4] -
	       neg2[-2] - neg2[1] - neg2[4]);
      pixel = (pixel + 4) >> 3;
      if (pixel < 0)
	*dst++ = 0;
      else if (pixel < 255)
	*dst++ = pixel;
      else
	*dst++ = 255;

      pixel = (pos_lut[*src++] - neg0[-1] - neg0[2] - neg0[5] -
	       neg1[-1] - neg1[5] -
	       neg2[-1] - neg2[2] - neg2[5]);
      pixel = (pixel + 4) >> 3;
      if (pixel < 0)
	*dst++ = 0;
      else if (pixel < 255)
	*dst++ = pixel;
      else
	*dst++ = 255;

      neg0 += 3;
      neg1 += 3;
      neg2 += 3;
      width --;
    };

  *dst++ = *src++;
  *dst++ = *src++;
  *dst++ = *src++;
}

/** 'sharpen()' - Sharpen an image using a convolution filter. **/
void  sharpen(int width, int height,
	unsigned char *src_region, unsigned char *dest_region, 
	int sharpen_percent
) {
    unsigned char   *src_rows[4],	/* Source pixel rows */
		    *src_ptr,		/* Current source pixel */
		    *dst_row;		/* Destination pixel row */
    long int	*neg_rows[4],	/* Negative coefficient rows */
		*neg_ptr;	/* Current negative coefficient */
    int		i,		/* Looping vars */
		y,		/* Current location in image */
		row,		/* Current row in src_rows */
		count,		/* Current number of filled src_rows */
		pitch;		/* Byte width of the image */
    int		img_bpp=3;		/* Bytes-per-pixel in image */
    int		neg_lut[256];		/* Negative coefficient LUT */
    int		pos_lut[256];		/* Positive coefficient LUT */

    compute_luts (sharpen_percent,pos_lut,neg_lut);
    pitch = width * img_bpp;

    for (row = 0; row < 4; row ++)
    {
    	    src_rows[row] = new (unsigned char, pitch);
	    neg_rows[row] = new (long int, pitch);
    }

    dst_row = new (unsigned char, pitch);

    /** Pre-load the first row for the filter...  **/
    memcpy(src_rows[0], src_region, pitch);

    for (i = pitch, src_ptr = src_rows[0], neg_ptr = neg_rows[0];
	 i > 0;   i --, src_ptr ++, neg_ptr ++)
	    *neg_ptr = neg_lut[*src_ptr];

    row   = 1;
    count = 1;
  
    /** Sharpen...  **/

    for (y = 0; y < height; y ++)
    {
	/** Load the next pixel row... **/
	if ((y + 1) < height)
	{
	    /** Check to see if our src_rows[] array is overflowing yet... **/
	    if (count >= 3)
		count --;

	    /** Grab the next row... **/
	    memcpy(src_rows[row], (src_region + pitch*(y+1)), pitch);

	    for (i = pitch, src_ptr = src_rows[row], neg_ptr = neg_rows[row];
		 i > 0;  i --, src_ptr ++, neg_ptr ++)
		    *neg_ptr = neg_lut[*src_ptr];
	    count ++;
	    row = (row + 1) & 3;
	}
	else
	{
	  /** No more pixels at the bottom...  Drop the oldest samples... **/
	  count --;
	}

	/** Now sharpen pixels and save the results... **/

	if (count == 3)
	{
	     rgb_filter(width, pos_lut, neg_lut,
		    src_rows[(row + 2) & 3], dst_row,
		    neg_rows[(row + 1) & 3] + img_bpp,
		    neg_rows[(row + 2) & 3] + img_bpp,
		    neg_rows[(row + 3) & 3] + img_bpp);

	    /** Set the row...  **/
	    memcpy((dest_region + pitch*y), dst_row, pitch);
	}
	else if (count == 2)
	{
	    if (y == 0)	/* first row */
		memcpy((dest_region), src_rows[0], pitch);
	    else                  /* last row  */
		memcpy((dest_region + pitch*(y)), src_rows[(height-1)&3], pitch);
	}
    } /* for */
    
    /** OK, we're done.  Free all memory used...  **/
    for (row = 0; row < 4; row ++)
    {
	free (src_rows[row]);
	free (neg_rows[row]);
    }
    free (dst_row);
}
