icns

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

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 }