ico.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. // Package ico registers image.Decode and DecodeConfig support
  2. // for the icon (container) format.
  3. package ico
  4. import (
  5. "bytes"
  6. "encoding/binary"
  7. "errors"
  8. "image"
  9. "io"
  10. "io/ioutil"
  11. "image/png"
  12. "golang.org/x/image/bmp"
  13. )
  14. type icondir struct {
  15. Reserved uint16
  16. Type uint16
  17. Count uint16
  18. Entries []icondirEntry
  19. }
  20. type icondirEntry struct {
  21. Width byte
  22. Height byte
  23. PaletteCount byte
  24. Reserved byte
  25. ColorPlanes uint16
  26. BitsPerPixel uint16
  27. Size uint32
  28. Offset uint32
  29. }
  30. func (dir *icondir) FindBestIcon() *icondirEntry {
  31. if len(dir.Entries) == 0 {
  32. return nil
  33. }
  34. best := dir.Entries[0]
  35. for _, e := range dir.Entries {
  36. if (e.width() > best.width()) && (e.height() > best.height()) {
  37. best = e
  38. }
  39. }
  40. return &best
  41. }
  42. // ParseIco parses the icon and returns meta information for the icons as icondir.
  43. func ParseIco(r io.Reader) (*icondir, error) {
  44. dir := icondir{}
  45. var err error
  46. err = binary.Read(r, binary.LittleEndian, &dir.Reserved)
  47. if err != nil {
  48. return nil, err
  49. }
  50. err = binary.Read(r, binary.LittleEndian, &dir.Type)
  51. if err != nil {
  52. return nil, err
  53. }
  54. err = binary.Read(r, binary.LittleEndian, &dir.Count)
  55. if err != nil {
  56. return nil, err
  57. }
  58. for i := uint16(0); i < dir.Count; i++ {
  59. entry := icondirEntry{}
  60. e := parseIcondirEntry(r, &entry)
  61. if e != nil {
  62. return nil, e
  63. }
  64. dir.Entries = append(dir.Entries, entry)
  65. }
  66. return &dir, err
  67. }
  68. func parseIcondirEntry(r io.Reader, e *icondirEntry) error {
  69. err := binary.Read(r, binary.LittleEndian, e)
  70. if err != nil {
  71. return err
  72. }
  73. return nil
  74. }
  75. type dibHeader struct {
  76. dibHeaderSize uint32
  77. width uint32
  78. height uint32
  79. }
  80. func (e *icondirEntry) ColorCount() int {
  81. if e.PaletteCount == 0 {
  82. return 256
  83. }
  84. return int(e.PaletteCount)
  85. }
  86. func (e *icondirEntry) width() int {
  87. if e.Width == 0 {
  88. return 256
  89. }
  90. return int(e.Width)
  91. }
  92. func (e *icondirEntry) height() int {
  93. if e.Height == 0 {
  94. return 256
  95. }
  96. return int(e.Height)
  97. }
  98. // DecodeConfig returns just the dimensions of the largest image
  99. // contained in the icon withou decoding the entire icon file.
  100. func DecodeConfig(r io.Reader) (image.Config, error) {
  101. dir, err := ParseIco(r)
  102. if err != nil {
  103. return image.Config{}, err
  104. }
  105. best := dir.FindBestIcon()
  106. if best == nil {
  107. return image.Config{}, errInvalid
  108. }
  109. return image.Config{Width: best.width(), Height: best.height()}, nil
  110. }
  111. // The bitmap header structure we read from an icondirEntry
  112. type bitmapHeaderRead struct {
  113. Size uint32
  114. Width uint32
  115. Height uint32
  116. Planes uint16
  117. BitCount uint16
  118. Compression uint32
  119. ImageSize uint32
  120. XPixelsPerMeter uint32
  121. YPixelsPerMeter uint32
  122. ColorsUsed uint32
  123. ColorsImportant uint32
  124. }
  125. // The bitmap header structure we need to generate for bmp.Decode()
  126. type bitmapHeaderWrite struct {
  127. sigBM [2]byte
  128. fileSize uint32
  129. resverved [2]uint16
  130. pixOffset uint32
  131. Size uint32
  132. Width uint32
  133. Height uint32
  134. Planes uint16
  135. BitCount uint16
  136. Compression uint32
  137. ImageSize uint32
  138. XPixelsPerMeter uint32
  139. YPixelsPerMeter uint32
  140. ColorsUsed uint32
  141. ColorsImportant uint32
  142. }
  143. var errInvalid = errors.New("ico: invalid ICO image")
  144. // Decode returns the largest image contained in the icon
  145. // which might be a bmp or png
  146. func Decode(r io.Reader) (image.Image, error) {
  147. icoBytes, err := ioutil.ReadAll(r)
  148. if err != nil {
  149. return nil, err
  150. }
  151. r = bytes.NewReader(icoBytes)
  152. dir, err := ParseIco(r)
  153. if err != nil {
  154. return nil, errInvalid
  155. }
  156. best := dir.FindBestIcon()
  157. if best == nil {
  158. return nil, errInvalid
  159. }
  160. return parseImage(best, icoBytes)
  161. }
  162. func parseImage(entry *icondirEntry, icoBytes []byte) (image.Image, error) {
  163. r := bytes.NewReader(icoBytes)
  164. r.Seek(int64(entry.Offset), 0)
  165. // Try PNG first then BMP
  166. img, err := png.Decode(r)
  167. if err != nil {
  168. return parseBMP(entry, icoBytes)
  169. }
  170. return img, nil
  171. }
  172. func parseBMP(entry *icondirEntry, icoBytes []byte) (image.Image, error) {
  173. bmpBytes, err := makeFullBMPBytes(entry, icoBytes)
  174. if err != nil {
  175. return nil, err
  176. }
  177. return bmp.Decode(bmpBytes)
  178. }
  179. func makeFullBMPBytes(entry *icondirEntry, icoBytes []byte) (*bytes.Buffer, error) {
  180. r := bytes.NewReader(icoBytes)
  181. r.Seek(int64(entry.Offset), 0)
  182. var err error
  183. h := bitmapHeaderRead{}
  184. err = binary.Read(r, binary.LittleEndian, &h)
  185. if err != nil {
  186. return nil, err
  187. }
  188. if h.Size != 40 || h.Planes != 1 {
  189. return nil, errInvalid
  190. }
  191. var pixOffset uint32
  192. if h.ColorsUsed == 0 && h.BitCount <= 8 {
  193. pixOffset = 14 + 40 + 4*(1<<h.BitCount)
  194. } else {
  195. pixOffset = 14 + 40 + 4*h.ColorsUsed
  196. }
  197. writeHeader := &bitmapHeaderWrite{
  198. sigBM: [2]byte{'B', 'M'},
  199. fileSize: 14 + 40 + uint32(len(icoBytes)), // correct? important?
  200. pixOffset: pixOffset,
  201. Size: 40,
  202. Width: uint32(h.Width),
  203. Height: uint32(h.Height / 2),
  204. Planes: h.Planes,
  205. BitCount: h.BitCount,
  206. Compression: h.Compression,
  207. ColorsUsed: h.ColorsUsed,
  208. ColorsImportant: h.ColorsImportant,
  209. }
  210. buf := new(bytes.Buffer)
  211. if err = binary.Write(buf, binary.LittleEndian, writeHeader); err != nil {
  212. return nil, err
  213. }
  214. io.CopyN(buf, r, int64(entry.Size))
  215. return buf, nil
  216. }
  217. const icoHeader = "\x00\x00\x01\x00"
  218. func init() {
  219. image.RegisterFormat("ico", icoHeader, Decode, DecodeConfig)
  220. }