indexed_test.go (5806B)
1 package icns 2 3 import ( 4 "bytes" 5 "errors" 6 "image" 7 "image/color" 8 "testing" 9 ) 10 11 // TestPalettes pins the anchors of the two fixed colour tables. The cube is 12 // indexed 36R + 6G + B over the six levels, descending from white. 13 func TestPalettes(t *testing.T) { 14 t.Parallel() 15 tests := []struct { 16 desc string 17 index int 18 want color.NRGBA 19 }{ 20 {"white leads the table", 0, color.NRGBA{0xFF, 0xFF, 0xFF, 0xFF}}, 21 {"blue steps down first", 1, color.NRGBA{0xFF, 0xFF, 0xCC, 0xFF}}, 22 {"blue bottoms out", 5, color.NRGBA{0xFF, 0xFF, 0x00, 0xFF}}, 23 {"green steps after blue wraps", 6, color.NRGBA{0xFF, 0xCC, 0xFF, 0xFF}}, 24 {"red steps after green wraps", 36, color.NRGBA{0xCC, 0xFF, 0xFF, 0xFF}}, 25 {"the cube ends before black", 214, color.NRGBA{0x00, 0x00, 0x33, 0xFF}}, 26 {"the red ramp follows the cube", 215, color.NRGBA{0xEE, 0x00, 0x00, 0xFF}}, 27 {"the red ramp ends", 224, color.NRGBA{0x11, 0x00, 0x00, 0xFF}}, 28 {"then green", 225, color.NRGBA{0x00, 0xEE, 0x00, 0xFF}}, 29 {"then blue", 235, color.NRGBA{0x00, 0x00, 0xEE, 0xFF}}, 30 {"then grey", 245, color.NRGBA{0xEE, 0xEE, 0xEE, 0xFF}}, 31 {"the grey ramp ends", 254, color.NRGBA{0x11, 0x11, 0x11, 0xFF}}, 32 {"black is last", 255, color.NRGBA{0x00, 0x00, 0x00, 0xFF}}, 33 } 34 for _, tt := range tests { 35 t.Run(tt.desc, func(st *testing.T) { 36 if got := macPalette8[tt.index]; got != tt.want { 37 st.Fatalf("index %d = %v, want %v", tt.index, got, tt.want) 38 } 39 }) 40 } 41 if got := macPalette4[0]; got != (color.NRGBA{0xFF, 0xFF, 0xFF, 0xFF}) { 42 t.Errorf("4-bit index 0 = %v, want white", got) 43 } 44 if got := macPalette4[15]; got != (color.NRGBA{0x00, 0x00, 0x00, 0xFF}) { 45 t.Errorf("4-bit index 15 = %v, want black", got) 46 } 47 // Every entry of the 256 table must be distinct, which the construction 48 // only manages because black is held back from the cube. 49 seen := map[color.NRGBA]int{} 50 for i, c := range macPalette8 { 51 if first, ok := seen[c]; ok { 52 t.Errorf("index %d repeats index %d (%v)", i, first, c) 53 } 54 seen[c] = i 55 } 56 } 57 58 // bitmapMask builds a "#" element: a one bit icon followed by its one bit 59 // mask, with the left half of the icon opaque. 60 func bitmapMask(w, h int) []byte { 61 plane := w * h / 8 62 out := make([]byte, plane*2) 63 for i := 0; i < w*h; i++ { 64 x := i % w 65 if x < w/4 { 66 out[i/8] |= 0x80 >> (i % 8) // Ink. 67 } 68 if x < w/2 { 69 out[plane+i/8] |= 0x80 >> (i % 8) // Opaque. 70 } 71 } 72 return out 73 } 74 75 func TestDecodeIndexed(t *testing.T) { 76 t.Parallel() 77 78 t.Run("one bit icon carries its own mask", func(st *testing.T) { 79 data := file(encodeElement("ICN#", bitmapMask(32, 32))) 80 img, err := Decode(bytes.NewReader(data)) 81 if err != nil { 82 st.Fatal(err) 83 } 84 if got := img.Bounds().Size(); got != image.Pt(32, 32) { 85 st.Fatalf("decoded %v, want 32x32", got) 86 } 87 at := func(x, y int) color.NRGBA { 88 return color.NRGBAModel.Convert(img.At(x, y)).(color.NRGBA) 89 } 90 if got := at(2, 5); got != (color.NRGBA{0, 0, 0, 0xFF}) { 91 st.Errorf("ink pixel = %v, want opaque black", got) 92 } 93 if got := at(12, 5); got != (color.NRGBA{0xFF, 0xFF, 0xFF, 0xFF}) { 94 st.Errorf("paper pixel = %v, want opaque white", got) 95 } 96 if got := at(24, 5); got.A != 0 { 97 st.Errorf("pixel outside the mask = %v, want transparent", got) 98 } 99 }) 100 101 t.Run("eight bit icon takes the companion mask", func(st *testing.T) { 102 // Index 3 of the 256 table, well inside the colour cube. 103 pixels := make([]byte, 32*32) 104 for i := range pixels { 105 pixels[i] = 3 106 } 107 data := file( 108 encodeElement("icl8", pixels), 109 encodeElement("ICN#", bitmapMask(32, 32)), 110 ) 111 img, err := Decode(bytes.NewReader(data)) 112 if err != nil { 113 st.Fatal(err) 114 } 115 at := func(x, y int) color.NRGBA { 116 return color.NRGBAModel.Convert(img.At(x, y)).(color.NRGBA) 117 } 118 want := macPalette8[3] 119 if got := at(4, 4); got != want { 120 st.Errorf("inside the mask = %v, want %v", got, want) 121 } 122 if got := at(24, 4); got.A != 0 { 123 st.Errorf("outside the mask = %v, want transparent", got) 124 } 125 }) 126 127 t.Run("four bit icon packs two pixels per byte", func(st *testing.T) { 128 pixels := make([]byte, 16*16/2) 129 for i := range pixels { 130 pixels[i] = 0x3A // Index 3 then index 10. 131 } 132 data := file( 133 encodeElement("ics4", pixels), 134 encodeElement("ics#", bitmapMask(16, 16)), 135 ) 136 img, err := Decode(bytes.NewReader(data)) 137 if err != nil { 138 st.Fatal(err) 139 } 140 at := func(x, y int) color.NRGBA { 141 return color.NRGBAModel.Convert(img.At(x, y)).(color.NRGBA) 142 } 143 if got := at(0, 0); got != macPalette4[3] { 144 st.Errorf("first pixel = %v, want %v", got, macPalette4[3]) 145 } 146 if got := at(1, 0); got != macPalette4[10] { 147 st.Errorf("second pixel = %v, want %v", got, macPalette4[10]) 148 } 149 }) 150 151 t.Run("the mini icon is not square", func(st *testing.T) { 152 data := file( 153 encodeElement("icm8", make([]byte, 16*12)), 154 encodeElement("icm#", bitmapMask(16, 12)), 155 ) 156 img, err := Decode(bytes.NewReader(data)) 157 if err != nil { 158 st.Fatal(err) 159 } 160 if got := img.Bounds().Size(); got != image.Pt(16, 12) { 161 st.Fatalf("decoded %v, want 16x12", got) 162 } 163 }) 164 165 t.Run("richer depths come first", func(st *testing.T) { 166 data := file( 167 encodeElement("ICN#", bitmapMask(32, 32)), 168 encodeElement("icl4", make([]byte, 32*32/2)), 169 encodeElement("icl8", make([]byte, 32*32)), 170 ) 171 d, err := NewDecoder(bytes.NewReader(data)) 172 if err != nil { 173 st.Fatal(err) 174 } 175 var got []string 176 for _, icon := range d.Icons() { 177 got = append(got, icon.ID) 178 } 179 want := []string{"icl8", "icl4", "ICN#"} 180 for i := range want { 181 if got[i] != want[i] { 182 st.Fatalf("icons = %v, want %v", got, want) 183 } 184 } 185 }) 186 187 t.Run("short data", func(st *testing.T) { 188 data := file(encodeElement("icl8", make([]byte, 10))) 189 if _, err := Decode(bytes.NewReader(data)); !errors.Is(err, ErrMalformed) { 190 st.Fatalf("error = %v, want ErrMalformed", err) 191 } 192 }) 193 }