From 0044075cbf64f67b77c522af11f130a23817217c Mon Sep 17 00:00:00 2001 From: David Vogel Date: Mon, 8 Aug 2022 23:05:58 +0200 Subject: [PATCH] Draw entities and their component's bounding boxes --- .vscode/settings.json | 5 + bin/stitch/README.md | 8 +- bin/stitch/entity.go | 236 +++++++++++++++++++++++++++++++ bin/stitch/imagetile.go | 43 ++++-- bin/stitch/imagetiles.go | 3 +- bin/stitch/medianBlendedImage.go | 2 + bin/stitch/stitch.go | 39 ++++- go.mod | 15 +- go.sum | 93 ++++++++++++ 9 files changed, 424 insertions(+), 20 deletions(-) create mode 100644 bin/stitch/entity.go diff --git a/.vscode/settings.json b/.vscode/settings.json index 6fcdc8c..ee6784f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,11 +1,13 @@ { "cSpell.words": [ + "aabb", "backbuffer", "basicfont", "cheggaaa", "dofile", "Downscales", "downscaling", + "DPMM", "executables", "Fullscreen", "goarch", @@ -16,6 +18,7 @@ "kbinani", "Lanczos", "ldflags", + "linearize", "lowram", "manifoldco", "mapcap", @@ -23,10 +26,12 @@ "noita", "prerender", "promptui", + "rasterizer", "savegames", "schollz", "svenstaro", "tcnksm", + "tdewolff", "Vogel", "xmax", "xmin", diff --git a/bin/stitch/README.md b/bin/stitch/README.md index 5836f12..d526fc3 100644 --- a/bin/stitch/README.md +++ b/bin/stitch/README.md @@ -29,11 +29,13 @@ example list of files: - Either run the program and follow the interactive prompt. - Or run the program with parameters: - `divide int` - A downscaling factor. 2 will produce an image with half the side lengths. (default 1) + A downscaling factor. 2 will produce an image with half the side lengths. Defaults to 1. - `input string` - The source path of the image tiles to be stitched. (default "..\\..\\output") + The source path of the image tiles to be stitched. Defaults to "./..//..//output") + - `entities` + The source path of the `entities.json` file. This contains Noita specific entity data. Defaults to "./../../output/entities.json". - `output string` - The path and filename of the resulting stitched image. (default "output.png") + The path and filename of the resulting stitched image. Defaults to "output.png". - `xmax int` Right bound of the output rectangle. This coordinate is not included in the output. - `xmin int` diff --git a/bin/stitch/entity.go b/bin/stitch/entity.go new file mode 100644 index 0000000..fe137de --- /dev/null +++ b/bin/stitch/entity.go @@ -0,0 +1,236 @@ +// Copyright (c) 2022 David Vogel +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +package main + +import ( + "encoding/json" + "image/color" + "log" + "os" + + "github.com/tdewolff/canvas" +) + +var entityDisplayFontFamily = canvas.NewFontFamily("times") +var entityDisplayFontFace *canvas.FontFace + +var entityDisplayAreaDamageStyle = canvas.Style{ + FillColor: color.RGBA{100, 0, 0, 100}, + StrokeColor: canvas.Transparent, + StrokeWidth: 1.0, + StrokeCapper: canvas.ButtCap, + StrokeJoiner: canvas.MiterJoin, + DashOffset: 0.0, + Dashes: []float64{}, + FillRule: canvas.NonZero, +} + +var entityDisplayMaterialAreaCheckerStyle = canvas.Style{ + FillColor: color.RGBA{0, 0, 127, 127}, + StrokeColor: canvas.Transparent, + StrokeWidth: 1.0, + StrokeCapper: canvas.ButtCap, + StrokeJoiner: canvas.MiterJoin, + DashOffset: 0.0, + Dashes: []float64{}, + FillRule: canvas.NonZero, +} + +var entityDisplayTeleportStyle = canvas.Style{ + FillColor: color.RGBA{0, 127, 0, 127}, + StrokeColor: canvas.Transparent, + StrokeWidth: 1.0, + StrokeCapper: canvas.ButtCap, + StrokeJoiner: canvas.MiterJoin, + DashOffset: 0.0, + Dashes: []float64{}, + FillRule: canvas.NonZero, +} + +var entityDisplayHitBoxStyle = canvas.Style{ + FillColor: color.RGBA{64, 64, 0, 64}, + StrokeColor: color.RGBA{0, 0, 0, 64}, + StrokeWidth: 1.0, + StrokeCapper: canvas.ButtCap, + StrokeJoiner: canvas.MiterJoin, + DashOffset: 0.0, + Dashes: []float64{}, + FillRule: canvas.NonZero, +} + +var entityDisplayCollisionTriggerStyle = canvas.Style{ + FillColor: color.RGBA{0, 64, 64, 64}, + StrokeColor: color.RGBA{0, 0, 0, 64}, + StrokeWidth: 1.0, + StrokeCapper: canvas.ButtCap, + StrokeJoiner: canvas.MiterJoin, + DashOffset: 0.0, + Dashes: []float64{}, + FillRule: canvas.NonZero, +} + +func init() { + fontName := "NimbusRoman-Regular" + + if err := entityDisplayFontFamily.LoadLocalFont(fontName, canvas.FontRegular); err != nil { + log.Printf("Couldn't load font %q: %v", fontName, err) + } + + entityDisplayFontFace = entityDisplayFontFamily.Face(48.0, canvas.White, canvas.FontRegular, canvas.FontNormal) +} + +type Entity struct { + Filename string `json:"filename"` + Transform EntityTransform `json:"transform"` + Children []Entity `json:"children"` + Components []Component `json:"components"` + Name string `json:"name"` + Tags []string `json:"tags"` +} + +type EntityTransform struct { + X float32 `json:"x"` + Y float32 `json:"y"` + ScaleX float32 `json:"scaleX"` + ScaleY float32 `json:"scaleY"` + Rotation float32 `json:"rotation"` +} + +type Component struct { + TypeName string `json:"typeName"` + Members map[string]any `json:"members"` +} + +func loadEntities(path string) ([]Entity, error) { + file, err := os.Open(path) + if err != nil { + return nil, err + } + + var result []Entity + + jsonDec := json.NewDecoder(file) + if err := jsonDec.Decode(&result); err != nil { + return nil, err + } + + return result, nil +} + +func (e Entity) Draw(c *canvas.Context) { + x, y := float64(e.Transform.X), float64(e.Transform.Y) + + for _, component := range e.Components { + switch component.TypeName { + case "AreaDamageComponent": // Area damage like in cursed rock. + var aabbMinX, aabbMinY, aabbMaxX, aabbMaxY float64 + if member, ok := component.Members["aabb_min"]; ok { + if aabbMin, ok := member.([]any); ok && len(aabbMin) == 2 { + aabbMinX, _ = aabbMin[0].(float64) + aabbMinY, _ = aabbMin[1].(float64) + } + } + if member, ok := component.Members["aabb_max"]; ok { + if aabbMax, ok := member.([]any); ok && len(aabbMax) == 2 { + aabbMaxX, _ = aabbMax[0].(float64) + aabbMaxY, _ = aabbMax[1].(float64) + } + } + if aabbMinX < aabbMaxX && aabbMinY < aabbMaxY { + c.Style = entityDisplayAreaDamageStyle + c.DrawPath(x+aabbMinX, y+aabbMinY, canvas.Rectangle(aabbMaxX-aabbMinX, aabbMaxY-aabbMinY)) + } + if member, ok := component.Members["circle_radius"]; ok { + if radius, ok := member.(float64); ok && radius > 0 { + // Theoretically we need to clip the damage area to the intersection of the AABB and the circle, but meh. + cx, cy := (aabbMinX+aabbMaxX)/2, (aabbMinY+aabbMaxY)/2 + c.Style = entityDisplayAreaDamageStyle + c.DrawPath(x+cx, y+cy, canvas.Circle(radius)) + } + } + + case "MaterialAreaCheckerComponent": // Checks for materials in the given AABB. + var aabbMinX, aabbMinY, aabbMaxX, aabbMaxY float64 + if member, ok := component.Members["area_aabb"]; ok { + if aabb, ok := member.([]any); ok && len(aabb) == 4 { + aabbMinX, _ = aabb[0].(float64) + aabbMinY, _ = aabb[1].(float64) + aabbMaxX, _ = aabb[2].(float64) + aabbMaxY, _ = aabb[3].(float64) + } + } + if aabbMinX < aabbMaxX && aabbMinY < aabbMaxY { + c.Style = entityDisplayMaterialAreaCheckerStyle + c.DrawPath(x+aabbMinX, y+aabbMinY, canvas.Rectangle(aabbMaxX-aabbMinX, aabbMaxY-aabbMinY)) + } + + case "TeleportComponent": + var aabbMinX, aabbMinY, aabbMaxX, aabbMaxY float64 + if member, ok := component.Members["source_location_camera_aabb"]; ok { + if aabb, ok := member.([]any); ok && len(aabb) == 4 { + aabbMinX, _ = aabb[0].(float64) + aabbMinY, _ = aabb[1].(float64) + aabbMaxX, _ = aabb[2].(float64) + aabbMaxY, _ = aabb[3].(float64) + } + } + if aabbMinX < aabbMaxX && aabbMinY < aabbMaxY { + c.Style = entityDisplayTeleportStyle + c.DrawPath(x+aabbMinX, y+aabbMinY, canvas.Rectangle(aabbMaxX-aabbMinX, aabbMaxY-aabbMinY)) + } + + case "HitboxComponent": // General hit box component. + var aabbMinX, aabbMinY, aabbMaxX, aabbMaxY float64 + if member, ok := component.Members["aabb_min_x"]; ok { + aabbMinX, _ = member.(float64) + } + if member, ok := component.Members["aabb_min_y"]; ok { + aabbMinY, _ = member.(float64) + } + if member, ok := component.Members["aabb_max_x"]; ok { + aabbMaxX, _ = member.(float64) + } + if member, ok := component.Members["aabb_max_y"]; ok { + aabbMaxY, _ = member.(float64) + } + if aabbMinX < aabbMaxX && aabbMinY < aabbMaxY { + c.Style = entityDisplayHitBoxStyle + c.DrawPath(x+aabbMinX, y+aabbMinY, canvas.Rectangle(aabbMaxX-aabbMinX, aabbMaxY-aabbMinY)) + } + + case "CollisionTriggerComponent": // Checks if another entity is inside the box with the given width and height. + var width, height float64 + path := &canvas.Path{} + if member, ok := component.Members["width"]; ok { + width, _ = member.(float64) + } + if member, ok := component.Members["height"]; ok { + height, _ = member.(float64) + } + if width > 0 && height > 0 { + path = canvas.Rectangle(width, height).Translate(-width/2, -height/2) + } + //if member, ok := component.Members["radius"]; ok { + // if radius, ok := member.(float64); ok && radius > 0 { + // path = path.Append(canvas.Circle(radius)) + // path.And() + // } + //} + if !path.Empty() { + c.Style = entityDisplayCollisionTriggerStyle + c.DrawPath(x, y, path) + } + + } + } + + c.SetFillColor(color.RGBA{255, 255, 255, 128}) + c.SetStrokeColor(color.RGBA{255, 0, 0, 255}) + c.DrawPath(x, y, canvas.Circle(3)) + + //text := canvas.NewTextLine(entityDisplayFontFace, fmt.Sprintf("%s\n%s", e.Name, e.Filename), canvas.Left) + //c.DrawText(x, y, text) +} diff --git a/bin/stitch/imagetile.go b/bin/stitch/imagetile.go index d68544e..080b135 100644 --- a/bin/stitch/imagetile.go +++ b/bin/stitch/imagetile.go @@ -14,6 +14,8 @@ import ( "time" "github.com/nfnt/resize" + "github.com/tdewolff/canvas" + "github.com/tdewolff/canvas/renderers/rasterizer" ) type imageTile struct { @@ -25,9 +27,11 @@ type imageTile struct { image image.Image // Either a rectangle or an RGBA image. The bounds of this image are determined by the filename. imageMutex *sync.RWMutex // - imageUsedFlag bool // Flag signalling, that the image was used recently + imageUsedFlag bool // Flag signalling, that the image was used recently. pixelErrorSum uint64 // Sum of the difference between the (sub)pixels of all overlapping images. 0 Means that all overlapping images are identical. + + entities []Entity // List of entities that may lie on or near this image tile. } func (it *imageTile) GetImage() (*image.RGBA, error) { @@ -35,23 +39,23 @@ func (it *imageTile) GetImage() (*image.RGBA, error) { it.imageUsedFlag = true // Race condition may happen on this flag, but doesn't matter here. - // Check if the image is already loaded + // Check if the image is already loaded. if img, ok := it.image.(*image.RGBA); ok { it.imageMutex.RUnlock() return img, nil } it.imageMutex.RUnlock() - // It's possible that the image got changed in between here + // It's possible that the image got changed in between here. it.imageMutex.Lock() defer it.imageMutex.Unlock() - // Check again if the image is already loaded + // Check again if the image is already loaded. if img, ok := it.image.(*image.RGBA); ok { return img, nil } - // Store rectangle of the old image + // Store rectangle of the old image. oldRect := it.image.Bounds() file, err := os.Open(it.fileName) @@ -74,8 +78,31 @@ func (it *imageTile) GetImage() (*image.RGBA, error) { return &image.RGBA{}, fmt.Errorf("expected an RGBA image, got %T instead", img) } - // Restore the position of the image rectangle - imgRGBA.Rect = imgRGBA.Rect.Add(oldRect.Min) + scaledRect := imgRGBA.Rect.Add(oldRect.Min) + + // Draw entities. + // tdewolff/canvas doesn't respect the image boundaries, so we have to draw on the image before we move its rectangle. + if len(it.entities) > 0 { + c := canvas.New(float64(imgRGBA.Rect.Dx()), float64(imgRGBA.Rect.Dy())) + ctx := canvas.NewContext(c) + ctx.SetCoordSystem(canvas.CartesianIV) + ctx.SetCoordRect(canvas.Rect{X: -float64(oldRect.Min.X), Y: -float64(oldRect.Min.Y), W: float64(imgRGBA.Rect.Dx()), H: float64(imgRGBA.Rect.Dy())}, float64(imgRGBA.Rect.Dx()), float64(imgRGBA.Rect.Dy())) + for _, entity := range it.entities { + // Check if entity origin is near or around the current image rectangle. + entityOrigin := image.Point{int(entity.Transform.X), int(entity.Transform.Y)} + if entityOrigin.In(scaledRect.Inset(-512)) { + entity.Draw(ctx) + } + } + + // Theoretically we would need to linearize imgRGBA first, but DefaultColorSpace assumes that the color space is linear already. + r := rasterizer.FromImage(imgRGBA, canvas.DPMM(1.0), canvas.DefaultColorSpace) + c.Render(r) + r.Close() // This just transforms the image's luminance curve back from linear into non linear. + } + + // Restore the position of the image rectangle. + imgRGBA.Rect = scaledRect it.image = imgRGBA @@ -83,7 +110,7 @@ func (it *imageTile) GetImage() (*image.RGBA, error) { go func() { for it.imageUsedFlag { it.imageUsedFlag = false - time.Sleep(100 * time.Millisecond) + time.Sleep(500 * time.Millisecond) } it.imageMutex.Lock() diff --git a/bin/stitch/imagetiles.go b/bin/stitch/imagetiles.go index 4471372..1fd3979 100644 --- a/bin/stitch/imagetiles.go +++ b/bin/stitch/imagetiles.go @@ -22,7 +22,7 @@ import ( var regexFileParse = regexp.MustCompile(`^(-?\d+),(-?\d+).png$`) -func loadImages(path string, scaleDivider int) ([]imageTile, error) { +func loadImages(path string, entities []Entity, scaleDivider int) ([]imageTile, error) { var imageTiles []imageTile if scaleDivider < 1 { @@ -59,6 +59,7 @@ func loadImages(path string, scaleDivider int) ([]imageTile, error) { scaleDivider: scaleDivider, image: image.Rect(x/scaleDivider, y/scaleDivider, (x+width)/scaleDivider, (y+height)/scaleDivider), imageMutex: &sync.RWMutex{}, + entities: entities, }) } diff --git a/bin/stitch/medianBlendedImage.go b/bin/stitch/medianBlendedImage.go index 9efc2e9..a8239e1 100644 --- a/bin/stitch/medianBlendedImage.go +++ b/bin/stitch/medianBlendedImage.go @@ -8,6 +8,7 @@ package main import ( "image" "image/color" + "log" ) // MedianBlendedImageRowHeight defines the height of the cached output image. @@ -65,6 +66,7 @@ func (mbi *MedianBlendedImage) At(x, y int) color.Color { // TODO: Don't use hilbert curve here if err := StitchGrid(mbi.tiles, mbi.cachedRow, 512, nil); err != nil { + log.Printf("StitchGrid failed: %v", err) return color.RGBA{} } } diff --git a/bin/stitch/stitch.go b/bin/stitch/stitch.go index bcf544e..c8289c6 100644 --- a/bin/stitch/stitch.go +++ b/bin/stitch/stitch.go @@ -21,6 +21,7 @@ import ( ) var flagInputPath = flag.String("input", filepath.Join(".", "..", "..", "output"), "The source path of the image tiles to be stitched.") +var flagEntitiesInputPath = flag.String("entities", filepath.Join(".", "..", "..", "output", "entities.json"), "The source path of the entities.json file.") var flagOutputPath = flag.String("output", filepath.Join(".", "output.png"), "The path and filename of the resulting stitched image.") var flagScaleDivider = flag.Int("divide", 1, "A downscaling factor. 2 will produce an image with half the side lengths.") var flagXMin = flag.Int("xmin", 0, "Left bound of the output rectangle. This coordinate is included in the output.") @@ -35,7 +36,7 @@ func main() { flag.Parse() - // Query the user, if there were no cmd arguments given + // Query the user, if there were no cmd arguments given. if flag.NFlag() == 0 { prompt := promptui.Prompt{ Label: "Enter downscaling factor:", @@ -62,7 +63,7 @@ func main() { fmt.Sscanf(result, "%d", flagScaleDivider) } - // Query the user, if there were no cmd arguments given + // Query the user, if there were no cmd arguments given. if flag.NFlag() == 0 { prompt := promptui.Prompt{ Label: "Enter input path:", @@ -77,8 +78,32 @@ func main() { *flagInputPath = result } + // Query the user, if there were no cmd arguments given. + if flag.NFlag() == 0 { + prompt := promptui.Prompt{ + Label: "Enter \"entities.json\" path:", + Default: *flagEntitiesInputPath, + AllowEdit: true, + } + + result, err := prompt.Run() + if err != nil { + log.Panicf("Error while getting user input: %v", err) + } + *flagEntitiesInputPath = result + } + + // Load entities if requested. + entities, err := loadEntities(*flagEntitiesInputPath) + if err != nil { + log.Printf("Failed to load entities: %v", err) + } + if len(entities) > 0 { + log.Printf("Got %v entities.", len(entities)) + } + log.Printf("Starting to read tile information at \"%v\"", *flagInputPath) - tiles, err := loadImages(*flagInputPath, *flagScaleDivider) + tiles, err := loadImages(*flagInputPath, entities, *flagScaleDivider) if err != nil { log.Panic(err) } @@ -107,13 +132,13 @@ func main() { } defer pprof.StopCPUProfile()*/ - // If the output rect is empty, use the rectangle that encloses all tiles + // If the output rect is empty, use the rectangle that encloses all tiles. outputRect := image.Rect(*flagXMin, *flagYMin, *flagXMax, *flagYMax) if outputRect.Empty() { outputRect = totalBounds } - // Query the user, if there were no cmd arguments given + // Query the user, if there were no cmd arguments given. if flag.NFlag() == 0 { prompt := promptui.Prompt{ Label: "Enter output rectangle (xMin,yMin;xMax,yMax):", @@ -145,7 +170,7 @@ func main() { outputRect = image.Rect(xMin, yMin, xMax, yMax) } - // Query the user, if there were no cmd arguments given + // Query the user, if there were no cmd arguments given. /*if flag.NFlag() == 0 { fmt.Println("\nYou can now define a cleanup threshold. This mode will DELETE input images based on their similarity with other overlapping input images. The range is from 0, where no images are deleted, to 1 where all images will be deleted. A good value to get rid of most artifacts is 0.999. If you enter a threshold above 0, the program will not stitch, but DELETE some of your input images. If you want to stitch, enter 0.") prompt := promptui.Prompt{ @@ -202,7 +227,7 @@ func main() { return } - // Query the user, if there were no cmd arguments given + // Query the user, if there were no cmd arguments given. if flag.NFlag() == 0 { prompt := promptui.Prompt{ Label: "Enter output filename and path:", diff --git a/go.mod b/go.mod index a54fca5..b940e9b 100644 --- a/go.mod +++ b/go.mod @@ -3,27 +3,40 @@ module github.com/Dadido3/noita-mapcap go 1.18 require ( + github.com/1lann/promptui v0.8.1-0.20220708222609-81fad96dd5e1 github.com/cheggaaa/pb/v3 v3.1.0 github.com/coreos/go-semver v0.3.0 github.com/google/hilbert v0.0.0-20181122061418-320f2e35a565 github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 + github.com/tdewolff/canvas v0.0.0-20220627195642-6566432f4b20 golang.org/x/exp v0.0.0-20220713135740-79cabaa25d75 golang.org/x/image v0.0.0-20220617043117-41969df76e82 ) require ( - github.com/1lann/promptui v0.8.1-0.20220708222609-81fad96dd5e1 // indirect + github.com/ByteArena/poly2tri-go v0.0.0-20170716161910-d102ad91854f // indirect github.com/VividCortex/ewma v1.2.0 // indirect + github.com/adrg/strutil v0.3.0 // indirect + github.com/adrg/sysfont v0.1.2 // indirect + github.com/adrg/xdg v0.4.0 // indirect + github.com/benoitkugler/textlayout v0.1.3 // indirect + github.com/benoitkugler/textprocessing v0.0.2 // indirect github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect + github.com/dsnet/compress v0.0.1 // indirect github.com/fatih/color v1.13.0 // indirect github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5 // indirect + github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81 // indirect + github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240 // indirect github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect github.com/rivo/uniseg v0.2.0 // indirect + github.com/tdewolff/minify/v2 v2.11.10 // indirect + github.com/tdewolff/parse/v2 v2.6.0 // indirect golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect + golang.org/x/text v0.3.7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 1559102..7fb667d 100644 --- a/go.sum +++ b/go.sum @@ -1,29 +1,78 @@ +git.sr.ht/~sbinet/gg v0.3.1 h1:LNhjNn8DerC8f9DHLz6lS0YYul/b602DUxDgGkd/Aik= github.com/1lann/promptui v0.8.1-0.20220708222609-81fad96dd5e1 h1:LejjvYg4tCW5HO7q/1nzPrprh47oUD9OUySQ29pDp5c= github.com/1lann/promptui v0.8.1-0.20220708222609-81fad96dd5e1/go.mod h1:cnC/60IoLiDM0GhdKYJ6oO7AwpZe1IQfPnSKlAURgHw= +github.com/ByteArena/poly2tri-go v0.0.0-20170716161910-d102ad91854f h1:l7moT9o/v/9acCWA64Yz/HDLqjcRTvc0noQACi4MsJw= +github.com/ByteArena/poly2tri-go v0.0.0-20170716161910-d102ad91854f/go.mod h1:vIOkSdX3NDCPwgu8FIuTat2zDF0FPXXQ0RYFRy+oQic= github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA= github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= +github.com/adrg/strutil v0.2.2/go.mod h1:EF2fjOFlGTepljfI+FzgTG13oXthR7ZAil9/aginnNQ= +github.com/adrg/strutil v0.3.0 h1:bi/HB2zQbDihC8lxvATDTDzkT4bG7PATtVnDYp5rvq4= +github.com/adrg/strutil v0.3.0/go.mod h1:Jz0wzBVE6Uiy9wxo62YEqEY1Nwto3QlLl1Il5gkLKWU= +github.com/adrg/sysfont v0.1.2 h1:MSU3KREM4RhsQ+7QgH7wPEPTgAgBIz0Hw6Nd4u7QgjE= +github.com/adrg/sysfont v0.1.2/go.mod h1:6d3l7/BSjX9VaeXWJt9fcrftFaD/t7l11xgSywCPZGk= +github.com/adrg/xdg v0.3.0/go.mod h1:7I2hH/IT30IsupOpKZ5ue7/qNi3CoKzD6tL3HwpaRMQ= +github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls= +github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E= +github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw= +github.com/benoitkugler/pstokenizer v1.0.0/go.mod h1:l1G2Voirz0q/jj0TQfabNxVsa8HZXh/VMxFSRALWTiE= +github.com/benoitkugler/textlayout v0.0.10/go.mod h1:puH4v13Uz7uIhIH0XMk5jgc8U3MXcn5r3VlV9K8n0D8= +github.com/benoitkugler/textlayout v0.1.3 h1:Jv0E28xDkke3KrWle90yOLtBmZsUqXLBy70lZRfbKN0= +github.com/benoitkugler/textlayout v0.1.3/go.mod h1:o+1hFV+JSHBC9qNLIuwVoLedERU7sBPgEFcuSgfvi/w= +github.com/benoitkugler/textlayout-testdata v0.1.1 h1:AvFxBxpfrQd8v55qH59mZOJOQjtD6K2SFe9/HvnIbJk= +github.com/benoitkugler/textprocessing v0.0.2 h1:PHduXv1+LsLxDIdeR3sG1qvHhWwkbL+ZZcjkOmu38T4= +github.com/benoitkugler/textprocessing v0.0.2/go.mod h1:QwonW08YlX3qeZ3vv91Wyic3JqG+MXBa05N6rHwJaOc= +github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U= github.com/cheggaaa/pb/v3 v3.1.0 h1:3uouEsl32RL7gTiQsuaXD4Bzbfl5tGztXGUvXbs4O04= github.com/cheggaaa/pb/v3 v3.1.0/go.mod h1:YjrevcBqadFDaGQKRdmZxTY42pXEqda48Ea3lt0K/BE= +github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/djherbis/atime v1.1.0/go.mod h1:28OF6Y8s3NQWwacXc5eZTsEsiMzp7LF8MbXE+XJPdBE= +github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q= +github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo= +github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5 h1:Y5Q2mEwfzjMt5+3u70Gtw93ZOu2UuPeeeTBDntF7FoY= github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5/go.mod h1:uF6rMu/1nvu+5DpiRLwusA6xB8zlkNoGzKn8lmYONUo= +github.com/go-fonts/dejavu v0.1.0 h1:JSajPXURYqpr+Cu8U9bt8K+XcACIHWqWrvWCKyeFmVQ= +github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= +github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= +github.com/go-fonts/liberation v0.2.0 h1:jAkAWJP4S+OsrPLZM4/eC9iW7CtHy+HBXrEwZXWo5VM= +github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= +github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= +github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81 h1:6zl3BbBhdnMkpSj2YY30qV3gDcVBGtFgVsV3+/i+mKQ= +github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= +github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= +github.com/go-pdf/fpdf v0.6.0 h1:MlgtGIfsdMEEQJr2le6b/HNr1ZlQwxyWr77r2aj2U/8= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/google/hilbert v0.0.0-20181122061418-320f2e35a565 h1:KBAlCAY6eLC44FiEwbzEbHnpVlw15iVM4ZK8QpRIp4U= github.com/google/hilbert v0.0.0-20181122061418-320f2e35a565/go.mod h1:xn6EodFfRzV6j8NXQRPjngeHWlrpOrsZPKuuLRThU1k= github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240 h1:dy+DS31tGEGCsZzB45HmJJNHjur8GDgtRNX9U7HnSX4= github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240/go.mod h1:3P4UH/k22rXyHIJD2w4h2XMqPX4Of/eySEZq9L6wqc4= +github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329 h1:qq2nCpSrXrmvDGRxW0ruW9BVEV1CN2a9YDOExdt+U0o= github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329/go.mod h1:2VPVQDR4wO7KXHwP+DAypEy67rXf+okUx2zjgpCxZw4= +github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/lxn/win v0.0.0-20210218163916-a377121e959e h1:H+t6A/QJMbhCSEH5rAuRxh+CtW96g0Or0Fxa9IKr4uc= github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk= +github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= @@ -36,24 +85,68 @@ github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4 github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= +github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= +github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= +github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= +github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.3 h1:dAm0YRdRQlWojc3CrCRgPBzG5f941d0zvAKu7qY4e+I= +github.com/tdewolff/canvas v0.0.0-20220627195642-6566432f4b20 h1:n1uiUjN7FaL+7vXRcXi/W5mAggYzfRwcKOV6JP9U1ag= +github.com/tdewolff/canvas v0.0.0-20220627195642-6566432f4b20/go.mod h1:EUhKKb2ofHjd7fnOdhBaqZYlTdF2Mu/gtYg2bwIt6wU= +github.com/tdewolff/minify/v2 v2.11.10 h1:2tk9nuKfc8YOTD8glZ7JF/VtE8W5HOgmepWdjcPtRro= +github.com/tdewolff/minify/v2 v2.11.10/go.mod h1:dHOS3dk+nJ0M3q3uM3VlNzTb70cou+ov0ki7C4PAFgM= +github.com/tdewolff/parse/v2 v2.6.0 h1:f2D7w32JtqjCv6SczWkfwK+m15et42qEtDnZXHoNY70= +github.com/tdewolff/parse/v2 v2.6.0/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho= +github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= +github.com/tdewolff/test v1.0.7 h1:8Vs0142DmPFW/bQeHRP3MV19m1gvndjUb1sn8yy74LM= +github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= golang.org/x/exp v0.0.0-20220713135740-79cabaa25d75 h1:x03zeu7B2B11ySp+daztnwM5oBJ/8wGUSqrwcw9L0RA= golang.org/x/exp v0.0.0-20220713135740-79cabaa25d75/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= +golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/image v0.0.0-20220617043117-41969df76e82 h1:KpZB5pUSBvrHltNEdK/tw0xlPeD13M6M6aGP32gKqiw= golang.org/x/image v0.0.0-20220617043117-41969df76e82/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY= +golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gonum.org/v1/plot v0.11.0 h1:z2ZkgNqW34d0oYUzd80RRlc0L9kWtenqK4kflZG1lGc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=