icns

Easily create .icns files (Mac Icons) with this Go library or the included CLI.
Log | Files | Refs | LICENSE

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 }