| // Copyright 2014 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| //go:build ignore |
| // +build ignore |
| |
| // This build tag means that "go install golang.org/x/image/..." doesn't |
| // install this manual test. Use "go run main.go" to explicitly run it. |
| |
| // Program webp-manual-test checks that the Go WEBP library's decodings match |
| // the C WEBP library's. |
| package main // import "golang.org/x/image/cmd/webp-manual-test" |
| |
| import ( |
| "bytes" |
| "encoding/hex" |
| "flag" |
| "fmt" |
| "image" |
| "io" |
| "log" |
| "os" |
| "os/exec" |
| "path/filepath" |
| "sort" |
| "strings" |
| |
| "golang.org/x/image/webp" |
| ) |
| |
| var ( |
| dwebp = flag.String("dwebp", "/usr/bin/dwebp", "path to the dwebp program "+ |
| "installed from https://developers.google.com/speed/webp/download") |
| testdata = flag.String("testdata", "", "path to the libwebp-test-data directory "+ |
| "checked out from https://chromium.googlesource.com/webm/libwebp-test-data") |
| ) |
| |
| func main() { |
| flag.Parse() |
| if err := checkDwebp(); err != nil { |
| flag.Usage() |
| log.Fatal(err) |
| } |
| if *testdata == "" { |
| flag.Usage() |
| log.Fatal("testdata flag was not specified") |
| } |
| |
| f, err := os.Open(*testdata) |
| if err != nil { |
| log.Fatalf("Open: %v", err) |
| } |
| defer f.Close() |
| names, err := f.Readdirnames(-1) |
| if err != nil { |
| log.Fatalf("Readdirnames: %v", err) |
| } |
| sort.Strings(names) |
| |
| nFail, nPass := 0, 0 |
| for _, name := range names { |
| if !strings.HasSuffix(name, "webp") { |
| continue |
| } |
| if err := test(name); err != nil { |
| fmt.Printf("FAIL\t%s\t%v\n", name, err) |
| nFail++ |
| } else { |
| fmt.Printf("PASS\t%s\n", name) |
| nPass++ |
| } |
| } |
| fmt.Printf("%d PASS, %d FAIL, %d TOTAL\n", nPass, nFail, nPass+nFail) |
| if nFail != 0 { |
| os.Exit(1) |
| } |
| } |
| |
| func checkDwebp() error { |
| if *dwebp == "" { |
| return fmt.Errorf("dwebp flag was not specified") |
| } |
| if _, err := os.Stat(*dwebp); err != nil { |
| return fmt.Errorf("could not find dwebp program at %q", *dwebp) |
| } |
| b, err := exec.Command(*dwebp, "-version").Output() |
| if err != nil { |
| return fmt.Errorf("could not determine the dwebp program version for %q: %v", *dwebp, err) |
| } |
| switch s := string(bytes.TrimSpace(b)); s { |
| case "0.4.0", "0.4.1", "0.4.2": |
| return fmt.Errorf("the dwebp program version %q for %q has a known bug "+ |
| "(https://bugs.chromium.org/p/webp/issues/detail?id=239). Please use a newer version.", s, *dwebp) |
| } |
| return nil |
| } |
| |
| // test tests a single WEBP image. |
| func test(name string) error { |
| filename := filepath.Join(*testdata, name) |
| f, err := os.Open(filename) |
| if err != nil { |
| return fmt.Errorf("Open: %v", err) |
| } |
| defer f.Close() |
| |
| gotImage, err := webp.Decode(f) |
| if err != nil { |
| return fmt.Errorf("Decode: %v", err) |
| } |
| format, encode := "-pgm", encodePGM |
| if _, lossless := gotImage.(*image.NRGBA); lossless { |
| format, encode = "-pam", encodePAM |
| } |
| got, err := encode(gotImage) |
| if err != nil { |
| return fmt.Errorf("encode: %v", err) |
| } |
| |
| stdout := new(bytes.Buffer) |
| stderr := new(bytes.Buffer) |
| c := exec.Command(*dwebp, filename, format, "-o", "/dev/stdout") |
| c.Stdout = stdout |
| c.Stderr = stderr |
| if err := c.Run(); err != nil { |
| os.Stderr.Write(stderr.Bytes()) |
| return fmt.Errorf("executing dwebp: %v", err) |
| } |
| want := stdout.Bytes() |
| |
| if len(got) != len(want) { |
| return fmt.Errorf("encodings have different length: got %d, want %d", len(got), len(want)) |
| } |
| for i, g := range got { |
| if w := want[i]; g != w { |
| return fmt.Errorf("encodings differ at position 0x%x: got 0x%02x, want 0x%02x", i, g, w) |
| } |
| } |
| return nil |
| } |
| |
| // encodePAM encodes gotImage in the PAM format. |
| func encodePAM(gotImage image.Image) ([]byte, error) { |
| m, ok := gotImage.(*image.NRGBA) |
| if !ok { |
| return nil, fmt.Errorf("lossless image did not decode to an *image.NRGBA") |
| } |
| b := m.Bounds() |
| w, h := b.Dx(), b.Dy() |
| buf := new(bytes.Buffer) |
| fmt.Fprintf(buf, "P7\nWIDTH %d\nHEIGHT %d\nDEPTH 4\nMAXVAL 255\nTUPLTYPE RGB_ALPHA\nENDHDR\n", w, h) |
| for y := b.Min.Y; y < b.Max.Y; y++ { |
| o := m.PixOffset(b.Min.X, y) |
| buf.Write(m.Pix[o : o+4*w]) |
| } |
| return buf.Bytes(), nil |
| } |
| |
| // encodePGM encodes gotImage in the PGM format in the IMC4 layout. |
| func encodePGM(gotImage image.Image) ([]byte, error) { |
| var ( |
| m *image.YCbCr |
| ma *image.NYCbCrA |
| ) |
| switch g := gotImage.(type) { |
| case *image.YCbCr: |
| m = g |
| case *image.NYCbCrA: |
| m = &g.YCbCr |
| ma = g |
| default: |
| return nil, fmt.Errorf("lossy image did not decode to an *image.YCbCr") |
| } |
| if m.SubsampleRatio != image.YCbCrSubsampleRatio420 { |
| return nil, fmt.Errorf("lossy image did not decode to a 4:2:0 YCbCr") |
| } |
| b := m.Bounds() |
| w, h := b.Dx(), b.Dy() |
| w2, h2 := (w+1)/2, (h+1)/2 |
| outW, outH := 2*w2, h+h2 |
| if ma != nil { |
| outH += h |
| } |
| buf := new(bytes.Buffer) |
| fmt.Fprintf(buf, "P5\n%d %d\n255\n", outW, outH) |
| for y := b.Min.Y; y < b.Max.Y; y++ { |
| o := m.YOffset(b.Min.X, y) |
| buf.Write(m.Y[o : o+w]) |
| if w&1 != 0 { |
| buf.WriteByte(0x00) |
| } |
| } |
| for y := b.Min.Y; y < b.Max.Y; y += 2 { |
| o := m.COffset(b.Min.X, y) |
| buf.Write(m.Cb[o : o+w2]) |
| buf.Write(m.Cr[o : o+w2]) |
| } |
| if ma != nil { |
| for y := b.Min.Y; y < b.Max.Y; y++ { |
| o := ma.AOffset(b.Min.X, y) |
| buf.Write(ma.A[o : o+w]) |
| if w&1 != 0 { |
| buf.WriteByte(0x00) |
| } |
| } |
| } |
| return buf.Bytes(), nil |
| } |
| |
| // dump can be useful for debugging. |
| func dump(w io.Writer, b []byte) { |
| h := hex.Dumper(w) |
| h.Write(b) |
| h.Close() |
| } |