| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213 |
- package ico
- import (
- "bytes"
- "encoding/binary"
- "fmt"
- "image"
- "image/color"
- "image/draw"
- "image/png"
- "io"
- bmp "github.com/jsummers/gobmp"
- )
- func init() {
- image.RegisterFormat("ico", "\x00\x00\x01\x00?????\x00", Decode, DecodeConfig)
- }
- // ---- public ----
- func Decode(r io.Reader) (image.Image, error) {
- var d decoder
- if err := d.decode(r); err != nil {
- return nil, err
- }
- return d.images[0], nil
- }
- func DecodeAll(r io.Reader) ([]image.Image, error) {
- var d decoder
- if err := d.decode(r); err != nil {
- return nil, err
- }
- return d.images, nil
- }
- func DecodeConfig(r io.Reader) (image.Config, error) {
- var (
- d decoder
- cfg image.Config
- err error
- )
- if err = d.decodeHeader(r); err != nil {
- return cfg, err
- }
- if err = d.decodeEntries(r); err != nil {
- return cfg, err
- }
- e := d.entries[0]
- buf := make([]byte, e.Size+14)
- n, err := io.ReadFull(r, buf[14:])
- if err != nil && err != io.ErrUnexpectedEOF {
- return cfg, err
- }
- buf = buf[:14+n]
- if n > 8 && bytes.Equal(buf[14:22], pngHeader) {
- return png.DecodeConfig(bytes.NewReader(buf[14:]))
- }
- d.forgeBMPHead(buf, &e)
- return bmp.DecodeConfig(bytes.NewReader(buf))
- }
- // ---- private ----
- type direntry struct {
- Width byte
- Height byte
- Palette byte
- _ byte
- Plane uint16
- Bits uint16
- Size uint32
- Offset uint32
- }
- type head struct {
- Zero uint16
- Type uint16
- Number uint16
- }
- type decoder struct {
- head head
- entries []direntry
- images []image.Image
- }
- func (d *decoder) decode(r io.Reader) (err error) {
- if err = d.decodeHeader(r); err != nil {
- return err
- }
- if err = d.decodeEntries(r); err != nil {
- return err
- }
- d.images = make([]image.Image, d.head.Number)
- for i := range d.entries {
- e := &(d.entries[i])
- data := make([]byte, e.Size+14)
- n, err := io.ReadFull(r, data[14:])
- if err != nil && err != io.ErrUnexpectedEOF {
- return err
- }
- data = data[:14+n]
- if n > 8 && bytes.Equal(data[14:22], pngHeader) { // decode as PNG
- if d.images[i], err = png.Decode(bytes.NewReader(data[14:])); err != nil {
- return err
- }
- } else { // decode as BMP
- maskData := d.forgeBMPHead(data, e)
- if maskData != nil {
- data = data[:n+14-len(maskData)]
- }
- if d.images[i], err = bmp.Decode(bytes.NewReader(data)); err != nil {
- return err
- }
- bounds := d.images[i].Bounds()
- mask := image.NewAlpha(image.Rect(0, 0, bounds.Dx(), bounds.Dy()))
- masked := image.NewNRGBA(image.Rect(0, 0, bounds.Dx(), bounds.Dy()))
- for row := 0; row < int(e.Height); row++ {
- for col := 0; col < int(e.Width); col++ {
- if maskData != nil {
- rowSize := (int(e.Width) + 31) / 32 * 4
- if (maskData[row*rowSize+col/8]>>(7-uint(col)%8))&0x01 != 1 {
- mask.SetAlpha(col, int(e.Height)-row-1, color.Alpha{255})
- }
- } else { // 32-Bit
- rowSize := (int(e.Width)*32 + 31) / 32 * 4
- offset := int(binary.LittleEndian.Uint32(data[10:14]))
- mask.SetAlpha(col, int(e.Height)-row-1, color.Alpha{data[offset+row*rowSize+col*4+3]})
- }
- }
- }
- draw.DrawMask(masked, masked.Bounds(), d.images[i], bounds.Min, mask, bounds.Min, draw.Src)
- d.images[i] = masked
- }
- }
- return nil
- }
- func (d *decoder) decodeHeader(r io.Reader) error {
- binary.Read(r, binary.LittleEndian, &(d.head))
- if d.head.Zero != 0 || d.head.Type != 1 {
- return fmt.Errorf("corrupted head: [%x,%x]", d.head.Zero, d.head.Type)
- }
- return nil
- }
- func (d *decoder) decodeEntries(r io.Reader) error {
- n := int(d.head.Number)
- d.entries = make([]direntry, n)
- for i := 0; i < n; i++ {
- if err := binary.Read(r, binary.LittleEndian, &(d.entries[i])); err != nil {
- return err
- }
- }
- return nil
- }
- func (d *decoder) forgeBMPHead(buf []byte, e *direntry) (mask []byte) {
- // See en.wikipedia.org/wiki/BMP_file_format
- data := buf[14:]
- imageSize := len(data)
- if e.Bits != 32 {
- maskSize := (int(e.Width) + 31) / 32 * 4 * int(e.Height)
- imageSize -= maskSize
- if imageSize <= 0 {
- return
- }
- mask = data[imageSize:]
- }
- copy(buf[0:2], "\x42\x4D") // Magic number
- dibSize := binary.LittleEndian.Uint32(data[:4])
- w := binary.LittleEndian.Uint32(data[4:8])
- h := binary.LittleEndian.Uint32(data[8:12])
- if h > w {
- binary.LittleEndian.PutUint32(data[8:12], h/2)
- }
- binary.LittleEndian.PutUint32(buf[2:6], uint32(imageSize)) // File size
- // Calculate offset into image data
- numColors := binary.LittleEndian.Uint32(data[32:36])
- bits := binary.LittleEndian.Uint16(data[14:16])
- switch bits {
- case 1, 2, 4, 8:
- x := uint32(1 << bits)
- if numColors == 0 || numColors > x {
- numColors = x
- }
- default:
- numColors = 0
- }
- var numColorsSize uint32
- switch dibSize {
- case 12, 64:
- numColorsSize = numColors * 3
- default:
- numColorsSize = numColors * 4
- }
- offset := 14 + dibSize + numColorsSize
- if dibSize > 40 && int(dibSize-4) <= len(data) {
- offset += binary.LittleEndian.Uint32(data[dibSize-8 : dibSize-4])
- }
- binary.LittleEndian.PutUint32(buf[10:14], offset)
- return
- }
- var pngHeader = []byte{'\x89', 'P', 'N', 'G', '\r', '\n', '\x1a', '\n'}
|