Search code examples
arrayspython-3.ximagevector-graphicsreportlab

How do I convert pixels/numpy-array into vector-dots?


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')?


Solution

  • 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")