From a66d664505de9a79f7f4f07b93c97a3d21ec3c54 Mon Sep 17 00:00:00 2001 From: Dmitry Timoshkov Date: Sat, 13 Jun 2015 10:53:58 +0300 Subject: [PATCH] gdiplus: Implement GdipCreateRegionRgnData. --- dlls/gdiplus/region.c | 257 +++++++++++++++++++++++++++++------- dlls/gdiplus/tests/region.c | 65 +++++++++ 2 files changed, 277 insertions(+), 45 deletions(-) diff --git a/dlls/gdiplus/region.c b/dlls/gdiplus/region.c index 572df62a7ea..8988d707224 100644 --- a/dlls/gdiplus/region.c +++ b/dlls/gdiplus/region.c @@ -1,5 +1,6 @@ /* * 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 @@ -76,6 +77,28 @@ WINE_DEFAULT_DEBUG_CHANNEL(gdiplus); #define FLAGS_NOFLAGS 0x0 #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 */ @@ -729,15 +752,9 @@ static void write_element(const region_element* element, DWORD *buffer, { INT i; const GpPath* path = element->elementdata.path; - struct _pathheader - { - DWORD size; - DWORD magic; - DWORD count; - DWORD flags; - } *pathheader; + struct path_header *pathheader; - pathheader = (struct _pathheader *)(buffer + *filled); + pathheader = (struct path_header *)(buffer + *filled); pathheader->flags = is_integer_path(path) ? FLAGS_INTPATH : FLAGS_NOFLAGS; /* 3 for headers, once again size doesn't count itself */ @@ -778,14 +795,6 @@ static void write_element(const region_element* element, DWORD *buffer, } } -struct region_header -{ - DWORD size; - DWORD checksum; - DWORD magic; - DWORD num_children; -}; - /***************************************************************************** * GdipGetRegionData [GDIPLUS.@] * @@ -856,36 +865,190 @@ GpStatus WINGDIPAPI GdipGetRegionData(GpRegion *region, BYTE *buffer, UINT size, return Ok; } -static inline GpStatus read_dword(DWORD **buffer, INT *size, DWORD *ret) +static inline void init_memory_buffer(struct memory_buffer *mbuf, const BYTE *buffer, INT size) { - if (*size < sizeof(DWORD)) - return GenericError; - - *ret = **buffer; - (*buffer)++; - (*size) -= sizeof(DWORD); - return Ok; + mbuf->buffer = buffer; + mbuf->size = size; + mbuf->pos = 0; } -static GpStatus read_element(GpRegion *region, region_element *element, DWORD **buffer, INT *size) +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; - status = read_dword(buffer, size, &element->type); - if (status != Ok) - return status; + type = buffer_read(mbuf, sizeof(*type)); + if (!type) return Ok; - switch (element->type) + TRACE("type %#x\n", *type); + + node->type = *type; + + switch (node->type) { - case RegionDataInfiniteRect: - case RegionDataEmptyRect: - break; - default: - FIXME("region element type 0x%08x not supported\n", element->type); - return NotImplemented; + case CombineModeReplace: + case CombineModeIntersect: + case CombineModeUnion: + case CombineModeXor: + case CombineModeExclude: + case CombineModeComplement: + { + region_element *left, *right; + + left = GdipAlloc(sizeof(region_element)); + if (!left) return OutOfMemory; + right = GdipAlloc(sizeof(region_element)); + if (!right) + { + GdipFree(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; + } + } + + GdipFree(left); + GdipFree(right); + return status; } - return Ok; + 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 (path_header->magic != VERSION_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; } /***************************************************************************** @@ -893,28 +1056,32 @@ static GpStatus read_element(GpRegion *region, region_element *element, DWORD ** */ GpStatus WINGDIPAPI GdipCreateRegionRgnData(GDIPCONST BYTE *data, INT size, GpRegion **region) { - struct region_header *region_header; - DWORD *buffer = (DWORD*)data; + 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 < sizeof(*region_header) || !region) + if (!data || !size) return InvalidParameter; - region_header = (struct region_header *)buffer; - if (region_header->magic != VERSION_MAGIC && region_header->magic != VERSION_MAGIC2) + init_memory_buffer(&mbuf, data, size); + + region_header = buffer_read(&mbuf, sizeof(*region_header)); + if (!region_header || (region_header->magic != VERSION_MAGIC && + region_header->magic != VERSION_MAGIC2)) return InvalidParameter; status = GdipCreateRegion(region); if (status != Ok) return status; - /* skip header */ - buffer += 4; - size -= sizeof(*region_header); + count = 0; + status = read_element(&mbuf, *region, &(*region)->node, &count); + if (status == Ok && !count) + status = InvalidParameter; - status = read_element(*region, &(*region)->node, &buffer, &size); if (status != Ok) { GdipDeleteRegion(*region); diff --git a/dlls/gdiplus/tests/region.c b/dlls/gdiplus/tests/region.c index 92569c70992..38d05cdc866 100644 --- a/dlls/gdiplus/tests/region.c +++ b/dlls/gdiplus/tests/region.c @@ -102,6 +102,56 @@ static void verify_region(HRGN hrgn, const RECT *rc) rgn.data.rdh.rcBound.left, rgn.data.rdh.rcBound.top, rgn.data.rdh.rcBound.right, rgn.data.rdh.rcBound.bottom); } +static void test_region_data(DWORD *data, UINT size, INT line) +{ + GpStatus status; + GpRegion *region; + DWORD buf[256]; + UINT needed, i; + + status = GdipCreateRegionRgnData((BYTE *)data, size, ®ion); + /* Windows always fails to create an empty path in a region */ + if (data[4] == RGNDATA_PATH) + { + struct path_header + { + DWORD size; + DWORD magic; + DWORD count; + DWORD flags; + } *path_header = (struct path_header *)(data + 5); + if (!path_header->count) + { + ok_(__FILE__, line)(status == GenericError, "expected GenericError, got %d\n", status); + return; + } + } + + ok_(__FILE__, line)(status == Ok, "GdipCreateRegionRgnData error %d\n", status); + if (status != Ok) return; + + needed = 0; + status = GdipGetRegionDataSize(region, &needed); + ok_(__FILE__, line)(status == Ok, "status %d\n", status); + ok_(__FILE__, line)(needed == size, "data size mismatch: %u != %u\n", needed, size); + + memset(buf, 0xee, sizeof(buf)); + needed = 0; + status = GdipGetRegionData(region, (BYTE *)buf, sizeof(buf), &needed); + ok_(__FILE__, line)(status == Ok, "status %08x\n", status); + ok_(__FILE__, line)(needed == size, "data size mismatch: %u != %u\n", needed, size); + + size /= sizeof(DWORD); + for (i = 0; i < size - 1; i++) + { + if (i == 1) continue; /* data[1] never matches */ + ok_(__FILE__, line)(data[i] == buf[i], "off %u: %#x != %#x\n", i, data[i], buf[i]); + } + /* some Windows versions fail to properly clear the aligned DWORD */ + ok_(__FILE__, line)(data[size - 1] == buf[size - 1] || broken(data[size - 1] != buf[size - 1]), + "off %u: %#x != %#x\n", size - 1, data[size - 1], buf[size - 1]); +} + static void test_getregiondata(void) { GpStatus status; @@ -143,6 +193,7 @@ static void test_getregiondata(void) expect_dword(buf + 3, 0); expect_dword(buf + 4, RGNDATA_INFINITE_RECT); expect_dword(buf + 6, 0xeeeeeeee); + test_region_data(buf, needed, __LINE__); status = GdipSetEmpty(region); ok(status == Ok, "status %08x\n", status); @@ -160,6 +211,7 @@ static void test_getregiondata(void) expect_dword(buf + 3, 0); expect_dword(buf + 4, RGNDATA_EMPTY_RECT); expect_dword(buf + 6, 0xeeeeeeee); + test_region_data(buf, needed, __LINE__); status = GdipSetInfinite(region); ok(status == Ok, "status %08x\n", status); @@ -177,6 +229,7 @@ static void test_getregiondata(void) expect_dword(buf + 3, 0); expect_dword(buf + 4, RGNDATA_INFINITE_RECT); expect_dword(buf + 6, 0xeeeeeeee); + test_region_data(buf, needed, __LINE__); status = GdipDeleteRegion(region); ok(status == Ok, "status %08x\n", status); @@ -205,6 +258,7 @@ static void test_getregiondata(void) expect_float(buf + 7, 100.0); expect_float(buf + 8, 200.0); expect_dword(buf + 10, 0xeeeeeeee); + test_region_data(buf, needed, __LINE__); rect.X = 50; rect.Y = 30; @@ -290,6 +344,7 @@ static void test_getregiondata(void) expect_float(buf + 37, 22.0); expect_float(buf + 38, 55.0); expect_dword(buf + 39, 0xeeeeeeee); + test_region_data(buf, needed, __LINE__); status = GdipDeleteRegion(region2); ok(status == Ok, "status %08x\n", status); @@ -331,6 +386,7 @@ static void test_getregiondata(void) expect_float(buf + 16, 28.0); expect_dword(buf + 17, 0x81010100); expect_dword(buf + 18, 0xeeeeeeee); + test_region_data(buf, needed, __LINE__); rect.X = 50; rect.Y = 30; @@ -371,6 +427,7 @@ static void test_getregiondata(void) expect_float(buf + 22, 10.0); expect_float(buf + 23, 20.0); expect_dword(buf + 24, 0xeeeeeeee); + test_region_data(buf, needed, __LINE__); status = GdipDeleteRegion(region); ok(status == Ok, "status %08x\n", status); @@ -403,6 +460,7 @@ static void test_getregiondata(void) ok(*(buf + 8) == 0x4000 /* before win7 */ || *(buf + 8) == 0, "expected 0x4000 or 0, got %08x\n", *(buf + 8)); expect_dword(buf + 10, 0xeeeeeeee); + test_region_data(buf, needed, __LINE__); /* Transform an empty region */ status = GdipCreateMatrix(&matrix); @@ -453,6 +511,7 @@ static void test_getregiondata(void) expect(6, point[3].Y); expect_dword(buf + 13, 0x81010100); /* 0x01010100 if we don't close the path */ expect_dword(buf + 14, 0xeeeeeeee); + test_region_data(buf, needed, __LINE__); status = GdipTranslateRegion(region, 0.6, 0.8); expect(Ok, status); @@ -480,6 +539,7 @@ static void test_getregiondata(void) expect_float(buf + 16, 6.8); expect_dword(buf + 17, 0x81010100); /* 0x01010100 if we don't close the path */ expect_dword(buf + 18, 0xeeeeeeee); + test_region_data(buf, needed, __LINE__); status = GdipDeletePath(path); expect(Ok, status); @@ -522,6 +582,7 @@ static void test_getregiondata(void) expect_float(buf + 16, 6.2); expect_dword(buf + 17, 0x01010100); expect_dword(buf + 18, 0xeeeeeeee); + test_region_data(buf, needed, __LINE__); status = GdipDeletePath(path); expect(Ok, status); @@ -584,6 +645,7 @@ static void test_getregiondata(void) ok(*(buf + 28) == 0x00000101 || *(buf + 28) == 0x43050101 /* Win 7 */, "expected 00000101 or 43050101 got %08x\n", *(buf + 28)); expect_dword(buf + 29, 0xeeeeeeee); + test_region_data(buf, needed, __LINE__); status = GdipDeletePath(path); expect(Ok, status); @@ -627,6 +689,7 @@ static void test_getregiondata(void) expect(23, point[3].Y); expect_dword(buf + 13, 0x81010100); /* 0x01010100 if we don't close the path */ expect_dword(buf + 14, 0xeeeeeeee); + test_region_data(buf, needed, __LINE__); status = GdipDeletePath(path); expect(Ok, status); @@ -669,6 +732,7 @@ static void test_getregiondata(void) expect_float(buf + 16, 2300.0); expect_dword(buf + 17, 0x81010100); /* 0x01010100 if we don't close the path */ expect_dword(buf + 18, 0xeeeeeeee); + test_region_data(buf, needed, __LINE__); status = GdipDeletePath(path); expect(Ok, status); @@ -732,6 +796,7 @@ static void test_getregiondata(void) *(buf + 33) == 0x43030303 /* 32-bit win7 */ || *(buf + 33) == 0x4c030303 /* 64-bit win7 */, "expected 0x00030303 or 0x43030303 or 0x4c030303 got %08x\n", *(buf + 33)); expect_dword(buf + 34, 0xeeeeeeee); + test_region_data(buf, needed, __LINE__); status = GdipDeletePath(path); expect(Ok, status);