【教学类-74-05】20260216剪影马(黑色填充图案转黑线条白填充)

背景需求:

2026年2月7日-13日扬州镇江旅行,在镇江南山公园的树上看到一些吉祥挂牌。

2026年是马年,我想做一些马的外形轮廓,做吊穗挂牌

操作步骤

1.请Deepseek写一些AI关键词作图

极简主义线稿,纯黑线条,无填充,白色背景,马的轮廓图,矢量风格,干净的线条,高对比度,只有轮廓没有内部细节,扁平化设计,孤立在白色背景上,高清

内部有眼睛,肌肉、马尾,我只要外轮廓

AI提示了"剪影",影子是很好的填色工具,确保只有轮廓

马的剪影,纯黑色块,只有外部形状轮廓,没有任何内部细节和线条,高度概括的马的外形,极简风格,白色背景,高对比度,矢量风格,高清。

下载后

删除 双头、影子、缺失部位的图片,还有32张

2.代码:提取描边

python 复制代码
'''
对"切边原图"操作,制作白色斑点狗------黑白化+背景透明+切边+统一大小+只要黑色描边,黑色转白色
图边距0
豆包,阿夏
20250216
'''

import os
from PIL import Image, ImageDraw, ImageFilter
import numpy as np

def convert_to_pure_bw(image_path, output_path, threshold=128):
    """
    将图片转为纯黑白两色(黑色0,0,0和白色255,255,255)
    
    参数:
    image_path: 输入图片路径
    output_path: 输出图片路径
    threshold: 二值化阈值
    """
    try:
        img = Image.open(image_path)
        img = img.convert('L')  # 转为灰度图
        
        # 二值化处理
        binary_img = img.point(lambda x: 0 if x < threshold else 255, '1')
        binary_img = binary_img.convert('RGB')  # 转回RGB模式
        
        # 保存黑白图片
        binary_img.save(output_path)
        print(f"黑白图生成: {os.path.basename(image_path)}")
        return output_path
        
    except Exception as e:
        print(f"处理图片 {image_path} 时出错: {str(e)}")
        return None

def crop_transparent_edges(image, margin=0):
    """
    裁剪掉图片的透明边缘,并保留指定的间距
    
    参数:
    image: PIL Image对象(RGBA模式)
    margin: 要保留的间距(磅/像素)
    
    返回:
    裁剪后的PIL Image对象
    """
    # 转换为numpy数组
    data = np.array(image)
    
    # 获取alpha通道
    alpha = data[:, :, 3]
    
    # 找到非透明像素的位置
    non_transparent = np.where(alpha > 0)
    
    if len(non_transparent[0]) == 0:
        # 如果全是透明像素,返回原图
        return image
    
    # 获取非透明区域的边界
    top = np.min(non_transparent[0])
    bottom = np.max(non_transparent[0])
    left = np.min(non_transparent[1])
    right = np.max(non_transparent[1])
    
    # 添加间距
    top = max(0, top - margin)
    bottom = min(image.height - 1, bottom + margin)
    left = max(0, left - margin)
    right = min(image.width - 1, right + margin)
    
    # 裁剪图片
    cropped_image = image.crop((left, top, right + 1, bottom + 1))
    
    return cropped_image

def make_background_transparent(bw_image_path, output_path, tolerance=30, margin=0):
    """
    将黑白图片的白色背景变为透明并裁剪透明边缘
    
    参数:
    bw_image_path: 黑白图片路径
    output_path: 输出图片路径
    tolerance: 颜色容差,控制背景识别的灵敏度
    margin: 裁剪后保留的间距(磅/像素)
    """
    # 打开黑白图片并转换为RGBA模式
    with Image.open(bw_image_path) as img:
        # 转换为RGBA模式
        img = img.convert('RGBA')
        
        # 获取图片数据
        data = np.array(img)
        
        # 创建白色背景掩码:判断像素是否为白色(在容差范围内)
        white_mask = (
            (data[:, :, 0] >= 255 - tolerance) & 
            (data[:, :, 1] >= 255 - tolerance) & 
            (data[:, :, 2] >= 255 - tolerance)
        )
        
        # 将白色背景像素的alpha通道设为0(透明)
        data[white_mask, 3] = 0
        
        # 转换回Image
        result = Image.fromarray(data)
        
        # 裁剪透明边缘并保留间距
        cropped_result = crop_transparent_edges(result, margin)
        
        # 保存结果
        cropped_result.save(output_path, 'PNG')

