Filament Tutorial

Welcome to Filament, a humanist programming language. Computers are amazing. They can calculate incredible things super fast. They can answer questions and draw graphics. However, computers are actually very dumb. All they do is simple arithmetic. What makes them amazing is that they can do it super duper fast . To do smart things humans have to teach them. This is called programming. Anyone can program, including you!


Filament understands arithmetic. Try typing in a math question like 2+2 then press the 'run' button (or type control-return on your keyboard). Filament will show you the answer: 4 . Now try dividing 4 by 2 or multiplying 3 by 5. Type in 4/2 . Run it. Then type 3 * 5 . Filament uses / to mean division and * for multiplication.

Filament understands longer math equations too. For example, imagine you have a refrigerator box that is 7 feet tall by 4 feet wide by 4 feet deep. You could find out the volume of the box by multiplying all of the sides.

7 * 4 * 4


In the above problem only we know that the 7 meant 7 feet . The computer doesn't know because we didn't tell it. Fortunately Filament lets us tell the computer exactly what units we mean. Let's try that again with units:

7feet * 4feet * 4feet
112 foot

Now we get 112 cuft . Cool. Filament knows to convert the answer into cubic feet. But what if we didn't want cubic feet. We are talking about volume and there are several different units that could represent volume. Let's ask Filament to convert it into gallons instead.

7ft * 4ft * 4ft as gal
837.815 gallon

which gives us the answer 837.81 gallons .

Notice that this time we abbreviated feet to ft and gal for gallons . Filament understands the full names and abbrevations for over a hundred kinds of units, and it can convert between any of them. Here's a few more examples to try.

Convert your height into centimeters. I'm 5 foot four inches so

5feet + 4inches as cm
162.560 centimeter

Some kitchen math:

2cups + 4tablespoons
36 tablespoon

Calculate your age in seconds.

today() - date(month : 1, day : 15, year : 2003) as seconds
570585600 second

If you try to convert something that can't be converted, like area to volume, then Filament will let you know. Try this:

7ft * 4ft as gal
209.454 gallon

results in Error. Cannot convert ft^2 to gallons.

Units are very important. They help make sure our calculations are correct. Even professionals get this wrong some times. NASA once lost a space probe worth over 100 million dollars because the software didn't convert correctly between imperial and metric units.


Now lets try a more complex problem. In one of the Superman movies he flies so fast that the world turns backwards and reverses time. That got me thinking. Is that realistic? The earth is pretty big. How long would it really take him to fly around the world?

We need some information first. How fast can Superman fly? Apparently the comics are pretty vague about his speed. Some say it's faster than light, some say it's infinite, some say it's just slighly slower than The Flash. Since this is about the real world let's go with an older claim, that Superman is faster than a speeding bullet . According to the internet, the fastest bullet ever made was was the .220 Swift which can regularly exceed 4,000 feet per second. The fastest recorded shot was at 4,665 ft/s , so we'll go with that.

Now wee need to know how big the earth is. The earth isn't perfectly spherical and of course it would depend on exactly which part of the earth superman flew, but according to Wikipedia the average (mean) radius of the Earth is 6,371.0 kilometers. We also know the circumference of a circle is 2 * pi * radius . So the equation is

(6371.0km * pi * 2) / 4000ft/s as hours

Pretty fast. He could almost go three times around the earth in a single 24 hour day. But not as fast as the movie said.

Programming is both fun and useful. We can instruct computers to help us answer all sorts of interesting questions. In the next section we'll learn about groups of numbers called lists, and how to do interesting math with them.


Now let's take a look at lists. Imagine you want to add up some numbers. You could do it by adding each number separately like this:

4 + 5 + 6 + 7 + 8

or you could make them a list and use the sum function.


Sum is a built in function that will add all of the items in a list. There are a lot of other cool functions that work on lists. You can sort a list

1, 4, 7, 8

get the length


or combine sum and length to find the average

