From 82842ae0af4dbdc730c6ee26074a0f4bb0964d07 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 7 Aug 2023 15:14:26 +0200 Subject: [PATCH] Apply PageUnit to clip regions. Previously `PageUnit` was being ignored when applying/calculating clips. Adds a `gdip_get_page_transform` function which combines the `clip_matrix` and `PageUnit` scaling (together with its inverse in `gdip_get_inverse_page_transform`) and uses this when setting the clip region and calculating clip bounds. Also adds tests for these operations. --- src/graphics-cairo.c | 9 ++- src/graphics-private.h | 2 + src/graphics.c | 70 ++++++++++++------ tests/testclip.c | 161 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 218 insertions(+), 24 deletions(-) diff --git a/src/graphics-cairo.c b/src/graphics-cairo.c index 8fb76c3e1..fe621b90f 100644 --- a/src/graphics-cairo.c +++ b/src/graphics-cairo.c @@ -835,11 +835,16 @@ cairo_SetGraphicsClip (GpGraphics *graphics) if (gdip_is_InfiniteRegion (graphics->overall_clip)) return Ok; - if (gdip_is_matrix_empty (graphics->clip_matrix)) { + /* Clip region is in device coordinates but we're drawing in page coordinates + * so we need to draw with an inverse page transform */ + GpMatrix page; + gdip_get_inverse_page_transform(graphics, &page); + + if (gdip_is_matrix_empty (&page)) { work = graphics->overall_clip; } else { GdipCloneRegion (graphics->overall_clip, &work); - GdipTransformRegion (work, graphics->clip_matrix); + GdipTransformRegion (work, &page); } switch (work->type) { diff --git a/src/graphics-private.h b/src/graphics-private.h index e91a2c2fa..e48a4ca25 100644 --- a/src/graphics-private.h +++ b/src/graphics-private.h @@ -157,6 +157,8 @@ typedef struct _Graphics { } Graphics; float gdip_unit_conversion (Unit from, Unit to, float dpi, GraphicsType type, float nSrc) GDIP_INTERNAL; +void gdip_get_page_transform(GpGraphics* graphics, GpMatrix* matrix) GDIP_INTERNAL; +void gdip_get_inverse_page_transform(GpGraphics* graphics, GpMatrix* matrix) GDIP_INTERNAL; void gdip_set_cairo_clipping (GpGraphics *graphics) GDIP_INTERNAL; GpStatus gdip_calculate_overall_clipping (GpGraphics *graphics) GDIP_INTERNAL; diff --git a/src/graphics.c b/src/graphics.c index 44685e4ed..3cc10d7db 100644 --- a/src/graphics.c +++ b/src/graphics.c @@ -112,6 +112,24 @@ gdip_unit_conversion (Unit from, Unit to, float dpi, GraphicsType type, float nS } } +void +gdip_get_page_transform(GpGraphics* graphics, GpMatrix* matrix) +{ + /* The page transformaion converts page coordinates to device coordinates */ + double scaleX = gdip_unit_conversion(graphics->page_unit, UnitDisplay, graphics->dpi_x, graphics->type, 1); + double scaleY = gdip_unit_conversion(graphics->page_unit, UnitDisplay, graphics->dpi_y, graphics->type, 1); + + cairo_matrix_init_scale(matrix, scaleX, scaleY); + cairo_matrix_multiply(matrix, graphics->copy_of_ctm, matrix); +} + +void +gdip_get_inverse_page_transform(GpGraphics* graphics, GpMatrix* matrix) +{ + gdip_get_page_transform(graphics, matrix); + cairo_matrix_invert(matrix); +} + static void gdip_graphics_reset (GpGraphics *graphics) { @@ -2097,19 +2115,17 @@ GdipSetClipPath (GpGraphics *graphics, GpPath *path, CombineMode combineMode) if (!path || combineMode > CombineModeComplement) return InvalidParameter; + GpMatrix page; + gdip_get_page_transform(graphics, &page); + /* if the matrix is empty, avoid path cloning and transform */ - if (gdip_is_matrix_empty (graphics->clip_matrix)) { + if (gdip_is_matrix_empty (&page)) { work = path; } else { - cairo_matrix_t inverted; - - gdip_cairo_matrix_copy (&inverted, graphics->clip_matrix); - cairo_matrix_invert (&inverted); - status = GdipClonePath (path, &work); if (status != Ok) return status; - GdipTransformPath (work, &inverted); + GdipTransformPath (work, &page); } status = GdipCombineRegionPath (graphics->clip, work, combineMode); @@ -2152,17 +2168,15 @@ GdipSetClipRegion (GpGraphics *graphics, GpRegion *region, CombineMode combineMo if (!region || combineMode > CombineModeComplement) return InvalidParameter; + GpMatrix page; + gdip_get_page_transform(graphics, &page); + /* if the matrix is empty, avoid region cloning and transform */ - if (gdip_is_matrix_empty (graphics->clip_matrix)) { + if (gdip_is_matrix_empty (&page)) { work = region; } else { - cairo_matrix_t inverted; - - gdip_cairo_matrix_copy (&inverted, graphics->clip_matrix); - cairo_matrix_invert (&inverted); - GdipCloneRegion (region, &work); - GdipTransformRegion (work, &inverted); + GdipTransformRegion (work, &page); } status = GdipCombineRegionRegion (graphics->clip, work, combineMode); @@ -2292,9 +2306,12 @@ GdipGetClip (GpGraphics *graphics, GpRegion *region) gdip_clear_region (region); gdip_copy_region (graphics->clip, region); - if (gdip_is_matrix_empty (graphics->clip_matrix)) + GpMatrix page; + gdip_get_inverse_page_transform(graphics, &page); + + if (gdip_is_matrix_empty (&page)) return Ok; - return GdipTransformRegion (region, graphics->clip_matrix); + return GdipTransformRegion (region, &page); } GpStatus WINGDIPAPI @@ -2322,12 +2339,15 @@ GdipGetClipBounds (GpGraphics *graphics, GpRectF *rect) return Ok; } + GpMatrix page; + gdip_get_inverse_page_transform(graphics, &page); + /* if the matrix is empty, avoid region cloning and transform */ - if (gdip_is_matrix_empty (graphics->clip_matrix)) { + if (gdip_is_matrix_empty (&page)) { work = graphics->clip; } else { GdipCloneRegion (graphics->clip, &work); - GdipTransformRegion (work, graphics->clip_matrix); + GdipTransformRegion (work, &page); } status = GdipGetRegionBounds (work, graphics, rect); @@ -2398,8 +2418,11 @@ GpStatus gdip_get_visible_clip (GpGraphics *graphics, GpRegion **visible_clip) if (status != Ok) return status; - if (!gdip_is_matrix_empty (graphics->clip_matrix)) { - GdipTransformRegion (clip, graphics->clip_matrix); + GpMatrix page; + gdip_get_inverse_page_transform(graphics, &page); + + if (!gdip_is_matrix_empty (&page)) { + GdipTransformRegion (clip, &page); } status = GdipCombineRegionRectI (clip, &graphics->bounds, CombineModeIntersect); @@ -2436,10 +2459,13 @@ GdipGetVisibleClipBounds (GpGraphics *graphics, GpRectF *rect) rect->X += graphics->clip_matrix->x0; rect->Y += graphics->clip_matrix->y0; } else if (!gdip_is_InfiniteRegion (clip)) { + GpMatrix page; + gdip_get_inverse_page_transform(graphics, &page); + /* if the matrix is empty, avoid region cloning and transform */ - if (!gdip_is_matrix_empty (graphics->clip_matrix)) { + if (!gdip_is_matrix_empty (&page)) { GdipCloneRegion (graphics->overall_clip, &clip); - GdipTransformRegion (clip, graphics->clip_matrix); + GdipTransformRegion (clip, &page); } RectF clipbound; diff --git a/tests/testclip.c b/tests/testclip.c index 163850326..fbafbbbe1 100644 --- a/tests/testclip.c +++ b/tests/testclip.c @@ -20,6 +20,8 @@ using namespace Gdiplus; using namespace DllExports; #endif +static void assertClipBounds(GpGraphics* graphics, double x, double y, double width, double height); + static void test_gdip_clip() { @@ -547,6 +549,163 @@ test_gdip_clip_path () C (GdipDisposeImage (bitmap)); } +static void +test_gdip_clip_page_units () +{ + GpBitmap *bitmap = 0; + GpGraphics *graphics; + GpMatrix* matrix; + BOOL contains; + + C (GdipCreateMatrix(&matrix)); + C (GdipCreateBitmapFromScan0 (612, 792, 0, PixelFormat32bppARGB, NULL, &bitmap)); + C (GdipGetImageGraphicsContext (bitmap, &graphics)); + + /* Set the clip rect in Display units. */ + C (GdipSetClipRect (graphics, 20, 30, 40, 50, CombineModeReplace)); + + /* Clip bounds should be as requested */ + assertClipBounds(graphics, 20, 30, 40, 50); + + /* Change to Document units */ + C (GdipSetPageUnit(graphics, UnitDocument)); + + /* Clip bounds should be scaled */ + assertClipBounds(graphics, 62.5, 93.75, 125, 156.25); + + /* Hit testing should be correct */ + C (GdipIsVisiblePoint(graphics, 70, 100, &contains)); + assert (contains); + C (GdipIsVisiblePoint(graphics, 60, 90, &contains)); + assert (!contains); + + /* Set the clip rect in Document units. */ + C (GdipSetPageUnit(graphics, UnitDocument)); + C (GdipSetClipRect (graphics, 62.5, 93.75, 125, 156.25, CombineModeReplace)); + + /* Clip bounds should be equal to requested */ + assertClipBounds (graphics, 62.5, 93.75, 125, 156.25); + + /* Hit testing should be correct */ + C (GdipIsVisiblePoint(graphics, 70, 100, &contains)); + assert (contains); + C (GdipIsVisiblePoint(graphics, 60, 90, &contains)); + assert (!contains); + + /* Set the clip in Display units with translate transform */ + C (GdipSetPageUnit(graphics, UnitDisplay)); + C (GdipTranslateWorldTransform(graphics, 20, 30, MatrixOrderAppend)); + C (GdipSetClipRect (graphics, 0, 0, 40, 50, CombineModeReplace)); + + /* Clip bounds and matrix should be as requested */ + assertClipBounds (graphics, 0, 0, 40, 50); + C (GdipGetWorldTransform(graphics, matrix)); + verifyMatrix(matrix, 1, 0, 0, 1, 20, 30); + + /* Change to Document units */ + C (GdipSetPageUnit(graphics, UnitDocument)); + + /* Clip bounds should be scaled and matrix the same */ + assertClipBounds (graphics, 42.5, 63.75, 125, 156.25); + C (GdipGetWorldTransform(graphics, matrix)); + verifyMatrix(matrix, 1, 0, 0, 1, 20, 30); + + GdipDeleteMatrix(matrix); +} + +static void +test_gdip_clip_path_page_units () +{ + GpBitmap *bitmap = 0; + GpGraphics *graphics; + GpMatrix* matrix; + GpPath* path; + BOOL contains; + + C (GdipCreateMatrix(&matrix)); + C (GdipCreateBitmapFromScan0 (612, 792, 0, PixelFormat32bppARGB, NULL, &bitmap)); + C (GdipGetImageGraphicsContext (bitmap, &graphics)); + + /* Set the clip path in Display units. */ + C (GdipCreatePath(FillModeWinding, &path)); + C (GdipAddPathRectangle(path, 20, 30, 40, 50)); + C (GdipSetClipPath (graphics, path, CombineModeReplace)); + + /* Clip bounds should be as requested */ + assertClipBounds(graphics, 20, 30, 40, 50); + + /* Change to Document units */ + C (GdipSetPageUnit(graphics, UnitDocument)); + + /* Clip bounds should be scaled */ + assertClipBounds(graphics, 62.5, 93.75, 125, 156.25); + + /* Hit testing should be correct */ + C (GdipIsVisiblePoint(graphics, 70, 100, &contains)); + assert (contains); + C (GdipIsVisiblePoint(graphics, 60, 90, &contains)); + assert (!contains); + + /* Set the clip rect in Document units. */ + C (GdipSetPageUnit(graphics, UnitDocument)); + C (GdipResetPath(path)); + C (GdipAddPathRectangle(path, 62.5, 93.75, 125, 156.25)); + C (GdipSetClipPath (graphics, path, CombineModeReplace)); + + /* Clip bounds should be equal to requested */ + assertClipBounds (graphics, 62.5, 93.75, 125, 156.25); + + /* Hit testing should be correct */ + C (GdipIsVisiblePoint(graphics, 70, 100, &contains)); + assert (contains); + C (GdipIsVisiblePoint(graphics, 60, 90, &contains)); + assert (!contains); + + /* Set the clip in Display units with translate transform */ + C (GdipSetPageUnit(graphics, UnitDisplay)); + C (GdipTranslateWorldTransform(graphics, 20, 30, MatrixOrderAppend)); + C (GdipResetPath(path)); + C (GdipAddPathRectangle(path, 0, 0, 40, 50)); + C (GdipSetClipPath (graphics, path, CombineModeReplace)); + + /* Clip bounds and matrix should be as requested */ + assertClipBounds (graphics, 0, 0, 40, 50); + C (GdipGetWorldTransform(graphics, matrix)); + verifyMatrix(matrix, 1, 0, 0, 1, 20, 30); + + /* Change to Document units */ + C (GdipSetPageUnit(graphics, UnitDocument)); + + /* Clip bounds should be scaled and matrix the same */ + assertClipBounds (graphics, 42.5, 63.75, 125, 156.25); + C (GdipGetWorldTransform(graphics, matrix)); + verifyMatrix(matrix, 1, 0, 0, 1, 20, 30); + + GdipDeleteMatrix(matrix); + GdipDeletePath(path); +} + +void +assertClipBounds(GpGraphics* graphics, double x, double y, double width, double height) +{ + GpRectF bounds; + GpRegion* region; + + C (GdipCreateRegion(®ion)); + + C (GdipGetClipBounds(graphics, &bounds)); + assertEqualRectFInline (bounds, x, y, width, height); + + C (GdipGetClip(graphics, region)); + C (GdipGetRegionBounds(region, graphics, &bounds)); + assertEqualRectFInline (bounds, x, y, width, height); + + C (GdipGetVisibleClipBounds(graphics, &bounds)); + assertEqualRectFInline (bounds, x, y, width, height); + + GdipDeleteRegion(region); +} + int main(int argc, char**argv) { @@ -557,6 +716,8 @@ main(int argc, char**argv) test_gdip_clip_containers (); test_gdip_clip_visible (); test_gdip_clip_path (); + test_gdip_clip_page_units (); + test_gdip_clip_path_page_units (); SHUTDOWN; return 0;