def add_black_outline_and_invert(image_path, output_path, outline_width=5):
    """
    给图片添加黑色描边并将黑色部分填充为白色
    
    参数:
    image_path: 输入图片路径
    output_path: 输出图片路径
    outline_width: 描边宽度(磅/像素)
    """
    with Image.open(image_path) as img:
        # 确保是RGBA模式
        if img.mode != 'RGBA':
            img = img.convert('RGBA')
        
        # 创建新图像用于绘制结果
        result_img = Image.new('RGBA', img.size, (255, 255, 255, 0))
        draw = ImageDraw.Draw(result_img)
        
        # 获取原图数据
        img_data = np.array(img)
        
        # 找到黑色像素(RGB值接近0)且不透明的像素
        # 先获取alpha通道
        alpha_channel = img_data[:, :, 3]
        
        # 找到黑色区域(R,G,B都小于30,且不透明)
        black_mask = (
            (img_data[:, :, 0] <= 30) & 
            (img_data[:, :, 1] <= 30) & 
            (img_data[:, :, 2] <= 30) &
            (alpha_channel > 0)
        )
        
        # 将黑色区域转换为白色(在新图像中)
        # 获取黑色区域的坐标
        black_coords = np.where(black_mask)
        
        # 在新图像中绘制白色区域(原来的黑色部分)
        for y, x in zip(black_coords[0], black_coords[1]):
            result_img.putpixel((x, y), (255, 255, 255, 255))
        
        # 创建边缘检测图像
        # 创建一个二值图像:黑色区域为1,其他为0
        binary = np.zeros((img.height, img.width), dtype=np.uint8)
        binary[black_mask] = 255
        
        # 转换为PIL图像
        binary_img = Image.fromarray(binary)
        
        # 使用边缘检测找到轮廓
        # 膨胀操作来找到边缘
        from PIL import ImageFilter
        
        # 先进行边缘检测
        edges = binary_img.filter(ImageFilter.FIND_EDGES)
        edges_data = np.array(edges)
        
        # 找到边缘像素
        edge_pixels = np.where(edges_data > 0)
        
        # 在结果图像上绘制黑色轮廓
        # 使用画圆的方式绘制轮廓,确保宽度可控
        for y, x in zip(edge_pixels[0], edge_pixels[1]):
            # 计算圆的边界
            left = x - outline_width // 2
            top = y - outline_width // 2
            right = left + outline_width
            bottom = top + outline_width
            
            # 绘制圆形描边点
            draw.ellipse([left, top, right, bottom], fill=(0, 0, 0, 255))
        
        # 保存结果
        result_img.save(output_path, 'PNG')

def resize_to_uniform_size(image_path, output_path, target_size=(200, 200)):
    """
    将图片调整为统一大小,保持宽高比,在空白处填充透明
    
    参数:
    image_path: 输入图片路径
    output_path: 输出图片路径
    target_size: 目标尺寸 (宽, 高)
    """
    with Image.open(image_path) as img:
        if img.mode != 'RGBA':
            img = img.convert('RGBA')
        
        # 创建新的透明背景图片
        new_img = Image.new('RGBA', target_size, (255, 255, 255, 0))
        
        # 计算缩放比例,保持宽高比
        img_ratio = img.width / img.height
        target_ratio = target_size[0] / target_size[1]
        
        if img_ratio > target_ratio:
            # 图片较宽,按宽度缩放
            new_width = target_size[0]
            new_height = int(target_size[0] / img_ratio)
        else:
            # 图片较高,按高度缩放
            new_height = target_size[1]
            new_width = int(target_size[1] * img_ratio)
        
        # 缩放图片
        resized_img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
        
        # 计算居中位置
        x = (target_size[0] - new_width) // 2
        y = (target_size[1] - new_height) // 2
        
        # 将缩放后的图片粘贴到新图片上
        new_img.paste(resized_img, (x, y), resized_img)
        
        # 保存结果
        new_img.save(output_path, 'PNG')

def resize_to_exact_size(image_path, output_path, target_size=(200, 200)):
    """
    将图片拉伸到精确的目标尺寸
    
    参数:
    image_path: 输入图片路径
    output_path: 输出图片路径
    target_size: 目标尺寸 (宽, 高)
    """
    with Image.open(image_path) as img:
        if img.mode != 'RGBA':
            img = img.convert('RGBA')
        
        # 直接拉伸到目标尺寸
        resized_img = img.resize(target_size, Image.Resampling.LANCZOS)
        
        # 保存结果
        resized_img.save(output_path, 'PNG')

def process_single_image_keep_ratio(image_path, output_path, outline_output_path, 
                                  bw_threshold=128, transparency_tolerance=30, 
                                  margin=0, target_size=(200, 200), outline_width=5):
    """
    处理单张图片:黑白化 → 透明化 → 保持比例统一尺寸 → 描边和颜色反转
    
    参数:
    image_path: 输入图片路径
    output_path: 输出图片路径(描边前)
    outline_output_path: 描边后的输出图片路径
    bw_threshold: 黑白化阈值
    transparency_tolerance: 透明化容差
    margin: 裁剪边距
    target_size: 目标尺寸
    outline_width: 描边宽度
    """
    try:
        # 步骤1: 转为黑白图片(临时文件)
        temp_bw_path = output_path.replace('.png', '_bw_temp.png')
        bw_path = convert_to_pure_bw(image_path, temp_bw_path, bw_threshold)
        
        if not bw_path:
            return False
        
        # 步骤2: 背景透明化(临时文件)
        temp_transparent_path = output_path.replace('.png', '_trans_temp.png')
        make_background_transparent(bw_path, temp_transparent_path, transparency_tolerance, margin)
        
        # 步骤3: 保持比例统一尺寸
        resize_to_uniform_size(temp_transparent_path, output_path, target_size)
        
        # 步骤4: 添加黑色描边并将黑色部分变为白色
        add_black_outline_and_invert(output_path, outline_output_path, outline_width)
        
        # 清理临时文件
        if os.path.exists(temp_bw_path):
            os.remove(temp_bw_path)
        if os.path.exists(temp_transparent_path):
            os.remove(temp_transparent_path)
        
        return True
        
    except Exception as e:
        print(f"处理图片 {image_path} 时出错: {str(e)}")
        # 清理临时文件
        for temp_path in ['temp_bw_path', 'temp_transparent_path']:
            if temp_path in locals() and os.path.exists(locals()[temp_path]):
                os.remove(locals()[temp_path])
        return False

