1 /*
2 Copyright (c) 2011-2017 Bastian Maerkisch. All rights reserved.
3
4 Redistribution and use in source and binary forms, with or without modification,
5 are permitted provided that the following conditions are met:
6
7 1. Redistributions of source code must retain the above copyright notice,
8 this list of conditions and the following disclaimer.
9
10 2. Redistributions in binary form must reproduce the above copyright notice,
11 this list of conditions and the following disclaimer in the documentation
12 and/or other materials provided with the distribution.
13
14 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
18 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26 // include iostream / cstdio _before_ syscfg.h in order
27 // to avoid re-definition by wtext.h/winmain.c routines
28 #include <iostream>
29 #include <vector>
30 extern "C" {
31 # include "syscfg.h"
32 }
33 #include <windows.h>
34 #include <windowsx.h>
35 #define GDIPVER 0x0110
36 #include <gdiplus.h>
37 #include <tchar.h>
38 #include <wchar.h>
39 #ifdef __WATCOMC__
40 // swprintf_s is missing from <cwchar>
41 # define swprintf_s(s, c, f, ...) swprintf(s, c, f, __VA_ARGS__)
42 #endif
43
44 #include "wgdiplus.h"
45 #include "wgnuplib.h"
46 #include "winmain.h"
47 #include "wcommon.h"
48 using namespace Gdiplus;
49 // do not use namespace std: otherwise MSVC complains about
50 // ambiguous symbol bool
51 //using namespace std;
52
53 static bool gdiplusInitialized = false;
54 static ULONG_PTR gdiplusToken;
55
56 #define MINMAX(a,val,b) (((val) <= (a)) ? (a) : ((val) <= (b) ? (val) : (b)))
57 const int pattern_num = 8;
58
59 enum draw_target { DRAW_SCREEN, DRAW_PRINTER, DRAW_PLOTTER, DRAW_METAFILE };
60
61 static Color gdiplusCreateColor(COLORREF color, double alpha);
62 static void gdiplusSetDashStyle(Pen *pen, enum DashStyle style);
63 static void gdiplusPolyline(Graphics &graphics, Pen &pen, Point *points, int polyi);
64 Brush * gdiplusPatternBrush(int style, COLORREF color, double alpha, COLORREF backcolor, BOOL transparent);
65 static void gdiplusDot(Graphics &graphics, Brush &brush, int x, int y);
66 static Font * SetFont_gdiplus(Graphics &graphics, LPRECT rect, LPGW lpgw, LPTSTR fontname, int size);
67 static void do_draw_gdiplus(LPGW lpgw, Graphics &graphics, LPRECT rect, enum draw_target target);
68
69
70 /* Internal state of enhanced text processing.
71 */
72
73 static struct {
74 Graphics * graphics; /* graphics object */
75 Font * font;
76 SolidBrush * brush;
77 StringFormat *stringformat;
78 } enhstate_gdiplus;
79
80
81 /* **************** .... ************************* */
82
83
84 void
gdiplusInit(void)85 gdiplusInit(void)
86 {
87 if (!gdiplusInitialized) {
88 gdiplusInitialized = true;
89 GdiplusStartupInput gdiplusStartupInput;
90 GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
91 }
92 }
93
94
95 void
gdiplusCleanup(void)96 gdiplusCleanup(void)
97 {
98 if (gdiplusInitialized) {
99 gdiplusInitialized = false;
100 GdiplusShutdown(gdiplusToken);
101 }
102 }
103
104
105 static Color
gdiplusCreateColor(COLORREF color,double alpha)106 gdiplusCreateColor(COLORREF color, double alpha)
107 {
108 ARGB argb = Color::MakeARGB(
109 BYTE(255 * alpha),
110 GetRValue(color), GetGValue(color), GetBValue(color));
111 return Color(argb);
112 }
113
114
115 static void
gdiplusSetDashStyle(Pen * pen,enum DashStyle style)116 gdiplusSetDashStyle(Pen *pen, enum DashStyle style)
117 {
118 const REAL dashstyles[4][6] = {
119 { 16.f, 8.f }, // dash
120 { 3.f, 3.f }, // dot
121 { 8.f, 5.f, 3.f, 5.f }, // dash dot
122 { 8.f, 4.f, 3.f, 4.f, 3.f, 4.f } // dash dot dot
123 };
124 const int dashstyle_len[4] = { 2, 2, 4, 6 };
125
126 style = static_cast<enum DashStyle>(style % 5);
127 if (style == 0)
128 pen->SetDashStyle(style);
129 else
130 pen->SetDashPattern(dashstyles[style - 1], dashstyle_len[style - 1]);
131 }
132
133
134 /* **************** GDI+ only functions ********************************** */
135
136
137 static void
gdiplusPolyline(Graphics & graphics,Pen & pen,Point * points,int polyi)138 gdiplusPolyline(Graphics &graphics, Pen &pen, Point *points, int polyi)
139 {
140 // Dash patterns get scaled with line width, in contrast to GDI.
141 // Avoid smearing out caused by antialiasing for small line widths.
142 SmoothingMode mode = graphics.GetSmoothingMode();
143
144 if ((points[0].X != points[polyi - 1].X) ||
145 (points[0].Y != points[polyi - 1].Y))
146 graphics.DrawLines(&pen, points, polyi);
147 else
148 graphics.DrawPolygon(&pen, points, polyi - 1);
149
150 /* restore */
151 if (mode != SmoothingModeNone)
152 graphics.SetSmoothingMode(mode);
153 }
154
155
156 static void
gdiplusPolyline(Graphics & graphics,Pen & pen,PointF * points,int polyi)157 gdiplusPolyline(Graphics &graphics, Pen &pen, PointF *points, int polyi)
158 {
159 // Dash patterns get scaled with line width, in contrast to GDI.
160 // Avoid smearing out caused by antialiasing for small line widths.
161 SmoothingMode mode = graphics.GetSmoothingMode();
162
163 bool all_vert_or_horz = true;
164 for (int i = 1; i < polyi; i++)
165 if (!((points[i - 1].X == points[i].X) ||
166 (points[i - 1].Y == points[i].Y)))
167 all_vert_or_horz = false;
168
169 // if all lines are horizontal or vertical we snap to nearest pixel
170 // to avoid "blurry" lines
171 if (all_vert_or_horz) {
172 for (int i = 0; i < polyi; i++) {
173 points[i].X = INT(points[i].X + 0.5);
174 points[i].Y = INT(points[i].Y + 0.5);
175 }
176 }
177
178 if ((points[0].X != points[polyi - 1].X) ||
179 (points[0].Y != points[polyi - 1].Y))
180 graphics.DrawLines(&pen, points, polyi);
181 else
182 graphics.DrawPolygon(&pen, points, polyi - 1);
183
184 /* restore */
185 if (mode != SmoothingModeNone)
186 graphics.SetSmoothingMode(mode);
187 }
188
189
190 Brush *
gdiplusPatternBrush(int style,COLORREF color,double alpha,COLORREF backcolor,BOOL transparent)191 gdiplusPatternBrush(int style, COLORREF color, double alpha, COLORREF backcolor, BOOL transparent)
192 {
193 Color gdipColor = gdiplusCreateColor(color, alpha);
194 Color gdipBackColor = gdiplusCreateColor(backcolor, transparent ? 0 : 1.);
195 Brush * brush;
196 style %= pattern_num;
197 const HatchStyle styles[] = { HatchStyleTotal, HatchStyleDiagonalCross,
198 HatchStyleZigZag, HatchStyleTotal,
199 HatchStyleForwardDiagonal, HatchStyleBackwardDiagonal,
200 HatchStyleLightDownwardDiagonal, HatchStyleDarkUpwardDiagonal };
201 switch (style) {
202 case 0:
203 brush = new SolidBrush(gdipBackColor);
204 break;
205 case 3:
206 brush = new SolidBrush(gdipColor);
207 break;
208 default:
209 brush = new HatchBrush(styles[style], gdipColor, gdipBackColor);
210 }
211 return brush;
212 }
213
214
215 static void
gdiplusDot(Graphics & graphics,Brush & brush,int x,int y)216 gdiplusDot(Graphics &graphics, Brush &brush, int x, int y)
217 {
218 /* no antialiasing in order to avoid blurred pixel */
219 SmoothingMode mode = graphics.GetSmoothingMode();
220 graphics.SetSmoothingMode(SmoothingModeNone);
221 graphics.FillRectangle(&brush, x, y, 1, 1);
222 graphics.SetSmoothingMode(mode);
223 }
224
225
226 static Font *
SetFont_gdiplus(Graphics & graphics,LPRECT rect,LPGW lpgw,LPTSTR fontname,int size)227 SetFont_gdiplus(Graphics &graphics, LPRECT rect, LPGW lpgw, LPTSTR fontname, int size)
228 {
229 if ((fontname == NULL) || (*fontname == 0))
230 fontname = lpgw->deffontname;
231 if (size == 0)
232 size = lpgw->deffontsize;
233
234 /* make a local copy */
235 fontname = _tcsdup(fontname);
236
237 /* save current font */
238 _tcscpy(lpgw->fontname, fontname);
239 lpgw->fontsize = size;
240
241 // apply fontscale
242 size *= lpgw->fontscale;
243
244 /* set up font style */
245 INT fontStyle = FontStyleRegular;
246 LPTSTR italic, bold, underline, strikeout;
247 if ((italic = _tcsstr(fontname, TEXT(" Italic"))) != NULL)
248 fontStyle |= FontStyleItalic;
249 else if ((italic = _tcsstr(fontname, TEXT(":Italic"))) != NULL)
250 fontStyle |= FontStyleItalic;
251 if ((bold = _tcsstr(fontname, TEXT(" Bold"))) != NULL)
252 fontStyle |= FontStyleBold;
253 else if ((bold = _tcsstr(fontname, TEXT(":Bold"))) != NULL)
254 fontStyle |= FontStyleBold;
255 if ((underline = _tcsstr(fontname, TEXT(" Underline"))) != NULL)
256 fontStyle |= FontStyleUnderline;
257 if ((strikeout = _tcsstr(fontname, TEXT(" Strikeout"))) != NULL)
258 fontStyle |= FontStyleStrikeout;
259 if (italic) *italic = 0;
260 if (bold) *bold = 0;
261 if (underline) *underline = 0;
262 if (strikeout) *strikeout = 0;
263
264 #ifdef UNICODE
265 const FontFamily * fontFamily = new FontFamily(fontname);
266 #else
267 LPWSTR family = UnicodeText(fontname, lpgw->encoding);
268 const FontFamily * fontFamily = new FontFamily(family);
269 free(family);
270 #endif
271 free(fontname);
272 Font * font;
273 int fontHeight;
274 bool deleteFontFamily = true;
275 if (fontFamily->GetLastStatus() != Ok) {
276 delete fontFamily;
277 #if (!defined(__MINGW32__) || defined(__MINGW64_VERSION_MAJOR))
278 // MinGW 4.8.1 does not have this
279 fontFamily = FontFamily::GenericSansSerif();
280 deleteFontFamily = false;
281 #else
282 #ifdef UNICODE
283 fontFamily = new FontFamily(GraphDefaultFont());
284 #else
285 family = UnicodeText(GraphDefaultFont(), S_ENC_DEFAULT); // should always be available
286 fontFamily = new FontFamily(family);
287 free(family);
288 #endif
289 #endif
290 }
291 font = new Font(fontFamily, size, fontStyle, UnitPoint);
292 double scale = font->GetSize() / fontFamily->GetEmHeight(fontStyle) * graphics.GetDpiY() / 72.;
293 /* store text metrics for later use */
294 lpgw->tmHeight = fontHeight = scale * (fontFamily->GetCellAscent(fontStyle) + fontFamily->GetCellDescent(fontStyle));
295 lpgw->tmAscent = scale * fontFamily->GetCellAscent(fontStyle);
296 lpgw->tmDescent = scale * fontFamily->GetCellDescent(fontStyle);
297 if (deleteFontFamily)
298 delete fontFamily;
299
300 RectF box;
301 graphics.MeasureString(L"0123456789", -1, font, PointF(0, 0), StringFormat::GenericTypographic(), &box);
302 lpgw->vchar = MulDiv(fontHeight, lpgw->ymax, rect->bottom - rect->top);
303 lpgw->hchar = MulDiv(box.Width, lpgw->xmax, 10 * (rect->right - rect->left));
304 lpgw->htic = MulDiv(lpgw->hchar, 2, 5);
305 unsigned cy = MulDiv(box.Width, 2 * graphics.GetDpiY(), 50 * graphics.GetDpiX());
306 lpgw->vtic = MulDiv(cy, lpgw->ymax, rect->bottom - rect->top);
307
308 // Can always rotate text.
309 lpgw->rotate = TRUE;
310
311 return font;
312 }
313
314
315 void
InitFont_gdiplus(LPGW lpgw,HDC hdc,LPRECT rect)316 InitFont_gdiplus(LPGW lpgw, HDC hdc, LPRECT rect)
317 {
318 gdiplusInit();
319 Graphics graphics(hdc);
320 // call for the side effects: set vchar/hchar and text metrics
321 Font * font = SetFont_gdiplus(graphics, rect, lpgw, lpgw->fontname, lpgw->fontsize);
322 // TODO: save font object for later use
323 delete font;
324 }
325
326
327 static void
EnhancedSetFont()328 EnhancedSetFont()
329 {
330 if ((enhstate.lpgw->fontsize != enhstate.fontsize) ||
331 (_tcscmp(enhstate.lpgw->fontname, enhstate.fontname) != 0)) {
332 if (enhstate_gdiplus.font)
333 delete enhstate_gdiplus.font;
334 enhstate_gdiplus.font = SetFont_gdiplus(*enhstate_gdiplus.graphics, enhstate.rect, enhstate.lpgw, enhstate.fontname, enhstate.fontsize);
335 }
336 }
337
338
339 static unsigned
EnhancedTextLength(char * text)340 EnhancedTextLength(char * text)
341 {
342 LPWSTR textw = UnicodeTextWithEscapes(enhanced_text, enhstate.lpgw->encoding);
343 RectF box;
344 enhstate_gdiplus.graphics->MeasureString(textw, -1, enhstate_gdiplus.font, PointF(0, 0), enhstate_gdiplus.stringformat, &box);
345 free(textw);
346 return ceil(box.Width);
347 }
348
349
350 static void
EnhancedPutText(int x,int y,char * text)351 EnhancedPutText(int x, int y, char * text)
352 {
353 LPWSTR textw = UnicodeTextWithEscapes(text, enhstate.lpgw->encoding);
354 Graphics *g = enhstate_gdiplus.graphics;
355 if (enhstate.lpgw->angle == 0) {
356 PointF pointF(x, y + enhstate.lpgw->tmDescent);
357 g->DrawString(textw, -1, enhstate_gdiplus.font, pointF, enhstate_gdiplus.stringformat, enhstate_gdiplus.brush);
358 } else {
359 /* shift rotated text correctly */
360 g->TranslateTransform(x, y);
361 g->RotateTransform(-enhstate.lpgw->angle);
362 g->DrawString(textw, -1, enhstate_gdiplus.font, PointF(0, enhstate.lpgw->tmDescent), enhstate_gdiplus.stringformat, enhstate_gdiplus.brush);
363 g->ResetTransform();
364 }
365 free(textw);
366 }
367
368
369 static void
EnhancedCleanup()370 EnhancedCleanup()
371 {
372 delete enhstate_gdiplus.font;
373 delete enhstate_gdiplus.stringformat;
374 }
375
376
377 static void
draw_enhanced_init(LPGW lpgw,Graphics & graphics,SolidBrush & brush,LPRECT rect)378 draw_enhanced_init(LPGW lpgw, Graphics &graphics, SolidBrush &brush, LPRECT rect)
379 {
380 enhstate.set_font = &EnhancedSetFont;
381 enhstate.text_length = &EnhancedTextLength;
382 enhstate.put_text = &EnhancedPutText;
383 enhstate.cleanup = &EnhancedCleanup;
384
385 enhstate_gdiplus.graphics = &graphics;
386 enhstate_gdiplus.font = SetFont_gdiplus(graphics, rect, lpgw, lpgw->fontname, lpgw->fontsize);
387 enhstate_gdiplus.brush = &brush;
388 enhstate.res_scale = graphics.GetDpiY() / 96.;
389
390 enhstate_gdiplus.stringformat = new StringFormat(StringFormat::GenericTypographic());
391 enhstate_gdiplus.stringformat->SetAlignment(StringAlignmentNear);
392 enhstate_gdiplus.stringformat->SetLineAlignment(StringAlignmentFar);
393 INT flags = enhstate_gdiplus.stringformat->GetFormatFlags();
394 flags |= StringFormatFlagsMeasureTrailingSpaces;
395 enhstate_gdiplus.stringformat->SetFormatFlags(flags);
396 }
397
398
399 void
drawgraph_gdiplus(LPGW lpgw,HDC hdc,LPRECT rect)400 drawgraph_gdiplus(LPGW lpgw, HDC hdc, LPRECT rect)
401 {
402 gdiplusInit();
403 Graphics graphics(hdc);
404 do_draw_gdiplus(lpgw, graphics, rect, DRAW_SCREEN);
405 }
406
407
408 void
metafile_gdiplus(LPGW lpgw,HDC hdc,LPRECT lprect,LPWSTR name)409 metafile_gdiplus(LPGW lpgw, HDC hdc, LPRECT lprect, LPWSTR name)
410 {
411 gdiplusInit();
412 Rect rect(lprect->left, lprect->top, lprect->right - lprect->left, lprect->bottom - lprect->top);
413 Metafile metafile(name, hdc, rect, MetafileFrameUnitPixel, EmfTypeEmfPlusDual, NULL);
414 Graphics graphics(&metafile);
415 do_draw_gdiplus(lpgw, graphics, lprect, DRAW_METAFILE);
416 }
417
418
419 HENHMETAFILE
clipboard_gdiplus(LPGW lpgw,HDC hdc,LPRECT lprect)420 clipboard_gdiplus(LPGW lpgw, HDC hdc, LPRECT lprect)
421 {
422 gdiplusInit();
423 Rect rect(lprect->left, lprect->top, lprect->right - lprect->left, lprect->bottom - lprect->top);
424 Metafile metafile(hdc, rect, MetafileFrameUnitPixel, EmfTypeEmfPlusDual, NULL);
425 // Note: We can only get a valid handle once the graphics object has released
426 // the metafile. Creating the graphics object on the heap seems the only way to
427 // achieve that.
428 Graphics * graphics = Graphics::FromImage(&metafile);
429 do_draw_gdiplus(lpgw, *graphics, lprect, DRAW_METAFILE);
430 delete graphics;
431 return metafile.GetHENHMETAFILE();
432 }
433
434
435 void
print_gdiplus(LPGW lpgw,HDC hdc,HANDLE printer,LPRECT rect)436 print_gdiplus(LPGW lpgw, HDC hdc, HANDLE printer, LPRECT rect)
437 {
438 gdiplusInit();
439
440 // temporarily turn of antialiasing
441 BOOL aa = lpgw->antialiasing;
442 lpgw->antialiasing = FALSE;
443
444 Graphics graphics(hdc, printer);
445 graphics.SetPageUnit(UnitPixel);
446 do_draw_gdiplus(lpgw, graphics, rect, DRAW_PRINTER);
447
448 // restore settings
449 lpgw->antialiasing = aa;
450 }
451
452
453 static void
do_draw_gdiplus(LPGW lpgw,Graphics & graphics,LPRECT rect,enum draw_target target)454 do_draw_gdiplus(LPGW lpgw, Graphics &graphics, LPRECT rect, enum draw_target target)
455 {
456 /* draw ops */
457 unsigned int ngwop = 0;
458 struct GWOP *curptr;
459 struct GWOPBLK *blkptr;
460
461 /* layers and hypertext */
462 unsigned plotno = 0;
463 bool gridline = false;
464 bool skipplot = false;
465 bool keysample = false;
466 bool interactive;
467 LPWSTR hypertext = NULL;
468 int hypertype = 0;
469
470 /* colors */
471 bool isColor = true; /* use colors? */
472 COLORREF last_color = 0; /* currently selected color */
473 double alpha_c = 1.; /* alpha for transparency */
474
475 /* text */
476 Font * font;
477
478 /* lines */
479 double line_width = lpgw->linewidth; /* current line width */
480 double lw_scale = 1.;
481 LOGPEN cur_penstruct; /* current pen settings */
482
483 /* polylines and polygons */
484 int polymax = 200; /* size of ppt */
485 int polyi = 0; /* number of points in ppt */
486 std::vector<PointF> ppt(polymax); /* storage of polyline/polygon-points */
487 int last_polyi = 0; /* number of points in last_poly */
488 std::vector<PointF> last_poly(0); /* storage of last filled polygon */
489 unsigned int lastop = -1; /* used for plotting last point on a line */
490
491 /* filled polygons and boxes */
492 int last_fillstyle = -1;
493 COLORREF last_fillcolor = 0;
494 double last_fill_alpha = 1.;
495 bool transparent = false; /* transparent fill? */
496 Brush * pattern_brush = NULL;
497 Brush * fill_brush = NULL;
498 Bitmap * poly_bitmap = NULL;
499 Graphics * poly_graphics = NULL;
500 float poly_scale = 2.f;
501
502 /* images */
503 POINT corners[4]; /* image corners */
504 int color_mode = 0; /* image color mode */
505
506 #ifdef EAM_BOXED_TEXT
507 struct s_boxedtext {
508 TBOOLEAN boxing;
509 t_textbox_options option;
510 POINT margin;
511 POINT start;
512 RECT box;
513 int angle;
514 } boxedtext;
515 #endif
516
517 /* point symbols */
518 enum win_pointtypes last_symbol = W_invalid_pointtype;
519 CachedBitmap *cb = NULL;
520 POINT cb_ofs;
521 bool ps_caching = false;
522
523 /* coordinates and lengths */
524 float xdash, ydash; /* the transformed coordinates */
525 int rr, rl, rt, rb; /* coordinates of drawing area */
526 int htic, vtic; /* tic sizes */
527 float hshift, vshift; /* correction of text position */
528
529 /* indices */
530 int seq = 0; /* sequence counter for W_image and W_boxedtext */
531
532 if (lpgw->locked) return;
533
534 /* clear hypertexts only in display sessions */
535 interactive = (target == DRAW_SCREEN);
536 if (interactive)
537 clear_tooltips(lpgw);
538
539 /* Need to scale line widths for raster printers so they are the same
540 as on screen */
541 if (target == DRAW_PRINTER) {
542 HDC hdc = graphics.GetHDC();
543 HDC hdc_screen = GetDC(NULL);
544 lw_scale = (double) GetDeviceCaps(hdc, LOGPIXELSX) /
545 (double) GetDeviceCaps(hdc_screen, LOGPIXELSY);
546 line_width *= lw_scale;
547 ReleaseDC(NULL, hdc_screen);
548 graphics.ReleaseHDC(hdc);
549 }
550
551 // only cache point symbols when drawing to a screen
552 ps_caching = (target == DRAW_SCREEN);
553
554 rr = rect->right;
555 rl = rect->left;
556 rt = rect->top;
557 rb = rect->bottom;
558
559 if (lpgw->antialiasing) {
560 graphics.SetSmoothingMode(SmoothingModeAntiAlias8x8);
561 graphics.SetTextRenderingHint(TextRenderingHintClearTypeGridFit);
562 }
563 graphics.SetInterpolationMode(InterpolationModeNearestNeighbor);
564
565 /* background fill */
566 if (isColor)
567 graphics.Clear(gdiplusCreateColor(lpgw->background, 1.));
568 else
569 graphics.Clear(Color(255, 255, 255));
570
571 /* Init brush and pens: need to be created after the Graphics object. */
572 SolidBrush solid_brush(gdiplusCreateColor(lpgw->background, 1.));
573 SolidBrush solid_fill_brush(gdiplusCreateColor(lpgw->background, 1.));
574 cur_penstruct = (lpgw->color && isColor) ? lpgw->colorpen[2] : lpgw->monopen[2];
575 last_color = cur_penstruct.lopnColor;
576
577 Pen pen(Color(0, 0, 0));
578 Pen solid_pen(Color(0, 0, 0));
579 pen.SetColor(gdiplusCreateColor(last_color, 1.));
580 pen.SetLineCap(lpgw->rounded ? LineCapRound : LineCapSquare,
581 lpgw->rounded ? LineCapRound : LineCapSquare,
582 DashCapFlat);
583 pen.SetLineJoin(lpgw->rounded ? LineJoinRound : LineJoinMiter);
584 solid_pen.SetColor(gdiplusCreateColor(last_color, 1.));
585 solid_pen.SetLineCap(lpgw->rounded ? LineCapRound : LineCapSquare,
586 lpgw->rounded ? LineCapRound : LineCapSquare,
587 DashCapFlat);
588 solid_pen.SetLineJoin(lpgw->rounded ? LineJoinRound : LineJoinMiter);
589
590 htic = (lpgw->org_pointsize * MulDiv(lpgw->htic, rr - rl, lpgw->xmax) + 1);
591 vtic = (lpgw->org_pointsize * MulDiv(lpgw->vtic, rb - rt, lpgw->ymax) + 1);
592
593 lpgw->angle = 0;
594 lpgw->justify = LEFT;
595 StringFormat stringFormat(StringFormat::GenericTypographic());
596 stringFormat.SetAlignment(StringAlignmentNear);
597 stringFormat.SetLineAlignment(StringAlignmentNear);
598 INT flags = stringFormat.GetFormatFlags();
599 flags |= StringFormatFlagsMeasureTrailingSpaces;
600 stringFormat.SetFormatFlags(flags);
601 font = SetFont_gdiplus(graphics, rect, lpgw, NULL, 0);
602
603 /* calculate text shifting for horizontal text */
604 hshift = 0.0;
605 vshift = - lpgw->tmHeight / 2;
606
607 /* init layer variables */
608 lpgw->numplots = 0;
609 lpgw->hasgrid = FALSE;
610 for (unsigned i = 0; i < lpgw->maxkeyboxes; i++) {
611 lpgw->keyboxes[i].left = INT_MAX;
612 lpgw->keyboxes[i].right = 0;
613 lpgw->keyboxes[i].bottom = INT_MAX;
614 lpgw->keyboxes[i].top = 0;
615 }
616
617 #ifdef EAM_BOXED_TEXT
618 boxedtext.boxing = FALSE;
619 #endif
620
621 /* do the drawing */
622 blkptr = lpgw->gwopblk_head;
623 curptr = NULL;
624 if (blkptr != NULL) {
625 if (!blkptr->gwop)
626 blkptr->gwop = (struct GWOP *) GlobalLock(blkptr->hblk);
627 if (!blkptr->gwop)
628 return;
629 curptr = (struct GWOP *)blkptr->gwop;
630 }
631 if (curptr == NULL)
632 return;
633
634 while (ngwop < lpgw->nGWOP) {
635 // transform the coordinates
636 if (lpgw->oversample) {
637 xdash = float(curptr->x) * (rr - rl - 1) / float(lpgw->xmax) + rl;
638 ydash = float(rb) - float(curptr->y) * (rb - rt - 1) / float(lpgw->ymax) + rt - 1;
639 } else {
640 xdash = MulDiv(curptr->x, rr - rl - 1, lpgw->xmax) + rl;
641 ydash = rb - MulDiv(curptr->y, rb - rt - 1, lpgw->ymax) + rt - 1;
642 }
643
644 /* finish last filled polygon */
645 if ((!last_poly.empty()) &&
646 (((lastop == W_filled_polygon_draw) && (curptr->op != W_fillstyle)) ||
647 ((curptr->op == W_fillstyle) && (curptr->x != unsigned(last_fillstyle))))) {
648 if (poly_graphics == NULL) {
649 // changing smoothing mode is necessary in case of new/unknown code paths
650 SmoothingMode mode = graphics.GetSmoothingMode();
651 if (lpgw->antialiasing && !lpgw->polyaa)
652 graphics.SetSmoothingMode(SmoothingModeNone);
653 graphics.FillPolygon(fill_brush, last_poly.data(), last_polyi);
654 graphics.SetSmoothingMode(mode);
655 } else {
656 poly_graphics->FillPolygon(fill_brush, last_poly.data(), last_polyi);
657 }
658 last_polyi = 0;
659 last_poly.clear();
660 }
661
662 /* handle layer commands first */
663 if (curptr->op == W_layer) {
664 t_termlayer layer = (t_termlayer) curptr->x;
665 switch (layer) {
666 case TERM_LAYER_BEFORE_PLOT:
667 plotno++;
668 lpgw->numplots = plotno;
669 if (plotno >= lpgw->maxhideplots) {
670 unsigned int idx;
671 lpgw->maxhideplots += 10;
672 lpgw->hideplot = (BOOL *) realloc(lpgw->hideplot, lpgw->maxhideplots * sizeof(BOOL));
673 for (idx = plotno; idx < lpgw->maxhideplots; idx++)
674 lpgw->hideplot[idx] = FALSE;
675 }
676 if (plotno <= lpgw->maxhideplots)
677 skipplot = (lpgw->hideplot[plotno - 1] > 0);
678 break;
679 case TERM_LAYER_AFTER_PLOT:
680 skipplot = false;
681 break;
682 #if 0
683 case TERM_LAYER_BACKTEXT:
684 case TERM_LAYER_FRONTTEXT
685 case TERM_LAYER_END_TEXT:
686 break;
687 #endif
688 case TERM_LAYER_BEGIN_GRID:
689 gridline = true;
690 lpgw->hasgrid = TRUE;
691 break;
692 case TERM_LAYER_END_GRID:
693 gridline = false;
694 break;
695 case TERM_LAYER_BEGIN_KEYSAMPLE:
696 keysample = true;
697 break;
698 case TERM_LAYER_END_KEYSAMPLE:
699 /* grey out keysample if graph is hidden */
700 if ((plotno <= lpgw->maxhideplots) && lpgw->hideplot[plotno - 1]) {
701 ARGB argb = Color::MakeARGB(128, 192, 192, 192);
702 Color transparentgrey(argb);
703 SolidBrush greybrush(transparentgrey);
704 LPRECT bb = lpgw->keyboxes + plotno - 1;
705 graphics.FillRectangle(&greybrush, INT(bb->left), INT(bb->bottom),
706 bb->right - bb->left, bb->top - bb->bottom);
707 }
708 keysample = false;
709 break;
710 case TERM_LAYER_RESET:
711 case TERM_LAYER_RESET_PLOTNO:
712 plotno = 0;
713 break;
714 case TERM_LAYER_BEGIN_PM3D_MAP:
715 case TERM_LAYER_BEGIN_PM3D_FLUSH:
716 // Antialiasing of pm3d polygons is obtained by drawing to a
717 // bitmap four times as large and copying it back with interpolation
718 if (lpgw->antialiasing && lpgw->polyaa) {
719 poly_bitmap = new Bitmap(poly_scale * (rr - rl), poly_scale * (rb - rt), &graphics);
720 poly_graphics = Graphics::FromImage(poly_bitmap);
721 poly_graphics->SetSmoothingMode(SmoothingModeNone);
722 Matrix transform(poly_scale, 0.0f, 0.0f, poly_scale, 0.0f, 0.0f);
723 poly_graphics->SetTransform(&transform);
724 }
725 break;
726 case TERM_LAYER_END_PM3D_MAP:
727 case TERM_LAYER_END_PM3D_FLUSH:
728 if (poly_graphics != NULL) {
729 delete poly_graphics;
730 poly_graphics = NULL;
731 graphics.SetInterpolationMode(InterpolationModeHighQualityBilinear);
732 graphics.SetPixelOffsetMode(PixelOffsetModeHighQuality);
733 graphics.DrawImage(poly_bitmap, 0, 0, rr - rl, rb - rt);
734 graphics.SetInterpolationMode(InterpolationModeNearestNeighbor);
735 graphics.SetPixelOffsetMode(PixelOffsetModeNone);
736 delete poly_bitmap;
737 }
738 break;
739 case TERM_LAYER_BEGIN_IMAGE:
740 case TERM_LAYER_BEGIN_COLORBOX:
741 // Turn of antialiasing for failsafe/pixel images and color boxes
742 // to avoid seams.
743 if (lpgw->antialiasing)
744 graphics.SetSmoothingMode(SmoothingModeNone);
745 break;
746 case TERM_LAYER_END_IMAGE:
747 case TERM_LAYER_END_COLORBOX:
748 if (lpgw->antialiasing)
749 graphics.SetSmoothingMode(SmoothingModeAntiAlias8x8);
750 break;
751 default:
752 break;
753 };
754 }
755
756 /* Hide this layer? Do not skip commands which could affect key samples: */
757 if (!(skipplot || (gridline && lpgw->hidegrid)) ||
758 keysample || (curptr->op == W_line_type) || (curptr->op == W_setcolor)
759 || (curptr->op == W_pointsize) || (curptr->op == W_line_width)
760 || (curptr->op == W_dash_type)) {
761
762 /* special case hypertexts */
763 if ((hypertext != NULL) && (hypertype == TERM_HYPERTEXT_TOOLTIP)) {
764 /* point symbols */
765 if ((curptr->op >= W_dot) && (curptr->op <= W_dot + WIN_POINT_TYPES)) {
766 RECT rect;
767 rect.left = xdash - htic - 0.5;
768 rect.right = xdash + htic + 0.5;
769 rect.top = ydash - vtic - 0.5;
770 rect.bottom = ydash + vtic + 0.5;
771 add_tooltip(lpgw, &rect, hypertext);
772 hypertext = NULL;
773 }
774 }
775
776 switch (curptr->op) {
777 case 0: /* have run past last in this block */
778 break;
779
780 case W_layer: /* already handled above */
781 break;
782
783 case W_polyline: {
784 POINTL * poly = (POINTL *) LocalLock(curptr->htext);
785 polyi = curptr->x;
786 PointF * points = new PointF[polyi];
787 for (int i = 0; i < polyi; i++) {
788 // transform the coordinates
789 if (lpgw->oversample) {
790 points[i].X = float(poly[i].x) * (rr - rl - 1) / float(lpgw->xmax) + rl;
791 points[i].Y = float(rb) - float(poly[i].y) * (rb - rt - 1) / float(lpgw->ymax) + rt - 1;
792 } else {
793 points[i].X = MulDiv(poly[i].x, rr - rl - 1, lpgw->xmax) + rl;
794 points[i].Y = rb - MulDiv(poly[i].y, rb - rt - 1, lpgw->ymax) + rt - 1;
795 }
796 }
797 LocalUnlock(poly);
798 if (poly_graphics == NULL)
799 gdiplusPolyline(graphics, pen, points, polyi);
800 else
801 gdiplusPolyline(*poly_graphics, pen, points, polyi);
802 if (keysample) {
803 draw_update_keybox(lpgw, plotno, points[0].X, points[0].Y);
804 draw_update_keybox(lpgw, plotno, points[polyi - 1].X, points[polyi - 1].Y);
805 }
806 delete [] points;
807 break;
808 }
809
810 case W_line_type: {
811 int cur_pen = (int)curptr->x % WGNUMPENS;
812
813 /* create new pens */
814 if (cur_pen > LT_NODRAW) {
815 cur_pen += 2;
816 cur_penstruct.lopnWidth =
817 (lpgw->color && isColor) ? lpgw->colorpen[cur_pen].lopnWidth : lpgw->monopen[cur_pen].lopnWidth;
818 cur_penstruct.lopnColor = lpgw->colorpen[cur_pen].lopnColor;
819 if (!lpgw->color || !isColor) {
820 COLORREF color = cur_penstruct.lopnColor;
821 unsigned luma = luma_from_color(GetRValue(color), GetGValue(color), GetBValue(color));
822 cur_penstruct.lopnColor = RGB(luma, luma, luma);
823 }
824 cur_penstruct.lopnStyle =
825 lpgw->dashed ? lpgw->monopen[cur_pen].lopnStyle : lpgw->colorpen[cur_pen].lopnStyle;
826 } else if (cur_pen == LT_NODRAW) {
827 cur_pen = WGNUMPENS;
828 cur_penstruct.lopnStyle = PS_NULL;
829 cur_penstruct.lopnColor = 0;
830 cur_penstruct.lopnWidth.x = 1;
831 } else { /* <= LT_BACKGROUND */
832 cur_pen = WGNUMPENS;
833 cur_penstruct.lopnStyle = PS_SOLID;
834 cur_penstruct.lopnColor = lpgw->background;
835 cur_penstruct.lopnWidth.x = 1;
836 }
837 cur_penstruct.lopnWidth.x *= line_width;
838
839 Color color = gdiplusCreateColor(cur_penstruct.lopnColor, 1.);
840 solid_brush.SetColor(color);
841
842 solid_pen.SetColor(color);
843 solid_pen.SetWidth(cur_penstruct.lopnWidth.x);
844
845 pen.SetColor(color);
846 pen.SetWidth(cur_penstruct.lopnWidth.x);
847 if (cur_penstruct.lopnStyle <= PS_DASHDOTDOT)
848 // cast is safe since GDI and GDI+ use the same numbers
849 gdiplusSetDashStyle(&pen, static_cast<DashStyle>(cur_penstruct.lopnStyle));
850 else
851 pen.SetDashStyle(DashStyleSolid);
852
853 /* remember this color */
854 last_color = cur_penstruct.lopnColor;
855 alpha_c = 1.;
856 break;
857 }
858
859 case W_dash_type: {
860 int dt = static_cast<int>(curptr->x);
861
862 if (dt >= 0) {
863 dt %= WGNUMPENS;
864 dt += 2;
865 cur_penstruct.lopnStyle = lpgw->monopen[dt].lopnStyle;
866 gdiplusSetDashStyle(&pen, static_cast<DashStyle>(cur_penstruct.lopnStyle));
867 } else if (dt == DASHTYPE_SOLID) {
868 cur_penstruct.lopnStyle = PS_SOLID;
869 gdiplusSetDashStyle(&pen, static_cast<DashStyle>(cur_penstruct.lopnStyle));
870 } else if (dt == DASHTYPE_AXIS) {
871 dt = 1;
872 cur_penstruct.lopnStyle =
873 lpgw->dashed ? lpgw->monopen[dt].lopnStyle : lpgw->colorpen[dt].lopnStyle;
874 gdiplusSetDashStyle(&pen, static_cast<DashStyle>(cur_penstruct.lopnStyle));
875 } else if (dt == DASHTYPE_CUSTOM) {
876 t_dashtype * dash = static_cast<t_dashtype *>(LocalLock(curptr->htext));
877 INT count = 0;
878 while ((dash->pattern[count] != 0.) && (count < DASHPATTERN_LENGTH)) count++;
879 pen.SetDashPattern(dash->pattern, count);
880 LocalUnlock(curptr->htext);
881 }
882 break;
883 }
884
885 case W_text_encoding:
886 lpgw->encoding = (set_encoding_id) curptr->x;
887 break;
888
889 case W_put_text: {
890 char * str;
891 str = (char *) LocalLock(curptr->htext);
892 if (str) {
893 LPWSTR textw = UnicodeText(str, lpgw->encoding);
894 if (textw) {
895 if (lpgw->angle == 0) {
896 PointF pointF(xdash + hshift, ydash + vshift);
897 graphics.DrawString(textw, -1, font, pointF, &stringFormat, &solid_brush);
898 } else {
899 /* shift rotated text correctly */
900 graphics.TranslateTransform(xdash + hshift, ydash + vshift);
901 graphics.RotateTransform(-lpgw->angle);
902 graphics.DrawString(textw, -1, font, PointF(0,0), &stringFormat, &solid_brush);
903 graphics.ResetTransform();
904 }
905 RectF size;
906 int dxl, dxr;
907 #ifndef EAM_BOXED_TEXT
908 if (keysample) {
909 #else
910 if (keysample || boxedtext.boxing) {
911 #endif
912 graphics.MeasureString(textw, -1, font, PointF(0,0), &stringFormat, &size);
913 if (lpgw->justify == LEFT) {
914 dxl = 0;
915 dxr = size.Width + 0.5;
916 } else if (lpgw->justify == CENTRE) {
917 dxl = dxr = size.Width / 2;
918 } else {
919 dxl = size.Width + 0.5;
920 dxr = 0;
921 }
922 }
923 if (keysample) {
924 draw_update_keybox(lpgw, plotno, xdash - dxl, ydash - size.Height / 2);
925 draw_update_keybox(lpgw, plotno, xdash + dxr, ydash + size.Height / 2);
926 }
927 #ifdef EAM_BOXED_TEXT
928 if (boxedtext.boxing) {
929 if (boxedtext.box.left > (xdash - boxedtext.start.x - dxl))
930 boxedtext.box.left = xdash - boxedtext.start.x - dxl;
931 if (boxedtext.box.right < (xdash - boxedtext.start.x + dxr))
932 boxedtext.box.right = xdash - boxedtext.start.x + dxr;
933 if (boxedtext.box.top > (ydash - boxedtext.start.y - size.Height / 2))
934 boxedtext.box.top = ydash - boxedtext.start.y - size.Height / 2;
935 if (boxedtext.box.bottom < (ydash - boxedtext.start.y + size.Height / 2))
936 boxedtext.box.bottom = ydash - boxedtext.start.y + size.Height / 2;
937 /* We have to remember the text angle as well. */
938 boxedtext.angle = lpgw->angle;
939 }
940 #endif
941 free(textw);
942 }
943 }
944 LocalUnlock(curptr->htext);
945 break;
946 }
947
948 case W_enhanced_text: {
949 char * str = (char *) LocalLock(curptr->htext);
950 if (str) {
951 RECT extend;
952 draw_enhanced_init(lpgw, graphics, solid_brush, rect);
953 draw_enhanced_text(lpgw, rect, xdash, ydash, str);
954 draw_get_enhanced_text_extend(&extend);
955
956 if (keysample) {
957 draw_update_keybox(lpgw, plotno, xdash - extend.left, ydash - extend.top);
958 draw_update_keybox(lpgw, plotno, xdash + extend.right, ydash + extend.bottom);
959 }
960 #ifdef EAM_BOXED_TEXT
961 if (boxedtext.boxing) {
962 if (boxedtext.box.left > (boxedtext.start.x - xdash - extend.left))
963 boxedtext.box.left = boxedtext.start.x - xdash - extend.left;
964 if (boxedtext.box.right < (boxedtext.start.x - xdash + extend.right))
965 boxedtext.box.right = boxedtext.start.x - xdash + extend.right;
966 if (boxedtext.box.top > (boxedtext.start.y - ydash - extend.top))
967 boxedtext.box.top = boxedtext.start.y - ydash - extend.top;
968 if (boxedtext.box.bottom < (boxedtext.start.y - ydash + extend.bottom))
969 boxedtext.box.bottom = boxedtext.start.y - ydash + extend.bottom;
970 /* We have to store the text angle as well. */
971 boxedtext.angle = lpgw->angle;
972 }
973 #endif
974 }
975 LocalUnlock(curptr->htext);
976 break;
977 }
978
979 case W_hypertext:
980 if (interactive) {
981 /* Make a copy for future reference */
982 char * str = (char *) LocalLock(curptr->htext);
983 free(hypertext);
984 hypertext = UnicodeText(str, lpgw->encoding);
985 hypertype = curptr->x;
986 LocalUnlock(curptr->htext);
987 }
988 break;
989
990 #ifdef EAM_BOXED_TEXT
991 case W_boxedtext:
992 if (seq == 0) {
993 boxedtext.option = (t_textbox_options) curptr->x;
994 seq++;
995 break;
996 }
997 seq = 0;
998 switch (boxedtext.option) {
999 case TEXTBOX_INIT:
1000 /* initialise bounding box */
1001 boxedtext.box.left = boxedtext.box.right = 0;
1002 boxedtext.box.bottom = boxedtext.box.top = 0;
1003 boxedtext.start.x = xdash;
1004 boxedtext.start.y = ydash;
1005 /* Note: initialising the text angle here would be best IMHO,
1006 but current core code does not set this until the actual
1007 print-out is done. */
1008 boxedtext.angle = lpgw->angle;
1009 boxedtext.boxing = TRUE;
1010 break;
1011 case TEXTBOX_OUTLINE:
1012 case TEXTBOX_BACKGROUNDFILL: {
1013 /* draw rectangle */
1014 int dx = boxedtext.margin.x;
1015 int dy = boxedtext.margin.y;
1016 if ((boxedtext.angle % 90) == 0) {
1017 Rect rect;
1018
1019 switch (boxedtext.angle) {
1020 case 0:
1021 rect.X = + boxedtext.box.left;
1022 rect.Y = + boxedtext.box.top;
1023 rect.Width = boxedtext.box.right - boxedtext.box.left;
1024 rect.Height = boxedtext.box.bottom - boxedtext.box.top;
1025 rect.Inflate(dx, dy);
1026 break;
1027 case 90:
1028 rect.X = + boxedtext.box.top;
1029 rect.Y = - boxedtext.box.right;
1030 rect.Height = boxedtext.box.right - boxedtext.box.left;
1031 rect.Width = boxedtext.box.bottom - boxedtext.box.top;
1032 rect.Inflate(dy, dx);
1033 break;
1034 case 180:
1035 rect.X = - boxedtext.box.right;
1036 rect.Y = - boxedtext.box.bottom;
1037 rect.Width = boxedtext.box.right - boxedtext.box.left;
1038 rect.Height = boxedtext.box.bottom - boxedtext.box.top;
1039 rect.Inflate(dx, dy);
1040 break;
1041 case 270:
1042 rect.X = - boxedtext.box.bottom;
1043 rect.Y = + boxedtext.box.left;
1044 rect.Height = boxedtext.box.right - boxedtext.box.left;
1045 rect.Width = boxedtext.box.bottom - boxedtext.box.top;
1046 rect.Inflate(dy, dx);
1047 break;
1048 }
1049 rect.Offset(boxedtext.start.x, boxedtext.start.y);
1050 if (boxedtext.option == TEXTBOX_OUTLINE) {
1051 graphics.DrawRectangle(&pen, rect);
1052 } else {
1053 /* Fill bounding box with current color. */
1054 graphics.FillRectangle(&solid_brush, rect);
1055 }
1056 } else {
1057 float theta = boxedtext.angle * M_PI / 180.;
1058 float sin_theta = sin(theta);
1059 float cos_theta = cos(theta);
1060 PointF rect[5];
1061
1062 rect[0].X = (boxedtext.box.left - dx) * cos_theta +
1063 (boxedtext.box.top - dy) * sin_theta;
1064 rect[0].Y = -(boxedtext.box.left - dx) * sin_theta +
1065 (boxedtext.box.top - dy) * cos_theta;
1066 rect[1].X = (boxedtext.box.left - dx) * cos_theta +
1067 (boxedtext.box.bottom + dy) * sin_theta;
1068 rect[1].Y = -(boxedtext.box.left - dx) * sin_theta +
1069 (boxedtext.box.bottom + dy) * cos_theta;
1070 rect[2].X = (boxedtext.box.right + dx) * cos_theta +
1071 (boxedtext.box.bottom + dy) * sin_theta;
1072 rect[2].Y = -(boxedtext.box.right + dx) * sin_theta +
1073 (boxedtext.box.bottom + dy) * cos_theta;
1074 rect[3].X = (boxedtext.box.right + dx) * cos_theta +
1075 (boxedtext.box.top - dy) * sin_theta;
1076 rect[3].Y = -(boxedtext.box.right + dx) * sin_theta +
1077 (boxedtext.box.top - dy) * cos_theta;
1078 for (int i = 0; i < 4; i++) {
1079 rect[i].X += boxedtext.start.x;
1080 rect[i].Y += boxedtext.start.y;
1081 }
1082 if (boxedtext.option == TEXTBOX_OUTLINE) {
1083 rect[4].X = rect[0].X;
1084 rect[4].Y = rect[0].Y;
1085 gdiplusPolyline(graphics, pen, rect, 5);
1086 } else {
1087 graphics.FillPolygon(fill_brush, rect, 4);
1088 }
1089 }
1090 boxedtext.boxing = FALSE;
1091 break;
1092 }
1093 case TEXTBOX_MARGINS:
1094 /* Adjust size of whitespace around text: default is 1/2 char height + 2 char widths. */
1095 boxedtext.margin.x = MulDiv(curptr->x * lpgw->hchar, rr - rl, 1000 * lpgw->xmax);
1096 boxedtext.margin.y = MulDiv(curptr->y * lpgw->hchar, rr - rl, 1000 * lpgw->xmax);
1097 break;
1098 default:
1099 break;
1100 }
1101 break;
1102 #endif
1103
1104 case W_fillstyle: {
1105 /* HBB 20010916: new entry, needed to squeeze the many
1106 * parameters of a filled box call through the bottleneck
1107 * of the fixed number of parameters in GraphOp() and
1108 * struct GWOP, respectively. */
1109 /* FIXME: resetting polyi here should not be necessary */
1110 polyi = 0; /* start new sequence */
1111 int fillstyle = curptr->x;
1112
1113 /* Eliminate duplicate fillstyle requests. */
1114 if ((fillstyle == last_fillstyle) &&
1115 (last_color == last_fillcolor) &&
1116 (last_fill_alpha == alpha_c))
1117 break;
1118
1119 transparent = false;
1120 switch (fillstyle & 0x0f) {
1121 case FS_TRANSPARENT_SOLID: {
1122 double alpha = (fillstyle >> 4) / 100.;
1123 solid_fill_brush.SetColor(gdiplusCreateColor(last_color, alpha));
1124 fill_brush = &solid_fill_brush;
1125 break;
1126 }
1127 case FS_SOLID: {
1128 if (alpha_c < 1.) {
1129 solid_fill_brush.SetColor(gdiplusCreateColor(last_color, alpha_c));
1130 } else if ((int)(fillstyle >> 4) == 100) {
1131 /* special case this common choice */
1132 solid_fill_brush.SetColor(gdiplusCreateColor(last_color, 1.));
1133 } else {
1134 double density = MINMAX(0, (int)(fillstyle >> 4), 100) * 0.01;
1135 COLORREF color =
1136 RGB(255 - density * (255 - GetRValue(last_color)),
1137 255 - density * (255 - GetGValue(last_color)),
1138 255 - density * (255 - GetBValue(last_color)));
1139 solid_fill_brush.SetColor(gdiplusCreateColor(color, 1.));
1140 }
1141 fill_brush = &solid_fill_brush;
1142 break;
1143 }
1144 case FS_TRANSPARENT_PATTERN:
1145 transparent = true;
1146 /* intentionally fall through */
1147 case FS_PATTERN: {
1148 /* style == 2 --> use fill pattern according to
1149 * fillpattern. Pattern number is enumerated */
1150 int pattern = GPMAX(fillstyle >> 4, 0) % pattern_num;
1151 if (pattern_brush)
1152 delete pattern_brush;
1153 pattern_brush = gdiplusPatternBrush(pattern,
1154 last_color, 1., lpgw->background, transparent);
1155 fill_brush = pattern_brush;
1156 break;
1157 }
1158 case FS_EMPTY:
1159 /* FIXME: Instead of filling with background color, we should not fill at all in this case! */
1160 /* fill with background color */
1161 solid_fill_brush.SetColor(gdiplusCreateColor(lpgw->background, 1.));
1162 fill_brush = &solid_fill_brush;
1163 break;
1164 case FS_DEFAULT:
1165 default:
1166 /* Leave the current brush and color in place */
1167 solid_fill_brush.SetColor(gdiplusCreateColor(last_color, 1.));
1168 fill_brush = &solid_fill_brush;
1169 break;
1170 }
1171 last_fillstyle = fillstyle;
1172 last_fillcolor = last_color;
1173 last_fill_alpha = alpha_c;
1174 break;
1175 }
1176
1177 case W_move:
1178 // This is used for filled boxes only.
1179 ppt[0].X = xdash;
1180 ppt[0].Y = ydash;
1181 break;
1182
1183 case W_boxfill: {
1184 /* NOTE: the x and y passed with this call are the coordinates of the
1185 * lower right corner of the box. The upper left corner was stored into
1186 * ppt[0] by a preceding W_move, and the style was set
1187 * by a W_fillstyle call. */
1188 // snap to pixel grid
1189 Point p1(xdash + 0.5, ydash + 0.5);
1190 Point p2(ppt[0].X + 0.5, ppt[0].Y + 0.5);
1191 Point p;
1192 int height, width;
1193
1194 p.X = GPMIN(p1.X, p2.X);
1195 p.Y = GPMIN(p1.Y, p2.Y);
1196 width = abs(p2.X - p1.X);
1197 height = abs(p1.Y - p2.Y);
1198
1199 SmoothingMode mode = graphics.GetSmoothingMode();
1200 graphics.SetSmoothingMode(SmoothingModeNone);
1201 graphics.FillRectangle(fill_brush, p.X, p.Y, width, height);
1202 graphics.SetSmoothingMode(mode);
1203 if (keysample)
1204 draw_update_keybox(lpgw, plotno, xdash + 1, ydash);
1205 polyi = 0;
1206 break;
1207 }
1208
1209 case W_text_angle:
1210 if (lpgw->angle != (int)curptr->x) {
1211 lpgw->angle = (int)curptr->x;
1212 /* recalculate shifting of rotated text */
1213 hshift = - sin(M_PI / 180. * lpgw->angle) * lpgw->tmHeight / 2.;
1214 vshift = - cos(M_PI / 180. * lpgw->angle) * lpgw->tmHeight / 2.;
1215 if (lpgw->antialiasing) {
1216 // Cleartype is only applied to non-rotated text
1217 if ((lpgw->angle % 180) != 0)
1218 graphics.SetTextRenderingHint(TextRenderingHintAntiAliasGridFit);
1219 else
1220 graphics.SetTextRenderingHint(TextRenderingHintClearTypeGridFit);
1221 }
1222 }
1223 break;
1224
1225 case W_justify:
1226 switch (curptr->x) {
1227 case LEFT:
1228 stringFormat.SetAlignment(StringAlignmentNear);
1229 break;
1230 case RIGHT:
1231 stringFormat.SetAlignment(StringAlignmentFar);
1232 break;
1233 case CENTRE:
1234 stringFormat.SetAlignment(StringAlignmentCenter);
1235 break;
1236 }
1237 lpgw->justify = curptr->x;
1238 break;
1239
1240 case W_font: {
1241 int size = curptr->x;
1242 char * fontname = (char *) LocalLock(curptr->htext);
1243 delete font;
1244 #ifdef UNICODE
1245 /* FIXME: Maybe this should be in win.trm instead. */
1246 LPWSTR wfontname = UnicodeText(fontname, lpgw->encoding);
1247 font = SetFont_gdiplus(graphics, rect, lpgw, wfontname, size);
1248 free(wfontname);
1249 #else
1250 font = SetFont_gdiplus(graphics, rect, lpgw, fontname, size);
1251 #endif
1252 LocalUnlock(curptr->htext);
1253 /* recalculate shifting of rotated text */
1254 hshift = - sin(M_PI / 180. * lpgw->angle) * lpgw->tmHeight / 2.;
1255 vshift = - cos(M_PI / 180. * lpgw->angle) * lpgw->tmHeight / 2.;
1256 break;
1257 }
1258
1259 case W_pointsize:
1260 if (curptr->x > 0) {
1261 double pointsize = curptr->x / 100.0;
1262 htic = MulDiv(pointsize * lpgw->pointscale * lpgw->htic, rr - rl, lpgw->xmax) + 1;
1263 vtic = MulDiv(pointsize * lpgw->pointscale * lpgw->vtic, rb - rt, lpgw->ymax) + 1;
1264 } else {
1265 htic = vtic = 0;
1266 }
1267 /* invalidate point symbol cache */
1268 last_symbol = W_invalid_pointtype;
1269 break;
1270
1271 case W_line_width:
1272 /* HBB 20000813: this may look strange, but it ensures
1273 * that linewidth is exactly 1 iff it's in default
1274 * state */
1275 line_width = curptr->x == 100 ? 1 : (curptr->x / 100.0);
1276 line_width *= lpgw->linewidth * lw_scale;
1277 if (poly_graphics != NULL)
1278 line_width *= poly_scale;
1279 solid_pen.SetWidth(line_width);
1280 pen.SetWidth(line_width);
1281 /* invalidate point symbol cache */
1282 last_symbol = W_invalid_pointtype;
1283 break;
1284
1285 case W_setcolor: {
1286 COLORREF color;
1287
1288 /* distinguish gray values and RGB colors */
1289 if (curptr->htext != NULL) { /* TC_LT */
1290 int pen = (int)curptr->x % WGNUMPENS;
1291 color = (pen <= LT_NODRAW) ? lpgw->background : lpgw->colorpen[pen + 2].lopnColor;
1292 if (!lpgw->color || !isColor) {
1293 unsigned luma = luma_from_color(GetRValue(color), GetGValue(color), GetBValue(color));
1294 color = RGB(luma, luma, luma);
1295 }
1296 alpha_c = 1.;
1297 } else { /* TC_RGB */
1298 rgb255_color rgb255;
1299 rgb255.r = (curptr->y & 0xff);
1300 rgb255.g = (curptr->x >> 8);
1301 rgb255.b = (curptr->x & 0xff);
1302 alpha_c = 1. - ((curptr->y >> 8) & 0xff) / 255.;
1303
1304 if (lpgw->color || ((rgb255.r == rgb255.g) && (rgb255.r == rgb255.b))) {
1305 /* Use colors or this is already gray scale */
1306 color = RGB(rgb255.r, rgb255.g, rgb255.b);
1307 } else {
1308 /* convert to gray */
1309 unsigned luma = luma_from_color(rgb255.r, rgb255.g, rgb255.b);
1310 color = RGB(luma, luma, luma);
1311 }
1312 }
1313
1314 /* update brushes and pens */
1315 Color pcolor = gdiplusCreateColor(color, alpha_c);
1316 solid_brush.SetColor(pcolor);
1317 pen.SetColor(pcolor);
1318 solid_pen.SetColor(pcolor);
1319
1320 /* invalidate point symbol cache */
1321 if (last_color != color)
1322 last_symbol = W_invalid_pointtype;
1323
1324 /* remember this color */
1325 cur_penstruct.lopnColor = color;
1326 last_color = color;
1327 break;
1328 }
1329
1330 case W_filled_polygon_pt: {
1331 /* a point of the polygon is coming */
1332 if (polyi >= polymax) {
1333 polymax += 200;
1334 ppt.reserve(polymax + 1);
1335 }
1336 ppt[polyi].X = xdash;
1337 ppt[polyi].Y = ydash;
1338 polyi++;
1339 break;
1340 }
1341
1342 case W_filled_polygon_draw: {
1343 bool found = false;
1344 int i, k;
1345 //bool same_rot = true;
1346
1347 // Test if successive polygons share a common edge:
1348 if ((!last_poly.empty()) && (polyi > 2)) {
1349 // Check for a common edge with previous filled polygon.
1350 for (i = 0; (i < polyi) && !found; i++) {
1351 for (k = 0; (k < last_polyi) && !found; k++) {
1352 if ((ppt[i].X == last_poly[k].X) && (ppt[i].Y == last_poly[k].Y)) {
1353 if ((ppt[(i + 1) % polyi].X == last_poly[(k + 1) % last_polyi].X) &&
1354 (ppt[(i + 1) % polyi].Y == last_poly[(k + 1) % last_polyi].Y)) {
1355 //found = true;
1356 //same_rot = true;
1357 }
1358 // This is the dominant case for filling between curves,
1359 // see fillbetween.dem and polar.dem.
1360 if ((ppt[(i + 1) % polyi].X == last_poly[(k + last_polyi - 1) % last_polyi].X) &&
1361 (ppt[(i + 1) % polyi].Y == last_poly[(k + last_polyi - 1) % last_polyi].Y)) {
1362 found = true;
1363 //same_rot = false;
1364 }
1365 }
1366 }
1367 }
1368 }
1369
1370 if (found) { // merge polygons
1371 // rewind
1372 i--; k--;
1373
1374 int extra = polyi - 2;
1375 // extend buffer to make room for extra points
1376 last_poly.reserve(last_polyi + extra + 1);
1377 /* TODO: should use memmove instead */
1378 for (int n = last_polyi - 1; n >= k; n--) {
1379 last_poly[n + extra].X = last_poly[n].X;
1380 last_poly[n + extra].Y = last_poly[n].Y;
1381 }
1382 // copy new points
1383 for (int n = 0; n < extra; n++) {
1384 last_poly[k + n].X = ppt[(i + 2 + n) % polyi].X;
1385 last_poly[k + n].Y = ppt[(i + 2 + n) % polyi].Y;
1386 }
1387 last_polyi += extra;
1388 } else {
1389 if (!last_poly.empty()) {
1390 if (poly_graphics == NULL) {
1391 // changing smoothing mode is still necessary in case of new/unknown code paths
1392 SmoothingMode mode = graphics.GetSmoothingMode();
1393 if (lpgw->antialiasing && !lpgw->polyaa)
1394 graphics.SetSmoothingMode(SmoothingModeNone);
1395 graphics.FillPolygon(fill_brush, last_poly.data(), last_polyi);
1396 graphics.SetSmoothingMode(mode);
1397 } else {
1398 poly_graphics->FillPolygon(fill_brush, last_poly.data(), last_polyi);
1399 }
1400 }
1401 // save the current polygon
1402 last_poly = ppt;
1403 last_polyi = polyi;
1404 }
1405
1406 polyi = 0;
1407 break;
1408 }
1409
1410 case W_image: {
1411 /* Due to the structure of gwop 6 entries are needed in total. */
1412 if (seq == 0) {
1413 /* First OP contains only the color mode */
1414 color_mode = curptr->x;
1415 } else if (seq < 5) {
1416 /* Next four OPs contain the `corner` array */
1417 corners[seq - 1].x = xdash + 0.5;
1418 corners[seq - 1].y = ydash + 0.5;
1419 } else {
1420 /* The last OP contains the image and it's size */
1421 char * image = (char *) LocalLock(curptr->htext);
1422 unsigned int width = curptr->x;
1423 unsigned int height = curptr->y;
1424 if (image) {
1425 Bitmap * bitmap;
1426
1427 graphics.SetPixelOffsetMode(PixelOffsetModeHighQuality);
1428
1429 /* create clip region */
1430 Rect clipRect(
1431 (INT) GPMIN(corners[2].x, corners[3].x), (INT) GPMIN(corners[2].y, corners[3].y),
1432 abs(corners[2].x - corners[3].x), abs(corners[2].y - corners[3].y));
1433 graphics.SetClip(clipRect);
1434
1435 if (color_mode != IC_RGBA) {
1436 int pad_bytes = (4 - (3 * width) % 4) % 4; /* scan lines start on ULONG boundaries */
1437 int stride = width * 3 + pad_bytes;
1438 bitmap = new Bitmap(width, height, stride, PixelFormat24bppRGB, (BYTE *) image);
1439 } else {
1440 int stride = width * 4;
1441 bitmap = new Bitmap(width, height, stride, PixelFormat32bppARGB, (BYTE *) image);
1442 }
1443
1444 if (bitmap) {
1445 /* image is upside-down */
1446 bitmap->RotateFlip(RotateNoneFlipY);
1447 if (lpgw->color) {
1448 graphics.DrawImage(bitmap,
1449 (INT) GPMIN(corners[0].x, corners[1].x),
1450 (INT) GPMIN(corners[0].y, corners[1].y),
1451 abs(corners[1].x - corners[0].x),
1452 abs(corners[1].y - corners[0].y));
1453 } else {
1454 /* convert to grayscale */
1455 ColorMatrix cm = {{{0.30f, 0.30f, 0.30f, 0, 0},
1456 {0.59f, 0.59f, 0.59f, 0, 0},
1457 {0.11f, 0.11f, 0.11f, 0, 0},
1458 {0, 0, 0, 1, 0},
1459 {0, 0, 0, 0, 1}
1460 }};
1461 ImageAttributes ia;
1462 ia.SetColorMatrix(&cm, ColorMatrixFlagsDefault, ColorAdjustTypeBitmap);
1463 graphics.DrawImage(bitmap,
1464 RectF((INT) GPMIN(corners[0].x, corners[1].x),
1465 (INT) GPMIN(corners[0].y, corners[1].y),
1466 abs(corners[1].x - corners[0].x),
1467 abs(corners[1].y - corners[0].y)),
1468 0, 0, width, height,
1469 UnitPixel, &ia);
1470 }
1471 delete bitmap;
1472 }
1473 graphics.ResetClip();
1474 graphics.SetPixelOffsetMode(PixelOffsetModeNone);
1475 }
1476 LocalUnlock(curptr->htext);
1477 }
1478 seq = (seq + 1) % 6;
1479 break;
1480 }
1481
1482 default: {
1483 enum win_pointtypes symbol = (enum win_pointtypes) curptr->op;
1484
1485 /* This covers only point symbols. All other codes should be
1486 handled in the switch statement. */
1487 if ((symbol < W_dot) || (symbol > W_last_pointtype))
1488 break;
1489
1490 // draw cached point symbol
1491 if (ps_caching && (last_symbol == symbol) && (cb != NULL)) {
1492 // always draw point symbols on integer (pixel) positions
1493 if (lpgw->oversample)
1494 graphics.DrawCachedBitmap(cb, INT(xdash + 0.5) - cb_ofs.x, INT(ydash + 0.5) - cb_ofs.y);
1495 else
1496 graphics.DrawCachedBitmap(cb, xdash - cb_ofs.x, ydash - cb_ofs.y);
1497 break;
1498 } else {
1499 if (cb != NULL) {
1500 delete cb;
1501 cb = NULL;
1502 }
1503 }
1504
1505 Bitmap *b = 0;
1506 Graphics *g = 0;
1507 int xofs;
1508 int yofs;
1509
1510 // Switch between cached and direct drawing
1511 if (ps_caching) {
1512 // Create a compatible bitmap
1513 b = new Bitmap(2 * htic + 3, 2 * vtic + 3, &graphics);
1514 g = Graphics::FromImage(b);
1515 if (lpgw->antialiasing)
1516 g->SetSmoothingMode(SmoothingModeAntiAlias8x8);
1517 cb_ofs.x = xofs = htic + 1;
1518 cb_ofs.y = yofs = vtic + 1;
1519 last_symbol = symbol;
1520 } else {
1521 g = &graphics;
1522 // snap to pixel
1523 if (lpgw->oversample) {
1524 xofs = xdash + 0.5;
1525 yofs = ydash + 0.5;
1526 } else {
1527 xofs = xdash;
1528 yofs = ydash;
1529 }
1530 }
1531
1532 switch (symbol) {
1533 case W_dot:
1534 gdiplusDot(*g, solid_brush, xofs, yofs);
1535 break;
1536 case W_plus: /* do plus */
1537 case W_star: /* do star: first plus, then cross */
1538 g->DrawLine(&solid_pen, xofs - htic, yofs, xofs + htic, yofs);
1539 g->DrawLine(&solid_pen, xofs, yofs - vtic, xofs, yofs + vtic);
1540 if (symbol == W_plus)
1541 break;
1542 case W_cross: /* do X */
1543 g->DrawLine(&solid_pen, xofs - htic, yofs - vtic, xofs + htic - 1, yofs + vtic);
1544 g->DrawLine(&solid_pen, xofs - htic, yofs + vtic, xofs + htic - 1, yofs - vtic);
1545 break;
1546 case W_circle: /* do open circle */
1547 g->DrawEllipse(&solid_pen, xofs - htic, yofs - htic, 2 * htic, 2 * htic);
1548 break;
1549 case W_fcircle: /* do filled circle */
1550 g->FillEllipse(&solid_brush, xofs - htic, yofs - htic, 2 * htic, 2 * htic);
1551 break;
1552 default: { /* potentially closed figure */
1553 Point p[6];
1554 int i;
1555 int shape = 0;
1556 int filled = 0;
1557 int index = 0;
1558 const float pointshapes[6][10] = {
1559 {-1, -1, +1, -1, +1, +1, -1, +1, 0, 0}, /* box */
1560 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, /* dummy, circle */
1561 { 0, -4./3, -4./3, 2./3,
1562 4./3, 2./3, 0, 0}, /* triangle */
1563 { 0, 4./3, -4./3, -2./3,
1564 4./3, -2./3, 0, 0}, /* inverted triangle */
1565 { 0, +1, -1, 0, 0, -1, +1, 0, 0, 0}, /* diamond */
1566 { 0, 1, 0.95106, 0.30902, 0.58779, -0.80902,
1567 -0.58779, -0.80902, -0.95106, 0.30902} /* pentagon */
1568 };
1569
1570 // This should never happen since all other codes should be
1571 // handled in the switch statement.
1572 if ((symbol < W_box) || (symbol > W_last_pointtype))
1573 break;
1574
1575 // Calculate index, instead of an ugly long switch statement;
1576 // Depends on definition of commands in wgnuplib.h.
1577 index = (symbol - W_box);
1578 shape = index / 2;
1579 filled = (index % 2) > 0;
1580
1581 for (i = 0; i < 5; ++i) {
1582 if (pointshapes[shape][i * 2 + 1] == 0
1583 && pointshapes[shape][i * 2] == 0)
1584 break;
1585 p[i].X = xofs + htic * pointshapes[shape][i * 2] + 0.5;
1586 p[i].Y = yofs + vtic * pointshapes[shape][i * 2 + 1] + 0.5;
1587 }
1588 if (filled) {
1589 /* filled polygon with border */
1590 g->FillPolygon(&solid_brush, p, i);
1591 } else {
1592 /* Outline polygon */
1593 p[i].X = p[0].X;
1594 p[i].Y = p[0].Y;
1595 gdiplusPolyline(*g, solid_pen, p, i + 1);
1596 gdiplusDot(*g, solid_brush, xofs, yofs);
1597 }
1598 } /* default case */
1599 } /* switch (point symbol) */
1600
1601 if (b != NULL) {
1602 // create a cached bitmap for faster redrawing
1603 cb = new CachedBitmap(b, &graphics);
1604 // display point symbol snapped to pixel
1605 if (lpgw->oversample)
1606 graphics.DrawCachedBitmap(cb, INT(xdash + 0.5) - xofs, INT(ydash + 0.5) - yofs);
1607 else
1608 graphics.DrawCachedBitmap(cb, xdash - xofs, ydash - yofs);
1609 delete b;
1610 delete g;
1611 }
1612
1613 if (keysample) {
1614 draw_update_keybox(lpgw, plotno, xdash + htic, ydash + vtic);
1615 draw_update_keybox(lpgw, plotno, xdash - htic, ydash - vtic);
1616 }
1617 break;
1618 } /* default case */
1619 } /* switch(opcode) */
1620 } /* hide layer? */
1621
1622 lastop = curptr->op;
1623 ngwop++;
1624 curptr++;
1625 if ((unsigned)(curptr - blkptr->gwop) >= GWOPMAX) {
1626 GlobalUnlock(blkptr->hblk);
1627 blkptr->gwop = (struct GWOP *)NULL;
1628 if ((blkptr = blkptr->next) == NULL)
1629 /* If exact multiple of GWOPMAX entries are queued,
1630 * next will be NULL. Only the next GraphOp() call would
1631 * have allocated a new block */
1632 break;
1633 if (!blkptr->gwop)
1634 blkptr->gwop = (struct GWOP *)GlobalLock(blkptr->hblk);
1635 if (!blkptr->gwop)
1636 break;
1637 curptr = (struct GWOP *)blkptr->gwop;
1638 }
1639 } /* while (ngwop < lpgw->nGWOP) */
1640
1641 /* clean-up */
1642 if (pattern_brush)
1643 delete pattern_brush;
1644 if (cb)
1645 delete cb;
1646 if (font)
1647 delete font;
1648 ppt.clear();
1649 }
1650
1651
1652 UINT nImageCodecs = 0; // number of image encoders
1653 ImageCodecInfo * pImageCodecInfo = NULL; // list of image encoders
1654
1655
1656 static int
1657 gdiplusGetEncoders()
1658 {
1659 UINT num = 0;
1660 UINT size = 0;
1661
1662 GetImageEncodersSize(&nImageCodecs, &size);
1663 if (size == 0)
1664 return -1;
1665 pImageCodecInfo = (ImageCodecInfo *) malloc(size);
1666 if (pImageCodecInfo == NULL)
1667 return -1;
1668 GetImageEncoders(nImageCodecs, size, pImageCodecInfo);
1669 return num;
1670 }
1671
1672
1673 void
1674 SaveAsBitmap(LPGW lpgw)
1675 {
1676 static OPENFILENAMEW Ofn;
1677 static WCHAR lpstrCustomFilter[256] = { '\0' };
1678 static WCHAR lpstrFileName[MAX_PATH] = { '\0' };
1679 static WCHAR lpstrFileTitle[MAX_PATH] = { '\0' };
1680 UINT i;
1681
1682 // make sure GDI+ is initialized
1683 gdiplusInit();
1684
1685 // ask GDI+ about supported encoders
1686 if (pImageCodecInfo == NULL)
1687 if (gdiplusGetEncoders() < 0)
1688 std::cerr << "Error: GDI+ could not retrieve the list of encoders" << std::endl;
1689 #if 0
1690 for (i = 0; i < nImageCodecs; i++) {
1691 char * descr = AnsiText(pImageCodecInfo[i].FormatDescription, S_ENC_DEFAULT);
1692 char * ext = AnsiText(pImageCodecInfo[i].FilenameExtension, S_ENC_DEFAULT);
1693 std::cerr << i << ": " << descr << " " << ext << std::endl;
1694 free(descr);
1695 free(ext);
1696 }
1697 #endif
1698
1699 // assemble filter list
1700 UINT npng = 1;
1701 size_t len;
1702 for (i = 0, len = 1; i < nImageCodecs; i++) {
1703 len += wcslen(pImageCodecInfo[i].FormatDescription) + wcslen(pImageCodecInfo[i].FilenameExtension) + 2;
1704 // make PNG the default selection
1705 if (wcsnicmp(pImageCodecInfo[i].FormatDescription, L"PNG", 3) == 0)
1706 npng = i + 1;
1707 }
1708 LPWSTR filter = (LPWSTR) malloc(len * sizeof(WCHAR));
1709 swprintf_s(filter, len, L"%ls\t%ls\t", pImageCodecInfo[0].FormatDescription, pImageCodecInfo[0].FilenameExtension);
1710 for (i = 1; i < nImageCodecs; i++) {
1711 size_t len2 = wcslen(pImageCodecInfo[i].FormatDescription) + wcslen(pImageCodecInfo[i].FilenameExtension) + 3;
1712 LPWSTR type = (LPWSTR) malloc(len2 * sizeof(WCHAR));
1713 swprintf_s(type, len2, L"%ls\t%ls\t", pImageCodecInfo[i].FormatDescription, pImageCodecInfo[i].FilenameExtension);
1714 wcscat(filter, type);
1715 free(type);
1716 }
1717 for (i = 1; i < len; i++) {
1718 if (filter[i] == TEXT('\t'))
1719 filter[i] = TEXT('\0');
1720 }
1721
1722 // init save file dialog parameters
1723 Ofn.lStructSize = sizeof(OPENFILENAME);
1724 Ofn.hwndOwner = lpgw->hWndGraph;
1725 Ofn.lpstrInitialDir = NULL;
1726 Ofn.lpstrFilter = filter;
1727 Ofn.lpstrCustomFilter = lpstrCustomFilter;
1728 Ofn.nMaxCustFilter = 255;
1729 Ofn.nFilterIndex = npng;
1730 Ofn.lpstrFile = lpstrFileName;
1731 Ofn.nMaxFile = MAX_PATH;
1732 Ofn.lpstrFileTitle = lpstrFileTitle;
1733 Ofn.nMaxFileTitle = MAX_PATH;
1734 Ofn.lpstrInitialDir = NULL;
1735 Ofn.lpstrTitle = NULL;
1736 Ofn.Flags = OFN_OVERWRITEPROMPT | OFN_NOCHANGEDIR | OFN_NOREADONLYRETURN;
1737 Ofn.lpstrDefExt = L"png";
1738
1739 if (GetSaveFileNameW(&Ofn) != 0) {
1740 // Note that there might be a copy in lpgw->hBitmap., but documentation
1741 // says we may not use that (although it seems to work).
1742 // So we get a new copy of the screen:
1743 HBITMAP hBitmap = GraphGetBitmap(lpgw);
1744 Bitmap * bitmap = Bitmap::FromHBITMAP(hBitmap, 0);
1745 UINT ntype = Ofn.nFilterIndex - 1;
1746 #if 0
1747 LPWSTR wtype = pImageCodecInfo[ntype].FormatDescription;
1748 char * type = AnsiText(wtype, S_ENC_DEFAULT);
1749 SizeF size;
1750 bitmap->GetPhysicalDimension(&size);
1751 std::cerr << std::endl << "Saving bitmap: size: " << size.Width << " x " << size.Height << " type: " << type << " ..." << std::endl;
1752 free(type);
1753 #endif
1754 bitmap->Save(Ofn.lpstrFile, &(pImageCodecInfo[ntype].Clsid), NULL);
1755 delete bitmap;
1756 DeleteObject(hBitmap);
1757 }
1758 free(filter);
1759 }
1760
1761
1762 static Bitmap *
1763 ResizeBitmap(Bitmap &bmp, INT width, INT height)
1764 {
1765 unsigned iheight = bmp.GetHeight();
1766 unsigned iwidth = bmp.GetWidth();
1767 if (width != height) {
1768 double aspect = (double)iwidth / iheight;
1769 if (iwidth > iheight)
1770 height = static_cast<unsigned>(width / aspect);
1771 else
1772 width = static_cast<unsigned>(height * aspect);
1773 }
1774 Bitmap * nbmp = new Bitmap(width, height, bmp.GetPixelFormat());
1775 Graphics graphics(nbmp);
1776 graphics.DrawImage(&bmp, 0, 0, width - 1, height - 1);
1777 return nbmp;
1778 }
1779
1780
1781 HBITMAP
1782 gdiplusLoadBitmap(LPWSTR file, int size)
1783 {
1784 gdiplusInit();
1785 Bitmap ibmp(file);
1786 Bitmap * bmp = ResizeBitmap(ibmp, size, size);
1787 HBITMAP hbitmap;
1788 Color color(0, 0, 0);
1789 bmp->GetHBITMAP(color, &hbitmap);
1790 delete bmp;
1791 return hbitmap;
1792 }
1793