| // Copyright 2012 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. |
| |
| package bmp |
| |
| import ( |
| "bytes" |
| "encoding/binary" |
| "fmt" |
| "image" |
| "io" |
| "os" |
| "testing" |
| |
| _ "image/png" |
| ) |
| |
| const testdataDir = "../testdata/" |
| |
| func compare(img0, img1 image.Image) error { |
| b := img1.Bounds() |
| if !b.Eq(img0.Bounds()) { |
| return fmt.Errorf("wrong image size: want %s, got %s", img0.Bounds(), b) |
| } |
| for y := b.Min.Y; y < b.Max.Y; y++ { |
| for x := b.Min.X; x < b.Max.X; x++ { |
| c0 := img0.At(x, y) |
| c1 := img1.At(x, y) |
| r0, g0, b0, a0 := c0.RGBA() |
| r1, g1, b1, a1 := c1.RGBA() |
| if r0 != r1 || g0 != g1 || b0 != b1 || a0 != a1 { |
| return fmt.Errorf("pixel at (%d, %d) has wrong color: want %v, got %v", x, y, c0, c1) |
| } |
| } |
| } |
| return nil |
| } |
| |
| // TestDecode tests that decoding a PNG image and a BMP image result in the |
| // same pixel data. |
| func TestDecode(t *testing.T) { |
| testCases := []string{ |
| "colormap", |
| "colormap-0", |
| "colormap-251", |
| "video-001", |
| "yellow_rose-small", |
| "yellow_rose-small-v5", |
| "bmp_1bpp", |
| "bmp_4bpp", |
| "bmp_8bpp", |
| } |
| |
| for _, tc := range testCases { |
| f0, err := os.Open(testdataDir + tc + ".png") |
| if err != nil { |
| t.Errorf("%s: Open PNG: %v", tc, err) |
| continue |
| } |
| defer f0.Close() |
| img0, _, err := image.Decode(f0) |
| if err != nil { |
| t.Errorf("%s: Decode PNG: %v", tc, err) |
| continue |
| } |
| |
| f1, err := os.Open(testdataDir + tc + ".bmp") |
| if err != nil { |
| t.Errorf("%s: Open BMP: %v", tc, err) |
| continue |
| } |
| defer f1.Close() |
| img1, _, err := image.Decode(f1) |
| if err != nil { |
| t.Errorf("%s: Decode BMP: %v", tc, err) |
| continue |
| } |
| |
| if err := compare(img0, img1); err != nil { |
| t.Errorf("%s: %v", tc, err) |
| continue |
| } |
| } |
| } |
| |
| func TestDecodeConstructed(t *testing.T) { |
| for _, tc := range []struct { |
| name string |
| b []byte |
| wantErr error |
| }{{ |
| name: "1x1 paletted", |
| b: bmpBuilder{ |
| width: 1, |
| height: 1, |
| planes: 1, |
| bitsPerPixel: 1, |
| colorsUsed: 2, |
| colorTable: []colorTableEntry{ |
| {0, 0, 0}, |
| {0xff, 0xff, 0xff}, |
| }, |
| data: []byte{0, 0, 0, 0}, |
| }.Bytes(), |
| wantErr: nil, // successful base case |
| }, { |
| name: "1x1 rgb", |
| b: bmpBuilder{ |
| width: 1, |
| height: 1, |
| planes: 1, |
| bitsPerPixel: 24, |
| data: []byte{ |
| 0, 0, 0, 0, |
| }, |
| }.Bytes(), |
| wantErr: nil, // successful base case |
| }, { |
| name: "invalid palette index", |
| b: bmpBuilder{ |
| width: 1, |
| height: 1, |
| planes: 1, |
| bitsPerPixel: 8, |
| colorsUsed: 2, |
| colorTable: []colorTableEntry{ |
| {0, 0, 0}, |
| {0xff, 0xff, 0xff}, |
| }, |
| data: []byte{ |
| 2, 0, 0, 0, // index 2 |
| }, |
| }.Bytes(), |
| wantErr: errInvalidPaletteIndex, |
| }, { |
| name: "0x1", |
| b: bmpBuilder{ |
| width: 1, |
| height: 0, |
| planes: 1, |
| bitsPerPixel: 24, |
| data: []byte{ |
| 0, 0, 0, 0, |
| }, |
| }.Bytes(), |
| wantErr: ErrUnsupported, |
| }, { |
| name: "excessive width and height", |
| b: bmpBuilder{ |
| width: 2147483647, |
| height: 2147483647, |
| planes: 1, |
| bitsPerPixel: 24, |
| }.Bytes(), |
| wantErr: ErrUnsupported, |
| }} { |
| img, _, err := image.Decode(bytes.NewReader(tc.b)) |
| if err != tc.wantErr { |
| t.Errorf("%v: Decode error %v; want %v", tc.name, err, tc.wantErr) |
| } |
| if err != nil { |
| continue |
| } |
| _ = img.At(0, 0) // try accessing a pixel |
| } |
| } |
| |
| // TestEOF tests that decoding a BMP image returns io.ErrUnexpectedEOF |
| // when there are no headers or data is empty |
| func TestEOF(t *testing.T) { |
| _, err := Decode(bytes.NewReader(nil)) |
| if err != io.ErrUnexpectedEOF { |
| t.Errorf("Error should be io.ErrUnexpectedEOF on nil but got %v", err) |
| } |
| } |
| |
| type bmpBuilder struct { |
| width int32 |
| height int32 |
| planes uint16 |
| bitsPerPixel uint16 |
| compression uint32 |
| imageSize uint32 |
| xppm uint32 |
| yppm uint32 |
| colorsUsed uint32 |
| colorsImportant uint32 |
| colorTable []colorTableEntry |
| data []byte |
| } |
| |
| type colorTableEntry struct { |
| r, g, b byte |
| } |
| |
| func (b bmpBuilder) Bytes() []byte { |
| buf := []byte{ |
| 0x42, 0x4d, // 'BM' |
| 0x00, 0x00, 0x00, 0x00, // file size |
| 0x00, 0x00, 0x00, 0x00, // reserved |
| 0x00, 0x00, 0x00, 0x00, // data offset |
| 0x28, 0x00, 0x00, 0x00, // header size (40) |
| } |
| buf = binary.LittleEndian.AppendUint32(buf, uint32(b.width)) |
| buf = binary.LittleEndian.AppendUint32(buf, uint32(b.height)) |
| buf = binary.LittleEndian.AppendUint16(buf, b.planes) |
| buf = binary.LittleEndian.AppendUint16(buf, b.bitsPerPixel) |
| buf = binary.LittleEndian.AppendUint32(buf, b.compression) |
| buf = binary.LittleEndian.AppendUint32(buf, b.imageSize) |
| buf = binary.LittleEndian.AppendUint32(buf, b.xppm) |
| buf = binary.LittleEndian.AppendUint32(buf, b.yppm) |
| buf = binary.LittleEndian.AppendUint32(buf, b.colorsUsed) |
| buf = binary.LittleEndian.AppendUint32(buf, b.colorsImportant) |
| for _, e := range b.colorTable { |
| buf = append(buf, e.r, e.g, e.b, 0) |
| } |
| binary.LittleEndian.PutUint32(buf[10:], uint32(len(buf))) // data offset |
| buf = append(buf, b.data...) |
| binary.LittleEndian.PutUint32(buf[2:], uint32(len(buf))) // file size |
| return buf |
| } |