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