/****************************************************************************** * * Project: MapServer * Purpose: GD rendering and other GD related functions. * Author: Steve Lime and the MapServer team. * ****************************************************************************** * Copyright (c) 1996-2005 Regents of the University of Minnesota. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies of this Software or works derived from this Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************** * * $Log: mapgd.c,v $ * Revision 1.137 2006/09/06 05:51:35 sdlime * Applied Bart's quick fix for bug 1776. Real fix is addressed by rounding width and height before calling createBrush. However, that might still be an issue due to the rounding issues mentioned in that bug. This is a safe alternative for now. * * Revision 1.136 2006/08/30 16:06:37 hobu * northwest airlines sucks. I fixed the libiconv warning on my Memphis->Greenland->Boston leg of my trip to Italy * * Revision 1.135 2006/08/26 17:25:48 frank * use MS_NINT_GENERIC for symbol offset calculation (bug 1716) * * Revision 1.134 2006/08/22 13:47:23 hobu * make sure to cast the strings being passed into * gdImageString as (unsigned char *) to silence warnings * from the compiler * * Revision 1.133 2006/08/15 17:24:18 sdlime * Trimmed history... * * Revision 1.132 2006/05/16 05:36:02 sdlime * Fixed bug that required PIXMAP fills to define a bogus color. * * Revision 1.131 2006/04/26 03:25:47 sdlime * Applied most recent patch for curved labels. (bug 1620) * * Revision 1.130 2006/04/03 15:40:23 dan * Fixed FP exception in mapgd.c when pixmap symbol 'sizey' not set (bug 1735) * * Revision 1.129 2006/03/23 20:28:52 sdlime * Most recent patch for curved labels. (bug 1620) * * Revision 1.128 2006/03/21 06:28:28 sdlime * Fixed logic error so we use faster GD functions when we don't have to scale PIXMAP symbols. * * Revision 1.127 2006/03/16 04:45:24 sdlime * Reverted to old means of scaling symbols based solely on height. Fixed possiblity of memory leak with symbol rotation. Made rotation and scaling behavior more consistent across all GD rendering functions (point, line, polygon and circle). (bugs 1684 and 1705) * * Revision 1.126 2006/03/09 04:53:49 frank * added GD/PNG Quantize support * * Revision 1.125 2006/03/02 06:43:51 sdlime * Applied latest patch for curved labels. (bug 1620) * * Revision 1.124 2006/02/24 06:26:13 sdlime * Updated truetype shade symbols to use the symbol gap value to provide space around the symbol. Change affects both polygons and circles. The gap is not scaled yet. (bug 1674) * * Revision 1.123 2006/02/24 06:02:17 sdlime * Truetype shade symbols can now be antialiased using symbol-level or style-level ANTIALIAS TRUE. * * Revision 1.122 2006/02/18 20:59:13 sdlime * Initial code for curved labels. (bug 1620) * */ #include "map.h" #include "mapthread.h" #include #ifdef USE_ICONV #include #endif #ifdef _WIN32 #include #include #endif MS_CVSID("$Id: mapgd.c,v 1.137 2006/09/06 05:51:35 sdlime Exp $") static unsigned char PNGsig[8] = {137, 80, 78, 71, 13, 10, 26, 10}; /* 89 50 4E 47 0D 0A 1A 0A hex */ static unsigned char JPEGsig[3] = {255, 216, 255}; /* FF D8 FF hex */ int msImageSetPenGD(gdImagePtr img, colorObj *color) { if(MS_VALID_COLOR(*color)) color->pen = gdImageColorResolve(img, color->red, color->green, color->blue); else color->pen = -1; return(MS_SUCCESS); } int msCompareColors(colorObj *c1, colorObj *c2) { if(c1->red != c2->red || c1->green != c2->green || c1->blue != c2->blue) return MS_FALSE; return MS_TRUE; } static gdImagePtr searchImageCache(struct imageCacheObj *ic, styleObj *style, int size) { struct imageCacheObj *icp; icp = ic; while(icp) { if (icp->symbol == style->symbol && msCompareColors(&(icp->color), &(style->color)) == MS_TRUE && msCompareColors(&(icp->outlinecolor), &(style->outlinecolor)) == MS_TRUE && msCompareColors(&(icp->backgroundcolor), &(style->backgroundcolor)) == MS_TRUE && icp->size == size) { return(icp->img); } icp = icp->next; } return(NULL); } static struct imageCacheObj *addImageCache(struct imageCacheObj *ic, int *icsize, styleObj *style, int size, gdImagePtr img) { struct imageCacheObj *icp; if(*icsize > MS_IMAGECACHESIZE) { /* remove last element, size stays the same */ icp = ic; while(icp->next && icp->next->next) icp = icp->next; freeImageCache(icp->next); icp->next = NULL; } else *icsize += 1; if((icp = (struct imageCacheObj *)malloc(sizeof(struct imageCacheObj))) == NULL) { msSetError(MS_MEMERR, NULL, "initImageCache()"); return(NULL); } icp->img = img; icp->color = style->color; icp->outlinecolor = style->outlinecolor; icp->backgroundcolor = style->backgroundcolor; icp->symbol = style->symbol; icp->size = size; icp->next = ic; /* insert at the beginning */ return(icp); } /* * Take a pass through the mapObj and pre-allocate colors for layers that are ON or DEFAULT. This replicates the pre-4.0 behavior of * MapServer and should be used only with paletted images. */ void msPreAllocateColorsGD(imageObj *image, mapObj *map) { int i, j, k; layerObj *lp; classObj *cp; styleObj *sp; if(!image) return; /* nothing to do */ if(gdImageTrueColor(image->img.gd)) return; for(i=0; inumlayers; i++) { lp = &(map->layers[i]); if(lp->status == MS_ON || lp->status == MS_DEFAULT) { for(j=0; jnumclasses; j++) { cp = &(lp->class[j]); msImageSetPenGD(image->img.gd, &(cp->label.backgroundcolor)); msImageSetPenGD(image->img.gd, &(cp->label.backgroundshadowcolor)); msImageSetPenGD(image->img.gd, &(cp->label.color)); msImageSetPenGD(image->img.gd, &(cp->label.outlinecolor)); msImageSetPenGD(image->img.gd, &(cp->label.shadowcolor)); for(k=0; knumstyles; k++) { sp = &(cp->styles[k]); msImageSetPenGD(image->img.gd, &(sp->backgroundcolor)); msImageSetPenGD(image->img.gd, &(sp->color)); msImageSetPenGD(image->img.gd, &(sp->outlinecolor)); } } } } } /* * Utility function to create a GD image. Returns * a pointer to an imageObj structure. */ imageObj *msImageCreateGD(int width, int height, outputFormatObj *format, char *imagepath, char *imageurl) { imageObj *image; if(width > 0 && height > 0) { image = (imageObj *)calloc(1,sizeof(imageObj)); if(format->imagemode == MS_IMAGEMODE_RGB || format->imagemode == MS_IMAGEMODE_RGBA) { image->img.gd = gdImageCreateTrueColor(width, height); gdImageAlphaBlending( image->img.gd, 0); /* off by default, from alans@wunderground.com */ } else image->img.gd = gdImageCreate(width, height); if(image->img.gd) { image->format = format; format->refcount++; image->width = width; image->height = height; image->imagepath = NULL; image->imageurl = NULL; if(imagepath) image->imagepath = strdup(imagepath); if(imageurl) image->imageurl = strdup(imageurl); return image; } else free(image); } else { msSetError(MS_IMGERR, "Cannot create GD image of size %d x %d.", "msImageCreateGD()", width, height ); } return NULL; } /** * Utility function to initialize the color of an image. The background * color is passed, but the outputFormatObj is consulted to see if the * transparency should be set (for RGBA images). Note this function only * affects TrueColor images. */ void msImageInitGD( imageObj *image, colorObj *background ) { if(image->format->imagemode == MS_IMAGEMODE_PC256) { gdImageColorAllocate(image->img.gd, background->red, background->green, background->blue); return; } { int pen, pixels, line; int *tpixels; if(image->format->imagemode == MS_IMAGEMODE_RGBA) pen = gdTrueColorAlpha(background->red, background->green, background->blue, image->format->transparent ? 127:0); else pen = gdTrueColor(background->red, background->green, background->blue); for(line = 0; line < image->img.gd->sy; line++ ) { pixels = image->img.gd->sx; tpixels = image->img.gd->tpixels[line]; while(pixels-- > 0) *(tpixels++) = pen; } } } /* msImageLoadGDStream is called by msImageLoadGD and is useful * by itself */ /*imageObj *msImageLoadGDStream(FILE *stream) { gdImagePtr img=NULL; const char *driver = NULL; char bytes[8]; imageObj *image = NULL; image = (imageObj *)calloc(1,sizeof(imageObj)); fread(bytes,8,1,stream); // read some bytes to try and identify the file rewind(stream); // reset the image for the readers if (memcmp(bytes,"GIF8",4)==0) { #ifdef USE_GD_GIF img = gdImageCreateFromGif(stream); driver = "GD/GIF"; image->img.gd = img; image->imagepath = NULL; image->imageurl = NULL; image->width = img->sx; image->height = img->sy; #else msSetError(MS_MISCERR, "Unable to load GIF reference image.", "msImageLoadGD()"); fclose(stream); return(NULL); #endif } else if (memcmp(bytes,PNGsig,8)==0) { #ifdef USE_GD_PNG img = gdImageCreateFromPng(stream); driver = "GD/PNG"; image->img.gd = img; image->imagepath = NULL; image->imageurl = NULL; image->width = img->sx; image->height = img->sy; #else msSetError(MS_MISCERR, "Unable to load PNG reference image.", "msImageLoadGD()"); fclose(stream); return(NULL); #endif } else if (memcmp(bytes,JPEGsig,3)==0) { #ifdef USE_GD_JPEG img = gdImageCreateFromJpeg(stream); driver = "GD/JPEG"; image->img.gd = img; image->imagepath = NULL; image->imageurl = NULL; image->width = img->sx; image->height = img->sy; #else msSetError(MS_MISCERR, "Unable to load JPEG reference image.", "msImageLoadGD()"); fclose(stream); return(NULL); #endif } if(!img) { msSetError(MS_GDERR, "Unable to initialize image", "msLoadImageGDStream()"); fclose(stream); return(NULL); } image->format = msCreateDefaultOutputFormat( NULL, driver ); image->format->refcount++; if( image->format == NULL ) { msSetError(MS_GDERR, "Unable to create default OUTPUTFORMAT definition for driver '%s'.", "msImageLoadGDStream()", driver ); return(NULL); } if( gdImageTrueColor(img) && image->format->imagemode == MS_IMAGEMODE_PC256 ) { image->format->imagemode = MS_IMAGEMODE_RGB; } else if( !gdImageTrueColor(img) && (image->format->imagemode == MS_IMAGEMODE_RGB || image->format->imagemode == MS_IMAGEMODE_RGBA) ) { image->format->imagemode = MS_IMAGEMODE_PC256; } if (gdImageGetInterlaced(img)) { msSetOutputFormatOption( image->format, "INTERLACE", "ON" ); } else { msSetOutputFormatOption( image->format, "INTERLACE", "OFF" ); } return image; }*/ /* =========================================================================== msImageLoadGDCtx So that we can avoid passing a FILE* to GD, all gd image IO is now done through the fileIOCtx interface defined at the end of this file. The old msImageLoadStreamGD function has been removed. ========================================================================= */ imageObj *msImageLoadGDCtx(gdIOCtx* ctx, const char *driver) { gdImagePtr img=NULL; imageObj *image = NULL; #ifdef USE_GD_GIF if (strcasecmp(driver, "GD/GIF") == MS_SUCCESS) img = gdImageCreateFromGifCtx(ctx); #endif #ifdef USE_GD_PNG if (strcasecmp(driver, "GD/PNG") == MS_SUCCESS || strcasecmp(driver, "GD/PNG24") == MS_SUCCESS) img = gdImageCreateFromPngCtx(ctx); #endif #ifdef USE_GD_JPEG if (strcasecmp(driver, "GD/JPEG") == MS_SUCCESS) img = gdImageCreateFromJpegCtx(ctx); #endif if (!img) { msSetError(MS_GDERR, "Unable to initialize image", "msLoadImageGDStream()"); return(NULL); } /* Initialize an imageObj */ image = (imageObj *) calloc(1, sizeof(imageObj)); image->img.gd = img; image->imagepath = NULL; image->imageurl = NULL; image->width = gdImageSX(img); image->height = gdImageSY(img); /* Create an outputFormatObj for the format. */ image->format = msCreateDefaultOutputFormat( NULL, driver ); image->format->refcount++; if( image->format == NULL ) { msSetError(MS_GDERR, "Unable to create default OUTPUTFORMAT for driver '%s'.", "msImageLoadGDStream()", driver ); msFreeImage(image); return(NULL); } /* ** Try to ensure that truecolor images are handled as MS_IMAGEMODE_RGB ** and that colormapped images are MS_IMAGEMODE_PC256. This would generally ** only be an issue for PNG which can be either. */ if ( gdImageTrueColor(img) && image->format->imagemode == MS_IMAGEMODE_PC256 ) { image->format->imagemode = MS_IMAGEMODE_RGB; } else if ( !gdImageTrueColor(img) && (image->format->imagemode == MS_IMAGEMODE_RGB || image->format->imagemode == MS_IMAGEMODE_RGBA) ) { image->format->imagemode = MS_IMAGEMODE_PC256; } /* ** Try to ensure we use the same interlacing on the output image as we ** found in the source image. (Bug 1039) */ if (gdImageGetInterlaced(img)) { msSetOutputFormatOption( image->format, "INTERLACE", "ON" ); } else { msSetOutputFormatOption( image->format, "INTERLACE", "OFF" ); } return image; } /* msImageLoadGD now calls msImageLoadGDCtx to do the work, change * made as part of the resolution of bugs 550 and 1047 */ imageObj *msImageLoadGD(const char *filename) { FILE *stream; gdIOCtx *ctx; imageObj *image; char bytes[8]; stream = fopen(filename, "rb"); if (!stream) { msSetError(MS_IOERR, "(%s)", "msImageLoadGD()", filename ); return(NULL); } /* Detect image format */ fread(bytes,8,1,stream); /* read some bytes to try and identify the file */ rewind(stream); /* reset the image for the readers */ if (memcmp(bytes,"GIF8",4)==0) { #ifdef USE_GD_GIF ctx = msNewGDFileCtx(stream); image = msImageLoadGDCtx(ctx, "GD/GIF"); ctx->gd_free(ctx); #else msSetError(MS_MISCERR, "Unable to load GIF image.", "msImageLoadGD()"); fclose(stream); return(NULL); #endif } else if (memcmp(bytes,PNGsig,8)==0) { #ifdef USE_GD_PNG ctx = msNewGDFileCtx(stream); image = msImageLoadGDCtx(ctx, "GD/PNG"); ctx->gd_free(ctx); #else msSetError(MS_MISCERR, "Unable to load PNG image.", "msImageLoadGD()"); fclose(stream); return(NULL); #endif } else if (memcmp(bytes,JPEGsig,3)==0) { #ifdef USE_GD_JPEG ctx = msNewGDFileCtx(stream); image = msImageLoadGDCtx(ctx, "GD/JPEG"); ctx->gd_free(ctx); #else msSetError(MS_MISCERR, "Unable to load JPEG image.", "msImageLoadGD()"); fclose(stream); return(NULL); #endif } else { msSetError(MS_MISCERR, "Unable to load %s in any format.", "msImageLoadGD()", filename); fclose(stream); return(NULL); } fclose(stream); if (!image) { msSetError(MS_GDERR, "Unable to initialize image '%s'", "msLoadImageGD()", filename); return(NULL); } return(image); } static gdImagePtr createFuzzyBrush(int size, int r, int g, int b) { gdImagePtr brush; int x, y, c, dx, dy; long bgcolor, color; int a; double d, min_d, max_d; double hardness=.5; if(size % 2 == 0) /* requested an even-sized brush, subtract one from size */ size--; brush = gdImageCreateTrueColor(size+2, size+2); gdImageAlphaBlending(brush, 0); /* don't blend */ bgcolor = gdImageColorAllocateAlpha(brush, 255, 255, 255, 127); /* fill the brush as transparent */ gdImageFilledRectangle(brush, 0, 0, gdImageSX(brush), gdImageSY(brush), bgcolor); c = (gdImageSX(brush)-1)/2; /* center coordinate (x=y) */ min_d = hardness*(size/2.0) - 0.5; max_d = gdImageSX(brush)/2.0; color = gdImageColorAllocateAlpha(brush, r, g, b, 0); gdImageFilledEllipse(brush, c, c, gdImageSX(brush), gdImageSY(brush), color); /* draw the base circle as opaque */ for(y=0; ybackgroundcolor.pen >= 0) *bgcolor = gdImageColorAllocate(brush, style->backgroundcolor.red, style->backgroundcolor.green, style->backgroundcolor.blue); else { *bgcolor = gdImageColorAllocate(brush, gdImageRed(img,0), gdImageGreen(img, 0), gdImageBlue(img, 0)); gdImageColorTransparent(brush,0); } if(style->color.pen >= 0) *fgcolor = gdImageColorAllocate(brush, style->color.red, style->color.green, style->color.blue); else /* try outline color */ *fgcolor = gdImageColorAllocate(brush, style->outlinecolor.red, style->outlinecolor.green, style->outlinecolor.blue); } else { brush = gdImageCreateTrueColor(width, height); gdImageAlphaBlending(brush, 0); if(style->backgroundcolor.pen >= 0) *bgcolor = gdTrueColor(style->backgroundcolor.red, style->backgroundcolor.green, style->backgroundcolor.blue); else *bgcolor = -1; gdImageFilledRectangle(brush, 0, 0, width, height, *bgcolor); if(style->color.pen >= 0) *fgcolor = gdTrueColor(style->color.red, style->color.green, style->color.blue); else /* try outline color */ *fgcolor = gdTrueColor(style->outlinecolor.red, style->outlinecolor.green, style->outlinecolor.blue); } return(brush); } /* Function to create a custom hatch symbol. */ static gdImagePtr createHatch(gdImagePtr img, int sx, int sy, rectObj *clip, styleObj *style, double scalefactor) { gdImagePtr hatch; int x1, x2, y1, y2; int size, width; double angle; int fg, bg; hatch = createBrush(img, sx, sy, style, &fg, &bg); if(style->antialias == MS_TRUE) { gdImageSetAntiAliased(hatch, fg); fg = gdAntiAliased; } if(style->size == -1) size = 1; /* TODO: Can we use msSymbolGetDefaultSize() here? See bug 751. */ else size = style->size; size = MS_NINT(size*scalefactor); size = MS_MAX(size, style->minsize); size = MS_MIN(size, style->maxsize); width = MS_NINT(style->width*scalefactor); width = MS_MAX(width, style->minwidth); width = MS_MIN(width, style->maxwidth); gdImageSetThickness(hatch, width); /* normalize the angle (180 to 0, 0 is east, 90 is north 180 is west) */ angle = fmod(style->angle, 360.0); if(angle < 0) angle += 360; if(angle >= 180) angle -= 180; if(angle >= 45 && angle <= 90) { x2 = (int)clip->minx; /* 0 */ y2 = (int)clip->miny; /* 0 */ y1 = (int)clip->maxy-1; /* sy-1 */ x1 = (int) (x2 - (y2 - y1)/tan(-MS_DEG_TO_RAD*angle)); size = MS_ABS(MS_NINT(size/sin(MS_DEG_TO_RAD*(angle)))); while(x1 < clip->maxx) { /* sx */ gdImageLine(hatch, x1, y1, x2, y2, fg); x1+=size; x2+=size; } } else if(angle <= 135 && angle > 90) { x2 = (int)clip->minx; /* 0 */ y2 = (int)clip->maxy-1; /* sy-1 */ y1 = (int)clip->miny; /* 0 */ x1 = (int) (x2 - (y2 - y1)/tan(-MS_DEG_TO_RAD*angle)); size = MS_ABS(MS_NINT(size/sin(MS_DEG_TO_RAD*(angle)))); while(x1 < clip->maxx) { /* sx */ gdImageLine(hatch, x1, y1, x2, y2, fg); x1+=size; x2+=size; } } else if(angle >= 0 && angle < 45) { x1 = (int)clip->minx; /* 0 */ y1 = (int)clip->miny; /* 0 */ x2 = (int)clip->maxx-1; /* sx-1 */ y2 = (int)(y1 + (x2 - x1)*tan(-MS_DEG_TO_RAD*angle)); size = MS_ABS(MS_NINT(size/cos(MS_DEG_TO_RAD*(angle)))); while(y2 < clip->maxy) { /* sy */ gdImageLine(hatch, x1, y1, x2, y2, fg); y1+=size; y2+=size; } } else if(angle < 180 && angle > 135) { x2 = (int)clip->maxx-1; /* sx-1 */ y2 = (int)clip->miny; /* 0 */ x1 = (int)clip->minx; /* 0 */ y1 = (int) (y2 - (x2 - x1)*tan(-MS_DEG_TO_RAD*angle)); size = MS_ABS(MS_NINT(size/cos(MS_DEG_TO_RAD*(angle)))); while(y1 < clip->maxy) { /* sy */ gdImageLine(hatch, x1, y1, x2, y2, fg); y1+=size; y2+=size; } } gdImageSetThickness(hatch, 1); return(hatch); } /* ** A series of low level drawing routines not available in GD, but specific to GD */ static void imageScanline(gdImagePtr im, int x1, int x2, int y, int c) { int x; /* TO DO: This fix (adding if/then) was to address circumstances in the polygon fill code */ /* where x2 < x1. There may be something wrong in that code, but at any rate this is safer. */ if(x1 < x2) for(x=x1; x<=x2; x++) gdImageSetPixel(im, x, y, c); else for(x=x2; x<=x1; x++) gdImageSetPixel(im, x, y, c); } static double getAngleFromDxDy(double dx, double dy) { double angle; if (!dx) { if (dy > 0) angle = MS_PI2; else angle = MS_3PI2; } else { angle = atan(dy/dx); if (dx < 0) angle += MS_PI; else if (dx > 0 && dy < 0) angle += MS_2PI; } return(angle); } static gdPoint generateGDLineIntersection(gdPoint a, gdPoint b, gdPoint c, gdPoint d) { gdPoint p; double r; double denominator, numerator; if(b.x == c.x && b.y == c.y) return(b); numerator = ((a.y-c.y)*(d.x-c.x) - (a.x-c.x)*(d.y-c.y)); denominator = ((b.x-a.x)*(d.y-c.y) - (b.y-a.y)*(d.x-c.x)); r = numerator/denominator; p.x = MS_NINT(a.x + r*(b.x-a.x)); p.y = MS_NINT(a.y + r*(b.y-a.y)); return(p); } static void ImageFilledPolygonAA (gdImagePtr im, gdPointPtr p, int n, int c, int antialias) { if (antialias) { /* gdImageSetAntiAliased(im, c); */ gdImageSetAntiAliasedDontBlend(im, c, c); gdImageFilledPolygon(im, p, n, gdAntiAliased); } else { gdImageFilledPolygon(im, p, n, c); } } static void imageFilledSegment(gdImagePtr im, double x, double y, double sz, double angle_from, double angle_to, int c, int antialias) { int j=0; double angle, dx, dy; static gdPoint points[38]; double angle_dif; sz -= 0.1; if (sz < 4) angle_dif = MS_PI2; else if (sz < 12) angle_dif = MS_PI/5; else if (sz < 20) angle_dif = MS_PI2/7; else angle_dif = MS_PI2/10; angle = angle_from; while(angle < angle_to) { dx = cos(angle)*sz; dy = sin(angle)*sz; points[j].x = MS_NINT(x + dx); points[j].y = MS_NINT(y + dy); angle += angle_dif; j++; } if (j) { dx = cos(angle_to)*sz; dy = sin(angle_to)*sz; points[j].x = MS_NINT(x + dx); points[j].y = MS_NINT(y + dy); j++; points[j].x = MS_NINT(x); points[j].y = MS_NINT(y); j++; points[j].x = points[0].x; points[j].y = points[0].y; j++; /* gdImagePolygon(im, points, j, c); */ /* gdImageFilledPolygon(im, points, j, c); */ ImageFilledPolygonAA(im, points, j, c, antialias); } } static void imageOffsetPolyline(gdImagePtr img, shapeObj *p, int color, int offsetx, int offsety) { int i, j, first; int dx, dy, dx0=0, dy0=0, ox=0, oy=0, limit; double x, y, x0=0.0, y0=0.0, k=0.0, k0=0.0, q=0.0, q0=0.0; float par=(float)0.71; if(offsety == -99) { limit = offsetx*offsetx/4; for (i = 0; i < p->numlines; i++) { first = 1; for(j=1; jline[i].numpoints; j++) { ox=0; oy=0; dx = (int)(p->line[i].point[j].x - p->line[i].point[j-1].x); dy = (int)(p->line[i].point[j].y - p->line[i].point[j-1].y); /* offset setting - quick approximation, may be changed with goniometric functions */ if(dx==0) { /* vertical line */ if(dy==0) continue; /* checking unique points */ ox=(dy>0) ? -offsetx : offsetx; } else { k = (double)dy/(double)dx; if(MS_ABS(k)<0.5) { oy = (dx>0) ? offsetx : -offsetx; } else { if (MS_ABS(k)<2.1) { oy = (int) ((dx>0) ? offsetx*par : -offsetx*par); ox = (int) ((dy>0) ? -offsetx*par : offsetx*par); } else ox = (int)((dy>0) ? -offsetx : offsetx); } q = p->line[i].point[j-1].y+oy - k*(p->line[i].point[j-1].x+ox); } /* offset line points computation */ if(first==1) { /* first point */ first = 0; x = p->line[i].point[j-1].x+ox; y = p->line[i].point[j-1].y+oy; } else { /* middle points */ if((dx*dx+dy*dy)>limit){ /* if the points are too close */ if(dx0==0) { /* checking verticals */ if(dx==0) continue; x = x0; y = k*x + q; } else { if(dx==0) { x = p->line[i].point[j-1].x+ox; y = k0*x + q0; } else { if(k==k0) continue; /* checking equal direction */ x = (q-q0)/(k0-k); y = k*x+q; } } }else{/* need to be refined */ x = p->line[i].point[j-1].x+ox; y = p->line[i].point[j-1].y+oy; } gdImageLine(img, (int)x0, (int)y0, (int)x, (int)y, color); } dx0 = dx; dy0 = dy; x0 = x, y0 = y; k0 = k; q0=q; } /* last point */ if(first==0)gdImageLine(img, (int)x0, (int)y0, (int)(p->line[i].point[p->line[i].numpoints-1].x+ox), (int)(p->line[i].point[p->line[i].numpoints-1].y+oy), color); } } else { /* normal offset (eg. drop shadow) */ for (i = 0; i < p->numlines; i++) for(j=1; jline[i].numpoints; j++) gdImageLine(img, (int)p->line[i].point[j-1].x+offsetx, (int)p->line[i].point[j-1].y+offsety, (int)p->line[i].point[j].x+offsetx, (int)p->line[i].point[j].y+offsety, color); } } typedef enum {CLIP_LEFT, CLIP_MIDDLE, CLIP_RIGHT} CLIP_STATE; #define CLIP_CHECK(min, a, max) ((a) < (min) ? CLIP_LEFT : ((a) > (max) ? CLIP_RIGHT : CLIP_MIDDLE)); #define ROUND(a) ( (a) + 0.5 ) #define SWAP( a, b, t) ( (t) = (a), (a) = (b), (b) = (t) ) #define EDGE_CHECK( x0, x, x1) ((x) < MS_MIN( (x0), (x1)) ? CLIP_LEFT : ((x) > MS_MAX( (x0), (x1)) ? CLIP_RIGHT : CLIP_MIDDLE )) static void imagePolyline(gdImagePtr img, shapeObj *p, int color, int offsetx, int offsety) { int i, j; if(offsetx != 0 || offsety != 0) imageOffsetPolyline(img, p, color, offsetx, offsety); else { for (i = 0; i < p->numlines; i++) for(j=1; jline[i].numpoints; j++) gdImageLine(img, (int)p->line[i].point[j-1].x, (int)p->line[i].point[j-1].y, (int)p->line[i].point[j].x, (int)p->line[i].point[j].y, color); } } static void imageFilledPolygon(gdImagePtr im, shapeObj *p, int c, int offsetx, int offsety) { float *slope; pointObj *point1, *point2, *testpoint1, *testpoint2; int i, j, k, l, m, nfound, *xintersect, temp, sign; int x, y, ymin, ymax, *horiz, wrong_order; int n; if(p->numlines == 0) return; #if 0 if( c & 0xFF000000 ) gdImageAlphaBlending( im, 1 ); #endif /* calculate the total number of vertices */ n=0; for(i=0; inumlines; i++) n += p->line[i].numpoints; /* Allocate slope and horizontal detection arrays */ slope = (float *)calloc(n, sizeof(float)); horiz = (int *)calloc(n, sizeof(int)); /* Since at most only one intersection is added per edge, there can only be at most n intersections per scanline */ xintersect = (int *)calloc(n, sizeof(int)); /* Find the min and max Y */ ymin = (int)(p->line[0].point[0].y); ymax = ymin; for(l=0,j=0; jnumlines; j++) { point1 = &( p->line[j].point[p->line[j].numpoints-1] ); for(i=0; i < p->line[j].numpoints; i++,l++) { point2 = &( p->line[j].point[i] ); if(point1->y == point2->y) { horiz[l] = 1; slope[l] = 0.0; } else { horiz[l] = 0; slope[l] = (float)((point2->x - point1->x) / (point2->y - point1->y)); } ymin = (int) (MS_MIN(ymin, point1->y)); ymax = (int) (MS_MAX(ymax, point2->y)); point1 = point2; } } for(y = ymin; y <= ymax; y++) { /* for each scanline */ nfound = 0; for(j=0, l=0; jnumlines; j++) { /* for each line, l is overall point counter */ m = l; /* m is offset from begining of all vertices */ point1 = &( p->line[j].point[p->line[j].numpoints-1] ); for(i=0; i < p->line[j].numpoints; i++, l++) { point2 = &( p->line[j].point[i] ); if(EDGE_CHECK(point1->y, y, point2->y) == CLIP_MIDDLE) { if(horiz[l]) /* First, is this edge horizontal ? */ continue; /* Did we intersect the first point point ? */ if(y == point1->y) { /* Yes, must find first non-horizontal edge */ k = i-1; if(k < 0) k = p->line[j].numpoints-1; while(horiz[m+k]) { k--; if(k < 0) k = p->line[j].numpoints-1; } /* Now perform sign test */ if(k > 0) testpoint1 = &( p->line[j].point[k-1] ); else testpoint1 = &( p->line[j].point[p->line[j].numpoints-1] ); testpoint2 = &( p->line[j].point[k] ); sign = (int) ((testpoint2->y - testpoint1->y) * (point2->y - point1->y)); if(sign < 0) xintersect[nfound++] = (int) point1->x; /* All done for point matching case */ } else { /* Not at the first point, find the intersection*/ x = (int)(ROUND(point1->x + (y - point1->y)*slope[l])); xintersect[nfound++] = x; } } /* End of checking this edge */ point1 = point2; /* Go on to next edge */ } } /* Finished this scanline, draw all of the spans */ /* First, sort the intersections */ do { wrong_order = 0; for(i=0; i < nfound-1; i++) { if(xintersect[i] > xintersect[i+1]) { wrong_order = 1; SWAP(xintersect[i], xintersect[i+1], temp); } } } while(wrong_order); /* Great, now we can draw the spans */ for(i=0; i < nfound; i += 2) imageScanline(im, xintersect[i]+offsetx, xintersect[i+1]+offsetx, y+offsety, c); } /* End of scanline loop */ /* Finally, draw all of the horizontal edges */ for(j=0, l=0; jnumlines; j++) { point1 = &( p->line[j].point[p->line[j].numpoints - 1] ); for(i=0; iline[j].numpoints; i++, l++) { point2 = &( p->line[j].point[i] ); if(horiz[l]) imageScanline(im, (int)(point1->x+offsetx), (int)(point2->x+offsetx), (int)(point2->y+offsety), c); point1 = point2; } } #if 0 gdImageAlphaBlending( im, 0 ); #endif free(slope); free(horiz); free(xintersect); } /* ---------------------------------------------------------------------------*/ /* Stroke an ellipse with a line symbol of the specified size and color */ /* ---------------------------------------------------------------------------*/ void msCircleDrawLineSymbolGD(symbolSetObj *symbolset, gdImagePtr img, pointObj *p, double r, styleObj *style, double scalefactor) { int i, j; symbolObj *symbol; int x, y, ox, oy; int bc, fc; int brush_bc, brush_fc; int width; double size, d; gdImagePtr brush=NULL; gdPoint points[MS_MAXVECTORPOINTS]; if(!p) return; if(style->backgroundcolor.pen == MS_PEN_UNSET) msImageSetPenGD(img, &(style->backgroundcolor)); if(style->color.pen == MS_PEN_UNSET) msImageSetPenGD(img, &(style->color)); if(style->outlinecolor.pen == MS_PEN_UNSET) msImageSetPenGD(img, &(style->outlinecolor)); symbol = &(symbolset->symbol[style->symbol]); bc = style->backgroundcolor.pen; fc = style->color.pen; if(fc==-1) fc = style->outlinecolor.pen; width = style->width; if(style->size == -1) size = msSymbolGetDefaultSize( &( symbolset->symbol[style->symbol] ) ); else size = style->size; size = MS_NINT(size*scalefactor); size = MS_MAX(size, style->minsize); size = MS_MIN(size, style->maxsize); width = MS_NINT(style->width*scalefactor); width = MS_MAX(width, style->minwidth); width = MS_MIN(width, style->maxwidth); if(style->symbol > symbolset->numsymbols || style->symbol < 0) return; /* no such symbol, 0 is OK */ if(fc < 0) return; /* nothing to do */ if(size < 1) return; /* size too small */ ox = MS_NINT(style->offsetx*scalefactor); oy = (style->offsety < -90) ? style->offsety : (int)(style->offsety*scalefactor); /* ** handle the most simple case */ if(style->symbol == 0) { if(gdImageTrueColor(img) && width > 1 && style->antialias == MS_TRUE) { /* use a fuzzy brush */ if((brush = searchImageCache(symbolset->imagecache, style, width)) == NULL) { brush = createFuzzyBrush(width, gdImageRed(img, fc), gdImageGreen(img, fc), gdImageBlue(img, fc)); symbolset->imagecache = addImageCache(symbolset->imagecache, &symbolset->imagecachesize, style, width, brush); } gdImageSetBrush(img, brush); gdImageArc(img, (int)p->x + ox, (int)p->y + oy, (int)2*r, (int)2*r, 0, 360, gdBrushed); } else { gdImageSetThickness(img, width); if(style->antialias == MS_TRUE) { gdImageSetAntiAliased(img, fc); gdImageArc(img, (int)p->x + ox, (int)p->y + oy, (int)2*r, (int)2*r, 0, 360, gdAntiAliased); gdImageSetAntiAliased(img, -1); } else gdImageArc(img, (int)p->x + ox, (int)p->y + oy, (int)2*r, (int)2*r, 0, 360, fc); gdImageSetThickness(img, 1); } return; /* done with easiest case */ } switch(symbol->type) { case(MS_SYMBOL_SIMPLE): if(gdImageTrueColor(img) && width > 1 && style->antialias == MS_TRUE) { /* use a fuzzy brush */ if((brush = searchImageCache(symbolset->imagecache, style, width)) == NULL) { brush = createFuzzyBrush(width, gdImageRed(img, fc), gdImageGreen(img, fc), gdImageBlue(img, fc)); symbolset->imagecache = addImageCache(symbolset->imagecache, &symbolset->imagecachesize, style, width, brush); } gdImageSetBrush(img, brush); fc = 1; bc = 0; } else { gdImageSetThickness(img, width); if(bc == -1) bc = gdTransparent; } break; case(MS_SYMBOL_TRUETYPE): /* msImageTruetypePolyline(img, p, symbol, fc, size, symbolset->fontset); */ return; break; case(MS_SYMBOL_CARTOLINE): return; break; case(MS_SYMBOL_ELLIPSE): bc = gdTransparent; d = size/symbol->sizey; x = MS_NINT(symbol->sizex*d); y = MS_NINT(symbol->sizey*d); if((x < 2) && (y < 2)) break; if(gdImageTrueColor(img) && x > 1 && style->antialias == MS_TRUE && x == y) { /* use a fuzzy brush */ /* create the brush image if not already in the cache */ if((brush = searchImageCache(symbolset->imagecache, style, x)) == NULL) { brush = createFuzzyBrush(x, gdImageRed(img, fc), gdImageGreen(img, fc), gdImageBlue(img, fc)); symbolset->imagecache = addImageCache(symbolset->imagecache, &symbolset->imagecachesize, style, x, brush); } } else { /* create the brush image if not already in the cache */ if((brush = searchImageCache(symbolset->imagecache, style, (int)size)) == NULL) { brush = createBrush(img, x, y, style, &brush_fc, &brush_bc); /* not in cache, create */ x = MS_NINT(brush->sx/2); /* center the ellipse */ y = MS_NINT(brush->sy/2); /* draw in the brush image */ if(symbol->filled) gdImageFilledEllipse(brush, x, y, MS_NINT(d*symbol->points[0].x), MS_NINT(d*symbol->points[0].y), brush_fc); else gdImageArc(brush, x, y, MS_NINT(d*symbol->points[0].x), MS_NINT(d*symbol->points[0].y), 0, 360, brush_fc); symbolset->imagecache = addImageCache(symbolset->imagecache, &symbolset->imagecachesize, style, (int)size, brush); } } gdImageSetBrush(img, brush); fc=1; bc=0; break; case(MS_SYMBOL_PIXMAP): gdImageSetBrush(img, symbol->img); fc=1; bc=0; break; case(MS_SYMBOL_VECTOR): if(bc == -1) bc = gdTransparent; d = size/symbol->sizey; x = MS_NINT(symbol->sizex*d); y = MS_NINT(symbol->sizey*d); if((x < 2) && (y < 2)) break; /* create the brush image */ if((brush = searchImageCache(symbolset->imagecache, style, (int)size)) == NULL) { brush = createBrush(img, x, y, style, &brush_fc, &brush_bc); /* not in cache, create */ /* draw in the brush image */ for(i=0;i < symbol->numpoints;i++) { points[i].x = MS_NINT(d*symbol->points[i].x); points[i].y = MS_NINT(d*symbol->points[i].y); } gdImageFilledPolygon(brush, points, symbol->numpoints, brush_fc); symbolset->imagecache = addImageCache(symbolset->imagecache, &symbolset->imagecachesize, style, (int) size, brush); } gdImageSetBrush(img, brush); fc = 1; bc = 0; break; } if(symbol->stylelength > 0) { int *styleDashed; int k=0, sc; /* Alloc styleDashed array large enough for this style */ int numElemStyle=0; for(i=0; istylelength; i++) { numElemStyle += symbol->style[i]; } styleDashed = (int *)malloc(numElemStyle * sizeof(int)); sc = fc; /* start with foreground color */ for(i=0; istylelength; i++) { for(j=0; jstyle[i]; j++) { styleDashed[k] = sc; k++; } if(sc==fc) sc = bc; else sc = fc; } gdImageSetStyle(img, styleDashed, k); free(styleDashed); if(!brush && !symbol->img) gdImageArc(img, (int)p->x + ox, (int)p->y + oy, (int)(2*r), (int)(2*r), 0, 360, gdStyled); else gdImageArc(img, (int)p->x + ox, (int)p->y + oy, (int)(2*r), (int)(2*r), 0, 360, gdStyledBrushed); } else { if(!brush && !symbol->img) gdImageArc(img, (int)p->x + ox, (int)p->y + oy, (int)(2*r), (int)(2*r), 0, 360, fc); else gdImageArc(img, (int)p->x + ox, (int)p->y + oy, (int)(2*r), (int)(2*r), 0, 360, gdBrushed); } return; } /* ------------------------------------------------------------------------------- */ /* Fill a circle with a shade symbol of the specified size and color */ /* ------------------------------------------------------------------------------- */ void msCircleDrawShadeSymbolGD(symbolSetObj *symbolset, gdImagePtr img, pointObj *p, double r, styleObj *style, double scalefactor) { char bRotated=MS_FALSE; symbolObj *symbol; int i; gdPoint oldpnt,newpnt; gdPoint sPoints[MS_MAXVECTORPOINTS]; gdImagePtr tile=NULL; int x, y, ox, oy; int fc, bc, oc; int tile_bc=-1, tile_fc=-1; /* colors (background and foreground) */ double size, d, angle, angle_radians; int width; int bbox[8]; rectObj rect; char *font=NULL; if(!p) return; symbol = &(symbolset->symbol[style->symbol]); if(!MS_VALID_COLOR(style->color) && MS_VALID_COLOR(style->outlinecolor) && symbol->type != MS_SYMBOL_PIXMAP) { /* use msDrawLineSymbolGD() instead (POLYLINE) */ msCircleDrawLineSymbolGD(symbolset, img, p, r, style, scalefactor); return; } if(style->backgroundcolor.pen == MS_PEN_UNSET) msImageSetPenGD(img, &(style->backgroundcolor)); if(style->color.pen == MS_PEN_UNSET) msImageSetPenGD(img, &(style->color)); if(style->outlinecolor.pen == MS_PEN_UNSET) msImageSetPenGD(img, &(style->outlinecolor)); bc = style->backgroundcolor.pen; fc = style->color.pen; oc = style->outlinecolor.pen; ox = style->offsetx; /* TODO: add scaling? */ oy = style->offsety; if(style->size == -1) { size = msSymbolGetDefaultSize( &( symbolset->symbol[style->symbol] ) ); size = MS_NINT(size*scalefactor); } else size = MS_NINT(style->size*scalefactor); size = MS_MAX(size, style->minsize); size = MS_MIN(size, style->maxsize); width = MS_NINT(style->width*scalefactor); width = MS_MAX(width, style->minwidth); width = MS_MIN(width, style->maxwidth); angle = (style->angle) ? style->angle : 0.0; angle_radians = angle*MS_DEG_TO_RAD; if(style->symbol > symbolset->numsymbols || style->symbol < 0) return; /* no such symbol, 0 is OK */ if(fc < 0 && symbol->type!=MS_SYMBOL_PIXMAP) return; /* nothing to do (colors are not required with PIXMAP symbols) */ if(size < 1) return; /* size too small */ if(style->symbol == 0) { /* solid fill */ if(style->antialias==MS_TRUE) { gdImageFilledEllipse(img, (int)p->x + ox, (int)p->y + oy, (int)(2*r), (int)(2*r), fc); if(oc>-1) gdImageSetAntiAliased(img, oc); else gdImageSetAntiAliased(img, fc); gdImageArc(img, (int)p->x, (int)p->y, (int)(2*r), (int)(2*r), 0, 360, gdAntiAliased); } else { gdImageFilledEllipse(img, (int)p->x, (int)p->y, (int)(2*r), (int)(2*r), fc); if(oc>-1) gdImageArc(img, (int)p->x, (int)p->y, (int)(2*r), (int)(2*r), 0, 360, oc); } return; /* done simple case */ } switch(symbol->type) { case(MS_SYMBOL_TRUETYPE): #ifdef USE_GD_FT font = msLookupHashTable(&(symbolset->fontset->fonts), symbol->font); if(!font) return; if(msGetCharacterSize(symbol->character, (int) size, font, &rect) != MS_SUCCESS) return; x = (int)(rect.maxx - rect.minx); y = (int)(rect.maxy - rect.miny); tile = createBrush(img, x+2*symbol->gap, y+2*symbol->gap, style, &tile_fc, &tile_bc); /* create the tile image */ x = (int) -rect.minx + symbol->gap; /* center the glyph */ y = (int) -rect.miny + symbol->gap; gdImageStringFT(tile, bbox, ((symbol->antialias || style->antialias)?(tile_fc):-(tile_fc)), font, size, 0, x, y, symbol->character); gdImageSetTile(img, tile); #endif break; case(MS_SYMBOL_PIXMAP): if (symbol->sizey) d = size/symbol->sizey; /* compute the scaling factor (d) on the unrotated symbol */ else d = 1; if (angle != 0.0 && angle != 360.0) { bRotated = MS_TRUE; symbol = msRotateSymbol(symbol, style->angle); } /* fill with the background color before drawing transparent symbol */ if(symbol->transparent == MS_TRUE && bc > -1) gdImageFilledEllipse(img, (int)p->x, (int)p->y, (int)(2*r), (int)(2*r), bc); if(d == 1) /* use symbol->img "as is", this should be the most common case */ gdImageSetTile(img, symbol->img); else { tile = createBrush(img, d*symbol->sizex, d*symbol->sizey, style, &tile_fc, &tile_bc); gdImageCopyResampled(tile, symbol->img, 0, 0, 0, 0, tile->sx, tile->sy, symbol->img->sx, symbol->img->sy); gdImageSetTile(img, tile); } break; case(MS_SYMBOL_ELLIPSE): d = size/symbol->sizey; /* size ~ height in pixels */ x = MS_NINT(symbol->sizex*d)+1; y = MS_NINT(symbol->sizey*d)+1; if((x <= 1) && (y <= 1)) { /* No sense using a tile, just fill solid */ gdImageFilledEllipse(img, (int)p->x, (int)p->y, (int)(2*r), (int)(2*r), fc); if(oc>-1) gdImageArc(img, (int)p->x, (int)p->y, (int)(2*r), (int)(2*r), 0, 360, oc); return; } tile = createBrush(img, x, y, style, &tile_fc, &tile_bc); /* create tile image */ x = MS_NINT(tile->sx/2); /* center the ellipse */ y = MS_NINT(tile->sy/2); /* draw in the tile image */ if(symbol->filled) gdImageFilledEllipse(tile, x, y, MS_NINT(d*symbol->points[0].x), MS_NINT(d*symbol->points[0].y), tile_fc); else gdImageArc(tile, x, y, MS_NINT(d*symbol->points[0].x), MS_NINT(d*symbol->points[0].y), 0, 360, tile_fc); gdImageSetTile(img, tile); break; case(MS_SYMBOL_VECTOR): d = size/symbol->sizey; /* compute the scaling factor (d) on the unrotated symbol */ if (angle != 0.0 && angle != 360.0) { bRotated = MS_TRUE; symbol = msRotateSymbol(symbol, style->angle); } x = MS_NINT(symbol->sizex*d)+1; y = MS_NINT(symbol->sizey*d)+1; if((x <= 1) && (y <= 1)) { /* No sense using a tile, just fill solid */ gdImageFilledEllipse(img, (int)p->x, (int)p->y, (int)(2*r), (int)(2*r), fc); if(oc>-1) gdImageArc(img, (int)p->x, (int)p->y, (int)(2*r), (int)(2*r), 0, 360, oc); return; } tile = createBrush(img, x, y, style, &tile_fc, &tile_bc); /* create tile image */ /* draw in the tile image */ if(symbol->filled) { for(i=0;i < symbol->numpoints;i++) { sPoints[i].x = MS_NINT(d*symbol->points[i].x); sPoints[i].y = MS_NINT(d*symbol->points[i].y); } gdImageFilledPolygon(tile, sPoints, symbol->numpoints, tile_fc); } else { /* shade is a vector drawing */ oldpnt.x = MS_NINT(d*symbol->points[0].x); /* convert first point in shade smbol */ oldpnt.y = MS_NINT(d*symbol->points[0].y); gdImageSetThickness(tile, width); /* step through the shade symbol */ for(i=1;i < symbol->numpoints;i++) { if((symbol->points[i].x < 0) && (symbol->points[i].y < 0)) { oldpnt.x = MS_NINT(d*symbol->points[i].x); oldpnt.y = MS_NINT(d*symbol->points[i].y); } else { if((symbol->points[i-1].x < 0) && (symbol->points[i-1].y < 0)) { /* Last point was PENUP, now a new beginning */ oldpnt.x = MS_NINT(d*symbol->points[i].x); oldpnt.y = MS_NINT(d*symbol->points[i].y); } else { newpnt.x = MS_NINT(d*symbol->points[i].x); newpnt.y = MS_NINT(d*symbol->points[i].y); gdImageLine(tile, oldpnt.x, oldpnt.y, newpnt.x, newpnt.y, tile_fc); oldpnt = newpnt; } } } /* end for loop */ gdImageSetThickness(tile, 1); } gdImageSetTile(img, tile); break; default: break; } /* fill the circle in the main image */ gdImageFilledEllipse(img, (int)p->x, (int)p->y, (int)(2*r), (int)(2*r), gdTiled); if(oc>-1) gdImageArc(img, (int)p->x, (int)p->y, (int)(2*r), (int)(2*r), 0, 360, oc); if(tile) gdImageDestroy(tile); if(bRotated) { /* free the rotated symbol */ msFreeSymbol(symbol); msFree(symbol); } return; } /* ------------------------------------------------------------------------------- */ /* Draw a single marker symbol of the specified size and color */ /* ------------------------------------------------------------------------------- */ void msDrawMarkerSymbolGD(symbolSetObj *symbolset, gdImagePtr img, pointObj *p, styleObj *style, double scalefactor) { char bRotated=MS_FALSE; symbolObj *symbol; int offset_x, offset_y, x, y, w, h; int j, k; gdPoint oldpnt,newpnt; gdPoint mPoints[MS_MAXVECTORPOINTS]; char *error=NULL; int fc, bc, oc; double size, d, angle, angle_radians; int width; int ox, oy; int bbox[8]; rectObj rect; char *font=NULL; if(!p) return; if(style->backgroundcolor.pen == MS_PEN_UNSET) msImageSetPenGD(img, &(style->backgroundcolor)); if(style->color.pen == MS_PEN_UNSET) msImageSetPenGD(img, &(style->color)); if(style->outlinecolor.pen == MS_PEN_UNSET) msImageSetPenGD(img, &(style->outlinecolor)); symbol = &(symbolset->symbol[style->symbol]); bc = style->backgroundcolor.pen; fc = style->color.pen; oc = style->outlinecolor.pen; ox = style->offsetx; /* TODO: add scaling? */ oy = style->offsety; if(style->size == -1) { size = msSymbolGetDefaultSize( &( symbolset->symbol[style->symbol] ) ); size = MS_NINT(size*scalefactor); } else size = MS_NINT(style->size*scalefactor); size = MS_MAX(size, style->minsize); size = MS_MIN(size, style->maxsize); width = MS_NINT(style->width*scalefactor); width = MS_MAX(width, style->minwidth); width = MS_MIN(width, style->maxwidth); angle = (style->angle) ? style->angle : 0.0; angle_radians = angle*MS_DEG_TO_RAD; if(style->symbol > symbolset->numsymbols || style->symbol < 0) return; /* no such symbol, 0 is OK */ if(fc<0 && oc<0 && symbol->type != MS_SYMBOL_PIXMAP) return; /* nothing to do (color not required for a pixmap symbol) */ if(size < 1) return; /* size too small */ if(style->symbol == 0 && fc >= 0) { /* simply draw a single pixel of the specified color */ gdImageSetPixel(img, (int)(p->x + ox), (int)(p->y + oy), fc); return; } switch(symbol->type) { case(MS_SYMBOL_TRUETYPE): /* TODO: Need to leverage the image cache! */ #ifdef USE_GD_FT font = msLookupHashTable(&(symbolset->fontset->fonts), symbol->font); if(!font) return; if(msGetCharacterSize(symbol->character, (int)size, font, &rect) != MS_SUCCESS) return; x = (int)(p->x + ox - (rect.maxx - rect.minx)/2 - rect.minx); y = (int)(p->y + oy - rect.maxy + (rect.maxy - rect.miny)/2); if( oc >= 0 ) { error = gdImageStringFT(img, bbox, ((symbol->antialias || style->antialias)?(oc):-(oc)), font, size, angle_radians, x, y-1, symbol->character); if(error) { msSetError(MS_TTFERR, error, "msDrawMarkerSymbolGD()"); return; } gdImageStringFT(img, bbox, ((symbol->antialias || style->antialias)?(oc):-(oc)), font, size, angle_radians, x, y+1, symbol->character); gdImageStringFT(img, bbox, ((symbol->antialias || style->antialias)?(oc):-(oc)), font, size, angle_radians, x+1, y, symbol->character); gdImageStringFT(img, bbox, ((symbol->antialias || style->antialias)?(oc):-(oc)), font, size, angle_radians, x-1, y, symbol->character); gdImageStringFT(img, bbox, ((symbol->antialias || style->antialias)?(oc):-(oc)), font, size, angle_radians, x+1, y+1, symbol->character); gdImageStringFT(img, bbox, ((symbol->antialias || style->antialias)?(oc):-(oc)), font, size, angle_radians, x+1, y-1, symbol->character); gdImageStringFT(img, bbox, ((symbol->antialias || style->antialias)?(oc):-(oc)), font, size, angle_radians, x-1, y+1, symbol->character); gdImageStringFT(img, bbox, ((symbol->antialias || style->antialias)?(oc):-(oc)), font, size, angle_radians, x-1, y-1, symbol->character); } gdImageStringFT(img, bbox, ((symbol->antialias || style->antialias)?(fc):-(fc)), font, size, angle_radians, x, y, symbol->character); #endif break; case(MS_SYMBOL_PIXMAP): if (symbol->sizey) d = size/symbol->sizey; /* compute the scaling factor (d) on the unrotated symbol */ else d = 1; if (angle != 0.0 && angle != 360.0) { bRotated = MS_TRUE; symbol = msRotateSymbol(symbol, style->angle); } if(d == 1) { /* don't scale */ offset_x = MS_NINT(p->x - .5*symbol->img->sx + ox); offset_y = MS_NINT(p->y - .5*symbol->img->sy + oy); gdImageCopy(img, symbol->img, offset_x, offset_y, 0, 0, symbol->img->sx, symbol->img->sy); } else { offset_x = MS_NINT(p->x - .5*symbol->img->sx*d + ox); offset_y = MS_NINT(p->y - .5*symbol->img->sy*d + oy); gdImageCopyResampled(img, symbol->img, offset_x, offset_y, 0, 0, (int)(symbol->img->sx*d), (int)(symbol->img->sy*d), symbol->img->sx, symbol->img->sy); } if(bRotated) { msFreeSymbol(symbol); /* clean up */ msFree(symbol); } break; case(MS_SYMBOL_ELLIPSE): w = MS_NINT((size*symbol->sizex/symbol->sizey)); /* ellipse size */ h = MS_NINT(size); x = MS_NINT(p->x + ox); /* GD will center the ellipse on x,y */ y = MS_NINT(p->y + oy); /* check for trivial cases - 1x1 and 2x2, GD does not do these well */ if(w==1 && h==1) { gdImageSetPixel(img, x, y, fc); return; } if(w==2 && h==2) { gdImageSetPixel(img, x, y, fc); gdImageSetPixel(img, x, y+1, fc); gdImageSetPixel(img, x+1, y, fc); gdImageSetPixel(img, x+1, y+1, fc); return; } /* for a circle interpret the style angle as the size of the arc (for drawing pies) */ if(w == h && style->angle != 360) { if(symbol->filled) { if(fc >= 0) gdImageFilledArc(img, x, y, w, h, 0, style->angle, fc, gdEdged|gdPie); if(oc >= 0) gdImageFilledArc(img, x, y, w, h, 0, style->angle, oc, gdEdged|gdNoFill); } else if(!symbol->filled) { if(fc < 0) fc = oc; /* try the outline color */ if(fc < 0) return; gdImageFilledArc(img, x, y, w, h, 0, style->angle, fc, gdEdged|gdNoFill); } } else { if(symbol->filled) { if(fc >= 0) gdImageFilledEllipse(img, x, y, w, h, fc); if(oc >= 0) gdImageArc(img, x, y, w, h, 0, 360, oc); } else if(!symbol->filled) { if(fc < 0) fc = oc; /* try the outline color */ if(fc < 0) return; gdImageArc(img, x, y, w, h, 0, 360, fc); } } break; case(MS_SYMBOL_VECTOR): /* TODO: Need to leverage the image cache! */ d = size/symbol->sizey; /* compute the scaling factor (d) on the unrotated symbol */ if (angle != 0.0 && angle != 360.0) { bRotated = MS_TRUE; symbol = msRotateSymbol(symbol, style->angle); } /* We avoid MS_NINT in this context because the potentially variable handling of 0.5 rounding is often a problem for symbols which are often an odd size (ie. 7pixels) and so if "p" is integral the value is always on a 0.5 boundary - bug 1716 */ offset_x = MS_NINT_GENERIC(p->x - d*.5*symbol->sizex + ox); offset_y = MS_NINT_GENERIC(p->y - d*.5*symbol->sizey + oy); if(symbol->filled) { /* if filled */ k = 0; /* point counter */ for(j=0;j < symbol->numpoints;j++) { if((symbol->points[j].x < 0) && (symbol->points[j].x < 0)) { /* new polygon (PENUP) */ if(k>2) { if(fc >= 0) gdImageFilledPolygon(img, mPoints, k, fc); if(oc >= 0) gdImagePolygon(img, mPoints, k, oc); } k = 0; /* reset point counter */ } else { mPoints[k].x = MS_NINT(d*symbol->points[j].x + offset_x); mPoints[k].y = MS_NINT(d*symbol->points[j].y + offset_y); k++; } } if(fc >= 0) gdImageFilledPolygon(img, mPoints, k, fc); if(oc >= 0) gdImagePolygon(img, mPoints, k, oc); } else { /* NOT filled */ if(fc < 0) fc = oc; /* try the outline color (reference maps sometimes do this when combining a box and a custom vector marker */ if(fc < 0) return; oldpnt.x = MS_NINT(d*symbol->points[0].x + offset_x); /* convert first point in marker */ oldpnt.y = MS_NINT(d*symbol->points[0].y + offset_y); gdImageSetThickness(img, width); for(j=1;j < symbol->numpoints;j++) { /* step through the marker */ if((symbol->points[j].x < 0) && (symbol->points[j].x < 0)) { oldpnt.x = MS_NINT(d*symbol->points[j].x + offset_x); oldpnt.y = MS_NINT(d*symbol->points[j].y + offset_y); } else { if((symbol->points[j-1].x < 0) && (symbol->points[j-1].y < 0)) { /* Last point was PENUP, now a new beginning */ oldpnt.x = MS_NINT(d*symbol->points[j].x + offset_x); oldpnt.y = MS_NINT(d*symbol->points[j].y + offset_y); } else { newpnt.x = MS_NINT(d*symbol->points[j].x + offset_x); newpnt.y = MS_NINT(d*symbol->points[j].y + offset_y); gdImageLine(img, oldpnt.x, oldpnt.y, newpnt.x, newpnt.y, fc); oldpnt = newpnt; } } } /* end for loop */ gdImageSetThickness(img, 1); /* restore thinkness */ } /* end if-then-else */ if(bRotated) { msFreeSymbol(symbol); /* clean up */ msFree(symbol); } break; default: break; } /* end switch statement */ return; } /* ------------------------------------------------------------------------------- */ /* Draw a line symbol of the specified size and color */ /* ------------------------------------------------------------------------------- */ void msDrawLineSymbolGD(symbolSetObj *symbolset, gdImagePtr img, shapeObj *p, styleObj *style, double scalefactor) { int i, j, k; symbolObj *symbol, *oldsymbol=NULL; int x, y; int ox, oy; int fc, bc; int brush_bc, brush_fc; double size, d, angle; int width; gdImagePtr brush=NULL; gdPoint points[MS_MAXVECTORPOINTS]; gdPoint oldpnt, newpnt; if(!p) return; if(p->numlines <= 0) return; if(style->backgroundcolor.pen == MS_PEN_UNSET) msImageSetPenGD(img, &(style->backgroundcolor)); if(style->outlinecolor.pen == MS_PEN_UNSET) msImageSetPenGD(img, &(style->outlinecolor)); if(style->color.pen == MS_PEN_UNSET) msImageSetPenGD(img, &(style->color)); symbol = &(symbolset->symbol[style->symbol]); bc = style->backgroundcolor.pen; fc = style->color.pen; if(fc==-1) fc = style->outlinecolor.pen; width = style->width; if(style->size == -1) size = msSymbolGetDefaultSize( &( symbolset->symbol[style->symbol] ) ); else size = style->size; /* TODO: Don't get this modification, is it needed elsewhere? */ /* if(size*scalefactor > style->maxsize) scalefactor = (float)style->maxsize/(float)size; */ /* if(size*scalefactor < style->minsize) scalefactor = (float)style->minsize/(float)size; */ size = MS_NINT(size*scalefactor); size = MS_MAX(size, style->minsize); size = MS_MIN(size, style->maxsize); width = MS_NINT(style->width*scalefactor); width = MS_MAX(width, style->minwidth); width = MS_MIN(width, style->maxwidth); angle = (style->angle) ? style->angle : 0.0; if(style->symbol > symbolset->numsymbols || style->symbol < 0) return; /* no such symbol, 0 is OK */ if(fc < 0 && symbol->type != MS_SYMBOL_PIXMAP) return; /* nothing to do (color not required for a pixmap symbol) */ if(size < 1) return; /* size too small */ ox = MS_NINT(style->offsetx*scalefactor); oy = (style->offsety < -90) ? style->offsety : (int)(style->offsety*scalefactor); /* ** handle the most simple case */ if(style->symbol == 0) { if(gdImageTrueColor(img) && width > 1 && style->antialias == MS_TRUE) { /* use a fuzzy brush */ if((brush = searchImageCache(symbolset->imagecache, style, width)) == NULL) { brush = createFuzzyBrush(width, gdImageRed(img, fc), gdImageGreen(img, fc), gdImageBlue(img, fc)); symbolset->imagecache = addImageCache(symbolset->imagecache, &symbolset->imagecachesize, style, width, brush); } gdImageSetBrush(img, brush); imagePolyline(img, p, gdBrushed, ox, oy); } else { gdImageSetThickness(img, width); if(style->antialias == MS_TRUE) { gdImageSetAntiAliased(img, fc); imagePolyline(img, p, gdAntiAliased, ox, oy); gdImageSetAntiAliased(img, -1); } else imagePolyline(img, p, fc, ox, oy); gdImageSetThickness(img, 1); } return; /* done with easiest case */ } switch(symbol->type) { case(MS_SYMBOL_SIMPLE): if(gdImageTrueColor(img) && width > 1 && style->antialias == MS_TRUE) { /* use a fuzzy brush */ if((brush = searchImageCache(symbolset->imagecache, style, width)) == NULL) { brush = createFuzzyBrush(width, gdImageRed(img, fc), gdImageGreen(img, fc), gdImageBlue(img, fc)); symbolset->imagecache = addImageCache(symbolset->imagecache, &symbolset->imagecachesize, style, width, brush); } gdImageSetBrush(img, brush); fc = 1; bc = 0; } else { gdImageSetThickness(img, width); if(bc == -1) bc = gdTransparent; } break; case(MS_SYMBOL_TRUETYPE): msImageTruetypePolyline(symbolset, img, p, style, scalefactor); return; break; case(MS_SYMBOL_CARTOLINE): msImageCartographicPolyline(img, p, style, symbol, fc, size, scalefactor); return; break; case(MS_SYMBOL_ELLIPSE): bc = gdTransparent; d = (size)/symbol->sizey; x = MS_NINT(symbol->sizex*d); y = MS_NINT(symbol->sizey*d); if((x < 2) && (y < 2)) break; /* no need for a brush */ if(gdImageTrueColor(img) && x > 1 && style->antialias == MS_TRUE && x == y) { /* use a fuzzy brush */ /* create the brush image if not already in the cache */ if((brush = searchImageCache(symbolset->imagecache, style, x)) == NULL) { brush = createFuzzyBrush(x, gdImageRed(img, fc), gdImageGreen(img, fc), gdImageBlue(img, fc)); symbolset->imagecache = addImageCache(symbolset->imagecache, &symbolset->imagecachesize, style, x, brush); } } else { /* create the brush image if not already in the cache */ if((brush = searchImageCache(symbolset->imagecache, style, (int)size)) == NULL) { brush = createBrush(img, x, y, style, &brush_fc, &brush_bc); /* not in cache, create it */ x = MS_NINT(brush->sx/2); /* center the ellipse */ y = MS_NINT(brush->sy/2); /* draw in the brush image */ if(symbol->filled) gdImageFilledEllipse(brush, x, y, MS_NINT(d*symbol->points[0].x), MS_NINT(d*symbol->points[0].y), brush_fc); else gdImageArc(brush, x, y, MS_NINT(d*symbol->points[0].x), MS_NINT(d*symbol->points[0].y), 0, 360, brush_fc); symbolset->imagecache = addImageCache(symbolset->imagecache, &symbolset->imagecachesize, style, (int)size, brush); } } gdImageSetBrush(img, brush); fc = 1; bc = 0; break; case(MS_SYMBOL_PIXMAP): /* todo: add scaling, offset and rotation */ gdImageSetBrush(img, symbol->img); fc = 1; bc = 0; break; case(MS_SYMBOL_VECTOR): if(bc == -1) bc = gdTransparent; d = (size)/symbol->sizey; /* compute the scaling factor (d) on the unrotated symbol */ if(angle != 0.0 && angle != 360.0) { /* rotate the symbol creating a new symbol object */ oldsymbol = symbol; symbol = msRotateSymbol(symbol, angle); } x = MS_NINT(symbol->sizex*d)+1; y = MS_NINT(symbol->sizey*d)+1; if (x < (symbol->sizex*d)) x += 1; if (y < (symbol->sizey*d)) y += 1; if((x < 2) && (y < 2)) break; /* create the brush image */ if((brush = searchImageCache(symbolset->imagecache, style, (int)size)) == NULL) { brush = createBrush(img, x, y, style, &brush_fc, &brush_bc); /* not in cache, create it */ if (symbol->filled) { k = 0; /* point counter */ for(i=0;i < symbol->numpoints;i++) { if((symbol->points[i].x < 0) && (symbol->points[i].x < 0)) { /* new polygon (PENUP) */ if(k>2) gdImageFilledPolygon(brush, points, k, brush_fc); k = 0; /* reset point counter */ } else { points[k].x = MS_NINT(d*symbol->points[i].x); points[k].y = MS_NINT(d*symbol->points[i].y); k++; } } if(k>2) gdImageFilledPolygon(brush, points, k, brush_fc); } else { oldpnt.x = MS_NINT(symbol->points[0].x*d); /* convert first point in symbol */ oldpnt.y = MS_NINT(symbol->points[0].y*d); /* draw in the brush image */ for (i=1;i < symbol->numpoints;i++) { if((symbol->points[i].x == -99.) && (symbol->points[i].y == -99.)) { oldpnt.x = MS_NINT(symbol->points[i].x*d); oldpnt.y = MS_NINT(symbol->points[i].y*d); } else { if ((symbol->points[i-1].x == -99.) && (symbol->points[i-1].y == -99.)) { /* Last point was PENUP, now a new beginning */ oldpnt.x = MS_NINT(symbol->points[i].x*d); oldpnt.y = MS_NINT(symbol->points[i].y*d); } else { newpnt.x = MS_NINT(symbol->points[i].x*d); newpnt.y = MS_NINT(symbol->points[i].y*d); gdImageLine(brush, oldpnt.x, oldpnt.y, newpnt.x, newpnt.y, brush_fc); oldpnt = newpnt; } } } } symbolset->imagecache = addImageCache(symbolset->imagecache, &symbolset->imagecachesize, style, (int)size, brush); } gdImageSetBrush(img, brush); fc = 1; bc = 0; break; } /* symbol type end-switch */ if(symbol->stylelength > 0) { int *styleDashed; int k=0, sc; /* Alloc styleDashed array large enough for this style */ int numElemStyle=0; for(i=0; istylelength; i++) { numElemStyle += symbol->style[i]; } styleDashed = (int *) malloc(numElemStyle * sizeof(int)); sc = fc; /* start with foreground color */ /* todo: scale the style/pattern */ for(i=0; istylelength; i++) { for(j=0; jstyle[i]; j++) { styleDashed[k] = sc; k++; } if(sc==fc) sc = bc; else sc = fc; } gdImageSetStyle(img, styleDashed, k); free(styleDashed); if(!brush && !symbol->img) imagePolyline(img, p, gdStyled, ox, oy); else imagePolyline(img, p, gdStyledBrushed, ox, oy); } else { if(!brush && !symbol->img) { if(style->antialias==MS_TRUE) { gdImageSetAntiAliased(img, fc); imagePolyline(img, p, gdAntiAliased, ox, oy); gdImageSetAntiAliased(img, -1); } else { imagePolyline(img, p, fc, ox, oy); } } else imagePolyline(img, p, gdBrushed, ox, oy); } /* clean up */ gdImageSetThickness(img, 1); if(oldsymbol) { msFreeSymbol(symbol); /* delete rotated version */ symbol = oldsymbol; } /* note, we don't free any brush because we are using the image cache for all line brushes and that is free'd later */ return; } /* ------------------------------------------------------------------------------- */ /* Draw a shade symbol of the specified size and color */ /* ------------------------------------------------------------------------------- */ void msDrawShadeSymbolGD(symbolSetObj *symbolset, gdImagePtr img, shapeObj *p, styleObj *style, double scalefactor) { char bRotated=MS_FALSE; symbolObj *symbol; int i, k; gdPoint oldpnt, newpnt; gdPoint sPoints[MS_MAXVECTORPOINTS]; gdImagePtr tile=NULL; int x, y, ox, oy; int tile_bc=-1, tile_fc=-1; /* colors (background and foreground) */ int fc, bc, oc; double size, d, angle, angle_radians; int width; int bbox[8]; rectObj rect; char *font=NULL; if(!p) return; if(p->numlines <= 0) return; if(style->backgroundcolor.pen == MS_PEN_UNSET) msImageSetPenGD(img, &(style->backgroundcolor)); if(style->color.pen == MS_PEN_UNSET) msImageSetPenGD(img, &(style->color)); if(style->outlinecolor.pen == MS_PEN_UNSET) msImageSetPenGD(img, &(style->outlinecolor)); symbol = &(symbolset->symbol[style->symbol]); bc = style->backgroundcolor.pen; fc = style->color.pen; oc = style->outlinecolor.pen; if(style->size == -1) { size = msSymbolGetDefaultSize( &( symbolset->symbol[style->symbol] ) ); size = MS_NINT(size*scalefactor); } else size = MS_NINT(style->size*scalefactor); size = MS_MAX(size, style->minsize); size = MS_MIN(size, style->maxsize); width = MS_NINT(style->width*scalefactor); width = MS_MAX(width, style->minwidth); width = MS_MIN(width, style->maxwidth); angle = (style->angle) ? style->angle : 0.0; angle_radians = angle*MS_DEG_TO_RAD; ox = MS_NINT(style->offsetx*scalefactor); /* should we scale the offsets? */ oy = MS_NINT(style->offsety*scalefactor); if(fc==-1 && oc!=-1 && symbol->type!=MS_SYMBOL_PIXMAP) { /* use msDrawLineSymbolGD() instead (POLYLINE) */ msDrawLineSymbolGD(symbolset, img, p, style, scalefactor); return; } if(style->symbol > symbolset->numsymbols || style->symbol < 0) return; /* no such symbol, 0 is OK */ if(fc < 0 && symbol->type!=MS_SYMBOL_PIXMAP) return; /* nothing to do (colors are not required with PIXMAP symbols) */ if(size < 1) return; /* size too small */ if(style->symbol == 0) { /* simply draw a single pixel of the specified color */ if(style->antialias==MS_TRUE) { imageFilledPolygon(img, p, fc, ox, oy); /* fill is NOT anti-aliased, the outline IS */ if(oc>-1) gdImageSetAntiAliased(img, oc); else gdImageSetAntiAliased(img, fc); imagePolyline(img, p, gdAntiAliased, ox, oy); } else { imageFilledPolygon(img, p, fc, ox, oy); if(oc>-1) imagePolyline(img, p, oc, ox, oy); } return; } switch(symbol->type) { case(MS_SYMBOL_HATCH): msComputeBounds(p); /* we need to know how big to make the tile */ /* tile = createHatch(img, (p->bounds.maxx-p->bounds.minx), (p->bounds.maxy-p->bounds.miny), style); */ tile = createHatch(img, img->sx, img->sy, &p->bounds, style, scalefactor); gdImageSetTile(img, tile); imageFilledPolygon(img, p, gdTiled, ox, oy); if(style->antialias==MS_TRUE && oc>-1) { gdImageSetAntiAliased(img, oc); imagePolyline(img, p, gdAntiAliased, ox, oy); } else if(oc>-1) imagePolyline(img, p, oc, ox, oy); gdImageDestroy(tile); break; case(MS_SYMBOL_TRUETYPE): #ifdef USE_GD_FT font = msLookupHashTable(&(symbolset->fontset->fonts), symbol->font); if(!font) return; if(msGetCharacterSize(symbol->character, (int)size, font, &rect) != MS_SUCCESS) return; x = (int)(rect.maxx - rect.minx); y = (int)(rect.maxy - rect.miny); /* create tile image */ tile = createBrush(img, x+2*symbol->gap, y+2*symbol->gap, style, &tile_fc, &tile_bc); /* center the glyph */ x = (int)-rect.minx + symbol->gap; y = (int)-rect.miny + symbol->gap; gdImageStringFT(tile, bbox, ((symbol->antialias || style->antialias)?(tile_fc):-(tile_fc)), font, size, 0, x, y, symbol->character); gdImageSetTile(img, tile); imageFilledPolygon(img, p, gdTiled, ox, oy); if(style->antialias==MS_TRUE && oc>-1) { gdImageSetAntiAliased(img, oc); imagePolyline(img, p, gdAntiAliased, ox, oy); } else if(oc>-1) imagePolyline(img, p, oc, ox, oy); gdImageDestroy(tile); #endif break; case(MS_SYMBOL_PIXMAP): if (symbol->sizey) d = size/symbol->sizey; /* compute the scaling factor (d) on the unrotated symbol */ else d = 1; if (angle != 0.0 && angle != 360.0) { bRotated = MS_TRUE; symbol = msRotateSymbol(symbol, style->angle); } if(d == 1) /* use symbol->img "as is", no scaling, this should be the most common case */ gdImageSetTile(img, symbol->img); else { tile = createBrush(img, d*symbol->sizex, d*symbol->sizey, style, &tile_fc, &tile_bc); gdImageCopyResampled(tile, symbol->img, 0, 0, 0, 0, tile->sx, tile->sy, symbol->img->sx, symbol->img->sy); gdImageSetTile(img, tile); } /* fill with the background color before drawing transparent symbol */ if(symbol->transparent == MS_TRUE && bc > -1) imageFilledPolygon(img, p, bc, ox, oy); imageFilledPolygon(img, p, gdTiled, ox, oy); if(style->antialias==MS_TRUE && oc>-1) { gdImageSetAntiAliased(img, oc); imagePolyline(img, p, gdAntiAliased, ox, oy); } else if(oc>-1) imagePolyline(img, p, oc, ox, oy); if(bRotated) { /* free the rotated symbol */ msFreeSymbol(symbol); msFree(symbol); } if(tile) /* we created a new tile */ gdImageDestroy(tile); break; case(MS_SYMBOL_ELLIPSE): d = size/symbol->sizey; /* size ~ height in pixels */ x = MS_NINT(symbol->sizex*d)+1; y = MS_NINT(symbol->sizey*d)+1; if((x <= 1) && (y <= 1)) { /* No sense using a tile, just fill solid */ if(style->antialias==MS_TRUE) { imageFilledPolygon(img, p, fc, ox, oy); /* fill is NOT anti-aliased, the outline IS */ if(oc>-1) gdImageSetAntiAliased(img, oc); else gdImageSetAntiAliased(img, fc); imagePolyline(img, p, gdAntiAliased, ox, oy); } else { imageFilledPolygon(img, p, fc, oy, oy); if(oc>-1) imagePolyline(img, p, oc, ox, oy); } return; } /* create tile image */ tile = createBrush(img, x, y, style, &tile_fc, &tile_bc); /* center ellipse */ x = MS_NINT(tile->sx/2); y = MS_NINT(tile->sy/2); /* draw in tile image */ if(symbol->filled) gdImageFilledEllipse(tile, x, y, MS_NINT(d*symbol->points[0].x), MS_NINT(d*symbol->points[0].y), tile_fc); else gdImageArc(tile, x, y, MS_NINT(d*symbol->points[0].x), MS_NINT(d*symbol->points[0].y), 0, 360, tile_fc); /* fill the polygon in the main image */ gdImageSetTile(img, tile); imageFilledPolygon(img, p, gdTiled, ox, oy); if(style->antialias==MS_TRUE && oc>-1) { gdImageSetAntiAliased(img, oc); imagePolyline(img, p, gdAntiAliased, ox, oy); } else if(oc>-1) imagePolyline(img, p, oc, ox, oy); gdImageDestroy(tile); break; case(MS_SYMBOL_VECTOR): d = size/symbol->sizey; /* compute the scaling factor (d) on the unrotated symbol */ if (angle != 0.0 && angle != 360.0) { bRotated = MS_TRUE; symbol = msRotateSymbol(symbol, style->angle); } x = MS_NINT(symbol->sizex*d)+1; y = MS_NINT(symbol->sizey*d)+1; if((x <= 1) && (y <= 1)) { /* No sense using a tile, just fill solid */ if(style->antialias==MS_TRUE) { imageFilledPolygon(img, p, fc, ox, oy); /* fill is NOT anti-aliased, the outline IS */ if(oc>-1) gdImageSetAntiAliased(img, oc); else gdImageSetAntiAliased(img, fc); imagePolyline(img, p, gdAntiAliased, ox, oy); } else { imageFilledPolygon(img, p, fc, ox, oy); if(oc>-1) imagePolyline(img, p, oc, ox, oy); } return; } if (angle != 0.0 && angle != 360.0) { bRotated = MS_TRUE; symbol = msRotateSymbol(symbol, style->angle); } /* create tile image */ tile = createBrush(img, x, y, style, &tile_fc, &tile_bc); /* draw in the tile image */ if(symbol->filled) { k = 0; /* point counter */ for(i=0;i < symbol->numpoints;i++) { if((symbol->points[i].x < 0) && (symbol->points[i].x < 0)) { /* new polygon (PENUP) */ if(k>2) gdImageFilledPolygon(tile, sPoints, k, tile_fc); k = 0; /* reset point counter */ } else { sPoints[k].x = MS_NINT(d*symbol->points[i].x); sPoints[k].y = MS_NINT(d*symbol->points[i].y); k++; } } if(k>2) gdImageFilledPolygon(tile, sPoints, k, tile_fc); } else { /* shade is a vector drawing */ oldpnt.x = MS_NINT(d*symbol->points[0].x); /* convert first point in shade smbol */ oldpnt.y = MS_NINT(d*symbol->points[0].y); gdImageSetThickness(tile, width); /* step through the shade sy */ for(i=1;i < symbol->numpoints;i++) { if((symbol->points[i].x < 0) && (symbol->points[i].y < 0)) { oldpnt.x = MS_NINT(d*symbol->points[i].x); oldpnt.y = MS_NINT(d*symbol->points[i].y); } else { if((symbol->points[i-1].x < 0) && (symbol->points[i-1].y < 0)) { /* Last point was PENUP, now a new beginning */ oldpnt.x = MS_NINT(d*symbol->points[i].x); oldpnt.y = MS_NINT(d*symbol->points[i].y); } else { newpnt.x = MS_NINT(d*symbol->points[i].x); newpnt.y = MS_NINT(d*symbol->points[i].y); gdImageLine(tile, oldpnt.x, oldpnt.y, newpnt.x, newpnt.y, tile_fc); oldpnt = newpnt; } } } /* end for loop */ gdImageSetThickness(tile, 1); } /* Fill the polygon in the main image */ gdImageSetTile(img, tile); imageFilledPolygon(img, p, gdTiled, ox, oy); if(style->antialias==MS_TRUE && oc>-1) { gdImageSetAntiAliased(img, oc); imagePolyline(img, p, gdAntiAliased, ox, oy); } else if(oc>-1) imagePolyline(img, p, oc, ox, oy); gdImageDestroy(tile); if(bRotated) { /* free the rotated symbol */ msFreeSymbol(symbol); msFree(symbol); } break; default: break; } return; } /* ---------------------------------------------------------- */ /* The basic function for drawing a cartographic line symbol */ /* of the specified size, color */ /* ---------------------------------------------------------- */ static void RenderCartoLine(gdImagePtr img, int gox, double *acoord, double *bcoord, double da, double db, double angle_n, double size, int c, symbolObj *symbol, double styleCoef, double *styleSize, int *styleIndex, int *styleVis, gdPoint *points, double *da_px, double *db_px, int offseta, int offsetb, int antialias) { double an, bn, da_pxn, db_pxn, d_size, a, b; double d_step_coef, da_pxn_coef, db_pxn_coef, da_px_coef, db_px_coef; int db_line, first, drawpoly=0; gdPoint poly_points[4]; static double last_style_size; /* Steps for one pixel */ *da_px = da/MS_ABS(db); *db_px = MS_SGN(db); if (gox) { da_pxn = *db_px*sin(angle_n); db_pxn = *db_px*cos(angle_n); } else { da_pxn = *db_px*cos(angle_n); db_pxn = *db_px*sin(angle_n); } d_step_coef = 1; da_px_coef = *da_px*d_step_coef; db_px_coef = *db_px*d_step_coef; da_pxn_coef = da_pxn*d_step_coef; db_pxn_coef = db_pxn*d_step_coef; if(offseta != 0 || offsetb != 0) { if (gox) { if(offseta == -99) { /* old-style offset (version 3.3 and earlier) */ *acoord += MS_SGN(db)*MS_ABS(da_pxn)*offsetb; *bcoord -= MS_SGN(da)*MS_ABS(db_pxn)*offsetb; } else { /* normal offset (eg. drop shadow) */ *acoord += offseta; *bcoord += offsetb; } } else { if(offsetb == -99) { /* old-style offset (version 3.3 and earlier) */ *acoord -= MS_SGN(db)*MS_ABS(da_pxn)*offseta; *bcoord += MS_SGN(da)*MS_ABS(db_pxn)*offseta; } else { /* normal offset (eg. drop shadow) */ *acoord += offseta; *bcoord += offsetb; } } } a = *acoord; b = *bcoord; /* if (antialias) { size -= 0.5; } else { if (!da || !db) size -= 0.5; }*/ /* Style counting */ if (symbol->stylelength > 0) { /* Style counting unit */ d_size = sqrt(pow(*da_px,2)+pow(*db_px,2))*d_step_coef; /* Line rendering */ first = 1; for (db_line = (int)(-1*d_step_coef); db_line++ < MS_ABS(db); b += db_px_coef) { /* Set the start of the first pixel */ an = a - da_pxn*(size-1)/2; bn = b - db_pxn*(size-1)/2; /* Save corners */ if (first) { if (gox) { if (MS_SGN(db) > 0) { points[0].x = MS_NINT(bn); points[0].y = MS_NINT(an); points[1].x = MS_NINT(bn+db_pxn*size); points[1].y = MS_NINT(an+da_pxn*size); } else { points[1].x = MS_NINT(bn); points[1].y = MS_NINT(an); points[0].x = MS_NINT(bn+db_pxn*size); points[0].y = MS_NINT(an+da_pxn*size); } poly_points[0].x = MS_NINT(bn); poly_points[0].y = MS_NINT(an); poly_points[1].x = MS_NINT(bn+db_pxn*size); poly_points[1].y = MS_NINT(an+da_pxn*size); } else { if (MS_SGN(db) > 0) { points[0].x = MS_NINT(an); points[0].y = MS_NINT(bn); points[1].x = MS_NINT(an+da_pxn*size); points[1].y = MS_NINT(bn+db_pxn*size); } else { points[1].x = MS_NINT(an); points[1].y = MS_NINT(bn); points[0].x = MS_NINT(an+da_pxn*size); points[0].y = MS_NINT(bn+db_pxn*size); } poly_points[0].x = MS_NINT(an); poly_points[0].y = MS_NINT(bn); poly_points[1].x = MS_NINT(an+da_pxn*size); poly_points[1].y = MS_NINT(bn+db_pxn*size); } first = 0; drawpoly = *styleVis; } if (*styleIndex == symbol->stylelength) *styleIndex = 0; *styleSize -= d_size; if (*styleSize < 0) { *styleSize = symbol->style[*styleIndex]*styleCoef; *styleSize -= d_size; (*styleIndex)++; *styleVis = *styleVis?0:1; if (*styleVis) { if (gox) { poly_points[0].x = MS_NINT(bn); poly_points[0].y = MS_NINT(an); poly_points[1].x = MS_NINT(bn+db_pxn*size); poly_points[1].y = MS_NINT(an+da_pxn*size); } else { poly_points[0].x = MS_NINT(an); poly_points[0].y = MS_NINT(bn); poly_points[1].x = MS_NINT(an+da_pxn*size); poly_points[1].y = MS_NINT(bn+db_pxn*size); } last_style_size = *styleSize+d_size; drawpoly = 1; } else { if (gox) { poly_points[3].x = MS_NINT(bn); poly_points[3].y = MS_NINT(an); poly_points[2].x = MS_NINT(bn+db_pxn*size); poly_points[2].y = MS_NINT(an+da_pxn*size); } else { poly_points[3].x = MS_NINT(an); poly_points[3].y = MS_NINT(bn); poly_points[2].x = MS_NINT(an+da_pxn*size); poly_points[2].y = MS_NINT(bn+db_pxn*size); } if (drawpoly) { if (last_style_size <= 1) { if (antialias) { /* gdImageSetAntiAliased(img, c); */ gdImageSetAntiAliasedDontBlend(img, c, c); gdImageLine(img, poly_points[0].x, poly_points[0].y, poly_points[1].x, poly_points[1].y, gdAntiAliased); } else { gdImageLine(img, poly_points[0].x, poly_points[0].y, poly_points[1].x, poly_points[1].y, c); } } else { if (antialias) { gdImageSetAntiAliasedDontBlend(img, c, c); if (size > 1) { gdImageFilledPolygon(img, poly_points, 4, gdAntiAliased); } else { gdImageLine(img, poly_points[0].x, poly_points[0].y, poly_points[3].x, poly_points[3].y, gdAntiAliased); } } else { if (size > 1) { gdImageFilledPolygon(img, poly_points, 4, c); } else { gdImageLine(img, poly_points[0].x, poly_points[0].y, poly_points[3].x, poly_points[3].y, c); } } } drawpoly = 0; } } } a += da_px_coef; } /* Solid line - not styled */ } else { /* Set the start of the first pixel */ an = a - da_pxn*(size-1)/2; bn = b - db_pxn*(size-1)/2; if (gox) { if (MS_SGN(db) > 0) { points[0].x = MS_NINT(bn); points[0].y = MS_NINT(an); points[1].x = MS_NINT(bn+db_pxn*size); points[1].y = MS_NINT(an+da_pxn*size); } else { points[1].x = MS_NINT(bn); points[1].y = MS_NINT(an); points[0].x = MS_NINT(bn+db_pxn*size); points[0].y = MS_NINT(an+da_pxn*size); } poly_points[0].x = MS_NINT(bn); poly_points[0].y = MS_NINT(an); poly_points[1].x = MS_NINT(bn+db_pxn*size); poly_points[1].y = MS_NINT(an+da_pxn*size); } else { if (MS_SGN(db) > 0) { points[0].x = MS_NINT(an); points[0].y = MS_NINT(bn); points[1].x = MS_NINT(an+da_pxn*size); points[1].y = MS_NINT(bn+db_pxn*size); } else { points[1].x = MS_NINT(an); points[1].y = MS_NINT(bn); points[0].x = MS_NINT(an+da_pxn*size); points[0].y = MS_NINT(bn+db_pxn*size); } poly_points[0].x = MS_NINT(an); poly_points[0].y = MS_NINT(bn); poly_points[1].x = MS_NINT(an+da_pxn*size); poly_points[1].y = MS_NINT(bn+db_pxn*size); } drawpoly = 1; *styleVis = 1; a += da; b += db; } /* Set the start of the first pixel */ an = a - da_pxn*(size-1)/2; bn = b - db_pxn*(size-1)/2; /* Save next corners */ if (gox) { if (MS_SGN(db) > 0) { points[3].x = MS_NINT(bn); points[3].y = MS_NINT(an); points[2].x = MS_NINT(bn+db_pxn*size); points[2].y = MS_NINT(an+da_pxn*size); } else { points[2].x = MS_NINT(bn); points[2].y = MS_NINT(an); points[3].x = MS_NINT(bn+db_pxn*size); points[3].y = MS_NINT(an+da_pxn*size); } } else { if (MS_SGN(db) > 0) { points[3].x = MS_NINT(an); points[3].y = MS_NINT(bn); points[2].x = MS_NINT(an+da_pxn*size); points[2].y = MS_NINT(bn+db_pxn*size); } else { points[2].x = MS_NINT(an); points[2].y = MS_NINT(bn); points[3].x = MS_NINT(an+da_pxn*size); points[3].y = MS_NINT(bn+db_pxn*size); } } if (drawpoly) { if (gox) { poly_points[3].x = MS_NINT(bn); poly_points[3].y = MS_NINT(an); poly_points[2].x = MS_NINT(bn+db_pxn*size); poly_points[2].y = MS_NINT(an+da_pxn*size); } else { poly_points[3].x = MS_NINT(an); poly_points[3].y = MS_NINT(bn); poly_points[2].x = MS_NINT(an+da_pxn*size); poly_points[2].y = MS_NINT(bn+db_pxn*size); } /* Antialias */ if (antialias) { /* gdImageSetAntiAliased(img, c); */ gdImageSetAntiAliasedDontBlend(img, c, c); if (size > 1) { gdImageFilledPolygon(img, poly_points, 4, gdAntiAliased); } else { gdImageLine(img, poly_points[0].x, poly_points[0].y, poly_points[3].x, poly_points[3].y, gdAntiAliased); } } else { if (size > 1) { gdImageFilledPolygon(img, poly_points, 4, c); } else { gdImageLine(img, poly_points[0].x, poly_points[0].y, poly_points[3].x, poly_points[3].y, c); } } } } /* ------------------------------------------------------------------------------- */ /* Draw a cartographic line symbol of the specified size, color, join and cap */ /* ------------------------------------------------------------------------------- */ void msImageCartographicPolyline(gdImagePtr img, shapeObj *p, styleObj *style, symbolObj *symbol, int c, double size, double scalefactor) { int i, j, k; double x, y, dx, dy, s, maxs, angle, angle_n, last_angle=0, angle_left_limit, angle_from, angle_to; double dy_px, dx_px, size_2; static int styleIndex, styleVis; static double styleSize=0, styleCoef=0, last_style_size=-1; static int last_style_c=-1, last_style_stylelength=-1, last_styleVis=0; /* char buffer[256]; */ gdPoint points[4], last_points[4]; gdPoint cap_join_points[6]; /* Style settings - continue with style on the next line from the same symbol */ if (symbol->stylelength > 0 && (last_style_c != c || last_style_size != size || last_style_stylelength != symbol->stylelength)) { styleIndex = symbol->stylelength; if(style->size == -1) { styleCoef = size/(msSymbolGetDefaultSize(symbol)); } else styleCoef = size/style->size; styleVis=0; last_styleVis=1; } last_style_c = c; last_style_size = size; last_style_stylelength = symbol->stylelength; /* Draw lines */ for (i = 0; i < p->numlines; i++) { for(j=1; jline[i].numpoints; j++) { x = p->line[i].point[j-1].x; y = p->line[i].point[j-1].y; /* dx, dy */ dx = p->line[i].point[j].x - x; dy = p->line[i].point[j].y - y; if (!dx && !dy) continue; /* A line angle */ angle = getAngleFromDxDy(dx, dy); /* Normal */ angle_n = angle - MS_PI2; /* Steps for one pixel */ if ((MS_ABS(dx) > MS_ABS(dy)) || (dx==dy)) { RenderCartoLine(img, 1, &y, &x, dy, dx, angle_n, size, c, symbol, styleCoef, &styleSize, &styleIndex, &styleVis, points, &dy_px, &dx_px, style->offsety, style->offsetx, style->antialias); } else { RenderCartoLine(img, 0, &x, &y, dx, dy, angle_n, size, c, symbol, styleCoef, &styleSize, &styleIndex, &styleVis, points, &dx_px, &dy_px, style->offsetx, style->offsety, style->antialias); } /* gdImageSetPixel(img, MS_NINT(points[0].x), MS_NINT(points[0].y), 2); */ /* gdImageLine(img, p->line[i].point[j-1].x, p->line[i].point[j-1].y, p->line[i].point[j].x, p->line[i].point[j].y, 3); */ /* sprintf(buffer, "styleVis: %d", styleVis); */ /* gdImageString(img, gdFontSmall, 10, 10, buffer, 1); */ /* Jointype */ /* if ((j > 1) && (angle != last_angle) && (last_styleVis)) { */ if (last_styleVis && (size > 3) && (j > 1) && (angle != last_angle)) { angle_left_limit = last_angle - MS_PI; switch (symbol->linejoin) { /* Miter */ case MS_CJC_NONE: /* do nothing */ break; /* Round */ case MS_CJC_ROUND: /* The current line is on the left site of the last line */ if ((angle_left_limit > 0 && angle_left_limit < angle && angle < last_angle) || (angle_left_limit < 0 && ((0 <= angle && angle < last_angle) || (angle_left_limit+MS_2PI < angle)))) { angle_from = angle_n - MS_PI; angle_to = last_angle - MS_PI2 - MS_PI; /* The current line is on the right site of the last line */ } else { angle_from = last_angle - MS_PI2; angle_to = angle_n; } if (angle_from > angle_to) angle_from -= MS_2PI; angle_from -= MS_DEG_TO_RAD*2; angle_to += MS_DEG_TO_RAD*2; imageFilledSegment(img, x, y, (size-1)/2, angle_from, angle_to, c, style->antialias); break; /* Miter */ case MS_CJC_MITER: /* Bevel */ case MS_CJC_BEVEL: /* The current line is on the left site of the last line */ if ((angle_left_limit > 0 && angle_left_limit < angle && angle < last_angle) || (angle_left_limit < 0 && ((0 <= angle && angle < last_angle) || (angle_left_limit+MS_2PI < angle)))) { cap_join_points[0].x = MS_NINT(last_points[3].x); cap_join_points[0].y = MS_NINT(last_points[3].y); cap_join_points[2].x = MS_NINT(points[0].x); cap_join_points[2].y = MS_NINT(points[0].y); if ((angle == last_angle + MS_PI) || (angle == last_angle - MS_PI)) { cap_join_points[1].x = MS_NINT(x+dx_px*size*symbol->linejoinmaxsize); cap_join_points[1].y = MS_NINT(y+dy_px*size*symbol->linejoinmaxsize); } else { cap_join_points[1] = generateGDLineIntersection(last_points[0], last_points[3], points[0], points[3]); } /* The current line is on the right site of the last line */ } else { cap_join_points[0].x = MS_NINT(last_points[2].x); cap_join_points[0].y = MS_NINT(last_points[2].y); cap_join_points[2].x = MS_NINT(points[1].x); cap_join_points[2].y = MS_NINT(points[1].y); if ((angle == last_angle + MS_PI) || (angle == last_angle - MS_PI)) { cap_join_points[1].x = MS_NINT(x-dx_px*size*symbol->linejoinmaxsize); cap_join_points[1].y = MS_NINT(y-dy_px*size*symbol->linejoinmaxsize); } else { cap_join_points[1] = generateGDLineIntersection(last_points[1], last_points[2], points[1], points[2]); } } /* Check the max join size */ dx = cap_join_points[1].x - x; dy = cap_join_points[1].y - y; s = sqrt(pow(dx,2)+pow(dy,2)); if (symbol->linejoin == MS_CJC_MITER) { /* Miter */ maxs = size*symbol->linejoinmaxsize; if (s > maxs) { s = s/maxs; dx = dx/s; dy = dy/s; cap_join_points[1].x = MS_NINT(x + dx); cap_join_points[1].y = MS_NINT(y + dy); } s = s?s:1; cap_join_points[3].x = MS_NINT(x - dx/s); cap_join_points[3].y = MS_NINT(y - dy/s); ImageFilledPolygonAA(img, cap_join_points, 4, c, style->antialias); /* Bevel */ } else { s = s?s:1; cap_join_points[1] = cap_join_points[2]; cap_join_points[2].x = MS_NINT(x - dx/s); cap_join_points[2].y = MS_NINT(y - dy/s); ImageFilledPolygonAA(img, cap_join_points, 3, c, style->antialias); } break; } } /* Captype */ if (symbol->linecap != MS_CJC_BUTT && size > 3 && ((j == 1) || (j == (p->line[i].numpoints-1)))) { size_2 = size/sqrt(pow(dx_px,2)+pow(dy_px,2))/2; switch (symbol->linecap) { /* Butt */ case MS_CJC_BUTT: /* do nothing */ break; /* Round */ case MS_CJC_ROUND: /* First point */ if (j == 1) { dx = points[0].x - points[1].x; dy = points[0].y - points[1].y; angle_from = angle + MS_PI2; angle_from -= angle_from > MS_2PI?MS_2PI:0; angle_to = angle_from + MS_PI; s = sqrt(pow(dx,2) + pow(dy,2))/2; imageFilledSegment(img, points[0].x+dx_px-dx/2.0, points[0].y+dy_px-dy/2.0, s, angle_from, angle_to, c, style->antialias); } /* Last point */ if (j == (p->line[i].numpoints-1)) { dx = points[2].x - points[3].x; dy = points[2].y - points[3].y; angle_from = angle - MS_PI2; angle_from += angle_from < 0?MS_2PI:0; angle_to = angle_from + MS_PI; s = sqrt(pow(dx,2) + pow(dy,2))/2; imageFilledSegment(img, points[2].x-dx_px-dx/2.0, points[2].y-dy_px-dy/2.0, s, angle_from, angle_to, c, style->antialias); } break; /* Square */ case MS_CJC_SQUARE: /* First point */ if (j == 1) { cap_join_points[0].x = MS_NINT(points[0].x+dx_px); cap_join_points[0].y = MS_NINT(points[0].y+dy_px); cap_join_points[1].x = MS_NINT(points[1].x+dx_px); cap_join_points[1].y = MS_NINT(points[1].y+dy_px); cap_join_points[2].x = MS_NINT(points[1].x - size_2*dx_px); cap_join_points[2].y = MS_NINT(points[1].y - size_2*dy_px); cap_join_points[3].x = MS_NINT(points[0].x - size_2*dx_px); cap_join_points[3].y = MS_NINT(points[0].y - size_2*dy_px); ImageFilledPolygonAA(img, cap_join_points, 4, c, style->antialias); } /* Last point */ if (j == (p->line[i].numpoints-1)) { cap_join_points[0].x = MS_NINT(points[2].x-dx_px); cap_join_points[0].y = MS_NINT(points[2].y-dy_px); cap_join_points[1].x = MS_NINT(points[3].x-dx_px); cap_join_points[1].y = MS_NINT(points[3].y-dy_px); cap_join_points[2].x = MS_NINT(points[3].x + size_2*dx_px); cap_join_points[2].y = MS_NINT(points[3].y + size_2*dy_px); cap_join_points[3].x = MS_NINT(points[2].x + size_2*dx_px); cap_join_points[3].y = MS_NINT(points[2].y + size_2*dy_px); ImageFilledPolygonAA(img, cap_join_points, 4, c, style->antialias); } break; /* Triangle */ case MS_CJC_TRIANGLE: /* First point */ if (j == 1) { cap_join_points[0].x = MS_NINT(points[0].x+dx_px); cap_join_points[0].y = MS_NINT(points[0].y+dy_px); cap_join_points[1].x = MS_NINT(points[1].x+dx_px); cap_join_points[1].y = MS_NINT(points[1].y+dy_px); cap_join_points[2].x = MS_NINT((points[0].x+points[1].x)/2 - size_2*dx_px); cap_join_points[2].y = MS_NINT((points[0].y+points[1].y)/2 - size_2*dy_px); ImageFilledPolygonAA(img, cap_join_points, 3, c, style->antialias); } /* Last point */ if (j == (p->line[i].numpoints-1)) { cap_join_points[0].x = MS_NINT(points[2].x-dx_px); cap_join_points[0].y = MS_NINT(points[2].y-dy_px); cap_join_points[1].x = MS_NINT(points[3].x-dx_px); cap_join_points[1].y = MS_NINT(points[3].y-dy_px); cap_join_points[2].x = MS_NINT((points[2].x+points[3].x)/2 + size_2*dx_px); cap_join_points[2].y = MS_NINT((points[2].y+points[3].y)/2 + size_2*dy_px); ImageFilledPolygonAA(img, cap_join_points, 3, c, style->antialias); } break; } } /* gdImageLine(img, points[0].x, points[0].y, points[1].x, points[1].y, 3); gdImageLine(img, points[1].x, points[1].y, points[2].x, points[2].y, 3); gdImageLine(img, points[2].x, points[2].y, points[3].x, points[3].y, 3); */ /* Copy the last point and angle */ for (k=0; k<4; k++) { last_points[k] = points[k]; /* gdImageSetPixel(img, MS_NINT(points[k].x), MS_NINT(points[k].y), 1); */ } last_angle = angle; last_styleVis = styleVis; } } } static void billboardGD(gdImagePtr img, shapeObj *shape, labelObj *label) { int i; shapeObj temp; msInitShape(&temp); msAddLine(&temp, &shape->line[0]); if(label->backgroundcolor.pen == MS_PEN_UNSET) msImageSetPenGD(img, &(label->backgroundcolor)); if(label->backgroundshadowcolor.pen == MS_PEN_UNSET) msImageSetPenGD(img, &(label->backgroundshadowcolor)); if(label->backgroundshadowcolor.pen >= 0) { for(i=0; ibackgroundshadowsizex; temp.line[0].point[i].y += label->backgroundshadowsizey; } imageFilledPolygon(img, &temp, label->backgroundshadowcolor.pen, 0, 0); for(i=0; ibackgroundshadowsizex; temp.line[0].point[i].y -= label->backgroundshadowsizey; } } imageFilledPolygon(img, &temp, label->backgroundcolor.pen, 0, 0); msFreeShape(&temp); } /* ** Simple charset converter. ** The return value must be freed by the caller. */ char *msGetEncodedString(const char *string, const char *encoding) { #ifdef USE_ICONV iconv_t cd = NULL; char *in, *inp; char *outp, *out = NULL; size_t len, bufsize, bufleft, status; cd = iconv_open("UTF-8", encoding); if(cd == (iconv_t)-1) { msSetError(MS_IDENTERR, "Encoding not supported by libiconv (%s).", "msGetEncodedString()", encoding); return NULL; } len = strlen(string); bufsize = len * 4; in = strdup(string); inp = in; out = (char*) malloc(bufsize); if(in == NULL || out == NULL){ msSetError(MS_MEMERR, NULL, "msGetEncodedString()"); msFree(in); iconv_close(cd); return NULL; } strcpy(out, in); outp = out; bufleft = bufsize; status = -1; while (len > 0){ status = iconv(cd, (const char**)&inp, &len, &outp, &bufleft); if(status == -1){ msFree(in); msFree(out); iconv_close(cd); return strdup(string); } } out[bufsize - bufleft] = '\0'; msFree(in); iconv_close(cd); return out; #else msSetError(MS_MISCERR, "Not implemeted since Iconv is not enabled.", "msGetEncodedString()"); return NULL; #endif } /* ** Simply draws a label based on the label point and the supplied label object. */ int msDrawTextGD(gdImagePtr img, pointObj labelPnt, char *string, labelObj *label, fontSetObj *fontset, double scalefactor) { int x, y; int oldAlphaBlending=0; if(!string) return(0); /* not errors, just don't want to do anything */ if(strlen(string) == 0) return(0); if( label->encoding != NULL ) { /* converting the label encoding */ string = msGetEncodedString(string, label->encoding); if(string == NULL) return(-1); } x = MS_NINT(labelPnt.x); y = MS_NINT(labelPnt.y); if(label->color.pen == MS_PEN_UNSET) msImageSetPenGD(img, &(label->color)); if(label->outlinecolor.pen == MS_PEN_UNSET) msImageSetPenGD(img, &(label->outlinecolor)); if(label->shadowcolor.pen == MS_PEN_UNSET) msImageSetPenGD(img, &(label->shadowcolor)); if(label->type == MS_TRUETYPE) { char *error=NULL, *font=NULL; int bbox[8]; double angle_radians = MS_DEG_TO_RAD*label->angle; double size; size = label->size*scalefactor; size = MS_MAX(size, label->minsize); size = MS_MIN(size, label->maxsize); #ifdef USE_GD_FT if(!fontset) { msSetError(MS_TTFERR, "No fontset defined.", "msDrawTextGD()"); if(label->encoding != NULL) msFree(string); return(-1); } if(!label->font) { msSetError(MS_TTFERR, "No Trueype font defined.", "msDrawTextGD()"); if(label->encoding != NULL) msFree(string); return(-1); } font = msLookupHashTable(&(fontset->fonts), label->font); if(!font) { msSetError(MS_TTFERR, "Requested font (%s) not found.", "msDrawTextGD()", label->font); if(label->encoding != NULL) msFree(string); return(-1); } if( gdImageTrueColor(img) ) { oldAlphaBlending = img->alphaBlendingFlag; gdImageAlphaBlending( img, 1 ); } if(label->outlinecolor.pen >= 0) { /* handle the outline color */ int os; /* outline size */ os = MS_NINT(ceil(size/10.0)); error = gdImageStringFT(img, bbox, ((label->antialias)?(label->outlinecolor.pen):-(label->outlinecolor.pen)), font, size, angle_radians, x, y-os, string); if(error) { if( gdImageTrueColor(img) ) gdImageAlphaBlending( img, oldAlphaBlending ); msSetError(MS_TTFERR, error, "msDrawTextGD()"); if(label->encoding != NULL) msFree(string); return(-1); } gdImageStringFT(img, bbox, ((label->antialias)?(label->outlinecolor.pen):-(label->outlinecolor.pen)), font, size, angle_radians, x, y+os, string); gdImageStringFT(img, bbox, ((label->antialias)?(label->outlinecolor.pen):-(label->outlinecolor.pen)), font, size, angle_radians, x+os, y, string); gdImageStringFT(img, bbox, ((label->antialias)?(label->outlinecolor.pen):-(label->outlinecolor.pen)), font, size, angle_radians, x-os, y, string); gdImageStringFT(img, bbox, ((label->antialias)?(label->outlinecolor.pen):-(label->outlinecolor.pen)), font, size, angle_radians, x-os, y-os, string); gdImageStringFT(img, bbox, ((label->antialias)?(label->outlinecolor.pen):-(label->outlinecolor.pen)), font, size, angle_radians, x-os, y+os, string); gdImageStringFT(img, bbox, ((label->antialias)?(label->outlinecolor.pen):-(label->outlinecolor.pen)), font, size, angle_radians, x+os, y-os, string); gdImageStringFT(img, bbox, ((label->antialias)?(label->outlinecolor.pen):-(label->outlinecolor.pen)), font, size, angle_radians, x+os, y+os, string); } if(label->shadowcolor.pen >= 0) { /* handle the shadow color */ error = gdImageStringFT(img, bbox, ((label->antialias)?(label->shadowcolor.pen):-(label->shadowcolor.pen)), font, size, angle_radians, x+label->shadowsizex, y+label->shadowsizey, string); if(error) { msSetError(MS_TTFERR, error, "msDrawTextGD()"); if(label->encoding != NULL) msFree(string); return(-1); } } gdImageStringFT(img, bbox, ((label->antialias)?(label->color.pen):-(label->color.pen)), font, size, angle_radians, x, y, string); if( gdImageTrueColor(img) ) gdImageAlphaBlending( img, oldAlphaBlending ); #else msSetError(MS_TTFERR, "TrueType font support is not available.", "msDrawTextGD()"); if(label->encoding != NULL) msFree(string); return(-1); #endif } else { /* MS_BITMAP */ char **token=NULL; int t, num_tokens; gdFontPtr fontPtr; if((fontPtr = msGetBitmapFont(label->size)) == NULL) { if(label->encoding != NULL) msFree(string); return(-1); } if(label->wrap != '\0') { if((token = split(string, label->wrap, &(num_tokens))) == NULL) { if(label->encoding != NULL) msFree(string); return(-1); } y -= fontPtr->h*num_tokens; for(t=0; toutlinecolor.pen >= 0) { gdImageString(img, fontPtr, x, y-1, (unsigned char *) token[t], label->outlinecolor.pen); gdImageString(img, fontPtr, x, y+1, (unsigned char *) token[t], label->outlinecolor.pen); gdImageString(img, fontPtr, x+1, y, (unsigned char *) token[t], label->outlinecolor.pen); gdImageString(img, fontPtr, x-1, y, (unsigned char *) token[t], label->outlinecolor.pen); gdImageString(img, fontPtr, x+1, y-1, (unsigned char *) token[t], label->outlinecolor.pen); gdImageString(img, fontPtr, x+1, y+1, (unsigned char *) token[t], label->outlinecolor.pen); gdImageString(img, fontPtr, x-1, y-1, (unsigned char *) token[t], label->outlinecolor.pen); gdImageString(img, fontPtr, x-1, y+1, (unsigned char *) token[t], label->outlinecolor.pen); } if(label->shadowcolor.pen >= 0) gdImageString(img, fontPtr, x+label->shadowsizex, y+label->shadowsizey, (unsigned char *) token[t], label->shadowcolor.pen); gdImageString(img, fontPtr, x, y, (unsigned char *) token[t], label->color.pen); y += fontPtr->h; /* shift down */ } msFreeCharArray(token, num_tokens); } else { y -= fontPtr->h; if(label->outlinecolor.pen >= 0) { gdImageString(img, fontPtr, x, y-1, (unsigned char *) string, label->outlinecolor.pen); gdImageString(img, fontPtr, x, y+1, (unsigned char *) string, label->outlinecolor.pen); gdImageString(img, fontPtr, x+1, y, (unsigned char *) string, label->outlinecolor.pen); gdImageString(img, fontPtr, x-1, y, (unsigned char *) string, label->outlinecolor.pen); gdImageString(img, fontPtr, x+1, y-1, (unsigned char *) string, label->outlinecolor.pen); gdImageString(img, fontPtr, x+1, y+1, (unsigned char *) string, label->outlinecolor.pen); gdImageString(img, fontPtr, x-1, y-1, (unsigned char *) string, label->outlinecolor.pen); gdImageString(img, fontPtr, x-1, y+1, (unsigned char *) string, label->outlinecolor.pen); } if(label->shadowcolor.pen >= 0) gdImageString(img, fontPtr, x+label->shadowsizex, y+label->shadowsizey, (unsigned char *) string, label->shadowcolor.pen); gdImageString(img, fontPtr, x, y, (unsigned char *) string, label->color.pen); } } if(label->encoding != NULL) msFree(string); return(0); } /* * Draw a label curved along a line */ int msDrawTextLineGD(gdImagePtr img, char *string, labelObj *label, labelPathObj *labelpath, fontSetObj *fontset, double scalefactor) { int oldAlphaBlending = 0; double size; int bbox[8]; int i; if ( !string ) return(0); /* do nothing */ if ( strlen(string) == 0 ) return(0); /* do nothing */ if( label->encoding != NULL ) { /* converting the label encoding */ string = msGetEncodedString(string, label->encoding); if(string == NULL) return(-1); } if(label->color.pen == MS_PEN_UNSET) msImageSetPenGD(img, &(label->color)); if(label->outlinecolor.pen == MS_PEN_UNSET) msImageSetPenGD(img, &(label->outlinecolor)); if(label->shadowcolor.pen == MS_PEN_UNSET) msImageSetPenGD(img, &(label->shadowcolor)); if(label->type == MS_TRUETYPE) { char *error=NULL, *font=NULL; char s[2]; size = label->size*scalefactor; size = MS_MAX(size, label->minsize); size = MS_MIN(size, label->maxsize); #ifdef USE_GD_FT if(!fontset) { msSetError(MS_TTFERR, "No fontset defined.", "msDrawTextLineGD()"); if(label->encoding != NULL) msFree(string); return(-1); } if(!label->font) { msSetError(MS_TTFERR, "No Trueype font defined.", "msDrawTextLineGD()"); if(label->encoding != NULL) msFree(string); return(-1); } font = msLookupHashTable(&(fontset->fonts), label->font); if(!font) { msSetError(MS_TTFERR, "Requested font (%s) not found.", "msDrawTextLineGD()", label->font); if(label->encoding != NULL) msFree(string); return(-1); } if( gdImageTrueColor(img) ) { oldAlphaBlending = img->alphaBlendingFlag; gdImageAlphaBlending( img, 1 ); } /* Iterate over the label line and draw each letter. First we render the shadow, then the outline, and finally the text. This keeps the entire shadow or entire outline below the foreground. */ for (i = 0; i < labelpath->path.numpoints; i++) { s[0] = string[i]; s[1] = '\0'; if(label->shadowcolor.pen >= 0) { /* handle the shadow color */ error = gdImageStringFT(img, bbox, ((label->antialias)?(label->shadowcolor.pen):-(label->shadowcolor.pen)), font, size, labelpath->angles[i], labelpath->path.point[i].x+label->shadowsizex, labelpath->path.point[i].y+label->shadowsizey, s); if(error) { msSetError(MS_TTFERR, error, "msDrawTextLineGD()"); if(label->encoding != NULL) msFree(string); return(-1); } } } /* Render the outline */ for (i = 0; i < labelpath->path.numpoints; i++) { int x, y; double theta; s[0] = string[i]; s[1] = '\0'; theta = labelpath->angles[i]; x = MS_NINT(labelpath->path.point[i].x); y = MS_NINT(labelpath->path.point[i].y); if(label->outlinecolor.pen >= 0) { /* handle the outline color */ int os; /* outline distance */ os = MS_NINT(ceil(size/10.0)); error = gdImageStringFT(img, bbox, ((label->antialias)?(label->outlinecolor.pen):-(label->outlinecolor.pen)), font, size, theta, x, y-os, s); if(error) { if( gdImageTrueColor(img) ) gdImageAlphaBlending( img, oldAlphaBlending ); msSetError(MS_TTFERR, error, "msDrawTextLineGD()"); if(label->encoding != NULL) msFree(string); return(-1); } gdImageStringFT(img, bbox, ((label->antialias)?(label->outlinecolor.pen):-(label->outlinecolor.pen)), font, size, theta, x, y+os, s); gdImageStringFT(img, bbox, ((label->antialias)?(label->outlinecolor.pen):-(label->outlinecolor.pen)), font, size, theta, x+os, y, s); gdImageStringFT(img, bbox, ((label->antialias)?(label->outlinecolor.pen):-(label->outlinecolor.pen)), font, size, theta, x-os, y, s); gdImageStringFT(img, bbox, ((label->antialias)?(label->outlinecolor.pen):-(label->outlinecolor.pen)), font, size, theta, x-os, y-os, s); gdImageStringFT(img, bbox, ((label->antialias)?(label->outlinecolor.pen):-(label->outlinecolor.pen)), font, size, theta, x-os, y+os, s); gdImageStringFT(img, bbox, ((label->antialias)?(label->outlinecolor.pen):-(label->outlinecolor.pen)), font, size, theta, x+os, y-os, s); gdImageStringFT(img, bbox, ((label->antialias)?(label->outlinecolor.pen):-(label->outlinecolor.pen)), font, size, theta, x+os, y+os, s); } } /* Render the foreground */ for (i = 0; i < labelpath->path.numpoints; i++) { s[0] = string[i]; s[1] = '\0'; gdImageStringFT(img, bbox, ((label->antialias)?(label->color.pen):-(label->color.pen)), font, size, labelpath->angles[i], labelpath->path.point[i].x, labelpath->path.point[i].y, s); } /* Uncomment this to see the label bounds */ /* imagePolyline(img, &(labelpath->bounds), label->color.pen, 0,0); */ if( gdImageTrueColor(img) ) gdImageAlphaBlending( img, oldAlphaBlending ); #else msSetError(MS_TTFERR, "TrueType font support is not available.", "msDrawTextGD()"); if(label->encoding != NULL) msFree(string); return(-1); #endif } else { /* MS_BITMAP */ msSetError(MS_TTFERR, "TrueType font support is not available and is required for angled text rendering.", "msDrawTextGD()"); if(label->encoding != NULL) msFree(string); return(-1); } if(label->encoding != NULL) msFree(string); return(0); } /* To DO: fix returned values to be MS_SUCCESS/MS_FAILURE */ int msDrawLabelCacheGD(gdImagePtr img, mapObj *map) { pointObj p; int i, l; int oldAlphaBlending=0; rectObj r; labelCacheMemberObj *cachePtr=NULL; layerObj *layerPtr=NULL; labelObj *labelPtr=NULL; int marker_width, marker_height; int marker_offset_x, marker_offset_y; rectObj marker_rect; int map_edge_buffer=0; const char *value; /* Look for labelcache_map_edge_buffer map metadata * If set then the value defines a buffer (in pixels) along the edge of the * map image where labels can't fall */ if ((value = msLookupHashTable(&(map->web.metadata), "labelcache_map_edge_buffer")) != NULL) { map_edge_buffer = atoi(value); if (map->debug) msDebug("msDrawLabelCacheGD(): labelcache_map_edge_buffer = %d\n", map_edge_buffer); } /* bug 490 - switch on alpha blending for label cache */ oldAlphaBlending = img->alphaBlendingFlag; gdImageAlphaBlending( img, 1); for(l=map->labelcache.numlabels-1; l>=0; l--) { cachePtr = &(map->labelcache.labels[l]); /* point to right spot in the label cache */ layerPtr = &(map->layers[cachePtr->layerindex]); /* set a couple of other pointers, avoids nasty references */ labelPtr = &(cachePtr->label); if(!cachePtr->text || strlen(cachePtr->text) == 0) continue; /* not an error, just don't want to do anything */ if(msGetLabelSize(cachePtr->text, labelPtr, &r, &(map->fontset), layerPtr->scalefactor, MS_TRUE) == -1) return(-1); if(labelPtr->autominfeaturesize && ((r.maxx-r.minx) > cachePtr->featuresize)) continue; /* label too large relative to the feature */ marker_offset_x = marker_offset_y = 0; /* assume no marker */ if((layerPtr->type == MS_LAYER_ANNOTATION && cachePtr->numstyles > 0) || layerPtr->type == MS_LAYER_POINT) { /* there *is* a marker */ /* TO DO: at the moment only checks the bottom style, perhaps should check all of them */ if(msGetMarkerSize(&map->symbolset, &(cachePtr->styles[0]), &marker_width, &marker_height, layerPtr->scalefactor) != MS_SUCCESS) return(-1); marker_offset_x = MS_NINT(marker_width/2.0); marker_offset_y = MS_NINT(marker_height/2.0); marker_rect.minx = MS_NINT(cachePtr->point.x - .5 * marker_width); marker_rect.miny = MS_NINT(cachePtr->point.y - .5 * marker_height); marker_rect.maxx = marker_rect.minx + (marker_width-1); marker_rect.maxy = marker_rect.miny + (marker_height-1); } if(labelPtr->position == MS_AUTO) { /* If we have a label path there are no alternate positions. Just check against the image boundaries, existing markers and existing labels. (Bug #1620) */ if ( cachePtr->labelpath ) { /* Assume label can be drawn */ cachePtr->status = MS_TRUE; /* Copy the bounds into the cache's polygon */ msCopyShape(&(cachePtr->labelpath->bounds), cachePtr->poly); msFreeShape(&(cachePtr->labelpath->bounds)); do { /* Check the bounds against the image */ if ( !labelPtr->partials ) { if ( labelInImage(img->sx, img->sy, cachePtr->poly, labelPtr->buffer + map_edge_buffer) == MS_FALSE) { cachePtr->status = MS_FALSE; break; } } /* Compare against rendered markers */ for ( i = 0; i < map->labelcache.nummarkers; i++ ) { if ( l != map->labelcache.markers[i].id ) { if ( intersectLabelPolygons(map->labelcache.markers[i].poly, cachePtr->poly ) == MS_TRUE ) { cachePtr->status = MS_FALSE; break; } } } if ( !cachePtr->status ) break; /* Compare against rendered labels */ for ( i = l+1; i < map->labelcache.numlabels; i++ ) { if ( map->labelcache.labels[i].status == MS_TRUE ) { /* Check mindistance */ if ( (labelPtr->mindistance != -1) && (cachePtr->classindex == map->labelcache.labels[i].classindex) && (strcmp(cachePtr->text,map->labelcache.labels[i].text) == 0) && (msDistancePointToPoint(&(cachePtr->point), &(map->labelcache.labels[i].point)) <= labelPtr->mindistance)) { /* label is a duplicate */ cachePtr->status = MS_FALSE; break; } if ( intersectLabelPolygons(map->labelcache.labels[i].poly, cachePtr->poly) == MS_TRUE ) { /* Labels intersect */ cachePtr->status = MS_FALSE; break; } } } /* Done */ } while ( 0 ); } else { /* We have a label point */ int first_pos, pos, last_pos; if ( labelPtr->type == MS_LAYER_LINE ) { /* There are three possible positions: UC, CC, LC */ first_pos = MS_UC; last_pos = MS_CC; } else { /* There are 8 possible outer positions: UL, LR, UR, LL, CR, CL, UC, LC */ first_pos = MS_UL; last_pos = MS_LC; } for(pos = first_pos; pos <= last_pos; pos++) { msFreeShape(cachePtr->poly); cachePtr->status = MS_TRUE; /* assume label *can* be drawn */ p = get_metrics(&(cachePtr->point), pos, r, (marker_offset_x + labelPtr->offsetx), (marker_offset_y + labelPtr->offsety), labelPtr->angle, labelPtr->buffer, cachePtr->poly); if(layerPtr->type == MS_LAYER_ANNOTATION && cachePtr->numstyles > 0) msRectToPolygon(marker_rect, cachePtr->poly); /* save marker bounding polygon */ if(!labelPtr->partials) { /* check against image first */ if(labelInImage(img->sx, img->sy, cachePtr->poly, labelPtr->buffer+map_edge_buffer) == MS_FALSE) { cachePtr->status = MS_FALSE; continue; /* next position */ } } for(i=0; ilabelcache.nummarkers; i++) { /* compare against points already drawn */ if(l != map->labelcache.markers[i].id) { /* labels can overlap their own marker */ if(intersectLabelPolygons(map->labelcache.markers[i].poly, cachePtr->poly) == MS_TRUE) { /* polys intersect */ cachePtr->status = MS_FALSE; break; } } } if(!cachePtr->status) continue; /* next position */ for(i=l+1; ilabelcache.numlabels; i++) { /* compare against rendered labels */ if(map->labelcache.labels[i].status == MS_TRUE) { /* compare bounding polygons and check for duplicates */ if((labelPtr->mindistance != -1) && (cachePtr->classindex == map->labelcache.labels[i].classindex) && (strcmp(cachePtr->text,map->labelcache.labels[i].text) == 0) && (msDistancePointToPoint(&(cachePtr->point), &(map->labelcache.labels[i].point)) <= labelPtr->mindistance)) { /* label is a duplicate */ cachePtr->status = MS_FALSE; break; } if(intersectLabelPolygons(map->labelcache.labels[i].poly, cachePtr->poly) == MS_TRUE) { /* polys intersect */ cachePtr->status = MS_FALSE; break; } } } if(cachePtr->status) /* found a suitable place for this label */ break; } /* next position */ } if(labelPtr->force) cachePtr->status = MS_TRUE; /* draw in spite of collisions based on last position, need a *best* position */ } else { cachePtr->status = MS_TRUE; /* assume label *can* be drawn */ if(labelPtr->position == MS_CC) /* don't need the marker_offset */ p = get_metrics(&(cachePtr->point), labelPtr->position, r, labelPtr->offsetx, labelPtr->offsety, labelPtr->angle, labelPtr->buffer, cachePtr->poly); else p = get_metrics(&(cachePtr->point), labelPtr->position, r, (marker_offset_x + labelPtr->offsetx), (marker_offset_y + labelPtr->offsety), labelPtr->angle, labelPtr->buffer, cachePtr->poly); if(layerPtr->type == MS_LAYER_ANNOTATION && cachePtr->numstyles > 0) msRectToPolygon(marker_rect, cachePtr->poly); /* save marker bounding polygon, part of overlap tests */ if(!labelPtr->force) { /* no need to check anything else */ if(!labelPtr->partials) { if(labelInImage(img->sx, img->sy, cachePtr->poly, labelPtr->buffer+map_edge_buffer) == MS_FALSE) cachePtr->status = MS_FALSE; } if(!cachePtr->status) continue; /* next label */ for(i=0; ilabelcache.nummarkers; i++) { /* compare against points already drawn */ if(l != map->labelcache.markers[i].id) { /* labels can overlap their own marker */ if(intersectLabelPolygons(map->labelcache.markers[i].poly, cachePtr->poly) == MS_TRUE) { /* polys intersect */ cachePtr->status = MS_FALSE; break; } } } if(!cachePtr->status) continue; /* next label */ for(i=l+1; ilabelcache.numlabels; i++) { /* compare against rendered label */ if(map->labelcache.labels[i].status == MS_TRUE) { /* compare bounding polygons and check for duplicates */ if((labelPtr->mindistance != -1) && (cachePtr->classindex == map->labelcache.labels[i].classindex) && (strcmp(cachePtr->text, map->labelcache.labels[i].text) == 0) && (msDistancePointToPoint(&(cachePtr->point), &(map->labelcache.labels[i].point)) <= labelPtr->mindistance)) { /* label is a duplicate */ cachePtr->status = MS_FALSE; break; } if(intersectLabelPolygons(map->labelcache.labels[i].poly, cachePtr->poly) == MS_TRUE) { /* polys intersect */ cachePtr->status = MS_FALSE; break; } } } } } /* end position if-then-else */ /* imagePolyline(img, cachePtr->poly, 1, 0, 0); */ if(!cachePtr->status) continue; /* next label */ if(layerPtr->type == MS_LAYER_ANNOTATION && cachePtr->numstyles > 0) { /* need to draw a marker */ for(i=0; inumstyles; i++) msDrawMarkerSymbolGD(&map->symbolset, img, &(cachePtr->point), &(cachePtr->styles[i]), layerPtr->scalefactor); } if(MS_VALID_COLOR(labelPtr->backgroundcolor)) billboardGD(img, cachePtr->poly, labelPtr); if ( cachePtr->labelpath ) { msDrawTextLineGD(img, cachePtr->text, labelPtr, cachePtr->labelpath, &(map->fontset), layerPtr->scalefactor); /* Draw the curved label */ } else { msDrawTextGD(img, p, cachePtr->text, labelPtr, &(map->fontset), layerPtr->scalefactor); /* actually draw the label */ } } /* next label */ /* bug 490 - alpha blending back */ gdImageAlphaBlending( img, oldAlphaBlending); return(0); } /* =========================================================================== msSaveImageGD Save an image to a file named filename, if filename is NULL it goes to stdout. This function wraps msSaveImageGDCtx. --SG ======================================================================== */ int msSaveImageGD( gdImagePtr img, char *filename, outputFormatObj *format ) { FILE *stream=NULL; gdIOCtx *ctx = NULL; int retval=MS_FAILURE; /* Try to open a file handle */ if (filename != NULL && strlen(filename) > 0) { stream = fopen(filename, "wb"); if (!stream) { msSetError(MS_IOERR, "Unable to open file %s for writing", "msSaveImageGD()", filename); return MS_FAILURE; } /* we wrap msSaveImageGDCtx in the same way that gdImageJpeg() wraps gdImageJpegCtx() (bug 1047). */ /* gdNewFileCtx is a semi-documented function from gd_io_file.c */ ctx = (gdIOCtx *) msNewGDFileCtx(stream); retval = msSaveImageGDCtx( img, ctx, format ); ctx->gd_free(ctx); fclose(stream); } /* Fall back on standard output, or MAPIO's replacement */ else { if ( msIO_needBinaryStdout() == MS_FAILURE ) return MS_FAILURE; stream = stdout; ctx = (gdIOCtx *) msNewGDFileCtx(stream); #ifdef USE_MAPIO ctx = msIO_getGDIOCtx( stream ); #endif /* we wrap msSaveImageGDCtx in the same way that gdImageJpeg() wraps gdImageJpegCtx() (bug 1047). */ retval = msSaveImageGDCtx( img, ctx, format ); if ( ctx != NULL ) free( ctx ); } return retval; } /* =========================================================================== msSaveImageGDCtx Save image data through gdIOCtx only. All mapio conditional compilation definitions have been moved up to msSaveImageGD (bug 1047). ======================================================================== */ int msSaveImageGDCtx( gdImagePtr img, gdIOCtx *ctx, outputFormatObj *format) { if ( format->imagemode == MS_IMAGEMODE_RGBA ) gdImageSaveAlpha( img, 1 ); else if ( format->imagemode == MS_IMAGEMODE_RGB ) gdImageSaveAlpha( img, 0 ); if ( strcasecmp("ON", msGetOutputFormatOption( format, "INTERLACE", "ON" )) == 0 ) gdImageInterlace(img, 1); if (format->transparent) gdImageColorTransparent(img, 0); if ( strcasecmp(format->driver,"gd/gif") == 0 ) { #ifdef USE_GD_GIF gdImageGifCtx( img, ctx ); #else msSetError(MS_MISCERR, "GIF output is not available.", "msSaveImageGDCtx()"); return(MS_FAILURE); #endif } else if ( strcasecmp(format->driver,"gd/png") == 0 ) { #ifdef USE_GD_PNG int force_pc256 = MS_FALSE; if( format->imagemode == MS_IMAGEMODE_RGB || format->imagemode == MS_IMAGEMODE_RGBA ) { const char *force_string = msGetOutputFormatOption( format, "QUANTIZE_FORCE", "OFF" ); if( strcasecmp(force_string,"on") == 0 || strcasecmp(force_string,"yes") == 0 || strcasecmp(force_string,"true") == 0 ) force_pc256 = MS_TRUE; } if( force_pc256 ) { gdImagePtr gdPImg; int dither, i; int colorsWanted = atoi(msGetOutputFormatOption( format, "QUANTIZE_COLORS", "256")); const char *dither_string = msGetOutputFormatOption( format, "QUANTIZE_DITHER", "YES"); if( strcasecmp(dither_string,"on") == 0 || strcasecmp(dither_string,"yes") == 0 || strcasecmp(dither_string,"true") == 0 ) dither = 1; else dither = 0; gdPImg = gdImageCreatePaletteFromTrueColor(img,dither,colorsWanted); /* It seems there is a bug in gd 2.0.33 and earlier that leaves the colors open[] flag set to one. */ for( i = 0; i < gdPImg->colorsTotal; i++ ) gdPImg->open[i] = 0; gdImagePngCtx( gdPImg, ctx ); gdImageDestroy( gdPImg ); } else gdImagePngCtx( img, ctx ); #else msSetError(MS_MISCERR, "PNG output is not available.", "msSaveImageGDCtx()"); return(MS_FAILURE); #endif } else if ( strcasecmp(format->driver,"gd/jpeg") == 0 ) { #ifdef USE_GD_JPEG gdImageJpegCtx(img, ctx, atoi(msGetOutputFormatOption( format, "QUALITY", "75"))); #else msSetError(MS_MISCERR, "JPEG output is not available.", "msSaveImageGDCtx()"); return(MS_FAILURE); #endif } else if ( strcasecmp(format->driver,"gd/wbmp") == 0 ) { #ifdef USE_GD_WBMP gdImageWBMPCtx(img, 1, ctx); #else msSetError(MS_MISCERR, "WBMP output is not available.", "msSaveImageGDCtx()"); return(MS_FAILURE); #endif } else { msSetError(MS_MISCERR, "Unknown output image type driver: %s.", "msSaveImageGDCtx()", format->driver ); return(MS_FAILURE); } return(MS_SUCCESS); } /* =========================================================================== msSaveImageBufferGD Save image data to a unsigned char * buffer. In the future we should try to merge this with msSaveImageStreamGD function. The returned buffer is owned by the caller. It should be freed with gdFree() ======================================================================== */ unsigned char *msSaveImageBufferGD(gdImagePtr img, int *size_ptr, outputFormatObj *format) { unsigned char *imgbytes; if (format->imagemode == MS_IMAGEMODE_RGBA) gdImageSaveAlpha(img, 1); else if (format->imagemode == MS_IMAGEMODE_RGB) gdImageSaveAlpha(img, 0); if (strcasecmp("ON", msGetOutputFormatOption(format, "INTERLACE", "ON" )) == 0) gdImageInterlace(img, 1); if (format->transparent) gdImageColorTransparent(img, 0); if (strcasecmp(format->driver, "gd/gif") == 0) { #ifdef USE_GD_GIF imgbytes = gdImageGifPtr(img, size_ptr); #else msSetError(MS_IMGERR, "GIF output is not available.", "msSaveImageBufferGD()"); return NULL; #endif } else if (strcasecmp(format->driver, "gd/png") == 0) { #ifdef USE_GD_PNG imgbytes = gdImagePngPtr(img, size_ptr); #else msSetError(MS_IMGERR, "PNG output is not available.", "msSaveImageBufferGD()"); return NULL; #endif } else if (strcasecmp(format->driver, "gd/jpeg") == 0) { #ifdef USE_GD_JPEG imgbytes = gdImageJpegPtr(img, size_ptr, atoi(msGetOutputFormatOption(format, "QUALITY", "75" ))); #else msSetError(MS_IMGERR, "JPEG output is not available.", "msSaveImageBufferGD()"); return NULL; #endif } else if (strcasecmp(format->driver, "gd/wbmp") == 0) { #ifdef USE_GD_WBMP imgbytes = gdImageWBMPPtr(img, size_ptr, 1); #else msSetError(MS_IMGERR, "WBMP output is not available.", "msSaveImageBufferGD()"); return NULL; #endif } else { msSetError(MS_IMGERR, "Unknown output image type driver: %s.", "msSaveImageBufferGD()", format->driver ); return NULL; } return imgbytes; } /** * Free gdImagePtr */ void msFreeImageGD(gdImagePtr img) { gdImageDestroy(img); } void msImageCopyMerge (gdImagePtr dst, gdImagePtr src, int dstX, int dstY, int srcX, int srcY, int w, int h, int pct) { int x, y; int oldAlphaBlending=0; /* for most cases the GD copy is fine */ if( !gdImageTrueColor(dst) || !gdImageTrueColor(src) ) { gdImageCopyMerge( dst, src, dstX, dstY, srcX, srcY, w, h, pct ); return; } /* ** Turn off blending in output image to prevent it doing it's own attempt ** at blending instead of using our result. */ oldAlphaBlending = dst->alphaBlendingFlag; gdImageAlphaBlending( dst, 0 ); /* gdImageAlphaBlending( dst, 0 ); */ for (y = 0; (y < h); y++) { for (x = 0; (x < w); x++) { int src_c = gdImageGetPixel (src, srcX + x, srcY + y); int dst_c = gdImageGetPixel (dst, dstX + x, dstY + y); int red, green, blue, res_alpha; int src_alpha = (127-gdTrueColorGetAlpha(src_c)); int dst_alpha = (127-gdTrueColorGetAlpha(dst_c)); if( gdTrueColorGetAlpha(src_c) == gdAlphaTransparent ) continue; /* Adjust dst alpha according to percentages */ dst_alpha = dst_alpha * ((100-pct)*src_alpha/127) / 100; /* adjust source according to transparency percentage */ src_alpha = src_alpha * (pct) / 100; /* Use simple additive model for resulting transparency */ res_alpha = src_alpha + dst_alpha; if( res_alpha > 127 ) res_alpha = 127; if( src_alpha + dst_alpha == 0 ) dst_alpha = 1; red = ((gdTrueColorGetRed( src_c ) * src_alpha) + (gdTrueColorGetRed( dst_c ) * dst_alpha)) / (src_alpha+dst_alpha); green = ((gdTrueColorGetGreen( src_c ) * src_alpha) + (gdTrueColorGetGreen( dst_c ) * dst_alpha)) / (src_alpha+dst_alpha); blue = ((gdTrueColorGetBlue( src_c ) * src_alpha) + (gdTrueColorGetBlue( dst_c ) * dst_alpha)) / (src_alpha+dst_alpha); gdImageSetPixel(dst,dstX+x,dstY+y, gdTrueColorAlpha( red, green, blue, 127-res_alpha )); } } /* ** Restore original alpha blending flag. */ /* gdImageAlphaBlending( dst, 0 ); */ gdImageAlphaBlending( dst, oldAlphaBlending ); } /* Code from gd_io_file.c has been brought into mapserver itself so that we can avoid the issue of passing a FILE* from mapserver to the bgd.dll on windows (bug 1047). See the file mapserver/GD-COPYING for full credits and copyright of the GD authors. */ /* * io_file.c * * Implements the file interface. * * As will all I/O modules, most functions are for local use only (called * via function pointers in the I/O context). * * Most functions are just 'wrappers' for standard file functions. * * Written/Modified 1999, Philip Warner. * */ /* #ifdef HAVE_CONFIG_H */ /* #include "config.h" */ /* #endif */ /* For platforms with incomplete ANSI defines. Fortunately, SEEK_SET is defined to be zero by the standard. */ #ifndef SEEK_SET #define SEEK_SET 0 #endif /* SEEK_SET */ /* #include */ /* #include */ /* #include */ #include "gd.h" /* this is used for creating images in main memory */ typedef struct fileIOCtx { gdIOCtx ctx; FILE *f; } fileIOCtx; static int fileGetbuf (gdIOCtx *, void *, int); static int filePutbuf (gdIOCtx *, const void *, int); static void filePutchar (gdIOCtx *, int); static int fileGetchar (gdIOCtx * ctx); static int fileSeek (struct gdIOCtx *, const int); static long fileTell (struct gdIOCtx *); static void msFreeFileCtx (gdIOCtx * ctx); /* return data as a dynamic pointer */ gdIOCtx *msNewGDFileCtx (FILE * f) { fileIOCtx *ctx; ctx = (fileIOCtx *) malloc (sizeof (fileIOCtx)); if (ctx == NULL) { return NULL; } ctx->f = f; ctx->ctx.getC = fileGetchar; ctx->ctx.putC = filePutchar; ctx->ctx.getBuf = fileGetbuf; ctx->ctx.putBuf = filePutbuf; ctx->ctx.tell = fileTell; ctx->ctx.seek = fileSeek; ctx->ctx.gd_free = msFreeFileCtx; return (gdIOCtx *) ctx; } static void msFreeFileCtx (gdIOCtx * ctx) { free(ctx); } static int filePutbuf (gdIOCtx * ctx, const void *buf, int size) { fileIOCtx *fctx; fctx = (fileIOCtx *) ctx; return fwrite (buf, 1, size, fctx->f); } static int fileGetbuf (gdIOCtx * ctx, void *buf, int size) { fileIOCtx *fctx; fctx = (fileIOCtx *) ctx; return (fread (buf, 1, size, fctx->f)); } static void filePutchar (gdIOCtx * ctx, int a) { unsigned char b; fileIOCtx *fctx; fctx = (fileIOCtx *) ctx; b = a; putc (b, fctx->f); } static int fileGetchar (gdIOCtx * ctx) { fileIOCtx *fctx; fctx = (fileIOCtx *) ctx; return getc (fctx->f); } static int fileSeek (struct gdIOCtx *ctx, const int pos) { fileIOCtx *fctx; fctx = (fileIOCtx *) ctx; return (fseek (fctx->f, pos, SEEK_SET) == 0); } static long fileTell (struct gdIOCtx *ctx) { fileIOCtx *fctx; fctx = (fileIOCtx *) ctx; return ftell (fctx->f); }