diff --git a/src/safeds/data/image/containers/_image.py b/src/safeds/data/image/containers/_image.py index 38cf88c4c..ddfeec3e6 100644 --- a/src/safeds/data/image/containers/_image.py +++ b/src/safeds/data/image/containers/_image.py @@ -2,6 +2,7 @@ import copy import io +import warnings from pathlib import Path from typing import Any, BinaryIO @@ -273,9 +274,9 @@ def flip_vertically(self) -> Image: result : Image The flipped image. """ - imagecopy = copy.deepcopy(self) - imagecopy._image = self._image.transpose(PIL.Image.FLIP_TOP_BOTTOM) - return imagecopy + image_copy = copy.deepcopy(self) + image_copy._image = self._image.transpose(PIL.Image.FLIP_TOP_BOTTOM) + return image_copy def flip_horizontally(self) -> Image: """ @@ -286,9 +287,69 @@ def flip_horizontally(self) -> Image: result : Image The flipped image. """ - imagecopy = copy.deepcopy(self) - imagecopy._image = self._image.transpose(PIL.Image.FLIP_LEFT_RIGHT) - return imagecopy + image_copy = copy.deepcopy(self) + image_copy._image = self._image.transpose(PIL.Image.FLIP_LEFT_RIGHT) + return image_copy + + def adjust_brightness(self, factor: float) -> Image: + """ + Adjust the brightness of an image. + + Parameters + ---------- + factor: float + The brightness factor. + 1.0 will not change the brightness. + Below 1.0 will result in a darker image. + Above 1.0 will resolut in a brighter image. + Has to be bigger than or equal to 0 (black). + + Returns + ------- + result: Image + The Image with adjusted brightness. + """ + if factor < 0: + raise ValueError("Brightness factor has to be 0 or bigger") + elif factor == 1: + warnings.warn( + "Brightness adjustment factor is 1.0, this will not make changes to the image.", + UserWarning, + stacklevel=2, + ) + + image_copy = copy.deepcopy(self) + image_copy._image = ImageEnhance.Brightness(image_copy._image).enhance(factor) + return image_copy + + def adjust_contrast(self, factor: float) -> Image: + """ + Adjust Contrast of image. + + Parameters + ---------- + factor: float + If factor > 1, increase contrast of image. + If factor = 1, no changes will be made. + If factor < 1, make image greyer. + Has to be bigger than or equal to 0 (gray). + + Returns + ------- + New image with adjusted contrast. + """ + if factor < 0: + raise ValueError("Contrast factor has to be 0 or bigger") + elif factor == 1: + warnings.warn( + "Contrast adjustment factor is 1.0, this will not make changes to the image.", + UserWarning, + stacklevel=2, + ) + + image_copy = copy.deepcopy(self) + image_copy._image = ImageEnhance.Contrast(image_copy._image).enhance(factor) + return image_copy def blur(self, radius: int = 1) -> Image: """ diff --git a/tests/resources/image/brightness/brightened_by_0.5.png b/tests/resources/image/brightness/brightened_by_0.5.png new file mode 100644 index 000000000..f2a45388c Binary files /dev/null and b/tests/resources/image/brightness/brightened_by_0.5.png differ diff --git a/tests/resources/image/brightness/brightened_by_10.png b/tests/resources/image/brightness/brightened_by_10.png new file mode 100644 index 000000000..bcf849d96 Binary files /dev/null and b/tests/resources/image/brightness/brightened_by_10.png differ diff --git a/tests/resources/image/brightness/to_brighten.png b/tests/resources/image/brightness/to_brighten.png new file mode 100644 index 000000000..2c9e5d18e Binary files /dev/null and b/tests/resources/image/brightness/to_brighten.png differ diff --git a/tests/resources/image/contrast/contrast_adjusted_by_0.75.png b/tests/resources/image/contrast/contrast_adjusted_by_0.75.png new file mode 100644 index 000000000..e0d1e1b41 Binary files /dev/null and b/tests/resources/image/contrast/contrast_adjusted_by_0.75.png differ diff --git a/tests/resources/image/contrast/contrast_adjusted_by_5.png b/tests/resources/image/contrast/contrast_adjusted_by_5.png new file mode 100644 index 000000000..67a6ecaaa Binary files /dev/null and b/tests/resources/image/contrast/contrast_adjusted_by_5.png differ diff --git a/tests/resources/image/contrast/to_adjust_contrast.png b/tests/resources/image/contrast/to_adjust_contrast.png new file mode 100644 index 000000000..2c9e5d18e Binary files /dev/null and b/tests/resources/image/contrast/to_adjust_contrast.png differ diff --git a/tests/safeds/data/image/containers/test_image.py b/tests/safeds/data/image/containers/test_image.py index 968a50d07..29237dcd9 100644 --- a/tests/safeds/data/image/containers/test_image.py +++ b/tests/safeds/data/image/containers/test_image.py @@ -242,9 +242,10 @@ def test_should_raise(self) -> None: 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 + image2 = image.flip_vertically() + image3 = Image.from_png_file(resolve_resource_path("image/flip_vertically.png")) + assert image != image2 + assert image2 == image3 def test_should_be_original(self) -> None: image = Image.from_png_file(resolve_resource_path("image/original.png")) @@ -255,9 +256,10 @@ def test_should_be_original(self) -> None: 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 + image2 = image.flip_horizontally() + image3 = Image.from_png_file(resolve_resource_path("image/flip_horizontally.png")) + assert image != image2 + assert image2 == image3 def test_should_be_original(self) -> None: image = Image.from_png_file(resolve_resource_path("image/original.png")) @@ -265,6 +267,56 @@ def test_should_be_original(self) -> None: assert image == image2 +class TestAdjustContrast: + @pytest.mark.parametrize("factor", [0.75, 5]) + def test_should_adjust_contrast(self, factor: float) -> None: + image = Image.from_png_file(resolve_resource_path("image/contrast/to_adjust_contrast.png")) + image2 = image.adjust_contrast(factor) + image3 = Image.from_png_file( + resolve_resource_path("image/contrast/contrast_adjusted_by_" + str(factor) + ".png"), + ) + assert image != image2 + assert image2 == image3 + + def test_should_not_adjust_contrast(self) -> None: + with pytest.warns( + UserWarning, + match="Contrast adjustment factor is 1.0, this will not make changes to the image.", + ): + image = Image.from_png_file(resolve_resource_path("image/contrast/to_adjust_contrast.png")) + image2 = image.adjust_contrast(1) + assert image == image2 + + def test_should_raise(self) -> None: + image = Image.from_png_file(resolve_resource_path("image/brightness/to_brighten.png")) + with pytest.raises(ValueError, match="Contrast factor has to be 0 or bigger"): + image.adjust_contrast(-1) + + +class TestBrightness: + @pytest.mark.parametrize("factor", [0.5, 10]) + def test_should_adjust_brightness(self, factor: float) -> None: + image = Image.from_png_file(resolve_resource_path("image/brightness/to_brighten.png")) + image2 = image.adjust_brightness(factor) + image3 = Image.from_png_file(resolve_resource_path("image/brightness/brightened_by_" + str(factor) + ".png")) + assert image != image2 + assert image2 == image3 + + def test_should_not_brighten(self) -> None: + with pytest.warns( + UserWarning, + match="Brightness adjustment factor is 1.0, this will not make changes to the image.", + ): + image = Image.from_png_file(resolve_resource_path("image/brightness/to_brighten.png")) + image2 = image.adjust_brightness(1) + assert image == image2 + + def test_should_raise(self) -> None: + image = Image.from_png_file(resolve_resource_path("image/brightness/to_brighten.png")) + with pytest.raises(ValueError, match="Brightness factor has to be 0 or bigger"): + image.adjust_brightness(-1) + + class TestInvertColors: def test_should_invert_colors_png(self) -> None: image = Image.from_png_file(resolve_resource_path("image/original.png"))