Interpolating colors

A simple DrawBot example showing basic interpolation maths at work.

# define two rgb colors
c1 = 0.5, 0.0, 0.3
c2 = 1.0, 0.6, 0.1

# total number of steps
steps = 14

# initial position
x, y = 0, 0

# calculate size for steps
w = width() / steps
h = height()

# iterate over the total amount of steps
for i in range(steps):

    # calculate interpolation factor for this step
    factor = i * 1.0 / (steps - 1)

    # interpolate each rgb channel separately
    r = c1[0] + factor * (c2[0] - c1[0])
    g = c1[1] + factor * (c2[1] - c1[1])
    b = c1[2] + factor * (c2[2] - c1[2])

    # draw a rectangle with the interpolated color
    fill(r, g, b)
    stroke(r, g, b)
    rect(x, y, w, h)

    # increase x-position for the next step
    x += w

Modify color values and number of steps and see how the result changes.

Interpolating position and size

Another visual example using DrawBot, this time interpolating position and size instead of RGB color channels.

# define position and size for two rectangles
x1, y1 = 98, 88
w1, h1 = 480, 492

x2, y2 = 912, 794
w2, h2 = 20, 126

# total amount of steps
steps = 22

# define blend mode and color
blendMode('multiply')
fill(0, 0.3, 1, 0.2)

# iterate over the total amount of steps
for i in range(steps):

    # calculate interpolation factor for this step
    factor = i * 1.0 / (steps - 1)

    # interpolate each rectangle attribute separately
    x = x1 + factor * (x2 - x1)
    y = y1 + factor * (y2 - y1)
    w = w1 + factor * (w2 - w1)
    h = h1 + factor * (h2 - h1)

    # draw a rectangle with the calculated variables
    rect(x, y, w, h)

Checking interpolation compatibility

Before interpolating two glyphs, we need to make sure that they are compatible. We can do that in code, using a glyph’s isCompatible method. This function returns two values:

  • The first value is a boolean indicating if the two glyphs are compatible.
  • If the first value is False, the second will contain a report of the problems.
f = CurrentFont()
g = f['O']
print(g.isCompatible(f['o']))
print(g.isCompatible(f['n']))
>>> (True, '')
>>> (False, '[Fatal] Contour 0 contains a different number of segments.\n[Fatal] Contour 1 contains a different number of segments.\n[Warning] The glyphs do not contain components with exactly the same base glyphs.')

Interpolating glyphs in the same font

This script generates a number of interpolation and extrapolation steps between two selected glyphs in the same font.

# settings
interpolationSteps = 5
extrapolateSteps = 2

# get the currentFont
f = CurrentFont()

if f is None:
    # no font open
    print("Oeps! There is not font open.")

else:
    # get the selection
    selection = f.selection

    # check if the selection contains only two glyphs
    if len(selection) != 2:
        print("Incompatible selection: two compatible glyphs are required.")

    else:
        # get the master glyphs
        source1 = f[selection[0]]
        source2 = f[selection[1]]

        # check if they are compatible
        if not source1.isCompatible(source2)[0]:
            # the glyphs are not compatible
            print("Incompatible masters: Glyph %s and %s are not compatible." % (source1.name, source2.name))

        else:
            # loop over the amount of required interpolations
            nameSteps = 0
            for i in range(-extrapolateSteps, interpolationSteps + extrapolateSteps + 1, 1):
                # create a new name
                name = "interpolation.%03i" % nameSteps
                nameSteps += 1
                # create the glyph if does not exist
                dest = f.newGlyph(name)
                # get the interpolation factor (a value between 0.0 and 1.0)
                factor = i / float(interpolationSteps)
                # interpolate between the two masters with the factor
                dest.interpolate(factor, source1, source2)

            # done!
            f.changed()

Interpolating glyphs into the current font

In this example, 3 fonts are open: the current font, where the glyphs will be interpolated, and two master fonts, named “Regular” and “Bold”. The master fonts are selected by their style names using AllFonts().getFontsByStyleName().

# the font where the interpolated glyphs will be stored
f = CurrentFont()

# the two master fonts
f1 = AllFonts().getFontsByStyleName('Regular')[0]
f2 = AllFonts().getFontsByStyleName('Bold')[0]

