I want do convert pixel values of a grayscale picture into vector dots, so that the grayscale value of a pixel determines the radius of the corresponding dot. But Im stuck completely, the project has to be finished on Sunday an Im really desperate at the moment
Background: For my university course "introduction to python", I want do construct a shameless copy of the "rasterbator" (https://rasterbator.net/) with python (but in a much more primitive way).
How do I want to approach this?: I load an image with PIL, make it grayscale and transform it to a numpy array. Then I slice the array into many little square arrays (one segment for every intended dot), calculate the mean values for each array and put it back together in one array that is now much smaller than the original one. Up to that point, I was able to do it (but it took me a long time). Now I want to "replace" the Pixels with dots and create several PDFs, so that you can print it, glue it together and have a large poster.
Could this approach work? Or am I barking up the wrong tree?
I'm a bloody python beginner. The problem with python is for me, that there are so many modules I dont know about. Probably the answer is really easy, but I simply dont know where to look. I would really appreciate if anyone can tell me, if I am heading the right way or point me in the right direction.
Many Thanks in advance
Here the code of what I've managed so far (it isnt much though)
from PIL import Image as img
import numpy as np
greyscale = np.asarray(img.open("test.jpg").convert("L")) #load picture into array and make it greyscale
end_width = 1500 # chosen width of final picture in mm (will be with kwargs later on)
dot_size = 13 #chosen dot-size of final pictutre in mm (will be with kwargs later on)
estimate_dot_count_x = int(np.ceil(end_width/dot_size)) # estimates the "horizontal resolution"
pixel_in_segment = int(np.ceil(greyscale.shape[1]/estimate_dot_count_x)) #calculates the edge length of a segment
W=pixel_in_segment #just for shorter formular later on
estimate_dot_count_y = int(np.ceil(greyscale.shape[0]/pixel_in_segment)) # estimates the "vertical resolution"
final_dot_count_x=int(np.ceil(greyscale.shape[1]/W)) #final horizontal resolution for shape of new array
final_dot_count_y=int(np.ceil(greyscale.shape[0]/W)) #final vertical resolution for shape of new array
#slice array into multiple pieces
tiles = [greyscale[x:x+W,y:y+W] for x in range(0,greyscale.shape[0],W) for y in range(0,greyscale.shape[1],W)]
#calculate mean values of each segment an safe it to list
average_list = []
for pixel in tiles:
result=int(np.mean(pixel))
average_list.append(result)
#convert list back into an array
downscale=np.asarray(average_list, dtype=int).reshape(final_dot_count_y,final_dot_count_x)
EDIT: somehow I manged to draw the array to vector-dots:
#inverse and normalize gray value so That I can multiply with max dot size
for ix,iy in np.ndindex(downscale.shape):
downscale[ix,iy]= float(1-downscale[ix,iy]*(1/255))
reportlab was the key I was looking for...
from reportlab.lib.units import mm
from reportlab.pdfgen import canvas
#making dots
def printing(c):
c.translate(spacing*0.5,imh-(spacing*0.5))
for ix,iy in np.ndindex(downscale.shape):
c.circle(iy*(spacing), ix*(-spacing), downscale[ix, iy]*max_dot_size, stroke=1, fill=1)
c = canvas.Canvas("hello.pdf", pagesize=(imwidth, imhight))
printing(c)
c.showPage()
c.save()
That raises the question: How can I tell reportlab, that I want to print this big canvas (which is like 2m x1.5m in dimension) to multiple pages in a common printer format ('letter' or 'A4')?
Just for your information, I could rebuild the "Rasterbator" The code is probably a bit messy and there is a lack of error handling but I'ts working perfectly fine and I ran out of time. So this is what I was uploading. Some variables are in german, sorry. I tend to mix languages. Have to change that.
the module reportlab is required
from PIL import Image as img
import numpy as np
from reportlab.lib.units import mm
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
from math import sqrt
#load image to array and make it greyscale
input_file = input("Please enter the image file you want do convert: ")
greyscale = np.asarray(img.open(input_file).convert("L"))
print("\n"+"Image resolution is " + str(greyscale.shape[1]) + "x" + str(greyscale.shape[0]))
#defining width of poster
print("\n"+"please enter the target width of your poster")
print("remember, the dimensions of an A4 sheet is: 210mm x 297mm ")
end_width= int(input("target poster width in mm: "))
#defining grid size of poster
print('\n'+'The distance between 2 Points in the grid. Choose the grid size wisely in relation to the size of your poster '+'\n'+'recommended size is 7-12mm')
print('please notice, that the maximum dot size is higher than the grid size (factor 1.4) to allow pure black coverage')
grid_size = int(input("Please enter the target grid size in mm: "))
#select orientation
print("your sheets can be arranged in portrait or landscape orientation")
print_format = input("Please enter p for portrait or l for landscape :")
if print_format=="l":
height, width = A4 #Landscape
elif print_format=="p":
width, height = A4 #Portrait
else:
print("-invalid input- continuing with default (portrait)")
width, height = A4 #Portrait
# calculates the "x-resolution" as a base for further calculations
estimate_dot_count_x = int(np.ceil(end_width/grid_size))
#calculates the size of a segment in array
pixel_in_segment = int(np.ceil(greyscale.shape[1]/estimate_dot_count_x))
W=pixel_in_segment #obsolete, just for shorter formulars later on
#final horizontal resolution for shape of new array
final_dot_count_x=int(np.ceil(greyscale.shape[1]/W))
#final vertical resolution for shape of new array
final_dot_count_y=int(np.ceil(greyscale.shape[0]/W))
#slice array into multiple pieces
tiles = [greyscale[x:x+W,y:y+W] for x in range(0,greyscale.shape[0],W) for y in range(0,greyscale.shape[1],W)]
#calculate mean values of each segment an safe it to list
average_list = []
for pixel in tiles:
result=int(np.mean(pixel))
average_list.append(result)
#convert list back into an array
downscale=np.asarray(average_list, dtype=float).reshape(final_dot_count_y,final_dot_count_x)
print('\n'+'downscaling picture...')
#prepare data to work in point scale
spacing=grid_size*mm
#calculating final poster size
imw=downscale.shape[1]*spacing
imh=downscale.shape[0]*spacing
#scaling dots to allow complete coverage with black for very dark areas
max_dot_size=spacing*sqrt(2)/2
#inverse and normalize pixel value
for ix,iy in np.ndindex(downscale.shape):
downscale[ix,iy]= float(1-downscale[ix,iy]*(1/255))
print('\n'+'printing image to pdf...')
#calculate numer of A4 sheets required for printing
pages_w = int(np.ceil(imw/width))
pages_h = int(np.ceil(imh/height))
#stuff for showing progress while printing
seitenzahl=0
gesamtseitenzahl = pages_w*pages_h
def printing(c):
#auxillary variables for iterating over poster
left=width*x
top=height*y
#iterate instructions
c.translate(-left+spacing*0.5,imh-top+(spacing*0.5))
#drawing the circles
for ix,iy in np.ndindex(downscale.shape):
c.circle(iy*(spacing), ix*(-spacing), downscale[ix, iy]*max_dot_size, stroke=1, fill=1)
#setting canvas properties
c = canvas.Canvas(str(input_file)+".pdf", pagesize=(width, height))
#make pages
for x in range(pages_w):
for y in range(pages_h):
#progress documentation
seitenzahl = seitenzahl+1
#call printing function
printing(c)
#progress documentation
print("printing page " + str(seitenzahl)+ " of " + str(gesamtseitenzahl))
c.showPage()
#save to disk
print('\n'+'save to disk...')
c.save()
print('...PDF successfully saved as ' + str(input_file)+".pdf")