Module: wine
Branch: master
Commit: 47e9980bb14df2ae8d8a196502843394f093255d
URL: https://gitlab.winehq.org/wine/wine/-/commit/47e9980bb14df2ae8d8a1965028433…
Author: Esme Povirk <esme(a)codeweavers.com>
Date: Sat Oct 28 15:12:57 2023 -0500
gdiplus: Check bounding box in GdipIsVisibleRegionPoint.
---
dlls/gdiplus/region.c | 218 +++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 217 insertions(+), 1 deletion(-)
diff --git a/dlls/gdiplus/region.c b/dlls/gdiplus/region.c
index 9617f492a51..3034dedae76 100644
--- a/dlls/gdiplus/region.c
+++ b/dlls/gdiplus/region.c
@@ -17,6 +17,7 @@
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
+#include <assert.h>
#include <stdarg.h>
#include "windef.h"
@@ -1313,6 +1314,208 @@ GpStatus WINGDIPAPI GdipIsVisibleRegionRectI(GpRegion* region, INT x, INT y, INT
return GdipIsVisibleRegionRect(region, (REAL)x, (REAL)y, (REAL)w, (REAL)h, graphics, res);
}
+/* get_region_bounding_box
+ *
+ * Returns a box guaranteed to enclose the entire region, but not guaranteed to be minimal.
+ * Sets "empty" if bounding box is empty.
+ * Sets "infinite" if everything outside bounding box is inside the region.
+ * In the infinite case, the bounding box encloses all points not in the region. */
+static void get_region_bounding_box(struct region_element *element,
+ REAL *min_x, REAL *min_y, REAL *max_x, REAL *max_y, BOOL *empty, BOOL *infinite)
+{
+ REAL left_min_x, left_min_y, left_max_x, left_max_y;
+ BOOL left_empty, left_infinite;
+ REAL right_min_x, right_min_y, right_max_x, right_max_y;
+ BOOL right_empty, right_infinite;
+ /* For combine modes, we convert the mode to flags as follows to simplify the logic:
+ * 0x8 = point in combined region if it's in both
+ * 0x4 = point in combined region if it's in left and not right
+ * 0x2 = point in combined region if it's not in left and is in right
+ * 0x1 = point in combined region if it's in neither region */
+ int flags;
+ const int combine_mode_flags[] = {
+ 0xa, /* CombineModeReplace - shouldn't be used */
+ 0x8, /* CombineModeIntersect */
+ 0xe, /* CombineModeUnion */
+ 0x6, /* CombineModeXor */
+ 0x4, /* CombineModeExclude */
+ 0x2, /* CombineModeComplement */
+ };
+
+ /* handle unit elements first */
+ switch (element->type)
+ {
+ case RegionDataInfiniteRect:
+ *min_x = *min_y = *max_x = *max_y = 0.0;
+ *empty = TRUE;
+ *infinite = TRUE;
+ return;
+ case RegionDataEmptyRect:
+ *min_x = *min_y = *max_x = *max_y = 0.0;
+ *empty = TRUE;
+ *infinite = FALSE;
+ return;
+ case RegionDataPath:
+ {
+ GpPath *path = element->elementdata.path;
+ int i;
+
+ if (path->pathdata.Count <= 1) {
+ *min_x = *min_y = *max_x = *max_y = 0.0;
+ *empty = TRUE;
+ *infinite = FALSE;
+ return;
+ }
+
+ *min_x = *max_x = path->pathdata.Points[0].X;
+ *min_y = *max_y = path->pathdata.Points[0].Y;
+ *empty = FALSE;
+ *infinite = FALSE;
+
+ for (i=1; i < path->pathdata.Count; i++)
+ {
+ if (path->pathdata.Points[i].X < *min_x)
+ *min_x = path->pathdata.Points[i].X;
+ else if (path->pathdata.Points[i].X > *max_x)
+ *max_x = path->pathdata.Points[i].X;
+ if (path->pathdata.Points[i].Y < *min_y)
+ *min_y = path->pathdata.Points[i].Y;
+ else if (path->pathdata.Points[i].Y > *max_y)
+ *max_y = path->pathdata.Points[i].Y;
+ }
+
+ return;
+ }
+ case RegionDataRect:
+ *min_x = element->elementdata.rect.X;
+ *min_y = element->elementdata.rect.Y;
+ *max_x = element->elementdata.rect.X + element->elementdata.rect.Width;
+ *max_y = element->elementdata.rect.Y + element->elementdata.rect.Height;
+ *empty = FALSE;
+ *infinite = FALSE;
+ return;
+ }
+
+ /* Should be only combine modes left */
+ assert(element->type < ARRAY_SIZE(combine_mode_flags));
+
+ flags = combine_mode_flags[element->type];
+
+ get_region_bounding_box(element->elementdata.combine.left,
+ &left_min_x, &left_min_y, &left_max_x, &left_max_y, &left_empty, &left_infinite);
+
+ if (left_infinite)
+ {
+ /* change our function so we can ignore the infinity */
+ flags = ((flags & 0x3) << 2) | ((flags & 0xc) >> 2);
+ }
+
+ if (left_empty && (flags & 0x3) == 0) {
+ /* no points in region regardless of right region, return empty */
+ *empty = TRUE;
+ *infinite = FALSE;
+ return;
+ }
+
+ if (left_empty && (flags & 0x3) == 0x3) {
+ /* all points in region regardless of right region, return infinite */
+ *empty = TRUE;
+ *infinite = TRUE;
+ return;
+ }
+
+ get_region_bounding_box(element->elementdata.combine.right,
+ &right_min_x, &right_min_y, &right_max_x, &right_max_y, &right_empty, &right_infinite);
+
+ if (right_infinite)
+ {
+ /* change our function so we can ignore the infinity */
+ flags = ((flags & 0x5) << 1) | ((flags & 0xa) >> 1);
+ }
+
+ /* result is infinite if points in neither region are in the result */
+ *infinite = (flags & 0x1);
+
+ if (*infinite)
+ {
+ /* Again, we modify our function to ignore the infinity.
+ * The points we care about are the ones that are different from the outside of our box,
+ * not the points inside the region, so we invert the whole thing.
+ * From here we can assume 0x1 is not set. */
+ flags ^= 0xf;
+ }
+
+ if (left_empty)
+ {
+ /* We already took care of the cases where the right region doesn't matter,
+ * so we can just use the right bounding box. */
+ *min_x = right_min_x;
+ *min_y = right_min_y;
+ *max_x = right_max_x;
+ *max_y = right_max_y;
+ *empty = right_empty;
+ return;
+ }
+
+ if (right_empty)
+ {
+ /* With no points in right region, and infinities eliminated, we only care
+ * about flag 0x4, the case where a point is in left region and not right. */
+ if (flags & 0x4)
+ {
+ /* We have a copy of the left region. */
+ *min_x = left_min_x;
+ *min_y = left_min_y;
+ *max_x = left_max_x;
+ *max_y = left_max_y;
+ *empty = left_empty;
+ return;
+ }
+ /* otherwise, it's an empty (or infinite) region */
+ *empty = TRUE;
+ return;
+ }
+
+ /* From here we know 0x1 isn't set, and we know at least one flag is set.
+ * We can ignore flag 0x8 because we must assume that any point within the
+ * intersection of the bounding boxes might be within the region. */
+ switch (flags & 0x6)
+ {
+ case 0x0:
+ /* intersection */
+ *min_x = fmaxf(left_min_x, right_min_x);
+ *min_y = fmaxf(left_min_y, right_min_y);
+ *max_x = fminf(left_max_x, right_max_x);
+ *max_y = fminf(left_max_y, right_max_y);
+ *empty = *min_x > *max_x || *min_y > *max_y;
+ return;
+ case 0x2:
+ /* right (or complement) */
+ *min_x = right_min_x;
+ *min_y = right_min_y;
+ *max_x = right_max_x;
+ *max_y = right_max_y;
+ *empty = right_empty;
+ return;
+ case 0x4:
+ /* left (or exclude) */
+ *min_x = left_min_x;
+ *min_y = left_min_y;
+ *max_x = left_max_x;
+ *max_y = left_max_y;
+ *empty = left_empty;
+ return;
+ case 0x6:
+ /* union (or xor) */
+ *min_x = fminf(left_min_x, right_min_x);
+ *min_y = fminf(left_min_y, right_min_y);
+ *max_x = fmaxf(left_max_x, right_max_x);
+ *max_y = fmaxf(left_max_y, right_max_y);
+ *empty = FALSE;
+ return;
+ }
+}
+
/*****************************************************************************
* GdipIsVisibleRegionPoint [GDIPLUS.@]
*/
@@ -1320,12 +1523,25 @@ GpStatus WINGDIPAPI GdipIsVisibleRegionPoint(GpRegion* region, REAL x, REAL y, G
{
HRGN hrgn;
GpStatus stat;
+ REAL min_x, min_y, max_x, max_y;
+ BOOL empty, infinite;
TRACE("(%p, %.2f, %.2f, %p, %p)\n", region, x, y, graphics, res);
if(!region || !res)
return InvalidParameter;
+ x = gdip_round(x);
+ y = gdip_round(y);
+
+ /* Check for cases where we can skip quantization. */
+ get_region_bounding_box(®ion->node, &min_x, &min_y, &max_x, &max_y, &empty, &infinite);
+ if (empty || x < min_x || y < min_y || x > max_x || y > max_y)
+ {
+ *res = infinite;
+ return Ok;
+ }
+
if((stat = GdipGetRegionHRgn(region, NULL, &hrgn)) != Ok)
return stat;
@@ -1335,7 +1551,7 @@ GpStatus WINGDIPAPI GdipIsVisibleRegionPoint(GpRegion* region, REAL x, REAL y, G
return Ok;
}
- *res = PtInRegion(hrgn, gdip_round(x), gdip_round(y));
+ *res = PtInRegion(hrgn, x, y);
DeleteObject(hrgn);