# the interpolation factor
factor = 0.4

# a list of glyph names to be interpolated
glyphNames = ['A', 'B', 'C', 'a', 'b', 'c']

# iterate over the glyph names
for glyphName in glyphNames:

    # if this glyph is not available in one of the masters, skip it
    if not glyphName in f1:
        print('%s not in %s, skipping…', (glyphName, f1))
        continue
    if not glyphName in f2:
        print('%s not in %s, skipping…' % (glyphName, f1))
        continue

    # if the glyph does not exist in the destination font, create it
    if not glyphName in f:
        f.newGlyph(glyphName)

    # interpolate glyph
    print('interpolating %s…' % glyphName)
    f[glyphName].interpolate(factor, f1[glyphName], f2[glyphName])
>>> A not in RoboType Bold, skipping…
>>> interpolating B…
>>> interpolating C…
>>> interpolating a…
>>> interpolating b…
>>> interpolating c…

Interpolating fonts

This example will generate a new font by interpolating two existing fonts. We assume that both fonts are compatible.

# get fonts
font1 = OpenFont()
font2 = OpenFont()

# define interpolation factor
factor = 0.5

# make destination font
destination = NewFont()

# this interpolates the glyphs
destination.interpolate(factor, font1, font2)

# this interpolates the kerning
# comment this line out of you're just testing
destination.kerning.interpolate(font1.kerning, font2.kerning, factor)

# done!
destination.changed()

Batch interpolating fonts

This script generates a series of instances by interpolating two master fonts.

The names and interpolation values of the individual instances are defined in the code as a list of tuples.

import os
from vanilla.dialogs import getFolder

# get masters and destination folder
font1 = OpenFont()
font2 = OpenFont()
folder = getFolder("Select a folder to save the interpolations")[0]

# instance names and interpolation factors
instances = [
    ("Light", 0.25),
    ("Regular", 0.5),
    ("Bold", 0.75),
]

# generate instances
print('generating instances...\n')
for instance in instances:
    styleName, factor = instance
    print("\tgenerating %s (%s)..." % (styleName, factor))

    # make a new font
    dst = NewFont()

    # interpolate the glyphs
    dst.interpolate(factor, font1, font2)

    # interpolate the kerning
    # comment this line out of you're just testing
    # dst.kerning.interpolate(font1.kerning, font2.kerning, value)

    # set font name
    dst.info.familyName = "MyFamily"
    dst.info.styleName = styleName
    dst.changed()

    # save instance
    fileName = '%s_%s.ufo' % (dst.info.familyName, dst.info.styleName)
    ufoPath = os.path.join(folder, fileName)
    print('\tsaving instances at %s...' % ufoPath)
    dst.save(ufoPath)

    # done with instance
    dst.close()
    print

print('...done.')
>>> generating instances...
>>>
>>>    generating Light (0.25)...
>>>    saving instances at /myFolder/MyFamily_Light.ufo...
>>>
>>>    generating Regular (0.5)...
>>>    saving instances at /myFolder/MyFamily_Regular.ufo...
>>>
>>>    generating Bold (0.75)...
>>>    saving instances at /myFolder/MyFamily_Bold.ufo...
>>>
>>> ...done.

Condensomatic

A script to generate a Condensed from Regular and Bold. A Type]Media classic.

# a sample font containing Regular and Bold versions of a glyph
f = CurrentFont()

# Regular and Bold glyph names
regular = f['regular']
bold = f['bold']

# measure the stem widths for each weight
regularStem = 70
boldStem = 170

# condensing factor
condenseFactor = 0.2

# calculate scale factor from Regular and Bold stem widths
xFactor = float(regularStem) / (regularStem + condenseFactor * (boldStem - regularStem))

# create a new glyph for the result
g = f.newGlyph('condensed')
g.markColor = 1, 1, 0, 0.7

# interpolate weights
g.interpolate((condenseFactor, 0), regular, bold)

# scale glyph horizontally
g.scaleBy((xFactor, 1))

# calculate glyph margins
g.leftMargin = (regular.leftMargin + bold.leftMargin) * 0.5 * (1.0 - condenseFactor)
g.rightMargin = (regular.rightMargin + bold.rightMargin) * 0.5 * (1.0 - condenseFactor)

Last edited on 27/03/2018