writer.go (2975B)
1 package icns 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "image" 7 "image/png" 8 "io" 9 ) 10 11 // Icon encodes an icns icon. 12 type Icon struct { 13 Type OsType 14 Image image.Image 15 16 elements []element 17 } 18 19 // WriteTo encodes the icon into wr. 20 func (i *Icon) WriteTo(wr io.Writer) (int64, error) { 21 if err := i.encode(); err != nil { 22 return 0, err 23 } 24 var written int64 25 for _, el := range i.elements { 26 n, err := writeElement(wr, el) 27 written += n 28 if err != nil { 29 return written, err 30 } 31 } 32 return written, nil 33 } 34 35 // encode builds the elements this icon occupies in the file. 36 func (i *Icon) encode() error { 37 if len(i.elements) > 0 { 38 return nil 39 } 40 if i.Type.enc == encodingRGB { 41 planes, mask := splitPlanes(i.Image, int(i.Type.Size)) 42 i.elements = []element{ 43 {id: i.Type.ID, payload: padRLE(packRLE(planes), len(planes))}, 44 {id: i.Type.mask, payload: mask}, 45 } 46 return nil 47 } 48 data, err := encodeImage(i.Image) 49 if err != nil { 50 return err 51 } 52 i.elements = []element{{id: i.Type.ID, payload: data}} 53 return nil 54 } 55 56 func encodeImage(img image.Image) ([]byte, error) { 57 buf := bytes.NewBuffer(nil) 58 if err := png.Encode(buf, img); err != nil { 59 return nil, err 60 } 61 return buf.Bytes(), nil 62 } 63 64 // writeElement writes one element: its 4-byte type, its length counting the 65 // header, then its payload. 66 func writeElement(wr io.Writer, el element) (int64, error) { 67 var header [elementHeaderSize]byte 68 copy(header[:4], el.id) 69 binary.BigEndian.PutUint32(header[4:], uint32(elementHeaderSize+len(el.payload))) 70 n, err := wr.Write(header[:]) 71 written := int64(n) 72 if err != nil { 73 return written, err 74 } 75 n, err = wr.Write(el.payload) 76 return written + int64(n), err 77 } 78 79 // IconSet encodes a set of icons into an ICNS file. 80 type IconSet struct { 81 Icons []*Icon 82 } 83 84 // WriteTo writes the ICNS file to wr. 85 func (s *IconSet) WriteTo(wr io.Writer) (int64, error) { 86 var elements []element 87 for _, icon := range s.Icons { 88 if icon == nil { 89 continue 90 } 91 if err := icon.encode(); err != nil { 92 return 0, err 93 } 94 elements = append(elements, icon.elements...) 95 } 96 body := bytes.NewBuffer(nil) 97 // The table of contents comes first and covers everything after it, so a 98 // reader can index the file without walking every element. 99 if _, err := writeElement(body, tableOfContents(elements)); err != nil { 100 return 0, err 101 } 102 for _, el := range elements { 103 if _, err := writeElement(body, el); err != nil { 104 return 0, err 105 } 106 } 107 // The file is itself an element enclosing all the others. 108 return writeElement(wr, element{id: "icns", payload: body.Bytes()}) 109 } 110 111 // tableOfContents lists each element's type and total size, in order. 112 func tableOfContents(elements []element) element { 113 toc := element{ 114 id: "TOC ", 115 payload: make([]byte, 0, len(elements)*elementHeaderSize), 116 } 117 for _, el := range elements { 118 toc.payload = append(toc.payload, el.id...) 119 toc.payload = binary.BigEndian.AppendUint32(toc.payload, uint32(elementHeaderSize+len(el.payload))) 120 } 121 return toc 122 }