def process_single_image_stretch(image_path, output_path, outline_output_path, 
                               bw_threshold=128, transparency_tolerance=30, 
                               margin=0, target_size=(200, 200), outline_width=5):
    """
    处理单张图片:黑白化 → 透明化 → 精确拉伸到目标尺寸 → 描边和颜色反转
    
    参数:
    image_path: 输入图片路径
    output_path: 输出图片路径(描边前)
    outline_output_path: 描边后的输出图片路径
    bw_threshold: 黑白化阈值
    transparency_tolerance: 透明化容差
    margin: 裁剪边距
    target_size: 目标尺寸
    outline_width: 描边宽度
    """
    try:
        # 步骤1: 转为黑白图片(临时文件)
        temp_bw_path = output_path.replace('.png', '_bw_temp.png')
        bw_path = convert_to_pure_bw(image_path, temp_bw_path, bw_threshold)
        
        if not bw_path:
            return False
        
        # 步骤2: 背景透明化(临时文件)
        temp_transparent_path = output_path.replace('.png', '_trans_temp.png')
        make_background_transparent(bw_path, temp_transparent_path, transparency_tolerance, margin)
        
        # 步骤3: 精确拉伸到目标尺寸
        resize_to_exact_size(temp_transparent_path, output_path, target_size)
        
        # 步骤4: 添加黑色描边并将黑色部分变为白色
        add_black_outline_and_invert(output_path, outline_output_path, outline_width)
        
        # 清理临时文件
        if os.path.exists(temp_bw_path):
            os.remove(temp_bw_path)
        if os.path.exists(temp_transparent_path):
            os.remove(temp_transparent_path)
        
        return True
        
    except Exception as e:
        print(f"处理图片 {image_path} 时出错: {str(e)}")
        # 清理临时文件
        for temp_path in ['temp_bw_path', 'temp_transparent_path']:
            if temp_path in locals() and os.path.exists(locals()[temp_path]):
                os.remove(locals()[temp_path])
        return False

def batch_process_images(input_dir, output_dir_keep_ratio, output_dir_stretch,
                        output_dir_keep_ratio_outline, output_dir_stretch_outline,
                        bw_threshold=128, transparency_tolerance=30, margin=0, 
                        target_size=(200, 200), outline_width=5):
    """
    批量处理文件夹中的所有图片,生成四个版本的输出
    
    参数:
    input_dir: 输入文件夹路径
    output_dir_keep_ratio: 保持比例的输出文件夹路径(描边前)
    output_dir_stretch: 拉伸撑满的输出文件夹路径(描边前)
    output_dir_keep_ratio_outline: 保持比例的描边后输出文件夹路径
    output_dir_stretch_outline: 拉伸撑满的描边后输出文件夹路径
    bw_threshold: 黑白化阈值
    transparency_tolerance: 透明化容差
    margin: 裁剪边距
    target_size: 目标尺寸
    outline_width: 描边宽度
    """
    # 创建输出文件夹(如果不存在)
    os.makedirs(output_dir_keep_ratio, exist_ok=True)
    os.makedirs(output_dir_stretch, exist_ok=True)
    os.makedirs(output_dir_keep_ratio_outline, exist_ok=True)
    os.makedirs(output_dir_stretch_outline, exist_ok=True)
    
    # 支持的图片格式
    supported_formats = ('.png', '.jpg', '.jpeg', '.gif', '.bmp', '.tiff')
    
    # 遍历输入文件夹中的所有文件
    processed_count = 0
    for filename in os.listdir(input_dir):
        # 检查文件是否为支持的图片格式
        if filename.lower().endswith(supported_formats):
            input_path = os.path.join(input_dir, filename)
            
            # 构建输出文件路径,统一保存为PNG格式
            output_filename = os.path.splitext(filename)[0] + '.png'
            
            # 保持比例版本
            output_path_keep_ratio = os.path.join(output_dir_keep_ratio, output_filename)
            output_path_keep_ratio_outline = os.path.join(output_dir_keep_ratio_outline, output_filename)
            
            # 拉伸撑满版本
            output_path_stretch = os.path.join(output_dir_stretch, output_filename)
            output_path_stretch_outline = os.path.join(output_dir_stretch_outline, output_filename)
            
            print(f"正在处理: {filename}")
            
            # 处理保持比例版本
            success_keep_ratio = process_single_image_keep_ratio(
                input_path, output_path_keep_ratio, output_path_keep_ratio_outline,
                bw_threshold, transparency_tolerance, margin, target_size, outline_width
            )
            
            # 处理拉伸撑满版本
            success_stretch = process_single_image_stretch(
                input_path, output_path_stretch, output_path_stretch_outline,
                bw_threshold, transparency_tolerance, margin, target_size, outline_width
            )
            
            if success_keep_ratio and success_stretch:
                print(f"✓ 成功处理: {filename} -> 保持比例 & 拉伸版本(包含描边版本)")
                processed_count += 1
            else:
                print(f"✗ 处理失败或部分失败: {filename}")
    
    print(f"\n批量处理完成!成功处理 {processed_count} 张图片,生成四个版本")

