1 /******************************************************************************
2  * $Id$
3  *
4  * Project:  MapServer
5  * Purpose:  Legend generation.
6  * Author:   Steve Lime and the MapServer team.
7  *
8  ******************************************************************************
9  * Copyright (c) 1996-2005 Regents of the University of Minnesota.
10  *
11  * Permission is hereby granted, free of charge, to any person obtaining a
12  * copy of this software and associated documentation files (the "Software"),
13  * to deal in the Software without restriction, including without limitation
14  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15  * and/or sell copies of the Software, and to permit persons to whom the
16  * Software is furnished to do so, subject to the following conditions:
17  *
18  * The above copyright notice and this permission notice shall be included in
19  * all copies of this Software or works derived from this Software.
20  *
21  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27  * DEALINGS IN THE SOFTWARE.
28  *****************************************************************************/
29 
30 #include "mapserver.h"
31 
32 
33 
34 #define PSF .8
35 #define VMARGIN 5 /* margin at top and bottom of legend graphic */
36 #define HMARGIN 5 /* margin at left and right of legend graphic */
37 
38 
msDrawGradientSymbol(rendererVTableObj * renderer,imageObj * image_draw,double x_center,double y_center,int width,int height,styleObj * style)39 static int msDrawGradientSymbol(rendererVTableObj* renderer,
40                                 imageObj* image_draw,
41                                 double x_center,
42                                 double y_center,
43                                 int width,
44                                 int height,
45                                 styleObj* style)
46 {
47     int i, j;
48     unsigned char *r,*g,*b,*a;
49     symbolObj symbol;
50     rasterBufferObj* rb;
51     symbolStyleObj symbolStyle;
52     int ret;
53 
54     initSymbol(&symbol);
55     rb = (rasterBufferObj*)calloc(1,sizeof(rasterBufferObj));
56     symbol.pixmap_buffer = rb;
57     rb->type = MS_BUFFER_BYTE_RGBA;
58     rb->width = width;
59     rb->height = height;
60     rb->data.rgba.row_step = rb->width * 4;
61     rb->data.rgba.pixel_step = 4;
62     rb->data.rgba.pixels = (unsigned char*)malloc(
63                                 rb->width*rb->height*4*sizeof(unsigned char));
64     b = rb->data.rgba.b = &rb->data.rgba.pixels[0];
65     g = rb->data.rgba.g = &rb->data.rgba.pixels[1];
66     r = rb->data.rgba.r = &rb->data.rgba.pixels[2];
67     a = rb->data.rgba.a = &rb->data.rgba.pixels[3];
68     for( j = 0; j < rb->height; j++ )
69     {
70         for( i = 0; i < rb->width; i++ )
71         {
72             msValueToRange(style, style->minvalue +
73                 (double)i / rb->width * (style->maxvalue - style->minvalue), MS_COLORSPACE_RGB);
74             b[4*(j * rb->width + i)] = style->color.blue;
75             g[4*(j * rb->width + i)] = style->color.green;
76             r[4*(j * rb->width + i)] = style->color.red;
77             a[4*(j * rb->width + i)] = style->color.alpha;
78         }
79     }
80     INIT_SYMBOL_STYLE(symbolStyle);
81     ret = renderer->renderPixmapSymbol(image_draw, x_center, y_center, &symbol, &symbolStyle);
82     msFreeSymbol(&symbol);
83     return ret;
84 }
85 
86 /*
87  * generic function for drawing a legend icon. (added for bug #2348)
88  * renderer specific drawing functions shouldn't be called directly, but through
89  * this function
90  */
msDrawLegendIcon(mapObj * map,layerObj * lp,classObj * theclass,int width,int height,imageObj * image,int dstX,int dstY,int scale_independant,class_hittest * hittest)91 int msDrawLegendIcon(mapObj *map, layerObj *lp, classObj *theclass,
92                      int width, int height, imageObj *image, int dstX, int dstY,
93                      int scale_independant, class_hittest *hittest)
94 {
95   int i, type, hasmarkersymbol, ret=MS_SUCCESS;
96   double offset;
97   double polygon_contraction = 0.5; /* used to account for the width of a polygon's outline */
98   shapeObj box, zigzag;
99   lineObj box_line,zigzag_line;
100   pointObj box_point[5], zigzag_point[4];
101   pointObj marker;
102   char szPath[MS_MAXPATHLEN];
103   styleObj outline_style;
104   imageObj *image_draw = image;
105   rendererVTableObj *renderer;
106   outputFormatObj *transFormat = NULL, *altFormat=NULL;
107   const char *alternativeFormatString = NULL;
108 
109   if(!MS_RENDERER_PLUGIN(image->format)) {
110     msSetError(MS_MISCERR,"unsupported image format","msDrawLegendIcon()");
111     return MS_FAILURE;
112   }
113 
114   alternativeFormatString = msLayerGetProcessingKey(lp, "RENDERER");
115   if (MS_RENDERER_PLUGIN(image_draw->format) && alternativeFormatString!=NULL &&
116       (altFormat=  msSelectOutputFormat(map, alternativeFormatString))) {
117     msInitializeRendererVTable(altFormat);
118 
119     image_draw = msImageCreate(image->width, image->height,
120                                altFormat, image->imagepath, image->imageurl, map->resolution, map->defresolution, &map->imagecolor);
121     image_draw->map = map;
122     renderer = MS_IMAGE_RENDERER(image_draw);
123   } else {
124     renderer = MS_IMAGE_RENDERER(image_draw);
125     if (lp->compositer && renderer->compositeRasterBuffer) {
126       image_draw = msImageCreate(image->width, image->height,
127               image->format, image->imagepath, image->imageurl, map->resolution, map->defresolution, NULL);
128       if (!image_draw) {
129         msSetError(MS_MISCERR, "Unable to initialize temporary transparent image.",
130                 "msDrawLegendIcon()");
131         return (MS_FAILURE);
132       }
133       image_draw->map = map;
134     }
135   }
136 
137 
138   if(renderer->supports_clipping && MS_VALID_COLOR(map->legend.outlinecolor)) {
139     /* keep GD specific code here for now as it supports clipping */
140     rectObj clip;
141     clip.maxx = dstX + width - 1;
142     clip.maxy = dstY + height -1;
143     clip.minx = dstX;
144     clip.miny = dstY;
145     renderer->setClip(image_draw,clip);
146   }
147 
148   /* if the class has a keyimage, treat it as a point layer
149    * (the keyimage will be treated there) */
150   if(theclass->keyimage != NULL) {
151     type = MS_LAYER_POINT;
152   } else {
153     /* some polygon layers may be better drawn using zigzag if there is no fill */
154     type = lp->type;
155     if(type == MS_LAYER_POLYGON) {
156       type = MS_LAYER_LINE;
157       for(i=0; i<theclass->numstyles; i++) {
158         if(MS_VALID_COLOR(theclass->styles[i]->color)) { /* there is a fill */
159           type = MS_LAYER_POLYGON;
160         }
161         if(MS_VALID_COLOR(theclass->styles[i]->outlinecolor)) { /* there is an outline */
162           polygon_contraction = MS_MAX(polygon_contraction, theclass->styles[i]->width / 2.0);
163         }
164       }
165     }
166   }
167 
168   /* initialize the box used for polygons and for outlines */
169   msInitShape(&box);
170   box.line = &box_line;
171   box.numlines = 1;
172   box.line[0].point = box_point;
173   box.line[0].numpoints = 5;
174   box.type = MS_SHAPE_POLYGON;
175 
176   box.line[0].point[0].x = dstX + polygon_contraction;
177   box.line[0].point[0].y = dstY + polygon_contraction;
178   box.line[0].point[1].x = dstX + width - polygon_contraction;
179   box.line[0].point[1].y = dstY + polygon_contraction;
180   box.line[0].point[2].x = dstX + width - polygon_contraction;
181   box.line[0].point[2].y = dstY + height - polygon_contraction;
182   box.line[0].point[3].x = dstX + polygon_contraction;
183   box.line[0].point[3].y = dstY + height - polygon_contraction;
184   box.line[0].point[4].x = box.line[0].point[0].x;
185   box.line[0].point[4].y = box.line[0].point[0].y;
186 
187 
188 
189   /*
190   ** now draw the appropriate color/symbol/size combination
191   */
192 
193   /* Scalefactor will be infinity when SIZEUNITS is set in LAYER */
194   if(lp->sizeunits != MS_PIXELS) {
195     lp->scalefactor = 1.0;
196   }
197 
198   switch(type) {
199     case MS_LAYER_POINT:
200       marker.x = dstX + MS_NINT(width / 2.0);
201       marker.y = dstY + MS_NINT(height / 2.0);
202       if(theclass->keyimage != NULL) {
203         int symbolNum;
204         styleObj imgStyle;
205         symbolObj *symbol=NULL;
206         symbolNum = msAddImageSymbol(&(map->symbolset), msBuildPath(szPath, map->mappath, theclass->keyimage));
207         if(symbolNum == -1) {
208           msSetError(MS_IMGERR, "Failed to open legend key image", "msCreateLegendIcon()");
209           return(MS_FAILURE);
210         }
211 
212         symbol = map->symbolset.symbol[symbolNum];
213 
214         initStyle(&imgStyle);
215         /*set size so that symbol will be scaled properly #3296*/
216         if (width/symbol->sizex < height/symbol->sizey)
217           imgStyle.size = symbol->sizey*(width/symbol->sizex);
218         else
219           imgStyle.size = symbol->sizey*(height/symbol->sizey);
220 
221         if (imgStyle.size > imgStyle.maxsize)
222           imgStyle.maxsize = imgStyle.size;
223 
224         imgStyle.symbol = symbolNum;
225         ret = msDrawMarkerSymbol(map ,image_draw,&marker,&imgStyle, 1.0);
226         if(UNLIKELY(ret == MS_FAILURE)) goto legend_icon_cleanup;
227         /* TO DO: we may want to handle this differently depending on the relative size of the keyimage */
228       } else {
229         for(i=0; i<theclass->numstyles; i++) {
230           if(!scale_independant && map->scaledenom > 0) {
231             styleObj *lp = theclass->styles[i];
232             if((lp->maxscaledenom > 0) && (map->scaledenom > lp->maxscaledenom)) continue;
233             if((lp->minscaledenom > 0) && (map->scaledenom <= lp->minscaledenom)) continue;
234           }
235           if(hittest && hittest->stylehits[i].status == 0) continue;
236           ret = msDrawMarkerSymbol(map, image_draw, &marker, theclass->styles[i], lp->scalefactor * image->resolutionfactor);
237           if(UNLIKELY(ret == MS_FAILURE)) goto legend_icon_cleanup;
238         }
239       }
240       break;
241     case MS_LAYER_LINE:
242       offset = 1;
243       /* To set the offset, we only check the size/width parameter of the first style */
244       if (theclass->numstyles > 0) {
245         if (theclass->styles[0]->symbol > 0 && theclass->styles[0]->symbol < map->symbolset.numsymbols &&
246               map->symbolset.symbol[theclass->styles[0]->symbol]->type != MS_SYMBOL_SIMPLE)
247             offset = theclass->styles[0]->size/2;
248         else
249             offset = theclass->styles[0]->width/2;
250       }
251       msInitShape(&zigzag);
252       zigzag.line = &zigzag_line;
253       zigzag.numlines = 1;
254       zigzag.line[0].point = zigzag_point;
255       zigzag.line[0].numpoints = 4;
256       zigzag.type = MS_SHAPE_LINE;
257 
258       zigzag.line[0].point[0].x = dstX + offset;
259       zigzag.line[0].point[0].y = dstY + height - offset;
260       zigzag.line[0].point[1].x = dstX + MS_NINT(width / 3.0) - 1;
261       zigzag.line[0].point[1].y = dstY + offset;
262       zigzag.line[0].point[2].x = dstX + MS_NINT(2.0 * width / 3.0) - 1;
263       zigzag.line[0].point[2].y = dstY + height - offset;
264       zigzag.line[0].point[3].x = dstX + width - offset;
265       zigzag.line[0].point[3].y = dstY + offset;
266 
267       for(i=0; i<theclass->numstyles; i++) {
268         if(!scale_independant && map->scaledenom > 0) {
269           styleObj *lp = theclass->styles[i];
270           if((lp->maxscaledenom > 0) && (map->scaledenom > lp->maxscaledenom)) continue;
271           if((lp->minscaledenom > 0) && (map->scaledenom <= lp->minscaledenom)) continue;
272         }
273         if(hittest && hittest->stylehits[i].status == 0) continue;
274         if (theclass->styles[i]->_geomtransform.type == MS_GEOMTRANSFORM_NONE ||
275             theclass->styles[i]->_geomtransform.type == MS_GEOMTRANSFORM_LABELPOINT ||
276             theclass->styles[i]->_geomtransform.type == MS_GEOMTRANSFORM_LABELPOLY) {
277 	  if (theclass->styles[i]->outlinewidth > 0) {
278 	    /* Swap the style contents to render the outline first,
279 	     * and then restore the style to render the interior of the line
280 	     */
281 	    msOutlineRenderingPrepareStyle(theclass->styles[i], map, lp, image);
282 	    ret = msDrawLineSymbol(map, image_draw, &zigzag, theclass->styles[i], lp->scalefactor * image_draw->resolutionfactor);
283 	    msOutlineRenderingRestoreStyle(theclass->styles[i], map, lp, image);
284             if(UNLIKELY(ret == MS_FAILURE)) goto legend_icon_cleanup;
285 	  }
286           ret = msDrawLineSymbol(map, image_draw, &zigzag, theclass->styles[i], lp->scalefactor * image_draw->resolutionfactor);
287           if(UNLIKELY(ret == MS_FAILURE)) goto legend_icon_cleanup;
288         }
289         else {
290 	  if (theclass->styles[i]->outlinewidth > 0) {
291 	    /* Swap the style contents to render the outline first,
292 	     * and then restore the style to render the interior of the line
293 	     */
294 	    msOutlineRenderingPrepareStyle(theclass->styles[i], map, lp, image);
295 	    ret = msDrawTransformedShape(map, image_draw, &zigzag, theclass->styles[i], lp->scalefactor * image_draw->resolutionfactor);
296 	    msOutlineRenderingRestoreStyle(theclass->styles[i], map, lp, image);
297             if(UNLIKELY(ret == MS_FAILURE)) goto legend_icon_cleanup;
298 	  }
299           ret = msDrawTransformedShape(map, image_draw, &zigzag, theclass->styles[i], lp->scalefactor * image_draw->resolutionfactor);
300           if(UNLIKELY(ret == MS_FAILURE)) goto legend_icon_cleanup;
301         }
302       }
303 
304       break;
305     case MS_LAYER_CIRCLE:
306     case MS_LAYER_RASTER:
307     case MS_LAYER_CHART:
308     case MS_LAYER_POLYGON:
309       for(i=0; i<theclass->numstyles; i++) {
310         if(!scale_independant && map->scaledenom > 0) {
311           styleObj *lp = theclass->styles[i];
312           if((lp->maxscaledenom > 0) && (map->scaledenom > lp->maxscaledenom)) continue;
313           if((lp->minscaledenom > 0) && (map->scaledenom <= lp->minscaledenom)) continue;
314         }
315         if(hittest && hittest->stylehits[i].status == 0) continue;
316         if (theclass->styles[i]->_geomtransform.type == MS_GEOMTRANSFORM_NONE ||
317             theclass->styles[i]->_geomtransform.type == MS_GEOMTRANSFORM_LABELPOINT ||
318             theclass->styles[i]->_geomtransform.type == MS_GEOMTRANSFORM_LABELPOLY) {
319 
320             if (MS_VALID_COLOR(theclass->styles[i]->mincolor))
321             {
322                 ret = msDrawGradientSymbol(renderer,
323                                            image_draw,
324                                            dstX + width / 2.,
325                                            dstY + height / 2.0,
326                                            width - 2 * polygon_contraction,
327                                            height - 2 * polygon_contraction,
328                                            theclass->styles[i]);
329             }
330             else
331             {
332                 ret = msDrawShadeSymbol(map, image_draw, &box, theclass->styles[i], lp->scalefactor * image_draw->resolutionfactor);
333             }
334             if(UNLIKELY(ret == MS_FAILURE)) goto legend_icon_cleanup;
335         }
336         else {
337           ret = msDrawTransformedShape(map, image_draw, &box,
338                                  theclass->styles[i], lp->scalefactor);
339           if(UNLIKELY(ret == MS_FAILURE)) goto legend_icon_cleanup;
340         }
341       }
342       break;
343     default:
344       return MS_FAILURE;
345       break;
346   } /* end symbol drawing */
347 
348   /* handle label styles */
349   for(i=0; i<theclass->numlabels; i++) {
350     labelObj *l = theclass->labels[i];
351     if(!scale_independant && map->scaledenom > 0) {
352       if(msScaleInBounds(map->scaledenom, l->minscaledenom, l->maxscaledenom)) {
353         int j;
354         for(j=0; j<l->numstyles; j++) {
355           styleObj *s = l->styles[j];
356           marker.x = dstX + MS_NINT(width / 2.0);
357           marker.y = dstY + MS_NINT(height / 2.0);
358           if(s->_geomtransform.type == MS_GEOMTRANSFORM_LABELPOINT) {
359             ret = msDrawMarkerSymbol(map, image_draw, &marker, s, lp->scalefactor);
360             if(UNLIKELY(ret == MS_FAILURE)) goto legend_icon_cleanup;
361           }
362         }
363       }
364     }
365   }
366 
367   /* handle "pure" text layers, i.e. layers with no symbology */
368   hasmarkersymbol = 0;
369   if(theclass->numstyles == 0) {
370     for(i=0; i<theclass->numlabels; i++) {
371       labelObj *l = theclass->labels[i];
372       if(!scale_independant && map->scaledenom > 0) {
373         if(msScaleInBounds(map->scaledenom, l->minscaledenom, l->maxscaledenom)) {
374           int j;
375           for(j=0; j<l->numstyles; j++) {
376             styleObj *s = l->styles[j];
377             if(s->_geomtransform.type == MS_GEOMTRANSFORM_LABELPOINT) {
378               hasmarkersymbol = 1;
379             }
380           }
381         }
382       }
383     }
384   } else {
385     hasmarkersymbol = 1;
386   }
387 
388   if(!hasmarkersymbol && theclass->numlabels>0) {
389     textSymbolObj ts;
390     pointObj textstartpt;
391     marker.x = dstX + MS_NINT(width / 2.0);
392     marker.y = dstY + MS_NINT(height / 2.0);
393     initTextSymbol(&ts);
394     msPopulateTextSymbolForLabelAndString(&ts,theclass->labels[0],msStrdup("Az"),lp->scalefactor*image_draw->resolutionfactor,image_draw->resolutionfactor, duplicate_always);
395     ts.label->size = height - 1;
396     ts.rotation = 0;
397     ret = msComputeTextPath(map,&ts);
398     if(UNLIKELY(ret == MS_FAILURE)) goto legend_icon_cleanup;
399     textstartpt = get_metrics(&marker,MS_CC,ts.textpath,0,0,0,0,NULL);
400     ret = msDrawTextSymbol(map,image_draw, textstartpt, &ts);
401     freeTextSymbol(&ts);
402     if(UNLIKELY(ret == MS_FAILURE)) goto legend_icon_cleanup;
403 
404   }
405 
406 
407   /* handle an outline if necessary */
408   if(MS_VALID_COLOR(map->legend.outlinecolor)) {
409     initStyle(&outline_style);
410     outline_style.color = map->legend.outlinecolor;
411     ret = msDrawLineSymbol(map, image_draw, &box, &outline_style, image_draw->resolutionfactor);
412     if(UNLIKELY(ret == MS_FAILURE)) goto legend_icon_cleanup;
413     /* reset clipping rectangle */
414     if(renderer->supports_clipping)
415       renderer->resetClip(image_draw);
416   }
417 
418   if (altFormat) {
419     rendererVTableObj *renderer = MS_IMAGE_RENDERER(image);
420     rendererVTableObj *altrenderer = MS_IMAGE_RENDERER(image_draw);
421     rasterBufferObj rb;
422     memset(&rb,0,sizeof(rasterBufferObj));
423 
424     ret = altrenderer->getRasterBufferHandle(image_draw,&rb);
425     if(UNLIKELY(ret == MS_FAILURE)) goto legend_icon_cleanup;
426     ret = renderer->mergeRasterBuffer(image,&rb,((lp->compositer)?lp->compositer->opacity*0.01:1.0),0,0,0,0,rb.width,rb.height);
427     if(UNLIKELY(ret == MS_FAILURE)) goto legend_icon_cleanup;
428     /*
429      * hack to work around bug #3834: if we have use an alternate renderer, the symbolset may contain
430      * symbols that reference it. We want to remove those references before the altFormat is destroyed
431      * to avoid a segfault and/or a leak, and so the the main renderer doesn't pick the cache up thinking
432      * it's for him.
433      */
434     for(i=0; i<map->symbolset.numsymbols; i++) {
435       if (map->symbolset.symbol[i]!=NULL) {
436         symbolObj *s = map->symbolset.symbol[i];
437         if(s->renderer == altrenderer) {
438           altrenderer->freeSymbol(s);
439           s->renderer = NULL;
440         }
441       }
442     }
443 
444   } else if(image != image_draw) {
445     rendererVTableObj *renderer = MS_IMAGE_RENDERER(image_draw);
446     rasterBufferObj rb;
447     memset(&rb,0,sizeof(rasterBufferObj));
448 
449     ret = renderer->getRasterBufferHandle(image_draw,&rb);
450     if(UNLIKELY(ret == MS_FAILURE)) goto legend_icon_cleanup;
451     ret = renderer->mergeRasterBuffer(image,&rb,((lp->compositer)?lp->compositer->opacity*0.01:1.0),0,0,0,0,rb.width,rb.height);
452     if(UNLIKELY(ret == MS_FAILURE)) goto legend_icon_cleanup;
453 
454     /* deref and possibly free temporary transparent output format.  */
455     msApplyOutputFormat( &transFormat, NULL, MS_NOOVERRIDE, MS_NOOVERRIDE, MS_NOOVERRIDE );
456 
457   }
458 
459 legend_icon_cleanup:
460   if(image != image_draw) {
461     msFreeImage(image_draw);
462   }
463   return ret;
464 }
465 
msCreateLegendIcon(mapObj * map,layerObj * lp,classObj * class,int width,int height,int scale_independant)466 imageObj *msCreateLegendIcon(mapObj* map, layerObj* lp, classObj* class, int width, int height, int scale_independant)
467 {
468   imageObj *image;
469   outputFormatObj *format = NULL;
470   int i = 0;
471 
472   rendererVTableObj *renderer = MS_MAP_RENDERER(map);
473 
474   if( !renderer ) {
475     msSetError(MS_MISCERR, "invalid map outputformat", "msCreateLegendIcon()");
476     return(NULL);
477   }
478 
479   /* ensure we have an image format representing the options for the legend */
480   msApplyOutputFormat(&format, map->outputformat, map->legend.transparent, map->legend.interlace, MS_NOOVERRIDE);
481 
482   image = msImageCreate(width,height,format,map->web.imagepath, map->web.imageurl,
483                         map->resolution, map->defresolution, &(map->legend.imagecolor));
484 
485   /* drop this reference to output format */
486   msApplyOutputFormat( &format, NULL, MS_NOOVERRIDE, MS_NOOVERRIDE, MS_NOOVERRIDE );
487 
488   if(image == NULL) {
489     msSetError(MS_IMGERR, "Unable to initialize image.","msCreateLegendIcon()");
490     return(NULL);
491   }
492   image->map = map;
493 
494   /* Call drawLegendIcon with destination (0, 0) */
495   /* Return an empty image if lp==NULL || class=NULL  */
496   /* (If class is NULL draw the legend for all classes. Modifications done */
497   /* Fev 2004 by AY) */
498   if (lp) {
499     if (class) {
500       if(UNLIKELY(MS_FAILURE == msDrawLegendIcon(map, lp, class, width, height, image, 0, 0, scale_independant, NULL))) {
501         msFreeImage(image);
502         return NULL;
503       }
504     } else {
505       for (i=0; i<lp->numclasses; i++) {
506         if(UNLIKELY(MS_FAILURE == msDrawLegendIcon(map, lp, lp->class[i], width, height, image, 0, 0, scale_independant, NULL))) {
507           msFreeImage(image);
508           return NULL;
509         }
510       }
511     }
512   }
513   return image;
514 }
515 
516 /*
517  * Calculates the optimal size for the legend. If the optional layerObj
518  * argument is given, the legend size will be calculated for only that
519  * layer. Otherwise, the legend size is calculated for all layers that
520  * are not MS_OFF or of MS_LAYER_QUERY type.
521  *
522  * Returns one of:
523  *   MS_SUCCESS
524  *   MS_FAILURE
525  */
msLegendCalcSize(mapObj * map,int scale_independent,int * size_x,int * size_y,int * layer_index,int num_layers,map_hittest * hittest,double resolutionfactor)526 int msLegendCalcSize(mapObj *map, int scale_independent, int *size_x, int *size_y,
527                      int *layer_index, int num_layers, map_hittest *hittest,
528                      double resolutionfactor)
529 {
530   int i, j;
531   int status, maxwidth=0, nLegendItems=0;
532   char *text;
533   layerObj *lp;
534   rectObj rect;
535   int current_layers=0;
536 
537   /* reset sizes */
538   *size_x = 0;
539   *size_y = 0;
540 
541   /* enable scale-dependent calculations */
542   if(!scale_independent) {
543     map->cellsize = msAdjustExtent(&(map->extent), map->width, map->height);
544     status = msCalculateScale(map->extent, map->units, map->width, map->height, map->resolution, &map->scaledenom);
545     if(status != MS_SUCCESS) return MS_FAILURE;
546   }
547 
548   /*
549    * step through all map classes, and for each one that will be displayed
550    * calculate the label size
551    */
552   if (layer_index != NULL && num_layers >0)
553     current_layers  = num_layers;
554   else
555     current_layers = map->numlayers;
556 
557   for(i=0; i< current_layers; i++) {
558     int layerindex;
559     if (layer_index != NULL && num_layers > 0)
560       layerindex = layer_index[i];
561     else
562       layerindex = map->layerorder[i];
563 
564     lp = (GET_LAYER(map, layerindex));
565 
566     if((lp->status == MS_OFF && (layer_index == NULL || num_layers <= 0)) || (lp->type == MS_LAYER_QUERY)) /* skip it */
567       continue;
568 
569     if(hittest && hittest->layerhits[layerindex].status == 0) continue;
570 
571     if(!scale_independent && map->scaledenom > 0) {
572       if((lp->maxscaledenom > 0) && (map->scaledenom > lp->maxscaledenom)) continue;
573       if((lp->minscaledenom > 0) && (map->scaledenom <= lp->minscaledenom)) continue;
574     }
575 
576     for(j=lp->numclasses-1; j>=0; j--) {
577       textSymbolObj ts;
578       text = lp->class[j]->title?lp->class[j]->title:lp->class[j]->name; /* point to the right legend text, title takes precedence */
579       if(!text) continue; /* skip it */
580 
581       /* skip the class if the classgroup is defined */
582       if(lp->classgroup && (lp->class[j]->group == NULL || strcasecmp(lp->class[j]->group, lp->classgroup) != 0))
583         continue;
584 
585       /* verify class scale */
586       if(!scale_independent && map->scaledenom > 0) {
587         if((lp->class[j]->maxscaledenom > 0) && (map->scaledenom > lp->class[j]->maxscaledenom)) continue;
588         if((lp->class[j]->minscaledenom > 0) && (map->scaledenom <= lp->class[j]->minscaledenom)) continue;
589       }
590       if(hittest && hittest->layerhits[layerindex].classhits[j].status == 0) continue;
591 
592       if(*text) {
593         initTextSymbol(&ts);
594         msPopulateTextSymbolForLabelAndString(&ts,&map->legend.label,msStrdup(text),resolutionfactor,resolutionfactor, 0);
595         if(UNLIKELY(MS_FAILURE == msGetTextSymbolSize(map,&ts,&rect))) {
596           freeTextSymbol(&ts);
597           return MS_FAILURE;
598         }
599         freeTextSymbol(&ts);
600 
601         maxwidth = MS_MAX(maxwidth, MS_NINT(rect.maxx - rect.minx));
602         *size_y += MS_MAX(MS_NINT(rect.maxy - rect.miny), map->legend.keysizey);
603       } else {
604         *size_y += map->legend.keysizey;
605       }
606       nLegendItems++;
607     }
608   }
609 
610   /* Calculate the size of the legend: */
611   /*   - account for the Y keyspacing */
612   *size_y += (2*VMARGIN) + ((nLegendItems-1) * map->legend.keyspacingy);
613   /*   - determine the legend width */
614   *size_x = (2*HMARGIN) + maxwidth + map->legend.keyspacingx + map->legend.keysizex;
615 
616   if(*size_y <=0 ||  *size_x <=0)
617     return MS_FAILURE;
618 
619   return MS_SUCCESS;
620 }
621 
622 /*
623 ** Creates a GD image of a legend for a specific map. msDrawLegend()
624 ** respects the current scale, and classes without a name are not
625 ** added to the legend.
626 **
627 ** scale_independent is used for WMS GetLegendGraphic. It should be set to
628 ** MS_FALSE in most cases. If it is set to MS_TRUE then the layers' minscale
629 ** and maxscale are ignored and layers that are currently out of scale still
630 ** show up in the legend.
631 */
msDrawLegend(mapObj * map,int scale_independent,map_hittest * hittest)632 imageObj *msDrawLegend(mapObj *map, int scale_independent, map_hittest *hittest)
633 {
634   int i,j,ret=MS_SUCCESS; /* loop counters */
635   pointObj pnt;
636   int size_x, size_y=0;
637   layerObj *lp;
638   rectObj rect;
639   imageObj *image = NULL;
640   outputFormatObj *format = NULL;
641   char *text;
642 
643   struct legend_struct {
644     int height;
645     textSymbolObj ts;
646     int layerindex, classindex;
647     struct legend_struct* pred;
648   };
649   typedef struct legend_struct legendlabel;
650   legendlabel *head=NULL,*cur=NULL;
651 
652   if(!MS_RENDERER_PLUGIN(map->outputformat)) {
653     msSetError(MS_MISCERR,"unsupported output format","msDrawLegend()");
654     return NULL;
655   }
656   if(msValidateContexts(map) != MS_SUCCESS) return NULL; /* make sure there are no recursive REQUIRES or LABELREQUIRES expressions */
657   if(msLegendCalcSize(map, scale_independent, &size_x, &size_y, NULL, 0, hittest, map->resolution/map->defresolution) != MS_SUCCESS) return NULL;
658 
659   /*
660    * step through all map classes, and for each one that will be displayed
661    * keep a reference to its label size and text
662    */
663 
664   for(i=0; i<map->numlayers; i++) {
665     lp = (GET_LAYER(map, map->layerorder[i]));
666 
667     if((lp->status == MS_OFF) || (lp->type == MS_LAYER_QUERY)) /* skip it */
668       continue;
669 
670     if(hittest && hittest->layerhits[map->layerorder[i]].status == 0)
671       continue;
672 
673     if(!scale_independent && map->scaledenom > 0) {
674       if((lp->maxscaledenom > 0) && (map->scaledenom > lp->maxscaledenom)) continue;
675       if((lp->minscaledenom > 0) && (map->scaledenom <= lp->minscaledenom)) continue;
676     }
677 
678     if(!scale_independent && lp->maxscaledenom <=0 && lp->minscaledenom <=0) {
679       if((lp->maxgeowidth > 0) && ((map->extent.maxx - map->extent.minx) > lp->maxgeowidth)) continue;
680       if((lp->mingeowidth > 0) && ((map->extent.maxx - map->extent.minx) < lp->mingeowidth)) continue;
681     }
682 
683     /* set the scale factor so that scale dependant symbols are drawn in the legend with their default size */
684     if(lp->sizeunits != MS_PIXELS) {
685       map->cellsize = msAdjustExtent(&(map->extent), map->width, map->height);
686       lp->scalefactor = (msInchesPerUnit(lp->sizeunits,0)/msInchesPerUnit(map->units,0)) / map->cellsize;
687     }
688 
689     for(j=lp->numclasses-1; j>=0; j--) {
690       text = lp->class[j]->title?lp->class[j]->title:lp->class[j]->name; /* point to the right legend text, title takes precedence */
691       if(!text) continue; /* skip it */
692 
693       /* skip the class if the classgroup is defined */
694       if(lp->classgroup && (lp->class[j]->group == NULL || strcasecmp(lp->class[j]->group, lp->classgroup) != 0))
695         continue;
696 
697       if(!scale_independent && map->scaledenom > 0) {  /* verify class scale here */
698         if((lp->class[j]->maxscaledenom > 0) && (map->scaledenom > lp->class[j]->maxscaledenom)) continue;
699         if((lp->class[j]->minscaledenom > 0) && (map->scaledenom <= lp->class[j]->minscaledenom)) continue;
700       }
701 
702       if(hittest && hittest->layerhits[map->layerorder[i]].classhits[j].status == 0) {
703           continue;
704       }
705 
706       cur = (legendlabel*) msSmallMalloc(sizeof(legendlabel));
707       initTextSymbol(&cur->ts);
708       if(*text) {
709         msPopulateTextSymbolForLabelAndString(&cur->ts,&map->legend.label,msStrdup(text),map->resolution/map->defresolution,map->resolution/map->defresolution, 0);
710         if(UNLIKELY(MS_FAILURE == msComputeTextPath(map,&cur->ts))) {
711           ret = MS_FAILURE;
712           goto cleanup;
713         }
714         if(UNLIKELY(MS_FAILURE == msGetTextSymbolSize(map,&cur->ts,&rect))) {
715           ret = MS_FAILURE;
716           goto cleanup;
717         }
718         cur->height = MS_MAX(MS_NINT(rect.maxy - rect.miny), map->legend.keysizey);
719       } else {
720         cur->height = map->legend.keysizey;
721       }
722 
723       cur->classindex = j;
724       cur->layerindex = map->layerorder[i];
725       cur->pred = head;
726       head = cur;
727     }
728   }
729 
730 
731   /* ensure we have an image format representing the options for the legend. */
732   msApplyOutputFormat(&format, map->outputformat, map->legend.transparent, map->legend.interlace, MS_NOOVERRIDE);
733 
734   /* initialize the legend image */
735   image = msImageCreate(size_x, size_y, format, map->web.imagepath, map->web.imageurl, map->resolution, map->defresolution, &map->legend.imagecolor);
736   if(!image) {
737     msSetError(MS_MISCERR, "Unable to initialize image.", "msDrawLegend()");
738     return NULL;
739   }
740   image->map = map;
741 
742   /* image = renderer->createImage(size_x,size_y,format,&(map->legend.imagecolor)); */
743 
744   /* drop this reference to output format */
745   msApplyOutputFormat(&format, NULL, MS_NOOVERRIDE, MS_NOOVERRIDE, MS_NOOVERRIDE);
746 
747   pnt.y = VMARGIN;
748   pnt.x = HMARGIN + map->legend.keysizex + map->legend.keyspacingx;
749 
750   while(cur) { /* cur initially points on the last legend item, i.e. the one that should be at the top */
751     class_hittest *ch = NULL;
752 
753     /* set the scale factor so that scale dependant symbols are drawn in the legend with their default size */
754     if(map->layers[cur->layerindex]->sizeunits != MS_PIXELS) {
755       map->cellsize = msAdjustExtent(&(map->extent), map->width, map->height);
756       map->layers[cur->layerindex]->scalefactor = (msInchesPerUnit(map->layers[cur->layerindex]->sizeunits,0)/msInchesPerUnit(map->units,0)) / map->cellsize;
757     }
758     if(hittest) {
759       ch = &hittest->layerhits[cur->layerindex].classhits[cur->classindex];
760     }
761     ret = msDrawLegendIcon(map, map->layers[cur->layerindex], map->layers[cur->layerindex]->class[cur->classindex],  map->legend.keysizex,  map->legend.keysizey, image, HMARGIN, (int) pnt.y, scale_independent, ch);
762     if(UNLIKELY(ret != MS_SUCCESS))
763       goto cleanup;
764 
765     pnt.y += cur->height;
766 
767     if(cur->ts.annotext) {
768       pointObj textPnt = pnt;
769       textPnt.y -= cur->ts.textpath->bounds.bbox.maxy;
770       textPnt.y += map->legend.label.offsety;
771       textPnt.x += map->legend.label.offsetx;
772       ret = msDrawTextSymbol(map,image,textPnt,&cur->ts);
773       if(UNLIKELY(ret == MS_FAILURE))
774         goto cleanup;
775       freeTextSymbol(&cur->ts);
776     }
777 
778     pnt.y += map->legend.keyspacingy; /* bump y for next label */
779 
780     /* clean up */
781     head = cur;
782     cur = cur->pred;
783     free(head);
784   } /* next legend */
785 
786 cleanup:
787   while(cur) {
788     freeTextSymbol(&cur->ts);
789     head = cur;
790     cur = cur->pred;
791     free(head);
792   }
793   if(UNLIKELY(ret != MS_SUCCESS)) {
794     if(image) msFreeImage(image);
795     return NULL;
796   }
797   return(image);
798 }
799 
800 /* TODO */
msEmbedLegend(mapObj * map,imageObj * img)801 int msEmbedLegend(mapObj *map, imageObj *img)
802 {
803   int s,l;
804   pointObj point;
805   imageObj *image = NULL;
806   symbolObj *legendSymbol;
807   char* imageType = NULL;
808 
809   rendererVTableObj *renderer;
810 
811   s = msGetSymbolIndex(&(map->symbolset), "legend", MS_FALSE);
812   if(s != -1)
813     msRemoveSymbol(&(map->symbolset), s); /* solves some caching issues in AGG with long-running processes */
814 
815   if(msGrowSymbolSet(&map->symbolset) == NULL)
816     return -1;
817   s = map->symbolset.numsymbols;
818   legendSymbol = map->symbolset.symbol[s];
819   map->symbolset.numsymbols++;
820   initSymbol(legendSymbol);
821 
822   if(!MS_RENDERER_PLUGIN(map->outputformat) || !MS_MAP_RENDERER(map)->supports_pixel_buffer) {
823     imageType = msStrdup(map->imagetype); /* save format */
824     if MS_DRIVER_CAIRO(map->outputformat)
825       map->outputformat = msSelectOutputFormat( map, "cairopng" );
826     else
827       map->outputformat = msSelectOutputFormat( map, "png" );
828 
829     msInitializeRendererVTable(map->outputformat);
830   }
831   renderer = MS_MAP_RENDERER(map);
832 
833   /* render the legend. */
834   image = msDrawLegend(map, MS_FALSE, NULL);
835   if( image == NULL ) {
836     msFree(imageType);
837     return MS_FAILURE;
838   }
839 
840   if (imageType) {
841     map->outputformat = msSelectOutputFormat( map, imageType ); /* restore format */
842     msFree(imageType);
843   }
844 
845   /* copy renderered legend image into symbol */
846   legendSymbol->pixmap_buffer = calloc(1,sizeof(rasterBufferObj));
847   MS_CHECK_ALLOC(legendSymbol->pixmap_buffer, sizeof(rasterBufferObj), MS_FAILURE);
848 
849   if(MS_SUCCESS != renderer->getRasterBufferCopy(image,legendSymbol->pixmap_buffer))
850     return MS_FAILURE;
851   legendSymbol->renderer = renderer;
852 
853   msFreeImage( image );
854 
855   if(!legendSymbol->pixmap_buffer) return(MS_FAILURE); /* something went wrong creating scalebar */
856 
857   legendSymbol->type = MS_SYMBOL_PIXMAP; /* intialize a few things */
858   legendSymbol->name = msStrdup("legend");
859   legendSymbol->sizex = legendSymbol->pixmap_buffer->width;
860   legendSymbol->sizey = legendSymbol->pixmap_buffer->height;
861 
862   /* I'm not too sure this test is sufficient ... NFW. */
863   /* if(map->legend.transparent == MS_ON) */
864   /*  gdImageColorTransparent(legendSymbol->img_deprecated, 0); */
865 
866   switch(map->legend.position) {
867     case(MS_LL):
868       point.x = MS_NINT(legendSymbol->sizex/2.0);
869       point.y = map->height - MS_NINT(legendSymbol->sizey/2.0);
870       break;
871     case(MS_LR):
872       point.x = map->width - MS_NINT(legendSymbol->sizex/2.0);
873       point.y = map->height - MS_NINT(legendSymbol->sizey/2.0);
874       break;
875     case(MS_LC):
876       point.x = MS_NINT(map->width/2.0);
877       point.y = map->height - MS_NINT(legendSymbol->sizey/2.0);
878       break;
879     case(MS_UR):
880       point.x = map->width - MS_NINT(legendSymbol->sizex/2.0);
881       point.y = MS_NINT(legendSymbol->sizey/2.0);
882       break;
883     case(MS_UL):
884       point.x = MS_NINT(legendSymbol->sizex/2.0);
885       point.y = MS_NINT(legendSymbol->sizey/2.0);
886       break;
887     case(MS_UC):
888       point.x = MS_NINT(map->width/2.0);
889       point.y = MS_NINT(legendSymbol->sizey/2.0);
890       break;
891   }
892 
893   l = msGetLayerIndex(map, "__embed__legend");
894   if(l == -1) {
895     if(msGrowMapLayers(map) == NULL)
896       return(-1);
897     l = map->numlayers;
898     map->numlayers++;
899     if(initLayer((GET_LAYER(map, l)), map) == -1) return(-1);
900     GET_LAYER(map, l)->name = msStrdup("__embed__legend");
901     GET_LAYER(map, l)->type = MS_LAYER_POINT;
902 
903     if(msGrowLayerClasses( GET_LAYER(map, l) ) == NULL)
904       return(-1);
905     if(initClass(GET_LAYER(map, l)->class[0]) == -1) return(-1);
906     GET_LAYER(map, l)->numclasses = 1; /* so we make sure to free it */
907 
908     /* update the layer order list with the layer's index. */
909     map->layerorder[l] = l;
910   }
911 
912   GET_LAYER(map, l)->status = MS_ON;
913 
914   if(map->legend.postlabelcache) { /* add it directly to the image */
915     if(UNLIKELY(msMaybeAllocateClassStyle(GET_LAYER(map, l)->class[0], 0)==MS_FAILURE)) return MS_FAILURE;
916     GET_LAYER(map, l)->class[0]->styles[0]->symbol = s;
917     if(UNLIKELY(MS_FAILURE == msDrawMarkerSymbol(map, img, &point, GET_LAYER(map, l)->class[0]->styles[0], 1.0)))
918       return MS_FAILURE;
919   } else {
920     if(!GET_LAYER(map, l)->class[0]->labels) {
921       if(msGrowClassLabels(GET_LAYER(map, l)->class[0]) == NULL) return MS_FAILURE;
922       initLabel(GET_LAYER(map, l)->class[0]->labels[0]);
923       GET_LAYER(map, l)->class[0]->numlabels = 1;
924       GET_LAYER(map, l)->class[0]->labels[0]->force = MS_TRUE;
925       GET_LAYER(map, l)->class[0]->labels[0]->size = MS_MEDIUM; /* must set a size to have a valid label definition */
926       GET_LAYER(map, l)->class[0]->labels[0]->priority = MS_MAX_LABEL_PRIORITY;
927     }
928     if(GET_LAYER(map, l)->class[0]->labels[0]->numstyles == 0) {
929       if(msGrowLabelStyles(GET_LAYER(map,l)->class[0]->labels[0]) == NULL)
930         return(MS_FAILURE);
931       GET_LAYER(map,l)->class[0]->labels[0]->numstyles = 1;
932       initStyle(GET_LAYER(map,l)->class[0]->labels[0]->styles[0]);
933       GET_LAYER(map,l)->class[0]->labels[0]->styles[0]->_geomtransform.type = MS_GEOMTRANSFORM_LABELPOINT;
934     }
935     GET_LAYER(map,l)->class[0]->labels[0]->styles[0]->symbol = s;
936     if(UNLIKELY(MS_FAILURE == msAddLabel(map, img, GET_LAYER(map, l)->class[0]->labels[0], l, 0, NULL, &point, -1, NULL)))
937       return MS_FAILURE;
938   }
939 
940   /* Mark layer as deleted so that it doesn't interfere with html legends or with saving maps */
941   GET_LAYER(map, l)->status = MS_DELETE;
942 
943   return MS_SUCCESS;
944 }
945