523 lines
15 KiB
C
523 lines
15 KiB
C
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif /* HAVE_CONFIG_H */
|
|
|
|
#include "gd.h"
|
|
#include "gd_errors.h"
|
|
#include "gd_intern.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 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));
|
|
}
|
|
}
|
|
}
|