#include "gd.h"
#include <math.h>

/* In tests this is sufficient to prevent obvious artifacts */
#define MAG 4

#define PI 3.141592
#define DEG2RAD(x) ((x)*PI/180.)

#define MAX(x,y) ((x) > (y) ? (x) : (y))
#define MIN(x,y) ((x) < (y) ? (x) : (y))

#define MAX4(x,y,z,w) \
	((MAX((x),(y))) > (MAX((z),(w))) ? (MAX((x),(y))) : (MAX((z),(w))))
#define MIN4(x,y,z,w) \
	((MIN((x),(y))) < (MIN((z),(w))) ? (MIN((x),(y))) : (MIN((z),(w))))

#define MAXX(x) MAX4(x[0],x[2],x[4],x[6])
#define MINX(x) MIN4(x[0],x[2],x[4],x[6])
#define MAXY(x) MAX4(x[1],x[3],x[5],x[7])
#define MINY(x) MIN4(x[1],x[3],x[5],x[7])

BGD_DECLARE(char *)
gdImageStringFTCircle (gdImagePtr im,
		       int cx,
		       int cy,
		       double radius,
		       double textRadius,
		       double fillPortion,
		       char *font,
		       double points, char *top, char *bottom, int fgcolor)
{
  char *err;
  int w;
  int brect[8];
  int sx1, sx2, sy1, sy2, sx, sy;
  int x, y;
  int fr, fg, fb, fa;
  int ox, oy;
  double prop;
  gdImagePtr im1;
  gdImagePtr im2;
  gdImagePtr im3;
  /* obtain brect so that we can size the image */
  err = gdImageStringFT ((gdImagePtr) NULL,
			 &brect[0], 0, font, points * MAG, 0, 0, 0, bottom);
  if (err)
    {
      return err;
    }
  sx1 = MAXX (brect) - MINX (brect) + 6;
  sy1 = MAXY (brect) - MINY (brect) + 6;
  err = gdImageStringFT ((gdImagePtr) NULL,
			 &brect[0], 0, font, points * MAG, 0, 0, 0, top);
  if (err)
    {
      return err;
    }
  sx2 = MAXX (brect) - MINX (brect) + 6;
  sy2 = MAXY (brect) - MINY (brect) + 6;
  /* Pad by 4 pixels to allow for slight errors
     observed in the bounding box returned by freetype */
  if (sx1 > sx2)
    {
      sx = sx1 * 2 + 4;
    }
  else
    {
      sx = sx2 * 2 + 4;
    }
  if (sy1 > sy2)
    {
      sy = sy1;
    }
  else
    {
      sy = sy2;
    }
  im1 = gdImageCreateTrueColor (sx, sy);
  if (!im1)
    {
      return "could not create first image";
    }
  err = gdImageStringFT (im1, 0, gdTrueColor (255, 255, 255),
			 font, points * MAG,
			 0, ((sx / 2) - sx1) / 2, points * MAG, bottom);
  if (err)
    {
      gdImageDestroy (im1);
      return err;
    }
  /* We don't know the descent, which would be needed to do this
     with the angle parameter. Instead, implement a simple
     flip operation ourselves. */
  err = gdImageStringFT (im1, 0, gdTrueColor (255, 255, 255),
			 font, points * MAG,
			 0, sx / 2 + ((sx / 2) - sx2) / 2, points * MAG, top);
  if (err)
    {
      gdImageDestroy (im1);
      return err;
    }
  /* Flip in place is tricky, be careful not to double-swap things */
  if (sy & 1)
    {
      for (y = 0; (y <= (sy / 2)); y++)
	{
	  int xlimit = sx - 2;
	  if (y == (sy / 2))
	    {
	      /* If there is a "middle" row, be careful
	         not to swap twice! */
	      xlimit -= (sx / 4);
	    }
	  for (x = (sx / 2) + 2; (x < xlimit); x++)
	    {
	      int t;
	      int ox = sx - x + (sx / 2) - 1;
	      int oy = sy - y - 1;
	      t = im1->tpixels[oy][ox];
	      im1->tpixels[oy][ox] = im1->tpixels[y][x];
	      im1->tpixels[y][x] = t;
	    }
	}
    }
  else
    {
      for (y = 0; (y < (sy / 2)); y++)
	{
	  int xlimit = sx - 2;
	  for (x = (sx / 2) + 2; (x < xlimit); x++)
	    {
	      int t;
	      int ox = sx - x + (sx / 2) - 1;
	      int oy = sy - y - 1;
	      t = im1->tpixels[oy][ox];
	      im1->tpixels[oy][ox] = im1->tpixels[y][x];
	      im1->tpixels[y][x] = t;
	    }
	}
    }
#if STEP_PNGS
  {
    FILE *out = fopen ("gdfx1.png", "wb");
    gdImagePng (im1, out);
    fclose (out);
  }
#endif /* STEP_PNGS */
  /* Resample taller; the exact proportions of the text depend on the
     ratio of textRadius to radius, and the value of fillPortion */
  if (sx > sy * 10)
    {
      w = sx;
    }
  else
    {
      w = sy * 10;
    }
  im2 = gdImageCreateTrueColor (w, w);
  if (!im2)
    {
      gdImageDestroy (im1);
      return "could not create resampled image";
    }
  prop = textRadius / radius;
  gdImageCopyResampled (im2, im1,
			gdImageSX (im2) * (1.0 - fillPortion) / 4,
			sy * 10 * (1.0 - prop),
			0, 0,
			gdImageSX (im2) * fillPortion / 2, sy * 10 * prop,
			gdImageSX (im1) / 2, gdImageSY (im1));
  gdImageCopyResampled (im2, im1,
			(gdImageSX (im2) / 2) +
			gdImageSX (im2) * (1.0 - fillPortion) / 4,
			sy * 10 * (1.0 - prop),
			gdImageSX (im1) / 2, 0,
			gdImageSX (im2) * fillPortion / 2, sy * 10 * prop,
			gdImageSX (im1) / 2, gdImageSY (im1));
#if STEP_PNGS
  {
    FILE *out = fopen ("gdfx2.png", "wb");
    gdImagePng (im2, out);
    fclose (out);
  }
#endif /* STEP_PNGS */
  /* Ready to produce a circle */
  im3 = gdImageSquareToCircle (im2, radius);
  gdImageDestroy (im1);
  gdImageDestroy (im2);
  /* Now blend im3 with the destination. Cheat a little. The
     source (im3) is white-on-black, so we can use the
     red component as a basis for alpha as long as we're
     careful to shift off the extra bit and invert
     (alpha ranges from 0 to 127 where 0 is OPAQUE). 
     Also be careful to allow for an alpha component
     in the fgcolor parameter itself (gug!) */
  fr = gdTrueColorGetRed (fgcolor);
  fg = gdTrueColorGetGreen (fgcolor);
  fb = gdTrueColorGetBlue (fgcolor);
  fa = gdTrueColorGetAlpha (fgcolor);
  ox = cx - (im3->sx / 2);
  oy = cy - (im3->sy / 2);
  for (y = 0; (y < im3->sy); y++)
    {
      for (x = 0; (x < im3->sx); x++)
	{
	  int a = gdTrueColorGetRed (im3->tpixels[y][x]) >> 1;
	  a *= (127 - fa);
	  a /= 127;
	  a = 127 - a;
	  gdImageSetPixel (im, x + ox, y + oy,
			   gdTrueColorAlpha (fr, fg, fb, a));
	}
    }
  gdImageDestroy (im3);
  return 0;
}

