go.image/cmd/webp-manual-test: new program to test Go's WEBP
compatibility with the C libwebp implementation.

Current status:
104 PASS, 5 FAIL, 109 TOTAL
Of those 5 failures, 3 are because Go doesn't support lossy-with-alpha
yet, and the other 2 (lossless_vec_2_13.webp,
lossy_extreme_probabilities.webp) look like genuine bugs.

LGTM=r
R=r, pascal.massimino
CC=golang-codereviews
https://golang.org/cl/150000043
diff --git a/cmd/webp-manual-test/main.go b/cmd/webp-manual-test/main.go
new file mode 100644
index 0000000..f08c183
--- /dev/null
+++ b/cmd/webp-manual-test/main.go
@@ -0,0 +1,165 @@
+// Program webp-manual-test checks that the Go WEBP library's decodings match
+// the C WEBP library's.
+package main
+
+import (
+	"bytes"
+	"encoding/hex"
+	"flag"
+	"fmt"
+	"image"
+	"io"
+	"log"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"sort"
+	"strings"
+
+	"code.google.com/p/go.image/webp"
+)
+
+var (
+	dwebp = flag.String("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 *dwebp == "" {
+		flag.Usage()
+		log.Fatal("dwebp flag was not specified")
+	}
+	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)
+	}
+}
+
+// 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 := "-pam", encodePAM
+	if _, lossy := gotImage.(*image.YCbCr); lossy {
+		format, encode = "-pgm", encodePGM
+	}
+	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) {
+	m, ok := gotImage.(*image.YCbCr)
+	if !ok {
+		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
+	buf := new(bytes.Buffer)
+	fmt.Fprintf(buf, "P5\n%d %d\n255\n", 2*w2, h+h2)
+	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])
+	}
+	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()
+}