1 #ifdef HAVE_CONFIG_H
2 #include "config.h"
3 #endif /* HAVE_CONFIG_H */
4 
5 #include "gd.h"
6 #include "gd_errors.h"
7 #include <math.h>
8 
9 /* In tests this is sufficient to prevent obvious artifacts */
10 #define MAG 4
11 
12 #define PI 3.141592
13 #define DEG2RAD(x) ((x)*PI/180.)
14 
15 #define MAX(x,y) ((x) > (y) ? (x) : (y))
16 #define MIN(x,y) ((x) < (y) ? (x) : (y))
17 
18 #define MAX4(x,y,z,w) \
19 	((MAX((x),(y))) > (MAX((z),(w))) ? (MAX((x),(y))) : (MAX((z),(w))))
20 #define MIN4(x,y,z,w) \
21 	((MIN((x),(y))) < (MIN((z),(w))) ? (MIN((x),(y))) : (MIN((z),(w))))
22 
23 #define MAXX(x) MAX4(x[0],x[2],x[4],x[6])
24 #define MINX(x) MIN4(x[0],x[2],x[4],x[6])
25 #define MAXY(x) MAX4(x[1],x[3],x[5],x[7])
26 #define MINY(x) MIN4(x[1],x[3],x[5],x[7])
27 
28 /**
29  * Function: gdImageStringFTCircle
30  *
31  * Draw text curved along the top and bottom of a circular area of an image.
32  *
33  * Parameters:
34  *  im          - The image to draw onto.
35  *  cx          - The x-coordinate of the center of the circular area.
36  *  cy          - The y-coordinate of the center of the circular area.
37  *  radius      - The radius of the circular area.
38  *  textRadius  - The height of each character; if textRadius is 1/2 of radius,
39  *	              characters extend halfway from the edge to the center.
40  *  fillPortion - The percentage of the 180 degrees of the circular area
41  *                assigned to each section of text, that is actually occupied
42  *                by text. The value has to be in range 0.0 to 1.0, with useful
43  *                values from about 0.4 to 0.9; 0.9 looks better than 1.0 which
44  *                is rather crowded.
45  *  font        - The fontlist that is passed to <gdImageStringFT>.
46  *  points      - The point size, which functions as a hint. Although the size
47  *                of the text is determined by radius, textRadius and
48  *                fillPortion, a point size that 'hints' appropriately should be
49  *                passed. If it's known that the text will be large, a large
50  *                point size such as 24.0 should be passed to get the best
51  *                results.
52  *  top         - The text to draw clockwise at the top of the circular area.
53  *  bottom      - The text to draw counterclockwise at the bottom of the
54  *                circular area.
55  *  fgcolor     - The font color.
56  *
57  * Returns:
58  *  NULL on success, or an error string on failure.
59  */
60 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)61 gdImageStringFTCircle (gdImagePtr im,
62                        int cx,
63                        int cy,
64                        double radius,
65                        double textRadius,
66                        double fillPortion,
67                        char *font,
68                        double points, char *top, char *bottom, int fgcolor)
69 {
70 	char *err;
71 	int w;
72 	int brect[8];
73 	int sx1, sx2, sy1, sy2, sx, sy;
74 	int x, y;
75 	int fr, fg, fb, fa;
76 	int ox, oy;
77 	double prop;
78 	gdImagePtr im1;
79 	gdImagePtr im2;
80 	gdImagePtr im3;
81 	/* obtain brect so that we can size the image */
82 	err = gdImageStringFT ((gdImagePtr) NULL,
83 	                       &brect[0], 0, font, points * MAG, 0, 0, 0, bottom);
84 	if (err) {
85 		return err;
86 	}
87 	sx1 = MAXX (brect) - MINX (brect) + 6;
88 	sy1 = MAXY (brect) - MINY (brect) + 6;
89 	err = gdImageStringFT ((gdImagePtr) NULL,
90 	                       &brect[0], 0, font, points * MAG, 0, 0, 0, top);
91 	if (err) {
92 		return err;
93 	}
94 	sx2 = MAXX (brect) - MINX (brect) + 6;
95 	sy2 = MAXY (brect) - MINY (brect) + 6;
96 	/* Pad by 4 pixels to allow for slight errors
97 	   observed in the bounding box returned by freetype */
98 	if (sx1 > sx2) {
99 		sx = sx1 * 2 + 4;
100 	} else {
101 		sx = sx2 * 2 + 4;
102 	}
103 	if (sy1 > sy2) {
104 		sy = sy1;
105 	} else {
106 		sy = sy2;
107 	}
108 	im1 = gdImageCreateTrueColor (sx, sy);
109 	if (!im1) {
110 		return "could not create first image";
111 	}
112 	err = gdImageStringFT (im1, 0, gdTrueColor (255, 255, 255),
113 	                       font, points * MAG,
114 	                       0, ((sx / 2) - sx1) / 2, points * MAG, bottom);
115 	if (err) {
116 		gdImageDestroy (im1);
117 		return err;
118 	}
119 	/* We don't know the descent, which would be needed to do this
120 	   with the angle parameter. Instead, implement a simple
121 	   flip operation ourselves. */
122 	err = gdImageStringFT (im1, 0, gdTrueColor (255, 255, 255),
123 	                       font, points * MAG,
124 	                       0, sx / 2 + ((sx / 2) - sx2) / 2, points * MAG, top);
125 	if (err) {
126 		gdImageDestroy (im1);
127 		return err;
128 	}
129 	/* Flip in place is tricky, be careful not to double-swap things */
130 	if (sy & 1) {
131 		for (y = 0; (y <= (sy / 2)); y++) {
132 			int xlimit = sx - 2;
133 			if (y == (sy / 2)) {
134 				/* If there is a "middle" row, be careful
135 				   not to swap twice! */
136 				xlimit -= (sx / 4);
137 			}
138 			for (x = (sx / 2) + 2; (x < xlimit); x++) {
139 				int t;
140 				int ox = sx - x + (sx / 2) - 1;
141 				int oy = sy - y - 1;
142 				t = im1->tpixels[oy][ox];
143 				im1->tpixels[oy][ox] = im1->tpixels[y][x];
144 				im1->tpixels[y][x] = t;
145 			}
146 		}
147 	} else {
148 		for (y = 0; (y < (sy / 2)); y++) {
149 			int xlimit = sx - 2;
150 			for (x = (sx / 2) + 2; (x < xlimit); x++) {
151 				int t;
152 				int ox = sx - x + (sx / 2) - 1;
153 				int oy = sy - y - 1;
154 				t = im1->tpixels[oy][ox];
155 				im1->tpixels[oy][ox] = im1->tpixels[y][x];
156 				im1->tpixels[y][x] = t;
157 			}
158 		}
159 	}
160 #if STEP_PNGS
161 	{
162 		FILE *out = fopen ("gdfx1.png", "wb");
163 		gdImagePng (im1, out);
164 		fclose (out);
165 	}
166 #endif /* STEP_PNGS */
167 	/* Resample taller; the exact proportions of the text depend on the
168 	   ratio of textRadius to radius, and the value of fillPortion */
169 	if (sx > sy * 10) {
170 		w = sx;
171 	} else {
172 		w = sy * 10;
173 	}
174 	im2 = gdImageCreateTrueColor (w, w);
175 	if (!im2) {
176 		gdImageDestroy (im1);
177 		return "could not create resampled image";
178 	}
179 	prop = textRadius / radius;
180 	gdImageCopyResampled (im2, im1,
181 	                      gdImageSX (im2) * (1.0 - fillPortion) / 4,
182 	                      sy * 10 * (1.0 - prop),
183 	                      0, 0,
184 	                      gdImageSX (im2) * fillPortion / 2, sy * 10 * prop,
185 	                      gdImageSX (im1) / 2, gdImageSY (im1));
186 	gdImageCopyResampled (im2, im1,
187 	                      (gdImageSX (im2) / 2) +
188 	                      gdImageSX (im2) * (1.0 - fillPortion) / 4,
189 	                      sy * 10 * (1.0 - prop),
190 	                      gdImageSX (im1) / 2, 0,
191 	                      gdImageSX (im2) * fillPortion / 2, sy * 10 * prop,
192 	                      gdImageSX (im1) / 2, gdImageSY (im1));
193 #if STEP_PNGS
194 	{
195 		FILE *out = fopen ("gdfx2.png", "wb");
196 		gdImagePng (im2, out);
197 		fclose (out);
198 	}
199 #endif /* STEP_PNGS */
200 
201 	gdImageDestroy (im1);
202 
203 	/* Ready to produce a circle */
204 	im3 = gdImageSquareToCircle (im2, radius);
205 	if (im3 == NULL) {
206 		gdImageDestroy(im2);
207 		return 0;
208 	}
209 	gdImageDestroy (im2);
210 	/* Now blend im3 with the destination. Cheat a little. The
211 	   source (im3) is white-on-black, so we can use the
212 	   red component as a basis for alpha as long as we're
213 	   careful to shift off the extra bit and invert
214 	   (alpha ranges from 0 to 127 where 0 is OPAQUE).
215 	   Also be careful to allow for an alpha component
216 	   in the fgcolor parameter itself (gug!) */
217 	fr = gdTrueColorGetRed (fgcolor);
218 	fg = gdTrueColorGetGreen (fgcolor);
219 	fb = gdTrueColorGetBlue (fgcolor);
220 	fa = gdTrueColorGetAlpha (fgcolor);
221 	ox = cx - (im3->sx / 2);
222 	oy = cy - (im3->sy / 2);
223 	for (y = 0; (y < im3->sy); y++) {
224 		for (x = 0; (x < im3->sx); x++) {
225 			int a = gdTrueColorGetRed (im3->tpixels[y][x]) >> 1;
226 			a *= (127 - fa);
227 			a /= 127;
228 			a = 127 - a;
229 			gdImageSetPixel (im, x + ox, y + oy,
230 			                 gdTrueColorAlpha (fr, fg, fb, a));
231 		}
232 	}
233 	gdImageDestroy (im3);
234 	return 0;
235 }
236 
237 #if GDFX_MAIN
238 
239 int
main(int argc,char * argv[])240 main (int argc, char *argv[])
241 {
242 	FILE *in;
243 	FILE *out;
244 	gdImagePtr im;
245 	int radius;
246 	/* Create an image of text on a circle, with an
247 	   alpha channel so that we can copy it onto a
248 	   background */
249 	in = fopen ("eleanor.jpg", "rb");
250 	if (!in) {
251 		im = gdImageCreateTrueColor (300, 300);
252 	} else {
253 		im = gdImageCreateFromJpeg (in);
254 		fclose (in);
255 	}
256 	if (gdImageSX (im) < gdImageSY (im)) {
257 		radius = gdImageSX (im) / 2;
258 	} else {
259 		radius = gdImageSY (im) / 2;
260 	}
261 	gdImageStringFTCircle (im,
262 	                       gdImageSX (im) / 2,
263 	                       gdImageSY (im) / 2,
264 	                       radius,
265 	                       radius / 2,
266 	                       0.8,
267 	                       "arial",
268 	                       24,
269 	                       "top text",
270 	                       "bottom text", gdTrueColorAlpha (240, 240, 255, 32));
271 	out = fopen ("gdfx.png", "wb");
272 	if (!out) {
273 		gd_error("Can't create gdfx.png\n");
274 		return 1;
275 	}
276 	gdImagePng (im, out);
277 	fclose (out);
278 	gdImageDestroy (im);
279 	return 0;
280 }
281 
282 #endif /* GDFX_MAIN */
283 
284 /* Note: don't change these */
285 #define SUPER 2
286 #define SUPERBITS1 1
287 #define SUPERBITS2 2
288 
289 /**
290  * Function: gdImageSquareToCircle
291  *
292  * Apply polar coordinate transformation to an image.
293  *
294  * The X axis of the original will be remapped to theta (angle) and the Y axis
295  * of the original will be remapped to rho (distance from center).
296  *
297  * Parameters:
298  *  im     - The image, which must be square, i.e. width == height.
299  *  radius - The radius of the new image, i.e. width == height == radius * 2.
300  *
301  * Returns:
302  *  The transformed image, or NULL on failure.
303  */
304 BGD_DECLARE(gdImagePtr)
gdImageSquareToCircle(gdImagePtr im,int radius)305 gdImageSquareToCircle (gdImagePtr im, int radius)
306 {
307 	int x, y;
308 	double c;
309 	gdImagePtr im2;
310 	if (im->sx != im->sy) {
311 		/* Source image must be square */
312 		return 0;
313 	}
314 	im2 = gdImageCreateTrueColor (radius * 2, radius * 2);
315 	if (!im2) {
316 		return 0;
317 	}
318 	/* Supersampling for a nicer result */
319 	c = (im2->sx / 2) * SUPER;
320 	for (y = 0; (y < im2->sy * SUPER); y++) {
321 		for (x = 0; (x < im2->sx * SUPER); x++) {
322 			double rho = sqrt ((x - c) * (x - c) + (y - c) * (y - c));
323 			int pix;
324 			int cpix;
325 			double theta;
326 			double ox;
327 			double oy;
328 			int red, green, blue, alpha;
329 			if (rho > c) {
330 				continue;
331 			}
332 			theta = atan2 (x - c, y - c) + PI / 2;
333 			if (theta < 0) {
334 				theta += 2 * PI;
335 			}
336 			/* Undo supersampling */
337 			oy = (rho * im->sx) / (im2->sx * SUPER / 2);
338 			ox = theta * im->sx / (3.141592653 * 2);
339 			pix = gdImageGetPixel (im, ox, oy);
340 			cpix = im2->tpixels[y >> SUPERBITS1][x >> SUPERBITS1];
341 			red =
342 			    (gdImageRed (im, pix) >> SUPERBITS2) + gdTrueColorGetRed (cpix);
343 			green =
344 			    (gdImageGreen (im, pix) >> SUPERBITS2) +
345 			    gdTrueColorGetGreen (cpix);
346 			blue =
347 			    (gdImageBlue (im, pix) >> SUPERBITS2) + gdTrueColorGetBlue (cpix);
348 			alpha =
349 			    (gdImageAlpha (im, pix) >> SUPERBITS2) +
350 			    gdTrueColorGetAlpha (cpix);
351 			im2->tpixels[y >> SUPERBITS1][x >> SUPERBITS1] =
352 			    gdTrueColorAlpha (red, green, blue, alpha);
353 		}
354 	}
355 	/* Restore full dynamic range, 0-63 yields 0-252. Replication of
356 	   first 2 bits in last 2 bits has the desired effect. Note
357 	   slightly different arithmetic for alpha which is 7-bit.
358 	   NOTE: only correct for SUPER == 2 */
359 	for (y = 0; (y < im2->sy); y++) {
360 		for (x = 0; (x < im2->sx); x++) {
361 			/* Copy first 2 bits to last 2 bits, matching the
362 			   dynamic range of the original cheaply */
363 			int cpix = im2->tpixels[y][x];
364 
365 			im2->tpixels[y][x] = gdTrueColorAlpha ((gdTrueColorGetRed (cpix) &
366 			                                        0xFC) +
367 			                                       ((gdTrueColorGetRed (cpix) &
368 			                                               0xC0) >> 6),
369 			                                       (gdTrueColorGetGreen (cpix) &
370 			                                        0xFC) +
371 			                                       ((gdTrueColorGetGreen (cpix)
372 			                                               & 0xC0) >> 6),
373 			                                       (gdTrueColorGetBlue (cpix) &
374 			                                        0xFC) +
375 			                                       ((gdTrueColorGetBlue (cpix) &
376 			                                               0xC0) >> 6),
377 			                                       (gdTrueColorGetAlpha (cpix) &
378 			                                        0x7C) +
379 			                                       ((gdTrueColorGetAlpha (cpix)
380 			                                               & 0x60) >> 6));
381 		}
382 	}
383 	return im2;
384 }
385 
386 /* 2.0.16: Called by gdImageSharpen to avoid excessive code repetition
387     Added on 2003-11-19 by
388     Paul Troughton (paul<dot>troughton<at>ieee<dot>org)
389     Given filter coefficents and colours of three adjacent pixels,
390 returns new colour for centre pixel
391 */
392 
393 int
gdImageSubSharpen(int pc,int c,int nc,float inner_coeff,float outer_coeff)394 gdImageSubSharpen (int pc, int c, int nc, float inner_coeff, float
395                    outer_coeff)
396 {
397 	float red, green, blue, alpha;
398 
399 	red = inner_coeff * gdTrueColorGetRed (c) + outer_coeff *
400 	      (gdTrueColorGetRed (pc) + gdTrueColorGetRed (nc));
401 	green = inner_coeff * gdTrueColorGetGreen (c) + outer_coeff *
402 	        (gdTrueColorGetGreen (pc) + gdTrueColorGetGreen (nc));
403 	blue = inner_coeff * gdTrueColorGetBlue (c) + outer_coeff *
404 	       (gdTrueColorGetBlue (pc) + gdTrueColorGetBlue (nc));
405 	alpha = gdTrueColorGetAlpha (c);
406 
407 	/* Clamping, as can overshoot bounds in either direction */
408 	if (red > 255.0f) {
409 		red = 255.0f;
410 	}
411 	if (green > 255.0f) {
412 		green = 255.0f;
413 	}
414 	if (blue > 255.0f) {
415 		blue = 255.0f;
416 	}
417 	if (red < 0.0f) {
418 		red = 0.0f;
419 	}
420 	if (green < 0.0f) {
421 		green = 0.0f;
422 	}
423 	if (blue < 0.0f) {
424 		blue = 0.0f;
425 	}
426 
427 	return gdTrueColorAlpha ((int) red, (int) green, (int) blue, (int) alpha);
428 }
429 
430 /**
431  * Function: gdImageSharpen
432  *
433  * Sharpen an image.
434  *
435  * Uses a simple 3x3 convolution kernel and makes use of separability.
436  * It's faster, but less flexible, than full-blown unsharp masking.
437  * Silently does nothing to non-truecolor images and for pct<0, as it's not a useful blurring function.
438  *
439  * Parameters:
440  *  pct - The sharpening percentage, which can be greater than 100.
441  *
442  * Author:
443  *  Paul Troughton (paul<dot>troughton<at>ieee<dot>org)
444  */
445 BGD_DECLARE(void)
gdImageSharpen(gdImagePtr im,int pct)446 gdImageSharpen (gdImagePtr im, int pct)
447 {
448 	int x, y;
449 	int sx, sy;
450 	float inner_coeff, outer_coeff;
451 
452 	sx = im->sx;
453 	sy = im->sy;
454 
455 	/* Must sum to 1 to avoid overall change in brightness.
456 	 * Scaling chosen so that pct=100 gives 1-D filter [-1 6 -1]/4,
457 	 * resulting in a 2-D filter [1 -6 1; -6 36 -6; 1 -6 1]/16,
458 	 * which gives noticeable, but not excessive, sharpening
459 	 */
460 
461 	outer_coeff = -pct / 400.0;
462 	inner_coeff = 1 - 2 * outer_coeff;
463 
464 	/* Don't try to do anything with non-truecolor images, as
465 	   pointless,
466 	   * nor for pct<=0, as small kernel size leads to nasty
467 	   artefacts when blurring
468 	 */
469 	if ((im->trueColor) && (pct > 0)) {
470 
471 		/* First pass, 1-D convolution column-wise */
472 		for (x = 0; x < sx; x++) {
473 
474 			/* pc is colour of previous pixel; c of the
475 			   current pixel and nc of the next */
476 			int pc, c, nc;
477 
478 			/* Replicate edge pixel at image boundary */
479 			pc = gdImageGetPixel (im, x, 0);
480 
481 			/* Stop looping before last pixel to avoid
482 			   conditional within loop */
483 			for (y = 0; y < sy - 1; y++) {
484 
485 				c = gdImageGetPixel (im, x, y);
486 
487 				nc = gdImageGetTrueColorPixel (im, x, y + 1);
488 
489 				/* Update centre pixel to new colour */
490 				gdImageSetPixel (im, x, y,
491 				                 gdImageSubSharpen (pc, c, nc, inner_coeff,
492 				                                    outer_coeff));
493 
494 				/* Save original colour of current
495 				   pixel for next time round */
496 				pc = c;
497 			}
498 
499 			/* Deal with last pixel, replicating current
500 			   pixel at image boundary */
501 			c = gdImageGetPixel (im, x, y);
502 			gdImageSetPixel (im, x, y, gdImageSubSharpen
503 			                 (pc, c, c, inner_coeff, outer_coeff));
504 		}
505 
506 		/* Second pass, 1-D convolution row-wise */
507 		for (y = 0; y < sy; y++) {
508 			int pc, c;
509 			pc = gdImageGetPixel (im, 0, y);
510 			for (x = 0; x < sx - 1; x++) {
511 				int c, nc;
512 				c = gdImageGetPixel (im, x, y);
513 				nc = gdImageGetTrueColorPixel (im, x + 1, y);
514 				gdImageSetPixel (im, x, y,
515 				                 gdImageSubSharpen (pc, c, nc, inner_coeff,
516 				                                    outer_coeff));
517 				pc = c;
518 			}
519 			c = gdImageGetPixel (im, x, y);
520 			gdImageSetPixel (im, x, y, gdImageSubSharpen
521 			                 (pc, c, c, inner_coeff, outer_coeff));
522 		}
523 	}
524 }
525