/* * 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" #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; } /* GdipDrawPie/GdipFillPie helper function */ static GpStatus draw_pie(GpGraphics *graphics, HBRUSH gdibrush, HPEN gdipen, REAL x, REAL y, REAL width, REAL height, REAL startAngle, REAL sweepAngle) { INT save_state; REAL x_0, y_0, x_1, y_1, x_2, y_2; if(!graphics) return InvalidParameter; save_state = SaveDC(graphics->hdc); EndPath(graphics->hdc); SelectObject(graphics->hdc, gdipen); SelectObject(graphics->hdc, gdibrush); x_0 = x + (width/2.0); y_0 = y + (height/2.0); deg2xy(startAngle+sweepAngle, x_0, y_0, &x_1, &y_1); deg2xy(startAngle, x_0, y_0, &x_2, &y_2); Pie(graphics->hdc, roundr(x), roundr(y), roundr(x+width), roundr(y+height), roundr(x_1), roundr(y_1), roundr(x_2), roundr(y_2)); RestoreDC(graphics->hdc, save_state); return Ok; } /* 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. */ 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 ? floorf(x2 - dx) : ceilf(x2 - dx)); pt[0].y = ((y2 - y1) >= 0 ? floorf(y2 - dy) : ceilf(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 ? floorf(x2 - dx) : ceilf(x2 - dx)); pt[0].y = ((y2 - y1) >= 0 ? floorf(y2 - dy) : ceilf(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 GpStatus draw_polyline(HDC hdc, GpPen *pen, GDIPCONST GpPointF * pt, INT count, BOOL caps) { POINT *pti; REAL x = pt[count - 1].X, y = pt[count - 1].Y; INT i; GpStatus status = GenericError; if(!count) return Ok; pti = GdipAlloc(count * sizeof(POINT)); if(!pti){ status = OutOfMemory; goto end; } 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); end: GdipFree(pti); 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. */ static void shorten_bezier_amt(GpPointF * pt, REAL amt) { GpPointF origpt[4]; REAL percent = 0.00, dx, dy, origx = pt[3].X, origy = pt[3].Y, diff = -1.0; INT i; 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[2].X, pt[2].Y, &pt[3].X, &pt[3].Y, percent); shorten_line_percent(pt[1].X, pt[1].Y, &pt[2].X, &pt[2].Y, percent); shorten_line_percent(pt[2].X, pt[2].Y, &pt[3].X, &pt[3].Y, percent); shorten_line_percent(pt[0].X, pt[0].Y, &pt[1].X, &pt[1].Y, percent); shorten_line_percent(pt[1].X, pt[1].Y, &pt[2].X, &pt[2].Y, percent); shorten_line_percent(pt[2].X, pt[2].Y, &pt[3].X, &pt[3].Y, percent); dx = pt[3].X - origx; dy = pt[3].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. FIXME: Startcaps not implemented. */ static GpStatus draw_polybezier(HDC hdc, GpPen *pen, GDIPCONST GpPointF * pt, INT count, BOOL caps) { POINT *pti; GpPointF *ptf; INT i; GpStatus status = GenericError; if(!count) return Ok; pti = GdipAlloc(count * sizeof(POINT)); ptf = GdipAlloc(4 * sizeof(GpPointF)); if(!pti || !ptf){ status = OutOfMemory; goto end; } memcpy(ptf, &pt[count-4], 4 * sizeof(GpPointF)); if(caps){ if(pen->endcap == LineCapArrowAnchor) shorten_bezier_amt(ptf, pen->width); /* 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(hdc, pen->color, pen->endcap, pen->width, pt[count - 1].X - (ptf[3].X - ptf[2].X), pt[count - 1].Y - (ptf[3].Y - ptf[2].Y), pt[count - 1].X, pt[count - 1].Y); } for(i = 0; i < count - 4; i ++){ pti[i].x = roundr(pt[i].X); pti[i].y = roundr(pt[i].Y); } for(i = 0; i < 4; i ++){ pti[i + count - 4].x = roundr(ptf[i].X); pti[i + count - 4].y = roundr(ptf[i].Y); } PolyBezier(hdc, pti, count); status = Ok; end: GdipFree(pti); GdipFree(ptf); return status; } /* 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; } /* Draws a combination of bezier curves and lines between points. */ static GpStatus draw_poly(HDC hdc, GpPen *pen, GDIPCONST GpPointF * pt, GDIPCONST BYTE * types, INT count, BOOL caps) { POINT *pti = GdipAlloc(count * sizeof(POINT)); BYTE *tp = GdipAlloc(count); GpPointF *ptf = NULL; REAL x = pt[count - 1].X, y = pt[count - 1].Y; INT i; GpStatus status = GenericError; if(!count){ status = Ok; goto end; } if(!pti || !tp){ status = OutOfMemory; goto end; } for(i = 0; 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; } } for(i = 0; i < count; i++){ pti[i].x = roundr(pt[i].X); pti[i].y = roundr(pt[i].Y); } /* 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: ptf = GdipAlloc(4 * sizeof(GpPointF)); if(!ptf){ status = OutOfMemory; goto end; } memcpy(ptf, &pt[count - 4], 4 * sizeof(GpPointF)); if(pen->endcap == LineCapArrowAnchor) shorten_bezier_amt(ptf, pen->width); draw_cap(hdc, pen->color, pen->endcap, pen->width, pt[count - 1].X - (ptf[3].X - ptf[2].X), pt[count - 1].Y - (ptf[3].Y - ptf[2].Y), pt[count - 1].X, pt[count - 1].Y); for(i = 0; i < 4; i++){ pti[i + count - 4].x = roundr(ptf[i].X); pti[i + count - 4].y = roundr(ptf[i].Y); } break; case PathPointTypeLine: 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); pti[count - 1].x = roundr(x); pti[count - 1].y = roundr(y); break; default: ERR("Bad path last point\n"); goto end; } } for(i = 0; i < count; i++){ tp[i] = convert_path_point_type(types[i]); } PolyDraw(hdc, pti, tp, count); status = Ok; end: GdipFree(pti); GdipFree(ptf); GdipFree(tp); return status; } GpStatus WINGDIPAPI GdipCreateFromHDC(HDC hdc, GpGraphics **graphics) { if(hdc == NULL) return OutOfMemory; if(graphics == NULL) return InvalidParameter; *graphics = GdipAlloc(sizeof(GpGraphics)); if(!*graphics) return OutOfMemory; (*graphics)->hdc = hdc; (*graphics)->hwnd = NULL; (*graphics)->smoothing = SmoothingModeDefault; (*graphics)->compqual = CompositingQualityDefault; (*graphics)->interpolation = InterpolationModeDefault; (*graphics)->pixeloffset = PixelOffsetModeDefault; 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 GdipDeleteGraphics(GpGraphics *graphics) { if(!graphics) return InvalidParameter; if(graphics->hwnd) ReleaseDC(graphics->hwnd, graphics->hdc); 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 = SaveDC(graphics->hdc); EndPath(graphics->hdc); SelectObject(graphics->hdc, pen->gdipen); retval = draw_polybezier(graphics->hdc, pen, points, num_pts, TRUE); RestoreDC(graphics->hdc, 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 = SaveDC(graphics->hdc); EndPath(graphics->hdc); SelectObject(graphics->hdc, pen->gdipen); retval = draw_polybezier(graphics->hdc, pen, pt, 4, TRUE); RestoreDC(graphics->hdc, 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 = SaveDC(graphics->hdc); EndPath(graphics->hdc); SelectObject(graphics->hdc, pen->gdipen); retval = draw_polybezier(graphics->hdc, pen, pt, len_pt, TRUE); GdipFree(pt); RestoreDC(graphics->hdc, 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 = SaveDC(graphics->hdc); EndPath(graphics->hdc); SelectObject(graphics->hdc, pen->gdipen); retval = draw_polyline(graphics->hdc, pen, pt, 2, TRUE); RestoreDC(graphics->hdc, 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 = SaveDC(graphics->hdc); EndPath(graphics->hdc); SelectObject(graphics->hdc, pen->gdipen); retval = draw_polyline(graphics->hdc, pen, points, count, TRUE); RestoreDC(graphics->hdc, 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 = SaveDC(graphics->hdc); EndPath(graphics->hdc); SelectObject(graphics->hdc, pen->gdipen); retval = draw_poly(graphics->hdc, pen, path->pathdata.Points, path->pathdata.Types, path->pathdata.Count, TRUE); RestoreDC(graphics->hdc, save_state); return retval; } GpStatus WINGDIPAPI GdipDrawPie(GpGraphics *graphics, GpPen *pen, REAL x, REAL y, REAL width, REAL height, REAL startAngle, REAL sweepAngle) { if(!pen) return InvalidParameter; return draw_pie(graphics, GetStockObject(NULL_BRUSH), pen->gdipen, x, y, width, height, startAngle, sweepAngle); } GpStatus WINGDIPAPI GdipDrawRectangleI(GpGraphics *graphics, GpPen *pen, INT x, INT y, INT width, INT height) { INT save_state; if(!pen || !graphics) return InvalidParameter; save_state = SaveDC(graphics->hdc); EndPath(graphics->hdc); SelectObject(graphics->hdc, pen->gdipen); SelectObject(graphics->hdc, GetStockObject(NULL_BRUSH)); Rectangle(graphics->hdc, x, y, x + width, y + height); RestoreDC(graphics->hdc, save_state); return Ok; } GpStatus WINGDIPAPI GdipFillPie(GpGraphics *graphics, GpBrush *brush, REAL x, REAL y, REAL width, REAL height, REAL startAngle, REAL sweepAngle) { if(!brush) return InvalidParameter; return draw_pie(graphics, brush->gdibrush, GetStockObject(NULL_PEN), x, y, width, height, startAngle, sweepAngle); } /* 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; } /* 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 GdipRestoreGraphics(GpGraphics *graphics, GraphicsState state) { if(!graphics) return InvalidParameter; FIXME("graphics state not implemented\n"); return NotImplemented; } GpStatus WINGDIPAPI GdipSaveGraphics(GpGraphics *graphics, GraphicsState *state) { if(!graphics || !state) return InvalidParameter; FIXME("graphics state not implemented\n"); return NotImplemented; } 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 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; }