gvrender_gdiplus.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  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 <cassert>
  12. #include <climits>
  13. #include <stdlib.h>
  14. #include <string.h>
  15. #include <gvc/gvplugin_device.h>
  16. #include <gvc/gvplugin_render.h>
  17. #include <gvc/gvio.h>
  18. #include "gvplugin_gdiplus.h"
  19. #include <memory>
  20. #include <vector>
  21. using namespace std;
  22. using namespace Gdiplus;
  23. /* Graphics for internal use, so that we can record image etc. for subsequent retrieval off the job struct */
  24. struct ImageGraphics: public Graphics
  25. {
  26. Image *image;
  27. IStream *stream;
  28. ImageGraphics(Image *newImage, IStream *newStream):
  29. Graphics(newImage), image(newImage), stream(newStream)
  30. {
  31. }
  32. };
  33. static void gdiplusgen_begin_job(GVJ_t *job)
  34. {
  35. UseGdiplus();
  36. if (!job->external_context)
  37. job->context = nullptr;
  38. else if (job->device.id == FORMAT_METAFILE)
  39. {
  40. /* save the passed-in context in the window field, so we can create a Metafile in the context field later on */
  41. job->window = job->context;
  42. auto m = reinterpret_cast<Metafile**>(job->window);
  43. *m = nullptr;
  44. job->context = nullptr;
  45. }
  46. }
  47. static void gdiplusgen_end_job(GVJ_t *job)
  48. {
  49. auto context = reinterpret_cast<Graphics*>(job->context);
  50. if (!job->external_context) {
  51. /* flush and delete the graphics */
  52. ImageGraphics *imageGraphics = static_cast<ImageGraphics *>(context);
  53. Image *image = imageGraphics->image;
  54. IStream *stream = imageGraphics->stream;
  55. delete imageGraphics;
  56. switch (job->device.id) {
  57. case FORMAT_EMF:
  58. case FORMAT_EMFPLUS:
  59. case FORMAT_METAFILE:
  60. break;
  61. default:
  62. SaveBitmapToStream(*static_cast<Bitmap *>(image), stream, job->device.id);
  63. break;
  64. }
  65. delete image; /* NOTE: in the case of EMF, this actually flushes out the image to the underlying stream */
  66. /* blast the streamed buffer back to the gvdevice */
  67. /* NOTE: this is somewhat inefficient since we should be streaming directly to gvdevice rather than buffering first */
  68. /* ... however, GDI+ requires any such direct IStream to implement Seek Read, Write, Stat methods and gvdevice really only offers a write-once model */
  69. HGLOBAL buffer = nullptr;
  70. GetHGlobalFromStream(stream, &buffer);
  71. stream->Release();
  72. gvwrite(job, (char*)GlobalLock(buffer), GlobalSize(buffer));
  73. GlobalFree(buffer);
  74. }
  75. else if (job->device.id == FORMAT_METAFILE)
  76. delete context;
  77. }
  78. static void gdiplusgen_begin_page(GVJ_t *job)
  79. {
  80. if (!job->context)
  81. {
  82. if (!job->external_context && job->device.id != FORMAT_METAFILE) {
  83. /* allocate memory and attach stream to it */
  84. HGLOBAL buffer = GlobalAlloc(GMEM_MOVEABLE, 0);
  85. IStream *stream = nullptr;
  86. CreateStreamOnHGlobal(buffer, FALSE, &stream); /* FALSE means don't deallocate buffer when releasing stream */
  87. Image *image;
  88. switch (job->device.id) {
  89. case FORMAT_EMF:
  90. case FORMAT_EMFPLUS:
  91. /* EMF image */
  92. image = new Metafile (stream,
  93. DeviceContext().hdc,
  94. RectF(0.0f, 0.0f, job->width, job->height),
  95. MetafileFrameUnitPixel,
  96. job->device.id == FORMAT_EMFPLUS ? EmfTypeEmfPlusOnly : EmfTypeEmfPlusDual);
  97. /* output in EMF for wider compatibility; output in EMF+ for antialiasing etc. */
  98. break;
  99. default:
  100. /* bitmap image */
  101. image = new Bitmap (job->width, job->height, PixelFormat32bppARGB);
  102. break;
  103. }
  104. job->context = new ImageGraphics(image, stream);
  105. }
  106. else if (job->device.id == FORMAT_METAFILE)
  107. {
  108. /* create EMF image in the job window which was set during begin job */
  109. Metafile* metafile = new Metafile(DeviceContext().hdc,
  110. RectF(0.0f, 0.0f, job->width, job->height),
  111. MetafileFrameUnitPixel,
  112. EmfTypeEmfPlusOnly);
  113. auto m = reinterpret_cast<Metafile**>(job->window);
  114. *m = metafile;
  115. job->context = new Graphics(metafile);
  116. }
  117. }
  118. /* start graphics state */
  119. Graphics *context = (Graphics *)job->context;
  120. context->SetSmoothingMode(SmoothingModeHighQuality);
  121. context->SetTextRenderingHint(TextRenderingHintAntiAlias);
  122. /* set up the context transformation */
  123. context->ResetTransform();
  124. context->ScaleTransform(job->scale.x, job->scale.y);
  125. context->RotateTransform(-job->rotation);
  126. context->TranslateTransform(job->translation.x, -job->translation.y);
  127. }
  128. static void gdiplusgen_textspan(GVJ_t *job, pointf p, textspan_t *span)
  129. {
  130. auto context = reinterpret_cast<Graphics*>(job->context);
  131. /* adjust text position */
  132. switch (span->just) {
  133. case 'r':
  134. p.x -= span->size.x;
  135. break;
  136. case 'l':
  137. p.x -= 0.0;
  138. break;
  139. case 'n':
  140. default:
  141. p.x -= span->size.x / 2.0;
  142. break;
  143. }
  144. p.y += span->yoffset_centerline + span->yoffset_layout;
  145. Layout* layout;
  146. if (span->free_layout == &gdiplus_free_layout)
  147. layout = reinterpret_cast<Layout*>(span->layout);
  148. else
  149. layout = new Layout(span->font->name, span->font->size, span->str);
  150. /* draw the text */
  151. SolidBrush brush(Color(job->obj->pencolor.u.rgba [3], job->obj->pencolor.u.rgba [0], job->obj->pencolor.u.rgba [1], job->obj->pencolor.u.rgba [2]));
  152. context->DrawString(&layout->text[0], layout->text.size(), layout->font.get(), PointF(p.x, -p.y), GetGenericTypographic(), &brush);
  153. if (span->free_layout != &gdiplus_free_layout)
  154. delete layout;
  155. }
  156. static vector<PointF> points(pointf *A, int n)
  157. {
  158. /* convert Graphviz pointf (struct of double) to GDI+ PointF (struct of float) */
  159. vector<PointF> newPoints;
  160. for (int i = 0; i < n; ++i)
  161. newPoints.push_back(PointF(A[i].x, -A[i].y));
  162. return newPoints;
  163. }
  164. static void gdiplusgen_path(GVJ_t *job, const GraphicsPath &pathname,
  165. int filled) {
  166. auto context = reinterpret_cast<Graphics *>(job->context);
  167. /* fill the given path with job fill color */
  168. if (filled) {
  169. SolidBrush fill_brush(Color(job->obj->fillcolor.u.rgba [3], job->obj->fillcolor.u.rgba [0], job->obj->fillcolor.u.rgba [1], job->obj->fillcolor.u.rgba [2]));
  170. context->FillPath(&fill_brush, &pathname);
  171. }
  172. /* draw the given path from job pen color and pen width */
  173. Pen draw_pen(Color(job->obj->pencolor.u.rgba [3], job->obj->pencolor.u.rgba [0], job->obj->pencolor.u.rgba [1], job->obj->pencolor.u.rgba [2]),
  174. job->obj->penwidth);
  175. /*
  176. * Set line type
  177. * See http://msdn.microsoft.com/en-us/library/ms535050%28v=vs.85%29.aspx
  178. */
  179. switch (job->obj->pen) {
  180. case PEN_NONE:
  181. return;
  182. case PEN_DASHED:
  183. draw_pen.SetDashStyle(DashStyleDash);
  184. break;
  185. case PEN_DOTTED:
  186. draw_pen.SetDashStyle(DashStyleDot);
  187. break;
  188. case PEN_SOLID:
  189. break;
  190. }
  191. context->DrawPath(&draw_pen, &pathname);
  192. }
  193. static void gdiplusgen_ellipse(GVJ_t *job, pointf *A, int filled)
  194. {
  195. /* convert ellipse into path */
  196. GraphicsPath pathname;
  197. double dx = A[1].x - A[0].x;
  198. double dy = A[1].y - A[0].y;
  199. pathname.AddEllipse(RectF(A[0].x - dx, -A[0].y - dy, dx * 2.0, dy * 2.0));
  200. /* draw the path */
  201. gdiplusgen_path(job, pathname, filled);
  202. }
  203. static void gdiplusgen_polygon(GVJ_t *job, pointf *A, size_t n, int filled) {
  204. /* convert polygon into path */
  205. GraphicsPath pathname;
  206. assert(n <= INT_MAX);
  207. pathname.AddPolygon(&points(A, n).front(), (int)n);
  208. /* draw the path */
  209. gdiplusgen_path(job, pathname, filled);
  210. }
  211. static void gdiplusgen_bezier(GVJ_t *job, pointf *A, size_t n, int filled) {
  212. /* convert the beziers into path */
  213. GraphicsPath pathname;
  214. assert(n <= INT_MAX);
  215. pathname.AddBeziers(&points(A, n).front(), (int)n);
  216. /* draw the path */
  217. gdiplusgen_path(job, pathname, filled);
  218. }
  219. static void gdiplusgen_polyline(GVJ_t *job, pointf *A, size_t n) {
  220. /* convert the lines into path */
  221. GraphicsPath pathname;
  222. assert(n <= INT_MAX);
  223. pathname.AddLines(&points(A,n).front(), (int)n);
  224. /* draw the path */
  225. gdiplusgen_path(job, pathname, 0);
  226. }
  227. static gvrender_engine_t gdiplusgen_engine = {
  228. gdiplusgen_begin_job,
  229. gdiplusgen_end_job,
  230. 0, /* gdiplusgen_begin_graph */
  231. 0, /* gdiplusgen_end_graph */
  232. 0, /* gdiplusgen_begin_layer */
  233. 0, /* gdiplusgen_end_layer */
  234. gdiplusgen_begin_page,
  235. 0, /* gdiplusgen_end_page */
  236. 0, /* gdiplusgen_begin_cluster */
  237. 0, /* gdiplusgen_end_cluster */
  238. 0, /* gdiplusgen_begin_nodes */
  239. 0, /* gdiplusgen_end_nodes */
  240. 0, /* gdiplusgen_begin_edges */
  241. 0, /* gdiplusgen_end_edges */
  242. 0, /* gdiplusgen_begin_node */
  243. 0, /* gdiplusgen_end_node */
  244. 0, /* gdiplusgen_begin_edge */
  245. 0, /* gdiplusgen_end_edge */
  246. 0, /* gdiplusgen_begin_anchor */
  247. 0, /* gdiplusgen_end_anchor */
  248. 0, /* gdiplusgen_begin_label */
  249. 0, /* gdiplusgen_end_label */
  250. gdiplusgen_textspan,
  251. 0,
  252. gdiplusgen_ellipse,
  253. gdiplusgen_polygon,
  254. gdiplusgen_bezier,
  255. gdiplusgen_polyline,
  256. 0, /* gdiplusgen_comment */
  257. 0, /* gdiplusgen_library_shape */
  258. };
  259. static gvrender_features_t render_features_gdiplus = {
  260. GVRENDER_Y_GOES_DOWN | GVRENDER_DOES_TRANSFORM, /* flags */
  261. 4., /* default pad - graph units */
  262. nullptr, /* knowncolors */
  263. 0, /* sizeof knowncolors */
  264. RGBA_BYTE /* color_type */
  265. };
  266. static gvdevice_features_t device_features_gdiplus_emf = {
  267. GVDEVICE_BINARY_FORMAT
  268. | GVDEVICE_DOES_TRUECOLOR
  269. | GVRENDER_NO_WHITE_BG,/* flags */
  270. {0.,0.}, /* default margin - points */
  271. {0.,0.}, /* default page width, height - points */
  272. {72.,72.} /* dpi */
  273. };
  274. static gvdevice_features_t device_features_gdiplus = {
  275. GVDEVICE_BINARY_FORMAT
  276. | GVDEVICE_DOES_TRUECOLOR,/* flags */
  277. {0.,0.}, /* default margin - points */
  278. {0.,0.}, /* default page width, height - points */
  279. {96.,96.} /* dpi */
  280. };
  281. gvplugin_installed_t gvrender_gdiplus_types[] = {
  282. {0, "gdiplus", 1, &gdiplusgen_engine, &render_features_gdiplus},
  283. {0, nullptr, 0, nullptr, nullptr}
  284. };
  285. gvplugin_installed_t gvdevice_gdiplus_types[] = {
  286. {FORMAT_METAFILE, "metafile:gdiplus", 8, nullptr, &device_features_gdiplus_emf},
  287. {FORMAT_BMP, "bmp:gdiplus", 8, nullptr, &device_features_gdiplus},
  288. {FORMAT_EMF, "emf:gdiplus", 8, nullptr, &device_features_gdiplus_emf},
  289. {FORMAT_EMFPLUS, "emfplus:gdiplus", 8, nullptr, &device_features_gdiplus_emf},
  290. {FORMAT_GIF, "gif:gdiplus", 8, nullptr, &device_features_gdiplus},
  291. {FORMAT_JPEG, "jpe:gdiplus", 8, nullptr, &device_features_gdiplus},
  292. {FORMAT_JPEG, "jpeg:gdiplus", 8, nullptr, &device_features_gdiplus},
  293. {FORMAT_JPEG, "jpg:gdiplus", 8, nullptr, &device_features_gdiplus},
  294. {FORMAT_PNG, "png:gdiplus", 8, nullptr, &device_features_gdiplus},
  295. {FORMAT_TIFF, "tif:gdiplus", 8, nullptr, &device_features_gdiplus},
  296. {FORMAT_TIFF, "tiff:gdiplus", 8, nullptr, &device_features_gdiplus},
  297. {0, nullptr, 0, nullptr, nullptr}
  298. };