# -*- coding: utf-8 -*- """ 生成图片模块 """ # 列举导入模块 import cv2 import numpy from pathlib import Path import random from typing import List, Dict, Optional, Union, Literal, Tuple from uuid import uuid4 class Canvas: """ 画布,用于绘制图片 """ def __init__( self, materials_folder_path: str = r"E:\jianying\materials\纯图", canvas_height: int = 1920, canvas_width: int = 1080, ): """ 初始化 :param materials_folder_path: 所有素材文件夹的根目录路径 :param canvas_height: 画布高度 :param canvas_width: 画布宽度 """ # 构建所有素材文件夹的根目录路径 # self.materials_folder_path = Path(materials_folder_path) self.materials_folder_path = Path( "/Users/liubiren/Python/短视频合成自动化/纯图" ) self.canvas_height, self.canvas_width = canvas_height, canvas_width # 创建画布(默认尺寸为 1920x1080,底色为白色) self.canvas = ( numpy.ones((self.canvas_height, self.canvas_width, 4), dtype=numpy.uint8) * 255 ) def _get_materials_paths(self, folder_name: str) -> List[Path]: """ 根据素材文件夹名称获取该文件夹内所有素材路径列表 :return: 该文件夹内所有素材路径列表 """ # 构建指定素材文件路径 folder_path = self.materials_folder_path / folder_name if not (folder_path.exists() and folder_path.is_dir()): raise RuntimeError(f"{folder_name} 素材文件夹不存在") # 获取该文件夹内所有素材路径列表 material_paths = list(folder_path.rglob(pattern="*.png")) if not material_paths: raise RuntimeError(f"{folder_name} 素材文件夹为空") return material_paths def _read_material( self, material_path: Path, ) -> numpy.ndarray: """ 读取素材 :param material_path: 素素材文件路径 :return: 素材 OpenCV 数据 """ with open(file=material_path, mode="rb") as file: material_buf = numpy.frombuffer( buffer=file.read(), dtype=numpy.uint8 ) # 二进制数据 # 解码为图像 OpenCV 数据 material = cv2.imdecode(buf=material_buf, flags=cv2.IMREAD_UNCHANGED) if material is None: raise RuntimeError(f"读取素材失败") return material def _resize_material( self, material: numpy.ndarray, scale: Union[Literal["fixed_width"], float] = 1.0, ) -> numpy.ndarray: """ 缩放素材 :param material: 素材 OpenCV 数据 :param scale: 缩放比例 :return: 缩放后素材 OpenCV 数据 """ # 获取素材高度和宽度 material_height, material_width = material.shape[:2] match scale: case "fixed_width": material_height, material_width = ( int(material_height * self.canvas_width / material_width), self.canvas_width, ) case _: material_height, material_width = int(material_height * scale), int( material_width * scale ) return cv2.resize( src=material, dsize=(material_width, material_height), interpolation=cv2.INTER_LINEAR, ) def _paste_material( self, material: numpy.ndarray, position: Union[str, Tuple[Optional[int], Optional[int]]] = "center", ) -> None: """ 粘贴素材 :param material: 素材 OpenCV 数据 :param position: 粘贴位置 :return: None """ # 获取素材高度和宽度 material_height, material_width = material.shape[:2] # 计算素材在画布原点坐标 x = (self.canvas_width - material_width) // 2 # 水平方向坐标 y = (self.canvas_height - material_height) // 2 # 垂直方向坐标 match position: case "center": # 居中 pass case _: if position[0] is not None: x = position[0] - material_width // 2 if position[1] is not None: y = position[1] - material_height // 2 # 计算素材左上角坐标 x1, y1 = max(x, 0), max(y, 0) # 计算素材右下角坐标 x2, y2 = min(x + material_width, self.canvas_width), min( y + material_height, self.canvas_height ) # 截取素材有效区域 material = material[y1 - y : y2 - y, x1 - x : x2 - x] # 取出画布对应区域 area = self.canvas[y1:y2, x1:x2] # 透明度计算 alpha_fg = material[..., 3] / 255.0 alpha_bg = area[..., 3] / 255.0 # 颜色混合 for c in range(3): area[..., c] = ( material[..., c] * alpha_fg + area[..., c] * alpha_bg * (1 - alpha_fg) ).astype(numpy.uint8) # 新透明度 area[..., 3] = ((alpha_fg + alpha_bg * (1 - alpha_fg)) * 255).astype( numpy.uint8 ) # 写回画布 self.canvas[y1:y2, x1:x2] = area def _save(self) -> None: """ 保存图片 :return: None """ # 构建图片路径 image_path = self.materials_folder_path / "成品" / f"{uuid4().hex.upper()}.jpg" # 编码为二进制数据 success, image_encoded = cv2.imencode( ".jpg", img=self.canvas, params=[cv2.IMWRITE_JPEG_QUALITY, 95] ) if success: with open(file=(Path(__file__).parent / image_path), mode="wb") as file: file.write(image_encoded.tobytes()) print("✅ 图片保存成功!") else: print("❌ 图片编码失败!") def draw(self, folder_names: List[str], counts: int) -> None: """ 绘制图片 :param folder_names: 绘制所用素材文件夹名称列表 :param counts: 绘制图片数量 :return: None """ materials_paths = {} # 绘制所用素材文件夹名称和其素材路径列表 for folder_name in folder_names: materials_paths.update( {folder_name: self._get_materials_paths(folder_name)} ) current_count = 0 # 当前绘制图片数量计数 while current_count < counts: self.canvas = ( numpy.ones( (self.canvas_height, self.canvas_width, 4), dtype=numpy.uint8 ) * 255 ) # 竖纹素材 vertical_strip = self._read_material( material_path=random.choice(materials_paths["竖纹"]) ) # 等宽缩放竖纹素材 vertical_strip = self._resize_material( material=vertical_strip, scale="fixed_width" ) # 粘贴竖纹素材到画布 self._paste_material(material=vertical_strip) # 按钮素材 button = self._read_material( material_path=random.choice(materials_paths["按钮"]) ) # 粘贴按钮素材到画布 self._paste_material(material=button) # 箭头素材 arrow = self._read_material( material_path=random.choice(materials_paths["箭头"]) ) # 粘贴箭头素材到画布 self._paste_material(material=arrow, position=(None, 1280)) # 声明素材 declaration = self._read_material( material_path=random.choice(materials_paths["声明"]) ) # 粘贴声明素材到画布 self._paste_material(material=declaration, position=(None, 1640)) # 保存图片 self._save() current_count += 1 canvas = Canvas() canvas.draw( folder_names=["竖纹", "按钮", "箭头", "声明"], counts=500, )