feat(assets): seven asset commands + Run dispatch/helpers
check-dupes, clean-dupes, check-mdl, fix-mdl, convert, upscale, compile behind a single runner indirection, mirroring internal/depot's Run shape. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
package assets
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/png"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// writeTestPNG writes a 16x16 image: top half red, bottom half blue. The size
|
||||
// and the half-way split keep every 4x4 DXT block a single flat color, so the
|
||||
// DXT1 round trip stays lossless and only the flip is under test. (A 4x4 image
|
||||
// is one mixed DXT block that magick's encoder collapses to a single color.)
|
||||
func writeTestPNG(t *testing.T, path string) {
|
||||
t.Helper()
|
||||
const n = 16
|
||||
img := image.NewRGBA(image.Rect(0, 0, n, n))
|
||||
for y := 0; y < n; y++ {
|
||||
c := color.RGBA{255, 0, 0, 255} // red
|
||||
if y >= n/2 {
|
||||
c = color.RGBA{0, 0, 255, 255} // blue
|
||||
}
|
||||
for x := 0; x < n; x++ {
|
||||
img.Set(x, y, c)
|
||||
}
|
||||
}
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
if err := png.Encode(f, img); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func topRowIsBlue(t *testing.T, pngPath string) bool {
|
||||
t.Helper()
|
||||
f, err := os.Open(pngPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
img, err := png.Decode(f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
r, _, b, _ := img.At(0, 0).RGBA()
|
||||
return b > r // blue dominates the top-left after a flip
|
||||
}
|
||||
|
||||
func TestConvertFlipsOnceAndRoundTrips(t *testing.T) {
|
||||
magick := look("magick")
|
||||
if magick == "" {
|
||||
t.Skip("magick not on PATH")
|
||||
}
|
||||
dir := t.TempDir()
|
||||
src := filepath.Join(dir, "tex.png")
|
||||
writeTestPNG(t, src)
|
||||
|
||||
// Convert PNG -> DDS (in place: tex.png becomes tex.dds, original removed).
|
||||
var out, errw bytes.Buffer
|
||||
if code := runConvert([]string{"--to", "dds", dir}, &out, &errw); code != exitOK {
|
||||
t.Fatalf("to-dds exit = %d\n%s", code, errw.String())
|
||||
}
|
||||
dds := filepath.Join(dir, "tex.dds")
|
||||
if _, err := os.Stat(dds); err != nil {
|
||||
t.Fatalf("dds not produced: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(src); !os.IsNotExist(err) {
|
||||
t.Fatal("source png was not replaced")
|
||||
}
|
||||
|
||||
// Decode the DDS RAW (no extra flip) and confirm a single flip happened:
|
||||
// the source top row was red, so the DDS top row must now be blue.
|
||||
raw := filepath.Join(dir, "raw.png")
|
||||
if err := exec.Command(magick, dds, raw).Run(); err != nil {
|
||||
t.Fatalf("raw decode: %v", err)
|
||||
}
|
||||
if !topRowIsBlue(t, raw) {
|
||||
t.Fatal("expected the DDS to be vertically flipped vs the source")
|
||||
}
|
||||
|
||||
// Convert DDS -> PNG (another flip). Result should match the original.
|
||||
out.Reset()
|
||||
errw.Reset()
|
||||
if code := runConvert([]string{"--to", "png", dir}, &out, &errw); code != exitOK {
|
||||
t.Fatalf("to-png exit = %d\n%s", code, errw.String())
|
||||
}
|
||||
restored := filepath.Join(dir, "tex.png")
|
||||
if topRowIsBlue(t, restored) {
|
||||
t.Fatal("round trip did not restore the original orientation")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertMissingBackendFailsClosed(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
writeTestPNG(t, filepath.Join(dir, "tex.png"))
|
||||
var out, errw bytes.Buffer
|
||||
code := runConvert([]string{"--backend", "/nonexistent/magick", dir}, &out, &errw)
|
||||
if code != exitTool {
|
||||
t.Fatalf("missing backend exit = %d, want %d", code, exitTool)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user