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