nums << [8,4,7,1] sum(nums) / length(nums)

Making Lists

Sometimes you need to generate a list. Suppose you wanted to know the sum of every number from 0 to 100. Of course you could write out the numbers directly, but Filament has a way to generate lists for you. It's called range .

range(10) sum(range(100))

Range is flexible. You can give it both a start and end number, or even jump by steps.

range(min : 20, max : 30)
20, 21, 22, 23, 24, 25, 26, 27, 28, 29
range(100, step : 10)
0, 10, 20, 30, 40, 50, 60, 70, 80, 90

Remember that range will start at the minmum and go to one less than the max. So 0 to 10 will go up to 9.

Filament can handle big lists. If you ask for range(0,10_000_000) it will show the the first few and then ... before the last few.

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24 ... 9975, 9976, 9977, 9978, 9979, 9980, 9981, 9982, 9983, 9984, 9985, 9986, 9987, 9988, 9989, 9990, 9991, 9992, 9993, 9994, 9995, 9996, 9997, 9998, 9999]

Lists are very useful for lots of things, but sometimes you get more numbers than you need. Suppose you wanted all the numbers from from 0 to 20 minus the first three. Use take(list,3). Want just the last three use take(list,-3)

list << range(10) take(list, 3)
0, 1, 2
list << range(10) take(list, -3)
7, 8, 9

You can also remove items from a list with drop

drop(range(10), 8)
8, 9

And finally you can join two lists together

join([4,2], [8,6])
4, 2, 8, 6

In addition to holding data, lists let you do things that you could do on a single number, but in bulk. You can add a single number to a list

1 + [1,2,3]
2, 3, 4

or add two lists together

[1,2,3] + [4,5,6]
5, 7, 9

Math with Lists

It might seem strange to do math on lists but it's actually quite useful. Image you had a list of prices and you would like to offer a 20% discount. You can do that with a single multiplication.

prices << [4.86,5.23,10.99,8.43] sale_prices << 0.8 * prices
3.888, 4.184, 8.792, 6.744

Suppose you sold lemonade on four weekends in april, and another four in july. It would be nice to compare the sales for the different weekends to see if july did better thanks to warmer weather. You can do this by just subtracting two lists.

april << [34,44,56,42] july << [67,45,77,98] july - april
33, 1, 21, 56

Doing math with lists is also great for working with vectors. You can add them, multiply as the dot product, and calculate the magnitude.

V1 << [0,0,5] V2 << [1,0,1] V1 + V2 V1 * V2 sqrt(sum(power(V1, 2)))

Filtering and Mapping Lists

Lists let you search for data too. You can find items using select and a small function. Let's find all of the prime numbers up to 10000

select(range(100), where : is_prime)
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97

or all numbers evenly divisible by 5

def div5 ( x : ? ) { x mod5 =0 } select(range(100), where : div5)
0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95

And one of the best parts about lists is that they can hold more than numbers. You can work with lists of strings, numbers, booleans. Consider this simple list of people.

friends << ["Bart","homer","Ned"]
"Bart", "homer", "Ned"

or a list of booleans

true, false, true

Drawing Data

Bar Charts

One of the coolest things about lists is that you can draw them. Just send a list into the chart function to see it as a bar chart. Suppose you had a list of heights of your friends.


or just want to draw the numbers from 0 to 9.


Plotting equations

While you could use range , map , and chart to draw pictures of x , power(x,2) , sin() or other math equations, there is a much better way: using the plot function.

A quadratic equation

plot(y : (x)->{(x ** 2 - 3 * x - 4)})

Sine wave

plot(y : theta->sin(theta * 2))

A polar Archimedes spiral

spiral << theta->0.25 * theta plot(polar : spiral, min : 0, max : pi * 32)

Charts Datasets

