1
0

jxl.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. package imagemeta
  2. import (
  3. "bytes"
  4. "encoding/binary"
  5. "io"
  6. "github.com/imgproxy/imgproxy/v3/imagetype"
  7. )
  8. const (
  9. jxlCodestreamHeaderMinSize = 4
  10. jxlCodestreamHeaderMaxSize = 11
  11. )
  12. var jxlCodestreamMarker = []byte{0xff, 0x0a}
  13. var jxlISOBMFFMarker = []byte{0x00, 0x00, 0x00, 0x0C, 0x4A, 0x58, 0x4C, 0x20, 0x0D, 0x0A, 0x87, 0x0A}
  14. var jxlSizeSizes = []uint64{9, 13, 18, 30}
  15. var jxlRatios = [][]uint64{
  16. {1, 1},
  17. {12, 10},
  18. {4, 3},
  19. {3, 2},
  20. {16, 9},
  21. {5, 4},
  22. {2, 1},
  23. }
  24. type jxlBitReader struct {
  25. buf uint64
  26. bufLen uint64
  27. }
  28. func NewJxlBitReader(data []byte) *jxlBitReader {
  29. return &jxlBitReader{
  30. buf: binary.LittleEndian.Uint64(data),
  31. bufLen: uint64(len(data) * 8),
  32. }
  33. }
  34. func (br *jxlBitReader) Read(n uint64) (uint64, error) {
  35. if n > br.bufLen {
  36. return 0, io.EOF
  37. }
  38. mask := uint64(1<<n) - 1
  39. res := br.buf & mask
  40. br.buf >>= n
  41. br.bufLen -= n
  42. return res, nil
  43. }
  44. func jxlReadJxlc(r io.Reader, boxDataSize uint64) ([]byte, error) {
  45. if boxDataSize < jxlCodestreamHeaderMinSize {
  46. return nil, newFormatError("JPEG XL", "invalid codestream box")
  47. }
  48. toRead := boxDataSize
  49. if toRead > jxlCodestreamHeaderMaxSize {
  50. toRead = jxlCodestreamHeaderMaxSize
  51. }
  52. return heifReadN(r, toRead)
  53. }
  54. func jxlReadJxlp(r io.Reader, boxDataSize uint64, codestream []byte) ([]byte, bool, error) {
  55. if boxDataSize < 4 {
  56. return nil, false, newFormatError("JPEG XL", "invalid jxlp box")
  57. }
  58. jxlpInd, err := heifReadN(r, 4)
  59. if err != nil {
  60. return nil, false, err
  61. }
  62. last := jxlpInd[0] == 0x80
  63. readLeft := jxlCodestreamHeaderMaxSize - len(codestream)
  64. if readLeft <= 0 {
  65. return codestream, last, nil
  66. }
  67. toRead := boxDataSize - 4
  68. if uint64(readLeft) < toRead {
  69. toRead = uint64(readLeft)
  70. }
  71. data, err := heifReadN(r, toRead)
  72. if err != nil {
  73. return nil, last, err
  74. }
  75. if codestream == nil {
  76. codestream = make([]byte, 0, jxlCodestreamHeaderMaxSize)
  77. }
  78. return append(codestream, data...), last, nil
  79. }
  80. // We can reuse HEIF functions to read ISO BMFF boxes
  81. func jxlFindCodestream(r io.Reader) ([]byte, error) {
  82. var (
  83. codestream []byte
  84. last bool
  85. )
  86. for {
  87. boxType, boxDataSize, err := heifReadBoxHeader(r)
  88. if err != nil {
  89. return nil, err
  90. }
  91. switch boxType {
  92. // jxlc box contins full codestream.
  93. // We can just read and return its header
  94. case "jxlc":
  95. codestream, err = jxlReadJxlc(r, boxDataSize)
  96. return codestream, err
  97. // jxlp partial codestream.
  98. // We should read its data until we read jxlCodestreamHeaderSize bytes
  99. case "jxlp":
  100. codestream, last, err = jxlReadJxlp(r, boxDataSize, codestream)
  101. if err != nil {
  102. return nil, err
  103. }
  104. csLen := len(codestream)
  105. if csLen >= jxlCodestreamHeaderMaxSize || (last && csLen >= jxlCodestreamHeaderMinSize) {
  106. return codestream, nil
  107. }
  108. if last {
  109. return nil, newFormatError("JPEG XL", "invalid codestream box")
  110. }
  111. // Skip other boxes
  112. default:
  113. if err := heifDiscardN(r, boxDataSize); err != nil {
  114. return nil, err
  115. }
  116. }
  117. }
  118. }
  119. func jxlParseSize(br *jxlBitReader, small bool) (uint64, error) {
  120. if small {
  121. size, err := br.Read(5)
  122. return (size + 1) * 8, err
  123. } else {
  124. selector, err := br.Read(2)
  125. if err != nil {
  126. return 0, err
  127. }
  128. sizeSize := jxlSizeSizes[selector]
  129. size, err := br.Read(sizeSize)
  130. return size + 1, err
  131. }
  132. }
  133. func jxlDecodeCodestreamHeader(buf []byte) (width, height uint64, err error) {
  134. if len(buf) < jxlCodestreamHeaderMinSize {
  135. return 0, 0, newFormatError("JPEG XL", "invalid codestream header")
  136. }
  137. if !bytes.Equal(buf[0:2], jxlCodestreamMarker) {
  138. return 0, 0, newFormatError("JPEG XL", "missing codestream marker")
  139. }
  140. br := NewJxlBitReader(buf[2:])
  141. smallBit, sbErr := br.Read(1)
  142. if sbErr != nil {
  143. return 0, 0, sbErr
  144. }
  145. small := smallBit == 1
  146. height, err = jxlParseSize(br, small)
  147. if err != nil {
  148. return 0, 0, err
  149. }
  150. ratioIdx, riErr := br.Read(3)
  151. if riErr != nil {
  152. return 0, 0, riErr
  153. }
  154. if ratioIdx == 0 {
  155. width, err = jxlParseSize(br, small)
  156. } else {
  157. ratio := jxlRatios[ratioIdx-1]
  158. width = height * ratio[0] / ratio[1]
  159. }
  160. return
  161. }
  162. func DecodeJxlMeta(r io.Reader) (Meta, error) {
  163. var (
  164. tmp [12]byte
  165. codestream []byte
  166. width, height uint64
  167. err error
  168. )
  169. if _, err = io.ReadFull(r, tmp[:2]); err != nil {
  170. return nil, err
  171. }
  172. if bytes.Equal(tmp[0:2], jxlCodestreamMarker) {
  173. if _, err = io.ReadFull(r, tmp[2:]); err != nil {
  174. return nil, err
  175. }
  176. codestream = tmp[:]
  177. } else {
  178. if _, err = io.ReadFull(r, tmp[2:12]); err != nil {
  179. return nil, err
  180. }
  181. if !bytes.Equal(tmp[0:12], jxlISOBMFFMarker) {
  182. return nil, newFormatError("JPEG XL", "invalid header")
  183. }
  184. codestream, err = jxlFindCodestream(r)
  185. if err != nil {
  186. return nil, err
  187. }
  188. }
  189. width, height, err = jxlDecodeCodestreamHeader(codestream)
  190. if err != nil {
  191. return nil, err
  192. }
  193. return &meta{
  194. format: imagetype.JXL,
  195. width: int(width),
  196. height: int(height),
  197. }, nil
  198. }
  199. func init() {
  200. RegisterFormat(string(jxlCodestreamMarker), DecodeJxlMeta)
  201. RegisterFormat(string(jxlISOBMFFMarker), DecodeJxlMeta)
  202. }