简体   繁体   中英

Drawing an almost straight line

I got these arrays representing squared paper, or maybe pixels if you like. For consistency in this thread, let "1" represent a black cell, "0" represent a white cell.

Now, I want to draw a (black) straight line from point A to point B. I can't just use a vanilla http://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm , because I need to blacken every cell that would be hit if it were a sheet of squared paper and you drew a line from the center of square A to the center of square B.

So these should work out as follows:

+—+—+—+    +—+—+—+
|A| | |    |█| | |
+—+—+—+    +—+—+—+
| | | |    |█|█| |
+—+—+—+    +—+—+—+
| | | | →  | |█|█|
+—+—+—+    +—+—+—+
| | |B|    | | |█|
+—+—+—+    +—+—+—+

+—+—+—+    +—+—+—+
|A| | |    |█| | |
+—+—+—+    +—+—+—+
| | | | →  | |█| |
+—+—+—+    +—+—+—+
| | |B|    | | |█|
+—+—+—+    +—+—+—+

+—+—+—+    +—+—+—+
|A| | | →  |█|█| |
+—+—+—+    +—+—+—+
| | |B|    | |█|█|
+—+—+—+    +—+—+—+

Feel free to imagine these sketches to be more square-like, or to actually sketch that on a sheet of squared paper! It might clear things up.

And like always, feel free to post anything that might help. Thanks!


(A) Solution

Also using this example. And using the indices as they're used by my framework, so it looks a little different than the one in the answers.

 X 0 1 2      X 0 1 2
Y +—+—+—+ →  Y +—+—+—+
0 |A| | | →  0 |█| | |
  +—+—+—+ →    +—+—+—+
1 | | | | →  1 |█|█| |
  +—+—+—+ →    +—+—+—+
2 | | | | →  2 | |█|█|
  +—+—+—+ →    +—+—+—+
3 | | |B| →  3 | | |█|
  +—+—+—+ →    +—+—+—+

A being (0,0) =: (a1,a2), B being (2,3) =: (b1,b2), so the expected set of points is {(0,0), (0,1), (1,1), (1,2), (2,2), (2,3)}

First, we formalize the straight which would be drawn on actual paper.

Therefore first define constant m which holds our slope:

    (b2 - a2)
m = —————————
    (b1 - a1)

For arbitrary (A, B), the straight is now defined by:
g: y = (x - a1) · m + a2

and inverted:
ǵ: x = (y - a2) / m + a1

Note that for m = 0, the whole thing does fail. But a straight-to-the-right line does not only sound like a straightforward thing.

in this example, the straights are

m = 3/2
g: y = x * 3/2
ǵ: x = y * 2/3

We'll feed these functions with the "line values" (exactly between 2 integers x and x+1, widely known as "x and a halve") within our bounding box. So first we'll go between a1 and b1 (feed g), then between a2 and b2 (feed ǵ):

g(0.5) = 1/2 * 3/2 = 0.75 // 1 < g(0.5) + 0.5 < 2
g(1.5) = 3/2 * 3/2 = 2.25 // 2 < g(1.5) + 0.5 < 3

ǵ(0.5) = 1/2 * 2/3 = 0.33 // 0 < ǵ(0.5) + 0.5 < 1
ǵ(1.5) = 3/2 * 2/3 = 1    // 1 < ǵ(1.5) + 0.5 < 2
ǵ(2.5) = 5/2 * 2/3 = 1.66 // 2 < ǵ(2.5) + 0.5 < 3
  1. First means the straight crosses the first vertical line above (shown as "below") the first horizontal line. Points to get black: (0,1), (1,1).
  2. Second means the straight crosses the second vertical line above the second horizontal line. Points to get black: (1,2), (2,2).
  3. Third means the straight crosses the first horizontal line after (shown as "right of") the 0th vertical line. Points to get black: (0,0), (0,1).
  4. Fourth means the straight crosses the second horizontal line after the first vertical line. Points to get black: (1,1), (1,2).
  5. Fifth means the straight crosses the third horizontal line after the second vertical line. Points to get black: (2,2), (2,3).

All points: {(0,1), (1,1), (1,2), (2,2), (0,0), (0,1) , (1,1) , (1,2) , (2,2) , (2,3)}

Without duplicates : {(0,0), (0,1), (1,1), (1,2), (2,2), (2,3)} (which is exactly as expected)

  • So for a line that crosses the y'th vertical line above the x'th horizontal one, we paint (x-1,y) and (x,y) black.
  • While for a line that crosses the x'th horizontal line after the y'th vertical one, we paint (x,y-1) and (x,y).
  • (never true here) And for a line that crosses the x'th horizontal right on the y'th vertical one, we paint for m > 0 (x-1,y-1) and (x,y). If m < 0, that'd make for (x-1,y) and (x,y-1)

Feels about right, that's my idea.

Any thoughts? Please comment!

~ LDer ~

Let's assume that we're working with this example:

+—+—+—+    +—+—+—+
|A| | |    |█| | |
+—+—+—+    +—+—+—+
| | | |    |█|█| |
+—+—+—+    +—+—+—+
| | | | →  | |█|█|
+—+—+—+    +—+—+—+
| | |B|    | | |█|
+—+—+—+    +—+—+—+

It's a rectangle 3x4 .

We need to define two functions:
y(x) = 4 * (1 - x/3)
x(y) = 3 * (1 - y/4) .

Now let's pass to them all integer x and y inside our rectangle.
On each step we are painting black pixel with coordinates
[x; ceil(y(x)) - 1] [x; ceil(y(x)) - 1] and [ceil(x(y)) - 1; y - 1] [ceil(x(y)) - 1; y - 1] :

y(0) == 4      # paint 0;3
y(1) == 2.(6)  # paint 1;2
y(2) == 1.(3)  # paint 2;1

x(0) == 3      # paint 2;-1 (-1 is not a valid pixel coordinate, 
               # so we may just throw away this, as there are no pixels below y=0)
x(1) == 2.25   # paint 2;0
x(2) == 1.5    # paint 1;1
x(3) == 0.75   # paint 0;2

Both x(y) and y(x) functions are giving you second coordinate of a point on the diagonal, and when we're passing integer values to them, we're finding intersections of our diagonal with the grid.
The y -function would give us all pixels that are right to every vertical line and the x -function - that are below every horizontal (that's why y - 1 ). The only issue will be with corner intersections, it can be solved by one more conditional.

Initialisation Give the vertices of your "squares" coordinates (x,y) . For each square, v1 is top left, v2 top right, v3 bottom right and v4 bottom left. Calculate the equation of your line y = a*x + b .

Algorithm

For each square do
    for each vertex of the square do
        calculate valueOfVertex = y - a*x - b
    if two valueOfVertex have an opposite sign
    then cellColor == 1
    else cellcolor == 0

The output should be what you expect. The only problem maybe if one valueOfVertex = 0 and all the others are stricly positive or negative. But it's easy to handle.

Explanation If the line crosses the square (or pixel), you can find two vertice which aren't on the same side of the line. So one lies in the positive half plane, the other in the negative half plane.

Improvement Some simple tricks will prevent you from testing every square of the bounding box. If you find a square that is entirely in a half plane, you can discard some squares. In your examples, if the square tested is above the line, you can discard all the squares that are above and to the right.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM