github.com/richardwilkes/toolbox@v1.121.0/formats/icon/icns/encoder.go (about) 1 // Copyright (c) 2016-2024 by Richard A. Wilkes. All rights reserved. 2 // 3 // This Source Code Form is subject to the terms of the Mozilla Public 4 // License, version 2.0. If a copy of the MPL was not distributed with 5 // this file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 // 7 // This Source Code Form is "Incompatible With Secondary Licenses", as 8 // defined by the Mozilla Public License, version 2.0. 9 10 package icns 11 12 import ( 13 "bytes" 14 "encoding/binary" 15 "image" 16 "image/draw" 17 "image/png" 18 "io" 19 "sort" 20 21 "github.com/richardwilkes/toolbox/errs" 22 ) 23 24 // See https://en.wikipedia.org/wiki/Apple_Icon_Image_format for information on the .icns file format. 25 26 type header struct { 27 Magic [4]uint8 28 Length uint32 29 } 30 31 type entryHeader struct { 32 IconType [4]uint8 33 Length uint32 34 } 35 36 type iconInfo struct { 37 buffer []byte 38 iconType [4]byte 39 } 40 41 // Encode one or more images into an .icns. At least one image must be provided. macOS recommends providing 1024x1024, 42 // 512x512, 256x256, 128x128, 64x64, 32x32, and 16x16. Note that sizes other than these will not be considered valid. 43 func Encode(w io.Writer, images ...image.Image) error { 44 if len(images) == 0 { 45 return errs.New("must supply at least 1 image") 46 } 47 sort.Slice(images, func(i, j int) bool { 48 return images[i].Bounds().Dx() > images[j].Bounds().Dx() 49 }) 50 var info []*iconInfo 51 for _, img := range images { 52 width := img.Bounds().Dx() 53 if width != img.Bounds().Dy() { 54 return errs.New("image must be square") 55 } 56 var ii *iconInfo 57 var err error 58 switch width { 59 case 1024: 60 if ii, err = createPNGData(img, [4]byte{'i', 'c', '1', '0'}); err != nil { 61 return err 62 } 63 info = append(info, ii) 64 case 512: 65 if ii, err = createPNGData(img, [4]byte{'i', 'c', '0', '9'}); err != nil { 66 return err 67 } 68 info = append(info, ii) 69 if ii, err = createPNGData(img, [4]byte{'i', 'c', '1', '4'}); err != nil { 70 return err 71 } 72 info = append(info, ii) 73 case 256: 74 if ii, err = createPNGData(img, [4]byte{'i', 'c', '0', '8'}); err != nil { 75 return err 76 } 77 info = append(info, ii) 78 if ii, err = createPNGData(img, [4]byte{'i', 'c', '1', '3'}); err != nil { 79 return err 80 } 81 info = append(info, ii) 82 case 128: 83 if ii, err = createPNGData(img, [4]byte{'i', 'c', '0', '7'}); err != nil { 84 return err 85 } 86 info = append(info, ii) 87 case 64: 88 if ii, err = createPNGData(img, [4]byte{'i', 'c', '1', '2'}); err != nil { 89 return err 90 } 91 info = append(info, ii) 92 case 32: 93 if ii, err = createPNGData(img, [4]byte{'i', 'c', '1', '1'}); err != nil { 94 return err 95 } 96 info = append(info, ii) 97 if ii, err = createARGBData(img, [4]byte{'i', 'c', '0', '5'}); err != nil { 98 return err 99 } 100 info = append(info, ii) 101 case 16: 102 if ii, err = createARGBData(img, [4]byte{'i', 'c', '0', '4'}); err != nil { 103 return err 104 } 105 info = append(info, ii) 106 default: 107 return errs.New("invalid image size") 108 } 109 } 110 totalBytes := 8 + 8*len(info) 111 for _, one := range info { 112 totalBytes += len(one.buffer) 113 } 114 if err := binary.Write(w, binary.BigEndian, header{ 115 Magic: [4]uint8{'i', 'c', 'n', 's'}, 116 Length: uint32(totalBytes), 117 }); err != nil { 118 return errs.Wrap(err) 119 } 120 for _, one := range info { 121 if err := binary.Write(w, binary.BigEndian, entryHeader{ 122 IconType: one.iconType, 123 Length: 8 + uint32(len(one.buffer)), 124 }); err != nil { 125 return errs.Wrap(err) 126 } 127 if _, err := w.Write(one.buffer); err != nil { 128 return errs.Wrap(err) 129 } 130 } 131 return nil 132 } 133 134 func createPNGData(img image.Image, iconType [4]byte) (*iconInfo, error) { 135 var buffer bytes.Buffer 136 if _, ok := img.(*image.RGBA); !ok { 137 bounds := img.Bounds() 138 m := image.NewRGBA(bounds) 139 draw.Draw(m, bounds, img, bounds.Min, draw.Src) 140 img = m 141 } 142 if err := png.Encode(&buffer, img); err != nil { 143 return nil, errs.Wrap(err) 144 } 145 return &iconInfo{ 146 iconType: iconType, 147 buffer: buffer.Bytes(), 148 }, nil 149 } 150 151 func createARGBData(img image.Image, iconType [4]byte) (*iconInfo, error) { 152 var buffer bytes.Buffer 153 buffer.Write([]byte{'A', 'R', 'G', 'B'}) 154 var nrgba *image.NRGBA 155 nrgba, ok := img.(*image.NRGBA) 156 if !ok { 157 bounds := img.Bounds() 158 nrgba = image.NewNRGBA(bounds) 159 draw.Draw(nrgba, bounds, img, bounds.Min, draw.Src) 160 } 161 size := len(nrgba.Pix) 162 a := make([]byte, size/4) 163 r := make([]byte, size/4) 164 g := make([]byte, size/4) 165 b := make([]byte, size/4) 166 for i := 0; i < size; i += 4 { 167 j := i / 4 168 r[j] = nrgba.Pix[i] 169 g[j] = nrgba.Pix[i+1] 170 b[j] = nrgba.Pix[i+2] 171 a[j] = nrgba.Pix[i+3] 172 } 173 if err := writeChannel(&buffer, a); err != nil { 174 return nil, err 175 } 176 if err := writeChannel(&buffer, r); err != nil { 177 return nil, err 178 } 179 if err := writeChannel(&buffer, g); err != nil { 180 return nil, err 181 } 182 if err := writeChannel(&buffer, b); err != nil { 183 return nil, err 184 } 185 return &iconInfo{ 186 iconType: iconType, 187 buffer: buffer.Bytes(), 188 }, nil 189 } 190 191 func writeChannel(buffer *bytes.Buffer, data []byte) error { 192 size := len(data) 193 for i := 0; i < size; i += 128 { 194 count := size - i 195 if count > 128 { 196 count = 128 197 } 198 if err := buffer.WriteByte(byte(count - 1)); err != nil { 199 return errs.Wrap(err) 200 } 201 if _, err := buffer.Write(data[i : i+count]); err != nil { 202 return errs.Wrap(err) 203 } 204 } 205 return nil 206 }