Interpolating colors

This example shows how to interpolate two RGB colors by interpolating each R, G, B color value separately.

size(1000, 200)

# define two rgb colors
color1 = 0.5, 0.0, 0.3
color2 = 1.0, 0.6, 0.1

# total number of steps
steps = 12

# 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 = color1[0] + factor * (color2[0] - color1[0])
    g = color1[1] + factor * (color2[1] - color1[1])
    b = color1[2] + factor * (color2[2] - color1[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

Interpolating position and size

The next example shows the same approach used to interpolate position and size.

# 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 with code using a glyph’s isCompatible method. This function returns two values:

  • The first 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']))
(True, '')
print(g.isCompatible(f['n']))
(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

The script below shows how to generate a number of interpolation and extrapolation steps between two selected glyphs in the current 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.selectedGlyphNames

    # 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 between two sources

This example shows how to interpolate glyphs from two source fonts into a third font.

The script expects 3 fonts open:

  1. the current font, where the glyphs will be interpolated
  2. one source font named “Regular”
  3. another source font named “Bold”
# 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 glyphName not in f1:
        print(f'{glyphName} not in {f1}, skipping…')
        continue
    if glyphName not in f2:
        print(f'{glyphName} not in {f2}, skipping…')
        continue

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

    # interpolate glyph
    print(f'interpolating {glyphName}…')
    f[glyphName].interpolate(factor, f1[glyphName], f2[glyphName])

Interpolating fonts

Generate a new font by interpolating two source fonts.

# get fonts
font1 = OpenFont('path/to/font1.ufo')
font2 = OpenFont('path/to/font2.ufo')

# define interpolation factor
factor = 0.5

# make destination font
font3 = NewFont()

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

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

# done!
font3.save('path/to/font3.ufo')

Batch interpolating fonts

The following example generates a set of instances by interpolating two source fonts.

The names and interpolation values of each instance are defined in a list of tuples.

import os
from vanilla.dialogs import getFolder

# get masters and destination folder
font1 = OpenFont('path/to/font1.ufo')
font2 = OpenFont('path/to/font2.ufo')
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(f"\tgenerating {styleName} ({factor})...")

    # make a new font
    destFont = NewFont()

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

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

    # save instance
    fileName = f'{destFont.info.familyName}_{destFont.info.styleName}.ufo'
    ufoPath = os.path.join(folder, fileName)
    print(f'\tsaving instances at {ufoPath}...')
    destFont.save(ufoPath)

    # done with instance
    destFont.close()
    print()   # empty line in console

print('...done.\n')

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 handy script to generate a Condensed from a Regular and a Bold.

  • calculates a scaling factor from stem widths of Regular and Bold
  • uses interpolation and horizontal scaling to create condensed glyphs
# get Condensed, Regular and Bold fonts
condensedFont = CurrentFont()
regularFont = AllFonts().getFontsByStyleName('Roman')[0]
boldFont = AllFonts().getFontsByStyleName('Bold')[0]

# measure the Regular and Bold stem widths
regularStem = 95
boldStem = 140

# condensing factor
condenseFactor = 0.85

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

# interpolate selected glyphs
for glyphName in condensedFont.templateSelectedGlyphNames:

    # get Condensed, Regular and Bold glyphs
    condensedGlyph = condensedFont.newGlyph(glyphName, clear=True)
    regularGlyph = regularFont[glyphName]
    boldGlyph = boldFont[glyphName]

    # interpolate Regular and Bold
    condensedGlyph.clear()
    condensedGlyph.interpolate((condenseFactor, 0), regularGlyph, boldGlyph)

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

    # calculate glyph margins
    condensedGlyph.leftMargin = (regularGlyph.leftMargin + boldGlyph.leftMargin) * 0.5 * (1.0 - condenseFactor)
    condensedGlyph.rightMargin = (regularGlyph.rightMargin + boldGlyph.rightMargin) * 0.5 * (1.0 - condenseFactor)

    # done!
    condensedGlyph.changed()
Last edited on 01/09/2021