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:
| M | icns_test.go | | | 58 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | reader.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)
+}