forked from TheAlgorithms/Java
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathWusLine.java
More file actions
235 lines (204 loc) · 8.22 KB
/
WusLine.java
File metadata and controls
235 lines (204 loc) · 8.22 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
package com.thealgorithms.geometry;
import java.awt.Point;
import java.util.ArrayList;
import java.util.List;
/**
* The {@code WusLine} class implements Xiaolin Wu's line drawing algorithm,
* which produces anti-aliased lines by varying pixel brightness
* according to the line's proximity to pixel centers.
*
* This implementation returns the pixel coordinates along with
* their associated intensity values (in range [0.0, 1.0]), allowing
* rendering systems to blend accordingly.
*
* The algorithm works by:
* - Computing a line's intersection with pixel boundaries
* - Assigning intensity values based on distance from pixel centers
* - Drawing pairs of pixels perpendicular to the line's direction
*
* Reference: Xiaolin Wu, "An Efficient Antialiasing Technique",
* Computer Graphics (SIGGRAPH '91 Proceedings).
*
*/
public final class WusLine {
private WusLine() {
// Utility class; prevent instantiation.
}
/**
* Represents a pixel and its intensity for anti-aliased rendering.
*
* The intensity value determines how bright the pixel should be drawn,
* with 1.0 being fully opaque and 0.0 being fully transparent.
*/
public static class Pixel {
/** The pixel's coordinate on the screen. */
public final Point point;
/** The pixel's intensity value, clamped to the range [0.0, 1.0]. */
public final double intensity;
/**
* Constructs a new Pixel with the given coordinates and intensity.
*
* @param x the x-coordinate of the pixel
* @param y the y-coordinate of the pixel
* @param intensity the brightness/opacity of the pixel, will be clamped to [0.0, 1.0]
*/
public Pixel(int x, int y, double intensity) {
this.point = new Point(x, y);
this.intensity = Math.clamp(intensity, 0.0, 1.0);
}
}
/**
* Internal class to hold processed endpoint data.
*/
private static class EndpointData {
final int xPixel;
final int yPixel;
final double yEnd;
final double xGap;
EndpointData(int xPixel, int yPixel, double yEnd, double xGap) {
this.xPixel = xPixel;
this.yPixel = yPixel;
this.yEnd = yEnd;
this.xGap = xGap;
}
}
/**
* Draws an anti-aliased line using Wu's algorithm.
*
* The algorithm produces smooth lines by drawing pairs of pixels at each
* x-coordinate (or y-coordinate for steep lines), with intensities based on
* the line's distance from pixel centers.
*
* @param x0 the x-coordinate of the line's start point
* @param y0 the y-coordinate of the line's start point
* @param x1 the x-coordinate of the line's end point
* @param y1 the y-coordinate of the line's end point
* @return a list of {@link Pixel} objects representing the anti-aliased line,
* ordered from start to end
*/
public static List<Pixel> drawLine(int x0, int y0, int x1, int y1) {
List<Pixel> pixels = new ArrayList<>();
// Determine if the line is steep (more vertical than horizontal)
boolean steep = Math.abs(y1 - y0) > Math.abs(x1 - x0);
if (steep) {
// For steep lines, swap x and y coordinates to iterate along y-axis
int temp = x0;
x0 = y0;
y0 = temp;
temp = x1;
x1 = y1;
y1 = temp;
}
if (x0 > x1) {
// Ensure we always draw from left to right
int temp = x0;
x0 = x1;
x1 = temp;
temp = y0;
y0 = y1;
y1 = temp;
}
// Calculate the line's slope
double deltaX = x1 - (double) x0;
double deltaY = y1 - (double) y0;
double gradient = (deltaX == 0) ? 1.0 : deltaY / deltaX;
// Process the first endpoint
EndpointData firstEndpoint = processEndpoint(x0, y0, gradient, true);
addEndpointPixels(pixels, firstEndpoint, steep);
// Process the second endpoint
EndpointData secondEndpoint = processEndpoint(x1, y1, gradient, false);
addEndpointPixels(pixels, secondEndpoint, steep);
// Draw the main line between endpoints
drawMainLine(pixels, firstEndpoint, secondEndpoint, gradient, steep);
return pixels;
}
/**
* Processes a line endpoint to determine its pixel coordinates and intensities.
*
* @param x the x-coordinate of the endpoint
* @param y the y-coordinate of the endpoint
* @param gradient the slope of the line
* @param isStart true if this is the start endpoint, false if it's the end
* @return an {@link EndpointData} object containing processed endpoint information
*/
private static EndpointData processEndpoint(double x, double y, double gradient, boolean isStart) {
double xEnd = round(x);
double yEnd = y + gradient * (xEnd - x);
double xGap = isStart ? rfpart(x + 0.5) : fpart(x + 0.5);
int xPixel = (int) xEnd;
int yPixel = (int) Math.floor(yEnd);
return new EndpointData(xPixel, yPixel, yEnd, xGap);
}
/**
* Adds the two endpoint pixels (one above, one below the line) to the pixel list.
*
* @param pixels the list to add pixels to
* @param endpoint the endpoint data containing coordinates and gaps
* @param steep true if the line is steep (coordinates should be swapped)
*/
private static void addEndpointPixels(List<Pixel> pixels, EndpointData endpoint, boolean steep) {
double fractionalY = fpart(endpoint.yEnd);
double complementFractionalY = rfpart(endpoint.yEnd);
if (steep) {
pixels.add(new Pixel(endpoint.yPixel, endpoint.xPixel, complementFractionalY * endpoint.xGap));
pixels.add(new Pixel(endpoint.yPixel + 1, endpoint.xPixel, fractionalY * endpoint.xGap));
} else {
pixels.add(new Pixel(endpoint.xPixel, endpoint.yPixel, complementFractionalY * endpoint.xGap));
pixels.add(new Pixel(endpoint.xPixel, endpoint.yPixel + 1, fractionalY * endpoint.xGap));
}
}
/**
* Draws the main portion of the line between the two endpoints.
*
* @param pixels the list to add pixels to
* @param firstEndpoint the processed start endpoint
* @param secondEndpoint the processed end endpoint
* @param gradient the slope of the line
* @param steep true if the line is steep (coordinates should be swapped)
*/
private static void drawMainLine(List<Pixel> pixels, EndpointData firstEndpoint, EndpointData secondEndpoint, double gradient, boolean steep) {
// Start y-intersection after the first endpoint
double intersectionY = firstEndpoint.yEnd + gradient;
// Iterate through x-coordinates between the endpoints
for (int x = firstEndpoint.xPixel + 1; x < secondEndpoint.xPixel; x++) {
int yFloor = (int) Math.floor(intersectionY);
double fractionalPart = fpart(intersectionY);
double complementFractionalPart = rfpart(intersectionY);
if (steep) {
pixels.add(new Pixel(yFloor, x, complementFractionalPart));
pixels.add(new Pixel(yFloor + 1, x, fractionalPart));
} else {
pixels.add(new Pixel(x, yFloor, complementFractionalPart));
pixels.add(new Pixel(x, yFloor + 1, fractionalPart));
}
intersectionY += gradient;
}
}
/**
* Returns the fractional part of a number.
*
* @param x the input number
* @return the fractional part (always in range [0.0, 1.0))
*/
private static double fpart(double x) {
return x - Math.floor(x);
}
/**
* Returns the reverse fractional part of a number (1 - fractional part).
*
* @param x the input number
* @return 1.0 minus the fractional part (always in range (0.0, 1.0])
*/
private static double rfpart(double x) {
return 1.0 - fpart(x);
}
/**
* Rounds a number to the nearest integer.
*
* @param x the input number
* @return the nearest integer value as a double
*/
private static double round(double x) {
return Math.floor(x + 0.5);
}
}