gvrender_gd.c 17 KB

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