You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

277 lines
7.6 KiB

4 years ago
  1. #
  2. # The Python Imaging Library
  3. # $Id$
  4. #
  5. # JPEG2000 file handling
  6. #
  7. # History:
  8. # 2014-03-12 ajh Created
  9. #
  10. # Copyright (c) 2014 Coriolis Systems Limited
  11. # Copyright (c) 2014 Alastair Houghton
  12. #
  13. # See the README file for information on usage and redistribution.
  14. #
  15. from . import Image, ImageFile
  16. import struct
  17. import os
  18. import io
  19. __version__ = "0.1"
  20. def _parse_codestream(fp):
  21. """Parse the JPEG 2000 codestream to extract the size and component
  22. count from the SIZ marker segment, returning a PIL (size, mode) tuple."""
  23. hdr = fp.read(2)
  24. lsiz = struct.unpack('>H', hdr)[0]
  25. siz = hdr + fp.read(lsiz - 2)
  26. lsiz, rsiz, xsiz, ysiz, xosiz, yosiz, xtsiz, ytsiz, \
  27. xtosiz, ytosiz, csiz \
  28. = struct.unpack_from('>HHIIIIIIIIH', siz)
  29. ssiz = [None]*csiz
  30. xrsiz = [None]*csiz
  31. yrsiz = [None]*csiz
  32. for i in range(csiz):
  33. ssiz[i], xrsiz[i], yrsiz[i] \
  34. = struct.unpack_from('>BBB', siz, 36 + 3 * i)
  35. size = (xsiz - xosiz, ysiz - yosiz)
  36. if csiz == 1:
  37. if (yrsiz[0] & 0x7f) > 8:
  38. mode = 'I;16'
  39. else:
  40. mode = 'L'
  41. elif csiz == 2:
  42. mode = 'LA'
  43. elif csiz == 3:
  44. mode = 'RGB'
  45. elif csiz == 4:
  46. mode = 'RGBA'
  47. else:
  48. mode = None
  49. return (size, mode)
  50. def _parse_jp2_header(fp):
  51. """Parse the JP2 header box to extract size, component count and
  52. color space information, returning a PIL (size, mode) tuple."""
  53. # Find the JP2 header box
  54. header = None
  55. while True:
  56. lbox, tbox = struct.unpack('>I4s', fp.read(8))
  57. if lbox == 1:
  58. lbox = struct.unpack('>Q', fp.read(8))[0]
  59. hlen = 16
  60. else:
  61. hlen = 8
  62. if lbox < hlen:
  63. raise SyntaxError('Invalid JP2 header length')
  64. if tbox == b'jp2h':
  65. header = fp.read(lbox - hlen)
  66. break
  67. else:
  68. fp.seek(lbox - hlen, os.SEEK_CUR)
  69. if header is None:
  70. raise SyntaxError('could not find JP2 header')
  71. size = None
  72. mode = None
  73. bpc = None
  74. nc = None
  75. hio = io.BytesIO(header)
  76. while True:
  77. lbox, tbox = struct.unpack('>I4s', hio.read(8))
  78. if lbox == 1:
  79. lbox = struct.unpack('>Q', hio.read(8))[0]
  80. hlen = 16
  81. else:
  82. hlen = 8
  83. content = hio.read(lbox - hlen)
  84. if tbox == b'ihdr':
  85. height, width, nc, bpc, c, unkc, ipr \
  86. = struct.unpack('>IIHBBBB', content)
  87. size = (width, height)
  88. if unkc:
  89. if nc == 1 and (bpc & 0x7f) > 8:
  90. mode = 'I;16'
  91. elif nc == 1:
  92. mode = 'L'
  93. elif nc == 2:
  94. mode = 'LA'
  95. elif nc == 3:
  96. mode = 'RGB'
  97. elif nc == 4:
  98. mode = 'RGBA'
  99. break
  100. elif tbox == b'colr':
  101. meth, prec, approx = struct.unpack_from('>BBB', content)
  102. if meth == 1:
  103. cs = struct.unpack_from('>I', content, 3)[0]
  104. if cs == 16: # sRGB
  105. if nc == 1 and (bpc & 0x7f) > 8:
  106. mode = 'I;16'
  107. elif nc == 1:
  108. mode = 'L'
  109. elif nc == 3:
  110. mode = 'RGB'
  111. elif nc == 4:
  112. mode = 'RGBA'
  113. break
  114. elif cs == 17: # grayscale
  115. if nc == 1 and (bpc & 0x7f) > 8:
  116. mode = 'I;16'
  117. elif nc == 1:
  118. mode = 'L'
  119. elif nc == 2:
  120. mode = 'LA'
  121. break
  122. elif cs == 18: # sYCC
  123. if nc == 3:
  124. mode = 'RGB'
  125. elif nc == 4:
  126. mode = 'RGBA'
  127. break
  128. if size is None or mode is None:
  129. raise SyntaxError("Malformed jp2 header")
  130. return (size, mode)
  131. ##
  132. # Image plugin for JPEG2000 images.
  133. class Jpeg2KImageFile(ImageFile.ImageFile):
  134. format = "JPEG2000"
  135. format_description = "JPEG 2000 (ISO 15444)"
  136. def _open(self):
  137. sig = self.fp.read(4)
  138. if sig == b'\xff\x4f\xff\x51':
  139. self.codec = "j2k"
  140. self._size, self.mode = _parse_codestream(self.fp)
  141. else:
  142. sig = sig + self.fp.read(8)
  143. if sig == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a':
  144. self.codec = "jp2"
  145. self._size, self.mode = _parse_jp2_header(self.fp)
  146. else:
  147. raise SyntaxError('not a JPEG 2000 file')
  148. if self.size is None or self.mode is None:
  149. raise SyntaxError('unable to determine size/mode')
  150. self.reduce = 0
  151. self.layers = 0
  152. fd = -1
  153. length = -1
  154. try:
  155. fd = self.fp.fileno()
  156. length = os.fstat(fd).st_size
  157. except:
  158. fd = -1
  159. try:
  160. pos = self.fp.tell()
  161. self.fp.seek(0, 2)
  162. length = self.fp.tell()
  163. self.fp.seek(pos, 0)
  164. except:
  165. length = -1
  166. self.tile = [('jpeg2k', (0, 0) + self.size, 0,
  167. (self.codec, self.reduce, self.layers, fd, length))]
  168. def load(self):
  169. if self.reduce:
  170. power = 1 << self.reduce
  171. adjust = power >> 1
  172. self._size = (int((self.size[0] + adjust) / power),
  173. int((self.size[1] + adjust) / power))
  174. if self.tile:
  175. # Update the reduce and layers settings
  176. t = self.tile[0]
  177. t3 = (t[3][0], self.reduce, self.layers, t[3][3], t[3][4])
  178. self.tile = [(t[0], (0, 0) + self.size, t[2], t3)]
  179. return ImageFile.ImageFile.load(self)
  180. def _accept(prefix):
  181. return (prefix[:4] == b'\xff\x4f\xff\x51' or
  182. prefix[:12] == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a')
  183. # ------------------------------------------------------------
  184. # Save support
  185. def _save(im, fp, filename):
  186. if filename.endswith('.j2k'):
  187. kind = 'j2k'
  188. else:
  189. kind = 'jp2'
  190. # Get the keyword arguments
  191. info = im.encoderinfo
  192. offset = info.get('offset', None)
  193. tile_offset = info.get('tile_offset', None)
  194. tile_size = info.get('tile_size', None)
  195. quality_mode = info.get('quality_mode', 'rates')
  196. quality_layers = info.get('quality_layers', None)
  197. num_resolutions = info.get('num_resolutions', 0)
  198. cblk_size = info.get('codeblock_size', None)
  199. precinct_size = info.get('precinct_size', None)
  200. irreversible = info.get('irreversible', False)
  201. progression = info.get('progression', 'LRCP')
  202. cinema_mode = info.get('cinema_mode', 'no')
  203. fd = -1
  204. if hasattr(fp, "fileno"):
  205. try:
  206. fd = fp.fileno()
  207. except:
  208. fd = -1
  209. im.encoderconfig = (
  210. offset,
  211. tile_offset,
  212. tile_size,
  213. quality_mode,
  214. quality_layers,
  215. num_resolutions,
  216. cblk_size,
  217. precinct_size,
  218. irreversible,
  219. progression,
  220. cinema_mode,
  221. fd
  222. )
  223. ImageFile._save(im, fp, [('jpeg2k', (0, 0)+im.size, 0, kind)])
  224. # ------------------------------------------------------------
  225. # Registry stuff
  226. Image.register_open(Jpeg2KImageFile.format, Jpeg2KImageFile, _accept)
  227. Image.register_save(Jpeg2KImageFile.format, _save)
  228. Image.register_extensions(Jpeg2KImageFile.format,
  229. [".jp2", ".j2k", ".jpc", ".jpf", ".jpx", ".j2c"])
  230. Image.register_mime(Jpeg2KImageFile.format, 'image/jp2')
  231. Image.register_mime(Jpeg2KImageFile.format, 'image/jpx')