ico.go (5017B)
1 // Package ico implements an encoder and decoder for the Windows icon format. 2 // 3 // An ico file holds several sizes of the same icon, the way an icns does, so 4 // a program that ships both writes one source image to each and lets the 5 // platform pick. The sizes written are the ones Windows draws, from 16 up to 6 // 256 pixels. 7 // 8 // Each icon is stored either as a device independent bitmap, which every 9 // version of Windows reads, or as a PNG, which Vista introduced and which 10 // keeps the largest size from dominating the file. 11 package ico 12 13 import ( 14 "errors" 15 "fmt" 16 "image" 17 "io" 18 19 "github.com/jackmordaunt/icns/v4/internal/resample" 20 ) 21 22 // InterpolationFunction is the algorithm used to resize the image. 23 type InterpolationFunction = resample.Function 24 25 // InterpolationFunction constants, ordered from fastest to highest quality. 26 const ( 27 // Nearest-neighbor interpolation 28 NearestNeighbor = resample.NearestNeighbor 29 // Bilinear interpolation 30 Bilinear = resample.Bilinear 31 // Bicubic interpolation (with cubic hermite spline) 32 Bicubic = resample.Bicubic 33 // Mitchell-Netravali interpolation 34 MitchellNetravali = resample.MitchellNetravali 35 // Lanczos interpolation (a=2) 36 Lanczos2 = resample.Lanczos2 37 // Lanczos interpolation (a=3) 38 Lanczos3 = resample.Lanczos3 39 ) 40 41 // Errors returned by the decoder. They are wrapped with detail, so compare 42 // with errors.Is. 43 var ( 44 // ErrInvalidHeader means the data does not begin with an icon directory. 45 ErrInvalidHeader = errors.New("invalid header for ico file") 46 // ErrMalformed means an offset or length disagrees with the data 47 // present; the file is truncated or corrupt. 48 ErrMalformed = errors.New("malformed ico file") 49 // ErrNoIcons means the directory holds no entries. 50 ErrNoIcons = errors.New("no icons found") 51 // ErrUnsupportedFormat means an icon is stored in a way this package 52 // cannot read, and no registered decoder reads it either. 53 ErrUnsupportedFormat = errors.New("unsupported image format") 54 ) 55 56 // ErrImageTooSmall is returned when the image is too small to process. 57 type ErrImageTooSmall struct { 58 need int 59 image image.Image 60 } 61 62 func (err ErrImageTooSmall) Error() string { 63 b := err.image.Bounds() 64 return fmt.Sprintf("image is too small: %dx%d, need at least %dx%d", b.Dx(), b.Dy(), err.need, err.need) 65 } 66 67 // Format is how an icon's pixels are stored inside the file. 68 type Format int 69 70 const ( 71 // FormatBMP is a device independent bitmap with a one bit mask. 72 FormatBMP Format = iota 73 // FormatPNG is a whole PNG file. 74 FormatPNG 75 ) 76 77 func (f Format) String() string { 78 switch f { 79 case FormatBMP: 80 return "BMP" 81 case FormatPNG: 82 return "PNG" 83 } 84 return fmt.Sprintf("unknown format %d", f) 85 } 86 87 // sizes are the icon sizes written, largest first. Windows draws icons at 88 // each of them, and 256 is the largest the format addresses. 89 var sizes = []uint{256, 128, 64, 48, 32, 24, 16} 90 91 // Sizes returns the icon sizes an encoded file holds, largest first. 92 func Sizes() []uint { 93 out := make([]uint, len(sizes)) 94 copy(out, sizes) 95 return out 96 } 97 98 // smallest is the size below which there is nothing worth writing. 99 const smallest = 16 100 101 // Encoder encodes ico files from a source image. 102 type Encoder struct { 103 Wr io.Writer 104 Algorithm InterpolationFunction 105 } 106 107 // NewEncoder initialises an encoder. 108 func NewEncoder(wr io.Writer) *Encoder { 109 return &Encoder{ 110 Wr: wr, 111 Algorithm: MitchellNetravali, 112 } 113 } 114 115 // WithAlgorithm applies the interpolation function used to resize the image. 116 func (enc *Encoder) WithAlgorithm(a InterpolationFunction) *Encoder { 117 enc.Algorithm = a 118 return enc 119 } 120 121 // Encode writes the image at every size the file holds, resizing it for each. 122 func (enc *Encoder) Encode(img image.Image) error { 123 if enc.Wr == nil { 124 return errors.New("cannot write to nil writer") 125 } 126 if img == nil { 127 return errors.New("cannot encode nil image") 128 } 129 return enc.write(nil, img) 130 } 131 132 // EncodeSizes writes artwork supplied per size, so a drawing made for one 133 // size is used there rather than reduced from a larger one. Sizes given no 134 // artwork are filled from the largest image supplied, which also sets the 135 // largest icon written. 136 func (enc *Encoder) EncodeSizes(images map[uint]image.Image) error { 137 if enc.Wr == nil { 138 return errors.New("cannot write to nil writer") 139 } 140 if len(images) == 0 { 141 return errors.New("cannot encode without an image") 142 } 143 var source image.Image 144 for _, size := range sizes { 145 img, ok := images[size] 146 if !ok { 147 continue 148 } 149 if img == nil { 150 return fmt.Errorf("cannot encode nil image for %d", size) 151 } 152 // sizes runs largest first, so the first match is the biggest slot 153 // that was filled. 154 if source == nil || resample.BiggestSide(img) > resample.BiggestSide(source) { 155 source = img 156 } 157 } 158 if source == nil { 159 return errors.New("no image was given for a size this format holds") 160 } 161 return enc.write(images, source) 162 } 163 164 // Encode writes img to wr in ico format, at every size the file holds. 165 func Encode(wr io.Writer, img image.Image) error { 166 return NewEncoder(wr).Encode(img) 167 }