107 lines
2.5 KiB
Go
107 lines
2.5 KiB
Go
|
|
package maze
|
||
|
|
|
||
|
|
import (
|
||
|
|
"fmt"
|
||
|
|
"image"
|
||
|
|
"image/color"
|
||
|
|
"image/png"
|
||
|
|
"os"
|
||
|
|
)
|
||
|
|
|
||
|
|
var (
|
||
|
|
// DefaultWallColor is used for wall cells.
|
||
|
|
DefaultWallColor = color.RGBA{R: 0, G: 0, B: 0, A: 255}
|
||
|
|
// DefaultPathColor is used for open maze cells.
|
||
|
|
DefaultPathColor = color.RGBA{R: 255, G: 255, B: 255, A: 255}
|
||
|
|
// DefaultSolutionColor is used to highlight solution cells.
|
||
|
|
DefaultSolutionColor = color.RGBA{R: 0, G: 180, B: 0, A: 255}
|
||
|
|
)
|
||
|
|
|
||
|
|
// RenderOptions controls how a maze image is rendered.
|
||
|
|
type RenderOptions struct {
|
||
|
|
Scale int
|
||
|
|
WallColor color.RGBA
|
||
|
|
PathColor color.RGBA
|
||
|
|
SolutionColor color.RGBA
|
||
|
|
HighlightPath bool
|
||
|
|
}
|
||
|
|
|
||
|
|
// DefaultRenderOptions returns baseline rendering options.
|
||
|
|
func DefaultRenderOptions() RenderOptions {
|
||
|
|
return RenderOptions{
|
||
|
|
Scale: 5,
|
||
|
|
WallColor: DefaultWallColor,
|
||
|
|
PathColor: DefaultPathColor,
|
||
|
|
SolutionColor: DefaultSolutionColor,
|
||
|
|
HighlightPath: false,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// RenderImage converts a maze grid into an RGBA image.
|
||
|
|
//
|
||
|
|
// If options.HighlightPath is true, Solve is used and solution cells are painted
|
||
|
|
// with options.SolutionColor.
|
||
|
|
func RenderImage(grid Grid, options RenderOptions) (*image.RGBA, error) {
|
||
|
|
width, height, err := validateGrid(grid)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
if options.Scale <= 0 {
|
||
|
|
return nil, fmt.Errorf("scale must be greater than zero")
|
||
|
|
}
|
||
|
|
|
||
|
|
img := image.NewRGBA(image.Rect(0, 0, width*options.Scale, height*options.Scale))
|
||
|
|
|
||
|
|
var solutionPath [][]bool
|
||
|
|
if options.HighlightPath {
|
||
|
|
solutionPath, err = Solve(grid)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
for cellY := 0; cellY < height; cellY++ {
|
||
|
|
startY := cellY * options.Scale
|
||
|
|
for cellX := 0; cellX < width; cellX++ {
|
||
|
|
startX := cellX * options.Scale
|
||
|
|
|
||
|
|
pixel := options.WallColor
|
||
|
|
if grid[cellY][cellX] == 1 {
|
||
|
|
pixel = options.PathColor
|
||
|
|
if options.HighlightPath && solutionPath[cellY][cellX] {
|
||
|
|
pixel = options.SolutionColor
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
for dy := 0; dy < options.Scale; dy++ {
|
||
|
|
row := img.Pix[(startY+dy)*img.Stride:]
|
||
|
|
for dx := 0; dx < options.Scale; dx++ {
|
||
|
|
offset := (startX + dx) * 4
|
||
|
|
row[offset+0] = pixel.R
|
||
|
|
row[offset+1] = pixel.G
|
||
|
|
row[offset+2] = pixel.B
|
||
|
|
row[offset+3] = pixel.A
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return img, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// SavePNG renders a maze image and writes it to a PNG file.
|
||
|
|
func SavePNG(grid Grid, outputPath string, options RenderOptions) error {
|
||
|
|
img, err := RenderImage(grid, options)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
f, err := os.Create(outputPath)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
defer f.Close()
|
||
|
|
|
||
|
|
return png.Encode(f, img)
|
||
|
|
}
|