Drawing diagonal lines on an image

13,525

Solution 1

Most graphic libraries have some way to draw a line directly.

In JES there is the addLine function, so you could do

addLine(picture, 0, 0, width, height)

If you're stuck with setting single pixels, you should have a look at Bresenham Line Algorithm, which is one of the most efficient algorithms to draw lines.

A note to your code: What you're doing with two nested loops is the following

for each column in the picture
  for each row in the current column
     set the pixel in the current column and current row to black

so basically youre filling the entire image with black pixels.

EDIT

To draw multiple diagonal lines across the whole image (leaving a space between them), you could use the following loop

width = getWidth(picture)
height = getHeight(picture)
space = 10
for x in range(0, 2*width, space):
  addLine(picture, x, 0, x-width, height)

This gives you an image like (the example is hand-drawn ...)

diagonal lines

This makes use of the clipping functionality, most graphics libraries provide, i.e. parts of the line that are not within the image are simply ignored. Note that without 2*width (i.e. if x goes only up to with), only the upper left half of the lines would be drawn...

Solution 2

I would like to add some math considerations to the discussion...

(Just because it is sad that JES's addLine function draws black lines only and is quite limited...)

Note : The following code uses the Bresenham's Line Algorithm pointed out by MartinStettner (so thanks to him).

The Bresenham's line algorithm is an algorithm which determines which order to form a close approximation to a straight line between two given points. Since a pixel is an atomic entity, a line can only be drawn on a computer screen by using some kind of approximation.

Note : To understand the following code, you will need to remember a little bit of your basic school math courses (line equation & trigonometry).

Code :

# The following is fast implementation and contains side effects...

import random

# Draw point, with check if the point is in the image area
def drawPoint(pic, col, x, y):
   if (x >= 0) and (x < getWidth(pic)) and (y >= 0) and (y < getHeight(pic)):
     px = getPixel(pic, x, y)
     setColor(px, col)


# Draw line segment, given two points
# From Bresenham's line algorithm
# http://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm
def drawLine(pic, col, x0, y0, x1, y1):

   dx = abs(x1-x0)
   dy = abs(y1-y0) 
   sx = sy = 0

   #sx = 1 if x0 < x1 else -1
   #sy = 1 if y0 < y1 else -1

   if (x0 < x1): 
     sx = 1 
   else: 
     sx = -1
   if (y0 < y1):
     sy = 1 
   else: 
     sy = -1

   err = dx - dy

   while (True):

     drawPoint(pic, col, x0, y0)

     if (x0 == x1) and (y0 == y1): 
       break

     e2 = 2 * err
     if (e2 > -dy):
       err = err - dy
       x0 = x0 + sx

     if (x0 == x1) and (y0 == y1):
       drawPoint(pic, col, x0, y0)
       break

     if (e2 <  dx):
       err = err + dx
       y0 = y0 + sy 


# Draw infinite line from segment
def drawInfiniteLine(pic, col, x0, y0, x1, y1):
   # y = m * x + b
   m = (y0-y1) / (x0-x1)
   # y0 = m * x0 + b   =>   b = y0 - m * x0
   b = y0 - m * x0

   x0 = 0
   y0 = int(m*x0 + b)
   # get a 2nd point far away from the 1st one
   x1 = getWidth(pic) 
   y1 = int(m*x1 + b)

   drawLine(pic, col, x0, y0, x1, y1)


# Draw infinite line from origin point and angle
# Angle 'theta' expressed in degres
def drawInfiniteLineA(pic, col, x, y, theta):

   # y = m * x + b
   dx = y * tan(theta * pi / 180.0)  # (need radians)
   dy = y

   if (dx == 0):
     dx += 0.000000001 # Avoid to divide by zero 

   m = dy / dx

   # y = m * x + b   =>   b = y - m * x
   b = y - m * x

   # get a 2nd point far away from the 1st one
   x1 = 2 * getWidth(pic)
   y1 = m*x1 + b

   drawInfiniteLine(pic, col, x, y, x1, y1)


# Draw multiple parallele lines, given offset and angle
def multiLines(pic, col, offset, theta, randOffset = 0):
   # Range is [-2*width, 2*width] to cover the whole surface
   for i in xrange(-2*getWidth(pic), 2*getWidth(pic), offset):
      drawInfiniteLineA(pic, col, i + random.randint(0, randOffset), 1, theta)

# Draw multiple lines, given offset, angle and angle offset
def multiLinesA(pic, col, offsetX, offsetY, theta, offsetA):
   j = 0
   # Range is [-2*width, 2*width] to cover the whole surface
   for i in xrange(-2*getWidth(pic), 2*getWidth(pic), offsetX):
      drawInfiniteLineA(pic, col, i, j, theta)
      j += offsetY
      theta += offsetA



file = pickAFile()
picture = makePicture(file)
color = makeColor(0, 65, 65) #pickAColor()
#drawline(picture, color, 10, 10, 100, 100)
#drawInfiniteLine(picture, color, 10, 10, 100, 100)
#drawInfiniteLineA(picture, color, 50, 50, 135.0)
#multiLines(picture, color, 20, 56.0)
#multiLines(picture, color, 10, 56.0, 15)
multiLinesA(picture, color, 10, 2, 1.0, 1.7) 

show(picture)


Output (Painting by Pierre Soulages) :


enter image description here

enter image description here

enter image description here


Hope this gave some fun and ideas to JES students... And to others as well...

Solution 3

Where does your picture object comes from? What is it? What is not working so far? And what library for image access are you trying to use? (I mean, where do you get, or intend to get "getWidth, getHeight, getPixel, setColor) from?

I think no library that gives you a "pixel" as a whole object which can be used in a setColor call exists, and if it does, it would be the slowest thing in the World - maybe in the galaxy.

On the other hand, if these methods did exist and your Picture, the code above would cover all the image in black - you are getting all possible "y" values (from 0 to height) inside all possible x values (from 0 to width) of the image, and coloring each Black.

Drawing a line would require you to change x, and y at the same time, more like:

(using another "imaginary library", but one more plausible:

for x, y in zip(range(0, width), range(0, height)):
   picture.setPixel((x,y), Black) )

This would sort of work, but the line would not be perfect unless the image was perfectly square - else it would skip pixels in the widest direction of the image. To solve that a more refined algorithm is needed - but that is second to you have a real way to access pixels on an image - like using Python's Imaging Library (PIL or Pillow), or pygame, or some other library.

Share:
13,525
user2194374
Author by

user2194374

Updated on June 04, 2022

Comments

  • user2194374
    user2194374 about 2 years

    Hi im trying to draw diagonal lines across an image top right to bottom left here is my code so far.

      width = getWidth(picture)
      height = getHeight(picture)
      for x in range(0, width):
        for y in range(0, height):
          pixel = getPixel(picture, x, y)
          setColor(pixel, black)
    

    Thanks

    • MartinStettner
      MartinStettner over 11 years
      You should mention the graphic library you're using...
    • user2194374
      user2194374 over 11 years
      im using jython environment for students
  • user2194374
    user2194374 over 11 years
    Im using jython environment for students the picture is created earlier and yes it is very slow to get all the pixels. Yes i thought that it would be all black too but it makes a thick black line on the left hand side from top to bottom.
  • MartinStettner
    MartinStettner over 11 years
    In fact, JES (Jython Environmen for Students) does give you a "Pixel object" as return value to getPixel...
  • user2194374
    user2194374 over 11 years
    I did use addLine but then realised i needed it to be across the whole image
  • MartinStettner
    MartinStettner over 11 years
    And what was wrong with the line produced by addLine? If you use (0,0) and (widht,height) as end points for the line, it should be across the whole image after all ...
  • user2194374
    user2194374 over 11 years
    Yep except it only makes a thick black line the height of the picture in the left of the picture. I can add a step to the range which spreads the lines out but im not sure how to make them diagonal.
  • user2194374
    user2194374 over 11 years
    the addLine only makes one line i need it to be multiple lines spread evenly diagonally from top right to bottom left.
  • MartinStettner
    MartinStettner over 11 years
    There is only a single line "From top right to bottom left" :) Do you mean something like imgur.com/foYMBPe perhaps?
  • user2194374
    user2194374 over 11 years
    this is what addLine(picture,width,0,0,height) does imgur.com/Zj5r8TV which is what i want but i need it to go across the whole image but with space inbetween the lines
  • jsbueno
    jsbueno over 11 years
    Thanks for clarifying that @MartinStettner - I'd never guess such thing would be for real.
  • Gauthier Boaglio
    Gauthier Boaglio almost 11 years
    @MartinStettner Thanks for the direct link to Bresenham's Line Algorithm. That worked like a charm!
  • Gauthier Boaglio
    Gauthier Boaglio almost 11 years
    @everyone One important side effect appears when m, at the beginning of drawInfiniteLine(), tends to be infinite (float overflow). This can be avoided by doing something like: if (abs(m) > 100.0): m = 100.0 right after the computation of m...
  • Gauthier Boaglio
    Gauthier Boaglio almost 11 years
    I don't edit this answer one more time, but this FIX I'm talking about can be seen here: stackoverflow.com/a/17288511/1715716