ico_test.go (5386B)
1 package ico 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "image" 7 "image/color" 8 "reflect" 9 "testing" 10 ) 11 12 // gradient returns a side by side image whose colour and alpha vary with 13 // position, so a round trip cannot pass by accident. 14 func gradient(side int) *image.NRGBA { 15 img := image.NewNRGBA(image.Rect(0, 0, side, side)) 16 for y := 0; y < side; y++ { 17 for x := 0; x < side; x++ { 18 img.SetNRGBA(x, y, color.NRGBA{ 19 R: uint8(x * 255 / side), 20 G: uint8(y * 255 / side), 21 B: uint8((x + y) * 255 / (2 * side)), 22 A: uint8(255 - y*255/(2*side)), 23 }) 24 } 25 } 26 return img 27 } 28 29 func solid(side int, c color.NRGBA) *image.NRGBA { 30 img := image.NewNRGBA(image.Rect(0, 0, side, side)) 31 for i := 0; i < len(img.Pix); i += 4 { 32 img.Pix[i], img.Pix[i+1], img.Pix[i+2], img.Pix[i+3] = c.R, c.G, c.B, c.A 33 } 34 return img 35 } 36 37 func centre(img image.Image) color.NRGBA { 38 b := img.Bounds() 39 return color.NRGBAModel.Convert(img.At(b.Min.X+b.Dx()/2, b.Min.Y+b.Dy()/2)).(color.NRGBA) 40 } 41 42 func same(a, b image.Image) bool { 43 if a.Bounds().Size() != b.Bounds().Size() { 44 return false 45 } 46 ab, bb := a.Bounds(), b.Bounds() 47 for y := 0; y < ab.Dy(); y++ { 48 for x := 0; x < ab.Dx(); x++ { 49 if a.At(ab.Min.X+x, ab.Min.Y+y) != b.At(bb.Min.X+x, bb.Min.Y+y) { 50 ar, ag, al, aa := a.At(ab.Min.X+x, ab.Min.Y+y).RGBA() 51 br, bg, bl, ba := b.At(bb.Min.X+x, bb.Min.Y+y).RGBA() 52 if ar != br || ag != bg || al != bl || aa != ba { 53 return false 54 } 55 } 56 } 57 } 58 return true 59 } 60 61 func TestEncode(t *testing.T) { 62 t.Parallel() 63 buf := bytes.NewBuffer(nil) 64 if err := Encode(buf, gradient(256)); err != nil { 65 t.Fatal(err) 66 } 67 d, err := NewDecoder(bytes.NewReader(buf.Bytes())) 68 if err != nil { 69 t.Fatal(err) 70 } 71 var ( 72 got []int 73 formats []Format 74 ) 75 for _, icon := range d.Icons() { 76 got = append(got, icon.Width) 77 formats = append(formats, icon.Format) 78 if icon.Width != icon.Height { 79 t.Errorf("icon %s is not square", icon) 80 } 81 } 82 if want := []int{256, 128, 64, 48, 32, 24, 16}; !reflect.DeepEqual(got, want) { 83 t.Fatalf("sizes = %v, want %v", got, want) 84 } 85 // The largest is a PNG, since a bitmap at 256 is a quarter of a megabyte. 86 want := []Format{FormatPNG, FormatBMP, FormatBMP, FormatBMP, FormatBMP, FormatBMP, FormatBMP} 87 if !reflect.DeepEqual(formats, want) { 88 t.Fatalf("formats = %v, want %v", formats, want) 89 } 90 } 91 92 // TestDirectory reads the directory by hand, since every reader of the file 93 // finds its icons through those offsets. 94 func TestDirectory(t *testing.T) { 95 t.Parallel() 96 buf := bytes.NewBuffer(nil) 97 if err := Encode(buf, gradient(64)); err != nil { 98 t.Fatal(err) 99 } 100 data := buf.Bytes() 101 if got := binary.LittleEndian.Uint16(data[0:2]); got != 0 { 102 t.Errorf("reserved = %d, want 0", got) 103 } 104 if got := binary.LittleEndian.Uint16(data[2:4]); got != 1 { 105 t.Errorf("type = %d, want 1 for an icon", got) 106 } 107 count := int(binary.LittleEndian.Uint16(data[4:6])) 108 if count != 5 { // 64, 48, 32, 24 and 16. 109 t.Fatalf("count = %d, want 5", count) 110 } 111 offset := directorySize + entrySize*count 112 for i := 0; i < count; i++ { 113 row := data[directorySize+entrySize*i:] 114 size := int(binary.LittleEndian.Uint32(row[8:12])) 115 at := int(binary.LittleEndian.Uint32(row[12:16])) 116 if at != offset { 117 t.Errorf("icon %d starts at %d, want %d, so the images are not packed in order", i, at, offset) 118 } 119 if at+size > len(data) { 120 t.Fatalf("icon %d runs past the end of the file", i) 121 } 122 offset += size 123 } 124 if offset != len(data) { 125 t.Errorf("the icons end at %d but the file is %d bytes", offset, len(data)) 126 } 127 } 128 129 // TestRoundTrip checks that an image at an exact icon size comes back 130 // unchanged: nothing is resampled and a 32 bit bitmap is lossless. 131 func TestRoundTrip(t *testing.T) { 132 t.Parallel() 133 src := gradient(64) 134 buf := bytes.NewBuffer(nil) 135 if err := Encode(buf, src); err != nil { 136 t.Fatal(err) 137 } 138 img, err := Decode(bytes.NewReader(buf.Bytes())) 139 if err != nil { 140 t.Fatal(err) 141 } 142 if !same(img, src) { 143 t.Fatal("the decoded icon differs from the source") 144 } 145 } 146 147 func TestEncodeSizes(t *testing.T) { 148 t.Parallel() 149 var ( 150 images = map[uint]image.Image{} 151 want = map[uint]color.NRGBA{} 152 ) 153 for i, size := range Sizes() { 154 c := color.NRGBA{R: uint8(20 + i*30), G: uint8(200 - i*20), B: 0x40, A: 0xFF} 155 images[size] = solid(int(size), c) 156 want[size] = c 157 } 158 buf := bytes.NewBuffer(nil) 159 if err := NewEncoder(buf).EncodeSizes(images); err != nil { 160 t.Fatal(err) 161 } 162 d, err := NewDecoder(bytes.NewReader(buf.Bytes())) 163 if err != nil { 164 t.Fatal(err) 165 } 166 icons := d.Icons() 167 if len(icons) != len(want) { 168 t.Fatalf("encoded %d icons, want %d", len(icons), len(want)) 169 } 170 for _, icon := range icons { 171 img, err := icon.Decode() 172 if err != nil { 173 t.Fatalf("%s: %v", icon, err) 174 } 175 if got := centre(img); got != want[uint(icon.Width)] { 176 t.Errorf("the %d pixel icon is %v, want %v", icon.Width, got, want[uint(icon.Width)]) 177 } 178 } 179 } 180 181 func TestEncodeRejects(t *testing.T) { 182 t.Parallel() 183 buf := bytes.NewBuffer(nil) 184 if err := Encode(buf, nil); err == nil { 185 t.Error("encoding a nil image was accepted") 186 } 187 if err := Encode(nil, gradient(64)); err == nil { 188 t.Error("encoding to a nil writer was accepted") 189 } 190 if err := Encode(buf, gradient(8)); err == nil { 191 t.Error("encoding an image below the smallest icon was accepted") 192 } 193 if err := NewEncoder(buf).EncodeSizes(nil); err == nil { 194 t.Error("encoding without artwork was accepted") 195 } 196 }