1

I am trying to superimpose a grid onto an image so that I can use it to position text. I am using PIL (Python) to draw the grid. Ideally I would have dashed and reasonably transparent minor gridlines so that I can see through to the image, and solid and less transparent major gridlines.

A working example of what I am trying to do is below.

My questions refer to the following lines (simplified for presentation):

draw.line( (10, 0, 10, imgHeight), fill = (180, 180, 255, 1) )
draw.line( (10, 0, 10, imgHeight), fill = (180, 180, 255, 200) )

1) I understood the fourth parameter of the fill controls the transparency of the line, with 0 being completely transparent and 255 being completely opaque. However, I cannot detect any difference between a value or 1 or 200, and indeed, when I use 1, it hides the text beneath it when I thought I would be able to see it.

2) How can you make dashed lines in PIL? I have not seen how. Is it possible?

Finally, I am relatively new to programming and python, if there are any tips you can offer on the below MWE to bring it up to a better standard, I would be most grateful.

Note: The example below uses red lines for the major gridlines, but I would like them to be the same colour as the minor gridlines, with the minor gridlines transparent and dashed.

import PIL
from PIL import Image, ImageFont, ImageDraw

grid = 'on'
minor = 5
major = 50

font = ImageFont.truetype('arial.ttf', 10)
textColor = (38, 23, 255)

img = Image.open('2013MCS7.jpg')
draw = ImageDraw.Draw(img)


def gridlines(img, gridWidth, color, lineWidth=1, direction='b', label='n', labelRepeat=None, LabelFont='arial', labelFontSize=10):
    '''
    Draws gridlines on an image.
        img : the image to be modified
        gridwith : (int > 0) : size of gridlines in pixels
        color : tuple of length 3 or 4 (4th argument controls transparency) : color of gridlines and labels
        lineWidth : (int > 0) : width of gridlines
        direction : ('v','h','b') : specifies either vetical gridlines, horizontal gridlines or both
        label : ('y','n') : turns grid labels on or off
        labelRepeat : 
    '''
    imgWidth, imgHeight = img.size
    draw = ImageDraw.Draw(img)
    textColor = (color[0], color[1], color[2])
    textFont = ImageFont.truetype(LabelFont+'.ttf', labelFontSize) 

    # add gridlines
    if direction.lower() == 'b' or direction.lower() == 'v':
        for v in range(1, int(imgWidth/gridWidth)+1):
            draw.line( (v*gridWidth, 0, v*gridWidth, imgHeight), fill = color, width = lineWidth )

    if direction.lower() == 'b' or direction.lower() == 'h':
        for h in range(1, int(imgHeight/gridWidth)+1):
            draw.line( (0, h*gridWidth, imgWidth, h*gridWidth), fill = color, width = lineWidth )

    # add labels
    if label.lower() == 'y':
        for v in range(1, int(imgWidth/gridWidth)+1):
            for h in range(1, int(imgHeight/gridWidth)+1):
                if v == 1:
                    draw.text( ( 3, 1+h*gridWidth), str(h), fill = textColor, font = textFont )
                if h == 1:
                    draw.text( ( 1+v*gridWidth, 3), str(v), fill = textColor, font = textFont )
                if labelRepeat is not None:
                    if ( h % labelRepeat == 0 ) and ( v % labelRepeat == 0 ): 
                        draw.text( ( 1+v*gridWidth, 1+h*gridWidth), '('+str(h)+','+str(v)+')', fill = textColor, font = textFont )



# draw gridlines

if grid == 'on':
    gridlines(img, minor, (180, 180, 255,   1))
    gridlines(img, major, (255,   0,   0, 100), label='Y', labelRepeat=3)

# populate form

draw.text( (6*major+2*minor+3, 6*major+5*minor+2), 'econ_total', fill=textColor, font=font)
draw.text( (6*major+2*minor+3, 7*major+1*minor+2), 'notional_taxed', fill=textColor, font=font)
draw.text( (6*major+2*minor+3, 7*major+7*minor+2), 'notional_employer', fill=textColor, font=font)
draw.text( (6*major+2*minor+3, 8*major+4*minor+3), 'supca_total', fill=textColor, font=font)
draw.text( (6*major+2*minor+3, 9*major+2*minor-1), 'cgt_exempt_sb_ret', fill=textColor, font=font)
draw.text( (6*major+2*minor+3, 9*major+7*minor+0), 'cgt_exempt_sb_15yr', fill=textColor, font=font)


del draw
img.save('marked-up - 2013MCS7.jpg')
brb
  • 1,123
  • 17
  • 40

1 Answers1

4

First, replace grid = 'on' with a boolean. You should be using True or False, not string comparisons, to check if the setting is enabled with if grid:.

Second, PIL doesn't have the ability to draw dashed lines by default. To draw a dashed line you have to draw several small lines spaced apart from each other. However, for what you're trying to do, dashed lines should not be necessary; Setting the opacity of a line can be done with a high degree of control.

Finally, you should be able to achieve very fine control over the opacity of the lines with your approach. The gridlines() function is doing a lot of extra work; It redraws the image for no reason, draws text, etc. So what's most likely going on is your grid lines are being drawn over each other several times, causing them to get more and more opaque.

If it turns our your grid drawing is fine, then you should save your image as a PNG, not a JPEG. JPEG does not render red very well since it's designed to save photographic imagery and that means it dedicates more information to storing greens and blues, which our eyes see more of.

Soviut
  • 88,194
  • 49
  • 192
  • 260
  • Thank you. I will replace the on/off with a True/False and save in a PNG format as you have suggested. 2 quick questions. (1) when you say 'it redraws the image for no reason' are you referring to the statement 'draw = ImageDraw.Draw(img)' in the function, or is it happening elsewhere as well (that I am not aware of)? I put this in the function to make it self-contained without having to pass draw, but I will rework. (2) I don't understand 'So what's most likely going on is your grid lines are being drawn over each other several times'. I cannot see how this happens. Can you elaborate? – brb Apr 25 '17 at 05:37
  • Don't forget to mark this question correct if it solves your problem. – Soviut May 01 '17 at 14:38
  • It didn't really solve my problem as per comment above. I cannot get the transparency to work (it is still opaque for me) and I cannot see how my code is resulting in grid lines being drawn over each other multiple times (the for loop draws them at different intervals). I have taken out the second reference to 'draw = ImageDraw.Draw(img)' as I assume this is what is meant by 'it redraws the image for no reason', but I orginally did this to make the function self contained without passing the argument draw as I thought this would be better, but I am not sure of the cost of drawing this twice. – brb May 02 '17 at 08:38