Xiaolin Wu's line algorithm: Difference between revisions
(Better D entry) |
m (→{{header|REXX}}: corrected the DO-END comment labels, aligned some statements, added whitespace, simplified ROUND subroutine, added comments. -- ~~~~) |
||
Line 793: | Line 793: | ||
<br>'''Note''': If at the beginning of the routine abs(''dx'') < abs(''dy'') is true, then all plotting should be done with ''x'' and ''y'' reversed. |
<br>'''Note''': If at the beginning of the routine abs(''dx'') < abs(''dy'') is true, then all plotting should be done with ''x'' and ''y'' reversed. |
||
<lang rexx>/*REXX program plots/draws a line using the Xiaolin Wu line algorithm.*/ |
<lang rexx>/*REXX program plots/draws a line using the Xiaolin Wu line algorithm.*/ |
||
⚫ | |||
background = 'fa'x /*background char: middle-dot. */ |
background = 'fa'x /*background char: middle-dot. */ |
||
image. = background /*fill the array with middle-dots*/ |
|||
plotC = '░▒▓█' /*chars used for plotting points.*/ |
|||
EoE = 1000 /*EOE = End Of Earth, er... plot.*/ |
|||
do j=-EoE to +EoE /*draw grid from lowest──►highest*/ |
|||
image.0 |
image.j.0 = '─' /*draw the horizontal axis. */ |
||
image.0.j = '│' /* " " verical " */ |
|||
end /*j*/ |
|||
image.0.0 = '┼' /*"draw" the axis origin (char). */ |
|||
parse arg xi yi xf yf . /*allow specifying line-end pts. */ |
parse arg xi yi xf yf . /*allow specifying line-end pts. */ |
||
if xi=='' | xi==',' then xi = 1 |
if xi=='' | xi==',' then xi = 1 /*if not specified, use default. */ |
||
if yi=='' | yi==',' then yi = 2 |
if yi=='' | yi==',' then yi = 2 /* " " " " " */ |
||
if xf=='' | xf==',' then xf = 11 |
if xf=='' | xf==',' then xf = 11 /* " " " " " */ |
||
if yf=='' | yf==',' then yf = 12 |
if yf=='' | yf==',' then yf = 12 /* " " " " " */ |
||
minX=0; minY=0 /*used as limits for plotting. */ |
|||
⚫ | |||
minX=0; maxX=0 |
|||
call line_draw xi, yi, xf, yf /*call subroutine and draw line. */ |
|||
minY=0; maxY=0 |
|||
border = 2 /*allow additional space for plot*/ |
|||
border=2 |
minX=minX-border*2; maxX=maxX+border*2 |
||
minY=minY-border ; maxY=maxY+border |
|||
⚫ | |||
minY=minY-border ; maxY=maxY+border |
|||
do x=minX to maxX |
|||
_=_ || image.x.y |
|||
end /*x*/ |
|||
say _ /*display row*/ |
|||
end /*y*/ |
|||
⚫ | |||
⚫ | |||
exit /*stick a fork in it, we're done.*/ |
exit /*stick a fork in it, we're done.*/ |
||
/*────────────────────────────────DRAW_LINE subroutine──────────────────*/ |
/*────────────────────────────────DRAW_LINE subroutine──────────────────*/ |
||
line_draw: procedure expose background image. minX maxX minY maxY plotC |
line_draw: procedure expose background image. minX maxX minY maxY plotC |
||
parse arg x1, y1, x2, y2; switchXY=0 |
parse arg x1, y1, x2, y2; switchXY=0; dx=x2-x1 |
||
⚫ | |||
dx=x2-x1 |
|||
dy=y2-y1 |
|||
if abs(dx)<abs(dy) then do |
if abs(dx)<abs(dy) then do |
||
parse value x1 y1 with y1 x1 /*swap x1 & y1*/ |
parse value x1 y1 with y1 x1 /*swap x1 & y1*/ |
||
Line 839: | Line 836: | ||
gradient = dy/dx |
gradient = dy/dx |
||
xend = round(x1) /*────1st endpoint────────────────────*/ |
|||
yend |
yend = y1 + gradient * (xend-x1) |
||
⚫ | |||
xgap = 1 - fpart(x1+.5) |
|||
xpx11 = xend |
|||
ypx11 = floor(yend); ypx11_=ypx11+1 |
xpx11 = xend; ypx11 = floor(yend); ypx11_=ypx11+1 |
||
call plotXY xpx11, ypx11, brite(1-fpart(yend*xgap)),switchXY |
call plotXY xpx11, ypx11, brite(1-fpart(yend*xgap)),switchXY |
||
call plotXY xpx11, ypx11_, brite( fpart(yend*xgap)),switchXY |
call plotXY xpx11, ypx11_, brite( fpart(yend*xgap)),switchXY |
||
⚫ | |||
xend = round(x2) /*────2nd endpoint────────────────────*/ |
|||
yend |
yend = y2 + gradient * (xend-x2) |
||
xgap = fpart(x2+.5) |
|||
⚫ | |||
xpx12 = xend |
|||
⚫ | |||
call plotXY xpx12, ypx12, brite(1-fpart(yend*xgap)), switchXY |
call plotXY xpx12, ypx12, brite(1-fpart(yend*xgap)), switchXY |
||
call plotXY xpx12, ypx12_, brite( fpart(yend*xgap)), switchXY |
call plotXY xpx12, ypx12_, brite( fpart(yend*xgap)), switchXY |
||
do x=xpx11+1 to xpx12-1 /*────draw the line───────────────────*/ |
do x=xpx11+1 to xpx12-1 /*────draw the line───────────────────*/ |
||
!intery = floor(intery) |
!intery = floor(intery) |
||
!intery_ = !intery+1 |
!intery_ = !intery+1 |
||
call plotXY x, !intery, brite(1-fpart(intery)), switchXY |
call plotXY x, !intery, brite(1-fpart(intery)), switchXY |
||
call plotXY x, !intery_, brite( fpart(intery)), switchXY |
call plotXY x, !intery_, brite( fpart(intery)), switchXY |
||
intery = intery + gradient |
intery = intery + gradient |
||
end |
end /*x*/ |
||
return |
return |
||
/*────────────────────────────────BRITE subroutine──────────────────────*/ |
/*────────────────────────────────BRITE subroutine──────────────────────*/ |
||
brite: procedure expose background plotC; parse arg p |
brite: procedure expose background plotC; parse arg p |
||
return substr(background || plotC, 1+round(abs(p)*length(plotC)), 1) |
return substr(background || plotC, 1+round(abs(p)*length(plotC)), 1) |
||
/*────────────────────────────────PLOTXY subroutine─────────────────────*/ |
/*────────────────────────────────PLOTXY subroutine─────────────────────*/ |
||
plotXY: procedure expose image. minX maxX minY maxY |
plotXY: procedure expose image. minX maxX minY maxY |
||
parse arg xx, yy, bc, switchYX; if switchYX then parse arg yy, xx |
parse arg xx, yy, bc, switchYX; if switchYX then parse arg yy, xx |
||
image.xx.yy=bc; minX=min(minX,xx); maxX=max(maxX,xx) |
image.xx.yy=bc; minX=min(minX,xx); maxX=max(maxX,xx) |
||
minY=min(minY,yy); maxY=max(maxY,yy) |
minY=min(minY,yy); maxY=max(maxY,yy) |
||
return |
return |
||
/*────────────────────────────────FLOOR subroutine──────────────────────*/ |
/*────────────────────────────────FLOOR subroutine──────────────────────*/ |
||
floor: procedure; parse arg ?; |
floor: procedure; parse arg ?; _=trunc(?); return _-(?<0)*(?\=_) |
||
/*────────────────────────────────FPART subroutine──────────────────────*/ |
/*────────────────────────────────FPART subroutine──────────────────────*/ |
||
fpart: procedure; parse arg ?; return abs(?-trunc(?)) |
fpart: procedure; parse arg ?; return abs(?-trunc(?)) |
||
/*────────────────────────────────ROUND subroutine─────────arg2 is place*/ |
/*────────────────────────────────ROUND subroutine─────────arg2 is place*/ |
||
round: |
round: return format(arg(1), , word(arg(2) 0, 1))</lang> |
||
'''output''' when using the default input |
'''output''' when using the default input |
||
<pre style="overflow:scroll"> |
<pre style="overflow:scroll"> |
Revision as of 19:09, 7 January 2013
You are encouraged to solve this task according to the task description, using any language you may know.
Implement the Xiaolin Wu's line algorithm as described in Wikipedia. This algorithm draw antialiased lines. See Bresenham's line algorithm for aliased lines.
BBC BASIC
<lang bbcbasic> PROCdrawAntiAliasedLine(100, 100, 600, 400, 0, 0, 0)
END DEF PROCdrawAntiAliasedLine(x1, y1, x2, y2, r%, g%, b%) LOCAL dx, dy, xend, yend, grad, yf, xgap, ix1%, iy1%, ix2%, iy2%, x% dx = x2 - x1 dy = y2 - y1 IF ABS(dx) < ABS(dy) THEN SWAP x1, y1 SWAP x2, y2 SWAP dx, dy ENDIF IF x2 < x1 THEN SWAP x1, x2 SWAP y1, y2 ENDIF grad = dy / dx xend = INT(x1 + 0.5) yend = y1 + grad * (xend - x1) xgap = xend + 0.5 - x1 ix1% = xend iy1% = INT(yend) PROCplot(ix1%, iy1%, r%, b%, g%, (INT(yend) + 1 - yend) * xgap) PROCplot(ix1%, iy1% + 1, r%, b%, g%, (yend - INT(yend)) * xgap) yf = yend + grad xend = INT(x2 + 0.5) yend = y2 + grad * (xend - x2) xgap = x2 + 0.5 - xend ix2% = xend iy2% = INT(yend) PROCplot(ix2%, iy2%, r%, b%, g%, (INT(yend) + 1 - yend) * xgap) PROCplot(ix2%, iy2% + 1, r%, b%, g%, (yend - INT(yend)) * xgap) FOR x% = ix1% + 1 TO ix2% - 1 PROCplot(x%, INT(yf), r%, b%, g%, INT(yf) + 1 - yf) PROCplot(x%, INT(yf) + 1, r%, b%, g%, yf - INT(yf)) yf += grad NEXT ENDPROC DEF PROCplot(X%, Y%, R%, G%, B%, a) LOCAL C% C% = TINT(X%*2,Y%*2) COLOUR 1, R%*a + (C% AND 255)*(1-a), \ \ G%*a + (C% >> 8 AND 255)*(1-a), \ \ B%*a + (C% >> 16 AND 255)*(1-a) GCOL 1 LINE X%*2, Y%*2, X%*2, Y%*2 ENDPROC</lang>
C
This implementation follows straightforwardly the pseudocode given on Wikipedia. (Further analysis of the code could give suggestions for improvements).
<lang c>void draw_line_antialias(
image img, unsigned int x0, unsigned int y0, unsigned int x1, unsigned int y1, color_component r, color_component g, color_component b );</lang>
<lang c>inline void _dla_changebrightness(rgb_color_p from, rgb_color_p to, float br) {
if ( br > 1.0 ) br = 1.0; /* linear... Maybe something more complex could give better look */ to->red = br * (float)from->red; to->green = br * (float)from->green; to->blue = br * (float)from->blue;
}
- define plot_(X,Y,D) do{ rgb_color f_; \
f_.red = r; f_.green = g; f_.blue = b; \ _dla_plot(img, (X), (Y), &f_, (D)) ; }while(0)
inline void _dla_plot(image img, int x, int y, rgb_color_p col, float br) {
rgb_color oc; _dla_changebrightness(col, &oc, br); put_pixel_clip(img, x, y, oc.red, oc.green, oc.blue);
}
- define ipart_(X) ((int)(X))
- define round_(X) ((int)(((double)(X))+0.5))
- define fpart_(X) (((double)(X))-(double)ipart_(X))
- define rfpart_(X) (1.0-fpart_(X))
- define swap_(a, b) do{ __typeof__(a) tmp; tmp = a; a = b; b = tmp; }while(0)
void draw_line_antialias(
image img, unsigned int x1, unsigned int y1, unsigned int x2, unsigned int y2, color_component r, color_component g, color_component b )
{
double dx = (double)x2 - (double)x1; double dy = (double)y2 - (double)y1; if ( fabs(dx) > fabs(dy) ) { if ( x2 < x1 ) { swap_(x1, x2); swap_(y1, y2); } double gradient = dy / dx; double xend = round_(x1); double yend = y1 + gradient*(xend - x1); double xgap = rfpart_(x1 + 0.5); int xpxl1 = xend; int ypxl1 = ipart_(yend); plot_(xpxl1, ypxl1, rfpart_(yend)*xgap); plot_(xpxl1, ypxl1+1, fpart_(yend)*xgap); double intery = yend + gradient;
xend = round_(x2); yend = y2 + gradient*(xend - x2); xgap = fpart_(x2+0.5); int xpxl2 = xend; int ypxl2 = ipart_(yend); plot_(xpxl2, ypxl2, rfpart_(yend) * xgap); plot_(xpxl2, ypxl2 + 1, fpart_(yend) * xgap);
int x; for(x=xpxl1+1; x <= (xpxl2-1); x++) { plot_(x, ipart_(intery), rfpart_(intery)); plot_(x, ipart_(intery) + 1, fpart_(intery)); intery += gradient; } } else { if ( y2 < y1 ) { swap_(x1, x2); swap_(y1, y2); } double gradient = dx / dy; double yend = round_(y1); double xend = x1 + gradient*(yend - y1); double ygap = rfpart_(y1 + 0.5); int ypxl1 = yend; int xpxl1 = ipart_(xend); plot_(xpxl1, ypxl1, rfpart_(xend)*ygap); plot_(xpxl1, ypxl1+1, fpart_(xend)*ygap); double interx = xend + gradient;
yend = round_(y2); xend = x2 + gradient*(yend - y2); ygap = fpart_(y2+0.5); int ypxl2 = yend; int xpxl2 = ipart_(xend); plot_(xpxl2, ypxl2, rfpart_(xend) * ygap); plot_(xpxl2, ypxl2 + 1, fpart_(xend) * ygap);
int y; for(y=ypxl1+1; y <= (ypxl2-1); y++) { plot_(ipart_(interx), y, rfpart_(interx)); plot_(ipart_(interx) + 1, y, fpart_(interx)); interx += gradient; } }
}
- undef swap_
- undef plot_
- undef ipart_
- undef fpart_
- undef round_
- undef rfpart_</lang>
D
This performs the mixing of the colors, both in grey scale and RGB. <lang d>import std.math, std.algorithm, grayscale_image;
/// Plots anti-aliased line by Xiaolin Wu's line algorithm. void aaLine(Color)(ref Image!Color img, double x1, double y1,
double x2, double y2, in Color color)
/*pure*/ nothrow {
// Straight translation of Wikipedia pseudocode. static double round(in double x) /*pure*/ nothrow { return floor(x + 0.5); // Not pure. }
static double fpart(in double x) /*pure*/ nothrow { return x - floor(x); }
static double rfpart(in double x) /*pure*/ nothrow { return 1 - fpart(x); }
auto dx = x2 - x1; auto dy = y2 - y1; immutable ax = abs(dx); immutable ay = abs(dy);
static Color mixColors(in Color c1, in Color c2, in double p) pure nothrow { static if (is(Color == RGB)) return Color(cast(ubyte)(c1.r * p + c2.r * (1 - p)), cast(ubyte)(c1.g * p + c2.g * (1 - p)), cast(ubyte)(c1.b * p + c2.b * (1 - p))); else // This doesn't work for every kind of Color. return Color(cast(ubyte)(c1 * p + c2 * (1 - p))); }
// Plot function set here to handle the two cases of slope. void delegate(in int, in int, in double) nothrow plot; if (ax < ay) { swap(x1, y1); swap(x2, y2); swap(dx, dy); plot = (x, y, p) { assert(p >= 0.0 && p <= 1.0); img[y, x] = mixColors(color, img[y, x], p); }; } else { plot = (x, y, p) { assert(p >= 0.0 && p <= 1.0); img[x, y] = mixColors(color, img[x, y], p); }; }
if (x2 < x1) { swap(x1, x2); swap(y1, y2); } immutable gradient = dy / dx;
// Handle first endpoint. auto xEnd = round(x1); auto yEnd = y1 + gradient * (xEnd - x1); auto xGap = rfpart(x1 + 0.5); // This will be used in the main loop. immutable xpxl1 = cast(int)xEnd; immutable ypxl1 = cast(int)floor(yEnd); plot(xpxl1, ypxl1, rfpart(yEnd) * xGap); plot(xpxl1, ypxl1 + 1, fpart(yEnd) * xGap); // First y-intersection for the main loop. auto yInter = yEnd + gradient;
// Handle second endpoint. xEnd = round(x2); yEnd = y2 + gradient * (xEnd - x2); xGap = fpart(x2 + 0.5); // This will be used in the main loop. immutable xpxl2 = cast(int)xEnd; immutable ypxl2 = cast(int)floor(yEnd); plot(xpxl2, ypxl2, rfpart(yEnd) * xGap); plot(xpxl2, ypxl2 + 1, fpart(yEnd) * xGap);
// Main loop. foreach (immutable x; xpxl1 + 1 .. xpxl2) { plot(x, cast(int)floor(yInter), rfpart(yInter)); plot(x, cast(int)floor(yInter) + 1, fpart(yInter)); yInter += gradient; }
}
void main() {
auto im1 = new Image!Gray(400, 300); im1.clear(Gray.white); im1.aaLine(7.4, 12.3, 307, 122.5, Gray.black); im1.aaLine(177.4, 12.3, 127, 222.5, Gray.black); im1.savePGM("xiaolin_lines1.pgm");
auto im2 = new Image!RGB(400, 300); im2.clear(RGB(0, 255, 0)); immutable red = RGB(255, 0, 0); im2.aaLine(7.4, 12.3, 307, 122.5, red); im2.aaLine(177.4, 12.3, 127, 222.5, red); im2.savePPM6("xiaolin_lines2.ppm");
}</lang>
Go
<lang go>package raster
import "math"
func ipart(x float64) float64 {
return math.Floor(x)
}
func round(x float64) float64 {
return ipart(x + .5)
}
func fpart(x float64) float64 {
return x - ipart(x)
}
func rfpart(x float64) float64 {
return 1 - fpart(x)
}
// AaLine plots anti-aliased line by Xiaolin Wu's line algorithm. func (g *Grmap) AaLine(x1, y1, x2, y2 float64) {
// straight translation of WP pseudocode dx := x2 - x1 dy := y2 - y1 ax := dx if ax < 0 { ax = -ax } ay := dy if ay < 0 { ay = -ay } // plot function set here to handle the two cases of slope var plot func(int, int, float64) if ax < ay { x1, y1 = y1, x1 x2, y2 = y2, x2 dx, dy = dy, dx plot = func(x, y int, c float64) { g.SetPx(y, x, uint16(c*math.MaxUint16)) } } else { plot = func(x, y int, c float64) { g.SetPx(x, y, uint16(c*math.MaxUint16)) } } if x2 < x1 { x1, x2 = x2, x1 y1, y2 = y2, y1 } gradient := dy / dx
// handle first endpoint xend := round(x1) yend := y1 + gradient*(xend-x1) xgap := rfpart(x1 + .5) xpxl1 := int(xend) // this will be used in the main loop ypxl1 := int(ipart(yend)) plot(xpxl1, ypxl1, rfpart(yend)*xgap) plot(xpxl1, ypxl1+1, fpart(yend)*xgap) intery := yend + gradient // first y-intersection for the main loop
// handle second endpoint xend = round(x2) yend = y2 + gradient*(xend-x2) xgap = fpart(x2 + 0.5) xpxl2 := int(xend) // this will be used in the main loop ypxl2 := int(ipart(yend)) plot(xpxl2, ypxl2, rfpart(yend)*xgap) plot(xpxl2, ypxl2+1, fpart(yend)*xgap)
// main loop for x := xpxl1 + 1; x <= xpxl2-1; x++ { plot(x, int(ipart(intery)), rfpart(intery)) plot(x, int(ipart(intery))+1, fpart(intery)) intery = intery + gradient }
}</lang> Demonstration program: <lang go>package main
// Files required to build supporting package raster are found in: // * This task (immediately above) // * Bitmap // * Grayscale image // * Write a PPM file
import "raster"
func main() {
g := raster.NewGrmap(400, 300) g.AaLine(7.4, 12.3, 307, 122.5) g.AaLine(177.4, 12.3, 127, 222.5) g.Bitmap().WritePpmFile("wu.ppm")
}</lang>
J
Solution: <lang j>load'gl2' coinsert'jgl2'
drawpt=:4 :0"0 1
glrgb <.(-.x)*255 255 255 glpixel y
)
drawLine=:3 :0 NB. drawline x1,y1,x2,y2
pts=. 2 2$y isreversed=. </ |d=. -~/pts r=. |.^:isreversed"1 pts=. /:~ pts \:"1 |d gradient=. %~/ (\:|)d
'x y'=. |:pts xend=. <.0.5+ x yend=. y + gradient* xend-x xgap=. -.1|x+0.5
n=. i. >: -~/ xend 'xlist ylist'=. (n*/~1,gradient) + ({.xend),({.yend) weights=. ((2&}.,~ xgap*2&{.)&.(_1&|.) (,.~-.) 1|ylist) weights (drawpt r)"1 2 (,:+&0 1)"1 xlist,.<.ylist
)</lang>
Example use: Requires J6 or earlier for wd to work. <lang j> wd'pc win closeok; xywh 0 0 300 200;cc g isigraph; pas 0 0; pshow;'
glpaint drawLine 10 10 590 390</lang>
Liberty BASIC
<lang lb> NoMainWin WindowWidth = 270 WindowHeight = 290 UpperLeftX=int((DisplayWidth-WindowWidth)/2) UpperLeftY=int((DisplayHeight-WindowHeight)/2)
Global variablesInitialized : variablesInitialized = 0 Global BackColor$ : BackColor$ = "0 0 0" ' BackColor$ = "255 255 255"
'now, right click randomizes BG
Global size : size = 1'4 global mousepoints.mouseX0, mousepoints.mouseY0, mousepoints.mouseX1, mousepoints.mouseY1
'StyleBits #main.gbox, 0, _WS_BORDER, 0, 0 GraphicBox #main.gbox, 0, 0, 253, 252
Open "Click Twice to Form Line" For Window As #main Print #main, "TrapClose quit" Print #main.gbox, "Down; Color Black" Print #main.gbox, "Down; fill ";BackColor$ Print #main.gbox, "When leftButtonUp gBoxClick" Print #main.gbox, "When rightButtonUp RandomBG" Print #main.gbox, "Size "; size
result = drawAntiAliasedLine(126.5, 0, 126.5, 252, "255 0 0") result = drawAntiAliasedLine(0, 126, 253, 126, "255 0 0") result = drawAntiAliasedLine(0, 0, 253, 252, "255 0 0") result = drawAntiAliasedLine(253, 0, 0, 252, "255 0 0") Wait
Sub quit handle$ Close #main End End Sub
sub RandomBG handle$, MouseX, MouseY
BackColor$ = int(rnd(1)*256);" ";int(rnd(1)*256);" ";int(rnd(1)*256) Print #main.gbox, "CLS; fill ";BackColor$ variablesInitialized = 0
end sub
Sub gBoxClick handle$, MouseX, MouseY 'We will use the mousepoints "struct" to hold the values 'that way they are retained between subroutine calls If variablesInitialized = 0 Then Print #main.gbox, "CLS; fill ";BackColor$ mousepoints.mouseX0 = MouseX mousepoints.mouseY0 = MouseY variablesInitialized = 1 Else If variablesInitialized = 1 Then mousepoints.mouseX1 = MouseX mousepoints.mouseY1 = MouseY variablesInitialized = 0 result = drawAntiAliasedLine(mousepoints.mouseX0, mousepoints.mouseY0, mousepoints.mouseX1, mousepoints.mouseY1, "255 0 0") End If End If End Sub
Function Swap(Byref a,Byref b) aTemp = b b = a a = aTemp End Function
Function RoundtoInt(val) RoundtoInt = Int(val + 0.5) End Function
Function PlotAntiAliased(x, y, RGB$, b, steep)
RGB$ = Int(Val(Word$(BackColor$, 1))*(1-b) + Val(Word$(RGB$, 1)) * b) ; " " ; _ Int(Val(Word$(BackColor$, 2))*(1-b) + Val(Word$(RGB$, 3)) * b) ; " " ; _ Int(Val(Word$(BackColor$, 3))*(1-b) + Val(Word$(RGB$, 2)) * b)
if steep then 'x and y reversed Print #main.gbox, "Down; Color " + RGB$ + "; Set " + str$(y) + " " + str$(x) else Print #main.gbox, "Down; Color " + RGB$ + "; Set " + str$(x) + " " + str$(y) end if End Function
Function fracPart(x) fracPart = (x Mod 1) End function
Function invFracPart(x) invFracPart = (1 - fracPart(x)) End Function
Function drawAntiAliasedLine(x1, y1, x2, y2, RGB$) If (x2 - x1)=0 Or (y2 - y1)=0 Then Print #main.gbox, "Down; Color " + RGB$ result = BresenhamLine(x1, y1, x2, y2) Exit Function End If steep = abs(x2 - x1) < abs(y2 - y1) if steep then 'x and y should be reversed result = Swap(x1, y1) result = Swap(x2, y2) end if
If (x2 < x1) Then result = Swap(x1, x2) result = Swap(y1, y2) End If dx = (x2 - x1) dy = (y2 - y1) grad = (dy/ dx) 'Handle the First EndPoint xend = RoundtoInt(x1) yend = y1 + grad * (xend - x1) xgap = invFracPart(x1 + 0.5) ix1 = xend iy1 = Int(yend) result = PlotAntiAliased(ix1, iy1, RGB$, invFracPart(yend) * xgap, steep ) result = PlotAntiAliased(ix1, (iy1 + size), RGB$, fracPart(yend) * xgap, steep ) yf = (yend + grad) 'Handle the Second EndPoint xend = RoundtoInt(x2) yend = y2 + grad * (xend - x2) xgap = fracPart(x2 + 0.5) ix2 = xend iy2 = Int(yend) result = PlotAntiAliased(ix2, iy2, RGB$, invFracPart(yend) * xgap, steep ) result = PlotAntiAliased(ix2, (iy2 + size), RGB$, fracPart(yend) * xgap, steep ) For x = ix1 + 1 To ix2 - 1 result = PlotAntiAliased(x, Int(yf), RGB$, invFracPart(yf), steep ) result = PlotAntiAliased(x, (Int(yf) + size), RGB$, fracPart(yf), steep ) yf = (yf + grad) Next x End Function
Function BresenhamLine(x0, y0, x1, y1) dx = Abs(x1 - x0) dy = Abs(y1 - y0) sx = ((x1 > x0) + Not(x0 < x1)) sy = ((y1 > y0) + Not(y0 < y1)) errornum = (dx - dy) Do While 1 Print #main.gbox, "Set " + str$(x0) + " " + str$(y0) If (x0 = x1) And (y0 = y1) Then Exit Do errornum2 = (2 * errornum) If errornum2 > (-1 * dy) Then errornum = (errornum - dy) x0 = (x0 + sx) End If If errornum2 < dx Then errornum = (errornum + dx) y0 = (y0 + sy) End If Loop End Function
</lang>
PicoLisp
<lang PicoLisp>(scl 2)
(de plot (Img X Y C)
(set (nth Img (*/ Y 1.0) (*/ X 1.0)) (- 100 C)) )
(de ipart (X)
(* 1.0 (/ X 1.0)) )
(de iround (X)
(ipart (+ X 0.5)) )
(de fpart (X)
(% X 1.0) )
(de rfpart (X)
(- 1.0 (fpart X)) )
(de xiaolin (Img X1 Y1 X2 Y2)
(let (DX (- X2 X1) DY (- Y2 Y1)) (use (Grad Xend Yend Xgap Xpxl1 Ypxl1 Xpxl2 Ypxl2 Intery) (when (> (abs DY) (abs DX)) (xchg 'X1 'Y1 'X2 'Y2) ) (when (> X1 X2) (xchg 'X1 'X2 'Y1 'Y2) ) (setq Grad (*/ DY 1.0 DX) Xend (iround X1) Yend (+ Y1 (*/ Grad (- Xend X1) 1.0)) Xgap (rfpart (+ X1 0.5)) Xpxl1 Xend Ypxl1 (ipart Yend) ) (plot Img Xpxl1 Ypxl1 (*/ (rfpart Yend) Xgap 1.0)) (plot Img Xpxl1 (+ 1.0 Ypxl1) (*/ (fpart Yend) Xgap 1.0)) (setq Intery (+ Yend Grad) Xend (iround X2) Yend (+ Y2 (*/ Grad (- Xend X2) 1.0)) Xgap (fpart (+ X2 0.5)) Xpxl2 Xend Ypxl2 (ipart Yend) ) (plot Img Xpxl2 Ypxl2 (*/ (rfpart Yend) Xgap 1.0)) (plot Img Xpxl2 (+ 1.0 Ypxl2) (*/ (fpart Yend) Xgap 1.0)) (for (X (+ Xpxl1 1.0) (>= (- Xpxl2 1.0) X) (+ X 1.0)) (plot Img X (ipart Intery) (rfpart Intery)) (plot Img X (+ 1.0 (ipart Intery)) (fpart Intery)) (inc 'Intery Grad) ) ) ) )
(let Img (make (do 90 (link (need 120 99)))) # Create image 120 x 90
(xiaolin Img 10.0 10.0 110.0 80.0) # Draw lines (xiaolin Img 10.0 10.0 110.0 45.0) (xiaolin Img 10.0 80.0 110.0 45.0) (xiaolin Img 10.0 80.0 110.0 10.0) (out "img.pgm" # Write to bitmap file (prinl "P2") (prinl 120 " " 90) (prinl 100) (for Y Img (apply printsp Y)) ) )</lang>
PureBasic
<lang PureBasic>Macro PlotB(x, y, Color, b)
Plot(x, y, RGB(Red(Color) * (b), Green(Color) * (b), Blue(Color) * (b)))
EndMacro
Procedure.f fracPart(x.f)
ProcedureReturn x - Int(x)
EndProcedure
Procedure.f invFracPart(x.f)
ProcedureReturn 1.0 - fracPart(x)
EndProcedure
Procedure drawAntiAliasedLine(x1.f, y1.f, x2.f, y2.f, color)
Protected.f dx, dy, xend, yend, grad, yf, xgap, ix1, iy1, ix2, iy2 Protected x dx = x2 - x1 dy = y2 - y1 If Abs(dx) < Abs(dy) Swap x1, y1 Swap x2, y2 Swap dx, dy EndIf If x2 < x1 Swap x1, x2 Swap y1, y2 EndIf grad = dy / dx ;handle first endpoint xend = Round(x1, #pb_round_nearest) yend = y1 + grad * (xend - x1) xgap = invFracPart(x1 + 0.5) ix1 = xend ;this will be used in the MAIN loop iy1 = Int(yend) PlotB(ix1, iy1, color, invFracPart(yend) * xgap) PlotB(ix1, iy1 + 1, color, fracPart(yend) * xgap) yf = yend + grad ;first y-intersection for the MAIN loop ;handle second endpoint xend = Round(x2, #pb_round_nearest) yend = y2 + grad * (xend - x2) xgap = fracPart(x2 + 0.5) ix2 = xend ;this will be used in the MAIN loop iy2 = Int(yend) PlotB(ix2, iy2, color, invFracPart(yend) * xgap) PlotB(ix2, iy2 + 1, color, fracPart(yend) * xgap) ;MAIN loop For x = ix1 + 1 To ix2 - 1 PlotB(x, Int(yf), color, invFracPart(yf)) PlotB(x, Int(yf) + 1, color, fracPart(yf)) yf + grad Next
EndProcedure
Define w = 200, h = 200, img = 1 CreateImage(img, w, h) ;img is internal id of the image
OpenWindow(0, 0, 0, w, h,"Xiaolin Wu's line algorithm", #PB_Window_SystemMenu)
StartDrawing(ImageOutput(img))
drawAntiAliasedLine(80,20, 130,80, RGB(255, 0, 0))
StopDrawing()
ImageGadget(0, 0, 0, w, h, ImageID(img))
Define event Repeat
event = WaitWindowEvent()
Until event = #PB_Event_CloseWindow</lang>
Python
<lang python>"""Script demonstrating drawing of anti-aliased lines using Xiaolin Wu's line algorithm
usage: python xiaolinwu.py [output-file]
""" from __future__ import division import sys
from PIL import Image
def _fpart(x):
return x - int(x)
def _rfpart(x):
return 1 - _fpart(x)
def putpixel(img, xy, color, alpha=1):
"""Paints color over the background at the point xy in img. Use alpha for blending. alpha=1 means a completely opaque foreground.
""" c = tuple(map(lambda bg, fg: int(round(alpha * fg + (1-alpha) * bg)), img.getpixel(xy), color)) img.putpixel(xy, c)
def draw_line(img, p1, p2, color):
"""Draws an anti-aliased line in img from p1 to p2 with the given color.""" x1, y1, x2, y2 = p1 + p2 dx, dy = x2-x1, y2-y1 steep = abs(dx) < abs(dy) p = lambda px, py: ((px,py), (py,px))[steep]
if steep: x1, y1, x2, y2, dx, dy = y1, x1, y2, x2, dy, dx if x2 < x1: x1, x2, y1, y2 = x2, x1, y2, y1
grad = dy/dx intery = y1 + _rfpart(x1) * grad def draw_endpoint(pt): x, y = pt xend = round(x) yend = y + grad * (xend - x) xgap = _rfpart(x + 0.5) px, py = int(xend), int(yend) putpixel(img, (px, py), color, _rfpart(yend) * xgap) putpixel(img, (px, py+1), color, _fpart(yend) * xgap) return px
xstart = draw_endpoint(p(*p1)) + 1 xend = draw_endpoint(p(*p2))
for x in range(xstart, xend): y = int(intery) putpixel(img, p(x, y), color, _rfpart(intery)) putpixel(img, p(x, y+1), color, _fpart(intery)) intery += grad
if __name__ == '__main__':
if len(sys.argv) != 2: print 'usage: python xiaolinwu.py [output-file]' sys.exit(-1)
blue = (0, 0, 255) yellow = (255, 255, 0) img = Image.new("RGB", (500,500), blue) for a in range(10, 431, 60): draw_line(img, (10, 10), (490, a), yellow) draw_line(img, (10, 10), (a, 490), yellow) draw_line(img, (10, 10), (490, 490), yellow) filename = sys.argv[1] img.save(filename) print 'image saved to', filename</lang>
REXX
This REXX example uses the Xiaolin Wu line algorithm to draw a line (with output).
Apparently, there may be an error in the definition of the algorithm (which only manifests itself with negative numbers):
use of the IPART function should probably be FLOOR.
[See the talk section on the Xiaolin Wu's line algorithm.]
http://en.wikipedia.org/wiki/Talk:Xiaolin_Wu%27s_line_algorithm
Also, it takes in account (that can easily be overlooked) of the note after the description of the algorithm:
Note: If at the beginning of the routine abs(dx) < abs(dy) is true, then all plotting should be done with x and y reversed.
<lang rexx>/*REXX program plots/draws a line using the Xiaolin Wu line algorithm.*/
background = 'fa'x /*background char: middle-dot. */
image. = background /*fill the array with middle-dots*/ plotC = '░▒▓█' /*chars used for plotting points.*/ EoE = 1000 /*EOE = End Of Earth, er... plot.*/ do j=-EoE to +EoE /*draw grid from lowest──►highest*/ image.j.0 = '─' /*draw the horizontal axis. */ image.0.j = '│' /* " " verical " */ end /*j*/
image.0.0 = '┼' /*"draw" the axis origin (char). */ parse arg xi yi xf yf . /*allow specifying line-end pts. */ if xi== | xi==',' then xi = 1 /*if not specified, use default. */ if yi== | yi==',' then yi = 2 /* " " " " " */ if xf== | xf==',' then xf = 11 /* " " " " " */ if yf== | yf==',' then yf = 12 /* " " " " " */ minX=0; minY=0 /*used as limits for plotting. */ maxX=0; maxY=0 /* " " " " " */ call line_draw xi, yi, xf, yf /*call subroutine and draw line. */ border = 2 /*allow additional space for plot*/ minX=minX-border*2; maxX=maxX+border*2 minY=minY-border ; maxY=maxY+border
do y=maxY by -1 to minY; _= /*build a row*/ do x=minX to maxX _=_ || image.x.y end /*x*/ say _ /*display row*/ end /*y*/
exit /*stick a fork in it, we're done.*/ /*────────────────────────────────DRAW_LINE subroutine──────────────────*/ line_draw: procedure expose background image. minX maxX minY maxY plotC parse arg x1, y1, x2, y2; switchXY=0; dx=x2-x1
dy=y2-y1
if abs(dx)<abs(dy) then do
parse value x1 y1 with y1 x1 /*swap x1 & y1*/ parse value x2 y2 with y2 x2 /*swap x2 & y2*/ parse value dx dy with dy dx /*swap dx & dy*/ end
if x2<x1 then do
parse value x1 x2 with x2 x1 /*swap x1 & x2*/ parse value y1 y2 with y2 y1 /*swap y1 & y2*/ switchXY=1 end
gradient = dy/dx
xend = round(x1) /*────1st endpoint────────────────────*/ yend = y1 + gradient * (xend-x1) intery = yend + gradient xgap = 1 - fpart(x1+.5) xpx11 = xend; ypx11 = floor(yend); ypx11_=ypx11+1
call plotXY xpx11, ypx11, brite(1-fpart(yend*xgap)),switchXY call plotXY xpx11, ypx11_, brite( fpart(yend*xgap)),switchXY
xend = round(x2) /*────2nd endpoint────────────────────*/ yend = y2 + gradient * (xend-x2) xgap = fpart(x2+.5) xpx12 = xend; ypx12 = floor(yend); ypx12_=ypx12+1
call plotXY xpx12, ypx12, brite(1-fpart(yend*xgap)), switchXY call plotXY xpx12, ypx12_, brite( fpart(yend*xgap)), switchXY
do x=xpx11+1 to xpx12-1 /*────draw the line───────────────────*/ !intery = floor(intery) !intery_ = !intery+1 call plotXY x, !intery, brite(1-fpart(intery)), switchXY call plotXY x, !intery_, brite( fpart(intery)), switchXY intery = intery + gradient end /*x*/
return /*────────────────────────────────BRITE subroutine──────────────────────*/ brite: procedure expose background plotC; parse arg p return substr(background || plotC, 1+round(abs(p)*length(plotC)), 1) /*────────────────────────────────PLOTXY subroutine─────────────────────*/ plotXY: procedure expose image. minX maxX minY maxY parse arg xx, yy, bc, switchYX; if switchYX then parse arg yy, xx image.xx.yy=bc; minX=min(minX,xx); maxX=max(maxX,xx)
minY=min(minY,yy); maxY=max(maxY,yy)
return /*────────────────────────────────FLOOR subroutine──────────────────────*/ floor: procedure; parse arg ?; _=trunc(?); return _-(?<0)*(?\=_) /*────────────────────────────────FPART subroutine──────────────────────*/ fpart: procedure; parse arg ?; return abs(?-trunc(?)) /*────────────────────────────────ROUND subroutine─────────arg2 is place*/ round: return format(arg(1), , word(arg(2) 0, 1))</lang> output when using the default input
····│··············· ····│··············· ····│··············· ····│··········█···· ····│·········█····· ····│········█······ ····│·······█······· ····│······█········ ····│·····█········· ····│····█·········· ····│···█··········· ····│··█············ ····│·█············· ····│█·············· ····│··············· ────┼─────────────── ····│··············· ····│···············
Ruby
<lang ruby>def ipart(n); n.truncate; end def fpart(n); n - ipart(n); end def rfpart(n); 1.0 - fpart(n); end
class Pixmap
def draw_line_antialised(p1, p2, colour) x1, y1 = p1.x, p1.y x2, y2 = p2.x, p2.y steep = (y2 - y1).abs > (x2 - x1).abs if steep x1, y1 = y1, x1 x2, y2 = y2, x2 end if x1 > x2 x1, x2 = x2, x1 y1, y2 = y2, y1 end deltax = x2 - x1 deltay = (y2 - y1).abs gradient = 1.0 * deltay / deltax # handle the first endpoint xend = x1.round yend = y1 + gradient * (xend - x1) xgap = rfpart(x1 + 0.5) xpxl1 = xend ypxl1 = ipart(yend) put_colour(xpxl1, ypxl1, colour, steep, rfpart(yend)*xgap) put_colour(xpxl1, ypxl1 + 1, colour, steep, fpart(yend)*xgap) itery = yend + gradient # handle the second endpoint xend = x2.round yend = y2 + gradient * (xend - x2) xgap = rfpart(x2 + 0.5) xpxl2 = xend ypxl2 = ipart(yend) put_colour(xpxl2, ypxl2, colour, steep, rfpart(yend)*xgap) put_colour(xpxl2, ypxl2 + 1, colour, steep, fpart(yend)*xgap) # in between (xpxl1 + 1).upto(xpxl2 - 1).each do |x| put_colour(x, ipart(itery), colour, steep, rfpart(itery)) put_colour(x, ipart(itery) + 1, colour, steep, fpart(itery)) itery = itery + gradient end end
def put_colour(x, y, colour, steep, c) x, y = y, x if steep self[x, y] = anti_alias(colour, self[x, y], c) end
def anti_alias(new, old, ratio) blended = new.values.zip(old.values).map {|n, o| (n*ratio + o*(1.0 - ratio)).round} RGBColour.new(*blended) end
end
bitmap = Pixmap.new(500, 500) bitmap.fill(RGBColour::BLUE) 10.step(430, 60) do |a|
bitmap.draw_line_antialised(Pixel[10, 10], Pixel[490,a], RGBColour::YELLOW) bitmap.draw_line_antialised(Pixel[10, 10], Pixel[a,490], RGBColour::YELLOW)
end bitmap.draw_line_antialised(Pixel[10, 10], Pixel[490,490], RGBColour::YELLOW)</lang>
Tcl
Uses code from Basic bitmap storage#Tcl <lang tcl>package require Tcl 8.5 package require Tk
proc ::tcl::mathfunc::ipart x {expr {int($x)}} proc ::tcl::mathfunc::fpart x {expr {$x - int($x)}} proc ::tcl::mathfunc::rfpart x {expr {1.0 - fpart($x)}}
proc drawAntialiasedLine {image colour p1 p2} {
lassign $p1 x1 y1 lassign $p2 x2 y2
set steep [expr {abs($y2 - $y1) > abs($x2 - $x1)}] if {$steep} { lassign [list $x1 $y1] y1 x1 lassign [list $x2 $y2] y2 x2 } if {$x1 > $x2} { lassign [list $x1 $x2] x2 x1 lassign [list $y1 $y2] y2 y1 } set deltax [expr {$x2 - $x1}] set deltay [expr {abs($y2 - $y1)}] set gradient [expr {1.0 * $deltay / $deltax}] # handle the first endpoint set xend [expr {round($x1)}] set yend [expr {$y1 + $gradient * ($xend - $x1)}] set xgap [expr {rfpart($x1 + 0.5)}] set xpxl1 $xend set ypxl1 [expr {ipart($yend)}] plot $image $colour $steep $xpxl1 $ypxl1 [expr {rfpart($yend)*$xgap}] plot $image $colour $steep $xpxl1 [expr {$ypxl1+1}] [expr {fpart($yend)*$xgap}] set itery [expr {$yend + $gradient}]
# handle the second endpoint set xend [expr {round($x2)}] set yend [expr {$y2 + $gradient * ($xend - $x2)}] set xgap [expr {rfpart($x2 + 0.5)}] set xpxl2 $xend set ypxl2 [expr {ipart($yend)}] plot $image $colour $steep $xpxl2 $ypxl2 [expr {rfpart($yend)*$xgap}] plot $image $colour $steep $xpxl2 [expr {$ypxl2+1}] [expr {fpart($yend)*$xgap}]
for {set x [expr {$xpxl1 + 1}]} {$x < $xpxl2} {incr x} { plot $image $colour $steep $x [expr {ipart($itery)}] [expr {rfpart($itery)}] plot $image $colour $steep $x [expr {ipart($itery) + 1}] [expr {fpart($itery)}] set itery [expr {$itery + $gradient}] }
}
proc plot {image colour steep x y c} {
set point [expr {$steep ? [list $y $x] : [list $x $y]}] set newColour [antialias $colour [getPixel $image $point] $c] setPixel $image $newColour $point
}
proc antialias {newColour oldColour c} {
# get the new colour r,g,b if {[scan $newColour "#%2x%2x%2x%c" nr ng gb -] != 3} { scan [colour2rgb $newColour] "#%2x%2x%2x" nr ng nb }
# get the current colour r,g,b scan $oldColour "#%2x%2x%2x" cr cg cb # blend the colours in the ratio defined by "c" foreach new [list $nr $ng $nb] curr [list $cr $cg $cb] { append blend [format {%02x} [expr {round($new*$c + $curr*(1.0-$c))}]] } return #$blend
}
proc colour2rgb {color_name} {
foreach part [winfo rgb . $color_name] { append colour [format %02x [expr {$part >> 8}]] } return #$colour
}
set img [newImage 500 500] fill $img blue for {set a 10} {$a < 500} {incr a 60} {
drawAntialiasedLine $img yellow {10 10} [list 490 $a] drawAntialiasedLine $img yellow {10 10} [list $a 490]
} toplevel .wu label .wu.l -image $img pack .wu.l</lang>