funground API reference¶
Every public name in funground, with its signature, arguments and an example. This page is generated from the docstrings in the code, so help(f.name) in Python shows the same text. Do not edit it by hand: change the docstring and run python tools/make_api_reference.py.
Related pages: the Quick Reference (every function by topic, with pictures), the guide (learn step by step), When something goes wrong, the glossary, funground compared with p5 and DrawBot and the documentation front door.
In the examples, import funground as f is understood.
Contents¶
- Sketch and loop:
size,resize_canvas,full_screen,run,loop,no_loop,redraw,is_looping,exit,stop,frame_rate,show,smooth,no_smooth - Shapes:
point,line,rect,square,ellipse,circle,triangle,quad,polygon,arc,rect_mode,ellipse_mode - Curves and custom shapes:
bezier,bezier_point,bezier_tangent,curve,curve_point,curve_tangent,curve_tightness,begin_shape,end_shape,vertex,bezier_vertex,quadratic_vertex,curve_vertex,begin_contour,end_contour - Colour:
background,clear,color,color_mode,hsb,hsl,lerp_color,linear_gradient,radial_gradient - Fill and stroke:
fill,no_fill,stroke,no_stroke,stroke_width,stroke_cap,stroke_join,miter_limit,stroke_dash,no_dash,shadow,no_shadow,opacity,blend_mode,erase,no_erase,tint,no_tint - Text and fonts:
text,text_align,text_ascent,text_descent,text_leading,text_box,text_path,text_to_points,current_font,text_size,text_style,text_tracking,text_fallback,system_font,text_features,font_variations,text_width,text_font,load_font - Transforms and the state stack:
translate,rotate,scale,shear_x,shear_y,apply_matrix,reset_matrix,push,pop,saved_state - Paths and clipping:
path,draw_path,clip,no_clip - Marks:
mark - Play:
variations,keep - Pictures and layers:
create_graphics,layer,hide_layer,show_layer - Images and SVG:
image,image_mode,load_image,load_svg,svg_paths - Pixels and filters:
get,set,load_pixels,update_pixels,filter - Randomness and noise:
random,random_choice,random_gaussian,random_seed,noise,noise_detail,noise_seed - Maths:
constrain,distance,lerp,map_range,mag,norm,degrees,radians - Time:
year,month,day,hour,minute,second,millis - Interaction and controls:
key_down,cursor,no_cursor,create_button,create_checkbox,create_slider - Saving:
save,save_frames,save_gif,save_movie - Ground:
area,grid,mm,inch,ground - Motion and pages:
frame_duration,new_page,page_size,page_count - Sound:
load_sound,create_sound,tone,note,pluck,melody,sequence,mix,drone,note_to_frequency,frequency_to_note,chord_notes - Music analysis and the microphone:
microphone,microphones - Drawing sound:
draw_wave,draw_spectrum,spectrogram,draw_pitch_line - Ragas and talas:
ragas,raga,talas,tala_info,tala,match_ragas - Classes:
Vector,FormattedString - Live values:
delta_time,frame_count,ground,height,is_key_pressed,is_mouse_pressed,key,key_code,mouse_button,mouse_x,mouse_y,pixels,pmouse_x,pmouse_y,width - Classes in detail:
Sound,Microphone,Picture,Vector,FormattedString,Run,PathBuilder,Mark,Area,Ground,Cell,Grid,Path,Font,Control,Slider,Checkbox,Button,Color,Gradient,Raga,Tala - Constants and reference tables
Sketch and loop¶
f.size¶
f.size(width: 'int | str', height: 'int | None' = None, *, title: 'str' = 'funground', fps: 'int' = 60, margin: 'float | tuple' = 0, landscape: 'bool' = False) -> 'None'
Create the canvas, or change its size.
Call it once, at the start of setup(). In an animated sketch it opens the window. In a script (a file with no draw()) it makes a canvas with no window; use show() to look at it. If setup() never calls size(), run() opens a 640 by 480 window for you. Instead of numbers you can give a page name, such as size("A4") (see page_size). The margin is a guide for your drawing: read it back with f.ground.content, or use grid(). It does not clip anything.
| Argument | Meaning |
|---|---|
width |
the canvas width in pixels (above 0), or a page name such as "A4". |
height |
the canvas height in pixels (above 0). Leave it out when width is a page name. |
title |
the text in the window's title bar. The default is "funground". |
fps |
the target frames per second. The default is 60. It must be above 0. |
margin |
the space kept free around the edge. One number is used on all four sides. A tuple of four numbers is (top, right, bottom, left). The default is 0. It stays when the canvas changes size or a new page starts. |
landscape |
True turns a named page on its side, as in size("A4", landscape=True). The default is False. It only goes with a page name. |
Raises.
ValueError: the width, height or fps is 0 or less, a page name has a height, the name is unknown, a margin is negative, or the margins leave no room.
f.size("A4", margin=f.mm(15))
See also: ground, grid, run, resize_canvas, full_screen, page_size.
f.resize_canvas¶
f.resize_canvas(width: 'int', height: 'int') -> 'None'
Change the canvas size while the sketch runs.
f.width and f.height follow the new size.
| Argument | Meaning |
|---|---|
width, height |
the new size in pixels. |
f.resize_canvas(800, 600)
See also: size, full_screen.
f.full_screen¶
f.full_screen() -> 'None'
Fill the whole screen with the canvas.
Use it instead of size(), in setup(). f.width and f.height become the screen's size. Escape still ends the sketch. In a script it makes the canvas the screen's size, and show() then shows it full screen.
def setup():
f.full_screen()
See also: size, resize_canvas.
f.run¶
f.run(*, fps: 'int | None' = None, max_frames: 'int | None' = None) -> 'None'
Start the sketch: call setup() once, then draw() again and again.
Put it as the last line of your file. It finds the functions called setup() and draw() in your file, so you do not pass them in. setup() is optional. draw() is required. The window closes with its close button, the Escape key or stop().
| Argument | Meaning |
|---|---|
fps |
the frames per second, replacing the one given to size(). The default None keeps it. |
max_frames |
stop by itself after this many frames. The default None runs until the window closes. It is mostly for tests and for runs with no window. |
Raises.
RuntimeError: f.run() cannot find the file that called it, or the file mixes the script style with run().
f.run(max_frames=300)
See also: size, stop, no_loop.
f.loop¶
f.loop() -> 'None'
Call draw() every frame again, after no_loop().
f.no_loop¶
f.no_loop() -> 'None'
Stop calling draw() every frame. The window stays open.
draw() still runs once at the start, and after redraw(). Use loop() to start again.
f.no_loop()
See also: loop, redraw, is_looping.
f.redraw¶
f.redraw() -> 'None'
Call draw() once more.
Use it, for example, after a key press when the sketch is not looping.
def key_pressed():
f.redraw()
f.is_looping¶
f.is_looping() -> 'bool'
Return whether draw() is called every frame.
Returns. True while the sketch is looping. False after no_loop().
f.exit¶
f.exit() -> 'None'
End the sketch after this frame.
It is the same as stop(). It shadows Python's own exit() only inside funground.
f.exit()
See also: stop.
f.stop¶
f.stop() -> 'None'
Ask the sketch to stop.
The sketch ends after the current frame. It is the same as exit().
if f.frame_count > 600:
f.stop()
f.frame_rate¶
f.frame_rate() -> 'float'
Return the frames per second that the sketch is really achieving.
The number is smoothed over a few frames. It is 0.0 until the second frame.
Returns. A number of frames a second.
f.text(round(f.frame_rate()), 10, 10)
f.show¶
f.show() -> 'None'
Open a window on what a script has drawn, and wait until it is closed.
This is for the script style (a file with no draw()). Call size() first. The window also closes with the Escape key. With a document of several pages, the arrow keys turn the pages. With no window (FUNGROUND_HEADLESS=1) it returns at once.
f.size(400, 300)
f.circle(200, 150, 100)
f.show()
See also: size, new_page, save.
f.smooth¶
f.smooth() -> 'None'
Draw smooth (anti-aliased) edges again.
This is the start. Use it after no_smooth().
See also: no_smooth.
f.no_smooth¶
f.no_smooth() -> 'None'
Draw hard, pixel-sharp edges from now on.
There is no anti-aliasing, which suits pixel art. It is a setting of the sketch, so pop() does not undo it. Use smooth() to go back.
f.no_smooth()
See also: smooth.
Shapes¶
f.point¶
f.point(x: 'float', y: 'float') -> 'None'
Draw a dot.
The dot has the stroke colour and is about stroke_width pixels across.
| Argument | Meaning |
|---|---|
x, y |
the position, in pixels. |
f.stroke_width(6)
f.point(400, 100)
See also: line, stroke, stroke_width.
f.line¶
f.line(x1: 'float', y1: 'float', x2: 'float', y2: 'float') -> 'None'
Draw a straight line between two points.
A line uses the stroke only. The fill does not matter.
| Argument | Meaning |
|---|---|
x1, y1 |
where the line starts. |
x2, y2 |
where the line ends. |
f.line(220, 160, 340, 220)
See also: stroke, stroke_width, point.
f.rect¶
f.rect(x: 'float', y: 'float', width: 'float', height: 'float', *radii: 'float') -> 'None'
Draw a rectangle.
It uses the current fill and stroke. By default (x, y) is the top-left corner. rect_mode() can change that. Corner radii cannot be negative, and big ones are cut to half the shorter side.
| Argument | Meaning |
|---|---|
x, y |
the corner, in pixels from the top-left of the canvas. |
width |
the width, in pixels. |
height |
the height, in pixels. |
radii |
optional corner rounding. Give one number to round all four corners, or four numbers (top-left, top-right, bottom-right, bottom-left). |
f.rect(40, 160, 120, 60)
f.rect(40, 240, 120, 60, 12)
See also: square, rect_mode, fill, stroke.
f.square¶
f.square(x: 'float', y: 'float', size: 'float', *radii: 'float') -> 'None'
Draw a square.
It is placed like rect(): by its top-left corner, unless rect_mode() says otherwise.
| Argument | Meaning |
|---|---|
x, y |
the corner, in pixels. |
size |
the length of each side, in pixels. |
radii |
optional corner rounding, as for rect(): one number, or four. |
f.square(40, 40, 60, 10)
f.ellipse¶
f.ellipse(x: 'float', y: 'float', width: 'float', height: 'float') -> 'None'
Draw an ellipse.
It uses the current fill and stroke. By default (x, y) is the centre. ellipse_mode() can change that.
| Argument | Meaning |
|---|---|
x, y |
the centre, in pixels from the top-left corner. |
width |
the width across, in pixels. |
height |
the height, in pixels. |
f.ellipse(220, 80, 100, 50)
See also: circle, ellipse_mode, arc.
f.circle¶
f.circle(x: 'float', y: 'float', diameter: 'float') -> 'None'
Draw a circle.
The circle uses the current fill and stroke. By default (x, y) is the centre. ellipse_mode() can change that. The last number is the diameter, not the radius.
| Argument | Meaning |
|---|---|
x, y |
the centre, in pixels from the top-left corner. |
diameter |
the width across, in pixels. |
f.circle(200, 150, 80)
See also: ellipse, ellipse_mode, fill, stroke.
f.triangle¶
f.triangle(x1: 'float', y1: 'float', x2: 'float', y2: 'float', x3: 'float', y3: 'float') -> 'None'
Draw a triangle through three corners.
It uses the current fill and stroke.
| Argument | Meaning |
|---|---|
x1, y1 |
the first corner. |
x2, y2 |
the second corner. |
x3, y3 |
the third corner. |
f.triangle(10, 90, 90, 90, 50, 10)
f.quad¶
f.quad(x1: 'float', y1: 'float', x2: 'float', y2: 'float', x3: 'float', y3: 'float', x4: 'float', y4: 'float') -> 'None'
Draw a four-sided shape through four corners.
The corners are joined in the order you give them. It uses the current fill and stroke.
| Argument | Meaning |
|---|---|
x1, y1 |
the first corner. |
x2, y2 |
the second corner. |
x3, y3 |
the third corner. |
x4, y4 |
the fourth corner. |
f.quad(20, 20, 80, 20, 80, 80, 20, 80)
f.polygon¶
f.polygon(points: 'list[tuple[float, float]]') -> 'None'
Draw a closed shape through a list of points.
It uses the current fill and stroke. The last point is joined back to the first.
| Argument | Meaning |
|---|---|
points |
a list of (x, y) pairs, at least two. |
Raises.
ValueError: an item is not an (x, y) pair, or there are fewer than two points.
f.polygon([(0, 0), (50, 20), (20, 60)])
See also: triangle, quad, begin_shape.
f.arc¶
f.arc(x: 'float', y: 'float', width: 'float', height: 'float', start: 'float', stop: 'float', mode: 'str' = 'open') -> 'None'
Draw part of an ellipse.
Angles are in degrees and go clockwise, starting from the right (3 o'clock). If stop is less than start, 360 is added to it. An arc is never more than one full turn. ellipse_mode() changes how x, y, width and height are read.
| Argument | Meaning |
|---|---|
x, y |
the centre of the whole ellipse. |
width, height |
the size of the whole ellipse, in pixels. |
start, stop |
the first and last angle, in degrees. |
mode |
"open" (the default: the area is filled, but only the curve is outlined), "chord" (closed by a straight line) or "pie" (closed through the centre, like a slice). |
Raises.
ValueError: the mode is not one of those words.
f.arc(100, 100, 80, 80, 0, 270, "pie")
See also: ellipse, ellipse_mode.
f.rect_mode¶
f.rect_mode(mode: 'str') -> 'None'
Choose how rect() and square() read their numbers.
The choice is saved by push() and brought back by pop().
| Argument | Meaning |
|---|---|
mode |
"corner" (the default: x, y is the top-left corner, then the size), "corners" (two opposite corners), "center" (x, y is the middle, then the size) or "radius" (x, y is the middle, then half-sizes). |
Raises.
ValueError: the mode is not one of those words.
f.rect_mode("center")
f.rect(200, 150, 80, 40)
See also: rect, square, ellipse_mode, image_mode.
f.ellipse_mode¶
f.ellipse_mode(mode: 'str') -> 'None'
Choose how ellipse(), circle() and arc() read their numbers.
The choice is saved by push() and brought back by pop().
| Argument | Meaning |
|---|---|
mode |
"center" (the default: x, y is the middle), "radius" (x, y is the middle, then half-sizes), "corner" (x, y is the top-left of the box around the shape) or "corners" (two opposite corners of that box). |
Raises.
ValueError: the mode is not one of those words.
f.ellipse_mode("corner")
f.ellipse(20, 20, 100, 60)
See also: ellipse, circle, arc, rect_mode.
Curves and custom shapes¶
f.bezier¶
f.bezier(x1: 'float', y1: 'float', cx1: 'float', cy1: 'float', cx2: 'float', cy2: 'float', x2: 'float', y2: 'float') -> 'None'
Draw a Bezier curve from one point to another.
Two control points pull the curve. It is stroked only, never filled.
| Argument | Meaning |
|---|---|
x1, y1 |
where the curve starts. |
cx1, cy1 |
the first control point. |
cx2, cy2 |
the second control point. |
x2, y2 |
where the curve ends. |
f.bezier(20, 80, 20, 0, 180, 0, 180, 80)
See also: curve, bezier_vertex, bezier_point.
f.bezier_point¶
f.bezier_point(a: 'float', b: 'float', c: 'float', d: 'float', t: 'float') -> 'float'
Return one coordinate of a Bezier curve at a place along it.
Call it once for x and once for y.
| Argument | Meaning |
|---|---|
a |
the start coordinate. |
b |
the first control coordinate. |
c |
the second control coordinate. |
d |
the end coordinate. |
t |
the place along the curve, 0 (start) to 1 (end). |
Returns. The coordinate at t.
x = f.bezier_point(20, 20, 180, 180, 0.5)
y = f.bezier_point(80, 0, 0, 80, 0.5)
See also: bezier, bezier_tangent.
f.bezier_tangent¶
f.bezier_tangent(a: 'float', b: 'float', c: 'float', d: 'float', t: 'float') -> 'float'
Return the slope of one coordinate of a Bezier curve at a place along it.
Call it once for x and once for y, then use math.atan2 to get the direction.
| Argument | Meaning |
|---|---|
a |
the start coordinate. |
b |
the first control coordinate. |
c |
the second control coordinate. |
d |
the end coordinate. |
t |
the place along the curve, 0 (start) to 1 (end). |
Returns. The rate of change of the coordinate at t.
See also: bezier, bezier_point.
f.curve¶
f.curve(x1: 'float', y1: 'float', x2: 'float', y2: 'float', x3: 'float', y3: 'float', x4: 'float', y4: 'float') -> 'None'
Draw a smooth curve from point 2 to point 3.
Points 1 and 4 only steer it. It is stroked only, never filled. curve_tightness() changes how tight it is.
| Argument | Meaning |
|---|---|
x1, y1 |
the first steering point. |
x2, y2 |
where the curve starts. |
x3, y3 |
where the curve ends. |
x4, y4 |
the last steering point. |
f.curve(0, 100, 40, 40, 160, 40, 200, 100)
See also: bezier, curve_vertex, curve_tightness.
f.curve_point¶
f.curve_point(a: 'float', b: 'float', c: 'float', d: 'float', t: 'float') -> 'float'
Return one coordinate of a curve() segment at a place along it.
It uses the current curve_tightness(). Call it once for x and once for y.
| Argument | Meaning |
|---|---|
a, b, c, d |
the four coordinates, as for curve(): the first and last steer, and the curve runs from b to c. |
t |
the place along the segment, 0 (at b) to 1 (at c). |
Returns. The coordinate at t.
See also: curve, curve_tangent, curve_tightness.
f.curve_tangent¶
f.curve_tangent(a: 'float', b: 'float', c: 'float', d: 'float', t: 'float') -> 'float'
Return the slope of one coordinate of a curve() segment at a place along it.
| Argument | Meaning |
|---|---|
a, b, c, d |
the four coordinates, as for curve(). |
t |
the place along the segment, 0 to 1. |
Returns. The rate of change of the coordinate at t.
See also: curve, curve_point.
f.curve_tightness¶
f.curve_tightness(tightness: 'float') -> 'None'
Set how tight curve_vertex() and curve() curves are.
| Argument | Meaning |
|---|---|
tightness |
0 is smooth (the default). 1 gives straight lines. |
f.curve_tightness(0.5)
See also: curve_vertex, curve.
f.begin_shape¶
f.begin_shape() -> 'None'
Start a shape that you build from its corners.
Call vertex() for each corner, then end_shape(). Calling it twice without end_shape() is an error. A shape still open when draw() ends is dropped with a warning.
Raises.
RuntimeError: a shape is already being built.
f.begin_shape()
f.vertex(100, 20)
f.vertex(180, 80)
f.vertex(60, 90)
f.end_shape(close=True)
See also: vertex, end_shape, begin_contour.
f.end_shape¶
f.end_shape(close: 'bool' = False) -> 'None'
Draw the shape that you built from corners.
It uses the current fill and stroke. An open shape is only stroked, never filled. A shape that crosses itself, like a five-pointed star, is filled right through its centre.
| Argument | Meaning |
|---|---|
close |
True joins the last corner to the first, then fills and strokes it. The default False leaves the shape open. |
Raises.
RuntimeError: there is no begin_shape() before it.
f.end_shape(close=True)
See also: begin_shape, vertex.
f.vertex¶
f.vertex(x: 'float', y: 'float') -> 'None'
Add a corner to the shape begun by begin_shape().
| Argument | Meaning |
|---|---|
x, y |
the corner, in pixels. |
Raises.
RuntimeError: there is no begin_shape() before it.
See also: begin_shape, end_shape, bezier_vertex.
f.bezier_vertex¶
f.bezier_vertex(cx1: 'float', cy1: 'float', cx2: 'float', cy2: 'float', x: 'float', y: 'float') -> 'None'
Add a Bezier curve segment to the shape being built.
It goes from the last point to (x, y). It needs a vertex() before it.
| Argument | Meaning |
|---|---|
cx1, cy1 |
the first control point, which pulls the start of the curve. |
cx2, cy2 |
the second control point, which pulls the end of the curve. |
x, y |
where the curve ends. |
Raises.
RuntimeError: there is no begin_shape() or no earlier vertex().
f.begin_shape()
f.vertex(20, 80)
f.bezier_vertex(20, 0, 180, 0, 180, 80)
f.end_shape()
See also: quadratic_vertex, curve_vertex, bezier.
f.quadratic_vertex¶
f.quadratic_vertex(cx: 'float', cy: 'float', x: 'float', y: 'float') -> 'None'
Add a quadratic curve segment to the shape being built.
It goes from the last point to (x, y), pulled by one control point.
| Argument | Meaning |
|---|---|
cx, cy |
the control point. |
x, y |
where the curve ends. |
Raises.
RuntimeError: there is no begin_shape() or no earlier vertex().
f.quadratic_vertex(100, 0, 180, 80)
See also: bezier_vertex, curve_vertex.
f.curve_vertex¶
f.curve_vertex(x: 'float', y: 'float') -> 'None'
Add a point for a smooth curve that passes through the points.
Give at least four in a row. The first and last only steer the curve. curve_tightness() changes how tight it is.
| Argument | Meaning |
|---|---|
x, y |
the point, in pixels. |
f.begin_shape()
f.curve_vertex(0, 100)
f.curve_vertex(40, 40)
f.curve_vertex(160, 40)
f.curve_vertex(200, 100)
f.end_shape()
See also: curve, curve_tightness, bezier_vertex.
f.begin_contour¶
f.begin_contour() -> 'None'
Start a hole inside the shape being built.
List the hole's corners with vertex(), then call end_contour().
See also: end_contour, begin_shape.
f.end_contour¶
f.end_contour() -> 'None'
Finish the hole started by begin_contour().
See also: begin_contour, end_shape.
Colour¶
f.background¶
f.background(color: 'Color', *more: 'float') -> 'None'
Fill the whole canvas with one colour.
It ignores the fill, the stroke, the transform and any clip. Call it near the start of draw() to clear the last frame. Leave it out when you want trails. Inside a with f.layer(...) block it clears that layer. Inside a function drawn by variations(), it paints that version's whole picture.
| Argument | Meaning |
|---|---|
color |
a colour name such as "white", a hex string such as "#F05A45", an (r, g, b) or (r, g, b, a) tuple, a colour from f.color(), or a gradient. One number is a grey. |
more |
extra numbers, so that background(30, 30, 60) works like background((30, 30, 60)). They follow the colour mode. A name, hex string or gradient takes no extra numbers. |
Raises.
ValueError: the colour is not understood, or numbers follow a name or gradient.
f.background("white")
f.background(30, 30, 60)
See also: clear, fill, color_mode.
f.clear¶
f.clear() -> 'None'
Make the whole canvas transparent.
A PNG saved afterwards keeps the transparency. The window shows it as black.
See also: background, erase, save.
f.color¶
f.color(*values)
Make a colour that you can read and reuse.
You can pass it to fill(), stroke(), background() and so on. It has these parts: red, green, blue and alpha (each 0 to 255), hue (0 to 360), and saturation, brightness and lightness (each 0 to 100). Numbers follow the colour mode. A tuple always means red, green, blue. Use hsb() or hsl() to give a hue.
| Argument | Meaning |
|---|---|
values |
one colour (a name, a hex string, a tuple or another colour), or one grey number, or a grey and alpha, or three or four numbers (red, green, blue and optional alpha). |
Returns. A colour with parts such as .red, .green, .blue, .alpha, .hue, .saturation, .brightness and .lightness.
Raises.
ValueError: there are no values, or more than four, or the colour is not understood.
c = f.color("tomato")
print(c.red, c.hue)
See also: hsb, hsl, lerp_color, color_mode.
f.color_mode¶
f.color_mode(mode: 'str', max1: 'float | None' = None, max2: 'float | None' = None, max3: 'float | None' = None, max_alpha: 'float | None' = None) -> 'None'
Choose how numbers become a colour.
It affects colours given as numbers. Names, hex strings and colour objects are not affected. Each mode remembers its own ranges. The choice is saved by push() and brought back by pop().
| Argument | Meaning |
|---|---|
mode |
"rgb" (the default; ranges 255), "hsb" or "hsl" (ranges 360, 100, 100, and opacity 0 to 1). |
max1 |
with only this one, it sets the top of all four ranges. With max2 and max3 as well, it is the top of the first (red or hue). |
max2 |
the top of the second range (green or saturation). |
max3 |
the top of the third range (blue, brightness or lightness). |
max_alpha |
the top of the opacity range. Leave it out to keep the opacity range. |
Raises.
ValueError: the mode is unknown, a range is not a number above 0, or exactly two ranges are given.
f.color_mode("hsb", 360, 100, 100)
f.fill(200, 80, 90)
See also: color, hsb, hsl, fill.
f.hsb¶
f.hsb(hue: 'float', saturation: 'float', brightness: 'float', alpha: 'float' = 255)
Make a colour from hue, saturation and brightness.
The ranges are always these, whatever the colour mode is.
| Argument | Meaning |
|---|---|
hue |
the colour wheel position, 0 to 360. It wraps round. |
saturation |
how strong the colour is, 0 to 100. |
brightness |
how light it is, 0 to 100. |
alpha |
the opacity, 0 (invisible) to 255 (solid). The default is 255. |
Returns. A colour, as from color().
f.fill(f.hsb(200, 80, 90))
See also: hsl, color, color_mode.
f.hsl¶
f.hsl(hue: 'float', saturation: 'float', lightness: 'float', alpha: 'float' = 255)
Make a colour from hue, saturation and lightness.
The ranges are always these, whatever the colour mode is.
| Argument | Meaning |
|---|---|
hue |
the colour wheel position, 0 to 360. It wraps round. |
saturation |
how strong the colour is, 0 to 100. |
lightness |
how light it is, 0 to 100. |
alpha |
the opacity, 0 (invisible) to 255 (solid). The default is 255. |
Returns. A colour, as from color().
f.fill(f.hsl(30, 90, 60))
See also: hsb, color, color_mode.
f.lerp_color¶
f.lerp_color(c1: 'Color', c2: 'Color', amount: 'float')
Return a colour part of the way between two colours.
It mixes red, green, blue and alpha.
| Argument | Meaning |
|---|---|
c1 |
the first colour, in any form that fill() takes. |
c2 |
the second colour. |
amount |
from 0 to 1. 0 gives c1, 1 gives c2 and 0.5 is halfway. |
Returns. A colour, as from color().
f.fill(f.lerp_color("red", "blue", 0.5))
See also: color, lerp, linear_gradient.
f.linear_gradient¶
f.linear_gradient(x1: 'float', y1: 'float', x2: 'float', y2: 'float', colors, stops=None)
Make a gradient: colours blended along a line.
Use it like a colour in fill(), stroke() or background().
| Argument | Meaning |
|---|---|
x1, y1 |
where the line starts (the first colour). |
x2, y2 |
where the line ends (the last colour). |
colors |
a list of colours, in any form that fill() takes. |
stops |
where each colour sits along the line, from 0 to 1. The default None spaces them evenly. |
Returns. A gradient. Give it to fill(), stroke() or background().
f.fill(f.linear_gradient(0, 0, 200, 0, ["red", "blue"]))
f.rect(0, 0, 200, 100)
See also: radial_gradient, fill, lerp_color.
f.radial_gradient¶
f.radial_gradient(x: 'float', y: 'float', radius: 'float', colors, stops=None)
Make a gradient: colours blended outward from a point.
The first colour is at the centre. Use it like a colour in fill(), stroke() or background().
| Argument | Meaning |
|---|---|
x, y |
the centre. |
radius |
the distance where the last colour is reached, in pixels. |
colors |
a list of colours, in any form that fill() takes. |
stops |
where each colour sits, from 0 (centre) to 1 (the radius). The default None spaces them evenly. |
Returns. A gradient. Give it to fill(), stroke() or background().
f.background(f.radial_gradient(320, 200, 300, ["white", "navy"]))
See also: linear_gradient, fill.
Fill and stroke¶
f.fill¶
f.fill(color: 'Color', *more: 'float') -> 'None'
Set the colour that shapes are filled with.
It applies to the shapes you draw after the call. The start is white. The choice is saved by push() and brought back by pop().
| Argument | Meaning |
|---|---|
color |
a colour name such as "gold", a hex string such as "#F05A45", an (r, g, b) or (r, g, b, a) tuple, a colour from f.color(), or a gradient. One number is a grey. |
more |
extra numbers, so that fill(255, 99, 71) works like fill((255, 99, 71)). They follow the colour mode. A name, hex string or gradient takes no extra numbers. |
Raises.
ValueError: the colour is not understood, or numbers follow a name or gradient.
f.fill("gold")
f.fill(255, 99, 71, 128)
See also: no_fill, stroke, color_mode, linear_gradient.
f.no_fill¶
f.no_fill() -> 'None'
Do not fill shapes drawn after this.
Only their outlines are drawn. Use fill() to start filling again.
f.no_fill()
f.circle(100, 100, 50)
f.stroke¶
f.stroke(color: 'Color', *more: 'float') -> 'None'
Set the colour of outlines and lines.
It applies to what you draw after the call. The start is black. The choice is saved by push() and brought back by pop().
| Argument | Meaning |
|---|---|
color |
a colour name such as "black", a hex string, an (r, g, b) or (r, g, b, a) tuple, a colour from f.color(), or a gradient. One number is a grey. |
more |
extra numbers, so that stroke(0, 0, 255) works like stroke((0, 0, 255)). They follow the colour mode. A name, hex string or gradient takes no extra numbers. |
Raises.
ValueError: the colour is not understood, or numbers follow a name or gradient.
f.stroke("navy")
See also: no_stroke, stroke_width, fill.
f.no_stroke¶
f.no_stroke() -> 'None'
Do not draw outlines or lines after this.
Use stroke() to start again.
f.no_stroke()
f.circle(100, 100, 50)
f.stroke_width¶
f.stroke_width(pixels: 'int') -> 'None'
Set how thick outlines and lines are.
The stroke is centred on the edge of the shape: half of it lies outside and half inside. The start is 1. The choice is saved by push() and brought back by pop().
| Argument | Meaning |
|---|---|
pixels |
the thickness, in pixels. It must be 1 or more. Fractions such as 2.5 are kept. |
Raises.
ValueError: pixels is less than 1.
f.stroke_width(3)
See also: stroke, stroke_cap, stroke_join, stroke_dash.
f.stroke_cap¶
f.stroke_cap(cap: 'str') -> 'None'
Choose how the ends of lines look.
| Argument | Meaning |
|---|---|
cap |
"round" (the default), "square" (extends past the end by half the width) or "butt" (stops flat at the end). |
Raises.
ValueError: the cap is not one of those words.
f.stroke_cap("butt")
See also: stroke_join, stroke_width.
f.stroke_join¶
f.stroke_join(join: 'str') -> 'None'
Choose how corners of outlines look.
| Argument | Meaning |
|---|---|
join |
"round" (the default), "miter" (sharp) or "bevel" (cut off). |
Raises.
ValueError: the join is not one of those words.
f.stroke_join("miter")
See also: miter_limit, stroke_cap, stroke_width.
f.miter_limit¶
f.miter_limit(limit: 'float') -> 'None'
Set how far a sharp "miter" corner may stick out.
Past that limit the corner is cut off (bevelled). It matters only when stroke_join is "miter".
| Argument | Meaning |
|---|---|
limit |
the ratio of the point's length to the stroke width. The default is 10. It must be 1 or more. |
Raises.
ValueError: limit is less than 1.
f.miter_limit(4)
See also: stroke_join.
f.stroke_dash¶
f.stroke_dash(pattern: 'float | list[float]', offset: 'float' = 0) -> 'None'
Draw outlines and lines as dashes.
Use no_dash() for solid lines again.
| Argument | Meaning |
|---|---|
pattern |
one length, or a list of lengths that alternate dash, gap, dash, gap and so on. Lengths are in pixels, 0 or more, and not all 0. |
offset |
how far into the pattern to start, in pixels. The default is 0. |
Raises.
ValueError: the pattern is empty, has a negative length, or is all zeros.
f.stroke_dash([12, 4])
f.line(20, 50, 300, 50)
See also: no_dash, stroke_width.
f.no_dash¶
f.no_dash() -> 'None'
Draw solid outlines and lines again.
See also: stroke_dash.
f.shadow¶
f.shadow(x_offset: 'float', y_offset: 'float', blur: 'float' = 5, color: 'Color' = (0, 0, 0, 128)) -> 'None'
Give everything drawn after this a soft shadow.
Stop it with no_shadow(). The choice is saved by push() and brought back by pop().
| Argument | Meaning |
|---|---|
x_offset, y_offset |
how far the shadow moves from the shape, in pixels. |
blur |
how soft the edge is, in pixels, 0 or more. The default is 5. |
color |
the shadow colour. The default is black at half strength. |
Raises.
ValueError: blur is negative.
f.shadow(4, 6, blur=8)
f.no_shadow¶
f.no_shadow() -> 'None'
Stop drawing shadows.
See also: shadow.
f.opacity¶
f.opacity(amount: 'float') -> 'None'
Make everything drawn after this see-through.
The choice is saved by push() and brought back by pop().
| Argument | Meaning |
|---|---|
amount |
from 0 (invisible) to 255 (solid, the default). |
Raises.
ValueError: amount is not a number from 0 to 255.
f.opacity(128)
See also: blend_mode, fill, tint.
f.blend_mode¶
f.blend_mode(mode: 'str') -> 'None'
Choose how what you draw next mixes with what is already there.
The choice is saved by push() and brought back by pop().
| Argument | Meaning |
|---|---|
mode |
"normal" (the default), "multiply", "screen", "overlay", "darken", "lighten", "add", "difference", "exclusion", "dodge", "burn", "hard_light", "soft_light", "hue", "saturation", "color" or "luminosity". |
Raises.
ValueError: the mode is not one of those words.
f.blend_mode("multiply")
See also: opacity, tint, erase.
f.erase¶
f.erase(fill_strength: 'float' = 255, stroke_strength: 'float' = 255) -> 'None'
Make everything drawn after this remove what is under it, instead of painting.
Colours, gradients, tint, blend mode, opacity and shadow are ignored. Use it to cut holes in a picture from create_graphics(). Stop it with no_erase().
| Argument | Meaning |
|---|---|
fill_strength |
how much shape fills remove. 255 (the default) removes completely, making it see-through. 128 removes about half. 0 removes nothing. |
stroke_strength |
the same for outlines. |
Raises.
ValueError: a strength is not a number from 0 to 255.
f.erase()
f.circle(50, 50, 40)
f.no_erase()
See also: no_erase, clear, create_graphics.
f.no_erase¶
f.no_erase() -> 'None'
Go back to painting.
See also: erase.
f.tint¶
f.tint(color, *more: 'float') -> 'None'
Colour every picture drawn with image() from now on.
Red, green and blue of each pixel are multiplied by the tint's, and the alpha is multiplied in. Shapes and text are not tinted. The choice is saved by push() and brought back by pop().
| Argument | Meaning |
|---|---|
color |
any colour that fill() takes, except a gradient. One number is a grey. |
more |
extra numbers, as for fill(). tint(255, 128) draws pictures half see-through. |
f.tint("gold")
f.image(photo, 0, 0)
See also: no_tint, image, opacity.
f.no_tint¶
f.no_tint() -> 'None'
Stop tinting pictures.
See also: tint.
Text and fonts¶
f.text¶
f.text(message: 'object', x: 'float', y: 'float', color: 'Color | None' = None) -> 'None'
Draw text.
By default (x, y) is the top-left corner of the text; text_align() can change that. The text uses the fill colour. If there is no fill it uses the stroke colour, and if there is neither it is white. A new line in the message starts a new line of text. The default size is 20 (see text_size).
| Argument | Meaning |
|---|---|
message |
what to write. Any value works: str() is applied. A FormattedString gives each part its own look. |
x, y |
the position, in pixels. |
color |
a colour for this text only, in any form that fill() takes. The default None uses the fill. |
f.fill("black")
f.text("Hello", 30, 40)
f.text(f.frame_count, 30, 80, color="navy")
See also: text_size, text_align, text_width, text_box, text_font, FormattedString.
f.text_align¶
f.text_align(horizontal: 'str', vertical: 'str | None' = None) -> 'None'
Choose which point of the text (x, y) means.
The default is left and top. The choice is saved by push() and brought back by pop().
| Argument | Meaning |
|---|---|
horizontal |
"left" (the default), "center" or "right". |
vertical |
"top" (the default), "center", "baseline" or "bottom". Leave it out to keep the current choice. |
Raises.
ValueError: a word is not one of those.
f.text_align("center", "center")
f.text("Middle", f.width / 2, f.height / 2)
See also: text, text_width.
f.text_ascent¶
f.text_ascent() -> 'float'
Return how far letters reach above the baseline, in pixels.
It is measured at the current text size and font.
See also: text_descent, text_size.
f.text_descent¶
f.text_descent() -> 'float'
Return how far letters such as g and y reach below the baseline, in pixels.
It is measured at the current text size and font.
See also: text_ascent, text_size.
f.text_leading¶
f.text_leading(leading: 'float | None') -> 'None'
Set the distance from one line's baseline to the next.
It matters when a message has several lines, and in text_box().
| Argument | Meaning |
|---|---|
leading |
the distance in pixels, 0 or more. None (the default behaviour) means automatic: 1.25 times the text size. |
Raises.
ValueError: leading is negative.
f.text_leading(30)
See also: text_size, text, text_box.
f.text_box¶
f.text_box(message: 'object', x: 'float', y: 'float', width: 'float', height: 'float | None' = None, color: 'Color | None' = None) -> 'str | FormattedString'
Draw text wrapped inside a box, and return whatever did not fit.
Words are moved to a new line when they would pass the right edge. With a height, only the lines that fit are drawn. Give the returned text to another box to flow a story across columns.
| Argument | Meaning |
|---|---|
message |
the text, a string or a FormattedString. |
x, y |
the top-left corner of the box. |
width |
the width of the box, in pixels. It must be above 0. |
height |
the height of the box, in pixels, 0 or more. None (the default) means no limit. |
color |
a colour for this text only. The default None uses the fill, as text() does. |
Returns. The text that did not fit: a string, or a FormattedString if you gave one. It is "" when everything fitted.
Raises.
ValueError: the width is not above 0, or the height is negative.
rest = f.text_box("A long story that wraps inside a box.", 20, 60, 280, 100)
See also: text, text_width, text_leading, FormattedString.
f.text_path¶
f.text_path(message: 'object', x: 'float', y: 'float') -> 'PathBuilder'
Return the outlines of some text as a path.
It is set with the current font, style, size, alignment and leading, just like text(). The path has no colour, and it is not moved by the current transform until you draw it. You can cut it, outline it, fill it, or clip with it.
| Argument | Meaning |
|---|---|
message |
what to write. Any value works, or a FormattedString. |
x, y |
the position, as for text(). |
Returns. A path (like f.path()). Draw it with draw_path(), or use union(), intersection(), expand_stroke() and so on.
f.draw_path(f.text_path("Hi", 20, 20))
See also: text, text_to_points, draw_path, clip.
f.text_to_points¶
f.text_to_points(message: 'object', x: 'float', y: 'float', spacing: 'float' = 5) -> 'list[tuple[float, float]]'
Return points along the outlines of some text.
It draws nothing. Each outline starts at its first point, and curves are measured along the curve. An empty message gives an empty list.
| Argument | Meaning |
|---|---|
message |
what to write, as for text(). |
x, y |
the position, as for text(). |
spacing |
the distance between points, in pixels. The default is 5. It must be above 0. |
Returns. A list of (x, y) tuples.
Raises.
ValueError: spacing is 0 or less.TypeError: spacing is not a number.
for x, y in f.text_to_points("hi", 20, 20, 6):
f.circle(x, y, 3)
f.current_font¶
f.current_font() -> 'Font'
Return the font that text is set in now.
It works for the built-in font too.
Returns. A Font. Ask it font.family(), font.style(), font.variations() (axis tag to (minimum, default, maximum); empty if the font is not variable), font.features() (a sorted list of OpenType tags) and font.contains(text) (True when every character has a glyph).
print(f.current_font().contains("e"))
See also: text_font, load_font, system_font.
f.text_size¶
f.text_size(size: 'int') -> 'None'
Set the size of text.
The size is an em of that many pixels, as in CSS. Capital letters come out about three-quarters of it. The start is 20. The choice is saved by push() and brought back by pop().
| Argument | Meaning |
|---|---|
size |
the size in pixels, above 0. A fraction is cut to a whole number. |
Raises.
ValueError: size is 0 or less.
f.text_size(28)
See also: text, text_width, text_font, text_leading.
f.text_style¶
f.text_style(style: 'str') -> 'None'
Choose one of the four styles of the built-in font.
It is ignored once you have chosen a font with text_font(), but remembered for when you go back to the built-in one.
| Argument | Meaning |
|---|---|
style |
"normal", "bold", "italic" or "bold_italic". |
Raises.
ValueError: the style is not one of those words.
f.text_style("bold")
See also: text_font, text_size.
f.text_tracking¶
f.text_tracking(pixels: 'float') -> 'None'
Add space after every letter.
The space is added after the last letter too. It changes text_width(), alignment, text_box() and text_path(). The start is 0. The choice is saved by push() and brought back by pop().
| Argument | Meaning |
|---|---|
pixels |
the extra space in pixels. A negative number tightens the text. |
f.text_tracking(4)
See also: text_width, text_size.
f.text_fallback¶
f.text_fallback(*fonts) -> 'None'
Choose fonts to use for letters that the current font does not have.
They are tried before funground's built-in fallbacks (emoji, symbols and Devanagari). Emoji are drawn in one colour, the fill. Part of the drawing state, so push() and pop() keep it.
| Argument | Meaning |
|---|---|
fonts |
fonts from load_font() or system_font(). Give none to use only the built-in fallbacks. Give None on its own to turn fallback off, so a missing letter is an empty box. |
f.text_fallback(f.system_font("Arial"))
See also: system_font, load_font, text_font.
f.system_font¶
f.system_font(name: 'str')
Find a font installed on this computer.
A sketch that uses it can look different on another computer. Font collections (.ttc files) are read too.
| Argument | Meaning |
|---|---|
name |
the family name, such as "Arial" (capitals do not matter; the Regular face is used when there are several), or a family with a style, such as "Nirmala UI Bold". |
Returns. A Font, for text_font() or text_fallback().
Raises.
FileNotFoundError: no installed font has that name.
f.text_font(f.system_font("Arial"))
See also: load_font, text_font, text_fallback.
f.text_features¶
f.text_features(**features: 'bool') -> 'None'
Turn OpenType features of the font on or off.
Calls add to each other. With no arguments it goes back to the font's defaults.
| Argument | Meaning |
|---|---|
features |
each name is a four-letter feature tag, and each value is True or False. For example liga=False turns ligatures off. |
Raises.
TypeError: a value is not True or False.
f.text_features(liga=False)
See also: font_variations, current_font.
f.font_variations¶
f.font_variations(**axes: 'float') -> 'None'
Set the axes of a variable font.
An axis that the font does not have is ignored, so it is harmless with an ordinary font. With no arguments it goes back to the font's defaults.
| Argument | Meaning |
|---|---|
axes |
each name is an axis tag, and each value is a number. For example wght=700 is bold. |
Raises.
TypeError: a value is not a number.
f.font_variations(wght=700)
See also: text_features, current_font.
f.text_width¶
f.text_width(message: 'object') -> 'float'
Return how wide some text will be, in pixels.
It is measured at the current text size and font, and it is the exact distance text() advances. Use it to centre or right-align text.
| Argument | Meaning |
|---|---|
message |
what to measure. Any value works, or a FormattedString. |
Returns. The width in pixels.
msg = "Hello"
f.text(msg, (f.width - f.text_width(msg)) / 2, 100)
See also: text, text_size, text_align.
f.text_font¶
f.text_font(font, size: 'float | None' = None) -> 'None'
Choose the font for the text you draw after this.
The choice is saved by push() and brought back by pop().
| Argument | Meaning |
|---|---|
font |
a font from load_font() or system_font(), a path to a font file, or None for the built-in family. |
size |
a text size to set at the same time. The default None keeps the size. |
Raises.
TypeError: font is not a Font, a path or None.
f.text_font(f.load_font("fonts/MyFont.ttf"), 24)
f.text_font(None)
See also: load_font, system_font, text_size, text_style.
f.load_font¶
f.load_font(path: 'str', face: 'int | str' = 0)
Load a font file.
Pass the result to text_font(), text_fallback() and so on. A relative path is looked for next to your sketch file first, then in the current folder.
| Argument | Meaning |
|---|---|
path |
a .ttf or .otf font file, or a .ttc collection. |
face |
for a .ttc collection, which font to use: its number (0 is the first) or its style as a word, such as "Bold". The default is 0. |
Returns. A Font. Ask it font.family(), font.style(), font.variations(), font.features() and font.contains(text).
Raises.
FileNotFoundError: the file is not found in either place.
mono = f.load_font("fonts/DejaVuSansMono.ttf")
f.text_font(mono)
See also: text_font, system_font, current_font.
Transforms and the state stack¶
f.translate¶
f.translate(dx: 'float', dy: 'float') -> 'None'
Move the origin.
Everything drawn after this is shifted by (dx, dy). Transforms add up, and they apply to shapes, text, paths and clips. Order matters: translate then rotate turns about the new origin. The stack is reset at the start of every draw().
| Argument | Meaning |
|---|---|
dx |
how far to move to the right, in pixels. |
dy |
how far to move down, in pixels. |
f.translate(f.width / 2, f.height / 2)
See also: rotate, scale, push, pop.
f.rotate¶
f.rotate(degrees: 'float') -> 'None'
Turn later drawing about the current origin.
Degrees are used. 90 is a quarter turn clockwise on the screen. Each call adds to the turns before it.
| Argument | Meaning |
|---|---|
degrees |
the angle, in degrees. Use radians() or degrees() to convert if you work in radians. |
f.translate(200, 150)
f.rotate(45)
f.rect(-40, -25, 80, 50)
See also: translate, scale, radians, push.
f.scale¶
f.scale(sx: 'float', sy: 'float | None' = None) -> 'None'
Grow or shrink later drawing about the origin.
Each call multiplies the scale already in force.
| Argument | Meaning |
|---|---|
sx |
the factor sideways. 2 doubles, 0.5 halves. If sy is left out it is used both ways. |
sy |
the factor up and down. The default None means the same as sx. |
Raises.
ValueError: a factor is 0.
f.scale(2)
f.scale(2, 1)
See also: translate, rotate, push.
f.shear_x¶
f.shear_x(degrees: 'float') -> 'None'
Slant later drawing sideways.
Each point moves along x by tan(degrees) times its y.
| Argument | Meaning |
|---|---|
degrees |
the slant, in degrees. |
f.shear_x(20)
See also: shear_y, apply_matrix.
f.shear_y¶
f.shear_y(degrees: 'float') -> 'None'
Slant later drawing up or down.
Each point moves along y by tan(degrees) times its x.
| Argument | Meaning |
|---|---|
degrees |
the slant, in degrees. |
See also: shear_x, apply_matrix.
f.apply_matrix¶
f.apply_matrix(a: 'float', b: 'float', c: 'float', d: 'float', e: 'float', f: 'float') -> 'None'
Multiply a whole transform into later drawing.
A point (x, y) goes to x' = ax + cy + e and y' = bx + dy + f.
| Argument | Meaning |
|---|---|
a, b, c, d |
the four numbers that stretch, turn and slant. a and d are the sideways and up-and-down scale. |
e, f |
how far to move along x and y, in pixels. |
f.apply_matrix(1, 0, 0.5, 1, 0, 0)
See also: translate, rotate, scale, shear_x, reset_matrix.
f.reset_matrix¶
f.reset_matrix() -> 'None'
Forget every translate, rotate, scale and shear so far.
It lasts until the enclosing pop().
See also: translate, push, pop.
f.push¶
f.push() -> 'None'
Save the current transform and style.
pop() brings them back. The style includes fill, stroke, stroke width, text settings, modes and clip. If you forget a pop(), funground pops for you at the end of draw() and prints a warning.
f.push()
f.rotate(30)
f.rect(0, 0, 80, 40)
f.pop()
See also: pop, saved_state.
f.pop¶
f.pop() -> 'None'
Restore the transform and style saved by the last push().
A pop() with nothing to restore prints a warning and is ignored.
See also: push, saved_state.
f.saved_state¶
f.saved_state() -> 'AbstractContextManager[None]'
Return a with block that saves and restores the transform and style.
It does push() on entry and pop() on exit, even if the block raises an error.
Returns. A context manager. Use it as with f.saved_state():.
with f.saved_state():
f.rotate(30)
f.rect(0, 0, 80, 40)
Paths and clipping¶
f.path¶
f.path() -> 'PathBuilder'
Make a new, empty path that you can reuse.
Chain its methods, each of which returns the builder: move_to(x, y), line_to(x, y), curve_to(cx1, cy1, cx2, cy2, x, y), quad_to(cx, cy, x, y), close(), rect(), ellipse(), circle() and polygon(). Draw it with draw_path(), or use it with clip(). Only closed paths are filled.
Returns. A path builder. It also has union(), intersection(), difference(), xor(), expand_stroke(), bounds(), contains(), translate(), scale(), rotate() and copy().
tri = f.path().move_to(0, 0).line_to(40, 0).line_to(20, -30).close()
f.draw_path(tri)
See also: draw_path, clip, text_path, svg_paths.
f.draw_path¶
f.draw_path(path: 'PathBuilder') -> 'None'
Draw a path made with path().
It is filled (if it is closed) and then stroked, with the current style and transform.
| Argument | Meaning |
|---|---|
path |
the path to draw. |
f.draw_path(f.path().circle(50, 50, 40))
See also: path, clip, text_path.
f.clip¶
f.clip(path: 'PathBuilder') -> 'None'
Limit later drawing to the inside of a path.
The path is treated as closed and follows the current transform. The clip lasts until the enclosing pop() or the end of the saved_state() block, so put it inside one. background() ignores the clip.
| Argument | Meaning |
|---|---|
path |
a path made with path() or text_path(). |
with f.saved_state():
f.clip(f.path().circle(100, 100, 80))
f.background("gold")
See also: no_clip, path, saved_state.
f.no_clip¶
f.no_clip() -> 'None'
Remove clipping.
It lasts until the enclosing pop() or the end of the saved_state() block, which brings the previous clip back.
See also: clip, saved_state.
Marks¶
f.mark¶
f.mark(path: 'PathBuilder | str | None' = None, *, fill: 'Color | None' = <not given>, stroke: 'Color | None' = <not given>, stroke_width: 'float | None' = None) -> 'Mark'
Make a mark: a drawing kept as a value, to place as often as you like.
Use it with with: with f.mark() as m: records the drawing calls in the block instead of drawing them. Each part keeps the fill, stroke and font it was drawn with. The block starts with the current style and no transform, so (0, 0) is the mark's own origin. When it ends, the transform, style and clip are exactly as before, even after an error. Then m.place(x, y) draws the mark. Inside the block you cannot open a layer, make controls, save files, or read or change pixels.
Give a path instead to make a mark from it at once. It is filled (if closed) and stroked with the current style, changed by fill, stroke and stroke_width when you give them.
Give the name of an SVG file to make a mark of its shapes, each with the file's own colours. A name that is not a full path is looked for next to the sketch first, as load_svg() does. The mark's origin is the file's top-left corner.
A mark stays sharp at any size, and stays real shapes and text in a saved PDF or SVG. It cannot be changed once it is made.
| Argument | Meaning |
|---|---|
path |
a path made with f.path(), or the name of an .svg file. Leave it out to record a block. |
fill |
the fill for a path, as fill() takes it. None means no fill. Left out, it is the current fill. |
stroke |
the stroke for a path, as stroke() takes it. None means no stroke. Left out, it is the current stroke. |
stroke_width |
the stroke width for a path. Left out (None), it is the current width. |
Returns. A Mark, with place(), bounds(), width, height and is_empty.
Raises.
TypeError: path is not a path or a file name, or fill, stroke or stroke_width is given without a path.ValueError: the file name does not end in ".svg", or the file is not an SVG file.FileNotFoundError: the SVG file is not there.
leaf = f.mark(f.path().ellipse(0, 0, 40, 16), fill="olive", stroke=None)
badge = f.mark("badge.svg")
with f.mark() as flower:
f.fill("gold")
f.circle(0, 0, 24)
flower.place(100, 100)
See also: path, load_svg, saved_state, layer.
Play¶
f.variations¶
f.variations(fn, *, columns: 'int | None' = None, **values) -> 'list'
Draw several versions of a drawing side by side, as a labelled contact sheet.
Write the drawing as a function with a parameter, then give a list of values to try. variations() calls the function once for each value, and draws each result in its own cell over f.ground.content, with a thin frame and a label such as "gap = 35".
With one parameter the versions go in one row while every cell stays readable: each picture at least 100 units wide, with its label fitting on one line under it. When they do not fit, they wrap into the grid that makes the cells largest (near-square on a square canvas). columns= chooses the number of columns yourself. With two parameters it tries every pair: one row for each value of the first, one column for each value of the second, so columns= is not allowed.
Each version is drawn as if on the whole canvas, then made smaller to fit its cell. Every cell is made smaller by the same amount and keeps the canvas's coordinates, so a change of size or position shows. Each cell shows only the canvas's rectangle: drawing outside the canvas is cut off in the sheet, but kept in the returned mark. Each version starts from the style you have when you call variations() and no transform, so one version's fill() cannot change the next. Each version also starts from the same random seed, so the versions differ only in the parameter. background() in the function paints only that version's cell; in the returned mark it is a rectangle the size of the canvas.
It works in a script, in setup(), and in draw(), where it draws the sheet again every frame.
| Argument | Meaning |
|---|---|
fn |
the function that draws one version. It is called with the parameters by name, such as fn(gap=35). |
columns |
with one parameter, how many columns the sheet has, a whole number above 0. The default None chooses: one row while it stays readable, otherwise a grid. A parameter of your function cannot be called columns. |
values |
one or two parameters, each with a list of values to try, such as gap=[10, 20, 35]. |
Returns. A list with one (values, mark) pair for each version, in order. values is a dictionary such as {"gap": 35}; mark is that version as a Mark, without its frame or label, so chosen.place(0, 0) draws it full size.
Raises.
TypeError: fn cannot be called, a parameter is given one value instead of a list, or columns is not a whole number.ValueError: there is no parameter, more than two, a list is empty, columns is 0 or less or given with two parameters, or the cells do not fit in the canvas.
def study(gap):
for i in range(12):
f.circle(f.ground.content.left + i * gap, f.ground.content.cy, 20)
f.variations(study, gap=[10, 20, 35, 60])
See also: keep, mark, random_seed.
f.keep¶
f.keep(note: 'str' = '', *, pdf: 'bool' = False, **settings) -> 'str'
Save this version of your picture in a studio folder, with what made it.
The files go in a folder called studio next to your sketch file (or in the current folder when there is no file). They are numbered in order, after any already there: 001.png, 002.png and so on. Each kept version has a picture (.png), a copy of your sketch (.py), and a record (.json). The record holds your note, the settings you give, every control's value, the random and noise seeds, the size and margin, the page or frame, the date, the versions of funground and Python, the fonts used and the files read. It also lists what it could not keep, such as fonts installed on this computer. It prints one line saying where it saved. Grid guides from show() are not in the picture.
In a script it keeps the canvas as drawn so far, at once. In an animated sketch it keeps the next frame that is drawn: the frame being drawn when you call it from draw(), or the next one when you call it from key_pressed() or another event. The picture, the copy and the record are all written when that frame is complete, so the frame number, the controls, the seeds and the fonts in the record are that frame's. If no frame is drawn (after no_loop()), it keeps the frame on the screen.
The seeds make funground's random() and noise() repeatable: f.random_seed(n) and f.noise_seed(n) with the recorded numbers give the same values again. A recorded seed makes funground's randomness repeatable; it does not make every sketch reproducible. The record's lists of fonts, files read and what it could not keep say what else the picture depends on.
| Argument | Meaning |
|---|---|
note |
a few words about this version, such as "gap 35 reads as a rhythm". |
pdf |
True also saves a .pdf, a vector drawing that stays sharp when printed. The default is False. |
settings |
any values you want to remember with it, such as gap=35. |
Returns. The path of the files without their ending, such as "studio/007". In an animated sketch the files appear there when the frame is complete.
Raises.
RuntimeError: it is used inside awith f.mark()block, or before f.size().TypeError: the note is not text.
f.keep("gap 35 reads as a rhythm", gap=35)
f.keep("for printing", pdf=True)
See also: variations, save, random_seed.
Pictures and layers¶
f.create_graphics¶
f.create_graphics(width: 'int', height: 'int') -> 'Picture'
Make a picture: an off-screen canvas.
It starts transparent. It has the same drawing commands as f (fill, circle, text, push, pop, transforms, clip and more) but its own state, transform and pixels. Nothing about it resets between frames. Draw it on the canvas with image().
| Argument | Meaning |
|---|---|
width, height |
the size in pixels. |
Returns. A Picture, with width and height, the drawing commands, get(), set(), copy(), resize(w, h), mask(other), filter(), load_pixels(), pixels, update_pixels() and save(path).
g = f.create_graphics(200, 100)
g.fill("tomato")
g.circle(100, 50, 80)
f.image(g, 20, 20)
See also: image, layer, load_image.
f.layer¶
f.layer(name: 'str') -> 'Picture'
Return the layer with a given name, and make it if it is new.
A layer is a see-through picture the size of the canvas. Use it with with: everything drawn inside the block goes to the layer. Layers are put over the canvas in the order they were first made. A layer keeps its drawing from frame to frame until you call background() or clear() inside it. Inside the block, f.width, f.mouse_x, random() and the like keep their canvas meaning. Layers do not nest. In a saved PDF or SVG each layer is a real, named layer.
| Argument | Meaning |
|---|---|
name |
a name in quotes. It must not be empty. |
Returns. A Picture, so with f.layer("sky") as sky: gives you sky.get(), sky.filter() and sky.save().
Raises.
RuntimeError: a layer block is already open.ValueError: the name is empty.
with f.layer("sky"):
f.background("skyblue")
See also: hide_layer, show_layer, create_graphics.
f.hide_layer¶
f.hide_layer(name: 'str') -> 'None'
Stop showing a layer.
It keeps its drawing, and show_layer() brings it back. A hidden layer is left out of the window, a PNG and get(). It is kept in a PDF or SVG, switched off.
| Argument | Meaning |
|---|---|
name |
the layer's name. |
Raises.
ValueError: there is no layer with that name.
See also: show_layer, layer.
f.show_layer¶
f.show_layer(name: 'str') -> 'None'
Show a layer that hide_layer() hid.
| Argument | Meaning |
|---|---|
name |
the layer's name. |
Raises.
ValueError: there is no layer with that name.
See also: hide_layer, layer.
Images and SVG¶
f.image¶
f.image(picture, x: 'float', y: 'float', width: 'float | None' = None, height: 'float | None' = None, sx: 'float | None' = None, sy: 'float | None' = None, sw: 'float | None' = None, sh: 'float | None' = None) -> 'None'
Draw a picture.
It is drawn as it is at this moment: later drawing on the picture does not change what was placed. image_mode() changes how x, y, width and height are read. tint() colours it.
| Argument | Meaning |
|---|---|
picture |
a Picture, from load_image(), load_svg(), create_graphics(), get() or spectrogram(). |
x, y |
where to put it, by default the top-left corner. |
width, height |
the size to stretch it to. The default None uses the picture's own size. |
sx, sy, sw, sh |
give all four to draw only that part of the picture (in its own pixels) into the box. A part that reaches outside the picture is clipped and keeps its place. sw and sh must be above 0. |
Raises.
ValueError: only some of sx, sy, sw and sh were given, or sw or sh is not above 0.
f.image(photo, 0, 0)
f.image(photo, 0, 0, 200, 150, 40, 30, 100, 75)
See also: load_image, create_graphics, image_mode, tint.
f.image_mode¶
f.image_mode(mode: 'str') -> 'None'
Choose how image() reads its numbers.
The choice is saved by push() and brought back by pop().
| Argument | Meaning |
|---|---|
mode |
"corner" (the default: x, y is the top-left corner), "center" (x, y is the middle) or "corners" (x, y and the opposite corner; it needs a width and height). |
Raises.
ValueError: the mode is not one of those words.
f.image_mode("center")
f.load_image¶
f.load_image(path: 'str') -> 'Picture'
Read an image file and return it as a picture.
Draw it with image(). A relative path is looked for next to your sketch file first, then in the current folder. Photos from phones are turned the right way up. Transparency is kept. You can draw on the picture like any other.
| Argument | Meaning |
|---|---|
path |
the file name: PNG, JPEG, GIF, BMP, TGA and others. |
Returns. A Picture, with width and height in pixels.
Raises.
FileNotFoundError: the file is not found in either place.ValueError: the file is not an image.
photo = f.load_image("photo.jpg")
f.image(photo, 0, 0)
See also: image, load_svg, create_graphics.
f.load_svg¶
f.load_svg(path: 'str') -> 'Picture'
Read an SVG file and return a picture of its shapes.
It stays sharp at any size, and stays vector in a saved PDF or SVG. The size is the file's width and height (96 to the inch), or its viewBox. It reads paths, rect, circle, ellipse, line, polyline, polygon, groups, use, transforms, fill and stroke colours, stroke width, caps, joins, dashes and fill-rule. A gradient or pattern uses its first colour. Text, images, filters, masks and animation are ignored. A relative path is looked for next to your sketch file first, then in the current folder.
| Argument | Meaning |
|---|---|
path |
the SVG file name. |
Returns. A Picture. Draw it with image().
Raises.
FileNotFoundError: the file is not found.ValueError: the file is not an SVG.
badge = f.load_svg("badge.svg")
f.image(badge, 20, 20)
See also: svg_paths, load_image, image.
f.svg_paths¶
f.svg_paths(path: 'str') -> 'list[PathBuilder]'
Read an SVG file and return its shapes as paths.
There is one path for each shape, in the file's own coordinates. Use them for booleans, clips or your own colours.
| Argument | Meaning |
|---|---|
path |
the SVG file name. A relative path is looked for next to your sketch file first, then in the current folder. |
Returns. A list of path builders, as from path().
Raises.
FileNotFoundError: the file is not found.ValueError: the file is not an SVG.
shapes = f.svg_paths("badge.svg")
f.draw_path(shapes[0])
See also: load_svg, path, draw_path.
Pixels and filters¶
f.get¶
f.get(x: 'float', y: 'float', w: 'float | None' = None, h: 'float | None' = None)
Read a pixel, or copy a region of the canvas.
It sees what has been drawn so far, this frame included. Outside the canvas is transparent. Coordinates are rounded down. On a high-resolution screen it reads the top-left real pixel of a logical one.
| Argument | Meaning |
|---|---|
x, y |
the pixel, or the top-left corner of the region. |
w, h |
the size of the region. Give both to copy a region. Leave them out (None) to read one pixel. |
Returns. With no w and h: a colour with .red, .green, .blue and .alpha. With w and h: a new Picture copied from that region.
c = f.get(10, 20)
part = f.get(0, 0, 50, 50)
See also: set, load_pixels, image.
f.set¶
f.set(x: 'float', y: 'float', color, *more: 'float') -> 'None'
Make one pixel exactly one colour.
Fill, stroke, transform, clip, tint, opacity and blend mode do not apply. Outside the canvas, nothing happens.
| Argument | Meaning |
|---|---|
x, y |
the pixel. |
color |
any colour that fill() takes. |
more |
extra numbers, as for fill(). |
f.set(10, 20, "red")
See also: get, update_pixels.
f.load_pixels¶
f.load_pixels() -> 'None'
Copy the canvas into f.pixels.
f.pixels is then a bytearray with red, green, blue and alpha (0 to 255, not premultiplied) for every pixel, row by row from the top left. The pixel at (x, y) starts at index (y * f.width + x) * 4. Change the numbers in place, then call update_pixels(). A Python loop over a whole 640 by 400 canvas takes seconds.
f.load_pixels()
f.pixels[(10 * f.width + 20) * 4] = 255
f.update_pixels()
See also: update_pixels, get, set.
f.update_pixels¶
f.update_pixels() -> 'None'
Write f.pixels back onto the canvas.
It works like a set() of every pixel.
Raises.
RuntimeError: load_pixels() was not called first.
See also: load_pixels, set.
f.filter¶
f.filter(kind: 'str', value: 'float | None' = None) -> 'None'
Change everything drawn so far, in place.
Alpha is kept, except by "opaque". "posterize", "erode" and "dilate" are faster with funground[extras] installed, but the result is the same.
| Argument | Meaning |
|---|---|
kind |
"threshold", "gray", "opaque", "invert", "blur", "posterize", "erode" or "dilate". |
value |
for "threshold", a level from 0 to 1 (default 0.5). For "blur", a radius in pixels (default 1). For "posterize", the number of levels, 2 to 255 (required). The others take none. |
Raises.
ValueError: the kind is unknown, or the value is out of range.
f.filter("blur", 3)
See also: get, create_graphics.
Randomness and noise¶
f.random¶
f.random(low: 'float' = 1.0, high: 'float | None' = None) -> 'float'
Return a random number.
random(10) gives a number from 0 up to 10. random(5, 10) gives a number from 5 up to 10. random() gives a number from 0 up to 1. It uses its own generator and never touches Python's own random module.
| Argument | Meaning |
|---|---|
low |
with one argument, the top of the range (it starts at 0). With high as well, the bottom. |
high |
the top of the range. The default None means low is the top. |
Returns. A floating-point number.
x = f.random(f.width)
d = f.random(10, 40)
See also: random_seed, random_choice, random_gaussian, noise.
f.random_choice¶
f.random_choice(items)
Return one item picked at random.
It can be repeated with random_seed().
| Argument | Meaning |
|---|---|
items |
a list, a tuple or a string. It must not be empty. |
Returns. One of the items. For a string, one letter.
Raises.
ValueError: there are no items.
f.fill(f.random_choice(["red", "gold", "skyblue"]))
See also: random, random_seed.
f.random_gaussian¶
f.random_gaussian(mean: 'float' = 0.0, sd: 'float' = 1.0) -> 'float'
Return a random number from a bell curve.
Most values are near the mean. About two thirds are within one sd of it.
| Argument | Meaning |
|---|---|
mean |
the middle of the bell. The default is 0.0. |
sd |
the spread. The default is 1.0. It cannot be negative. |
Returns. A floating-point number.
Raises.
ValueError: sd is negative.
f.random_gaussian(200, 30)
See also: random, random_seed.
f.random_seed¶
f.random_seed(seed: 'int | None' = None) -> 'None'
Make random() repeatable.
The same seed gives the same sequence. It also covers random_gaussian(), random_choice() and the random parts of sounds. Any whole number works, however large or negative, and f.keep() records it exactly as you gave it.
When you do not call random_seed(), funground picks a seed at the start of the run (a number below 1,000,000) and f.keep() records it, so f.random_seed(that number) gives the same random numbers again. A recorded seed makes funground's randomness repeatable; it does not make every sketch reproducible, because a sketch can also depend on the mouse, the clock, files or Python's own random module.
| Argument | Meaning |
|---|---|
seed |
a whole number. The default None starts from a different, unpredictable place each time, and records where. |
f.random_seed(7)
See also: random, noise_seed.
f.noise¶
f.noise(x: 'float', y: 'float' = 0.0, z: 'float' = 0.0) -> 'float'
Return a smooth random number from 0 to 1.
Nearby inputs give nearby outputs, so it makes gentle wandering. Take small steps through the input, such as x * 0.01. noise_detail() changes how bumpy it is.
| Argument | Meaning |
|---|---|
x |
the place along a line. |
y |
a second direction. The default is 0.0. |
z |
a third direction, often used for time. The default is 0.0. |
Returns. A number from 0 to 1.
y = f.height * f.noise(f.frame_count * 0.01)
See also: noise_seed, noise_detail, random.
f.noise_detail¶
f.noise_detail(octaves: 'int', falloff: 'float | None' = None) -> 'None'
Choose how much detail noise() has.
| Argument | Meaning |
|---|---|
octaves |
how many layers of detail to add. The default is 4. It must be 1 or more. Fewer is smoother and faster. |
falloff |
how much each layer fades, above 0 and below 1. The default None keeps the current one (0.5 at the start). |
Raises.
ValueError: octaves is less than 1, or falloff is not between 0 and 1.
f.noise_detail(2)
See also: noise, noise_seed.
f.noise_seed¶
f.noise_seed(seed: 'int') -> 'None'
Make noise() repeatable.
The same seed gives the same values as p5.js's noiseSeed. Any whole number works, and f.keep() records it exactly as you gave it. Without noise_seed(), funground picks one the first time noise() is used, and f.keep() records that.
| Argument | Meaning |
|---|---|
seed |
a whole number. |
f.noise_seed(1)
See also: noise, random_seed.
Maths¶
f.constrain¶
f.constrain(value: 'float', low: 'float', high: 'float') -> 'float'
Keep a number inside a minimum and a maximum.
| Argument | Meaning |
|---|---|
value |
the number to limit. |
low |
the smallest answer. |
high |
the largest answer. |
Returns. low if value is below it, high if value is above it, otherwise value.
x = f.constrain(x, 20, f.width - 20)
f.distance¶
f.distance(x1: 'float', y1: 'float', x2: 'float', y2: 'float') -> 'float'
Return the straight-line distance between two points.
| Argument | Meaning |
|---|---|
x1, y1 |
the first point. |
x2, y2 |
the second point. |
Returns. The distance, in the same units as the points.
d = f.distance(x, y, f.mouse_x, f.mouse_y)
f.lerp¶
f.lerp(start: 'float', stop: 'float', amount: 'float') -> 'float'
Return the number that is part of the way from start to stop.
| Argument | Meaning |
|---|---|
start |
the first number. |
stop |
the second number. |
amount |
how far to go. 0 gives start, 1 gives stop, and 0.5 is halfway. It may go beyond 0 and 1. |
Returns. start + (stop - start) * amount.
print(f.lerp(0, 100, 0.25))
See also: norm, map_range, lerp_color.
f.map_range¶
f.map_range(value: 'float', start1: 'float', stop1: 'float', start2: 'float', stop2: 'float', clamp: 'bool' = False) -> 'float'
Re-scale a number from one range to another.
| Argument | Meaning |
|---|---|
value |
the number to convert. |
start1, stop1 |
the range it is in now. |
start2, stop2 |
the range to convert it to. |
clamp |
True keeps the answer inside the second range. The default False lets it go beyond. |
Returns. The converted number.
Raises.
ValueError: start1 and stop1 are the same.
grey = f.map_range(f.mouse_x, 0, f.width, 0, 255)
See also: lerp, norm, constrain.
f.mag¶
f.mag(x: 'float', y: 'float') -> 'float'
Return the length of the arrow (x, y).
That is the distance from (0, 0).
| Argument | Meaning |
|---|---|
x, y |
the sideways and up-and-down parts. |
Returns. The length. mag(3, 4) is 5.
print(f.mag(3, 4))
See also: distance.
f.norm¶
f.norm(value: 'float', start: 'float', stop: 'float') -> 'float'
Return where a number sits between two others, as 0 to 1.
| Argument | Meaning |
|---|---|
value |
the number to place. |
start |
the number that gives 0. |
stop |
the number that gives 1. |
Returns. (value - start) / (stop - start).
Raises.
ValueError: start and stop are the same.
print(f.norm(25, 0, 100))
f.degrees¶
f.degrees(radians: 'float') -> 'float'
Convert radians to degrees.
Use it, for example, to give rotate() an angle from math.atan2.
| Argument | Meaning |
|---|---|
radians |
an angle in radians. |
Returns. The angle in degrees.
import math
f.rotate(f.degrees(math.atan2(1, 1)))
f.radians¶
f.radians(degrees: 'float') -> 'float'
Convert degrees to radians.
Use it with math.sin and math.cos. rotate() itself takes degrees.
| Argument | Meaning |
|---|---|
degrees |
an angle in degrees. |
Returns. The angle in radians.
import math
y = math.sin(f.radians(45))
Time¶
f.year¶
f.year() -> 'int'
Return the year, for example 2026.
f.month¶
f.month() -> 'int'
Return the month, from 1 to 12.
f.day¶
f.day() -> 'int'
Return the day of the month, from 1 to 31.
f.hour¶
f.hour() -> 'int'
Return the clock's hour, from 0 to 23.
print(f.hour(), f.minute(), f.second())
f.minute¶
f.minute() -> 'int'
Return the clock's minutes, from 0 to 59.
f.second¶
f.second() -> 'int'
Return the clock's seconds, from 0 to 59.
f.millis¶
f.millis() -> 'int'
Return the milliseconds since the sketch started running.
Returns. A whole number of thousandths of a second.
seconds = f.millis() / 1000
See also: frame_rate.
Interaction and controls¶
f.key_down¶
f.key_down(key: 'str | int') -> 'bool'
Return whether a key is held down right now.
Escape also closes the sketch. With no window, no key is ever down.
| Argument | Meaning |
|---|---|
key |
"left", "right", "up", "down", "space", "enter", "escape", or a single character such as "a" or "7". Capitals do not matter. |
Returns. True while the key is held.
Raises.
ValueError: the name is not one of those.
if f.key_down("left"):
x -= 3
See also: is_key_pressed, key, key_code.
f.cursor¶
f.cursor(kind: 'str' = 'arrow') -> 'None'
Choose the shape of the mouse pointer over the canvas.
| Argument | Meaning |
|---|---|
kind |
"arrow" (the default), "cross", "hand", "move", "text" or "wait". |
Raises.
ValueError: the kind is not one of those names.
f.cursor("hand")
See also: no_cursor.
f.no_cursor¶
f.no_cursor() -> 'None'
Hide the mouse pointer over the canvas.
Use cursor() to show it again.
See also: cursor.
f.create_button¶
f.create_button(label: 'str') -> 'Button'
Make a button in the panel below the canvas.
Make it in setup(), and read it in draw().
| Argument | Meaning |
|---|---|
label |
the text on the button. |
Returns. A Button. button.clicked() is True once for each click since it was last asked.
reset = f.create_button("reset")
if reset.clicked():
f.background("white")
See also: create_slider, create_checkbox.
f.create_checkbox¶
f.create_checkbox(label: 'str', checked: 'bool' = False) -> 'Checkbox'
Make a tick box in the panel below the canvas.
Make it in setup(), and read it in draw().
| Argument | Meaning |
|---|---|
label |
the text beside the box. |
checked |
whether it starts ticked. The default is False. |
Returns. A Checkbox. box.checked() reads it. box.checked(True) sets it.
grid = f.create_checkbox("grid")
if grid.checked():
f.line(0, 0, f.width, f.height)
See also: create_slider, create_button.
f.create_slider¶
f.create_slider(low: 'float', high: 'float', value: 'float | None' = None, step: 'float | None' = None, label: 'str | None' = None) -> 'Slider'
Make a slider in a panel below the canvas.
Make it in setup() (or at the top of the file) and keep it in a variable. Read it in draw(). The panel is not part of the picture: it is not in f.width or f.height, saves, or get(). Making a control inside draw(), or in a script, is a RuntimeError. Without a window the slider keeps its value.
| Argument | Meaning |
|---|---|
low, high |
the smallest and largest value. low must be below high. |
value |
where the slider starts. The default None starts at low. |
step |
the size of each move. The default None moves smoothly. It must be above 0. |
label |
the text beside the slider. The default None has no label. |
Returns. A Slider. slider.value() reads the value. slider.value(v) sets it.
Raises.
ValueError: low is not below high, or step is 0 or less.
size = f.create_slider(10, 100, 40, step=5, label="size")
f.circle(200, 150, size.value())
See also: create_checkbox, create_button.
Saving¶
f.save¶
f.save(path: 'str', *, text: 'str' = 'live') -> 'None'
Save this frame to a picture or document file.
The file is written when the frame is complete, and it holds everything that frame drew, including what comes after the call. In a script, it writes at once. A .png holds pixels. A .pdf or .svg holds a true vector drawing. A .gif or .mp4 records an animation (see save_gif and save_movie). Any other extension is an error that names the choices. In a document of several pages, a PDF holds every page.
A PNG is written at the screen's real resolution, so on a 2x display a 640 by 400 window gives a 1280 by 800 file.
| Argument | Meaning |
|---|---|
path |
the file name. Its ending decides the kind: ".png", ".pdf" or ".svg". |
text |
for a PDF or SVG only. "live" (the default) keeps text real and editable. "shapes" draws every letter as a shape, so the file looks right without the font. A PNG ignores it. |
Raises.
ValueError: the file ending is not one that save() knows, or text is not "live" or "shapes".
if f.key_down("s"):
f.save("my_sketch.png")
See also: save_frames, save_gif, save_movie, new_page.
f.save_frames¶
f.save_frames(pattern: 'str', count: 'int') -> 'None'
Save this frame and the next ones as numbered picture files.
| Argument | Meaning |
|---|---|
pattern |
the file name with a run of # signs, which becomes the frame number. "frames/####.png" gives frames/0001.png, frames/0002.png and so on. |
count |
how many frames to save in all, this one included. |
f.save_frames("frames/####.png", 60)
See also: save, save_gif, save_movie.
f.save_gif¶
f.save_gif(path: 'str', seconds: 'float') -> 'None'
Record the next few seconds of an animated sketch into a GIF.
The GIF loops for ever. Each frame lasts 1 divided by the frame rate. It needs Pillow (install funground[extras]) or ffmpeg. It is an error to call it again while recording, or in a script (use frame_duration() and save() for a script).
| Argument | Meaning |
|---|---|
path |
the file name. It must end in ".gif". |
seconds |
how long to record, starting from the next frame. |
f.save_gif("spin.gif", 2)
See also: save_movie, save_frames, frame_duration.
f.save_movie¶
f.save_movie(path: 'str', seconds: 'float') -> 'None'
Record the next few seconds of an animated sketch into an MP4 movie.
It needs ffmpeg on your PATH. A canvas with an odd width or height is padded by one pixel.
| Argument | Meaning |
|---|---|
path |
the file name. It must end in ".mp4". |
seconds |
how long to record, starting from the next frame. |
f.save_movie("spin.mp4", 2)
See also: save_gif, save_frames, frame_duration.
Ground¶
f.area¶
f.area(x: 'float', y: 'float', w: 'float', h: 'float') -> 'Area'
Make an area: a rectangle kept as a value.
An area has left, top, right, bottom, width, height, cx and cy (its centre). inset() gives a smaller area inside it, and grid() divides it into cells. f.ground, f.ground.content and every grid cell are areas too. An area may be 0 wide or 0 high, but not less. It draws nothing by itself.
| Argument | Meaning |
|---|---|
x |
the left edge. |
y |
the top edge. |
w |
the width, 0 or more. |
h |
the height, 0 or more. |
Returns. A new Area.
Raises.
TypeError: a value is not a number.ValueError: w or h is negative, or a value is not finite.
panel = f.area(40, 40, 320, 200)
f.rect(panel.left, panel.top, panel.width, panel.height)
for cell in panel.inset(10).grid(4, 2, gutter=6):
f.circle(cell.cx, cell.cy, 20)
f.grid¶
f.grid(cols: 'int', rows: 'int', *, gutter: 'float | tuple' = 0, area: 'object' = None) -> 'Grid'
Divide an area into a grid of equal cells.
The cells cover f.ground.content (the canvas inside the margins), or the area you give. The grid works like a list of its cells, row by row, left to right: a for loop goes through them, len() counts them, g[0] is the top-left cell, g[-1] the last, and g[1:3] gives a list. Each cell is an area (left, top, right, bottom, width, height, cx, cy) with col, row and index, all counted from 0, so a cell can be the area of another grid.
The grid also has g.cell(col, row), g.span(col, row, cols, rows) for one area over several cells and the gutters between them, g.column_count and g.row_count, g.columns and g.rows as lists of areas, and g.show() to draw guide lines in the window.
| Argument | Meaning |
|---|---|
cols |
how many columns. A whole number above 0. |
rows |
how many rows. A whole number above 0. |
gutter |
the gap between cells. One number is used both across and down. A tuple (column_gutter, row_gutter) sets them apart. The default is 0. |
area |
what to divide: an area such as f.ground, a cell or f.area(...), or any object with left, top, width and height. The default None is f.ground.content. |
Returns. A Grid of cols times rows cells.
Raises.
TypeError: cols or rows is not a whole number, a gutter is not a number, or area has no edges.ValueError: cols or rows is 0 or less, a gutter is negative, or the gutters leave no room for the cells.
g = f.grid(3, 2, gutter=10)
for cell in g:
f.circle(cell.cx, cell.cy, cell.width * 0.8)
g.show()
See also: area, ground, size, mm.
f.mm¶
f.mm(n: 'float') -> 'float'
Convert millimetres to funground units.
One unit is one point, as in a PDF: 72 to the inch, so 1 mm is about 2.83 units. It is a plain conversion. It does not change the canvas.
| Argument | Meaning |
|---|---|
n |
a length in millimetres. |
Returns. The length in funground units, as a float.
f.size("A4", margin=f.mm(15))
f.inch¶
f.inch(n: 'float') -> 'float'
Convert inches to funground units.
One unit is one point, as in a PDF: 72 to the inch. It is a plain conversion. It does not change the canvas.
| Argument | Meaning |
|---|---|
n |
a length in inches. |
Returns. The length in funground units, as a float. inch(1) is 72.0.
f.size(f.inch(6), f.inch(4))
f.ground¶
f.ground
The canvas as an area, with its margins.
f.ground is the whole canvas and f.ground.content is the part inside the margins. Both are areas, so they have left, top, right, bottom, width, height, cx and cy, and inset() and grid(). f.ground is made fresh each time you read it, so it always matches the canvas. You do not make a Ground yourself: set the margins with f.size(..., margin=...).
f.size("A5", margin=f.mm(12))
f.background("linen")
c = f.ground.content
f.rect(c.left, c.top, c.width, c.height)
Motion and pages¶
f.frame_duration¶
f.frame_duration(seconds: 'float') -> 'None'
Set how long each page is shown in a saved GIF or MP4.
This is for scripts. It applies to this page and the pages after it, until you call it again. The start is 0.1 seconds. Then save("x.gif") or save("x.mp4") writes every page as a frame, all of the same size.
| Argument | Meaning |
|---|---|
seconds |
how long to show the page. It must be above 0. |
Raises.
ValueError: seconds is 0 or less.
f.frame_duration(0.2)
See also: new_page, save, save_gif.
f.new_page¶
f.new_page(width: 'int | str | None' = None, height: 'int | None' = None) -> 'None'
End this page and start a blank one.
This is for scripts (a file with no draw()). Colours, text settings and other styles carry over. The transform starts afresh. After a few pages, save("x.pdf") writes every page. PNG and SVG write x_1.png, x_2.png and so on. In a window, the arrow keys turn the pages.
| Argument | Meaning |
|---|---|
width |
a page width in points, or a page name such as "A4" (see page_size). Leave it out to keep the size. |
height |
the page height in points. Leave it out when width is a name or None. |
Raises.
RuntimeError: an animated sketch has one canvas, so it cannot start a new page.ValueError: a page name was given together with a height, or the name is unknown.
f.new_page("A5")
See also: page_count, page_size, save.
f.page_size¶
f.page_size(name: 'str', landscape: 'bool' = False) -> 'tuple[int, int]'
Return the size of a named page, in points.
Add "Landscape" to the name, or give landscape=True, to turn the page on its side.
| Argument | Meaning |
|---|---|
name |
"A3", "A4", "A5", "B5", "Letter", "Legal", "Tabloid" or "Square". Capitals do not matter. "A4Landscape" is also allowed. |
landscape |
True turns the page on its side. The default False keeps it upright. |
Returns. A (width, height) tuple of whole numbers. page_size("A4") is (595, 842).
Raises.
ValueError: the name is not one of the page names.
f.size(*f.page_size("A4"))
f.page_count¶
f.page_count() -> 'int'
Return how many pages the document has so far.
Returns. A whole number, 1 or more.
print(f.page_count())
See also: new_page.
Sound¶
f.load_sound¶
f.load_sound(path: 'str')
Read a sound file and return a sound.
A relative path is looked for next to your sketch file first, then in the current folder. With no sound device (or FUNGROUND_HEADLESS=1) it plays silently and keeps time.
| Argument | Meaning |
|---|---|
path |
a WAV, OGG or MP3 file. |
Returns. A Sound: play(), loop(), stop(), pause(), set_volume(v), is_playing(), duration(), current_time(), level(), spectrum(bands), pitch(), pan(p), reverb(amount), samples() and save(path).
Raises.
FileNotFoundError: the file is not found in either place.ValueError: the file is not sound.
beep = f.load_sound("beep.wav")
beep.play()
See also: create_sound, tone, note, draw_wave.
f.create_sound¶
f.create_sound(samples, rate: 'int' = 44100)
Make a sound from a list of numbers.
It is an ordinary sound. sound.samples() gives the numbers back.
| Argument | Meaning |
|---|---|
samples |
a list of numbers from -1 to 1, one channel. Numbers outside that range are clipped. |
rate |
how many numbers make one second. The default is 44100. |
Returns. A Sound: play(), loop(), stop(), level(), spectrum(bands), samples() and so on.
Raises.
ValueError: the list is empty or has things that are not numbers.
import math
s = f.create_sound([math.sin(i / 9) for i in range(44100)])
s.play()
See also: tone, load_sound, mix.
f.tone¶
f.tone(frequency: 'float', seconds: 'float', wave: 'str' = 'sine', volume: 'float' = 0.5, attack: 'float' = 0.01, release: 'float' = 0.15, decay: 'float' = 0.15, sustain: 'float' = 0.7)
Make a sound that is one steady tone.
The loudness follows an envelope. It rises to full over the attack, falls to the sustain level over the decay, holds, and fades out over the last release seconds. If attack and release do not fit, they are made shorter. Noise repeats after random_seed().
| Argument | Meaning |
|---|---|
frequency |
the pitch, in hertz. |
seconds |
how long the sound lasts. |
wave |
"sine" (the default), "soft", "triangle", "square", "saw" or "noise". |
volume |
from 0 to 1. The default 0.5 is half, so sounds can play together without clipping. |
attack |
seconds to rise to full. The default is 0.01. |
release |
seconds to fade out at the end. The default is 0.15. |
decay |
seconds to fall to the sustain level. The default is 0.15. |
sustain |
the level held after the decay, 0 to 1. The default is 0.7. |
Returns. A Sound: play(), loop(), stop(), level() and so on.
f.tone(440, 1, "square").play()
See also: note, pluck, melody, create_sound.
f.note¶
f.note(name: 'str', seconds: 'float', wave: 'str' = 'sine', volume: 'float' = 0.5, attack: 'float' = 0.01, release: 'float' = 0.15, decay: 'float' = 0.15, sustain: 'float' = 0.7)
Make a sound that is one named note.
It takes the same options as tone().
| Argument | Meaning |
|---|---|
name |
a note name such as "A4" (440 hertz), "C#5" or "Bb3". |
seconds |
how long the sound lasts. |
wave |
"sine" (the default), "soft", "triangle", "square", "saw" or "noise". |
volume |
from 0 to 1. The default is 0.5. |
attack |
seconds to rise to full. The default is 0.01. |
release |
seconds to fade out at the end. The default is 0.15. |
decay |
seconds to fall to the sustain level. The default is 0.15. |
sustain |
the level held after the decay, 0 to 1. The default is 0.7. |
Returns. A Sound: play(), loop(), stop(), level() and so on.
Raises.
ValueError: the note name is not understood.
f.note("C4", 2, attack=0.5, sustain=1).play()
See also: tone, melody, note_to_frequency.
f.pluck¶
f.pluck(name_or_frequency, seconds: 'float', volume: 'float' = 0.5)
Make a sound like a plucked string.
It dies away by itself. It repeats after random_seed().
| Argument | Meaning |
|---|---|
name_or_frequency |
a note name such as "E3", or a frequency in hertz. |
seconds |
how long the sound lasts. |
volume |
from 0 to 1. The default is 0.5. |
Returns. A Sound: play(), loop(), stop(), reverb() and so on.
f.pluck("E3", 2).play()
f.melody¶
f.melody(text: 'str', tempo: 'float' = 120, wave: 'str' = 'soft', sa: 'str | None' = None, tuning: 'str' = 'equal', volume: 'float' = 0.5)
Make a sound from a string of notes.
Separate the tokens with spaces. A token is a note name, "-" for a rest, or "[C4 E4 G4]" for a chord. Add ":2" after a token for how many beats it lasts (1 if left off). Notes are smooth: each one fades out over 0.15 seconds while the next begins, so the sound lasts that much longer than its beats when it ends on a note. With sa given, the notes are sargam: S r R g G m M P d D n N, with ' for the octave above and a comma for the octave below. In sargam, "S~G" glides from S to G over the token's beats, and "(R)G" touches R for about 60 milliseconds and then plays G.
| Argument | Meaning |
|---|---|
text |
the notes, such as "C4 E4 G4:2 - [C4 E4 G4]:4". |
tempo |
beats a minute. The default is 120. |
wave |
the tone, as for tone(). The default is "soft". |
sa |
a note name such as "C4" that makes the notes sargam, with that note as Sa. The default None reads note names. |
tuning |
"equal" (the default) or "just". "just" uses just-intonation ratios from Sa, and needs sa. |
volume |
from 0 to 1. The default is 0.5. |
Returns. A Sound: play(), loop(), stop() and so on.
Raises.
ValueError: a token cannot be read. The message names it.
f.melody("C4 E4 G4:2 -", tempo=100).play()
See also: note, sequence, mix, raga.
f.sequence¶
f.sequence(*sounds)
Make a new sound that plays the given sounds one after another.
| Argument | Meaning |
|---|---|
sounds |
the sounds to join, in order. |
Returns. A Sound that lasts as long as all of them together.
f.sequence(f.note("C4", 1), f.note("E4", 1)).play()
f.mix¶
f.mix(*sounds)
Make a new sound that plays the given sounds together.
A soft limiter keeps the loudest moments at or below 0.9, so the sum never clips. Quiet sums are not changed.
| Argument | Meaning |
|---|---|
sounds |
the sounds to play together. |
Returns. A Sound as long as the longest of them.
f.mix(f.note("C4", 2), f.note("E4", 2), f.note("G4", 2)).play()
See also: sequence, chord_notes.
f.drone¶
f.drone(sa, seconds: 'float', pattern: 'str' = "P S' S' S", volume: 'float' = 0.5)
Make a tanpura-like drone.
It is plucked strings that ring on, played in turn over and over. It loops smoothly with sound.loop(). It repeats after random_seed().
| Argument | Meaning |
|---|---|
sa |
the base note: a note name such as "D3", or a frequency in hertz. |
seconds |
how long the sound lasts. |
pattern |
the strings to pluck, as swaras. The default is "P S' S' S". It may start with m or N instead of P. |
volume |
its loudest point, 0 to 1. The default is 0.5. |
Returns. A Sound: loop(), play(), reverb() and so on.
f.drone("D3", 8).loop()
f.note_to_frequency¶
f.note_to_frequency(name: 'str', sa: 'str | None' = None) -> 'float'
Return the frequency of a note, in hertz.
| Argument | Meaning |
|---|---|
name |
a note name such as "A4". With sa given, a swara such as "G" or "N,". |
sa |
a note name such as "C4" that the swara is counted from. The default None reads an ordinary note name. |
Returns. The frequency in hertz. note_to_frequency("A4") is 440.
Raises.
ValueError: the name is not understood.
print(f.note_to_frequency("A4"))
See also: frequency_to_note, note, tone.
f.frequency_to_note¶
f.frequency_to_note(hz: 'float', sa: 'str | None' = None) -> 'str'
Return the name of the note nearest to a frequency.
Sharps are used, such as "C#5".
| Argument | Meaning |
|---|---|
hz |
the frequency, in hertz. |
sa |
a note name such as "C4". With it, the answer is the nearest swara, such as "G'". The default None gives an ordinary note name. |
Returns. A note name, such as "A4".
print(f.frequency_to_note(440))
See also: note_to_frequency.
f.chord_notes¶
f.chord_notes(name: 'str') -> 'list[str]'
Return the note names in a chord.
| Argument | Meaning |
|---|---|
name |
a note (with # or b), followed by nothing, m, dim, aug, 7, maj7 or m7. For example "C", "Am" or "Bdim". |
Returns. A list of note names, with sharps. chord_notes("C") is ["C", "E", "G"], and chord_notes("Am") is ["A", "C", "E"].
Raises.
ValueError: the chord name is not understood.
print(f.chord_notes("Am"))
Music analysis and the microphone¶
f.microphone¶
f.microphone(name: 'str | None' = None)
Make a microphone object for the computer's default input.
Call mic.start() to listen. While it listens, mic.level(), mic.spectrum(bands) and mic.pitch() work as they do on a sound. mic.capture(seconds) returns the last few seconds as a sound. It is never played back. With FUNGROUND_HEADLESS=1 it is silent and hears nothing. On a Mac, allow microphone access in System Settings.
| Argument | Meaning |
|---|---|
name |
part of the name of the input to use. The default None uses the default input. See microphones(). |
Returns. A Microphone: start(), stop(), is_listening(), level(), spectrum(bands), pitch(), capture(seconds), is_onset(), chroma() and chord().
Raises.
RuntimeError: there is no microphone, or access is refused.
mic = f.microphone()
mic.start()
See also: microphones, draw_wave, draw_spectrum, draw_pitch_line.
f.microphones¶
f.microphones() -> 'list[str]'
Return the names of the computer's microphones (inputs).
Returns. A list of names. Give part of one to microphone().
print(f.microphones())
See also: microphone.
Drawing sound¶
f.draw_wave¶
f.draw_wave(source, x: 'float', y: 'float', w: 'float', h: 'float') -> 'None'
Draw the wave of a sound, a microphone or a list of numbers.
It is drawn inside a box, with the current fill, stroke and transform. For a sound it shows the whole sound, with a line at the place it is playing. For a microphone it shows the last half second. A sound's shape is worked out once for each width, so it is quick.
| Argument | Meaning |
|---|---|
source |
a Sound, a Microphone or a list of numbers. |
x, y |
the top-left corner of the box. |
w, h |
the width and height of the box, in pixels. |
f.draw_wave(song, 20, 20, 400, 80)
See also: draw_spectrum, spectrogram, draw_pitch_line.
f.draw_spectrum¶
f.draw_spectrum(source, x: 'float', y: 'float', w: 'float', h: 'float', bands: 'int' = 32) -> 'None'
Draw bars for a spectrum, as it is now.
The bars stand on the bottom of a box, in the current style.
| Argument | Meaning |
|---|---|
source |
a Sound or a Microphone. |
x, y |
the top-left corner of the box. |
w, h |
the width and height of the box, in pixels. |
bands |
how many bars. The default is 32. |
f.draw_spectrum(mic, 20, 120, 400, 80)
See also: draw_wave, spectrogram.
f.spectrogram¶
f.spectrogram(sound, width: 'int', height: 'int') -> 'Picture'
Make a picture of a whole sound.
Time goes across. Pitch goes up (about 40 hertz to 16 kilohertz, spaced like notes). Louder is brighter. The brightest colour is the current fill. It takes a moment (about a second for 10 seconds of sound at 400 by 200), so make it once, in setup().
| Argument | Meaning |
|---|---|
sound |
the Sound to look at. |
width, height |
the size of the picture, in pixels. |
Returns. A Picture. Draw it with image(), or save it.
pic = f.spectrogram(song, 400, 200)
f.image(pic, 20, 20)
See also: draw_wave, draw_spectrum, image.
f.draw_pitch_line¶
f.draw_pitch_line(source, x: 'float', y: 'float', w: 'float', h: 'float', seconds: 'float' = 5, low: 'str' = 'C3', high: 'str' = 'C6', sa: 'str | None' = None) -> 'None'
Draw the pitch of a sound or microphone as a scrolling line.
Call it once in every frame, because it reads source.pitch() each time. The line scrolls left. Faint guide lines are labelled with note names, or with swaras when sa is given. The line has a gap where pitch() is None.
| Argument | Meaning |
|---|---|
source |
a Sound or a Microphone. |
x, y |
the top-left corner of the box. |
w, h |
the width and height of the box, in pixels. |
seconds |
how much time the box shows. The default is 5. |
low |
the note at the bottom. The default is "C3". |
high |
the note at the top. The default is "C6". |
sa |
a note name such as "C4". With it the guide lines are swaras. The default None uses note names. |
f.draw_pitch_line(mic, 20, 20, 400, 150)
See also: draw_wave, microphone, frequency_to_note.
Ragas and talas¶
f.ragas¶
f.ragas() -> 'list[str]'
Return the names of the ragas in funground's small built-in table.
For example "Yaman" and "Bhupali".
Returns. A list of names.
See also: raga, match_ragas, talas.
f.raga¶
f.raga(name: 'str')
Return one raga from the built-in table.
| Argument | Meaning |
|---|---|
name |
the raga's name. Capitals do not matter. |
Returns. A read-only record with name, thaat, swaras, aroha, avaroha and pakad (sargam strings for melody(sa=...)), vadi, samvadi, time, notes and sources.
Raises.
ValueError: the name is not in the table. The message lists the table.
f.melody(f.raga("Yaman").aroha, sa="D4").play()
See also: ragas, melody, match_ragas.
f.talas¶
f.talas() -> 'list[str]'
Return the names of the talas in the built-in table.
They are Teentaal, Ektaal, Jhaptaal, Rupak, Dadra and Keherwa.
Returns. A list of names.
See also: tala_info, tala, ragas.
f.tala_info¶
f.tala_info(name: 'str')
Return one tala from the built-in table.
| Argument | Meaning |
|---|---|
name |
the tala's name. Capitals do not matter. |
Returns. A read-only record with name, beats, vibhag (the divisions), tali (claps) and khali (waves) as beat numbers, sam (beat 1), bols (the theka, one bol a beat), notes and sources.
Raises.
ValueError: the name is not in the table.
print(f.tala_info("Rupak").khali)
f.tala¶
f.tala(name: 'str', tempo: 'float' = 80, cycles: 'int' = 1)
Make a sound of a tala's theka played with simple drum sounds.
The sam is accented, and the khali part is softer. The sound is exactly beats * 60 / tempo * cycles seconds long.
| Argument | Meaning |
|---|---|
name |
the tala's name, such as "Teentaal". See talas(). |
tempo |
beats a minute. The default is 80. |
cycles |
how many times round the tala. The default is 1. |
Returns. A Sound: play(), loop() and so on.
Raises.
ValueError: the name is not in the table.
f.tala("Teentaal", tempo=100).loop()
See also: talas, tala_info, drone.
f.match_ragas¶
f.match_ragas(histogram) -> 'list[tuple[str, float]]'
Rank the table's ragas by how well their swaras fit a histogram.
It compares note sets only, so ragas with the same swaras score almost the same (for example Bhupali and Deshkar). It is a learning aid, not a judge.
| Argument | Meaning |
|---|---|
histogram |
twelve numbers, as from sound.swara_histogram(sa). |
Returns. A list of (name, score) pairs, best first, with scores from 0 to 1.
h = song.swara_histogram("C#4")
print(f.match_ragas(h)[0])
Classes¶
-
Vector: see the Vector class. -
FormattedString: see the FormattedString class.
Live values¶
These look like variables. Read them as f.width, f.mouse_x and so on. They always hold the current value, and you cannot set them.
f.delta_time¶
The seconds that the previous frame took.
It is a number, such as 0.016 at 60 frames a second. It is 0.0 in the first frame. It is updated once a frame. Use it to move at the same speed on any computer.
x += 120 * f.delta_time
f.frame_count¶
How many frames have been completed since the sketch started.
It is a whole number. It is 0 during the first draw(), 1 during the second, and so on. It goes up by one after each draw().
f.rotate(f.frame_count * 3)
f.ground¶
The canvas as an area, with its margins.
It is a read-only value, always up to date, like width. It is an Area, so it has left, top, right, bottom, width, height, cx and cy (the centre), and inset() and grid(). It also has margin, a tuple (top, right, bottom, left) from size(). It starts at (0, 0), so left and top are 0, and right and bottom are the canvas width and height. Its content is the area inside the margins: a value of the same kind, whose own margin is (0, 0, 0, 0) and whose own content is itself. With no margin, ground.content is ground. It follows size(), resize_canvas() and new_page(). The margin stays when the canvas changes size. Margins are a guide: drawing outside them is allowed. Inside a with f.layer(...) block it is still the canvas.
f.size(400, 300, margin=20)
f.rect(f.ground.content.left, f.ground.content.top, f.ground.content.width, f.ground.content.height)
f.height¶
The height of the canvas, in pixels.
It is a whole number, exactly what you gave to size(). It is 480 until you call size(). It changes with resize_canvas() and full_screen().
f.line(0, f.height, f.width, 0)
f.is_key_pressed¶
Whether any key is held down now.
It is True or False. It is updated before every call to draw(). It is always False with no window.
if f.is_key_pressed:
f.fill("gold")
f.is_mouse_pressed¶
Whether a mouse button is held down now.
It is True or False. It is True while any of the first three mouse buttons is held. It is updated before every call to draw(). To act once when a click starts, define a mouse_pressed() function instead.
if f.is_mouse_pressed:
f.fill("tomato")
f.key¶
The last key that was pressed or released.
It is a one-character string such as "a" or " ", or a name such as "left", "right", "up", "down", "enter" or "escape". It is None before any key has been used. It changes when a key goes down or up, and it stays after the key is let go. To ask whether a key is held right now, use key_down().
if f.key == "r":
f.background("white")
f.key_code¶
The code of the last key that was pressed or released.
It is a whole number, or None before any key has been used. It changes at the same time as key. Most programs use key instead, which is easier to read.
print(f.key_code)
f.mouse_button¶
The last mouse button that was pressed or released.
It is the word "left", "center" or "right", or None before any button has been used. It changes when a button goes down or up, and it stays after the button is let go.
if f.mouse_button == "right":
f.background("white")
f.mouse_x¶
The mouse's x position over the canvas, in pixels from the left edge.
It is a number. It is updated before every call to draw(). It is 0 with no window.
f.circle(f.mouse_x, f.mouse_y, 30)
f.mouse_y¶
The mouse's y position over the canvas, in pixels from the top edge.
It is a number. It is updated before every call to draw(). It is 0 with no window. The panel of controls below the canvas is not counted.
f.circle(f.mouse_x, f.mouse_y, 30)
f.pixels¶
The canvas pixels, after load_pixels().
It is a bytearray with red, green, blue and alpha (0 to 255, not premultiplied) for every pixel, row by row from the top left. The pixel at (x, y) starts at index (y * f.width + x) * 4. It is None until you call load_pixels(). Change the numbers in place, then call update_pixels(). Inside a with f.layer(...) block it holds the layer's pixels.
f.load_pixels()
f.pixels[0] = 255
f.update_pixels()
f.pmouse_x¶
Where the mouse's x was in the previous frame, in pixels.
It is a number. It is updated before every call to draw(), together with mouse_x. Use it with mouse_x to draw a line that follows the mouse.
f.line(f.pmouse_x, f.pmouse_y, f.mouse_x, f.mouse_y)
f.pmouse_y¶
Where the mouse's y was in the previous frame, in pixels.
It is a number. It is updated before every call to draw(), together with mouse_y.
f.line(f.pmouse_x, f.pmouse_y, f.mouse_x, f.mouse_y)
f.width¶
The width of the canvas, in pixels.
It is a whole number, exactly what you gave to size(). It is 640 until you call size(). It changes with resize_canvas() and full_screen(). On a high-resolution screen it is still the number you asked for.
f.circle(f.width / 2, f.height / 2, 80)
Classes in detail¶
Sound¶
Sound(device_sound, mixer, silent: 'bool', frame_source=None, made=None)
A sound that you can play, listen to and change.
You get one from f.load_sound() (a file), f.create_sound() (a list of numbers), f.tone(), f.note(), f.pluck(), f.melody(), f.drone() and f.tala() (made for you), f.sequence() and f.mix() (made from other sounds), and mic.capture() (made from what a microphone heard). All of them are the same kind of object.
A sound keeps its own place and state (playing, paused or stopped), kept by a clock. So is_playing() and the listening methods behave the same with or without a sound device. With no device, or with FUNGROUND_HEADLESS=1, a sound plays silently and keeps time.
Methods that change a sound's state (play, loop, stop, pause, set_volume, pan) change this sound. Methods that make something new (reverb) give you a new sound and leave this one alone.
beep = f.tone(440, 1)
beep.play()
f.circle(200, 200, 50 + 300 * beep.level())
Sound.beats¶
Sound.beats() -> 'list[float]'
The times of the beats, at the sound's tempo.
They are lined up with the onsets. It looks at the whole sound.
Returns. a list of times in seconds from the start, or an empty list when there is no clear pulse.
song = f.melody("C4 E4 G4 E4 A3 C4 E4 C4", tempo=100, wave="triangle")
beats = song.beats()
song.play()
See also: tempo, onsets, is_onset.
Sound.chord¶
Sound.chord() -> 'str | None'
The chord sounding now, as a name.
The name is a note followed by nothing (major), m, dim, aug, 7, maj7 or m7, such as "C", "Am", "G7", "Fmaj7" or "Bdim". One voice on its own is not a chord. It is meant for clear chords on a piano, a guitar or a synth, not for a busy band.
Returns. the chord name, or None when no chord stands out.
f.text(song.chord() or "-", 150, 100)
See also: chroma, key, chord_notes.
Sound.chroma¶
Sound.chroma() -> 'list[float]'
How strong each of the twelve note names is right now, whatever the octave.
The numbers are for C, C#, D, D#, E, F, F#, G, G#, A, A# and B, in that order. The strongest is 1. All of them are 0 when nothing is playing or listening, or when it is quiet.
Returns. a list of 12 numbers from 0 to 1.
notes = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
for i, strength in enumerate(song.chroma()):
f.text(notes[i], 20 + i * 30, 380 - 200 * strength)
Sound.current_time¶
Sound.current_time() -> 'float'
Where the sound is now.
A sound that is stopped is at 0. A paused sound stays where it stopped.
Returns. the time in seconds from the start of the sound.
snd = f.melody("C4 E4 G4")
snd.loop()
f.rect(0, 190, 400 * snd.current_time() / snd.duration(), 20)
Sound.duration¶
Sound.duration() -> 'float'
The length of the sound.
Returns. the length in seconds.
snd = f.tone(440, 2)
print(snd.duration()) # 2.0 or a little more
See also: current_time.
Sound.get_volume¶
Sound.get_volume() -> 'float'
The loudness set by set_volume().
Returns. a number from 0 to 1. It is 1 until you change it.
snd = f.tone(440, 1)
snd.set_volume(0.5)
print(snd.get_volume())
See also: set_volume.
Sound.is_onset¶
Sound.is_onset() -> 'bool'
Whether a new note or hit is heard in this frame.
Use it to flash a light on each beat or note. It looks at the last 0.3 seconds. It is False when nothing is playing or listening.
Returns. True in the frame where a new note or hit begins, otherwise False.
if song.is_onset():
f.background("white")
Sound.is_playing¶
Sound.is_playing() -> 'bool'
Whether the sound is playing now.
A paused sound is not playing. A sound that has reached its end is not playing.
Returns. True while the sound is playing, otherwise False.
if not song.is_playing():
song.play()
Sound.key¶
Sound.key() -> 'str | None'
The key of the whole sound.
It adds up the chroma over the whole sound and says which key fits best. It is worked out once.
Returns. a name like "G major" or "E minor", or None when no key stands out.
song = f.melody("C4 E4 G4 C5 G4 E4 C4")
print(song.key())
Sound.level¶
Sound.level() -> 'float'
How loud the sound is right now, from 0 (silent) to 1.
It is the root mean square of the last 1/30 of a second, so it follows the music. It is 0 when the sound is not playing, or the microphone is not listening.
Returns. a number from 0 to 1.
snd = f.load_sound("song.wav") # your own file
snd.loop()
f.circle(200, 200, 50 + 300 * snd.level())
See also: spectrum, pitch, is_onset.
Sound.loop¶
Sound.loop() -> 'None'
Play the sound over and over, until you call stop() or pause().
drone = f.drone("D3", 8)
drone.loop()
Sound.onsets¶
Sound.onsets() -> 'list[float]'
The times where a note or a hit begins.
It looks at the whole sound. It is worked out once, then remembered. A three-minute song takes a few seconds the first time. A microphone has no whole sound, so use mic.capture(seconds).onsets().
Returns. a list of times in seconds from the start, earliest first.
song = f.melody("C4 E4 G4 C5")
print(song.onsets())
See also: is_onset, tempo, beats.
Sound.pan¶
Sound.pan(position: 'float') -> 'None'
Move the sound between the left and right speakers.
It works on any sound. level() and spectrum() do not change with pan. save() keeps the pan.
| Argument | Meaning |
|---|---|
position |
a number from -1 (all left) to 1 (all right). 0 is the middle. |
Raises.
ValueError: if position is not a number from -1 to 1.
snd = f.tone(440, 1)
snd.pan(-1) # left speaker only
snd.play()
See also: set_volume, play.
Sound.pause¶
Sound.pause() -> 'None'
Stop the sound and keep the place, so that play() carries on from there.
Nothing happens if the sound is not playing.
snd = f.tone(440, 5)
snd.play()
snd.pause()
snd.play() # carries on
See also: play, stop, current_time.
Sound.pitch¶
Sound.pitch() -> 'float | None'
The frequency of the one voice or instrument heard right now.
It looks at the last 2 048 samples. It is for one note at a time, not for chords. It looks for pitches from 50 to 2000 Hz. It gives None when the sound is quiet, when it has no clear pitch (noise has none), or when nothing is playing or listening.
Returns. the frequency in hertz, or None.
mic = f.microphone()
mic.start()
hz = mic.pitch()
if hz:
f.text(f.frequency_to_note(hz), 20, 40)
See also: chroma, chord, level.
Sound.play¶
Sound.play() -> 'None'
Play the sound from the start, or from where pause() left it.
Playing a sound that is already playing starts it again from the start. A sound that is not looping stops by itself at the end.
beep = f.tone(440, 1)
beep.play()
See also: loop, pause, stop, is_playing.
Sound.reverb¶
Sound.reverb(amount: 'float' = 0.3) -> "'Sound'"
Make a new sound that is this sound played in a room.
The room's echoes ring on after the end, so the new sound is longer, by up to 1.5 seconds at 1. The new sound has one channel. The volume and pan are not carried over. This sound is not changed.
It takes a moment to work out: about a twentieth of a second for each second of sound. Make the new sound once, at the start, not in draw().
| Argument | Meaning |
|---|---|
amount |
the size of the room, from 0 (no room, the sound is unchanged) to 1 (a large hall). It is 0.3 at first. |
Returns. a new Sound.
Raises.
ValueError: if amount is not a number from 0 to 1.
dry = f.pluck("G3", 1.5)
wet = dry.reverb(0.5)
wet.play()
Sound.samples¶
Sound.samples() -> 'list[float]'
The sound as a list of numbers.
A sound is a long list of numbers, one for each tiny slice of time. A mono sound has 44 100 of them a second. If the sound has more than one channel, they are mixed into one. Draw them to see the wave. The pan and the volume are not in the numbers.
Returns. a list of numbers from -1 to 1.
wave = f.tone(220, 1).samples()
for x in range(399):
f.line(x, 100 - 80 * wave[x * 4], x + 1, 100 - 80 * wave[x * 4 + 4])
See also: create_sound, save.
Sound.save¶
Sound.save(path: 'str') -> 'None'
Write the sound to a 16-bit WAV file.
The pan is kept in the file. The volume is not.
| Argument | Meaning |
|---|---|
path |
the file name, such as "tune.wav". |
Raises.
ValueError: if path is empty or not text, or the file cannot be written.
f.melody("C4 E4 G4").save("tune.wav") # writes a new file
See also: samples, create_sound.
Sound.set_volume¶
Sound.set_volume(volume: 'float') -> 'None'
Set how loud the sound plays.
It does not change the numbers inside the sound, so level() and samples() are not affected.
| Argument | Meaning |
|---|---|
volume |
a number from 0 (silent) to 1 (full). It is 1 at first. |
Raises.
ValueError: if volume is not a number from 0 to 1.
snd = f.tone(440, 1)
snd.set_volume(0.3)
snd.play()
See also: get_volume, pan.
Sound.spectrum¶
Sound.spectrum(bands: 'int' = 32) -> 'list[float]'
How strong the sound is now in each of several ranges of pitch, from low notes to high notes.
The ranges are called bands. They are spaced evenly in pitch, from 40 Hz to 16 kHz, so each one is about the same number of notes wide. A band is strong when the sound has a lot of that pitch. You get a list of zeros when the sound is not playing, or the microphone is not listening.
| Argument | Meaning |
|---|---|
bands |
how many ranges to split the sound into, a whole number from 1 to 256. It is 32 at first. |
Returns. a list of bands numbers from 0 to 1, lowest pitch first.
Raises.
ValueError: if bands is not a whole number from 1 to 256.
snd = f.tone(220, 2)
snd.play()
for i, strength in enumerate(snd.spectrum(16)):
f.rect(20 + i * 24, 380, 20, -300 * strength)
See also: level, pitch, chroma.
Sound.stop¶
Sound.stop() -> 'None'
Stop the sound and go back to its start.
snd = f.tone(440, 5)
snd.play()
snd.stop()
Sound.swara_histogram¶
Sound.swara_histogram(sa) -> 'list[float]'
How much of the singing was on each swara.
Each number is the share of the time with a clear pitch that was spent within 50 cents (half a semitone) of that swara, in any octave. The twelve swaras above Sa are S, r, R, g, G, m, M, P, d, D, n and N. The numbers add up to 1, or are all 0 if nothing had a pitch.
| Argument | Meaning |
|---|---|
sa |
the note that Sa is, as a note name like "D4" or as a number of hertz. |
Returns. a list of 12 numbers, the first for S and the last for N.
hist = recording.swara_histogram("D4")
print(f.match_ragas(hist)[0])
See also: tonic, match_ragas.
Sound.tempo¶
Sound.tempo() -> 'float | None'
The speed of the music.
It looks at the whole sound, and is worked out once. A sound with no clear pulse has no tempo.
Returns. the speed in beats a minute, from 60 to 200, or None when there is no clear pulse.
song = f.melody("C4 E4 G4 E4 A3 C4 E4 C4", tempo=100, wave="triangle")
print(song.tempo())
Sound.tonic¶
Sound.tonic() -> 'float | None'
A guess at Sa, the starting note of a singer, in hertz.
It counts how long each pitch was heard, folded into one octave. It then picks the pitch that best explains a strong Sa and Pa. It is a rough guide, not an answer. For a microphone it uses the last 10 seconds. For a sound it uses the whole sound.
Returns. the frequency of Sa in hertz, or None when too little had a clear pitch.
sa = recording.tonic()
if sa:
print(f.frequency_to_note(sa))
See also: swara_histogram, pitch, match_ragas.
Microphone¶
Microphone(device_name: 'str | None', frame_source=None, silent: 'bool' = False)
A microphone that your sketch can listen to.
You get one from f.microphone(). Call start() to begin listening and stop() to end. While it listens, level(), spectrum(), pitch(), is_onset(), chroma(), chord(), tonic() and swara_histogram() work as they do on a Sound. They tell you about the last fraction of a second. When it is not listening they give 0, a list of zeros, None or False.
capture(seconds) gives you a Sound made of the last few seconds it heard (up to 10), so you can play it, save it, draw it or look at its whole-sound features such as tempo().
The microphone is never played back, so there is no squeal from the speakers. Nothing is recorded to a file unless you call save() on a capture. With FUNGROUND_HEADLESS=1 it is silent and hears nothing.
mic = f.microphone()
mic.start()
f.circle(200, 200, 20 + 400 * mic.level())
Microphone.beats¶
Microphone.beats()
Not for a microphone, because it looks at a whole sound.
Use mic.capture(seconds).beats() to look at what was heard.
Raises.
ValueError: always, with a message that points to capture().
See also: capture.
Microphone.capture¶
Microphone.capture(seconds: 'float')
Make a new sound from the last few seconds the microphone heard.
If it has heard less than that, the start is silence, so the sound is always as long as you asked. It is an ordinary Sound: play it, save it, or draw its wave with samples().
| Argument | Meaning |
|---|---|
seconds |
how much to keep, more than 0 and at most 10. |
Returns. a new Sound that is seconds long.
Raises.
ValueError: if seconds is not more than 0 and at most 10.
mic = f.microphone()
mic.start()
recording = mic.capture(1) # use it after about a second
recording.save("heard.wav") # writes a new file
Microphone.chord¶
Microphone.chord() -> 'str | None'
The chord sounding now, as a name.
The name is a note followed by nothing (major), m, dim, aug, 7, maj7 or m7, such as "C", "Am", "G7", "Fmaj7" or "Bdim". One voice on its own is not a chord. It is meant for clear chords on a piano, a guitar or a synth, not for a busy band.
Returns. the chord name, or None when no chord stands out.
f.text(song.chord() or "-", 150, 100)
See also: chroma, key, chord_notes.
Microphone.chroma¶
Microphone.chroma() -> 'list[float]'
How strong each of the twelve note names is right now, whatever the octave.
The numbers are for C, C#, D, D#, E, F, F#, G, G#, A, A# and B, in that order. The strongest is 1. All of them are 0 when nothing is playing or listening, or when it is quiet.
Returns. a list of 12 numbers from 0 to 1.
notes = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
for i, strength in enumerate(song.chroma()):
f.text(notes[i], 20 + i * 30, 380 - 200 * strength)
Microphone.close¶
Microphone.close() -> 'None'
Stop listening and let go of the microphone.
funground calls it when a sketch ends, so you do not need to.
mic.close()
See also: stop.
Microphone.is_listening¶
Microphone.is_listening() -> 'bool'
Whether the microphone is listening.
Returns. True between start() and stop(), otherwise False.
if not mic.is_listening():
mic.start()
Microphone.is_onset¶
Microphone.is_onset() -> 'bool'
Whether a new note or hit is heard in this frame.
Use it to flash a light on each beat or note. It looks at the last 0.3 seconds. It is False when nothing is playing or listening.
Returns. True in the frame where a new note or hit begins, otherwise False.
if song.is_onset():
f.background("white")
Microphone.key¶
Microphone.key()
Not for a microphone, because it looks at a whole sound.
Use mic.capture(seconds).key() to look at what was heard.
Raises.
ValueError: always, with a message that points to capture().
Microphone.level¶
Microphone.level() -> 'float'
How loud the sound is right now, from 0 (silent) to 1.
It is the root mean square of the last 1/30 of a second, so it follows the music. It is 0 when the sound is not playing, or the microphone is not listening.
Returns. a number from 0 to 1.
snd = f.load_sound("song.wav") # your own file
snd.loop()
f.circle(200, 200, 50 + 300 * snd.level())
See also: spectrum, pitch, is_onset.
Microphone.onsets¶
Microphone.onsets()
Not for a microphone, because it looks at a whole sound.
Use mic.capture(seconds).onsets() to look at what was heard.
Raises.
ValueError: always, with a message that points to capture().
Microphone.pitch¶
Microphone.pitch() -> 'float | None'
The frequency of the one voice or instrument heard right now.
It looks at the last 2 048 samples. It is for one note at a time, not for chords. It looks for pitches from 50 to 2000 Hz. It gives None when the sound is quiet, when it has no clear pitch (noise has none), or when nothing is playing or listening.
Returns. the frequency in hertz, or None.
mic = f.microphone()
mic.start()
hz = mic.pitch()
if hz:
f.text(f.frequency_to_note(hz), 20, 40)
See also: chroma, chord, level.
Microphone.spectrum¶
Microphone.spectrum(bands: 'int' = 32) -> 'list[float]'
How strong the sound is now in each of several ranges of pitch, from low notes to high notes.
The ranges are called bands. They are spaced evenly in pitch, from 40 Hz to 16 kHz, so each one is about the same number of notes wide. A band is strong when the sound has a lot of that pitch. You get a list of zeros when the sound is not playing, or the microphone is not listening.
| Argument | Meaning |
|---|---|
bands |
how many ranges to split the sound into, a whole number from 1 to 256. It is 32 at first. |
Returns. a list of bands numbers from 0 to 1, lowest pitch first.
Raises.
ValueError: if bands is not a whole number from 1 to 256.
snd = f.tone(220, 2)
snd.play()
for i, strength in enumerate(snd.spectrum(16)):
f.rect(20 + i * 24, 380, 20, -300 * strength)
See also: level, pitch, chroma.
Microphone.start¶
Microphone.start() -> 'None'
Start listening.
It forgets what it heard before. Calling it when it is already listening does nothing.
Raises.
RuntimeError: if there is no microphone, or the computer will not let funground use it.
mic = f.microphone()
mic.start()
See also: stop, is_listening, capture.
Microphone.stop¶
Microphone.stop() -> 'None'
Stop listening, but keep what was heard, so that capture() still works.
recording = mic.capture(2)
mic.stop()
Microphone.swara_histogram¶
Microphone.swara_histogram(sa) -> 'list[float]'
How much of the singing was on each swara.
Each number is the share of the time with a clear pitch that was spent within 50 cents (half a semitone) of that swara, in any octave. The twelve swaras above Sa are S, r, R, g, G, m, M, P, d, D, n and N. The numbers add up to 1, or are all 0 if nothing had a pitch.
| Argument | Meaning |
|---|---|
sa |
the note that Sa is, as a note name like "D4" or as a number of hertz. |
Returns. a list of 12 numbers, the first for S and the last for N.
hist = recording.swara_histogram("D4")
print(f.match_ragas(hist)[0])
See also: tonic, match_ragas.
Microphone.tempo¶
Microphone.tempo()
Not for a microphone, because it looks at a whole sound.
Use mic.capture(seconds).tempo() to look at what was heard.
Raises.
ValueError: always, with a message that points to capture().
See also: capture.
Microphone.tonic¶
Microphone.tonic() -> 'float | None'
A guess at Sa, the starting note of a singer, in hertz.
It counts how long each pitch was heard, folded into one octave. It then picks the pitch that best explains a strong Sa and Pa. It is a rough guide, not an answer. For a microphone it uses the last 10 seconds. For a sound it uses the whole sound.
Returns. the frequency of Sa in hertz, or None when too little had a clear pitch.
sa = recording.tonic()
if sa:
print(f.frequency_to_note(sa))
See also: swara_histogram, pitch, match_ragas.
Picture¶
Picture(width: 'int', height: 'int', backing_scale: 'float', name: 'str') -> 'None'
A surface that you can draw on and then draw onto the canvas, save, copy or use as a mask.
You get one from f.create_graphics(width, height) (an empty, see-through canvas), f.layer(name) (a layer that sits over the canvas), f.load_image(path) (a picture file), f.load_svg(path) (a vector file), f.get(x, y, w, h) (a copy of part of the canvas) and f.spectrogram() (a picture of a sound).
A picture has its own state and its own transform, and nothing about it is cleared between frames. It starts see-through. Draw on it with the same commands as f.: fill, stroke, circle, rect, line, text, push, pop, translate, rotate, scale, begin_shape, path, clip, background, clear, get, set, filter and so on. For example, g.circle(50, 50, 20) draws on the picture g. Window-only commands such as size(), run() and cursor(), and plain helpers such as random() and noise(), are not on a picture.
The picture's width and height, in pixels, are g.width and g.height. Its name is g.name.
A layer made by f.layer() is a picture too. Use it with with: everything drawn inside the block goes to the layer.
g = f.create_graphics(100, 100)
g.fill("tomato")
g.circle(50, 50, 80)
f.image(g, 10, 10)
f.image(g, 120, 10, 50, 50) # the same picture, smaller
Picture.copy¶
Picture.copy() -> "'Picture'"
Make a new picture with the same pixels and drawing history.
Changing one picture never changes the other. The copy starts with the default drawing state and transform, like any new picture.
Returns. a new Picture.
g = f.create_graphics(100, 100)
g.circle(50, 50, 80)
h = g.copy()
h.background("black") # g is not changed
Picture.from_pixels¶
Picture.from_pixels(width: 'int', height: 'int', bgra: 'bytes', name: 'str') -> "'Picture'"
Make a picture from decoded image pixels.
f.load_image() uses it. You do not need to call it.
| Argument | Meaning |
|---|---|
width, height |
the size of the image in pixels. |
bgra |
the pixels as bytes: blue, green, red and alpha for each pixel, with the colour already multiplied by the alpha. |
name |
the name for the picture. |
Returns. a new Picture whose scale is 1.
Raises.
RuntimeError: if bgra is not the right length for the size.
See also: copy.
Picture.image¶
Picture.image(picture: "'Picture'", x: 'float', y: 'float', width: 'float | None' = None, height: 'float | None' = None, sx: 'float | None' = None, sy: 'float | None' = None, sw: 'float | None' = None, sh: 'float | None' = None) -> 'None'
Draw another picture onto this one, the same as f.image() does on the canvas.
You can give a box to fit it in, and a part of the picture to use. It uses this picture's current transform, clip, blend mode and opacity.
| Argument | Meaning |
|---|---|
picture |
the Picture to draw. It cannot be this picture. |
x, y |
where to put it (the top left corner, unless image_mode() says otherwise). |
width, height |
the size to draw it, in pixels. They are the picture's own size if left out. |
sx, sy, sw, sh |
the part of the picture to use, as a box in its own pixels. Give all four or none. |
Raises.
TypeError: if picture is not a Picture.ValueError: if picture is this picture, if only some of sx, sy, sw and sh are given, or if sw or sh is 0 or less.
g = f.create_graphics(200, 200)
stamp = f.create_graphics(20, 20)
stamp.fill("gold")
stamp.circle(10, 10, 18)
g.image(stamp, 50, 50)
Picture.mask¶
Picture.mask(other: "'Picture'") -> 'None'
Make this picture see-through where another picture is see-through, in place.
Each pixel's alpha is multiplied by the alpha of the other picture, which is scaled to this picture's size first. Only how see-through the mask is matters, not its colours. Draw a shape on a see-through picture to use it as a stencil.
| Argument | Meaning |
|---|---|
other |
the Picture to use as the mask. |
Raises.
TypeError: if other is not a Picture.
photo = f.load_image("photo.png") # your own file
stencil = f.create_graphics(photo.width, photo.height)
stencil.circle(photo.width / 2, photo.height / 2, photo.width)
photo.mask(stencil) # now it is a circle
Picture.pixels¶
Picture.pixels # property
The colour of every pixel, after load_pixels().
It is a list of numbers, four for each pixel (red, green, blue, alpha, each from 0 to 255), row by row from the top left. Change the numbers, then call update_pixels() to put them back on the picture.
Returns. the numbers, or None before g.load_pixels() has been called.
g = f.create_graphics(10, 10)
g.background("white")
g.load_pixels()
g.pixels[0] = 0 # no red in the first pixel
g.update_pixels()
Picture.resize¶
Picture.resize(width: 'int', height: 'int') -> 'None'
Change the size of the picture, in place, scaling its pixels smoothly.
Put a 0 for one side to keep the shape of the picture. The drawing history is dropped. The transform, the clip and any open push() start again.
| Argument | Meaning |
|---|---|
width, height |
the new size in pixels, whole numbers. One of them may be 0 to keep the shape. Both may not be 0. |
Raises.
ValueError: if a side is not a whole number, is negative, or both are 0.
photo = f.load_image("photo.png") # your own file
photo.resize(200, 0) # 200 wide, with the height to match
Picture.save¶
Picture.save(path: 'str', *, text: 'str' = 'live') -> 'None'
Write the picture to a file straight away.
The file type comes from the name. A PNG file gets the picture's pixels. A PDF or SVG file replays the drawing as shapes when it can. It cannot when the picture drew more than 10 000 things since its last opaque background or clear, or when its pixels were changed directly. Then the pixels are put in the file as an image instead, so saving never fails for being too big.
| Argument | Meaning |
|---|---|
path |
the file name, such as "art.png", "art.pdf" or "art.svg". |
text |
for PDF and SVG files only. "live" keeps text as text. "shapes" draws every letter as a shape. It is "live" at first. |
g = f.create_graphics(200, 200)
g.circle(100, 100, 150)
g.save("circle.png") # writes a new file
Vector¶
Vector(x: 'float' = 0.0, y: 'float' = 0.0) -> 'None'
A 2D vector: two numbers, x and y, that you can add, scale, turn and measure.
Make one with f.Vector(3, 4). Read and change its parts as v.x and v.y. Use it for a position, a speed or a push. Angles are in degrees, like every angle in funground. y grows downward, so 90 degrees points down.
The methods add, sub, mult, div, normalize, limit, set_mag, set_heading, rotate, lerp and set change the vector in place and give it back, so calls can be chained: position.add(velocity). The operators +, -, * and / make a new vector instead, and leave the first one alone. A vector can be unpacked like a pair, as in x, y = v, and compared with == against another vector or an (x, y) pair. It is changeable, so it cannot be a dictionary key.
| Argument | Meaning |
|---|---|
x, y |
the two parts of the vector. Both are 0 at first. |
v = f.Vector(3, 4)
print(v.mag()) # 5.0
v.add(1, 1).mult(2) # now (8, 10)
w = v + f.Vector(1, 0) # a new vector, v is not changed
Vector.add¶
Vector.add(x, y=None) -> 'Vector'
Add to the vector, in place.
| Argument | Meaning |
|---|---|
x, y |
either two numbers, or x can be a Vector or an (x, y) pair with y left out. |
Returns. this vector, changed.
Raises.
TypeError: if the values are not numbers, a Vector or an (x, y) pair.
position = f.Vector(100, 100)
velocity = f.Vector(2, 1)
position.add(velocity)
Vector.angle_between¶
Vector.angle_between(other) -> 'float'
The angle that turns this vector's direction onto another's.
| Argument | Meaning |
|---|---|
other |
a Vector or an (x, y) pair. Neither vector may be zero. |
Returns. the signed angle in degrees.
Raises.
ValueError: if either vector is zero.
print(f.Vector(1, 0).angle_between(f.Vector(0, 1))) # 90.0
See also: heading, dot, cross.
Vector.copy¶
Vector.copy() -> 'Vector'
Make a new vector with the same x and y.
Changing the copy does not change the original.
Returns. a new Vector.
a = f.Vector(1, 2)
b = a.copy()
b.add(5, 5) # a is still (1, 2)
See also: set.
Vector.cross¶
Vector.cross(other) -> 'float'
The 2D cross product of this vector and another.
It is positive when the other vector is clockwise from this one on the screen.
| Argument | Meaning |
|---|---|
other |
a Vector or an (x, y) pair. |
Returns. a number.
print(f.Vector(1, 0).cross(f.Vector(0, 1))) # 1.0
See also: dot, angle_between.
Vector.dist¶
Vector.dist(other) -> 'float'
The distance from this point to another.
| Argument | Meaning |
|---|---|
other |
a Vector or an (x, y) pair. |
Returns. the distance, as a number that is 0 or more.
if position.dist(target) < 5:
print("arrived")
Vector.div¶
Vector.div(n: 'float') -> 'Vector'
Divide both parts by a number, in place.
| Argument | Meaning |
|---|---|
n |
the number to divide by. |
Returns. this vector, changed.
Raises.
ZeroDivisionError: if n is 0.
v = f.Vector(10, 20).div(2) # (5, 10)
See also: mult.
Vector.dot¶
Vector.dot(other) -> 'float'
The dot product of this vector and another.
It is x * other.x + y * other.y. It is 0 when the two are at right angles.
| Argument | Meaning |
|---|---|
other |
a Vector or an (x, y) pair. |
Returns. a number.
print(f.Vector(1, 0).dot(f.Vector(0, 1))) # 0.0
See also: cross, angle_between.
Vector.from_angle¶
Vector.from_angle(angle: 'float', length: 'float' = 1.0) -> 'Vector'
Make a vector that points at an angle.
0 degrees points right and 90 degrees points down, as on the screen.
| Argument | Meaning |
|---|---|
angle |
the direction in degrees. |
length |
how long the vector is. It is 1 at first. |
Returns. a new Vector.
v = f.Vector.from_angle(90, 10) # (0, 10): ten pixels down
Vector.heading¶
Vector.heading() -> 'float'
The direction the vector points in.
Returns. the angle in degrees, from -180 to 180. 0 is right and 90 is down.
print(f.Vector(0, 1).heading()) # 90.0
See also: set_heading, angle_between, from_angle.
Vector.lerp¶
Vector.lerp(target, amount: 'float') -> 'Vector'
Move part of the way towards another vector, in place.
| Argument | Meaning |
|---|---|
target |
the vector to move towards, or an (x, y) pair. |
amount |
how much of the way to go, from 0 (stay) to 1 (arrive). Numbers outside 0 to 1 go past. |
Returns. this vector, changed.
position.lerp(f.Vector(f.mouse_x, f.mouse_y), 0.1) # follow the mouse
Vector.limit¶
Vector.limit(maximum: 'float') -> 'Vector'
Shorten the vector, in place, if it is longer than a maximum length.
A vector that is already short enough is not changed.
| Argument | Meaning |
|---|---|
maximum |
the longest the vector may be. |
Returns. this vector, changed.
velocity.add(acceleration).limit(8)
Vector.mag¶
Vector.mag() -> 'float'
The length of the vector.
Returns. the length, as a number that is 0 or more.
print(f.Vector(3, 4).mag()) # 5.0
See also: mag_sq, set_mag, normalize.
Vector.mag_sq¶
Vector.mag_sq() -> 'float'
The length of the vector, squared.
It is quicker than mag(), because it needs no square root. Use it to compare lengths.
Returns. x * x + y * y.
print(f.Vector(3, 4).mag_sq()) # 25.0
See also: mag.
Vector.mult¶
Vector.mult(n: 'float') -> 'Vector'
Multiply both parts by a number, in place.
| Argument | Meaning |
|---|---|
n |
the number to multiply by. A negative number turns the vector round. |
Returns. this vector, changed.
velocity.mult(0.99) # slow down a little
See also: div, set_mag, limit.
Vector.normalize¶
Vector.normalize() -> 'Vector'
Make the length 1, keeping the direction, in place.
A zero vector stays zero.
Returns. this vector, changed.
direction = f.Vector(3, 4).normalize() # (0.6, 0.8)
See also: set_mag, limit, mag.
Vector.random_2d¶
Vector.random_2d() -> 'Vector'
Make a vector of length 1 that points in a random direction.
f.random_seed() makes the choice repeatable.
Returns. a new Vector of length 1.
push = f.Vector.random_2d().mult(5)
See also: from_angle, set_mag.
Vector.rotate¶
Vector.rotate(angle: 'float') -> 'Vector'
Turn the vector by an angle, in place.
A positive angle turns clockwise on the screen, like f.rotate().
| Argument | Meaning |
|---|---|
angle |
how far to turn, in degrees. |
Returns. this vector, changed.
v = f.Vector(1, 0).rotate(90) # about (0, 1)
See also: set_heading, heading.
Vector.set¶
Vector.set(x, y=None) -> 'Vector'
Give the vector new x and y values, in place.
| Argument | Meaning |
|---|---|
x, y |
either two numbers, or x can be a Vector or an (x, y) pair with y left out. |
Returns. this vector, changed.
Raises.
TypeError: if the values are not numbers, a Vector or an (x, y) pair.
v = f.Vector(1, 2)
v.set(10, 20)
v.set((3, 4))
Vector.set_heading¶
Vector.set_heading(angle: 'float') -> 'Vector'
Turn the vector to point at an angle, in place, keeping its length.
| Argument | Meaning |
|---|---|
angle |
the new direction in degrees. 0 is right and 90 is down. |
Returns. this vector, changed.
v = f.Vector(5, 0).set_heading(90) # about (0, 5)
Vector.set_mag¶
Vector.set_mag(length: 'float') -> 'Vector'
Give the vector a new length, in place, keeping its direction.
A zero vector stays zero, because it has no direction.
| Argument | Meaning |
|---|---|
length |
the new length. |
Returns. this vector, changed.
v = f.Vector(3, 4).set_mag(10) # (6, 8)
See also: mag, normalize, limit.
Vector.sub¶
Vector.sub(x, y=None) -> 'Vector'
Take away from the vector, in place.
| Argument | Meaning |
|---|---|
x, y |
either two numbers, or x can be a Vector or an (x, y) pair with y left out. |
Returns. this vector, changed.
Raises.
TypeError: if the values are not numbers, a Vector or an (x, y) pair.
to_mouse = f.Vector(f.mouse_x, f.mouse_y).sub(position)
FormattedString¶
FormattedString() -> 'None'
Text made of runs, where each run can have its own font, size, style, colour and so on.
Make an empty one with f.FormattedString(), then add runs with append(). Draw it with f.text() or f.text_box() in place of an ordinary string. f.text_box() gives back what did not fit as a FormattedString, and its runs keep their settings.
A setting that you leave out for a run is not fixed. It follows the drawing state when the text is drawn, so a push() or a text_size() still reaches it.
str(line) is the plain text, len(line) is the number of characters, and a + b joins two formatted strings (or a formatted string and an ordinary string) into a new one.
line = f.FormattedString()
line.append("big ", size=60, color="tomato")
line.append("and small", size=20)
f.text(line, 20, 100)
FormattedString.append¶
FormattedString.append(text: 'object', font=None, size: 'float | None' = None, style: 'str | None' = None, color=None, tracking: 'float | None' = None, features: 'dict | None' = None, variations: 'dict | None' = None) -> "'FormattedString'"
Add a run of text to the end.
Every setting you give belongs to this run. A setting you leave out follows the drawing state when the text is drawn. Numbers for the colour are read in the colour mode of the moment you call append().
| Argument | Meaning |
|---|---|
text |
what to add. It is turned into text with str(). Empty text adds no run. |
font |
a font from f.load_font(), or the path of a font file. |
size |
the text size in pixels, more than 0. |
style |
"normal", "bold", "italic" or "bold_italic". It is for the built-in font only. |
color |
the colour of this run. Any colour that f.fill() takes. |
tracking |
the extra space after every letter, in pixels. |
features |
a dictionary of OpenType features to turn on or off, like {"liga": False}. |
variations |
a dictionary of axes of a variable font, like {"wght": 700}. |
Returns. this same FormattedString, so calls can chain.
Raises.
TypeError: if font, size, tracking, features or variations is of the wrong kind.ValueError: if size is 0 or less, or style is not one of the four.
line = f.FormattedString()
line.append("Hello ", size=40).append("world", style="bold", color="navy")
FormattedString.lines¶
FormattedString.lines() -> 'list[list[tuple[str, int | None]]]'
Split the text at every new line.
funground uses it when it draws text. You do not need to call it.
Returns. a list of lines. Each line is a list of (text, run index) pieces.
See also: wrap.
FormattedString.runs¶
FormattedString.runs # property
The runs, in order.
Returns. a tuple of Run objects.
line = f.FormattedString().append("a").append("b", size=30)
print(len(line.runs)) # 2
See also: append.
FormattedString.wrap¶
FormattedString.wrap(fits) -> 'tuple[list[list[tuple[str, int | None]]], list[int]]'
Break the text into lines that fit, at spaces.
funground uses it when it draws text in a box. You do not need to call it. A word that is wider than the box is broken between letters. A blank line keeps one empty piece, so that it still has a height.
| Argument | Meaning |
|---|---|
fits |
a function that takes a candidate line, a list of (text, run index) pieces, and says whether it is narrow enough. None means no wrapping, only new lines. |
Returns. (lines, starts). lines is a list of lines, each a list of (text, run index) pieces. starts[i] is the character where line i begins.
See also: lines.
Run¶
Run(text: 'str', font: 'str | None' = None, size: 'int | None' = None, style: 'str | None' = None, color: 'Color | None' = None, tracking: 'float | None' = None, features: 'tuple | None' = None, variations: 'tuple | None' = None) -> None
One piece of a FormattedString: some text and the settings that were given for it.
You get the runs of a FormattedString from its runs property. A run is read-only. The fields are text, font (the name of the font, or None), size, style, color, tracking, features and variations. A field that is None was not given, so it follows the drawing state when the text is drawn.
line = f.FormattedString().append("Hello ", size=40).append("world", style="bold")
for run in line.runs:
print(run.text, run.size)
Run.apply¶
Run.apply(state)
Work out the drawing state for this run.
funground uses it when it draws formatted text. You do not need to call it.
| Argument | Meaning |
|---|---|
state |
a drawing state, the current settings for fill, text and so on. |
Returns. a drawing state: the one you gave, with this run's own settings put on top.
See also: with_text.
Run.with_text¶
Run.with_text(text: 'str') -> "'Run'"
Make a new run with the same settings and different text.
funground uses it when it breaks text into lines. You do not need to call it.
| Argument | Meaning |
|---|---|
text |
the new text. |
Returns. a new Run.
See also: apply.
PathBuilder¶
PathBuilder(path: 'Path | None' = None) -> 'None'
A path that you build step by step, then draw, clip with, cut, outline or measure.
You get one from f.path(), f.text_path() and f.svg_paths(). Draw it with f.draw_path(p) or use it as a clip with f.clip(p). A path does not draw anything until you do that, so you can reuse it as often as you like.
Most methods change the path and give it back, so you can chain calls: f.path().move_to(0, 0).line_to(50, 0). The building methods are move_to, line_to, curve_to, quad_to, close, rect, ellipse, circle and polygon. They change this builder. The methods that make a new shape (union, intersection, difference, xor, remove_overlap, expand_stroke, translate, scale, rotate and copy) give you a new builder and leave this one alone. The operators do the same: a | b is union, a & b is intersection, a - b and a % b are difference, and a ^ b is xor.
Only closed paths are filled. An open path is stroked.
star = f.path().polygon([(0, -50), (15, -15), (50, -10), (22, 10), (30, 45), (0, 25), (-30, 45), (-22, 10), (-50, -10), (-15, -15)])
f.translate(200, 200)
f.draw_path(star)
PathBuilder.bounds¶
PathBuilder.bounds()
The box that exactly holds the path.
Returns. (x, y, w, h), the top left corner and the size, or None when the path has no lines or curves.
x, y, w, h = f.path().circle(50, 50, 60).bounds()
See also: contains.
PathBuilder.circle¶
PathBuilder.circle(x: 'float', y: 'float', d: 'float') -> "'PathBuilder'"
Add a circle as a closed part of the path.
| Argument | Meaning |
|---|---|
x, y |
the centre. |
d |
the diameter, which is the distance across. |
Returns. this builder, changed.
p = f.path().circle(50, 50, 60)
PathBuilder.close¶
PathBuilder.close() -> "'PathBuilder'"
Join the current point back to where the part of the path began.
Only closed paths are filled.
Returns. this builder, changed.
Raises.
ValueError: if the path has no starting point yet. Begin with move_to().
p = f.path().move_to(10, 10).line_to(90, 10).line_to(50, 80).close()
See also: move_to, line_to, is_closed.
PathBuilder.contains¶
PathBuilder.contains(x: 'float', y: 'float') -> 'bool'
Whether a point is inside the shape that the path fills.
| Argument | Meaning |
|---|---|
x, y |
the point. |
Returns. True when the point is inside what is filled, otherwise False.
button = f.path().rect(10, 10, 80, 30)
if button.contains(f.mouse_x, f.mouse_y):
f.fill("gold")
See also: bounds.
PathBuilder.copy¶
PathBuilder.copy() -> "'PathBuilder'"
Make a new builder with the same path.
Building on the copy does not change the original.
Returns. a new PathBuilder.
a = f.path().move_to(0, 0).line_to(50, 0)
b = a.copy().line_to(50, 50) # a is not changed
See also: translate.
PathBuilder.curve_to¶
PathBuilder.curve_to(cx1: 'float', cy1: 'float', cx2: 'float', cy2: 'float', x: 'float', y: 'float') -> "'PathBuilder'"
Add a smooth curve from the current point to a new point (a cubic Bezier curve).
The two control points pull the curve towards them.
| Argument | Meaning |
|---|---|
cx1, cy1 |
the first control point. |
cx2, cy2 |
the second control point. |
x, y |
the end of the curve. |
Returns. this builder, changed.
Raises.
ValueError: if the path has no starting point yet. Begin with move_to().
p = f.path().move_to(10, 90).curve_to(10, 10, 90, 10, 90, 90)
PathBuilder.difference¶
PathBuilder.difference(other: "'PathBuilder'") -> "'PathBuilder'"
Make a new path of what this path covers, minus what another path covers.
| Argument | Meaning |
|---|---|
other |
another path made with f.path(). |
Returns. a new PathBuilder. Neither original is changed.
Raises.
TypeError: if other is not a path from f.path().
ring = f.path().circle(50, 50, 80).difference(f.path().circle(50, 50, 40))
See also: union, intersection, xor.
PathBuilder.ellipse¶
PathBuilder.ellipse(x: 'float', y: 'float', w: 'float', h: 'float') -> "'PathBuilder'"
Add an ellipse as a closed part of the path.
| Argument | Meaning |
|---|---|
x, y |
the centre. |
w, h |
the width and the height. |
Returns. this builder, changed.
p = f.path().ellipse(50, 50, 80, 40)
PathBuilder.expand_stroke¶
PathBuilder.expand_stroke(width: 'float', cap: 'str' = 'round', join: 'str' = 'round', miter_limit: 'float' = 10, dash=None) -> "'PathBuilder'"
Make a new path that is the outline a stroke would paint, so you can fill, cut or clip with it.
Open parts of the path are included. Overlaps are removed.
| Argument | Meaning |
|---|---|
width |
how thick the stroke is. It must be more than 0. |
cap |
the ends of open parts: "round", "square" or "butt". It is "round" at first. |
join |
the corners: "round", "miter" or "bevel". It is "round" at first. |
miter_limit |
how long a pointed corner may get, at least 1. It is 10 at first. |
dash |
None for a solid line, or a length, or a list of lengths for dashes and gaps, such as [10, 5]. It is None at first. |
Returns. a new closed PathBuilder.
Raises.
ValueError: if width, cap, join, miter_limit or dash is not allowed.
line = f.path().move_to(10, 50).line_to(90, 50)
outline = line.expand_stroke(8, cap="butt", dash=[10, 5])
See also: remove_overlap, contains.
PathBuilder.geometry¶
PathBuilder.geometry # property
The plain path data built so far.
It cannot be changed. It is what funground draws.
Returns. a Path (from funground.geometry).
See also: is_empty, is_closed.
PathBuilder.intersection¶
PathBuilder.intersection(other: "'PathBuilder'") -> "'PathBuilder'"
Make a new path of only what both paths cover.
| Argument | Meaning |
|---|---|
other |
another path made with f.path(). |
Returns. a new PathBuilder. Neither original is changed.
Raises.
TypeError: if other is not a path from f.path().
lens = f.path().circle(40, 50, 60).intersection(f.path().circle(70, 50, 60))
See also: union, difference, xor.
PathBuilder.is_closed¶
PathBuilder.is_closed # property
Whether every part of the path that draws something is closed.
Only closed paths are filled. Open ones are stroked.
Returns. True when the path is closed, otherwise False. An empty path is not closed.
print(f.path().circle(50, 50, 60).is_closed) # True
PathBuilder.is_empty¶
PathBuilder.is_empty # property
Whether nothing has been added to the path.
Returns. True when the path has no parts, otherwise False.
print(f.path().is_empty) # True
See also: is_closed.
PathBuilder.line_to¶
PathBuilder.line_to(x: 'float', y: 'float') -> "'PathBuilder'"
Add a straight line from the current point to a new point.
| Argument | Meaning |
|---|---|
x, y |
the end of the line. |
Returns. this builder, changed.
Raises.
ValueError: if the path has no starting point yet. Begin with move_to().
p = f.path().move_to(10, 10).line_to(90, 10).line_to(50, 80).close()
See also: move_to, curve_to, close.
PathBuilder.move_to¶
PathBuilder.move_to(x: 'float', y: 'float') -> "'PathBuilder'"
Start a new part of the path at a point, without drawing.
| Argument | Meaning |
|---|---|
x, y |
the point. |
Returns. this builder, changed.
p = f.path().move_to(10, 10).line_to(90, 10)
PathBuilder.polygon¶
PathBuilder.polygon(points) -> "'PathBuilder'"
Add a closed shape through a list of corners.
| Argument | Meaning |
|---|---|
points |
a list of at least 3 (x, y) pairs. |
Returns. this builder, changed.
Raises.
ValueError: if there are fewer than 3 points.
p = f.path().polygon([(50, 10), (90, 80), (10, 80)])
PathBuilder.quad_to¶
PathBuilder.quad_to(cx: 'float', cy: 'float', x: 'float', y: 'float') -> "'PathBuilder'"
Add a curve from the current point to a new point, pulled towards one control point (a quadratic Bezier curve).
| Argument | Meaning |
|---|---|
cx, cy |
the control point. |
x, y |
the end of the curve. |
Returns. this builder, changed.
Raises.
ValueError: if the path has no starting point yet. Begin with move_to().
p = f.path().move_to(10, 90).quad_to(50, 10, 90, 90)
PathBuilder.rect¶
PathBuilder.rect(x: 'float', y: 'float', w: 'float', h: 'float', *radii: 'float') -> "'PathBuilder'"
Add a rectangle as a closed part of the path.
| Argument | Meaning |
|---|---|
x, y |
the top left corner. |
w, h |
the width and the height. |
radii |
no radius, one radius for all four corners, or four radii (top left, top right, bottom right, bottom left). A radius is cut down to half the shorter side. |
Returns. this builder, changed.
Raises.
ValueError: if the number of radii is not 0, 1 or 4, or a radius is negative.
p = f.path().rect(10, 10, 80, 50, 8)
See also: ellipse, circle, polygon.
PathBuilder.remove_overlap¶
PathBuilder.remove_overlap() -> "'PathBuilder'"
Make a new path that fills the same area, drawn as one clean outline with no overlapping parts.
Returns. a new PathBuilder. This one is not changed.
clean = f.path().circle(40, 50, 60).circle(70, 50, 60).remove_overlap()
See also: union, expand_stroke.
PathBuilder.rotate¶
PathBuilder.rotate(degrees: 'float', cx: 'float' = 0, cy: 'float' = 0) -> "'PathBuilder'"
Make a new path that is this path turned about a point.
| Argument | Meaning |
|---|---|
degrees |
how far to turn it. A positive angle turns clockwise on the screen. |
cx, cy |
the point to turn about. It is (0, 0) at first. |
Returns. a new PathBuilder. This one is not changed.
bar = f.path().rect(-40, -5, 80, 10).rotate(45)
PathBuilder.scale¶
PathBuilder.scale(sx: 'float', sy: 'float | None' = None) -> "'PathBuilder'"
Make a new path that is this path scaled about the point (0, 0).
| Argument | Meaning |
|---|---|
sx |
how much to scale across. 2 makes it twice as wide. |
sy |
how much to scale down. If you leave it out, it is the same as sx. |
Returns. a new PathBuilder. This one is not changed.
big = f.path().circle(0, 0, 40).scale(2)
PathBuilder.translate¶
PathBuilder.translate(dx: 'float', dy: 'float') -> "'PathBuilder'"
Make a new path that is this path moved.
| Argument | Meaning |
|---|---|
dx, dy |
how far to move it across and down. |
Returns. a new PathBuilder. This one is not changed.
moved = f.path().circle(0, 0, 40).translate(100, 100)
PathBuilder.union¶
PathBuilder.union(other: "'PathBuilder'") -> "'PathBuilder'"
Make a new path of everything that either path covers.
| Argument | Meaning |
|---|---|
other |
another path made with f.path(). |
Returns. a new PathBuilder. Neither original is changed.
Raises.
TypeError: if other is not a path from f.path().
both = f.path().circle(40, 50, 60).union(f.path().circle(70, 50, 60))
See also: intersection, difference, xor.
PathBuilder.xor¶
PathBuilder.xor(other: "'PathBuilder'") -> "'PathBuilder'"
Make a new path of what exactly one of the two paths covers.
| Argument | Meaning |
|---|---|
other |
another path made with f.path(). |
Returns. a new PathBuilder. Neither original is changed.
Raises.
TypeError: if other is not a path from f.path().
p = f.path().circle(40, 50, 60).xor(f.path().circle(70, 50, 60))
See also: union, intersection, difference.
Mark¶
Mark() -> 'None'
A drawing kept as a value, which you can place as often as you like.
Make one with a block: with f.mark() as m:. The drawing calls inside the block are recorded, not
drawn, each with the fill, stroke and font it was drawn with. Or make one from a path with
f.mark(path, fill=..., stroke=...). Then m.place(x, y) draws it, with its own origin (0, 0) at
(x, y), and with any scale, rotation and opacity.
A mark keeps the drawing steps, not pixels. It is sharp at any size on screen, and real shapes and text in a saved PDF or SVG. A finished mark cannot be changed, and placing it never changes it. To make a variation, make a new mark, for example in a function that takes the parts that vary.
A mark's block starts with the current style and with no transform, so its coordinates are its own. When the block ends, the transform, style and clip are exactly as before it, even if the block raised an error. Inside the block you cannot open a layer, make controls, save files or read pixels.
with f.mark() as flower:
for a in range(0, 360, 60):
with f.saved_state():
f.rotate(a)
f.fill("tomato")
f.ellipse(0, -30, 18, 40)
f.fill("gold")
f.circle(0, 0, 24)
flower.place(150, 200)
flower.place(400, 200, scale=0.5, rotate=15, opacity=0.6)
Mark.bounds¶
Mark.bounds() -> 'tuple[float, float, float, float] | None'
Return the area the mark covers, in its own coordinates.
The area includes stroke widths, joins and caps, and text. It is never smaller than what the mark paints, and at most a tiny bit larger. The mark's origin (0, 0) is where place() puts (x, y), so x and y are often negative.
Returns. (x, y, w, h): the left, the top, the width and the height. None for an empty mark.
Raises.
RuntimeError: the mark is unfinished.
x, y, w, h = flower.bounds()
See also: width, height, is_empty, place.
Mark.height¶
Mark.height # property
The height of the area the mark covers, from bounds(); 0 for an empty mark.
Raises.
RuntimeError: the mark is unfinished.
f.size(400, int(flower.height) + 20)
Mark.is_empty¶
Mark.is_empty # property
Whether the mark paints nothing.
Returns. True when the mark paints nothing (and bounds() is None), otherwise False.
Raises.
RuntimeError: the mark is unfinished.
if not flower.is_empty:
flower.place(100, 100)
See also: bounds.
Mark.place¶
Mark.place(x: 'float', y: 'float', *, scale: 'float | tuple[float, float] | None' = None, rotate: 'float' = 0, opacity: 'float' = 1, anchor: 'str' = 'origin', width: 'float | None' = None, height: 'float | None' = None, style: 'str' = 'own') -> 'None'
Draw the mark now, with its origin at (x, y).
The mark follows the current transform and clip, so it works inside saved_state() and translate(), inside a layer block, and inside another mark's block (marks can be made of marks). The mark itself never changes.
The mark is moved to (x, y), then turned by rotate, then sized by scale, all around the anchor point. So with anchor="center" the centre of the mark lands on (x, y) and stays there.
width and height size the mark from its bounds, keeping its proportions. Give one of them, and the mark is scaled to that width or height. Give both, and the mark is scaled to fit inside a box of that size and centred in it; then anchor places the box ("origin" counts as "top-left"). This happens before rotate.
style="own" (the default) draws every part with the style it was recorded with. style="current" draws every part with the style in force now instead, so f.fill("black") then place(..., style="current") gives a black silhouette. It replaces, for each part: on closed shapes and paths, the fill, stroke, stroke width, caps, joins, miter limit and dash (a gradient fill too, and no_fill() or no_stroke() now take the fill or stroke away); on lines and open paths, the stroke, its width, caps and dash; on text, its colour, which comes from the fill as text() does (font, size and shaping stay). Pictures are unchanged. Marks inside the mark follow the same rules. Blend mode and opacity never come from the current style: opacity is the argument here.
| Argument | Meaning |
|---|---|
x, y |
where the anchor point lands. |
scale |
one number to size both ways, or two as (sx, sy). Left out (None), the mark keeps its own size. A negative number mirrors it. 0 is an error. |
rotate |
degrees to turn, clockwise on screen. |
opacity |
from 0 (invisible) to 1 (solid, the default). The whole mark fades as one piece, so its overlapping parts do not show through each other. |
anchor |
which point of the mark lands on (x, y). "origin" (the default) is the mark's own (0, 0). The others use bounds(): "center", "top-left", "top", "top-right", "left", "right", "bottom-left", "bottom" and "bottom-right". |
width, height |
the size to make the mark, in pixels, keeping its proportions (see above). They cannot be used with scale. |
style |
"own" (the default) or "current" (see above). |
Raises.
RuntimeError: the mark is unfinished, or still being recorded.ValueError: the anchor or style is unknown, the mark is empty and the anchor is not "origin" or a width or height is given, opacity is outside 0 to 1, scale is 0, width or height is not above 0, or scale is given with width or height.TypeError: a number is not a number.
flower.place(150, 200)
flower.place(400, 200, scale=0.5, rotate=15, opacity=0.6)
flower.place(200, 150, anchor="center", width=80)
f.fill("black")
flower.place(300, 200, style="current")
See also: mark, bounds, saved_state.
Mark.width¶
Mark.width # property
The width of the area the mark covers, from bounds(); 0 for an empty mark.
Raises.
RuntimeError: the mark is unfinished.
gap = flower.width + 10
Area¶
Area(left: 'float', top: 'float', width: 'float', height: 'float') -> 'None'
A rectangle on the canvas, kept as a value.
An area has a left edge, a top edge, a width and a height, in canvas units. It also gives its right and bottom edges and its centre. Make one with f.area(x, y, w, h). f.ground, f.ground.content and every cell of a grid are areas too, so anything that takes an area takes them.
An area never changes. inset() and grid() give new values. An area may be 0 wide or 0 high, but never less.
panel = f.area(40, 40, 300, 200)
f.rect(panel.left, panel.top, panel.width, panel.height)
f.circle(panel.cx, panel.cy, 20)
Area.bottom¶
Area.bottom # property
The y of the area's bottom edge: top + height.
Area.cx¶
Area.cx # property
The x of the area's centre.
Area.cy¶
Area.cy # property
The y of the area's centre.
Area.grid¶
Area.grid(cols: 'int', rows: 'int', *, gutter: 'float | tuple' = 0) -> "'Grid'"
Return a grid of equal cells over this area.
It is the same as f.grid(cols, rows, gutter=gutter, area=this_area). A cell is an area too, so a cell can hold a grid of its own.
| Argument | Meaning |
|---|---|
cols |
how many columns. A whole number above 0. |
rows |
how many rows. A whole number above 0. |
gutter |
the gap between cells. One number is used both across and down. A tuple (column_gutter, row_gutter) sets them apart. The default is 0. |
Returns. A Grid. It goes row by row, left to right, like a list of cells.
Raises.
TypeError: cols or rows is not a whole number, or a gutter is not a number.ValueError: cols or rows is 0 or less, a gutter is negative, or the gutters leave no room for the cells.
for cell in f.ground.content.grid(4, 3, gutter=8):
f.circle(cell.cx, cell.cy, cell.width * 0.5)
Area.height¶
Area.height # property
How high the area is: 0 or more.
Area.inset¶
Area.inset(all: 'float | None' = None, *, top: 'float | None' = None, right: 'float | None' = None, bottom: 'float | None' = None, left: 'float | None' = None) -> "'Area'"
Return a smaller area inside this one.
One number moves every edge in by that much. top=, right=, bottom= and left= move one edge each, and a side you name wins over the one number. A side you leave out does not move. A negative number moves that edge out instead, which makes the area bigger, as for a bleed around a page. The area itself does not change.
| Argument | Meaning |
|---|---|
all |
how far to move every edge in. The default None moves none of them. |
top |
how far to move the top edge down. The default None uses all. |
right |
how far to move the right edge left. The default None uses all. |
bottom |
how far to move the bottom edge up. The default None uses all. |
left |
how far to move the left edge right. The default None uses all. |
Returns. A new Area. It may be 0 wide or 0 high.
Raises.
TypeError: a side is not a number.ValueError: the insets are more than the width or the height, which would leave a negative size.
page = f.ground.inset(20)
header = page.inset(bottom=page.height - 60)
Area.left¶
Area.left # property
The x of the area's left edge.
Area.right¶
Area.right # property
The x of the area's right edge: left + width.
Area.top¶
Area.top # property
The y of the area's top edge.
Area.width¶
Area.width # property
How wide the area is: 0 or more.
Ground¶
Ground(left: 'float', top: 'float', width: 'float', height: 'float', margin: 'tuple' = (0, 0, 0, 0)) -> 'None'
The canvas as an area, with its margins.
f.ground is the whole canvas and f.ground.content is the part inside the margins. Both are areas, so they have left, top, right, bottom, width, height, cx and cy, and inset() and grid(). f.ground is made fresh each time you read it, so it always matches the canvas. You do not make a Ground yourself: set the margins with f.size(..., margin=...).
f.size("A5", margin=f.mm(12))
f.background("linen")
c = f.ground.content
f.rect(c.left, c.top, c.width, c.height)
Ground.bottom¶
Ground.bottom # property
The y of the area's bottom edge: top + height.
Ground.content¶
Ground.content # property
The area inside the margins. With no margin it is this same object, and its own margin is (0, 0, 0, 0).
Ground.cx¶
Ground.cx # property
The x of the area's centre.
Ground.cy¶
Ground.cy # property
The y of the area's centre.
Ground.grid¶
Ground.grid(cols: 'int', rows: 'int', *, gutter: 'float | tuple' = 0) -> "'Grid'"
Return a grid of equal cells over this area.
It is the same as f.grid(cols, rows, gutter=gutter, area=this_area). A cell is an area too, so a cell can hold a grid of its own.
| Argument | Meaning |
|---|---|
cols |
how many columns. A whole number above 0. |
rows |
how many rows. A whole number above 0. |
gutter |
the gap between cells. One number is used both across and down. A tuple (column_gutter, row_gutter) sets them apart. The default is 0. |
Returns. A Grid. It goes row by row, left to right, like a list of cells.
Raises.
TypeError: cols or rows is not a whole number, or a gutter is not a number.ValueError: cols or rows is 0 or less, a gutter is negative, or the gutters leave no room for the cells.
for cell in f.ground.content.grid(4, 3, gutter=8):
f.circle(cell.cx, cell.cy, cell.width * 0.5)
Ground.height¶
Ground.height # property
How high the area is: 0 or more.
Ground.inset¶
Ground.inset(all: 'float | None' = None, *, top: 'float | None' = None, right: 'float | None' = None, bottom: 'float | None' = None, left: 'float | None' = None) -> "'Area'"
Return a smaller area inside this one.
One number moves every edge in by that much. top=, right=, bottom= and left= move one edge each, and a side you name wins over the one number. A side you leave out does not move. A negative number moves that edge out instead, which makes the area bigger, as for a bleed around a page. The area itself does not change.
| Argument | Meaning |
|---|---|
all |
how far to move every edge in. The default None moves none of them. |
top |
how far to move the top edge down. The default None uses all. |
right |
how far to move the right edge left. The default None uses all. |
bottom |
how far to move the bottom edge up. The default None uses all. |
left |
how far to move the left edge right. The default None uses all. |
Returns. A new Area. It may be 0 wide or 0 high.
Raises.
TypeError: a side is not a number.ValueError: the insets are more than the width or the height, which would leave a negative size.
page = f.ground.inset(20)
header = page.inset(bottom=page.height - 60)
Ground.left¶
Ground.left # property
The x of the area's left edge.
Ground.margin¶
Ground.margin # property
The margins as (top, right, bottom, left). (0, 0, 0, 0) when there are none.
Ground.right¶
Ground.right # property
The x of the area's right edge: left + width.
Ground.top¶
Ground.top # property
The y of the area's top edge.
Ground.width¶
Ground.width # property
How wide the area is: 0 or more.
Cell¶
Cell(left: 'float', top: 'float', width: 'float', height: 'float', col: 'int', row: 'int', index: 'int') -> 'None'
One cell of a grid: an area that knows its column, row and place in the grid.
A cell has everything an area has (left, top, width, height, cx, cy and so on), plus col, row and index, all counted from 0. A cell can be the area of another grid, so grids nest.
for cell in f.grid(3, 2, gutter=10):
if cell.row == 0:
f.circle(cell.cx, cell.cy, cell.width * 0.5)
Cell.bottom¶
Cell.bottom # property
The y of the area's bottom edge: top + height.
Cell.col¶
Cell.col # property
The cell's column, counted from 0 at the left.
Cell.cx¶
Cell.cx # property
The x of the area's centre.
Cell.cy¶
Cell.cy # property
The y of the area's centre.
Cell.grid¶
Cell.grid(cols: 'int', rows: 'int', *, gutter: 'float | tuple' = 0) -> "'Grid'"
Return a grid of equal cells over this area.
It is the same as f.grid(cols, rows, gutter=gutter, area=this_area). A cell is an area too, so a cell can hold a grid of its own.
| Argument | Meaning |
|---|---|
cols |
how many columns. A whole number above 0. |
rows |
how many rows. A whole number above 0. |
gutter |
the gap between cells. One number is used both across and down. A tuple (column_gutter, row_gutter) sets them apart. The default is 0. |
Returns. A Grid. It goes row by row, left to right, like a list of cells.
Raises.
TypeError: cols or rows is not a whole number, or a gutter is not a number.ValueError: cols or rows is 0 or less, a gutter is negative, or the gutters leave no room for the cells.
for cell in f.ground.content.grid(4, 3, gutter=8):
f.circle(cell.cx, cell.cy, cell.width * 0.5)
Cell.height¶
Cell.height # property
How high the area is: 0 or more.
Cell.index¶
Cell.index # property
The cell's place in the grid, counted from 0, row by row: row * column_count + col.
Cell.inset¶
Cell.inset(all: 'float | None' = None, *, top: 'float | None' = None, right: 'float | None' = None, bottom: 'float | None' = None, left: 'float | None' = None) -> "'Area'"
Return a smaller area inside this one.
One number moves every edge in by that much. top=, right=, bottom= and left= move one edge each, and a side you name wins over the one number. A side you leave out does not move. A negative number moves that edge out instead, which makes the area bigger, as for a bleed around a page. The area itself does not change.
| Argument | Meaning |
|---|---|
all |
how far to move every edge in. The default None moves none of them. |
top |
how far to move the top edge down. The default None uses all. |
right |
how far to move the right edge left. The default None uses all. |
bottom |
how far to move the bottom edge up. The default None uses all. |
left |
how far to move the left edge right. The default None uses all. |
Returns. A new Area. It may be 0 wide or 0 high.
Raises.
TypeError: a side is not a number.ValueError: the insets are more than the width or the height, which would leave a negative size.
page = f.ground.inset(20)
header = page.inset(bottom=page.height - 60)
Cell.left¶
Cell.left # property
The x of the area's left edge.
Cell.right¶
Cell.right # property
The x of the area's right edge: left + width.
Cell.row¶
Cell.row # property
The cell's row, counted from 0 at the top.
Cell.top¶
Cell.top # property
The y of the area's top edge.
Cell.width¶
Cell.width # property
How wide the area is: 0 or more.
Grid¶
Grid(area, cols: 'int', rows: 'int', gutter: 'float | tuple' = 0) -> 'None'
An area divided into equal cells, with gutters between them.
f.grid() and area.grid() make one. A grid works like a list of its cells, row by row, left to right: a for loop goes through them, len() counts them, g[0] is the top-left cell and g[-1] the last one, and g[2:5] gives a list. It also finds cells by column and row, joins cells into larger areas, and draws guide lines to help you see the layout.
A grid never changes. Columns, rows and cells are all counted from 0.
g = f.grid(4, 3, gutter=10)
for cell in g:
f.circle(cell.cx, cell.cy, cell.width * 0.6)
banner = g.span(0, 0, cols=4)
g.show()
Grid.area¶
Grid.area # property
The area the grid divides.
Grid.cell¶
Grid.cell(col: 'int', row: 'int') -> 'Cell'
Return the cell at a column and a row.
Both count from 0, so g.cell(0, 0) is the top-left cell. It is the same cell as g[row * g.column_count + col].
| Argument | Meaning |
|---|---|
col |
the column, from 0 at the left. |
row |
the row, from 0 at the top. |
Returns. The Cell.
Raises.
TypeError: col or row is not a whole number.IndexError: there is no such column or row. The message says which ones there are.
corner = f.grid(3, 3).cell(2, 0)
f.rect(corner.left, corner.top, corner.width, corner.height)
See also: span, columns, rows.
Grid.column_count¶
Grid.column_count # property
How many columns the grid has.
Grid.columns¶
Grid.columns # property
The columns as a list of areas, left to right, each as high as the whole grid.
Grid.gutter¶
Grid.gutter # property
The gaps between cells, as (column_gutter, row_gutter).
Grid.row_count¶
Grid.row_count # property
How many rows the grid has.
Grid.rows¶
Grid.rows # property
The rows as a list of areas, top to bottom, each as wide as the whole grid.
Grid.show¶
Grid.show(*, color='#1e90ff', in_files: 'bool' = False) -> 'None'
Draw thin guide lines that show the grid: the outline of its area and of every cell.
Guides help you see a layout while you work. They are drawn over everything else in the window, and they are not part of the picture: they are left out of every saved file (PNG, PDF, SVG, f.keep(), save_frames(), GIFs and movies), and get() and pixels do not see them. Give in_files=True to make them ordinary drawing instead, so they are saved too; then later drawing can cover them.
The lines are 1 unit wide and follow the current transform. show() does not change the fill, stroke, transform, clip or anything else. In an animated sketch the guides last one frame, so call show() in draw().
| Argument | Meaning |
|---|---|
color |
the colour of the lines. The default is a clear blue. |
in_files |
True draws the guides as ordinary drawing, so saved files have them. The default False shows them only in the window. |
Raises.
RuntimeError: it is used inside awith f.mark()block without in_files=True, or before f.size().
g = f.grid(4, 3, gutter=10)
g.show()
g.show(color="red", in_files=True)
Grid.span¶
Grid.span(col: 'int', row: 'int', cols: 'int' = 1, rows: 'int' = 1) -> 'Area'
Return one area that covers several cells, with the gutters between them.
The area starts at the cell (col, row) and is cols cells wide and rows cells high. The gutters inside it are part of it, so its edges line up with the cells around it.
| Argument | Meaning |
|---|---|
col |
the column of the top-left cell, from 0. |
row |
the row of the top-left cell, from 0. |
cols |
how many columns it covers. The default is 1. |
rows |
how many rows it covers. The default is 1. |
Returns. A new Area.
Raises.
TypeError: a value is not a whole number.ValueError: cols or rows is less than 1.IndexError: the cells it would cover are not all in the grid. The message says which ones there are.
g = f.grid(4, 3, gutter=10)
title = g.span(0, 0, cols=4)
f.rect(title.left, title.top, title.width, title.height)
See also: cell, columns, rows.
Path¶
Path(segments: 'tuple[Segment, ...]' = ()) -> None
The plain data of a path: a fixed list of moves, lines, curves and closes.
You get one from PathBuilder.geometry. Most sketches never need it, because f.path() gives you a PathBuilder with friendlier methods. A Path cannot be changed. Every method that builds returns a new Path and leaves the first alone.
The parts are in Path.segments, a tuple. Each part is a tuple: ("move", (x, y)), ("line", (x, y)), ("cubic", (cx1, cy1), (cx2, cy2), (x, y)) or ("close",). A Path is also a sequence: len(p) counts the parts and you can loop over them. y grows downward and angles are in degrees.
p = f.path().circle(50, 50, 60).geometry
print(len(p), p.is_closed)
Path.arc_to¶
Path.arc_to(cx: 'float', cy: 'float', rx: 'float', ry: 'float', start: 'float', stop: 'float') -> "'Path'"
Make a new path with an arc of an ellipse added.
The point at angle a is (cx + rx * cos a, cy + ry * sin a), so 0 degrees is right and 90 degrees is straight down. The arc begins with a line from the current point to the start of the arc, or with a move if the path is empty.
| Argument | Meaning |
|---|---|
cx, cy |
the centre of the ellipse. |
rx, ry |
the radii across and down. |
start, stop |
the angles where the arc begins and ends, in degrees, turning clockwise on the screen. If stop is not above start, only the line to the start is added. |
Returns. a new Path.
Path.bounds¶
Path.bounds() -> 'tuple[float, float, float, float] | None'
The box that holds every point of the path, including curve control points.
Because it counts control points, a curved path may have a smaller real extent. PathBuilder.bounds() gives the exact one.
Returns. (min_x, min_y, max_x, max_y), or None for an empty path.
See also: points.
Path.close¶
Path.close() -> "'Path'"
Make a new path with the current part closed.
Returns. a new Path.
Path.cubic_to¶
Path.cubic_to(c1x: 'float', c1y: 'float', c2x: 'float', c2y: 'float', x: 'float', y: 'float') -> "'Path'"
Make a new path with a cubic Bezier curve added.
| Argument | Meaning |
|---|---|
c1x, c1y |
the first control point. |
c2x, c2y |
the second control point. |
x, y |
the end of the curve. |
Returns. a new Path.
Path.current_point¶
Path.current_point() -> 'Point'
The point where the path now ends.
Returns. an (x, y) pair.
Raises.
ValueError: if the path is empty, so it has no current point.
Path.ellipse¶
Path.ellipse(cx: 'float', cy: 'float', rx: 'float', ry: 'float') -> "'Path'"
Make a new path that is one closed ellipse.
It is built from four curves, and is within about 0.03 per cent of a true ellipse.
| Argument | Meaning |
|---|---|
cx, cy |
the centre. |
rx, ry |
the radii across and down. |
Returns. a new Path.
See also: rect.
Path.is_closed¶
Path.is_closed # property
Whether every part that draws something ends with a close.
Only closed shapes are filled. A move at the end on its own draws nothing, and does not count.
Returns. True when the path is closed. An empty path is not closed.
Path.is_empty¶
Path.is_empty # property
Whether the path has no parts.
Returns. True when there are no parts, otherwise False.
See also: is_closed.
Path.line_to¶
Path.line_to(x: 'float', y: 'float') -> "'Path'"
Make a new path with a straight line added.
| Argument | Meaning |
|---|---|
x, y |
the end of the line. |
Returns. a new Path.
Path.move_to¶
Path.move_to(x: 'float', y: 'float') -> "'Path'"
Make a new path with a new part started at a point.
| Argument | Meaning |
|---|---|
x, y |
the point. |
Returns. a new Path.
Path.points¶
Path.points() -> 'Iterator[Point]'
Every point in the path, including curve control points, in order.
Returns. an iterator of (x, y) pairs.
See also: bounds, current_point.
Path.quad_to¶
Path.quad_to(cx: 'float', cy: 'float', x: 'float', y: 'float') -> "'Path'"
Make a new path with a quadratic Bezier curve added.
It is stored as the same curve written as a cubic, so a path only needs one kind of curve.
| Argument | Meaning |
|---|---|
cx, cy |
the control point. |
x, y |
the end of the curve. |
Returns. a new Path.
Raises.
ValueError: if the path has no current point.
See also: cubic_to.
Path.rect¶
Path.rect(x: 'float', y: 'float', w: 'float', h: 'float') -> "'Path'"
Make a new path that is one closed rectangle.
| Argument | Meaning |
|---|---|
x, y |
the top left corner. |
w, h |
the width and the height. |
Returns. a new Path.
See also: rounded_rect, ellipse.
Path.rounded_rect¶
Path.rounded_rect(x: 'float', y: 'float', w: 'float', h: 'float', radii: 'tuple[float, float, float, float]') -> "'Path'"
Make a new path that is one closed rectangle with rounded corners.
| Argument | Meaning |
|---|---|
x, y |
the top left corner. |
w, h |
the width and the height. |
radii |
four corner radii: top left, top right, bottom right, bottom left. They must already be no more than half the shorter side. A radius of 0 gives a sharp corner. |
Returns. a new Path.
See also: rect.
Path.transformed¶
Path.transformed(t: 'Transform') -> "'Path'"
Make a new path with every point moved by a transform.
| Argument | Meaning |
|---|---|
t |
a Transform from funground.geometry, which turns a point (x, y) into (ax + cy + e, bx + dy + f). |
Returns. a new Path. It is this path itself when the transform changes nothing.
See also: bounds.
Font¶
Font(name: 'str') -> 'None'
A font that you can pass to f.text_font(), or ask questions about.
You get one from f.load_font(path) (a font file), f.system_font(name) (a font installed on this computer) or f.current_font() (the font in use now). The font's name, which funground uses to find it, is font.name: the file name, or the file name with ~2, ~3 and so on when two files share a name. A font from a collection (.ttc) file has # and the face number after the file name.
Two fonts are equal when they have the same name.
face = f.load_font("MyFont.ttf") # your own file
f.text_font(face)
print(face.family(), face.style())
Font.contains¶
Font.contains(text: 'str') -> 'bool'
Whether the font has a shape for every character of some text.
Spaces count. A new line is ignored.
| Argument | Meaning |
|---|---|
text |
the text to check. |
Returns. True when every character has a shape in the font, otherwise False.
if not f.current_font().contains("hello"):
print("some letters are missing")
Font.family¶
Font.family() -> 'str'
The family name of the font, as written in the font file.
Returns. the name, such as "DejaVu Sans".
print(f.current_font().family())
See also: style, variations, features.
Font.features¶
Font.features() -> 'list[str]'
The OpenType features that the font has.
Use f.text_features() to turn one on or off.
Returns. a sorted list of four-letter tags, such as ["kern", "liga"].
print(f.current_font().features())
See also: variations, contains.
Font.style¶
Font.style() -> 'str'
The style name of the font, as written in the font file.
Returns. the name, such as "Bold" or "Book".
print(f.current_font().style())
See also: family.
Font.variations¶
Font.variations() -> 'dict[str, tuple[float, float, float]]'
The axes of a variable font and how far each one can go.
An ordinary font has none. Use f.font_variations() to set an axis.
Returns. a dictionary from the axis tag, such as "wght", to (minimum, default, maximum). It is {} for a font that is not variable.
for tag, (low, normal, high) in f.current_font().variations().items():
print(tag, low, normal, high)
Control¶
Control(label: 'str | None') -> 'None'
What a slider, a checkbox and a button share: a label.
You do not make a Control yourself. Use f.create_slider(), f.create_checkbox() and f.create_button(). They sit in a panel below the canvas, in the order you made them. Make them in setup() and read them in draw().
Control.label¶
Control.label # property
The text shown beside the control.
Returns. the label, or "" when it has none.
box = f.create_checkbox("Show grid")
print(box.label) # Show grid
See also: create_slider, create_checkbox, create_button.
Control.visible¶
Control.visible(flag=<not given>)
Read whether the control is shown, or hide or show it.
A hidden control is not drawn and cannot be pressed, dragged or clicked. Its space in the panel stays blank, so the panel keeps its height and nothing moves when you show it again. It keeps its value. value(), checked() and clicked() still work while it is hidden. You can call visible() in draw().
| Argument | Meaning |
|---|---|
flag |
True to show the control, False to hide it. Leave it out to read. |
Returns. True or False when you leave flag out. None when you set it.
size = f.create_slider(10, 100, 50)
size.visible(False)
print(size.visible()) # False
See also: value, checked, create_slider.
Slider¶
Slider(low, high, value=None, step=None, label=None) -> 'None'
A control that holds a number between a low and a high end. You drag it in the panel below the canvas.
You get one from f.create_slider(low, high, value, step, label). Make it in setup(). In draw(), read it with slider.value(). It starts at value, or at low if you give none. If you give a step, the value moves in jumps of that size from low. Whole numbers in give whole numbers out, when low and step are both whole.
size_slider = f.create_slider(10, 200, 50, label="size")
def draw():
f.circle(200, 200, size_slider.value())
Slider.high¶
Slider.high # property
The largest value the slider can have.
Returns. the high end, as given.
Slider.label¶
Slider.label # property
The text shown beside the control.
Returns. the label, or "" when it has none.
box = f.create_checkbox("Show grid")
print(box.label) # Show grid
See also: create_slider, create_checkbox, create_button.
Slider.low¶
Slider.low # property
The smallest value the slider can have.
Returns. the low end, as given.
Slider.step¶
Slider.step # property
The size of each jump of the slider.
Returns. the step as given, or None when the slider moves smoothly.
Slider.text¶
Slider.text() -> 'str'
The value as the panel shows it.
Returns. the value as text. A step of 0.1 gives one decimal place.
s = f.create_slider(0, 1, 0.5, 0.1)
print(s.text()) # 0.5
See also: value.
Slider.value¶
Slider.value(v=<not given>)
Read the slider's number, or set it.
With no argument it reads the value. With a number it sets the value, which is kept between low and high and rounded to the step. It changes in the panel too.
| Argument | Meaning |
|---|---|
v |
the new value. Leave it out to read the value. |
Returns. the value when you leave v out. None when you set it.
Raises.
TypeError: if v is not a number.ValueError: if v is infinity or nan.
s = f.create_slider(0, 1, 0.5)
print(s.value()) # 0.5
s.value(0.8)
Slider.visible¶
Slider.visible(flag=<not given>)
Read whether the control is shown, or hide or show it.
A hidden control is not drawn and cannot be pressed, dragged or clicked. Its space in the panel stays blank, so the panel keeps its height and nothing moves when you show it again. It keeps its value. value(), checked() and clicked() still work while it is hidden. You can call visible() in draw().
| Argument | Meaning |
|---|---|
flag |
True to show the control, False to hide it. Leave it out to read. |
Returns. True or False when you leave flag out. None when you set it.
size = f.create_slider(10, 100, 50)
size.visible(False)
print(size.visible()) # False
See also: value, checked, create_slider.
Checkbox¶
Checkbox(label, checked=False) -> 'None'
A box that is ticked or not. You click it in the panel below the canvas.
You get one from f.create_checkbox(label, checked). Make it in setup(). In draw(), read it with box.checked().
grid = f.create_checkbox("Show grid", True)
def draw():
if grid.checked():
f.line(0, 200, 400, 200)
Checkbox.checked¶
Checkbox.checked(b=<not given>)
Read whether the box is ticked, or tick or untick it.
| Argument | Meaning |
|---|---|
b |
True to tick it, False to untick it. Leave it out to read. |
Returns. True or False when you leave b out. None when you set it.
box = f.create_checkbox("Fill", True)
if box.checked():
box.checked(False)
See also: create_checkbox.
Checkbox.label¶
Checkbox.label # property
The text shown beside the control.
Returns. the label, or "" when it has none.
box = f.create_checkbox("Show grid")
print(box.label) # Show grid
See also: create_slider, create_checkbox, create_button.
Checkbox.visible¶
Checkbox.visible(flag=<not given>)
Read whether the control is shown, or hide or show it.
A hidden control is not drawn and cannot be pressed, dragged or clicked. Its space in the panel stays blank, so the panel keeps its height and nothing moves when you show it again. It keeps its value. value(), checked() and clicked() still work while it is hidden. You can call visible() in draw().
| Argument | Meaning |
|---|---|
flag |
True to show the control, False to hide it. Leave it out to read. |
Returns. True or False when you leave flag out. None when you set it.
size = f.create_slider(10, 100, 50)
size.visible(False)
print(size.visible()) # False
See also: value, checked, create_slider.
Button¶
Button(label) -> 'None'
A button that you click in the panel below the canvas.
You get one from f.create_button(label). Make it in setup(). In draw(), ask button.clicked(). It is True once for each click.
again = f.create_button("Again")
def draw():
if again.clicked():
f.background("white")
Button.clicked¶
Button.clicked() -> 'bool'
Whether the button was clicked since you last asked.
It is True once for each click. Asking again straight away gives False.
Returns. True if there was a click since the last time you asked, otherwise False.
if again.clicked():
f.background("white")
See also: create_button.
Button.label¶
Button.label # property
The text shown beside the control.
Returns. the label, or "" when it has none.
box = f.create_checkbox("Show grid")
print(box.label) # Show grid
See also: create_slider, create_checkbox, create_button.
Button.visible¶
Button.visible(flag=<not given>)
Read whether the control is shown, or hide or show it.
A hidden control is not drawn and cannot be pressed, dragged or clicked. Its space in the panel stays blank, so the panel keeps its height and nothing moves when you show it again. It keeps its value. value(), checked() and clicked() still work while it is hidden. You can call visible() in draw().
| Argument | Meaning |
|---|---|
flag |
True to show the control, False to hide it. Leave it out to read. |
Returns. True or False when you leave flag out. None when you set it.
size = f.create_slider(10, 100, 50)
size.visible(False)
print(size.visible()) # False
See also: value, checked, create_slider.
Color¶
Color(r: 'int', g: 'int', b: 'int', a: 'int' = 255) -> None
A colour, as red, green, blue and alpha numbers.
You get one from f.color(), f.hsb(), f.hsl(), f.lerp_color() and f.get(x, y). You can give it to fill(), stroke(), background() and every other command that takes a colour. A colour cannot be changed.
Read its parts as c.red, c.green, c.blue and c.alpha (each a whole number from 0 to 255), c.hue (0 to 360) and c.saturation, c.brightness and c.lightness (each 0 to 100). The short names c.r, c.g, c.b and c.a are the same as red, green, blue and alpha.
c = f.color("tomato")
print(c.red, c.green, c.blue) # 255 99 71
f.fill(c)
Color.alpha¶
Color.alpha # property
How solid the colour is.
Returns. a whole number from 0 (see-through) to 255 (solid).
print(f.color(255, 0, 0, 128).alpha) # 128
See also: rgba.
Color.blue¶
Color.blue # property
How much blue the colour has.
Returns. a whole number from 0 to 255.
print(f.color("tomato").blue)
Color.brightness¶
Color.brightness # property
How bright the colour is, in the HSB way of measuring.
Returns. a number from 0 (black) to 100 (full brightness).
print(f.color("red").brightness) # 100.0
See also: hue, saturation, lightness.
Color.from_hsb¶
Color.from_hsb(h: 'float', s: 'float', b: 'float', a: 'float' = 255) -> "'Color'"
Make a colour from hue, saturation and brightness.
f.hsb() does the same, and is the one to use in a sketch.
| Argument | Meaning |
|---|---|
h |
the hue in degrees. It wraps round, so 370 is the same as 10. |
s |
the saturation, from 0 to 100. Numbers outside are cut off. |
b |
the brightness, from 0 to 100. Numbers outside are cut off. |
a |
the alpha, from 0 to 255. It is 255 at first. |
Returns. a new Color.
Raises.
ValueError: if a value is not a number.
Color.from_hsl¶
Color.from_hsl(h: 'float', s: 'float', l: 'float', a: 'float' = 255) -> "'Color'"
Make a colour from hue, saturation and lightness.
f.hsl() does the same, and is the one to use in a sketch.
| Argument | Meaning |
|---|---|
h |
the hue in degrees. It wraps round, so 370 is the same as 10. |
s |
the saturation, from 0 to 100. Numbers outside are cut off. |
l |
the lightness, from 0 to 100. Numbers outside are cut off. |
a |
the alpha, from 0 to 255. It is 255 at first. |
Returns. a new Color.
Raises.
ValueError: if a value is not a number.
See also: from_hsb, lightness.
Color.green¶
Color.green # property
How much green the colour has.
Returns. a whole number from 0 to 255.
print(f.color("tomato").green)
Color.hue¶
Color.hue # property
The hue of the colour: its place on the colour wheel.
Returns. a number from 0 to 360. Red is 0, green is 120 and blue is 240.
print(f.color("blue").hue) # 240.0
See also: saturation, brightness, lightness.
Color.lerp¶
Color.lerp(other: "'Color'", amount: 'float') -> "'Color'"
Make a colour that is part of the way from this colour to another.
f.lerp_color() does the same. It mixes red, green, blue and alpha.
| Argument | Meaning |
|---|---|
other |
the Color to mix towards. |
amount |
how much of the way to go, from 0 (this colour) to 1 (the other). Numbers outside are cut off. |
Returns. a new Color.
mid = f.color("red").lerp(f.color("blue"), 0.5)
See also: from_hsb.
Color.lightness¶
Color.lightness # property
How light the colour is, in the HSL way of measuring.
Returns. a number from 0 (black) to 100 (white). A pure colour such as red is 50.
print(f.color("red").lightness) # 50.0
See also: brightness, hue.
Color.parse¶
Color.parse(value: 'ColorLike') -> "'Color'"
Read a colour from any of the forms that funground accepts.
Every command that takes a colour uses it. You do not need to call it.
| Argument | Meaning |
|---|---|
value |
a name like "tomato", a hex string like "#FF6347", a tuple of 2, 3 or 4 numbers, one grey number, or another Color. |
Returns. a Color. If you give a Color, you get the same one back.
Raises.
ValueError: if the colour is not understood, or a part is not from 0 to 255.TypeError: if the value is True or False.
Color.red¶
Color.red # property
How much red the colour has.
Returns. a whole number from 0 to 255.
print(f.color("tomato").red)
Color.rgb¶
Color.rgb # property
The red, green and blue parts together.
Returns. a tuple (red, green, blue), each a whole number from 0 to 255.
r, g, b = f.color("tomato").rgb
See also: rgba.
Color.rgba¶
Color.rgba # property
The red, green, blue and alpha parts together.
Returns. a tuple (red, green, blue, alpha), each a whole number from 0 to 255.
print(f.color("tomato").rgba) # (255, 99, 71, 255)
See also: rgb.
Color.saturation¶
Color.saturation # property
How strong the colour is, in the HSB way of measuring.
Returns. a number from 0 (grey) to 100 (the strongest colour).
print(f.color("red").saturation) # 100.0
See also: hue, brightness.
Gradient¶
Gradient(kind: 'str', points: 'tuple[float, ...]', stops: 'tuple[tuple[float, Color], ...]') -> None
A blend of colours that you can use in place of a colour.
You get one from f.linear_gradient() (colours along a line) and f.radial_gradient() (colours outward from a centre). Give it to fill(), stroke() or background(). It is read in the drawing space of the moment you draw the shape, so it follows translate(), rotate() and scale() like the shape does. In a PDF or SVG file it stays a true gradient.
A gradient cannot be changed. Its parts are kind ("linear" or "radial"), points (x1, y1, x2, y2 for a linear gradient, and x, y, radius for a radial one) and stops (a tuple of (position, colour) pairs, with positions from 0 to 1).
sky = f.linear_gradient(0, 0, 0, 400, ["navy", "skyblue"])
f.background(sky)
Raga¶
Raga(name: 'str', thaat: 'str', swaras: 'tuple[str, ...]', aroha: 'str', avaroha: 'str', pakad: 'str', vadi: 'str', samvadi: 'str', time: 'str', notes: 'str' = '', sources: 'tuple[str, ...]' = ()) -> None
One raga from funground's small table. It is read-only.
You get one from f.raga(name). Its parts are: name: the raga's name. thaat: the parent scale it belongs to. swaras: the notes it uses, as a tuple of letters such as ("S", "R", "G"). Small r, g, d and n are komal (flat). M is tivra (sharp) Ma. aroha: the way up, as a sargam string that f.melody(text, sa=...) can play. avaroha: the way down, as a sargam string. pakad: a phrase that marks the raga, as a sargam string. vadi: the most important swara. samvadi: the second most important swara. time: the traditional time of day to perform it. notes: where the sources differ, and other things to know. It may be empty. sources: a tuple of texts that say where each fact was checked.
str(raga) gives a short summary. A raga is much more than its notes, and the table gives a start, not the raga.
yaman = f.raga("Yaman")
print(yaman.thaat, yaman.swaras)
f.melody(yaman.aroha, sa="D4").play()
Tala¶
Tala(name: 'str', beats: 'int', vibhag: 'tuple[int, ...]', tali: 'tuple[int, ...]', khali: 'tuple[int, ...]', sam: 'int', bols: 'tuple[str, ...]', notes: 'str' = '', sources: 'tuple[str, ...]' = ()) -> None
One tala from funground's small table. It is read-only.
You get one from f.tala_info(name). Beats are counted from 1, so the sam is beat 1. Its parts are: name: the tala's name. beats: how many beats in one cycle. vibhag: the divisions, as a tuple of beat counts such as (4, 4, 4, 4). tali: the beat numbers that are claps. khali: the beat numbers that are waves (the empty beats). sam: the first beat, always 1. bols: the theka, the drum syllables, one for each beat. notes: where the sources differ, and other things to know. It may be empty. sources: a tuple of texts that say where each fact was checked.
str(tala) gives a short summary. Use f.tala(name) to hear the theka.
teentaal = f.tala_info("Teentaal")
print(teentaal.beats, teentaal.bols)
Constants and reference tables¶
Every table here is generated from the funground source, so it cannot drift from the code.
Fixed words¶
| Where it is used | Accepted words |
|---|---|
f.blend_mode(mode) |
normal, multiply, screen, overlay, darken, lighten, add, difference, exclusion, dodge, burn, hard_light, soft_light, hue, saturation, color, luminosity |
f.stroke_cap(cap) |
round, square, butt |
f.stroke_join(join) |
round, miter, bevel |
f.text_align(horizontal, vertical), horizontal |
left, center, right |
f.text_align(horizontal, vertical), vertical |
top, center, baseline, bottom |
f.text_style(style) |
normal, bold, italic, bold_italic |
f.color_mode(mode) |
rgb, hsb, hsl |
f.cursor(kind) |
arrow, cross, hand, move, text, wait |
f.filter(kind) |
threshold, gray, opaque, invert, blur, posterize, erode, dilate |
f.save(path, text=...), text |
live, shapes |
| Sound waves | sine, square, saw, triangle, soft, noise |
Colour modes and ranges¶
The default range of each colour channel in each mode (first, second, third, alpha).
| Mode | First | Second | Third | Alpha |
|---|---|---|---|---|
rgb |
255 | 255 | 255 | 255 |
hsb |
360 | 100 | 100 | 1 |
hsl |
360 | 100 | 100 | 1 |
Keys and the mouse¶
f.key is a single character such as "a", or one of these names. f.mouse_button is one of the buttons below.
| Kind | Names |
|---|---|
| Key names | left, right, up, down, space, enter, escape |
| Mouse buttons | left, center, right |
| Callbacks you can define | mouse_pressed, mouse_released, mouse_moved, mouse_dragged, mouse_clicked, mouse_wheel, key_pressed, key_released, key_typed |
Page sizes¶
For f.page_size(name), in points (1/72 inch). Any capitals are accepted.
| Name | Width | Height |
|---|---|---|
A3 |
842 | 1191 |
A4 |
595 | 842 |
A5 |
420 | 595 |
B5 |
499 | 709 |
Letter |
612 | 792 |
Legal |
612 | 1008 |
Tabloid |
792 | 1224 |
Square |
600 | 600 |
Files¶
| Function | Formats |
|---|---|
f.save(path) |
.png, .pdf, .svg |
f.save_gif, f.save_movie |
.gif, .mp4 |
Notes, sargam and chords¶
- Note names: a letter from
CDEFGAB, an optional#orb, then an octave number, such asC4,F#3orBb2. The twelve names in an octave areC,C#,D,D#,E,F,F#,G,G#,A,A#,B. - Sargam (for
sa=): a swara letter, then'for each octave up or,for each octave down. Lower-case letters are komal, and capitalMis tivra Ma. The swaras, with their just-intonation ratio above Sa, are:
| Swara | Ratio from Sa |
|---|---|
S |
1 |
r |
16/15 |
R |
9/8 |
g |
6/5 |
G |
5/4 |
m |
4/3 |
M |
45/32 |
P |
3/2 |
d |
8/5 |
D |
5/3 |
n |
9/5 |
N |
15/8 |
- Chord names (for
f.chord_notes): a note name then a suffix. Suffixes and their semitones:
| Suffix | Semitones above the root |
|---|---|
(none) |
0 4 7 |
m |
0 3 7 |
dim |
0 3 6 |
aug |
0 4 8 |
7 |
0 4 7 10 |
maj7 |
0 4 7 11 |
m7 |
0 3 7 10 |
Ragas¶
From f.ragas() and f.raga(name). See the ragas and talas chapter.
| Name | Thaat | Time |
|---|---|---|
| Yaman | Kalyan | evening: the first prahar of the night, about 6 to 9 pm |
| Bhupali | Kalyan | evening: the first prahar of the night, about 6 to 9 pm |
| Bilawal | Bilawal | morning: the first prahar of the day, about 6 to 9 am |
| Khamaj | Khamaj | night: the second prahar, about 9 pm to midnight |
| Kafi | Kafi | night: the second prahar, about 9 pm to midnight |
| Bhairav | Bhairav | dawn: the first prahar of the day, about 6 to 9 am |
| Asavari | Asavari | late morning: the second prahar of the day, about 9 am to noon |
| Bhairavi | Bhairavi | morning: the first prahar of the day, about 6 to 9 am; by custom it ends a concert, at any hour |
| Todi | Todi | late morning: the second prahar of the day, about 9 am to noon |
| Marwa | Marwa | sunset: the end of the fourth prahar of the day, about 4 to 7 pm |
| Durga | Bilawal | night: the second prahar, about 9 pm to midnight |
| Desh | Khamaj | night: the second prahar, about 9 pm to midnight |
| Deshkar | Bilawal | morning: the first prahar of the day, about 6 to 9 am |
Talas¶
From f.talas() and f.tala_info(name).
| Name | Beats | Vibhag (divisions) |
|---|---|---|
| Teentaal | 16 | 4+4+4+4 |
| Ektaal | 12 | 2+2+2+2+2+2 |
| Jhaptaal | 10 | 2+3+2+3 |
| Rupak | 7 | 3+2+2 |
| Dadra | 6 | 3+3 |
| Keherwa | 8 | 4+4 |
Bundled fonts¶
Files in funground/fonts, always available with no install.
| File | Used for |
|---|---|
DejaVuSans-Bold.ttf |
f.text_style("bold") |
DejaVuSans-BoldOblique.ttf |
f.text_style("bold_italic") |
DejaVuSans-Oblique.ttf |
f.text_style("italic") |
DejaVuSans.ttf |
f.text_style("normal") |
NotoEmoji-Regular.ttf |
fallback for letters the font lacks |
NotoSansDevanagari-Regular.ttf |
fallback for letters the font lacks |
NotoSansSymbols2-Regular.ttf |
fallback for letters the font lacks |
Environment variables¶
| Variable | Meaning |
|---|---|
FUNGROUND_BACKING_SCALE |
Force the pixel-density scale of the window's backing store (a number). |
FUNGROUND_HEADLESS |
Set to 1, true or yes to run with no window (tests, servers, saving pictures). |
FUNGROUND_HIGHDPI |
Set to 1, true or yes to ask for a sharper window on a high-density screen. |
FUNGROUND_RENDERER |
Which drawing engine to use. Only cairo exists today, and it is the default. |
LOCALAPPDATA |
(see the source) |
SDL_AUDIODRIVER |
(see the source) |
SDL_VIDEODRIVER |
Read from pygame's own setting. dummy means no real window. |
WAYLAND_DISPLAY |
Read to detect a Wayland desktop on Linux. |
WINDIR |
(see the source) |
Named colours¶
All 665 names accepted wherever a colour is expected, such as f.fill("tomato"), with their hex values.
| Name | Hex | Name | Hex | Name | Hex | Name | Hex |
|---|---|---|---|---|---|---|---|
aliceblue |
#f0f8ff |
antiquewhite |
#faebd7 |
antiquewhite1 |
#ffefdb |
antiquewhite2 |
#eedfcc |
antiquewhite3 |
#cdc0b0 |
antiquewhite4 |
#8b8378 |
aqua |
#00ffff |
aquamarine |
#7fffd4 |
aquamarine1 |
#7fffd4 |
aquamarine2 |
#76eec6 |
aquamarine3 |
#66cdaa |
aquamarine4 |
#458b74 |
azure |
#f0ffff |
azure1 |
#f0ffff |
azure2 |
#e0eeee |
azure3 |
#c1cdcd |
azure4 |
#838b8b |
beige |
#f5f5dc |
bisque |
#ffe4c4 |
bisque1 |
#ffe4c4 |
bisque2 |
#eed5b7 |
bisque3 |
#cdb79e |
bisque4 |
#8b7d6b |
black |
#000000 |
blanchedalmond |
#ffebcd |
blue |
#0000ff |
blue1 |
#0000ff |
blue2 |
#0000ee |
blue3 |
#0000cd |
blue4 |
#00008b |
blueviolet |
#8a2be2 |
brown |
#a52a2a |
brown1 |
#ff4040 |
brown2 |
#ee3b3b |
brown3 |
#cd3333 |
brown4 |
#8b2323 |
burlywood |
#deb887 |
burlywood1 |
#ffd39b |
burlywood2 |
#eec591 |
burlywood3 |
#cdaa7d |
burlywood4 |
#8b7355 |
cadetblue |
#5f9ea0 |
cadetblue1 |
#98f5ff |
cadetblue2 |
#8ee5ee |
cadetblue3 |
#7ac5cd |
cadetblue4 |
#53868b |
chartreuse |
#7fff00 |
chartreuse1 |
#7fff00 |
chartreuse2 |
#76ee00 |
chartreuse3 |
#66cd00 |
chartreuse4 |
#458b00 |
chocolate |
#d2691e |
chocolate1 |
#ff7f24 |
chocolate2 |
#ee7621 |
chocolate3 |
#cd661d |
chocolate4 |
#8b4513 |
coral |
#ff7f50 |
coral1 |
#ff7256 |
coral2 |
#ee6a50 |
coral3 |
#cd5b45 |
coral4 |
#8b3e2f |
cornflowerblue |
#6495ed |
cornsilk |
#fff8dc |
cornsilk1 |
#fff8dc |
cornsilk2 |
#eee8cd |
cornsilk3 |
#cdc8b1 |
cornsilk4 |
#8b8878 |
crimson |
#dc143c |
cyan |
#00ffff |
cyan1 |
#00ffff |
cyan2 |
#00eeee |
cyan3 |
#00cdcd |
cyan4 |
#008b8b |
darkblue |
#00008b |
darkcyan |
#008b8b |
darkgoldenrod |
#b8860b |
darkgoldenrod1 |
#ffb90f |
darkgoldenrod2 |
#eead0e |
darkgoldenrod3 |
#cd950c |
darkgoldenrod4 |
#8b6508 |
darkgray |
#a9a9a9 |
darkgreen |
#006400 |
darkgrey |
#a9a9a9 |
darkkhaki |
#bdb76b |
darkmagenta |
#8b008b |
darkolivegreen |
#556b2f |
darkolivegreen1 |
#caff70 |
darkolivegreen2 |
#bcee68 |
darkolivegreen3 |
#a2cd5a |
darkolivegreen4 |
#6e8b3d |
darkorange |
#ff8c00 |
darkorange1 |
#ff7f00 |
darkorange2 |
#ee7600 |
darkorange3 |
#cd6600 |
darkorange4 |
#8b4500 |
darkorchid |
#9932cc |
darkorchid1 |
#bf3eff |
darkorchid2 |
#b23aee |
darkorchid3 |
#9a32cd |
darkorchid4 |
#68228b |
darkred |
#8b0000 |
darksalmon |
#e9967a |
darkseagreen |
#8fbc8f |
darkseagreen1 |
#c1ffc1 |
darkseagreen2 |
#b4eeb4 |
darkseagreen3 |
#9bcd9b |
darkseagreen4 |
#698b69 |
darkslateblue |
#483d8b |
darkslategray |
#2f4f4f |
darkslategray1 |
#97ffff |
darkslategray2 |
#8deeee |
darkslategray3 |
#79cdcd |
darkslategray4 |
#528b8b |
darkslategrey |
#2f4f4f |
darkturquoise |
#00ced1 |
darkviolet |
#9400d3 |
deeppink |
#ff1493 |
deeppink1 |
#ff1493 |
deeppink2 |
#ee1289 |
deeppink3 |
#cd1076 |
deeppink4 |
#8b0a50 |
deepskyblue |
#00bfff |
deepskyblue1 |
#00bfff |
deepskyblue2 |
#00b2ee |
deepskyblue3 |
#009acd |
deepskyblue4 |
#00688b |
dimgray |
#696969 |
dimgrey |
#696969 |
dodgerblue |
#1e90ff |
dodgerblue1 |
#1e90ff |
dodgerblue2 |
#1c86ee |
dodgerblue3 |
#1874cd |
dodgerblue4 |
#104e8b |
firebrick |
#b22222 |
firebrick1 |
#ff3030 |
firebrick2 |
#ee2c2c |
firebrick3 |
#cd2626 |
firebrick4 |
#8b1a1a |
floralwhite |
#fffaf0 |
forestgreen |
#228b22 |
fuchsia |
#ff00ff |
gainsboro |
#dcdcdc |
ghostwhite |
#f8f8ff |
gold |
#ffd700 |
gold1 |
#ffd700 |
gold2 |
#eec900 |
gold3 |
#cdad00 |
gold4 |
#8b7500 |
goldenrod |
#daa520 |
goldenrod1 |
#ffc125 |
goldenrod2 |
#eeb422 |
goldenrod3 |
#cd9b1d |
goldenrod4 |
#8b6914 |
gray |
#bebebe |
gray0 |
#000000 |
gray1 |
#030303 |
gray10 |
#1a1a1a |
gray100 |
#ffffff |
gray11 |
#1c1c1c |
gray12 |
#1f1f1f |
gray13 |
#212121 |
gray14 |
#242424 |
gray15 |
#262626 |
gray16 |
#292929 |
gray17 |
#2b2b2b |
gray18 |
#2e2e2e |
gray19 |
#303030 |
gray2 |
#050505 |
gray20 |
#333333 |
gray21 |
#363636 |
gray22 |
#383838 |
gray23 |
#3b3b3b |
gray24 |
#3d3d3d |
gray25 |
#404040 |
gray26 |
#424242 |
gray27 |
#454545 |
gray28 |
#474747 |
gray29 |
#4a4a4a |
gray3 |
#080808 |
gray30 |
#4d4d4d |
gray31 |
#4f4f4f |
gray32 |
#525252 |
gray33 |
#545454 |
gray34 |
#575757 |
gray35 |
#595959 |
gray36 |
#5c5c5c |
gray37 |
#5e5e5e |
gray38 |
#616161 |
gray39 |
#636363 |
gray4 |
#0a0a0a |
gray40 |
#666666 |
gray41 |
#696969 |
gray42 |
#6b6b6b |
gray43 |
#6e6e6e |
gray44 |
#707070 |
gray45 |
#737373 |
gray46 |
#757575 |
gray47 |
#787878 |
gray48 |
#7a7a7a |
gray49 |
#7d7d7d |
gray5 |
#0d0d0d |
gray50 |
#7f7f7f |
gray51 |
#828282 |
gray52 |
#858585 |
gray53 |
#878787 |
gray54 |
#8a8a8a |
gray55 |
#8c8c8c |
gray56 |
#8f8f8f |
gray57 |
#919191 |
gray58 |
#949494 |
gray59 |
#969696 |
gray6 |
#0f0f0f |
gray60 |
#999999 |
gray61 |
#9c9c9c |
gray62 |
#9e9e9e |
gray63 |
#a1a1a1 |
gray64 |
#a3a3a3 |
gray65 |
#a6a6a6 |
gray66 |
#a8a8a8 |
gray67 |
#ababab |
gray68 |
#adadad |
gray69 |
#b0b0b0 |
gray7 |
#121212 |
gray70 |
#b3b3b3 |
gray71 |
#b5b5b5 |
gray72 |
#b8b8b8 |
gray73 |
#bababa |
gray74 |
#bdbdbd |
gray75 |
#bfbfbf |
gray76 |
#c2c2c2 |
gray77 |
#c4c4c4 |
gray78 |
#c7c7c7 |
gray79 |
#c9c9c9 |
gray8 |
#141414 |
gray80 |
#cccccc |
gray81 |
#cfcfcf |
gray82 |
#d1d1d1 |
gray83 |
#d4d4d4 |
gray84 |
#d6d6d6 |
gray85 |
#d9d9d9 |
gray86 |
#dbdbdb |
gray87 |
#dedede |
gray88 |
#e0e0e0 |
gray89 |
#e3e3e3 |
gray9 |
#171717 |
gray90 |
#e5e5e5 |
gray91 |
#e8e8e8 |
gray92 |
#ebebeb |
gray93 |
#ededed |
gray94 |
#f0f0f0 |
gray95 |
#f2f2f2 |
gray96 |
#f5f5f5 |
gray97 |
#f7f7f7 |
gray98 |
#fafafa |
gray99 |
#fcfcfc |
green |
#00ff00 |
green1 |
#00ff00 |
green2 |
#00ee00 |
green3 |
#00cd00 |
green4 |
#008b00 |
greenyellow |
#adff2f |
grey |
#bebebe |
grey0 |
#000000 |
grey1 |
#030303 |
grey10 |
#1a1a1a |
grey100 |
#ffffff |
grey11 |
#1c1c1c |
grey12 |
#1f1f1f |
grey13 |
#212121 |
grey14 |
#242424 |
grey15 |
#262626 |
grey16 |
#292929 |
grey17 |
#2b2b2b |
grey18 |
#2e2e2e |
grey19 |
#303030 |
grey2 |
#050505 |
grey20 |
#333333 |
grey21 |
#363636 |
grey22 |
#383838 |
grey23 |
#3b3b3b |
grey24 |
#3d3d3d |
grey25 |
#404040 |
grey26 |
#424242 |
grey27 |
#454545 |
grey28 |
#474747 |
grey29 |
#4a4a4a |
grey3 |
#080808 |
grey30 |
#4d4d4d |
grey31 |
#4f4f4f |
grey32 |
#525252 |
grey33 |
#545454 |
grey34 |
#575757 |
grey35 |
#595959 |
grey36 |
#5c5c5c |
grey37 |
#5e5e5e |
grey38 |
#616161 |
grey39 |
#636363 |
grey4 |
#0a0a0a |
grey40 |
#666666 |
grey41 |
#696969 |
grey42 |
#6b6b6b |
grey43 |
#6e6e6e |
grey44 |
#707070 |
grey45 |
#737373 |
grey46 |
#757575 |
grey47 |
#787878 |
grey48 |
#7a7a7a |
grey49 |
#7d7d7d |
grey5 |
#0d0d0d |
grey50 |
#7f7f7f |
grey51 |
#828282 |
grey52 |
#858585 |
grey53 |
#878787 |
grey54 |
#8a8a8a |
grey55 |
#8c8c8c |
grey56 |
#8f8f8f |
grey57 |
#919191 |
grey58 |
#949494 |
grey59 |
#969696 |
grey6 |
#0f0f0f |
grey60 |
#999999 |
grey61 |
#9c9c9c |
grey62 |
#9e9e9e |
grey63 |
#a1a1a1 |
grey64 |
#a3a3a3 |
grey65 |
#a6a6a6 |
grey66 |
#a8a8a8 |
grey67 |
#ababab |
grey68 |
#adadad |
grey69 |
#b0b0b0 |
grey7 |
#121212 |
grey70 |
#b3b3b3 |
grey71 |
#b5b5b5 |
grey72 |
#b8b8b8 |
grey73 |
#bababa |
grey74 |
#bdbdbd |
grey75 |
#bfbfbf |
grey76 |
#c2c2c2 |
grey77 |
#c4c4c4 |
grey78 |
#c7c7c7 |
grey79 |
#c9c9c9 |
grey8 |
#141414 |
grey80 |
#cccccc |
grey81 |
#cfcfcf |
grey82 |
#d1d1d1 |
grey83 |
#d4d4d4 |
grey84 |
#d6d6d6 |
grey85 |
#d9d9d9 |
grey86 |
#dbdbdb |
grey87 |
#dedede |
grey88 |
#e0e0e0 |
grey89 |
#e3e3e3 |
grey9 |
#171717 |
grey90 |
#e5e5e5 |
grey91 |
#e8e8e8 |
grey92 |
#ebebeb |
grey93 |
#ededed |
grey94 |
#f0f0f0 |
grey95 |
#f2f2f2 |
grey96 |
#f5f5f5 |
grey97 |
#f7f7f7 |
grey98 |
#fafafa |
grey99 |
#fcfcfc |
honeydew |
#f0fff0 |
honeydew1 |
#f0fff0 |
honeydew2 |
#e0eee0 |
honeydew3 |
#c1cdc1 |
honeydew4 |
#838b83 |
hotpink |
#ff69b4 |
hotpink1 |
#ff6eb4 |
hotpink2 |
#ee6aa7 |
hotpink3 |
#cd6090 |
hotpink4 |
#8b3a62 |
indianred |
#cd5c5c |
indianred1 |
#ff6a6a |
indianred2 |
#ee6363 |
indianred3 |
#cd5555 |
indianred4 |
#8b3a3a |
indigo |
#4b0082 |
ivory |
#fffff0 |
ivory1 |
#fffff0 |
ivory2 |
#eeeee0 |
ivory3 |
#cdcdc1 |
ivory4 |
#8b8b83 |
khaki |
#f0e68c |
khaki1 |
#fff68f |
khaki2 |
#eee685 |
khaki3 |
#cdc673 |
khaki4 |
#8b864e |
lavender |
#e6e6fa |
lavenderblush |
#fff0f5 |
lavenderblush1 |
#fff0f5 |
lavenderblush2 |
#eee0e5 |
lavenderblush3 |
#cdc1c5 |
lavenderblush4 |
#8b8386 |
lawngreen |
#7cfc00 |
lemonchiffon |
#fffacd |
lemonchiffon1 |
#fffacd |
lemonchiffon2 |
#eee9bf |
lemonchiffon3 |
#cdc9a5 |
lemonchiffon4 |
#8b8970 |
lightblue |
#add8e6 |
lightblue1 |
#bfefff |
lightblue2 |
#b2dfee |
lightblue3 |
#9ac0cd |
lightblue4 |
#68838b |
lightcoral |
#f08080 |
lightcyan |
#e0ffff |
lightcyan1 |
#e0ffff |
lightcyan2 |
#d1eeee |
lightcyan3 |
#b4cdcd |
lightcyan4 |
#7a8b8b |
lightgoldenrod |
#eedd82 |
lightgoldenrod1 |
#ffec8b |
lightgoldenrod2 |
#eedc82 |
lightgoldenrod3 |
#cdbe70 |
lightgoldenrod4 |
#8b814c |
lightgoldenrodyellow |
#fafad2 |
lightgray |
#d3d3d3 |
lightgreen |
#90ee90 |
lightgrey |
#d3d3d3 |
lightpink |
#ffb6c1 |
lightpink1 |
#ffaeb9 |
lightpink2 |
#eea2ad |
lightpink3 |
#cd8c95 |
lightpink4 |
#8b5f65 |
lightsalmon |
#ffa07a |
lightsalmon1 |
#ffa07a |
lightsalmon2 |
#ee9572 |
lightsalmon3 |
#cd8162 |
lightsalmon4 |
#8b5742 |
lightseagreen |
#20b2aa |
lightskyblue |
#87cefa |
lightskyblue1 |
#b0e2ff |
lightskyblue2 |
#a4d3ee |
lightskyblue3 |
#8db6cd |
lightskyblue4 |
#607b8b |
lightslateblue |
#8470ff |
lightslategray |
#778899 |
lightslategrey |
#778899 |
lightsteelblue |
#b0c4de |
lightsteelblue1 |
#cae1ff |
lightsteelblue2 |
#bcd2ee |
lightsteelblue3 |
#a2b5cd |
lightsteelblue4 |
#6e7b8b |
lightyellow |
#ffffe0 |
lightyellow1 |
#ffffe0 |
lightyellow2 |
#eeeed1 |
lightyellow3 |
#cdcdb4 |
lightyellow4 |
#8b8b7a |
lime |
#00ff00 |
limegreen |
#32cd32 |
linen |
#faf0e6 |
magenta |
#ff00ff |
magenta1 |
#ff00ff |
magenta2 |
#ee00ee |
magenta3 |
#cd00cd |
magenta4 |
#8b008b |
maroon |
#b03060 |
maroon1 |
#ff34b3 |
maroon2 |
#ee30a7 |
maroon3 |
#cd2990 |
maroon4 |
#8b1c62 |
mediumaquamarine |
#66cdaa |
mediumblue |
#0000cd |
mediumorchid |
#ba55d3 |
mediumorchid1 |
#e066ff |
mediumorchid2 |
#d15fee |
mediumorchid3 |
#b452cd |
mediumorchid4 |
#7a378b |
mediumpurple |
#9370db |
mediumpurple1 |
#ab82ff |
mediumpurple2 |
#9f79ee |
mediumpurple3 |
#8968cd |
mediumpurple4 |
#5d478b |
mediumseagreen |
#3cb371 |
mediumslateblue |
#7b68ee |
mediumspringgreen |
#00fa9a |
mediumturquoise |
#48d1cc |
mediumvioletred |
#c71585 |
midnightblue |
#191970 |
mintcream |
#f5fffa |
mistyrose |
#ffe4e1 |
mistyrose1 |
#ffe4e1 |
mistyrose2 |
#eed5d2 |
mistyrose3 |
#cdb7b5 |
mistyrose4 |
#8b7d7b |
moccasin |
#ffe4b5 |
navajowhite |
#ffdead |
navajowhite1 |
#ffdead |
navajowhite2 |
#eecfa1 |
navajowhite3 |
#cdb38b |
navajowhite4 |
#8b795e |
navy |
#000080 |
navyblue |
#000080 |
oldlace |
#fdf5e6 |
olive |
#808000 |
olivedrab |
#6b8e23 |
olivedrab1 |
#c0ff3e |
olivedrab2 |
#b3ee3a |
olivedrab3 |
#9acd32 |
olivedrab4 |
#698b22 |
orange |
#ffa500 |
orange1 |
#ffa500 |
orange2 |
#ee9a00 |
orange3 |
#cd8500 |
orange4 |
#8b5a00 |
orangered |
#ff4500 |
orangered1 |
#ff4500 |
orangered2 |
#ee4000 |
orangered3 |
#cd3700 |
orangered4 |
#8b2500 |
orchid |
#da70d6 |
orchid1 |
#ff83fa |
orchid2 |
#ee7ae9 |
orchid3 |
#cd69c9 |
orchid4 |
#8b4789 |
palegoldenrod |
#eee8aa |
palegreen |
#98fb98 |
palegreen1 |
#9aff9a |
palegreen2 |
#90ee90 |
palegreen3 |
#7ccd7c |
palegreen4 |
#548b54 |
paleturquoise |
#afeeee |
paleturquoise1 |
#bbffff |
paleturquoise2 |
#aeeeee |
paleturquoise3 |
#96cdcd |
paleturquoise4 |
#668b8b |
palevioletred |
#db7093 |
palevioletred1 |
#ff82ab |
palevioletred2 |
#ee799f |
palevioletred3 |
#cd6889 |
palevioletred4 |
#8b475d |
papayawhip |
#ffefd5 |
peachpuff |
#ffdab9 |
peachpuff1 |
#ffdab9 |
peachpuff2 |
#eecbad |
peachpuff3 |
#cdaf95 |
peachpuff4 |
#8b7765 |
peru |
#cd853f |
pink |
#ffc0cb |
pink1 |
#ffb5c5 |
pink2 |
#eea9b8 |
pink3 |
#cd919e |
pink4 |
#8b636c |
plum |
#dda0dd |
plum1 |
#ffbbff |
plum2 |
#eeaeee |
plum3 |
#cd96cd |
plum4 |
#8b668b |
powderblue |
#b0e0e6 |
purple |
#a020f0 |
purple1 |
#9b30ff |
purple2 |
#912cee |
purple3 |
#7d26cd |
purple4 |
#551a8b |
red |
#ff0000 |
red1 |
#ff0000 |
red2 |
#ee0000 |
red3 |
#cd0000 |
red4 |
#8b0000 |
rosybrown |
#bc8f8f |
rosybrown1 |
#ffc1c1 |
rosybrown2 |
#eeb4b4 |
rosybrown3 |
#cd9b9b |
rosybrown4 |
#8b6969 |
royalblue |
#4169e1 |
royalblue1 |
#4876ff |
royalblue2 |
#436eee |
royalblue3 |
#3a5fcd |
royalblue4 |
#27408b |
saddlebrown |
#8b4513 |
salmon |
#fa8072 |
salmon1 |
#ff8c69 |
salmon2 |
#ee8262 |
salmon3 |
#cd7054 |
salmon4 |
#8b4c39 |
sandybrown |
#f4a460 |
seagreen |
#2e8b57 |
seagreen1 |
#54ff9f |
seagreen2 |
#4eee94 |
seagreen3 |
#43cd80 |
seagreen4 |
#2e8b57 |
seashell |
#fff5ee |
seashell1 |
#fff5ee |
seashell2 |
#eee5de |
seashell3 |
#cdc5bf |
seashell4 |
#8b8682 |
sienna |
#a0522d |
sienna1 |
#ff8247 |
sienna2 |
#ee7942 |
sienna3 |
#cd6839 |
sienna4 |
#8b4726 |
silver |
#c0c0c0 |
skyblue |
#87ceeb |
skyblue1 |
#87ceff |
skyblue2 |
#7ec0ee |
skyblue3 |
#6ca6cd |
skyblue4 |
#4a708b |
slateblue |
#6a5acd |
slateblue1 |
#836fff |
slateblue2 |
#7a67ee |
slateblue3 |
#6959cd |
slateblue4 |
#473c8b |
slategray |
#708090 |
slategray1 |
#c6e2ff |
slategray2 |
#b9d3ee |
slategray3 |
#9fb6cd |
slategray4 |
#6c7b8b |
slategrey |
#708090 |
snow |
#fffafa |
snow1 |
#fffafa |
snow2 |
#eee9e9 |
snow3 |
#cdc9c9 |
snow4 |
#8b8989 |
springgreen |
#00ff7f |
springgreen1 |
#00ff7f |
springgreen2 |
#00ee76 |
springgreen3 |
#00cd66 |
springgreen4 |
#008b45 |
steelblue |
#4682b4 |
steelblue1 |
#63b8ff |
steelblue2 |
#5cacee |
steelblue3 |
#4f94cd |
steelblue4 |
#36648b |
tan |
#d2b48c |
tan1 |
#ffa54f |
tan2 |
#ee9a49 |
tan3 |
#cd853f |
tan4 |
#8b5a2b |
teal |
#008080 |
thistle |
#d8bfd8 |
thistle1 |
#ffe1ff |
thistle2 |
#eed2ee |
thistle3 |
#cdb5cd |
thistle4 |
#8b7b8b |
tomato |
#ff6347 |
tomato1 |
#ff6347 |
tomato2 |
#ee5c42 |
tomato3 |
#cd4f39 |
tomato4 |
#8b3626 |
turquoise |
#40e0d0 |
turquoise1 |
#00f5ff |
turquoise2 |
#00e5ee |
turquoise3 |
#00c5cd |
turquoise4 |
#00868b |
violet |
#ee82ee |
violetred |
#d02090 |
violetred1 |
#ff3e96 |
violetred2 |
#ee3a8c |
violetred3 |
#cd3278 |
violetred4 |
#8b2252 |
wheat |
#f5deb3 |
wheat1 |
#ffe7ba |
wheat2 |
#eed8ae |
wheat3 |
#cdba96 |
wheat4 |
#8b7e66 |
white |
#ffffff |
whitesmoke |
#f5f5f5 |
yellow |
#ffff00 |
yellow1 |
#ffff00 |
yellow2 |
#eeee00 |
yellow3 |
#cdcd00 |
yellow4 |
#8b8b00 |
yellowgreen |
#9acd32 |