icns

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

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 }