icns

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

commit 53997348b8910014cc39e77d121af9f7e8e381f3
parent e6262f6023aba2df14b9f86976dcf57364f33acc
Author: Jack Mordaunt <jackmordaunt@gmail.com>
Date:   Sat, 17 Feb 2018 23:48:53 +0100

[+] Introduced Decoder func with unit test.

Diffstat:
Micns_test.go | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Areader.go | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 128 insertions(+), 0 deletions(-)

diff --git a/icns_test.go b/icns_test.go @@ -13,6 +13,64 @@ import ( "github.com/pkg/errors" ) +// TestDecode relies on Encode being correct. +func TestDecode(t *testing.T) { + t.Parallel() + tests := []struct { + desc string + img image.Image + + wantErr bool + }{ + { + "valid rectangle icon", + rect(0, 0, 1024, 1024), + false, + }, + } + for _, tt := range tests { + t.Run(tt.desc, func(st *testing.T) { + buf := bytes.NewBuffer(nil) + if err := Encode(buf, tt.img); err != nil { + st.Fatalf("unexpected error while encoding: %v", err) + } + img, err := Decode(buf) + if !tt.wantErr && err != nil { + st.Fatalf("unexpected error: %v", err) + } + if tt.wantErr && err == nil { + st.Fatalf("wanted error, got nil") + } + if !imageCompare(img, tt.img) { + st.Fatalf("decoded image is incorrect") + } + }) + } +} + +func imageCompare(left, right image.Image) bool { + if left == nil && right == nil { + return true + } + if left == nil && right != nil { + return false + } + if left != nil && right == nil { + return false + } + lb := left.Bounds() + for ii := lb.Min.X; ii <= lb.Max.X; ii++ { + for kk := lb.Min.Y; kk <= lb.Max.Y; kk++ { + lr, lg, lb, la := left.At(ii, kk).RGBA() + rr, rg, rb, ra := right.At(ii, kk).RGBA() + if lr != rr || lg != rg || lb != rb || la != ra { + return false + } + } + } + return true +} + // TestEncode tests for input validation, sanity checks and errors. // The validity of the encoding is not tested here. // Super large images are not tested because the resizing takes too diff --git a/reader.go b/reader.go @@ -0,0 +1,70 @@ +package icns + +import ( + "bytes" + "encoding/binary" + "errors" + "image" + "io" + "io/ioutil" +) + +// Decode finds the largest icon listed in the icns file and returns it, +// ignoring all other sizes. The format returned will be whatever the icon data +// is, typically jpeg or png. +func Decode(r io.Reader) (image.Image, error) { + data, err := ioutil.ReadAll(r) + if err != nil { + return nil, err + } + icnsHeader := data[0:4] + if string(icnsHeader) != "icns" { + return nil, errors.New("invalid header for icns file") + } + fileSize := binary.BigEndian.Uint32(data[4:8]) + icons := []OsType{} + datalist := []io.Reader{} + read := uint32(8) + for read < fileSize { + next := data[read : read+4] + read += 4 + switch string(next) { + case "TOC ": + read += 4 + continue + case "icnV": + read += 4 + continue + } + if isOsType(string(next)) { + iconSize := binary.BigEndian.Uint32(data[read : read+4]) + read += 4 + iconData := data[read : read+iconSize] + read += iconSize + icons = append(icons, osTypeFromID(string(next))) + datalist = append(datalist, bytes.NewBuffer(iconData)) + } + } + var max OsType + var maxData io.Reader + for ii, i := range icons { + if i.Size > max.Size { + max = i + maxData = datalist[ii] + } + } + img, _, err := image.Decode(maxData) + if err != nil { + return nil, err + } + return img, nil +} + +func isOsType(ID string) bool { + _, ok := getTypeFromID(ID) + return ok +} + +func init() { + image.RegisterFormat("icns", "icns", Decode, nil) +}