mirror of https://github.com/mum4k/termdash.git
215 lines
5.7 KiB
Go
215 lines
5.7 KiB
Go
// 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"
|
|
)
|
|
|
|
// braillePixelChange represents an action on a pixel on the braille canvas.
|
|
type braillePixelChange int
|
|
|
|
// String implements fmt.Stringer()
|
|
func (bpc braillePixelChange) String() string {
|
|
if n, ok := braillePixelChangeNames[bpc]; ok {
|
|
return n
|
|
}
|
|
return "braillePixelChangeUnknown"
|
|
}
|
|
|
|
// braillePixelChangeNames maps braillePixelChange values to human readable names.
|
|
var braillePixelChangeNames = map[braillePixelChange]string{
|
|
braillePixelChangeSet: "braillePixelChangeSet",
|
|
braillePixelChangeClear: "braillePixelChangeClear",
|
|
}
|
|
|
|
const (
|
|
braillePixelChangeUnknown braillePixelChange = iota
|
|
|
|
braillePixelChangeSet
|
|
braillePixelChangeClear
|
|
)
|
|
|
|
// 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
|
|
pixelChange braillePixelChange
|
|
}
|
|
|
|
// newBrailleLineOptions returns a new brailleLineOptions instance.
|
|
func newBrailleLineOptions() *brailleLineOptions {
|
|
return &brailleLineOptions{
|
|
pixelChange: braillePixelChangeSet,
|
|
}
|
|
}
|
|
|
|
// 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.
|
|
// Cell options on a braille canvas can only be set on the entire cell, not per
|
|
// pixel.
|
|
func BrailleLineCellOpts(cOpts ...cell.Option) BrailleLineOption {
|
|
return brailleLineOption(func(opts *brailleLineOptions) {
|
|
opts.cellOpts = cOpts
|
|
})
|
|
}
|
|
|
|
// BrailleLineClearPixels changes the behavior of BrailleLine, so that it
|
|
// clears the pixels belonging to the line instead of setting them.
|
|
// Useful in order to "erase" a line from the canvas as opposed to drawing one.
|
|
func BrailleLineClearPixels() BrailleLineOption {
|
|
return brailleLineOption(func(opts *brailleLineOptions) {
|
|
opts.pixelChange = braillePixelChangeClear
|
|
})
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
points := brailleLinePoints(start, end)
|
|
for _, p := range points {
|
|
switch opt.pixelChange {
|
|
case braillePixelChangeSet:
|
|
if err := bc.SetPixel(p, opt.cellOpts...); err != nil {
|
|
return fmt.Errorf("bc.SetPixel(%v) => %v", p, err)
|
|
}
|
|
case braillePixelChangeClear:
|
|
if err := bc.ClearPixel(p, opt.cellOpts...); err != nil {
|
|
return fmt.Errorf("bc.ClearPixel(%v) => %v", p, err)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// brailleLinePoints returns the points to set when drawing the line.
|
|
func brailleLinePoints(start, end image.Point) []image.Point {
|
|
// 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(end.X, end.Y, start.X, start.Y)
|
|
} else {
|
|
return lineLow(start.X, start.Y, end.X, end.Y)
|
|
}
|
|
} else {
|
|
if start.Y > end.Y {
|
|
return lineHigh(end.X, end.Y, start.X, start.Y)
|
|
} else {
|
|
return lineHigh(start.X, start.Y, end.X, end.Y)
|
|
}
|
|
}
|
|
}
|
|
|
|
// lineLow returns points that create a line whose horizontal projection
|
|
// (end.X - start.X) is longer than its vertical projection
|
|
// (end.Y - start.Y).
|
|
func lineLow(x0, y0, x1, y1 int) []image.Point {
|
|
deltaX := x1 - x0
|
|
deltaY := y1 - y0
|
|
|
|
stepY := 1
|
|
if deltaY < 0 {
|
|
stepY = -1
|
|
deltaY = -deltaY
|
|
}
|
|
|
|
var res []image.Point
|
|
diff := 2*deltaY - deltaX
|
|
y := y0
|
|
for x := x0; x <= x1; x++ {
|
|
res = append(res, image.Point{x, y})
|
|
if diff > 0 {
|
|
y += stepY
|
|
diff -= 2 * deltaX
|
|
}
|
|
diff += 2 * deltaY
|
|
}
|
|
return res
|
|
}
|
|
|
|
// lineHigh returns points that createa line whose vertical projection
|
|
// (end.Y - start.Y) is longer than its horizontal projection
|
|
// (end.X - start.X).
|
|
func lineHigh(x0, y0, x1, y1 int) []image.Point {
|
|
deltaX := x1 - x0
|
|
deltaY := y1 - y0
|
|
|
|
stepX := 1
|
|
if deltaX < 0 {
|
|
stepX = -1
|
|
deltaX = -deltaX
|
|
}
|
|
|
|
var res []image.Point
|
|
diff := 2*deltaX - deltaY
|
|
x := x0
|
|
for y := y0; y <= y1; y++ {
|
|
res = append(res, image.Point{x, y})
|
|
|
|
if diff > 0 {
|
|
x += stepX
|
|
diff -= 2 * deltaY
|
|
}
|
|
diff += 2 * deltaX
|
|
}
|
|
return res
|
|
}
|
|
|
|
// abs returns the absolute value of x.
|
|
func abs(x int) int {
|
|
if x < 0 {
|
|
return -x
|
|
}
|
|
return x
|
|
}
|