使用python生成模拟花朵生长的gif动图(Simulate flower grow process with python)

使用python生成模拟花朵生长的gif动图(Simulate flower grow process with python)

在网上读到刘大可的文章《关于“植物身上的黄金分割”的叫你恍然大悟的文章》,里面的动图非常有趣,本文是自己尝试用python语言生成这些动图的过程。

原理

首先要理解花瓣的生长过程,上面的文章中有一段话:
在这里插入图片描述使用python生成模拟花朵生长的gif动图(Simulate flower grow process with python)_第1张图片
图1: 图片来源:https://card.weibo.com/article/m/show/id/2309404407466321379574

那么如果以坐标原点为”顶端“,(x0,y0), (x1,y1)分别为先后分生出的新牙,那么如果夹角值固定,花瓣每次向外展开的长度是固定的,我们就可以简化为下面的高中数学问题:

使用python生成模拟花朵生长的gif动图(Simulate flower grow process with python)_第2张图片

其中a, b表示长度,是已知值,并且根据上面的假设,a=2b,python有专门求解(x1,y1)的库,我们要做的只是列出方程,在这里略去推导过程,求解x1,y1的python代码如下:

使用python生成模拟花朵生长的gif动图(Simulate flower grow process with python)_第3张图片其中ratio是图1中两个角度的比值。

实现

问题简化为给定两个点以及它们的夹角,求出第三个点,然后讲这第三个点加入到列表,当作新的x0,y0,和原点联合起来,再求出新的x1,y1。 在这个过程中,每个点还以相等的速度远离远点。这样反复迭代,就得到了花朵在每个时间点的所有点的坐标,每个时间点生成一张图片,把所有的图片制作成gif,就得到了上面的过程,主要代码如下:

from __future__ import division
from PIL import Image, ImageDraw
from images2gif import writeGif
import math
import numpy
import sympy as sym

pi = 3.1415926


def solve(a, b, x0, y0, ratio):
    theta = (360.0 * ratio) / (1.0 + ratio)
    if theta > 180.0:
        theta = 360 - theta

    angle = theta / 360.0 * 2 * pi
    x1, y1 = sym.symbols('x1,y1')
    f = sym.Eq(x1**2 + y1**2, b**2)
    g = sym.Eq(x0 * x1 + y0 * y1, a * b * math.cos(angle))
    return sym.solve([f, g], (x1, y1))


def extend(baseX, baseY, x, y, extend):
    signX_head = 1
    signY_head = 1

    if baseX > x:
        signX_head = -1
    else:
        signX_head = 1

    if baseY > y:
        signY_head = -1
    else:
        signY_head = 1

    length = math.sqrt((x-baseX)**2 + (y-baseY)**2)
    if length == 0:
        return (baseX, baseY)

    lfCos = math.fabs(math.fabs(x - baseX) / length)
    lfSin = math.fabs(math.fabs(y - baseY) / length)
    dx = extend * lfCos
    dy = extend * lfSin
    x = x + dx * signX_head
    y = y + dy * signY_head
    return (x, y)


def create_image_with_ball(width, height, points, ball_size):

    img = Image.new('RGB', (width, height), (255, 255, 255))
    draw = ImageDraw.Draw(img)
    # draw.ellipse takes a 4-tuple (x0, y0, x1, y1) where (x0, y0) is
    # the top-left bound of the box and (x1, y1) is the lower-right
    # bound of the box.
    for point in points:
        topLeftX = point[0] + width / 2.0
        topLeftY = -point[1] + height / 2.0
        lowRightX = point[0] + ball_size + width / 2.0
        lowRightY = -point[1] + ball_size + height / 2.0
        draw.ellipse((topLeftX, topLeftY, lowRightX, lowRightY), fill='black')
    return img


def isSameLine(x1, y1, x2, y2, x3, y3):
    return math.fabs((y1 - y2) * (x1 - x3) - (y1 - y3) * (x1 - x2)) <= 1e-3


if __name__ == '__main__':

    x0, y0 = 0, 0
    deltaLen = 5
    format = "flowerGrow_ratio_random__%s.gif"

    # ratios = [1.618, 0.36, 0.25316, 1.41421, 0.5, 0.2, 0.16666]
    # for ratio in ratios:
    for ratio in numpy.arange(0.1525 , 2 , 0.1):
        points = [(x0, y0)]
        frames = []
        for index in range(100):
            if len(points) == 1:
                points.append((0, 0 + deltaLen))
            elif len(points) == 2:
                points[1] = extend(x0, y0, points[1][0], points[1][1], deltaLen)
                a = deltaLen * 2
                b = deltaLen
                towPoints = solve(a, b, points[1][0], points[1][1], ratio)
                points.append(towPoints[0])
            else:
                for i in range(len(points)):
                    if i == 0:
                        continue
                    points[i] = extend(x0, y0, points[i][0], points[i][1], deltaLen)

                lastPoint = points[-1]
                x1 = lastPoint[0]
                y1 = lastPoint[1]
                lastSecond = points[-2]
                lenA = math.sqrt((x1-x0)**2 + (y1-y0)**2)
                lenB = deltaLen
                twoPoints = solve(lenA, lenB, x1, y1, ratio)
                if isSameLine(x0, y0, lastSecond[0], lastSecond[1], twoPoints[0][0], twoPoints[0][1]):
                    points.append(twoPoints[1])
                else:
                    points.append(twoPoints[0])

            new_frame = create_image_with_ball(800, 800, points, 20)
            frames.append(new_frame)

        # Save into a GIF file that loops forever
        filename = format % ratio
        print format % ratio
        writeGif(filename, frames, duration=0.2)

几点说明:

  1. images2gif来自https://github.com/lucyking/images2gif-Pillow,用来生成gif
  2. extend函数用来求解每个点远离的过程,其中(baseX, baseY)是基准点,在这里是原点
  3. 由于solve函数求解出来有两组解,花朵的生长不可能是堆叠式生长,新长出的花瓣方向和原点,x0,y0的方向必须不在一条直线上。

效果图

Ratio=1.618,黄金分割率的效果,可以看到花瓣分布均匀,空间利用率高:

Ratio=1.7525

Ratio=0.8525

Ratio=1.25

Ratio=1.0525

你可能感兴趣的:(python)