assets builder tools (#39)
Reviewed-on: #39 Reviewed-by: xtul <mpiasecki720@protonmail.com>
This commit was merged in pull request #39.
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
package assets
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var textureExts = map[string]bool{".dds": true, ".png": true, ".tga": true}
|
||||
|
||||
// runConvert converts textures in place, flipping vertically exactly once per
|
||||
// conversion. --to defaults to dds. Files already in the target format are
|
||||
// skipped.
|
||||
func runConvert(args []string, stdout, stderr io.Writer) int {
|
||||
fs := flag.NewFlagSet("convert", flag.ContinueOnError)
|
||||
fs.SetOutput(stderr)
|
||||
to := fs.String("to", "dds", "target format: dds|png|tga")
|
||||
backend := fs.String("backend", "magick", "ImageMagick-compatible binary")
|
||||
nonRecursive := fs.Bool("non-recursive", false, "do not descend into subdirectories")
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return exitUsage
|
||||
}
|
||||
target := strings.ToLower(*to)
|
||||
if target != "dds" && target != "png" && target != "tga" {
|
||||
fmt.Fprintln(stderr, "assets convert: --to must be dds, png, or tga")
|
||||
return exitUsage
|
||||
}
|
||||
dirs := fs.Args()
|
||||
if len(dirs) == 0 {
|
||||
fmt.Fprintln(stderr, "assets convert: usage: convert [--to dds|png|tga] <dir>...")
|
||||
return exitUsage
|
||||
}
|
||||
|
||||
magick := look(*backend)
|
||||
if magick == "" {
|
||||
fmt.Fprintf(stderr, "assets convert: backend %q not found — install ImageMagick (magick)\n", *backend)
|
||||
return exitTool
|
||||
}
|
||||
|
||||
files, err := walk(dirs, textureExts, !*nonRecursive)
|
||||
if err != nil {
|
||||
fmt.Fprintln(stderr, "assets convert:", err)
|
||||
return exitUsage
|
||||
}
|
||||
|
||||
targetExt := "." + target
|
||||
failed := false
|
||||
for _, src := range files {
|
||||
srcExt := strings.ToLower(filepath.Ext(src))
|
||||
if srcExt == targetExt {
|
||||
continue // already in the target format
|
||||
}
|
||||
dst := strings.TrimSuffix(src, filepath.Ext(src)) + targetExt
|
||||
var convErr error
|
||||
if target == "dds" {
|
||||
convErr = pngToDDS(magick, src, dst)
|
||||
} else {
|
||||
convErr = ddsToPNG(magick, src, dst) // works for any decodable source
|
||||
}
|
||||
if convErr != nil {
|
||||
fmt.Fprintf(stderr, "assets convert: %s: %v\n", src, convErr)
|
||||
failed = true
|
||||
continue
|
||||
}
|
||||
if dst != src {
|
||||
if err := os.Remove(src); err != nil {
|
||||
fmt.Fprintf(stderr, "assets convert: %s: %v\n", src, err)
|
||||
failed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if failed {
|
||||
return exitFail
|
||||
}
|
||||
return exitOK
|
||||
}
|
||||
|
||||
// ddsToPNG decodes src to dst (PNG/TGA by dst's extension), flipping vertically
|
||||
// once. The single flip is the defining NWN behavior.
|
||||
func ddsToPNG(magickBin, src, dst string) error {
|
||||
if out, err := runner("", nil, magickBin, src, "-flip", dst); err != nil {
|
||||
return fmt.Errorf("magick: %v: %s", err, strings.TrimSpace(string(out)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// pngToDDS encodes src to a DDS at dst, flipping vertically once and choosing
|
||||
// DXT1 for opaque images, DXT5 for images with alpha.
|
||||
func pngToDDS(magickBin, src, dst string) error {
|
||||
compression := "dxt1"
|
||||
if hasAlpha(magickBin, src) {
|
||||
compression = "dxt5"
|
||||
}
|
||||
// ponytail: mipmaps rely on magick's default full chain (NWN wants a
|
||||
// pyramid, which magick writes by default). Add `-define dds:mipmaps=N`
|
||||
// here if a specific count is ever required.
|
||||
out, err := runner("", nil, magickBin, src, "-flip",
|
||||
"-define", "dds:compression="+compression, dst)
|
||||
if err != nil {
|
||||
return fmt.Errorf("magick: %v: %s", err, strings.TrimSpace(string(out)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// hasAlpha reports whether src has any non-opaque pixel, via `magick identify`.
|
||||
// On any probe error it assumes alpha (DXT5), the safe/lossless-alpha default.
|
||||
func hasAlpha(magickBin, src string) bool {
|
||||
out, err := runner("", nil, magickBin, "identify", "-format", "%[opaque]", src)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
return !strings.EqualFold(strings.TrimSpace(string(out)), "true")
|
||||
}
|
||||
Reference in New Issue
Block a user