// Copyright 2018 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package draw // braille_line.go contains code that draws lines on a braille canvas. import ( "fmt" "image" "github.com/mum4k/termdash/canvas/braille" "github.com/mum4k/termdash/cell" ) // BrailleLineOption is used to provide options to BrailleLine(). type BrailleLineOption interface { // set sets the provided option. set(*brailleLineOptions) } // brailleLineOptions stores the provided options. type brailleLineOptions struct { cellOpts []cell.Option } // newBrailleLineOptions returns a new brailleLineOptions instance. func newBrailleLineOptions() *brailleLineOptions { return &brailleLineOptions{} } // brailleLineOption implements BrailleLineOption. type brailleLineOption func(*brailleLineOptions) // set implements BrailleLineOption.set. func (o brailleLineOption) set(opts *brailleLineOptions) { o(opts) } // BrailleLineCellOpts sets options on the cells that contain the line. func BrailleLineCellOpts(cOpts ...cell.Option) BrailleLineOption { return brailleLineOption(func(opts *brailleLineOptions) { opts.cellOpts = cOpts }) } // BrailleLine draws an approximated line segment on the braille canvas between // the two provided points. // Both start and end must be valid points within the canvas. Start and end can // be the same point in which case only one pixel will be set on the braille // canvas. // The start or end coordinates must not be negative. func BrailleLine(bc *braille.Canvas, start, end image.Point, opts ...BrailleLineOption) error { if start.X < 0 || start.Y < 0 { return fmt.Errorf("the start coordinates cannot be negative, got: %v", start) } if end.X < 0 || end.Y < 0 { return fmt.Errorf("the end coordinates cannot be negative, got: %v", end) } opt := newBrailleLineOptions() for _, o := range opts { o.set(opt) } // Implements Bresenham's line algorithm. // https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm vertProj := abs(end.Y - start.Y) horizProj := abs(end.X - start.X) if vertProj < horizProj { if start.X > end.X { return lineLow(bc, end.X, end.Y, start.X, start.Y, opt) } else { return lineLow(bc, start.X, start.Y, end.X, end.Y, opt) } } else { if start.Y > end.Y { return lineHigh(bc, end.X, end.Y, start.X, start.Y, opt) } else { return lineHigh(bc, start.X, start.Y, end.X, end.Y, opt) } } } // lineLow draws a line whose horizontal projection (end.X - start.X) is longer // than its vertical projection (end.Y - start.Y). func lineLow(bc *braille.Canvas, x0, y0, x1, y1 int, opt *brailleLineOptions) error { deltaX := x1 - x0 deltaY := y1 - y0 stepY := 1 if deltaY < 0 { stepY = -1 deltaY = -deltaY } diff := 2*deltaY - deltaX y := y0 for x := x0; x <= x1; x++ { p := image.Point{x, y} if err := bc.SetPixel(p, opt.cellOpts...); err != nil { return fmt.Errorf("lineLow bc.SetPixel(%v) => %v", p, err) } if diff > 0 { y += stepY diff -= 2 * deltaX } diff += 2 * deltaY } return nil } // lineHigh draws a line whose vertical projection (end.Y - start.Y) is longer // than its horizontal projection (end.X - start.X). func lineHigh(bc *braille.Canvas, x0, y0, x1, y1 int, opt *brailleLineOptions) error { deltaX := x1 - x0 deltaY := y1 - y0 stepX := 1 if deltaX < 0 { stepX = -1 deltaX = -deltaX } diff := 2*deltaX - deltaY x := x0 for y := y0; y <= y1; y++ { p := image.Point{x, y} if err := bc.SetPixel(p, opt.cellOpts...); err != nil { return fmt.Errorf("lineHigh bc.SetPixel(%v) => %v", p, err) } if diff > 0 { x += stepX diff -= 2 * deltaY } diff += 2 * deltaX } return nil } // abs returns the absolute value of x. func abs(x int) int { if x < 0 { return -x } return x }