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  }