An interpreter of image transformations, inspired by *Beyond Photography: The Digital Darkroom*
Darko is an interpreter of image transformations inspired by Popi (Portable Pico), described
in the classical book
Beyond Photography: The Digital Darkroom by
Gerard J. Holzmann.
Have fun distorting some pictures with one-liners, and feel free to contribute to this project.
Darko is written in Python. To run it you need to install numpy and OpenCV. Last time it was tried with Python 3.7.6, but any Python 3 version should work.
$ pip install SimpleParse
$ pip install opencv-python
You can run a demo that generates all images shown in the Catalogue of Transformations
by doing:
$ python darko.py
python -m pytest tests
Let’s apply some transformations on the following picture of scientist/physics-professor/safe-cracker/bongos-player/nobel-laureate extraordinaire Richard Feynman.
Let’s load the darko interpreter and an image, and let’s operate on it using bilinear sampling
import darko.interpreter
# Create an interpreter
darko_interpreter = darko.interpreter.Interpreter()
# Load an image, and reads subpixels with bilinear interpolation
darko_interpreter.load('feynman.jpg', sampling='bilinear')
Not let’s rotate the image by 45 degrees
# Apply the transformation
darko_interpreter.eval('new[x, y] = old[rect(r, a + rad(45))]')
# Save result to a file
darko_interpreter.save('feynman-rotated.jpg')
Zooming-in on the image
darko_interpreter.eval('new[x, y] = old[rect(r / 2, a)]')
darko_interpreter.save('feynman-zoomed.jpg')
Mirroring and translating the image
darko_interpreter.eval('new[x, y] = old[abs(X / 2 - x), y]')
darko_interpreter.save('feynman-mirrored.jpg')
Thresholding the image in 2-pixel blocks
transformation = """
new[x, y] = gray(old[floor(x / 2) * 2, floor(y / 2) * 2]) > 150 ?
rgb(255, 255, 255) : rgb(0, 0, 0)
"""
darko_interpreter.eval(transformation)
darko_interpreter.save('feynman-icon.jpg')
A Warhol-like mosaic
transformation = """
new[x, y] =
old[x % (X / 2) * 2, y % (Y / 2) * 2] *
((x < X / 2 ?
(y < Y / 2 ? rgb(255, 0, 0) : rgb(0, 255, 0)) :
(y < Y / 2 ? rgb(255, 255, 0) : rgb(0, 255, 255))) / Z)
"""
darko_interpreter.eval(transformation)
darko_interpreter.save('feynman-mosaic.jpg')
A funky late 60s or early 70s look
transformation = """
new[x, y] =
0.33 * ((gray(Z - old[x - 25, y]) / Z) * rgb(0, 0, 255)) +
0.33 * ((gray(Z - old[x, y]) / Z) * rgb(0, 255, 0)) +
0.33 * ((gray(Z - old[x + 25, y]) / Z) * rgb(255, 0, 0))
"""
darko_interpreter.eval(transformation)
darko_interpreter.save('feynman-lsd.jpg')
You can use either polar coordinates or rectangular coordinates in your transformations. The origin of the
rectangular coordinates is at the top-left corner of the image, and the origin of the polar coordinates is
in the center of the image.
The following keywords can be used in transformations:
old: original image
new: transformed image
x: current pixel’s x in rectangular coordinates
y: current pixel’s y in rectangular coordinates
r: current pixel’s radius in polar coordinates
a: current pixel’s angle (in radians) in polar coordinates
cx: x of center of image rectangular coordinates
cy: y of center of image rectangular coordinates
R: Half-length of image diagonal
X: Width of image
Y: Height of image
Z: Depth of image (255)
The following operators can be used within expressions
Arithmetic: +, -, , /, *, %
Comparison: <, >, ==, !=, <=, >=
Logic: &&, ||
Trinary: cond ? texpr : fexpr
rect(r, a) -> (x, y): Polar to rectangular coordinates
polar(x, y) -> (r, a): Rectangular to polar coordinates
sin(x): Sine
cos(x): Cosine
sqrt(x): Square root
deg(x): Radians to degrees
rad(x): Degrees to radians
gray(p): Gray level of pixel
rgb(r, g, b): Color tuple
ceil(x): Ceiling
floor(x): Floor
Most of the following transformations were taken from
Beyond Photography: The Digital Darkroom.
new[x, y] = old[rect(r, a - r / 50)]
new[x, y] = old[x + (x % 32) - 16, y]
new[x, y] = old[x + 10 * sin(rad(y) * 10), y]
new[x, y] = old[x + sin(rad(x)) * 150, y + sin(rad(y * 1.18)) * 89]
new[x, y] = old[x, y + 10 * sin(rad(y) * 10)]
new[x, y] = Z - old[x, y]
new[x, y] = old[x, y + (deg(a) + r / 4) % 64 - 16]
new[x, y] = old[rect(1.5 * r ** 2 / R, a)]
new[x, y] = old[rect(0.5 * sqrt(r * R), a)]
new[x, y] = old[x + 10 * sin(rad(y) * 5), y + 10 * sin(rad(x) * 5)]
new[x, y] = old[rect(r + 10 * sin(rad(r) * 10), a - r / 50)]
new[x, y] = old[rect(1.5 * r ** 2 / R + 10 * sin(rad(r) * 10), a)]
new[x, y] = old[floor(x / 10) * 10, floor(y / 10) * 10]
new[x, y - gray(old[x, y]) * 0.1] = old[x, y]