看到有些截图软件能做成撕边的效果

觉得这个在PPT汇报中也可以用,只是不想再装一个截图软件,何况有些截图是别人截好了的,应该直接在PPT中处理一下图片就行。
PPT我最初的思路是用VBA生成一个撕裂的形状,再跟图片做布尔运算,但是让几个AI工具试着写了写,发现VBA的自由度真心不够,也很难生成比较自然的撕纸效果。于是改变思路,用Python,生成一个上部是矩形,下部比较自然的倾斜撕痕,再保存成SVG文件,像下面的效果。


PPT中可以直接把这个SVG文件拖进去,转换为形状做遮罩,再盖在你要的图片上面,接着先选中图片,再选中遮罩,再在“形状-合并形状-相交”,再添加一个阴影,就可以出来撕纸的效果了。


PS可以用这个来填充图层蒙版,Illustrator可以用这个来生成剪切蒙版。存成SVG主要是为了通用性。
源码
以下是Python源代码,需要安装numpy, matplotlib,tkinter和scipy库
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.path import Path
import matplotlib.patches as patches
import random
from scipy.ndimage import gaussian_filter1d
from tkinter import Tk, Label, Button, Radiobutton, IntVar, messagebox
def perlin_noise(width, octaves=2, persistence=0.5):
noise = np.zeros(width)
for octave in range(1, octaves + 1):
scale = 2 ** octave
amplitude = persistence ** octave
points = width // scale + 1
x = np.linspace(0, 1, points)
segment = np.random.uniform(-1, 1, points)
segment[-1] = segment[0] # 保证首尾一致,避免突跳
interp_x = np.linspace(0, 1, width)
noise += amplitude * np.interp(interp_x, x, segment)
return noise / persistence
def add_teeth(noise, tooth_density=0.2, max_tooth_size=10):
for i in range(1, len(noise) - 1):
if random.random() < tooth_density:
direction = 1 if noise[i] > noise[i-1] else -1
noise[i] += direction * random.uniform(2, max_tooth_size)
return noise
def generate_tear_svg(output_path="paper_tear.svg", tear_direction="left_to_right"):
width, height = 800, 600
tear_height = height * 0.2 # 撕裂位置靠下方
# 主撕裂线:低频 + 平滑
main_noise = perlin_noise(width, octaves=2, persistence=0.5)
main_noise = (main_noise - np.min(main_noise)) * 30 # 控制波动幅度
main_noise = gaussian_filter1d(main_noise, sigma=20)
# 次级纹理(可选)
secondary_noise = perlin_noise(width, octaves=4, persistence=0.3) * 5
combined_noise = main_noise + secondary_noise
# 添加锯齿效果
final_noise = add_teeth(combined_noise, tooth_density=0.05, max_tooth_size=3)
# 随机选择撕裂方向
if tear_direction == "left_to_right":
slope = np.linspace(0, 30, width) # 左低右高
elif tear_direction == "right_to_left":
slope = np.linspace(30, 0, width) # 右低左高
final_noise += slope
# 构造路径
vertices = []
codes = []
vertices.append((0, tear_height + final_noise[0]))
codes.append(Path.MOVETO)
for x in range(1, width):
vertices.append((x, tear_height + final_noise[x]))
codes.append(Path.CURVE4 if x % 3 == 0 else Path.LINETO)
vertices.extend([(width, 0), (width, height), (0, height)])
codes.extend([Path.LINETO, Path.LINETO, Path.LINETO])
path = Path(vertices, codes)
# 生成SVG
fig, ax = plt.subplots(figsize=(width/100, height/100), dpi=100)
patch = patches.PathPatch(path, facecolor='black', lw=0)
ax.add_patch(patch)
ax.set_xlim(0, width)
ax.set_ylim(0, height)
ax.axis('off')
plt.savefig(output_path, format='svg', bbox_inches='tight', pad_inches=0, transparent=True)
plt.close()
print(f"SVG生成成功,已保存到 {output_path}")
def ask_user():
root = Tk()
root.title("选择生成数量")
root.geometry("300x200")
# 定义变量
choice = IntVar()
choice.set(1) # 默认选择1张
# 添加 Radiobutton
Label(root, text="请选择需要生成的图片数量:").pack(pady=10)
Radiobutton(root, text="1张", variable=choice, value=1).pack()
Radiobutton(root, text="5张", variable=choice, value=5).pack()
Radiobutton(root, text="10张", variable=choice, value=10).pack()
# 添加确认按钮
def confirm():
root.destroy() # 关闭窗口
return choice.get()
Button(root, text="确定", command=confirm).pack(pady=20)
root.mainloop()
return choice.get()
def main():
num_images = ask_user()
if num_images is None:
return
for i in range(num_images):
tear_direction = random.choice(["left_to_right", "right_to_left"])
output_path = f"paper_tear_{i + 1}.svg"
generate_tear_svg(output_path, tear_direction)
if __name__ == "__main__":
main()

