| 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 | |