Jpeg2KImagePlugin.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  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. import io
  16. import os
  17. import struct
  18. from . import Image, ImageFile
  19. def _parse_codestream(fp):
  20. """Parse the JPEG 2000 codestream to extract the size and component
  21. count from the SIZ marker segment, returning a PIL (size, mode) tuple."""
  22. hdr = fp.read(2)
  23. lsiz = struct.unpack(">H", hdr)[0]
  24. siz = hdr + fp.read(lsiz - 2)
  25. lsiz, rsiz, xsiz, ysiz, xosiz, yosiz, _, _, _, _, csiz = struct.unpack_from(
  26. ">HHIIIIIIIIH", siz
  27. )
  28. ssiz = [None] * csiz
  29. xrsiz = [None] * csiz
  30. yrsiz = [None] * csiz
  31. for i in range(csiz):
  32. ssiz[i], xrsiz[i], yrsiz[i] = struct.unpack_from(">BBB", siz, 36 + 3 * i)
  33. size = (xsiz - xosiz, ysiz - yosiz)
  34. if csiz == 1:
  35. if (yrsiz[0] & 0x7F) > 8:
  36. mode = "I;16"
  37. else:
  38. mode = "L"
  39. elif csiz == 2:
  40. mode = "LA"
  41. elif csiz == 3:
  42. mode = "RGB"
  43. elif csiz == 4:
  44. mode = "RGBA"
  45. else:
  46. mode = None
  47. return (size, mode)
  48. def _parse_jp2_header(fp):
  49. """Parse the JP2 header box to extract size, component count and
  50. color space information, returning a (size, mode, mimetype) tuple."""
  51. # Find the JP2 header box
  52. header = None
  53. mimetype = None
  54. while True:
  55. lbox, tbox = struct.unpack(">I4s", fp.read(8))
  56. if lbox == 1:
  57. lbox = struct.unpack(">Q", fp.read(8))[0]
  58. hlen = 16
  59. else:
  60. hlen = 8
  61. if lbox < hlen:
  62. raise SyntaxError("Invalid JP2 header length")
  63. if tbox == b"jp2h":
  64. header = fp.read(lbox - hlen)
  65. break
  66. elif tbox == b"ftyp":
  67. if fp.read(4) == b"jpx ":
  68. mimetype = "image/jpx"
  69. fp.seek(lbox - hlen - 4, os.SEEK_CUR)
  70. else:
  71. fp.seek(lbox - hlen, os.SEEK_CUR)
  72. if header is None:
  73. raise SyntaxError("could not find JP2 header")
  74. size = None
  75. mode = None
  76. bpc = None
  77. nc = None
  78. hio = io.BytesIO(header)
  79. while True:
  80. lbox, tbox = struct.unpack(">I4s", hio.read(8))
  81. if lbox == 1:
  82. lbox = struct.unpack(">Q", hio.read(8))[0]
  83. hlen = 16
  84. else:
  85. hlen = 8
  86. content = hio.read(lbox - hlen)
  87. if tbox == b"ihdr":
  88. height, width, nc, bpc, c, unkc, ipr = struct.unpack(">IIHBBBB", content)
  89. size = (width, height)
  90. if unkc:
  91. if nc == 1 and (bpc & 0x7F) > 8:
  92. mode = "I;16"
  93. elif nc == 1:
  94. mode = "L"
  95. elif nc == 2:
  96. mode = "LA"
  97. elif nc == 3:
  98. mode = "RGB"
  99. elif nc == 4:
  100. mode = "RGBA"
  101. break
  102. elif tbox == b"colr":
  103. meth, prec, approx = struct.unpack_from(">BBB", content)
  104. if meth == 1:
  105. cs = struct.unpack_from(">I", content, 3)[0]
  106. if cs == 16: # sRGB
  107. if nc == 1 and (bpc & 0x7F) > 8:
  108. mode = "I;16"
  109. elif nc == 1:
  110. mode = "L"
  111. elif nc == 3:
  112. mode = "RGB"
  113. elif nc == 4:
  114. mode = "RGBA"
  115. break
  116. elif cs == 17: # grayscale
  117. if nc == 1 and (bpc & 0x7F) > 8:
  118. mode = "I;16"
  119. elif nc == 1:
  120. mode = "L"
  121. elif nc == 2:
  122. mode = "LA"
  123. break
  124. elif cs == 18: # sYCC
  125. if nc == 3:
  126. mode = "RGB"
  127. elif nc == 4:
  128. mode = "RGBA"
  129. break
  130. if size is None or mode is None:
  131. raise SyntaxError("Malformed jp2 header")
  132. return (size, mode, mimetype)
  133. ##
  134. # Image plugin for JPEG2000 images.
  135. class Jpeg2KImageFile(ImageFile.ImageFile):
  136. format = "JPEG2000"
  137. format_description = "JPEG 2000 (ISO 15444)"
  138. def _open(self):
  139. sig = self.fp.read(4)
  140. if sig == b"\xff\x4f\xff\x51":
  141. self.codec = "j2k"
  142. self._size, self.mode = _parse_codestream(self.fp)
  143. else:
  144. sig = sig + self.fp.read(8)
  145. if sig == b"\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a":
  146. self.codec = "jp2"
  147. header = _parse_jp2_header(self.fp)
  148. self._size, self.mode, self.custom_mimetype = header
  149. else:
  150. raise SyntaxError("not a JPEG 2000 file")
  151. if self.size is None or self.mode is None:
  152. raise SyntaxError("unable to determine size/mode")
  153. self._reduce = 0
  154. self.layers = 0
  155. fd = -1
  156. length = -1
  157. try:
  158. fd = self.fp.fileno()
  159. length = os.fstat(fd).st_size
  160. except Exception:
  161. fd = -1
  162. try:
  163. pos = self.fp.tell()
  164. self.fp.seek(0, io.SEEK_END)
  165. length = self.fp.tell()
  166. self.fp.seek(pos)
  167. except Exception:
  168. length = -1
  169. self.tile = [
  170. (
  171. "jpeg2k",
  172. (0, 0) + self.size,
  173. 0,
  174. (self.codec, self._reduce, self.layers, fd, length),
  175. )
  176. ]
  177. @property
  178. def reduce(self):
  179. # https://github.com/python-pillow/Pillow/issues/4343 found that the
  180. # new Image 'reduce' method was shadowed by this plugin's 'reduce'
  181. # property. This attempts to allow for both scenarios
  182. return self._reduce or super().reduce
  183. @reduce.setter
  184. def reduce(self, value):
  185. self._reduce = value
  186. def load(self):
  187. if self.tile and self._reduce:
  188. power = 1 << self._reduce
  189. adjust = power >> 1
  190. self._size = (
  191. int((self.size[0] + adjust) / power),
  192. int((self.size[1] + adjust) / power),
  193. )
  194. # Update the reduce and layers settings
  195. t = self.tile[0]
  196. t3 = (t[3][0], self._reduce, self.layers, t[3][3], t[3][4])
  197. self.tile = [(t[0], (0, 0) + self.size, t[2], t3)]
  198. return ImageFile.ImageFile.load(self)
  199. def _accept(prefix):
  200. return (
  201. prefix[:4] == b"\xff\x4f\xff\x51"
  202. or prefix[:12] == b"\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a"
  203. )
  204. # ------------------------------------------------------------
  205. # Save support
  206. def _save(im, fp, filename):
  207. if filename.endswith(".j2k"):
  208. kind = "j2k"
  209. else:
  210. kind = "jp2"
  211. # Get the keyword arguments
  212. info = im.encoderinfo
  213. offset = info.get("offset", None)
  214. tile_offset = info.get("tile_offset", None)
  215. tile_size = info.get("tile_size", None)
  216. quality_mode = info.get("quality_mode", "rates")
  217. quality_layers = info.get("quality_layers", None)
  218. if quality_layers is not None and not (
  219. isinstance(quality_layers, (list, tuple))
  220. and all(
  221. [
  222. isinstance(quality_layer, (int, float))
  223. for quality_layer in quality_layers
  224. ]
  225. )
  226. ):
  227. raise ValueError("quality_layers must be a sequence of numbers")
  228. num_resolutions = info.get("num_resolutions", 0)
  229. cblk_size = info.get("codeblock_size", None)
  230. precinct_size = info.get("precinct_size", None)
  231. irreversible = info.get("irreversible", False)
  232. progression = info.get("progression", "LRCP")
  233. cinema_mode = info.get("cinema_mode", "no")
  234. fd = -1
  235. if hasattr(fp, "fileno"):
  236. try:
  237. fd = fp.fileno()
  238. except Exception:
  239. fd = -1
  240. im.encoderconfig = (
  241. offset,
  242. tile_offset,
  243. tile_size,
  244. quality_mode,
  245. quality_layers,
  246. num_resolutions,
  247. cblk_size,
  248. precinct_size,
  249. irreversible,
  250. progression,
  251. cinema_mode,
  252. fd,
  253. )
  254. ImageFile._save(im, fp, [("jpeg2k", (0, 0) + im.size, 0, kind)])
  255. # ------------------------------------------------------------
  256. # Registry stuff
  257. Image.register_open(Jpeg2KImageFile.format, Jpeg2KImageFile, _accept)
  258. Image.register_save(Jpeg2KImageFile.format, _save)
  259. Image.register_extensions(
  260. Jpeg2KImageFile.format, [".jp2", ".j2k", ".jpc", ".jpf", ".jpx", ".j2c"]
  261. )
  262. Image.register_mime(Jpeg2KImageFile.format, "image/jp2")