if __name__ == "__main__":
    # 输入文件夹
    path = r'D:\20260216马'
    # a = '00原图2'
    a = '00原图' 
    input_directory = path + fr'\{a}'
    
    # 输出文件夹 - 保持比例版本(描边前)
    output_directory_keep_ratio = os.path.join(path,  f"07{a[2:]}_黑白二色_透明背景_原比例")
    # 输出文件夹 - 拉伸撑满版本(描边前)
    output_directory_stretch = os.path.join(path, f"08{a[2:]}_黑白二色_透明背景_拉伸")
    
    # 输出文件夹 - 保持比例描边版本(描边后)
    output_directory_keep_ratio_outline = os.path.join(path, f"09{a[2:]}_黑白二色_透明背景_原比例_描边")
    # 输出文件夹 - 拉伸撑满描边版本(描边后)
    output_directory_stretch_outline = os.path.join(path, f"10{a[2:]}_黑白二色_透明背景_拉伸_描边")
    
    # 检查输入文件夹是否存在
    if not os.path.exists(input_directory):
        print(f"错误: 文件夹 '{input_directory}' 不存在")
    else:
        print(f"开始批量处理图片...")
        print(f"输入文件夹: {input_directory}")
        print(f"目标尺寸: 1600x1200")
        print(f"描边宽度: 5像素")
        print("-" * 50)
        
        # 执行批量处理
        batch_process_images(
            input_directory, 
            output_directory_keep_ratio,
            output_directory_stretch,
            output_directory_keep_ratio_outline,
            output_directory_stretch_outline,
            bw_threshold=128,           # 黑白化阈值(0-255)
            transparency_tolerance=30,  # 透明化容差
            margin=0,                   # 裁剪边距
            target_size=(1600, 1200),   # 目标尺寸
            outline_width=5              # 描边宽度(磅/像素)
        )
        print("\n" + "=" * 50)
        print("所有图片处理完成!")
        print(f"保持比例版本(描边前)保存在: {output_directory_keep_ratio}")
        print(f"拉伸撑满版本(描边前)保存在: {output_directory_stretch}")
        print(f"保持比例描边版本保存在: {output_directory_keep_ratio_outline}")
        print(f"拉伸撑满描边版本保存在: {output_directory_stretch_outline}")
        print("=" * 50)

制作A4一页四图(A6大小)的卡片

为了保留0.7的边距,把四个单元格的对齐方式修改

图片大小就是

w=14.85-0.7=14.15

h=10.5-0.7=9.8

python 复制代码
'''
把图片插入2*2模版内,四个对齐方式都靠近中心,删除PDF第二页
实现指定对齐方式:
创建 alignment_map 字典,定义每个单元格的对齐方式
单元格(0,0) - 右下对齐
单元格(0,1) - 左下对齐
单元格(1,0) - 右上对齐
单元格(1,1) - 左上对齐
Deepseek,阿夏
20260216
'''


import random
import math
from PIL import Image, ImageDraw, ImageFont
import os
import time
from docx import Document
from docx.shared import Cm, Pt
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx2pdf import convert
from PyPDF2 import PdfMerger, PdfReader, PdfWriter
import shutil

# 测试图片59张
path = r'D:\20260216马'
# 格子一共有几个
sl = 4
names = f"10原图_黑白二色_透明背景_拉伸_描边"
input_path = path + fr'\{names}'
mb = '马模版'
# 表格有2列
# 高度和宽度是多少厘米
h = 9.8  # 高度9.8cm
w = 14.15  # 宽度14.15cm

def remove_pdf_page(pdf_path, page_num_to_remove):
    """
    删除PDF指定页码(页码从0开始计数),覆盖原PDF文件
    :param pdf_path: PDF文件路径
    :param page_num_to_remove: 要删除的页码(第2页对应索引1)
    :return: 处理后的PDF路径(失败返回None)
    """
    try:
        reader = PdfReader(pdf_path)
        writer = PdfWriter()
        
        # 检查页码是否有效
        if page_num_to_remove >= len(reader.pages):
            print(f"PDF {os.path.basename(pdf_path)} 只有 {len(reader.pages)} 页,无第{page_num_to_remove+1}页,跳过删除")
            return pdf_path
        
        # 保留除指定页码外的所有页
        for idx, page in enumerate(reader.pages):
            if idx != page_num_to_remove:
                writer.add_page(page)
        
        # 覆盖原PDF文件(删除指定页后保存)
        with open(pdf_path, 'wb') as f:
            writer.write(f)
        
        print(f"已删除 {os.path.basename(pdf_path)} 第{page_num_to_remove+1}页,剩余 {len(writer.pages)} 页")
        return pdf_path
    except Exception as e:
        print(f"删除PDF页码失败 {os.path.basename(pdf_path)}: {e}")
        return None

