DrawBot IconΒΆ

The making of (by Andy Clymer)

from ufoLib.glifLib import readGlyphFromString
from fontTools.pens.cocoaPen import CocoaPen
from fontParts.fontshell.glyph import RGlyph
import random
import os

tryAndError = False
drawPencil = True
drawBubbles = True

# Big pencil for the file icon?
# (and then only save the first frame)
bigPencil = False

# Left handed?
leftHand = True

# Reduced color palette for file icon?
reducedPalette = False

folderPath = os.path.split(__file__)[0]
savePath = os.path.join(folderPath, "icon.gif")
singleFileSavePath = os.path.join(folderPath, "icon_%s.png")
#savePath = os.path.join(folderPath, "icon.png")


lightOrange = [1, 0.75, 0, 1]
orange = [1, 0.5, 0, 1]
redOrange = [1, 0.25, 0.1, 1]
darkOrange = [0.3, 0.15, 0, .4]
pinkish = [1, 0.5, 0.6, 1]
magenta = [0.9, 0.1, 0.5, 1]
purple = [0.7, 0.1, 0.8, 1]
darkPurple = [0.5, 0, 0.5]
brightPurple = [1, 0.1, 1, 1]
gray = [0.8, 0.8, 0.8, 1]
white = [1, 1, 1, 1]


# Glif string
iconGlifString = b"""<?xml version="1.0" encoding="UTF-8"?>
<glyph name="A" format="1">
  <advance width="0"/>
  <outline>
    <contour>
      <point x="95" y="407" type="line"/>
      <point x="328" y="442"/>
      <point x="422" y="391"/>
      <point x="422" y="258" type="curve" smooth="yes"/>
      <point x="422" y="124"/>
      <point x="328" y="73"/>
      <point x="95" y="108" type="curve"/>
    </contour>
    <contour>
      <point x="218" y="305" type="curve"/>
      <point x="218" y="210" type="line"/>
      <point x="275" y="206"/>
      <point x="300" y="221"/>
      <point x="300" y="258" type="curve" smooth="yes"/>
      <point x="300" y="295"/>
      <point x="275" y="310"/>
    </contour>
  </outline>
</glyph>
"""

# Read the string into a glyph object
iconGlyph = RGlyph()
pen = iconGlyph.getPointPen()
readGlyphFromString(iconGlifString, glyphObject=iconGlyph, pointPen=pen)

iconGlyph.scaleBy((1.12, 1.2))

# Fetch the path of the glyph as a NSBezierPath
pen = CocoaPen(None)
iconGlyph.draw(pen)
iconPath = pen.path
# ...and then convert it to a DrawBot BezierPath
iconPath = BezierPath(iconPath)

# Remove the inside contour of the glyph, and read another path
iconGlyph.removeContour(1)
# Fetch the path of the glyph as a NSBezierPath
pen = CocoaPen(None)
iconGlyph.draw(pen)
iconOutsidePath = pen.path
# ...and then convert it to a DrawBot BezierPath
iconOutsidePath = BezierPath(iconOutsidePath)



""" Helper functions """

def interpolate(f, a, b):
    v = (a + (b - a) * f)
    return v

def interpolateColor(f, color0=None, color1=None):
    # Default the two colors to pinkish orange and magenta:
    if not color0:
        if reducedPalette:
            color0 = white
        else: color0 = lightOrange
    if not color1:
        if reducedPalette:
            color1 = orange
        else: color1 = orange
    newColor = []
    # Interpolate
    for i in range(4):
        newColor.append(interpolate(f, color0[i], color1[i]))
    return tuple(newColor)


def drawBubble(size, phase):
    # Shift the phase
    if phase > 1:
        phase = phase - 1
    # Scale the phase, so that it doesn't happen all the time
    phase *= 3
    # Draw if it's durring the current phase
    if phase < 1:
        fill(1, 1, 1, 1-phase)
        stroke(1, 1, 1, 1)
        strokeWidth(10 * (1-phase))
        phaseSize = phase*size
        oval(-0.5*phaseSize, -0.5*phaseSize, phaseSize, phaseSize)


