heic.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. package imagemeta
  2. import (
  3. "bytes"
  4. "encoding/binary"
  5. "errors"
  6. "fmt"
  7. "io"
  8. )
  9. const heicBoxHeaderSize = int64(8)
  10. var heicBrand = []byte("heic")
  11. var heicPict = []byte("pict")
  12. type heicDimensionsData struct {
  13. Width, Height int64
  14. }
  15. func (d *heicDimensionsData) IsFilled() bool {
  16. return d.Width > 0 && d.Height > 0
  17. }
  18. func heicReadBoxHeader(r io.Reader) (boxType string, boxDataSize int64, err error) {
  19. b := make([]byte, heicBoxHeaderSize)
  20. _, err = r.Read(b)
  21. if err != nil {
  22. return
  23. }
  24. boxDataSize = int64(binary.BigEndian.Uint32(b[0:4])) - heicBoxHeaderSize
  25. boxType = string(b[4:8])
  26. return
  27. }
  28. func heicReadBoxData(r io.Reader, boxDataSize int64) (b []byte, err error) {
  29. b = make([]byte, boxDataSize)
  30. _, err = r.Read(b)
  31. return
  32. }
  33. func heicReadFtyp(r io.Reader, boxDataSize int64) error {
  34. if boxDataSize < 8 {
  35. return errors.New("Invalid ftyp data")
  36. }
  37. data, err := heicReadBoxData(r, boxDataSize)
  38. if err != nil {
  39. return err
  40. }
  41. if bytes.Equal(data[0:4], heicBrand) {
  42. return nil
  43. }
  44. if boxDataSize >= 12 {
  45. for i := int64(8); i < boxDataSize; i += 4 {
  46. if bytes.Equal(data[i:i+4], heicBrand) {
  47. return nil
  48. }
  49. }
  50. }
  51. return errors.New("Image is not compatible with heic")
  52. }
  53. func heicReadMeta(d *heicDimensionsData, r io.Reader, boxDataSize int64) error {
  54. if boxDataSize < 4 {
  55. return errors.New("Invalid meta data")
  56. }
  57. if _, err := r.Read(make([]byte, 4)); err != nil {
  58. return err
  59. }
  60. if boxDataSize > 4 {
  61. if err := heicReadBoxes(d, io.LimitReader(r, boxDataSize-4)); err != nil && err != io.EOF {
  62. return err
  63. }
  64. }
  65. return nil
  66. }
  67. func heicReadHldr(r io.Reader, boxDataSize int64) error {
  68. if boxDataSize < 12 {
  69. return errors.New("Invalid hdlr data")
  70. }
  71. data, err := heicReadBoxData(r, boxDataSize)
  72. if err != nil {
  73. return err
  74. }
  75. if !bytes.Equal(data[8:12], heicPict) {
  76. return fmt.Errorf("Invalid handler. Expected: pict, actual: %s", data[8:12])
  77. }
  78. return nil
  79. }
  80. func heicReadIspe(r io.Reader, boxDataSize int64) (w, h int64, err error) {
  81. if boxDataSize < 12 {
  82. return 0, 0, errors.New("Invalid ispe data")
  83. }
  84. data, err := heicReadBoxData(r, boxDataSize)
  85. if err != nil {
  86. return 0, 0, err
  87. }
  88. w = int64(binary.BigEndian.Uint32(data[4:8]))
  89. h = int64(binary.BigEndian.Uint32(data[8:12]))
  90. return
  91. }
  92. func heicReadBoxes(d *heicDimensionsData, r io.Reader) error {
  93. for {
  94. boxType, boxDataSize, err := heicReadBoxHeader(r)
  95. if err != nil {
  96. return err
  97. }
  98. if boxDataSize < 0 {
  99. return errors.New("Invalid box data")
  100. }
  101. // log.Printf("Box type: %s; Box data size: %d", boxType, boxDataSize)
  102. switch boxType {
  103. case "ftyp":
  104. if err := heicReadFtyp(r, boxDataSize); err != nil {
  105. return err
  106. }
  107. case "meta":
  108. if err := heicReadMeta(d, r, boxDataSize); err != nil {
  109. return err
  110. }
  111. if !d.IsFilled() {
  112. return errors.New("Dimensions data wan't found in meta box")
  113. }
  114. return nil
  115. case "hdlr":
  116. if err := heicReadHldr(r, boxDataSize); err != nil {
  117. return nil
  118. }
  119. case "iprp", "ipco":
  120. if err := heicReadBoxes(d, io.LimitReader(r, boxDataSize)); err != nil && err != io.EOF {
  121. return err
  122. }
  123. case "ispe":
  124. w, h, err := heicReadIspe(r, boxDataSize)
  125. if err != nil {
  126. return err
  127. }
  128. if w > d.Width || h > d.Height {
  129. d.Width, d.Height = w, h
  130. }
  131. case "mdat":
  132. return errors.New("mdat box occurred before meta box")
  133. default:
  134. if _, err := heicReadBoxData(r, boxDataSize); err != nil {
  135. return err
  136. }
  137. }
  138. }
  139. }
  140. func DecodeHeicMeta(r io.Reader) (Meta, error) {
  141. d := new(heicDimensionsData)
  142. if err := heicReadBoxes(d, r); err != nil && !d.IsFilled() {
  143. return nil, err
  144. }
  145. return &meta{
  146. format: "heic",
  147. width: int(d.Width),
  148. height: int(d.Height),
  149. }, nil
  150. }
  151. func init() {
  152. RegisterFormat("????ftyp", DecodeHeicMeta)
  153. }