def check_and_repair_image(image_path):
    """检查并修复图片文件"""
    try:
        with Image.open(image_path) as img:
            img.verify()  # 验证图片完整性
        return True
    except (IOError, SyntaxError, Exception) as e:
        print(f"图片文件损坏: {image_path}, 错误: {e}")
        return False

def get_valid_image_files(input_path):
    """获取有效的图片文件列表"""
    valid_files = []
    for file in os.listdir(input_path):
        if file.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.gif')):
            file_path = os.path.join(input_path, file)
            if check_and_repair_image(file_path):
                valid_files.append(file_path)
            else:
                print(f"跳过损坏的图片: {file}")
    return valid_files

print('----1、检查图片文件------------')
file_paths = get_valid_image_files(input_path)
print(f"找到 {len(file_paths)} 张有效图片")

if len(file_paths) == 0:
    print("没有找到有效的图片文件,程序退出")
    exit()

grouped_files = [file_paths[i:i+sl] for i in range(0, len(file_paths), sl)]
print(f"分成 {len(grouped_files)} 组")

print('----2、创建临时文件夹------------')
new_folder = path + r'\零时文件夹'
if os.path.exists(new_folder):
    shutil.rmtree(new_folder)
os.makedirs(new_folder, exist_ok=True)

print('----3、插入docx,制作pdf------------')

def create_document_with_images(group, group_index):
    """在已有模板中插入图片,实现指定的对齐方式"""
    try:
        # 检查模板文件是否存在
        template_path = path + fr'\{mb}.docx'
        if not os.path.exists(template_path):
            print(f"模板文件不存在: {template_path}")
            return False
        
        # 打开模板文档
        doc = Document(template_path)
        
        # 检查文档中是否有表格
        if len(doc.tables) == 0:
            print("模板文档中没有找到表格")
            return False
        
        # 获取第一个表格
        table = doc.tables[0]
        
        # 计算表格的行列数
        rows = len(table.rows)
        cols = len(table.columns)
        total_cells = rows * cols
        
        print(f"表格大小: {rows}行 x {cols}列, 共{total_cells}个单元格")
        
        # 设置单元格边距为0
        for row in table.rows:
            for cell in row.cells:
                # 设置单元格边距为0
                cell.margin_top = Cm(0)
                cell.margin_bottom = Cm(0)
                cell.margin_left = Cm(0)
                cell.margin_right = Cm(0)
        
        # 定义每个单元格的对齐方式
        # 单元格(0,0) - 右下对齐
        # 单元格(0,1) - 左下对齐  
        # 单元格(1,0) - 右上对齐
        # 单元格(1,1) - 左上对齐
        alignment_map = {
            (0, 0): {'vertical': 'bottom', 'horizontal': 'right'},   # 右下
            (0, 1): {'vertical': 'bottom', 'horizontal': 'left'},    # 左下
            (1, 0): {'vertical': 'top', 'horizontal': 'right'},      # 右上
            (1, 1): {'vertical': 'top', 'horizontal': 'left'}        # 左上
        }
        
        # 遍历每个单元格,并插入图片
        for cell_index, image_file in enumerate(group):
            if not image_file or not os.path.exists(image_file):
                print(f"图片文件不存在: {image_file}")
                continue
            
            # 如果单元格索引超出表格范围,跳过
            if cell_index >= total_cells:
                print(f"图片数量超过表格容量,跳过第{cell_index + 1}张图片")
                break
            
            # 计算行和列的位置
            row = cell_index // cols
            col = cell_index % cols
            
            # 获取单元格
            cell = table.cell(row, col)
            
            # 清除单元格内容
            for paragraph in cell.paragraphs:
                p = paragraph._element
                p.getparent().remove(p)
            
            # 添加新段落
            cell_paragraph = cell.add_paragraph()
            
            # 设置水平对齐方式
            if alignment_map[(row, col)]['horizontal'] == 'left':
                cell_paragraph.alignment = WD_ALIGN_PARAGRAPH.LEFT
            elif alignment_map[(row, col)]['horizontal'] == 'right':
                cell_paragraph.alignment = WD_ALIGN_PARAGRAPH.RIGHT
            else:
                cell_paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
            
            # 设置段落格式,确保垂直对齐
            # 段前和段后间距设为0
            cell_paragraph.paragraph_format.space_before = Pt(0)
            cell_paragraph.paragraph_format.space_after = Pt(0)
            cell_paragraph.paragraph_format.line_spacing = 1.0
            
            run = cell_paragraph.add_run()
            
            try:
                # 插入图片,严格按照指定尺寸(不保持宽高比)
                run.add_picture(image_file, 
                              width=Cm(w),
                              height=Cm(h))
                print(f"成功插入图片到表格位置({row+1},{col+1}), 对齐方式: {alignment_map[(row, col)]}")
                print(f"  插入尺寸: {w}cm x {h}cm (严格按照指定尺寸)")
            except Exception as e:
                print(f"插入图片失败 {image_file}: {e}")
                continue
        
        # 保存Word文档
        docx_path = os.path.join(new_folder, f'{group_index + 1:03d}.docx')
        doc.save(docx_path)
        print(f"创建文档成功: {group_index + 1:03d}.docx")
        return True
        
    except Exception as e:
        print(f"创建文档失败 组{group_index + 1}: {e}")
        return False

