Skip to content

Commit

Permalink
Merge branch 'main' into remove_defaults
Browse files Browse the repository at this point in the history
  • Loading branch information
lars-reimann authored Jun 23, 2023
2 parents e987812 + 3520ce2 commit 7097df3
Show file tree
Hide file tree
Showing 10 changed files with 118 additions and 149 deletions.
6 changes: 4 additions & 2 deletions .github/workflows/megalinter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,7 @@ on:
jobs:
megalinter:
uses: lars-reimann/.github/.github/workflows/megalinter-reusable.yml@main
secrets:
PAT: ${{ secrets.PAT }}
permissions:
contents: write
issues: write
pull-requests: write
107 changes: 38 additions & 69 deletions src/safeds/data/image/containers/_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
from PIL.Image import open as open_image

from safeds.data.image.typing import ImageFormat
from safeds.exceptions import WrongFileExtensionError


class Image:
Expand Down Expand Up @@ -205,24 +204,16 @@ def _repr_png_(self) -> bytes | None:

def resize(self, new_width: int, new_height: int) -> Image:
"""
Return an image that has been resized to a given size.
Return a new image that has been resized to a given size.
Returns
-------
result : Image
The image with the given width and height
"""
data = io.BytesIO()
repr_png = self._repr_png_()
repr_jpeg = self._repr_jpeg_()
if repr_png is not None:
data = io.BytesIO(repr_png)
elif repr_jpeg is not None:
data = io.BytesIO(repr_jpeg)

new_image = Image(data, self._format)
new_image._image = new_image._image.resize((new_width, new_height))
return new_image
image_copy = copy.deepcopy(self)
image_copy._image = image_copy._image.resize((new_width, new_height))
return image_copy

def convert_to_grayscale(self) -> Image:
"""
Expand All @@ -233,10 +224,9 @@ def convert_to_grayscale(self) -> Image:
grayscale_image : Image
The grayscale image.
"""
data = io.BytesIO()
grayscale_image = self._image.convert("L")
grayscale_image.save(data, format=self._format.value)
return Image(data, self._format)
image_copy = copy.deepcopy(self)
image_copy._image = image_copy._image.convert("L")
return image_copy

def crop(self, x: int, y: int, width: int, height: int) -> Image:
"""
Expand All @@ -254,17 +244,9 @@ def crop(self, x: int, y: int, width: int, height: int) -> Image:
result : Image
The image with the
"""
data = io.BytesIO()
repr_png = self._repr_png_()
repr_jpeg = self._repr_jpeg_()
if repr_png is not None:
data = io.BytesIO(repr_png)
elif repr_jpeg is not None:
data = io.BytesIO(repr_jpeg)

new_image = Image(data, self._format)
new_image._image = new_image._image.crop((x, y, (x + width), (y + height)))
return new_image
image_copy = copy.deepcopy(self)
image_copy._image = image_copy._image.crop((x, y, (x + width), (y + height)))
return image_copy

def flip_vertically(self) -> Image:
"""
Expand Down Expand Up @@ -367,17 +349,9 @@ def blur(self, radius: int) -> Image:
result : Image
The blurred image
"""
data = io.BytesIO()
repr_png = self._repr_png_()
repr_jpeg = self._repr_jpeg_()
if repr_png is not None:
data = io.BytesIO(repr_png)
elif repr_jpeg is not None:
data = io.BytesIO(repr_jpeg)

new_image = Image(data, self._format)
new_image._image = new_image._image.filter(ImageFilter.BoxBlur(radius))
return new_image
image_copy = copy.deepcopy(self)
image_copy._image = image_copy._image.filter(ImageFilter.BoxBlur(radius))
return image_copy

def sharpen(self, factor: float) -> Image:
"""
Expand Down Expand Up @@ -406,51 +380,46 @@ def invert_colors(self) -> Image:
result : Image
The image with inverted colors.
"""
data = io.BytesIO()
repr_png = self._repr_png_()
repr_jpeg = self._repr_jpeg_()
if repr_png is not None:
data = io.BytesIO(repr_png)
elif repr_jpeg is not None:
data = io.BytesIO(repr_jpeg)

new_image = Image(data, self._format)
new_image._image = ImageOps.invert(new_image._image)
return new_image
image_copy = copy.deepcopy(self)
image_copy._image = ImageOps.invert(image_copy._image)
return image_copy

def rotate_right(self) -> Image:
"""
Return the png-image clockwise rotated by 90 degrees.
Return the image rotated 90 degrees clockwise.
Returns
-------
result : Image
The image clockwise rotated by 90 degrees.
The image rotated 90 degrees clockwise.
"""
if self.format != ImageFormat.PNG:
raise WrongFileExtensionError("/image", ".png")

