Back to index

tetex-bin  3.0
gdfx.c
Go to the documentation of this file.
00001 #include "gd.h"
00002 #include <math.h>
00003 
00004 /* In tests this is sufficient to prevent obvious artifacts */
00005 #define MAG 4
00006 
00007 #define PI 3.141592
00008 #define DEG2RAD(x) ((x)*PI/180.)
00009 
00010 #define MAX(x,y) ((x) > (y) ? (x) : (y))
00011 #define MIN(x,y) ((x) < (y) ? (x) : (y))
00012 
00013 #define MAX4(x,y,z,w) \
00014        ((MAX((x),(y))) > (MAX((z),(w))) ? (MAX((x),(y))) : (MAX((z),(w))))
00015 #define MIN4(x,y,z,w) \
00016        ((MIN((x),(y))) < (MIN((z),(w))) ? (MIN((x),(y))) : (MIN((z),(w))))
00017 
00018 #define MAXX(x) MAX4(x[0],x[2],x[4],x[6])
00019 #define MINX(x) MIN4(x[0],x[2],x[4],x[6])
00020 #define MAXY(x) MAX4(x[1],x[3],x[5],x[7])
00021 #define MINY(x) MIN4(x[1],x[3],x[5],x[7])
00022 
00023 BGD_DECLARE(char *)
00024 gdImageStringFTCircle (gdImagePtr im,
00025                      int cx,
00026                      int cy,
00027                      double radius,
00028                      double textRadius,
00029                      double fillPortion,
00030                      char *font,
00031                      double points, char *top, char *bottom, int fgcolor)
00032 {
00033   char *err;
00034   int w;
00035   int brect[8];
00036   int sx1, sx2, sy1, sy2, sx, sy;
00037   int x, y;
00038   int fr, fg, fb, fa;
00039   int ox, oy;
00040   double prop;
00041   gdImagePtr im1;
00042   gdImagePtr im2;
00043   gdImagePtr im3;
00044   /* obtain brect so that we can size the image */
00045   err = gdImageStringFT ((gdImagePtr) NULL,
00046                       &brect[0], 0, font, points * MAG, 0, 0, 0, bottom);
00047   if (err)
00048     {
00049       return err;
00050     }
00051   sx1 = MAXX (brect) - MINX (brect) + 6;
00052   sy1 = MAXY (brect) - MINY (brect) + 6;
00053   err = gdImageStringFT ((gdImagePtr) NULL,
00054                       &brect[0], 0, font, points * MAG, 0, 0, 0, top);
00055   if (err)
00056     {
00057       return err;
00058     }
00059   sx2 = MAXX (brect) - MINX (brect) + 6;
00060   sy2 = MAXY (brect) - MINY (brect) + 6;
00061   /* Pad by 4 pixels to allow for slight errors
00062      observed in the bounding box returned by freetype */
00063   if (sx1 > sx2)
00064     {
00065       sx = sx1 * 2 + 4;
00066     }
00067   else
00068     {
00069       sx = sx2 * 2 + 4;
00070     }
00071   if (sy1 > sy2)
00072     {
00073       sy = sy1;
00074     }
00075   else
00076     {
00077       sy = sy2;
00078     }
00079   im1 = gdImageCreateTrueColor (sx, sy);
00080   if (!im1)
00081     {
00082       return "could not create first image";
00083     }
00084   err = gdImageStringFT (im1, 0, gdTrueColor (255, 255, 255),
00085                       font, points * MAG,
00086                       0, ((sx / 2) - sx1) / 2, points * MAG, bottom);
00087   if (err)
00088     {
00089       gdImageDestroy (im1);
00090       return err;
00091     }
00092   /* We don't know the descent, which would be needed to do this
00093      with the angle parameter. Instead, implement a simple
00094      flip operation ourselves. */
00095   err = gdImageStringFT (im1, 0, gdTrueColor (255, 255, 255),
00096                       font, points * MAG,
00097                       0, sx / 2 + ((sx / 2) - sx2) / 2, points * MAG, top);
00098   if (err)
00099     {
00100       gdImageDestroy (im1);
00101       return err;
00102     }
00103   /* Flip in place is tricky, be careful not to double-swap things */
00104   if (sy & 1)
00105     {
00106       for (y = 0; (y <= (sy / 2)); y++)
00107        {
00108          int xlimit = sx - 2;
00109          if (y == (sy / 2))
00110            {
00111              /* If there is a "middle" row, be careful
00112                 not to swap twice! */
00113              xlimit -= (sx / 4);
00114            }
00115          for (x = (sx / 2) + 2; (x < xlimit); x++)
00116            {
00117              int t;
00118              int ox = sx - x + (sx / 2) - 1;
00119              int oy = sy - y - 1;
00120              t = im1->tpixels[oy][ox];
00121              im1->tpixels[oy][ox] = im1->tpixels[y][x];
00122              im1->tpixels[y][x] = t;
00123            }
00124        }
00125     }
00126   else
00127     {
00128       for (y = 0; (y < (sy / 2)); y++)
00129        {
00130          int xlimit = sx - 2;
00131          for (x = (sx / 2) + 2; (x < xlimit); x++)
00132            {
00133              int t;
00134              int ox = sx - x + (sx / 2) - 1;
00135              int oy = sy - y - 1;
00136              t = im1->tpixels[oy][ox];
00137              im1->tpixels[oy][ox] = im1->tpixels[y][x];
00138              im1->tpixels[y][x] = t;
00139            }
00140        }
00141     }
00142 #if STEP_PNGS
00143   {
00144     FILE *out = fopen ("gdfx1.png", "wb");
00145     gdImagePng (im1, out);
00146     fclose (out);
00147   }
00148 #endif /* STEP_PNGS */
00149   /* Resample taller; the exact proportions of the text depend on the
00150      ratio of textRadius to radius, and the value of fillPortion */
00151   if (sx > sy * 10)
00152     {
00153       w = sx;
00154     }
00155   else
00156     {
00157       w = sy * 10;
00158     }
00159   im2 = gdImageCreateTrueColor (w, w);
00160   if (!im2)
00161     {
00162       gdImageDestroy (im1);
00163       return "could not create resampled image";
00164     }
00165   prop = textRadius / radius;
00166   gdImageCopyResampled (im2, im1,
00167                      gdImageSX (im2) * (1.0 - fillPortion) / 4,
00168                      sy * 10 * (1.0 - prop),
00169                      0, 0,
00170                      gdImageSX (im2) * fillPortion / 2, sy * 10 * prop,
00171                      gdImageSX (im1) / 2, gdImageSY (im1));
00172   gdImageCopyResampled (im2, im1,
00173                      (gdImageSX (im2) / 2) +
00174                      gdImageSX (im2) * (1.0 - fillPortion) / 4,
00175                      sy * 10 * (1.0 - prop),
00176                      gdImageSX (im1) / 2, 0,
00177                      gdImageSX (im2) * fillPortion / 2, sy * 10 * prop,
00178                      gdImageSX (im1) / 2, gdImageSY (im1));
00179 #if STEP_PNGS
00180   {
00181     FILE *out = fopen ("gdfx2.png", "wb");
00182     gdImagePng (im2, out);
00183     fclose (out);
00184   }
00185 #endif /* STEP_PNGS */
00186   /* Ready to produce a circle */
00187   im3 = gdImageSquareToCircle (im2, radius);
00188   gdImageDestroy (im1);
00189   gdImageDestroy (im2);
00190   /* Now blend im3 with the destination. Cheat a little. The
00191      source (im3) is white-on-black, so we can use the
00192      red component as a basis for alpha as long as we're
00193      careful to shift off the extra bit and invert
00194      (alpha ranges from 0 to 127 where 0 is OPAQUE). 
00195      Also be careful to allow for an alpha component
00196      in the fgcolor parameter itself (gug!) */
00197   fr = gdTrueColorGetRed (fgcolor);
00198   fg = gdTrueColorGetGreen (fgcolor);
00199   fb = gdTrueColorGetBlue (fgcolor);
00200   fa = gdTrueColorGetAlpha (fgcolor);
00201   ox = cx - (im3->sx / 2);
00202   oy = cy - (im3->sy / 2);
00203   for (y = 0; (y < im3->sy); y++)
00204     {
00205       for (x = 0; (x < im3->sx); x++)
00206        {
00207          int a = gdTrueColorGetRed (im3->tpixels[y][x]) >> 1;
00208          a *= (127 - fa);
00209          a /= 127;
00210          a = 127 - a;
00211          gdImageSetPixel (im, x + ox, y + oy,
00212                         gdTrueColorAlpha (fr, fg, fb, a));
00213        }
00214     }
00215   gdImageDestroy (im3);
00216   return 0;
00217 }
00218 
00219 #if GDFX_MAIN
00220 
00221 int
00222 main (int argc, char *argv[])
00223 {
00224   FILE *in;
00225   FILE *out;
00226   gdImagePtr im;
00227   int radius;
00228   /* Create an image of text on a circle, with an
00229      alpha channel so that we can copy it onto a
00230      background */
00231   in = fopen ("eleanor.jpg", "rb");
00232   if (!in)
00233     {
00234       im = gdImageCreateTrueColor (300, 300);
00235     }
00236   else
00237     {
00238       im = gdImageCreateFromJpeg (in);
00239       fclose (in);
00240     }
00241   if (gdImageSX (im) < gdImageSY (im))
00242     {
00243       radius = gdImageSX (im) / 2;
00244     }
00245   else
00246     {
00247       radius = gdImageSY (im) / 2;
00248     }
00249   gdStringFTCircle (im,
00250                   gdImageSX (im) / 2,
00251                   gdImageSY (im) / 2,
00252                   radius,
00253                   radius / 2,
00254                   0.8,
00255                   "arial",
00256                   24,
00257                   "top text",
00258                   "bottom text", gdTrueColorAlpha (240, 240, 255, 32));
00259   out = fopen ("gdfx.png", "wb");
00260   if (!out)
00261     {
00262       fprintf (stderr, "Can't create gdfx.png\n");
00263       return 1;
00264     }
00265   gdImagePng (im, out);
00266   fclose (out);
00267   gdImageDestroy (im);
00268   return 0;
00269 }
00270 
00271 #endif /* GDFX_MAIN */
00272 
00273 /* Note: don't change these */
00274 #define SUPER 2
00275 #define SUPERBITS1 1
00276 #define SUPERBITS2 2
00277 
00278 BGD_DECLARE(gdImagePtr)
00279 gdImageSquareToCircle (gdImagePtr im, int radius)
00280 {
00281   int x, y;
00282   double c;
00283   gdImagePtr im2;
00284   if (im->sx != im->sy)
00285     {
00286       /* Source image must be square */
00287       return 0;
00288     }
00289   im2 = gdImageCreateTrueColor (radius * 2, radius * 2);
00290   /* Supersampling for a nicer result */
00291   c = (im2->sx / 2) * SUPER;
00292   for (y = 0; (y < im2->sy * SUPER); y++)
00293     {
00294       for (x = 0; (x < im2->sx * SUPER); x++)
00295        {
00296          double rho = sqrt ((x - c) * (x - c) + (y - c) * (y - c));
00297          int pix;
00298          int cpix;
00299          double theta;
00300          double ox;
00301          double oy;
00302          int red, green, blue, alpha;
00303          if (rho > c)
00304            {
00305              continue;
00306            }
00307          theta = atan2 (x - c, y - c) + PI / 2;
00308          if (theta < 0)
00309            {
00310              theta += 2 * PI;
00311            }
00312          /* Undo supersampling */
00313          oy = (rho * im->sx) / (im2->sx * SUPER / 2);
00314          ox = theta * im->sx / (3.141592653 * 2);
00315          pix = gdImageGetPixel (im, ox, oy);
00316          cpix = im2->tpixels[y >> SUPERBITS1][x >> SUPERBITS1];
00317          red =
00318            (gdImageRed (im, pix) >> SUPERBITS2) + gdTrueColorGetRed (cpix);
00319          green =
00320            (gdImageGreen (im, pix) >> SUPERBITS2) +
00321            gdTrueColorGetGreen (cpix);
00322          blue =
00323            (gdImageBlue (im, pix) >> SUPERBITS2) + gdTrueColorGetBlue (cpix);
00324          alpha =
00325            (gdImageAlpha (im, pix) >> SUPERBITS2) +
00326            gdTrueColorGetAlpha (cpix);
00327          im2->tpixels[y >> SUPERBITS1][x >> SUPERBITS1] =
00328            gdTrueColorAlpha (red, green, blue, alpha);
00329        }
00330     }
00331   /* Restore full dynamic range, 0-63 yields 0-252. Replication of
00332      first 2 bits in last 2 bits has the desired effect. Note
00333      slightly different arithmetic for alpha which is 7-bit. 
00334      NOTE: only correct for SUPER == 2 */
00335   for (y = 0; (y < im2->sy); y++)
00336     {
00337       for (x = 0; (x < im2->sx); x++)
00338        {
00339          /* Copy first 2 bits to last 2 bits, matching the
00340             dynamic range of the original cheaply */
00341          int cpix = im2->tpixels[y][x];
00342 
00343          im2->tpixels[y][x] = gdTrueColorAlpha ((gdTrueColorGetRed (cpix) &
00344                                             0xFC) +
00345                                            ((gdTrueColorGetRed (cpix) &
00346                                              0xC0) >> 6),
00347                                            (gdTrueColorGetGreen (cpix) &
00348                                             0xFC) +
00349                                            ((gdTrueColorGetGreen (cpix)
00350                                              & 0xC0) >> 6),
00351                                            (gdTrueColorGetBlue (cpix) &
00352                                             0xFC) +
00353                                            ((gdTrueColorGetBlue (cpix) &
00354                                              0xC0) >> 6),
00355                                            (gdTrueColorGetAlpha (cpix) &
00356                                             0x7C) +
00357                                            ((gdTrueColorGetAlpha (cpix)
00358                                              & 0x60) >> 6));
00359        }
00360     }
00361   return im2;
00362 }
00363 
00364 /* 2.0.16: Called by gdImageSharpen to avoid excessive code repetition
00365     Added on 2003-11-19 by
00366     Paul Troughton (paul<dot>troughton<at>ieee<dot>org)
00367     Given filter coefficents and colours of three adjacent pixels, 
00368 returns new colour for centre pixel
00369 */
00370 
00371 int
00372 gdImageSubSharpen (int pc, int c, int nc, float inner_coeff, float
00373                  outer_coeff)
00374 {
00375   float red, green, blue, alpha;
00376 
00377   red = inner_coeff * gdTrueColorGetRed (c) + outer_coeff *
00378     (gdTrueColorGetRed (pc) + gdTrueColorGetRed (nc));
00379   green = inner_coeff * gdTrueColorGetGreen (c) + outer_coeff *
00380     (gdTrueColorGetGreen (pc) + gdTrueColorGetGreen (nc));
00381   blue = inner_coeff * gdTrueColorGetBlue (c) + outer_coeff *
00382     (gdTrueColorGetBlue (pc) + gdTrueColorGetBlue (nc));
00383   alpha = gdTrueColorGetAlpha (c);
00384 
00385   /* Clamping, as can overshoot bounds in either direction */
00386   if (red > 255.0f)
00387     {
00388       red = 255.0f;
00389     }
00390   if (green > 255.0f)
00391     {
00392       green = 255.0f;
00393     }
00394   if (blue > 255.0f)
00395     {
00396       blue = 255.0f;
00397     }
00398   if (red < 0.0f)
00399     {
00400       red = 0.0f;
00401     }
00402   if (green < 0.0f)
00403     {
00404       green = 0.0f;
00405     }
00406   if (blue < 0.0f)
00407     {
00408       blue = 0.0f;
00409     }
00410 
00411   return gdTrueColorAlpha ((int) red, (int) green, (int) blue, (int) alpha);
00412 }
00413 
00414 /*
00415   * Sharpen function added on 2003-11-19
00416   * by Paul Troughton (paul<dot>troughton<at>ieee<dot>org)
00417   * Simple 3x3 convolution kernel
00418   * Makes use of seperability
00419   * Faster, but less flexible, than full-blown unsharp masking
00420   * pct is sharpening percentage, and can be greater than 100
00421   * Silently does nothing to non-truecolor images
00422   * Silently does nothing for pct<0, as not a useful blurring function
00423   * Leaves transparency/alpha-channel untouched
00424   */
00425 BGD_DECLARE(void)
00426 gdImageSharpen (gdImagePtr im, int pct)
00427 {
00428   int x, y;
00429   int sx, sy;
00430   float inner_coeff, outer_coeff;
00431 
00432   sx = im->sx;
00433   sy = im->sy;
00434 
00435   /* Must sum to 1 to avoid overall change in brightness.
00436    * Scaling chosen so that pct=100 gives 1-D filter [-1 6 -1]/4,
00437    * resulting in a 2-D filter [1 -6 1; -6 36 -6; 1 -6 1]/16,
00438    * which gives noticeable, but not excessive, sharpening
00439    */
00440 
00441   outer_coeff = -pct / 400.0;
00442   inner_coeff = 1 - 2 * outer_coeff;
00443 
00444   /* Don't try to do anything with non-truecolor images, as 
00445      pointless,
00446      * nor for pct<=0, as small kernel size leads to nasty 
00447      artefacts when blurring
00448    */
00449   if ((im->trueColor) && (pct > 0))
00450     {
00451 
00452       /* First pass, 1-D convolution column-wise */
00453       for (x = 0; x < sx; x++)
00454        {
00455 
00456          /* pc is colour of previous pixel; c of the 
00457             current pixel and nc of the next */
00458          int pc, c, nc;
00459 
00460          /* Replicate edge pixel at image boundary */
00461          pc = gdImageGetPixel (im, x, 0);
00462 
00463          /* Stop looping before last pixel to avoid 
00464             conditional within loop */
00465          for (y = 0; y < sy - 1; y++)
00466            {
00467 
00468              c = gdImageGetPixel (im, x, y);
00469 
00470              nc = gdImageGetTrueColorPixel (im, x, y + 1);
00471 
00472              /* Update centre pixel to new colour */
00473              gdImageSetPixel (im, x, y,
00474                             gdImageSubSharpen (pc, c, nc, inner_coeff,
00475                                             outer_coeff));
00476 
00477              /* Save original colour of current 
00478                 pixel for next time round */
00479              pc = c;
00480            }
00481 
00482          /* Deal with last pixel, replicating current 
00483             pixel at image boundary */
00484          c = gdImageGetPixel (im, x, y);
00485          gdImageSetPixel (im, x, y, gdImageSubSharpen
00486                         (pc, c, c, inner_coeff, outer_coeff));
00487        }
00488 
00489       /* Second pass, 1-D convolution row-wise */
00490       for (y = 0; y < sy; y++)
00491        {
00492          int pc, c;
00493          pc = gdImageGetPixel (im, 0, y);
00494          for (x = 0; x < sx - 1; x++)
00495            {
00496              int c, nc;
00497              c = gdImageGetPixel (im, x, y);
00498              nc = gdImageGetTrueColorPixel (im, x + 1, y);
00499              gdImageSetPixel (im, x, y,
00500                             gdImageSubSharpen (pc, c, nc, inner_coeff,
00501                                             outer_coeff));
00502              pc = c;
00503            }
00504          c = gdImageGetPixel (im, x, y);
00505          gdImageSetPixel (im, x, y, gdImageSubSharpen
00506                         (pc, c, c, inner_coeff, outer_coeff));
00507        }
00508     }
00509 }