From 1ff33a4a29302a02124708e677ef7781ee2b535f Mon Sep 17 00:00:00 2001 From: The many faced demon <154847721+themanyfaceddemon@users.noreply.github.com> Date: Mon, 29 Jul 2024 00:12:01 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9D=D0=B5=D0=B1=D0=BE=D0=BB=D1=8C=D1=88?= =?UTF-8?q?=D0=B0=D1=8F=20=D0=BE=D0=BF=D1=82=D0=BC=D0=B8=D0=B7=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81=D0=B0=20=D1=82?= =?UTF-8?q?=D0=B5=D0=BA=D1=81=D1=82=D1=83=D1=80=20=D0=B8=20=D0=B2=D1=8B?= =?UTF-8?q?=D0=BA=D0=B8=D0=B4=D1=8B=D1=88=20=D0=B0=D1=83=D0=B4=D0=B8=D0=BE?= =?UTF-8?q?=20(#3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Описание PR'ра Оптимизирован немного класс текстур (его надо переписывать) Сделан выкидыш класса текстур ### Техническая информация - [x] PR полностью завершён. - [x] Мне **НЕ** нужна помощь для завершения PR. - [ ] Проверено на локальной машине. - [ ] Документация обновлена. - [x] Тесты обновлены / добавлены. ### Изменения changes: NOT --- .gitignore | 7 +- Code/systems/audio_system/__init__.py | 0 Code/systems/audio_system/audio_manager.py | 6 ++ Code/systems/texture_system/__init__.py | 3 + Code/systems/texture_system/color.py | 68 +++++++++++++++++++ Code/systems/texture_system/texture_system.py | 66 ++++++------------ Tests/Texture/TextureSystem.py | 32 ++------- requirements.txt | 1 + 8 files changed, 110 insertions(+), 73 deletions(-) create mode 100644 Code/systems/audio_system/__init__.py create mode 100644 Code/systems/audio_system/audio_manager.py create mode 100644 Code/systems/texture_system/color.py diff --git a/.gitignore b/.gitignore index 06ec1a8..3590828 100644 --- a/.gitignore +++ b/.gitignore @@ -19,12 +19,13 @@ venv.bak/ .vs/* .vscode/* -# Data -[Dd]ata/* +# Server data Content/Servers/* # Texture -Sprites/ +Content/Compiled/* +*_compiled*.gif +*_compiled*.png # PyQT6 *_ui.py diff --git a/Code/systems/audio_system/__init__.py b/Code/systems/audio_system/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Code/systems/audio_system/audio_manager.py b/Code/systems/audio_system/audio_manager.py new file mode 100644 index 0000000..7a30019 --- /dev/null +++ b/Code/systems/audio_system/audio_manager.py @@ -0,0 +1,6 @@ +from pydub import AudioSegment +from pydub.playback import play + + +class AudioManager: + __slots__ = [] diff --git a/Code/systems/texture_system/__init__.py b/Code/systems/texture_system/__init__.py index 76df2f1..ff1c5e5 100644 --- a/Code/systems/texture_system/__init__.py +++ b/Code/systems/texture_system/__init__.py @@ -1 +1,4 @@ +from systems.texture_system.color import Color from systems.texture_system.texture_system import TextureSystem + +__all__ = ['Color', 'TextureSystem'] diff --git a/Code/systems/texture_system/color.py b/Code/systems/texture_system/color.py new file mode 100644 index 0000000..c362f6c --- /dev/null +++ b/Code/systems/texture_system/color.py @@ -0,0 +1,68 @@ +from typing import Tuple + + +class Color: + __slots__ = ['_r', '_g', '_b', '_a'] + + def __init__(self, r: int, g: int, b: int, a: int) -> None: + self.r = r + self.g = g + self.b = b + self.a = a + + @property + def r(self) -> int: + return self._r + + @r.setter + def r(self, value: int) -> None: + if not (0 <= value <= 255): + raise ValueError("Invalid value for r. It must be between 0 and 255") + + self._r = value + + @property + def g(self) -> int: + return self._g + + @g.setter + def g(self, value: int) -> None: + if not (0 <= value <= 255): + raise ValueError("Invalid value for g. It must be between 0 and 255") + + self._g = value + + @property + def b(self) -> int: + return self._b + + @b.setter + def b(self, value: int) -> None: + if not (0 <= value <= 255): + raise ValueError("Invalid value for b. It must be between 0 and 255") + + self._b = value + + @property + def a(self) -> int: + return self._a + + @a.setter + def a(self, value: int) -> None: + if not (0 <= value <= 255): + raise ValueError("Invalid value for a. It must be between 0 and 255") + + self._a = value + + def __str__(self) -> str: + return f"{self.r}_{self.g}_{self.b}_{self.a}" + + def __repr__(self) -> str: + return f"Color(r={self.r}, g={self.g}, b={self.b}, a={self.a})" + + @staticmethod + def from_tuple(value: Tuple[int, int, int, int]) -> "Color": + return Color(value[0], value[1], value[2], value[3]) + + def to_tuple(self) -> Tuple[int, int, int, int]: + return (self.r, self.g, self.r, self.a) diff --git a/Code/systems/texture_system/texture_system.py b/Code/systems/texture_system/texture_system.py index 0b101e0..65841a9 100644 --- a/Code/systems/texture_system/texture_system.py +++ b/Code/systems/texture_system/texture_system.py @@ -5,6 +5,8 @@ import yaml from PIL import Image, ImageSequence +from root_path import ROOT_PATH +from systems.texture_system.color import Color class TextureSystem: @@ -12,7 +14,7 @@ class TextureSystem: """ __slots__ = [] DEFAULT_FPS: int = 24 - DEFAULT_COLOR: Tuple[int, int, int, int] = (255, 255, 255, 255) + DEFAULT_COLOR: Color = Color(255, 255, 255, 255) @staticmethod def _get_hash_list(layers: List[Dict[str, Any]]) -> str: @@ -54,32 +56,6 @@ def _slice_image(image: Image.Image, frame_width: int, frame_height: int, num_fr return frames - @staticmethod - def _get_color_str(color: Tuple[int, int, int, int]) -> str: - """Возвращает строковое представление цвета. - - Args: - color (Tuple[int, int, int, int]): Цвет в формате RGBA. - - Returns: - str: Строковое представление цвета. - """ - TextureSystem._validate_color(color) - return '_'.join(map(str, color)) - - @staticmethod - def _validate_color(color: Tuple[int, int, int, int]) -> None: - """Проверяет, что цвет в формате RGBA имеет корректные значения. - - Args: - color (Tuple[int, int, int, int]): Цвет в формате RGBA. - - Raises: - ValueError: Если значения цвета находятся вне диапазона 0-255. - """ - if not all(0 <= c <= 255 for c in color): - raise ValueError("Invalid RGBA color format for texture. All values must be between 0 и 255") - @staticmethod def get_textures(path: str) -> List[Dict[str, Any]]: """Загружает текстуры из указанного пути. @@ -93,7 +69,7 @@ def get_textures(path: str) -> List[Dict[str, Any]]: with open(f"{path}/info.yml", 'r') as file: info = yaml.safe_load(file) - return info.get('Sprites', []) + return info.get('Texture', []) @staticmethod def get_state_info(path: str, state: str) -> Tuple[int, int, int, bool]: @@ -112,7 +88,7 @@ def get_state_info(path: str, state: str) -> Tuple[int, int, int, bool]: with open(f"{path}/info.yml", 'r') as file: info = yaml.safe_load(file) - info = info.get('Sprites', []) + info = info.get('Texture', []) sprite_info = next((sprite for sprite in info if sprite['name'] == state), None) if not sprite_info: @@ -126,13 +102,13 @@ def get_state_info(path: str, state: str) -> Tuple[int, int, int, bool]: return frame_width, frame_height, num_frames, is_mask @staticmethod - def _get_compiled(path: str, state: str, color: Optional[Tuple[int, int, int, int]] = None, is_gif: bool = False) -> Union[Image.Image, List[Image.Image], None]: + def _get_compiled(path: str, state: str, color: Optional[Color] = None, is_gif: bool = False) -> Union[Image.Image, List[Image.Image], None]: """Проверяет наличие компилированного изображения или GIF. Args: path (str): Путь к файлу. state (str): Имя состояния. - color (Optional[Tuple[int, int, int, int]], optional): Цвет в формате RGBA. По умолчанию None. + color (Optional[Color], optional): Цвет в формате RGBA. По умолчанию None. is_gif (bool, optional): Указывает, является ли изображение GIF. По умолчанию False. Returns: @@ -140,7 +116,7 @@ def _get_compiled(path: str, state: str, color: Optional[Tuple[int, int, int, in """ image_path: str = f"{path}/{state}" if color: - image_path += f"_compiled_{TextureSystem._get_color_str(color)}" + image_path += f"_compiled_{color}" image_path += ".gif" if is_gif else ".png" @@ -155,13 +131,13 @@ def _get_compiled(path: str, state: str, color: Optional[Tuple[int, int, int, in return None @staticmethod - def get_image_recolor(path: str, state: str, color: Tuple[int, int, int, int] = DEFAULT_COLOR) -> Image.Image: + def get_image_recolor(path: str, state: str, color: Color = DEFAULT_COLOR) -> Image.Image: """Возвращает перекрашенное изображение указанного состояния. Args: path (str): Путь к файлу. state (str): Имя состояния. - color (Tuple[int, int, int, int], optional): Цвет в формате RGBA. По умолчанию DEFAULT_COLOR. + color (Color, optional): Цвет в формате RGBA. По умолчанию DEFAULT_COLOR. Returns: Image.Image: Перекрашенное изображение. @@ -174,16 +150,16 @@ def get_image_recolor(path: str, state: str, color: Tuple[int, int, int, int] = image = image.convert("RGBA") new_colored_image = [ ( - int(pixel[0] * color[0] / 255), - int(pixel[0] * color[1] / 255), - int(pixel[0] * color[2] / 255), + int(pixel[0] * color.r / 255), + int(pixel[0] * color.g / 255), + int(pixel[0] * color.b / 255), pixel[3] ) if pixel[3] != 0 else pixel for pixel in image.getdata() ] image.putdata(new_colored_image) - image.save(f"{path}/{state}_compiled_{TextureSystem._get_color_str(color)}.png") + image.save(f"{path}/{state}_compiled_{color}.png") return image @staticmethod @@ -207,13 +183,13 @@ def get_image(path: str, state: str) -> Image.Image: raise FileNotFoundError(f"Image file for state '{state}' not found in path '{path}'.") @staticmethod - def get_gif_recolor(path: str, state: str, color: Tuple[int, int, int, int] = DEFAULT_COLOR, fps: int = DEFAULT_FPS) -> List[Image.Image]: + def get_gif_recolor(path: str, state: str, color: Color = DEFAULT_COLOR, fps: int = DEFAULT_FPS) -> List[Image.Image]: """Возвращает перекрашенный GIF указанного состояния. Args: path (str): Путь к файлу. state (str): Имя состояния. - color (Tuple[int, int, int, int], optional): Цвет в формате RGBA. По умолчанию DEFAULT_COLOR. + color (Color, optional): Цвет в формате RGBA. По умолчанию DEFAULT_COLOR. fps (int, optional): Частота кадров. По умолчанию DEFAULT_FPS. Returns: @@ -229,7 +205,7 @@ def get_gif_recolor(path: str, state: str, color: Tuple[int, int, int, int] = DE frames = TextureSystem._slice_image(image, frame_width, frame_height, num_frames) - output_path = f"{path}/{state}_compiled_{TextureSystem._get_color_str(color)}.gif" + output_path = f"{path}/{state}_compiled_{color}.gif" frames[0].save(output_path, save_all=True, append_images=frames[1:], duration=1000//fps, loop=0) return frames @@ -289,7 +265,7 @@ def merge_layers(layers: List[Dict[str, Any]], fps: int = DEFAULT_FPS) -> Union[ Returns: Union[Image.Image, List[Image.Image]]: Объединенное изображение или список кадров GIF. """ - base_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'Sprites', 'compiled')) + base_path = os.path.join(ROOT_PATH, 'Content', 'Compiled') if not os.path.exists(base_path): os.makedirs(base_path) @@ -335,7 +311,7 @@ def merge_layers(layers: List[Dict[str, Any]], fps: int = DEFAULT_FPS) -> Union[ final_images[i] = final_image_expanded else: if is_mask: - final_image = TextureSystem.get_image_recolor(first_layer['path'], first_layer['state'], first_layer['color']) + final_image = TextureSystem.get_image_recolor(first_layer['path'], first_layer['state'], Color.from_tuple(first_layer['color'])) else: final_image = TextureSystem.get_image(first_layer['path'], first_layer['state']) @@ -349,7 +325,7 @@ def merge_layers(layers: List[Dict[str, Any]], fps: int = DEFAULT_FPS) -> Union[ if is_gif: if is_mask: - recolored_frames = TextureSystem.get_gif_recolor(layer['path'], layer['state'], layer['color'], fps) + recolored_frames = TextureSystem.get_gif_recolor(layer['path'], layer['state'], Color.from_tuple(layer['color']), fps) for i in range(max_frames): recolored_frame_expanded = Image.new("RGBA", (max_width, max_height)) frame_to_use = recolored_frames[min(i, len(recolored_frames) - 1)] # Используем последний кадр, если i превышает количество кадров @@ -370,7 +346,7 @@ def merge_layers(layers: List[Dict[str, Any]], fps: int = DEFAULT_FPS) -> Union[ final_images.append(normal_frame_expanded) else: if is_mask: - recolored_image = TextureSystem.get_image_recolor(layer['path'], layer['state'], layer['color']) + recolored_image = TextureSystem.get_image_recolor(layer['path'], layer['state'], Color.from_tuple(layer['color'])) recolored_image_expanded = Image.new("RGBA", (max_width, max_height)) recolored_image_expanded.paste(recolored_image, (0, 0)) for i in range(len(final_images)): diff --git a/Tests/Texture/TextureSystem.py b/Tests/Texture/TextureSystem.py index 62751e4..1be76f6 100644 --- a/Tests/Texture/TextureSystem.py +++ b/Tests/Texture/TextureSystem.py @@ -7,18 +7,16 @@ import yaml from PIL import Image -from Code.systems.texture_system import TextureSystem +from Code.systems.texture_system import * class TestTextureSystem(unittest.TestCase): def setUp(self): - self.test_dir = 'test_sprites' + self.test_dir = 'test_texture' os.makedirs(self.test_dir, exist_ok=True) - self.compiled_dir = os.path.abspath(os.path.join(self.test_dir, 'compiled')) - os.makedirs(self.compiled_dir, exist_ok=True) info_data = { - 'Sprites': [ + 'Texture': [ { 'name': 'state1', 'size': {'x': 100, 'y': 100}, @@ -68,22 +66,6 @@ def test_get_hash_list(self): expected_hash = hashlib.sha256(pickle.dumps(layers)).hexdigest() self.assertEqual(TextureSystem._get_hash_list(layers), expected_hash) - def test_get_color_str(self): - color = (255, 128, 64, 32) - expected_str = '255_128_64_32' - self.assertEqual(TextureSystem._get_color_str(color), expected_str) - - def test_validate_color(self): - valid_color = (255, 128, 64, 32) - try: - TextureSystem._validate_color(valid_color) - except ValueError: - self.fail("_validate_color raised ValueError unexpectedly!") - - invalid_color = (256, 128, 64, 32) - with self.assertRaises(ValueError): - TextureSystem._validate_color(invalid_color) - def test_slice_image(self): image = Image.new('RGBA', (450, 150), 'white') frames = TextureSystem._slice_image(image, 150, 150, 3) @@ -129,7 +111,7 @@ def test_get_state_info(self): def test_get_compiled_png(self): path = self.test_dir state = 'state1' - color = (255, 255, 255, 255) + color = Color(255, 255, 255, 255) image = TextureSystem.get_image_recolor(path, state, color) compiled_image = TextureSystem._get_compiled(path, state, color, is_gif=False) @@ -139,7 +121,7 @@ def test_get_compiled_png(self): def test_get_compiled_gif(self): path = self.test_dir state = 'state1' - color = (255, 255, 255, 255) + color = Color(255, 255, 255, 255) gif_frames = TextureSystem.get_gif_recolor(path, state, color) compiled_gif_frames = TextureSystem._get_compiled(path, state, color, is_gif=True) @@ -151,7 +133,7 @@ def test_get_compiled_gif(self): def test_get_image_recolor(self): path = self.test_dir state = 'state1' - color = (255, 0, 0, 255) + color = Color(255, 0, 0, 255) image = TextureSystem.get_image_recolor(path, state, color) expected_path = os.path.join(path, f'{state}_compiled_255_0_0_255.png') self.assertTrue(os.path.exists(expected_path)) @@ -169,7 +151,7 @@ def test_get_image(self): def test_get_gif_recolor(self): path = self.test_dir state = 'state2' - color = (255, 0, 0, 255) + color = Color(255, 0, 0, 255) gif_frames = TextureSystem.get_gif_recolor(path, state, color) expected_path = os.path.join(path, f'{state}_compiled_255_0_0_255.gif') self.assertTrue(os.path.exists(expected_path)) diff --git a/requirements.txt b/requirements.txt index 2a91214..12812c6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ msgpack Pillow +pydub PyQt6 PyYAML requests