【教学类-102-03】自制剪纸图案(留白边、沿线剪)03——Python制作白色描边和黑点虚线间隔(透明png图片)
背景需求:
用Python对透明背景png图案的制作拓展白色描边,制作点状虚线
from PIL import Image, ImageDraw
import os
# 距离图片边缘的距离(留多少白边)
bianju = 10
# 留白线的粗细(剪切边的粗细)
bianju2 = 2
# 圆点的大小
dot_size = 5
# 圆点之间的间隔
dot_spacing = 5
def detect_edges(image):
"""检测图像的边缘。如果像素透明,则标记为边缘。"""
if image.mode != 'RGBA':
image = image.convert('RGBA')
width, height = image.size
edges = Image.new('L', (width, height), 0)
draw = ImageDraw.Draw(edges)
pixels = image.load()
for y in range(1, height - 1):
for x in range(1, width - 1):
r, g, b, a = pixels[x, y]
if a == 0:
continue
neighbors = [
pixels[x-1, y], pixels[x+1, y],
pixels[x, y-1], pixels[x, y+1]
]
for nr, ng, nb, na in neighbors:
if na == 0:
draw.point((x, y), fill=255)
break
return edges
def expand_edges(edges, distance):
"""扩展图像的边缘。根据给定的距离,将边缘像素扩展到指定范围。"""
expanded_edges = Image.new('L', edges.size, 0)
draw = ImageDraw.Draw(expanded_edges)
pixels = edges.load()
for y in range(edges.height):
for x in range(edges.width):
if pixels[x, y] > 0:
for dy in range(-distance, distance + 1):
for dx in range(-distance, distance + 1):
nx, ny = x + dx, y + dy
if 0 <= nx < edges.width and 0 <= ny < edges.height:
draw.point((nx, ny), fill=255)
return expanded_edges
def process_images(input_folder, output_folder):
"""处理输入文件夹中的所有PNG图像,并将结果保存到输出文件夹中。"""
if not os.path.exists(output_folder):
os.makedirs(output_folder)
for filename in os.listdir(input_folder):
if filename.endswith(".png"):
image_path = os.path.join(input_folder, filename)
image = Image.open(image_path).convert('RGBA')
edges = detect_edges(image)
expanded_edges = expand_edges(edges, bianju)
result = Image.new('RGBA', image.size, (255, 255, 255, 0))
red_fill = Image.new('RGBA', image.size, (255, 255, 255, 255))
masked_red_fill = Image.composite(red_fill, Image.new('RGBA', image.size), expanded_edges)
final_result = Image.alpha_composite(image, masked_red_fill)
temp_image = Image.new('RGBA', final_result.size, (0, 0, 0, 0))
draw = ImageDraw.Draw(temp_image)
pixels = final_result.load()
# 用于交替绘制黑白圆点的计数器
dot_counter = 0
for y in range(1, final_result.height - 1, dot_spacing):
for x in range(1, final_result.width - 1, dot_spacing):
r, g, b, a = pixels[x, y]
if a == 0:
continue
# 检查是否是边缘像素
neighbors = [
pixels[x-1, y], pixels[x+1, y],
pixels[x, y-1], pixels[x, y+1]
]
edge_found = False
for nr, ng, nb, na in neighbors:
if na == 0:
edge_found = True
break
if edge_found:
# 交替绘制黑白圆点
if dot_counter % 2 == 0:
draw.ellipse([
x - dot_size//2, y - dot_size//2,
x + dot_size//2, y + dot_size//2
], fill=(0, 0, 0))
else:
draw.ellipse([
x - dot_size//2, y - dot_size//2,
x + dot_size//2, y + dot_size//2
], fill=(255, 255, 255))
dot_counter += 1
final_result = Image.alpha_composite(final_result, temp_image)
final_result = Image.alpha_composite(final_result, image)
output_path = os.path.join(output_folder, filename)
final_result.save(output_path)
print(f"Processed {filename} and saved to {output_path}")
if __name__ == '__main__':
path = r'C:\Users\jg2yXRZ\OneDrive\桌面\20250401边缘线剪纸'
input_folder = path + r'\01_01原图'
output_folder = path + r'\01_02边图'
os.makedirs(output_folder, exist_ok=True)
process_images(input_folder, output_folder)
换了一张边距充分的图片也是一样的结果
黑白圆点不平均,
、
代码展示
from PIL import Image, ImageDraw
import os
import math
import numpy as np
from skimage import measure
# 参数配置
WHITE_BORDER_WIDTH = 5 # 白色边缘宽度
DOT_SIZE = 5 # 圆点直径
DOT_SPACING = 10 # 圆点间距(像素)
DOT_OFFSET = 3 # 圆点向外偏移量
def precise_edge_detection(image):
"""使用更精确的边缘检测算法"""
if image.mode != 'RGBA':
image = image.convert('RGBA')
# 转换为numpy数组处理
arr = np.array(image)
alpha = arr[:,:,3]
# 使用sobel算子检测边缘
from scipy import ndimage
sobel_x = ndimage.sobel(alpha, axis=0)
sobel_y = ndimage.sobel(alpha, axis=1)
edge_mask = np.hypot(sobel_x, sobel_y) > 0
# 转换为PIL图像
edge_image = Image.fromarray((edge_mask * 255).astype(np.uint8))
return edge_image
def create_white_border(edge_image, width):
"""创建白色边缘区域"""
from scipy import ndimage
arr = np.array(edge_image)
distance = ndimage.distance_transform_edt(arr == 0)
white_border = (distance <= width) & (distance > 0)
return Image.fromarray((white_border * 255).astype(np.uint8))
def get_smooth_contour(white_border):
"""使用Marching Squares算法获取平滑轮廓"""
arr = np.array(white_border) > 0
contours = measure.find_contours(arr, 0.5)
if not contours:
return []
# 取最长的轮廓
main_contour = max(contours, key=len)
# 简化轮廓点
from scipy.interpolate import splprep, splev
tck, u = splprep(main_contour.T, u=None, s=0.0, per=1)
u_new = np.linspace(u.min(), u.max(), len(main_contour)//2)
x_new, y_new = splev(u_new, tck, der=0)
return list(zip(x_new, y_new))
def distribute_dots_on_contour(contour, image_size):
"""在轮廓上均匀分布圆点"""
if not contour:
return []
# 计算轮廓总长度
total_length = 0
lengths = []
for i in range(len(contour)):
x1, y1 = contour[i]
x2, y2 = contour[(i+1)%len(contour)]
segment_length = math.hypot(x2-x1, y2-y1)
lengths.append(segment_length)
total_length += segment_length
# 计算需要的圆点数量
num_dots = int(total_length / DOT_SPACING)
if num_dots < 1:
return []
# 均匀分布点
dot_positions = []
target_length = 0
current_segment = 0
accumulated_length = 0
for _ in range(num_dots):
# 找到目标位置所在的线段
while accumulated_length + lengths[current_segment] < target_length:
accumulated_length += lengths[current_segment]
current_segment = (current_segment + 1) % len(contour)
# 计算在线段上的位置
segment_start = contour[current_segment]
segment_end = contour[(current_segment+1)%len(contour)]
ratio = (target_length - accumulated_length) / lengths[current_segment]
x = segment_start[0] * (1-ratio) + segment_end[0] * ratio
y = segment_start[1] * (1-ratio) + segment_end[1] * ratio
# 计算法线方向(向外)
dx = segment_end[0] - segment_start[0]
dy = segment_end[1] - segment_start[1]
nx = -dy / math.hypot(dx, dy)
ny = dx / math.hypot(dx, dy)
# 向外偏移
dot_x = x + nx * DOT_OFFSET
dot_y = y + ny * DOT_OFFSET
# 确保在图像范围内
dot_x = max(DOT_SIZE//2, min(dot_x, image_size[0] - DOT_SIZE//2 - 1))
dot_y = max(DOT_SIZE//2, min(dot_y, image_size[1] - DOT_SIZE//2 - 1))
dot_positions.append((dot_x, dot_y))
target_length += total_length / num_dots
if target_length > total_length:
target_length -= total_length
return dot_positions
def process_image(image_path, output_path):
"""处理单个图像"""
try:
image = Image.open(image_path).convert('RGBA')
except:
print(f"无法打开图像: {image_path}")
return
# 第一步:精确边缘检测
edges = precise_edge_detection(image)
# 第二步:创建白色边缘
white_border = create_white_border(edges, WHITE_BORDER_WIDTH)
# 创建白色边缘图像
white_image = Image.new('RGBA', image.size, (0,0,0,0))
white_draw = ImageDraw.Draw(white_image)
white_draw.bitmap((0,0), white_border.convert('L'), fill=(255,255,255,255))
# 合并图像
result = Image.alpha_composite(image, white_image)
# 第三步:获取平滑轮廓
contour = get_smooth_contour(white_border)
if contour:
# 第四步:在轮廓上均匀分布圆点
dot_positions = distribute_dots_on_contour(contour, image.size)
# 绘制圆点
draw = ImageDraw.Draw(result)
for i, (x, y) in enumerate(dot_positions):
if i % 2 == 0:
draw.ellipse([
x-DOT_SIZE//2, y-DOT_SIZE//2,
x+DOT_SIZE//2, y+DOT_SIZE//2
], fill=(0, 0, 0, 255)) # 黑点
else:
draw.ellipse([
x-DOT_SIZE//2, y-DOT_SIZE//2,
x+DOT_SIZE//2, y+DOT_SIZE//2
], fill=(255, 255, 255, 255)) # 白点
# 保存结果
result.save(output_path)
print(f"处理完成: {output_path}")
def process_folder(input_folder, output_folder):
"""处理文件夹中的所有PNG图像"""
if not os.path.exists(output_folder):
os.makedirs(output_folder)
for filename in os.listdir(input_folder):
if filename.lower().endswith('.png'):
input_path = os.path.join(input_folder, filename)
output_path = os.path.join(output_folder, filename)
process_image(input_path, output_path)
if __name__ == '__main__':
input_folder = r'C:\Users\jg2yXRZ\OneDrive\桌面\20250401边缘线剪纸\01_01原图'
output_folder = r'C:\Users\jg2yXRZ\OneDrive\桌面\20250401边缘线剪纸\01_02边图'
process_folder(input_folder, output_folder)
我试了无数次修改,这个黑点子始终是逆时针旋转90度
换一个问法继续测试
第1次修改
'''
094把内部边缘线变成点状
deepseek 阿夏
20250405
'''
from PIL import Image, ImageDraw
import os
# 白边宽度(像素)
white_border_width = 20
# 黑点直径(像素)
dot_size = 5
# 黑点间距(像素)
dot_spacing = 5
def get_edge_pixels(image):
"""
获取图像中不透明像素与透明像素交界的边缘像素坐标
:param image: 输入的RGBA图像
:return: 边缘像素坐标列表
"""
edge_pixels = []
pixels = image.load()
width, height = image.size
for y in range(height):
for x in range(width):
if pixels[x, y][3] > 0: # 如果当前像素不透明
# 检查4邻域像素
neighbors = [
(x-1, y), (x+1, y),
(x, y-1), (x, y+1)
]
for nx, ny in neighbors:
if 0 <= nx < width and 0 <= ny < height:
if pixels[nx, ny][3] == 0: # 如果邻域像素透明
edge_pixels.append((x, y))
break
return edge_pixels
def expand_edge_pixels(edge_pixels, distance):
"""
扩展边缘像素坐标到指定距离
:param edge_pixels: 原始边缘像素坐标列表
:param distance: 扩展距离(像素)
:return: 扩展后的像素坐标集合
"""
expanded_pixels = set()
for x, y in edge_pixels:
for dy in range(-distance, distance+1):
for dx in range(-distance, distance+1):
nx, ny = x + dx, y + dy
expanded_pixels.add((nx, ny))
return expanded_pixels
def draw_dots_on_border(image, original_edge_pixels, border_pixels):
"""
在原始边缘与白边交界处绘制黑点
:param image: 原始图像
:param original_edge_pixels: 原始边缘像素坐标
:param border_pixels: 白边区域像素坐标集合
:return: 带有点状边框的图像
"""
# 创建一个透明图层用于绘制点
dot_layer = Image.new('RGBA', image.size, (0, 0, 0, 0))
draw = ImageDraw.Draw(dot_layer)
# 找出需要绘制黑点的位置(原始边缘与白边交界处)
dots_to_draw = []
for x, y in original_edge_pixels:
if (x, y) in border_pixels:
dots_to_draw.append((x, y))
# 按照间距绘制黑点
for i in range(0, len(dots_to_draw), dot_spacing):
x, y = dots_to_draw[i]
draw.ellipse([
(x - dot_size//2, y - dot_size//2),
(x + dot_size//2, y + dot_size//2)
], fill=(0, 0, 0, 255))
return dot_layer
def process_image(input_path, output_path):
"""
处理单个图像
:param input_path: 输入图像路径
:param output_path: 输出图像路径
"""
# 打开原始图像
original = Image.open(input_path).convert('RGBA')
width, height = original.size
# 获取原始图像的边缘像素
original_edge_pixels = get_edge_pixels(original)
# 扩展边缘像素创建白边区域
border_pixels = expand_edge_pixels(original_edge_pixels, white_border_width)
# 创建白边图层
white_border = Image.new('RGBA', (width, height), (255, 255, 255, 255))
# 创建蒙版
mask = Image.new('L', (width, height), 0)
mask_pixels = mask.load()
for x, y in border_pixels:
if 0 <= x < width and 0 <= y < height:
mask_pixels[x, y] = 255
white_border.putalpha(mask)
# 将白边与原始图像合成
result = Image.alpha_composite(original, white_border)
# 在白边与原始图像交界处绘制黑点
dot_layer = draw_dots_on_border(original, original_edge_pixels, border_pixels)
result = Image.alpha_composite(result, dot_layer)
# 保存结果
result.save(output_path, format='PNG')
print(f"Processed and saved to {output_path}")
def process_images(input_folder, output_folder):
"""
处理文件夹中的所有PNG图像
:param input_folder: 输入文件夹路径
:param output_folder: 输出文件夹路径
"""
if not os.path.exists(output_folder):
os.makedirs(output_folder)
for filename in os.listdir(input_folder):
if filename.lower().endswith(".png"):
input_path = os.path.join(input_folder, filename)
output_path = os.path.join(output_folder, filename)
process_image(input_path, output_path)
if __name__ == '__main__':
path = r'C:\Users\jg2yXRZ\OneDrive\桌面\20250401边缘线剪纸'
input_folder = os.path.join(path, '03_01正方形原图')
output_folder = os.path.join(path, '03_02正方形边图')
os.makedirs(output_folder, exist_ok=True)
process_images(input_folder, output_folder)
青蛙的黑点在青蛙外轮廓上(我需要的是白色描边的外侧
第2次修改
'''
095把白色拓展边缘全部做成点状
deepseek 阿夏
20250405
'''
from PIL import Image, ImageDraw
import os
import math
# 距离图片边缘的距离(留多少白边)
bianju = 20 # 增加白边宽度到20磅
# 黑点的直径
dot_size = 5
# 黑点之间的间距
dot_spacing = 5
def detect_edges(image):
"""
检测图像的边缘。如果像素透明,则标记为边缘。
:param image: 输入的图像
:return: 包含边缘信息的图像
"""
if image.mode != 'RGBA':
image = image.convert('RGBA')
width, height = image.size
edges = Image.new('L', (width, height), 0)
draw = ImageDraw.Draw(edges)
pixels = image.load()
for y in range(1, height - 1):
for x in range(1, width - 1):
r, g, b, a = pixels[x, y]
if a == 0:
continue
neighbors = [
pixels[x-1, y], pixels[x+1, y],
pixels[x, y-1], pixels[x, y+1]
]
for nr, ng, nb, na in neighbors:
if na == 0:
draw.point((x, y), fill=255)
break
return edges
def expand_edges(edges, distance):
"""
扩展图像的边缘。根据给定的距离,将边缘像素扩展到指定范围。
:param edges: 包含边缘信息的图像
:param distance: 扩展的距离
:return: 扩展后的边缘图像
"""
expanded_edges = Image.new('L', edges.size, 0)
draw = ImageDraw.Draw(expanded_edges)
pixels = edges.load()
for y in range(edges.height):
for x in range(edges.width):
if pixels[x, y] > 0:
for dy in range(-distance, distance + 1):
for dx in range(-distance, distance + 1):
nx, ny = x + dx, y + dy
if 0 <= nx < edges.width and 0 <= ny < edges.height:
draw.point((nx, ny), fill=255)
return expanded_edges
def draw_dotted_border(image, edge_mask):
"""
在图像的边缘区域绘制点状边框
:param image: 原始图像
:param edge_mask: 边缘区域掩膜
:return: 带有点状边框的图像
"""
# 创建一个透明图层用于绘制点
dot_layer = Image.new('RGBA', image.size, (0, 0, 0, 0))
draw = ImageDraw.Draw(dot_layer)
pixels = edge_mask.load()
width, height = edge_mask.size
# 计算点的步长(直径+间距)
step = dot_size + dot_spacing
# 遍历图像,在边缘区域绘制点
for y in range(0, height, step):
for x in range(0, width, step):
if 0 <= x < width and 0 <= y < height and pixels[x, y] > 0:
# 绘制一个黑点
draw.ellipse([
(x - dot_size//2, y - dot_size//2),
(x + dot_size//2, y + dot_size//2)
], fill=(0, 0, 0, 255))
return dot_layer
def process_images(input_folder, output_folder):
"""
处理输入文件夹中的所有PNG图像,并将结果保存到输出文件夹中。
:param input_folder: 输入文件夹路径
:param output_folder: 输出文件夹路径
"""
if not os.path.exists(output_folder):
os.makedirs(output_folder)
for filename in os.listdir(input_folder):
if filename.endswith(".png"):
image_path = os.path.join(input_folder, filename)
image = Image.open(image_path).convert('RGBA')
# 检测图像的边缘
edges = detect_edges(image)
# 扩展边缘像素,扩展距离为bianju
expanded_edges = expand_edges(edges, bianju)
# 创建一个白色边缘层
white_border = Image.new('RGBA', image.size, (255, 255, 255, 255))
# 将白色边缘应用到扩展边缘区域
white_border.putalpha(expanded_edges)
# 将原始图像与白色边缘合成
result_with_white_border = Image.alpha_composite(image, white_border)
# 在边缘区域绘制点状边框
dot_border = draw_dotted_border(image, expanded_edges)
# 将点状边框与结果合成
final_result = Image.alpha_composite(result_with_white_border, dot_border)
# 保存结果(保持透明背景)
output_path = os.path.join(output_folder, filename)
final_result.save(output_path, format='PNG')
print(f"Processed {filename} and saved to {output_path}")
if __name__ == '__main__':
path = r'C:\Users\jg2yXRZ\OneDrive\桌面\20250401边缘线剪纸'
input_folder = os.path.join(path, '03_01正方形原图')
output_folder = os.path.join(path, '03_02正方形边图')
os.makedirs(output_folder, exist_ok=True)
process_images(input_folder, output_folder)
它把白色描边20磅的位置全部填充了点子
第3次修改
'''
096内外黑色小点子
deepseek 阿夏
20250405
'''
from PIL import Image, ImageDraw
import os
# 白边宽度(像素)
white_border_width = 20
# 黑点直径(像素)
dot_size = 5
# 黑点间距(像素)
dot_spacing = 5
def get_edge_pixels(image):
"""
获取图像中不透明像素与透明像素交界的边缘像素坐标
:param image: 输入的RGBA图像
:return: 边缘像素坐标列表
"""
edge_pixels = []
pixels = image.load()
width, height = image.size
for y in range(height):
for x in range(width):
if pixels[x, y][3] > 0: # 如果当前像素不透明
# 检查4邻域像素
neighbors = [
(x-1, y), (x+1, y),
(x, y-1), (x, y+1)
]
for nx, ny in neighbors:
if 0 <= nx < width and 0 <= ny < height:
if pixels[nx, ny][3] == 0: # 如果邻域像素透明
edge_pixels.append((x, y))
break
return edge_pixels
def expand_edge_pixels(edge_pixels, distance):
"""
扩展边缘像素坐标到指定距离
:param edge_pixels: 原始边缘像素坐标列表
:param distance: 扩展距离(像素)
:return: 扩展后的像素坐标集合
"""
expanded_pixels = set()
for x, y in edge_pixels:
for dy in range(-distance, distance+1):
for dx in range(-distance, distance+1):
nx, ny = x + dx, y + dy
expanded_pixels.add((nx, ny))
return expanded_pixels
def get_outer_edge_pixels(border_pixels, width, height):
"""
获取白边区域的外边缘像素(与透明区域接触的一侧)
:param border_pixels: 白边区域的所有像素
:param width: 图像宽度
:param height: 图像高度
:return: 外边缘像素列表
"""
outer_edge = set()
border_set = border_pixels
for x, y in border_set:
# 检查8邻域是否有透明像素
neighbors = [
(x-1, y-1), (x, y-1), (x+1, y-1),
(x-1, y), (x+1, y),
(x-1, y+1), (x, y+1), (x+1, y+1)
]
for nx, ny in neighbors:
if (0 <= nx < width and 0 <= ny < height) and (nx, ny) not in border_set:
outer_edge.add((x, y))
break
return list(outer_edge)
def draw_dots_on_outer_edge(image, outer_edge_pixels):
"""
在白边外侧边缘绘制黑点
:param image: 原始图像
:param outer_edge_pixels: 外边缘像素坐标
:return: 带有点状边框的图像
"""
# 创建一个透明图层用于绘制点
dot_layer = Image.new('RGBA', image.size, (0, 0, 0, 0))
draw = ImageDraw.Draw(dot_layer)
# 按照间距绘制黑点
for i in range(0, len(outer_edge_pixels), dot_spacing):
x, y = outer_edge_pixels[i]
draw.ellipse([
(x - dot_size//2, y - dot_size//2),
(x + dot_size//2, y + dot_size//2)
], fill=(0, 0, 0, 255))
return dot_layer
def process_image(input_path, output_path):
"""
处理单个图像
:param input_path: 输入图像路径
:param output_path: 输出图像路径
"""
# 打开原始图像
original = Image.open(input_path).convert('RGBA')
width, height = original.size
# 获取原始图像的边缘像素
original_edge_pixels = get_edge_pixels(original)
# 扩展边缘像素创建白边区域
border_pixels = expand_edge_pixels(original_edge_pixels, white_border_width)
# 获取白边区域的外边缘像素(靠近透明区域的一侧)
outer_edge_pixels = get_outer_edge_pixels(border_pixels, width, height)
# 创建白边图层
white_border = Image.new('RGBA', (width, height), (255, 255, 255, 255))
# 创建蒙版
mask = Image.new('L', (width, height), 0)
mask_pixels = mask.load()
for x, y in border_pixels:
if 0 <= x < width and 0 <= y < height:
mask_pixels[x, y] = 255
white_border.putalpha(mask)
# 将白边与原始图像合成
result = Image.alpha_composite(original, white_border)
# 在白边外侧绘制黑点
dot_layer = draw_dots_on_outer_edge(original, outer_edge_pixels)
result = Image.alpha_composite(result, dot_layer)
# 保存结果
result.save(output_path, format='PNG')
print(f"Processed and saved to {output_path}")
def process_images(input_folder, output_folder):
"""
处理文件夹中的所有PNG图像
:param input_folder: 输入文件夹路径
:param output_folder: 输出文件夹路径
"""
if not os.path.exists(output_folder):
os.makedirs(output_folder)
for filename in os.listdir(input_folder):
if filename.lower().endswith(".png"):
input_path = os.path.join(input_folder, filename)
output_path = os.path.join(output_folder, filename)
process_image(input_path, output_path)
if __name__ == '__main__':
path = r'C:\Users\jg2yXRZ\OneDrive\桌面\20250401边缘线剪纸'
input_folder = os.path.join(path, '03_01正方形原图')
output_folder = os.path.join(path, '03_02正方形边图')
os.makedirs(output_folder, exist_ok=True)
process_images(input_folder, output_folder)
白色描边的外侧和内侧都是黑点子
我感觉无论如何白色描边都会遮挡青蛙外轮廓,所以需要把原图青蛙最后覆盖在最上面,这样就能挡住内侧的黑点子。
第4次修改
'''
097把原图覆盖在最上面
deepseek 阿夏
20250405
'''
from PIL import Image, ImageDraw
import os
# 白边宽度(像素)
white_border_width = 20
# 黑点直径(像素)
dot_size = 5
# 黑点间距(像素)
dot_spacing = 5
def get_edge_pixels(image):
"""
获取图像中不透明像素与透明像素交界的边缘像素坐标
:param image: 输入的RGBA图像
:return: 边缘像素坐标列表
"""
edge_pixels = []
pixels = image.load()
width, height = image.size
for y in range(height):
for x in range(width):
if pixels[x, y][3] > 0: # 如果当前像素不透明
# 检查4邻域像素
neighbors = [
(x-1, y), (x+1, y),
(x, y-1), (x, y+1)
]
for nx, ny in neighbors:
if 0 <= nx < width and 0 <= ny < height:
if pixels[nx, ny][3] == 0: # 如果邻域像素透明
edge_pixels.append((x, y))
break
return edge_pixels
def expand_edge_pixels(edge_pixels, distance):
"""
扩展边缘像素坐标到指定距离
:param edge_pixels: 原始边缘像素坐标列表
:param distance: 扩展距离(像素)
:return: 扩展后的像素坐标集合
"""
expanded_pixels = set()
for x, y in edge_pixels:
for dy in range(-distance, distance+1):
for dx in range(-distance, distance+1):
nx, ny = x + dx, y + dy
expanded_pixels.add((nx, ny))
return expanded_pixels
def get_outer_edge_pixels(border_pixels, width, height):
"""
获取白边区域的外边缘像素(与透明区域接触的一侧)
:param border_pixels: 白边区域的所有像素
:param width: 图像宽度
:param height: 图像高度
:return: 外边缘像素列表
"""
outer_edge = set()
border_set = border_pixels
for x, y in border_set:
# 检查8邻域是否有透明像素
neighbors = [
(x-1, y-1), (x, y-1), (x+1, y-1),
(x-1, y), (x+1, y),
(x-1, y+1), (x, y+1), (x+1, y+1)
]
for nx, ny in neighbors:
if (0 <= nx < width and 0 <= ny < height) and (nx, ny) not in border_set:
outer_edge.add((x, y))
break
return list(outer_edge)
def draw_dots_on_outer_edge(image, outer_edge_pixels):
"""
在白边外侧边缘绘制黑点
:param image: 原始图像
:param outer_edge_pixels: 外边缘像素坐标
:return: 带有点状边框的图像
"""
# 创建一个透明图层用于绘制点
dot_layer = Image.new('RGBA', image.size, (0, 0, 0, 0))
draw = ImageDraw.Draw(dot_layer)
# 按照间距绘制黑点
for i in range(0, len(outer_edge_pixels), dot_spacing):
x, y = outer_edge_pixels[i]
draw.ellipse([
(x - dot_size//2, y - dot_size//2),
(x + dot_size//2, y + dot_size//2)
], fill=(0, 0, 0, 255))
return dot_layer
def process_image(input_path, output_path):
"""
处理单个图像
:param input_path: 输入图像路径
:param output_path: 输出图像路径
"""
# 打开原始图像并保留副本
original = Image.open(input_path).convert('RGBA')
original_copy = original.copy() # 创建原始图像的副本用于最后覆盖
width, height = original.size
# 获取原始图像的边缘像素
original_edge_pixels = get_edge_pixels(original)
# 扩展边缘像素创建白边区域
border_pixels = expand_edge_pixels(original_edge_pixels, white_border_width)
# 获取白边区域的外边缘像素(靠近透明区域的一侧)
outer_edge_pixels = get_outer_edge_pixels(border_pixels, width, height)
# 创建白边图层
white_border = Image.new('RGBA', (width, height), (255, 255, 255, 255))
# 创建蒙版
mask = Image.new('L', (width, height), 0)
mask_pixels = mask.load()
for x, y in border_pixels:
if 0 <= x < width and 0 <= y < height:
mask_pixels[x, y] = 255
white_border.putalpha(mask)
# 将白边与原始图像合成
result = Image.alpha_composite(original, white_border)
# 在白边外侧绘制黑点
dot_layer = draw_dots_on_outer_edge(original, outer_edge_pixels)
result = Image.alpha_composite(result, dot_layer)
# 最后将原始图像覆盖在最上层
result = Image.alpha_composite(result, original_copy)
# 保存结果
result.save(output_path, format='PNG')
print(f"Processed and saved to {output_path}")
def process_images(input_folder, output_folder):
"""
处理文件夹中的所有PNG图像
:param input_folder: 输入文件夹路径
:param output_folder: 输出文件夹路径
"""
if not os.path.exists(output_folder):
os.makedirs(output_folder)
for filename in os.listdir(input_folder):
if filename.lower().endswith(".png"):
input_path = os.path.join(input_folder, filename)
output_path = os.path.join(output_folder, filename)
process_image(input_path, output_path)
if __name__ == '__main__':
path = r'C:\Users\jg2yXRZ\OneDrive\桌面\20250401边缘线剪纸'
input_folder = os.path.join(path, '03_01正方形原图')
output_folder = os.path.join(path, '03_02正方形边图')
os.makedirs(output_folder, exist_ok=True)
process_images(input_folder, output_folder)
青蛙外形完整了,白色描边旁边也有黑点子了。
但是黑色点子分布不均匀
第4次修改
'''
剪纸外轮廓描边虚线点制作(黑点)沿线剪——平均边缘点子的距离(最终效果)
deepseek 阿夏
20250405
'''
from PIL import Image, ImageDraw
import os
import math
# 白边宽度(像素)
white_border_width = 30
# 黑点直径(像素)
dot_size = 10
# 黑点间距(像素)
dot_spacing = dot_size*2 # 增加间距确保均匀分布
def get_edge_pixels(image):
"""获取图像中不透明像素与透明像素交界的边缘像素坐标"""
edge_pixels = []
pixels = image.load()
width, height = image.size
for y in range(height):
for x in range(width):
if pixels[x, y][3] > 0: # 不透明像素
# 检查4邻域
for dx, dy in [(-1,0),(1,0),(0,-1),(0,1)]:
nx, ny = x+dx, y+dy
if 0 <= nx < width and 0 <= ny < height:
if pixels[nx, ny][3] == 0: # 邻域透明
edge_pixels.append((x, y))
break
return edge_pixels
def expand_edge_pixels(edge_pixels, distance, width, height):
"""扩展边缘像素坐标到指定距离"""
expanded_pixels = set()
for x, y in edge_pixels:
for dy in range(-distance, distance+1):
for dx in range(-distance, distance+1):
nx, ny = x+dx, y+dy
if 0 <= nx < width and 0 <= ny < height:
expanded_pixels.add((nx, ny))
return expanded_pixels
def get_contour_pixels(border_pixels, width, height):
"""获取白边区域的外轮廓像素(使用边缘追踪算法)"""
# 找到起始点(最左上角的边界像素)
start_point = None
for y in range(height):
for x in range(width):
if (x,y) in border_pixels:
start_point = (x,y)
break
if start_point:
break
if not start_point:
return []
# 使用Moore-Neighbor追踪算法获取轮廓
contour = []
current = start_point
previous = (current[0]-1, current[1]) # 假设从左侧开始
directions = [
(0, -1), (1, -1), (1, 0), (1, 1),
(0, 1), (-1, 1), (-1, 0), (-1, -1)
]
while True:
contour.append(current)
# 找到下一个边界点
found = False
start_dir = (directions.index((previous[0]-current[0], previous[1]-current[1])) + 1) % 8
for i in range(8):
dir_idx = (start_dir + i) % 8
dx, dy = directions[dir_idx]
neighbor = (current[0]+dx, current[1]+dy)
if 0 <= neighbor[0] < width and 0 <= neighbor[1] < height:
if neighbor in border_pixels:
previous = current
current = neighbor
found = True
break
if not found or current == start_point:
break
return contour
def draw_uniform_dots(image, contour, dot_size, dot_spacing):
"""在轮廓上均匀绘制黑点"""
dot_layer = Image.new('RGBA', image.size, (0, 0, 0, 0))
draw = ImageDraw.Draw(dot_layer)
if not contour:
return dot_layer
# 计算轮廓总长度
total_length = 0
segments = []
for i in range(len(contour)):
p1 = contour[i]
p2 = contour[(i+1)%len(contour)]
dx = p2[0] - p1[0]
dy = p2[1] - p1[1]
length = math.sqrt(dx*dx + dy*dy)
segments.append((p1, p2, length))
total_length += length
# 计算需要绘制的点数
num_dots = int(total_length / dot_spacing)
if num_dots == 0:
num_dots = 1
# 均匀分布点
step = total_length / num_dots
current_dist = 0
segment_idx = 0
remaining_seg = segments[0][2]
for _ in range(num_dots):
# 找到当前点所在线段
while current_dist > remaining_seg and segment_idx < len(segments)-1:
current_dist -= remaining_seg
segment_idx += 1
remaining_seg = segments[segment_idx][2]
p1, p2, seg_len = segments[segment_idx]
ratio = current_dist / seg_len
x = p1[0] + ratio * (p2[0] - p1[0])
y = p1[1] + ratio * (p2[1] - p1[1])
# 绘制黑点
draw.ellipse([
(x - dot_size/2, y - dot_size/2),
(x + dot_size/2, y + dot_size/2)
], fill=(0, 0, 0, 255))
current_dist += step
return dot_layer
def process_image(input_path, output_path):
"""处理单个图像"""
original = Image.open(input_path).convert('RGBA')
original_copy = original.copy()
width, height = original.size
# 获取边缘并扩展白边
edge_pixels = get_edge_pixels(original)
border_pixels = expand_edge_pixels(edge_pixels, white_border_width, width, height)
# 获取精确的外轮廓
contour = get_contour_pixels(border_pixels, width, height)
# 创建白边图层
white_border = Image.new('RGBA', (width, height), (255, 255, 255, 255))
mask = Image.new('L', (width, height), 0)
mask_pixels = mask.load()
for x, y in border_pixels:
mask_pixels[x, y] = 255
white_border.putalpha(mask)
# 合成白边
result = Image.alpha_composite(original, white_border)
# 绘制均匀分布的黑点
dot_layer = draw_uniform_dots(original, contour, dot_size, dot_spacing)
result = Image.alpha_composite(result, dot_layer)
# 覆盖原始图像
result = Image.alpha_composite(result, original_copy)
result.save(output_path, format='PNG')
print(f"Processed: {os.path.basename(input_path)}")
def process_images(input_folder, output_folder):
"""批量处理图像"""
if not os.path.exists(output_folder):
os.makedirs(output_folder)
for filename in os.listdir(input_folder):
if filename.lower().endswith('.png'):
input_path = os.path.join(input_folder, filename)
output_path = os.path.join(output_folder, filename)
process_image(input_path, output_path)
if __name__ == '__main__':
path = r'C:\Users\jg2yXRZ\OneDrive\桌面\20250401边缘线剪纸'
input_folder = os.path.join(path, '03_01正方形原图')
output_folder = os.path.join(path, '03_02正方形边图')
process_images(input_folder, output_folder)
遍历所有png图片
最终代码
'''
剪纸外轮廓描边虚线点制作(黑点)沿线剪——平均边缘点子的距离(最终效果)
deepseek 阿夏
20250405
'''
from PIL import Image, ImageDraw
import os
import math
# 白边宽度(像素)
white_border_width = 30
# 黑点直径(像素)
dot_size = 10
# 黑点间距(像素)
dot_spacing = dot_size*2 # 增加间距确保均匀分布
def get_edge_pixels(image):
"""获取图像中不透明像素与透明像素交界的边缘像素坐标"""
edge_pixels = []
pixels = image.load()
width, height = image.size
for y in range(height):
for x in range(width):
if pixels[x, y][3] > 0: # 不透明像素
# 检查4邻域
for dx, dy in [(-1,0),(1,0),(0,-1),(0,1)]:
nx, ny = x+dx, y+dy
if 0 <= nx < width and 0 <= ny < height:
if pixels[nx, ny][3] == 0: # 邻域透明
edge_pixels.append((x, y))
break
return edge_pixels
def expand_edge_pixels(edge_pixels, distance, width, height):
"""扩展边缘像素坐标到指定距离"""
expanded_pixels = set()
for x, y in edge_pixels:
for dy in range(-distance, distance+1):
for dx in range(-distance, distance+1):
nx, ny = x+dx, y+dy
if 0 <= nx < width and 0 <= ny < height:
expanded_pixels.add((nx, ny))
return expanded_pixels
def get_contour_pixels(border_pixels, width, height):
"""获取白边区域的外轮廓像素(使用边缘追踪算法)"""
# 找到起始点(最左上角的边界像素)
start_point = None
for y in range(height):
for x in range(width):
if (x,y) in border_pixels:
start_point = (x,y)
break
if start_point:
break
if not start_point:
return []
# 使用Moore-Neighbor追踪算法获取轮廓
contour = []
current = start_point
previous = (current[0]-1, current[1]) # 假设从左侧开始
directions = [
(0, -1), (1, -1), (1, 0), (1, 1),
(0, 1), (-1, 1), (-1, 0), (-1, -1)
]
while True:
contour.append(current)
# 找到下一个边界点
found = False
start_dir = (directions.index((previous[0]-current[0], previous[1]-current[1])) + 1) % 8
for i in range(8):
dir_idx = (start_dir + i) % 8
dx, dy = directions[dir_idx]
neighbor = (current[0]+dx, current[1]+dy)
if 0 <= neighbor[0] < width and 0 <= neighbor[1] < height:
if neighbor in border_pixels:
previous = current
current = neighbor
found = True
break
if not found or current == start_point:
break
return contour
def draw_uniform_dots(image, contour, dot_size, dot_spacing):
"""在轮廓上均匀绘制黑点"""
dot_layer = Image.new('RGBA', image.size, (0, 0, 0, 0))
draw = ImageDraw.Draw(dot_layer)
if not contour:
return dot_layer
# 计算轮廓总长度
total_length = 0
segments = []
for i in range(len(contour)):
p1 = contour[i]
p2 = contour[(i+1)%len(contour)]
dx = p2[0] - p1[0]
dy = p2[1] - p1[1]
length = math.sqrt(dx*dx + dy*dy)
segments.append((p1, p2, length))
total_length += length
# 计算需要绘制的点数
num_dots = int(total_length / dot_spacing)
if num_dots == 0:
num_dots = 1
# 均匀分布点
step = total_length / num_dots
current_dist = 0
segment_idx = 0
remaining_seg = segments[0][2]
for _ in range(num_dots):
# 找到当前点所在线段
while current_dist > remaining_seg and segment_idx < len(segments)-1:
current_dist -= remaining_seg
segment_idx += 1
remaining_seg = segments[segment_idx][2]
p1, p2, seg_len = segments[segment_idx]
ratio = current_dist / seg_len
x = p1[0] + ratio * (p2[0] - p1[0])
y = p1[1] + ratio * (p2[1] - p1[1])
# 绘制黑点
draw.ellipse([
(x - dot_size/2, y - dot_size/2),
(x + dot_size/2, y + dot_size/2)
], fill=(0, 0, 0, 255))
current_dist += step
return dot_layer
def process_image(input_path, output_path):
"""处理单个图像"""
original = Image.open(input_path).convert('RGBA')
original_copy = original.copy()
width, height = original.size
# 获取边缘并扩展白边
edge_pixels = get_edge_pixels(original)
border_pixels = expand_edge_pixels(edge_pixels, white_border_width, width, height)
# 获取精确的外轮廓
contour = get_contour_pixels(border_pixels, width, height)
# 创建白边图层
white_border = Image.new('RGBA', (width, height), (255, 255, 255, 255))
mask = Image.new('L', (width, height), 0)
mask_pixels = mask.load()
for x, y in border_pixels:
mask_pixels[x, y] = 255
white_border.putalpha(mask)
# 合成白边
result = Image.alpha_composite(original, white_border)
# 绘制均匀分布的黑点
dot_layer = draw_uniform_dots(original, contour, dot_size, dot_spacing)
result = Image.alpha_composite(result, dot_layer)
# 覆盖原始图像
result = Image.alpha_composite(result, original_copy)
result.save(output_path, format='PNG')
print(f"Processed: {os.path.basename(input_path)}")
def process_images(input_folder, output_folder):
"""批量处理图像"""
if not os.path.exists(output_folder):
os.makedirs(output_folder)
for filename in os.listdir(input_folder):
if filename.lower().endswith('.png'):
input_path = os.path.join(input_folder, filename)
output_path = os.path.join(output_folder, filename)
process_image(input_path, output_path)
if __name__ == '__main__':
path = r'C:\Users\jg2yXRZ\OneDrive\桌面\20250401边缘线剪纸'
input_folder = os.path.join(path, '03_01正方形原图')
output_folder = os.path.join(path, '03_02正方形边图')
process_images(input_folder, output_folder)
切掉上下左右白边+统一大小(1000*1000)
'''
图片处理流程:
1. 裁剪透明白边,保存透明PNG
2. 对裁剪后的图片统一尺寸,保存透明PNG
deepseek、阿夏
20250405
'''
import os
from PIL import Image
# 全局设置
path = r'C:\Users\jg2yXRZ\OneDrive\桌面\20250401边缘线剪纸'
input_folder = path + r'\03_02正方形边图' # 原始图片文件夹
cropped_folder = path + r'\03_03切边图透明' # 裁剪后的透明图片
resized_folder = path + r'\03_04统一图' # 统一尺寸后的图片
# 创建输出文件夹
os.makedirs(cropped_folder, exist_ok=True)
os.makedirs(resized_folder, exist_ok=True)
# 参数设置
transparent_edge = 40 # 裁剪时不保留额外透明边距
target_width = 1000 # 统一宽度
target_height = 1000 # 统一高度
def find_content_boundary(image):
"""
找到图像中非透明内容的精确边界
:param image: PIL Image对象(RGBA模式)
:return: (left, top, right, bottom) 内容边界坐标
"""
if image.mode != 'RGBA':
image = image.convert('RGBA')
width, height = image.size
pixels = image.load()
left, right = width, 0
top, bottom = height, 0
for y in range(height):
for x in range(width):
if pixels[x, y][3] > 0: # 非透明像素
if x < left: left = x
if x > right: right = x
if y < top: top = y
if y > bottom: bottom = y
return left, top, right+1, bottom+1 # +1确保包含边界像素
def crop_to_content(image):
"""精确裁剪到非透明内容边界"""
boundary = find_content_boundary(image)
return image.crop(boundary)
def resize_with_transparency(image, target_size):
"""
保持透明背景调整图像尺寸
:param image: 已裁剪的图片
:param target_size: (width, height)目标尺寸
:return: 调整尺寸后的图像
"""
# 计算缩放比例(保持宽高比)
width_ratio = target_size[0] / image.width
height_ratio = target_size[1] / image.height
scale_ratio = min(width_ratio, height_ratio)
# 等比缩放
new_width = int(image.width * scale_ratio)
new_height = int(image.height * scale_ratio)
resized = image.resize((new_width, new_height), Image.LANCZOS)
# 创建新透明画布
new_image = Image.new('RGBA', target_size, (0, 0, 0, 0))
# 计算居中位置
x_offset = (target_size[0] - new_width) // 2
y_offset = (target_size[1] - new_height) // 2
# 粘贴缩放后的图像
new_image.paste(resized, (x_offset, y_offset), resized)
return new_image
def process_images():
"""完整的图片处理流程"""
# 第一步:精确裁剪透明白边
print("=== 开始裁剪透明白边 ===")
for filename in os.listdir(input_folder):
if filename.lower().endswith('.png'):
input_path = os.path.join(input_folder, filename)
output_path = os.path.join(cropped_folder, filename)
try:
img = Image.open(input_path).convert('RGBA')
cropped = crop_to_content(img)
cropped.save(output_path, format='PNG')
print(f"裁剪完成: {filename}")
except Exception as e:
print(f"裁剪失败 {filename}: {str(e)}")
# 第二步:统一已裁剪图片的尺寸
print("\n=== 开始统一已裁剪图片的尺寸 ===")
for filename in os.listdir(cropped_folder):
if filename.lower().endswith('.png'):
input_path = os.path.join(cropped_folder, filename)
output_path = os.path.join(resized_folder, filename)
try:
img = Image.open(input_path).convert('RGBA')
resized = resize_with_transparency(img, (target_width, target_height))
resized.save(output_path, format='PNG')
print(f"尺寸统一完成: {filename}")
except Exception as e:
print(f"尺寸调整失败 {filename}: {str(e)}")
print("\n=== 处理完成 ===")
print(f"裁剪后的图片保存在: {cropped_folder}")
print(f"统一尺寸的图片保存在: {resized_folder}")
if __name__ == '__main__':
process_images()
切掉PNG透明图的白边(没有预留上下左右的边距,只有0,顶天立地)
统一PNG图片,大小1000*1000(没有预留上下左右的边距)
做成gif
'''
青蛙图片虚线描边后,做成gif动画,便于贴入CSDN
deepseek,阿夏
20250404
'''
size=1000
d=1000
c=20
from PIL import Image
import os
def create_optimized_gif(input_folder, output_file, duration=d, max_size=size, colors=c, skip_frames=1):
"""完整优化版本"""
images = []
for i, filename in enumerate(sorted(os.listdir(input_folder))):
if i % (skip_frames + 1) != 0: # 跳过部分帧
continue
if filename.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.gif')):
filepath = os.path.join(input_folder, filename)
try:
img = Image.open(filepath)
# 调整尺寸
if max_size:
img.thumbnail((max_size, max_size), Image.LANCZOS)
# 转换为P模式(调色板)并减少颜色
if img.mode != 'P':
img = img.convert('P', palette=Image.ADAPTIVE, colors=colors)
images.append(img)
except Exception as e:
print(f"无法加载图片 {filename}: {e}")
if not images:
print("没有找到可用的图片文件")
return
# 保存为优化后的GIF
images[0].save(
output_file,
save_all=True,
append_images=images[1:],
duration=duration,
loop=0,
optimize=True,
disposal=2
)
print(f"优化后的GIF已创建: {output_file} (大小: {os.path.getsize(output_file)/1024:.2f}KB)")
if __name__ == "__main__":
path=r'C:\Users\jg2yXRZ\OneDrive\桌面\20250401边缘线剪纸'
input_folder = path+r"\03_04统一图" # 图片所在的文件夹
output_file =path+ r"\Python青蛙切边虚线描边.gif" # 输出的GIF文件名
# 创建GIF,每帧显示3秒(3000毫秒)
create_optimized_gif( input_folder, output_file, duration=d, max_size=size, colors=c, skip_frames=1)
# from PIL import Image
# import os
# def create_gif(input_folder, output_file, duration=2000, loop=0):
# """
# 将文件夹中的图片合并为GIF
# 参数:
# input_folder: 包含图片的文件夹路径
# output_file: 输出的GIF文件路径
# duration: 每帧显示时间(毫秒),默认3000(3秒)
# loop: 循环次数,0表示无限循环
# """
# # 获取文件夹中所有图片文件
# images = []
# for filename in sorted(os.listdir(input_folder)):
# if filename.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.gif')):
# filepath = os.path.join(input_folder, filename)
# try:
# img = Image.open(filepath)
# images.append(img)
# except Exception as e:
# print(f"无法加载图片 {filename}: {e}")
# if not images:
# print("没有找到可用的图片文件")
# return
# # 保存第一张图片以获取尺寸
# first_image = images[0]
# # 保存为GIF
# first_image.save(
# output_file,
# save_all=True,
# append_images=images[1:],
# duration=duration,
# loop=loop
# )
# print(f"GIF已成功创建: {output_file}")
# # 使用示例
# if __name__ == "__main__":
# path=r'C:\Users\jg2yXRZ\OneDrive\桌面\20250401边缘线剪纸'
# input_folder = path+r"\02_02青蛙白色点图" # 图片所在的文件夹
# output_file =path+ r"\青蛙虚线描边.gif" # 输出的GIF文件名
# # 创建GIF,每帧显示3秒(3000毫秒)
# create_gif(input_folder, output_file, duration=2000)