gdiplus: Added linecap rendering for GdipDrawBezier.
[wine/testsucceed.git] / dlls / gdiplus / graphics.c
blob5f730ab3a6ba31f1cf6fdd21e7186c913e4cfb15
1 /*
2 * Copyright (C) 2007 Google (Evan Stade)
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.
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.
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
19 #include <stdarg.h>
20 #include <math.h>
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"
30 WINE_DEFAULT_DEBUG_CHANNEL(gdiplus);
32 /* looks-right constants */
33 #define TENSION_CONST (0.3)
34 #define ANCHOR_WIDTH (2.0)
35 #define MAX_ITERS (50)
37 static inline INT roundr(REAL x)
39 return (INT) floor(x+0.5);
42 static inline REAL deg2rad(REAL degrees)
44 return (M_PI*2.0) * degrees / 360.0;
47 /* Converts angle (in degrees) to x/y coordinates */
48 static void deg2xy(REAL angle, REAL x_0, REAL y_0, REAL *x, REAL *y)
50 REAL radAngle, hypotenuse;
52 radAngle = deg2rad(angle);
53 hypotenuse = 50.0; /* arbitrary */
55 *x = x_0 + cos(radAngle) * hypotenuse;
56 *y = y_0 + sin(radAngle) * hypotenuse;
59 /* GdipDrawPie/GdipFillPie helper function */
60 static GpStatus draw_pie(GpGraphics *graphics, HBRUSH gdibrush, HPEN gdipen,
61 REAL x, REAL y, REAL width, REAL height, REAL startAngle, REAL sweepAngle)
63 HGDIOBJ old_pen, old_brush;
64 REAL x_0, y_0, x_1, y_1, x_2, y_2;
66 if(!graphics)
67 return InvalidParameter;
69 old_pen = SelectObject(graphics->hdc, gdipen);
70 old_brush = SelectObject(graphics->hdc, gdibrush);
72 x_0 = x + (width/2.0);
73 y_0 = y + (height/2.0);
75 deg2xy(startAngle+sweepAngle, x_0, y_0, &x_1, &y_1);
76 deg2xy(startAngle, x_0, y_0, &x_2, &y_2);
78 Pie(graphics->hdc, roundr(x), roundr(y), roundr(x+width), roundr(y+height),
79 roundr(x_1), roundr(y_1), roundr(x_2), roundr(y_2));
81 SelectObject(graphics->hdc, old_pen);
82 SelectObject(graphics->hdc, old_brush);
84 return Ok;
87 /* GdipDrawCurve helper function.
88 * Calculates Bezier points from cardinal spline points. */
89 static void calc_curve_bezier(CONST GpPointF *pts, REAL tension, REAL *x1,
90 REAL *y1, REAL *x2, REAL *y2)
92 REAL xdiff, ydiff;
94 /* calculate tangent */
95 xdiff = pts[2].X - pts[0].X;
96 ydiff = pts[2].Y - pts[0].Y;
98 /* apply tangent to get control points */
99 *x1 = pts[1].X - tension * xdiff;
100 *y1 = pts[1].Y - tension * ydiff;
101 *x2 = pts[1].X + tension * xdiff;
102 *y2 = pts[1].Y + tension * ydiff;
105 /* GdipDrawCurve helper function.
106 * Calculates Bezier points from cardinal spline endpoints. */
107 static void calc_curve_bezier_endp(REAL xend, REAL yend, REAL xadj, REAL yadj,
108 REAL tension, REAL *x, REAL *y)
110 /* tangent at endpoints is the line from the endpoint to the adjacent point */
111 *x = roundr(tension * (xadj - xend) + xend);
112 *y = roundr(tension * (yadj - yend) + yend);
115 /* Draws the linecap the specified color and size on the hdc. The linecap is in
116 * direction of the line from x1, y1 to x2, y2 and is anchored on x2, y2. */
117 static void draw_cap(HDC hdc, COLORREF color, GpLineCap cap, REAL size,
118 REAL x1, REAL y1, REAL x2, REAL y2)
120 HGDIOBJ oldbrush, oldpen;
121 HBRUSH brush;
122 HPEN pen;
123 POINT pt[4];
124 REAL theta, dsmall, dbig, dx, dy, invert;
126 if(x2 != x1)
127 theta = atan((y2 - y1) / (x2 - x1));
128 else if(y2 != y1){
129 theta = M_PI_2 * (y2 > y1 ? 1.0 : -1.0);
131 else
132 return;
134 invert = ((x2 - x1) >= 0.0 ? 1.0 : -1.0);
135 brush = CreateSolidBrush(color);
136 pen = CreatePen(PS_SOLID, 1, color);
137 oldbrush = SelectObject(hdc, brush);
138 oldpen = SelectObject(hdc, pen);
140 switch(cap){
141 case LineCapFlat:
142 break;
143 case LineCapSquare:
144 case LineCapSquareAnchor:
145 case LineCapDiamondAnchor:
146 size = size * (cap & LineCapNoAnchor ? ANCHOR_WIDTH : 1.0) / 2.0;
147 if(cap == LineCapDiamondAnchor){
148 dsmall = cos(theta + M_PI_2) * size;
149 dbig = sin(theta + M_PI_2) * size;
151 else{
152 dsmall = cos(theta + M_PI_4) * size;
153 dbig = sin(theta + M_PI_4) * size;
156 /* calculating the latter points from the earlier points makes them
157 * look a little better because of rounding issues */
158 pt[0].x = roundr(x2 - dsmall);
159 pt[1].x = roundr(((REAL)pt[0].x) + dbig + dsmall);
161 pt[0].y = roundr(y2 - dbig);
162 pt[3].y = roundr(((REAL)pt[0].y) + dsmall + dbig);
164 pt[1].y = roundr(y2 - dsmall);
165 pt[2].y = roundr(dbig + dsmall + ((REAL)pt[1].y));
167 pt[3].x = roundr(x2 - dbig);
168 pt[2].x = roundr(((REAL)pt[3].x) + dsmall + dbig);
170 Polygon(hdc, pt, 4);
172 break;
173 case LineCapArrowAnchor:
174 size = size * 4.0 / sqrt(3.0);
176 dx = cos(M_PI / 6.0 + theta) * size * invert;
177 dy = sin(M_PI / 6.0 + theta) * size * invert;
179 pt[0].x = roundr(x2 - dx);
180 pt[0].y = roundr(y2 - dy);
182 dx = cos(- M_PI / 6.0 + theta) * size * invert;
183 dy = sin(- M_PI / 6.0 + theta) * size * invert;
185 pt[1].x = roundr(x2 - dx);
186 pt[1].y = roundr(y2 - dy);
188 pt[2].x = roundr(x2);
189 pt[2].y = roundr(y2);
191 Polygon(hdc, pt, 3);
193 break;
194 case LineCapRoundAnchor:
195 dx = dy = ANCHOR_WIDTH * size / 2.0;
197 x2 = (REAL) roundr(x2 - dx);
198 y2 = (REAL) roundr(y2 - dy);
200 Ellipse(hdc, (INT) x2, (INT) y2, roundr(x2 + 2.0 * dx),
201 roundr(y2 + 2.0 * dy));
202 break;
203 case LineCapTriangle:
204 size = size / 2.0;
205 dx = cos(M_PI_2 + theta) * size;
206 dy = sin(M_PI_2 + theta) * size;
208 /* Using roundr here can make the triangle float off the end of the
209 * line. */
210 pt[0].x = ((x2 - x1) >= 0 ? floor(x2 - dx) : ceil(x2 - dx));
211 pt[0].y = ((y2 - y1) >= 0 ? floor(y2 - dy) : ceil(y2 - dy));
212 pt[1].x = roundr(pt[0].x + 2.0 * dx);
213 pt[1].y = roundr(pt[0].y + 2.0 * dy);
215 dx = cos(theta) * size * invert;
216 dy = sin(theta) * size * invert;
218 pt[2].x = roundr(x2 + dx);
219 pt[2].y = roundr(y2 + dy);
221 Polygon(hdc, pt, 3);
223 break;
224 case LineCapRound:
225 dx = -cos(M_PI_2 + theta) * size * invert;
226 dy = -sin(M_PI_2 + theta) * size * invert;
228 pt[0].x = ((x2 - x1) >= 0 ? floor(x2 - dx) : ceil(x2 - dx));
229 pt[0].y = ((y2 - y1) >= 0 ? floor(y2 - dy) : ceil(y2 - dy));
230 pt[1].x = roundr(pt[0].x + 2.0 * dx);
231 pt[1].y = roundr(pt[0].y + 2.0 * dy);
233 dx = dy = size / 2.0;
235 x2 = (REAL) roundr(x2 - dx);
236 y2 = (REAL) roundr(y2 - dy);
238 Pie(hdc, (INT) x2, (INT) y2, roundr(x2 + 2.0 * dx),
239 roundr(y2 + 2.0 * dy), pt[0].x, pt[0].y, pt[1].x, pt[1].y);
240 break;
241 case LineCapCustom:
242 FIXME("line cap not implemented\n");
243 default:
244 break;
247 SelectObject(hdc, oldbrush);
248 SelectObject(hdc, oldpen);
249 DeleteObject(brush);
250 DeleteObject(pen);
253 /* Shortens the line by the given percent by changing x2, y2.
254 * If percent is > 1.0 then the line will change direction. */
255 static void shorten_line_percent(REAL x1, REAL y1, REAL *x2, REAL *y2, REAL percent)
257 REAL dist, theta, dx, dy;
259 if((y1 == *y2) && (x1 == *x2))
260 return;
262 dist = sqrt((*x2 - x1) * (*x2 - x1) + (*y2 - y1) * (*y2 - y1)) * percent;
263 theta = (*x2 == x1 ? M_PI_2 : atan((*y2 - y1) / (*x2 - x1)));
264 dx = cos(theta) * dist;
265 dy = sin(theta) * dist;
267 *x2 = *x2 + fabs(dx) * (*x2 > x1 ? -1.0 : 1.0);
268 *y2 = *y2 + fabs(dy) * (*y2 > y1 ? -1.0 : 1.0);
271 /* Shortens the line by the given amount by changing x2, y2.
272 * If the amount is greater than the distance, the line will become length 0. */
273 static void shorten_line_amt(REAL x1, REAL y1, REAL *x2, REAL *y2, REAL amt)
275 REAL dx, dy, percent;
277 dx = *x2 - x1;
278 dy = *y2 - y1;
279 if(dx == 0 && dy == 0)
280 return;
282 percent = amt / sqrt(dx * dx + dy * dy);
283 if(percent >= 1.0){
284 *x2 = x1;
285 *y2 = y1;
286 return;
289 shorten_line_percent(x1, y1, x2, y2, percent);
292 /* Draws lines between the given points, and if caps is true then draws an endcap
293 * at the end of the last line. FIXME: Startcaps not implemented. */
294 static void draw_polyline(HDC hdc, GpPen *pen, GDIPCONST GpPointF * pt,
295 INT count, BOOL caps)
297 POINT *pti = GdipAlloc(count * sizeof(POINT));
298 REAL x = pt[count - 1].X, y = pt[count - 1].Y;
299 INT i;
301 if(caps){
302 if(pen->endcap == LineCapArrowAnchor)
303 shorten_line_amt(pt[count-2].X, pt[count-2].Y, &x, &y, pen->width);
305 draw_cap(hdc, pen->color, pen->endcap, pen->width, pt[count-2].X,
306 pt[count-2].Y, pt[count - 1].X, pt[count - 1].Y);
309 for(i = 0; i < count - 1; i ++){
310 pti[i].x = roundr(pt[i].X);
311 pti[i].y = roundr(pt[i].Y);
314 pti[i].x = roundr(x);
315 pti[i].y = roundr(y);
317 Polyline(hdc, pti, count);
318 GdipFree(pti);
321 /* Conducts a linear search to find the bezier points that will back off
322 * the endpoint of the curve by a distance of amt. Linear search works
323 * better than binary in this case because there are multiple solutions,
324 * and binary searches often find a bad one. I don't think this is what
325 * Windows does but short of rendering the bezier without GDI's help it's
326 * the best we can do. */
327 static void shorten_bezier_amt(GpPointF * pt, REAL amt)
329 GpPointF origpt[4];
330 REAL percent = 0.00, dx, dy, origx = pt[3].X, origy = pt[3].Y, diff = -1.0;
331 INT i;
333 memcpy(origpt, pt, sizeof(GpPointF) * 4);
335 for(i = 0; (i < MAX_ITERS) && (diff < amt); i++){
336 /* reset bezier points to original values */
337 memcpy(pt, origpt, sizeof(GpPointF) * 4);
338 /* Perform magic on bezier points. Order is important here.*/
339 shorten_line_percent(pt[2].X, pt[2].Y, &pt[3].X, &pt[3].Y, percent);
340 shorten_line_percent(pt[1].X, pt[1].Y, &pt[2].X, &pt[2].Y, percent);
341 shorten_line_percent(pt[2].X, pt[2].Y, &pt[3].X, &pt[3].Y, percent);
342 shorten_line_percent(pt[0].X, pt[0].Y, &pt[1].X, &pt[1].Y, percent);
343 shorten_line_percent(pt[1].X, pt[1].Y, &pt[2].X, &pt[2].Y, percent);
344 shorten_line_percent(pt[2].X, pt[2].Y, &pt[3].X, &pt[3].Y, percent);
346 dx = pt[3].X - origx;
347 dy = pt[3].Y - origy;
349 diff = sqrt(dx * dx + dy * dy);
350 percent += 0.0005 * amt;
354 /* Draws bezier curves between given points, and if caps is true then draws an
355 * endcap at the end of the last line. FIXME: Startcaps not implemented. */
356 static void draw_polybezier(HDC hdc, GpPen *pen, GDIPCONST GpPointF * pt,
357 INT count, BOOL caps)
359 POINT *pti = GdipAlloc(count * sizeof(POINT));
360 GpPointF *ptf = GdipAlloc(4 * sizeof(GpPointF));
361 INT i;
363 memcpy(ptf, &pt[count-4], 4 * sizeof(GpPointF));
365 if(caps){
366 if(pen->endcap == LineCapArrowAnchor)
367 shorten_bezier_amt(ptf, pen->width);
369 draw_cap(hdc, pen->color, pen->endcap, pen->width, ptf[3].X,
370 ptf[3].Y, pt[count - 1].X, pt[count - 1].Y);
373 for(i = 0; i < count - 4; i ++){
374 pti[i].x = roundr(pt[i].X);
375 pti[i].y = roundr(pt[i].Y);
377 for(i = 0; i < 4; i ++){
378 pti[i].x = roundr(ptf[i].X);
379 pti[i].y = roundr(ptf[i].Y);
382 PolyBezier(hdc, pti, count);
383 GdipFree(pti);
384 GdipFree(ptf);
387 GpStatus WINGDIPAPI GdipCreateFromHDC(HDC hdc, GpGraphics **graphics)
389 if(hdc == NULL)
390 return OutOfMemory;
392 if(graphics == NULL)
393 return InvalidParameter;
395 *graphics = GdipAlloc(sizeof(GpGraphics));
396 if(!*graphics) return OutOfMemory;
398 (*graphics)->hdc = hdc;
399 (*graphics)->hwnd = NULL;
401 return Ok;
404 GpStatus WINGDIPAPI GdipCreateFromHWND(HWND hwnd, GpGraphics **graphics)
406 GpStatus ret;
408 if((ret = GdipCreateFromHDC(GetDC(hwnd), graphics)) != Ok)
409 return ret;
411 (*graphics)->hwnd = hwnd;
413 return Ok;
416 GpStatus WINGDIPAPI GdipDeleteGraphics(GpGraphics *graphics)
418 if(!graphics) return InvalidParameter;
419 if(graphics->hwnd)
420 ReleaseDC(graphics->hwnd, graphics->hdc);
422 HeapFree(GetProcessHeap(), 0, graphics);
424 return Ok;
427 GpStatus WINGDIPAPI GdipDrawArc(GpGraphics *graphics, GpPen *pen, REAL x,
428 REAL y, REAL width, REAL height, REAL startAngle, REAL sweepAngle)
430 HGDIOBJ old_pen;
431 REAL x_0, y_0, x_1, y_1, x_2, y_2;
433 if(!graphics || !pen)
434 return InvalidParameter;
436 old_pen = SelectObject(graphics->hdc, pen->gdipen);
438 x_0 = x + (width/2.0);
439 y_0 = y + (height/2.0);
441 deg2xy(startAngle+sweepAngle, x_0, y_0, &x_1, &y_1);
442 deg2xy(startAngle, x_0, y_0, &x_2, &y_2);
444 Arc(graphics->hdc, roundr(x), roundr(y), roundr(x+width), roundr(y+height),
445 roundr(x_1), roundr(y_1), roundr(x_2), roundr(y_2));
447 SelectObject(graphics->hdc, old_pen);
449 return Ok;
452 GpStatus WINGDIPAPI GdipDrawBezier(GpGraphics *graphics, GpPen *pen, REAL x1,
453 REAL y1, REAL x2, REAL y2, REAL x3, REAL y3, REAL x4, REAL y4)
455 INT save_state;
456 GpPointF pt[4];
458 if(!graphics || !pen)
459 return InvalidParameter;
461 pt[0].X = x1;
462 pt[0].Y = y1;
463 pt[1].X = x2;
464 pt[1].Y = y2;
465 pt[2].X = x3;
466 pt[2].Y = y3;
467 pt[3].X = x4;
468 pt[3].Y = y4;
470 save_state = SaveDC(graphics->hdc);
471 EndPath(graphics->hdc);
472 SelectObject(graphics->hdc, pen->gdipen);
474 draw_polybezier(graphics->hdc, pen, pt, 4, TRUE);
476 RestoreDC(graphics->hdc, save_state);
478 return Ok;
481 /* Approximates cardinal spline with Bezier curves. */
482 GpStatus WINGDIPAPI GdipDrawCurve2(GpGraphics *graphics, GpPen *pen,
483 GDIPCONST GpPointF *points, INT count, REAL tension)
485 HGDIOBJ old_pen;
487 /* PolyBezier expects count*3-2 points. */
488 int i, len_pt = count*3-2;
489 POINT pt[len_pt];
490 REAL x1, x2, y1, y2;
492 if(!graphics || !pen)
493 return InvalidParameter;
495 tension = tension * TENSION_CONST;
497 calc_curve_bezier_endp(points[0].X, points[0].Y, points[1].X, points[1].Y,
498 tension, &x1, &y1);
500 pt[0].x = roundr(points[0].X);
501 pt[0].y = roundr(points[0].Y);
502 pt[1].x = roundr(x1);
503 pt[1].y = roundr(y1);
505 for(i = 0; i < count-2; i++){
506 calc_curve_bezier(&(points[i]), tension, &x1, &y1, &x2, &y2);
508 pt[3*i+2].x = roundr(x1);
509 pt[3*i+2].y = roundr(y1);
510 pt[3*i+3].x = roundr(points[i+1].X);
511 pt[3*i+3].y = roundr(points[i+1].Y);
512 pt[3*i+4].x = roundr(x2);
513 pt[3*i+4].y = roundr(y2);
516 calc_curve_bezier_endp(points[count-1].X, points[count-1].Y,
517 points[count-2].X, points[count-2].Y, tension, &x1, &y1);
519 pt[len_pt-2].x = x1;
520 pt[len_pt-2].y = y1;
521 pt[len_pt-1].x = roundr(points[count-1].X);
522 pt[len_pt-1].y = roundr(points[count-1].Y);
524 old_pen = SelectObject(graphics->hdc, pen->gdipen);
526 PolyBezier(graphics->hdc, pt, len_pt);
528 SelectObject(graphics->hdc, old_pen);
530 return Ok;
533 GpStatus WINGDIPAPI GdipDrawLineI(GpGraphics *graphics, GpPen *pen, INT x1,
534 INT y1, INT x2, INT y2)
536 INT save_state;
537 GpPointF pt[2];
539 if(!pen || !graphics)
540 return InvalidParameter;
542 pt[0].X = (REAL)x1;
543 pt[0].Y = (REAL)y1;
544 pt[1].X = (REAL)x2;
545 pt[1].Y = (REAL)y2;
547 save_state = SaveDC(graphics->hdc);
548 EndPath(graphics->hdc);
549 SelectObject(graphics->hdc, pen->gdipen);
551 draw_polyline(graphics->hdc, pen, pt, 2, TRUE);
553 RestoreDC(graphics->hdc, save_state);
555 return Ok;
558 GpStatus WINGDIPAPI GdipDrawLines(GpGraphics *graphics, GpPen *pen, GDIPCONST
559 GpPointF *points, INT count)
561 HGDIOBJ old_obj;
562 INT i;
564 if(!pen || !graphics || (count < 2))
565 return InvalidParameter;
567 old_obj = SelectObject(graphics->hdc, pen->gdipen);
568 MoveToEx(graphics->hdc, roundr(points[0].X), roundr(points[0].Y), NULL);
570 for(i = 1; i < count; i++){
571 LineTo(graphics->hdc, roundr(points[i].X), roundr(points[i].Y));
574 SelectObject(graphics->hdc, old_obj);
576 return Ok;
579 GpStatus WINGDIPAPI GdipDrawPie(GpGraphics *graphics, GpPen *pen, REAL x,
580 REAL y, REAL width, REAL height, REAL startAngle, REAL sweepAngle)
582 if(!pen)
583 return InvalidParameter;
585 return draw_pie(graphics, GetStockObject(NULL_BRUSH), pen->gdipen, x, y,
586 width, height, startAngle, sweepAngle);
589 GpStatus WINGDIPAPI GdipDrawRectangleI(GpGraphics *graphics, GpPen *pen, INT x,
590 INT y, INT width, INT height)
592 LOGBRUSH lb;
593 HPEN hpen;
594 HGDIOBJ old_obj;
596 if(!pen || !graphics)
597 return InvalidParameter;
599 lb.lbStyle = BS_SOLID;
600 lb.lbColor = pen->color;
601 lb.lbHatch = 0;
603 hpen = ExtCreatePen(PS_GEOMETRIC | PS_ENDCAP_SQUARE, (INT) pen->width,
604 &lb, 0, NULL);
606 old_obj = SelectObject(graphics->hdc, hpen);
608 /* assume pen aligment centered */
609 MoveToEx(graphics->hdc, x, y, NULL);
610 LineTo(graphics->hdc, x+width, y);
611 LineTo(graphics->hdc, x+width, y+height);
612 LineTo(graphics->hdc, x, y+height);
613 LineTo(graphics->hdc, x, y);
615 SelectObject(graphics->hdc, old_obj);
616 DeleteObject(hpen);
618 return Ok;
621 GpStatus WINGDIPAPI GdipFillPie(GpGraphics *graphics, GpBrush *brush, REAL x,
622 REAL y, REAL width, REAL height, REAL startAngle, REAL sweepAngle)
624 if(!brush)
625 return InvalidParameter;
627 return draw_pie(graphics, brush->gdibrush, GetStockObject(NULL_PEN), x, y,
628 width, height, startAngle, sweepAngle);