/* * Copyright (C) 2008 Google (Lei Zhang) * Copyright (C) 2013 Dmitry Timoshkov * * 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 "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_INTPATH 0x4000 struct memory_buffer { const BYTE *buffer; INT size, pos; }; struct region_header { DWORD size; DWORD checksum; DWORD magic; DWORD num_children; }; struct path_header { DWORD size; DWORD magic; DWORD count; DWORD flags; }; /* 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; 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 += write_path_data(element->elementdata.path, NULL); needed += sizeof(DWORD); /* Extra DWORD for path 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->num_children = 0; 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 = heap_alloc_zero(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: stat = GdipClonePath(element->elementdata.path, &(*element2)->elementdata.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->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 = heap_alloc_zero(sizeof(GpRegion)); if (!*clone) return OutOfMemory; element = &(*clone)->node; (*clone)->num_children = region->num_children; 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)); heap_free(path_region); return Ok; } left = heap_alloc_zero(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; heap_free(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 %s %d\n", region, debugstr_rectf(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)); heap_free(rect_region); return Ok; } left = heap_alloc_zero(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; heap_free(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)); heap_free(reg2copy); return Ok; } left = heap_alloc_zero(sizeof(region_element)); if (!left) return OutOfMemory; *left = region1->node; stat = clone_element(®ion2->node, &right); if (stat != Ok) { heap_free(left); return OutOfMemory; } fuse_region(region1, left, right, mode); region1->num_children += region2->num_children; return Ok; } /***************************************************************************** * GdipCreateRegion [GDIPLUS.@] */ GpStatus WINGDIPAPI GdipCreateRegion(GpRegion **region) { TRACE("%p\n", region); if(!region) return InvalidParameter; *region = heap_alloc_zero(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; GpStatus stat; TRACE("%p, %p\n", path, region); if (!(path && region)) return InvalidParameter; *region = heap_alloc_zero(sizeof(GpRegion)); if(!*region) return OutOfMemory; stat = init_region(*region, RegionDataPath); if (stat != Ok) { GdipDeleteRegion(*region); return stat; } element = &(*region)->node; stat = GdipClonePath(path, &element->elementdata.path); if (stat != Ok) { GdipDeleteRegion(*region); return stat; } 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 = heap_alloc_zero(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); } /****************************************************************************** * GdipCreateRegionHrgn [GDIPLUS.@] */ GpStatus WINGDIPAPI GdipCreateRegionHrgn(HRGN hrgn, GpRegion **region) { DWORD size; LPRGNDATA buf; LPRECT rect; GpStatus stat; GpPath* path; GpRegion* local; DWORD i; TRACE("(%p, %p)\n", hrgn, region); if(!region || !(size = GetRegionData(hrgn, 0, NULL))) return InvalidParameter; buf = heap_alloc_zero(size); if(!buf) return OutOfMemory; if(!GetRegionData(hrgn, size, buf)){ heap_free(buf); return GenericError; } if(buf->rdh.nCount == 0){ if((stat = GdipCreateRegion(&local)) != Ok){ heap_free(buf); return stat; } if((stat = GdipSetEmpty(local)) != Ok){ heap_free(buf); GdipDeleteRegion(local); return stat; } *region = local; heap_free(buf); return Ok; } if((stat = GdipCreatePath(FillModeAlternate, &path)) != Ok){ heap_free(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){ heap_free(buf); GdipDeletePath(path); return stat; } rect++; } stat = GdipCreateRegionPath(path, region); heap_free(buf); GdipDeletePath(path); return stat; } /***************************************************************************** * GdipDeleteRegion [GDIPLUS.@] */ GpStatus WINGDIPAPI GdipDeleteRegion(GpRegion *region) { TRACE("%p\n", region); if (!region) return InvalidParameter; delete_element(®ion->node); heap_free(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.Y); 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 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: { DWORD size = write_path_data(element->elementdata.path, buffer + *filled + 1); write_dword(buffer, filled, size); *filled += size / sizeof(DWORD); 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 it's * 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) { struct region_header *region_header; INT filled = 0; UINT required; GpStatus status; TRACE("%p, %p, %d, %p\n", region, buffer, size, needed); if (!region || !buffer || !size) return InvalidParameter; status = GdipGetRegionDataSize(region, &required); if (status != Ok) return status; if (size < required) { if (needed) *needed = size; return InsufficientBuffer; } region_header = (struct region_header *)buffer; region_header->size = sizeheader_size + get_element_size(®ion->node); region_header->checksum = 0; region_header->magic = VERSION_MAGIC2; region_header->num_children = region->num_children; filled += 4; /* 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; } static inline void init_memory_buffer(struct memory_buffer *mbuf, const BYTE *buffer, INT size) { mbuf->buffer = buffer; mbuf->size = size; mbuf->pos = 0; } static inline const void *buffer_read(struct memory_buffer *mbuf, INT size) { if (mbuf->size - mbuf->pos >= size) { const void *data = mbuf->buffer + mbuf->pos; mbuf->pos += size; return data; } return NULL; } static GpStatus read_element(struct memory_buffer *mbuf, GpRegion *region, region_element *node, INT *count) { GpStatus status; const DWORD *type; type = buffer_read(mbuf, sizeof(*type)); if (!type) return Ok; TRACE("type %#x\n", *type); node->type = *type; switch (node->type) { case CombineModeReplace: case CombineModeIntersect: case CombineModeUnion: case CombineModeXor: case CombineModeExclude: case CombineModeComplement: { region_element *left, *right; left = heap_alloc_zero(sizeof(region_element)); if (!left) return OutOfMemory; right = heap_alloc_zero(sizeof(region_element)); if (!right) { heap_free(left); return OutOfMemory; } status = read_element(mbuf, region, left, count); if (status == Ok) { status = read_element(mbuf, region, right, count); if (status == Ok) { node->elementdata.combine.left = left; node->elementdata.combine.right = right; region->num_children += 2; return Ok; } } heap_free(left); heap_free(right); return status; } case RegionDataRect: { const GpRectF *rc; rc = buffer_read(mbuf, sizeof(*rc)); if (!rc) { ERR("failed to read rect data\n"); return InvalidParameter; } node->elementdata.rect = *rc; *count += 1; return Ok; } case RegionDataPath: { GpPath *path; const struct path_header *path_header; const BYTE *types; path_header = buffer_read(mbuf, sizeof(*path_header)); if (!path_header) { ERR("failed to read path header\n"); return InvalidParameter; } if (!VALID_MAGIC(path_header->magic)) { ERR("invalid path header magic %#x\n", path_header->magic); return InvalidParameter; } /* Windows always fails to create an empty path in a region */ if (!path_header->count) { TRACE("refusing to create an empty path in a region\n"); return GenericError; } status = GdipCreatePath(FillModeAlternate, &path); if (status) return status; node->elementdata.path = path; if (!lengthen_path(path, path_header->count)) return OutOfMemory; path->pathdata.Count = path_header->count; if (path_header->flags & ~FLAGS_INTPATH) FIXME("unhandled path flags %#x\n", path_header->flags); if (path_header->flags & FLAGS_INTPATH) { const packed_point *pt; DWORD i; pt = buffer_read(mbuf, sizeof(*pt) * path_header->count); if (!pt) { ERR("failed to read packed %u path points\n", path_header->count); return InvalidParameter; } for (i = 0; i < path_header->count; i++) { path->pathdata.Points[i].X = (REAL)pt[i].X; path->pathdata.Points[i].Y = (REAL)pt[i].Y; } } else { const GpPointF *ptf; ptf = buffer_read(mbuf, sizeof(*ptf) * path_header->count); if (!ptf) { ERR("failed to read %u path points\n", path_header->count); return InvalidParameter; } memcpy(path->pathdata.Points, ptf, sizeof(*ptf) * path_header->count); } types = buffer_read(mbuf, path_header->count); if (!types) { ERR("failed to read %u path types\n", path_header->count); return InvalidParameter; } memcpy(path->pathdata.Types, types, path_header->count); if (path_header->count & 3) { if (!buffer_read(mbuf, 4 - (path_header->count & 3))) { ERR("failed to read rounding %u bytes\n", 4 - (path_header->count & 3)); return InvalidParameter; } } *count += 1; return Ok; } case RegionDataEmptyRect: case RegionDataInfiniteRect: *count += 1; return Ok; default: FIXME("element type %#x is not supported\n", *type); break; } return InvalidParameter; } /***************************************************************************** * GdipCreateRegionRgnData [GDIPLUS.@] */ GpStatus WINGDIPAPI GdipCreateRegionRgnData(GDIPCONST BYTE *data, INT size, GpRegion **region) { const struct region_header *region_header; struct memory_buffer mbuf; GpStatus status; INT count; TRACE("(%p, %d, %p)\n", data, size, region); if (!data || !size) return InvalidParameter; init_memory_buffer(&mbuf, data, size); region_header = buffer_read(&mbuf, sizeof(*region_header)); if (!region_header || !VALID_MAGIC(region_header->magic)) return InvalidParameter; status = GdipCreateRegion(region); if (status != Ok) return status; count = 0; status = read_element(&mbuf, *region, &(*region)->node, &count); if (status == Ok && !count) status = InvalidParameter; if (status != Ok) { GdipDeleteRegion(*region); *region = NULL; } return status; } /***************************************************************************** * 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 = sizeof(DWORD) * 2 + sizeheader_size + get_element_size(®ion->node); 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 (!path->pathdata.Count) /* PathToRegion doesn't support empty paths */ { *hrgn = CreateRectRgn( 0, 0, 0, 0 ); return *hrgn ? Ok : OutOfMemory; } if (!graphics) { new_hdc = CreateCompatibleDC(0); if (!new_hdc) return OutOfMemory; stat = GdipCreateFromHDC(new_hdc, &new_graphics); graphics = new_graphics; if (stat != Ok) { DeleteDC(new_hdc); return stat; } } else if (!graphics->hdc) { graphics->hdc = new_hdc = CreateCompatibleDC(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) { DeleteDC(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.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: left = CreateRectRgn(-(1 << 22), -(1 << 22), 1 << 22, 1 << 22); break; 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: right = CreateRectRgn(-(1 << 22), -(1 << 22), 1 << 22, 1 << 22); break; 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; } SetRect(&rect, ceilr(x), ceilr(y), ceilr(x + w), 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)); heap_free(new_region); } else return stat; } /* Fall-through to do the actual conversion. */ case RegionDataPath: if (!element->elementdata.path->pathdata.Count) return Ok; stat = GdipTransformMatrixPoints(matrix, element->elementdata.path->pathdata.Points, element->elementdata.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.path->pathdata.Count; i++){ element->elementdata.path->pathdata.Points[i].X += dx; element->elementdata.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 = heap_alloc_zero(data_size); if (*data) GetRegionData(hrgn, data_size, *data); else stat = OutOfMemory; DeleteObject(hrgn); } else { data_size = sizeof(RGNDATAHEADER) + sizeof(RECT); *data = heap_alloc_zero(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; heap_free(data); } return stat; } GpStatus WINGDIPAPI GdipGetRegionScansI(GpRegion *region, GpRect *scans, INT *count, GpMatrix *matrix) { GpStatus stat; DWORD 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; irdh.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; } } heap_free(data); } return Ok; } GpStatus WINGDIPAPI GdipGetRegionScans(GpRegion *region, GpRectF *scans, INT *count, GpMatrix *matrix) { GpStatus stat; DWORD 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; irdh.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; } } heap_free(data); } return Ok; }