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  }