You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现类似Procreate的填充算法与圆形渐变动画效果?

实现Procreate风格的圆形渐变填充动画(基于多边形洪水填充)

我正在开发一款美观的洪水填充算法,注意到Procreate的填充动画效果非常出色——慢放后能看到它采用圆形扩散填充模式,同时围绕目标色生成不同尺寸的颜色渐变。我希望保留现有的多边形填充功能,仅需实现这种动画与渐变效果,但不知道该从哪里入手。

现有非递归洪水填充代码:

import mahotas
import numpy as np
import cv2 as cv
from shapely.geometry import Polygon


def flood_fill(poly, new_color):
    
    # separate the x's and y's
    xs = [i[0] for i in poly.exterior.coords]
    ys = [i[1] for i in poly.exterior.coords]
    minx, maxx = min(xs), max(xs)
    miny, maxy = min(ys), max(ys)
    X = int(maxx - minx)
    Y = int(maxy - miny)
    # create a new polygon cord that have no excess white space 
    newPoly = [(int(x - minx), int(y - miny))
               for (x, y) in poly.exterior.coords]
    # creates a mask for all the points in the polygon
    grid = np.zeros((X, Y), dtype=np.int8)
    mahotas.polygon.fill_polygon(newPoly, grid)
    # after masking return to points and translate back to original positions
    points = [[x + minx, y + miny] for (x, y) in zip(*np.nonzero(grid))]


    # now turn all the corresponding pixels in the blank_iamge to a desiered color
    for x, y in points:
        blank_image[int(y), int(x)] = np.array(new_color)

# create a blank image
blank_image = np.zeros((300, 400, 3), np.uint8)
blank_image[:, :] = (255, 255, 255)


flood_fill(poly=Polygon([[0,0],[0,100],[100,100],[100,0]]),new_color= (255,0,0))

cv.imshow("blank",blank_image)
cv.waitKey(0)

实现思路

Procreate的填充效果核心是从中心向外扩散的同心圆环渐变,结合多边形掩码限制填充区域。具体步骤:

  • 计算多边形的中心作为扩散起点
  • 生成从0到多边形最大内切圆半径的半径序列,控制扩散节奏
  • 为每个半径的圆环生成渐变颜色(从背景色向目标色过渡)
  • 利用多边形掩码,只渲染圆环落在多边形内部的像素
  • 逐帧渲染圆环,通过控制每帧的显示时间实现动画

修改后的完整代码

import mahotas
import numpy as np
import cv2 as cv
from shapely.geometry import Polygon
from shapely.geometry import Point

def get_polygon_center(poly):
    """计算多边形的中心坐标"""
    centroid = poly.centroid
    return (int(centroid.x), int(centroid.y))

def get_max_radius(poly, center):
    """计算从中心到多边形边界的最大距离(扩散的最大半径)"""
    center_point = Point(center)
    max_dist = max(center_point.distance(Point(coord)) for coord in poly.exterior.coords)
    return int(max_dist)

def generate_gradient_color(bg_color, target_color, progress):
    """根据进度生成渐变颜色:progress从0到1,从背景色过渡到目标色"""
    bg = np.array(bg_color, dtype=np.float32)
    target = np.array(target_color, dtype=np.float32)
    color = bg + (target - bg) * progress
    return tuple(map(int, color))

def animated_flood_fill(poly, new_color, bg_color=(255,255,255), fps=30):
    # 复用原有逻辑生成多边形掩码
    xs = [i[0] for i in poly.exterior.coords]
    ys = [i[1] for i in poly.exterior.coords]
    minx, maxx = min(xs), max(xs)
    miny, maxy = min(ys), max(ys)
    X = int(maxx - minx)
    Y = int(maxy - miny)
    newPoly = [(int(x - minx), int(y - miny)) for (x, y) in poly.exterior.coords]
    
    # 生成掩码(1为多边形内部,0为外部)
    grid = np.zeros((Y, X), dtype=np.uint8)  # 调整维度适配OpenCV图像格式
    mahotas.polygon.fill_polygon(newPoly, grid)
    # 将掩码转换为原始图像坐标的掩码
    full_mask = np.zeros_like(blank_image[:, :, 0], dtype=np.uint8)
    full_mask[int(miny):int(maxy), int(minx):int(maxx)] = grid
    
    # 计算扩散起点和最大半径
    center = get_polygon_center(poly)
    max_r = get_max_radius(poly, center)
    
    # 生成半径序列(步长越小动画越平滑)
    radii = np.linspace(0, max_r, num=int(max_r * 0.8), dtype=int)
    
    # 逐帧渲染动画
    for r in radii:
        # 计算当前扩散进度
        progress = r / max_r
        # 生成当前圆环的渐变颜色
        current_color = generate_gradient_color(bg_color, new_color, progress)
        
        # 绘制当前圆环
        temp_img = blank_image.copy()
        cv.circle(temp_img, center, r, current_color, thickness=2)
        
        # 用掩码过滤,只保留多边形内部的像素
        temp_img = cv.bitwise_and(temp_img, temp_img, mask=full_mask)
        blank_image[full_mask == 1] = temp_img[full_mask == 1]
        
        # 显示当前帧
        cv.imshow("animated_fill", blank_image)
        # 控制帧率,按q可提前终止
        if cv.waitKey(int(1000/fps)) & 0xFF == ord('q'):
            break
    
    # 最后填充完整目标色,完成动画
    blank_image[full_mask == 1] = new_color
    cv.imshow("animated_fill", blank_image)
    cv.waitKey(0)

# 创建空白图像
blank_image = np.zeros((300, 400, 3), np.uint8)
blank_image[:, :] = (255, 255, 255)

# 测试动画填充
animated_flood_fill(
    poly=Polygon([[0,0],[0,100],[100,100],[100,0]]),
    new_color=(255,0,0),
    fps=30
)

cv.destroyAllWindows()

代码说明

  1. 核心工具函数:

    • get_polygon_center:利用shapely的centroid属性获取多边形中心,作为扩散起点
    • get_max_radius:计算中心到多边形最远顶点的距离,确定扩散的最大半径
    • generate_gradient_color:根据当前扩散进度,生成从背景色到目标色的过渡色
  2. 动画逻辑:

    • 复用原有代码生成多边形掩码,确保填充区域严格限制在多边形内
    • 生成线性递增的半径序列,控制扩散的平滑度
    • 逐帧绘制同心圆环,通过掩码过滤后合并到原始图像,实现扩散效果
    • 最后填充完整目标色,完成动画收尾
  3. 可调参数:

    • fps:控制动画帧率,数值越大动画越快
    • radii的num参数:调整生成的圆环数量,数值越大动画越平滑
    • cv.circle的thickness:调整圆环宽度,模拟Procreate的渐变层次

内容的提问来源于stack exchange,提问作者Learner

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 22:31:13