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
)
64 REAL x_0
, y_0
, x_1
, y_1
, x_2
, y_2
;
67 return InvalidParameter
;
69 save_state
= SaveDC(graphics
->hdc
);
70 EndPath(graphics
->hdc
);
71 SelectObject(graphics
->hdc
, gdipen
);
72 SelectObject(graphics
->hdc
, gdibrush
);
74 x_0
= x
+ (width
/2.0);
75 y_0
= y
+ (height
/2.0);
77 deg2xy(startAngle
+sweepAngle
, x_0
, y_0
, &x_1
, &y_1
);
78 deg2xy(startAngle
, x_0
, y_0
, &x_2
, &y_2
);
80 Pie(graphics
->hdc
, roundr(x
), roundr(y
), roundr(x
+width
), roundr(y
+height
),
81 roundr(x_1
), roundr(y_1
), roundr(x_2
), roundr(y_2
));
83 RestoreDC(graphics
->hdc
, save_state
);
88 /* GdipDrawCurve helper function.
89 * Calculates Bezier points from cardinal spline points. */
90 static void calc_curve_bezier(CONST GpPointF
*pts
, REAL tension
, REAL
*x1
,
91 REAL
*y1
, REAL
*x2
, REAL
*y2
)
95 /* calculate tangent */
96 xdiff
= pts
[2].X
- pts
[0].X
;
97 ydiff
= pts
[2].Y
- pts
[0].Y
;
99 /* apply tangent to get control points */
100 *x1
= pts
[1].X
- tension
* xdiff
;
101 *y1
= pts
[1].Y
- tension
* ydiff
;
102 *x2
= pts
[1].X
+ tension
* xdiff
;
103 *y2
= pts
[1].Y
+ tension
* ydiff
;
106 /* GdipDrawCurve helper function.
107 * Calculates Bezier points from cardinal spline endpoints. */
108 static void calc_curve_bezier_endp(REAL xend
, REAL yend
, REAL xadj
, REAL yadj
,
109 REAL tension
, REAL
*x
, REAL
*y
)
111 /* tangent at endpoints is the line from the endpoint to the adjacent point */
112 *x
= roundr(tension
* (xadj
- xend
) + xend
);
113 *y
= roundr(tension
* (yadj
- yend
) + yend
);
116 /* Draws the linecap the specified color and size on the hdc. The linecap is in
117 * direction of the line from x1, y1 to x2, y2 and is anchored on x2, y2. */
118 static void draw_cap(HDC hdc
, COLORREF color
, GpLineCap cap
, REAL size
,
119 REAL x1
, REAL y1
, REAL x2
, REAL y2
)
121 HGDIOBJ oldbrush
, oldpen
;
125 REAL theta
, dsmall
, dbig
, dx
, dy
, invert
;
128 theta
= atan((y2
- y1
) / (x2
- x1
));
130 theta
= M_PI_2
* (y2
> y1
? 1.0 : -1.0);
135 invert
= ((x2
- x1
) >= 0.0 ? 1.0 : -1.0);
136 brush
= CreateSolidBrush(color
);
137 pen
= CreatePen(PS_SOLID
, 1, color
);
138 oldbrush
= SelectObject(hdc
, brush
);
139 oldpen
= SelectObject(hdc
, pen
);
145 case LineCapSquareAnchor
:
146 case LineCapDiamondAnchor
:
147 size
= size
* (cap
& LineCapNoAnchor
? ANCHOR_WIDTH
: 1.0) / 2.0;
148 if(cap
== LineCapDiamondAnchor
){
149 dsmall
= cos(theta
+ M_PI_2
) * size
;
150 dbig
= sin(theta
+ M_PI_2
) * size
;
153 dsmall
= cos(theta
+ M_PI_4
) * size
;
154 dbig
= sin(theta
+ M_PI_4
) * size
;
157 /* calculating the latter points from the earlier points makes them
158 * look a little better because of rounding issues */
159 pt
[0].x
= roundr(x2
- dsmall
);
160 pt
[1].x
= roundr(((REAL
)pt
[0].x
) + dbig
+ dsmall
);
162 pt
[0].y
= roundr(y2
- dbig
);
163 pt
[3].y
= roundr(((REAL
)pt
[0].y
) + dsmall
+ dbig
);
165 pt
[1].y
= roundr(y2
- dsmall
);
166 pt
[2].y
= roundr(dbig
+ dsmall
+ ((REAL
)pt
[1].y
));
168 pt
[3].x
= roundr(x2
- dbig
);
169 pt
[2].x
= roundr(((REAL
)pt
[3].x
) + dsmall
+ dbig
);
174 case LineCapArrowAnchor
:
175 size
= size
* 4.0 / sqrt(3.0);
177 dx
= cos(M_PI
/ 6.0 + theta
) * size
* invert
;
178 dy
= sin(M_PI
/ 6.0 + theta
) * size
* invert
;
180 pt
[0].x
= roundr(x2
- dx
);
181 pt
[0].y
= roundr(y2
- dy
);
183 dx
= cos(- M_PI
/ 6.0 + theta
) * size
* invert
;
184 dy
= sin(- M_PI
/ 6.0 + theta
) * size
* invert
;
186 pt
[1].x
= roundr(x2
- dx
);
187 pt
[1].y
= roundr(y2
- dy
);
189 pt
[2].x
= roundr(x2
);
190 pt
[2].y
= roundr(y2
);
195 case LineCapRoundAnchor
:
196 dx
= dy
= ANCHOR_WIDTH
* size
/ 2.0;
198 x2
= (REAL
) roundr(x2
- dx
);
199 y2
= (REAL
) roundr(y2
- dy
);
201 Ellipse(hdc
, (INT
) x2
, (INT
) y2
, roundr(x2
+ 2.0 * dx
),
202 roundr(y2
+ 2.0 * dy
));
204 case LineCapTriangle
:
206 dx
= cos(M_PI_2
+ theta
) * size
;
207 dy
= sin(M_PI_2
+ theta
) * size
;
209 /* Using roundr here can make the triangle float off the end of the
211 pt
[0].x
= ((x2
- x1
) >= 0 ? floor(x2
- dx
) : ceil(x2
- dx
));
212 pt
[0].y
= ((y2
- y1
) >= 0 ? floor(y2
- dy
) : ceil(y2
- dy
));
213 pt
[1].x
= roundr(pt
[0].x
+ 2.0 * dx
);
214 pt
[1].y
= roundr(pt
[0].y
+ 2.0 * dy
);
216 dx
= cos(theta
) * size
* invert
;
217 dy
= sin(theta
) * size
* invert
;
219 pt
[2].x
= roundr(x2
+ dx
);
220 pt
[2].y
= roundr(y2
+ dy
);
226 dx
= -cos(M_PI_2
+ theta
) * size
* invert
;
227 dy
= -sin(M_PI_2
+ theta
) * size
* invert
;
229 pt
[0].x
= ((x2
- x1
) >= 0 ? floor(x2
- dx
) : ceil(x2
- dx
));
230 pt
[0].y
= ((y2
- y1
) >= 0 ? floor(y2
- dy
) : ceil(y2
- dy
));
231 pt
[1].x
= roundr(pt
[0].x
+ 2.0 * dx
);
232 pt
[1].y
= roundr(pt
[0].y
+ 2.0 * dy
);
234 dx
= dy
= size
/ 2.0;
236 x2
= (REAL
) roundr(x2
- dx
);
237 y2
= (REAL
) roundr(y2
- dy
);
239 Pie(hdc
, (INT
) x2
, (INT
) y2
, roundr(x2
+ 2.0 * dx
),
240 roundr(y2
+ 2.0 * dy
), pt
[0].x
, pt
[0].y
, pt
[1].x
, pt
[1].y
);
243 FIXME("line cap not implemented\n");
248 SelectObject(hdc
, oldbrush
);
249 SelectObject(hdc
, oldpen
);
254 /* Shortens the line by the given percent by changing x2, y2.
255 * If percent is > 1.0 then the line will change direction. */
256 static void shorten_line_percent(REAL x1
, REAL y1
, REAL
*x2
, REAL
*y2
, REAL percent
)
258 REAL dist
, theta
, dx
, dy
;
260 if((y1
== *y2
) && (x1
== *x2
))
263 dist
= sqrt((*x2
- x1
) * (*x2
- x1
) + (*y2
- y1
) * (*y2
- y1
)) * percent
;
264 theta
= (*x2
== x1
? M_PI_2
: atan((*y2
- y1
) / (*x2
- x1
)));
265 dx
= cos(theta
) * dist
;
266 dy
= sin(theta
) * dist
;
268 *x2
= *x2
+ fabs(dx
) * (*x2
> x1
? -1.0 : 1.0);
269 *y2
= *y2
+ fabs(dy
) * (*y2
> y1
? -1.0 : 1.0);
272 /* Shortens the line by the given amount by changing x2, y2.
273 * If the amount is greater than the distance, the line will become length 0. */
274 static void shorten_line_amt(REAL x1
, REAL y1
, REAL
*x2
, REAL
*y2
, REAL amt
)
276 REAL dx
, dy
, percent
;
280 if(dx
== 0 && dy
== 0)
283 percent
= amt
/ sqrt(dx
* dx
+ dy
* dy
);
290 shorten_line_percent(x1
, y1
, x2
, y2
, percent
);
293 /* Draws lines between the given points, and if caps is true then draws an endcap
294 * at the end of the last line. FIXME: Startcaps not implemented. */
295 static void draw_polyline(HDC hdc
, GpPen
*pen
, GDIPCONST GpPointF
* pt
,
296 INT count
, BOOL caps
)
298 POINT
*pti
= GdipAlloc(count
* sizeof(POINT
));
299 REAL x
= pt
[count
- 1].X
, y
= pt
[count
- 1].Y
;
303 if(pen
->endcap
== LineCapArrowAnchor
)
304 shorten_line_amt(pt
[count
-2].X
, pt
[count
-2].Y
, &x
, &y
, pen
->width
);
306 draw_cap(hdc
, pen
->color
, pen
->endcap
, pen
->width
, pt
[count
-2].X
,
307 pt
[count
-2].Y
, pt
[count
- 1].X
, pt
[count
- 1].Y
);
310 for(i
= 0; i
< count
- 1; i
++){
311 pti
[i
].x
= roundr(pt
[i
].X
);
312 pti
[i
].y
= roundr(pt
[i
].Y
);
315 pti
[i
].x
= roundr(x
);
316 pti
[i
].y
= roundr(y
);
318 Polyline(hdc
, pti
, count
);
322 /* Conducts a linear search to find the bezier points that will back off
323 * the endpoint of the curve by a distance of amt. Linear search works
324 * better than binary in this case because there are multiple solutions,
325 * and binary searches often find a bad one. I don't think this is what
326 * Windows does but short of rendering the bezier without GDI's help it's
327 * the best we can do. */
328 static void shorten_bezier_amt(GpPointF
* pt
, REAL amt
)
331 REAL percent
= 0.00, dx
, dy
, origx
= pt
[3].X
, origy
= pt
[3].Y
, diff
= -1.0;
334 memcpy(origpt
, pt
, sizeof(GpPointF
) * 4);
336 for(i
= 0; (i
< MAX_ITERS
) && (diff
< amt
); i
++){
337 /* reset bezier points to original values */
338 memcpy(pt
, origpt
, sizeof(GpPointF
) * 4);
339 /* Perform magic on bezier points. Order is important here.*/
340 shorten_line_percent(pt
[2].X
, pt
[2].Y
, &pt
[3].X
, &pt
[3].Y
, percent
);
341 shorten_line_percent(pt
[1].X
, pt
[1].Y
, &pt
[2].X
, &pt
[2].Y
, percent
);
342 shorten_line_percent(pt
[2].X
, pt
[2].Y
, &pt
[3].X
, &pt
[3].Y
, percent
);
343 shorten_line_percent(pt
[0].X
, pt
[0].Y
, &pt
[1].X
, &pt
[1].Y
, percent
);
344 shorten_line_percent(pt
[1].X
, pt
[1].Y
, &pt
[2].X
, &pt
[2].Y
, percent
);
345 shorten_line_percent(pt
[2].X
, pt
[2].Y
, &pt
[3].X
, &pt
[3].Y
, percent
);
347 dx
= pt
[3].X
- origx
;
348 dy
= pt
[3].Y
- origy
;
350 diff
= sqrt(dx
* dx
+ dy
* dy
);
351 percent
+= 0.0005 * amt
;
355 /* Draws bezier curves between given points, and if caps is true then draws an
356 * endcap at the end of the last line. FIXME: Startcaps not implemented. */
357 static void draw_polybezier(HDC hdc
, GpPen
*pen
, GDIPCONST GpPointF
* pt
,
358 INT count
, BOOL caps
)
360 POINT
*pti
= GdipAlloc(count
* sizeof(POINT
));
361 GpPointF
*ptf
= GdipAlloc(4 * sizeof(GpPointF
));
364 memcpy(ptf
, &pt
[count
-4], 4 * sizeof(GpPointF
));
367 if(pen
->endcap
== LineCapArrowAnchor
)
368 shorten_bezier_amt(ptf
, pen
->width
);
370 draw_cap(hdc
, pen
->color
, pen
->endcap
, pen
->width
, ptf
[3].X
,
371 ptf
[3].Y
, pt
[count
- 1].X
, pt
[count
- 1].Y
);
374 for(i
= 0; i
< count
- 4; i
++){
375 pti
[i
].x
= roundr(pt
[i
].X
);
376 pti
[i
].y
= roundr(pt
[i
].Y
);
378 for(i
= 0; i
< 4; i
++){
379 pti
[i
].x
= roundr(ptf
[i
].X
);
380 pti
[i
].y
= roundr(ptf
[i
].Y
);
383 PolyBezier(hdc
, pti
, count
);
388 GpStatus WINGDIPAPI
GdipCreateFromHDC(HDC hdc
, GpGraphics
**graphics
)
394 return InvalidParameter
;
396 *graphics
= GdipAlloc(sizeof(GpGraphics
));
397 if(!*graphics
) return OutOfMemory
;
399 (*graphics
)->hdc
= hdc
;
400 (*graphics
)->hwnd
= NULL
;
405 GpStatus WINGDIPAPI
GdipCreateFromHWND(HWND hwnd
, GpGraphics
**graphics
)
409 if((ret
= GdipCreateFromHDC(GetDC(hwnd
), graphics
)) != Ok
)
412 (*graphics
)->hwnd
= hwnd
;
417 GpStatus WINGDIPAPI
GdipDeleteGraphics(GpGraphics
*graphics
)
419 if(!graphics
) return InvalidParameter
;
421 ReleaseDC(graphics
->hwnd
, graphics
->hdc
);
423 HeapFree(GetProcessHeap(), 0, graphics
);
428 GpStatus WINGDIPAPI
GdipDrawArc(GpGraphics
*graphics
, GpPen
*pen
, REAL x
,
429 REAL y
, REAL width
, REAL height
, REAL startAngle
, REAL sweepAngle
)
432 REAL x_0
, y_0
, x_1
, y_1
, x_2
, y_2
;
434 if(!graphics
|| !pen
)
435 return InvalidParameter
;
437 old_pen
= SelectObject(graphics
->hdc
, pen
->gdipen
);
439 x_0
= x
+ (width
/2.0);
440 y_0
= y
+ (height
/2.0);
442 deg2xy(startAngle
+sweepAngle
, x_0
, y_0
, &x_1
, &y_1
);
443 deg2xy(startAngle
, x_0
, y_0
, &x_2
, &y_2
);
445 Arc(graphics
->hdc
, roundr(x
), roundr(y
), roundr(x
+width
), roundr(y
+height
),
446 roundr(x_1
), roundr(y_1
), roundr(x_2
), roundr(y_2
));
448 SelectObject(graphics
->hdc
, old_pen
);
453 GpStatus WINGDIPAPI
GdipDrawBezier(GpGraphics
*graphics
, GpPen
*pen
, REAL x1
,
454 REAL y1
, REAL x2
, REAL y2
, REAL x3
, REAL y3
, REAL x4
, REAL y4
)
459 if(!graphics
|| !pen
)
460 return InvalidParameter
;
471 save_state
= SaveDC(graphics
->hdc
);
472 EndPath(graphics
->hdc
);
473 SelectObject(graphics
->hdc
, pen
->gdipen
);
475 draw_polybezier(graphics
->hdc
, pen
, pt
, 4, TRUE
);
477 RestoreDC(graphics
->hdc
, save_state
);
482 /* Approximates cardinal spline with Bezier curves. */
483 GpStatus WINGDIPAPI
GdipDrawCurve2(GpGraphics
*graphics
, GpPen
*pen
,
484 GDIPCONST GpPointF
*points
, INT count
, REAL tension
)
488 /* PolyBezier expects count*3-2 points. */
489 int i
, len_pt
= count
*3-2;
493 if(!graphics
|| !pen
)
494 return InvalidParameter
;
496 tension
= tension
* TENSION_CONST
;
498 calc_curve_bezier_endp(points
[0].X
, points
[0].Y
, points
[1].X
, points
[1].Y
,
501 pt
[0].x
= roundr(points
[0].X
);
502 pt
[0].y
= roundr(points
[0].Y
);
503 pt
[1].x
= roundr(x1
);
504 pt
[1].y
= roundr(y1
);
506 for(i
= 0; i
< count
-2; i
++){
507 calc_curve_bezier(&(points
[i
]), tension
, &x1
, &y1
, &x2
, &y2
);
509 pt
[3*i
+2].x
= roundr(x1
);
510 pt
[3*i
+2].y
= roundr(y1
);
511 pt
[3*i
+3].x
= roundr(points
[i
+1].X
);
512 pt
[3*i
+3].y
= roundr(points
[i
+1].Y
);
513 pt
[3*i
+4].x
= roundr(x2
);
514 pt
[3*i
+4].y
= roundr(y2
);
517 calc_curve_bezier_endp(points
[count
-1].X
, points
[count
-1].Y
,
518 points
[count
-2].X
, points
[count
-2].Y
, tension
, &x1
, &y1
);
522 pt
[len_pt
-1].x
= roundr(points
[count
-1].X
);
523 pt
[len_pt
-1].y
= roundr(points
[count
-1].Y
);
525 old_pen
= SelectObject(graphics
->hdc
, pen
->gdipen
);
527 PolyBezier(graphics
->hdc
, pt
, len_pt
);
529 SelectObject(graphics
->hdc
, old_pen
);
534 GpStatus WINGDIPAPI
GdipDrawLineI(GpGraphics
*graphics
, GpPen
*pen
, INT x1
,
535 INT y1
, INT x2
, INT y2
)
540 if(!pen
|| !graphics
)
541 return InvalidParameter
;
548 save_state
= SaveDC(graphics
->hdc
);
549 EndPath(graphics
->hdc
);
550 SelectObject(graphics
->hdc
, pen
->gdipen
);
552 draw_polyline(graphics
->hdc
, pen
, pt
, 2, TRUE
);
554 RestoreDC(graphics
->hdc
, save_state
);
559 GpStatus WINGDIPAPI
GdipDrawLines(GpGraphics
*graphics
, GpPen
*pen
, GDIPCONST
560 GpPointF
*points
, INT count
)
565 if(!pen
|| !graphics
|| (count
< 2))
566 return InvalidParameter
;
568 old_obj
= SelectObject(graphics
->hdc
, pen
->gdipen
);
569 MoveToEx(graphics
->hdc
, roundr(points
[0].X
), roundr(points
[0].Y
), NULL
);
571 for(i
= 1; i
< count
; i
++){
572 LineTo(graphics
->hdc
, roundr(points
[i
].X
), roundr(points
[i
].Y
));
575 SelectObject(graphics
->hdc
, old_obj
);
580 GpStatus WINGDIPAPI
GdipDrawPie(GpGraphics
*graphics
, GpPen
*pen
, REAL x
,
581 REAL y
, REAL width
, REAL height
, REAL startAngle
, REAL sweepAngle
)
584 return InvalidParameter
;
586 return draw_pie(graphics
, GetStockObject(NULL_BRUSH
), pen
->gdipen
, x
, y
,
587 width
, height
, startAngle
, sweepAngle
);
590 GpStatus WINGDIPAPI
GdipDrawRectangleI(GpGraphics
*graphics
, GpPen
*pen
, INT x
,
591 INT y
, INT width
, INT height
)
597 if(!pen
|| !graphics
)
598 return InvalidParameter
;
600 lb
.lbStyle
= BS_SOLID
;
601 lb
.lbColor
= pen
->color
;
604 hpen
= ExtCreatePen(PS_GEOMETRIC
| PS_ENDCAP_SQUARE
, (INT
) pen
->width
,
607 old_obj
= SelectObject(graphics
->hdc
, hpen
);
609 /* assume pen aligment centered */
610 MoveToEx(graphics
->hdc
, x
, y
, NULL
);
611 LineTo(graphics
->hdc
, x
+width
, y
);
612 LineTo(graphics
->hdc
, x
+width
, y
+height
);
613 LineTo(graphics
->hdc
, x
, y
+height
);
614 LineTo(graphics
->hdc
, x
, y
);
616 SelectObject(graphics
->hdc
, old_obj
);
622 GpStatus WINGDIPAPI
GdipFillPie(GpGraphics
*graphics
, GpBrush
*brush
, REAL x
,
623 REAL y
, REAL width
, REAL height
, REAL startAngle
, REAL sweepAngle
)
626 return InvalidParameter
;
628 return draw_pie(graphics
, brush
->gdibrush
, GetStockObject(NULL_PEN
), x
, y
,
629 width
, height
, startAngle
, sweepAngle
);