imagecopy = copy.deepcopy(self)
imagecopy._image = imagecopy._image.rotate(270, expand=True)
return imagecopy
image_copy = copy.deepcopy(self)
image_copy._image = image_copy._image.rotate(270, expand=True)
return image_copy

def rotate_left(self) -> Image:
"""
Return the png-image counter-clockwise rotated by 90 degrees.
Return the image rotated 90 degrees counter-clockwise.
Returns
-------
result : Image
The image counter-clockwise rotated by 90 degrees.
The image rotated 90 degrees counter-clockwise.
"""
image_copy = copy.deepcopy(self)
image_copy._image = image_copy._image.rotate(90, expand=True)
return image_copy

Raises
------
WrongFileExtensionError
If given a File that's not PNG.
def find_edges(self) -> Image:
"""
if self.format != ImageFormat.PNG:
raise WrongFileExtensionError("/image", ".png")
Return a grayscale image with the highlighted edges.
imagecopy = copy.deepcopy(self)
imagecopy._image = imagecopy._image.rotate(90, expand=True)
return imagecopy
Returns
-------
result : Image
The image with edges found.
"""
image_copy = copy.deepcopy(self)
image_copy = image_copy.convert_to_grayscale()
image_copy._image = image_copy._image.filter(ImageFilter.FIND_EDGES)
return image_copy
File renamed without changes
Binary file removed tests/resources/image/blurredboy.jpg
Binary file not shown.
Binary file removed tests/resources/image/boy.jpg
Binary file not shown.
Binary file added tests/resources/image/edgyBoy.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed tests/resources/image/original.jpg
Binary file not shown.
Binary file removed tests/resources/image/white.jpg
Binary file not shown.
Binary file removed tests/resources/image/whiteCropped.jpg
Binary file not shown.
154 changes: 76 additions & 78 deletions tests/safeds/data/image/containers/test_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from safeds.data.image.containers import Image
from safeds.data.image.typing import ImageFormat
from safeds.data.tabular.containers import Table
from safeds.exceptions._data import WrongFileExtensionError

from tests.helpers import resolve_resource_path

Expand Down Expand Up @@ -182,20 +181,14 @@ class TestResize:
@pytest.mark.parametrize(
("image", "new_width", "new_height", "new_size"),
[
(
Image.from_jpeg_file(resolve_resource_path("image/white_square.jpg")),
2,
3,
(2, 3),
),
(
Image.from_png_file(resolve_resource_path("image/white_square.png")),
2,
3,
(2, 3),
),
],
ids=[".jpg", ".png"],
ids=["(2, 3)"],
)
def test_should_return_resized_image(
self,
Expand All @@ -220,7 +213,7 @@ class TestConvertToGrayscale:
)
def test_convert_to_grayscale(self, image: Image, expected: Image) -> None:
grayscale_image = image.convert_to_grayscale()
assert grayscale_image._image.tobytes() == expected._image.tobytes()
assert grayscale_image == expected


class TestEQ:
Expand Down Expand Up @@ -319,54 +312,51 @@ def test_should_raise(self) -> None:


class TestInvertColors:
def test_should_invert_colors_png(self) -> None:
image = Image.from_png_file(resolve_resource_path("image/original.png"))
image = image.invert_colors()
image2 = Image.from_png_file(resolve_resource_path("image/inverted_colors_original.png"))
assert image == image2

def test_should_invert_colors_jpeg(self) -> None:
image = Image.from_jpeg_file(resolve_resource_path("image/original.jpg"))
@pytest.mark.parametrize(
("image", "expected"),
[
(
Image.from_png_file(resolve_resource_path("image/original.png")),
Image.from_png_file(resolve_resource_path("image/inverted_colors_original.png")),
),
],
ids=["invert-colors"],
)
def test_should_invert_colors(self, image: Image, expected: Image) -> None:
image = image.invert_colors()
image.to_jpeg_file(resolve_resource_path("image/inverted_colors_original1.jpg"))
image = Image.from_jpeg_file(resolve_resource_path("image/inverted_colors_original1.jpg"))
image2 = Image.from_jpeg_file(resolve_resource_path("image/inverted_colors_original.jpg"))
assert image == image2
Path.unlink(Path(resolve_resource_path("image/inverted_colors_original1.jpg")))
assert image == expected


class TestBlur:
def test_should_return_blurred_png_image(self) -> None:
image = Image.from_png_file(resolve_resource_path("image/boy.png"))
image = image.blur(2)
image.to_png_file(resolve_resource_path("image/blurredboy1.png"))
image = Image.from_png_file(resolve_resource_path("image/blurredboy1.png"))
image2 = Image.from_png_file(resolve_resource_path("image/blurredboy.png"))
assert image._image == image2._image

