| 1 | /* $Id$ $Revision$ */ | 
|---|
| 2 | /* vim:set shiftwidth=4 ts=8: */ | 
|---|
| 3 |  | 
|---|
| 4 | /************************************************************************* | 
|---|
| 5 | * Copyright (c) 2011 AT&T Intellectual Property | 
|---|
| 6 | * All rights reserved. This program and the accompanying materials | 
|---|
| 7 | * are made available under the terms of the Eclipse Public License v1.0 | 
|---|
| 8 | * which accompanies this distribution, and is available at | 
|---|
| 9 | * http://www.eclipse.org/legal/epl-v10.html | 
|---|
| 10 | * | 
|---|
| 11 | * Contributors: See CVS logs. Details at http://www.graphviz.org/ | 
|---|
| 12 | *************************************************************************/ | 
|---|
| 13 |  | 
|---|
| 14 | #include "config.h" | 
|---|
| 15 |  | 
|---|
| 16 | #include <stdlib.h> | 
|---|
| 17 | #include <stddef.h> | 
|---|
| 18 | #include <stdint.h> | 
|---|
| 19 | #include <string.h> | 
|---|
| 20 | #include <fcntl.h> | 
|---|
| 21 |  | 
|---|
| 22 | #include "gvplugin_render.h" | 
|---|
| 23 | #include "gvplugin_device.h" | 
|---|
| 24 | #include "gvcint.h"	/* for gvc->g for agget */ | 
|---|
| 25 | #include "gd.h" | 
|---|
| 26 |  | 
|---|
| 27 | #ifndef INT32_MAX | 
|---|
| 28 | #define INT32_MAX              (2147483647) | 
|---|
| 29 | #endif | 
|---|
| 30 | #ifndef UINT32_MAX | 
|---|
| 31 | #define UINT32_MAX             (4294967295U) | 
|---|
| 32 | #endif | 
|---|
| 33 |  | 
|---|
| 34 |  | 
|---|
| 35 | typedef enum { | 
|---|
| 36 | FORMAT_GIF, | 
|---|
| 37 | FORMAT_JPEG, | 
|---|
| 38 | FORMAT_PNG, | 
|---|
| 39 | FORMAT_WBMP, | 
|---|
| 40 | FORMAT_GD, | 
|---|
| 41 | FORMAT_GD2, | 
|---|
| 42 | FORMAT_XBM, | 
|---|
| 43 | } format_type; | 
|---|
| 44 |  | 
|---|
| 45 | extern boolean mapbool(char *); | 
|---|
| 46 | extern pointf Bezier(pointf * V, int degree, double t, pointf * Left, pointf * Right); | 
|---|
| 47 |  | 
|---|
| 48 | #define BEZIERSUBDIVISION 10 | 
|---|
| 49 |  | 
|---|
| 50 | static void gdgen_resolve_color(GVJ_t * job, gvcolor_t * color) | 
|---|
| 51 | { | 
|---|
| 52 | gdImagePtr im = (gdImagePtr) job->context; | 
|---|
| 53 | int alpha; | 
|---|
| 54 |  | 
|---|
| 55 | if (!im) | 
|---|
| 56 | return; | 
|---|
| 57 |  | 
|---|
| 58 | /* convert alpha (normally an "opacity" value) to gd's "transparency" */ | 
|---|
| 59 | alpha = (255 - color->u.rgba[3]) * gdAlphaMax / 255; | 
|---|
| 60 |  | 
|---|
| 61 | if(alpha == gdAlphaMax) | 
|---|
| 62 | color->u.index = gdImageGetTransparent(im); | 
|---|
| 63 | else | 
|---|
| 64 | color->u.index = gdImageColorResolveAlpha(im, | 
|---|
| 65 | color->u.rgba[0], | 
|---|
| 66 | color->u.rgba[1], | 
|---|
| 67 | color->u.rgba[2], | 
|---|
| 68 | alpha); | 
|---|
| 69 | color->type = COLOR_INDEX; | 
|---|
| 70 | } | 
|---|
| 71 |  | 
|---|
| 72 | static int white, black, transparent, basecolor; | 
|---|
| 73 |  | 
|---|
| 74 | #define GD_XYMAX INT32_MAX | 
|---|
| 75 |  | 
|---|
| 76 | static void gdgen_begin_page(GVJ_t * job) | 
|---|
| 77 | { | 
|---|
| 78 | char *bgcolor_str = NULL, *truecolor_str = NULL; | 
|---|
| 79 | boolean truecolor_p = FALSE;	/* try to use cheaper paletted mode */ | 
|---|
| 80 | boolean bg_transparent_p = FALSE; | 
|---|
| 81 | gdImagePtr im = NULL; | 
|---|
| 82 |  | 
|---|
| 83 | truecolor_str = agget((graph_t*)(job->gvc->g), "truecolor");	/* allow user to force truecolor */ | 
|---|
| 84 | bgcolor_str = agget((graph_t*)(job->gvc->g), "bgcolor"); | 
|---|
| 85 |  | 
|---|
| 86 | if (truecolor_str && truecolor_str[0]) | 
|---|
| 87 | truecolor_p = mapbool(truecolor_str); | 
|---|
| 88 |  | 
|---|
| 89 | if (bgcolor_str && strcmp(bgcolor_str, "transparent") == 0) { | 
|---|
| 90 | bg_transparent_p = TRUE; | 
|---|
| 91 | if (job->render.features->flags & GVDEVICE_DOES_TRUECOLOR) | 
|---|
| 92 | truecolor_p = TRUE;	/* force truecolor */ | 
|---|
| 93 | } | 
|---|
| 94 |  | 
|---|
| 95 | if (GD_has_images(job->gvc->g)) | 
|---|
| 96 | truecolor_p = TRUE;	/* force truecolor */ | 
|---|
| 97 |  | 
|---|
| 98 | if (job->external_context) { | 
|---|
| 99 | if (job->common->verbose) | 
|---|
| 100 | fprintf(stderr, "%s: using existing GD image\n", job->common->cmdname); | 
|---|
| 101 | im = (gdImagePtr) (job->context); | 
|---|
| 102 | } else { | 
|---|
| 103 | if (job->width * job->height >= GD_XYMAX) { | 
|---|
| 104 | double scale = sqrt(GD_XYMAX / (job->width * job->height)); | 
|---|
| 105 | job->width *= scale; | 
|---|
| 106 | job->height *= scale; | 
|---|
| 107 | job->zoom *= scale; | 
|---|
| 108 | fprintf(stderr, | 
|---|
| 109 | "%s: graph is too large for gd-renderer bitmaps. Scaling by %g to fit\n", | 
|---|
| 110 | job->common->cmdname, scale); | 
|---|
| 111 | } | 
|---|
| 112 | if (truecolor_p) { | 
|---|
| 113 | if (job->common->verbose) | 
|---|
| 114 | fprintf(stderr, | 
|---|
| 115 | "%s: allocating a %dK TrueColor GD image (%d x %d pixels)\n", | 
|---|
| 116 | job->common->cmdname, | 
|---|
| 117 | ROUND(job->width * job->height * 4 / 1024.), | 
|---|
| 118 | job->width, job->height); | 
|---|
| 119 | im = gdImageCreateTrueColor(job->width, job->height); | 
|---|
| 120 | } else { | 
|---|
| 121 | if (job->common->verbose) | 
|---|
| 122 | fprintf(stderr, | 
|---|
| 123 | "%s: allocating a %dK PaletteColor GD image (%d x %d pixels)\n", | 
|---|
| 124 | job->common->cmdname, | 
|---|
| 125 | ROUND(job->width * job->height / 1024.), | 
|---|
| 126 | job->width, job->height); | 
|---|
| 127 | im = gdImageCreate(job->width, job->height); | 
|---|
| 128 | } | 
|---|
| 129 | job->context = (void *) im; | 
|---|
| 130 | } | 
|---|
| 131 |  | 
|---|
| 132 | if (!im) { | 
|---|
| 133 | job->common->errorfn( "gdImageCreate returned NULL. Malloc problem?\n"); | 
|---|
| 134 | return; | 
|---|
| 135 | } | 
|---|
| 136 |  | 
|---|
| 137 | /* first color is the default background color */ | 
|---|
| 138 | /*   - used for margins - if any */ | 
|---|
| 139 | transparent = gdImageColorResolveAlpha(im, | 
|---|
| 140 | gdRedMax - 1, gdGreenMax, | 
|---|
| 141 | gdBlueMax, gdAlphaTransparent); | 
|---|
| 142 | gdImageColorTransparent(im, transparent); | 
|---|
| 143 |  | 
|---|
| 144 | white = gdImageColorResolveAlpha(im, | 
|---|
| 145 | gdRedMax, gdGreenMax, gdBlueMax, | 
|---|
| 146 | gdAlphaOpaque); | 
|---|
| 147 |  | 
|---|
| 148 | black = gdImageColorResolveAlpha(im, 0, 0, 0, gdAlphaOpaque); | 
|---|
| 149 |  | 
|---|
| 150 | /* Blending must be off to lay a transparent basecolor. | 
|---|
| 151 | Nothing to blend with anyway. */ | 
|---|
| 152 | gdImageAlphaBlending(im, FALSE); | 
|---|
| 153 | gdImageFill(im, im->sx / 2, im->sy / 2, transparent); | 
|---|
| 154 | /* Blend everything else together, | 
|---|
| 155 | especially fonts over non-transparent backgrounds */ | 
|---|
| 156 | gdImageAlphaBlending(im, TRUE); | 
|---|
| 157 | } | 
|---|
| 158 |  | 
|---|
| 159 | extern int gvdevice_gd_putBuf (gdIOCtx *context, const void *buffer, int len); | 
|---|
| 160 | extern void gvdevice_gd_putC (gdIOCtx *context, int C); | 
|---|
| 161 |  | 
|---|
| 162 | static void gdgen_end_page(GVJ_t * job) | 
|---|
| 163 | { | 
|---|
| 164 | gdImagePtr im = (gdImagePtr) job->context; | 
|---|
| 165 |  | 
|---|
| 166 | gdIOCtx ctx; | 
|---|
| 167 |  | 
|---|
| 168 | ctx.putBuf = gvdevice_gd_putBuf; | 
|---|
| 169 | ctx.putC = gvdevice_gd_putC; | 
|---|
| 170 | ctx.tell = (void*)job;    /* hide *job here */ | 
|---|
| 171 |  | 
|---|
| 172 | if (!im) | 
|---|
| 173 | return; | 
|---|
| 174 | if (job->external_context) { | 
|---|
| 175 | /* leave image in memory to be handled by Gdtclft output routines */ | 
|---|
| 176 | #ifdef MYTRACE | 
|---|
| 177 | fprintf(stderr, "gdgen_end_graph (to memory)\n"); | 
|---|
| 178 | #endif | 
|---|
| 179 | } else { | 
|---|
| 180 | /* Only save the alpha channel in outputs that support it if | 
|---|
| 181 | the base color was transparent.   Otherwise everything | 
|---|
| 182 | was blended so there is no useful alpha info */ | 
|---|
| 183 | gdImageSaveAlpha(im, (basecolor == transparent)); | 
|---|
| 184 | switch (job->render.id) { | 
|---|
| 185 | case FORMAT_GIF: | 
|---|
| 186 | #ifdef HAVE_GD_GIF | 
|---|
| 187 | gdImageTrueColorToPalette(im, 0, 256); | 
|---|
| 188 | gdImageGifCtx(im, &ctx); | 
|---|
| 189 | #endif | 
|---|
| 190 | break; | 
|---|
| 191 | case FORMAT_JPEG: | 
|---|
| 192 | #ifdef HAVE_GD_JPEG | 
|---|
| 193 | /* | 
|---|
| 194 | * Write IM to OUTFILE as a JFIF-formatted JPEG image, using | 
|---|
| 195 | * quality JPEG_QUALITY.  If JPEG_QUALITY is in the range | 
|---|
| 196 | * 0-100, increasing values represent higher quality but also | 
|---|
| 197 | * larger image size.  If JPEG_QUALITY is negative, the | 
|---|
| 198 | * IJG JPEG library's default quality is used (which should | 
|---|
| 199 | * be near optimal for many applications).  See the IJG JPEG | 
|---|
| 200 | * library documentation for more details.  */ | 
|---|
| 201 | #define JPEG_QUALITY -1 | 
|---|
| 202 | gdImageJpegCtx(im, &ctx, JPEG_QUALITY); | 
|---|
| 203 | #endif | 
|---|
| 204 |  | 
|---|
| 205 | break; | 
|---|
| 206 | case FORMAT_PNG: | 
|---|
| 207 | #ifdef HAVE_GD_PNG | 
|---|
| 208 | gdImagePngCtx(im, &ctx); | 
|---|
| 209 | #endif | 
|---|
| 210 | break; | 
|---|
| 211 |  | 
|---|
| 212 | #ifdef HAVE_GD_GIF | 
|---|
| 213 | case FORMAT_WBMP: | 
|---|
| 214 | { | 
|---|
| 215 | /* Use black for the foreground color for the B&W wbmp image. */ | 
|---|
| 216 | int black = gdImageColorResolveAlpha(im, 0, 0, 0, gdAlphaOpaque); | 
|---|
| 217 | gdImageWBMPCtx(im, black, &ctx); | 
|---|
| 218 | } | 
|---|
| 219 | break; | 
|---|
| 220 | #endif | 
|---|
| 221 |  | 
|---|
| 222 | case FORMAT_GD: | 
|---|
| 223 | gdImageGd(im, job->output_file); | 
|---|
| 224 | break; | 
|---|
| 225 |  | 
|---|
| 226 | #ifdef HAVE_LIBZ | 
|---|
| 227 | case FORMAT_GD2: | 
|---|
| 228 | #define GD2_CHUNKSIZE 128 | 
|---|
| 229 | #define GD2_RAW 1 | 
|---|
| 230 | #define GD2_COMPRESSED 2 | 
|---|
| 231 | gdImageGd2(im, job->output_file, GD2_CHUNKSIZE, GD2_COMPRESSED); | 
|---|
| 232 | break; | 
|---|
| 233 | #endif | 
|---|
| 234 |  | 
|---|
| 235 | case FORMAT_XBM: | 
|---|
| 236 | #if 0 | 
|---|
| 237 | /* libgd support only reading .xpm files */ | 
|---|
| 238 | #ifdef HAVE_GD_XPM | 
|---|
| 239 | gdImageXbm(im, job->output_file); | 
|---|
| 240 | #endif | 
|---|
| 241 | #endif | 
|---|
| 242 | break; | 
|---|
| 243 | } | 
|---|
| 244 | gdImageDestroy(im); | 
|---|
| 245 | #ifdef MYTRACE | 
|---|
| 246 | fprintf(stderr, "gdgen_end_graph (to file)\n"); | 
|---|
| 247 | #endif | 
|---|
| 248 | job->context = NULL; | 
|---|
| 249 | } | 
|---|
| 250 | } | 
|---|
| 251 |  | 
|---|
| 252 | static void gdgen_missingfont(char *err, char *fontreq) | 
|---|
| 253 | { | 
|---|
| 254 | static char *lastmissing = 0; | 
|---|
| 255 | static int n_errors = 0; | 
|---|
| 256 |  | 
|---|
| 257 | if (n_errors >= 20) | 
|---|
| 258 | return; | 
|---|
| 259 | if ((lastmissing == 0) || (strcmp(lastmissing, fontreq))) { | 
|---|
| 260 | #ifdef HAVE_GD_FONTCONFIG | 
|---|
| 261 | #if 0 | 
|---|
| 262 | /* FIXME - error function */ | 
|---|
| 263 | agerr(AGERR, "%s : %s\n", err, fontreq); | 
|---|
| 264 | #endif | 
|---|
| 265 | #else | 
|---|
| 266 | char *p = getenv( "GDFONTPATH"); | 
|---|
| 267 | if (!p) | 
|---|
| 268 | p = DEFAULT_FONTPATH; | 
|---|
| 269 | #if 0 | 
|---|
| 270 | /* FIXME - error function */ | 
|---|
| 271 | agerr(AGERR, "%s : %s in %s\n", err, fontreq, p); | 
|---|
| 272 | #endif | 
|---|
| 273 | #endif | 
|---|
| 274 | if (lastmissing) | 
|---|
| 275 | free(lastmissing); | 
|---|
| 276 | lastmissing = strdup(fontreq); | 
|---|
| 277 | n_errors++; | 
|---|
| 278 | #if 0 | 
|---|
| 279 | /* FIXME - error function */ | 
|---|
| 280 | if (n_errors >= 20) | 
|---|
| 281 | agerr(AGWARN, "(font errors suppressed)\n"); | 
|---|
| 282 | #endif | 
|---|
| 283 | } | 
|---|
| 284 | } | 
|---|
| 285 |  | 
|---|
| 286 | /* fontsize at which text is omitted entirely */ | 
|---|
| 287 | #define FONTSIZE_MUCH_TOO_SMALL 0.15 | 
|---|
| 288 | /* fontsize at which text is rendered by a simple line */ | 
|---|
| 289 | #define FONTSIZE_TOO_SMALL 1.5 | 
|---|
| 290 |  | 
|---|
| 291 | #ifdef _WIN32 | 
|---|
| 292 | #   define GD_IMPORT __declspec(dllimport) | 
|---|
| 293 | #else | 
|---|
| 294 | #   define GD_IMPORT extern | 
|---|
| 295 | #endif | 
|---|
| 296 | GD_IMPORT gdFontPtr gdFontTiny, gdFontSmall, gdFontMediumBold, gdFontLarge, gdFontGiant; | 
|---|
| 297 |  | 
|---|
| 298 | void gdgen_text(gdImagePtr im, pointf spf, pointf epf, int fontcolor, double fontsize, int fontdpi, double fontangle, char *fontname, char *str) | 
|---|
| 299 | { | 
|---|
| 300 | gdFTStringExtra strex; | 
|---|
| 301 | point sp, ep; /* start point, end point, in pixels */ | 
|---|
| 302 |  | 
|---|
| 303 | PF2P(spf, sp); | 
|---|
| 304 | PF2P(epf, ep); | 
|---|
| 305 |  | 
|---|
| 306 | strex.flags = gdFTEX_RESOLUTION; | 
|---|
| 307 | strex.hdpi = strex.vdpi = fontdpi; | 
|---|
| 308 |  | 
|---|
| 309 | if (strstr(fontname, "/")) | 
|---|
| 310 | strex.flags |= gdFTEX_FONTPATHNAME; | 
|---|
| 311 | else | 
|---|
| 312 | strex.flags |= gdFTEX_FONTCONFIG; | 
|---|
| 313 |  | 
|---|
| 314 | if (fontsize <= FONTSIZE_MUCH_TOO_SMALL) { | 
|---|
| 315 | /* ignore entirely */ | 
|---|
| 316 | } else if (fontsize <= FONTSIZE_TOO_SMALL) { | 
|---|
| 317 | /* draw line in place of text */ | 
|---|
| 318 | gdImageLine(im, sp.x, sp.y, ep.x, ep.y, fontcolor); | 
|---|
| 319 | } else { | 
|---|
| 320 | #ifdef HAVE_GD_FREETYPE | 
|---|
| 321 | char *err; | 
|---|
| 322 | int brect[8]; | 
|---|
| 323 | #ifdef HAVE_GD_FONTCONFIG | 
|---|
| 324 | char* fontlist = fontname; | 
|---|
| 325 | #else | 
|---|
| 326 | extern char *gd_alternate_fontlist(char *font); | 
|---|
| 327 | char* fontlist = gd_alternate_fontlist(fontname); | 
|---|
| 328 | #endif | 
|---|
| 329 | err = gdImageStringFTEx(im, brect, fontcolor, | 
|---|
| 330 | fontlist, fontsize, fontangle, sp.x, sp.y, str, &strex); | 
|---|
| 331 |  | 
|---|
| 332 | if (err) { | 
|---|
| 333 | /* revert to builtin fonts */ | 
|---|
| 334 | gdgen_missingfont(err, fontname); | 
|---|
| 335 | #endif | 
|---|
| 336 | sp.y += 2; | 
|---|
| 337 | if (fontsize <= 8.5) { | 
|---|
| 338 | gdImageString(im, gdFontTiny, sp.x, sp.y - 9, (unsigned char*)str, fontcolor); | 
|---|
| 339 | } else if (fontsize <= 9.5) { | 
|---|
| 340 | gdImageString(im, gdFontSmall, sp.x, sp.y - 12, (unsigned char*)str, fontcolor); | 
|---|
| 341 | } else if (fontsize <= 10.5) { | 
|---|
| 342 | gdImageString(im, gdFontMediumBold, sp.x, sp.y - 13, (unsigned char*)str, fontcolor); | 
|---|
| 343 | } else if (fontsize <= 11.5) { | 
|---|
| 344 | gdImageString(im, gdFontLarge, sp.x, sp.y - 14, (unsigned char*)str, fontcolor); | 
|---|
| 345 | } else { | 
|---|
| 346 | gdImageString(im, gdFontGiant, sp.x, sp.y - 15, (unsigned char*)str, fontcolor); | 
|---|
| 347 | } | 
|---|
| 348 | #ifdef HAVE_GD_FREETYPE | 
|---|
| 349 | } | 
|---|
| 350 | #endif | 
|---|
| 351 | } | 
|---|
| 352 | } | 
|---|
| 353 |  | 
|---|
| 354 | extern char* gd_psfontResolve (PostscriptAlias* pa); | 
|---|
| 355 |  | 
|---|
| 356 | static void gdgen_textspan(GVJ_t * job, pointf p, textspan_t * span) | 
|---|
| 357 | { | 
|---|
| 358 | gdImagePtr im = (gdImagePtr) job->context; | 
|---|
| 359 | pointf spf, epf; | 
|---|
| 360 | double spanwidth = span->size.x * job->zoom * job->dpi.x / POINTS_PER_INCH; | 
|---|
| 361 | char* fontname; | 
|---|
| 362 | #ifdef HAVE_GD_FONTCONFIG | 
|---|
| 363 | PostscriptAlias *pA; | 
|---|
| 364 | #endif | 
|---|
| 365 |  | 
|---|
| 366 | if (!im) | 
|---|
| 367 | return; | 
|---|
| 368 |  | 
|---|
| 369 | switch (span->just) { | 
|---|
| 370 | case 'l': | 
|---|
| 371 | spf.x = 0.0; | 
|---|
| 372 | break; | 
|---|
| 373 | case 'r': | 
|---|
| 374 | spf.x = -spanwidth; | 
|---|
| 375 | break; | 
|---|
| 376 | default: | 
|---|
| 377 | case 'n': | 
|---|
| 378 | spf.x = -spanwidth / 2; | 
|---|
| 379 | break; | 
|---|
| 380 | } | 
|---|
| 381 | epf.x = spf.x + spanwidth; | 
|---|
| 382 |  | 
|---|
| 383 | if (job->rotation) { | 
|---|
| 384 | spf.y = -spf.x + p.y; | 
|---|
| 385 | epf.y = epf.x + p.y; | 
|---|
| 386 | epf.x = spf.x = p.x; | 
|---|
| 387 | } | 
|---|
| 388 | else { | 
|---|
| 389 | spf.x += p.x; | 
|---|
| 390 | epf.x += p.x; | 
|---|
| 391 | epf.y = spf.y = p.y - span->yoffset_centerline * job->zoom * job->dpi.x / POINTS_PER_INCH; | 
|---|
| 392 | } | 
|---|
| 393 |  | 
|---|
| 394 | #ifdef HAVE_GD_FONTCONFIG | 
|---|
| 395 | pA = span->font->postscript_alias; | 
|---|
| 396 | if (pA) | 
|---|
| 397 | fontname = gd_psfontResolve (pA); | 
|---|
| 398 | else | 
|---|
| 399 | #endif | 
|---|
| 400 | fontname = span->font->name; | 
|---|
| 401 |  | 
|---|
| 402 | gdgen_text(im, spf, epf, | 
|---|
| 403 | job->obj->pencolor.u.index, | 
|---|
| 404 | span->font->size * job->zoom, | 
|---|
| 405 | job->dpi.x, | 
|---|
| 406 | job->rotation ? (M_PI / 2) : 0, | 
|---|
| 407 | fontname, | 
|---|
| 408 | span->str); | 
|---|
| 409 | } | 
|---|
| 410 |  | 
|---|
| 411 | static int gdgen_set_penstyle(GVJ_t * job, gdImagePtr im, gdImagePtr* brush) | 
|---|
| 412 | { | 
|---|
| 413 | obj_state_t *obj = job->obj; | 
|---|
| 414 | int i, pen, width, dashstyle[40]; | 
|---|
| 415 |  | 
|---|
| 416 | if (obj->pen == PEN_DASHED) { | 
|---|
| 417 | for (i = 0; i < 10; i++) | 
|---|
| 418 | dashstyle[i] = obj->pencolor.u.index; | 
|---|
| 419 | for (; i < 20; i++) | 
|---|
| 420 | dashstyle[i] = gdTransparent; | 
|---|
| 421 | gdImageSetStyle(im, dashstyle, 20); | 
|---|
| 422 | pen = gdStyled; | 
|---|
| 423 | } else if (obj->pen == PEN_DOTTED) { | 
|---|
| 424 | for (i = 0; i < 2; i++) | 
|---|
| 425 | dashstyle[i] = obj->pencolor.u.index; | 
|---|
| 426 | for (; i < 14; i++) | 
|---|
| 427 | dashstyle[i] = gdTransparent; | 
|---|
| 428 | gdImageSetStyle(im, dashstyle, 12); | 
|---|
| 429 | pen = gdStyled; | 
|---|
| 430 | } else { | 
|---|
| 431 | pen = obj->pencolor.u.index; | 
|---|
| 432 | } | 
|---|
| 433 |  | 
|---|
| 434 | width = obj->penwidth * job->zoom; | 
|---|
| 435 | if (width < PENWIDTH_NORMAL) | 
|---|
| 436 | width = PENWIDTH_NORMAL;  /* gd can't do thin lines */ | 
|---|
| 437 | gdImageSetThickness(im, width); | 
|---|
| 438 | /* use brush instead of Thickness to improve end butts */ | 
|---|
| 439 | if (width != PENWIDTH_NORMAL) { | 
|---|
| 440 | if (im->trueColor) { | 
|---|
| 441 | *brush = gdImageCreateTrueColor(width,width); | 
|---|
| 442 | } | 
|---|
| 443 | else { | 
|---|
| 444 | *brush = gdImageCreate(width, width); | 
|---|
| 445 | gdImagePaletteCopy(*brush, im); | 
|---|
| 446 | } | 
|---|
| 447 | gdImageFilledRectangle(*brush, 0, 0, width - 1, width - 1, | 
|---|
| 448 | obj->pencolor.u.index); | 
|---|
| 449 | gdImageSetBrush(im, *brush); | 
|---|
| 450 | if (pen == gdStyled) | 
|---|
| 451 | pen = gdStyledBrushed; | 
|---|
| 452 | else | 
|---|
| 453 | pen = gdBrushed; | 
|---|
| 454 | } | 
|---|
| 455 |  | 
|---|
| 456 | return pen; | 
|---|
| 457 | } | 
|---|
| 458 |  | 
|---|
| 459 | static void | 
|---|
| 460 | gdgen_bezier(GVJ_t * job, pointf * A, int n, int arrow_at_start, | 
|---|
| 461 | int arrow_at_end, int filled) | 
|---|
| 462 | { | 
|---|
| 463 | obj_state_t *obj = job->obj; | 
|---|
| 464 | gdImagePtr im = (gdImagePtr) job->context; | 
|---|
| 465 | pointf p0, p1, V[4]; | 
|---|
| 466 | int i, j, step, pen; | 
|---|
| 467 | boolean pen_ok, fill_ok; | 
|---|
| 468 | gdImagePtr brush = NULL; | 
|---|
| 469 | gdPoint F[4]; | 
|---|
| 470 |  | 
|---|
| 471 | if (!im) | 
|---|
| 472 | return; | 
|---|
| 473 |  | 
|---|
| 474 | pen = gdgen_set_penstyle(job, im, &brush); | 
|---|
| 475 | pen_ok = (pen != gdImageGetTransparent(im)); | 
|---|
| 476 | fill_ok = (filled && obj->fillcolor.u.index != gdImageGetTransparent(im)); | 
|---|
| 477 |  | 
|---|
| 478 | if (pen_ok || fill_ok) { | 
|---|
| 479 | V[3] = A[0]; | 
|---|
| 480 | PF2P(A[0], F[0]); | 
|---|
| 481 | PF2P(A[n-1], F[3]); | 
|---|
| 482 | for (i = 0; i + 3 < n; i += 3) { | 
|---|
| 483 | V[0] = V[3]; | 
|---|
| 484 | for (j = 1; j <= 3; j++) | 
|---|
| 485 | V[j] = A[i + j]; | 
|---|
| 486 | p0 = V[0]; | 
|---|
| 487 | for (step = 1; step <= BEZIERSUBDIVISION; step++) { | 
|---|
| 488 | p1 = Bezier(V, 3, (double) step / BEZIERSUBDIVISION, NULL, NULL); | 
|---|
| 489 | PF2P(p0, F[1]); | 
|---|
| 490 | PF2P(p1, F[2]); | 
|---|
| 491 | if (pen_ok) | 
|---|
| 492 | gdImageLine(im, F[1].x, F[1].y, F[2].x, F[2].y, pen); | 
|---|
| 493 | if (fill_ok) | 
|---|
| 494 | gdImageFilledPolygon(im, F, 4, obj->fillcolor.u.index); | 
|---|
| 495 | p0 = p1; | 
|---|
| 496 | } | 
|---|
| 497 | } | 
|---|
| 498 | } | 
|---|
| 499 | if (brush) | 
|---|
| 500 | gdImageDestroy(brush); | 
|---|
| 501 | } | 
|---|
| 502 |  | 
|---|
| 503 | static gdPoint *points; | 
|---|
| 504 | static int points_allocated; | 
|---|
| 505 |  | 
|---|
| 506 | static void gdgen_polygon(GVJ_t * job, pointf * A, int n, int filled) | 
|---|
| 507 | { | 
|---|
| 508 | obj_state_t *obj = job->obj; | 
|---|
| 509 | gdImagePtr im = (gdImagePtr) job->context; | 
|---|
| 510 | gdImagePtr brush = NULL; | 
|---|
| 511 | int i; | 
|---|
| 512 | int pen; | 
|---|
| 513 | boolean pen_ok, fill_ok; | 
|---|
| 514 |  | 
|---|
| 515 | if (!im) | 
|---|
| 516 | return; | 
|---|
| 517 |  | 
|---|
| 518 | pen = gdgen_set_penstyle(job, im, &brush); | 
|---|
| 519 | pen_ok = (pen != gdImageGetTransparent(im)); | 
|---|
| 520 | fill_ok = (filled && obj->fillcolor.u.index != gdImageGetTransparent(im)); | 
|---|
| 521 |  | 
|---|
| 522 | if (pen_ok || fill_ok) { | 
|---|
| 523 | if (n > points_allocated) { | 
|---|
| 524 | points = realloc(points, n * sizeof(gdPoint)); | 
|---|
| 525 | points_allocated = n; | 
|---|
| 526 | } | 
|---|
| 527 | for (i = 0; i < n; i++) { | 
|---|
| 528 | points[i].x = ROUND(A[i].x); | 
|---|
| 529 | points[i].y = ROUND(A[i].y); | 
|---|
| 530 | } | 
|---|
| 531 | if (fill_ok) | 
|---|
| 532 | gdImageFilledPolygon(im, points, n, obj->fillcolor.u.index); | 
|---|
| 533 |  | 
|---|
| 534 | if (pen_ok) | 
|---|
| 535 | gdImagePolygon(im, points, n, pen); | 
|---|
| 536 | } | 
|---|
| 537 | if (brush) | 
|---|
| 538 | gdImageDestroy(brush); | 
|---|
| 539 | } | 
|---|
| 540 |  | 
|---|
| 541 | static void gdgen_ellipse(GVJ_t * job, pointf * A, int filled) | 
|---|
| 542 | { | 
|---|
| 543 | obj_state_t *obj = job->obj; | 
|---|
| 544 | gdImagePtr im = (gdImagePtr) job->context; | 
|---|
| 545 | double dx, dy; | 
|---|
| 546 | int pen; | 
|---|
| 547 | boolean pen_ok, fill_ok; | 
|---|
| 548 | gdImagePtr brush = NULL; | 
|---|
| 549 |  | 
|---|
| 550 | if (!im) | 
|---|
| 551 | return; | 
|---|
| 552 |  | 
|---|
| 553 | pen = gdgen_set_penstyle(job, im, &brush); | 
|---|
| 554 | pen_ok = (pen != gdImageGetTransparent(im)); | 
|---|
| 555 | fill_ok = (filled && obj->fillcolor.u.index != gdImageGetTransparent(im)); | 
|---|
| 556 |  | 
|---|
| 557 | dx = 2 * (A[1].x - A[0].x); | 
|---|
| 558 | dy = 2 * (A[1].y - A[0].y); | 
|---|
| 559 |  | 
|---|
| 560 | if (fill_ok) | 
|---|
| 561 | gdImageFilledEllipse(im, ROUND(A[0].x), ROUND(A[0].y), | 
|---|
| 562 | ROUND(dx), ROUND(dy), | 
|---|
| 563 | obj->fillcolor.u.index); | 
|---|
| 564 | if (pen_ok) | 
|---|
| 565 | gdImageArc(im, ROUND(A[0].x), ROUND(A[0].y), ROUND(dx), ROUND(dy), | 
|---|
| 566 | 0, 360, pen); | 
|---|
| 567 | if (brush) | 
|---|
| 568 | gdImageDestroy(brush); | 
|---|
| 569 | } | 
|---|
| 570 |  | 
|---|
| 571 | static void gdgen_polyline(GVJ_t * job, pointf * A, int n) | 
|---|
| 572 | { | 
|---|
| 573 | gdImagePtr im = (gdImagePtr) job->context; | 
|---|
| 574 | pointf p, p1; | 
|---|
| 575 | int i; | 
|---|
| 576 | int pen; | 
|---|
| 577 | boolean pen_ok; | 
|---|
| 578 | gdImagePtr brush = NULL; | 
|---|
| 579 |  | 
|---|
| 580 | if (!im) | 
|---|
| 581 | return; | 
|---|
| 582 |  | 
|---|
| 583 | pen = gdgen_set_penstyle(job, im, &brush); | 
|---|
| 584 | pen_ok = (pen != gdImageGetTransparent(im)); | 
|---|
| 585 |  | 
|---|
| 586 | if (pen_ok) { | 
|---|
| 587 | p = A[0]; | 
|---|
| 588 | for (i = 1; i < n; i++) { | 
|---|
| 589 | p1 = A[i]; | 
|---|
| 590 | gdImageLine(im, ROUND(p.x), ROUND(p.y), | 
|---|
| 591 | ROUND(p1.x), ROUND(p1.y), pen); | 
|---|
| 592 | p = p1; | 
|---|
| 593 | } | 
|---|
| 594 | } | 
|---|
| 595 | if (brush) | 
|---|
| 596 | gdImageDestroy(brush); | 
|---|
| 597 | } | 
|---|
| 598 |  | 
|---|
| 599 | static gvrender_engine_t gdgen_engine = { | 
|---|
| 600 | 0,				/* gdgen_begin_job */ | 
|---|
| 601 | 0,				/* gdgen_end_job */ | 
|---|
| 602 | 0,				/* gdgen_begin_graph */ | 
|---|
| 603 | 0,				/* gdgen_end_graph */ | 
|---|
| 604 | 0,				/* gdgen_begin_layer */ | 
|---|
| 605 | 0,				/* gdgen_end_layer */ | 
|---|
| 606 | gdgen_begin_page, | 
|---|
| 607 | gdgen_end_page, | 
|---|
| 608 | 0,				/* gdgen_begin_cluster */ | 
|---|
| 609 | 0,				/* gdgen_end_cluster */ | 
|---|
| 610 | 0,				/* gdgen_begin_nodes */ | 
|---|
| 611 | 0,				/* gdgen_end_nodes */ | 
|---|
| 612 | 0,				/* gdgen_begin_edges */ | 
|---|
| 613 | 0,				/* gdgen_end_edges */ | 
|---|
| 614 | 0,				/* gdgen_begin_node */ | 
|---|
| 615 | 0,				/* gdgen_end_node */ | 
|---|
| 616 | 0,				/* gdgen_begin_edge */ | 
|---|
| 617 | 0,				/* gdgen_end_edge */ | 
|---|
| 618 | 0,				/* gdgen_begin_anchor */ | 
|---|
| 619 | 0,				/* gdgen_end_anchor */ | 
|---|
| 620 | 0,				/* gdgen_begin_label */ | 
|---|
| 621 | 0,				/* gdgen_end_label */ | 
|---|
| 622 | gdgen_textspan, | 
|---|
| 623 | gdgen_resolve_color, | 
|---|
| 624 | gdgen_ellipse, | 
|---|
| 625 | gdgen_polygon, | 
|---|
| 626 | gdgen_bezier, | 
|---|
| 627 | gdgen_polyline, | 
|---|
| 628 | 0,				/* gdgen_comment */ | 
|---|
| 629 | 0,				/* gdgen_library_shape */ | 
|---|
| 630 | }; | 
|---|
| 631 |  | 
|---|
| 632 | static gvrender_features_t render_features_gd = { | 
|---|
| 633 | GVRENDER_Y_GOES_DOWN,	/* flags */ | 
|---|
| 634 | 4.,                         /* default pad - graph units */ | 
|---|
| 635 | NULL,			/* knowncolors */ | 
|---|
| 636 | 0,				/* sizeof knowncolors */ | 
|---|
| 637 | RGBA_BYTE,			/* color_type */ | 
|---|
| 638 | }; | 
|---|
| 639 |  | 
|---|
| 640 | static gvdevice_features_t device_features_gd = { | 
|---|
| 641 | GVDEVICE_BINARY_FORMAT,	/* flags */ | 
|---|
| 642 | {0.,0.},			/* default margin - points */ | 
|---|
| 643 | {0.,0.},                    /* default page width, height - points */ | 
|---|
| 644 | {96.,96.},			/* default dpi */ | 
|---|
| 645 | }; | 
|---|
| 646 |  | 
|---|
| 647 | static gvdevice_features_t device_features_gd_tc = { | 
|---|
| 648 | GVDEVICE_BINARY_FORMAT | 
|---|
| 649 | | GVDEVICE_DOES_TRUECOLOR,/* flags */ | 
|---|
| 650 | {0.,0.},			/* default margin - points */ | 
|---|
| 651 | {0.,0.},                    /* default page width, height - points */ | 
|---|
| 652 | {96.,96.},			/* default dpi */ | 
|---|
| 653 | }; | 
|---|
| 654 |  | 
|---|
| 655 | static gvdevice_features_t device_features_gd_tc_no_writer = { | 
|---|
| 656 | GVDEVICE_BINARY_FORMAT | 
|---|
| 657 | | GVDEVICE_DOES_TRUECOLOR | 
|---|
| 658 | | GVDEVICE_NO_WRITER,	/* flags */ | 
|---|
| 659 | {0.,0.},			/* default margin - points */ | 
|---|
| 660 | {0.,0.},                    /* default page width, height - points */ | 
|---|
| 661 | {96.,96.},			/* default dpi */ | 
|---|
| 662 | }; | 
|---|
| 663 |  | 
|---|
| 664 | gvplugin_installed_t gvrender_gd_types[] = { | 
|---|
| 665 | {FORMAT_GD, "gd", 1, &gdgen_engine, &render_features_gd}, | 
|---|
| 666 | {0, NULL, 0, NULL, NULL} | 
|---|
| 667 | }; | 
|---|
| 668 |  | 
|---|
| 669 | gvplugin_installed_t gvdevice_gd_types2[] = { | 
|---|
| 670 | #ifdef HAVE_GD_GIF | 
|---|
| 671 | {FORMAT_GIF, "gif:gd", 1, NULL, &device_features_gd_tc},  /* pretend gif is truecolor because it supports transparency */ | 
|---|
| 672 | {FORMAT_WBMP, "wbmp:gd", 1, NULL, &device_features_gd}, | 
|---|
| 673 | #endif | 
|---|
| 674 |  | 
|---|
| 675 | #ifdef HAVE_GD_JPEG | 
|---|
| 676 | {FORMAT_JPEG, "jpe:gd", 1, NULL, &device_features_gd}, | 
|---|
| 677 | {FORMAT_JPEG, "jpeg:gd", 1, NULL, &device_features_gd}, | 
|---|
| 678 | {FORMAT_JPEG, "jpg:gd", 1, NULL, &device_features_gd}, | 
|---|
| 679 | #endif | 
|---|
| 680 |  | 
|---|
| 681 | #ifdef HAVE_GD_PNG | 
|---|
| 682 | {FORMAT_PNG, "png:gd", 1, NULL, &device_features_gd_tc}, | 
|---|
| 683 | #endif | 
|---|
| 684 |  | 
|---|
| 685 | {FORMAT_GD, "gd:gd", 1, NULL, &device_features_gd_tc_no_writer}, | 
|---|
| 686 |  | 
|---|
| 687 | #ifdef HAVE_LIBZ | 
|---|
| 688 | {FORMAT_GD2, "gd2:gd", 1, NULL, &device_features_gd_tc_no_writer}, | 
|---|
| 689 | #endif | 
|---|
| 690 |  | 
|---|
| 691 | #if 0 | 
|---|
| 692 | /* libgd has no support for xbm as output */ | 
|---|
| 693 | #ifdef HAVE_GD_XPM | 
|---|
| 694 | {FORMAT_XBM, "xbm:gd", 1, NULL, &device_features_gd}, | 
|---|
| 695 | #endif | 
|---|
| 696 | #endif | 
|---|
| 697 | {0, NULL, 0, NULL, NULL} | 
|---|
| 698 | }; | 
|---|
| 699 |  | 
|---|