четверг, 20 августа 2009 г.

2D фракталы на основе L-систем

В ходе игр с python испытывал некую нехватку интересных задач, как собственно при изучении любого нового языка программирования. И тут друг предложил интересную задачу с народно-хозяйственным эффектом - написать прогу для рисования фракталов на основе L-систем. Мне понравилась идея, и понравилось использовать pyhton для её реализации.

Вот пример осенне-фрактального древа:



Далее ещё пара картинок и, собственно, сами скрипты.


Кривая Пеано-Госпера:

Peano-Gosper curve

кривая Гильберта:

Hilbert Curve

Прежде всего для работы с png необходим PNGCanvas.


Данная программа разбита на два модуля - LSystem.py и Turtle.py.
Первый отвечает за генерацию инструкций по L-системе для второго модуля, который, как видно по названию, является черепахой и просто рисует на основе инструкций.

Работа скрипта занимает вдвое больше времени чем надо только для отрисовки. Это связанно с тем что черепаха (предсказывает будущее) проходит по интсрукциям дважды, сперва вычисляя размеры холста, а затем уже рисует.

L-системы задаются в data-файлах

Пример файла для кривой Гильберта
---------------------------
L
4
6
2
L=+ R F - L F L - F R +
R=- L F + R F R + F L -
---------------------------

L #это аксиома, необходимо разделять слова пробелами.
4 #шаг изменения угла операциями + и - равен 360/4
6 #длинна штриха оставляемого черепахой по команде F
2 #начальный угол черепахи =360/2 (пожалуйста, не ставьте там ноль =) )
L=+ R F - L F L - F R + #правило, необходимо разделять слова пробелами, и писать слитно со знаком равно

Скачать все скрипты и PNGCanvas в одном архиве.

LSystem.py

  1. #!/usr/bin/python
  2. # -*- encoding: utf-8 -*-
  3. """L-System parser"""
  4. __version__="0.3"
  5. __author__ = "Anton Valter (avalter.blogspot.com)"
  6. __copyright__ = "CC Attribution Anton Valter"
  7.  
  8.  
  9.  
  10. class LSystem(object):
  11.   def __init__(self,funcs,deep=3):
  12.     self.x=0
  13.     self.y=0
  14.     self.leftB=0
  15.     self.rightB=0
  16.     self.topB=0
  17.     self.bottomB=0
  18.     self.rules = dict()
  19.     self.deep = deep
  20.     self.funcs=funcs
  21.     print self.funcs
  22.  
  23.   def setAxiom(self,axiom):
  24.     self.axiom = axiom.split()
  25.  
  26.   def registerRule(self,name,rule):
  27.     self.rules[name]=rule.split()
  28.  
  29.   def setDeep(self,deep):
  30.     self.deep = deep
  31.  
  32.   def interSteps(self,string,deep):
  33.     for c in string:
  34.       if c in self.rules:
  35.         if deep<self.deep:
  36.            for ch in self.interSteps(self.rules[c],deep+1):
  37.             yield ch
  38.         elif c in self.funcs:
  39.           yield c
  40.       else:
  41.         yield c
  42.  
  43.   def steps(self):
  44.     return self.interSteps(self.axiom,1)
  45.  
  46. # vim:set tabstop=4 softtabstop=4 shiftwidth=4 expandtab:
* This source code was highlighted with Source Code Highlighter.


Turtle.py:

#!/usr/bin/python
# -*- encoding: utf-8 -*-

"""2D Turtle for L-Systems"""
__version__ = "0.8"
__author__ = "Anton Valter (avalter.blogspot.com)"
__copyright__ = "CC Attribution Anton Valter"


from PNGCanvas import PNGCanvas
import math
import re
from LSystem import LSystem

maxw = 4000 #максимальные размеры изображения
maxh = 4000 #при привышении - прерывание

class Turtle(object):
  def __init__(self):
    self.x=float(0)
    self.y=float(0)
    self.leftB=0
    self.rightB=0
    self.topB=0
    self.bottomB=0
    self.stack = list()
    self.p=0
    self.alpha=0
    self.step=50
    self.backcolor=[0,0,0,0]
    self.basedrawcolor=(0,0,0,0xff)
    self.drawcolor=self.basedrawcolor
    self.stepcount=0
    self.baseAlpha=0
    self.colorstep=50
    self.basestep=10

  def setBorders(self,left,right,top,bottom):
    self.leftB=left
    self.rightB=right
    self.topB=top
    self.bottomB=bottom

  def prepareCanvas(self,width,height):
    if width<2:
      width=5
    if height<2:
      height=5
    self.c = PNGCanvas(width,height)
    self.c.color = self.backcolor
