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
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
;
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
);
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
)
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
;
124 REAL theta
, dsmall
, dbig
, dx
, dy
, invert
;
127 theta
= atan((y2
- y1
) / (x2
- x1
));
129 theta
= M_PI_2
* (y2
> y1
? 1.0 : -1.0);
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
);
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
;
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
);
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
);
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
));
203 case LineCapTriangle
:
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
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
);
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
);
242 FIXME("line cap not implemented\n");
247 SelectObject(hdc
, oldbrush
);
248 SelectObject(hdc
, oldpen
);
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
))
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
;
279 if(dx
== 0 && dy
== 0)
282 percent
= amt
/ sqrt(dx
* dx
+ dy
* dy
);
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
;
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
);
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
)
330 REAL percent
= 0.00, dx
, dy
, origx
= pt
[3].X
, origy
= pt
[3].Y
, diff
= -1.0;
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
));
363 memcpy(ptf
, &pt
[count
-4], 4 * sizeof(GpPointF
));
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
);
387 GpStatus WINGDIPAPI
GdipCreateFromHDC(HDC hdc
, GpGraphics
**graphics
)
393 return InvalidParameter
;
395 *graphics
= GdipAlloc(sizeof(GpGraphics
));
396 if(!*graphics
) return OutOfMemory
;
398 (*graphics
)->hdc
= hdc
;
399 (*graphics
)->hwnd
= NULL
;
404 GpStatus WINGDIPAPI
GdipCreateFromHWND(HWND hwnd
, GpGraphics
**graphics
)
408 if((ret
= GdipCreateFromHDC(GetDC(hwnd
), graphics
)) != Ok
)
411 (*graphics
)->hwnd
= hwnd
;
416 GpStatus WINGDIPAPI
GdipDeleteGraphics(GpGraphics
*graphics
)
418 if(!graphics
) return InvalidParameter
;
420 ReleaseDC(graphics
->hwnd
, graphics
->hdc
);
422 HeapFree(GetProcessHeap(), 0, graphics
);
427 GpStatus WINGDIPAPI
GdipDrawArc(GpGraphics
*graphics
, GpPen
*pen
, REAL x
,
428 REAL y
, REAL width
, REAL height
, REAL startAngle
, REAL sweepAngle
)
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
);
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
)
458 if(!graphics
|| !pen
)
459 return InvalidParameter
;
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
);
481 /* Approximates cardinal spline with Bezier curves. */
482 GpStatus WINGDIPAPI
GdipDrawCurve2(GpGraphics
*graphics
, GpPen
*pen
,
483 GDIPCONST GpPointF
*points
, INT count
, REAL tension
)
487 /* PolyBezier expects count*3-2 points. */
488 int i
, len_pt
= count
*3-2;
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
,
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
);
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
);
533 GpStatus WINGDIPAPI
GdipDrawLineI(GpGraphics
*graphics
, GpPen
*pen
, INT x1
,
534 INT y1
, INT x2
, INT y2
)
539 if(!pen
|| !graphics
)
540 return InvalidParameter
;
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
);
558 GpStatus WINGDIPAPI
GdipDrawLines(GpGraphics
*graphics
, GpPen
*pen
, GDIPCONST
559 GpPointF
*points
, INT count
)
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
);
579 GpStatus WINGDIPAPI
GdipDrawPie(GpGraphics
*graphics
, GpPen
*pen
, REAL x
,
580 REAL y
, REAL width
, REAL height
, REAL startAngle
, REAL sweepAngle
)
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
)
596 if(!pen
|| !graphics
)
597 return InvalidParameter
;
599 lb
.lbStyle
= BS_SOLID
;
600 lb
.lbColor
= pen
->color
;
603 hpen
= ExtCreatePen(PS_GEOMETRIC
| PS_ENDCAP_SQUARE
, (INT
) pen
->width
,
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
);
621 GpStatus WINGDIPAPI
GdipFillPie(GpGraphics
*graphics
, GpBrush
*brush
, REAL x
,
622 REAL y
, REAL width
, REAL height
, REAL startAngle
, REAL sweepAngle
)
625 return InvalidParameter
;
627 return draw_pie(graphics
, brush
->gdibrush
, GetStockObject(NULL_PEN
), x
, y
,
628 width
, height
, startAngle
, sweepAngle
);