# 处理每一组图片
success_count = 0
for group_index, group in enumerate(grouped_files):
    if create_document_with_images(group, group_index):
        success_count += 1

print(f"成功创建 {success_count} 个Word文档")

print('----4、转换为PDF并删除第二页------------')
pdf_files = []
if success_count > 0:
    # 获取所有DOCX文件并按数字排序
    docx_files = [f for f in os.listdir(new_folder) if f.endswith('.docx')]
    docx_files.sort()
    
    for docx_file in docx_files:
        docx_path = os.path.join(new_folder, docx_file)
        pdf_path = docx_path.replace('.docx', '.pdf')
        try:
            # 转换Word为PDF
            convert(docx_path, pdf_path)
            print(f"转换成功: {docx_file} -> {os.path.basename(pdf_path)}")
            
            # 删除PDF的第二页(页码索引从0开始,第2页对应索引1)
            processed_pdf_path = remove_pdf_page(pdf_path, page_num_to_remove=1)
            if processed_pdf_path:
                pdf_files.append(processed_pdf_path)
            
            time.sleep(0.5)  # 短暂等待避免冲突
        except Exception as e:
            print(f"转换/处理失败 {docx_file}: {e}")

print('----5、合并PDF------------')
if pdf_files:
    # 按文件名排序
    pdf_files.sort()
    
    # 合并PDF
    merger = PdfMerger()
    for pdf_file in pdf_files:
        try:
            merger.append(pdf_file)
            print(f"添加PDF: {os.path.basename(pdf_file)}")
        except Exception as e:
            print(f"添加PDF失败 {pdf_file}: {e}")
    
    # 保存合并后的PDF
    pdf_output_path = path + fr'\{mb[:2]}{names[2:4]}{mb}(A4一页{sl}张)共{len(file_paths)}图.pdf'
    try:
        merger.write(pdf_output_path)
        merger.close()
        print(f"PDF合并完成: {pdf_output_path}")
    except Exception as e:
        print(f"PDF合并失败: {e}")
else:
    print("没有可合并的PDF文件")

print('----6、清理临时文件------------')
try:
    shutil.rmtree(new_folder)
    print("临时文件夹已清理")
except Exception as e:
    print(f"清理临时文件夹失败: {e}")

print('----程序执行完成------------')
print(f"使用的图片尺寸: 宽度{w}cm x 高度{h}cm")

靠的太近了,裁剪容易剪坏,还是老样子上下左右居中,保留裁剪白边

添加中间十字的虚线,框架到下一页了,高度调小10.5改成10.49

对齐方式都是上下左右居中

python 复制代码
'''
把图片插入2*2模版内,四个对齐方式都靠近中心,删除PDF第二页
实现指定对齐方式:
创建 alignment_map 字典,定义每个单元格的对齐方式
单元格(0,0) - 右下对齐
单元格(0,1) - 左下对齐
单元格(1,0) - 右上对齐
单元格(1,1) - 左上对齐
Deepseek,阿夏
20260216
'''


import random
import math
from PIL import Image, ImageDraw, ImageFont
import os
import time
from docx import Document
from docx.shared import Cm, Pt
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx2pdf import convert
from PyPDF2 import PdfMerger, PdfReader, PdfWriter
import shutil

# 测试图片59张
path = r'D:\20260216马'
# 格子一共有几个
sl = 4
names = f"10原图_黑白二色_透明背景_拉伸_描边"
input_path = path + fr'\{names}'
mb = '马模版'
# 表格有2列
# 高度和宽度是多少厘米
h = 10.5-0.7-0.7  # 高度9.8cm
w = 14.85-0.7-0.7  # 宽度14.15cm

def remove_pdf_page(pdf_path, page_num_to_remove):
    """
    删除PDF指定页码(页码从0开始计数),覆盖原PDF文件
    :param pdf_path: PDF文件路径
    :param page_num_to_remove: 要删除的页码(第2页对应索引1)
    :return: 处理后的PDF路径(失败返回None)
    """
    try:
        reader = PdfReader(pdf_path)
        writer = PdfWriter()
        
        # 检查页码是否有效
        if page_num_to_remove >= len(reader.pages):
            print(f"PDF {os.path.basename(pdf_path)} 只有 {len(reader.pages)} 页,无第{page_num_to_remove+1}页,跳过删除")
            return pdf_path
        
        # 保留除指定页码外的所有页
        for idx, page in enumerate(reader.pages):
            if idx != page_num_to_remove:
                writer.add_page(page)
        
        # 覆盖原PDF文件(删除指定页后保存)
        with open(pdf_path, 'wb') as f:
            writer.write(f)
        
        print(f"已删除 {os.path.basename(pdf_path)} 第{page_num_to_remove+1}页,剩余 {len(writer.pages)} 页")
        return pdf_path
    except Exception as e:
        print(f"删除PDF页码失败 {os.path.basename(pdf_path)}: {e}")
        return None

