ico.go 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. package imagemeta
  2. import (
  3. "bytes"
  4. "encoding/binary"
  5. "io"
  6. "github.com/imgproxy/imgproxy/v3/imagetype"
  7. )
  8. type IcoMeta struct {
  9. Meta
  10. offset int
  11. size int
  12. }
  13. func (m *IcoMeta) BestImageOffset() int {
  14. return m.offset
  15. }
  16. func (m *IcoMeta) BestImageSize() int {
  17. return m.size
  18. }
  19. func icoBestSize(r io.Reader) (width, height byte, offset uint32, size uint32, err error) {
  20. var tmp [16]byte
  21. if _, err = io.ReadFull(r, tmp[:6]); err != nil {
  22. return
  23. }
  24. count := binary.LittleEndian.Uint16(tmp[4:6])
  25. for i := uint16(0); i < count; i++ {
  26. if _, err = io.ReadFull(r, tmp[:]); err != nil {
  27. return
  28. }
  29. if tmp[0] > width || tmp[1] > height || tmp[0] == 0 || tmp[1] == 0 {
  30. width = tmp[0]
  31. height = tmp[1]
  32. size = binary.LittleEndian.Uint32(tmp[8:12])
  33. offset = binary.LittleEndian.Uint32(tmp[12:16])
  34. }
  35. }
  36. return
  37. }
  38. func BestIcoPage(r io.Reader) (int, int, error) {
  39. _, _, offset, size, err := icoBestSize(r)
  40. return int(offset), int(size), err
  41. }
  42. func DecodeIcoMeta(r io.Reader) (*IcoMeta, error) {
  43. bwidth, bheight, offset, size, err := icoBestSize(r)
  44. if err != nil {
  45. return nil, err
  46. }
  47. width := int(bwidth)
  48. height := int(bheight)
  49. if width == 0 {
  50. width = 256
  51. }
  52. if height == 0 {
  53. height = 256
  54. }
  55. return &IcoMeta{
  56. Meta: &meta{
  57. format: imagetype.ICO,
  58. width: width,
  59. height: height,
  60. },
  61. offset: int(offset),
  62. size: int(size),
  63. }, nil
  64. }
  65. func init() {
  66. RegisterFormat(
  67. "\x00\x00\x01\x00",
  68. func(r io.Reader) (Meta, error) { return DecodeIcoMeta(r) },
  69. )
  70. }
  71. // FixBmpHeader fixes an incomplete header of BMP stored in ICO
  72. func FixBmpHeader(b []byte) ([]byte, error) {
  73. buf := new(bytes.Buffer)
  74. fileSize := uint32(14 + len(b))
  75. buf.Grow(int(fileSize))
  76. buf.Write(bmpMagick)
  77. if err := binary.Write(buf, binary.LittleEndian, &fileSize); err != nil {
  78. return nil, err
  79. }
  80. reserved := uint32(0)
  81. if err := binary.Write(buf, binary.LittleEndian, &reserved); err != nil {
  82. return nil, err
  83. }
  84. colorUsed := binary.LittleEndian.Uint32(b[32:36])
  85. bitCount := binary.LittleEndian.Uint16(b[14:16])
  86. var pixOffset uint32
  87. if colorUsed == 0 && bitCount <= 8 {
  88. pixOffset = 14 + 40 + 4*(1<<bitCount)
  89. } else {
  90. pixOffset = 14 + 40 + 4*colorUsed
  91. }
  92. if err := binary.Write(buf, binary.LittleEndian, &pixOffset); err != nil {
  93. return nil, err
  94. }
  95. // Write size and width
  96. buf.Write(b[:8])
  97. // For some reason ICO stores double height
  98. height := binary.LittleEndian.Uint32(b[8:12]) / 2
  99. if err := binary.Write(buf, binary.LittleEndian, &height); err != nil {
  100. return nil, err
  101. }
  102. // Write the rest
  103. buf.Write(b[12:])
  104. return buf.Bytes(), nil
  105. }