Skip to content

Commit

Permalink
fix clipping of image boundaries
Browse files Browse the repository at this point in the history
  • Loading branch information
mozman committed Jan 2, 2024
1 parent e45adfc commit aaf7fc2
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 9 deletions.
25 changes: 24 additions & 1 deletion src/ezdxf/addons/drawing/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@
BkPath2d: TypeAlias = NumpyPath2d
BkPoints2d: TypeAlias = NumpyPoints2d

# fmt: off
_IMAGE_FLIP_MATRIX = [
1.0, 0.0, 0.0, 0.0,
0.0, -1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 999, 0.0, 1.0 # index 13: 999 = image height
]
# fmt: on


@dataclasses.dataclass
class ImageData:
Expand All @@ -27,7 +36,8 @@ class ImageData:
image: an array of RGBA pixels
transform: the transformation to apply to the image when drawing
(the transform from pixel coordinates to wcs)
pixel_boundary_path: boundary path vertices in pixel coordinates
pixel_boundary_path: boundary path vertices in pixel coordinates, the image
coordinate system has an inverted y-axis and the top-left corner is (0, 0)
"""

Expand All @@ -36,6 +46,19 @@ class ImageData:
pixel_boundary_path: NumpyPoints2d
use_clipping_boundary: bool = False

def image_size(self) -> tuple[int, int]:
"""Returns the image size as tuple (width, height)."""
image_height, image_width, *_ = self.image.shape
return image_width, image_height

def flip_matrix(self) -> Matrix44:
"""Returns the transformation matrix to align the image coordinate system with
the WCS.
"""
_, image_height = self.image_size()
_IMAGE_FLIP_MATRIX[13] = image_height
return Matrix44(_IMAGE_FLIP_MATRIX)


class BackendInterface(ABC):
"""Public interface for 2D rendering backends."""
Expand Down
16 changes: 9 additions & 7 deletions src/ezdxf/addons/drawing/designer.py
Original file line number Diff line number Diff line change
Expand Up @@ -438,11 +438,7 @@ def draw_text(
def draw_image(self, image_data: ImageData, properties: Properties) -> None:
if self.clipping_portal.is_active:
# the pixel boundary path can be split into multiple paths
boundary_paths = _clip_image_boundary_path(
self.clipping_portal,
image_data.pixel_boundary_path,
image_data.transform,
)
boundary_paths = _clip_image_boundary_path(self.clipping_portal, image_data)
image_data.transform = self.clipping_portal.transform_matrix(
image_data.transform
)
Expand Down Expand Up @@ -497,10 +493,15 @@ def _mask_image(image_data: ImageData) -> None:


def _clip_image_boundary_path(
clipping_portal: ClippingPortal, pixel_boundary_path: BkPoints2d, m: Matrix44
clipping_portal: ClippingPortal, image_data: ImageData
) -> list[BkPoints2d]:
pixel_boundary_path = image_data.pixel_boundary_path

# flip image coordinate system
m = image_data.flip_matrix() @ image_data.transform
original = [pixel_boundary_path]
# include transformation applied by the clipping portal

# inverse matrix includes the transformation applied by the clipping portal
inverse = clipping_portal.transform_matrix(m)
try:
inverse.inverse()
Expand All @@ -512,6 +513,7 @@ def _clip_image_boundary_path(
wcs_polygon.transform_inplace(m)
clipped_wcs_polygons = clipping_portal.clip_polygon(wcs_polygon)
if (len(clipped_wcs_polygons) == 1) and (clipped_wcs_polygons[0] is wcs_polygon):
# this shows the caller that the image boundary path wasn't clipped
return original
for polygon in clipped_wcs_polygons:
polygon.transform_inplace(inverse)
Expand Down
3 changes: 2 additions & 1 deletion src/ezdxf/entities/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,8 @@ def get_wcs_transform(self) -> Matrix44:

def pixel_boundary_path(self) -> list[Vec2]:
"""Returns the boundary path as closed loop in pixel coordinates. Resolves the
simple form of two vertices as a rectangle.
simple form of two vertices as a rectangle. The image coordinate system has an
inverted y-axis and the top-left corner is (0, 0).
.. versionchanged:: 1.2.0
Expand Down

0 comments on commit aaf7fc2

Please sign in to comment.