# Make some random bubble data
bubbles = []
if drawBubbles:
    for i in range(100):
        bubbles.append(
            (random.randint(0, 512), # x
            random.randint(0, 512), # y
            random.randint(30, 100), # size
            random.random()) # phase
            )


""" Start drawing """


size(512, 512)

def drawIcon(timeFactor):
    # timeFactor is the timeline position, between 0 and 1

    # Temporary background color
    #fill(0.85)
    #rect(0, 0, 512, 512)

    translate(256, 256)
    scale(1.1)
    translate(-256, -256)
    translate(-27, -51)


    fill(None)
    # Transparent shadow under the "D"
    with savedState():

        #fill(*darkOrange)
        stroke(*darkOrange)
        strokeWidth(60)

        drawPath(iconPath)


    # Gradient within the "D"
    save()
    # Clip
    clipPath(iconPath)
    # Move to the center of the canvas
    translate(256, 256)
    circleCount = 30
    for i in range(circleCount):
        f = i/circleCount
        angle = (f * 360) + (360 * timeFactor)
        x = 120 * sin(radians(angle+90))
        y = 120 * cos(radians(angle+90))
        colorFactor = f * f * f # Use an exponential curve for the color factor
        stroke(None)
        fill(*interpolateColor(colorFactor))
        #shadow((0, 0), 50, interpolateColor(colorFactor)) # Extra smoothness?
        oval(x-150, y-150, 300, 300)
    restore()

    # Bubbles
    for bubble in bubbles:
        save()
        clipPath(iconPath)
        translate(bubble[0], bubble[1])
        drawBubble(bubble[2], timeFactor + bubble[3])
        restore()

    # Pencil location
    angle = (f * 360) + (360 * timeFactor) + 70
    x = 120 * sin(radians(angle+90)) + 80
    y = 90 * cos(radians(angle+90)) + 10
    if not leftHand:
        x -= 60
        y -= 20

    # Shadow inside the "D"
    save()
    shadowPath = iconPath.copy()
    # Add the pencil shadow
    shadowX = 256
    shadowY = 300
    if leftHand:
        shadowX -= 40
        shadowY -= 10
    if drawPencil:
        if not bigPencil:
            shadowPath.oval(shadowX+x, shadowY+y, 50, 50)
    clipPath(iconPath)
    translate(-20, -20)
    strokeWidth(61)
    stroke(0, 0, 0, 0.25)
    drawPath(shadowPath)
    restore()

    # White stroke on top of the "D"
    fill(None)
    stroke(1)
    strokeWidth(30)
    drawPath(iconPath)

    # Pencil
    if drawPencil:
        save()
        translate(256, 286)
        # Rotate the pencil with each step
        pencilRotationAngle = 10 * cos(radians(angle))
        translate(x, y)
        # Pencil
        rotate(pencilRotationAngle)
        # And an additional amount for the base angle of the pencil
        if leftHand:
            rotate(50)
        else: rotate(-25)
        if bigPencil:
            scale(1.9, 1.9)
            strokeWidth(14)
            translate(25, 10)
        else: strokeWidth(18)
        fill(None)
        if reducedPalette:
            stroke(1)
            fill(*orange)
        else:
            stroke(*darkPurple)
            fill(*brightPurple)
        polygon((0, 0), (-40, 40), (-40, 140), (40, 140), (40, 40), close=True)
        # Pencil end
        oval(-40, 130, 80, 40)
        # Pencil tip
        if reducedPalette:
            fill(1)
        else: fill(*darkPurple)
        stroke(None)
        oval(-20, 0, 40, 40)
        restore()



totalFrames = 21
if tryAndError:
    totalFrames = 1
for i in range(totalFrames):
    f = i / totalFrames
    if not i == 0:
        newPage()
    frameDuration(1/10)
    drawIcon(f)
    if not tryAndError:
        pass#saveImage(singleFileSavePath % i)


if not tryAndError:
    saveImage(savePath)