/* * Copyright (C) 2007 Google (Evan Stade) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ #include #include #include "windef.h" #include "winbase.h" #include "winuser.h" #include "wingdi.h" #define COBJMACROS #include "objbase.h" #include "ocidl.h" #include "olectl.h" #include "ole2.h" #include "winreg.h" #include "shlwapi.h" #include "gdiplus.h" #include "gdiplus_private.h" #include "wine/debug.h" WINE_DEFAULT_DEBUG_CHANNEL(gdiplus); /* looks-right constants */ #define TENSION_CONST (0.3) #define ANCHOR_WIDTH (2.0) #define MAX_ITERS (50) /* Converts angle (in degrees) to x/y coordinates */ static void deg2xy(REAL angle, REAL x_0, REAL y_0, REAL *x, REAL *y) { REAL radAngle, hypotenuse; radAngle = deg2rad(angle); hypotenuse = 50.0; /* arbitrary */ *x = x_0 + cos(radAngle) * hypotenuse; *y = y_0 + sin(radAngle) * hypotenuse; } /* Converts from gdiplus path point type to gdi path point type. */ static BYTE convert_path_point_type(BYTE type) { BYTE ret; switch(type & PathPointTypePathTypeMask){ case PathPointTypeBezier: ret = PT_BEZIERTO; break; case PathPointTypeLine: ret = PT_LINETO; break; case PathPointTypeStart: ret = PT_MOVETO; break; default: ERR("Bad point type\n"); return 0; } if(type & PathPointTypeCloseSubpath) ret |= PT_CLOSEFIGURE; return ret; } static INT prepare_dc(GpGraphics *graphics, GpPen *pen) { HPEN gdipen; REAL width; INT save_state = SaveDC(graphics->hdc), i, numdashes; GpPointF pt[2]; DWORD dash_array[MAX_DASHLEN]; EndPath(graphics->hdc); if(pen->unit == UnitPixel){ width = pen->width; } else{ /* Get an estimate for the amount the pen width is affected by the world * transform. (This is similar to what some of the wine drivers do.) */ pt[0].X = 0.0; pt[0].Y = 0.0; pt[1].X = 1.0; pt[1].Y = 1.0; GdipTransformMatrixPoints(graphics->worldtrans, pt, 2); width = sqrt((pt[1].X - pt[0].X) * (pt[1].X - pt[0].X) + (pt[1].Y - pt[0].Y) * (pt[1].Y - pt[0].Y)) / sqrt(2.0); width *= pen->width * convert_unit(graphics->hdc, pen->unit == UnitWorld ? graphics->unit : pen->unit); } if(pen->dash == DashStyleCustom){ numdashes = min(pen->numdashes, MAX_DASHLEN); TRACE("dashes are: "); for(i = 0; i < numdashes; i++){ dash_array[i] = roundr(width * pen->dashes[i]); TRACE("%d, ", dash_array[i]); } TRACE("\n and the pen style is %x\n", pen->style); gdipen = ExtCreatePen(pen->style, roundr(width), &pen->brush->lb, numdashes, dash_array); } else gdipen = ExtCreatePen(pen->style, roundr(width), &pen->brush->lb, 0, NULL); SelectObject(graphics->hdc, gdipen); return save_state; } static void restore_dc(GpGraphics *graphics, INT state) { DeleteObject(SelectObject(graphics->hdc, GetStockObject(NULL_PEN))); RestoreDC(graphics->hdc, state); } /* This helper applies all the changes that the points listed in ptf need in * order to be drawn on the device context. In the end, this should include at * least: * -scaling by page unit * -applying world transformation * -converting from float to int * Native gdiplus uses gdi32 to do all this (via SetMapMode, SetViewportExtEx, * SetWindowExtEx, SetWorldTransform, etc.) but we cannot because we are using * gdi to draw, and these functions would irreparably mess with line widths. */ static void transform_and_round_points(GpGraphics *graphics, POINT *pti, GpPointF *ptf, INT count) { REAL unitscale; GpMatrix *matrix; int i; unitscale = convert_unit(graphics->hdc, graphics->unit); /* apply page scale */ if(graphics->unit != UnitDisplay) unitscale *= graphics->scale; GdipCloneMatrix(graphics->worldtrans, &matrix); GdipScaleMatrix(matrix, unitscale, unitscale, MatrixOrderAppend); GdipTransformMatrixPoints(matrix, ptf, count); GdipDeleteMatrix(matrix); for(i = 0; i < count; i++){ pti[i].x = roundr(ptf[i].X); pti[i].y = roundr(ptf[i].Y); } } /* GdipDrawPie/GdipFillPie helper function */ static void draw_pie(GpGraphics *graphics, REAL x, REAL y, REAL width, REAL height, REAL startAngle, REAL sweepAngle) { GpPointF ptf[4]; POINT pti[4]; ptf[0].X = x; ptf[0].Y = y; ptf[1].X = x + width; ptf[1].Y = y + height; deg2xy(startAngle+sweepAngle, x + width / 2.0, y + width / 2.0, &ptf[2].X, &ptf[2].Y); deg2xy(startAngle, x + width / 2.0, y + width / 2.0, &ptf[3].X, &ptf[3].Y); transform_and_round_points(graphics, pti, ptf, 4); Pie(graphics->hdc, pti[0].x, pti[0].y, pti[1].x, pti[1].y, pti[2].x, pti[2].y, pti[3].x, pti[3].y); } /* GdipDrawCurve helper function. * Calculates Bezier points from cardinal spline points. */ static void calc_curve_bezier(CONST GpPointF *pts, REAL tension, REAL *x1, REAL *y1, REAL *x2, REAL *y2) { REAL xdiff, ydiff; /* calculate tangent */ xdiff = pts[2].X - pts[0].X; ydiff = pts[2].Y - pts[0].Y; /* apply tangent to get control points */ *x1 = pts[1].X - tension * xdiff; *y1 = pts[1].Y - tension * ydiff; *x2 = pts[1].X + tension * xdiff; *y2 = pts[1].Y + tension * ydiff; } /* GdipDrawCurve helper function. * Calculates Bezier points from cardinal spline endpoints. */ static void calc_curve_bezier_endp(REAL xend, REAL yend, REAL xadj, REAL yadj, REAL tension, REAL *x, REAL *y) { /* tangent at endpoints is the line from the endpoint to the adjacent point */ *x = roundr(tension * (xadj - xend) + xend); *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. Probably * should not be called on an hdc that has a path you care about. */ static void draw_cap(GpGraphics *graphics, COLORREF color, GpLineCap cap, REAL size, const GpCustomLineCap *custom, REAL x1, REAL y1, REAL x2, REAL y2) { HGDIOBJ oldbrush = NULL, oldpen = NULL; GpMatrix *matrix = NULL; HBRUSH brush = NULL; HPEN pen = NULL; PointF ptf[4], *custptf = NULL; POINT pt[4], *custpt = NULL; BYTE *tp = NULL; REAL theta, dsmall, dbig, dx, dy = 0.0; INT i, count; LOGBRUSH lb; BOOL customstroke; if((x1 == x2) && (y1 == y2)) return; theta = gdiplus_atan2(y2 - y1, x2 - x1); customstroke = (cap == LineCapCustom) && custom && (!custom->fill); if(!customstroke){ brush = CreateSolidBrush(color); lb.lbStyle = BS_SOLID; lb.lbColor = color; lb.lbHatch = 0; pen = ExtCreatePen(PS_GEOMETRIC | PS_SOLID | PS_ENDCAP_FLAT | PS_JOIN_MITER, 1, &lb, 0, NULL); oldbrush = SelectObject(graphics->hdc, brush); oldpen = SelectObject(graphics->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; } ptf[0].X = x2 - dsmall; ptf[1].X = x2 + dbig; ptf[0].Y = y2 - dbig; ptf[3].Y = y2 + dsmall; ptf[1].Y = y2 - dsmall; ptf[2].Y = y2 + dbig; ptf[3].X = x2 - dbig; ptf[2].X = x2 + dsmall; transform_and_round_points(graphics, pt, ptf, 4); Polygon(graphics->hdc, pt, 4); break; case LineCapArrowAnchor: size = size * 4.0 / sqrt(3.0); dx = cos(M_PI / 6.0 + theta) * size; dy = sin(M_PI / 6.0 + theta) * size; ptf[0].X = x2 - dx; ptf[0].Y = y2 - dy; dx = cos(- M_PI / 6.0 + theta) * size; dy = sin(- M_PI / 6.0 + theta) * size; ptf[1].X = x2 - dx; ptf[1].Y = y2 - dy; ptf[2].X = x2; ptf[2].Y = y2; transform_and_round_points(graphics, pt, ptf, 3); Polygon(graphics->hdc, pt, 3); break; case LineCapRoundAnchor: dx = dy = ANCHOR_WIDTH * size / 2.0; ptf[0].X = x2 - dx; ptf[0].Y = y2 - dy; ptf[1].X = x2 + dx; ptf[1].Y = y2 + dy; transform_and_round_points(graphics, pt, ptf, 2); Ellipse(graphics->hdc, pt[0].x, pt[0].y, pt[1].x, pt[1].y); break; case LineCapTriangle: size = size / 2.0; dx = cos(M_PI_2 + theta) * size; dy = sin(M_PI_2 + theta) * size; ptf[0].X = x2 - dx; ptf[0].Y = y2 - dy; ptf[1].X = x2 + dx; ptf[1].Y = y2 + dy; dx = cos(theta) * size; dy = sin(theta) * size; ptf[2].X = x2 + dx; ptf[2].Y = y2 + dy; transform_and_round_points(graphics, pt, ptf, 3); Polygon(graphics->hdc, pt, 3); break; case LineCapRound: dx = dy = size / 2.0; ptf[0].X = x2 - dx; ptf[0].Y = y2 - dy; ptf[1].X = x2 + dx; ptf[1].Y = y2 + dy; dx = -cos(M_PI_2 + theta) * size; dy = -sin(M_PI_2 + theta) * size; ptf[2].X = x2 - dx; ptf[2].Y = y2 - dy; ptf[3].X = x2 + dx; ptf[3].Y = y2 + dy; transform_and_round_points(graphics, pt, ptf, 4); Pie(graphics->hdc, pt[0].x, pt[0].y, pt[1].x, pt[1].y, pt[2].x, pt[2].y, pt[3].x, pt[3].y); break; case LineCapCustom: if(!custom) break; count = custom->pathdata.Count; custptf = GdipAlloc(count * sizeof(PointF)); custpt = GdipAlloc(count * sizeof(POINT)); tp = GdipAlloc(count); if(!custptf || !custpt || !tp || (GdipCreateMatrix(&matrix) != Ok)) goto custend; memcpy(custptf, custom->pathdata.Points, count * sizeof(PointF)); GdipScaleMatrix(matrix, size, size, MatrixOrderAppend); GdipRotateMatrix(matrix, (180.0 / M_PI) * (theta - M_PI_2), MatrixOrderAppend); GdipTranslateMatrix(matrix, x2, y2, MatrixOrderAppend); GdipTransformMatrixPoints(matrix, custptf, count); transform_and_round_points(graphics, custpt, custptf, count); for(i = 0; i < count; i++) tp[i] = convert_path_point_type(custom->pathdata.Types[i]); if(custom->fill){ BeginPath(graphics->hdc); PolyDraw(graphics->hdc, custpt, tp, count); EndPath(graphics->hdc); StrokeAndFillPath(graphics->hdc); } else PolyDraw(graphics->hdc, custpt, tp, count); custend: GdipFree(custptf); GdipFree(custpt); GdipFree(tp); GdipDeleteMatrix(matrix); break; default: break; } if(!customstroke){ SelectObject(graphics->hdc, oldbrush); SelectObject(graphics->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. * If percent is negative it can lengthen the line. */ 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 = gdiplus_atan2((*y2 - y1), (*x2 - x1)); dx = cos(theta) * dist; dy = sin(theta) * dist; *x2 = *x2 + dx; *y2 = *y2 + dy; } /* 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. * If the amount is negative, it can lengthen the line. */ 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. */ static GpStatus draw_polyline(GpGraphics *graphics, GpPen *pen, GDIPCONST GpPointF * pt, INT count, BOOL caps) { POINT *pti = NULL; GpPointF *ptcopy = NULL; GpStatus status = GenericError; if(!count) return Ok; pti = GdipAlloc(count * sizeof(POINT)); ptcopy = GdipAlloc(count * sizeof(GpPointF)); if(!pti || !ptcopy){ status = OutOfMemory; goto end; } memcpy(ptcopy, pt, count * sizeof(GpPointF)); if(caps){ if(pen->endcap == LineCapArrowAnchor) shorten_line_amt(ptcopy[count-2].X, ptcopy[count-2].Y, &ptcopy[count-1].X, &ptcopy[count-1].Y, pen->width); else if((pen->endcap == LineCapCustom) && pen->customend) shorten_line_amt(ptcopy[count-2].X, ptcopy[count-2].Y, &ptcopy[count-1].X, &ptcopy[count-1].Y, pen->customend->inset * pen->width); if(pen->startcap == LineCapArrowAnchor) shorten_line_amt(ptcopy[1].X, ptcopy[1].Y, &ptcopy[0].X, &ptcopy[0].Y, pen->width); else if((pen->startcap == LineCapCustom) && pen->customstart) shorten_line_amt(ptcopy[1].X, ptcopy[1].Y, &ptcopy[0].X, &ptcopy[0].Y, pen->customstart->inset * pen->width); draw_cap(graphics, pen->brush->lb.lbColor, pen->endcap, pen->width, pen->customend, pt[count - 2].X, pt[count - 2].Y, pt[count - 1].X, pt[count - 1].Y); draw_cap(graphics, pen->brush->lb.lbColor, pen->startcap, pen->width, pen->customstart, pt[1].X, pt[1].Y, pt[0].X, pt[0].Y);\ } transform_and_round_points(graphics, pti, ptcopy, count); Polyline(graphics->hdc, pti, count); end: GdipFree(pti); GdipFree(ptcopy); return status; } /* Conducts a linear search to find the bezier points that will back off * the endpoint of the curve by a distance of amt. Linear search works * better than binary in this case because there are multiple solutions, * and binary searches often find a bad one. I don't think this is what * Windows does but short of rendering the bezier without GDI's help it's * the best we can do. If rev then work from the start of the passed points * instead of the end. */ static void shorten_bezier_amt(GpPointF * pt, REAL amt, BOOL rev) { GpPointF origpt[4]; REAL percent = 0.00, dx, dy, origx, origy, diff = -1.0; INT i, first = 0, second = 1, third = 2, fourth = 3; if(rev){ first = 3; second = 2; third = 1; fourth = 0; } origx = pt[fourth].X; origy = pt[fourth].Y; memcpy(origpt, pt, sizeof(GpPointF) * 4); for(i = 0; (i < MAX_ITERS) && (diff < amt); i++){ /* reset bezier points to original values */ memcpy(pt, origpt, sizeof(GpPointF) * 4); /* Perform magic on bezier points. Order is important here.*/ shorten_line_percent(pt[third].X, pt[third].Y, &pt[fourth].X, &pt[fourth].Y, percent); shorten_line_percent(pt[second].X, pt[second].Y, &pt[third].X, &pt[third].Y, percent); shorten_line_percent(pt[third].X, pt[third].Y, &pt[fourth].X, &pt[fourth].Y, percent); shorten_line_percent(pt[first].X, pt[first].Y, &pt[second].X, &pt[second].Y, percent); shorten_line_percent(pt[second].X, pt[second].Y, &pt[third].X, &pt[third].Y, percent); shorten_line_percent(pt[third].X, pt[third].Y, &pt[fourth].X, &pt[fourth].Y, percent); dx = pt[fourth].X - origx; dy = pt[fourth].Y - origy; diff = sqrt(dx * dx + dy * dy); percent += 0.0005 * amt; } } /* Draws bezier curves between given points, and if caps is true then draws an * endcap at the end of the last line. */ static GpStatus draw_polybezier(GpGraphics *graphics, GpPen *pen, GDIPCONST GpPointF * pt, INT count, BOOL caps) { POINT *pti; GpPointF *ptcopy; GpStatus status = GenericError; if(!count) return Ok; pti = GdipAlloc(count * sizeof(POINT)); ptcopy = GdipAlloc(count * sizeof(GpPointF)); if(!pti || !ptcopy){ status = OutOfMemory; goto end; } memcpy(ptcopy, pt, count * sizeof(GpPointF)); if(caps){ if(pen->endcap == LineCapArrowAnchor) shorten_bezier_amt(&ptcopy[count-4], pen->width, FALSE); else if((pen->endcap == LineCapCustom) && pen->customend) shorten_bezier_amt(&ptcopy[count-4], pen->width * pen->customend->inset, FALSE); if(pen->startcap == LineCapArrowAnchor) shorten_bezier_amt(ptcopy, pen->width, TRUE); else if((pen->startcap == LineCapCustom) && pen->customstart) shorten_bezier_amt(ptcopy, pen->width * pen->customstart->inset, TRUE); /* the direction of the line cap is parallel to the direction at the * end of the bezier (which, if it has been shortened, is not the same * as the direction from pt[count-2] to pt[count-1]) */ draw_cap(graphics, pen->brush->lb.lbColor, pen->endcap, pen->width, pen->customend, pt[count - 1].X - (ptcopy[count - 1].X - ptcopy[count - 2].X), pt[count - 1].Y - (ptcopy[count - 1].Y - ptcopy[count - 2].Y), pt[count - 1].X, pt[count - 1].Y); draw_cap(graphics, pen->brush->lb.lbColor, pen->startcap, pen->width, pen->customstart, pt[0].X - (ptcopy[0].X - ptcopy[1].X), pt[0].Y - (ptcopy[0].Y - ptcopy[1].Y), pt[0].X, pt[0].Y); } transform_and_round_points(graphics, pti, ptcopy, count); PolyBezier(graphics->hdc, pti, count); status = Ok; end: GdipFree(pti); GdipFree(ptcopy); return status; } /* Draws a combination of bezier curves and lines between points. */ static GpStatus draw_poly(GpGraphics *graphics, GpPen *pen, GDIPCONST GpPointF * pt, GDIPCONST BYTE * types, INT count, BOOL caps) { POINT *pti = GdipAlloc(count * sizeof(POINT)); BYTE *tp = GdipAlloc(count); GpPointF *ptcopy = GdipAlloc(count * sizeof(GpPointF)); INT i, j; GpStatus status = GenericError; if(!count){ status = Ok; goto end; } if(!pti || !tp || !ptcopy){ status = OutOfMemory; goto end; } for(i = 1; i < count; i++){ if((types[i] & PathPointTypePathTypeMask) == PathPointTypeBezier){ if((i + 2 >= count) || !(types[i + 1] & PathPointTypeBezier) || !(types[i + 1] & PathPointTypeBezier)){ ERR("Bad bezier points\n"); goto end; } i += 2; } } memcpy(ptcopy, pt, count * sizeof(GpPointF)); /* If we are drawing caps, go through the points and adjust them accordingly, * and draw the caps. */ if(caps){ switch(types[count - 1] & PathPointTypePathTypeMask){ case PathPointTypeBezier: if(pen->endcap == LineCapArrowAnchor) shorten_bezier_amt(&ptcopy[count - 4], pen->width, FALSE); else if((pen->endcap == LineCapCustom) && pen->customend) shorten_bezier_amt(&ptcopy[count - 4], pen->width * pen->customend->inset, FALSE); draw_cap(graphics, pen->brush->lb.lbColor, pen->endcap, pen->width, pen->customend, pt[count - 1].X - (ptcopy[count - 1].X - ptcopy[count - 2].X), pt[count - 1].Y - (ptcopy[count - 1].Y - ptcopy[count - 2].Y), pt[count - 1].X, pt[count - 1].Y); break; case PathPointTypeLine: if(pen->endcap == LineCapArrowAnchor) shorten_line_amt(ptcopy[count - 2].X, ptcopy[count - 2].Y, &ptcopy[count - 1].X, &ptcopy[count - 1].Y, pen->width); else if((pen->endcap == LineCapCustom) && pen->customend) shorten_line_amt(ptcopy[count - 2].X, ptcopy[count - 2].Y, &ptcopy[count - 1].X, &ptcopy[count - 1].Y, pen->customend->inset * pen->width); draw_cap(graphics, pen->brush->lb.lbColor, pen->endcap, pen->width, pen->customend, pt[count - 2].X, pt[count - 2].Y, pt[count - 1].X, pt[count - 1].Y); break; default: ERR("Bad path last point\n"); goto end; } /* Find start of points */ for(j = 1; j < count && ((types[j] & PathPointTypePathTypeMask) == PathPointTypeStart); j++); switch(types[j] & PathPointTypePathTypeMask){ case PathPointTypeBezier: if(pen->startcap == LineCapArrowAnchor) shorten_bezier_amt(&ptcopy[j - 1], pen->width, TRUE); else if((pen->startcap == LineCapCustom) && pen->customstart) shorten_bezier_amt(&ptcopy[j - 1], pen->width * pen->customstart->inset, TRUE); draw_cap(graphics, pen->brush->lb.lbColor, pen->startcap, pen->width, pen->customstart, pt[j - 1].X - (ptcopy[j - 1].X - ptcopy[j].X), pt[j - 1].Y - (ptcopy[j - 1].Y - ptcopy[j].Y), pt[j - 1].X, pt[j - 1].Y); break; case PathPointTypeLine: if(pen->startcap == LineCapArrowAnchor) shorten_line_amt(ptcopy[j].X, ptcopy[j].Y, &ptcopy[j - 1].X, &ptcopy[j - 1].Y, pen->width); else if((pen->startcap == LineCapCustom) && pen->customstart) shorten_line_amt(ptcopy[j].X, ptcopy[j].Y, &ptcopy[j - 1].X, &ptcopy[j - 1].Y, pen->customstart->inset * pen->width); draw_cap(graphics, pen->brush->lb.lbColor, pen->startcap, pen->width, pen->customstart, pt[j].X, pt[j].Y, pt[j - 1].X, pt[j - 1].Y); break; default: ERR("Bad path points\n"); goto end; } } transform_and_round_points(graphics, pti, ptcopy, count); for(i = 0; i < count; i++){ tp[i] = convert_path_point_type(types[i]); } PolyDraw(graphics->hdc, pti, tp, count); status = Ok; end: GdipFree(pti); GdipFree(ptcopy); GdipFree(tp); return status; } GpStatus WINGDIPAPI GdipCreateFromHDC(HDC hdc, GpGraphics **graphics) { GpStatus retval; if(hdc == NULL) return OutOfMemory; if(graphics == NULL) return InvalidParameter; *graphics = GdipAlloc(sizeof(GpGraphics)); if(!*graphics) return OutOfMemory; if((retval = GdipCreateMatrix(&(*graphics)->worldtrans)) != Ok){ GdipFree(*graphics); return retval; } (*graphics)->hdc = hdc; (*graphics)->hwnd = NULL; (*graphics)->smoothing = SmoothingModeDefault; (*graphics)->compqual = CompositingQualityDefault; (*graphics)->interpolation = InterpolationModeDefault; (*graphics)->pixeloffset = PixelOffsetModeDefault; (*graphics)->compmode = CompositingModeSourceOver; (*graphics)->unit = UnitDisplay; (*graphics)->scale = 1.0; return Ok; } GpStatus WINGDIPAPI GdipCreateFromHWND(HWND hwnd, GpGraphics **graphics) { GpStatus ret; if((ret = GdipCreateFromHDC(GetDC(hwnd), graphics)) != Ok) return ret; (*graphics)->hwnd = hwnd; return Ok; } GpStatus WINGDIPAPI GdipCreateMetafileFromEmf(HENHMETAFILE hemf, BOOL delete, GpMetafile **metafile) { static int calls; if(!hemf || !metafile) return InvalidParameter; if(!(calls++)) FIXME("not implemented\n"); return NotImplemented; } GpStatus WINGDIPAPI GdipCreateMetafileFromWmf(HMETAFILE hwmf, BOOL delete, GDIPCONST WmfPlaceableFileHeader * placeable, GpMetafile **metafile) { IStream *stream = NULL; UINT read; BYTE* copy; HENHMETAFILE hemf; GpStatus retval = GenericError; if(!hwmf || !metafile || !placeable) return InvalidParameter; *metafile = NULL; read = GetMetaFileBitsEx(hwmf, 0, NULL); if(!read) return GenericError; copy = GdipAlloc(read); GetMetaFileBitsEx(hwmf, read, copy); hemf = SetWinMetaFileBits(read, copy, NULL, NULL); GdipFree(copy); read = GetEnhMetaFileBits(hemf, 0, NULL); copy = GdipAlloc(read); GetEnhMetaFileBits(hemf, read, copy); DeleteEnhMetaFile(hemf); if(CreateStreamOnHGlobal(copy, TRUE, &stream) != S_OK){ ERR("could not make stream\n"); GdipFree(copy); goto err; } *metafile = GdipAlloc(sizeof(GpMetafile)); if(!*metafile){ retval = OutOfMemory; goto err; } if(OleLoadPicture(stream, 0, FALSE, &IID_IPicture, (LPVOID*) &((*metafile)->image.picture)) != S_OK) goto err; (*metafile)->image.type = ImageTypeMetafile; (*metafile)->bounds.X = ((REAL) placeable->BoundingBox.Left) / ((REAL) placeable->Inch); (*metafile)->bounds.Y = ((REAL) placeable->BoundingBox.Right) / ((REAL) placeable->Inch); (*metafile)->bounds.Width = ((REAL) (placeable->BoundingBox.Right - placeable->BoundingBox.Left)) / ((REAL) placeable->Inch); (*metafile)->bounds.Height = ((REAL) (placeable->BoundingBox.Bottom - placeable->BoundingBox.Top)) / ((REAL) placeable->Inch); (*metafile)->unit = UnitInch; if(delete) DeleteMetaFile(hwmf); return Ok; err: GdipFree(*metafile); IStream_Release(stream); return retval; } GpStatus WINGDIPAPI GdipCreateStreamOnFile(GDIPCONST WCHAR * filename, UINT access, IStream **stream) { DWORD dwMode; HRESULT ret; if(!stream || !filename) return InvalidParameter; if(access & GENERIC_WRITE) dwMode = STGM_SHARE_DENY_WRITE | STGM_WRITE | STGM_CREATE; else if(access & GENERIC_READ) dwMode = STGM_SHARE_DENY_WRITE | STGM_READ | STGM_FAILIFTHERE; else return InvalidParameter; ret = SHCreateStreamOnFileW(filename, dwMode, stream); return hresult_to_status(ret); } GpStatus WINGDIPAPI GdipDeleteGraphics(GpGraphics *graphics) { if(!graphics) return InvalidParameter; if(graphics->hwnd) ReleaseDC(graphics->hwnd, graphics->hdc); GdipDeleteMatrix(graphics->worldtrans); HeapFree(GetProcessHeap(), 0, graphics); return Ok; } GpStatus WINGDIPAPI GdipDrawArc(GpGraphics *graphics, GpPen *pen, REAL x, REAL y, REAL width, REAL height, REAL startAngle, REAL sweepAngle) { INT save_state, num_pts; GpPointF points[MAX_ARC_PTS]; GpStatus retval; if(!graphics || !pen) return InvalidParameter; num_pts = arc2polybezier(points, x, y, width, height, startAngle, sweepAngle); save_state = prepare_dc(graphics, pen); retval = draw_polybezier(graphics, pen, points, num_pts, TRUE); restore_dc(graphics, save_state); return retval; } GpStatus WINGDIPAPI GdipDrawBezier(GpGraphics *graphics, GpPen *pen, REAL x1, REAL y1, REAL x2, REAL y2, REAL x3, REAL y3, REAL x4, REAL y4) { INT save_state; GpPointF pt[4]; GpStatus retval; if(!graphics || !pen) return InvalidParameter; pt[0].X = x1; pt[0].Y = y1; pt[1].X = x2; pt[1].Y = y2; pt[2].X = x3; pt[2].Y = y3; pt[3].X = x4; pt[3].Y = y4; save_state = prepare_dc(graphics, pen); retval = draw_polybezier(graphics, pen, pt, 4, TRUE); restore_dc(graphics, save_state); return retval; } /* Approximates cardinal spline with Bezier curves. */ GpStatus WINGDIPAPI GdipDrawCurve2(GpGraphics *graphics, GpPen *pen, GDIPCONST GpPointF *points, INT count, REAL tension) { /* PolyBezier expects count*3-2 points. */ INT i, len_pt = count*3-2, save_state; GpPointF *pt; REAL x1, x2, y1, y2; GpStatus retval; if(!graphics || !pen) return InvalidParameter; pt = GdipAlloc(len_pt * sizeof(GpPointF)); tension = tension * TENSION_CONST; calc_curve_bezier_endp(points[0].X, points[0].Y, points[1].X, points[1].Y, tension, &x1, &y1); pt[0].X = points[0].X; pt[0].Y = points[0].Y; pt[1].X = x1; pt[1].Y = y1; for(i = 0; i < count-2; i++){ calc_curve_bezier(&(points[i]), tension, &x1, &y1, &x2, &y2); pt[3*i+2].X = x1; pt[3*i+2].Y = y1; pt[3*i+3].X = points[i+1].X; pt[3*i+3].Y = points[i+1].Y; pt[3*i+4].X = x2; pt[3*i+4].Y = y2; } calc_curve_bezier_endp(points[count-1].X, points[count-1].Y, points[count-2].X, points[count-2].Y, tension, &x1, &y1); pt[len_pt-2].X = x1; pt[len_pt-2].Y = y1; pt[len_pt-1].X = points[count-1].X; pt[len_pt-1].Y = points[count-1].Y; save_state = prepare_dc(graphics, pen); retval = draw_polybezier(graphics, pen, pt, len_pt, TRUE); GdipFree(pt); restore_dc(graphics, save_state); return retval; } GpStatus WINGDIPAPI GdipDrawImageI(GpGraphics *graphics, GpImage *image, INT x, INT y) { UINT width, height, srcw, srch; if(!graphics || !image) return InvalidParameter; GdipGetImageWidth(image, &width); GdipGetImageHeight(image, &height); srcw = width * (((REAL) INCH_HIMETRIC) / ((REAL) GetDeviceCaps(graphics->hdc, LOGPIXELSX))); srch = height * (((REAL) INCH_HIMETRIC) / ((REAL) GetDeviceCaps(graphics->hdc, LOGPIXELSY))); if(image->type != ImageTypeMetafile){ y += height; height *= -1; } IPicture_Render(image->picture, graphics->hdc, x, y, width, height, 0, 0, srcw, srch, NULL); return Ok; } /* FIXME: partially implemented (only works for rectangular parallelograms) */ GpStatus WINGDIPAPI GdipDrawImagePointsRect(GpGraphics *graphics, GpImage *image, GDIPCONST GpPointF *points, INT count, REAL srcx, REAL srcy, REAL srcwidth, REAL srcheight, GpUnit srcUnit, GDIPCONST GpImageAttributes* imageAttributes, DrawImageAbort callback, VOID * callbackData) { GpPointF ptf[3]; POINT pti[3]; REAL dx, dy; TRACE("%p %p %p %d %f %f %f %f %d %p %p %p\n", graphics, image, points, count, srcx, srcy, srcwidth, srcheight, srcUnit, imageAttributes, callback, callbackData); if(!graphics || !image || !points || !imageAttributes || count != 3) return InvalidParameter; if(srcUnit == UnitInch) dx = dy = (REAL) INCH_HIMETRIC; else if(srcUnit == UnitPixel){ dx = ((REAL) INCH_HIMETRIC) / ((REAL) GetDeviceCaps(graphics->hdc, LOGPIXELSX)); dy = ((REAL) INCH_HIMETRIC) / ((REAL) GetDeviceCaps(graphics->hdc, LOGPIXELSY)); } else return NotImplemented; memcpy(ptf, points, 3 * sizeof(GpPointF)); transform_and_round_points(graphics, pti, ptf, 3); /* IPicture renders bitmaps with the y-axis reversed * FIXME: flipping for unknown image type might not be correct. */ if(image->type != ImageTypeMetafile){ INT temp; temp = pti[0].y; pti[0].y = pti[2].y; pti[2].y = temp; } if(IPicture_Render(image->picture, graphics->hdc, pti[0].x, pti[0].y, pti[1].x - pti[0].x, pti[2].y - pti[0].y, srcx * dx, srcy * dy, srcwidth * dx, srcheight * dy, NULL) != S_OK){ if(callback) callback(callbackData); return GenericError; } return Ok; } GpStatus WINGDIPAPI GdipDrawImageRectRect(GpGraphics *graphics, GpImage *image, REAL dstx, REAL dsty, REAL dstwidth, REAL dstheight, REAL srcx, REAL srcy, REAL srcwidth, REAL srcheight, GpUnit srcUnit, GDIPCONST GpImageAttributes* imageattr, DrawImageAbort callback, VOID * callbackData) { GpPointF points[3]; points[0].X = dstx; points[0].Y = dsty; points[1].X = dstx + dstwidth; points[1].Y = dsty; points[2].X = dstx; points[2].Y = dsty + dstheight; return GdipDrawImagePointsRect(graphics, image, points, 3, srcx, srcy, srcwidth, srcheight, srcUnit, imageattr, callback, callbackData); } GpStatus WINGDIPAPI GdipDrawLine(GpGraphics *graphics, GpPen *pen, REAL x1, REAL y1, REAL x2, REAL y2) { INT save_state; GpPointF pt[2]; GpStatus retval; if(!pen || !graphics) return InvalidParameter; pt[0].X = x1; pt[0].Y = y1; pt[1].X = x2; pt[1].Y = y2; save_state = prepare_dc(graphics, pen); retval = draw_polyline(graphics, pen, pt, 2, TRUE); restore_dc(graphics, save_state); return retval; } GpStatus WINGDIPAPI GdipDrawLineI(GpGraphics *graphics, GpPen *pen, INT x1, INT y1, INT x2, INT y2) { INT save_state; GpPointF pt[2]; GpStatus retval; 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 = prepare_dc(graphics, pen); retval = draw_polyline(graphics, pen, pt, 2, TRUE); restore_dc(graphics, save_state); return retval; } GpStatus WINGDIPAPI GdipDrawLines(GpGraphics *graphics, GpPen *pen, GDIPCONST GpPointF *points, INT count) { INT save_state; GpStatus retval; if(!pen || !graphics || (count < 2)) return InvalidParameter; save_state = prepare_dc(graphics, pen); retval = draw_polyline(graphics, pen, points, count, TRUE); restore_dc(graphics, save_state); return retval; } GpStatus WINGDIPAPI GdipDrawPath(GpGraphics *graphics, GpPen *pen, GpPath *path) { INT save_state; GpStatus retval; if(!pen || !graphics) return InvalidParameter; save_state = prepare_dc(graphics, pen); retval = draw_poly(graphics, pen, path->pathdata.Points, path->pathdata.Types, path->pathdata.Count, TRUE); restore_dc(graphics, save_state); return retval; } GpStatus WINGDIPAPI GdipDrawPie(GpGraphics *graphics, GpPen *pen, REAL x, REAL y, REAL width, REAL height, REAL startAngle, REAL sweepAngle) { INT save_state; if(!graphics || !pen) return InvalidParameter; save_state = prepare_dc(graphics, pen); SelectObject(graphics->hdc, GetStockObject(NULL_BRUSH)); draw_pie(graphics, x, y, width, height, startAngle, sweepAngle); restore_dc(graphics, save_state); return Ok; } GpStatus WINGDIPAPI GdipDrawRectangleI(GpGraphics *graphics, GpPen *pen, INT x, INT y, INT width, INT height) { INT save_state; GpPointF ptf[4]; POINT pti[4]; if(!pen || !graphics) return InvalidParameter; ptf[0].X = x; ptf[0].Y = y; ptf[1].X = x + width; ptf[1].Y = y; ptf[2].X = x + width; ptf[2].Y = y + height; ptf[3].X = x; ptf[3].Y = y + height; save_state = prepare_dc(graphics, pen); SelectObject(graphics->hdc, GetStockObject(NULL_BRUSH)); transform_and_round_points(graphics, pti, ptf, 4); Polygon(graphics->hdc, pti, 4); restore_dc(graphics, save_state); return Ok; } GpStatus WINGDIPAPI GdipFillPath(GpGraphics *graphics, GpBrush *brush, GpPath *path) { INT save_state; GpStatus retval; if(!brush || !graphics || !path) return InvalidParameter; save_state = SaveDC(graphics->hdc); EndPath(graphics->hdc); SelectObject(graphics->hdc, brush->gdibrush); SetPolyFillMode(graphics->hdc, (path->fill == FillModeAlternate ? ALTERNATE : WINDING)); BeginPath(graphics->hdc); retval = draw_poly(graphics, NULL, path->pathdata.Points, path->pathdata.Types, path->pathdata.Count, FALSE); if(retval != Ok) goto end; EndPath(graphics->hdc); FillPath(graphics->hdc); retval = Ok; end: RestoreDC(graphics->hdc, save_state); return retval; } GpStatus WINGDIPAPI GdipFillPie(GpGraphics *graphics, GpBrush *brush, REAL x, REAL y, REAL width, REAL height, REAL startAngle, REAL sweepAngle) { INT save_state; if(!graphics || !brush) return InvalidParameter; save_state = SaveDC(graphics->hdc); EndPath(graphics->hdc); SelectObject(graphics->hdc, brush->gdibrush); SelectObject(graphics->hdc, GetStockObject(NULL_PEN)); draw_pie(graphics, x, y, width, height, startAngle, sweepAngle); RestoreDC(graphics->hdc, save_state); return Ok; } GpStatus WINGDIPAPI GdipFillPolygon(GpGraphics *graphics, GpBrush *brush, GDIPCONST GpPointF *points, INT count, GpFillMode fillMode) { INT save_state; GpPointF *ptf = NULL; POINT *pti = NULL; GpStatus retval = Ok; if(!graphics || !brush || !points || !count) return InvalidParameter; ptf = GdipAlloc(count * sizeof(GpPointF)); pti = GdipAlloc(count * sizeof(POINT)); if(!ptf || !pti){ retval = OutOfMemory; goto end; } memcpy(ptf, points, count * sizeof(GpPointF)); save_state = SaveDC(graphics->hdc); EndPath(graphics->hdc); SelectObject(graphics->hdc, brush->gdibrush); SelectObject(graphics->hdc, GetStockObject(NULL_PEN)); SetPolyFillMode(graphics->hdc, (fillMode == FillModeAlternate ? ALTERNATE : WINDING)); transform_and_round_points(graphics, pti, ptf, count); Polygon(graphics->hdc, pti, count); RestoreDC(graphics->hdc, save_state); end: GdipFree(ptf); GdipFree(pti); return retval; } GpStatus WINGDIPAPI GdipFillPolygonI(GpGraphics *graphics, GpBrush *brush, GDIPCONST GpPoint *points, INT count, GpFillMode fillMode) { INT save_state, i; GpPointF *ptf = NULL; POINT *pti = NULL; GpStatus retval = Ok; if(!graphics || !brush || !points || !count) return InvalidParameter; ptf = GdipAlloc(count * sizeof(GpPointF)); pti = GdipAlloc(count * sizeof(POINT)); if(!ptf || !pti){ retval = OutOfMemory; goto end; } for(i = 0; i < count; i ++){ ptf[i].X = (REAL) points[i].X; ptf[i].Y = (REAL) points[i].Y; } save_state = SaveDC(graphics->hdc); EndPath(graphics->hdc); SelectObject(graphics->hdc, brush->gdibrush); SelectObject(graphics->hdc, GetStockObject(NULL_PEN)); SetPolyFillMode(graphics->hdc, (fillMode == FillModeAlternate ? ALTERNATE : WINDING)); transform_and_round_points(graphics, pti, ptf, count); Polygon(graphics->hdc, pti, count); RestoreDC(graphics->hdc, save_state); end: GdipFree(ptf); GdipFree(pti); return retval; } GpStatus WINGDIPAPI GdipFillRectangle(GpGraphics *graphics, GpBrush *brush, REAL x, REAL y, REAL width, REAL height) { INT save_state; GpPointF ptf[4]; POINT pti[4]; if(!graphics || !brush) return InvalidParameter; ptf[0].X = x; ptf[0].Y = y; ptf[1].X = x + width; ptf[1].Y = y; ptf[2].X = x + width; ptf[2].Y = y + height; ptf[3].X = x; ptf[3].Y = y + height; save_state = SaveDC(graphics->hdc); EndPath(graphics->hdc); SelectObject(graphics->hdc, brush->gdibrush); SelectObject(graphics->hdc, GetStockObject(NULL_PEN)); transform_and_round_points(graphics, pti, ptf, 4); Polygon(graphics->hdc, pti, 4); RestoreDC(graphics->hdc, save_state); return Ok; } GpStatus WINGDIPAPI GdipFillRectangleI(GpGraphics *graphics, GpBrush *brush, INT x, INT y, INT width, INT height) { INT save_state; GpPointF ptf[4]; POINT pti[4]; if(!graphics || !brush) return InvalidParameter; ptf[0].X = x; ptf[0].Y = y; ptf[1].X = x + width; ptf[1].Y = y; ptf[2].X = x + width; ptf[2].Y = y + height; ptf[3].X = x; ptf[3].Y = y + height; save_state = SaveDC(graphics->hdc); EndPath(graphics->hdc); SelectObject(graphics->hdc, brush->gdibrush); SelectObject(graphics->hdc, GetStockObject(NULL_PEN)); transform_and_round_points(graphics, pti, ptf, 4); Polygon(graphics->hdc, pti, 4); RestoreDC(graphics->hdc, save_state); return Ok; } /* FIXME: Compositing mode is not used anywhere except the getter/setter. */ GpStatus WINGDIPAPI GdipGetCompositingMode(GpGraphics *graphics, CompositingMode *mode) { if(!graphics || !mode) return InvalidParameter; *mode = graphics->compmode; return Ok; } /* FIXME: Compositing quality is not used anywhere except the getter/setter. */ GpStatus WINGDIPAPI GdipGetCompositingQuality(GpGraphics *graphics, CompositingQuality *quality) { if(!graphics || !quality) return InvalidParameter; *quality = graphics->compqual; return Ok; } /* FIXME: Interpolation mode is not used anywhere except the getter/setter. */ GpStatus WINGDIPAPI GdipGetInterpolationMode(GpGraphics *graphics, InterpolationMode *mode) { if(!graphics || !mode) return InvalidParameter; *mode = graphics->interpolation; return Ok; } GpStatus WINGDIPAPI GdipGetPageScale(GpGraphics *graphics, REAL *scale) { if(!graphics || !scale) return InvalidParameter; *scale = graphics->scale; return Ok; } GpStatus WINGDIPAPI GdipGetPageUnit(GpGraphics *graphics, GpUnit *unit) { if(!graphics || !unit) return InvalidParameter; *unit = graphics->unit; return Ok; } /* FIXME: Pixel offset mode is not used anywhere except the getter/setter. */ GpStatus WINGDIPAPI GdipGetPixelOffsetMode(GpGraphics *graphics, PixelOffsetMode *mode) { if(!graphics || !mode) return InvalidParameter; *mode = graphics->pixeloffset; return Ok; } /* FIXME: Smoothing mode is not used anywhere except the getter/setter. */ GpStatus WINGDIPAPI GdipGetSmoothingMode(GpGraphics *graphics, SmoothingMode *mode) { if(!graphics || !mode) return InvalidParameter; *mode = graphics->smoothing; return Ok; } GpStatus WINGDIPAPI GdipGetWorldTransform(GpGraphics *graphics, GpMatrix *matrix) { if(!graphics || !matrix) return InvalidParameter; memcpy(matrix, graphics->worldtrans, sizeof(GpMatrix)); return Ok; } GpStatus WINGDIPAPI GdipRestoreGraphics(GpGraphics *graphics, GraphicsState state) { static int calls; if(!graphics) return InvalidParameter; if(!(calls++)) FIXME("graphics state not implemented\n"); return NotImplemented; } GpStatus WINGDIPAPI GdipRotateWorldTransform(GpGraphics *graphics, REAL angle, GpMatrixOrder order) { if(!graphics) return InvalidParameter; return GdipRotateMatrix(graphics->worldtrans, angle, order); } GpStatus WINGDIPAPI GdipSaveGraphics(GpGraphics *graphics, GraphicsState *state) { static int calls; if(!graphics || !state) return InvalidParameter; if(!(calls++)) FIXME("graphics state not implemented\n"); return NotImplemented; } GpStatus WINGDIPAPI GdipScaleWorldTransform(GpGraphics *graphics, REAL sx, REAL sy, GpMatrixOrder order) { if(!graphics) return InvalidParameter; return GdipScaleMatrix(graphics->worldtrans, sx, sy, order); } GpStatus WINGDIPAPI GdipSetCompositingMode(GpGraphics *graphics, CompositingMode mode) { if(!graphics) return InvalidParameter; graphics->compmode = mode; return Ok; } GpStatus WINGDIPAPI GdipSetCompositingQuality(GpGraphics *graphics, CompositingQuality quality) { if(!graphics) return InvalidParameter; graphics->compqual = quality; return Ok; } GpStatus WINGDIPAPI GdipSetInterpolationMode(GpGraphics *graphics, InterpolationMode mode) { if(!graphics) return InvalidParameter; graphics->interpolation = mode; return Ok; } GpStatus WINGDIPAPI GdipSetPageScale(GpGraphics *graphics, REAL scale) { if(!graphics || (scale <= 0.0)) return InvalidParameter; graphics->scale = scale; return Ok; } GpStatus WINGDIPAPI GdipSetPageUnit(GpGraphics *graphics, GpUnit unit) { if(!graphics || (unit == UnitWorld)) return InvalidParameter; graphics->unit = unit; return Ok; } GpStatus WINGDIPAPI GdipSetPixelOffsetMode(GpGraphics *graphics, PixelOffsetMode mode) { if(!graphics) return InvalidParameter; graphics->pixeloffset = mode; return Ok; } GpStatus WINGDIPAPI GdipSetSmoothingMode(GpGraphics *graphics, SmoothingMode mode) { if(!graphics) return InvalidParameter; graphics->smoothing = mode; return Ok; } GpStatus WINGDIPAPI GdipSetWorldTransform(GpGraphics *graphics, GpMatrix *matrix) { if(!graphics || !matrix) return InvalidParameter; GdipDeleteMatrix(graphics->worldtrans); return GdipCloneMatrix(matrix, &graphics->worldtrans); } GpStatus WINGDIPAPI GdipTranslateWorldTransform(GpGraphics *graphics, REAL dx, REAL dy, GpMatrixOrder order) { if(!graphics) return InvalidParameter; return GdipTranslateMatrix(graphics->worldtrans, dx, dy, order); }