reader.go (9481B)
1 package icns 2 3 import ( 4 "bytes" 5 "cmp" 6 "encoding/binary" 7 "errors" 8 "fmt" 9 "image" 10 "io" 11 "slices" 12 ) 13 14 var ( 15 jpeg2000header = []byte{0x00, 0x00, 0x00, 0x0c, 0x6a, 0x50, 0x20, 0x20} 16 argbHeader = []byte("ARGB") 17 ) 18 19 // Decoder reads an icns file and decodes its icons on demand, so a caller 20 // after one size does not pay for the rest. 21 type Decoder struct { 22 entries []Entry 23 } 24 25 // NewDecoder reads r and identifies the icons it holds without decoding any 26 // of their pixels. 27 func NewDecoder(r io.Reader) (*Decoder, error) { 28 entries, err := decode(r) 29 if err != nil { 30 return nil, err 31 } 32 // Largest first, and at a given size the one carrying the most colour, 33 // since the older files hold several depths of the same icon. 34 slices.SortStableFunc(entries, func(a, b Entry) int { 35 if order := cmp.Compare(b.Size, a.Size); order != 0 { 36 return order 37 } 38 return cmp.Compare(b.colours(), a.colours()) 39 }) 40 return &Decoder{entries: entries}, nil 41 } 42 43 // Icons returns the icons in the file, largest first. 44 func (d *Decoder) Icons() []Entry { 45 return slices.Clone(d.entries) 46 } 47 48 // Entry is one icon in an icns file, before its pixels are decoded. 49 type Entry struct { 50 IconDescription 51 52 data []byte 53 // mask holds the alpha channel for ImageFormatRGB icons, when the file 54 // carries the matching mask element. 55 mask []byte 56 } 57 58 // Decode decodes the icon's pixels. The formats icns defines itself are 59 // decoded here; an element holding a whole image file is passed to 60 // image.Decode, so it is read by whatever the program has registered. 61 func (e Entry) Decode() (image.Image, error) { 62 switch e.ImageFormat { 63 case ImageFormatRGB: 64 data := e.data 65 // it32 is the one colour element that prefixes its planes with four 66 // zero bytes. 67 if e.ID == "it32" && len(data) >= 4 && binary.BigEndian.Uint32(data[:4]) == 0 { 68 data = data[4:] 69 } 70 img, err := decodeRGB(data, e.mask, int(e.Size)) 71 if err != nil { 72 return nil, fmt.Errorf("decoding icon %s %s: %w", e.OsType, e.ImageFormat, err) 73 } 74 return img, nil 75 case ImageFormatARGB: 76 img, err := decodeARGB(e.data[len(argbHeader):], int(e.Size)) 77 if err != nil { 78 return nil, fmt.Errorf("decoding icon %s %s: %w", e.OsType, e.ImageFormat, err) 79 } 80 return img, nil 81 case ImageFormatBitmap, ImageFormatIndexed: 82 img, err := e.indexed() 83 if err != nil { 84 return nil, fmt.Errorf("decoding icon %s %s: %w", e.OsType, e.ImageFormat, err) 85 } 86 return img, nil 87 default: 88 img, _, err := image.Decode(bytes.NewReader(e.data)) 89 if errors.Is(err, image.ErrFormat) { 90 return nil, fmt.Errorf("%w: icon %s is %s, which no registered decoder reads", ErrUnsupportedFormat, e.OsType, e.ImageFormat) 91 } 92 if err != nil { 93 return nil, fmt.Errorf("decoding icon %s %s: %w", e.OsType, e.ImageFormat, err) 94 } 95 return img, nil 96 } 97 } 98 99 // indexed decodes the icon types that hold an index per pixel, finding their 100 // alpha in the mask half of a companion element or of their own payload. 101 func (e Entry) indexed() (image.Image, error) { 102 var ( 103 width = int(e.Size) 104 height = int(e.Size) 105 bits = 1 106 ) 107 if e.height > 0 { 108 height = int(e.height) 109 } 110 switch e.enc { 111 case encodingIndexed4: 112 bits = 4 113 case encodingIndexed8: 114 bits = 8 115 } 116 // A "#" element holds its bitmap first and its mask second, whether it 117 // is the icon itself or the companion an indexed icon points at. 118 var ( 119 plane = width * height / 8 120 mask = e.mask 121 ) 122 if e.enc == encodingBitmap { 123 mask = e.data 124 } 125 if len(mask) >= plane*2 { 126 mask = mask[plane : plane*2] 127 } else { 128 mask = nil 129 } 130 return decodeIndexed(e.data, mask, width, height, bits) 131 } 132 133 // colours ranks how much colour an icon carries, so the richest at a size 134 // comes first. 135 func (e Entry) colours() int { 136 switch e.enc { 137 case encodingBitmap: 138 return 0 139 case encodingIndexed4: 140 return 1 141 case encodingIndexed8: 142 return 2 143 default: 144 return 3 145 } 146 } 147 148 // Payload returns the bytes the file stores for the icon. For PNG and JPEG 149 // 2000 icons it is a complete image file; for the colour and mask types it is 150 // the run-length encoded colour planes, without the mask that holds their 151 // alpha. 152 // 153 // The bytes are not copied, and must not be modified. 154 func (e Entry) Payload() []byte { 155 return e.data 156 } 157 158 // Decode returns the largest icon in the icns file that can be decoded, 159 // ignoring all other sizes. An icon in a format no registered decoder reads 160 // is passed over, so the result may be smaller than the largest present. 161 func Decode(r io.Reader) (image.Image, error) { 162 d, err := NewDecoder(r) 163 if err != nil { 164 return nil, err 165 } 166 for _, icon := range d.entries { 167 img, err := icon.Decode() 168 if errors.Is(err, ErrUnsupportedFormat) { 169 continue 170 } 171 return img, err 172 } 173 return nil, fmt.Errorf("%w: no icon is in a format a registered decoder reads", ErrUnsupportedFormat) 174 } 175 176 // DecodeAll extracts every icon resolution present in the icns data that can 177 // be decoded. An icon in a format no registered decoder reads is ignored. 178 func DecodeAll(r io.Reader) (images []image.Image, err error) { 179 d, err := NewDecoder(r) 180 if err != nil { 181 return nil, err 182 } 183 for _, icon := range d.entries { 184 img, err := icon.Decode() 185 if errors.Is(err, ErrUnsupportedFormat) { 186 continue 187 } 188 if err != nil { 189 return nil, err 190 } 191 images = append(images, img) 192 } 193 if len(images) == 0 { 194 return nil, fmt.Errorf("%w: no icon is in a format a registered decoder reads", ErrUnsupportedFormat) 195 } 196 // An element may hold an image of a size other than the one its type 197 // names, so order by what was actually decoded. 198 slices.SortStableFunc(images, func(a, b image.Image) int { 199 left, right := a.Bounds().Size(), b.Bounds().Size() 200 return cmp.Compare(right.X+right.Y, left.X+left.Y) 201 }) 202 return images, nil 203 } 204 205 // Probe extracts descriptions of the icons in the icns, largest first. 206 func Probe(r io.Reader) (desc []IconDescription, _ error) { 207 d, err := NewDecoder(r) 208 if err != nil { 209 return nil, err 210 } 211 for _, icon := range d.entries { 212 desc = append(desc, icon.IconDescription) 213 } 214 return desc, nil 215 } 216 217 // elementHeaderSize is the size of the type and length fields that begin 218 // every element, the file header included. 219 const elementHeaderSize = 8 220 221 // element is one type and payload pair from the file. 222 type element struct { 223 id string 224 payload []byte 225 } 226 227 // decode identifies the icons in the icns without decoding the image data. 228 // 229 // An icns file is a sequence of elements, each a 4-byte type followed by a 230 // 4-byte big-endian length that counts the whole element, header included. 231 // The file itself is one such element of type "icns" enclosing the rest. 232 // Every length is checked against the data present, and input that disagrees 233 // is reported as ErrMalformed. 234 func decode(r io.Reader) (icons []Entry, err error) { 235 elements, err := elementsOf(r) 236 if err != nil { 237 return nil, err 238 } 239 // Masks are separate elements that may appear either side of the icon 240 // they belong to, so the payloads are indexed before they are paired up. 241 payloads := make(map[string][]byte, len(elements)) 242 for _, el := range elements { 243 payloads[el.id] = el.payload 244 } 245 for _, el := range elements { 246 osType, ok := getTypeFromID(el.id) 247 if !ok || len(el.payload) == 0 { 248 // Elements this package does not read: the table of contents, 249 // version and name records, masks, and icon types it cannot 250 // decode. 251 continue 252 } 253 icon := Entry{ 254 IconDescription: IconDescription{OsType: osType}, 255 data: el.payload, 256 } 257 // Several types carry more than one format, so the payload decides 258 // wherever it says what it holds. 259 switch { 260 case osType.enc == encodingRGB: 261 icon.ImageFormat = ImageFormatRGB 262 icon.mask = payloads[osType.mask] 263 case osType.enc == encodingBitmap: 264 icon.ImageFormat = ImageFormatBitmap 265 case osType.enc == encodingIndexed4, osType.enc == encodingIndexed8: 266 icon.ImageFormat = ImageFormatIndexed 267 icon.mask = payloads[osType.mask] 268 case bytes.HasPrefix(el.payload, argbHeader): 269 icon.ImageFormat = ImageFormatARGB 270 case bytes.HasPrefix(el.payload, jpeg2000header): 271 icon.ImageFormat = ImageFormatJPEG2000 272 } 273 icons = append(icons, icon) 274 } 275 if len(icons) == 0 { 276 return nil, ErrNoIcons 277 } 278 return icons, nil 279 } 280 281 // elementsOf splits an icns file into its elements. 282 func elementsOf(r io.Reader) ([]element, error) { 283 data, err := io.ReadAll(r) 284 if err != nil { 285 return nil, err 286 } 287 if len(data) < elementHeaderSize || string(data[0:4]) != "icns" { 288 return nil, ErrInvalidHeader 289 } 290 fileSize := int(binary.BigEndian.Uint32(data[4:8])) 291 if fileSize > len(data) { 292 return nil, fmt.Errorf("%w: header declares %d bytes but only %d are present", ErrMalformed, fileSize, len(data)) 293 } 294 data = data[:fileSize] 295 var elements []element 296 for offset := elementHeaderSize; offset < len(data); { 297 if len(data)-offset < elementHeaderSize { 298 return nil, fmt.Errorf("%w: truncated element header at offset %d", ErrMalformed, offset) 299 } 300 var ( 301 id = string(data[offset : offset+4]) 302 size = int(binary.BigEndian.Uint32(data[offset+4 : offset+8])) 303 ) 304 if size < elementHeaderSize || size > len(data)-offset { 305 return nil, fmt.Errorf("%w: element %q at offset %d declares %d bytes", ErrMalformed, id, offset, size) 306 } 307 elements = append(elements, element{ 308 id: id, 309 payload: data[offset+elementHeaderSize : offset+size], 310 }) 311 offset += size 312 } 313 return elements, nil 314 } 315 316 func isOsType(ID string) bool { 317 _, ok := getTypeFromID(ID) 318 return ok 319 } 320 321 func init() { 322 image.RegisterFormat("icns", "icns", Decode, nil) 323 }