Better logging and output

This commit is contained in:
Justin Hawkins 2024-06-02 19:50:58 +09:30
parent 08d75df3a4
commit 7e357b8b2a

156
main.go
View File

@ -7,16 +7,42 @@ import (
"image/jpeg" "image/jpeg"
"image/png" "image/png"
"io" "io"
"log"
"os" "os"
"path/filepath" "path/filepath"
"sync"
"time"
"github.com/disintegration/imaging" "github.com/disintegration/imaging"
) )
const currentVersion = "0.01" const currentVersion = "0.01"
func main() { type resultPrinter struct {
batchTotal int
count int
lastFilename string
ch chan string
}
func (rp *resultPrinter) Reset(batchSize int) {
rp.batchTotal = batchSize
rp.count = 0
rp.lastFilename = ""
fmt.Print("\033[s")
}
func (rp *resultPrinter) Run() {
for last := range rp.ch {
rp.count++
rp.lastFilename = last
fmt.Print("\033[u\033[K") // restore the cursor position and clear the line
fmt.Printf("processing %5d/%5d - %s", rp.count, rp.batchTotal, rp.lastFilename)
}
}
func main() {
t0 := time.Now()
filenamePtr := flag.String("filename", "", "filename to open") filenamePtr := flag.String("filename", "", "filename to open")
tileSizePtr := flag.Int("tile-size", 256, "tile size, in pixels") tileSizePtr := flag.Int("tile-size", 256, "tile size, in pixels")
concurrencyPtr := flag.Int("concurrency", 5, "how many tiles to generate concurrently (threads)") concurrencyPtr := flag.Int("concurrency", 5, "how many tiles to generate concurrently (threads)")
@ -36,7 +62,7 @@ func main() {
return return
} }
fmt.Println("opening file:", *filenamePtr) log.Println("opening file:", *filenamePtr)
src, err := imaging.Open(*filenamePtr) src, err := imaging.Open(*filenamePtr)
if err != nil { if err != nil {
fmt.Println("Error: Could not open file:", err) fmt.Println("Error: Could not open file:", err)
@ -61,51 +87,88 @@ func main() {
} }
z := max_zoom z := max_zoom
fmt.Println("maximum zoom level is", max_zoom) log.Println("maximum zoom level is", max_zoom)
concurrency := *concurrencyPtr concurrency := *concurrencyPtr
sem := make(chan bool, concurrency)
fmt.Println("starting tiling with concurrency of", concurrency) log.Println("starting tiling with concurrency of", concurrency)
results := make(chan string)
rp := resultPrinter{
batchTotal: 0,
count: 0,
lastFilename: "",
ch: results,
}
// start the tileWorkers
jobs := make(chan tileJob)
for i := 0; i < concurrency; i++ {
go tileWorker(jobs, results)
}
go func() {
rp.Run()
}()
// outer loop for zoom // outer loop for zoom
for { for {
if z == max_zoom { if z == max_zoom {
// do nothing // do nothing
} else { } else {
// halve image size // halve image size
log.Print("resizing for next zoom level")
src = imaging.Resize(src, size.X/2, 0, imaging.NearestNeighbor) src = imaging.Resize(src, size.X/2, 0, imaging.NearestNeighbor)
// recalculate size // recalculate size
size = src.Bounds().Max size = src.Bounds().Max
} }
fmt.Print(fmt.Sprintf("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)
for y := 0; y < (size.Y / tile_size_y); y++ { yTiles := (size.Y / tile_size_y)
for x := 0; x < (size.X / tile_size_x); x++ { xTiles := (size.X / tile_size_x)
sem <- true tilesToRender := xTiles * yTiles
go tile(*baseName, *pathTemplate, *outFormat, src, z, x, y, tile_size_x, tile_size_y, sem)
rp.Reset(tilesToRender)
wg := sync.WaitGroup{}
wg.Add(tilesToRender)
for y := 0; y < yTiles; y++ {
for x := 0; x < xTiles; x++ {
jobs <- tileJob{
baseName: *baseName,
pathTemplate: *pathTemplate,
format: *outFormat,
src: src,
zoom: z,
x: x,
y: y,
tileSizeX: tile_size_x,
tileSizeY: tile_size_y,
wg: &wg,
}
} }
} }
wg.Wait() // wait for all tiles to be generated for this zoom level
z-- z--
if z < 0 { if z < 0 {
break break
} }
}
// drain at the end of each zoom level // let the last progress be printed out
// since we are about to modify the source image in memory // yes I know this is ugly :-)
for i := 0; i < cap(sem); i++ { time.Sleep(time.Millisecond * 10)
sem <- true fmt.Print("\033[u\033[K") // restore the cursor position and clear the line
} }
close(results)
log.Printf("done in %.2f", time.Since(t0).Seconds())
fmt.Println("done")
} }
func createPathAndFile(fn string) (io.WriteCloser, error) { func 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 {
@ -116,25 +179,40 @@ func createPathAndFile(fn string) (io.WriteCloser, error) {
return writer, err return writer, err
} }
func tile(basename string, pathTemplate string, format string, src image.Image, z, x, y int, tile_size_x, tile_size_y int, sem chan bool) { type tileJob struct {
defer func() { <-sem }() baseName string
output_filename := fmt.Sprintf(pathTemplate, basename, z, x, y, format) pathTemplate string
cropped := imaging.Crop(src, image.Rect(tile_size_x*x, tile_size_y*y, tile_size_x*x+tile_size_x, tile_size_y*y+tile_size_y)) format string
src image.Image
fmt.Printf("writing to %s\n", output_filename) zoom int
writer, err := createPathAndFile(output_filename) x, y int
if err != nil { tileSizeX int
panic(err) tileSizeY int
} wg *sync.WaitGroup
if format == "png" { }
err = png.Encode(writer, cropped)
} else if format == "jpg" { func tileWorker(jobs <-chan tileJob, results chan<- string) {
err = jpeg.Encode(writer, cropped, &jpeg.Options{ for j := range jobs {
Quality: 40, output_filename := fmt.Sprintf(j.pathTemplate, j.baseName, j.zoom, j.x, j.y, j.format)
}) 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))
}
if err != nil { // log.Printf("writing to %s", output_filename)
fmt.Println(err) writer, err := createPathAndFile(output_filename)
} if err != nil {
writer.Close() panic(err)
}
if j.format == "png" {
err = png.Encode(writer, cropped)
} else if j.format == "jpg" {
err = jpeg.Encode(writer, cropped, &jpeg.Options{
Quality: 40,
})
}
if err != nil {
panic(err)
}
writer.Close()
results <- output_filename
j.wg.Done()
}
} }