def check_and_repair_image(image_path):
    """检查并修复图片文件"""
    try:
        with Image.open(image_path) as img:
            img.verify()  # 验证图片完整性
        return True
    except (IOError, SyntaxError, Exception) as e:
        print(f"图片文件损坏: {image_path}, 错误: {e}")
        return False

def get_valid_image_files(input_path):
    """获取有效的图片文件列表"""
    valid_files = []
    for file in os.listdir(input_path):
        if file.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.gif')):
            file_path = os.path.join(input_path, file)
            if check_and_repair_image(file_path):
                valid_files.append(file_path)
            else:
                print(f"跳过损坏的图片: {file}")
    return valid_files

print('----1、检查图片文件------------')
file_paths = get_valid_image_files(input_path)
print(f"找到 {len(file_paths)} 张有效图片")

if len(file_paths) == 0:
    print("没有找到有效的图片文件,程序退出")
    exit()

grouped_files = [file_paths[i:i+sl] for i in range(0, len(file_paths), sl)]
print(f"分成 {len(grouped_files)} 组")

print('----2、创建临时文件夹------------')
new_folder = path + r'\零时文件夹'
if os.path.exists(new_folder):
    shutil.rmtree(new_folder)
os.makedirs(new_folder, exist_ok=True)

print('----3、插入docx,制作pdf------------')

def create_document_with_images(group, group_index):
    """在已有模板中插入图片,实现指定的对齐方式"""
    try:
        # 检查模板文件是否存在
        template_path = path + fr'\{mb}.docx'
        if not os.path.exists(template_path):
            print(f"模板文件不存在: {template_path}")
            return False
        
        # 打开模板文档
        doc = Document(template_path)
        
        # 检查文档中是否有表格
        if len(doc.tables) == 0:
            print("模板文档中没有找到表格")
            return False
        
        # 获取第一个表格
        table = doc.tables[0]
        
        # 计算表格的行列数
        rows = len(table.rows)
        cols = len(table.columns)
        total_cells = rows * cols
        
        print(f"表格大小: {rows}行 x {cols}列, 共{total_cells}个单元格")
        
        # 设置单元格边距为0
        for row in table.rows:
            for cell in row.cells:
                # 设置单元格边距为0
                cell.margin_top = Cm(0)
                cell.margin_bottom = Cm(0)
                cell.margin_left = Cm(0)
                cell.margin_right = Cm(0)
        
        # 定义每个单元格的对齐方式
        # 单元格(0,0) - 右下对齐
        # 单元格(0,1) - 左下对齐  
        # 单元格(1,0) - 右上对齐
        # 单元格(1,1) - 左上对齐
        alignment_map = {
            # (0, 0): {'vertical': 'bottom', 'horizontal': 'right'},   # 右下
            # (0, 1): {'vertical': 'bottom', 'horizontal': 'left'},    # 左下
            # (1, 0): {'vertical': 'top', 'horizontal': 'right'},      # 右上
            # (1, 1): {'vertical': 'top', 'horizontal': 'left'}        # 左上
            (0, 0): {'vertical': 'center', 'horizontal': 'center'},   # 右下
            (0, 1): {'vertical': 'center', 'horizontal': 'center'},    # 左下
            (1, 0): {'vertical': 'center', 'horizontal': 'center'},      # 右上
            (1, 1): {'vertical': 'center', 'horizontal': 'center'}        # 左上
        }
        
        # 遍历每个单元格,并插入图片
        for cell_index, image_file in enumerate(group):
            if not image_file or not os.path.exists(image_file):
                print(f"图片文件不存在: {image_file}")
                continue
            
            # 如果单元格索引超出表格范围,跳过
            if cell_index >= total_cells:
                print(f"图片数量超过表格容量,跳过第{cell_index + 1}张图片")
                break
            
            # 计算行和列的位置
            row = cell_index // cols
            col = cell_index % cols
            
            # 获取单元格
            cell = table.cell(row, col)
            
            # 清除单元格内容
            for paragraph in cell.paragraphs:
                p = paragraph._element
                p.getparent().remove(p)
            
            # 添加新段落
            cell_paragraph = cell.add_paragraph()
            
            # 设置水平对齐方式
            if alignment_map[(row, col)]['horizontal'] == 'left':
                cell_paragraph.alignment = WD_ALIGN_PARAGRAPH.LEFT
            elif alignment_map[(row, col)]['horizontal'] == 'right':
                cell_paragraph.alignment = WD_ALIGN_PARAGRAPH.RIGHT
            else:
                cell_paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
            
            # 设置段落格式,确保垂直对齐
            # 段前和段后间距设为0
            cell_paragraph.paragraph_format.space_before = Pt(0)
            cell_paragraph.paragraph_format.space_after = Pt(0)
            cell_paragraph.paragraph_format.line_spacing = 1.0
            
            run = cell_paragraph.add_run()
            
            try:
                # 插入图片,严格按照指定尺寸(不保持宽高比)
                run.add_picture(image_file, 
                              width=Cm(w),
                              height=Cm(h))
                print(f"成功插入图片到表格位置({row+1},{col+1}), 对齐方式: {alignment_map[(row, col)]}")
                print(f"  插入尺寸: {w}cm x {h}cm (严格按照指定尺寸)")
            except Exception as e:
                print(f"插入图片失败 {image_file}: {e}")
                continue
        
        # 保存Word文档
        docx_path = os.path.join(new_folder, f'{group_index + 1:03d}.docx')
        doc.save(docx_path)
        print(f"创建文档成功: {group_index + 1:03d}.docx")
        return True
        
    except Exception as e:
        print(f"创建文档失败 组{group_index + 1}: {e}")
        return False

