diff --git a/dlls/gdiplus/graphics.c b/dlls/gdiplus/graphics.c index 4765e3340b9..012cf2d1d78 100644 --- a/dlls/gdiplus/graphics.c +++ b/dlls/gdiplus/graphics.c @@ -27,8 +27,11 @@ #include "gdiplus_private.h" #include "wine/debug.h" -/* looks-right constant */ +WINE_DEFAULT_DEBUG_CHANNEL(gdiplus); + +/* looks-right constants */ #define TENSION_CONST (0.3) +#define ANCHOR_WIDTH (2.0) static inline INT roundr(REAL x) { @@ -108,6 +111,212 @@ static void calc_curve_bezier_endp(REAL xend, REAL yend, REAL xadj, REAL yadj, *y = roundr(tension * (yadj - yend) + yend); } +/* Draws the linecap the specified color and size on the hdc. The linecap is in + * direction of the line from x1, y1 to x2, y2 and is anchored on x2, y2. */ +static void draw_cap(HDC hdc, COLORREF color, GpLineCap cap, REAL size, + REAL x1, REAL y1, REAL x2, REAL y2) +{ + HGDIOBJ oldbrush, oldpen; + HBRUSH brush; + HPEN pen; + POINT pt[4]; + REAL theta, dsmall, dbig, dx, dy, invert; + + if(x2 != x1) + theta = atan((y2 - y1) / (x2 - x1)); + else if(y2 != y1){ + theta = M_PI_2 * (y2 > y1 ? 1.0 : -1.0); + } + else + return; + + invert = ((x2 - x1) >= 0.0 ? 1.0 : -1.0); + brush = CreateSolidBrush(color); + pen = CreatePen(PS_SOLID, 1, color); + oldbrush = SelectObject(hdc, brush); + oldpen = SelectObject(hdc, pen); + + switch(cap){ + case LineCapFlat: + break; + case LineCapSquare: + case LineCapSquareAnchor: + case LineCapDiamondAnchor: + size = size * (cap & LineCapNoAnchor ? ANCHOR_WIDTH : 1.0) / 2.0; + if(cap == LineCapDiamondAnchor){ + dsmall = cos(theta + M_PI_2) * size; + dbig = sin(theta + M_PI_2) * size; + } + else{ + dsmall = cos(theta + M_PI_4) * size; + dbig = sin(theta + M_PI_4) * size; + } + + /* calculating the latter points from the earlier points makes them + * look a little better because of rounding issues */ + pt[0].x = roundr(x2 - dsmall); + pt[1].x = roundr(((REAL)pt[0].x) + dbig + dsmall); + + pt[0].y = roundr(y2 - dbig); + pt[3].y = roundr(((REAL)pt[0].y) + dsmall + dbig); + + pt[1].y = roundr(y2 - dsmall); + pt[2].y = roundr(dbig + dsmall + ((REAL)pt[1].y)); + + pt[3].x = roundr(x2 - dbig); + pt[2].x = roundr(((REAL)pt[3].x) + dsmall + dbig); + + Polygon(hdc, pt, 4); + + break; + case LineCapArrowAnchor: + size = size * 4.0 / sqrt(3.0); + + dx = cos(M_PI / 6.0 + theta) * size * invert; + dy = sin(M_PI / 6.0 + theta) * size * invert; + + pt[0].x = roundr(x2 - dx); + pt[0].y = roundr(y2 - dy); + + dx = cos(- M_PI / 6.0 + theta) * size * invert; + dy = sin(- M_PI / 6.0 + theta) * size * invert; + + pt[1].x = roundr(x2 - dx); + pt[1].y = roundr(y2 - dy); + + pt[2].x = roundr(x2); + pt[2].y = roundr(y2); + + Polygon(hdc, pt, 3); + + break; + case LineCapRoundAnchor: + dx = dy = ANCHOR_WIDTH * size / 2.0; + + x2 = (REAL) roundr(x2 - dx); + y2 = (REAL) roundr(y2 - dy); + + Ellipse(hdc, (INT) x2, (INT) y2, roundr(x2 + 2.0 * dx), + roundr(y2 + 2.0 * dy)); + break; + case LineCapTriangle: + size = size / 2.0; + dx = cos(M_PI_2 + theta) * size; + dy = sin(M_PI_2 + theta) * size; + + /* Using roundr here can make the triangle float off the end of the + * line. */ + pt[0].x = ((x2 - x1) >= 0 ? floor(x2 - dx) : ceil(x2 - dx)); + pt[0].y = ((y2 - y1) >= 0 ? floor(y2 - dy) : ceil(y2 - dy)); + pt[1].x = roundr(pt[0].x + 2.0 * dx); + pt[1].y = roundr(pt[0].y + 2.0 * dy); + + dx = cos(theta) * size * invert; + dy = sin(theta) * size * invert; + + pt[2].x = roundr(x2 + dx); + pt[2].y = roundr(y2 + dy); + + Polygon(hdc, pt, 3); + + break; + case LineCapRound: + dx = -cos(M_PI_2 + theta) * size * invert; + dy = -sin(M_PI_2 + theta) * size * invert; + + pt[0].x = ((x2 - x1) >= 0 ? floor(x2 - dx) : ceil(x2 - dx)); + pt[0].y = ((y2 - y1) >= 0 ? floor(y2 - dy) : ceil(y2 - dy)); + pt[1].x = roundr(pt[0].x + 2.0 * dx); + pt[1].y = roundr(pt[0].y + 2.0 * dy); + + dx = dy = size / 2.0; + + x2 = (REAL) roundr(x2 - dx); + y2 = (REAL) roundr(y2 - dy); + + Pie(hdc, (INT) x2, (INT) y2, roundr(x2 + 2.0 * dx), + roundr(y2 + 2.0 * dy), pt[0].x, pt[0].y, pt[1].x, pt[1].y); + break; + case LineCapCustom: + FIXME("line cap not implemented\n"); + default: + break; + } + + SelectObject(hdc, oldbrush); + SelectObject(hdc, oldpen); + DeleteObject(brush); + DeleteObject(pen); +} + +/* Shortens the line by the given percent by changing x2, y2. + * If percent is > 1.0 then the line will change direction. */ +static void shorten_line_percent(REAL x1, REAL y1, REAL *x2, REAL *y2, REAL percent) +{ + REAL dist, theta, dx, dy; + + if((y1 == *y2) && (x1 == *x2)) + return; + + dist = sqrt((*x2 - x1) * (*x2 - x1) + (*y2 - y1) * (*y2 - y1)) * percent; + theta = (*x2 == x1 ? M_PI_2 : atan((*y2 - y1) / (*x2 - x1))); + dx = cos(theta) * dist; + dy = sin(theta) * dist; + + *x2 = *x2 + fabs(dx) * (*x2 > x1 ? -1.0 : 1.0); + *y2 = *y2 + fabs(dy) * (*y2 > y1 ? -1.0 : 1.0); +} + +/* Shortens the line by the given amount by changing x2, y2. + * If the amount is greater than the distance, the line will become length 0. */ +static void shorten_line_amt(REAL x1, REAL y1, REAL *x2, REAL *y2, REAL amt) +{ + REAL dx, dy, percent; + + dx = *x2 - x1; + dy = *y2 - y1; + if(dx == 0 && dy == 0) + return; + + percent = amt / sqrt(dx * dx + dy * dy); + if(percent >= 1.0){ + *x2 = x1; + *y2 = y1; + return; + } + + shorten_line_percent(x1, y1, x2, y2, percent); +} + +/* Draws lines between the given points, and if caps is true then draws an endcap + * at the end of the last line. FIXME: Startcaps not implemented. */ +static void draw_polyline(HDC hdc, GpPen *pen, GDIPCONST GpPointF * pt, + INT count, BOOL caps) +{ + POINT *pti = GdipAlloc(count * sizeof(POINT)); + REAL x = pt[count - 1].X, y = pt[count - 1].Y; + INT i; + + if(caps){ + if(pen->endcap == LineCapArrowAnchor) + shorten_line_amt(pt[count-2].X, pt[count-2].Y, &x, &y, pen->width); + + draw_cap(hdc, pen->color, pen->endcap, pen->width, pt[count-2].X, + pt[count-2].Y, pt[count - 1].X, pt[count - 1].Y); + } + + for(i = 0; i < count - 1; i ++){ + pti[i].x = roundr(pt[i].X); + pti[i].y = roundr(pt[i].Y); + } + + pti[i].x = roundr(x); + pti[i].y = roundr(y); + + Polyline(hdc, pti, count); + GdipFree(pti); +} + GpStatus WINGDIPAPI GdipCreateFromHDC(HDC hdc, GpGraphics **graphics) { if(hdc == NULL) @@ -248,16 +457,21 @@ GpStatus WINGDIPAPI GdipDrawLineI(GpGraphics *graphics, GpPen *pen, INT x1, INT y1, INT x2, INT y2) { INT save_state; + GpPointF pt[2]; if(!pen || !graphics) return InvalidParameter; + pt[0].X = (REAL)x1; + pt[0].Y = (REAL)y1; + pt[1].X = (REAL)x2; + pt[1].Y = (REAL)y2; + save_state = SaveDC(graphics->hdc); EndPath(graphics->hdc); SelectObject(graphics->hdc, pen->gdipen); - MoveToEx(graphics->hdc, x1, y1, NULL); - LineTo(graphics->hdc, x2, y2); + draw_polyline(graphics->hdc, pen, pt, 2, TRUE); RestoreDC(graphics->hdc, save_state); diff --git a/dlls/gdiplus/pen.c b/dlls/gdiplus/pen.c index 0fcbb694388..52515d5bb8c 100644 --- a/dlls/gdiplus/pen.c +++ b/dlls/gdiplus/pen.c @@ -77,11 +77,6 @@ GpStatus WINGDIPAPI GdipSetPenEndCap(GpPen *pen, GpLineCap cap) { if(!pen) return InvalidParameter; - if(cap != LineCapFlat){ - FIXME("Not implemented for non-flat EndCap\n"); - return NotImplemented; - } - pen->endcap = cap; return Ok;