├── .gitignore ├── go.mod ├── example └── example1.go ├── README.md ├── LICENSE.txt ├── drawline.go └── drawline_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.png 2 | 3 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/StephaneBunel/bresenham 2 | -------------------------------------------------------------------------------- /example/example1.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image" 5 | "image/color" 6 | "image/png" 7 | "os" 8 | 9 | "github.com/StephaneBunel/bresenham" 10 | ) 11 | 12 | func main() { 13 | var imgRect = image.Rect(0, 0, 500, 500) 14 | var img = image.NewRGBA(imgRect) 15 | var colBLUE = color.RGBA{0, 0, 255, 255} 16 | 17 | // draw line 18 | bresenham.DrawLine(img, 14, 71, 441, 317, colBLUE) 19 | 20 | // save image 21 | toimg, _ := os.Create("example1.png") 22 | defer toimg.Close() 23 | png.Encode(toimg, img) 24 | } 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Draw line with the Bresenham algorithm in Golang 2 | 3 | Because golang is lacking of a basic drawing library, this is how I rediscovered and implemented the bresenham algorithm to draw a line. 4 | 5 | # Example 6 | 7 | ```go 8 | package main 9 | 10 | import ( 11 | "image" 12 | "image/color" 13 | "image/png" 14 | "os" 15 | 16 | "github.com/StephaneBunel/bresenham" 17 | ) 18 | 19 | func main() { 20 | var imgRect = image.Rect(0, 0, 500, 500) 21 | var img = image.NewRGBA(imgRect) 22 | var colBLUE = color.RGBA{0, 0, 255, 255} 23 | 24 | // draw line 25 | bresenham.DrawLine(img, 14, 71, 441, 317, colBLUE) 26 | 27 | // save image in example1.png 28 | toimg, _ := os.Create("example1.png") 29 | defer toimg.Close() 30 | png.Encode(toimg, img) 31 | } 32 | ``` 33 | 34 | bresenham.DrawLine() gets a Plotter interface as it's first argument. 35 | 36 | ```go 37 | type Plotter interface { 38 | Set(x int, y int, c color.Color) 39 | } 40 | ``` 41 | 42 | # Benchmark 43 | 44 | ```sh 45 | go test -bench=. 46 | ``` 47 | 48 | NB: On a modern processor with dedicated floating point ALU, naive implementation could be on par (or faster) with the bresenham version. 49 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2021 Stéphane Bunel 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 10 | 11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12 | -------------------------------------------------------------------------------- /drawline.go: -------------------------------------------------------------------------------- 1 | package bresenham 2 | 3 | // 2016-10-22, Stéphane Bunel 4 | // * First implementation 5 | // 2021-11-27, Stéphane Bunel 6 | // * Add Plotter interface 7 | 8 | import ( 9 | "image/color" 10 | "image/draw" 11 | ) 12 | 13 | type Plotter interface { 14 | Set(x int, y int, c color.Color) 15 | } 16 | 17 | // Floating point 18 | func Bresenham_1(img draw.Image, x1, y1, x2, y2 int, col color.Color) { 19 | dx, dy := x2-x1, y2-y1 20 | a := float64(dy) / float64(dx) 21 | b := int(float64(y1) - a*float64(x1)) 22 | 23 | img.Set(x1, y1, col) 24 | for x := x1 + 1; x <= x2; x++ { 25 | y := int(a*float64(x)) + b 26 | img.Set(x, y, col) 27 | } 28 | } 29 | 30 | // Floating point with error accumulator 31 | func Bresenham_2(img draw.Image, x1, y1, x2, y2 int, col color.Color) { 32 | dx, dy := x2-x1, y2-y1 33 | a := float64(dy) / float64(dx) 34 | e, e_max, e_sub := 0.0, 0.5, 1.0 35 | y := y1 36 | 37 | img.Set(x1, y1, col) 38 | for x := x1 + 1; x <= x2; x++ { 39 | img.Set(x, y, col) 40 | e += a 41 | if e > e_max { 42 | y += 1 43 | e -= e_sub 44 | } 45 | } 46 | } 47 | 48 | // Integer float -> float * dx -> integer 49 | func Bresenham_3(img draw.Image, x1, y1, x2, y2 int, col color.Color) { 50 | dx, dy := x2-x1, y2-y1 51 | // e, e_max, e_sub := 0*dx, dx/2, dx 52 | e, e_max, e_sub := dx, dx>>1, dx 53 | y := y1 54 | 55 | img.Set(x1, y1, col) 56 | for x := x1 + 1; x <= x2; x++ { 57 | img.Set(x, y, col) 58 | e += dy // <= dy/dx * dx 59 | if e > e_max { 60 | y += 1 61 | e -= e_sub 62 | } 63 | } 64 | } 65 | 66 | // Integer; remove comparison (cmp -> bit test); remove variables; float -> float * 2 * dx -> integer 67 | func Bresenham_4(img draw.Image, x1, y1, x2, y2 int, col color.Color) { 68 | dx, dy := x2-x1, 2*(y2-y1) 69 | e, slope := dx, 2*dx 70 | for ; dx != 0; dx-- { 71 | img.Set(x1, y1, col) 72 | x1++ 73 | e -= dy 74 | if e < 0 { 75 | y1++ 76 | e += slope 77 | } 78 | } 79 | } 80 | 81 | // dx > dy; x1 < x2; y1 < y2 82 | func BresenhamDxXRYD(img draw.Image, x1, y1, x2, y2 int, col color.Color) { 83 | dx, dy := x2-x1, 2*(y2-y1) 84 | e, slope := dx, 2*dx 85 | for ; dx != 0; dx-- { 86 | img.Set(x1, y1, col) 87 | x1++ 88 | e -= dy 89 | if e < 0 { 90 | y1++ 91 | e += slope 92 | } 93 | } 94 | } 95 | 96 | // dy > dx; x1 < x2; y1 < y2 97 | func BresenhamDyXRYD(img draw.Image, x1, y1, x2, y2 int, col color.Color) { 98 | dx, dy := 2*(x2-x1), y2-y1 99 | e, slope := dy, 2*dy 100 | for ; dy != 0; dy-- { 101 | img.Set(x1, y1, col) 102 | y1++ 103 | e -= dx 104 | if e < 0 { 105 | x1++ 106 | e += slope 107 | } 108 | } 109 | } 110 | 111 | // dx > dy; x1 < x2; y1 > y2 112 | func BresenhamDxXRYU(img draw.Image, x1, y1, x2, y2 int, col color.Color) { 113 | dx, dy := x2-x1, 2*(y1-y2) 114 | e, slope := dx, 2*dx 115 | for ; dx != 0; dx-- { 116 | img.Set(x1, y1, col) 117 | x1++ 118 | e -= dy 119 | if e < 0 { 120 | y1-- 121 | e += slope 122 | } 123 | } 124 | } 125 | 126 | func BresenhamDyXRYU(img draw.Image, x1, y1, x2, y2 int, col color.Color) { 127 | dx, dy := 2*(x2-x1), y1-y2 128 | e, slope := dy, 2*dy 129 | for ; dy != 0; dy-- { 130 | img.Set(x1, y1, col) 131 | y1-- 132 | e -= dx 133 | if e < 0 { 134 | x1++ 135 | e += slope 136 | } 137 | } 138 | } 139 | 140 | // Generalized with integer 141 | func Bresenham(p Plotter, x1, y1, x2, y2 int, col color.Color) { 142 | var dx, dy, e, slope int 143 | 144 | // Because drawing p1 -> p2 is equivalent to draw p2 -> p1, 145 | // I sort points in x-axis order to handle only half of possible cases. 146 | if x1 > x2 { 147 | x1, y1, x2, y2 = x2, y2, x1, y1 148 | } 149 | 150 | dx, dy = x2-x1, y2-y1 151 | // Because point is x-axis ordered, dx cannot be negative 152 | if dy < 0 { 153 | dy = -dy 154 | } 155 | 156 | switch { 157 | 158 | // Is line a point ? 159 | case x1 == x2 && y1 == y2: 160 | p.Set(x1, y1, col) 161 | 162 | // Is line an horizontal ? 163 | case y1 == y2: 164 | for ; dx != 0; dx-- { 165 | p.Set(x1, y1, col) 166 | x1++ 167 | } 168 | p.Set(x1, y1, col) 169 | 170 | // Is line a vertical ? 171 | case x1 == x2: 172 | if y1 > y2 { 173 | y1, y2 = y2, y1 174 | } 175 | for ; dy != 0; dy-- { 176 | p.Set(x1, y1, col) 177 | y1++ 178 | } 179 | p.Set(x1, y1, col) 180 | 181 | // Is line a diagonal ? 182 | case dx == dy: 183 | if y1 < y2 { 184 | for ; dx != 0; dx-- { 185 | p.Set(x1, y1, col) 186 | x1++ 187 | y1++ 188 | } 189 | } else { 190 | for ; dx != 0; dx-- { 191 | p.Set(x1, y1, col) 192 | x1++ 193 | y1-- 194 | } 195 | } 196 | p.Set(x1, y1, col) 197 | 198 | // wider than high ? 199 | case dx > dy: 200 | if y1 < y2 { 201 | // BresenhamDxXRYD(img, x1, y1, x2, y2, col) 202 | dy, e, slope = 2*dy, dx, 2*dx 203 | for ; dx != 0; dx-- { 204 | p.Set(x1, y1, col) 205 | x1++ 206 | e -= dy 207 | if e < 0 { 208 | y1++ 209 | e += slope 210 | } 211 | } 212 | } else { 213 | // BresenhamDxXRYU(img, x1, y1, x2, y2, col) 214 | dy, e, slope = 2*dy, dx, 2*dx 215 | for ; dx != 0; dx-- { 216 | p.Set(x1, y1, col) 217 | x1++ 218 | e -= dy 219 | if e < 0 { 220 | y1-- 221 | e += slope 222 | } 223 | } 224 | } 225 | p.Set(x2, y2, col) 226 | 227 | // higher than wide. 228 | default: 229 | if y1 < y2 { 230 | // BresenhamDyXRYD(img, x1, y1, x2, y2, col) 231 | dx, e, slope = 2*dx, dy, 2*dy 232 | for ; dy != 0; dy-- { 233 | p.Set(x1, y1, col) 234 | y1++ 235 | e -= dx 236 | if e < 0 { 237 | x1++ 238 | e += slope 239 | } 240 | } 241 | } else { 242 | // BresenhamDyXRYU(img, x1, y1, x2, y2, col) 243 | dx, e, slope = 2*dx, dy, 2*dy 244 | for ; dy != 0; dy-- { 245 | p.Set(x1, y1, col) 246 | y1-- 247 | e -= dx 248 | if e < 0 { 249 | x1++ 250 | e += slope 251 | } 252 | } 253 | } 254 | p.Set(x2, y2, col) 255 | } 256 | } 257 | 258 | func DrawLine(p Plotter, x1, y1, x2, y2 int, col color.Color) { 259 | Bresenham(p, x1, y1, x2, y2, col) 260 | } 261 | -------------------------------------------------------------------------------- /drawline_test.go: -------------------------------------------------------------------------------- 1 | package bresenham 2 | 3 | // 2016-10-22, Stéphane Bunel 4 | // * go test 5 | // * go test -bench=. 6 | 7 | import ( 8 | "image" 9 | "image/color" 10 | "image/png" 11 | "os" 12 | "testing" 13 | ) 14 | 15 | var colRED = color.RGBA{255, 0, 0, 255} 16 | var colGREEN = color.RGBA{0, 255, 0, 255} 17 | var colBLUE = color.RGBA{0, 0, 255, 255} 18 | var colWHITE = color.RGBA{255, 255, 255, 255} 19 | 20 | func drawCross(img *image.RGBA, x, y int, col color.Color) { 21 | for s := -3; s < 4; s++ { 22 | img.Set(x+s, y, col) 23 | img.Set(x, y+s, col) 24 | } 25 | } 26 | 27 | func Test_BresenhamDxXRYD(t *testing.T) { 28 | var imgRect = image.Rect(0, 0, 100, 100) 29 | var img = image.NewRGBA(imgRect) 30 | 31 | drawCross(img, imgRect.Min.X, imgRect.Min.Y, colWHITE) 32 | drawCross(img, imgRect.Max.X-1, imgRect.Min.Y, colWHITE) 33 | drawCross(img, imgRect.Max.X-1, imgRect.Max.Y-1, colWHITE) 34 | drawCross(img, imgRect.Min.X, imgRect.Max.Y-1, colWHITE) 35 | 36 | x1, y1 := 17, 11 37 | drawCross(img, x1, y1, colRED) 38 | x2, y2 := 71, 41 39 | drawCross(img, x2, y2, colGREEN) 40 | 41 | BresenhamDxXRYD(img, x1, y1, x2, y2, colBLUE) 42 | 43 | // Save result 44 | filename := "bresenhamDxXRYD.png" 45 | toimg, _ := os.Create(filename) 46 | defer toimg.Close() 47 | png.Encode(toimg, img) 48 | } 49 | 50 | func Test_BresenhamDyXRYD(t *testing.T) { 51 | var imgRect = image.Rect(0, 0, 100, 100) 52 | var img = image.NewRGBA(imgRect) 53 | 54 | drawCross(img, imgRect.Min.X, imgRect.Min.Y, colWHITE) 55 | drawCross(img, imgRect.Max.X-1, imgRect.Min.Y, colWHITE) 56 | drawCross(img, imgRect.Max.X-1, imgRect.Max.Y-1, colWHITE) 57 | drawCross(img, imgRect.Min.X, imgRect.Max.Y-1, colWHITE) 58 | 59 | x1, y1 := 17, 11 60 | drawCross(img, x1, y1, colRED) 61 | x2, y2 := 47, 71 62 | drawCross(img, x2, y2, colGREEN) 63 | 64 | BresenhamDyXRYD(img, x1, y1, x2, y2, colBLUE) 65 | 66 | // Save result 67 | filename := "bresenhamDyXRYD.png" 68 | toimg, _ := os.Create(filename) 69 | defer toimg.Close() 70 | png.Encode(toimg, img) 71 | } 72 | 73 | func Test_BresenhamDxXRYU(t *testing.T) { 74 | var imgRect = image.Rect(0, 0, 100, 100) 75 | var img = image.NewRGBA(imgRect) 76 | 77 | drawCross(img, imgRect.Min.X, imgRect.Min.Y, colWHITE) 78 | drawCross(img, imgRect.Max.X-1, imgRect.Min.Y, colWHITE) 79 | drawCross(img, imgRect.Max.X-1, imgRect.Max.Y-1, colWHITE) 80 | drawCross(img, imgRect.Min.X, imgRect.Max.Y-1, colWHITE) 81 | 82 | x1, y1 := 11, 45 83 | drawCross(img, x1, y1, colRED) 84 | 85 | x2, y2 := 71, 7 86 | drawCross(img, x2, y2, colGREEN) 87 | 88 | BresenhamDxXRYU(img, x1, y1, x2, y2, colBLUE) 89 | 90 | // Save result 91 | filename := "bresenhamDxXRYU.png" 92 | toimg, _ := os.Create(filename) 93 | defer toimg.Close() 94 | png.Encode(toimg, img) 95 | } 96 | 97 | func Test_BresenhamDyXRYU(t *testing.T) { 98 | var imgRect = image.Rect(0, 0, 100, 100) 99 | var img = image.NewRGBA(imgRect) 100 | 101 | drawCross(img, imgRect.Min.X, imgRect.Min.Y, colWHITE) 102 | drawCross(img, imgRect.Max.X-1, imgRect.Min.Y, colWHITE) 103 | drawCross(img, imgRect.Max.X-1, imgRect.Max.Y-1, colWHITE) 104 | drawCross(img, imgRect.Min.X, imgRect.Max.Y-1, colWHITE) 105 | 106 | x1, y1 := 24, 71 107 | drawCross(img, x1, y1, colRED) 108 | x2, y2 := 47, 11 109 | drawCross(img, x2, y2, colGREEN) 110 | 111 | BresenhamDyXRYU(img, x1, y1, x2, y2, colBLUE) 112 | 113 | // Save result 114 | filename := "bresenhamDyXRYU.png" 115 | toimg, _ := os.Create(filename) 116 | defer toimg.Close() 117 | png.Encode(toimg, img) 118 | } 119 | 120 | func Test_Bresenham(t *testing.T) { 121 | var imgRect = image.Rect(0, 0, 100, 100) 122 | var img = image.NewRGBA(imgRect) 123 | var x1, y1, x2, y2 int 124 | 125 | drawCross(img, imgRect.Min.X, imgRect.Min.Y, colWHITE) 126 | drawCross(img, imgRect.Max.X-1, imgRect.Min.Y, colWHITE) 127 | drawCross(img, imgRect.Max.X-1, imgRect.Max.Y-1, colWHITE) 128 | drawCross(img, imgRect.Min.X, imgRect.Max.Y-1, colWHITE) 129 | 130 | // H line 131 | x1, y1, x2, y2 = 50, 20, 90, 20 132 | drawCross(img, x1, y1, colRED) 133 | drawCross(img, x2, y2, colGREEN) 134 | Bresenham(img, x1, y1, x2, y2, colBLUE) 135 | 136 | // V line 137 | x1, y1, x2, y2 = 70, 10, 70, 40 138 | drawCross(img, x1, y1, colRED) 139 | drawCross(img, x2, y2, colGREEN) 140 | Bresenham(img, x1, y1, x2, y2, colBLUE) 141 | 142 | // m=1 line 143 | x1, y1, x2, y2 = 60, 60, 90, 90 144 | drawCross(img, x1, y1, colRED) 145 | drawCross(img, x2, y2, colGREEN) 146 | Bresenham(img, x1, y1, x2, y2, colBLUE) 147 | 148 | // dxXLYD line 149 | x1, y1, x2, y2 = 45, 10, 4, 20 150 | drawCross(img, x1, y1, colRED) 151 | drawCross(img, x2, y2, colGREEN) 152 | Bresenham(img, x1, y1, x2, y2, colBLUE) 153 | 154 | // dxXRYU line 155 | x1, y1, x2, y2 = 10, 80, 60, 70 156 | drawCross(img, x1, y1, colRED) 157 | drawCross(img, x2, y2, colGREEN) 158 | Bresenham(img, x1, y1, x2, y2, colBLUE) 159 | 160 | // dyXRYD line 161 | x1, y1, x2, y2 = 12, 10, 44, 90 162 | drawCross(img, x1, y1, colRED) 163 | drawCross(img, x2, y2, colGREEN) 164 | Bresenham(img, x1, y1, x2, y2, colBLUE) 165 | 166 | // dyXLYU line 167 | x1, y1, x2, y2 = 30, 95, 8, 30 168 | drawCross(img, x1, y1, colRED) 169 | drawCross(img, x2, y2, colGREEN) 170 | Bresenham(img, x1, y1, x2, y2, colBLUE) 171 | 172 | // Save result 173 | filename := "bresenhamALL.png" 174 | toimg, _ := os.Create(filename) 175 | defer toimg.Close() 176 | png.Encode(toimg, img) 177 | } 178 | 179 | // ----- bench part ----- 180 | 181 | var imgRect = image.Rect(0, 0, 100, 100) 182 | var img = image.NewRGBA(imgRect) 183 | var x1, y1, x2, y2 = 10, 10, 90, 25 184 | 185 | func Benchmark_Bresenham_1(b *testing.B) { 186 | for i := 0; i < b.N; i++ { 187 | Bresenham_1(img, x1, y1, x2, y2, colWHITE) 188 | } 189 | } 190 | 191 | func Benchmark_Bresenham_2(b *testing.B) { 192 | for i := 0; i < b.N; i++ { 193 | Bresenham_2(img, x1, y1, x2, y2, colWHITE) 194 | } 195 | } 196 | 197 | func Benchmark_Bresenham_3(b *testing.B) { 198 | for i := 0; i < b.N; i++ { 199 | Bresenham_3(img, x1, y1, x2, y2, colWHITE) 200 | } 201 | } 202 | 203 | func Benchmark_Bresenham_4(b *testing.B) { 204 | for i := 0; i < b.N; i++ { 205 | Bresenham_4(img, x1, y1, x2, y2, colWHITE) 206 | } 207 | } 208 | 209 | func Benchmark_BresenhamDxXRYD(b *testing.B) { 210 | for i := 0; i < b.N; i++ { 211 | BresenhamDxXRYD(img, x1, y1, x2, y2, colWHITE) 212 | } 213 | } 214 | 215 | func Benchmark_Bresenham(b *testing.B) { 216 | for i := 0; i < b.N; i++ { 217 | Bresenham(img, x1, y1, x2, y2, colWHITE) 218 | } 219 | } 220 | --------------------------------------------------------------------------------