2014-09-25 01:44:20 +02:00
|
|
|
// Program webp-manual-test checks that the Go WEBP library's decodings match
|
|
|
|
// the C WEBP library's.
|
2014-12-09 03:51:12 +01:00
|
|
|
package main // import "golang.org/x/image/cmd/webp-manual-test"
|
2014-09-25 01:44:20 +02:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/hex"
|
|
|
|
"flag"
|
|
|
|
"fmt"
|
|
|
|
"image"
|
|
|
|
"io"
|
|
|
|
"log"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"path/filepath"
|
|
|
|
"sort"
|
|
|
|
"strings"
|
|
|
|
|
2014-11-09 22:52:14 +01:00
|
|
|
"golang.org/x/image/webp"
|
|
|
|
"golang.org/x/image/webp/nycbcra"
|
2014-09-25 01:44:20 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2014-10-20 01:46:12 +02:00
|
|
|
dwebp = flag.String("dwebp", "/usr/bin/dwebp", "path to the dwebp program "+
|
2014-09-25 01:44:20 +02:00
|
|
|
"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")
|
|
|
|
}
|
2014-10-20 01:46:12 +02:00
|
|
|
if _, err := os.Stat(*dwebp); err != nil {
|
|
|
|
flag.Usage()
|
|
|
|
log.Fatalf("could not find dwebp program at %q", *dwebp)
|
|
|
|
}
|
2014-09-25 01:44:20 +02:00
|
|
|
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)
|
|
|
|
}
|
2014-10-20 01:46:12 +02:00
|
|
|
format, encode := "-pgm", encodePGM
|
|
|
|
if _, lossless := gotImage.(*image.NRGBA); lossless {
|
|
|
|
format, encode = "-pam", encodePAM
|
2014-09-25 01:44:20 +02:00
|
|
|
}
|
|
|
|
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) {
|
2014-10-20 01:46:12 +02:00
|
|
|
var (
|
|
|
|
m *image.YCbCr
|
|
|
|
ma *nycbcra.Image
|
|
|
|
)
|
|
|
|
switch g := gotImage.(type) {
|
|
|
|
case *image.YCbCr:
|
|
|
|
m = g
|
|
|
|
case *nycbcra.Image:
|
|
|
|
m = &g.YCbCr
|
|
|
|
ma = g
|
|
|
|
default:
|
2014-09-25 01:44:20 +02:00
|
|
|
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
|
2014-10-20 01:46:12 +02:00
|
|
|
outW, outH := 2*w2, h+h2
|
|
|
|
if ma != nil {
|
|
|
|
outH += h
|
|
|
|
}
|
2014-09-25 01:44:20 +02:00
|
|
|
buf := new(bytes.Buffer)
|
2014-10-20 01:46:12 +02:00
|
|
|
fmt.Fprintf(buf, "P5\n%d %d\n255\n", outW, outH)
|
2014-09-25 01:44:20 +02:00
|
|
|
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])
|
|
|
|
}
|
2014-10-20 01:46:12 +02:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-09-25 01:44:20 +02:00
|
|
|
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()
|
|
|
|
}
|