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 /* Converts angle (in degrees) to x/y coordinates */
38 static void deg2xy(REAL angle, REAL x_0, REAL y_0, REAL *x, REAL *y)
40 REAL radAngle, hypotenuse;
42 radAngle = deg2rad(angle);
43 hypotenuse = 50.0; /* arbitrary */
45 *x = x_0 + cos(radAngle) * hypotenuse;
46 *y = y_0 + sin(radAngle) * hypotenuse;
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)
54 REAL x_0, y_0, x_1, y_1, x_2, y_2;
57 return InvalidParameter;
59 save_state = SaveDC(graphics->hdc);
60 EndPath(graphics->hdc);
61 SelectObject(graphics->hdc, gdipen);
62 SelectObject(graphics->hdc, gdibrush);
64 x_0 = x + (width/2.0);
65 y_0 = y + (height/2.0);
67 deg2xy(startAngle+sweepAngle, x_0, y_0, &x_1, &y_1);
68 deg2xy(startAngle, x_0, y_0, &x_2, &y_2);
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));
73 RestoreDC(graphics->hdc, save_state);
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)
85 /* calculate tangent */
86 xdiff = pts[2].X - pts[0].X;
87 ydiff = pts[2].Y - pts[0].Y;
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;
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)
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);
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)
111 HGDIOBJ oldbrush, oldpen;
115 REAL theta, dsmall, dbig, dx, dy, invert;
118 theta = atan((y2 - y1) / (x2 - x1));
120 theta = M_PI_2 * (y2 > y1 ? 1.0 : -1.0);
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);
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;
143 dsmall = cos(theta + M_PI_4) * size;
144 dbig = sin(theta + M_PI_4) * size;
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);
152 pt[0].y = roundr(y2 - dbig);
153 pt[3].y = roundr(((REAL)pt[0].y) + dsmall + dbig);
155 pt[1].y = roundr(y2 - dsmall);
156 pt[2].y = roundr(dbig + dsmall + ((REAL)pt[1].y));
158 pt[3].x = roundr(x2 - dbig);
159 pt[2].x = roundr(((REAL)pt[3].x) + dsmall + dbig);
164 case LineCapArrowAnchor:
165 size = size * 4.0 / sqrt(3.0);
167 dx = cos(M_PI / 6.0 + theta) * size * invert;
168 dy = sin(M_PI / 6.0 + theta) * size * invert;
170 pt[0].x = roundr(x2 - dx);
171 pt[0].y = roundr(y2 - dy);
173 dx = cos(- M_PI / 6.0 + theta) * size * invert;
174 dy = sin(- M_PI / 6.0 + theta) * size * invert;
176 pt[1].x = roundr(x2 - dx);
177 pt[1].y = roundr(y2 - dy);
179 pt[2].x = roundr(x2);
180 pt[2].y = roundr(y2);
185 case LineCapRoundAnchor:
186 dx = dy = ANCHOR_WIDTH * size / 2.0;
188 x2 = (REAL) roundr(x2 - dx);
189 y2 = (REAL) roundr(y2 - dy);
191 Ellipse(hdc, (INT) x2, (INT) y2, roundr(x2 + 2.0 * dx),
192 roundr(y2 + 2.0 * dy));
194 case LineCapTriangle:
196 dx = cos(M_PI_2 + theta) * size;
197 dy = sin(M_PI_2 + theta) * size;
199 /* Using roundr here can make the triangle float off the end of the
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);
206 dx = cos(theta) * size * invert;
207 dy = sin(theta) * size * invert;
209 pt[2].x = roundr(x2 + dx);
210 pt[2].y = roundr(y2 + dy);
216 dx = -cos(M_PI_2 + theta) * size * invert;
217 dy = -sin(M_PI_2 + theta) * size * invert;
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);
224 dx = dy = size / 2.0;
226 x2 = (REAL) roundr(x2 - dx);
227 y2 = (REAL) roundr(y2 - dy);
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);
233 FIXME("line cap not implemented\n");
238 SelectObject(hdc, oldbrush);
239 SelectObject(hdc, oldpen);
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)
248 REAL dist, theta, dx, dy;
250 if((y1 == *y2) && (x1 == *x2))
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;
258 *x2 = *x2 + fabs(dx) * (*x2 > x1 ? -1.0 : 1.0);
259 *y2 = *y2 + fabs(dy) * (*y2 > y1 ? -1.0 : 1.0);
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)
266 REAL dx, dy, percent;
270 if(dx == 0 && dy == 0)
273 percent = amt / sqrt(dx * dx + dy * dy);
280 shorten_line_percent(x1, y1, x2, y2, percent);
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)
288 POINT *pti = GdipAlloc(count * sizeof(POINT));
289 REAL x = pt[count - 1].X, y = pt[count - 1].Y;
293 if(pen->endcap == LineCapArrowAnchor)
294 shorten_line_amt(pt[count-2].X, pt[count-2].Y, &x, &y, pen->width);
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);
300 for(i = 0; i < count - 1; i ++){
301 pti[i].x = roundr(pt[i].X);
302 pti[i].y = roundr(pt[i].Y);
305 pti[i].x = roundr(x);
306 pti[i].y = roundr(y);
308 Polyline(hdc, pti, count);
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)
321 REAL percent = 0.00, dx, dy, origx = pt[3].X, origy = pt[3].Y, diff = -1.0;
324 memcpy(origpt, pt, sizeof(GpPointF) * 4);
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);
337 dx = pt[3].X - origx;
338 dy = pt[3].Y - origy;
340 diff = sqrt(dx * dx + dy * dy);
341 percent += 0.0005 * amt;
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)
350 POINT *pti = GdipAlloc(count * sizeof(POINT));
351 GpPointF *ptf = GdipAlloc(4 * sizeof(GpPointF));
354 memcpy(ptf, &pt[count-4], 4 * sizeof(GpPointF));
357 if(pen->endcap == LineCapArrowAnchor)
358 shorten_bezier_amt(ptf, pen->width);
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);
364 for(i = 0; i < count - 4; i ++){
365 pti[i].x = roundr(pt[i].X);
366 pti[i].y = roundr(pt[i].Y);
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);
373 PolyBezier(hdc, pti, count);
378 GpStatus WINGDIPAPI GdipCreateFromHDC(HDC hdc, GpGraphics **graphics)
384 return InvalidParameter;
386 *graphics = GdipAlloc(sizeof(GpGraphics));
387 if(!*graphics) return OutOfMemory;
389 (*graphics)->hdc = hdc;
390 (*graphics)->hwnd = NULL;
395 GpStatus WINGDIPAPI GdipCreateFromHWND(HWND hwnd, GpGraphics **graphics)
399 if((ret = GdipCreateFromHDC(GetDC(hwnd), graphics)) != Ok)
402 (*graphics)->hwnd = hwnd;
407 GpStatus WINGDIPAPI GdipDeleteGraphics(GpGraphics *graphics)
409 if(!graphics) return InvalidParameter;
411 ReleaseDC(graphics->hwnd, graphics->hdc);
413 HeapFree(GetProcessHeap(), 0, graphics);
418 GpStatus WINGDIPAPI GdipDrawArc(GpGraphics *graphics, GpPen *pen, REAL x,
419 REAL y, REAL width, REAL height, REAL startAngle, REAL sweepAngle)
421 INT save_state, num_pts;
422 GpPointF points[MAX_ARC_PTS];
424 if(!graphics || !pen)
425 return InvalidParameter;
427 num_pts = arc2polybezier(points, x, y, width, height, startAngle, sweepAngle);
429 save_state = SaveDC(graphics->hdc);
430 EndPath(graphics->hdc);
431 SelectObject(graphics->hdc, pen->gdipen);
433 draw_polybezier(graphics->hdc, pen, points, num_pts, TRUE);
435 RestoreDC(graphics->hdc, save_state);
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)
446 if(!graphics || !pen)
447 return InvalidParameter;
458 save_state = SaveDC(graphics->hdc);
459 EndPath(graphics->hdc);
460 SelectObject(graphics->hdc, pen->gdipen);
462 draw_polybezier(graphics->hdc, pen, pt, 4, TRUE);
464 RestoreDC(graphics->hdc, save_state);
469 /* Approximates cardinal spline with Bezier curves. */
470 GpStatus WINGDIPAPI GdipDrawCurve2(GpGraphics *graphics, GpPen *pen,
471 GDIPCONST GpPointF *points, INT count, REAL tension)
473 /* PolyBezier expects count*3-2 points. */
474 INT i, len_pt = count*3-2, save_state;
478 if(!graphics || !pen)
479 return InvalidParameter;
481 pt = GdipAlloc(len_pt * sizeof(GpPointF));
482 tension = tension * TENSION_CONST;
484 calc_curve_bezier_endp(points[0].X, points[0].Y, points[1].X, points[1].Y,
487 pt[0].X = points[0].X;
488 pt[0].Y = points[0].Y;
492 for(i = 0; i < count-2; i++){
493 calc_curve_bezier(&(points[i]), tension, &x1, &y1, &x2, &y2);
497 pt[3*i+3].X = points[i+1].X;
498 pt[3*i+3].Y = points[i+1].Y;
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);
508 pt[len_pt-1].X = points[count-1].X;
509 pt[len_pt-1].Y = points[count-1].Y;
511 save_state = SaveDC(graphics->hdc);
512 EndPath(graphics->hdc);
513 SelectObject(graphics->hdc, pen->gdipen);
515 draw_polybezier(graphics->hdc, pen, pt, len_pt, TRUE);
518 RestoreDC(graphics->hdc, save_state);
523 GpStatus WINGDIPAPI GdipDrawLineI(GpGraphics *graphics, GpPen *pen, INT x1,
524 INT y1, INT x2, INT y2)
529 if(!pen || !graphics)
530 return InvalidParameter;
537 save_state = SaveDC(graphics->hdc);
538 EndPath(graphics->hdc);
539 SelectObject(graphics->hdc, pen->gdipen);
541 draw_polyline(graphics->hdc, pen, pt, 2, TRUE);
543 RestoreDC(graphics->hdc, save_state);
548 GpStatus WINGDIPAPI GdipDrawLines(GpGraphics *graphics, GpPen *pen, GDIPCONST
549 GpPointF *points, INT count)
554 if(!pen || !graphics || (count < 2))
555 return InvalidParameter;
557 old_obj = SelectObject(graphics->hdc, pen->gdipen);
558 MoveToEx(graphics->hdc, roundr(points[0].X), roundr(points[0].Y), NULL);
560 for(i = 1; i < count; i++){
561 LineTo(graphics->hdc, roundr(points[i].X), roundr(points[i].Y));
564 SelectObject(graphics->hdc, old_obj);
569 GpStatus WINGDIPAPI GdipDrawPie(GpGraphics *graphics, GpPen *pen, REAL x,
570 REAL y, REAL width, REAL height, REAL startAngle, REAL sweepAngle)
573 return InvalidParameter;
575 return draw_pie(graphics, GetStockObject(NULL_BRUSH), pen->gdipen, x, y,
576 width, height, startAngle, sweepAngle);
579 GpStatus WINGDIPAPI GdipDrawRectangleI(GpGraphics *graphics, GpPen *pen, INT x,
580 INT y, INT width, INT height)
584 if(!pen || !graphics)
585 return InvalidParameter;
587 save_state = SaveDC(graphics->hdc);
588 EndPath(graphics->hdc);
589 SelectObject(graphics->hdc, pen->gdipen);
590 SelectObject(graphics->hdc, GetStockObject(NULL_BRUSH));
592 Rectangle(graphics->hdc, x, y, x + width, y + height);
594 RestoreDC(graphics->hdc, save_state);
599 GpStatus WINGDIPAPI GdipFillPie(GpGraphics *graphics, GpBrush *brush, REAL x,
600 REAL y, REAL width, REAL height, REAL startAngle, REAL sweepAngle)
603 return InvalidParameter;
605 return draw_pie(graphics, brush->gdibrush, GetStockObject(NULL_PEN), x, y,
606 width, height, startAngle, sweepAngle);