#if GDFX_MAIN

int
main (int argc, char *argv[])
{
  FILE *in;
  FILE *out;
  gdImagePtr im;
  int radius;
  /* Create an image of text on a circle, with an
     alpha channel so that we can copy it onto a
     background */
  in = fopen ("eleanor.jpg", "rb");
  if (!in)
    {
      im = gdImageCreateTrueColor (300, 300);
    }
  else
    {
      im = gdImageCreateFromJpeg (in);
      fclose (in);
    }
  if (gdImageSX (im) < gdImageSY (im))
    {
      radius = gdImageSX (im) / 2;
    }
  else
    {
      radius = gdImageSY (im) / 2;
    }
  gdStringFTCircle (im,
		    gdImageSX (im) / 2,
		    gdImageSY (im) / 2,
		    radius,
		    radius / 2,
		    0.8,
		    "arial",
		    24,
		    "top text",
		    "bottom text", gdTrueColorAlpha (240, 240, 255, 32));
  out = fopen ("gdfx.png", "wb");
  if (!out)
    {
      fprintf (stderr, "Can't create gdfx.png\n");
      return 1;
    }
  gdImagePng (im, out);
  fclose (out);
  gdImageDestroy (im);
  return 0;
}

#endif /* GDFX_MAIN */

/* Note: don't change these */
#define SUPER 2
#define SUPERBITS1 1
#define SUPERBITS2 2

