Be a Python titan! I’d like to put together some scripting idea for photography!
Last summer here in Graz was rather mixed. But to be honest I kind of like it unfortunately this summer is super hot at 35ºC+. While in Ireland all seasons are covered in one day or even within 20 minutes! That’s Irish weather for you, very much like Python script you can make things in 20 minutes in all kind directions.
Let’s begin with Python, why I’m I writing or talking about Python. It’s been around for such a long time. Certainly with the advent of the Raspberry PI, a mini computer which made Python more prevalent. One thing you can say about this scripting language, there’s most likely a module to do pretty much anything you can think of. This leads me onto photography and onto my quest to automate some of my workflows and crazy hair brained ideas. I bet you, there’s something in here that will also inspire you, so let’s get started…
What kind of things you can do with Python ?
- Getting Started with Jupyter Notebook
- Resize and Exporting
- TIFF to JPEG for exporting scanned images for Instagram or the web
- Converting an image to a negative and vice versa
- B&W Conversion & Sepia
- Add Tint for improving Salt Paper/Cyanotype negatives
- Putting all together
If you’d like to contribute to this page, please comment down below.
If you just wish to have the script for creating contact print negatives for Cyanotypes/VDB/Salt paper then jump down the page.
First off ideas…
Imagine taking an image with a digital camera, the image is processed for Cyanotype and printed on a transparency sheet for contact printing a Salt Paper or Cyanotype. However all this is portable using an Raspberry Pi and a portable printer running on battery. Then using the UV box that I designed previously for printing we’d have a complete mobile studio to setup anywhere we like, sounds crazy ? I think all the pieces to make this happen can be found on this page. But it’s just an idea!
Steps to do this idea…
- Take image with a digital camera using a Raspberry Pi with Python (probably using gPhoto2)
- Converting an image to a negative
- Add Contrast Curve
- Add Tint for improving Salt Paper/Cyanotype negatives
- Print image on transparency foil. (https://tutorials-raspberrypi.com/raspberry-pi-printer-setup-and-printing-images-by-pressing-button/)
Well are a few of mine…
- Automating: Cyanotype / VDB negatives from colour images
- Automating: Film scans when using DSLR
- Automating: Adding EXIF/GPS data for images and negative scans
- Preparing your scanned images TIFFs for Instagram / Twitter with a nice border.
First we’ll need a sample image to show the results for the code examples below

Getting Started with Jupyter Notebook
Firstly I’m going to start with a really super tool called Jupyter Notebook this will help you to play and try out things super easy.
You’ll need an installation of Python and Pip. First go to your command line in Windows/MacOs/Linux to see what’s already installed.
python -V
Should return something like : python 3.8.5 or higher!
MacOs Tip : If you’re trying to use Python on a MacOs. Please follow this guide which solutions quite a lot of issues with the default Python.
If you’ve no Python install or an older version please install Python and proceed next task installing Jupyter Notebook.
pip install jupyterlab pillow
Now Start the notebook
jupyter lab
Source: Edit images with Jupyter and Python
Now it’s time start coding!
Resizing and Exporting Images
So for example how about resizing and exporting images in Python script. That can’t be too hard for starter ?
x, y = pic.size
x //= 5
y //= 5
smaller = image.resize((x, y))
PILLOW
Python Imaging Library (abbreviated as PIL) (in newer versions known as Pillow) is a free and open-source additional library for the Python programming language that adds support for opening, manipulating, and saving many different image file formats. It is available for Windows, Mac OS X and Linux.
Pillow offers several standard procedures for image manipulation. These include:
- per-pixel manipulations,
- masking and transparency handling,
- image filtering, such as blurring, contouring, smoothing, or edge finding,
- image enhancing, such as sharpening, adjusting brightness, contrast or color,
- adding text to images and much more.
- also GPS/EXIF information
Source: https://en.wikipedia.org/wiki/Python_Imaging_Library
Library : https://pillow.readthedocs.io/en/stable/
pip install Pillow
Example: TIFF to JPEG for exporting scanned images for Instagram or the web
im = Image.open('sample.tiff')
print "Generating jpeg for %s" % name
im.thumbnail(im.size)
im.save('sample.jpeg', "JPEG", quality=100)
Add borders to an image
from PIL import Image, ImageOps
img = Image.open('original-image.png')
img_with_border = ImageOps.expand(img,border=300,fill='black')
img_with_border.save('imaged-with-border.png')
Add text to an image
from PIL import Image, ImageFont, ImageDraw img = Image.open("image.jpg") title_font = ImageFont.truetype('playfair/playfair-font.ttf', 200) title_text = "Caption for the image" image_editable = ImageDraw.Draw(img) image_editable.text((15,15), title_text, (237, 230, 211), font=title_font)
B&W Conversion & Sepia
As Cyanotypes and VDB are effectively monotone processes it goes without saying we’ll need to convert to B&W.
Also with the Pillow module, a simple bit of code will do a simple B&W conversion
image.convert('L') # convert image to
Check out https://www.blog.pythonlibrary.org/2017/10/11/convert-a-photo-to-black-and-white-in-python/
What need is the possibilities to use Red, Yellow, Green and Blue filters but more on that later. Let’s start with a Sepia Example
Sepia Example
To print our negative
def make_linear_ramp(white):
ramp = []
r, g, b = white
for i in range(255):
ramp.extend((int(r*i/255),int(g*i/255),int(b*i/255)))
return ramp
sepia = make_linear_ramp((255, 240, 192))
sepiaImage = pic.convert("L")
sepiaImage.putpalette(sepia)
sepiaImage = sepiaImage.convert("RGB")
sepiaImage.save("sepia.jpg")
Converting an image to a negative and vice versa
Again with using the Pillow library to do this rather simple operation. This should cover both negative to positive and the other way around.
ImageOps.invert(image)
Creating Tinted Images for Cyanotypes Negatives
To further improve negatives for contact printing for Salt Paper and Cyanotypes, a filter can be applied to the B&W negative. See my post for more information

from PIL import Image
from PIL.ImageColor import getcolor, getrgb
from PIL.ImageOps import grayscale
def image_tint(src, tint='#ffffff'):
#if Image.isStringType(src): # file path?
src = Image.open(src)
if src.mode not in ['RGB', 'RGBA']:
raise TypeError('Unsupported source image mode: {}'.format(src.mode))
src.load()
tr, tg, tb = getrgb(tint)
tl = getcolor(tint, "L") # tint color's overall luminosity
if not tl: tl = 1 # avoid division by zero
tl = float(tl) # compute luminosity preserving tint factors
sr, sg, sb = map(lambda tv: tv/tl, (tr, tg, tb)) # per component
# adjustments
# create look-up tables to map luminosity to adjusted tint
# (using floating-point math only to compute table)
luts = (tuple(map(lambda lr: int(lr*sr + 0.5), range(256))) +
tuple(map(lambda lg: int(lg*sg + 0.5), range(256))) +
tuple(map(lambda lb: int(lb*sb + 0.5), range(256))))
l = grayscale(src) # 8-bit luminosity version of whole image
if Image.getmodebands(src.mode) < 4:
merge_args = (src.mode, (l, l, l)) # for RGB verion of grayscale
else: # include copy of src image's alpha layer
a = Image.new("L", src.size)
a.putdata(src.getdata(3))
merge_args = (src.mode, (l, l, l, a)) # for RGBA verion of grayscale
luts += tuple(range(256)) # for 1:1 mapping of copied alpha values
return Image.merge(*merge_args).point(luts)
if __name__ == '__main__':
import os
import sys
input_image_path = 'Image-12.jpg'
print('tinting "{}"'.format(input_image_path))
root, ext = os.path.splitext(input_image_path)
suffix = '_result_py{}'.format(sys.version_info[0])
result_image_path = root+suffix+ext
print('creating "{}"'.format(result_image_path))
result = image_tint(input_image_path, '#FFBF73')
if os.path.exists(result_image_path): # delete any previous result file
os.remove(result_image_path)
result.save(result_image_path) # file name's extension determines format
print('done')
Souce:Stack Overflow

Let’s put altogether to make our Cyanotype negative
Putting a script together to create a preview which gives you an idea how a Cyanotype print might actually look like while the script produces the tinted negative for printing. It’s a kind of all-in-one script which gives three posssible outputs, less, more and no contrast.
from PIL import Image, ImageEnhance, ImageOps from PIL.ImageColor import getcolor, getrgb from PIL.ImageOps import grayscale def image_tint(src, tint='#ffffff'): #if Image.isStringType(src): # file path? src = Image.open(src) if src.mode not in ['RGB', 'RGBA']: raise TypeError('Unsupported source image mode: {}'.format(src.mode)) src.load() # Make Negative negative=ImageOps.invert(src) tr, tg, tb = getrgb(tint) tl = getcolor(tint, "L") # tint color's overall luminosity if not tl: tl = 1 # avoid division by zero tl = float(tl) # compute luminosity preserving tint factors sr, sg, sb = map(lambda tv: tv/tl, (tr, tg, tb)) # per component # adjustments # create look-up tables to map luminosity to adjusted tint # (using floating-point math only to compute table) luts = (tuple(map(lambda lr: int(lr*sr + 0.5), range(256))) + tuple(map(lambda lg: int(lg*sg + 0.5), range(256))) + tuple(map(lambda lb: int(lb*sb + 0.5), range(256)))) l = grayscale(negative) # 8-bit luminosity version of whole image if Image.getmodebands(negative.mode) < 4: merge_args = (negative.mode, (l, l, l)) # for RGB verion of grayscale else: # include copy of src image's alpha layer a = Image.new("L", src.size) a.putdata(negative.getdata(3)) merge_args = (negative.mode, (l, l, l, a)) # for RGBA verion of grayscale luts += tuple(range(256)) # for 1:1 mapping of copied alpha values return Image.merge(*merge_args).point(luts) if __name__ == '__main__': input_image_path = 'Image-12.jpg' # Tint Image result = image_tint(input_image_path, '#FFBF73') # Contrast enhancer = ImageEnhance.Contrast(result) factor = 1 #gives original image im_output = enhancer.enhance(factor) im_output.save('negative-image.jpg') ImageOps.invert(im_output).save('positive.jpg') factor = 1.5 #increase contrast im_output = enhancer.enhance(factor) im_output.save('more-contrast-negative.jpg') ImageOps.invert(im_output).save('more-contrast-positive.jpg') factor = 0.5 #decrease constrast im_output = enhancer.enhance(factor) im_output.save('less-contrast-negative.jpg') ImageOps.invert(im_output).save('less-contrast-positive.jpg')![]()
![]()
Next Level
As this code will run pretty much on any device. Taking a Raspberry PI and DSLR connect them together this would create a kind of automate system for producing contact printing negatives for Cyanotype/VBD/Salt paper.
Using gPhoto and Touchscreen a whole remote printing station could be made along with the UV Box
Probably Circuit Python would be a way to go and using PyCups to do the printing part
2 Comments
This article is awesome. My all confusion are clear. If you want to know, how to ad text in iamge with you can read from here https://rawcode.xyz/blog/How-to-add-text-in-Image-using-python.html
Hi Rakib,
Glad you enjoy and got some use out of it.
Best,
Gavin
By Post Author