| /* |
| * 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; |
| break; |
| case RegionDataEmptyRect: |
| case RegionDataInfiniteRect: |
| break; |
| case RegionDataPath: |
| (*element2)->elementdata.pathdata.pathheader = element->elementdata.pathdata.pathheader; |
| stat = GdipClonePath(element->elementdata.pathdata.path, |
| &(*element2)->elementdata.pathdata.path); |
| if (stat != Ok) goto clone_out; |
| 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) goto clone_out; |
| stat = clone_element(element->elementdata.combine.right, |
| &(*element2)->elementdata.combine.right); |
| if (stat != Ok) goto clone_out; |
| break; |
| } |
| |
| return Ok; |
| |
| clone_out: |
| 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) |
| goto out; |
| *left = region->node; |
| |
| stat = clone_element(&path_region->node, &right); |
| if (stat != Ok) |
| goto out; |
| |
| fuse_region(region, left, right, mode); |
| |
| GdipDeleteRegion(path_region); |
| return Ok; |
| |
| out: |
| 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) |
| goto out; |
| memcpy(left, ®ion->node, sizeof(region_element)); |
| |
| stat = clone_element(&rect_region->node, &right); |
| if (stat != Ok) |
| goto out; |
| |
| fuse_region(region, left, right, mode); |
| |
| GdipDeleteRegion(rect_region); |
| return Ok; |
| |
| out: |
| 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; |
| |
| 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); |
| return Ok; |
| } |
| |
| if(!GetRgnBox(hrgn, &r)){ |
| DeleteObject(hrgn); |
| return GenericError; |
| } |
| |
| rect->X = r.left; |
| rect->Y = r.top; |
| rect->Width = r.right - r.left; |
| rect->Height = r.bottom - r.top; |
| |
| return Ok; |
| } |
| |
| /***************************************************************************** |
| * 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 = roundr(rectf.X); |
| rect->Y = roundr(rectf.X); |
| rect->Width = roundr(rectf.Width); |
| rect->Height = roundr(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; |
| GpStatus stat; |
| INT save_state; |
| |
| if (!graphics) |
| { |
| new_hdc = GetDC(0); |
| if (!new_hdc) |
| return OutOfMemory; |
| |
| stat = GdipCreateFromHDC(new_hdc, &graphics); |
| if (stat != Ok) |
| { |
| ReleaseDC(0, new_hdc); |
| return stat; |
| } |
| } |
| |
| 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); |
| GdipDeleteGraphics(graphics); |
| } |
| |
| 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) |
| { |
| TRACE("(%p, %p, %p)\n", region, graphics, res); |
| |
| if(!region || !graphics || !res) |
| return InvalidParameter; |
| |
| *res = (region->node.type == RegionDataEmptyRect); |
| |
| 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, roundr(x), roundr(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; |
| } |
| |
| GpStatus WINGDIPAPI GdipTransformRegion(GpRegion *region, GpMatrix *matrix) |
| { |
| FIXME("(%p, %p): stub\n", region, matrix); |
| |
| return NotImplemented; |
| } |
| |
| /* 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); |
| } |