# 处理每一组图片
success_count = 0
for group_index, group in enumerate(grouped_files):
    if create_document_with_images(group, group_index):
        success_count += 1

print(f"成功创建 {success_count} 个Word文档")

print('----4、转换为PDF并删除第二页------------')
pdf_files = []
if success_count > 0:
    # 获取所有DOCX文件并按数字排序
    docx_files = [f for f in os.listdir(new_folder) if f.endswith('.docx')]
    docx_files.sort()
    
    for docx_file in docx_files:
        docx_path = os.path.join(new_folder, docx_file)
        pdf_path = docx_path.replace('.docx', '.pdf')
        try:
            # 转换Word为PDF
            convert(docx_path, pdf_path)
            print(f"转换成功: {docx_file} -> {os.path.basename(pdf_path)}")
            
            # 删除PDF的第二页(页码索引从0开始,第2页对应索引1)
            processed_pdf_path = remove_pdf_page(pdf_path, page_num_to_remove=1)
            if processed_pdf_path:
                pdf_files.append(processed_pdf_path)
            
            time.sleep(0.5)  # 短暂等待避免冲突
        except Exception as e:
            print(f"转换/处理失败 {docx_file}: {e}")

print('----5、合并PDF------------')
if pdf_files:
    # 按文件名排序
    pdf_files.sort()
    
    # 合并PDF
    merger = PdfMerger()
    for pdf_file in pdf_files:
        try:
            merger.append(pdf_file)
            print(f"添加PDF: {os.path.basename(pdf_file)}")
        except Exception as e:
            print(f"添加PDF失败 {pdf_file}: {e}")
    
    # 保存合并后的PDF
    pdf_output_path = path + fr'\{mb[:2]}{names[2:4]}{mb}(A4一页{sl}张)共{len(file_paths)}图.pdf'
    try:
        merger.write(pdf_output_path)
        merger.close()
        print(f"PDF合并完成: {pdf_output_path}")
    except Exception as e:
        print(f"PDF合并失败: {e}")
else:
    print("没有可合并的PDF文件")

print('----6、清理临时文件------------')
try:
    shutil.rmtree(new_folder)
    print("临时文件夹已清理")
except Exception as e:
    print(f"清理临时文件夹失败: {e}")

print('----程序执行完成------------')
print(f"使用的图片尺寸: 宽度{w}cm x 高度{h}cm")

感悟:

AI制作剪影图(黑色影子图案)也可以获取线描图案

除夕快乐!2026年也祝愿全家人平平安安,身体健康!

相关推荐
查士丁尼·绵2 小时前
通过sdk获取ecs指标
python·sdk
喵手2 小时前
Python爬虫实战:失败重试分级 - DNS/超时/403 分策略处理 + 重试退避等!
爬虫·python·爬虫实战·零基础python爬虫教学·失败重试分级·dns/超时·重试退避
得一录2 小时前
Python 算法高级篇:布谷鸟哈希算法与分布式哈希表
python·算法·aigc·哈希算法
Faker66363aaa2 小时前
基于Cascade-Mask-RCNN和RegNetX-4GF的果蝇检测与识别系统——COCO数据集训练与优化
python
聂 可 以2 小时前
解决Pycharm中(Python)软件包下载速度很慢、甚至下载失败的问题
ide·python·pycharm
七夜zippoe2 小时前
强化学习实战指南:从Q-Learning到PPO的工业级应用
python·openai·超参数调优·q-learning·mdp
JaydenAI2 小时前
[拆解LangChain执行引擎]非常规Pending Write的持久化
python·langchain
MoonPointer-Byte2 小时前
【Python实战】我开发了一款“诗意”待办软件:MoonTask(附源码+工程化思路)
开发语言·python·custom tkinter
~央千澈~2 小时前
抖音弹幕游戏开发之第11集:礼物触发功能·优雅草云桧·卓伊凡
java·前端·python