Changelog¶
What changed in funground, newest first. Release numbers follow the roadmap. Decisions are cited as D-nnn from the decision log.
Unreleased — will become 0.1¶
The first public release. It starts from playground v0.5, a small teaching wrapper around pygame-ce that was never published.
Renamed¶
- The library is now funground:
pip install funground,import funground as f(D-020). PlaygroundWarningandPlaygroundErrorareFungroundWarningandFungroundError.- Environment variables are
FUNGROUND_*, for exampleFUNGROUND_HEADLESS=1.
Changed from v0.5¶
- A single number is a grey, not a packed colour.
fill(0xFF0000FF)is no longer a colour; use"0xFF0000"or a tuple (D-032). - Drawing is done by Cairo and is anti-aliased. Fractional coordinates are honoured (D-005).
- The fourth colour value (alpha) now works: translucent fills and strokes (D-003).
- Strokes are centred on the shape's edge, with round ends and corners by default (D-004).
- Text uses the bundled DejaVu Sans font and looks the same on every computer.
text_size(n)is an n-pixel em, so text is about 1.4 times larger than in v0.5 (D-012). mouse_pressedis nowis_mouse_pressed, becausemouse_pressed()is a callback you can define (D-016). The old name gives an error that names the new one.- Crisp on scaled displays: coordinates are logical pixels, rendering is at full resolution.
Added¶
- Ground (S-132, D-069):
f.size()takes a page name (f.size("A4", landscape=True)) andmargin=.f.groundandf.ground.contentgive the canvas and the area inside the margins.f.grid(cols, rows, gutter=0, area=None)divides an area into cells, andf.mm()andf.inch()convert to funground units. - Areas and grids (S-132 part 4, contract G1, D-073):
f.area(x, y, w, h)makes an area, a rectangle kept as a value, withleft,top,right,bottom,width,height,cxandcy.area.inset(...)gives a smaller one (a negative inset a bigger one), andarea.grid(...)divides it.f.ground,f.ground.contentand every grid cell are areas.- Changed:
f.grid()returns a Grid, not a list. A Grid still works like the list did:for,len(),g[0],g[-1]and slices (which give a list). It addsg.cell(col, row)(from 0),g.span(col, row, cols, rows)(one area over several cells, gutters included),g.column_count,g.row_count,g.columns,g.rows,g.areaandg.gutter. Wrong cells and spans are errors that name the valid range. - Changed: a cell no longer has
x,y,wandh. Useleft,top,widthandheight, the same names as every area. g.show()draws thin guide lines for the grid in the window only. They are left out of saved files,f.keep()and pixels, and the drawing state does not change.g.show(in_files=True)draws them as ordinary lines (contract G2).- Play (S-132 parts 3 and 4, contracts E1 and E2, D-071, D-073): exploring and keeping versions.
f.variations(fn, gap=[...])draws a labelled contact sheet of a drawing for each value, or each pair of values. One parameter makes a row while every picture stays readable, otherwise a grid;columns=chooses. Every cell has the same scale, the same random seed and its own clean style. It returns the versions as(values, mark)pairs.f.keep(note, pdf=False, **settings)saves the picture, a copy of the sketch and a JSON record (the note, the settings, the controls, the random and noise seeds, the fonts and files used) in a numberedstudiofolder. In an animated sketch it keeps the next frame drawn, and the record is that frame's.- Every run records a random seed, and a noise seed when noise is used. A seed you give is kept exactly. A recorded seed makes funground's randomness repeatable; it does not make every sketch reproducible.
- Marks: a drawing kept as a value, to place as often as you like (S-132, contract K1–K3, D-069, D-070).
with f.mark() as m:records the drawing in the block instead of drawing it. Each part keeps its own style. The block starts from the current style and no transform, and leaves the transform, style and clip exactly as they were, even after an error. A layer, controls, saving and pixel calls inside the block are errors.f.mark(path, fill=..., stroke=..., stroke_width=...)makes a mark from a path, andf.mark("file.svg")from an SVG file's shapes, with the file's colours.m.place(x, y, scale=, rotate=, opacity=, anchor=, width=, height=, style=)draws it: on the canvas, in a layer, or inside another mark.anchorplaces it by a point of its bounds ("center","bottom-left"and so on).widthandheightsize it to fit, keeping its shape.opacityfades it as one piece.style="current"draws it with the current fill and stroke instead of its own, for silhouettes and outlines.m.bounds(),m.width,m.heightandm.is_emptymeasure it. A finished mark cannot be changed.- Marks stay vector: sharp on screen at any scale, and real shapes and text in PDF and SVG files.
- Projects: a new gallery area and guide chapter 18 with project capstones, each in three stages (make it work, make it yours, make it shine) and with challenge cards. The first two are the event poster series (a layered, multi-page PDF with real text, plus SVGs) and the rangoli and mandala generator (controls, path booleans, hsb palettes, SVG and PDF for print or laser cutting) (S-117, D-063). The last two are the raga explorer (pick a raga, hear its aroha and avaroha over a drone with reverb, see each swara light on a ladder, sing it back against the ladder with a gated, folded, smoothed pitch line, then see your swara histogram and the top three
match_ragasresults, with an honest note that it compares notes only) and a voice-controlled bird game (loudness flies the bird, with a room-quiet calibration, a space-bar fallback, gates, collisions, a score and sound effects) (S-117 part 2, D-063). - Layers:
with f.layer("sky"):draws on a named, see-through picture the size of the canvas; the layers are put over the canvas in the order they were first made, and each keeps its drawing from frame to frame.f.hide_layer(name)andf.show_layer(name)switch one off and on. PNG,f.get()and the window include the layers; a PDF or SVG draws each layer as shapes (named layers in those files come with S-096) (S-095, contract F16, D-053). - Layers in PDF and SVG files: each layer is a real, named layer. In a PDF it is an optional content group, listed in the Layers panel of Acrobat, Illustrator and browsers; in an SVG it is an Inkscape layer group (
inkscape:groupmode="layer"), which Inkscape and Illustrator show as a layer. A hidden layer is in the file, switched off (/OFFin a PDF,display:nonein an SVG). Text in a layer is still real PDF text. Sketches without layers write exactly the same files as before (S-096, contract F16, D-053). - GIF and MP4: a script saves every page as a frame with
f.save("x.gif")orf.save("x.mp4");f.frame_duration(seconds)sets how long a page and the pages after it are shown. An animated sketch records itself withf.save_gif(path, seconds)orf.save_movie(path, seconds). A GIF is written with Pillow (pip install funground[extras]) or ffmpeg; an MP4 needs ffmpeg on the PATH, and the error says how to install it (S-100, contract M1, D-048; how ffmpeg is supplied is D-045). - Controls:
f.create_slider(low, high, value=None, step=None, label=None),f.create_checkbox(label, checked=False)andf.create_button(label)make a slider, a tick box and a button in a panel below the canvas. Read them withslider.value(),box.checked()andbutton.clicked();slider.value(v)andbox.checked(b)set them. The panel is not part of the canvas, so it is not in the size, saves, pixels or the mouse position, and clicks on it do not reach the sketch's mouse callbacks. Make controls insetup(), not indraw()or in a script (S-101, D-047). - Hide and show controls: every control has
visible()andvisible(flag), likevalue()andchecked(). A hidden control is not drawn, cannot be pressed, dragged or clicked, and keeps its value. Its row stays blank, so the panel keeps its height.visible()works indraw()(S-127, D-067). - Path shapes and booleans: a path from
f.path()getsrect,ellipse,circleandpolygon, andunion,intersection,difference,xor(also|,&,-,^) andremove_overlap, each giving a new path (S-086). New dependency:skia-pathops, a small Skia library, now installs with funground (D-037, ADR-005). - Path outlines, queries and moves: a path gets
expand_stroke(cap, join, miter limit and dash, open lines too),bounds,contains,translate,scale,rotateandcopy(S-087). - Sound:
f.load_sound(path)reads a WAV, OGG or MP3 file and returns a sound withplay(),loop(),stop(),pause(),set_volume(),is_playing()andduration().level()(loudness) andspectrum(bands)(strength by pitch, from a built-in FFT) let a sketch follow the music. With no sound device a sound plays silently and keeps time, so a sketch behaves the same (S-098, S-099, contract A1, A2, D-046). - Making sound: you can now make sound as well as play it, with no new dependency (everything is computed in plain Python before it plays; D-055, D-056, ADR-006, contract A3).
f.create_sound(samples)makes a sound from a list of numbers, andsound.samples()gives them back, so a wave can be drawn.f.tone()andf.note()make sine, square, saw, triangle or noise tones with a fade in and out.f.pluck()is a plucked string.f.melody()plays a tune written as text, with rests, chords and beats. Withsa=it reads sargam, in equal or just tuning.f.sequence()andf.mix()join sounds,sound.pan()moves a sound between the speakers (loaded sounds too) andsound.save()writes a WAV file.sound.pitch()tells you the note of a single voice, andf.note_to_frequency()andf.frequency_to_note()convert both ways.- Microphone:
f.microphone()listens to the computer's microphone (f.microphones()lists them).mic.start(),mic.stop()andmic.is_listening()control it;mic.level(),mic.spectrum()andmic.pitch()work as on a sound;mic.capture(seconds)returns the last few seconds (up to 10) as a sound. It is never played back. No microphone, or no permission, is a plainRuntimeError; headless it is silent. Needs pygame-ce 2.5 or newer (S-108, D-058, contract A4). - Rhythm and harmony:
sound.onsets()(times where notes begin),sound.tempo()(60 to 200 beats a minute, orNone),sound.beats()andsound.key()("G major", "E minor") look at a whole sound, work it out once, and take a few seconds for a three-minute song.sound.is_onset(),sound.chroma()(twelve note-name strengths) andsound.chord()("C", "Am", "G7", "Fmaj7", "Bdim", orNone) work at the playing position, and on a microphone for what it heard last.f.chord_notes(name)lists a chord's notes. Two gallery examples in a new Music area: tap along to the beat, and see a chord (S-112, S-113, contract A5, A6, D-061). - Ragas and talas: a small built-in table of 13 Hindustani ragas (
f.ragas(),f.raga(name): thaat, swaras, aroha, avaroha, pakad, vadi, samvadi, time) and six talas (f.talas(),f.tala_info(name): beats, vibhag, tali, khali, sam, bols), every fact checked against published sources cited in the data.f.drone(sa, seconds, pattern)is a tanpura-like drone;f.tala(name, tempo, cycles)plays a theka with simple drum sounds;f.melodygains meend (S~G) and kan ((R)G).sound.tonic()andsound.swara_histogram(sa)(also on a microphone) listen for Sa and the swaras, andf.match_ragas(histogram)ranks the table's ragas by their notes: a learning aid, not a recogniser. Guide chapter 17 (S-115, D-057, D-062, contract A8). - Learning with sound: four more examples in the Music area: draw a wave and hear it (drag points,
create_sound,draw_spectrum), compose and save (a clickable grid,melody,mix, reverb, WAV and PDF poster), ear training (swaras and intervals, by click, key or singing, with a score and streak) and a piano roll of a tune written in code (withdraw_pitch_linelaid over it). Guide chapter 16 has a section on them (S-114, D-056). - Drawing sound: four ready-made views, each drawn into a box with the current fill, stroke and transform, so they work in layers and stay vector in PDF and SVG.
f.draw_wave(source, x, y, w, h)draws a sound (all of it, with a line where it is playing), a microphone (the last half second) or a list of numbers;f.draw_spectrum(source, x, y, w, h, bands=32)draws the spectrum bars;f.draw_pitch_line(source, x, y, w, h, seconds=5, low="C3", high="C6", sa=None)draws a scrolling pitch line with labelled note (or swara) guides and gaps where there is no pitch;f.spectrogram(sound, width, height)returns a picture with time across, pitch up and loudness as brightness of the current fill. A new "Music" area in the gallery has two examples (S-111, contract A7, D-061). - Font collections:
f.load_font(path, face=0)reads.ttcand.otcfiles, which hold several fonts.faceis a number or a style name, likeface="Bold".f.system_font()now looks inside collections too, sof.system_font("Nirmala UI")finds Windows' Telugu, Tamil, Bengali and Kannada font, andf.system_font("Nirmala UI Bold")finds a style. PDF and SVG files embed the face that was used as one font. Guide chapter 6 has a recipe for scripts your font does not have (S-119, contract T11 and T18). - Sound quality: made sounds are more pleasant by default, still in plain Python (S-118, D-064, contract A9, which amends A3).
- Headroom:
tone,note,pluck,melodyanddroneare made atvolume=0.5, so a few sounds can play together without clipping.f.mix()now has a soft limiter: a quiet sum is unchanged, a loud one never passes 0.9 (it used to be scaled down to 1). Talas peak at 0.5 too. - Clean waves: square, saw and triangle are built from their overtones up to just below half the sample rate, so high notes no longer alias into buzz. A new
wave="soft"(a few gently falling overtones, like a recorder) is the default forf.melody(). - Envelope:
toneandnotegaindecayandsustainbesideattackandrelease, on smooth exponential curves (defaults 0.01 s, 0.15 s, 0.7 and 0.15 s; the release was 0.05 s). Melody notes are legato: each note's release rings on over the next note, so a melody that ends on a note is 0.15 s longer than its beats. - Pluck: the burst that starts the string is low-pass filtered, so it sounds warmer (the drone already used a softer burst; both now share the code).
- Reverb:
sound.reverb(amount=0.3)returns the sound in a room, from 0 (dry) to 1 (a large hall), with up to 1.5 s of added ring. - The gallery's sound and music examples are retuned (levels, voices, reverb), and the visualiser's
tune.wavis remade at 44 100 Hz. - SVG files:
f.load_svg(path)reads an SVG file into a picture made of its shapes, so it stays sharp at any size and stays vector in a saved PDF or SVG;f.svg_paths(path)gives the shapes as paths, for booleans and clips. Paths, basic shapes, groups,<use>, transforms, colours with opacity, strokes, dashes andfill-ruleare read; gradients paint with their first colour; text, images, filters and masks are ignored (S-092, contract P11). New dependency:svgelements, a small pure-Python SVG reader, now installs with funground (D-041). - Rounded corners:
f.rect(x, y, w, h, *radii)andf.square(x, y, size, *radii)take one radius for all corners, or four in p5's order (top-left, top-right, bottom-right, bottom-left); the path builder'srecttakes them too (S-089). This is an approved change to the v0.5rectsignature (D-040): calls without a radius draw exactly as before. - Letters as a path:
f.text_path(message, x, y)returns the outlines of the text as a path, set exactly likef.text(), so you can cut them out of a shape, outline them, fill them with a gradient or clip with them (S-088). - Text as points:
f.text_to_points(message, x, y, spacing=5)returns(x, y)points along the outlines of the text, one everyspacingpixels, for dotted and particle looks (D-050). - Font information:
f.current_font()returns the font text is set in;font.family(),font.style(),font.variations(),font.features()andfont.contains(text)answer questions about it (D-050). - Letters your font does not have: when the current font lacks a character, funground draws it from another font: the ones you give to
f.text_fallback(*fonts), then three bundled Noto fonts (Noto Emoji for emoji, Noto Sans Symbols 2, Noto Sans Devanagari for Hindi), on the same baseline.f.text_fallback(None)turns it off, andf.system_font(name)finds an installed font by family name. Emoji are drawn in one colour. Text the font can already draw is unchanged. PDF text embeds one font subset for each font used (S-102, contract T18, D-052). The package grows by about 1.8 MB for the three SIL Open Font License fonts, whose licence text ships beside them. - Tracking, OpenType features, variable fonts:
f.text_tracking(pixels)adds space after every letter,f.text_features(liga=False)turns OpenType features on or off, andf.font_variations(wght=700)sets a variable font's axes. They apply tof.text,f.text_box,f.text_widthandf.text_path, and a saved state and a picture each keep their own (S-090). - Real text in PDFs: every PDF funground saves (
f.save("x.pdf"), a document of several pages, a picture'sg.save("x.pdf"), and pictures drawn into any of these) holds its text as real text that can be selected, searched and copied. Each font used is put into the file once, as a subset of the letters used, and the page looks the same as before. A font that forbids being put in a file is still saved as letter shapes. Text-heavy PDFs are about ten times smaller (S-094). New dependency:pypdf, a small pure-Python PDF library, now installs with funground (D-043). - Live text in SVG files: every SVG funground saves holds its text as live text, one
<text>per line, that Illustrator, Inkscape and browsers can edit. It names each font by family, with bold and italic, so those programs draw it in the installed font: install funground's fonts (thefontsfolder in the package) to edit with the same look. Letters from fallback fonts are<tspan>s with their own font. Colour, gradient, opacity, tracking, OpenType features and variations carry over, and text in a layer stays in its layer. For browsers, a subset of each font is embedded as an@font-face(left out for a font that forbids embedding). The program that opens the file spaces the text itself, so spacing can differ slightly. Erased text and text shadows stay shapes. A text-heavy SVG is about fifty times smaller; SVGs without text are unchanged (S-097, contract T19, D-059). - Text as shapes in a PDF or SVG:
f.save("poster_final.svg", text="shapes")andpicture.save(path, text="shapes")draw every letter as a vector shape, so the file looks the same on every computer and needs no font. The default,text="live", is real text in a PDF and live, editable text in an SVG. It applies to every page, picture and layer in the file; layers stay layers. PNG, GIF and MP4 ignore it; any other value is aValueError. The guide gives the rule for handoffs: save one copy live to edit and one as shapes to share (S-116, contract T20, D-060). - Mixed styles in one text:
f.FormattedString()holds runs of text, andappend(text, size=, style=, color=, font=, tracking=, features=, variations=)gives a run its own look; settings left out follow the drawing state.f.text,f.text_box,f.text_widthandf.text_pathtake one, a line is as tall as its tallest run, andf.text_boxreturns the overflow as a FormattedString (S-091). - Full screen in scripts:
f.full_screen()at the top of a script makes the canvas the screen's size, andf.show()shows it full screen (S-085). - Pages: in a script,
f.new_page()starts a new page (a size, or a name such as"A5");f.page_count()counts them andf.page_size("A4")gives a name's size.f.save("x.pdf")writes every page into one PDF, PNG and SVG writex_1.png,x_2.png, …, andf.show()turns pages with the arrow keys (S-084). - Pixels:
f.get(x, y)reads a colour,f.get(x, y, w, h)copies a region into a new picture,f.set(x, y, color)writes one pixel, andf.load_pixels(),f.pixelsandf.update_pixels()read and write them all at once, as in p5 (pixel density 1; also on pictures). Reading sees the drawing so far, this frame included. - Copy, resize, mask and filters:
g.copy(),g.resize(w, h)(a 0 keeps the shape) andg.mask(other)change pictures;f.filter(kind, value)andg.filter(...)apply"threshold","gray","opaque","invert","blur","posterize","erode"or"dilate"to the canvas or a picture.pip install funground[extras]makes"posterize","erode"and"dilate"faster; the results are the same without it. - Crisp pixel art: under
f.no_smooth(),f.image()scales pictures with the nearest pixel. - Erasing:
f.erase(fill_strength, stroke_strength)makes everything drawn after it remove what is under it, instead of painting;f.no_erase()stops it. Cut holes in a picture (also ong.erase). A PDF cannot show a hole cut straight in the canvas; a hole in a picture works (D-051). - Tint and parts of a picture:
f.tint(color)/f.no_tint()multiply the colour and alpha of pictures drawn withf.image(), andf.image(picture, x, y, w, h, sx, sy, sw, sh)draws only a part of a picture, as in p5 (also ong.image). - Gallery browser:
python -m funground.gallerybrowses every example by topic, with its picture, description and code, and runs it in its own window. It is itself a funground app. The examples, their data files and pictures now ship inside the package (D-049), so it works after a plain install. Press C (or the Copy button) to copy an example and its data into the current folder; it never overwrites (a taken name becomes_2).--listprints the examples with no window. In a checkoutpython tools/gallery_browser.pystill works (S-103). Each run has its own temporary folder; when an example saves a file, the browser says where (and prints it), and O (or the Folder button) opens it. - Scripts: a file can be a script instead of an animated sketch: no
draw(), nof.run().f.size()makes a canvas with no window,f.save()writes at once, and the newf.show()opens a window to look at it. A file that mixes the two styles gets a clear error, and a program that has adraw()but never callsf.run()gets a one-line hint when it ends (D-029). - Shapes:
square,triangle,quad,polygon,arc;begin_shape…end_shapewithvertex,bezier_vertex,quadratic_vertex,curve_vertex, contours for holes;bezier,curveand their point and tangent helpers; reusable paths withpath()anddraw_path. - Clipping:
clip,no_clip. Transparent canvas:clear. - Strokes:
stroke_cap,stroke_join,miter_limit,stroke_dash,no_dash;no_smooth/smoothfor pixel art. - Colour mode:
color_mode(mode, max1, max2, max3, max_alpha)as in p5, with modes"rgb","hsb"and"hsl";color_mode("rgb", 1)reads DrawBot's 0–1 colours (D-031). - Separate colour numbers:
fill(255, 0, 0),stroke(128),background(30, 30, 60)work as well as tuples, read in the current colour mode (D-032). - Grey numbers: one number is a grey and two are a grey and an opacity, as in p5:
fill(128),fill(128, 100)(D-032). - Colour:
hsb,hsl,colorobjects,lerp_color;linear_gradientandradial_gradient, usable wherever a colour goes;blend_mode,opacity,shadow. - Transforms:
translate,rotate(degrees),scale,shear_x,shear_y,apply_matrix,reset_matrix;push/popandwith f.saved_state():. - Text:
text_width,text_align,text_ascent,text_descent, several lines withtext_leading,text_boxwith word wrap and overflow,load_font,text_font,text_stylewith bundled bold and italic. - Animation and time:
no_loop,loop,redraw,is_looping,exit,millis,frame_rate,second…year;run(max_frames=n). - Input: callbacks
mouse_pressed,mouse_released,mouse_moved,mouse_dragged,mouse_clicked,mouse_wheel,key_pressed,key_released,key_typed; live valuespmouse_x,pmouse_y,mouse_button,key,key_code,is_key_pressed. - Maths and randomness:
map_range,lerp,norm,mag,radians,degrees,random_seed,random_gaussian,random_choice,noise(the same values as p5.js),noise_seed,noise_detail,Vector. - Drawing modes:
rect_mode,ellipse_modeandimage_modeplace shapes and pictures by their corner, opposite corners, centre or radius, as in p5.js. The defaults are unchanged (D-030). - Off-screen pictures:
create_graphicsandimage. - Image files:
load_imagereads PNG, JPEG, GIF, BMP and TGA files into a picture. Phone photos are turned the right way up (ADR-004). - Saving:
saveto PNG, PDF or SVG;save_framesfor numbered frames; PDF and SVG output is true vector. - Window:
resize_canvas,full_screen,cursor,no_cursor. - Running without a window:
FUNGROUND_HEADLESS=1. - Teaching material: a User Guide, an Examples Gallery with an example for every function, and a Quick Reference.
Licence¶
- funground is LGPL-2.1-only (D-025, D-084). Example code is CC0 (D-026). Dependencies keep their own licences; see THIRD_PARTY_LICENSES.md.
Still to come before 0.1¶
- Images, pages and multi-page documents, more path tools, richer typography, SVG import, sound, GIF and video export, and controls (Phase 3, D-027).