Skip to content

Commit

Permalink
added --colours
Browse files Browse the repository at this point in the history
  • Loading branch information
StarsoftAnalysis committed Apr 8, 2024
1 parent 21fb60d commit ab2fdec
Show file tree
Hide file tree
Showing 8 changed files with 185 additions and 26 deletions.
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,17 @@ still fit within the paper size and margin.
The result may look a bit tatty at the edges if the --framewidth is less than the --linewidth and --clip is not used.

* `--image | -i`
Use the original image as a background in the SVG image. Default false. Example: `--image`
Use the original image as a background in the SVG image. Default `false`. Example: `--image`

* `--clip | -c`
Clip borders of image, rather than breaking contours. This will hopefully allow filling contours, but won't work with AxiDraw. Default false.
Clip borders of image, rather than breaking contours. This will hopefully allow filling contours, but won't work with AxiDraw. Default `false`.

* `--colours | -C <hexcolour[,hexcolour]> | <hexcolour-hexcolour> `
Colours to use for filling, given as one or [six-digit hexadecimal RGB colour strings](https://developer.mozilla.org/en-US/docs/Web/CSS/hex-color) separated by commas.
Alternatively, two colours separated by a dash ('-') will be used as a range, and intermediate colours will be interpolated.
Implies `--clip`, because otherwise filling won't work.
The colours will cover a background image if `--image` is used as well.
Default: none -- no fill. Examples: `--colours ff0000` `--colours ff4444,44ff44,4444ff` `--colours 000000-ffffff`

* `--debug | -d`
Add extra bits to the SVG file and command line output -- intended for developer use only. Default false.
Expand All @@ -82,6 +89,12 @@ Add extra bits to the SVG file and command line output -- intended for developer

<img alt="Photo of breakwaters on a beach" src="examples/beach.png" title="Input image" width=45%>&nbsp;&nbsp;&nbsp;&nbsp;<img alt="The same photo after processing, showing as the outlines of shapes" src="examples/beach-hc-t32,64,96,128,160,192,224m15pA4LI.png" title="Created SVG image (converted to PNG)" width=45%>

`./hcontours examples/beach.png --colours ff0000,777700,00ff00,00ffff,0000ff,770077 -T5` produces this:

<img alt="Photo of breakwaters on a beach" src="examples/beach.png" title="Input image" width=45%>&nbsp;&nbsp;&nbsp;&nbsp;<img alt="The same photo after processing, showing as the outlines of shapes filled in with lurid colours" src="examples/beach-hc-T5m15pA4LCff0000,777700,00ff00,00ffff,0000ff,770077.svg" title="Created SVG image (converted to PNG)" width=45%>



## Requirements

* Go 1.22
Expand Down
Binary file modified examples/beach-hc-t32,64,96,128,160,192,224m15pA4LI.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
39 changes: 35 additions & 4 deletions hc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ func TestPWA(t *testing.T) {
}
}

