141 lines
3.3 KiB
Go
141 lines
3.3 KiB
Go
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"image"
|
|
"image/jpeg"
|
|
"image/png"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/disintegration/imaging"
|
|
)
|
|
|
|
const currentVersion = "0.01"
|
|
|
|
func main() {
|
|
|
|
filenamePtr := flag.String("filename", "", "filename to open")
|
|
tileSizePtr := flag.Int("tile-size", 256, "tile size, in pixels")
|
|
concurrencyPtr := flag.Int("concurrency", 5, "how many tiles to generate concurrently (threads)")
|
|
baseName := flag.String("basename", "tile", "base of the output files")
|
|
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")
|
|
|
|
flag.Parse()
|
|
|
|
if *filenamePtr == "" {
|
|
fmt.Println("Error: You must specify a filename with --filename")
|
|
return
|
|
}
|
|
|
|
if *outFormat != "jpg" && *outFormat != "png" {
|
|
fmt.Println("Error: -format must be jpg or png")
|
|
return
|
|
}
|
|
|
|
fmt.Println("opening file:", *filenamePtr)
|
|
src, err := imaging.Open(*filenamePtr)
|
|
if err != nil {
|
|
fmt.Println("Error: Could not open file:", err)
|
|
return
|
|
}
|
|
|
|
size := src.Bounds().Max
|
|
|
|
tile_size_x := *tileSizePtr
|
|
tile_size_y := *tileSizePtr
|
|
|
|
// work out maximum zoom
|
|
var max_zoom int
|
|
zoom_test_size_x := size.X
|
|
zoom_test_size_y := size.Y
|
|
for max_zoom = 0; ; max_zoom++ {
|
|
if zoom_test_size_x <= tile_size_x && zoom_test_size_y <= tile_size_y {
|
|
break
|
|
}
|
|
zoom_test_size_x = zoom_test_size_x >> 1
|
|
zoom_test_size_y = zoom_test_size_y >> 1
|
|
}
|
|
|
|
z := max_zoom
|
|
fmt.Println("maximum zoom level is", max_zoom)
|
|
|
|
concurrency := *concurrencyPtr
|
|
sem := make(chan bool, concurrency)
|
|
|
|
fmt.Println("starting tiling with concurrency of", concurrency)
|
|
|
|
// outer loop for zoom
|
|
for {
|
|
if z == max_zoom {
|
|
// do nothing
|
|
} else {
|
|
// halve image size
|
|
src = imaging.Resize(src, size.X/2, 0, imaging.NearestNeighbor)
|
|
// recalculate size
|
|
size = src.Bounds().Max
|
|
}
|
|
|
|
fmt.Print(fmt.Sprintf("zoom level: %d (%d x %d)\n", z, size.X, size.Y))
|
|
|
|
for y := 0; y < (size.Y / tile_size_y); y++ {
|
|
for x := 0; x < (size.X / tile_size_x); x++ {
|
|
sem <- true
|
|
go tile(*baseName, *pathTemplate, *outFormat, src, z, x, y, tile_size_x, tile_size_y, sem)
|
|
}
|
|
|
|
}
|
|
|
|
z--
|
|
if z < 0 {
|
|
break
|
|
}
|
|
}
|
|
|
|
// drain at the end of each zoom level
|
|
// since we are about to modify the source image in memory
|
|
for i := 0; i < cap(sem); i++ {
|
|
sem <- true
|
|
}
|
|
|
|
fmt.Println("done")
|
|
}
|
|
|
|
func createPathAndFile(fn string) (io.WriteCloser, error) {
|
|
|
|
dir, _ := filepath.Split(fn)
|
|
err := os.MkdirAll(dir, 0777)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
writer, err := os.Create(fn)
|
|
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) {
|
|
defer func() { <-sem }()
|
|
output_filename := fmt.Sprintf(pathTemplate, basename, z, x, y, format)
|
|
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))
|
|
|
|
fmt.Printf("writing to %s\n", output_filename)
|
|
writer, err := createPathAndFile(output_filename)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
if format == "png" {
|
|
err = png.Encode(writer, cropped)
|
|
} else if format == "jpg" {
|
|
err = jpeg.Encode(writer, cropped, &jpeg.Options{
|
|
Quality: 40,
|
|
})
|
|
}
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
writer.Close()
|
|
}
|