diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..9a7b08a --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,89 @@ +--- +linters-settings: + dupl: + threshold: 100 + funlen: + lines: 100 + statements: 50 + goconst: + min-len: 2 + min-occurrences: 2 + gocritic: + enabled-tags: + - diagnostic + - experimental + - opinionated + - performance + - style + disabled-checks: + gocyclo: + min-complexity: 15 + goimports: + local-prefixes: github.com/gucio321/d2dc6 + govet: + enable-all: true + check-shadowing: true + lll: + line-length: 140 + maligned: + suggest-new: true + misspell: + locale: US + +linters: + disable-all: true + enable: + - bodyclose + - deadcode + - depguard + - dogsled + - dupl + - errcheck + - errorlint + - exportloopref + - funlen + - gochecknoglobals + - gochecknoinits + - gocognit + - goconst + - gocritic + - gocyclo + - gofmt + - goimports + - gomnd + - goprintffuncname + - gosec + - gosimple + - govet + - ifshort + - ineffassign + - lll + - makezero + - misspell + - nakedret + - nolintlint + - prealloc + - revive + - rowserrcheck + - staticcheck + - structcheck + - stylecheck + - typecheck + - unconvert + - unparam + - unused + - varcheck + - whitespace + - wrapcheck + - wsl + +run: + timeout: 5m + skip-dirs: + - .github + - build + - web + +issues: + max-same-issues: 0 + exclude-use-default: false diff --git a/README.md b/README.md new file mode 100644 index 0000000..fc2f7c5 --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +[![Go Report Card](https://goreportcard.com/badge/github.com/OpenDiablo2/dc6)](https://goreportcard.com/report/github.com/OpenDiablo2/dc6) +[![GoDoc](https://pkg.go.dev/badge/github.com/OpenDiablo2/dc6?utm_source=godoc)](https://pkg.go.dev/mod/github.com/OpenDiablo2/dc6) + +## Description + +This is a module used to decoding and encoding Diablo II animation files (DC6) + +## Documentation + +Documentation describing this file format can be found here: +- [MarkKoz/DC6.ksy](https://gist.github.com/MarkKoz/874052801d7eddd1bb4a9b69cd1e9ac8) [and here](./docs/dc6.ksy) +- [The Phrozen Keep: DC6 Format](https://d2mods.info/forum/viewtopic.php?t=724#p148076) diff --git a/cmd/dc6-convert/main.go b/cmd/dc6-convert/main.go index aa117a7..5855db8 100644 --- a/cmd/dc6-convert/main.go +++ b/cmd/dc6-convert/main.go @@ -11,7 +11,7 @@ import ( "os" "path/filepath" - dc6lib "github.com/gravestench/dc6/pkg" + dc6lib "github.com/OpenDiablo2/dc6/pkg" gpl "github.com/gravestench/gpl/pkg" ) @@ -32,6 +32,7 @@ func main() { dc6Data, err := ioutil.ReadFile(*o.dc6Path) if err != nil { const fmtErr = "could not read file, %v" + fmt.Print(fmt.Errorf(fmtErr, err)) return @@ -59,8 +60,8 @@ func main() { dc6.SetPalette(color.Palette(*gplInstance)) } - numDirections := len(dc6.Directions) - framesPerDir := len(dc6.Directions[0].Frames) + numDirections := dc6.Frames.NumberOfDirections() + framesPerDir := dc6.Frames.FramesPerDirection() isMultiFrame := numDirections > 1 || framesPerDir > 1 outfilePath := *o.pngPath @@ -69,8 +70,8 @@ func main() { outfilePath = noExt + "_d%v_f%v.png" } - for dirIdx := range dc6.Directions { - for frameIdx := range dc6.Directions[dirIdx].Frames { + for dirIdx := 0; dirIdx < numDirections; dirIdx++ { + for frameIdx := 0; frameIdx < framesPerDir; frameIdx++ { outPath := outfilePath if isMultiFrame { @@ -82,8 +83,9 @@ func main() { log.Fatal(err) } - if err := png.Encode(f, dc6.Directions[dirIdx].Frames[frameIdx]); err != nil { + if err := png.Encode(f, dc6.Frames.Direction(dirIdx).Frame(frameIdx)); err != nil { _ = f.Close() + log.Fatal(err) } @@ -95,7 +97,7 @@ func main() { } func parseOptions(o *options) (terminate bool) { - o.dc6Path = flag.String("dc6lib", "", "input dc6lib file (required)") + o.dc6Path = flag.String("dc6", "", "input dc6lib file (required)") o.palPath = flag.String("pal", "", "input pal file (optional)") o.pngPath = flag.String("png", "", "path to png file (optional)") diff --git a/cmd/dc6-view/main.go b/cmd/dc6-view/main.go index b829b1d..2391ca7 100644 --- a/cmd/dc6-view/main.go +++ b/cmd/dc6-view/main.go @@ -7,11 +7,12 @@ import ( "image/color" "io/ioutil" "path" + "path/filepath" "github.com/AllenDang/giu" - pdc6lib "github.com/gravestench/dc6/pkg" - dc6widget "github.com/gravestench/dc6/pkg/giuwidget" + dc6lib "github.com/OpenDiablo2/dc6/pkg" + dc6widget "github.com/OpenDiablo2/dc6/pkg/giuwidget" gpl "github.com/gravestench/gpl/pkg" ) @@ -32,7 +33,7 @@ func main() { srcPath := *o.dc6Path - fileContents, err := ioutil.ReadFile(srcPath) + fileContents, err := ioutil.ReadFile(filepath.Clean(srcPath)) if err != nil { const fmtErr = "could not read file, %w" @@ -41,7 +42,7 @@ func main() { return } - dc6, err := pdc6lib.FromBytes(fileContents) + dc6, err := dc6lib.FromBytes(fileContents) if err != nil { fmt.Print(err) return @@ -63,7 +64,7 @@ func main() { dc6.SetPalette(color.Palette(*gplInstance)) } - f0 := dc6.Directions[0].Frames[0] + f0 := dc6.Frames.Direction(0).Frame(0) imgW := int(float64(f0.Width) * *o.scale) imgH := int(float64(f0.Height) * *o.scale) diff --git a/doc.go b/doc.go deleted file mode 100644 index 6278f16..0000000 --- a/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// Package dc6 provides a DC6 animation decoder, used by diablo 2. -package dc6 diff --git a/pkg/dc6.ksy b/docs/dc6.ksy similarity index 100% rename from pkg/dc6.ksy rename to docs/dc6.ksy diff --git a/pkg/frame_header.go b/docs/dc6_frame_header.go similarity index 62% rename from pkg/frame_header.go rename to docs/dc6_frame_header.go index 791e74a..10d27bb 100644 --- a/pkg/frame_header.go +++ b/docs/dc6_frame_header.go @@ -1,7 +1,8 @@ -package pkg +package docs -// FrameHeader represents the header of a frame in a DC6. -type FrameHeader struct { +// DC6FrameHeader represents the header of a frame in a DC6. +// this structure is unused in this module and is only a documentation +type DC6FrameHeader struct { Flipped int32 `struct:"int32"` Width int32 `struct:"int32"` Height int32 `struct:"int32"` diff --git a/pkg/header.go b/docs/dc6_header.go similarity index 53% rename from pkg/header.go rename to docs/dc6_header.go index 9671610..6d731f2 100644 --- a/pkg/header.go +++ b/docs/dc6_header.go @@ -1,11 +1,13 @@ -package pkg +// Package docs contains a documentation files (unused in real module) +package docs -// Header represents the file header of a DC6 file. -type Header struct { +// DC6Header represents the file header of a DC6 file. +// this structure is unused in this module and is only a documentation +type DC6Header struct { + Termination []byte `struct:"[4]byte"` Version int32 `struct:"int32"` Flags uint32 `struct:"uint32"` Encoding uint32 `struct:"uint32"` - Termination []byte `struct:"[4]byte"` Directions int32 `struct:"int32"` FramesPerDirection int32 `struct:"int32"` } diff --git a/go.mod b/go.mod index 0df4821..8b50aa9 100644 --- a/go.mod +++ b/go.mod @@ -1,12 +1,13 @@ -module github.com/gravestench/dc6 +module github.com/OpenDiablo2/dc6 go 1.16 require ( github.com/AllenDang/giu v0.5.4 + github.com/OpenDiablo2/bitstream v0.0.0-20210818234514-9fca7e40e2b3 github.com/enriquebris/goconcurrentqueue v0.6.0 github.com/go-resty/resty/v2 v2.6.0 // indirect - github.com/gravestench/bitstream v0.0.0-20210602033510-6e018ddc185f - github.com/gravestench/gpl v0.0.0-20210615232229-779e263cf91e // indirect + github.com/gravestench/gpl v0.0.0-20210615232229-779e263cf91e + github.com/stretchr/testify v1.7.0 golang.org/x/net v0.0.0-20210614182718-04defd469f4e // indirect ) diff --git a/go.sum b/go.sum index 5bdf242..7c69a8d 100644 --- a/go.sum +++ b/go.sum @@ -1,227 +1,46 @@ -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/AllenDang/giu v0.5.4 h1:OuSgcTZYH9buug1yWsJAvUr57aQwiJK7hjk+JYtv4tM= github.com/AllenDang/giu v0.5.4/go.mod h1:1CyOLkJREGGWFVx+p2z5+1KMhY3H7Zbl8gQ/huzThsI= github.com/AllenDang/imgui-go v1.12.1-0.20210509113325-c0e4c78e7a88 h1:dXpshnNwPl+QjFOiJNFlQrtOz1oA565EO9FUseKvlhU= github.com/AllenDang/imgui-go v1.12.1-0.20210509113325-c0e4c78e7a88/go.mod h1:+pYwstqlGGlU2lI1FREPlVAKP2nwV+bNhAlMrbQynhQ= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/JoshVarga/blast v0.0.0-20180421040937-681c804fb9f0 h1:tDnuU0igiBiQFjsvq1Bi7DpoUjqI76VVvW045vpeFeM= -github.com/JoshVarga/blast v0.0.0-20180421040937-681c804fb9f0/go.mod h1:h/5OEGj4G+fpYxluLjSMZbFY011ZxAntO98nCl8mrCs= -github.com/OpenDiablo2/HellSpawner v0.0.0-20210610233419-270fa82b5a6f h1:DTMkRe4rdztsEA+b6wwPMOm1b6g5LXamNdhlQYcJ0uI= -github.com/OpenDiablo2/HellSpawner v0.0.0-20210610233419-270fa82b5a6f/go.mod h1:6kHPqeq2trEuYdFDmH+SntjVRhFQxE2a/jHwDbjDoaA= -github.com/OpenDiablo2/OpenDiablo2 v0.0.0-20210514222603-a688d660a0f7 h1:TGxU+HHMHIl5aOZk/Yd3uEP+ALbxvysnuhbMkg1f6xY= -github.com/OpenDiablo2/OpenDiablo2 v0.0.0-20210514222603-a688d660a0f7/go.mod h1:IzC2iZNeFCs35eOl/wdvzqSGkHs30d94zomUoaA9QT0= -github.com/OpenDiablo2/dialog v0.0.0-20201230220514-26162241209f h1:pvMvvC9qn9EaHDbiCgblnrL4iyvwchcMKO81kSnlEsc= -github.com/OpenDiablo2/dialog v0.0.0-20201230220514-26162241209f/go.mod h1:pVhjsSdbHVR9+2HBRkrfvlZCmjC+jRhGjjKM3uYlfWY= -github.com/TheTitanrain/w32 v0.0.0-20180517000239-4f5cfb03fabf/go.mod h1:peYoMncQljjNS6tZwI9WVyQB3qZS6u79/N3mBOcnd3I= -github.com/TheTitanrain/w32 v0.0.0-20200114052255-2654d97dbd3d h1:2xp1BQbqcDDaikHnASWpVZRjibOxu7y9LhAv04whugI= -github.com/TheTitanrain/w32 v0.0.0-20200114052255-2654d97dbd3d/go.mod h1:peYoMncQljjNS6tZwI9WVyQB3qZS6u79/N3mBOcnd3I= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/OpenDiablo2/bitstream v0.0.0-20210818234514-9fca7e40e2b3 h1:FR8R13AaTKxSxTS8KzjmU7RnSsKnAVSOQUKYIoGhyY4= +github.com/OpenDiablo2/bitstream v0.0.0-20210818234514-9fca7e40e2b3/go.mod h1:Wrs2YpoAmqLq/uBAKxZnW145BgzsVsLwxafjI3hkMhE= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/enriquebris/goconcurrentqueue v0.6.0 h1:DJ97cgoPVoqlC4tTGBokn/omaB3o16yIs5QdAm6YEjc= github.com/enriquebris/goconcurrentqueue v0.6.0/go.mod h1:wGJhQNFI4wLNHleZLo5ehk1puj8M6OIl0tOjs3kwJus= -github.com/faiface/beep v1.0.2 h1:UB5DiRNmA4erfUYnHbgU4UB6DlBOrsdEFRtcc8sCkdQ= -github.com/faiface/beep v1.0.2/go.mod h1:1yLb5yRdHMsovYYWVqYLioXkVuziCSITW1oarTeduQM= github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3 h1:baVdMKlASEHrj19iqjARrPbaRisD7EuZEVJj6ZMLl1Q= github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3/go.mod h1:VEPNJUlxl5KdWjDvz6Q1l+rJlxF2i6xqDeGuGAxa87M= -github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= -github.com/gdamore/tcell v1.1.1/go.mod h1:K1udHkiR3cOtlpKG5tZPD5XxrF7v2y7lDq7Whcj+xkQ= github.com/go-gl/gl v0.0.0-20210315015930-ae072cafe09d/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk= github.com/go-gl/gl v0.0.0-20210501111010-69f74958bac0 h1:7xNa69TzlTrKtlBtE4yyNRFP9oqAneXAs9oKJkVOECs= github.com/go-gl/gl v0.0.0-20210501111010-69f74958bac0/go.mod h1:wjpnOv6ONl2SuJSxqCPVaPZibGFdSci9HFocT9qtVYM= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200707082815-5321531c36a2/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210410170116-ea3d685f79fb h1:T6gaWBvRzJjuOrdCtg8fXXjKai2xSDqWTcKFUPuw8Tw= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210410170116-ea3d685f79fb/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-restruct/restruct v1.2.0-alpha h1:2Lp474S/9660+SJjpVxoKuWX09JsXHSrdV7Nv3/gkvc= -github.com/go-restruct/restruct v1.2.0-alpha/go.mod h1:KqrpKpn4M8OLznErihXTGLlsXFGeLxHUrLRRI/1YjGk= -github.com/go-resty/resty/v2 v2.3.0 h1:JOOeAvjSlapTT92p8xiS19Zxev1neGikoHsXJeOq8So= github.com/go-resty/resty/v2 v2.3.0/go.mod h1:UpN9CgLZNsv4e9XG50UU8xdI0F43UQ4HmxLBDwaroHU= github.com/go-resty/resty/v2 v2.6.0 h1:joIR5PNLM2EFqqESUjCMGXrWmXNHEU9CEiK813oKYS4= github.com/go-resty/resty/v2 v2.6.0/go.mod h1:PwvJS6hvaPkjtjNg9ph+VrSD92bi5Zq73w/BIH7cC3Q= -github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gopherjs/gopherwasm v0.1.1/go.mod h1:kx4n9a+MzHH0BJJhvlsQ65hqLFXDO/m256AsaDPQ+/4= -github.com/gopherjs/gopherwasm v1.0.0/go.mod h1:SkZ8z7CWBz5VXbhJel8TxCmAcsQqzgWGR/8nMhyhZSI= -github.com/gravestench/akara v0.0.0-20201014060234-a64208a7fd3c/go.mod h1:fTeda1SogMg5Lkd4lXMEd/Pk/a5/gQuLGaAI2rn1PBQ= -github.com/gravestench/bitstream v0.0.0-20210602033510-6e018ddc185f h1:RylukO1Wy+GixLCRtLZqj0sd1PcYuabOWcHEp0Wmvdc= -github.com/gravestench/bitstream v0.0.0-20210602033510-6e018ddc185f/go.mod h1:FsgbN421v5maou5iKEE+7Rjzc9kaPVjCkqG4eQulejE= -github.com/gravestench/gpl v0.0.0-20210613190341-3dd16317a222 h1:T9brt7I0YcFb5JmT8O5jXE2Lk0jeitjwnB4PrWCDtVo= -github.com/gravestench/gpl v0.0.0-20210613190341-3dd16317a222/go.mod h1:1s4i4jzOTXxRqXjSIHgiARwwnG6sJyGX49MVBV2AurQ= github.com/gravestench/gpl v0.0.0-20210615232229-779e263cf91e h1:nTGSKcjAGzGMhV8d7LAe8+wEYyr3Fmq1VMggJBkm9B0= github.com/gravestench/gpl v0.0.0-20210615232229-779e263cf91e/go.mod h1:1s4i4jzOTXxRqXjSIHgiARwwnG6sJyGX49MVBV2AurQ= -github.com/gravestench/osinfo v0.0.0-20210525170338-34eb1ac010f7 h1:r0b+3cK7b5I2LLf7/uhed+VRjcWzOI7BwrCy54l8UlM= -github.com/gravestench/osinfo v0.0.0-20210525170338-34eb1ac010f7/go.mod h1:VaYXitHStkgczFCNysozZgiiK0aVITajAvR+jONEtzo= -github.com/hajimehoshi/bitmapfont/v2 v2.1.0/go.mod h1:2BnYrkTQGThpr/CY6LorYtt/zEPNzvE/ND69CRTaHMs= -github.com/hajimehoshi/ebiten/v2 v2.0.2/go.mod h1:AbHP/SS226aFTex/izULVwW0D2AuGyqC4AVwilmRjOg= -github.com/hajimehoshi/file2byteslice v0.0.0-20200812174855-0e5e8a80490e/go.mod h1:CqqAHp7Dk/AqQiwuhV1yT2334qbA/tFWQW0MD2dGqUE= -github.com/hajimehoshi/go-mp3 v0.1.1/go.mod h1:4i+c5pDNKDrxl1iu9iG90/+fhP37lio6gNhjCx9WBJw= -github.com/hajimehoshi/go-mp3 v0.3.1/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM= -github.com/hajimehoshi/oto v0.1.1/go.mod h1:hUiLWeBQnbDu4pZsAhOnGqMI1ZGibS6e2qhQdfpwz04= -github.com/hajimehoshi/oto v0.3.1/go.mod h1:e9eTLBB9iZto045HLbzfHJIc+jP3xaKrjZTghvb6fdM= -github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI= -github.com/hajimehoshi/oto v0.6.8/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI= -github.com/hajimehoshi/oto v0.7.1 h1:I7maFPz5MBCwiutOrz++DLdbr4rTzBsbBuV2VpgU9kk= -github.com/hajimehoshi/oto v0.7.1/go.mod h1:wovJ8WWMfFKvP587mhHgot/MBr4DnNy9m6EepeVGnos= -github.com/ianling/giu v0.5.1-0.20210524205023-4d3114338b69 h1:8/TnUIv7dy6smaOQiWsOB9MMHbiOgHrllYXRimQPx+Y= -github.com/ianling/giu v0.5.1-0.20210524205023-4d3114338b69/go.mod h1:twqK/c83RuUbBV8sWkV9TMpcdaBs3Pec2FBN5gC+hEA= -github.com/ianling/imgui-go v1.12.1-0.20210420174252-e90a22fb87e6 h1:cYyhJBJpHS3ccwS/Hn9SI4T3SUzORnfiBvB+/1mopLs= -github.com/ianling/imgui-go v1.12.1-0.20210420174252-e90a22fb87e6/go.mod h1:XJ3IHn74S/5gKA6R5tYya4x7bsucY4tHgfnGO94FmEc= -github.com/jakecoffman/cp v1.0.0/go.mod h1:JjY/Fp6d8E1CHnu74gWNnU0+b9VzEdUVPoJxg2PsTQg= -github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7 h1:g0fAGBisHaEQ0TRq1iBvemFRf+8AEWEmBESSiWB3Vsc= -github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk= -github.com/jfreymuth/oggvorbis v1.0.0/go.mod h1:abe6F9QRjuU9l+2jek3gj46lu40N4qlYxh2grqkLEDM= -github.com/jfreymuth/oggvorbis v1.0.1/go.mod h1:NqS+K+UXKje0FUYUPosyQ+XTVvjmVjps1aEZH1sumIk= -github.com/jfreymuth/vorbis v1.0.0/go.mod h1:8zy3lUAm9K/rJJk223RKy6vjCZTWC61NA2QD06bfOE0= -github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f h1:dKccXx7xA56UNqOcFIbuqFjAWPVtP688j5QMgmo6OHU= -github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f/go.mod h1:4rEELDSfUAlBSyUjPG0JnaNGjf13JySHFeRdD/3dLP0= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/lucasb-eyer/go-colorful v0.0.0-20181028223441-12d3b2882a08/go.mod h1:NXg0ArsFk0Y01623LgUqoqcouGDB+PwCCQlrwrG6xJ4= -github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= -github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mewkiz/flac v1.0.5/go.mod h1:EHZNU32dMF6alpurYyKHDLYpW1lYpBZ5WrXi/VuNIGs= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/ozankasikci/go-image-merge v0.2.3-0.20210426105355-ce64427c0c12 h1:LdAKX13BHUD2Ugj/PI7FjtUed9TksjhbtmkmorgOCEI= -github.com/ozankasikci/go-image-merge v0.2.3-0.20210426105355-ce64427c0c12/go.mod h1:NQ2aN0b21buFx3p+5x4dZrKuPSLh2uBukK7F30BrYTo= -github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= -github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 h1:Qj1ukM4GlMWXNdMBuXcXfz/Kw9s1qm0CLY32QxuSImI= -github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/profile v1.5.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18= +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.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/robertkrimen/otto v0.0.0-20200922221731-ef014fd054ac/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= -github.com/rs/zerolog v1.21.0/go.mod h1:ZPhntP/xmq1nnND05hhpAh2QMhSsA4UN3MGZ6O2J3hM= -github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= -github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo= -github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM= 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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/exp v0.0.0-20180710024300-14dda7b62fcd/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= -golang.org/x/exp v0.0.0-20201008143054-e3b2a7f2fdc7/go.mod h1:1phAWC201xIgDyaFpmDeZkgf70Q4Pd/CNqfRtVPtxNw= -golang.org/x/exp v0.0.0-20210526181343-b47a03e3048a h1:15PzmCQfHRcBYKPW5s3hmJVO2H/SpTv5rsEh10maPMk= -golang.org/x/exp v0.0.0-20210526181343-b47a03e3048a/go.mod h1:MSdmUWF4ZWBPSUbgUX/gaau5kvnbkSs9pgtY6B9JXDE= -golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20210504121937-7319ad40d33e h1:PzJMNfFQx+QO9hrC1GwZ4BoPGeNGhfeQEgcQFArEjPk= -golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mobile v0.0.0-20180806140643-507816974b79/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mobile v0.0.0-20200801112145-973feb4309de/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4= -golang.org/x/mobile v0.0.0-20201217150744-e6ae53a27f4f/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4= -golang.org/x/mobile v0.0.0-20210527171505-7e972142eb43 h1:YfX4EDYuRrJhCu1S8M+jsXVoQj+koh5ZIwpI7bzeQ38= -golang.org/x/mobile v0.0.0-20210527171505-7e972142eb43/go.mod h1:jFTmtFYCV0MFtXBU+J5V/+5AUeVS0ON/0WkE/KSrl6E= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120 h1:EZ3cVSzKOlJxAd8e8YAJ7no8nNypTxexh/YE/xW3ZEY= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210601080250-7ecdf8ef093b h1:qh4f65QIVFjq9eBURLEYWqaEXmOyqdUyiBSgaXWccWk= -golang.org/x/sys v0.0.0-20210601080250-7ecdf8ef093b/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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20201009162240-fcf82128ed91/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0/go.mod h1:OdE7CF6DbADk7lN8LIKRzRJTTZXIjtWgA5THM5lhBAw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= diff --git a/pkg/dc6.go b/pkg/dc6.go index e916ea4..daf9a69 100644 --- a/pkg/dc6.go +++ b/pkg/dc6.go @@ -1,216 +1,197 @@ package pkg import ( + "errors" "fmt" "image/color" "math" - "github.com/gravestench/bitstream" + "github.com/OpenDiablo2/bitstream" + + "github.com/OpenDiablo2/dc6/pkg/frames" ) const ( - endOfScanLine = 0x80 - maxRunLength = 0x7f -) + terminationSize = 4 -type scanlineState int + bytesPerInt32 = 4 -const ( - endOfLine scanlineState = iota - runOfTransparentPixels - runOfOpaquePixels + expectedDC6Version = 6 ) // DC6 represents a DC6 file. type DC6 struct { - Version int32 + Frames *frames.Frames + palette color.Palette Flags uint32 Encoding uint32 - Termination []byte // 4 bytes - Directions []*Direction - palette color.Palette + Termination [terminationSize]byte } -type Direction struct { - Frames []*Frame // size is Directions*FramesPerDirection -} +// New creates a new, empty DC6 +func New() *DC6 { + result := &DC6{ + Flags: 0, + Encoding: 0, + Frames: frames.New(), + } -// FromBytes uses restruct to read the binary dc6 data into structs then parses image data from the frame data. -func FromBytes(data []byte) (result *DC6, err error) { - result = &DC6{} + result.SetPalette(nil) - stream := bitstream.NewReader().FromBytes(data...) + return result +} - if err = result.decodeHeader(stream); err != nil { - return nil, err - } +// FromBytes loads a dc6 animation +func FromBytes(data []byte) (*DC6, error) { + d := New() - if err = result.decodeBody(stream); err != nil { + if err := d.FromBytes(data); err != nil { return nil, err } - return result, nil + return d, nil } -func (d *DC6) decodeHeader(stream *bitstream.Reader) (err error) { - const ( - versionBytes = 4 - flagsBytes = 4 - encodingBytes = 4 - terminationBytes = 4 - ) - - // only check last err - d.Version, _ = stream.Next(versionBytes).Bytes().AsInt32() - d.Flags, _ = stream.Next(flagsBytes).Bytes().AsUInt32() - d.Encoding, _ = stream.Next(encodingBytes).Bytes().AsUInt32() - d.Termination, err = stream.Next(terminationBytes).Bytes().AsBytes() - - return err +// FromBytes converts bite slice into DC6 structure +func (d *DC6) FromBytes(data []byte) error { + var err error + + r := bitstream.ReaderFromBytes(data...) + + err = d.loadHeader(r) + if err != nil { + return err + } + + frameCount := d.Frames.NumberOfDirections() * d.Frames.FramesPerDirection() + + // frame pointers - skip + _, err = r.Next(frameCount * bytesPerInt32).Bytes().AsBytes() + if err != nil { + return fmt.Errorf("reading frame pointers: %w", err) + } + + return d.loadFrames(r) } -func (d *DC6) decodeBody(stream *bitstream.Reader) (err error) { - const ( - terminatorSize = 3 - directionsBytes = 4 - framesPerDirectionBytes = 4 - framePointerBytes = 4 - ) - - const ( - frameFlippedBytes = 4 - frameWidthBytes = 4 - frameHeightBytes = 4 - frameOffsetXBytes = 4 - frameOffsetYBytes = 4 - frameUnknownBytes = 4 - frameNextBlockBytes = 4 - frameLengthBytes = 4 - ) - - numDirections, _ := stream.Next(directionsBytes).Bytes().AsUInt32() - framesPerDirection, err := stream.Next(framesPerDirectionBytes).Bytes().AsUInt32() - totalFrames := int(numDirections * framesPerDirection) - - d.Directions = make([]*Direction, numDirections) - - for i := 0; i < totalFrames; i++ { - if _, err = stream.Next(framePointerBytes).Bytes().AsUInt32(); err != nil { - return err - } +func (d *DC6) loadHeader(r *bitstream.Reader) error { + var err error + + r.Next(bytesPerInt32) // set readed data size to 4 bytes + + version, err := r.Bytes().AsInt32() + if err != nil { + return fmt.Errorf("reading version: %w", err) } - for idx := 0; idx < totalFrames; idx++ { - dirIdx := idx / int(framesPerDirection) - frameIdx := idx % int(framesPerDirection) + if version != expectedDC6Version { + return errors.New("unexpected dc6 version") + } - if d.Directions[dirIdx] == nil { - d.Directions[dirIdx] = &Direction{} - } + if d.Flags, err = r.Bytes().AsUInt32(); err != nil { + return fmt.Errorf("reading flags: %w", err) + } - if d.Directions[dirIdx].Frames == nil { - d.Directions[dirIdx].Frames = make([]*Frame, framesPerDirection) - } + if d.Encoding, err = r.Bytes().AsUInt32(); err != nil { + return fmt.Errorf("reading encoding type: %w", err) + } - frame := &Frame{dc6: d} - - // toss the errors, only check last err - frame.Flipped, _ = stream.Next(frameFlippedBytes).Bytes().AsUInt32() - frame.Width, _ = stream.Next(frameWidthBytes).Bytes().AsUInt32() - frame.Height, _ = stream.Next(frameHeightBytes).Bytes().AsUInt32() - frame.OffsetX, _ = stream.Next(frameOffsetXBytes).Bytes().AsInt32() - frame.OffsetY, _ = stream.Next(frameOffsetYBytes).Bytes().AsInt32() - frame.Unknown, _ = stream.Next(frameUnknownBytes).Bytes().AsUInt32() - frame.NextBlock, _ = stream.Next(frameNextBlockBytes).Bytes().AsUInt32() - frame.Length, _ = stream.Next(frameLengthBytes).Bytes().AsUInt32() - frame.FrameData, _ = stream.Next(int(frame.Length)).Bytes().AsBytes() - frame.Terminator, err = stream.Next(terminatorSize).Bytes().AsBytes() - - if err != nil { - return fmt.Errorf("could not decode body, %w", err) - } + termination, err := r.Next(terminationSize).Bytes().AsBytes() + if err != nil { + return fmt.Errorf("reading termination: %w", err) + } + + copy(d.Termination[:], termination) + + r.Next(bytesPerInt32) // set readed data size to 4 bytes - d.Directions[dirIdx].Frames[frameIdx] = frame + directions, err := r.Bytes().AsUInt32() + if err != nil { + return fmt.Errorf("reading directions number: %w", err) } - for idx := range d.Directions { - d.Directions[idx].decodeFrames() + d.Frames.SetNumberOfDirections(int(directions)) + + framesPerDirection, err := r.Bytes().AsUInt32() + if err != nil { + return fmt.Errorf("error reading a number of frames per direction: %w", err) } + d.Frames.SetFramesPerDirection(int(framesPerDirection)) + return nil } -// decodeFrame decodes the given frame to an indexed color texture -func (d *Direction) decodeFrames() { - for idx := range d.Frames { - d.decodeFrame(idx) +func (d *DC6) loadFrames(r *bitstream.Reader) (err error) { + for dir := 0; dir < d.Frames.NumberOfDirections(); dir++ { + for f := 0; f < d.Frames.FramesPerDirection(); f++ { + err = d.Frames.Direction(dir).Frame(f).Load(r, &d.palette) + if err != nil { + return fmt.Errorf("error loading frame %d at direction %d: %w", f, dir, err) + } + } } -} -func (d *Direction) decodeFrame(frameIndex int) { - frame := d.Frames[frameIndex] + return nil +} - indexData := make([]byte, frame.Width*frame.Height) - x := 0 - y := int(frame.Height) - 1 - offset := 0 +/* TODO: rewrite to use gravestench/bitstream +// Encode encodes dc6 animation back into byte slice +func (d *DC6) Encode() []byte { + sw := d2datautils.CreateStreamWriter() -loop: // this is a label for the loop, so the switch can break the loop (and not the switch) - for { - b := int(frame.FrameData[offset]) - offset++ + // Encode header + sw.PushInt32(expectedDC6Version) + sw.PushUint32(d.Flags) + sw.PushUint32(d.Encoding) - switch scanlineType(b) { - case endOfLine: - if y == 0 { - break loop - } + sw.PushBytes(d.Termination[:]...) - y-- + sw.PushUint32(uint32(d.Frames.NumberOfDirections())) + sw.PushUint32(uint32(d.Frames.FramesPerDirection())) - x = 0 - case runOfTransparentPixels: - transparentPixels := b & maxRunLength - x += transparentPixels - case runOfOpaquePixels: - for i := 0; i < b; i++ { - indexData[x+y*int(frame.Width)+i] = frame.FrameData[offset] - offset++ - } + numDirs := d.Frames.NumberOfDirections() + fpd := d.Frames.FramesPerDirection() - x += b + // encode frames + framesData := make([][][]byte, numDirs) + for dir := 0; dir < numDirs; dir++ { + framesData[dir] = make([][]byte, fpd) + for f := 0; f < fpd; f++ { + framesData[dir][f] = d.Frames.Direction(dir).Frame(f).Encode() } } - frame.IndexData = indexData -} + // current position in stream - terrible workaround, + // but d2datautils.StreamWriter doesn't currently hav any + // method to get byte position + currentPosition := 24 + + // frames data starts afte a frame pointers section + currentPosition += numDirs * fpd * bytesPerInt32 -func scanlineType(b int) scanlineState { - if b == endOfScanLine { - return endOfLine + // encode frame pointers + for dir := 0; dir < numDirs; dir++ { + for f := 0; f < fpd; f++ { + sw.PushUint32(uint32(currentPosition)) + currentPosition += len(framesData[dir][f]) + } } - if (b & endOfScanLine) > 0 { - return runOfTransparentPixels + for _, dirData := range framesData { + for _, frameData := range dirData { + sw.PushBytes(frameData...) + } } - return runOfOpaquePixels + return sw.GetBytes() } +*/ // Clone creates a copy of the DC6 func (d *DC6) Clone() *DC6 { clone := *d - - copy(clone.Termination, d.Termination) - - clone.Directions = make([]*Direction, len(d.Directions)) - for dirIdx := range d.Directions { - clone.Directions[dirIdx].Frames = make([]*Frame, len(d.Directions[dirIdx].Frames)) - for frameIdx := range d.Directions[dirIdx].Frames { - frame := *d.Directions[dirIdx].Frames[frameIdx] - clone.Directions[dirIdx].Frames[frameIdx] = &frame - } - } + clone.Frames = d.Frames.Clone() return &clone } diff --git a/pkg/dc6_test.go b/pkg/dc6_test.go new file mode 100644 index 0000000..1c77e3e --- /dev/null +++ b/pkg/dc6_test.go @@ -0,0 +1,102 @@ +package pkg + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/OpenDiablo2/dc6/pkg/frames" +) + +func TestDC6New(t *testing.T) { + if dc6 := New(); dc6 == nil { + t.Error("d2dc6.New() method returned nil") + } +} + +func getExampleDC6() *DC6 { + exampleDC6 := &DC6{ + Flags: 1, + Encoding: 0, + Termination: [terminationSize]byte{238, 238, 238, 238}, + Frames: frames.New(), + } + + exampleDC6.Frames.SetNumberOfDirections(1) + exampleDC6.Frames.SetFramesPerDirection(1) + /* + grid: { + { + Flipped: 0, + Width: 32, + Height: 26, + OffsetX: 45, + OffsetY: 24, + Unknown: 0, + NextBlock: 50, + FrameData: []byte{2, 23, 34, 128, 53, 64, 39, 43, 123, 12}, + Terminator: []byte{2, 8, 5}, + }, + { + Flipped: 0, + Width: 62, + Height: 36, + OffsetX: 15, + OffsetY: 28, + Unknown: 0, + NextBlock: 35, + FrameData: []byte{9, 33, 89, 148, 64, 64, 49, 81, 221, 19}, + Terminator: []byte{3, 7, 5}, + }, + }, + { + { + Flipped: 0, + Width: 62, + Height: 36, + OffsetX: 15, + OffsetY: 28, + Unknown: 0, + NextBlock: 35, + FrameData: []byte{9, 33, 89, 148, 64, 64, 49, 81, 121, 19}, + Terminator: []byte{3, 7, 5}, + }, + { + Flipped: 0, + Width: 32, + Height: 26, + OffsetX: 45, + OffsetY: 24, + Unknown: 0, + NextBlock: 50, + FrameData: []byte{2, 23, 34, 128, 53, 64, 39, 43, 123, 12}, + Terminator: []byte{2, 8, 5}, + }, + }, + }, + */ + + return exampleDC6 +} + +/* TODO: activate this test (fix Encode method) +func TestDC6Unmarshal(t *testing.T) { + exampleDC6 := getExampleDC6() + + data := exampleDC6.Encode() + + extractedDC6, err := Load(data) + if err != nil { + t.Error(err) + } + + assert.Equal(t, exampleDC6, extractedDC6, "encoded and decoded dc6 isn't equal") +} +*/ + +func TestDC6Clone(t *testing.T) { + exampleDC6 := getExampleDC6() + clonedDC6 := exampleDC6.Clone() + + assert.Equal(t, exampleDC6, clonedDC6, "cloned dc6 isn't equal to base") +} diff --git a/pkg/doc.go b/pkg/doc.go index 29c9192..1f9b9e3 100644 --- a/pkg/doc.go +++ b/pkg/doc.go @@ -1,2 +1,2 @@ -// Package d2dc6 contains the logic for loading and processing DC6 files. +// Package pkg provides a DC6 animation decoder, used by diablo 2. package pkg diff --git a/pkg/frame.go b/pkg/frame.go deleted file mode 100644 index 3b4c009..0000000 --- a/pkg/frame.go +++ /dev/null @@ -1,68 +0,0 @@ -package pkg - -import ( - "image" - "image/color" -) - -var _ image.PalettedImage = &Frame{} - -// Frame represents a single frame in a DC6. -type Frame struct { - dc6 *DC6 - Flipped uint32 - Width uint32 - Height uint32 - OffsetX int32 - OffsetY int32 - Unknown uint32 - NextBlock uint32 - Length uint32 - FrameData []byte // size is the value of Length - Terminator []byte // 3 bytes - IndexData []byte -} - -func (f *Frame) ColorIndexAt(x, y int) uint8 { - idx := (y * int(f.Width)) + x - - return f.IndexData[idx] -} - -func (f *Frame) ColorModel() color.Model { - return color.RGBAModel -} - -func (f *Frame) Bounds() image.Rectangle { - origin := image.Point{X: int(f.OffsetX), Y: int(f.OffsetY)} - delta := image.Point{X: int(f.Width), Y: int(f.Height)} - - return image.Rectangle{ - Min: origin, - Max: origin.Add(delta), - } -} - -func (f *Frame) At(x, y int) color.Color { - if f.dc6.palette == nil { - f.dc6.SetPalette(nil) - } - - cidx := f.ColorIndexAt(x, y) - - return f.dc6.palette[cidx] -} - -func (f *Frame) ToImageRGBA() *image.RGBA { - img := image.NewRGBA(image.Rectangle{ - Max: f.Bounds().Size(), - }) - - for py := 0; py < int(f.Height); py++ { - for px := 0; px < int(f.Width); px++ { - img.Set(px, py, f.At(px, py)) - } - } - - return img -} diff --git a/pkg/frames/doc.go b/pkg/frames/doc.go new file mode 100644 index 0000000..ffea3d5 --- /dev/null +++ b/pkg/frames/doc.go @@ -0,0 +1,2 @@ +// Package frames process dc6 frame data +package frames diff --git a/pkg/frames/frame.go b/pkg/frames/frame.go new file mode 100644 index 0000000..4467c00 --- /dev/null +++ b/pkg/frames/frame.go @@ -0,0 +1,154 @@ +package frames + +import ( + "fmt" + "image" + "image/color" + + "github.com/OpenDiablo2/bitstream" +) + +const ( + bytesPerInt32 = 4 + terminatorSize = 3 +) + +func newFrame() *Frame { + return &Frame{ + FrameData: make([]byte, 0), + Terminator: make([]byte, terminatorSize), + } +} + +// Frame represents a single frame in a DC6. +type Frame struct { + palette *color.Palette + FrameData []byte + Terminator []byte + IndexData []byte + Flipped uint32 + OffsetY int32 + Unknown uint32 + NextBlock uint32 + Width uint32 + Height uint32 + OffsetX int32 +} + +// Load loads frame data +func (f *Frame) Load(r *bitstream.Reader, palette *color.Palette) error { + f.palette = palette + + var err error + + r.Next(bytesPerInt32) // set bytes len to uint32 + + if f.Flipped, err = r.Bytes().AsUInt32(); err != nil { + return fmt.Errorf("reading flipped: %w", err) + } + + if f.Width, err = r.Bytes().AsUInt32(); err != nil { + return fmt.Errorf("reading width: %w", err) + } + + if f.Height, err = r.Bytes().AsUInt32(); err != nil { + return fmt.Errorf("reading height: %w", err) + } + + if f.OffsetX, err = r.Bytes().AsInt32(); err != nil { + return fmt.Errorf("reading x-offset: %w", err) + } + + if f.OffsetY, err = r.Bytes().AsInt32(); err != nil { + return fmt.Errorf("reading y-offset: %w", err) + } + + if f.Unknown, err = r.Bytes().AsUInt32(); err != nil { + return fmt.Errorf("reading frame unknown: %w", err) + } + + if f.NextBlock, err = r.Bytes().AsUInt32(); err != nil { + return fmt.Errorf("reading next block: %w", err) + } + + l, err := r.Bytes().AsUInt32() + if err != nil { + return fmt.Errorf("reading length of frame data: %w", err) + } + + if f.FrameData, err = r.Next(int(l)).Bytes().AsBytes(); err != nil { + return fmt.Errorf("reading frame data: %w", err) + } + + if f.Terminator, err = r.Next(terminatorSize).Bytes().AsBytes(); err != nil { + return fmt.Errorf("reading terminator: %w", err) + } + + f.decodeFrame() + + return nil +} + +/* TODO: rewrite to use gravestench/bitstream +// Encode encodes frame data into a byte slice +func (f *Frame) Encode() []byte { + sw := d2datautils.CreateStreamWriter() + sw.PushUint32(f.Flipped) + sw.PushUint32(f.Width) + sw.PushUint32(f.Height) + sw.PushInt32(f.OffsetX) + sw.PushInt32(f.OffsetY) + sw.PushUint32(f.Unknown) + sw.PushUint32(f.NextBlock) + sw.PushUint32(uint32(len(f.FrameData))) + sw.PushBytes(f.FrameData...) + sw.PushBytes(f.Terminator...) + + return sw.GetBytes() +} +*/ + +// ColorIndexAt returns color value at given x,y +func (f *Frame) ColorIndexAt(x, y int) uint8 { + idx := (y * int(f.Width)) + x + + return f.IndexData[idx] +} + +// ColorModel implements image.Image +func (f *Frame) ColorModel() color.Model { + return color.RGBAModel +} + +// Bounds returns frame bounds +func (f *Frame) Bounds() image.Rectangle { + origin := image.Point{X: int(f.OffsetX), Y: int(f.OffsetY)} + delta := image.Point{X: int(f.Width), Y: int(f.Height)} + + return image.Rectangle{ + Min: origin, + Max: origin.Add(delta), + } +} + +// At implements image.Image +func (f *Frame) At(x, y int) color.Color { + cidx := f.ColorIndexAt(x, y) + + return (*f.palette)[cidx] +} + +// ToImageRGBA converts frame to image.RGBA +func (f *Frame) ToImageRGBA() *image.RGBA { + img := image.NewRGBA(image.Rectangle{ + Max: f.Bounds().Size(), + }) + + for py := 0; py < int(f.Height); py++ { + for px := 0; px < int(f.Width); px++ { + img.Set(px, py, f.At(px, py)) + } + } + + return img +} diff --git a/pkg/frames/frame_decoder.go b/pkg/frames/frame_decoder.go new file mode 100644 index 0000000..6ca276c --- /dev/null +++ b/pkg/frames/frame_decoder.go @@ -0,0 +1,61 @@ +package frames + +type scanlineState int + +const ( + endOfLine scanlineState = iota + runOfTransparentPixels + runOfOpaquePixels + + endOfScanLine = 0x80 + maxRunLength = 0x7f +) + +// decodeFrame decodes the given frame to an indexed color texture +func (f *Frame) decodeFrame() { + indexData := make([]byte, f.Width*f.Height) + x := 0 + y := int(f.Height) - 1 + offset := 0 + +loop: // this is a label for the loop, so the switch can break the loop (and not the switch) + for { + b := int(f.FrameData[offset]) + offset++ + + switch scanlineType(b) { + case endOfLine: + if y == 0 { + break loop + } + + y-- + + x = 0 + case runOfTransparentPixels: + transparentPixels := b & maxRunLength + x += transparentPixels + case runOfOpaquePixels: + for i := 0; i < b; i++ { + indexData[x+y*int(f.Width)+i] = f.FrameData[offset] + offset++ + } + + x += b + } + } + + f.IndexData = indexData +} + +func scanlineType(b int) scanlineState { + if b == endOfScanLine { + return endOfLine + } + + if (b & endOfScanLine) > 0 { + return runOfTransparentPixels + } + + return runOfOpaquePixels +} diff --git a/pkg/frames/frames.go b/pkg/frames/frames.go new file mode 100644 index 0000000..de1b66a --- /dev/null +++ b/pkg/frames/frames.go @@ -0,0 +1,101 @@ +package frames + +// New creates a new frame grid +func New() *Frames { + return &Frames{ + numberOfDirections: 0, + framesPerDirection: 0, + grid: make([]Direction, 0), + } +} + +// Frames represents a grid of frames [directions][framesPerDirection] +type Frames struct { + grid []Direction + numberOfDirections, + framesPerDirection int +} + +// NumberOfDirections returns a number of directions in grid +func (f *Frames) NumberOfDirections() int { + return f.numberOfDirections +} + +// SetNumberOfDirections sets a number of directions +func (f *Frames) SetNumberOfDirections(n int) { + if n == f.numberOfDirections { + return + } + + if n > f.numberOfDirections { + for i := 0; i < n-f.framesPerDirection; i++ { + f.grid = append(f.grid, make(Direction, f.framesPerDirection)) + } + + f.numberOfDirections = n + + return + } + + f.grid = f.grid[:n] + + f.numberOfDirections = n +} + +// FramesPerDirection returns a number of frames per each direction +func (f *Frames) FramesPerDirection() int { + return f.framesPerDirection +} + +// SetFramesPerDirection sets a number of frames per direction +func (f *Frames) SetFramesPerDirection(n int) { + if n == f.framesPerDirection { + return + } + + for i := range f.grid { + if l := len(f.grid[i]); n > l { + for j := 0; j < n-l; j++ { + f.grid[i] = append(f.grid[i], newFrame()) + } + } + } + + f.framesPerDirection = n +} + +// Direction returns a specified direction +func (f *Frames) Direction(d int) Direction { + if d > len(f.grid) { + return nil + } + + return f.grid[d] +} + +// Clone clones frame grid +func (f *Frames) Clone() *Frames { + clone := &Frames{} + clone.SetNumberOfDirections(f.numberOfDirections) + clone.SetFramesPerDirection(f.framesPerDirection) + + for dir := range clone.grid { + for frame := range clone.grid[dir] { + *clone.grid[dir][frame] = *f.grid[dir][frame] + } + } + + return clone +} + +// Direction represents a frame set +type Direction []*Frame + +// Frame returns a specified frame, if f > FramesPerDirection, returns nil +func (d Direction) Frame(f int) *Frame { + if f > len(d) { + return nil + } + + return d[f] +} diff --git a/pkg/giuwidget/doc.go b/pkg/giuwidget/doc.go index 2435604..de36638 100644 --- a/pkg/giuwidget/doc.go +++ b/pkg/giuwidget/doc.go @@ -1,3 +1,3 @@ -// Package giu_widget provides a giu.Widget implementation for viewing and editing +// Package giuwidget provides a giu.Widget implementation for viewing and editing // the DC6 data structure. package giuwidget diff --git a/pkg/giuwidget/frame_viewer.go b/pkg/giuwidget/frame_viewer.go index b4e243b..30434f8 100644 --- a/pkg/giuwidget/frame_viewer.go +++ b/pkg/giuwidget/frame_viewer.go @@ -7,9 +7,10 @@ import ( "github.com/AllenDang/giu" - dc6 "github.com/gravestench/dc6/pkg" + dc6 "github.com/OpenDiablo2/dc6/pkg" ) +// FrameViewer creates frame viewer func FrameViewer(id string, d *dc6.DC6) *FrameViewerDC6 { return &FrameViewerDC6{ id: id, @@ -21,39 +22,43 @@ func FrameViewer(id string, d *dc6.DC6) *FrameViewerDC6 { var _ giu.Widget = &FrameViewerDC6{} type frameViewerState struct { - direction, frame int - scale float64 - images []*image.RGBA - textures []*giu.Texture + images []*image.RGBA + textures []*giu.Texture + + frame int32 + direction int32 + + scale float64 } func (fvs *frameViewerState) Dispose() { // noop } +// FrameViewerDC6 represents a dc6 frame viewer type FrameViewerDC6 struct { - id string - dc6 *dc6.DC6 - state *frameViewerState textureLoader TextureLoader + dc6 *dc6.DC6 + id string } -func (p *FrameViewerDC6) Build() { +// Build implements giu.Widget +func (fv *FrameViewerDC6) Build() { const ( imageW, imageH = 10, 10 ) - p.textureLoader.ResumeLoadingTextures() - p.textureLoader.ProcessTextureLoadRequests() + fv.textureLoader.ResumeLoadingTextures() + fv.textureLoader.ProcessTextureLoadRequests() - viewerState := p.getState() + viewerState := fv.getState() imageScale := viewerState.scale - dirIdx := 0 - frameIdx := 0 + dirIdx := int(viewerState.direction) + frameIdx := int(viewerState.frame) - textureIdx := dirIdx*len(p.dc6.Directions[dirIdx].Frames) + frameIdx + textureIdx := dirIdx*fv.dc6.Frames.FramesPerDirection() + frameIdx err := giu.Context.GetRenderer().SetTextureMagFilter(giu.TextureFilterNearest) if err != nil { @@ -62,56 +67,34 @@ func (p *FrameViewerDC6) Build() { var frameImage *giu.ImageWidget - if viewerState.textures == nil || len(viewerState.textures) <= int(frameIdx) || viewerState.textures[frameIdx] == nil { + if viewerState.textures == nil || len(viewerState.textures) <= frameIdx || viewerState.textures[frameIdx] == nil { frameImage = giu.Image(nil).Size(imageW, imageH) } else { - bw := p.dc6.Directions[dirIdx].Frames[frameIdx].Width - bh := p.dc6.Directions[dirIdx].Frames[frameIdx].Height + bw := fv.dc6.Frames.Direction(dirIdx).Frame(frameIdx).Width + bh := fv.dc6.Frames.Direction(dirIdx).Frame(frameIdx).Height w := float32(float64(bw) * imageScale) h := float32(float64(bh) * imageScale) frameImage = giu.Image(viewerState.textures[textureIdx]).Size(w, h) } - //numDirections := len(p.dc6.Directions) - //numFrames := len(p.dc6.Directions[0].Frames) - - giu.Layout{frameImage}.Build() + numDirections := fv.dc6.Frames.NumberOfDirections() + numFrames := fv.dc6.Frames.FramesPerDirection() + + giu.Layout{ + giu.Custom(func() { + if numDirections > 1 { + giu.SliderInt("direction", &viewerState.direction, 0, int32(numDirections-1)).Build() + } + }), + giu.Custom(func() { + if numFrames > 1 { + giu.SliderInt("frame", &viewerState.frame, 0, int32(numFrames-1)).Build() + } + }), + frameImage, + }.Build() } -//func (fv *FrameViewerDC6) Build() { -// s := fv.getState() -// if s == nil { -// return -// } -// -// fv.state = s -// -// absIndex := (len(fv.dc6.Directions) * s.direction) + s.frame -// -// frame := fv.dc6.Directions[s.direction].Frames[s.frame] -// w, h := float32(float64(frame.Width)*s.scale), float32(float64(frame.Height)*s.scale) -// -// layout := giu.Layout{ -// giu.Custom(func() { -// if s.textures == nil { -// return -// } -// -// if len(s.textures) <= absIndex { -// return -// } -// -// giu.Image(s.textures[absIndex]).Size(w, h) -// }), -// giu.Dummy(w, h), -// } -// -// fv.ResumeLoadingTextures() -// fv.ProcessTextureLoadRequests() -// -// layout.Build() -//} - func (fv *FrameViewerDC6) getStateID() string { return fmt.Sprintf("widget_%s", fv.id) } @@ -131,7 +114,8 @@ func (fv *FrameViewerDC6) getState() *frameViewerState { return state } -func (fv *FrameViewerDC6) SetScale(scale float64) { +// SetScale sets image scale +func (fv *FrameViewerDC6) SetScale(scale float64) *FrameViewerDC6 { s := fv.getState() if scale <= 0 { @@ -140,30 +124,9 @@ func (fv *FrameViewerDC6) SetScale(scale float64) { s.scale = scale - fv.setState(s) + return fv } func (fv *FrameViewerDC6) setState(s giu.Disposable) { giu.Context.SetState(fv.getStateID(), s) } - -func dirLookup(dir, numDirs int) int { - d4 := []int{0, 1, 2, 3} - d8 := []int{0, 5, 1, 6, 2, 7, 3, 4} - d16 := []int{0, 9, 5, 10, 1, 11, 6, 12, 2, 13, 7, 14, 3, 15, 4, 8} - - lookup := []int{0} - - switch numDirs { - case 4: - lookup = d4 - case 8: - lookup = d8 - case 16: - lookup = d16 - default: - dir = 0 - } - - return lookup[dir] -} diff --git a/pkg/giuwidget/state.go b/pkg/giuwidget/state.go index 1746ebd..4e2736f 100644 --- a/pkg/giuwidget/state.go +++ b/pkg/giuwidget/state.go @@ -13,19 +13,19 @@ func (fv *FrameViewerDC6) initState() { fv.setState(state) - numDirections := len(fv.dc6.Directions) - numFrames := len(fv.dc6.Directions[0].Frames) + numDirections := fv.dc6.Frames.NumberOfDirections() + numFrames := fv.dc6.Frames.FramesPerDirection() totalFrames := numDirections * numFrames state.images = make([]*image.RGBA, totalFrames) - for dirIdx := range fv.dc6.Directions { - for frameIdx := range fv.dc6.Directions[dirIdx].Frames { - fw := int(fv.dc6.Directions[dirIdx].Frames[frameIdx].Width) - fh := int(fv.dc6.Directions[dirIdx].Frames[frameIdx].Height) + for dirIdx := 0; dirIdx < fv.dc6.Frames.NumberOfDirections(); dirIdx++ { + for frameIdx := 0; frameIdx < fv.dc6.Frames.FramesPerDirection(); frameIdx++ { + fw := int(fv.dc6.Frames.Direction(dirIdx).Frame(frameIdx).Width) + fh := int(fv.dc6.Frames.Direction(dirIdx).Frame(frameIdx).Height) absoluteFrameIdx := (dirIdx * numFrames) + frameIdx - frame := fv.dc6.Directions[dirIdx].Frames[frameIdx] + frame := fv.dc6.Frames.Direction(dirIdx).Frame(frameIdx) pixels := frame.IndexData if state.images[absoluteFrameIdx] == nil { diff --git a/pkg/giuwidget/texture_loader.go b/pkg/giuwidget/texture_loader.go index b6c6224..9d18f5d 100644 --- a/pkg/giuwidget/texture_loader.go +++ b/pkg/giuwidget/texture_loader.go @@ -32,9 +32,9 @@ type TextureLoader interface { // textureLoader allows app to load textures and avoid unexpected panics type textureLoader struct { - canLoadTextures bool mutex *sync.Mutex loadQueue *goconcurrentqueue.FIFO + canLoadTextures bool } // newTextureLoader creates a new texture loader