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 }