diff --git a/src/safeds/data/image/containers/_image.py b/src/safeds/data/image/containers/_image.py index 4649b3e0b..2c3659859 100644 --- a/src/safeds/data/image/containers/_image.py +++ b/src/safeds/data/image/containers/_image.py @@ -1,9 +1,11 @@ from __future__ import annotations +import copy import io from pathlib import Path -from typing import BinaryIO +from typing import Any, BinaryIO +import PIL from PIL.Image import Image as PillowImage from PIL.Image import open as open_image @@ -138,6 +140,24 @@ def to_png_file(self, path: str | Path) -> None: # IPython integration # ------------------------------------------------------------------------------------------------------------------ + def __eq__(self, other: Any) -> bool: + """ + Compare two images. + + Parameters + ---------- + other: The image to compare to. + + Returns + ------- + equals : bool + Whether the two images contain equal pixel data. + + """ + if not isinstance(other, Image): + return NotImplemented + return self._image.tobytes() == other._image.tobytes() + def _repr_jpeg_(self) -> bytes | None: """ Return a JPEG image as bytes. @@ -182,7 +202,7 @@ def _repr_png_(self) -> bytes | None: def resize(self, new_width: int, new_height: int) -> Image: """ - Return the resized image. + Return an image that has been resized to a given size. Returns ------- @@ -200,3 +220,29 @@ def resize(self, new_width: int, new_height: int) -> Image: new_image = Image(data, self._format) new_image._image = new_image._image.resize((new_width, new_height)) return new_image + + def flip_vertically(self) -> Image: + """ + Flip the image vertically (horizontal axis, flips up-down and vice versa). + + Returns + ------- + result : Image + The flipped image. + """ + imagecopy = copy.deepcopy(self) + imagecopy._image = self._image.transpose(PIL.Image.FLIP_TOP_BOTTOM) + return imagecopy + + def flip_horizontally(self) -> Image: + """ + Flip the image horizontally (vertical axis, flips left-right and vice versa). + + Returns + ------- + result : Image + The flipped image. + """ + imagecopy = copy.deepcopy(self) + imagecopy._image = self._image.transpose(PIL.Image.FLIP_LEFT_RIGHT) + return imagecopy diff --git a/tests/resources/image/copy.png b/tests/resources/image/copy.png new file mode 100644 index 000000000..09e504aed Binary files /dev/null and b/tests/resources/image/copy.png differ diff --git a/tests/resources/image/flip_horizontally.png b/tests/resources/image/flip_horizontally.png new file mode 100644 index 000000000..bad057148 Binary files /dev/null and b/tests/resources/image/flip_horizontally.png differ diff --git a/tests/resources/image/flip_vertically.png b/tests/resources/image/flip_vertically.png new file mode 100644 index 000000000..2031af9a7 Binary files /dev/null and b/tests/resources/image/flip_vertically.png differ diff --git a/tests/resources/image/original.png b/tests/resources/image/original.png new file mode 100644 index 000000000..09e504aed Binary files /dev/null and b/tests/resources/image/original.png differ diff --git a/tests/safeds/data/image/containers/test_image.py b/tests/safeds/data/image/containers/test_image.py index 29164d27f..0b8e09bd6 100644 --- a/tests/safeds/data/image/containers/test_image.py +++ b/tests/safeds/data/image/containers/test_image.py @@ -4,6 +4,7 @@ import pytest from safeds.data.image.containers import Image from safeds.data.image.typing import ImageFormat +from safeds.data.tabular.containers import Table from tests.helpers import resolve_resource_path @@ -203,3 +204,46 @@ def test_should_return_resized_image( new_size: tuple[int, int], ) -> None: assert image.resize(new_width, new_height)._image.size == new_size + + +class TestEQ: + def test_should_be_equal(self) -> None: + image = Image.from_png_file(resolve_resource_path("image/original.png")) + image2 = Image.from_png_file(resolve_resource_path("image/copy.png")) + assert image == image2 + + def test_should_not_be_equal(self) -> None: + image = Image.from_png_file(resolve_resource_path("image/original.png")) + image2 = Image.from_png_file(resolve_resource_path("image/white_square.png")) + assert image != image2 + + def test_should_raise(self) -> None: + image = Image.from_png_file(resolve_resource_path("image/original.png")) + other = Table() + assert (image.__eq__(other)) is NotImplemented + + +class TestFlipVertically: + def test_should_flip_vertically(self) -> None: + image = Image.from_png_file(resolve_resource_path("image/original.png")) + image = image.flip_vertically() + image2 = Image.from_png_file(resolve_resource_path("image/flip_vertically.png")) + assert image == image2 + + def test_should_be_original(self) -> None: + image = Image.from_png_file(resolve_resource_path("image/original.png")) + image2 = image.flip_vertically().flip_vertically() + assert image == image2 + + +class TestFlipHorizontally: + def test_should_flip_horizontally(self) -> None: + image = Image.from_png_file(resolve_resource_path("image/original.png")) + image = image.flip_horizontally() + image2 = Image.from_png_file(resolve_resource_path("image/flip_horizontally.png")) + assert image == image2 + + def test_should_be_original(self) -> None: + image = Image.from_png_file(resolve_resource_path("image/original.png")) + image2 = image.flip_horizontally().flip_horizontally() + assert image == image2