diff --git a/README.md b/README.md
index e2b0a9a..3ba444d 100644
--- a/README.md
+++ b/README.md
@@ -21,3 +21,6 @@ You can also copy *.png files into the `examples` folder and include their names
That will create corresponding *.html files.
+## Binaries ##
+The dist folder includes binaries for different operating systems.
+They allow you to convert *.png files to svg by including the png file name(s) as command line arguments.
\ No newline at end of file
diff --git a/dist/darwin/386/pixel2svg b/dist/darwin/386/pixel2svg
new file mode 100644
index 0000000..159934b
Binary files /dev/null and b/dist/darwin/386/pixel2svg differ
diff --git a/dist/darwin/amd64/pixel2svg b/dist/darwin/amd64/pixel2svg
new file mode 100644
index 0000000..159934b
Binary files /dev/null and b/dist/darwin/amd64/pixel2svg differ
diff --git a/dist/freebsd/386/pixel2svg b/dist/freebsd/386/pixel2svg
new file mode 100644
index 0000000..159934b
Binary files /dev/null and b/dist/freebsd/386/pixel2svg differ
diff --git a/dist/freebsd/amd64/pixel2svg b/dist/freebsd/amd64/pixel2svg
new file mode 100644
index 0000000..159934b
Binary files /dev/null and b/dist/freebsd/amd64/pixel2svg differ
diff --git a/dist/freebsd/arm/pixel2svg b/dist/freebsd/arm/pixel2svg
new file mode 100644
index 0000000..159934b
Binary files /dev/null and b/dist/freebsd/arm/pixel2svg differ
diff --git a/dist/linux/386/pixel2svg b/dist/linux/386/pixel2svg
new file mode 100644
index 0000000..159934b
Binary files /dev/null and b/dist/linux/386/pixel2svg differ
diff --git a/dist/linux/amd64/pixel2svg b/dist/linux/amd64/pixel2svg
new file mode 100644
index 0000000..159934b
Binary files /dev/null and b/dist/linux/amd64/pixel2svg differ
diff --git a/dist/linux/arm/pixel2svg b/dist/linux/arm/pixel2svg
new file mode 100644
index 0000000..159934b
Binary files /dev/null and b/dist/linux/arm/pixel2svg differ
diff --git a/dist/netbsd/386/pixel2svg b/dist/netbsd/386/pixel2svg
new file mode 100644
index 0000000..159934b
Binary files /dev/null and b/dist/netbsd/386/pixel2svg differ
diff --git a/dist/netbsd/amd64/pixel2svg b/dist/netbsd/amd64/pixel2svg
new file mode 100644
index 0000000..159934b
Binary files /dev/null and b/dist/netbsd/amd64/pixel2svg differ
diff --git a/dist/netbsd/arm/pixel2svg b/dist/netbsd/arm/pixel2svg
new file mode 100644
index 0000000..159934b
Binary files /dev/null and b/dist/netbsd/arm/pixel2svg differ
diff --git a/dist/openbsd/386/pixel2svg b/dist/openbsd/386/pixel2svg
new file mode 100644
index 0000000..159934b
Binary files /dev/null and b/dist/openbsd/386/pixel2svg differ
diff --git a/dist/openbsd/amd64/pixel2svg b/dist/openbsd/amd64/pixel2svg
new file mode 100644
index 0000000..159934b
Binary files /dev/null and b/dist/openbsd/amd64/pixel2svg differ
diff --git a/examples/main.go b/examples/main.go
index aa6a52c..6fb3d94 100644
--- a/examples/main.go
+++ b/examples/main.go
@@ -79,22 +79,23 @@ func convertToUint8(rgbTone uint32) uint8 {
return uint8(rgbTone / 0x101)
}
-func ReadPNGPixels(filePath string) [][][4]uint8 {
+func ReadPNGPixels(filePath string) ([][][4]uint8, error) {
+
+ var infile *os.File
+ var err error
+ var src image.Image
+
// fmt.Println("\nReading \n", filePath)
- infile, err := os.Open(filePath)
- if err != nil {
- // replace this with good error handling
- panic(err)
+ if infile, err = os.Open(filePath); err != nil {
+ return nil, err
}
defer infile.Close()
// Decode will figure out what type of image is in the file on its own.
// We just have to be sure all the image packages we want are imported.
- src, _, err := image.Decode(infile)
- if err != nil {
- // replace this with good error handling
- panic(err)
+ if src, _, err = image.Decode(infile); err != nil {
+ return nil, err
}
colorGrid := [][][4]uint8{}
@@ -114,7 +115,14 @@ func ReadPNGPixels(filePath string) [][][4]uint8 {
colorGrid = append(colorGrid, newCol)
}
- return colorGrid
+ return colorGrid, nil
+}
+
+func addError(errors *[]string, summary string, err error) {
+ *errors = append(
+ *errors,
+ strings.Join([]string{summary, err.Error()}, " "),
+ )
}
/*
@@ -126,20 +134,41 @@ func ReadPNGPixels(filePath string) [][][4]uint8 {
* That will create corresponding *.html files.
*/
func main() {
+ errors := []string{}
var s pixels2svg.ShapeExtractor
+ var colorGrid [][][4]uint8
+ var err error
s.Init(sailboat())
s.WriteSVGToFile("example_sailboat.html")
- s.Init(ReadPNGPixels("test1.png"))
- s.WriteSVGToFile("example_test1.html")
+ if colorGrid, err = ReadPNGPixels("test1.png"); err == nil {
+ s.Init(colorGrid)
+ s.WriteSVGToFile("example_test1.html")
+ }
args := os.Args[1:]
+ if len(args) <= 0 {
+ println("\n To create from other png files, just add their names as command line arguments.")
+ }
+
for _, nextInput := range args {
if strings.HasSuffix(nextInput, ".png") {
- s.Init(ReadPNGPixels(nextInput))
+ colorGrid, err := ReadPNGPixels(nextInput)
+ if err != nil {
+ addError(&errors, strings.Join([]string{" Error: ", nextInput, " ... "}, ""), err)
+ continue
+ }
+ s.Init(colorGrid)
newName := strings.TrimSuffix(nextInput, ".png") + ".html"
s.WriteSVGToFile(newName)
}
}
+
+ if len(errors) > 0 {
+ println("\nRan into error(s) ...")
+ for _, nextErr := range errors {
+ println(nextErr)
+ }
+ }
}
diff --git a/pixels2svg/pixels2svg.go b/pixels2svg/pixels2svg.go
index 64544ea..1d224e6 100644
--- a/pixels2svg/pixels2svg.go
+++ b/pixels2svg/pixels2svg.go
@@ -28,6 +28,7 @@ type ShapeExtractor struct {
ColCount int
RowCount int
neighborEvaluators [8]evaluatorFunc
+ cellQueue [][2]int
}
func (s *ShapeExtractor) showAlreadyDone() {
@@ -215,10 +216,41 @@ func (s *ShapeExtractor) getLeftDirection(direction int) int {
* get the direction slightly to its right.
*/
func (s *ShapeExtractor) getAngledRightDirection(direction int) int {
- if direction >= 7 {
+ return (direction + 1) % 8
+}
+
+/*
+ * Assuming an outline walker is headed a certain direction,
+ * get the direction to the right (90 degrees).
+ */
+func (s *ShapeExtractor) getRight90Direction(direction int) int {
+ return (direction + 2) % 8
+}
+
+func (s *ShapeExtractor) getLatestDirection(colX1, rowY1, colX2, rowY2 int) int {
+ colDiff := colX2 - colX1
+ rowDiff := rowY2 - rowY1
+
+ switch {
+ case (colDiff == 0 && rowDiff < 0):
return 0
+ case (colDiff > 0 && rowDiff < 0):
+ return 1
+ case (colDiff > 0 && rowDiff == 0):
+ return 2
+ case (colDiff > 0 && rowDiff > 0):
+ return 3
+ case (colDiff == 0 && rowDiff > 0):
+ return 4
+ case (colDiff < 0 && rowDiff > 0):
+ return 5
+ case (colDiff < 0 && rowDiff == 0):
+ return 6
+ case (colDiff < 0 && rowDiff < 0):
+ return 7
}
- return direction + 1
+
+ return 0
}
/*
@@ -424,6 +456,43 @@ func (s *ShapeExtractor) GetPolygonsFromCell(
return allPolygons
}
+/*
+ * Check the neighboring cells. If they are the
+ * same color and not yet done, mark them as done and add them to the queue.
+ */
+func (s *ShapeExtractor) addNeighborsToQueue(colX, rowY int, color [4]uint8) {
+
+ for direction := 0; direction <= 7; direction += 1 {
+
+ evaluator := s.neighborEvaluators[direction]
+ isGood := evaluator(colX, rowY, color)
+
+ if isGood {
+ nextCol, nextRow := s.getCellInDirection(colX, rowY, direction)
+ s.alreadyDone[nextCol][nextRow] = true
+ s.cellQueue = append(s.cellQueue, [2]int{nextCol, nextRow})
+ }
+ }
+}
+
+/*
+ * For each cell in the queue, mark it as already done
+ * and remove it from the queue, but also
+ * add its neighbors to the queue if they have the same color
+ * and aren't done yet
+ */
+func (s *ShapeExtractor) markCellQueueDone(color [4]uint8) {
+ if len(s.cellQueue) < 1 {
+ return
+ }
+
+ colX, rowY := split2Int(s.cellQueue[0])
+ s.addNeighborsToQueue(colX, rowY, color)
+ s.cellQueue = s.cellQueue[1:]
+
+ s.markCellQueueDone(color)
+}
+
/*
* Given a polygon with an outline starting at a certain cell, mark
* all its points (outline and internal) as "alreadyDone".
@@ -432,31 +501,61 @@ func (s *ShapeExtractor) GetPolygonsFromCell(
* Assumes first point is the highest row of the outline and the
* left-most cell of that row.
*
- * From each point on the outline of the polygon, marks it and all cells
- * below it as already done until it runs into another cell of the outline
- * or a cell of a different color
+ * Marks each point on the outline as already done and also adds the
+ * cell to their "right" and "angled right" to a queue for continuing
+ * the process of marking as done.
*
*/
func (s *ShapeExtractor) markPolygonAlreadyDone(polygonOutline [][2]int) {
- firstCol, firstRow := split2Int(polygonOutline[0])
- color := s.grid[firstCol][firstRow]
- for _, nextPoint := range polygonOutline {
+ if len(polygonOutline) < 3 {
+ return
+ }
+
+ s.setNeighborEvaluators()
+ s.cellQueue = [][2]int{}
+
+ // deal with first cell on its own
+ prevCol, prevRow := split2Int(polygonOutline[0])
+ color := s.grid[prevCol][prevRow]
+ s.alreadyDone[prevCol][prevRow] = true
+
+ // Walk through outline and add cells to the right to the queue
+ // of cells to mark as already done
+ for _, nextPoint := range polygonOutline[1:] {
nextCol, nextRow := split2Int(nextPoint)
+ direction := s.getLatestDirection(prevCol, prevRow, nextCol, nextRow)
s.alreadyDone[nextCol][nextRow] = true
- for lowerRow := nextRow + 1; lowerRow < s.RowCount; lowerRow++ {
- if color != s.grid[nextCol][lowerRow] {
- break
- }
+ // Get its cell to the "right" and if necessary, add to the queue
+ innerDirection := s.getRight90Direction(direction)
- if IsPointIn2IntArray(nextCol, lowerRow, polygonOutline) {
- break
- }
+ evaluator := s.neighborEvaluators[innerDirection]
+ isGood := evaluator(nextCol, nextRow, color)
+ if isGood {
+ innerCol, innerRow := s.getCellInDirection(nextCol, nextRow, innerDirection)
+ s.alreadyDone[innerCol][innerRow] = true
- s.alreadyDone[nextCol][lowerRow] = true
+ s.cellQueue = append(s.cellQueue, [2]int{innerCol, innerRow})
}
+
+ // Get its cell slightly to the "right" and if necessary, add to the queue
+ innerDirection = s.getAngledRightDirection(direction)
+
+ evaluator = s.neighborEvaluators[innerDirection]
+ isGood = evaluator(nextCol, nextRow, color)
+ if isGood {
+ innerCol, innerRow := s.getCellInDirection(nextCol, nextRow, innerDirection)
+ s.alreadyDone[innerCol][innerRow] = true
+
+ s.cellQueue = append(s.cellQueue, [2]int{innerCol, innerRow})
+ }
+
+ prevCol = nextCol
+ prevRow = nextRow
}
+
+ s.markCellQueueDone(color)
// s.showAlreadyDone()
// println("\n")
}
diff --git a/pixels2svg/pixels2svg_test.go b/pixels2svg/pixels2svg_test.go
index 46acc0c..cf1867f 100644
--- a/pixels2svg/pixels2svg_test.go
+++ b/pixels2svg/pixels2svg_test.go
@@ -167,42 +167,37 @@ func compareSliceofStrings(results, expected []string) string {
return ""
}
+/*
+ * Five columns, four rows - all almost black
+ */
func getColorGrid() [][][4]uint8 {
+ black := [4]uint8{1, 1, 1, 1}
grid := [][][4]uint8{
- { // First column
- {1, 1, 1, 1},
- {1, 1, 1, 1},
- {1, 1, 1, 1},
- {1, 1, 1, 1},
- },
- { // Second column
- {1, 1, 1, 1},
- {1, 1, 1, 1},
- {1, 1, 1, 1},
- {1, 1, 1, 1},
- },
- { // Third column
- {1, 1, 1, 1},
- {1, 1, 1, 1},
- {1, 1, 1, 1},
- {1, 1, 1, 1},
- },
- { // Fourth column
- {1, 1, 1, 1},
- {1, 1, 1, 1},
- {1, 1, 1, 1},
- {1, 1, 1, 1},
- },
- { // Fifth column
- {1, 1, 1, 1},
- {1, 1, 1, 1},
- {1, 1, 1, 1},
- {1, 1, 1, 1},
- },
+ {black, black, black, black}, // Column 0
+ {black, black, black, black}, // Column 1
+ {black, black, black, black}, // Column 2
+ {black, black, black, black}, // Column 3
+ {black, black, black, black}, // Column 4
}
return grid
}
+/*
+ * 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
+ * 0 r r r r r r y y y y y y g g g g g g
+ * 1 r r r r r r b y y y y y g g g g g g
+ * 2 r r r r r r b b y y y y g g g g g g
+ * 3 r r r r r r b b b y y y g g g g g g
+ * 4 r r r b r r b b b b y y g g r r g g
+ * 5 r r r b r r b b b b b y g g r r g g
+ * 6 r r r b r r b b b b b b g g r r g g
+ * 7 r r r b r r b b b b b b g g r r g g
+ * 8 r r r r r r b b b b b b g g g g g g
+ * 9 r r r r r r b b b b b b g g g g g g
+ * 10 r r r r r r b b b b b b g g g g g g
+ * 11 r r r r r r b b b b b b g g g g g g
+ * 12
+ */
func getBigColorGrid() [][][4]uint8 {
gridColors := [][][4]uint8{}
@@ -522,6 +517,85 @@ func TestOutlinePolygonPartial(t *testing.T) {
}
}
+func TestAddNeighborsToQueueTopEdge(t *testing.T) {
+ var s ShapeExtractor
+
+ s.setNeighborEvaluators()
+
+ gridColors := getBigColorGrid()
+ s.Init(gridColors)
+ s.cellQueue = [][2]int{}
+ red := s.grid[0][0]
+
+ // outline and already done points
+ s.alreadyDone[2][0] = true
+ s.alreadyDone[3][0] = true
+ s.alreadyDone[4][0] = true
+ s.alreadyDone[2][1] = true
+
+ s.addNeighborsToQueue(3, 1, red)
+ results := s.cellQueue
+ expected := [][2]int{{4, 1}, {4, 2}, {3, 2}, {2, 2}}
+
+ err := compareOutlinePoints(results, expected)
+ if err != "" {
+ t.Errorf("Cell Queue. %s", err)
+ }
+}
+
+func TestAddNeighborsToQueueTopRightCorner(t *testing.T) {
+ var s ShapeExtractor
+
+ s.setNeighborEvaluators()
+
+ gridColors := getBigColorGrid()
+ s.Init(gridColors)
+ s.cellQueue = [][2]int{}
+ red := s.grid[0][0]
+
+ s.alreadyDone[3][0] = true
+ s.alreadyDone[4][0] = true
+ s.alreadyDone[5][0] = true
+ s.alreadyDone[5][1] = true
+ s.alreadyDone[5][2] = true
+ s.alreadyDone[3][1] = true
+
+ s.addNeighborsToQueue(4, 1, red) // (5, 0) and (5, 1) are on outline
+ results := s.cellQueue
+ expected := [][2]int{{4, 2}, {3, 2}}
+
+ err := compareOutlinePoints(results, expected)
+ if err != "" {
+ t.Errorf("Cell Queue. %s", err)
+ }
+}
+
+func TestAddNeighborsToQueueByInnerDiff(t *testing.T) {
+ var s ShapeExtractor
+
+ s.setNeighborEvaluators()
+
+ gridColors := getBigColorGrid()
+ s.Init(gridColors)
+ s.cellQueue = [][2]int{}
+ red := s.grid[0][0]
+
+ s.alreadyDone[2][2] = true
+ s.alreadyDone[3][2] = true
+ s.alreadyDone[4][2] = true
+ s.alreadyDone[5][2] = true
+ s.alreadyDone[5][3] = true
+
+ s.addNeighborsToQueue(3, 3, red)
+ results := s.cellQueue
+ expected := [][2]int{{4, 3}, {4, 4}, {2, 4}, {2, 3}}
+
+ err := compareOutlinePoints(results, expected)
+ if err != "" {
+ t.Errorf("Cell Queue. %s", err)
+ }
+}
+
/*
* | X | X | X | X | X | X |
* | X | 0 | 1 | X | 3 | 4 |
@@ -584,9 +658,9 @@ func TestMarkPolygonAlreadyDonePartial(t *testing.T) {
results := s.alreadyDone
expected := [][]bool{
- {false, false, true, true, true, true}, // column 0
- {false, true, true, false, false, true}, // column 1
- {false, true, true, false, false, true},
+ {false, false, true, true, true, true}, // column 0
+ {false, true, true, false, true, true}, // column 1
+ {false, true, true, false, true, true},
{false, false, true, true, true, true},
{false, true, true, true, true, true},
{false, true, true, true, true, true},
@@ -1066,9 +1140,7 @@ func TestGetSVGTextLarge(t *testing.T) {
-
-
`