diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96403d3 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__/* diff --git a/632.jpg b/docs/examples/632.jpg similarity index 100% rename from 632.jpg rename to docs/examples/632.jpg diff --git a/compile.png b/docs/examples/compile.png similarity index 100% rename from compile.png rename to docs/examples/compile.png diff --git a/img-test.png b/docs/examples/img-test.png similarity index 100% rename from img-test.png rename to docs/examples/img-test.png diff --git a/img2.jpg b/docs/examples/img2.jpg similarity index 100% rename from img2.jpg rename to docs/examples/img2.jpg diff --git a/m0oLR8Tx0zRG8s3SZQlQLnF8bhcnGu6AwzRA5aqi.png_4_1.png b/docs/examples/m0oLR8Tx0zRG8s3SZQlQLnF8bhcnGu6AwzRA5aqi.png_4_1.png similarity index 100% rename from m0oLR8Tx0zRG8s3SZQlQLnF8bhcnGu6AwzRA5aqi.png_4_1.png rename to docs/examples/m0oLR8Tx0zRG8s3SZQlQLnF8bhcnGu6AwzRA5aqi.png_4_1.png diff --git a/docs/examples/res.jpg b/docs/examples/res.jpg new file mode 100644 index 0000000..d8d97ff Binary files /dev/null and b/docs/examples/res.jpg differ diff --git a/filter.py b/filter.py index 4150df2..30ee8ee 100644 --- a/filter.py +++ b/filter.py @@ -1,28 +1,161 @@ -from PIL import Image +from argparse import ArgumentParser, Namespace +from typing import Tuple + import numpy as np -img = Image.open("img2.jpg") -arr = np.array(img) -a = len(arr) -a1 = len(arr[1]) -i = 0 -while i < a - 11: - j = 0 - while j < a1 - 11: - s = 0 - for n in range(i, i + 10): - for n1 in range(j, j + 10): - n1 = arr[n][n1][0] - n2 = arr[n][n1][1] - n3 = arr[n][n1][2] - M = n1 + n2 + n3 - s += M - s = int(s // 100) - for n in range(i, i + 10): - for n1 in range(j, j + 10): - arr[n][n1][0] = int(s // 50) * 50 - arr[n][n1][1] = int(s // 50) * 50 - arr[n][n1][2] = int(s // 50) * 50 - j = j + 10 - i = i + 10 -res = Image.fromarray(arr) -res.save('res.jpg') +from PIL import Image + + +class ValidationError(Exception): + pass + + +def _save_image(image_array: np.ndarray, output_image_path: str) -> None: + """ + Сохранить изображение по указанному пути + """ + res = Image.fromarray(image_array) + res.save(output_image_path) + + +def _load_image(input_image_path: str) -> np.ndarray: + """ + Загрузить изображение по указанному пути + """ + img = Image.open(input_image_path) + return np.array(img, dtype=np.uint8) + + +def _get_and_validate_size( + pixel_matrix: np.ndarray, segment_size: int +) -> Tuple[int, int]: + """ + Получение и валидация размеров матрицы пикселей + """ + rows_count = len(pixel_matrix) + + if not rows_count: + raise ValidationError("Изображение пустое") + + columns_count = len(pixel_matrix[0]) + + if rows_count % segment_size != 0 or columns_count % segment_size != 0: + raise ValidationError( + f"Невозможно разделить изображение размером: ({rows_count}, " + "{columns_count}) на сегменты размером: {segment_size}" + ) + + return rows_count, columns_count + + +def _calculate_mean_by_sector( + pixel_matrix: np.ndarray, begin_corrdinates: Tuple[int, int], segment_size: int +) -> int: + min_row_number, min_column_number = begin_corrdinates + return int( + pixel_matrix[ + min_row_number : min_row_number + segment_size, + min_column_number : min_column_number + segment_size, + ].mean() + ) + + +def _calculate_grayscale_gradation( + mean_mosaic_segment_value: int, grayscale_gradation: int +) -> int: + if mean_mosaic_segment_value < grayscale_gradation: + return 0 + + return (mean_mosaic_segment_value // grayscale_gradation) * grayscale_gradation + + +def _insert_grayscale_segment( + pixel_matrix: np.ndarray, + begin_corrdinates: Tuple[int, int], + segment_size: int, + grayscale_gradation_value: int, +) -> None: + min_row_number, min_column_number = begin_corrdinates + pixel_matrix[ + min_row_number : min_row_number + segment_size, + min_column_number : min_column_number + segment_size, + ][...].fill(grayscale_gradation_value) + + +def _create_pixel_art( + input_image_path: str, + output_image_path: str, + segment_size: int = 10, + grayscale_gradation: int = 50, +) -> None: + pixel_matrix = _load_image(input_image_path) + + rows_count, columns_count = _get_and_validate_size(pixel_matrix, segment_size) + + min_row_number = 0 + + while min_row_number < rows_count: + min_column_number = 0 + + while min_column_number < columns_count: + mean_mosaic_segment_value = _calculate_mean_by_sector( + pixel_matrix, (min_row_number, min_column_number), segment_size + ) + grayscale_gradation_value = _calculate_grayscale_gradation( + mean_mosaic_segment_value, grayscale_gradation + ) + + _insert_grayscale_segment( + pixel_matrix, + (min_row_number, min_column_number), + segment_size, + grayscale_gradation_value, + ) + + min_column_number = min_column_number + segment_size + min_row_number = min_row_number + segment_size + + _save_image(pixel_matrix, output_image_path) + + +def _parser() -> Namespace: + parser = ArgumentParser(description="Преобразование изображения в пиксель арт") + parser.add_argument( + "-i", + "--input-image", + type=str, + default="./docs/examples/img2.jpg", + help="Путь до ихображения которое вы хотите преобразовать", + ) + parser.add_argument( + "-o", + "--output-image", + type=str, + default="./docs/examples/res.jpg", + help="Путь, куда вы хотите сохранить изображение", + ) + parser.add_argument( + "-s", + "--segment-size", + type=int, + default=10, + help="Размер сегмента мозаики", + ) + parser.add_argument( + "-g", + "--grayscale-gradation", + type=int, + default=50, + help="Градация серого цвета", + ) + return parser.parse_args() + + +def main(): + args = _parser() + _create_pixel_art( + args.input_image, args.output_image, args.segment_size, args.grayscale_gradation + ) + + +if __name__ == "__main__": + main() diff --git a/requirements.dev.txt b/requirements.dev.txt new file mode 100644 index 0000000..9d0458a --- /dev/null +++ b/requirements.dev.txt @@ -0,0 +1,7 @@ +# formatters +isort==5.10.1 +black==21.10b0 + +# linters +pylint==2.11.1 +mypy==0.910 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b66939e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +numpy==1.21.4 +Pillow==8.4.0