/* * Copyright (C) 2008 Google (Lei Zhang) * * 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 "windef.h" #include "winbase.h" #include "wingdi.h" #include "objbase.h" #include "gdiplus.h" #include "gdiplus_private.h" #include "wine/debug.h" WINE_DEFAULT_DEBUG_CHANNEL(gdiplus); /********************************************************** * * Data returned by GdipGetRegionData looks something like this: * * struct region_data_header * { * DWORD size; size in bytes of the data - 8. * DWORD magic1; probably a checksum. * DWORD magic2; always seems to be 0xdbc01001 - version? * DWORD num_ops; number of combining ops * 2 * }; * * Then follows a sequence of combining ops and region elements. * * A region element is either a RECTF or some path data. * * Combining ops are just stored as their CombineMode value. * * Each RECTF is preceded by the DWORD 0x10000000. An empty rect is * stored as 0x10000002 (with no following RECTF) and an infinite rect * is stored as 0x10000003 (again with no following RECTF). * * Path data is preceded by the DWORD 0x10000001. Then follows a * DWORD size and then size bytes of data. * * The combining ops are stored in the reverse order to the region * elements and in the reverse order to which the region was * constructed. * * When two or more complex regions (ie those with more than one * element) are combined, the combining op for the two regions comes * first, then the combining ops for the region elements in region 1, * followed by the region elements for region 1, then follows the * combining ops for region 2 and finally region 2's region elements. * Presumably you're supposed to use the 0x1000000x header to find the * end of the op list (the count of the elements in each region is not * stored). * * When a simple region (1 element) is combined, it's treated as if a * single rect/path is being combined. * */ #define FLAGS_NOFLAGS 0x0 #define FLAGS_INTPATH 0x4000 /* Header size as far as header->size is concerned. This doesn't include * header->size or header->checksum */ static const INT sizeheader_size = sizeof(DWORD) * 2; typedef struct packed_point { short X; short Y; } packed_point; /* Everything is measured in DWORDS; round up if there's a remainder */ static inline INT get_pathtypes_size(const GpPath* path) { INT needed = path->pathdata.Count / sizeof(DWORD); if (path->pathdata.Count % sizeof(DWORD) > 0) needed++; return needed * sizeof(DWORD); } static inline INT get_element_size(const region_element* element) { INT needed = sizeof(DWORD); /* DWORD for the type */ switch(element->type) { case RegionDataRect: return needed + sizeof(GpRect); case RegionDataPath: needed += element->elementdata.pathdata.pathheader.size; needed += sizeof(DWORD); /* Extra DWORD for pathheader.size */ return needed; case RegionDataEmptyRect: case RegionDataInfiniteRect: return needed; default: needed += get_element_size(element->elementdata.combine.left); needed += get_element_size(element->elementdata.combine.right); return needed; } return 0; } /* Does not check parameters, caller must do that */ static inline GpStatus init_region(GpRegion* region, const RegionType type) { region->node.type = type; region->header.checksum = 0xdeadbeef; region->header.magic = VERSION_MAGIC; region->header.num_children = 0; region->header.size = sizeheader_size + get_element_size(®ion->node); return Ok; } static inline GpStatus clone_element(const region_element* element, region_element** element2) { GpStatus stat; /* root node is allocated with GpRegion */ if(!*element2){ *element2 = GdipAlloc(sizeof(region_element)); if (!*element2) return OutOfMemory; } (*element2)->type = element->type; switch (element->type) { case RegionDataRect: (*element2)->elementdata.rect = element->elementdata.rect; return Ok; case RegionDataEmptyRect: case RegionDataInfiniteRect: return Ok; case RegionDataPath: (*element2)->elementdata.pathdata.pathheader = element->elementdata.pathdata.pathheader; stat = GdipClonePath(element->elementdata.pathdata.path, &(*element2)->elementdata.pathdata.path); if (stat == Ok) return Ok; break; default: (*element2)->elementdata.combine.left = NULL; (*element2)->elementdata.combine.right = NULL; stat = clone_element(element->elementdata.combine.left, &(*element2)->elementdata.combine.left); if (stat == Ok) { stat = clone_element(element->elementdata.combine.right, &(*element2)->elementdata.combine.right); if (stat == Ok) return Ok; } break; } delete_element(*element2); *element2 = NULL; return stat; } /* Common code for CombineRegion* * All the caller has to do is get its format into an element */ static inline void fuse_region(GpRegion* region, region_element* left, region_element* right, const CombineMode mode) { region->node.type = mode; region->node.elementdata.combine.left = left; region->node.elementdata.combine.right = right; region->header.size = sizeheader_size + get_element_size(®ion->node); region->header.num_children += 2; } /***************************************************************************** * GdipCloneRegion [GDIPLUS.@] * * Creates a deep copy of the region * * PARAMS * region [I] source region * clone [O] resulting clone * * RETURNS * SUCCESS: Ok * FAILURE: InvalidParameter or OutOfMemory */ GpStatus WINGDIPAPI GdipCloneRegion(GpRegion *region, GpRegion **clone) { region_element *element; TRACE("%p %p\n", region, clone); if (!(region && clone)) return InvalidParameter; *clone = GdipAlloc(sizeof(GpRegion)); if (!*clone) return OutOfMemory; element = &(*clone)->node; (*clone)->header = region->header; return clone_element(®ion->node, &element); } /***************************************************************************** * GdipCombineRegionPath [GDIPLUS.@] */ GpStatus WINGDIPAPI GdipCombineRegionPath(GpRegion *region, GpPath *path, CombineMode mode) { GpRegion *path_region; region_element *left, *right = NULL; GpStatus stat; TRACE("%p %p %d\n", region, path, mode); if (!(region && path)) return InvalidParameter; stat = GdipCreateRegionPath(path, &path_region); if (stat != Ok) return stat; /* simply replace region data */ if(mode == CombineModeReplace){ delete_element(®ion->node); memcpy(region, path_region, sizeof(GpRegion)); GdipFree(path_region); return Ok; } left = GdipAlloc(sizeof(region_element)); if (left) { *left = region->node; stat = clone_element(&path_region->node, &right); if (stat == Ok) { fuse_region(region, left, right, mode); GdipDeleteRegion(path_region); return Ok; } } else stat = OutOfMemory; GdipFree(left); GdipDeleteRegion(path_region); return stat; } /***************************************************************************** * GdipCombineRegionRect [GDIPLUS.@] */ GpStatus WINGDIPAPI GdipCombineRegionRect(GpRegion *region, GDIPCONST GpRectF *rect, CombineMode mode) { GpRegion *rect_region; region_element *left, *right = NULL; GpStatus stat; TRACE("%p %p %d\n", region, rect, mode); if (!(region && rect)) return InvalidParameter; stat = GdipCreateRegionRect(rect, &rect_region); if (stat != Ok) return stat; /* simply replace region data */ if(mode == CombineModeReplace){ delete_element(®ion->node); memcpy(region, rect_region, sizeof(GpRegion)); GdipFree(rect_region); return Ok; } left = GdipAlloc(sizeof(region_element)); if (left) { memcpy(left, ®ion->node, sizeof(region_element)); stat = clone_element(&rect_region->node, &right); if (stat == Ok) { fuse_region(region, left, right, mode); GdipDeleteRegion(rect_region); return Ok; } } else stat = OutOfMemory; GdipFree(left); GdipDeleteRegion(rect_region); return stat; } /***************************************************************************** * GdipCombineRegionRectI [GDIPLUS.@] */ GpStatus WINGDIPAPI GdipCombineRegionRectI(GpRegion *region, GDIPCONST GpRect *rect, CombineMode mode) { GpRectF rectf; TRACE("%p %p %d\n", region, rect, mode); if (!rect) return InvalidParameter; rectf.X = (REAL)rect->X; rectf.Y = (REAL)rect->Y; rectf.Height = (REAL)rect->Height; rectf.Width = (REAL)rect->Width; return GdipCombineRegionRect(region, &rectf, mode); } /***************************************************************************** * GdipCombineRegionRegion [GDIPLUS.@] */ GpStatus WINGDIPAPI GdipCombineRegionRegion(GpRegion *region1, GpRegion *region2, CombineMode mode) { region_element *left, *right = NULL; GpStatus stat; GpRegion *reg2copy; TRACE("%p %p %d\n", region1, region2, mode); if(!(region1 && region2)) return InvalidParameter; /* simply replace region data */ if(mode == CombineModeReplace){ stat = GdipCloneRegion(region2, ®2copy); if(stat != Ok) return stat; delete_element(®ion1->node); memcpy(region1, reg2copy, sizeof(GpRegion)); GdipFree(reg2copy); return Ok; } left = GdipAlloc(sizeof(region_element)); if (!left) return OutOfMemory; *left = region1->node; stat = clone_element(®ion2->node, &right); if (stat != Ok) { GdipFree(left); return OutOfMemory; } fuse_region(region1, left, right, mode); region1->header.num_children += region2->header.num_children; return Ok; } /***************************************************************************** * GdipCreateRegion [GDIPLUS.@] */ GpStatus WINGDIPAPI GdipCreateRegion(GpRegion **region) { TRACE("%p\n", region); if(!region) return InvalidParameter; *region = GdipAlloc(sizeof(GpRegion)); if(!*region) return OutOfMemory; TRACE("=> %p\n", *region); return init_region(*region, RegionDataInfiniteRect); } /***************************************************************************** * GdipCreateRegionPath [GDIPLUS.@] * * Creates a GpRegion from a GpPath * * PARAMS * path [I] path to base the region on * region [O] pointer to the newly allocated region * * RETURNS * SUCCESS: Ok * FAILURE: InvalidParameter * * NOTES * If a path has no floating point points, its points will be stored as shorts * (INTPATH) * * If a path is empty, it is considered to be an INTPATH */ GpStatus WINGDIPAPI GdipCreateRegionPath(GpPath *path, GpRegion **region) { region_element* element; GpPoint *pointsi; GpPointF *pointsf; GpStatus stat; DWORD flags = FLAGS_INTPATH; INT count, i; TRACE("%p, %p\n", path, region); if (!(path && region)) return InvalidParameter; *region = GdipAlloc(sizeof(GpRegion)); if(!*region) return OutOfMemory; stat = init_region(*region, RegionDataPath); if (stat != Ok) { GdipDeleteRegion(*region); return stat; } element = &(*region)->node; count = path->pathdata.Count; /* Test to see if the path is an Integer path */ if (count) { pointsi = GdipAlloc(sizeof(GpPoint) * count); pointsf = GdipAlloc(sizeof(GpPointF) * count); if (!(pointsi && pointsf)) { GdipFree(pointsi); GdipFree(pointsf); GdipDeleteRegion(*region); return OutOfMemory; } stat = GdipGetPathPointsI(path, pointsi, count); if (stat != Ok) { GdipDeleteRegion(*region); return stat; } stat = GdipGetPathPoints(path, pointsf, count); if (stat != Ok) { GdipDeleteRegion(*region); return stat; } for (i = 0; i < count; i++) { if (!(pointsi[i].X == pointsf[i].X && pointsi[i].Y == pointsf[i].Y )) { flags = FLAGS_NOFLAGS; break; } } GdipFree(pointsi); GdipFree(pointsf); } stat = GdipClonePath(path, &element->elementdata.pathdata.path); if (stat != Ok) { GdipDeleteRegion(*region); return stat; } /* 3 for headers, once again size doesn't count itself */ element->elementdata.pathdata.pathheader.size = ((sizeof(DWORD) * 3)); switch(flags) { /* Floats, sent out as floats */ case FLAGS_NOFLAGS: element->elementdata.pathdata.pathheader.size += (sizeof(DWORD) * count * 2); break; /* INTs, sent out as packed shorts */ case FLAGS_INTPATH: element->elementdata.pathdata.pathheader.size += (sizeof(DWORD) * count); break; default: FIXME("Unhandled flags (%08x). Expect wrong results.\n", flags); } element->elementdata.pathdata.pathheader.size += get_pathtypes_size(path); element->elementdata.pathdata.pathheader.magic = VERSION_MAGIC; element->elementdata.pathdata.pathheader.count = count; element->elementdata.pathdata.pathheader.flags = flags; (*region)->header.size = sizeheader_size + get_element_size(element); return Ok; } /***************************************************************************** * GdipCreateRegionRect [GDIPLUS.@] */ GpStatus WINGDIPAPI GdipCreateRegionRect(GDIPCONST GpRectF *rect, GpRegion **region) { GpStatus stat; TRACE("%p, %p\n", rect, region); if (!(rect && region)) return InvalidParameter; *region = GdipAlloc(sizeof(GpRegion)); stat = init_region(*region, RegionDataRect); if(stat != Ok) { GdipDeleteRegion(*region); return stat; } (*region)->node.elementdata.rect.X = rect->X; (*region)->node.elementdata.rect.Y = rect->Y; (*region)->node.elementdata.rect.Width = rect->Width; (*region)->node.elementdata.rect.Height = rect->Height; return Ok; } /***************************************************************************** * GdipCreateRegionRectI [GDIPLUS.@] */ GpStatus WINGDIPAPI GdipCreateRegionRectI(GDIPCONST GpRect *rect, GpRegion **region) { GpRectF rectf; TRACE("%p, %p\n", rect, region); rectf.X = (REAL)rect->X; rectf.Y = (REAL)rect->Y; rectf.Width = (REAL)rect->Width; rectf.Height = (REAL)rect->Height; return GdipCreateRegionRect(&rectf, region); } GpStatus WINGDIPAPI GdipCreateRegionRgnData(GDIPCONST BYTE *data, INT size, GpRegion **region) { FIXME("(%p, %d, %p): stub\n", data, size, region); *region = NULL; return NotImplemented; } /****************************************************************************** * GdipCreateRegionHrgn [GDIPLUS.@] */ GpStatus WINGDIPAPI GdipCreateRegionHrgn(HRGN hrgn, GpRegion **region) { DWORD size; LPRGNDATA buf; LPRECT rect; GpStatus stat; GpPath* path; GpRegion* local; int i; TRACE("(%p, %p)\n", hrgn, region); if(!region || !(size = GetRegionData(hrgn, 0, NULL))) return InvalidParameter; buf = GdipAlloc(size); if(!buf) return OutOfMemory; if(!GetRegionData(hrgn, size, buf)){ GdipFree(buf); return GenericError; } if(buf->rdh.nCount == 0){ if((stat = GdipCreateRegion(&local)) != Ok){ GdipFree(buf); return stat; } if((stat = GdipSetEmpty(local)) != Ok){ GdipFree(buf); GdipDeleteRegion(local); return stat; } *region = local; GdipFree(buf); return Ok; } if((stat = GdipCreatePath(FillModeAlternate, &path)) != Ok){ GdipFree(buf); return stat; } rect = (LPRECT)buf->Buffer; for(i = 0; i < buf->rdh.nCount; i++){ if((stat = GdipAddPathRectangle(path, (REAL)rect->left, (REAL)rect->top, (REAL)(rect->right - rect->left), (REAL)(rect->bottom - rect->top))) != Ok){ GdipFree(buf); GdipDeletePath(path); return stat; } rect++; } stat = GdipCreateRegionPath(path, region); GdipFree(buf); GdipDeletePath(path); return stat; } /***************************************************************************** * GdipDeleteRegion [GDIPLUS.@] */ GpStatus WINGDIPAPI GdipDeleteRegion(GpRegion *region) { TRACE("%p\n", region); if (!region) return InvalidParameter; delete_element(®ion->node); GdipFree(region); return Ok; } /***************************************************************************** * GdipGetRegionBounds [GDIPLUS.@] */ GpStatus WINGDIPAPI GdipGetRegionBounds(GpRegion *region, GpGraphics *graphics, GpRectF *rect) { HRGN hrgn; RECT r; GpStatus status; TRACE("(%p, %p, %p)\n", region, graphics, rect); if(!region || !graphics || !rect) return InvalidParameter; /* Contrary to MSDN, native ignores the graphics transform. */ status = GdipGetRegionHRgn(region, NULL, &hrgn); if(status != Ok) return status; /* infinite */ if(!hrgn){ rect->X = rect->Y = -(REAL)(1 << 22); rect->Width = rect->Height = (REAL)(1 << 23); TRACE("%p => infinite\n", region); return Ok; } if(GetRgnBox(hrgn, &r)){ rect->X = r.left; rect->Y = r.top; rect->Width = r.right - r.left; rect->Height = r.bottom - r.top; TRACE("%p => %s\n", region, debugstr_rectf(rect)); } else status = GenericError; DeleteObject(hrgn); return status; } /***************************************************************************** * GdipGetRegionBoundsI [GDIPLUS.@] */ GpStatus WINGDIPAPI GdipGetRegionBoundsI(GpRegion *region, GpGraphics *graphics, GpRect *rect) { GpRectF rectf; GpStatus status; TRACE("(%p, %p, %p)\n", region, graphics, rect); if(!rect) return InvalidParameter; status = GdipGetRegionBounds(region, graphics, &rectf); if(status == Ok){ rect->X = gdip_round(rectf.X); rect->Y = gdip_round(rectf.X); rect->Width = gdip_round(rectf.Width); rect->Height = gdip_round(rectf.Height); } return status; } static inline void write_dword(DWORD* location, INT* offset, const DWORD write) { location[*offset] = write; (*offset)++; } static inline void write_float(DWORD* location, INT* offset, const FLOAT write) { ((FLOAT*)location)[*offset] = write; (*offset)++; } static inline void write_packed_point(DWORD* location, INT* offset, const GpPointF* write) { packed_point point; point.X = write->X; point.Y = write->Y; memcpy(location + *offset, &point, sizeof(packed_point)); (*offset)++; } static inline void write_path_types(DWORD* location, INT* offset, const GpPath* path) { memcpy(location + *offset, path->pathdata.Types, path->pathdata.Count); /* The unwritten parts of the DWORD (if any) must be cleared */ if (path->pathdata.Count % sizeof(DWORD)) ZeroMemory(((BYTE*)location) + (*offset * sizeof(DWORD)) + path->pathdata.Count, sizeof(DWORD) - path->pathdata.Count % sizeof(DWORD)); *offset += (get_pathtypes_size(path) / sizeof(DWORD)); } static void write_element(const region_element* element, DWORD *buffer, INT* filled) { write_dword(buffer, filled, element->type); switch (element->type) { case CombineModeReplace: case CombineModeIntersect: case CombineModeUnion: case CombineModeXor: case CombineModeExclude: case CombineModeComplement: write_element(element->elementdata.combine.left, buffer, filled); write_element(element->elementdata.combine.right, buffer, filled); break; case RegionDataRect: write_float(buffer, filled, element->elementdata.rect.X); write_float(buffer, filled, element->elementdata.rect.Y); write_float(buffer, filled, element->elementdata.rect.Width); write_float(buffer, filled, element->elementdata.rect.Height); break; case RegionDataPath: { INT i; const GpPath* path = element->elementdata.pathdata.path; memcpy(buffer + *filled, &element->elementdata.pathdata.pathheader, sizeof(element->elementdata.pathdata.pathheader)); *filled += sizeof(element->elementdata.pathdata.pathheader) / sizeof(DWORD); switch (element->elementdata.pathdata.pathheader.flags) { case FLAGS_NOFLAGS: for (i = 0; i < path->pathdata.Count; i++) { write_float(buffer, filled, path->pathdata.Points[i].X); write_float(buffer, filled, path->pathdata.Points[i].Y); } break; case FLAGS_INTPATH: for (i = 0; i < path->pathdata.Count; i++) { write_packed_point(buffer, filled, &path->pathdata.Points[i]); } } write_path_types(buffer, filled, path); break; } case RegionDataEmptyRect: case RegionDataInfiniteRect: break; } } /***************************************************************************** * GdipGetRegionData [GDIPLUS.@] * * Returns the header, followed by combining ops and region elements. * * PARAMS * region [I] region to retrieve from * buffer [O] buffer to hold the resulting data * size [I] size of the buffer * needed [O] (optional) how much data was written * * RETURNS * SUCCESS: Ok * FAILURE: InvalidParameter * * NOTES * The header contains the size, a checksum, a version string, and the number * of children. The size does not count itself or the checksum. * Version is always something like 0xdbc01001 or 0xdbc01002 * * An element is a RECT, or PATH; Combining ops are stored as their * CombineMode value. Special regions (infinite, empty) emit just their * op-code; GpRectFs emit their code followed by their points; GpPaths emit * their code followed by a second header for the path followed by the actual * path data. Followed by the flags for each point. The pathheader contains * the size of the data to follow, a version number again, followed by a count * of how many points, and any special flags which may apply. 0x4000 means its * a path of shorts instead of FLOAT. * * Combining Ops are stored in reverse order from when they were constructed; * the output is a tree where the left side combining area is always taken * first. */ GpStatus WINGDIPAPI GdipGetRegionData(GpRegion *region, BYTE *buffer, UINT size, UINT *needed) { INT filled = 0; TRACE("%p, %p, %d, %p\n", region, buffer, size, needed); if (!(region && buffer && size)) return InvalidParameter; memcpy(buffer, ®ion->header, sizeof(region->header)); filled += sizeof(region->header) / sizeof(DWORD); /* With few exceptions, everything written is DWORD aligned, * so use that as our base */ write_element(®ion->node, (DWORD*)buffer, &filled); if (needed) *needed = filled * sizeof(DWORD); return Ok; } /***************************************************************************** * GdipGetRegionDataSize [GDIPLUS.@] */ GpStatus WINGDIPAPI GdipGetRegionDataSize(GpRegion *region, UINT *needed) { TRACE("%p, %p\n", region, needed); if (!(region && needed)) return InvalidParameter; /* header.size doesn't count header.size and header.checksum */ *needed = region->header.size + sizeof(DWORD) * 2; return Ok; } static GpStatus get_path_hrgn(GpPath *path, GpGraphics *graphics, HRGN *hrgn) { HDC new_hdc=NULL; GpGraphics *new_graphics=NULL; GpStatus stat; INT save_state; if (!graphics) { new_hdc = GetDC(0); if (!new_hdc) return OutOfMemory; stat = GdipCreateFromHDC(new_hdc, &new_graphics); graphics = new_graphics; if (stat != Ok) { ReleaseDC(0, new_hdc); return stat; } } else if (!graphics->hdc) { graphics->hdc = new_hdc = GetDC(0); if (!new_hdc) return OutOfMemory; } save_state = SaveDC(graphics->hdc); EndPath(graphics->hdc); SetPolyFillMode(graphics->hdc, (path->fill == FillModeAlternate ? ALTERNATE : WINDING)); stat = trace_path(graphics, path); if (stat == Ok) { *hrgn = PathToRegion(graphics->hdc); stat = *hrgn ? Ok : OutOfMemory; } RestoreDC(graphics->hdc, save_state); if (new_hdc) { ReleaseDC(0, new_hdc); if (new_graphics) GdipDeleteGraphics(new_graphics); else graphics->hdc = NULL; } return stat; } static GpStatus get_region_hrgn(struct region_element *element, GpGraphics *graphics, HRGN *hrgn) { switch (element->type) { case RegionDataInfiniteRect: *hrgn = NULL; return Ok; case RegionDataEmptyRect: *hrgn = CreateRectRgn(0, 0, 0, 0); return *hrgn ? Ok : OutOfMemory; case RegionDataPath: return get_path_hrgn(element->elementdata.pathdata.path, graphics, hrgn); case RegionDataRect: { GpPath* path; GpStatus stat; GpRectF* rc = &element->elementdata.rect; stat = GdipCreatePath(FillModeAlternate, &path); if (stat != Ok) return stat; stat = GdipAddPathRectangle(path, rc->X, rc->Y, rc->Width, rc->Height); if (stat == Ok) stat = get_path_hrgn(path, graphics, hrgn); GdipDeletePath(path); return stat; } case CombineModeIntersect: case CombineModeUnion: case CombineModeXor: case CombineModeExclude: case CombineModeComplement: { HRGN left, right; GpStatus stat; int ret; stat = get_region_hrgn(element->elementdata.combine.left, graphics, &left); if (stat != Ok) { *hrgn = NULL; return stat; } if (left == NULL) { /* existing region is infinite */ switch (element->type) { case CombineModeIntersect: return get_region_hrgn(element->elementdata.combine.right, graphics, hrgn); case CombineModeXor: case CombineModeExclude: FIXME("cannot exclude from an infinite region\n"); /* fall-through */ case CombineModeUnion: case CombineModeComplement: *hrgn = NULL; return Ok; } } stat = get_region_hrgn(element->elementdata.combine.right, graphics, &right); if (stat != Ok) { DeleteObject(left); *hrgn = NULL; return stat; } if (right == NULL) { /* new region is infinite */ switch (element->type) { case CombineModeIntersect: *hrgn = left; return Ok; case CombineModeXor: case CombineModeComplement: FIXME("cannot exclude from an infinite region\n"); /* fall-through */ case CombineModeUnion: case CombineModeExclude: DeleteObject(left); *hrgn = NULL; return Ok; } } switch (element->type) { case CombineModeIntersect: ret = CombineRgn(left, left, right, RGN_AND); break; case CombineModeUnion: ret = CombineRgn(left, left, right, RGN_OR); break; case CombineModeXor: ret = CombineRgn(left, left, right, RGN_XOR); break; case CombineModeExclude: ret = CombineRgn(left, left, right, RGN_DIFF); break; case CombineModeComplement: ret = CombineRgn(left, right, left, RGN_DIFF); break; default: ret = ERROR; } DeleteObject(right); if (ret == ERROR) { DeleteObject(left); *hrgn = NULL; return GenericError; } *hrgn = left; return Ok; } default: FIXME("GdipGetRegionHRgn unimplemented for region type=%x\n", element->type); *hrgn = NULL; return NotImplemented; } } /***************************************************************************** * GdipGetRegionHRgn [GDIPLUS.@] */ GpStatus WINGDIPAPI GdipGetRegionHRgn(GpRegion *region, GpGraphics *graphics, HRGN *hrgn) { TRACE("(%p, %p, %p)\n", region, graphics, hrgn); if (!region || !hrgn) return InvalidParameter; return get_region_hrgn(®ion->node, graphics, hrgn); } GpStatus WINGDIPAPI GdipIsEmptyRegion(GpRegion *region, GpGraphics *graphics, BOOL *res) { GpStatus status; GpRectF rect; TRACE("(%p, %p, %p)\n", region, graphics, res); if(!region || !graphics || !res) return InvalidParameter; status = GdipGetRegionBounds(region, graphics, &rect); if (status != Ok) return status; *res = rect.Width == 0.0 && rect.Height == 0.0; TRACE("=> %d\n", *res); return Ok; } /***************************************************************************** * GdipIsEqualRegion [GDIPLUS.@] */ GpStatus WINGDIPAPI GdipIsEqualRegion(GpRegion *region, GpRegion *region2, GpGraphics *graphics, BOOL *res) { HRGN hrgn1, hrgn2; GpStatus stat; TRACE("(%p, %p, %p, %p)\n", region, region2, graphics, res); if(!region || !region2 || !graphics || !res) return InvalidParameter; stat = GdipGetRegionHRgn(region, graphics, &hrgn1); if(stat != Ok) return stat; stat = GdipGetRegionHRgn(region2, graphics, &hrgn2); if(stat != Ok){ DeleteObject(hrgn1); return stat; } *res = EqualRgn(hrgn1, hrgn2); /* one of GpRegions is infinite */ if(*res == ERROR) *res = (!hrgn1 && !hrgn2); DeleteObject(hrgn1); DeleteObject(hrgn2); return Ok; } /***************************************************************************** * GdipIsInfiniteRegion [GDIPLUS.@] */ GpStatus WINGDIPAPI GdipIsInfiniteRegion(GpRegion *region, GpGraphics *graphics, BOOL *res) { /* I think graphics is ignored here */ TRACE("(%p, %p, %p)\n", region, graphics, res); if(!region || !graphics || !res) return InvalidParameter; *res = (region->node.type == RegionDataInfiniteRect); return Ok; } /***************************************************************************** * GdipIsVisibleRegionRect [GDIPLUS.@] */ GpStatus WINGDIPAPI GdipIsVisibleRegionRect(GpRegion* region, REAL x, REAL y, REAL w, REAL h, GpGraphics *graphics, BOOL *res) { HRGN hrgn; GpStatus stat; RECT rect; TRACE("(%p, %.2f, %.2f, %.2f, %.2f, %p, %p)\n", region, x, y, w, h, graphics, res); if(!region || !res) return InvalidParameter; if((stat = GdipGetRegionHRgn(region, NULL, &hrgn)) != Ok) return stat; /* infinite */ if(!hrgn){ *res = TRUE; return Ok; } rect.left = ceilr(x); rect.top = ceilr(y); rect.right = ceilr(x + w); rect.bottom = ceilr(y + h); *res = RectInRegion(hrgn, &rect); DeleteObject(hrgn); return Ok; } /***************************************************************************** * GdipIsVisibleRegionRectI [GDIPLUS.@] */ GpStatus WINGDIPAPI GdipIsVisibleRegionRectI(GpRegion* region, INT x, INT y, INT w, INT h, GpGraphics *graphics, BOOL *res) { TRACE("(%p, %d, %d, %d, %d, %p, %p)\n", region, x, y, w, h, graphics, res); if(!region || !res) return InvalidParameter; return GdipIsVisibleRegionRect(region, (REAL)x, (REAL)y, (REAL)w, (REAL)h, graphics, res); } /***************************************************************************** * GdipIsVisibleRegionPoint [GDIPLUS.@] */ GpStatus WINGDIPAPI GdipIsVisibleRegionPoint(GpRegion* region, REAL x, REAL y, GpGraphics *graphics, BOOL *res) { HRGN hrgn; GpStatus stat; TRACE("(%p, %.2f, %.2f, %p, %p)\n", region, x, y, graphics, res); if(!region || !res) return InvalidParameter; if((stat = GdipGetRegionHRgn(region, NULL, &hrgn)) != Ok) return stat; /* infinite */ if(!hrgn){ *res = TRUE; return Ok; } *res = PtInRegion(hrgn, gdip_round(x), gdip_round(y)); DeleteObject(hrgn); return Ok; } /***************************************************************************** * GdipIsVisibleRegionPointI [GDIPLUS.@] */ GpStatus WINGDIPAPI GdipIsVisibleRegionPointI(GpRegion* region, INT x, INT y, GpGraphics *graphics, BOOL *res) { TRACE("(%p, %d, %d, %p, %p)\n", region, x, y, graphics, res); return GdipIsVisibleRegionPoint(region, (REAL)x, (REAL)y, graphics, res); } /***************************************************************************** * GdipSetEmpty [GDIPLUS.@] */ GpStatus WINGDIPAPI GdipSetEmpty(GpRegion *region) { GpStatus stat; TRACE("%p\n", region); if (!region) return InvalidParameter; delete_element(®ion->node); stat = init_region(region, RegionDataEmptyRect); return stat; } GpStatus WINGDIPAPI GdipSetInfinite(GpRegion *region) { GpStatus stat; TRACE("%p\n", region); if (!region) return InvalidParameter; delete_element(®ion->node); stat = init_region(region, RegionDataInfiniteRect); return stat; } /* Transforms GpRegion elements with given matrix */ static GpStatus transform_region_element(region_element* element, GpMatrix *matrix) { GpStatus stat; switch(element->type) { case RegionDataEmptyRect: case RegionDataInfiniteRect: return Ok; case RegionDataRect: { /* We can't transform a rectangle, so convert it to a path. */ GpRegion *new_region; GpPath *path; stat = GdipCreatePath(FillModeAlternate, &path); if (stat == Ok) { stat = GdipAddPathRectangle(path, element->elementdata.rect.X, element->elementdata.rect.Y, element->elementdata.rect.Width, element->elementdata.rect.Height); if (stat == Ok) stat = GdipCreateRegionPath(path, &new_region); GdipDeletePath(path); } if (stat == Ok) { /* Steal the element from the created region. */ memcpy(element, &new_region->node, sizeof(region_element)); HeapFree(GetProcessHeap(), 0, new_region); } else return stat; } /* Fall-through to do the actual conversion. */ case RegionDataPath: stat = GdipTransformMatrixPoints(matrix, element->elementdata.pathdata.path->pathdata.Points, element->elementdata.pathdata.path->pathdata.Count); return stat; default: stat = transform_region_element(element->elementdata.combine.left, matrix); if (stat == Ok) stat = transform_region_element(element->elementdata.combine.right, matrix); return stat; } } GpStatus WINGDIPAPI GdipTransformRegion(GpRegion *region, GpMatrix *matrix) { TRACE("(%p, %p)\n", region, matrix); if (!region || !matrix) return InvalidParameter; return transform_region_element(®ion->node, matrix); } /* Translates GpRegion elements with specified offsets */ static void translate_region_element(region_element* element, REAL dx, REAL dy) { INT i; switch(element->type) { case RegionDataEmptyRect: case RegionDataInfiniteRect: return; case RegionDataRect: element->elementdata.rect.X += dx; element->elementdata.rect.Y += dy; return; case RegionDataPath: for(i = 0; i < element->elementdata.pathdata.path->pathdata.Count; i++){ element->elementdata.pathdata.path->pathdata.Points[i].X += dx; element->elementdata.pathdata.path->pathdata.Points[i].Y += dy; } return; default: translate_region_element(element->elementdata.combine.left, dx, dy); translate_region_element(element->elementdata.combine.right, dx, dy); return; } } /***************************************************************************** * GdipTranslateRegion [GDIPLUS.@] */ GpStatus WINGDIPAPI GdipTranslateRegion(GpRegion *region, REAL dx, REAL dy) { TRACE("(%p, %f, %f)\n", region, dx, dy); if(!region) return InvalidParameter; translate_region_element(®ion->node, dx, dy); return Ok; } /***************************************************************************** * GdipTranslateRegionI [GDIPLUS.@] */ GpStatus WINGDIPAPI GdipTranslateRegionI(GpRegion *region, INT dx, INT dy) { TRACE("(%p, %d, %d)\n", region, dx, dy); return GdipTranslateRegion(region, (REAL)dx, (REAL)dy); } static GpStatus get_region_scans_data(GpRegion *region, GpMatrix *matrix, LPRGNDATA *data) { GpRegion *region_copy; GpStatus stat; HRGN hrgn; DWORD data_size; stat = GdipCloneRegion(region, ®ion_copy); if (stat == Ok) { stat = GdipTransformRegion(region_copy, matrix); if (stat == Ok) stat = GdipGetRegionHRgn(region_copy, NULL, &hrgn); if (stat == Ok) { if (hrgn) { data_size = GetRegionData(hrgn, 0, NULL); *data = GdipAlloc(data_size); if (*data) GetRegionData(hrgn, data_size, *data); else stat = OutOfMemory; DeleteObject(hrgn); } else { data_size = sizeof(RGNDATAHEADER) + sizeof(RECT); *data = GdipAlloc(data_size); if (*data) { (*data)->rdh.dwSize = sizeof(RGNDATAHEADER); (*data)->rdh.iType = RDH_RECTANGLES; (*data)->rdh.nCount = 1; (*data)->rdh.nRgnSize = sizeof(RECT); (*data)->rdh.rcBound.left = (*data)->rdh.rcBound.top = -0x400000; (*data)->rdh.rcBound.right = (*data)->rdh.rcBound.bottom = 0x400000; memcpy((*data)->Buffer, &(*data)->rdh.rcBound, sizeof(RECT)); } else stat = OutOfMemory; } } GdipDeleteRegion(region_copy); } return stat; } GpStatus WINGDIPAPI GdipGetRegionScansCount(GpRegion *region, UINT *count, GpMatrix *matrix) { GpStatus stat; LPRGNDATA data; TRACE("(%p, %p, %p)\n", region, count, matrix); if (!region || !count || !matrix) return InvalidParameter; stat = get_region_scans_data(region, matrix, &data); if (stat == Ok) { *count = data->rdh.nCount; GdipFree(data); } return stat; } GpStatus WINGDIPAPI GdipGetRegionScansI(GpRegion *region, GpRect *scans, INT *count, GpMatrix *matrix) { GpStatus stat; INT i; LPRGNDATA data; RECT *rects; if (!region || !count || !matrix) return InvalidParameter; stat = get_region_scans_data(region, matrix, &data); if (stat == Ok) { *count = data->rdh.nCount; rects = (RECT*)data->Buffer; if (scans) { for (i=0; i<data->rdh.nCount; i++) { scans[i].X = rects[i].left; scans[i].Y = rects[i].top; scans[i].Width = rects[i].right - rects[i].left; scans[i].Height = rects[i].bottom - rects[i].top; } } GdipFree(data); } return Ok; } GpStatus WINGDIPAPI GdipGetRegionScans(GpRegion *region, GpRectF *scans, INT *count, GpMatrix *matrix) { GpStatus stat; INT i; LPRGNDATA data; RECT *rects; if (!region || !count || !matrix) return InvalidParameter; stat = get_region_scans_data(region, matrix, &data); if (stat == Ok) { *count = data->rdh.nCount; rects = (RECT*)data->Buffer; if (scans) { for (i=0; i<data->rdh.nCount; i++) { scans[i].X = rects[i].left; scans[i].Y = rects[i].top; scans[i].Width = rects[i].right - rects[i].left; scans[i].Height = rects[i].bottom - rects[i].top; } } GdipFree(data); } return Ok; }