BGD_DECLARE(gdImagePtr)
gdImageSquareToCircle (gdImagePtr im, int radius)
{
  int x, y;
  double c;
  gdImagePtr im2;
  if (im->sx != im->sy)
    {
      /* Source image must be square */
      return 0;
    }
  im2 = gdImageCreateTrueColor (radius * 2, radius * 2);
  /* Supersampling for a nicer result */
  c = (im2->sx / 2) * SUPER;
  for (y = 0; (y < im2->sy * SUPER); y++)
    {
      for (x = 0; (x < im2->sx * SUPER); x++)
	{
	  double rho = sqrt ((x - c) * (x - c) + (y - c) * (y - c));
	  int pix;
	  int cpix;
	  double theta;
	  double ox;
	  double oy;
	  int red, green, blue, alpha;
	  if (rho > c)
	    {
	      continue;
	    }
	  theta = atan2 (x - c, y - c) + PI / 2;
	  if (theta < 0)
	    {
	      theta += 2 * PI;
	    }
	  /* Undo supersampling */
	  oy = (rho * im->sx) / (im2->sx * SUPER / 2);
	  ox = theta * im->sx / (3.141592653 * 2);
	  pix = gdImageGetPixel (im, ox, oy);
	  cpix = im2->tpixels[y >> SUPERBITS1][x >> SUPERBITS1];
	  red =
	    (gdImageRed (im, pix) >> SUPERBITS2) + gdTrueColorGetRed (cpix);
	  green =
	    (gdImageGreen (im, pix) >> SUPERBITS2) +
	    gdTrueColorGetGreen (cpix);
	  blue =
	    (gdImageBlue (im, pix) >> SUPERBITS2) + gdTrueColorGetBlue (cpix);
	  alpha =
	    (gdImageAlpha (im, pix) >> SUPERBITS2) +
	    gdTrueColorGetAlpha (cpix);
	  im2->tpixels[y >> SUPERBITS1][x >> SUPERBITS1] =
	    gdTrueColorAlpha (red, green, blue, alpha);
	}
    }
  /* Restore full dynamic range, 0-63 yields 0-252. Replication of
     first 2 bits in last 2 bits has the desired effect. Note
     slightly different arithmetic for alpha which is 7-bit. 
     NOTE: only correct for SUPER == 2 */
  for (y = 0; (y < im2->sy); y++)
    {
      for (x = 0; (x < im2->sx); x++)
	{
	  /* Copy first 2 bits to last 2 bits, matching the
	     dynamic range of the original cheaply */
	  int cpix = im2->tpixels[y][x];

	  im2->tpixels[y][x] = gdTrueColorAlpha ((gdTrueColorGetRed (cpix) &
						  0xFC) +
						 ((gdTrueColorGetRed (cpix) &
						   0xC0) >> 6),
						 (gdTrueColorGetGreen (cpix) &
						  0xFC) +
						 ((gdTrueColorGetGreen (cpix)
						   & 0xC0) >> 6),
						 (gdTrueColorGetBlue (cpix) &
						  0xFC) +
						 ((gdTrueColorGetBlue (cpix) &
						   0xC0) >> 6),
						 (gdTrueColorGetAlpha (cpix) &
						  0x7C) +
						 ((gdTrueColorGetAlpha (cpix)
						   & 0x60) >> 6));
	}
    }
  return im2;
}

/* 2.0.16: Called by gdImageSharpen to avoid excessive code repetition
    Added on 2003-11-19 by
    Paul Troughton (paul<dot>troughton<at>ieee<dot>org)
    Given filter coefficents and colours of three adjacent pixels, 
returns new colour for centre pixel
*/

