Практика сделана

This commit is contained in:
2025-05-18 18:12:16 +03:00
commit d225612d2e
15 changed files with 789 additions and 0 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+49
View File
@@ -0,0 +1,49 @@
import numpy as np
import random
from settings import WorldSettings
class ArtisticClimateGenerator:
def __init__(self, height_map: np.ndarray, settings: WorldSettings):
self.height_map = height_map
self.settings = settings
random.seed(settings.get_valid_seed())
# Оригинальные цвета из ClimateGenerator
self.biome_colors = {
'water': (0.2, 0.4, 0.8, 1.0),
'ice': (0.95, 0.95, 0.98, 0.8),
'tundra': (0.4, 0.6, 0.9, 0.9),
'taiga': (0.1, 0.3, 0.15, 1.0),
'forest': (0.3, 0.6, 0.3, 1.0),
'desert': (0.9, 0.6, 0.2, 1.0),
'steppe': (0.8, 0.75, 0.5, 1.0),
'swamp': (0.4, 0.35, 0.2, 0.9),
'jungle': (0.1, 0.5, 0.1, 1.0),
'savanna': (0.8, 0.7, 0.3, 1.0)
}
def generate_biome_map(self, enabled_biomes=None) -> np.ndarray:
"""Полностью случайное распределение биомов"""
if enabled_biomes is None:
enabled_biomes = list(self.biome_colors.keys())
height, width = self.height_map.shape
biome_map = np.zeros((height, width, 4), dtype=np.float32)
# Включаем воду, если она есть в списке
water_included = 'water' in enabled_biomes
water_level = 0.05
for y in range(height):
for x in range(width):
# Если это вода - рисуем воду и переходим к следующей точке
if water_included and self.height_map[y,x] < water_level:
biome_map[y,x] = self.biome_colors['water']
continue
# Случайный выбор любого биома
available_biomes = [b for b in enabled_biomes if b != 'water'] or ['forest']
random_biome = random.choice(available_biomes)
biome_map[y,x] = self.biome_colors[random_biome]
return biome_map
+117
View File
@@ -0,0 +1,117 @@
import numpy as np
from opensimplex import OpenSimplex
from settings import WorldSettings
from numba import njit
class ClimateGenerator:
def __init__(self, height_map: np.ndarray, settings: WorldSettings):
self.height_map = height_map
self.settings = settings
self.noise = OpenSimplex(seed=settings.get_valid_seed())
# Палитра биомов
self.biome_colors = {
'water': (0.2, 0.4, 0.8, 1.0),
'ice': (0.95, 0.95, 0.98, 0.8),
'tundra': (0.4, 0.6, 0.9, 0.9),
'taiga': (0.1, 0.3, 0.15, 1.0),
'forest': (0.3, 0.6, 0.3, 1.0),
'desert': (0.9, 0.6, 0.2, 1.0),
'steppe': (0.8, 0.75, 0.5, 1.0),
'swamp': (0.4, 0.35, 0.2, 0.9),
'jungle': (0.1, 0.5, 0.1, 1.0),
'savanna': (0.8, 0.7, 0.3, 1.0)
}
def generate_biome_map(self, enabled_biomes=None) -> np.ndarray:
"""Генерация карты биомов с учетом выбранных биомов"""
if enabled_biomes is None:
enabled_biomes = list(self.biome_colors.keys())
height, width = self.height_map.shape
# Генерация шумов
temp_noise = self._generate_noise(width, height, 40)
moist_noise = self._generate_noise(width, height, 50)
biome_map = np.zeros((height, width, 4), dtype=np.float32)
self._fill_biome_map(
biome_map,
self.height_map,
temp_noise,
moist_noise,
enabled_biomes
)
return biome_map
def _generate_noise(self, width, height, scale):
"""Генерация многооктавного шума"""
noise = np.zeros((height, width))
for octave in [1.0, 0.5, 0.25]:
for y in range(height):
for x in range(width):
nx = x / (scale * octave)
ny = y / (scale * octave)
noise[y, x] += self.noise.noise2(nx, ny) * octave
return (noise - noise.min()) / (noise.max() - noise.min())
@staticmethod
@njit
def _fill_biome_map(biome_map, height_map, temp_noise, moist_noise, enabled_biomes):
height, width = height_map.shape
water_level = 0.05
for y in range(height):
latitude = abs(y/height - 0.5) * 2 # 0 на экваторе, 1 на полюсах
for x in range(width):
h = height_map[y,x]
# Вода (всегда включена, без прозрачности)
if h < water_level:
biome_map[y,x] = (0.2, 0.4, 0.8, 1.0)
continue
# Климатические параметры
temp = (0.7 - latitude * 0.5) * (1.0 - h * 0.5) * (0.8 + temp_noise[y,x] * 0.4)
moist = moist_noise[y,x]
# Расчёт прозрачности: сильнее зависит от высоты
alpha = 1.0 - h**2 * 0.7 # h^2 делает прозрачность резче на вершинах
# Выбор биома
if temp < 0.3: # Холодные зоны
if h > 0.8 and 'ice' in enabled_biomes:
biome = (0.95, 0.95, 0.98, alpha * 0.7) # Лёд прозрачнее
elif moist > 0.65 and 'tundra' in enabled_biomes:
biome = (0.4, 0.6, 0.9, alpha)
elif 'taiga' in enabled_biomes:
biome = (0.1, 0.3, 0.15, alpha)
else:
biome = (0.3, 0.6, 0.3, alpha)
elif temp < 0.6: # Умеренные зоны
if moist > 0.7 and 'swamp' in enabled_biomes:
biome = (0.4, 0.35, 0.2, alpha)
elif moist > 0.5 and 'forest' in enabled_biomes:
biome = (0.3, 0.6, 0.3, alpha)
elif moist > 0.3 and 'steppe' in enabled_biomes:
biome = (0.8, 0.75, 0.5, alpha)
elif 'desert' in enabled_biomes:
biome = (0.9, 0.6, 0.2, alpha)
else:
biome = (0.3, 0.6, 0.3, alpha)
else: # Теплые зоны
if moist < 0.3 and 'desert' in enabled_biomes:
biome = (0.9, 0.6, 0.2, alpha)
elif moist < 0.5 and 'savanna' in enabled_biomes:
biome = (0.8, 0.7, 0.3, alpha)
elif moist > 0.7 and 'jungle' in enabled_biomes:
biome = (0.1, 0.5, 0.1, alpha)
elif 'forest' in enabled_biomes:
biome = (0.3, 0.6, 0.3, alpha)
else:
biome = (0.8, 0.75, 0.5, alpha)
biome_map[y,x] = biome
+391
View File
@@ -0,0 +1,391 @@
import tkinter as tk
from tkinter import ttk
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from generation_landwater import WorldGenerator
from generation_heightmap import HeightmapGenerator
from climate_zones_map import ClimateGenerator
from settings import WorldSettings
from map_styler import MapStyler
import threading
import tempfile
import os
from PIL import Image, ImageTk
class WorldGeneratorApp:
def __init__(self, root):
self.root = root
self.settings = WorldSettings()
self._init_variables()
self._setup_ui()
self.root.protocol("WM_DELETE_WINDOW", self._on_close)
def _init_variables(self):
"""Инициализация переменных"""
self.width_var = tk.IntVar(value=self.settings.width)
self.height_var = tk.IntVar(value=self.settings.height)
self.mode_var = tk.StringVar(value=self.settings.mode)
# Параметры рельефа
self.terrain_rough_var = tk.DoubleVar(value=self.settings.terrain_roughness)
self.continent_size_var = tk.DoubleVar(value=self.settings.continent_size)
self.continent_rough_var = tk.DoubleVar(value=self.settings.continent_roughness)
self.islands_dens_var = tk.DoubleVar(value=self.settings.islands_density)
self.islands_rough_var = tk.DoubleVar(value=self.settings.islands_roughness)
self.island_size_var = tk.DoubleVar(value=self.settings.island_size)
# Биомы
self.biome_vars = {
'ice': tk.BooleanVar(value=True),
'tundra': tk.BooleanVar(value=True),
'taiga': tk.BooleanVar(value=True),
'forest': tk.BooleanVar(value=True),
'desert': tk.BooleanVar(value=True),
'steppe': tk.BooleanVar(value=True),
'swamp': tk.BooleanVar(value=True),
'jungle': tk.BooleanVar(value=True),
'savanna': tk.BooleanVar(value=True)
}
# Данные карт
self.height_map = None
self.biome_map = None
self.map_window = None
self.canvas = None
def _setup_ui(self):
"""Настройка интерфейса"""
self.root.title("Генератор мира")
self.root.geometry("900x650")
main_frame = ttk.Frame(self.root)
main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# Панель управления
control_frame = ttk.LabelFrame(main_frame, text="Управление")
control_frame.pack(fill=tk.X, pady=5)
# Первый ряд кнопок
btn_row1 = ttk.Frame(control_frame)
btn_row1.pack(fill=tk.X)
buttons_row1 = [
("Сгенерировать землю", self.generate_land),
("Сгенерировать высоты", self.generate_height),
("Сгенерировать климат", self.generate_climate),
("Показать карту", self.show_map)
]
for i, (text, cmd) in enumerate(buttons_row1):
ttk.Button(btn_row1, text=text, command=cmd).grid(
row=0, column=i, padx=5, pady=5, sticky="ew")
btn_row1.grid_columnconfigure(i, weight=1)
# Второй ряд кнопок
btn_row2 = ttk.Frame(control_frame)
btn_row2.pack(fill=tk.X, pady=5)
buttons_row2 = [
("АВТО", self.auto_generate),
("Стилизовать под свиток", self.style_map),
("Настройки", self.open_settings)
]
for i, (text, cmd) in enumerate(buttons_row2):
ttk.Button(btn_row2, text=text, command=cmd).grid(
row=0, column=i, padx=5, pady=5, sticky="ew")
btn_row2.grid_columnconfigure(i, weight=1)
# Статус бар
self.status_var = tk.StringVar(value="Готов к работе")
ttk.Label(main_frame, textvariable=self.status_var,
relief=tk.SUNKEN, anchor=tk.W).pack(fill=tk.X, pady=5)
def _on_close(self):
"""Корректное закрытие приложения"""
if self.map_window:
self.map_window.destroy()
plt.close('all')
self.root.destroy()
def auto_generate(self):
"""Автоматическая генерация без стилизации"""
def _generate():
try:
self.status_var.set("Автогенерация: создание земли...")
self.generate_land()
self.status_var.set("Автогенерация: создание высот...")
self.generate_height()
self.status_var.set("Автогенерация: создание климата...")
self.generate_climate()
self.status_var.set("Автогенерация завершена!")
self.show_map()
except Exception as e:
self.status_var.set(f"Ошибка: {str(e)}")
threading.Thread(target=_generate, daemon=True).start()
def open_settings(self):
settings_win = tk.Toplevel(self.root)
settings_win.title("Настройки генерации")
settings_win.geometry("600x500")
notebook = ttk.Notebook(settings_win)
# Основные настройки
basic_frame = ttk.Frame(notebook)
self.create_basic_settings(basic_frame)
notebook.add(basic_frame, text="Основные")
# Настройки биомов
biome_frame = ttk.Frame(notebook)
self.create_biome_settings(biome_frame)
notebook.add(biome_frame, text="Биомы")
notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
# Кнопки управления
btn_frame = ttk.Frame(settings_win)
ttk.Button(btn_frame, text="Применить",
command=lambda: [self.save_settings(), settings_win.destroy()]).pack(side=tk.LEFT, padx=5)
ttk.Button(btn_frame, text="Отмена",
command=settings_win.destroy).pack(side=tk.LEFT, padx=5)
btn_frame.pack(pady=10)
def style_map(self):
"""Стилизация текущей карты под старинный свиток"""
if not hasattr(self, 'biome_map') and not hasattr(self, 'height_map'):
self.status_var.set("Ошибка: нет данных для стилизации")
return
try:
self.status_var.set("Стилизация...")
self.root.update_idletasks()
# Подготовка данных
if hasattr(self, 'biome_map') and self.biome_map is not None:
img_data = (self.biome_map[:, :, :3] * 255).astype(np.uint8)
else:
norm_height = (self.height_map - self.height_map.min()) / (self.height_map.max() - self.height_map.min())
img_data = (plt.cm.terrain(norm_height)[:, :, :3] * 255).astype(np.uint8)
# Создаем PIL Image и применяем стилизацию
pil_img = Image.fromarray(img_data, 'RGB')
styled_img = MapStyler.apply_parchment_effect(pil_img)
# Отображаем результат
self.preview_window = tk.Toplevel(self.root)
self.preview_window.title("Стилизованная карта")
img_tk = ImageTk.PhotoImage(styled_img)
label = ttk.Label(self.preview_window, image=img_tk)
label.image = img_tk
label.pack()
self.status_var.set("Стилизация завершена!")
except Exception as e:
self.status_var.set(f"Ошибка стилизации: {str(e)}")
def create_basic_settings(self, frame):
"""Настройки размера и типа карты"""
# Размер карты
size_frame = ttk.LabelFrame(frame, text="Размер карты")
size_frame.pack(fill=tk.X, padx=5, pady=5)
ttk.Label(size_frame, text="Ширина:").grid(row=0, column=0, sticky="e")
ttk.Spinbox(size_frame, from_=100, to=1000, textvariable=self.width_var).grid(
row=0, column=1, sticky="ew", padx=5)
ttk.Label(size_frame, text="Высота:").grid(row=1, column=0, sticky="e")
ttk.Spinbox(size_frame, from_=100, to=1000, textvariable=self.height_var).grid(
row=1, column=1, sticky="ew", padx=5)
# Тип генерации
type_frame = ttk.LabelFrame(frame, text="Тип ландшафта")
type_frame.pack(fill=tk.X, padx=5, pady=5)
types = ["land_only", "continent", "islands"]
texts = ["Только суша", "Материк", "Острова"]
for t, text in zip(types, texts):
ttk.Radiobutton(type_frame, text=text, variable=self.mode_var, value=t).pack(
anchor=tk.W)
# Параметры рельефа
terrain_frame = ttk.LabelFrame(frame, text="Параметры рельефа")
terrain_frame.pack(fill=tk.X, padx=5, pady=5)
params = [
("Шероховатость:", self.terrain_rough_var),
("Размер материка:", self.continent_size_var),
("Шероховатость материка:", self.continent_rough_var),
("Плотность островов:", self.islands_dens_var),
("Шероховатость островов:", self.islands_rough_var),
("Размер островов:", self.island_size_var)
]
for i, (text, var) in enumerate(params):
ttk.Label(terrain_frame, text=text).grid(row=i, column=0, sticky="e", padx=5)
ttk.Scale(terrain_frame, from_=0.1, to=1.0, variable=var,
orient=tk.HORIZONTAL).grid(row=i, column=1, sticky="ew", padx=5)
ttk.Label(terrain_frame, textvariable=var).grid(row=i, column=2, padx=5)
# Режим климата
climate_frame = ttk.LabelFrame(frame, text="Режим климата")
climate_frame.pack(fill=tk.X, padx=5, pady=5)
if not hasattr(self.settings, 'climate_mode'):
self.settings.climate_mode = 'realistic'
ttk.Radiobutton(climate_frame, text="Реалистичный",
variable=self.settings.climate_mode,
value="realistic").pack(anchor=tk.W)
ttk.Radiobutton(climate_frame, text="Художественный",
variable=self.settings.climate_mode,
value="artistic").pack(anchor=tk.W)
def create_biome_settings(self, frame):
"""Настройки биомов через чекбоксы"""
biome_frame = ttk.LabelFrame(frame, text="Выбор биомов")
biome_frame.pack(fill=tk.BOTH, padx=5, pady=5, expand=True)
for i, (biome, var) in enumerate(self.biome_vars.items()):
col = i % 3
row = i // 3
ttk.Checkbutton(
biome_frame,
text=biome.capitalize(),
variable=var
).grid(row=row, column=col, sticky="w", padx=5, pady=2)
def save_settings(self):
"""Сохраняет все настройки"""
try:
self.settings.width = self.width_var.get()
self.settings.height = self.height_var.get()
self.settings.mode = self.mode_var.get()
self.settings.terrain_roughness = self.terrain_rough_var.get()
self.settings.continent_size = self.continent_size_var.get()
self.settings.continent_roughness = self.continent_rough_var.get()
self.settings.islands_density = self.islands_dens_var.get()
self.settings.islands_roughness = self.islands_rough_var.get()
self.settings.island_size = self.island_size_var.get()
self.status_var.set("Настройки сохранены")
except Exception as e:
self.status_var.set(f"Ошибка сохранения: {str(e)}")
def generate_land(self):
"""Генерация карты земли/воды"""
try:
self.land_generator = WorldGenerator(
width=self.settings.width,
height=self.settings.height,
seed=self.settings.get_valid_seed()
)
if self.settings.mode == 'land_only':
self.height_map = self.land_generator.generate_land_only()
elif self.settings.mode == 'continent':
self.height_map = self.land_generator.generate_continent(
size=self.settings.continent_size,
roughness=self.settings.continent_roughness
)
else:
self.height_map = self.land_generator.generate_islands(
density=self.settings.islands_density,
island_size=self.settings.island_size,
roughness=self.settings.islands_roughness
)
self.biome_map = None
self.styled_map_path = None
self.status_var.set("Карта земли сгенерирована")
except Exception as e:
self.status_var.set(f"Ошибка генерации земли: {str(e)}")
def generate_height(self):
"""Генерация карты высот"""
if not hasattr(self, 'height_map') or self.height_map is None:
self.status_var.set("Ошибка: сначала сгенерируйте карту земли")
return
try:
self.height_generator = HeightmapGenerator(
width=self.settings.width,
height=self.settings.height,
seed=self.settings.get_valid_seed()
)
self.height_map = self.height_generator.generate_heightmap(
self.height_map,
roughness=self.settings.terrain_roughness
)
self.biome_map = None
self.styled_map_path = None
self.status_var.set("Карта высот сгенерирована")
except Exception as e:
self.status_var.set(f"Ошибка генерации высот: {str(e)}")
def generate_climate(self):
if not hasattr(self, 'height_map'):
self.status_var.set("Ошибка: сначала сгенерируйте карту высот")
return
try:
enabled_biomes = [b for b, var in self.biome_vars.items() if var.get()]
if self.settings.climate_mode == 'realistic':
from climate_zones_map import ClimateGenerator
self.climate_gen = ClimateGenerator(self.height_map, self.settings)
else:
from climate_zones_artistic import ArtisticClimateGenerator
self.climate_gen = ArtisticClimateGenerator(self.height_map, self.settings)
self.biome_map = self.climate_gen.generate_biome_map(['water'] + enabled_biomes)
self.status_var.set(f"Карта климата ({self.settings.climate_mode}) сгенерирована")
except Exception as e:
self.status_var.set(f"Ошибка генерации климата: {str(e)}")
def show_map(self):
"""Отображение карты во встроенном окне"""
if not hasattr(self, 'biome_map') and not hasattr(self, 'height_map'):
self.status_var.set("Нет данных для отображения")
return
if self.map_window:
self.map_window.destroy()
self.map_window = tk.Toplevel(self.root)
self.map_window.title("Просмотр карты")
fig = plt.Figure(figsize=(8, 6))
ax = fig.add_subplot(111)
if hasattr(self, 'biome_map') and self.biome_map is not None:
ax.imshow(self.biome_map)
ax.set_title("Карта биомов")
else:
ax.imshow(self.height_map, cmap='terrain')
ax.set_title("Карта высот")
ax.axis('off')
self.canvas = FigureCanvasTkAgg(fig, master=self.map_window)
self.canvas.draw()
self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
# Кнопка закрытия
ttk.Button(self.map_window, text="Закрыть",
command=self.map_window.destroy).pack(pady=5)
if __name__ == "__main__":
root = tk.Tk()
app = WorldGeneratorApp(root)
root.mainloop()
+40
View File
@@ -0,0 +1,40 @@
import numpy as np
from opensimplex import OpenSimplex
class HeightmapGenerator:
def __init__(self, width=200, height=200, seed=None):
self.width = width
self.height = height
self.seed = seed if seed is not None else np.random.randint(0, 1000000)
self.noise = OpenSimplex(seed=self.seed)
def generate_heightmap(self, land_water_map, roughness=0.5):
heightmap = np.zeros((self.height, self.width))
scale = 30 / (roughness + 0.1)
base_noise = np.zeros((self.height, self.width))
for y in range(self.height):
for x in range(self.width):
nx = x / scale
ny = y / scale
base_noise[y,x] = self.noise.noise2(nx, ny)
base_noise = (base_noise - base_noise.min()) / (base_noise.max() - base_noise.min())
detail_scale = scale * 0.3
detail_noise = np.zeros((self.height, self.width))
for y in range(self.height):
for x in range(self.width):
nx = x / detail_scale
ny = y / detail_scale
detail_noise[y,x] = self.noise.noise2(nx, ny) * 0.3
combined = base_noise + detail_noise
combined = np.where(land_water_map == 1, combined, 0)
combined = (combined - combined.min()) / (combined.max() - combined.min())
heightmap = np.where(land_water_map == 1,
np.round(combined * 9 + 1) / 10,
0)
return heightmap
+79
View File
@@ -0,0 +1,79 @@
import numpy as np
import opensimplex
import random
class WorldGenerator:
def __init__(self, width=200, height=200, seed=None):
self.width = width
self.height = height
self.seed = seed if seed is not None else random.randint(0, 1000000)
self.noise = opensimplex.OpenSimplex(seed=self.seed)
random.seed(self.seed)
def generate_land_only(self):
return np.ones((self.height, self.width))
def generate_continent(self, size=0.7, roughness=0.5):
world = np.zeros((self.height, self.width))
center_x, center_y = self.width//2, self.height//2
for y in range(self.height):
for x in range(self.width):
nx = (x - center_x) / (self.width * 0.8)
ny = (y - center_y) / (self.height * 0.8)
dist = np.sqrt(nx**2 + ny**2) * (1.0/size)
noise_val = self.noise.noise2(x * roughness/10, y * roughness/10)
world[y][x] = 1 if (1 - dist) + noise_val * 0.5 > 0.4 else 0
return world
def generate_islands(self, density=0.6, island_size=0.3, roughness=0.5):
world = np.zeros((self.height, self.width))
scale = 20 / (island_size + 0.1)
temp_map = np.zeros((self.height, self.width))
for y in range(self.height):
for x in range(self.width):
nx = x / scale * (1 + roughness)
ny = y / scale * (1 + roughness)
noise_val = self.noise.noise2(nx, ny)
threshold = 0.5 - density*0.4
if noise_val > threshold:
temp_map[y][x] = 1
islands = []
visited = np.zeros_like(temp_map)
for y in range(self.height):
for x in range(self.width):
if temp_map[y][x] == 1 and visited[y][x] == 0:
island = []
queue = [(y, x)]
visited[y][x] = 1
min_y, max_y = y, y
min_x, max_x = x, x
while queue:
cy, cx = queue.pop()
island.append((cy, cx))
min_y = min(min_y, cy)
max_y = max(max_y, cy)
min_x = min(min_x, cx)
max_x = max(max_x, cx)
for dy, dx in [(-1,0),(1,0),(0,-1),(0,1)]:
ny, nx = cy+dy, cx+dx
if (0 <= ny < self.height and 0 <= nx < self.width and
temp_map[ny][nx] == 1 and visited[ny][nx] == 0):
visited[ny][nx] = 1
queue.append((ny, nx))
if (min_y > 0 and max_y < self.height-1 and
min_x > 0 and max_x < self.width-1):
islands.append(island)
for island in islands:
for y, x in island:
world[y][x] = 1
return world
+10
View File
@@ -0,0 +1,10 @@
from frontend import WorldGeneratorApp
import tkinter as tk
def main():
root = tk.Tk()
app = WorldGeneratorApp(root)
root.mainloop()
if __name__ == "__main__":
main()
+82
View File
@@ -0,0 +1,82 @@
import numpy as np
from PIL import Image, ImageOps, ImageFilter, ImageDraw, ImageEnhance
import random
class MapStyler:
@staticmethod
def apply_parchment_effect(pil_image, output_path=None):
"""
Применяет эффект старинного свитка к изображению PIL
Args:
pil_image: PIL Image object
output_path: необязательный путь для сохранения
Returns:
PIL Image с эффектом
"""
# Текстура пергамента
width, height = pil_image.size
parchment = MapStyler._create_parchment_texture(width, height)
# Эффект старения
result_img = Image.blend(parchment, pil_image, 0.6)
# Эффекты старения
result_img = result_img.filter(ImageFilter.SMOOTH_MORE)
result_img = ImageEnhance.Contrast(result_img).enhance(1.2)
result_img = ImageEnhance.Color(result_img).enhance(0.9)
# Декоративные элементы
result_img = MapStyler._add_decorations(result_img)
if output_path:
result_img.save(output_path, quality=95)
return result_img
@staticmethod
def _create_parchment_texture(width, height):
"""Создает текстуру пергамента"""
# Цвет пергамента
base = Image.new('RGB', (width, height), color=(240, 230, 200))
# Добавляем шум и неравномерность
noise = np.random.normal(0, 0.1, (height, width, 3)) * 255
noise = np.clip(noise, -30, 30)
noise_img = Image.fromarray(noise.astype(np.uint8), 'RGB')
return Image.blend(base, noise_img, 0.3)
@staticmethod
def _add_decorations(img):
"""Добавляет декоративные элементы"""
draw = ImageDraw.Draw(img)
width, height = img.size
# Рамка в стиле старинной книги
border_color = (139, 69, 19) # Коричневый
border_width = max(width, height) // 100
draw.rectangle([(0, 0), (width-1, height-1)], outline=border_color, width=border_width)
# Угловые узоры
corner_size = min(width, height) // 8
corners = [(0, 0, 180, 270),(width-corner_size, 0, 270, 360),(0, height-corner_size, 90, 180),(width-corner_size, height-corner_size, 0, 90)]
for x, y, start, end in corners:
draw.arc([x, y, x+corner_size, y+corner_size], start, end, fill=border_color, width=2)
# Эффект потертости по краям
mask = Image.new('L', (width, height), 255)
edge_width = min(width, height) // 15
for i in range(edge_width):
alpha = int(255 * (i / edge_width) ** 0.5)
for side in ['top', 'bottom', 'left', 'right']:
if side in ['top', 'bottom']:
box = [0, i if side == 'top' else height-1-i,
width, i+1 if side == 'top' else height-i]
else:
box = [i if side == 'left' else width-1-i, 0,
i+1 if side == 'left' else width-i, height]
mask_draw = ImageDraw.Draw(mask)
mask_draw.rectangle(box, fill=alpha)
return Image.composite(img, Image.new('RGB', img.size, (220, 210, 180)), mask)
+21
View File
@@ -0,0 +1,21 @@
from dataclasses import dataclass
import numpy as np
@dataclass
class WorldSettings:
width: int = 400
height: int = 300
seed: int = None
mode: str = 'continent' # 'land_only', 'continent', 'islands'
# Параметры рельефа
terrain_roughness: float = 0.5
continent_size: float = 0.7
continent_roughness: float = 0.5
islands_density: float = 0.6
islands_roughness: float = 0.5
island_size: float = 0.5
climate_mode: str = 'realistic' # 'realistic' или 'artistic'
def get_valid_seed(self):
return self.seed if self.seed is not None else np.random.randint(0, 1000000)