gdiplus: Updated GdipDrawArc to use SaveDC()/RestoreDC()/line caps.
[wine] / dlls / gdiplus / graphics.c
1 /*
2  * Copyright (C) 2007 Google (Evan Stade)
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
17  */
18
19 #include <stdarg.h>
20 #include <math.h>
21
22 #include "windef.h"
23 #include "winbase.h"
24 #include "winuser.h"
25 #include "wingdi.h"
26 #include "gdiplus.h"
27 #include "gdiplus_private.h"
28 #include "wine/debug.h"
29
30 WINE_DEFAULT_DEBUG_CHANNEL(gdiplus);
31
32 /* looks-right constants */
33 #define TENSION_CONST (0.3)
34 #define ANCHOR_WIDTH (2.0)
35 #define MAX_ITERS (50)
36
37 /* Converts angle (in degrees) to x/y coordinates */
38 static void deg2xy(REAL angle, REAL x_0, REAL y_0, REAL *x, REAL *y)
39 {
40     REAL radAngle, hypotenuse;
41
42     radAngle = deg2rad(angle);
43     hypotenuse = 50.0; /* arbitrary */
44
45     *x = x_0 + cos(radAngle) * hypotenuse;
46     *y = y_0 + sin(radAngle) * hypotenuse;
47 }
48
49 /* GdipDrawPie/GdipFillPie helper function */
50 static GpStatus draw_pie(GpGraphics *graphics, HBRUSH gdibrush, HPEN gdipen,
51     REAL x, REAL y, REAL width, REAL height, REAL startAngle, REAL sweepAngle)
52 {
53     INT save_state;
54     REAL x_0, y_0, x_1, y_1, x_2, y_2;
55
56     if(!graphics)
57         return InvalidParameter;
58
59     save_state = SaveDC(graphics->hdc);
60     EndPath(graphics->hdc);
61     SelectObject(graphics->hdc, gdipen);
62     SelectObject(graphics->hdc, gdibrush);
63
64     x_0 = x + (width/2.0);
65     y_0 = y + (height/2.0);
66
67     deg2xy(startAngle+sweepAngle, x_0, y_0, &x_1, &y_1);
68     deg2xy(startAngle, x_0, y_0, &x_2, &y_2);
69
70     Pie(graphics->hdc, roundr(x), roundr(y), roundr(x+width), roundr(y+height),
71         roundr(x_1), roundr(y_1), roundr(x_2), roundr(y_2));
72
73     RestoreDC(graphics->hdc, save_state);
74
75     return Ok;
76 }
77
78 /* GdipDrawCurve helper function.
79  * Calculates Bezier points from cardinal spline points. */
80 static void calc_curve_bezier(CONST GpPointF *pts, REAL tension, REAL *x1,
81     REAL *y1, REAL *x2, REAL *y2)
82 {
83     REAL xdiff, ydiff;
84
85     /* calculate tangent */
86     xdiff = pts[2].X - pts[0].X;
87     ydiff = pts[2].Y - pts[0].Y;
88
89     /* apply tangent to get control points */
90     *x1 = pts[1].X - tension * xdiff;
91     *y1 = pts[1].Y - tension * ydiff;
92     *x2 = pts[1].X + tension * xdiff;
93     *y2 = pts[1].Y + tension * ydiff;
94 }
95
96 /* GdipDrawCurve helper function.
97  * Calculates Bezier points from cardinal spline endpoints. */
98 static void calc_curve_bezier_endp(REAL xend, REAL yend, REAL xadj, REAL yadj,
99     REAL tension, REAL *x, REAL *y)
100 {
101     /* tangent at endpoints is the line from the endpoint to the adjacent point */
102     *x = roundr(tension * (xadj - xend) + xend);
103     *y = roundr(tension * (yadj - yend) + yend);
104 }
105
106 /* Draws the linecap the specified color and size on the hdc.  The linecap is in
107  * direction of the line from x1, y1 to x2, y2 and is anchored on x2, y2. */
108 static void draw_cap(HDC hdc, COLORREF color, GpLineCap cap, REAL size,
109     REAL x1, REAL y1, REAL x2, REAL y2)
110 {
111     HGDIOBJ oldbrush, oldpen;
112     HBRUSH brush;
113     HPEN pen;
114     POINT pt[4];
115     REAL theta, dsmall, dbig, dx, dy, invert;
116
117     if(x2 != x1)
118         theta = atan((y2 - y1) / (x2 - x1));
119     else if(y2 != y1){
120         theta = M_PI_2 * (y2 > y1 ? 1.0 : -1.0);
121     }
122     else
123         return;
124
125     invert = ((x2 - x1) >= 0.0 ? 1.0 : -1.0);
126     brush = CreateSolidBrush(color);
127     pen = CreatePen(PS_SOLID, 1, color);
128     oldbrush = SelectObject(hdc, brush);
129     oldpen = SelectObject(hdc, pen);
130
131     switch(cap){
132         case LineCapFlat:
133             break;
134         case LineCapSquare:
135         case LineCapSquareAnchor:
136         case LineCapDiamondAnchor:
137             size = size * (cap & LineCapNoAnchor ? ANCHOR_WIDTH : 1.0) / 2.0;
138             if(cap == LineCapDiamondAnchor){
139                 dsmall = cos(theta + M_PI_2) * size;
140                 dbig = sin(theta + M_PI_2) * size;
141             }
142             else{
143                 dsmall = cos(theta + M_PI_4) * size;
144                 dbig = sin(theta + M_PI_4) * size;
145             }
146
147             /* calculating the latter points from the earlier points makes them
148              * look a little better because of rounding issues */
149             pt[0].x = roundr(x2 - dsmall);
150             pt[1].x = roundr(((REAL)pt[0].x) + dbig + dsmall);
151
152             pt[0].y = roundr(y2 - dbig);
153             pt[3].y = roundr(((REAL)pt[0].y) + dsmall + dbig);
154
155             pt[1].y = roundr(y2 - dsmall);
156             pt[2].y = roundr(dbig + dsmall + ((REAL)pt[1].y));
157
158             pt[3].x = roundr(x2 - dbig);
159             pt[2].x = roundr(((REAL)pt[3].x) + dsmall + dbig);
160
161             Polygon(hdc, pt, 4);
162
163             break;
164         case LineCapArrowAnchor:
165             size = size * 4.0 / sqrt(3.0);
166
167             dx = cos(M_PI / 6.0 + theta) * size * invert;
168             dy = sin(M_PI / 6.0 + theta) * size * invert;
169
170             pt[0].x = roundr(x2 - dx);
171             pt[0].y = roundr(y2 - dy);
172
173             dx = cos(- M_PI / 6.0 + theta) * size * invert;
174             dy = sin(- M_PI / 6.0 + theta) * size * invert;
175
176             pt[1].x = roundr(x2 - dx);
177             pt[1].y = roundr(y2 - dy);
178
179             pt[2].x = roundr(x2);
180             pt[2].y = roundr(y2);
181
182             Polygon(hdc, pt, 3);
183
184             break;
185         case LineCapRoundAnchor:
186             dx = dy = ANCHOR_WIDTH * size / 2.0;
187
188             x2 = (REAL) roundr(x2 - dx);
189             y2 = (REAL) roundr(y2 - dy);
190
191             Ellipse(hdc, (INT) x2, (INT) y2, roundr(x2 + 2.0 * dx),
192                 roundr(y2 + 2.0 * dy));
193             break;
194         case LineCapTriangle:
195             size = size / 2.0;
196             dx = cos(M_PI_2 + theta) * size;
197             dy = sin(M_PI_2 + theta) * size;
198
199             /* Using roundr here can make the triangle float off the end of the
200              * line. */
201             pt[0].x = ((x2 - x1) >= 0 ? floorf(x2 - dx) : ceilf(x2 - dx));
202             pt[0].y = ((y2 - y1) >= 0 ? floorf(y2 - dy) : ceilf(y2 - dy));
203             pt[1].x = roundr(pt[0].x + 2.0 * dx);
204             pt[1].y = roundr(pt[0].y + 2.0 * dy);
205
206             dx = cos(theta) * size * invert;
207             dy = sin(theta) * size * invert;
208
209             pt[2].x = roundr(x2 + dx);
210             pt[2].y = roundr(y2 + dy);
211
212             Polygon(hdc, pt, 3);
213
214             break;
215         case LineCapRound:
216             dx = -cos(M_PI_2 + theta) * size * invert;
217             dy = -sin(M_PI_2 + theta) * size * invert;
218
219             pt[0].x = ((x2 - x1) >= 0 ? floorf(x2 - dx) : ceilf(x2 - dx));
220             pt[0].y = ((y2 - y1) >= 0 ? floorf(y2 - dy) : ceilf(y2 - dy));
221             pt[1].x = roundr(pt[0].x + 2.0 * dx);
222             pt[1].y = roundr(pt[0].y + 2.0 * dy);
223
224             dx = dy = size / 2.0;
225
226             x2 = (REAL) roundr(x2 - dx);
227             y2 = (REAL) roundr(y2 - dy);
228
229             Pie(hdc, (INT) x2, (INT) y2, roundr(x2 + 2.0 * dx),
230                 roundr(y2 + 2.0 * dy), pt[0].x, pt[0].y, pt[1].x, pt[1].y);
231             break;
232         case LineCapCustom:
233             FIXME("line cap not implemented\n");
234         default:
235             break;
236     }
237
238     SelectObject(hdc, oldbrush);
239     SelectObject(hdc, oldpen);
240     DeleteObject(brush);
241     DeleteObject(pen);
242 }
243
244 /* Shortens the line by the given percent by changing x2, y2.
245  * If percent is > 1.0 then the line will change direction. */
246 static void shorten_line_percent(REAL x1, REAL  y1, REAL *x2, REAL *y2, REAL percent)
247 {
248     REAL dist, theta, dx, dy;
249
250     if((y1 == *y2) && (x1 == *x2))
251         return;
252
253     dist = sqrt((*x2 - x1) * (*x2 - x1) + (*y2 - y1) * (*y2 - y1)) * percent;
254     theta = (*x2 == x1 ? M_PI_2 : atan((*y2 - y1) / (*x2 - x1)));
255     dx = cos(theta) * dist;
256     dy = sin(theta) * dist;
257
258     *x2 = *x2 + fabs(dx) * (*x2 > x1 ? -1.0 : 1.0);
259     *y2 = *y2 + fabs(dy) * (*y2 > y1 ? -1.0 : 1.0);
260 }
261
262 /* Shortens the line by the given amount by changing x2, y2.
263  * If the amount is greater than the distance, the line will become length 0. */
264 static void shorten_line_amt(REAL x1, REAL y1, REAL *x2, REAL *y2, REAL amt)
265 {
266     REAL dx, dy, percent;
267
268     dx = *x2 - x1;
269     dy = *y2 - y1;
270     if(dx == 0 && dy == 0)
271         return;
272
273     percent = amt / sqrt(dx * dx + dy * dy);
274     if(percent >= 1.0){
275         *x2 = x1;
276         *y2 = y1;
277         return;
278     }
279
280     shorten_line_percent(x1, y1, x2, y2, percent);
281 }
282
283 /* Draws lines between the given points, and if caps is true then draws an endcap
284  * at the end of the last line.  FIXME: Startcaps not implemented. */
285 static void draw_polyline(HDC hdc, GpPen *pen, GDIPCONST GpPointF * pt,
286     INT count, BOOL caps)
287 {
288     POINT *pti = GdipAlloc(count * sizeof(POINT));
289     REAL x = pt[count - 1].X, y = pt[count - 1].Y;
290     INT i;
291
292     if(caps){
293         if(pen->endcap == LineCapArrowAnchor)
294             shorten_line_amt(pt[count-2].X, pt[count-2].Y, &x, &y, pen->width);
295
296         draw_cap(hdc, pen->color, pen->endcap, pen->width, pt[count-2].X,
297             pt[count-2].Y, pt[count - 1].X, pt[count - 1].Y);
298     }
299
300     for(i = 0; i < count - 1; i ++){
301         pti[i].x = roundr(pt[i].X);
302         pti[i].y = roundr(pt[i].Y);
303     }
304
305     pti[i].x = roundr(x);
306     pti[i].y = roundr(y);
307
308     Polyline(hdc, pti, count);
309     GdipFree(pti);
310 }
311
312 /* Conducts a linear search to find the bezier points that will back off
313  * the endpoint of the curve by a distance of amt. Linear search works
314  * better than binary in this case because there are multiple solutions,
315  * and binary searches often find a bad one. I don't think this is what
316  * Windows does but short of rendering the bezier without GDI's help it's
317  * the best we can do. */
318 static void shorten_bezier_amt(GpPointF * pt, REAL amt)
319 {
320     GpPointF origpt[4];
321     REAL percent = 0.00, dx, dy, origx = pt[3].X, origy = pt[3].Y, diff = -1.0;
322     INT i;
323
324     memcpy(origpt, pt, sizeof(GpPointF) * 4);
325
326     for(i = 0; (i < MAX_ITERS) && (diff < amt); i++){
327         /* reset bezier points to original values */
328         memcpy(pt, origpt, sizeof(GpPointF) * 4);
329         /* Perform magic on bezier points. Order is important here.*/
330         shorten_line_percent(pt[2].X, pt[2].Y, &pt[3].X, &pt[3].Y, percent);
331         shorten_line_percent(pt[1].X, pt[1].Y, &pt[2].X, &pt[2].Y, percent);
332         shorten_line_percent(pt[2].X, pt[2].Y, &pt[3].X, &pt[3].Y, percent);
333         shorten_line_percent(pt[0].X, pt[0].Y, &pt[1].X, &pt[1].Y, percent);
334         shorten_line_percent(pt[1].X, pt[1].Y, &pt[2].X, &pt[2].Y, percent);
335         shorten_line_percent(pt[2].X, pt[2].Y, &pt[3].X, &pt[3].Y, percent);
336
337         dx = pt[3].X - origx;
338         dy = pt[3].Y - origy;
339
340         diff = sqrt(dx * dx + dy * dy);
341         percent += 0.0005 * amt;
342     }
343 }
344
345 /* Draws bezier curves between given points, and if caps is true then draws an
346  * endcap at the end of the last line.  FIXME: Startcaps not implemented. */
347 static void draw_polybezier(HDC hdc, GpPen *pen, GDIPCONST GpPointF * pt,
348     INT count, BOOL caps)
349 {
350     POINT *pti = GdipAlloc(count * sizeof(POINT));
351     GpPointF *ptf = GdipAlloc(4 * sizeof(GpPointF));
352     INT i;
353
354     memcpy(ptf, &pt[count-4], 4 * sizeof(GpPointF));
355
356     if(caps){
357         if(pen->endcap == LineCapArrowAnchor)
358             shorten_bezier_amt(ptf, pen->width);
359
360         draw_cap(hdc, pen->color, pen->endcap, pen->width, ptf[3].X,
361             ptf[3].Y, pt[count - 1].X, pt[count - 1].Y);
362     }
363
364     for(i = 0; i < count - 4; i ++){
365         pti[i].x = roundr(pt[i].X);
366         pti[i].y = roundr(pt[i].Y);
367     }
368     for(i = 0; i < 4; i ++){
369         pti[i + count - 4].x = roundr(ptf[i].X);
370         pti[i + count - 4].y = roundr(ptf[i].Y);
371     }
372
373     PolyBezier(hdc, pti, count);
374     GdipFree(pti);
375     GdipFree(ptf);
376 }
377
378 GpStatus WINGDIPAPI GdipCreateFromHDC(HDC hdc, GpGraphics **graphics)
379 {
380     if(hdc == NULL)
381         return OutOfMemory;
382
383     if(graphics == NULL)
384         return InvalidParameter;
385
386     *graphics = GdipAlloc(sizeof(GpGraphics));
387     if(!*graphics)  return OutOfMemory;
388
389     (*graphics)->hdc = hdc;
390     (*graphics)->hwnd = NULL;
391
392     return Ok;
393 }
394
395 GpStatus WINGDIPAPI GdipCreateFromHWND(HWND hwnd, GpGraphics **graphics)
396 {
397     GpStatus ret;
398
399     if((ret = GdipCreateFromHDC(GetDC(hwnd), graphics)) != Ok)
400         return ret;
401
402     (*graphics)->hwnd = hwnd;
403
404     return Ok;
405 }
406
407 GpStatus WINGDIPAPI GdipDeleteGraphics(GpGraphics *graphics)
408 {
409     if(!graphics) return InvalidParameter;
410     if(graphics->hwnd)
411         ReleaseDC(graphics->hwnd, graphics->hdc);
412
413     HeapFree(GetProcessHeap(), 0, graphics);
414
415     return Ok;
416 }
417
418 GpStatus WINGDIPAPI GdipDrawArc(GpGraphics *graphics, GpPen *pen, REAL x,
419     REAL y, REAL width, REAL height, REAL startAngle, REAL sweepAngle)
420 {
421     INT save_state, num_pts;
422     GpPointF points[MAX_ARC_PTS];
423
424     if(!graphics || !pen)
425         return InvalidParameter;
426
427     num_pts = arc2polybezier(points, x, y, width, height, startAngle, sweepAngle);
428
429     save_state = SaveDC(graphics->hdc);
430     EndPath(graphics->hdc);
431     SelectObject(graphics->hdc, pen->gdipen);
432
433     draw_polybezier(graphics->hdc, pen, points, num_pts, TRUE);
434
435     RestoreDC(graphics->hdc, save_state);
436
437     return Ok;
438 }
439
440 GpStatus WINGDIPAPI GdipDrawBezier(GpGraphics *graphics, GpPen *pen, REAL x1,
441     REAL y1, REAL x2, REAL y2, REAL x3, REAL y3, REAL x4, REAL y4)
442 {
443     INT save_state;
444     GpPointF pt[4];
445
446     if(!graphics || !pen)
447         return InvalidParameter;
448
449     pt[0].X = x1;
450     pt[0].Y = y1;
451     pt[1].X = x2;
452     pt[1].Y = y2;
453     pt[2].X = x3;
454     pt[2].Y = y3;
455     pt[3].X = x4;
456     pt[3].Y = y4;
457
458     save_state = SaveDC(graphics->hdc);
459     EndPath(graphics->hdc);
460     SelectObject(graphics->hdc, pen->gdipen);
461
462     draw_polybezier(graphics->hdc, pen, pt, 4, TRUE);
463
464     RestoreDC(graphics->hdc, save_state);
465
466     return Ok;
467 }
468
469 /* Approximates cardinal spline with Bezier curves. */
470 GpStatus WINGDIPAPI GdipDrawCurve2(GpGraphics *graphics, GpPen *pen,
471     GDIPCONST GpPointF *points, INT count, REAL tension)
472 {
473     /* PolyBezier expects count*3-2 points. */
474     INT i, len_pt = count*3-2, save_state;
475     GpPointF *pt;
476     REAL x1, x2, y1, y2;
477
478     if(!graphics || !pen)
479         return InvalidParameter;
480
481     pt = GdipAlloc(len_pt * sizeof(GpPointF));
482     tension = tension * TENSION_CONST;
483
484     calc_curve_bezier_endp(points[0].X, points[0].Y, points[1].X, points[1].Y,
485         tension, &x1, &y1);
486
487     pt[0].X = points[0].X;
488     pt[0].Y = points[0].Y;
489     pt[1].X = x1;
490     pt[1].Y = y1;
491
492     for(i = 0; i < count-2; i++){
493         calc_curve_bezier(&(points[i]), tension, &x1, &y1, &x2, &y2);
494
495         pt[3*i+2].X = x1;
496         pt[3*i+2].Y = y1;
497         pt[3*i+3].X = points[i+1].X;
498         pt[3*i+3].Y = points[i+1].Y;
499         pt[3*i+4].X = x2;
500         pt[3*i+4].Y = y2;
501     }
502
503     calc_curve_bezier_endp(points[count-1].X, points[count-1].Y,
504         points[count-2].X, points[count-2].Y, tension, &x1, &y1);
505
506     pt[len_pt-2].X = x1;
507     pt[len_pt-2].Y = y1;
508     pt[len_pt-1].X = points[count-1].X;
509     pt[len_pt-1].Y = points[count-1].Y;
510
511     save_state = SaveDC(graphics->hdc);
512     EndPath(graphics->hdc);
513     SelectObject(graphics->hdc, pen->gdipen);
514
515     draw_polybezier(graphics->hdc, pen, pt, len_pt, TRUE);
516
517     GdipFree(pt);
518     RestoreDC(graphics->hdc, save_state);
519
520     return Ok;
521 }
522
523 GpStatus WINGDIPAPI GdipDrawLineI(GpGraphics *graphics, GpPen *pen, INT x1,
524     INT y1, INT x2, INT y2)
525 {
526     INT save_state;
527     GpPointF pt[2];
528
529     if(!pen || !graphics)
530         return InvalidParameter;
531
532     pt[0].X = (REAL)x1;
533     pt[0].Y = (REAL)y1;
534     pt[1].X = (REAL)x2;
535     pt[1].Y = (REAL)y2;
536
537     save_state = SaveDC(graphics->hdc);
538     EndPath(graphics->hdc);
539     SelectObject(graphics->hdc, pen->gdipen);
540
541     draw_polyline(graphics->hdc, pen, pt, 2, TRUE);
542
543     RestoreDC(graphics->hdc, save_state);
544
545     return Ok;
546 }
547
548 GpStatus WINGDIPAPI GdipDrawLines(GpGraphics *graphics, GpPen *pen, GDIPCONST
549     GpPointF *points, INT count)
550 {
551     HGDIOBJ old_obj;
552     INT i;
553
554     if(!pen || !graphics || (count < 2))
555         return InvalidParameter;
556
557     old_obj = SelectObject(graphics->hdc, pen->gdipen);
558     MoveToEx(graphics->hdc, roundr(points[0].X), roundr(points[0].Y), NULL);
559
560     for(i = 1; i < count; i++){
561         LineTo(graphics->hdc, roundr(points[i].X), roundr(points[i].Y));
562     }
563
564     SelectObject(graphics->hdc, old_obj);
565
566     return Ok;
567 }
568
569 GpStatus WINGDIPAPI GdipDrawPie(GpGraphics *graphics, GpPen *pen, REAL x,
570     REAL y, REAL width, REAL height, REAL startAngle, REAL sweepAngle)
571 {
572     if(!pen)
573         return InvalidParameter;
574
575     return draw_pie(graphics, GetStockObject(NULL_BRUSH), pen->gdipen, x, y,
576         width, height, startAngle, sweepAngle);
577 }
578
579 GpStatus WINGDIPAPI GdipDrawRectangleI(GpGraphics *graphics, GpPen *pen, INT x,
580     INT y, INT width, INT height)
581 {
582     INT save_state;
583
584     if(!pen || !graphics)
585         return InvalidParameter;
586
587     save_state = SaveDC(graphics->hdc);
588     EndPath(graphics->hdc);
589     SelectObject(graphics->hdc, pen->gdipen);
590     SelectObject(graphics->hdc, GetStockObject(NULL_BRUSH));
591
592     Rectangle(graphics->hdc, x, y, x + width, y + height);
593
594     RestoreDC(graphics->hdc, save_state);
595
596     return Ok;
597 }
598
599 GpStatus WINGDIPAPI GdipFillPie(GpGraphics *graphics, GpBrush *brush, REAL x,
600     REAL y, REAL width, REAL height, REAL startAngle, REAL sweepAngle)
601 {
602     if(!brush)
603         return InvalidParameter;
604
605     return draw_pie(graphics, brush->gdibrush, GetStockObject(NULL_PEN), x, y,
606         width, height, startAngle, sweepAngle);
607 }