// TODO colours tests
func TestFilename(t *testing.T) {
fmt.Println("TestFilename")
type testdataT struct {
Expand All @@ -87,10 +88,13 @@ func TestFilename(t *testing.T) {
dev bool
linewidth float64
framewidth float64
colours string // two hex colours, e.g. "0033ff,0c4088"
*/
testdata := []testdataT{
{OptsT{"file1.png", 100, 200, []int{44, 55}, -1, 15.0, "5x7", RectangleT{0, 0}, true, false, true, 1.0, 0.0}, "file1-hc-t44,55m15p5x7I.svg"},
{OptsT{"file1.png", 100, 200, []int{}, 3, 10.3, "200x300", RectangleT{0, 0}, false, true, false, 1.0, 2.0}, "file1-hc-T3m10.3p200x300F2C.svg"},
{OptsT{"file1.png", 100, 200, []int{44, 55}, -1, 15.0, "5x7", RectangleT{0, 0}, true, false, true, 1.0, 0.0, ""},
"file1-hc-t44,55m15p5x7I.svg"},
{OptsT{"file1.png", 100, 200, []int{}, 3, 10.3, "200x300", RectangleT{0, 0}, false, true, false, 1.0, 2.0, ""},
"file1-hc-T3m10.3p200x300F2C.svg"},
}
for i, td := range testdata {
filename := buildSVGfilename(td.opts)
Expand All @@ -100,6 +104,33 @@ func TestFilename(t *testing.T) {
}
}

func TestSetColours(t *testing.T) {
fmt.Println("TestSetColours")
type testdataT struct {
id int
colourString string
tcount int // just need the number of thresholds
colours []string // expected result
}
testdata := []testdataT{
{1, "abcdef", 1, []string{"abcdef", "abcdef"}},
{2, "abcdef", 2, []string{"abcdef", "abcdef"}},
{3, "abcdef,123456", 1, []string{"abcdef", "123456"}},
{4, "abcdef,123456", 3, []string{"abcdef", "123456"}},
{5, "111111-999999", 1, []string{"111111", "999999"}},
{6, "111111-999999", 4, []string{"111111", "333333", "555555", "777777", "999999"}},
{7, "FFFFFF-000000", 5, []string{"ffffff", "cccccc", "999999", "666666", "333333", "000000"}},
}
for _, td := range testdata {
svg := new(SVGfile)
svg.thresholds = make([]int, td.tcount+1) // plus 1 for the background
svg.setColours(td.colourString)
if !equalStringSlice(svg.colours, td.colours) {
t.Errorf("Wrong result for test %d: %s / %d. Wanted '%s' got '%s'\n", td.id, td.colourString, td.tcount, td.colours, svg.colours)
}
}
}

func TestTraceContour(t *testing.T) {
fmt.Println("TestTraceContour")
type testdataT struct {
Expand Down Expand Up @@ -269,10 +300,10 @@ func TestCreateSVG(t *testing.T) {
}
testdata := []testdataT{ // Compression is done for SVG contours
{"tests/test3.png", "tests/test3-hc-t128m15pA4LF2.svg", []int{128}, 15, 2.0, "A4L", false,
"<svg width=\"297mm\" height=\"210mm\" viewBox=\"0 0 297 210\" style=\"background-color:white\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" encoding=\"UTF-8\" >\n<!-- tests/test3-hc-t128m15pA4LF2.svg, created by hcontours version 0.1.1 -->\n<g stroke=\"black\" stroke-width=\"0.0455\" stroke-linecap=\"round\" stroke-linejoin=\"round\" fill=\"none\" transform=\"translate(60.5000,17.0000) scale(22.0000)\">\n<g inkscape:groupmode=\"layer\" inkscape:label=\"0 frame/background\" stroke=\"black\">\n<rect id=\"frame\" width=\"8.0909\" height=\"8.0909\" x=\"-0.0455\" y=\"-0.0455\" stroke-width=\"0.0909\" />\n</g>\n<g inkscape:groupmode=\"layer\" inkscape:label=\"128 contour\" stroke=\"black\">\n<polyline id=\"0\" points=\"1.00,0.50 1.50,0.00 \" />\n<polyline id=\"1\" points=\"2.50,0.00 3.00,0.50 3.00,1.50 1.50,3.00 0.50,3.00 0.00,2.50 \" />\n<polyline id=\"2\" points=\"0.00,1.50 1.00,0.50 \" />\n<polyline id=\"3\" points=\"4.00,0.50 4.50,0.00 \" />\n<polyline id=\"4\" points=\"0.00,4.50 0.50,4.00 2.50,4.00 4.00,2.50 4.00,0.50 \" />\n<polygon id=\"0\" points=\"5.00,4.50 5.50,4.00 6.00,4.50 6.00,5.50 5.50,6.00 4.50,6.00 4.00,5.50 5.00,4.50 \" />\n</g>\n<!-- 3 contours found at threshold 128, with length 1.00m -->\n<!-- Total contour length: 1.00m -->\n</g>\n</svg>\n",
"<svg width=\"297mm\" height=\"210mm\" viewBox=\"0 0 297 210\" style=\"background-color:white\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" encoding=\"UTF-8\" >\n<!-- tests/test3-hc-t128m15pA4LF2.svg, created by hcontours.test version 0.1.2 -->\n<g stroke=\"black\" stroke-width=\"0.0455\" stroke-linecap=\"round\" stroke-linejoin=\"round\" fill=\"none\" transform=\"translate(60.5000,17.0000) scale(22.0000)\">\n<g inkscape:groupmode=\"layer\" inkscape:label=\"0 background\" stroke=\"black\" >\n<rect id=\"frame\" width=\"8.0909\" height=\"8.0909\" x=\"-0.0455\" y=\"-0.0455\" stroke-width=\"0.0909\" />\n</g>\n<g inkscape:groupmode=\"layer\" inkscape:label=\"128 contour\" stroke=\"black\" >\n<polyline id=\"0\" points=\"1.00,0.50 1.50,0.00 \" />\n<polyline id=\"1\" points=\"2.50,0.00 3.00,0.50 3.00,1.50 1.50,3.00 0.50,3.00 0.00,2.50 \" />\n<polyline id=\"2\" points=\"0.00,1.50 1.00,0.50 \" />\n<polyline id=\"3\" points=\"4.00,0.50 4.50,0.00 \" />\n<polyline id=\"4\" points=\"0.00,4.50 0.50,4.00 2.50,4.00 4.00,2.50 4.00,0.50 \" />\n<polygon id=\"0\" points=\"5.00,4.50 5.50,4.00 6.00,4.50 6.00,5.50 5.50,6.00 4.50,6.00 4.00,5.50 5.00,4.50 \" />\n</g>\n<!-- 3 contours found at threshold 128, with length 1.00m -->\n<!-- Total contour length: 1.00m -->\n</g>\n</svg>\n",
},
{"tests/test4.png", "tests/test4-hc-t100,200m15pA4PC.svg", []int{100, 200}, 15, 0.0, "A4P", true,
"<svg width=\"210mm\" height=\"297mm\" viewBox=\"0 0 210 297\" style=\"background-color:white\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" encoding=\"UTF-8\" >\n<!-- tests/test4-hc-t100,200m15pA4PC.svg, created by hcontours version 0.1.1 -->\n<g stroke=\"black\" stroke-width=\"0.0333\" stroke-linecap=\"round\" stroke-linejoin=\"round\" fill=\"none\" transform=\"translate(15.0000,88.5000) scale(30.0000)\">\n<defs><clipPath id=\"clip1\" ><rect id=\"cliprect\" width=\"5.9667\" height=\"3.9667\" x=\"0.0167\" y=\"0.0167\" /></clipPath></defs>\n<g inkscape:groupmode=\"layer\" inkscape:label=\"100 contour\" stroke=\"black\">\n<path id=\"0\" clip-path=\"url(#clip1)\" d=\"M 1.11,0.50 L 1.50,-0.00 L 1.89,0.50 L 2.89,1.50 L 2.89,2.50 L 1.89,3.50 L 1.50,4.00 L 1.11,3.50 L 0.50,2.89 L -0.00,2.50 L -0.00,1.50 L 0.50,1.11 L 1.11,0.50 Z M 4.11,0.50 L 4.50,-0.00 L 5.50,-0.00 L 6.00,0.50 L 6.00,1.50 L 5.50,1.89 L 4.50,1.89 L 4.11,1.50 L 4.11,0.50 Z M 4.11,3.50 L 4.50,3.11 L 4.89,3.50 L 4.50,4.00 L 4.11,3.50 Z \" />\n</g>\n<g inkscape:groupmode=\"layer\" inkscape:label=\"200 contour\" stroke=\"black\">\n<path id=\"1\" clip-path=\"url(#clip1)\" d=\"M 0.72,0.50 L 1.50,-0.00 L 2.28,0.50 L 3.28,1.50 L 3.28,2.50 L 2.28,3.50 L 1.50,4.00 L 0.72,3.50 L 0.50,3.28 L -0.00,2.50 L -0.00,1.50 L 0.50,0.72 L 0.72,0.50 Z M 3.72,0.50 L 4.50,-0.00 L 5.50,-0.00 L 6.00,0.50 L 6.00,1.50 L 5.50,2.28 L 4.50,2.28 L 3.72,1.50 L 3.72,0.50 Z M 3.72,3.50 L 4.50,2.72 L 5.28,3.50 L 4.50,4.00 L 3.72,3.50 Z \" />\n</g>\n<!-- 3 contours found at threshold 100, with length 0.58m -->\n<!-- 3 contours found at threshold 200, with length 0.68m -->\n<!-- Total contour length: 1.26m -->\n</g>\n</svg>\n",
"<svg width=\"210mm\" height=\"297mm\" viewBox=\"0 0 210 297\" style=\"background-color:white\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" encoding=\"UTF-8\" >\n<!-- tests/test4-hc-t100,200m15pA4PC.svg, created by hcontours.test version 0.1.2 -->\n<g stroke=\"black\" stroke-width=\"0.0333\" stroke-linecap=\"round\" stroke-linejoin=\"round\" fill=\"none\" transform=\"translate(15.0000,88.5000) scale(30.0000)\">\n<defs><clipPath id=\"clip1\" ><rect id=\"cliprect\" width=\"5.9667\" height=\"3.9667\" x=\"0.0167\" y=\"0.0167\" /></clipPath></defs>\n<g inkscape:groupmode=\"layer\" inkscape:label=\"0 background\" stroke=\"black\" >\n</g>\n<g inkscape:groupmode=\"layer\" inkscape:label=\"200 contour\" stroke=\"black\" >\n<path id=\"0\" clip-path=\"url(#clip1)\" d=\"M 0.72,0.50 L 1.50,-0.00 L 2.28,0.50 L 3.28,1.50 L 3.28,2.50 L 2.28,3.50 L 1.50,4.00 L 0.72,3.50 L 0.50,3.28 L -0.00,2.50 L -0.00,1.50 L 0.50,0.72 L 0.72,0.50 Z M 3.72,0.50 L 4.50,-0.00 L 5.50,-0.00 L 6.00,0.50 L 6.00,1.50 L 5.50,2.28 L 4.50,2.28 L 3.72,1.50 L 3.72,0.50 Z M 3.72,3.50 L 4.50,2.72 L 5.28,3.50 L 4.50,4.00 L 3.72,3.50 Z \" />\n</g>\n<g inkscape:groupmode=\"layer\" inkscape:label=\"100 contour\" stroke=\"black\" >\n<path id=\"1\" clip-path=\"url(#clip1)\" d=\"M 1.11,0.50 L 1.50,-0.00 L 1.89,0.50 L 2.89,1.50 L 2.89,2.50 L 1.89,3.50 L 1.50,4.00 L 1.11,3.50 L 0.50,2.89 L -0.00,2.50 L -0.00,1.50 L 0.50,1.11 L 1.11,0.50 Z M 4.11,0.50 L 4.50,-0.00 L 5.50,-0.00 L 6.00,0.50 L 6.00,1.50 L 5.50,1.89 L 4.50,1.89 L 4.11,1.50 L 4.11,0.50 Z M 4.11,3.50 L 4.50,3.11 L 4.89,3.50 L 4.50,4.00 L 4.11,3.50 Z \" />\n</g>\n<!-- 3 contours found at threshold 100, with length 0.58m -->\n<!-- 3 contours found at threshold 200, with length 0.68m -->\n<!-- Total contour length: 1.26m -->\n</g>\n</svg>\n",
},
}
for _, td := range testdata {
Expand Down
35 changes: 29 additions & 6 deletions hcontours.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,18 @@ import (
_ "image/png"
"math"
"os"
"path"
"path/filepath"
"regexp"
"strconv"
"strings"

"github.com/spf13/pflag"
)

const hcVersion = "0.1.1"
var hcName = path.Base(os.Args[0])

const hcVersion = "0.1.2"

// Get the pixel value (0..255) at the given coordinates in the image
// Grey: Y = 0.299 R + 0.587 G + 0.114 B
Expand Down Expand Up @@ -226,7 +230,7 @@ func parsePaperSize(opts *OptsT) bool {
// something like 123x45
paperWidth, err := strconv.ParseFloat(dims[0], 64)
paperHeight, err := strconv.ParseFloat(dims[1], 64)
fmt.Printf("pps: pW=%v pH=%v err=%v\n", paperWidth, paperHeight, err)
//fmt.Printf("pps: pW=%v pH=%v err=%v\n", paperWidth, paperHeight, err)
if err != nil {
valid = false
} else {
Expand All @@ -253,6 +257,7 @@ func parseArgs(args []string) (OptsT, bool) {
pf.StringVarP(&opts.paper, "paper", "p", "A4L", "Paper size and orientation. A4L | A4P | A3L | A3P.")
pf.Float64VarP(&opts.linewidth, "linewidth", "l", 0.5, "Width of contour lines, in mm.")
pf.Float64VarP(&opts.framewidth, "framewidth", "f", 0.0, "Width of frame lines, if any, in mm.")
pf.StringVarP(&opts.colours, "colours", "C", "", "Colours used to fill the contour levels, e.g. 'ffff00,ff0000'. Implies --clip.")
pf.BoolVarP(&opts.image, "image", "i", false, "Use the original image as a background in the SVG image.")
pf.BoolVarP(&opts.clip, "clip", "c", false, "Clip borders of image, rather than breaking contours.")
pf.BoolVarP(&opts.debug, "debug", "d", false, "Add extra bits to the SVG -- intended for developer use only.")
Expand All @@ -274,6 +279,17 @@ func parseArgs(args []string) (OptsT, bool) {
opts.tcount = limitInt(opts.tcount, 1, 255)
opts.thresholds = evenThresholds(opts.tcount)
}
if pf.Changed("colours") {
// Case-insensitive, 6 hex-chars, comma, 6 hex-chars
validColourList := regexp.MustCompile(`(?i:^[0-9a-f]{6}(,[0-9a-f]{6})*$)`)
validColourRange := regexp.MustCompile(`(?i:^[0-9a-f]{6}-[0-9a-f]{6}$)`)
if !validColourList.MatchString(opts.colours) && !validColourRange.MatchString(opts.colours) {
fmt.Printf("Invalid colours '%s'\n", opts.colours)
ok = false
}
// implies --clip
opts.clip = true
}
opts.infile = pf.Arg(0)
ok = ok && parsePaperSize(&opts)
if ok {
Expand Down Expand Up @@ -305,7 +321,12 @@ func buildSVGfilename(opts OptsT) string {
} else {
tString = fmt.Sprintf("T%d", opts.tcount)
}
optString := fmt.Sprintf("-hc-%sm%gp%s%s%s%s", tString, opts.margin, opts.paper, frameString, imageString, clipString)
colourString := ""
if opts.colours != "" {
colourString = "C" + opts.colours
clipString = "" // don't need that as well
}
optString := fmt.Sprintf("-hc-%sm%gp%s%s%s%s%s", tString, opts.margin, opts.paper, frameString, imageString, clipString, colourString)
ext := filepath.Ext(opts.infile)
filename := strings.TrimSuffix(opts.infile, ext) + optString + ".svg"
return filename
Expand All @@ -324,8 +345,10 @@ func createSVG(opts OptsT) string {
scale := svgF.openStart(svgFilename, opts)
contourText := make([]string, len(opts.thresholds))
totalLen := 0.0
for i, threshold := range opts.thresholds {
svgF.layer(threshold, "contour")
for i := len(opts.thresholds) - 1; i >= 0; i-- {
threshold := opts.thresholds[i]
//fmt.Printf("cSVG: i=%d threshold=%d starting layer %d\n", i, threshold, i+1)
svgF.layer(i+1 /* threshold */, "contour", i)
contours, thresholdLen := contourFinder(img, opts.width, opts.height, threshold, opts.clip, svgF)
contourText[i] = fmt.Sprintf("%d contours found at threshold %d, with length %.2fm", len(contours), threshold, thresholdLen*scale/1000)
totalLen += thresholdLen
Expand All @@ -347,7 +370,7 @@ func main() {
if !ok {
os.Exit(1)
}
fmt.Printf("hcontours: processing '%s'\n", opts.infile)
fmt.Printf("%s: processing '%s'\n", hcName, opts.infile)
//fmt.Printf("\t%+v\n", opts)
//fmt.Printf("options: %#v\n", opts)
_ = createSVG(opts)
Expand Down
2 changes: 2 additions & 0 deletions make.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,7 @@
go build
./hcontours examples/Heightmap.png -t 64,128,192 --paper 200x200 --margin 0 --framewidth 1.0
./hcontours examples/beach.png -t 32,64,96,128,160,192,224 --paper A4L --image --linewidth 0.3
./hcontours examples/beach.png --colours ff0000,777700,00ff00,00ffff,0000ff,770077 -T5

inkscape "examples/beach-hc-t32,64,96,128,160,192,224m15pA4LI.svg" -o "examples/beach-hc-t32,64,96,128,160,192,224m15pA4LI.png"
inkscape "examples/beach-hc-T5m15pA4LCff0000,777700,00ff00,00ffff,0000ff,770077.svg" -o "examples/beach-hc-T5m15pA4LCff0000,777700,00ff00,00ffff,0000ff,770077.png"
Loading

0 comments on commit ab2fdec

Please sign in to comment.