icns

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

rle.go (5329B)


      1 package icns
      2 
      3 import (
      4 	"fmt"
      5 	"image"
      6 	"image/color"
      7 )
      8 
      9 // Legacy icon types store their colour as three run-length encoded planes,
     10 // one per channel, and their alpha in a separate mask element.
     11 
     12 // unpackRLE expands the icns variant of PackBits until want bytes are
     13 // produced. A lead byte below 128 introduces lead+1 literal bytes; a lead
     14 // byte of 128 or above repeats the byte after it lead-125 times. Data that is
     15 // already want bytes long is stored uncompressed and is returned as it is.
     16 //
     17 // The second result is how many bytes of data were read, which is short of
     18 // its length when the stream carries padding after the last run.
     19 func unpackRLE(data []byte, want int) ([]byte, int, error) {
     20 	if len(data) == want {
     21 		return data, want, nil
     22 	}
     23 	out := make([]byte, 0, want)
     24 	used := 0
     25 	for i := 0; i < len(data) && len(out) < want; {
     26 		lead := int(data[i])
     27 		i++
     28 		if lead < 128 {
     29 			n := lead + 1
     30 			if i+n > len(data) {
     31 				return nil, i, fmt.Errorf("%w: literal run of %d bytes overruns the element", ErrMalformed, n)
     32 			}
     33 			out = append(out, data[i:i+n]...)
     34 			i += n
     35 			used = i
     36 			continue
     37 		}
     38 		if i == len(data) {
     39 			return nil, i, fmt.Errorf("%w: repeat run with no byte to repeat", ErrMalformed)
     40 		}
     41 		for n := lead - 125; n > 0; n-- {
     42 			out = append(out, data[i])
     43 		}
     44 		i++
     45 		used = i
     46 	}
     47 	if len(out) != want {
     48 		return nil, len(data), fmt.Errorf("%w: expanded to %d bytes, want %d", ErrMalformed, len(out), want)
     49 	}
     50 	return out, used, nil
     51 }
     52 
     53 // packRLE compresses data into the icns variant of PackBits. A run of three
     54 // or more equal bytes is worth encoding, since it costs two bytes either way,
     55 // and anything shorter goes out as literals. Data that does not compress is
     56 // returned unchanged, which the decoder recognises by its length.
     57 func packRLE(data []byte) []byte {
     58 	out := make([]byte, 0, len(data))
     59 	for i := 0; i < len(data); {
     60 		run := 1
     61 		for i+run < len(data) && run < maxRepeat && data[i+run] == data[i] {
     62 			run++
     63 		}
     64 		if run >= 3 {
     65 			out = append(out, byte(run+125), data[i])
     66 			i += run
     67 			continue
     68 		}
     69 		// Literals up to the next run of three, since that run encodes more
     70 		// cheaply on its own.
     71 		start := i
     72 		for i < len(data) && i-start < maxLiteral {
     73 			if i+2 < len(data) && data[i] == data[i+1] && data[i] == data[i+2] {
     74 				break
     75 			}
     76 			i++
     77 		}
     78 		out = append(out, byte(i-start-1))
     79 		out = append(out, data[start:i]...)
     80 	}
     81 	if len(out) >= len(data) {
     82 		return data
     83 	}
     84 	return out
     85 }
     86 
     87 const (
     88 	// maxLiteral is the longest literal run, from a lead byte of 127.
     89 	maxLiteral = 128
     90 	// maxRepeat is the longest repeat, from a lead byte of 255.
     91 	maxRepeat = 130
     92 )
     93 
     94 // padRLE appends a byte to compressed data, which a decoder that drops the
     95 // last value of a stream then loses instead of a pixel. Apple's own reader
     96 // does exactly that on Apple silicon, turning the tail of the blue plane
     97 // black. Data that was stored uncompressed is left at its exact length,
     98 // which is how a reader tells the two apart.
     99 func padRLE(data []byte, uncompressed int) []byte {
    100 	if len(data) == uncompressed {
    101 		return data
    102 	}
    103 	return append(data, 0)
    104 }
    105 
    106 // splitPlanes separates an image into the three colour planes and the alpha
    107 // mask that the legacy elements store separately. The planes hold straight
    108 // colour, so alpha is divided back out.
    109 func splitPlanes(img image.Image, side int) (planes, mask []byte) {
    110 	pixels := side * side
    111 	planes = make([]byte, pixels*3)
    112 	mask = make([]byte, pixels)
    113 	origin := img.Bounds().Min
    114 	for y := 0; y < side; y++ {
    115 		for x := 0; x < side; x++ {
    116 			c := color.NRGBAModel.Convert(img.At(origin.X+x, origin.Y+y)).(color.NRGBA)
    117 			i := y*side + x
    118 			planes[i] = c.R
    119 			planes[pixels+i] = c.G
    120 			planes[pixels*2+i] = c.B
    121 			mask[i] = c.A
    122 		}
    123 	}
    124 	return planes, mask
    125 }
    126 
    127 // decodeARGB builds an image from the four run-length encoded planes that
    128 // follow an ARGB header, alpha first and then the colour channels.
    129 func decodeARGB(data []byte, side int) (image.Image, error) {
    130 	pixels := side * side
    131 	planes, _, err := unpackRLE(data, pixels*4)
    132 	if err != nil {
    133 		return nil, err
    134 	}
    135 	// Alpha is a plane of its own rather than folded into the colour, so the
    136 	// result is non-premultiplied.
    137 	img := image.NewNRGBA(image.Rect(0, 0, side, side))
    138 	for i := 0; i < pixels; i++ {
    139 		px := img.Pix[i*4 : i*4+4 : i*4+4]
    140 		px[3] = planes[i]
    141 		px[0] = planes[pixels+i]
    142 		px[1] = planes[pixels*2+i]
    143 		px[2] = planes[pixels*3+i]
    144 	}
    145 	return img, nil
    146 }
    147 
    148 // decodeRGB builds an image from run-length encoded colour planes and the
    149 // raw alpha of the matching mask element. A missing mask leaves the icon
    150 // opaque, which is how the icons that predate masks are meant to render.
    151 func decodeRGB(data, mask []byte, side int) (image.Image, error) {
    152 	pixels := side * side
    153 	planes, _, err := unpackRLE(data, pixels*3)
    154 	if err != nil {
    155 		return nil, err
    156 	}
    157 	if mask != nil && len(mask) != pixels {
    158 		return nil, fmt.Errorf("%w: mask holds %d bytes, want %d", ErrMalformed, len(mask), pixels)
    159 	}
    160 	// The planes carry straight colour and the mask carries alpha, so the
    161 	// result is non-premultiplied.
    162 	img := image.NewNRGBA(image.Rect(0, 0, side, side))
    163 	for i := 0; i < pixels; i++ {
    164 		px := img.Pix[i*4 : i*4+4 : i*4+4]
    165 		px[0] = planes[i]
    166 		px[1] = planes[pixels+i]
    167 		px[2] = planes[pixels*2+i]
    168 		px[3] = 0xFF
    169 		if mask != nil {
    170 			px[3] = mask[i]
    171 		}
    172 	}
    173 	return img, nil
    174 }