Fix remaining (?) edge case bugs, add tests
This commit is contained in:
parent
7e357b8b2a
commit
cdae5c7c0f
11
go.mod
11
go.mod
@ -4,4 +4,13 @@ go 1.22.1
|
|||||||
|
|
||||||
require github.com/disintegration/imaging v1.6.2
|
require github.com/disintegration/imaging v1.6.2
|
||||||
|
|
||||||
require golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 // indirect
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/stretchr/testify v1.9.0
|
||||||
|
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 // indirect
|
||||||
|
)
|
||||||
|
9
go.sum
9
go.sum
@ -1,5 +1,14 @@
|
|||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U=
|
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U=
|
||||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
63
main.go
63
main.go
@ -8,6 +8,7 @@ import (
|
|||||||
"image/png"
|
"image/png"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
@ -41,6 +42,8 @@ func (rp *resultPrinter) Run() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const defaultPathTemplate = "%s-%d-%d-%d.%s"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
t0 := time.Now()
|
t0 := time.Now()
|
||||||
filenamePtr := flag.String("filename", "", "filename to open")
|
filenamePtr := flag.String("filename", "", "filename to open")
|
||||||
@ -48,7 +51,7 @@ func main() {
|
|||||||
concurrencyPtr := flag.Int("concurrency", 5, "how many tiles to generate concurrently (threads)")
|
concurrencyPtr := flag.Int("concurrency", 5, "how many tiles to generate concurrently (threads)")
|
||||||
baseName := flag.String("basename", "tile", "base of the output files")
|
baseName := flag.String("basename", "tile", "base of the output files")
|
||||||
outFormat := flag.String("format", "png", "output format (jpg/png)")
|
outFormat := flag.String("format", "png", "output format (jpg/png)")
|
||||||
pathTemplate := flag.String("path-template", "%s-%d-%d-%d.%s", "template for output files - base, zoom, x, y, format")
|
pathTemplate := flag.String("path-template", defaultPathTemplate, "template for output files - base, zoom, x, y, format")
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
@ -63,16 +66,27 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.Println("opening file:", *filenamePtr)
|
log.Println("opening file:", *filenamePtr)
|
||||||
src, err := imaging.Open(*filenamePtr)
|
src, err := os.Open(*filenamePtr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Error: Could not open file:", err)
|
fmt.Println("Error: Could not open file:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
processImage(src, *baseName, *pathTemplate, *outFormat, *tileSizePtr, *concurrencyPtr, diskOutput{})
|
||||||
|
log.Printf("done in %.2f", time.Since(t0).Seconds())
|
||||||
|
}
|
||||||
|
|
||||||
|
func processImage(input io.Reader, basename string, pathTemplate string, format string, tileSize int, concurrency int, output outputter) {
|
||||||
|
|
||||||
|
src, err := imaging.Decode(input)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
size := src.Bounds().Max
|
size := src.Bounds().Max
|
||||||
|
|
||||||
tile_size_x := *tileSizePtr
|
tile_size_x := tileSize
|
||||||
tile_size_y := *tileSizePtr
|
tile_size_y := tileSize
|
||||||
|
|
||||||
// work out maximum zoom
|
// work out maximum zoom
|
||||||
var max_zoom int
|
var max_zoom int
|
||||||
@ -88,9 +102,6 @@ func main() {
|
|||||||
|
|
||||||
z := max_zoom
|
z := max_zoom
|
||||||
log.Println("maximum zoom level is", max_zoom)
|
log.Println("maximum zoom level is", max_zoom)
|
||||||
|
|
||||||
concurrency := *concurrencyPtr
|
|
||||||
|
|
||||||
log.Println("starting tiling with concurrency of", concurrency)
|
log.Println("starting tiling with concurrency of", concurrency)
|
||||||
|
|
||||||
results := make(chan string)
|
results := make(chan string)
|
||||||
@ -126,8 +137,16 @@ func main() {
|
|||||||
|
|
||||||
log.Printf("zoom level: %d (%d x %d)\n", z, size.X, size.Y)
|
log.Printf("zoom level: %d (%d x %d)\n", z, size.X, size.Y)
|
||||||
|
|
||||||
yTiles := (size.Y / tile_size_y)
|
// size 257 => 2
|
||||||
xTiles := (size.X / tile_size_x)
|
// size 256 => 1
|
||||||
|
// size 255 => 1
|
||||||
|
yTiles := int(math.Ceil(float64(size.Y) / float64(tile_size_y)))
|
||||||
|
xTiles := int(math.Ceil(float64(size.X) / float64(tile_size_x)))
|
||||||
|
|
||||||
|
if z == 0 {
|
||||||
|
xTiles = 1
|
||||||
|
yTiles = 1
|
||||||
|
}
|
||||||
tilesToRender := xTiles * yTiles
|
tilesToRender := xTiles * yTiles
|
||||||
|
|
||||||
rp.Reset(tilesToRender)
|
rp.Reset(tilesToRender)
|
||||||
@ -135,12 +154,14 @@ func main() {
|
|||||||
wg := sync.WaitGroup{}
|
wg := sync.WaitGroup{}
|
||||||
wg.Add(tilesToRender)
|
wg.Add(tilesToRender)
|
||||||
|
|
||||||
|
log.Printf("rendering %d tiles", tilesToRender)
|
||||||
|
|
||||||
for y := 0; y < yTiles; y++ {
|
for y := 0; y < yTiles; y++ {
|
||||||
for x := 0; x < xTiles; x++ {
|
for x := 0; x < xTiles; x++ {
|
||||||
jobs <- tileJob{
|
jobs <- tileJob{
|
||||||
baseName: *baseName,
|
baseName: basename,
|
||||||
pathTemplate: *pathTemplate,
|
pathTemplate: pathTemplate,
|
||||||
format: *outFormat,
|
format: format,
|
||||||
src: src,
|
src: src,
|
||||||
zoom: z,
|
zoom: z,
|
||||||
x: x,
|
x: x,
|
||||||
@ -148,6 +169,7 @@ func main() {
|
|||||||
tileSizeX: tile_size_x,
|
tileSizeX: tile_size_x,
|
||||||
tileSizeY: tile_size_y,
|
tileSizeY: tile_size_y,
|
||||||
wg: &wg,
|
wg: &wg,
|
||||||
|
output: output,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,11 +186,17 @@ func main() {
|
|||||||
fmt.Print("\033[u\033[K") // restore the cursor position and clear the line
|
fmt.Print("\033[u\033[K") // restore the cursor position and clear the line
|
||||||
}
|
}
|
||||||
close(results)
|
close(results)
|
||||||
log.Printf("done in %.2f", time.Since(t0).Seconds())
|
log.Printf("all tiles complete")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func createPathAndFile(fn string) (io.WriteCloser, error) {
|
type outputter interface {
|
||||||
|
CreatePathAndFile(fn string) (io.WriteCloser, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type diskOutput struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (do diskOutput) CreatePathAndFile(fn string) (io.WriteCloser, error) {
|
||||||
dir, _ := filepath.Split(fn)
|
dir, _ := filepath.Split(fn)
|
||||||
err := os.MkdirAll(dir, 0777)
|
err := os.MkdirAll(dir, 0777)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -188,6 +216,7 @@ type tileJob struct {
|
|||||||
x, y int
|
x, y int
|
||||||
tileSizeX int
|
tileSizeX int
|
||||||
tileSizeY int
|
tileSizeY int
|
||||||
|
output outputter
|
||||||
wg *sync.WaitGroup
|
wg *sync.WaitGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,7 +226,7 @@ func tileWorker(jobs <-chan tileJob, results chan<- string) {
|
|||||||
cropped := imaging.Crop(j.src, image.Rect(j.tileSizeX*j.x, j.tileSizeY*j.y, j.tileSizeX*j.x+j.tileSizeX, j.tileSizeY*j.y+j.tileSizeY))
|
cropped := imaging.Crop(j.src, image.Rect(j.tileSizeX*j.x, j.tileSizeY*j.y, j.tileSizeX*j.x+j.tileSizeX, j.tileSizeY*j.y+j.tileSizeY))
|
||||||
|
|
||||||
// log.Printf("writing to %s", output_filename)
|
// log.Printf("writing to %s", output_filename)
|
||||||
writer, err := createPathAndFile(output_filename)
|
writer, err := j.output.CreatePathAndFile(output_filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@ -207,6 +236,8 @@ func tileWorker(jobs <-chan tileJob, results chan<- string) {
|
|||||||
err = jpeg.Encode(writer, cropped, &jpeg.Options{
|
err = jpeg.Encode(writer, cropped, &jpeg.Options{
|
||||||
Quality: 40,
|
Quality: 40,
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
panic("bad format")
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
92
slice_test.go
Normal file
92
slice_test.go
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testWriteCloser struct {
|
||||||
|
written int
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (twc *testWriteCloser) Close() error {
|
||||||
|
twc.closed = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (twc *testWriteCloser) Write(b []byte) (int, error) {
|
||||||
|
twc.written += len(b)
|
||||||
|
return len(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type testOutputter struct {
|
||||||
|
closers map[string]*testWriteCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
func (to *testOutputter) CreatePathAndFile(fn string) (io.WriteCloser, error) {
|
||||||
|
to.closers[fn] = &testWriteCloser{}
|
||||||
|
return to.closers[fn], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (to *testOutputter) Dump() string {
|
||||||
|
out := ""
|
||||||
|
for k, v := range to.closers {
|
||||||
|
out += fmt.Sprintf("%20s: %6d/%t\n", k, v.written, v.closed)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTestOutputter() *testOutputter {
|
||||||
|
to := testOutputter{}
|
||||||
|
to.closers = make(map[string]*testWriteCloser)
|
||||||
|
return &to
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestXxx(t *testing.T) {
|
||||||
|
|
||||||
|
// testdata/gold.jpg: JPEG image data, JFIF standard 1.01, 794x447
|
||||||
|
f, err := os.Open("testdata/gold.jpg")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// with a tile size larger than the image itself, we should
|
||||||
|
// get a single image at zoom level 0
|
||||||
|
testOutputter := NewTestOutputter()
|
||||||
|
processImage(f, "indy", defaultPathTemplate, "jpg", 1000, 1, testOutputter)
|
||||||
|
t.Log(testOutputter.Dump())
|
||||||
|
assert.Equal(t, 1, len(testOutputter.closers))
|
||||||
|
assert.Equal(t, 28082, testOutputter.closers["indy-0-0-0.jpg"].written)
|
||||||
|
|
||||||
|
f, _ = os.Open("testdata/gold.jpg")
|
||||||
|
|
||||||
|
// with a tilesize smaller than *one* of the dimensions, 3 tiles
|
||||||
|
// 1@zoom 0, 2@zoom 1
|
||||||
|
testOutputter = NewTestOutputter()
|
||||||
|
processImage(f, "indy", defaultPathTemplate, "jpg", 500, 1, testOutputter)
|
||||||
|
t.Log(testOutputter.Dump())
|
||||||
|
assert.Equal(t, 3, len(testOutputter.closers))
|
||||||
|
assert.Equal(t, 10304, testOutputter.closers["indy-0-0-0.jpg"].written)
|
||||||
|
assert.Equal(t, 18014, testOutputter.closers["indy-1-0-0.jpg"].written)
|
||||||
|
assert.Equal(t, 10749, testOutputter.closers["indy-1-1-0.jpg"].written)
|
||||||
|
|
||||||
|
f, _ = os.Open("testdata/gold.jpg")
|
||||||
|
|
||||||
|
// with a tilesize smaller than *both* of the dimensions, 5 tiles
|
||||||
|
// zoom 0, zoom 1 x 4
|
||||||
|
testOutputter = NewTestOutputter()
|
||||||
|
processImage(f, "indy", defaultPathTemplate, "jpg", 400, 1, testOutputter)
|
||||||
|
t.Log(testOutputter.Dump())
|
||||||
|
assert.Equal(t, 5, len(testOutputter.closers))
|
||||||
|
assert.Equal(t, 10304, testOutputter.closers["indy-0-0-0.jpg"].written)
|
||||||
|
assert.Equal(t, 12607, testOutputter.closers["indy-1-0-0.jpg"].written)
|
||||||
|
assert.Equal(t, 12050, testOutputter.closers["indy-1-1-0.jpg"].written)
|
||||||
|
assert.Equal(t, 2722, testOutputter.closers["indy-1-0-1.jpg"].written)
|
||||||
|
assert.Equal(t, 2477, testOutputter.closers["indy-1-1-1.jpg"].written)
|
||||||
|
|
||||||
|
}
|
BIN
testdata/gold.jpg
vendored
Normal file
BIN
testdata/gold.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 65 KiB |
Loading…
x
Reference in New Issue
Block a user