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