#ifdef HAVE_CONFIG_H #include "config.h" #endif /* HAVE_CONFIG_H */ #include "gd.h" #include "gd_errors.h" #include /* 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 . * 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 (paultroughtonieeeorg) 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 (paultroughtonieeeorg) */ 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)); } } }