github.com/richardwilkes/toolbox@v1.121.0/formats/icon/ico/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 ico 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/ICO_(file_format) for information on the .ico file format. 25 26 type header struct { 27 Reserved uint16 28 ImageType uint16 29 Count uint16 30 } 31 32 type entry struct { 33 Width uint8 34 Height uint8 35 Colors uint8 36 Reserved uint8 37 Planes uint16 38 BitPerPixel uint16 39 Size uint32 40 Offset uint32 41 } 42 43 // Encode one or more images into an .ico. At least one image must be provided and no image may have a width or height 44 // greater than 256 pixels. Windows recommends providing 256x256, 48x48, 32x32, and 16x16 icons. 45 func Encode(w io.Writer, images ...image.Image) error { 46 if len(images) == 0 { 47 return errs.New("must supply at least 1 image") 48 } 49 sort.Slice(images, func(i, j int) bool { 50 return images[i].Bounds().Dx() > images[j].Bounds().Dx() 51 }) 52 list := make([][]byte, 0, len(images)) 53 for _, img := range images { 54 bounds := img.Bounds() 55 if bounds.Dx() > 256 || bounds.Dy() > 256 { 56 return errs.New("image too large - .ico has a 256x256 size limit") 57 } 58 if _, ok := img.(*image.RGBA); !ok { 59 m := image.NewRGBA(bounds) 60 draw.Draw(m, bounds, img, bounds.Min, draw.Src) 61 img = m 62 } 63 var buffer bytes.Buffer 64 if err := png.Encode(&buffer, img); err != nil { 65 return errs.Wrap(err) 66 } 67 list = append(list, buffer.Bytes()) 68 } 69 if err := binary.Write(w, binary.LittleEndian, header{ 70 ImageType: 1, 71 Count: uint16(len(images)), 72 }); err != nil { 73 return errs.Wrap(err) 74 } 75 offset := 6 + uint32(len(images))*16 76 for i, img := range images { 77 bounds := img.Bounds() 78 width := bounds.Dx() 79 if width > 255 { 80 width = 0 81 } 82 height := bounds.Dy() 83 if height > 255 { 84 height = 0 85 } 86 e := entry{ 87 Width: uint8(width), 88 Height: uint8(height), 89 Planes: 1, 90 BitPerPixel: 32, 91 Size: uint32(len(list[i])), 92 Offset: offset, 93 } 94 if err := binary.Write(w, binary.LittleEndian, e); err != nil { 95 return errs.Wrap(err) 96 } 97 offset += e.Size 98 } 99 for _, data := range list { 100 if _, err := w.Write(data); err != nil { 101 return errs.Wrap(err) 102 } 103 } 104 return nil 105 }