#    self.c.filledRectangle(0,0,width-1,height-1) // раскоменть если надо фон не прозрачный
    if width<2:
      width=5
    if height<2:
      height=5
    self.c.blendRect(1,1,width-1,height-1,width,0,self.c)
    self.c.color = self.drawcolor


  def produceSteps(self,drawFunction,t):
    self.alpha=self.baseAlpha
    self.drawcolor = self.basedrawcolor
    self.step = self.basestep
    for action in t.steps():
      if action=='+':
        self.alpha=self.alpha+self.p
      elif action=='-':
        self.alpha=self.alpha-self.p
      elif action=='[' or action=='{':
        self.stack.append((self.x,self.y,self.alpha,self.drawcolor,self.step))
      elif action==']' or action=='}':
        pos = self.stack.pop()
        self.x=pos[0]
        self.y=pos[1]
        self.alpha=pos[2]
        if action=='}':
          self.drawcolor = pos[3]
        self.step = pos[4]
      elif action=='F':
        newPosx=(self.x+math.cos(self.alpha)*self.step)
        newPosy=(self.y+math.sin(self.alpha)*self.step)
        drawFunction(newPosx,newPosy)
        self.x=newPosx
        self.y=newPosy
      elif action=='f':
        newPosx=(self.x+math.cos(self.alpha)*self.step)
        newPosy=(self.y+math.sin(self.alpha)*self.step)
        self.calcSize(newPosx,newPosy)
        self.x=newPosx
        self.y=newPosy
      elif re.match('\(([-+])?,([-+])?,([-+])?,([-+])?\)',action):
        m = re.match('\(([-+])?,([-+])?,([-+])?,([-+])?\)',action)
        r,g,b,a = m.group(1),m.group(2),m.group(3),m.group(4)
        rv,gv,bv,av=self.drawcolor

        def modifyColor(color,step,value):
          if color=='+':
            return min(value+step,0xff)
          if color=='-':
            return max(value-step,0x00)
          return value
         
        rv = modifyColor(r,self.colorstep,rv)
        gv = modifyColor(g,self.colorstep,gv)
        bv = modifyColor(b,self.colorstep,bv)
        av = modifyColor(a,self.colorstep,av)
        self.drawcolor=(rv,gv,bv,av)
      elif re.match('\((\d+)?,(\d+)?,(\d+)?,(\d+)?\)',action):
        m = re.match('\((\d+)?,(\d+)?,(\d+)?,(\d+)?\)',action)
        r,g,b,a = m.group(1),m.group(2),m.group(3),m.group(4)
        rv,gv,bv,av=self.drawcolor
        
        def setColor(color,value):
          if color:
            return min(max(int(color),0x00),0xff)
          return value

        rv = setColor(r,rv)
        gv = setColor(g,gv)
        bv = setColor(b,bv)
        av = setColor(a,av)
        self.drawcolor = rv,gv,bv,av
      elif re.match('\@(I)?(Q)?(\d+(?:\.\d+)?)',action):
        m = re.match('\@(I)?(Q)?(\d+(?:\.\d+)?)',action)
        i,q,n=m.group(1),m.group(2),m.group(3)
        num = float(n)
        if q:
          num = math.sqrt(num)
        if i:
          if num > 0.00001:
            num = 1.0/num
        self.step=self.step*num
          

  def draw(self,newposx,newposy):
    self.c.color=self.drawcolor
    self.stepProduced+=1    
    strStep = str(self.stepProduced)+'/'+str(self.stepcount-1)
    print strStep,self.c.color,self.step
    self.c.line(self.x,self.y,newposx,newposy)

  def calcSize(self,newposx,newposy):
    self.stepcount+=1
    if newposx > self.rightB:
      self.rightB=newposx
    if newposx < self.leftB:
      self.leftB=newposx
    if newposy > self.topB:
      self.topB=newposy
    if newposy < self.bottomB:
      self.bottomB=newposy

  def start(self,pngFileName,rulesFileName,deep):
    rules = open(rulesFileName,'r')
    t = LSystem(['F','-','+','f','[',']','(',')','{','}'],deep)
    t.setAxiom(rules.readline())
    self.p = 2*math.pi/float(rules.readline())
    self.basestep = float(rules.readline())
    self.baseAlpha = 2*math.pi/float(rules.readline())
    while 1:
      str = rules.readline()
      print str
      if not str: break
      rule=str.split('=')
      t.registerRule(rule[0],rule[1])
    rules.close()
    self.strlen=0
    self.calcSize(self.x,self.y)
    self.stepProduced=0
    self.produceSteps(self.calcSize,t)
    width=self.rightB-self.leftB
    height=self.topB-self.bottomB
    if height > maxh or width > maxw:
       return
    self.x=abs(self.leftB)
    self.y=abs(self.bottomB)
    self.prepareCanvas(int(width+1),int(height+1))
    self.produceSteps(self.draw,t)
    file = open(pngFileName,'wb')
    file.write(self.c.dump())
    file.close()


if __name__=='__main__':
  png = raw_input('outputfile:')
#  png = 'test.png'
  data = raw_input('datafile:')
  deep = int(raw_input('deep:'))
  t = Turtle()
  t.start(png,data,deep)
# vim:set tabstop=4 softtabstop=4 shiftwidth=4 expandtab:


* This source code was highlighted with Source Code Highlighter.



2 коммент.:

  1. Материал интересный, а кода как-то многовато для такой простенькой задачки. У меня функция рисования при помощи PIL занимает 50 строчек с отступами + пару десятков строк для обвязок и рисования некоторых графов.

    ОтветитьУдалить