Flat is a library for creating and manipulating digital forms of fine arts. Its aim is to enable experimentation with and testing of unpredictable or automated processes, to inspect the beginning of the "new".
It grew out of the needs for generative design, architecture and art. The concept of "design" is more of a subject of study yet to be delved into, hence the fitter term for subtitle is "infrastructure".
It is written in pure Python and distributed under a liberal license.
Flat library consists of three (slightly overlapping) parts: image, document and scene. An image is basically a container of pixels and a color kind. There are a few methods which operate over those pixels, such as "blur" or "put". It is possible to create completely new image, by opening a file or by "rasterizing" a page of document.
Document is then assembled from pages, and each page holds number of "placed" items, both of which can be exported, too. Here it also makes sense to introduce other entities: colors, shapes and strikes. There is support for following colors or color spaces, if you will: the usual ones (grayscale, RGB, CMYK, ...), spot colors that can be used for controlling the application of special colorants and "overprint", which allows for printing of a graphic figure without erasing anything below it. A shape includes both graphical properties such as stroke width or miter limit and means of creating items with said properties that are to be "placed" into a page, for example "line" or "circle". Strike is a similar combination of text attributes (font, size, color, ...) and a way of constructing text "spans". A span likewise connects text string to text attributes, one or more spans can form a "paragraph", which in turn may form "text" or "outlines". Outlines are similar to texts but they use paths of glyph outlines, instead of characters. One additional thing to note is that placed texts or outlines may be linked into a story or "chain" of blocks, making the text gradually "flow" from one text frame to another. Any of the items may be placed into a "group" as well.
Finally, a scene is made of (possibly light emitting) materials, meshes built of triangular faces defined by "triplets" of 3D vertices and a camera.
from flat import rgb, font, shape, strike, document
red = rgb(255, 0, 0)
lato = font.open('Lato-Reg.otf')
figure = shape().stroke(red).width(2.5)
headline = strike(lato).color(red).size(20, 24)
d = document(100, 100, 'mm')
p = d.addpage()
p.place(figure.circle(50, 50, 20))
p.place(headline.text('Hello world!')).frame(10, 10, 80, 80)
p.image(kind='rgb').png('hello.png')
p.svg('hello.svg')
d.pdf('hello.pdf')
Short commentary:
We first prepared some invariants which we are going to use later, like the body typeface, some RGB color or a typeface we opened from a font file. One can think of shape and strike as of customizable factories which produce more concrete objects, for example lines or spans of text.
Next is the basic document hierarchy with just one page that can have items be placed into. The origin of coordinate system (0, 0) is at the top left corner and most of the time the default unit is "points" (1 inch = 72 points). A placed item may have some additional properties as position or frame. The latter is used to define the boundaries inside whose the text may run. As Flat currently lacks any kind of color management, we need to use the same color space for rasterizing the page into a PNG file. To access a page at any time one can simply keep a reference to it (p). Lastly, note that PDF is one of the few graphic formats which can hold multiple pages.
image.open(path)path. Supported formats are JPEG and PNG.image(width, height, kind='rgb')width by height pixels in resolution, where kind can be one of: 'g' (grayscale), 'ga' (grayscale + alpha), 'rgb', 'rgba', 'cmyk'.copy()get(x, y)x, y.put(x, y, components)x, y to components.fill(components)components.white()black()blit(x, y, source)source. Position of the region is x, y in this image, 0, 0 in the source. Size of the region is the size of the source, cropping it to size of this image as necessary.crop(x, y, width, height)x, y and size width, height. The result will not enlarge beyond original size.flip(horizontal, vertical)transpose()rotate(clockwise)clockwise is True, anti-clockwise otherwise.resize(width=0, height=0, interpolation='bicubic')width by height, where interpolation can be one of: 'nearest', 'bicubic', 'lanczos'. Nearest-neighbor is fastest kernel and produces "pixelated" look when upsizing, bicubic is good general-purpose filter, Lanczos resampling preserves most detail and is slowest of the three. 0 width or height maintains the aspect ratio.rescale(factor, interpolation='bicubic')resize but uses scale factor to calculate new dimensions.blur(radius)dither(levels=2)levels using Burkes dithering.gamma(value)value on the image.invert()png(path='', optimized=False)path is set, save it as well. Improve the compression by setting optimized to True.jpeg(path='', quality=95)path is set, save it as well. Higher (up to 100) quality lowers the perceptible loss in image quality but increases the storage size.placedimagepage.place() instead.position(x, y)x, y.frame(x, y, width, height)x, y and resize it to width, height.fitwidth(width)width.fitheight(height)height.raw(width, height)put(x, y, r, g, b)x, y to r, g, b.tonemapped(key=0.18, white=1.0)image with reduced dynamic range of integer values 0-255, where key indicates whether the scene is subjectively light or dark, typically varying from 0.18 to 0.4, and white is the smallest luminance mapped to pure white.pagedocument.addpage() instead.meta(title)title.size(width, height, units='mm')width by height, where units can be one of: 'pt', 'mm', 'cm', 'in'.place(item)item on the page.chain(block)block, enabling its text to flow along the linked blocks. A chain eventually eliminates overflow.svg(path='', compress=False)path is set, save it as well. Reduce size by setting compress to True (currently not implemented).image(ppi=72, kind='g')ppi (pixels per inch) into image of kind.document.open(path)path. Currently not implemented.document(width=210.0, height=297.0, units='mm')width by heightunits.meta(title)title.size(width, height, units='mm')width by heightunits.addpage()pdf(path='', compress=False)path is set, save it as well. Reduce size by setting compress to True (currently not implemented).mesh.openstl(path)path.mesh(triplets)triplets of triangular face vertices. Each vertex is a triplet of x, y, z coordinates.stl(path='')path is set, save it as well.diffuse(reflectance, emittance=None)reflectance and emittance, each of being an RGB floating-point triplet.scene()environment(sky, ground)sky and ground emittance, each of being an RGB floating-point triplet.camera(origin, target, length=50.0)origin to target, each of being an 3D point coordinate. Set focal length to length in millimetres.clear()add(mesh, material)mesh combined with material to the scene.render(width, height, samples=10, multiprocessing=True, info=True)raw image with size width by height pixels using samples × samples number of path tracing samples. To use all available cores set multiprocessing to True and to report rendering progress set info to True.gray(intensity)intensity. 0 corresponds to black, 255 to white.ga(g, a)g and alpha a. 0 corresponds to black/transparent, 255 to white/opaque.rgb(r, g, b)r, g, b. 0 corresponds to absence of component, 255 to maximum intensity.rgba(r, g, b, a)r, g, b and alpha a. 0 corresponds to absence of component/coverage, 255 to maximum intensity/opacity.cmyk(c, m, y, k)c, m, y, k. 0 denotes the absence of colorant, 100 means maximum concentration.spot(name, fallback)name and CMYK fallback used in case of absence of colorant in output device.thinned(tint)tint. 0 denotes the absence of colorant, 100 means maximum concentration.overprint(color)color which enables overprinting. Argument may be one of: cmyk, spot or devicen. When printing without overprint a graphic figure erases everything beneath it. With overprint it is possible to stack a layer of paint over preceding layers.font.open(path, index=0)path.fontfont.open() instead.shape()gray(0) stroke, no fill, 'butt' cap, 'miter' join and 4.0 miter limit.stroke(color)color.fill(color)color.nostroke()nofill()width(value, units='pt')value, in units.cap(kind)kind, may be one of: 'butt', 'round', 'square'.join(kind)kind, may be one of: 'miter', 'round', 'bevel'.limit(value)limit.line(x0, y0, x1, y1)x0, y0 to x1, y1.polyline(*coordinates)coordinates.polygon(*coordinates)coordinates.rectangle(x, y, width, height)x, y and size width, height.circle(x, y, r)x, y and radius r.ellipse(x, y, rx, ry)x, y and horizontal/vertical radius rx/ry.path(*commands)commands. Valid types are: moveto, lineto, quato, curveto, closepath.placedshapepage.place() instead.position(x, y)x, y.moveto(x, y)x, y.lineto(x, y)x, y.quadto(x1, y1, x, y)x, y, using control point x1, y1.curveto(x1, y1, x2, y2, x, y)x, y, using control point x1, y1 and x2, y2.closepathmoveto, lineto, quadto, curveto, closepathtransform(a, b, c, d, e, f)a, b, c, d, e, f. parsepath(data)union(subject, clipper, perturbation=0.0)intersection(subject, clipper, perturbation=0.0)difference(subject, clipper, perturbation=0.0)subject and clipper polygons. Scatter the vertices by ± perturbation amount.strike(font)font. Defaults are: 10 pt size, 12 pt leading and gray(0) color.size(size, leading=0.0, units='pt')size and leading, in units. Zero leading calculates a default value according to size.color(color)color.width(string)string, in points.span(string)string. Spans may form a paragraph.paragraph(string)string. Paragraphs may form a text.text(string)string to paragraphs at newline characters, with each paragraph having one span. Placed texts may be chained to enable text flow. Text yields a sequence of characters tied to the font.outlines(string)string to paragraphs at newline characters, with each paragraph having one span. Placed outlines may be chained to enable text flow. Outlines yield a sequence of Bezier paths based on glyph outlines.paragraph(spans)spans.text.open(path, substitutes), outlines.open(path, substitutes)path and use font substitutes. Currently not implemented.text(paragraphs), outlines(*paragraphs)paragraphs.placedtext, placedoutlinespage.place() instead. Blocks have infinite sizes by default.position(x, y)x, y.frame(x, y, width, height)x, y, resize it to width, height and reflow it along the chain.overflow()lines()group.open(path)path. Currently not implemented.group(units='mm')units.units(units='mm')units of the group.place(item)item into the group.chain(block)block, enabling its text to flow along the linked blocks. A chain eventually eliminates overflow.placedgrouppage.place() instead.position(x, y)x, y.scale(factor)factor.tree(item)item.add(item)item and return it.layout()transpose()frame(x, y, width, height)x, y and resize it to width, height.nodes()x, y coordinates, parent, children and the original item.view(data)data into the viewer if called inside of Even, otherwise do nothing.pip install flat
It currently requires Python 2.7.
Alternatively, there is Even application which integrates the library, a viewer and Python editor.
Juraj Sukop, contact@xxyxyz.org