| /* |
| * 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 <stdarg.h> |
| #include <math.h> |
| |
| #include "windef.h" |
| #include "winbase.h" |
| #include "winerror.h" |
| #include "wine/debug.h" |
| #include "wingdi.h" |
| |
| #include "objbase.h" |
| |
| #include "winreg.h" |
| #include "shlwapi.h" |
| |
| #include "gdiplus.h" |
| #include "gdiplus_private.h" |
| |
| WINE_DEFAULT_DEBUG_CHANNEL(gdiplus); |
| |
| static const REAL mm_per_inch = 25.4; |
| static const REAL point_per_inch = 72.0; |
| |
| static Status WINAPI NotificationHook(ULONG_PTR *token) |
| { |
| TRACE("%p\n", token); |
| if(!token) |
| return InvalidParameter; |
| |
| return Ok; |
| } |
| |
| static void WINAPI NotificationUnhook(ULONG_PTR token) |
| { |
| TRACE("%ld\n", token); |
| } |
| |
| /***************************************************** |
| * DllMain |
| */ |
| BOOL WINAPI DllMain(HINSTANCE hinst, DWORD reason, LPVOID reserved) |
| { |
| TRACE("(%p, %d, %p)\n", hinst, reason, reserved); |
| |
| switch(reason) |
| { |
| case DLL_PROCESS_ATTACH: |
| DisableThreadLibraryCalls( hinst ); |
| init_generic_string_formats(); |
| break; |
| |
| case DLL_PROCESS_DETACH: |
| if (reserved) break; |
| free_installed_fonts(); |
| free_generic_string_formats(); |
| break; |
| } |
| return TRUE; |
| } |
| |
| /***************************************************** |
| * GdiplusStartup [GDIPLUS.@] |
| */ |
| Status WINAPI GdiplusStartup(ULONG_PTR *token, const struct GdiplusStartupInput *input, |
| struct GdiplusStartupOutput *output) |
| { |
| if(!token || !input) |
| return InvalidParameter; |
| |
| TRACE("%p %p %p\n", token, input, output); |
| TRACE("GdiplusStartupInput %d %p %d %d\n", input->GdiplusVersion, |
| input->DebugEventCallback, input->SuppressBackgroundThread, |
| input->SuppressExternalCodecs); |
| |
| if(input->GdiplusVersion < 1 || input->GdiplusVersion > 2) |
| return UnsupportedGdiplusVersion; |
| |
| if(input->SuppressBackgroundThread){ |
| if(!output) |
| return InvalidParameter; |
| |
| output->NotificationHook = NotificationHook; |
| output->NotificationUnhook = NotificationUnhook; |
| } |
| |
| *token = 0xdeadbeef; |
| |
| /* FIXME: DebugEventCallback ignored */ |
| |
| return Ok; |
| } |
| |
| GpStatus WINAPI GdiplusNotificationHook(ULONG_PTR *token) |
| { |
| FIXME("%p\n", token); |
| return NotificationHook(token); |
| } |
| |
| void WINAPI GdiplusNotificationUnhook(ULONG_PTR token) |
| { |
| FIXME("%ld\n", token); |
| NotificationUnhook(token); |
| } |
| |
| /***************************************************** |
| * GdiplusShutdown [GDIPLUS.@] |
| */ |
| ULONG WINAPI GdiplusShutdown_wrapper(ULONG_PTR token) |
| { |
| /* Notice the slightly different prototype from the official |
| * signature which forces us to use the _wrapper suffix. |
| */ |
| |
| /* FIXME: no object tracking */ |
| |
| /* "bricksntiles" expects a return value of 0, which native |
| * coincidentally gives. |
| */ |
| return 0; |
| } |
| |
| /***************************************************** |
| * GdipAlloc [GDIPLUS.@] |
| */ |
| void* WINGDIPAPI GdipAlloc(SIZE_T size) |
| { |
| return HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, size); |
| } |
| |
| /***************************************************** |
| * GdipFree [GDIPLUS.@] |
| */ |
| void WINGDIPAPI GdipFree(void* ptr) |
| { |
| HeapFree(GetProcessHeap(), 0, ptr); |
| } |
| |
| /* Calculates the bezier points needed to fill in the arc portion starting at |
| * angle start and ending at end. These two angles should be no more than 90 |
| * degrees from each other. x1, y1, x2, y2 describes the bounding box (upper |
| * left and width and height). Angles must be in radians. write_first indicates |
| * that the first bezier point should be written out (usually this is false). |
| * pt is the array of GpPointFs that gets written to. |
| **/ |
| static void add_arc_part(GpPointF * pt, REAL x1, REAL y1, REAL x2, REAL y2, |
| REAL start, REAL end, BOOL write_first) |
| { |
| REAL center_x, center_y, rad_x, rad_y, cos_start, cos_end, |
| sin_start, sin_end, a, half; |
| INT i; |
| |
| rad_x = x2 / 2.0; |
| rad_y = y2 / 2.0; |
| center_x = x1 + rad_x; |
| center_y = y1 + rad_y; |
| |
| cos_start = cos(start); |
| cos_end = cos(end); |
| sin_start = sin(start); |
| sin_end = sin(end); |
| |
| half = (end - start) / 2.0; |
| a = 4.0 / 3.0 * (1 - cos(half)) / sin(half); |
| |
| if(write_first){ |
| pt[0].X = cos_start; |
| pt[0].Y = sin_start; |
| } |
| pt[1].X = cos_start - a * sin_start; |
| pt[1].Y = sin_start + a * cos_start; |
| |
| pt[3].X = cos_end; |
| pt[3].Y = sin_end; |
| pt[2].X = cos_end + a * sin_end; |
| pt[2].Y = sin_end - a * cos_end; |
| |
| /* expand the points back from the unit circle to the ellipse */ |
| for(i = (write_first ? 0 : 1); i < 4; i ++){ |
| pt[i].X = pt[i].X * rad_x + center_x; |
| pt[i].Y = pt[i].Y * rad_y + center_y; |
| } |
| } |
| |
| /* We plot the curve as if it is on a circle then stretch the points. This |
| * adjusts the angles so that when we stretch the points they will end in the |
| * right place. This is only complicated because atan and atan2 do not behave |
| * conveniently. */ |
| static void unstretch_angle(REAL * angle, REAL rad_x, REAL rad_y) |
| { |
| REAL stretched; |
| INT revs_off; |
| |
| *angle = deg2rad(*angle); |
| |
| if(fabs(cos(*angle)) < 0.00001 || fabs(sin(*angle)) < 0.00001) |
| return; |
| |
| stretched = gdiplus_atan2(sin(*angle) / fabs(rad_y), cos(*angle) / fabs(rad_x)); |
| revs_off = gdip_round(*angle / (2.0 * M_PI)) - gdip_round(stretched / (2.0 * M_PI)); |
| stretched += ((REAL)revs_off) * M_PI * 2.0; |
| *angle = stretched; |
| } |
| |
| /* Stores the bezier points that correspond to the arc in points. If points is |
| * null, just return the number of points needed to represent the arc. */ |
| INT arc2polybezier(GpPointF * points, REAL x1, REAL y1, REAL x2, REAL y2, |
| REAL startAngle, REAL sweepAngle) |
| { |
| INT i; |
| REAL end_angle, start_angle, endAngle; |
| |
| endAngle = startAngle + sweepAngle; |
| unstretch_angle(&startAngle, x2 / 2.0, y2 / 2.0); |
| unstretch_angle(&endAngle, x2 / 2.0, y2 / 2.0); |
| |
| /* start_angle and end_angle are the iterative variables */ |
| start_angle = startAngle; |
| |
| for(i = 0; i < MAX_ARC_PTS - 1; i += 3){ |
| /* check if we've overshot the end angle */ |
| if( sweepAngle > 0.0 ) |
| { |
| if (start_angle >= endAngle) break; |
| end_angle = min(start_angle + M_PI_2, endAngle); |
| } |
| else |
| { |
| if (start_angle <= endAngle) break; |
| end_angle = max(start_angle - M_PI_2, endAngle); |
| } |
| |
| if (points) |
| add_arc_part(&points[i], x1, y1, x2, y2, start_angle, end_angle, i == 0); |
| |
| start_angle += M_PI_2 * (sweepAngle < 0.0 ? -1.0 : 1.0); |
| } |
| |
| if (i == 0) return 0; |
| else return i+1; |
| } |
| |
| COLORREF ARGB2COLORREF(ARGB color) |
| { |
| /* |
| Packing of these color structures: |
| COLORREF: 00bbggrr |
| ARGB: aarrggbb |
| FIXME:doesn't handle alpha channel |
| */ |
| return ((color & 0x0000ff) << 16) + |
| (color & 0x00ff00) + |
| ((color & 0xff0000) >> 16); |
| } |
| |
| HBITMAP ARGB2BMP(ARGB color) |
| { |
| BITMAPINFO bi; |
| HBITMAP result; |
| RGBQUAD *bits; |
| int alpha; |
| |
| if ((color & 0xff000000) == 0xff000000) return 0; |
| |
| bi.bmiHeader.biSize = sizeof(bi.bmiHeader); |
| bi.bmiHeader.biWidth = 1; |
| bi.bmiHeader.biHeight = 1; |
| bi.bmiHeader.biPlanes = 1; |
| bi.bmiHeader.biBitCount = 32; |
| bi.bmiHeader.biCompression = BI_RGB; |
| bi.bmiHeader.biSizeImage = 0; |
| bi.bmiHeader.biXPelsPerMeter = 0; |
| bi.bmiHeader.biYPelsPerMeter = 0; |
| bi.bmiHeader.biClrUsed = 0; |
| bi.bmiHeader.biClrImportant = 0; |
| |
| result = CreateDIBSection(0, &bi, DIB_RGB_COLORS, (void*)&bits, NULL, 0); |
| |
| bits[0].rgbReserved = alpha = (color>>24)&0xff; |
| bits[0].rgbRed = ((color>>16)&0xff)*alpha/255; |
| bits[0].rgbGreen = ((color>>8)&0xff)*alpha/255; |
| bits[0].rgbBlue = (color&0xff)*alpha/255; |
| |
| return result; |
| } |
| |
| /* Like atan2, but puts angle in correct quadrant if dx is 0. */ |
| REAL gdiplus_atan2(REAL dy, REAL dx) |
| { |
| if((dx == 0.0) && (dy != 0.0)) |
| return dy > 0.0 ? M_PI_2 : -M_PI_2; |
| |
| return atan2(dy, dx); |
| } |
| |
| GpStatus hresult_to_status(HRESULT res) |
| { |
| switch(res){ |
| case S_OK: |
| return Ok; |
| case E_OUTOFMEMORY: |
| return OutOfMemory; |
| case E_INVALIDARG: |
| return InvalidParameter; |
| default: |
| return GenericError; |
| } |
| } |
| |
| /* converts a given unit to its value in pixels */ |
| REAL units_to_pixels(REAL units, GpUnit unit, REAL dpi) |
| { |
| switch (unit) |
| { |
| case UnitPixel: |
| case UnitWorld: |
| case UnitDisplay: |
| return units; |
| case UnitPoint: |
| return units * dpi / point_per_inch; |
| case UnitInch: |
| return units * dpi; |
| case UnitDocument: |
| return units * dpi / 300.0; /* Per MSDN */ |
| case UnitMillimeter: |
| return units * dpi / mm_per_inch; |
| default: |
| FIXME("Unhandled unit type: %d\n", unit); |
| return 0; |
| } |
| } |
| |
| /* converts value in pixels to a given unit */ |
| REAL pixels_to_units(REAL pixels, GpUnit unit, REAL dpi) |
| { |
| switch (unit) |
| { |
| case UnitPixel: |
| case UnitWorld: |
| case UnitDisplay: |
| return pixels; |
| case UnitPoint: |
| return pixels * point_per_inch / dpi; |
| case UnitInch: |
| return pixels / dpi; |
| case UnitDocument: |
| return pixels * 300.0 / dpi; |
| case UnitMillimeter: |
| return pixels * mm_per_inch / dpi; |
| default: |
| FIXME("Unhandled unit type: %d\n", unit); |
| return 0; |
| } |
| } |
| |
| REAL units_scale(GpUnit from, GpUnit to, REAL dpi) |
| { |
| REAL pixels = units_to_pixels(1.0, from, dpi); |
| return pixels_to_units(pixels, to, dpi); |
| } |
| |
| /* Calculates Bezier points from cardinal spline points. */ |
| 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; |
| } |
| |
| /* Calculates Bezier points from cardinal spline endpoints. */ |
| 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 = gdip_round(tension * (xadj - xend) + xend); |
| *y = gdip_round(tension * (yadj - yend) + yend); |
| } |
| |
| /* make sure path has enough space for len more points */ |
| BOOL lengthen_path(GpPath *path, INT len) |
| { |
| /* initial allocation */ |
| if(path->datalen == 0){ |
| path->datalen = len * 2; |
| |
| path->pathdata.Points = heap_alloc_zero(path->datalen * sizeof(PointF)); |
| if(!path->pathdata.Points) return FALSE; |
| |
| path->pathdata.Types = heap_alloc_zero(path->datalen); |
| if(!path->pathdata.Types){ |
| heap_free(path->pathdata.Points); |
| return FALSE; |
| } |
| } |
| /* reallocation, double size of arrays */ |
| else if(path->datalen - path->pathdata.Count < len){ |
| while(path->datalen - path->pathdata.Count < len) |
| path->datalen *= 2; |
| |
| path->pathdata.Points = heap_realloc(path->pathdata.Points, path->datalen * sizeof(PointF)); |
| if(!path->pathdata.Points) return FALSE; |
| |
| path->pathdata.Types = heap_realloc(path->pathdata.Types, path->datalen); |
| if(!path->pathdata.Types) return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| void convert_32bppARGB_to_32bppPARGB(UINT width, UINT height, |
| BYTE *dst_bits, INT dst_stride, const BYTE *src_bits, INT src_stride) |
| { |
| INT x, y; |
| for (y=0; y<height; y++) |
| { |
| const BYTE *src=src_bits+y*src_stride; |
| BYTE *dst=dst_bits+y*dst_stride; |
| for (x=0; x<width; x++) |
| { |
| BYTE alpha=src[3]; |
| *dst++ = (*src++ * alpha + 127) / 255; |
| *dst++ = (*src++ * alpha + 127) / 255; |
| *dst++ = (*src++ * alpha + 127) / 255; |
| *dst++ = *src++; |
| } |
| } |
| } |
| |
| /* recursive deletion of GpRegion nodes */ |
| void delete_element(region_element* element) |
| { |
| switch(element->type) |
| { |
| case RegionDataRect: |
| break; |
| case RegionDataPath: |
| GdipDeletePath(element->elementdata.path); |
| break; |
| case RegionDataEmptyRect: |
| case RegionDataInfiniteRect: |
| break; |
| default: |
| delete_element(element->elementdata.combine.left); |
| delete_element(element->elementdata.combine.right); |
| heap_free(element->elementdata.combine.left); |
| heap_free(element->elementdata.combine.right); |
| break; |
| } |
| } |
| |
| const char *debugstr_rectf(const RectF* rc) |
| { |
| if (!rc) return "(null)"; |
| return wine_dbg_sprintf("(%0.2f,%0.2f,%0.2f,%0.2f)", rc->X, rc->Y, rc->Width, rc->Height); |
| } |
| |
| const char *debugstr_pointf(const PointF* pt) |
| { |
| if (!pt) return "(null)"; |
| return wine_dbg_sprintf("(%0.2f,%0.2f)", pt->X, pt->Y); |
| } |