123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435 |
- package imaging
- import (
- "bytes"
- "errors"
- "image"
- "image/color"
- "image/color/palette"
- "image/draw"
- "image/png"
- "io"
- "io/ioutil"
- "os"
- "path/filepath"
- "strings"
- "testing"
- )
- var (
- errCreate = errors.New("failed to create file")
- errClose = errors.New("failed to close file")
- errOpen = errors.New("failed to open file")
- )
- type badFS struct{}
- func (badFS) Create(name string) (io.WriteCloser, error) {
- if name == "badFile.jpg" {
- return badFile{ioutil.Discard}, nil
- }
- return nil, errCreate
- }
- func (badFS) Open(name string) (io.ReadCloser, error) {
- return nil, errOpen
- }
- type badFile struct {
- io.Writer
- }
- func (badFile) Close() error {
- return errClose
- }
- type quantizer struct {
- palette []color.Color
- }
- func (q quantizer) Quantize(p color.Palette, m image.Image) color.Palette {
- pal := make([]color.Color, len(p), cap(p))
- copy(pal, p)
- n := cap(p) - len(p)
- if n > len(q.palette) {
- n = len(q.palette)
- }
- for i := 0; i < n; i++ {
- pal = append(pal, q.palette[i])
- }
- return pal
- }
- func TestOpenSave(t *testing.T) {
- imgWithoutAlpha := image.NewNRGBA(image.Rect(0, 0, 4, 6))
- imgWithoutAlpha.Pix = []uint8{
- 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
- 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
- 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff,
- 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff,
- 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x88, 0x88, 0x88, 0xff, 0x88, 0x88, 0x88, 0xff,
- 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x88, 0x88, 0x88, 0xff, 0x88, 0x88, 0x88, 0xff,
- }
- imgWithAlpha := image.NewNRGBA(image.Rect(0, 0, 4, 6))
- imgWithAlpha.Pix = []uint8{
- 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
- 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
- 0xff, 0x00, 0x00, 0x80, 0xff, 0x00, 0x00, 0x80, 0x00, 0xff, 0x00, 0x80, 0x00, 0xff, 0x00, 0x80,
- 0xff, 0x00, 0x00, 0x80, 0xff, 0x00, 0x00, 0x80, 0x00, 0xff, 0x00, 0x80, 0x00, 0xff, 0x00, 0x80,
- 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x88, 0x88, 0x88, 0x00, 0x88, 0x88, 0x88, 0x00,
- 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x88, 0x88, 0x88, 0x00, 0x88, 0x88, 0x88, 0x00,
- }
- options := [][]EncodeOption{
- {
- JPEGQuality(100),
- },
- {
- JPEGQuality(99),
- GIFDrawer(draw.FloydSteinberg),
- GIFNumColors(256),
- GIFQuantizer(quantizer{palette.Plan9}),
- PNGCompressionLevel(png.BestSpeed),
- },
- }
- dir, err := ioutil.TempDir("", "imaging")
- if err != nil {
- t.Fatalf("failed to create temporary directory: %v", err)
- }
- defer os.RemoveAll(dir)
- for _, ext := range []string{"jpg", "jpeg", "png", "gif", "bmp", "tif", "tiff"} {
- filename := filepath.Join(dir, "test."+ext)
- img := imgWithoutAlpha
- if ext == "png" {
- img = imgWithAlpha
- }
- for _, opts := range options {
- err := Save(img, filename, opts...)
- if err != nil {
- t.Fatalf("failed to save image (%q): %v", filename, err)
- }
- img2, err := Open(filename)
- if err != nil {
- t.Fatalf("failed to open image (%q): %v", filename, err)
- }
- got := Clone(img2)
- delta := 0
- if ext == "jpg" || ext == "jpeg" || ext == "gif" {
- delta = 3
- }
- if !compareNRGBA(got, img, delta) {
- t.Fatalf("bad encode-decode result (ext=%q): got %#v want %#v", ext, got, img)
- }
- }
- }
- buf := &bytes.Buffer{}
- err = Encode(buf, imgWithAlpha, JPEG)
- if err != nil {
- t.Fatalf("failed to encode alpha to JPEG: %v", err)
- }
- buf = &bytes.Buffer{}
- err = Encode(buf, imgWithAlpha, Format(100))
- if err != ErrUnsupportedFormat {
- t.Fatalf("got %v want ErrUnsupportedFormat", err)
- }
- buf = bytes.NewBuffer([]byte("bad data"))
- _, err = Decode(buf)
- if err == nil {
- t.Fatalf("decoding bad data: expected error got nil")
- }
- err = Save(imgWithAlpha, filepath.Join(dir, "test.unknown"))
- if err != ErrUnsupportedFormat {
- t.Fatalf("got %v want ErrUnsupportedFormat", err)
- }
- prevFS := fs
- fs = badFS{}
- defer func() { fs = prevFS }()
- err = Save(imgWithAlpha, "test.jpg")
- if err != errCreate {
- t.Fatalf("got error %v want errCreate", err)
- }
- err = Save(imgWithAlpha, "badFile.jpg")
- if err != errClose {
- t.Fatalf("got error %v want errClose", err)
- }
- _, err = Open("test.jpg")
- if err != errOpen {
- t.Fatalf("got error %v want errOpen", err)
- }
- }
- func TestFormats(t *testing.T) {
- formatNames := map[Format]string{
- JPEG: "JPEG",
- PNG: "PNG",
- GIF: "GIF",
- BMP: "BMP",
- TIFF: "TIFF",
- Format(-1): "",
- }
- for format, name := range formatNames {
- got := format.String()
- if got != name {
- t.Fatalf("got format name %q want %q", got, name)
- }
- }
- }
- func TestFormatFromExtension(t *testing.T) {
- testCases := []struct {
- name string
- ext string
- want Format
- err error
- }{
- {
- name: "jpg without leading dot",
- ext: "jpg",
- want: JPEG,
- },
- {
- name: "jpg with leading dot",
- ext: ".jpg",
- want: JPEG,
- },
- {
- name: "jpg uppercase",
- ext: ".JPG",
- want: JPEG,
- },
- {
- name: "unsupported",
- ext: ".unsupportedextension",
- want: -1,
- err: ErrUnsupportedFormat,
- },
- }
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- got, err := FormatFromExtension(tc.ext)
- if err != tc.err {
- t.Errorf("got error %#v want %#v", err, tc.err)
- }
- if got != tc.want {
- t.Errorf("got result %#v want %#v", got, tc.want)
- }
- })
- }
- }
- func TestReadOrientation(t *testing.T) {
- testCases := []struct {
- path string
- orient orientation
- }{
- {"testdata/orientation_0.jpg", 0},
- {"testdata/orientation_1.jpg", 1},
- {"testdata/orientation_2.jpg", 2},
- {"testdata/orientation_3.jpg", 3},
- {"testdata/orientation_4.jpg", 4},
- {"testdata/orientation_5.jpg", 5},
- {"testdata/orientation_6.jpg", 6},
- {"testdata/orientation_7.jpg", 7},
- {"testdata/orientation_8.jpg", 8},
- }
- for _, tc := range testCases {
- f, err := os.Open(tc.path)
- if err != nil {
- t.Fatalf("%q: failed to open: %v", tc.path, err)
- }
- orient := readOrientation(f)
- if orient != tc.orient {
- t.Fatalf("%q: got orientation %d want %d", tc.path, orient, tc.orient)
- }
- }
- }
- func TestReadOrientationFails(t *testing.T) {
- testCases := []struct {
- name string
- data string
- }{
- {
- "empty",
- "",
- },
- {
- "missing SOI marker",
- "\xff\xe1",
- },
- {
- "missing APP1 marker",
- "\xff\xd8",
- },
- {
- "short read marker",
- "\xff\xd8\xff",
- },
- {
- "short read block size",
- "\xff\xd8\xff\xe1\x00",
- },
- {
- "invalid marker",
- "\xff\xd8\x00\xe1\x00\x00",
- },
- {
- "block size too small",
- "\xff\xd8\xff\xe0\x00\x01",
- },
- {
- "short read block",
- "\xff\xd8\xff\xe0\x00\x08\x00",
- },
- {
- "missing EXIF header",
- "\xff\xd8\xff\xe1\x00\xff",
- },
- {
- "invalid EXIF header",
- "\xff\xd8\xff\xe1\x00\xff\x00\x00\x00\x00",
- },
- {
- "missing EXIF header tail",
- "\xff\xd8\xff\xe1\x00\xff\x45\x78\x69\x66",
- },
- {
- "missing byte order tag",
- "\xff\xd8\xff\xe1\x00\xff\x45\x78\x69\x66\x00\x00",
- },
- {
- "invalid byte order tag",
- "\xff\xd8\xff\xe1\x00\xff\x45\x78\x69\x66\x00\x00\x00\x00",
- },
- {
- "missing byte order tail",
- "\xff\xd8\xff\xe1\x00\xff\x45\x78\x69\x66\x00\x00\x49\x49",
- },
- {
- "missing exif offset",
- "\xff\xd8\xff\xe1\x00\xff\x45\x78\x69\x66\x00\x00\x49\x49\x00\x2a",
- },
- {
- "invalid exif offset",
- "\xff\xd8\xff\xe1\x00\xff\x45\x78\x69\x66\x00\x00\x4d\x4d\x00\x2a\x00\x00\x00\x07",
- },
- {
- "read exif offset error",
- "\xff\xd8\xff\xe1\x00\xff\x45\x78\x69\x66\x00\x00\x4d\x4d\x00\x2a\x00\x00\x00\x09",
- },
- {
- "missing number of tags",
- "\xff\xd8\xff\xe1\x00\xff\x45\x78\x69\x66\x00\x00\x4d\x4d\x00\x2a\x00\x00\x00\x08",
- },
- {
- "zero number of tags",
- "\xff\xd8\xff\xe1\x00\xff\x45\x78\x69\x66\x00\x00\x4d\x4d\x00\x2a\x00\x00\x00\x08\x00\x00",
- },
- {
- "missing tag",
- "\xff\xd8\xff\xe1\x00\xff\x45\x78\x69\x66\x00\x00\x4d\x4d\x00\x2a\x00\x00\x00\x08\x00\x01",
- },
- {
- "missing tag offset",
- "\xff\xd8\xff\xe1\x00\xff\x45\x78\x69\x66\x00\x00\x4d\x4d\x00\x2a\x00\x00\x00\x08\x00\x01\x00\x00",
- },
- {
- "missing orientation tag",
- "\xff\xd8\xff\xe1\x00\xff\x45\x78\x69\x66\x00\x00\x4d\x4d\x00\x2a\x00\x00\x00\x08\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
- },
- {
- "missing orientation tag value offset",
- "\xff\xd8\xff\xe1\x00\xff\x45\x78\x69\x66\x00\x00\x4d\x4d\x00\x2a\x00\x00\x00\x08\x00\x01\x01\x12",
- },
- {
- "missing orientation value",
- "\xff\xd8\xff\xe1\x00\xff\x45\x78\x69\x66\x00\x00\x4d\x4d\x00\x2a\x00\x00\x00\x08\x00\x01\x01\x12\x00\x03\x00\x00\x00\x01",
- },
- {
- "invalid orientation value",
- "\xff\xd8\xff\xe1\x00\xff\x45\x78\x69\x66\x00\x00\x4d\x4d\x00\x2a\x00\x00\x00\x08\x00\x01\x01\x12\x00\x03\x00\x00\x00\x01\x00\x09",
- },
- }
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- if o := readOrientation(strings.NewReader(tc.data)); o != orientationUnspecified {
- t.Fatalf("got orientation %d want %d", o, orientationUnspecified)
- }
- })
- }
- }
- func TestAutoOrientation(t *testing.T) {
- toBW := func(img image.Image) []byte {
- b := img.Bounds()
- data := make([]byte, 0, b.Dx()*b.Dy())
- for x := b.Min.X; x < b.Max.X; x++ {
- for y := b.Min.Y; y < b.Max.Y; y++ {
- c := color.GrayModel.Convert(img.At(x, y)).(color.Gray)
- if c.Y < 128 {
- data = append(data, 1)
- } else {
- data = append(data, 0)
- }
- }
- }
- return data
- }
- f, err := os.Open("testdata/orientation_0.jpg")
- if err != nil {
- t.Fatalf("os.Open(%q): %v", "testdata/orientation_0.jpg", err)
- }
- orig, _, err := image.Decode(f)
- if err != nil {
- t.Fatalf("image.Decode(%q): %v", "testdata/orientation_0.jpg", err)
- }
- origBW := toBW(orig)
- testCases := []struct {
- path string
- }{
- {"testdata/orientation_0.jpg"},
- {"testdata/orientation_1.jpg"},
- {"testdata/orientation_2.jpg"},
- {"testdata/orientation_3.jpg"},
- {"testdata/orientation_4.jpg"},
- {"testdata/orientation_5.jpg"},
- {"testdata/orientation_6.jpg"},
- {"testdata/orientation_7.jpg"},
- {"testdata/orientation_8.jpg"},
- }
- for _, tc := range testCases {
- img, err := Open(tc.path, AutoOrientation(true))
- if err != nil {
- t.Fatal(err)
- }
- if img.Bounds() != orig.Bounds() {
- t.Fatalf("%s: got bounds %v want %v", tc.path, img.Bounds(), orig.Bounds())
- }
- imgBW := toBW(img)
- if !bytes.Equal(imgBW, origBW) {
- t.Fatalf("%s: got bw data %v want %v", tc.path, imgBW, origBW)
- }
- }
- if _, err := Decode(strings.NewReader("invalid data"), AutoOrientation(true)); err == nil {
- t.Fatal("expected error got nil")
- }
- }
|