int
gdImageSubSharpen (int pc, int c, int nc, float inner_coeff, float
		   outer_coeff)
{
  float red, green, blue, alpha;

  red = inner_coeff * gdTrueColorGetRed (c) + outer_coeff *
    (gdTrueColorGetRed (pc) + gdTrueColorGetRed (nc));
  green = inner_coeff * gdTrueColorGetGreen (c) + outer_coeff *
    (gdTrueColorGetGreen (pc) + gdTrueColorGetGreen (nc));
  blue = inner_coeff * gdTrueColorGetBlue (c) + outer_coeff *
    (gdTrueColorGetBlue (pc) + gdTrueColorGetBlue (nc));
  alpha = gdTrueColorGetAlpha (c);

  /* Clamping, as can overshoot bounds in either direction */
  if (red > 255.0f)
    {
      red = 255.0f;
    }
  if (green > 255.0f)
    {
      green = 255.0f;
    }
  if (blue > 255.0f)
    {
      blue = 255.0f;
    }
  if (red < 0.0f)
    {
      red = 0.0f;
    }
  if (green < 0.0f)
    {
      green = 0.0f;
    }
  if (blue < 0.0f)
    {
      blue = 0.0f;
    }

  return gdTrueColorAlpha ((int) red, (int) green, (int) blue, (int) alpha);
}

/*
  * Sharpen function added on 2003-11-19
  * by Paul Troughton (paul<dot>troughton<at>ieee<dot>org)
  * Simple 3x3 convolution kernel
  * Makes use of seperability
  * Faster, but less flexible, than full-blown unsharp masking
  * pct is sharpening percentage, and can be greater than 100
  * Silently does nothing to non-truecolor images
  * Silently does nothing for pct<0, as not a useful blurring function
  * Leaves transparency/alpha-channel untouched
  */
BGD_DECLARE(void)
gdImageSharpen (gdImagePtr im, int pct)
{
  int x, y;
  int sx, sy;
  float inner_coeff, outer_coeff;

  sx = im->sx;
  sy = im->sy;

  /* Must sum to 1 to avoid overall change in brightness.
   * Scaling chosen so that pct=100 gives 1-D filter [-1 6 -1]/4,
   * resulting in a 2-D filter [1 -6 1; -6 36 -6; 1 -6 1]/16,
   * which gives noticeable, but not excessive, sharpening
   */

  outer_coeff = -pct / 400.0;
  inner_coeff = 1 - 2 * outer_coeff;

  /* Don't try to do anything with non-truecolor images, as 
     pointless,
     * nor for pct<=0, as small kernel size leads to nasty 
     artefacts when blurring
   */
  if ((im->trueColor) && (pct > 0))
    {

      /* First pass, 1-D convolution column-wise */
      for (x = 0; x < sx; x++)
	{

	  /* pc is colour of previous pixel; c of the 
	     current pixel and nc of the next */
	  int pc, c, nc;

	  /* Replicate edge pixel at image boundary */
	  pc = gdImageGetPixel (im, x, 0);

	  /* Stop looping before last pixel to avoid 
	     conditional within loop */
	  for (y = 0; y < sy - 1; y++)
	    {

	      c = gdImageGetPixel (im, x, y);

	      nc = gdImageGetTrueColorPixel (im, x, y + 1);

	      /* Update centre pixel to new colour */
	      gdImageSetPixel (im, x, y,
			       gdImageSubSharpen (pc, c, nc, inner_coeff,
						  outer_coeff));

	      /* Save original colour of current 
	         pixel for next time round */
	      pc = c;
	    }

	  /* Deal with last pixel, replicating current 
	     pixel at image boundary */
	  c = gdImageGetPixel (im, x, y);
	  gdImageSetPixel (im, x, y, gdImageSubSharpen
			   (pc, c, c, inner_coeff, outer_coeff));
	}

      /* Second pass, 1-D convolution row-wise */
      for (y = 0; y < sy; y++)
	{
	  int pc, c;
	  pc = gdImageGetPixel (im, 0, y);
	  for (x = 0; x < sx - 1; x++)
	    {
	      int c, nc;
	      c = gdImageGetPixel (im, x, y);
	      nc = gdImageGetTrueColorPixel (im, x + 1, y);
	      gdImageSetPixel (im, x, y,
			       gdImageSubSharpen (pc, c, nc, inner_coeff,
						  outer_coeff));
	      pc = c;
	    }
	  c = gdImageGetPixel (im, x, y);
	  gdImageSetPixel (im, x, y, gdImageSubSharpen
			   (pc, c, c, inner_coeff, outer_coeff));
	}
    }
}
