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.
There is also a public repository hosting additional examples.
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.placedimage
page.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.page
document.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 height
units
.meta(
title
)
title
.size(
width, height, units='mm'
)
width
by height
units
.addpage()
pdf(
path='', compress=False, bleed=False, cropmarks=False
)
path
is set, save it as well. Reduce size by setting compress
to True
(currently not implemented), include bleed
or cropmarks
by setting the arguments to True
.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
.font
font.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
.placedshape
page.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
.closepath
moveto
, lineto
, quadto
, curveto
, closepath
transform(
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
, placedoutlines
page.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
.placedgroup
page.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
Since version 0.3, it requires Python 3.
Alternatively, there is Even application which integrates the library, a viewer and Python editor.
Juraj Sukop, contact@xxyxyz.org