rle_test.go (6562B)
1 package icns 2 3 import ( 4 "bytes" 5 "errors" 6 "image" 7 "image/color" 8 "testing" 9 ) 10 11 // rleLiterals encodes data as literal runs only, which is the simplest valid 12 // encoding and always longer than the input. 13 func rleLiterals(data []byte) []byte { 14 var out []byte 15 for len(data) > 0 { 16 n := min(len(data), 128) 17 out = append(out, byte(n-1)) 18 out = append(out, data[:n]...) 19 data = data[n:] 20 } 21 return out 22 } 23 24 func TestUnpackRLE(t *testing.T) { 25 t.Parallel() 26 tests := []struct { 27 desc string 28 data []byte 29 // want is the number of bytes asked for, which the caller knows from 30 // the icon's dimensions. It is stated rather than derived, because a 31 // stream whose length equals it is read as uncompressed. 32 want int 33 out []byte 34 err error 35 }{ 36 { 37 desc: "literal run", 38 data: []byte{0x02, 0x01, 0x02, 0x03}, 39 want: 3, 40 out: []byte{0x01, 0x02, 0x03}, 41 }, 42 { 43 desc: "repeat run", 44 data: []byte{0x80, 0x07}, 45 want: 3, 46 out: []byte{0x07, 0x07, 0x07}, 47 }, 48 { 49 desc: "literal then repeat", 50 data: []byte{0x02, 0x01, 0x02, 0x02, 0x82, 0x03}, 51 want: 8, 52 out: []byte{0x01, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03}, 53 }, 54 { 55 desc: "longest repeat", 56 data: []byte{0xFF, 0x09}, 57 want: 130, 58 out: bytes.Repeat([]byte{0x09}, 130), 59 }, 60 { 61 desc: "stored uncompressed", 62 data: []byte{0x01, 0x02, 0x03}, 63 want: 3, 64 out: []byte{0x01, 0x02, 0x03}, 65 }, 66 { 67 desc: "literal run overruns the element", 68 data: []byte{0x7F, 0x01, 0x02}, 69 want: 200, 70 err: ErrMalformed, 71 }, 72 { 73 desc: "repeat run with no byte to repeat", 74 data: []byte{0x04, 0x01, 0x02, 0x03, 0x04, 0x05, 0x80}, 75 want: 200, 76 err: ErrMalformed, 77 }, 78 { 79 desc: "expands short", 80 data: []byte{0x00, 0x01}, 81 want: 3, 82 err: ErrMalformed, 83 }, 84 { 85 desc: "a single run expands past what was asked for", 86 data: []byte{0xFF, 0x09}, 87 want: 3, 88 err: ErrMalformed, 89 }, 90 } 91 for _, tt := range tests { 92 t.Run(tt.desc, func(st *testing.T) { 93 got, _, err := unpackRLE(tt.data, tt.want) 94 if tt.err != nil { 95 if !errors.Is(err, tt.err) { 96 st.Fatalf("error = %v, want %v", err, tt.err) 97 } 98 return 99 } 100 if err != nil { 101 st.Fatalf("unexpected error: %v", err) 102 } 103 if !bytes.Equal(got, tt.out) { 104 st.Fatalf("got %v, want %v", got, tt.out) 105 } 106 }) 107 } 108 } 109 110 func TestPadRLE(t *testing.T) { 111 t.Parallel() 112 // Compressed data gets a byte so a reader that drops the last value of 113 // the stream loses the padding instead of a pixel. 114 if got := padRLE([]byte{0x80, 0x07}, 3); !bytes.Equal(got, []byte{0x80, 0x07, 0x00}) { 115 t.Errorf("compressed data = %v, want a trailing zero", got) 116 } 117 // Uncompressed data has to keep its exact length, which is how a reader 118 // tells that it was never compressed. 119 raw := []byte{1, 2, 3} 120 if got := padRLE(raw, len(raw)); !bytes.Equal(got, raw) { 121 t.Errorf("uncompressed data = %v, want it unchanged", got) 122 } 123 // Whatever the padding, the data still reads back. 124 planes := bytes.Repeat([]byte{0x40}, 768) 125 out, _, err := unpackRLE(padRLE(packRLE(planes), len(planes)), len(planes)) 126 if err != nil { 127 t.Fatal(err) 128 } 129 if !bytes.Equal(out, planes) { 130 t.Fatal("padded data did not survive the round trip") 131 } 132 } 133 134 // legacyIcon builds an is32 colour element and its s8mk mask for a gradient, 135 // returning the elements and the image they describe. 136 func legacyIcon(side int) (rgb, mask []byte, want *image.NRGBA) { 137 pixels := side * side 138 planes := make([]byte, pixels*3) 139 mask = make([]byte, pixels) 140 want = image.NewNRGBA(image.Rect(0, 0, side, side)) 141 for y := 0; y < side; y++ { 142 for x := 0; x < side; x++ { 143 i := y*side + x 144 c := color.NRGBA{ 145 R: uint8(x * 255 / side), 146 G: uint8(y * 255 / side), 147 B: 0x40, 148 A: uint8((x + y) * 255 / (2 * side)), 149 } 150 planes[i] = c.R 151 planes[pixels+i] = c.G 152 planes[pixels*2+i] = c.B 153 mask[i] = c.A 154 want.SetNRGBA(x, y, c) 155 } 156 } 157 return rleLiterals(planes), mask, want 158 } 159 160 func TestDecodeLegacyElements(t *testing.T) { 161 t.Parallel() 162 const side = 16 163 rgb, mask, want := legacyIcon(side) 164 165 t.Run("colour and mask", func(st *testing.T) { 166 data := file(encodeElement("is32", rgb), encodeElement("s8mk", mask)) 167 imgs, err := DecodeAll(bytes.NewReader(data)) 168 if err != nil { 169 st.Fatal(err) 170 } 171 if len(imgs) != 1 { 172 st.Fatalf("decoded %d icons, want 1", len(imgs)) 173 } 174 if !imageCompare(imgs[0], want) { 175 st.Fatal("decoded icon differs from the source") 176 } 177 }) 178 179 t.Run("mask ahead of the colour", func(st *testing.T) { 180 // Apple writes the mask after its element, but nothing requires it. 181 data := file(encodeElement("s8mk", mask), encodeElement("is32", rgb)) 182 imgs, err := DecodeAll(bytes.NewReader(data)) 183 if err != nil { 184 st.Fatal(err) 185 } 186 if !imageCompare(imgs[0], want) { 187 st.Fatal("decoded icon differs from the source") 188 } 189 }) 190 191 t.Run("no mask leaves the icon opaque", func(st *testing.T) { 192 data := file(encodeElement("is32", rgb)) 193 img, err := Decode(bytes.NewReader(data)) 194 if err != nil { 195 st.Fatal(err) 196 } 197 for y := 0; y < side; y++ { 198 for x := 0; x < side; x++ { 199 if _, _, _, a := img.At(x, y).RGBA(); a != 0xFFFF { 200 st.Fatalf("pixel (%d,%d) alpha = %d, want opaque", x, y, a) 201 } 202 } 203 } 204 }) 205 206 t.Run("uncompressed colour", func(st *testing.T) { 207 pixels := side * side 208 planes := make([]byte, pixels*3) 209 for i := range planes { 210 planes[i] = byte(i) 211 } 212 data := file(encodeElement("is32", planes), encodeElement("s8mk", mask)) 213 img, err := Decode(bytes.NewReader(data)) 214 if err != nil { 215 st.Fatal(err) 216 } 217 if got := img.Bounds().Dx(); got != side { 218 st.Fatalf("decoded a %dpx icon, want %d", got, side) 219 } 220 }) 221 222 t.Run("mask of the wrong length", func(st *testing.T) { 223 data := file(encodeElement("is32", rgb), encodeElement("s8mk", mask[:10])) 224 if _, err := Decode(bytes.NewReader(data)); !errors.Is(err, ErrMalformed) { 225 st.Fatalf("error = %v, want ErrMalformed", err) 226 } 227 }) 228 } 229 230 // TestDecodeIT32Header covers the one colour element that prefixes its planes 231 // with four zero bytes. 232 func TestDecodeIT32Header(t *testing.T) { 233 t.Parallel() 234 const side = 128 235 pixels := side * side 236 planes := make([]byte, pixels*3) 237 for i := range planes { 238 planes[i] = byte(i / side) 239 } 240 rgb := append([]byte{0, 0, 0, 0}, rleLiterals(planes)...) 241 mask := bytes.Repeat([]byte{0xFF}, pixels) 242 data := file(encodeElement("it32", rgb), encodeElement("t8mk", mask)) 243 img, err := Decode(bytes.NewReader(data)) 244 if err != nil { 245 t.Fatal(err) 246 } 247 if got := img.Bounds().Size(); got != image.Pt(side, side) { 248 t.Fatalf("decoded %v, want %dx%d", got, side, side) 249 } 250 }