Blob Blame History Raw
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */

#include "gd.h"
#include "gd_errors.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])

/**
 * Function: gdImageStringFTCircle
 *
 * Draw text curved along the top and bottom of a circular area of an image.
 *
 * Parameters:
 *  im          - The image to draw onto.
 *  cx          - The x-coordinate of the center of the circular area.
 *  cy          - The y-coordinate of the center of the circular area.
 *  radius      - The radius of the circular area.
 *  textRadius  - The height of each character; if textRadius is 1/2 of radius,
 *	              characters extend halfway from the edge to the center.
 *  fillPortion - The percentage of the 180 degrees of the circular area
 *                assigned to each section of text, that is actually occupied
 *                by text. The value has to be in range 0.0 to 1.0, with useful
 *                values from about 0.4 to 0.9; 0.9 looks better than 1.0 which
 *                is rather crowded.
 *  font        - The fontlist that is passed to <gdImageStringFT>.
 *  points      - The point size, which functions as a hint. Although the size
 *                of the text is determined by radius, textRadius and
 *                fillPortion, a point size that 'hints' appropriately should be
 *                passed. If it's known that the text will be large, a large
 *                point size such as 24.0 should be passed to get the best
 *                results.
 *  top         - The text to draw clockwise at the top of the circular area.
 *  bottom      - The text to draw counterclockwise at the bottom of the
 *                circular area.
 *  fgcolor     - The font color.
 *
 * Returns:
 *  NULL on success, or an error string on failure.
 */
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 */

	gdImageDestroy (im1);

	/* Ready to produce a circle */
	im3 = gdImageSquareToCircle (im2, radius);
	if (im3 == NULL) {
		gdImageDestroy(im2);
		return 0;
	}
	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;
	}
	gdImageStringFTCircle (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) {
		gd_error("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

/**
 * Function: gdImageSquareToCircle
 *
 * Apply polar coordinate transformation to an image.
 *
 * The X axis of the original will be remapped to theta (angle) and the Y axis
 * of the original will be remapped to rho (distance from center).
 *
 * Parameters:
 *  im     - The image, which must be square, i.e. width == height.
 *  radius - The radius of the new image, i.e. width == height == radius * 2.
 *
 * Returns:
 *  The transformed image, or NULL on failure.
 */
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);
	if (!im2) {
		return 0;
	}
	/* 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);
}

/**
 * Function: gdImageSharpen
 *
 * Sharpen an image.
 *
 * Uses a simple 3x3 convolution kernel and makes use of separability.
 * It's faster, but less flexible, than full-blown unsharp masking.
 * Silently does nothing to non-truecolor images and for pct<0, as it's not a useful blurring function.
 *
 * Parameters:
 *  pct - The sharpening percentage, which can be greater than 100.
 *
 * Author:
 *  Paul Troughton (paul<dot>troughton<at>ieee<dot>org)
 */
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));
		}
	}
}