def test_should_return_blurred_jpg_image(self) -> None:
image = Image.from_jpeg_file(resolve_resource_path("image/boy.jpg"))
@pytest.mark.parametrize(
("image", "expected"),
[
(
Image.from_png_file(resolve_resource_path("image/boy.png")),
Image.from_png_file(resolve_resource_path("image/blurredBoy.png")),
),
],
ids=["blur"],
)
def test_should_return_blurred_image(self, image: Image, expected: Image) -> None:
image = image.blur(2)
image.to_jpeg_file(resolve_resource_path("image/blurredboy1.jpg"))
image = Image.from_jpeg_file(resolve_resource_path("image/blurredboy1.jpg"))
image2 = Image.from_jpeg_file(resolve_resource_path("image/blurredboy.jpg"))
assert image._image == image2._image
Path.unlink(Path(resolve_resource_path("image/blurredboy1.jpg")))
Path.unlink(Path(resolve_resource_path("image/blurredboy1.png")))
assert image == expected


class TestCrop:
def test_should_crop_jpg_image(self) -> None:
image = Image.from_jpeg_file(resolve_resource_path("image/white.jpg"))
image = image.crop(0, 0, 100, 100)
image2 = Image.from_jpeg_file(resolve_resource_path("image/whiteCropped.jpg"))
assert image == image2

def test_should_crop_png_image(self) -> None:
image = Image.from_png_file(resolve_resource_path("image/white.png"))
@pytest.mark.parametrize(
("image", "expected"),
[
(
Image.from_png_file(resolve_resource_path("image/white.png")),
Image.from_png_file(resolve_resource_path("image/whiteCropped.png")),
),
],
ids=["crop"],
)
def test_should_return_cropped_image(self, image: Image, expected: Image) -> None:
image = image.crop(0, 0, 100, 100)
image2 = Image.from_png_file(resolve_resource_path("image/whiteCropped.png"))
assert image == image2
assert image == expected


class TestSharpen:
Expand All @@ -387,38 +377,46 @@ def test_should_not_sharpen(self) -> None:


class TestRotate:
def test_should_return_clockwise_rotated_image(
self,
) -> None:
image = Image.from_png_file(resolve_resource_path("image/snapshot_boxplot.png"))
@pytest.mark.parametrize(
("image", "expected"),
[
(
Image.from_png_file(resolve_resource_path("image/snapshot_boxplot.png")),
Image.from_png_file(resolve_resource_path("image/snapshot_boxplot_right_rotation.png")),
),
],
ids=["rotate-clockwise"],
)
def test_should_return_clockwise_rotated_image(self, image: Image, expected: Image) -> None:
image = image.rotate_right()
image2 = Image.from_png_file(resolve_resource_path("image/snapshot_boxplot_right_rotation.png"))
assert image == image2
assert image == expected

def test_should_return_counter_clockwise_rotated_image(
self,
) -> None:
image = Image.from_png_file(resolve_resource_path("image/snapshot_boxplot.png"))
@pytest.mark.parametrize(
("image", "expected"),
[
(
Image.from_png_file(resolve_resource_path("image/snapshot_boxplot.png")),
Image.from_png_file(resolve_resource_path("image/snapshot_boxplot_left_rotation.png")),
),
],
ids=["rotate-counter-clockwise"],
)
def test_should_return_counter_clockwise_rotated_image(self, image: Image, expected: Image) -> None:
image = image.rotate_left()
image2 = Image.from_png_file(resolve_resource_path("image/snapshot_boxplot_left_rotation.png"))
assert image == image2
assert image == expected

def test_should_raise_if_not_png_right(self) -> None:
with pytest.raises(
WrongFileExtensionError,
match=(
"The file /image has a wrong file extension. Please provide a file with the following extension\\(s\\):"
" .png"
),
):
Image.from_jpeg_file(resolve_resource_path("image/white_square.jpg")).rotate_right()

def test_should_raise_if_not_png_left(self) -> None:
with pytest.raises(
WrongFileExtensionError,
match=(
"The file /image has a wrong file extension. Please provide a file with the following extension\\(s\\):"
" .png"

class TestFindEdges:
@pytest.mark.parametrize(
("image", "expected"),
[
(
Image.from_png_file(resolve_resource_path("image/boy.png")),
Image.from_png_file(resolve_resource_path("image/edgyBoy.png")),
),
):
Image.from_jpeg_file(resolve_resource_path("image/white_square.jpg")).rotate_left()
],
ids=["find_edges"],
)
def test_should_return_edges_of_image(self, image: Image, expected: Image) -> None:
image = image.find_edges()
assert image == expected

0 comments on commit 7097df3

Please sign in to comment.