11

Im trying to create a slope in java. I can use the DrawLine function and it'll create a perfect one but I dont want to use that but rather create my own function for it. The problem is that it has gaps between the dots.

import java.applet.Applet;
import java.awt.Graphics;

public class slope extends Applet{



    public void drawLine(int x1, int y1, int x2, int y2, Graphics g) {

        double m = (y2 - y1) / (double)(x2-x1);
        double y = y1;
        for (int x =x1; x < x2; x++) {

            drawPoint(x,(int)y,g);
            y +=m;
        }
    }


    public void paint(Graphics g) {
        drawLine(20, 10, 300, 700, g); //has spaces between the dots 
        g.drawLine(20, 10, 300, 700); //this is perfect


    }

    private void drawPoint(int x, int y, Graphics g) {

        g.drawLine(x, y, x, y);

    }
}

enter image description here

glglgl
  • 89,107
  • 13
  • 149
  • 217
Charles Xavier
  • 1,015
  • 3
  • 14
  • 33
  • 3
    Depending on the angle, you should iterate over `x` or `y`. – Maarten Bodewes Feb 07 '19 at 14:46
  • 6
    It doesn't have "gaps between the dots". It's simply not a line. You draw individual points, why do you expect them do be connected? – f1sh Feb 07 '19 at 14:49
  • 10
    https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm – Steve Smith Feb 07 '19 at 14:49
  • 4
    @f1sh He's asking how to do it without the gaps. All lines are effectively dots, just without the gaps. – Steve Smith Feb 07 '19 at 14:50
  • Here's some Java code that should be easy to re-engineer: https://github.com/fragkakis/bresenham/blob/master/src/main/java/org/fragkakis/Bresenham.java – Steve Smith Feb 07 '19 at 14:52
  • 2
    Well, if `m` is larger then 1 than you will have gaps, obviously, so then you need to go over `y` and add `1 / m` to `x`. Extra points if you note that you need to handle negative values as well. – Maarten Bodewes Feb 07 '19 at 14:53
  • 1
    @CharlesXavier that only works if your `m` is rather small, meaning `-1.5 < m < 1.5` – f1sh Feb 07 '19 at 14:53
  • 1
    you're iterating through 'n' X points, and drawing a point for each. But your vertical distance is greater than 'n', so you're going to get gaps. Bresenham's algorithm is the way to go – Brian Agnew Feb 07 '19 at 14:54
  • 1
    I think I'm describing that algorithm, although I just made it up :) You'd need to use a double for `x` as well of course, and only convert back to `int` when you're drawing a point. @f1sh no, really between -1 and 1, otherwise you still have *occasional* gaps. – Maarten Bodewes Feb 07 '19 at 14:55
  • Just write two versions of the loop, one for line that is wider/more horizontal, and another for line that is taller/more vertical. First one you have already, now you need the right "if" condition, and an else branch which iterates over y and adjusts x according to the slope. – hyde Feb 07 '19 at 15:43
  • @f1sh FYI, raster images only have pixels. Nothing is "connected", it's only pixels that are next to each others, to create illusion of "line" or whatever. – hyde Feb 07 '19 at 15:47
  • @hyde i am aware of that. But his plot shows pixels that are 2+ pixels apart with no effort done to connect them in any way. – f1sh Feb 07 '19 at 15:49
  • 1
    That's the problem @f1sh I don't know how to connect it. Hence the 2 pixels. – Charles Xavier Feb 07 '19 at 15:52
  • i am sure it's already been told, but...did you try maximizing the dots? – aran Feb 27 '19 at 15:08
  • 1
    Hi, take a look on line rasterization algorithms, it will mostly never been ideal line in this angle (because of pixels), but eg. Bressenham's or DDA algorithms are having kind of anti-aliasing – xxxvodnikxxx Feb 27 '19 at 15:10
  • @SteveSmith *. All lines are effectively dots* well in infinity yes, but not in finite 2 dimension raster. – Antoniossss Feb 27 '19 at 15:19
  • You need to set the stroke width. See [stackoverflow.com/questions/2839508/java2d-increase-the-line-width](https://stackoverflow.com/questions/2839508/java2d-increase-the-line-width). – Jeff Miller Feb 27 '19 at 16:19
  • You should stick to the Java Naming Conventions: class names are written in PascalCase. – MC Emperor Feb 27 '19 at 16:44
  • @Antoniossss Not sure what you mean? Lines are dots, i.e. pixels. – Steve Smith Feb 27 '19 at 16:52

3 Answers3

2

Two loops: you loop over x++ only when deltaX > deltaY. else you loop over y++ only.

Dual stepping x and y in the same loop, deciding which should be incremented (assuming you have x as a function of y too) could lead to slower drawing due to extra tests and adjacent pixels may look like a dot in the line. You'd need to play with color intensity to do antialiasing by hand (gold plating). Two loops is much simpler.

fyi, you are trying to generate an image, you could also just set ints in a matrix and make an offscreen raw image (BufferedImage and it's .setRGB() method), which you draw later. That would likely be faster and avoid visible painting delays.

user2023577
  • 1,752
  • 1
  • 12
  • 23
2

Generally this is done by using an algorithm that doesn't step only along the x or y axis, but adjust the update increment by a variable amount, such that each dot is at most sqrt(2) away from each other.

So, if you think you have a point at the x value, but when you calculate it, you find that it is 3.5 pixels away (because the slope is very steep), you fall into a routine that calculates (typically recursively) an intermediate pixel between that x step

(x, y)
(0, 0) to (1, 5) distance 5.09
-> fill routine
   (0, 0) to (0.5, 2.5) distance 2.69
   -> fill routine
      (0, 0) to (0.25, 1.25) distance 1.34 < 1.41 
      (0.25, 1.25) to (0.5, 2.5) distance 1.34 < 1.41
      (0.5, 2.5) to (0.75, 3.75) distance 1.34 < 1.41
      (0.75, 3.75) to (1, 5) distance 1.34 < 1.41
(1, 5) to (2, 10) etc...

The reason one uses 1.41 (sqrt(2)) as the maximum distance allowed is because a of pixels at a 45 degree angle from the bottom of the screen would still appear connected.

Now, in your plotting, you'll need to round the values to align to the exact pixels. There are a number of ways to do this. The simplest is just to round to the next valid value, and this works most of the time. It does have one unfortunate side effect, that is your line will appear to have jagged steps (where the rounding is moving the pixel more, the step will appear more jagged). This jaggedness is called "aliasing" as the true point is presenting through a non-true representation of the point (the alias).

A second approach is to alternatively darken both pixels proportionally, based on how close the point is. A point that is at (0.5) on the x axis would darken both pixels by 50% while a point that is at (0.25) would darken the 0 pixel by 75% and the 1 pixel by 25%. This is anti-aliasing, and may result in a line that is slightly more fuzzy, but appears to be straighter. This fuzziness can be somewhat combated by drawing a thicker line.

I hope this gives you some idea of the math behind many of the higher quality drawing routines, and certainly there are approaches that are even more sophisticated than the one I just presented.

Edwin Buck
  • 69,361
  • 7
  • 100
  • 138
  • Of course both my approach and user2023577's suffer from issues when there are vertical lines, etc. But if you follow either approach, odds are you can handle those with ease. Generally it's done by faking an infinitesimal slope, one that won't be perceived on the screen, but is just large enough to avoid a divide by zero error – Edwin Buck Feb 28 '19 at 16:08
1

Based on the Bresenham Algorithm, here is a java implementation that assumes x2 > x1 and y2 > y1 and uses integer arithmetic

import java.applet.Applet;
import java.awt.*;

public class Slope extends Applet{

    private int x1 = 20, y1 = 10;
    private int x2 = 300, y2 = 700;

    @Override
    public void paint(Graphics g) {

        drawLine(x1, y1, x2, y2, g);
        //g.drawLine(x1, y1, x2, y2, g);  

    }

    private void drawPoint(int x, int y, Graphics g) {
        g.drawLine(x,y,x,y);
   }

   @Override
    public void init(){
        this.setSize(500,700);
   }

    private void drawLine(int x1,int y1,int x2,int y2,Graphics g){

        int dx = x2 - x1;
        int dy = y2 - y1;
        int xi = 1;
        int D = 2*dx - dy;
        int x = x1;

        for(int y = y1; y <y2; y++) {
            drawPoint(x,y,g);
            if(D > 0) {
                x = x + xi;
                D = D - 2 * dy;
            }
                D = D + 2 * dx;
            }
        }
}

Image Output

firephil
  • 830
  • 10
  • 18