Even better than pulling in your own data, is working with curated datasets that have already been assembled. Filament comes with datasets for

  • Periodic table of elements
  • Letters of the English Alphabet
  • Planets of the solar system
  • Countries of the world
  • When you load a dataset it will be shown as a table.

    elements << dataset("elements")
    Hydrogen11.008HHenry CavendishJanuary 1, 1766
    Helium24.0026022HePierre JanssenAugust 18, 1868
    Lithium36.94LiJohan August ArfwedsonJanuary 1, 1800
    Beryllium49.01218315BeLouis Nicolas VauquelinJanuary 1, 1798
    Boron510.81BJoseph Louis Gay-LussacJanuary 1, 1808
    Carbon612.011CAncient EgyptJanuary 1, 2500
    Nitrogen714.007NDaniel RutherfordJanuary 1, 1772
    Oxygen815.999OCarl Wilhelm ScheeleJanuary 1, 1771
    Fluorine918.9984031636FAndré-Marie AmpèreAugust 26, 1812
    Neon1020.17976NeMorris Travers
    Sodium1122.989769282NaHumphry Davy
    Magnesium1224.305MgJoseph Black
    Silicon1428.085SiJöns Jacob Berzelius
    Phosphorus1530.9737619985PHennig Brand
    Sulfur1632.06SAncient china
    Chlorine1735.45ClCarl Wilhelm Scheele
    Argon1839.9481ArLord Rayleigh
    Potassium1939.09831KHumphry Davy
    Calcium2040.0784CaHumphry Davy
    Scandium2144.9559085ScLars Fredrik Nilson
    Titanium2247.8671TiWilliam Gregor
    Vanadium2350.94151VAndrés Manuel del Río
    Chromium2451.99616CrLouis Nicolas Vauquelin
    Manganese2554.9380443MnTorbern Olof Bergman
    Iron2655.8452Fe5000 BC
    Cobalt2758.9331944CoGeorg Brandt
    Nickel2858.69344NiAxel Fredrik Cronstedt
    Copper2963.5463CuMiddle East
    Gallium3169.7231GaLecoq de Boisbaudran
    Germanium3272.6308GeClemens Winkler
    Arsenic3374.9215956AsBronze Age
    Selenium3478.9718SeJöns Jakob Berzelius
    Bromine3579.904BrAntoine Jérôme Balard
    Krypton3683.7982KrWilliam Ramsay
    Rubidium3785.46783RbRobert Bunsen
    Strontium3887.621SrWilliam Cruickshank (chemist)
    Yttrium3988.905842YJohan Gadolin
    Zirconium4091.2242ZrMartin Heinrich Klaproth
    Niobium4192.906372NbCharles Hatchett
    Molybdenum4295.951MoCarl Wilhelm Scheele
    Technetium4398TcEmilio Segrè
    Ruthenium44101.072RuKarl Ernst Claus
    Rhodium45102.905502RhWilliam Hyde Wollaston
    Palladium46106.421PdWilliam Hyde Wollaston
    Silver47107.86822Agunknown, before 5000 BC
    Cadmium48112.4144CdKarl Samuel Leberecht Hermann
    Indium49114.8181InFerdinand Reich
    Tin50118.7107Snunknown, before 3500 BC
    Antimony51121.7601Sbunknown, before 3000 BC
    Tellurium52127.603TeFranz-Joseph Müller von Reichenstein
    Iodine53126.904473IBernard Courtois
    Xenon54131.2936XeWilliam Ramsay
    Cesium55132.905451966CsRobert Bunsen
    Barium56137.3277BaCarl Wilhelm Scheele
    Lanthanum57138.905477LaCarl Gustaf Mosander
    Cerium58140.1161CeMartin Heinrich Klaproth
    Praseodymium59140.907662PrCarl Auer von Welsbach
    Neodymium60144.2423NdCarl Auer von Welsbach
    Promethium61145PmChien Shiung Wu
    Samarium62150.362SmLecoq de Boisbaudran
    Europium63151.9641EuEugène-Anatole Demarçay
    Gadolinium64157.253GdJean Charles Galissard de Marignac
    Terbium65158.925352TbCarl Gustaf Mosander
    Dysprosium66162.5001DyLecoq de Boisbaudran
    Holmium67164.930332HoMarc Delafontaine
    Erbium68167.2593ErCarl Gustaf Mosander
    Thulium69168.934222TmPer Teodor Cleve
    Ytterbium70173.0451YbJean Charles Galissard de Marignac
    Lutetium71174.96681LuGeorges Urbain
    Hafnium72178.492HfDirk Coster
    Tantalum73180.947882TaAnders Gustaf Ekeberg
    Tungsten74183.841WCarl Wilhelm Scheele
    Rhenium75186.2071ReMasataka Ogawa
    Osmium76190.233OsSmithson Tennant
    Iridium77192.2173IrSmithson Tennant
    Platinum78195.0849PtAntonio de Ulloa
    Gold79196.9665695AuMiddle East
    Mercury80200.5923Hgunknown, before 2000 BCE (Element)
    Thallium81204.38TlWilliam Crookes
    Lead82207.21PbMiddle East
    Bismuth83208.980401BiClaude François Geoffroy
    Polonium84209PoPierre Curie
    Astatine85210AtDale R. Corson
    Radon86222RnFriedrich Ernst Dorn
    Francium87223FrMarguerite Perey
    Radium88226RaPierre Curie
    Actinium89227AcFriedrich Oskar Giesel
    Thorium90232.03774ThJöns Jakob Berzelius
    Protactinium91231.035882PaWilliam Crookes
    Uranium92238.028913UMartin Heinrich Klaproth
    Neptunium93237NpEdwin McMillan
    Plutonium94244PuGlenn T. Seaborg
    Americium95243AmGlenn T. Seaborg
    Curium96247CmGlenn T. Seaborg
    Berkelium97247BkLawrence Berkeley National Laboratory
    Californium98251CfLawrence Berkeley National Laboratory
    Einsteinium99252EsLawrence Berkeley National Laboratory
    Fermium100257FmLawrence Berkeley National Laboratory
    Mendelevium101258MdLawrence Berkeley National Laboratory
    Nobelium102259NoJoint Institute for Nuclear Research
    Lawrencium103266LrLawrence Berkeley National Laboratory
    Rutherfordium104267RfJoint Institute for Nuclear Research
    Dubnium105268DbJoint Institute for Nuclear Research
    Seaborgium106269SgLawrence Berkeley National Laboratory
    Bohrium107270BhGesellschaft für Schwerionenforschung
    Hassium108269HsGesellschaft für Schwerionenforschung
    Meitnerium109278MtGesellschaft für Schwerionenforschung
    Darmstadtium110281DsGesellschaft für Schwerionenforschung
    Roentgenium111282RgGesellschaft für Schwerionenforschung
    Copernicium112285CnGesellschaft für Schwerionenforschung
    Flerovium114289FlJoint Institute for Nuclear Research
    Moscovium115289McJoint Institute for Nuclear Research
    Livermorium116293LvJoint Institute for Nuclear Research
    Tennessine117294TsJoint Institute for Nuclear Research
    Oganesson118294OgJoint Institute for Nuclear Research
    Ununennium119315UueGSI Helmholtz Centre for Heavy Ion Research

    Each column in the table is a field of each record in the datasets.

    Let's suppose you want to compare the sizes of the planets. First load the planets dataset.

    planets << dataset("planets")

    Now add a chart to draw the planets.

    planets << dataset("planets") chart(planets)

    Hmm. That doesn't look right. Chart doesn't know what part of the planets dataset we want to draw. We have to tell it. Let's use mean_radius for the height of the bar chart. For the label under each bar we can use the name property. We can tell the chart function what to do using the named arguments x_label and y .

    Now let's compare the radius of the orbit to the radius of the planet. This will show us if the smaller planets are clustered together or spread out.

    planets << dataset("planets") chart(planets, type : "scatter", x : "orbital_radius", y : "mean_radius")

    Now lets make the size of the circle represent the size of the planet and show the names

    planets << dataset("planets") chart(planets, type : "scatter", x : "orbital_radius", y : "mean_radius", size : "mean_radius", name : "name")

    Here's a fun one. Let's see which letters have one syllable vs two.

    //TODO: this is broken when doing builddocs chart(dataset('letters'), y_value:'syllables')

    Let's check out the relative heights of the tallest buildings in the world:

    buildings << dataset("tallest_buildings") b2 << take(buildings, 5) chart(b2, y : "height", x : "name")


    states << dataset("states") first_letter << (state : ?)->{take(get_field(state, "name"), 1)} states << map(states, first_letter) histogram(states)

    Statehood date by year by decade

    states << dataset("states") get_year << state-> field << get_field(state, "statehood_date") dt << date(field, format : "MMMM dd, yyyy") get_field(dt, "year") years << map(states, with : get_year) histogram(years, bucket : 10)

    Random Numbers

    Filament can generate random numbers for you. Now you might wonder why this is useful. It turns out random numnbers are incredibly useful for all sorts of things, from drawing graphics to simulations, and of course they are heavily used in video games.

    To make a random number just call random


    Every time you run this function it will return a different number.

    By default the number will always be between 0 and 1, but you can choose a different max and min if you want. Suppose you want twenty random numbers between 5 and 10 you could do this:

    range(20) >> map(with : n->random(min : 5, max : 10))
    8.416, 5.059, 6.922, 9.190, 8.431, 5.786, 8.621, 8.192, 5.903, 5.280, 5.415, 6.291, 5.616, 8.128, 5.377, 5.691, 9.626, 6.747, 7.131, 5.057

    Mapping a range is an easy way to create a list, then call random on each one to make the final numbers. However, making lists random numbers is so common you can just call random directly using count .

    random(min : 5, max : 10, count : 20)
    6.921, 8.424, 8.157, 9.260, 6.057, 7.271, 5.732, 7.119, 9.913, 8.501, 9.278, 9.445, 5.478, 7.077, 9.210, 7.632, 5.488, 5.918, 9.123, 8.844

    Making Shapes

    Filament makes it easy to draw shapes. Let's start with some simple squares.

    Drawing Squares

    To create a square call the rect function with a width and height, then send it into the draw() function to show up on the screen.

    rect(width : 100, height : 100) >> draw()

    By default the shape is the upper left, but you can change the x and y to put it wherever you want.

    rect(x : 200, y : 100, width : 100, height : 100) >> draw()

    If you want to draw more than one shape, just put them into a list. You can even use units for the size of your shapes. Below are two rectangles, one in centimeters and one in inches. If you don't include any unit then Filament assumes it is in pixels.

    [rect(x : 0, width : 1cm, height : 5cm),rect(x : 50, width : 1in, height : 1in)] >> draw()

    Along with size, you can also set the color of shapes using the fill argument. You can set it to named colors as strings like "red" , "green" , and "green" or use a list of three numbers between 0 and 1. These will be interpreted as RGB values.


    rect(width : 10cm, height : 10cm, fill : "blue") >> draw()

    is the same as

    rect(width : 10cm, height : 10cm, fill : [0,0,1]) >> draw()

    You can draw multiple shapes by putting them into a list, but then they might draw on top of each other unless you manually set their x,y positions. Since it's common to want to put shapes next to each other, you can use the row function to space them out, with an optional gap parameter

    [rect(width : 10cm, height : 20cm, fill : "red"),rect(width : 10cm, height : 20cm, fill : "green"),rect(width : 10cm, height : 20cm, fill : "blue")] >> row(gap : 1cm) >> draw()

    Can you guess what column does? :)

    This means you could make your own bar chart from scratch!

    range(5) >> map(with : n->rect(width : 1cm, height : n * 5cm)) >> row(valign : "bottom", halign : "center") >> draw()

    Other Shapes


    circle(x : 4cm, y : 4cm, radius : 2cm, fill : "aqua") >> draw()

    making art

    Make some cool artwork with random numbers

    make << (n)->{ rect(x : random() * 200, y : random() * 200, width : 100, height : 100, fill : [n / 100,random(),0]) } range(100) >> map(with : make) >> draw()

    Random Colors

    Now let's use it to make some random colors. First I want to give Martin Ankerl credit for his original article on making pretty random colors. Thanks Martin.

    In Filament, colors are made using a list of three numbers, each from 0 to 1. They represent the red, green, and blue (RGB) parts of the color. Thus [0,1,0] would be pure green and [1,1,1] is pure white.

    Let's make 20 randomly colored squares.

    make_square << ()->{ rect(width : 100, height : 100, fill : [random(),random(),random()]) } range(50) >> map(with : make_square) >> row(gap : 0) >> draw()

    Hmm. Those are pretty ugly. Some colors are too dark. Some are too similar. The problem is that we are using the RGB color space . We don't have a way to make the colors more similar easily. However, there is another color space we can use called HSL for Hue, Saturation, and Lightness. This way we can have a single number for the hue (the 'color'), and keep the saturation and lightness fixed. Then we can convert them back to RGB to draw them.

    For the conversion we have a handy function called HSL_TO_RGB function. It takes the hue, saturation, and lightness the 0-1 range, returns RGB in then 0-1 range.

    make_square << ()->{ color << [random(),0.8,0.9] rect(width : 100, height : 100, fill : HSL_TO_RGB(color)) } range(50) >> map(with : make_square) >> row(gap : 0) >> draw()

    In the code above we choose a random hue but keep the saturation at 0.8 (sort of pastel like) and the lightness almost at 100%.

    Let's try that again with different S and L values. Lower the saturation to 0.5, meaning it's partly gray, and move lightness to 50% (0.5)

    make_square << ()->{ color << [random(),0.5,0.5] rect(width : 100, height : 100, fill : HSL_TO_RGB(color)) } range(50) >> map(with : make_square) >> row(gap : 0) >> draw()

    These look better. Each color is the same distance from black and white, so we are getting some nice hues. However, it's still pretty random. You'll notice that sometimes the colors clump together. Instead we'd like to have them distributed evenly across the hue.

    There's a clever trick we can use with the golden ratio. Start with a random number then add the golden ratio (1.618) to it. Each time we wrap it back around at 1. This works because the hue is like a circle. When you go far enough to the right edge it wraps back around. Thanks to the golden ratio it will keep looping around and always pick a new hue value distant from the previous ones.

    Note that I'm using the Golden Ratio Conjugate, which is the reciprical of the Golden Ratio. Also note that Filament doesn't have loops yet, so I'm using a recursive version.

    GRC << 0.618033988749895 make_square << (depth,h)->{ color << [h,0.5,0.5] ifdepth <=0 then rect(width : 100, height : 100, fill : HSL_TO_RGB(color)) else [rect(width : 100, height : 100, fill : HSL_TO_RGB(color)),make_square(depth - 1, h + GRC)] } make_square(50, random()) >> row(gap : 0) >> draw()

    There we go. That looks great! Why don't you try it yourself and change the saturation and lightness values.


    New Images from Scratch

    Filament can create images with colors, just like shapes. The difference is that an image is made of pixels and you must specify a width and height.

    make_image(width : 100, height : 100) >> map_image(with : (x,y)->{ [1,0,0] })

    The code above creates an image filled with rect. The map_image calls the function you pass to it for every pixel. This function should return a new color for that pixel. Colors are represented as RGB, in the range 0 to 1, just like with shapes.

    This means red << [1,0,0] , blue << [0,0,1] , and gray << [0.5,0.5,0.5] or [50%, 50%, 50%]

    Let's make another image where instead of using a single color, we calculate a new random grayscale value for each pixel.

    make_image(width : 100, height : 100) >> map_image(with : (x,y,color)->{ n << random(min : 0, max : 1) [n,n,n] })

    So far we have ignored the x and y values, but if we use them we can create patterns that vary over space. Let's make stripes where teh color changes based on if the x value is even or odd.

    red << [1,0,0] green << [0,1,0] make_image(width : 100, height : 100) >> map_image(with : (x,y,color)->{ ifx mod2 =0 then red else green })

    We can even draw shapes pixel by pixel using implict equations. For example if let's calculate the distance of a pixel from the origin. If it's greater than some threshold then make it be red, otherwise green. This is similar to how GPU pixel shaders work.

    dist << (x,y)->{sqrt(x ** 2 + y ** 2)} make_image(width : 100, height : 100) >> map_image(with : (x,y,c)->{ ifdist(x, y) <100 then[0,1,0]else[1,0,0] })

    Loading Images from the Web

    We can load an image from the web using the load_image function. filament load_image(src:'')

    Once we have the image we can modify it. Let's drop the red channel by returning a new color that only includes the green and blue values from the original pixels.

    load_image(src : "") >> mapimage(with : (x,y,c)->{ v1 << 0 v2 << (c[1]) v3 << (c[2]) [v1,v2,v3] })

    We can also make an image grayscale by averaging the colors.

    load_image(src : "") >> mapimage(with : (x,y,c)->{ n << sum(c) / 3 [n,n,n] })

    Converting to grayscale by averaging the red green and blue does work, but it doesn't loook very good. Taht's because it doesn't acocunt for the fact that the human eye is more sensitive to blue than to green or red. Let's create a few functions to make it work better.

    first we need brightness base on a more accurate grayscale conversion

    brightness << (c)->{c[0] * 0.299 + c[1] * 0.587 + c[2] * 0.114}

    Now let's create a function to mix two colors together using a t value that goes from 0 to one. If t is 0 then you get only the first color. If it's 1 then you get only the second color. Otherwise you get a mixture of hte two. Sometimes this is called a lerp function, short for interpolate.

    mix << (t,a,b)->{a * t + b * (1 - t)}

    Now that we can mix two colors, we can use black and white for grayscale. But we could also use white and brown for a sepia town, or even something crazier like red and blue.

    brightness << (c)->{c[0] * 0.299 + c[1] * 0.587 + c[2] * 0.114} mix << (t,a,b)->{a * t + b * (1 - t)} white << [1,1,1] brown << [0.5,0.4,0.1] sepia << (x,y,color)->{ brightness(color) >> mix(white, brown) } load_image(src : "") >> mapimage(with : sepia)
    brightness << (c)->{c[0] * 0.299 + c[1] * 0.587 + c[2] * 0.114} mix << (t,a,b)->{a * t + b * (1 - t)} red << [1,0,0] blue << [0,0,1] rb << (x,y,color)->{ brightness(color) >> mix(red, blue) } load_image(src : "") >> mapimage(with : rb)

    Turtle Graphics

    Based on this tutorial. link

    Turtle graphics is a different way of generating images than setting pixels or drawing shapes on the cartesian plane. Instead of saying "put a shape here", you have a turtle with a pen which draws the shapes for you. I know that sounds confusing, so let's start with some examples.

    How Turtles Move

    Image a turtle walking around in an infinite plane. Starts at the center and is pointed north. You can tell turtle to move forward or to turn. to start, let's tell it to move forward by 100 pixels.

    turtle_start(0, 0, 0) turtle_pendown() turtle_forward(100) turtle_done()

    Now let's tell it to turn right and move forward another hundred.

    turtle_start(0, 0, 0) turtle_pendown() turtle_forward(100) turtle_right(90) turtle_forward(100) turtle_done()

    Now we have an L shape. The turtle is carrying a pen, so every where it moves it draws a line.

    Now let's make a square by doing it four times.

    turtle_start(0, 0, 0) turtle_forward(100) turtle_right(90) turtle_forward(100) turtle_right(90) turtle_forward(100) turtle_right(90) turtle_forward(100) turtle_right(90) turtle_done()

    Now we have a square. Of course, writing 'right' 'forward' over and over is annoying. Instead let's use a loop to do it four times.

    turtle_start(0, 0, 0) range(4) >> map(with : ()->{ turtle_forward(100) turtle_right(90) }) turtle_done()

    Much better.


    Now that we know how to make a square, we can clearly see how to draw a hexagon. Loop 6 times and turn right by 60 degrees

    turtle_start(0, 0, 0) range(6) >> map(with : ()->{ turtle_forward(100) turtle_right(60) }) turtle_done()

    We can start to see a pattern. For any regular polygon with N sides we loop N times and turn by 360/N. Let's make some code to draw any sort of polygon. We can change the value of N to draw a different shape. Here it is for an octogon.

    turtle_start(0, 0, 0) ngon << (N,side)->{ range(N) >> map(with : ()->{ turtle_forward(side) turtle_right(360 / N) }) } ngon(8, 60) turtle_done()

    If we change N to 100 and size to 10 it looks like a circle.

    turtle_start(0, 0, 0) ngon << (N,side)->{ range(N) >> map(with : ()->{ turtle_forward(side) turtle_right(360 / N) }) } ngon(100, 10) turtle_done()


    What's even cooler than drawing a shape from lines, is drawing a shape over and over. Let's go back to drawing a square, but do it over and over with a slight rotation.

    turtle_start(0, 0, 0) ngon << (N,side)->{ range(N) >> map(with : ()->{ turtle_forward(side) turtle_right(360 / N) }) } range(50) >> map(with : ()->{ ngon(4, 100) turtle_right(10) }) turtle_done()


    Now let's switch colors each time with turtle_pencolor()

    turtle_start(0, 0, 0) ngon << (N,side)->{ range(N) >> map(with : ()->{ turtle_forward(side) turtle_right(360 / N) }) } range(50) >> map(with : (i)->{ ngon(4, 100) turtle_right(10) t << (i / 50) turtle_pencolor([0,1 - t,t]) }) turtle_done()

    Turtle graphics is a really fun way to draw shapes incrementally. What we've done is define a small thing, a side, then repeat it to make a shape, then repeat that to make a more complex shape.

    Complex Shapes

    For our final example let's draw a flower. How could we do this? Why don't we start with a petal, figure that out, then draw it a bunch of times to make the flower. But how do we draw a petal? By breaking it down into even smaller pieces.

    We learned to draw a curve by doing lots of short lines. Let's do just a partial circle.

    turtle_start(0, 0, 0) arc << ()->{ map(range(120), with : (n)->{ turtle_forward(2) turtle_right(1) }) } arc() turtle_done()

    Perfect. Now we can rotate and do it again

    turtle_start(0, 0, 0) turtle_pendown() arc << ()->{ map(range(120), with : (n)->{ turtle_forward(2) turtle_right(1) }) } leaf << ()->{ arc() turtle_right(60) arc() turtle_right(60) } leaf() turtle_done()

    Now we have a full petal. To get a complete flower we just need to do it a bunch of times.

    turtle_start(0, 0, 0) turtle_pendown() arc << ()->{ map(range(120), with : (n)->{ turtle_forward(2) turtle_right(1) }) } leaf << ()->{ arc() turtle_right(60) arc() turtle_right(60) } range(36) >> map(with : ()->{ leaf() turtle_right(10) }) turtle_penup() turtle_done()

    Now we have a complete flower. Most programming is like this. We build big complex things out of lots of smaller, simpler pieces.