Jpeg2KImagePlugin.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. #
  2. # The Python Imaging Library
  3. # $Id$
  4. #
  5. # JPEG2000 file handling
  6. #
  7. # History:
  8. # 2014-03-12 ajh Created
  9. # 2021-06-30 rogermb Extract dpi information from the 'resc' header box
  10. #
  11. # Copyright (c) 2014 Coriolis Systems Limited
  12. # Copyright (c) 2014 Alastair Houghton
  13. #
  14. # See the README file for information on usage and redistribution.
  15. #
  16. from __future__ import annotations
  17. import io
  18. import os
  19. import struct
  20. from typing import cast
  21. from . import Image, ImageFile, ImagePalette, _binary
  22. TYPE_CHECKING = False
  23. if TYPE_CHECKING:
  24. from collections.abc import Callable
  25. from typing import IO
  26. class BoxReader:
  27. """
  28. A small helper class to read fields stored in JPEG2000 header boxes
  29. and to easily step into and read sub-boxes.
  30. """
  31. def __init__(self, fp: IO[bytes], length: int = -1) -> None:
  32. self.fp = fp
  33. self.has_length = length >= 0
  34. self.length = length
  35. self.remaining_in_box = -1
  36. def _can_read(self, num_bytes: int) -> bool:
  37. if self.has_length and self.fp.tell() + num_bytes > self.length:
  38. # Outside box: ensure we don't read past the known file length
  39. return False
  40. if self.remaining_in_box >= 0:
  41. # Inside box contents: ensure read does not go past box boundaries
  42. return num_bytes <= self.remaining_in_box
  43. else:
  44. return True # No length known, just read
  45. def _read_bytes(self, num_bytes: int) -> bytes:
  46. if not self._can_read(num_bytes):
  47. msg = "Not enough data in header"
  48. raise SyntaxError(msg)
  49. data = self.fp.read(num_bytes)
  50. if len(data) < num_bytes:
  51. msg = f"Expected to read {num_bytes} bytes but only got {len(data)}."
  52. raise OSError(msg)
  53. if self.remaining_in_box > 0:
  54. self.remaining_in_box -= num_bytes
  55. return data
  56. def read_fields(self, field_format: str) -> tuple[int | bytes, ...]:
  57. size = struct.calcsize(field_format)
  58. data = self._read_bytes(size)
  59. return struct.unpack(field_format, data)
  60. def read_boxes(self) -> BoxReader:
  61. size = self.remaining_in_box
  62. data = self._read_bytes(size)
  63. return BoxReader(io.BytesIO(data), size)
  64. def has_next_box(self) -> bool:
  65. if self.has_length:
  66. return self.fp.tell() + self.remaining_in_box < self.length
  67. else:
  68. return True
  69. def next_box_type(self) -> bytes:
  70. # Skip the rest of the box if it has not been read
  71. if self.remaining_in_box > 0:
  72. self.fp.seek(self.remaining_in_box, os.SEEK_CUR)
  73. self.remaining_in_box = -1
  74. # Read the length and type of the next box
  75. lbox, tbox = cast(tuple[int, bytes], self.read_fields(">I4s"))
  76. if lbox == 1:
  77. lbox = cast(int, self.read_fields(">Q")[0])
  78. hlen = 16
  79. else:
  80. hlen = 8
  81. if lbox < hlen or not self._can_read(lbox - hlen):
  82. msg = "Invalid header length"
  83. raise SyntaxError(msg)
  84. self.remaining_in_box = lbox - hlen
  85. return tbox
  86. def _parse_codestream(fp: IO[bytes]) -> tuple[tuple[int, int], str]:
  87. """Parse the JPEG 2000 codestream to extract the size and component
  88. count from the SIZ marker segment, returning a PIL (size, mode) tuple."""
  89. hdr = fp.read(2)
  90. lsiz = _binary.i16be(hdr)
  91. siz = hdr + fp.read(lsiz - 2)
  92. lsiz, rsiz, xsiz, ysiz, xosiz, yosiz, _, _, _, _, csiz = struct.unpack_from(
  93. ">HHIIIIIIIIH", siz
  94. )
  95. size = (xsiz - xosiz, ysiz - yosiz)
  96. if csiz == 1:
  97. ssiz = struct.unpack_from(">B", siz, 38)
  98. if (ssiz[0] & 0x7F) + 1 > 8:
  99. mode = "I;16"
  100. else:
  101. mode = "L"
  102. elif csiz == 2:
  103. mode = "LA"
  104. elif csiz == 3:
  105. mode = "RGB"
  106. elif csiz == 4:
  107. mode = "RGBA"
  108. else:
  109. msg = "unable to determine J2K image mode"
  110. raise SyntaxError(msg)
  111. return size, mode
  112. def _res_to_dpi(num: int, denom: int, exp: int) -> float | None:
  113. """Convert JPEG2000's (numerator, denominator, exponent-base-10) resolution,
  114. calculated as (num / denom) * 10^exp and stored in dots per meter,
  115. to floating-point dots per inch."""
  116. if denom == 0:
  117. return None
  118. return (254 * num * (10**exp)) / (10000 * denom)
  119. def _parse_jp2_header(
  120. fp: IO[bytes],
  121. ) -> tuple[
  122. tuple[int, int],
  123. str,
  124. str | None,
  125. tuple[float, float] | None,
  126. ImagePalette.ImagePalette | None,
  127. ]:
  128. """Parse the JP2 header box to extract size, component count,
  129. color space information, and optionally DPI information,
  130. returning a (size, mode, mimetype, dpi) tuple."""
  131. # Find the JP2 header box
  132. reader = BoxReader(fp)
  133. header = None
  134. mimetype = None
  135. while reader.has_next_box():
  136. tbox = reader.next_box_type()
  137. if tbox == b"jp2h":
  138. header = reader.read_boxes()
  139. break
  140. elif tbox == b"ftyp":
  141. if reader.read_fields(">4s")[0] == b"jpx ":
  142. mimetype = "image/jpx"
  143. assert header is not None
  144. size = None
  145. mode = None
  146. bpc = None
  147. nc = None
  148. dpi = None # 2-tuple of DPI info, or None
  149. palette = None
  150. while header.has_next_box():
  151. tbox = header.next_box_type()
  152. if tbox == b"ihdr":
  153. height, width, nc, bpc = header.read_fields(">IIHB")
  154. assert isinstance(height, int)
  155. assert isinstance(width, int)
  156. assert isinstance(bpc, int)
  157. size = (width, height)
  158. if nc == 1 and (bpc & 0x7F) > 8:
  159. mode = "I;16"
  160. elif nc == 1:
  161. mode = "L"
  162. elif nc == 2:
  163. mode = "LA"
  164. elif nc == 3:
  165. mode = "RGB"
  166. elif nc == 4:
  167. mode = "RGBA"
  168. elif tbox == b"colr" and nc == 4:
  169. meth, _, _, enumcs = header.read_fields(">BBBI")
  170. if meth == 1 and enumcs == 12:
  171. mode = "CMYK"
  172. elif tbox == b"pclr" and mode in ("L", "LA"):
  173. ne, npc = header.read_fields(">HB")
  174. assert isinstance(ne, int)
  175. assert isinstance(npc, int)
  176. max_bitdepth = 0
  177. for bitdepth in header.read_fields(">" + ("B" * npc)):
  178. assert isinstance(bitdepth, int)
  179. if bitdepth > max_bitdepth:
  180. max_bitdepth = bitdepth
  181. if max_bitdepth <= 8:
  182. palette = ImagePalette.ImagePalette("RGBA" if npc == 4 else "RGB")
  183. for i in range(ne):
  184. color: list[int] = []
  185. for value in header.read_fields(">" + ("B" * npc)):
  186. assert isinstance(value, int)
  187. color.append(value)
  188. palette.getcolor(tuple(color))
  189. mode = "P" if mode == "L" else "PA"
  190. elif tbox == b"res ":
  191. res = header.read_boxes()
  192. while res.has_next_box():
  193. tres = res.next_box_type()
  194. if tres == b"resc":
  195. vrcn, vrcd, hrcn, hrcd, vrce, hrce = res.read_fields(">HHHHBB")
  196. assert isinstance(vrcn, int)
  197. assert isinstance(vrcd, int)
  198. assert isinstance(hrcn, int)
  199. assert isinstance(hrcd, int)
  200. assert isinstance(vrce, int)
  201. assert isinstance(hrce, int)
  202. hres = _res_to_dpi(hrcn, hrcd, hrce)
  203. vres = _res_to_dpi(vrcn, vrcd, vrce)
  204. if hres is not None and vres is not None:
  205. dpi = (hres, vres)
  206. break
  207. if size is None or mode is None:
  208. msg = "Malformed JP2 header"
  209. raise SyntaxError(msg)
  210. return size, mode, mimetype, dpi, palette
  211. ##
  212. # Image plugin for JPEG2000 images.
  213. class Jpeg2KImageFile(ImageFile.ImageFile):
  214. format = "JPEG2000"
  215. format_description = "JPEG 2000 (ISO 15444)"
  216. def _open(self) -> None:
  217. sig = self.fp.read(4)
  218. if sig == b"\xff\x4f\xff\x51":
  219. self.codec = "j2k"
  220. self._size, self._mode = _parse_codestream(self.fp)
  221. self._parse_comment()
  222. else:
  223. sig = sig + self.fp.read(8)
  224. if sig == b"\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a":
  225. self.codec = "jp2"
  226. header = _parse_jp2_header(self.fp)
  227. self._size, self._mode, self.custom_mimetype, dpi, self.palette = header
  228. if dpi is not None:
  229. self.info["dpi"] = dpi
  230. if self.fp.read(12).endswith(b"jp2c\xff\x4f\xff\x51"):
  231. hdr = self.fp.read(2)
  232. length = _binary.i16be(hdr)
  233. self.fp.seek(length - 2, os.SEEK_CUR)
  234. self._parse_comment()
  235. else:
  236. msg = "not a JPEG 2000 file"
  237. raise SyntaxError(msg)
  238. self._reduce = 0
  239. self.layers = 0
  240. fd = -1
  241. length = -1
  242. try:
  243. fd = self.fp.fileno()
  244. length = os.fstat(fd).st_size
  245. except Exception:
  246. fd = -1
  247. try:
  248. pos = self.fp.tell()
  249. self.fp.seek(0, io.SEEK_END)
  250. length = self.fp.tell()
  251. self.fp.seek(pos)
  252. except Exception:
  253. length = -1
  254. self.tile = [
  255. ImageFile._Tile(
  256. "jpeg2k",
  257. (0, 0) + self.size,
  258. 0,
  259. (self.codec, self._reduce, self.layers, fd, length),
  260. )
  261. ]
  262. def _parse_comment(self) -> None:
  263. while True:
  264. marker = self.fp.read(2)
  265. if not marker:
  266. break
  267. typ = marker[1]
  268. if typ in (0x90, 0xD9):
  269. # Start of tile or end of codestream
  270. break
  271. hdr = self.fp.read(2)
  272. length = _binary.i16be(hdr)
  273. if typ == 0x64:
  274. # Comment
  275. self.info["comment"] = self.fp.read(length - 2)[2:]
  276. break
  277. else:
  278. self.fp.seek(length - 2, os.SEEK_CUR)
  279. @property # type: ignore[override]
  280. def reduce(
  281. self,
  282. ) -> (
  283. Callable[[int | tuple[int, int], tuple[int, int, int, int] | None], Image.Image]
  284. | int
  285. ):
  286. # https://github.com/python-pillow/Pillow/issues/4343 found that the
  287. # new Image 'reduce' method was shadowed by this plugin's 'reduce'
  288. # property. This attempts to allow for both scenarios
  289. return self._reduce or super().reduce
  290. @reduce.setter
  291. def reduce(self, value: int) -> None:
  292. self._reduce = value
  293. def load(self) -> Image.core.PixelAccess | None:
  294. if self.tile and self._reduce:
  295. power = 1 << self._reduce
  296. adjust = power >> 1
  297. self._size = (
  298. int((self.size[0] + adjust) / power),
  299. int((self.size[1] + adjust) / power),
  300. )
  301. # Update the reduce and layers settings
  302. t = self.tile[0]
  303. assert isinstance(t[3], tuple)
  304. t3 = (t[3][0], self._reduce, self.layers, t[3][3], t[3][4])
  305. self.tile = [ImageFile._Tile(t[0], (0, 0) + self.size, t[2], t3)]
  306. return ImageFile.ImageFile.load(self)
  307. def _accept(prefix: bytes) -> bool:
  308. return prefix.startswith(
  309. (b"\xff\x4f\xff\x51", b"\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a")
  310. )
  311. # ------------------------------------------------------------
  312. # Save support
  313. def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
  314. # Get the keyword arguments
  315. info = im.encoderinfo
  316. if isinstance(filename, str):
  317. filename = filename.encode()
  318. if filename.endswith(b".j2k") or info.get("no_jp2", False):
  319. kind = "j2k"
  320. else:
  321. kind = "jp2"
  322. offset = info.get("offset", None)
  323. tile_offset = info.get("tile_offset", None)
  324. tile_size = info.get("tile_size", None)
  325. quality_mode = info.get("quality_mode", "rates")
  326. quality_layers = info.get("quality_layers", None)
  327. if quality_layers is not None and not (
  328. isinstance(quality_layers, (list, tuple))
  329. and all(
  330. isinstance(quality_layer, (int, float)) for quality_layer in quality_layers
  331. )
  332. ):
  333. msg = "quality_layers must be a sequence of numbers"
  334. raise ValueError(msg)
  335. num_resolutions = info.get("num_resolutions", 0)
  336. cblk_size = info.get("codeblock_size", None)
  337. precinct_size = info.get("precinct_size", None)
  338. irreversible = info.get("irreversible", False)
  339. progression = info.get("progression", "LRCP")
  340. cinema_mode = info.get("cinema_mode", "no")
  341. mct = info.get("mct", 0)
  342. signed = info.get("signed", False)
  343. comment = info.get("comment")
  344. if isinstance(comment, str):
  345. comment = comment.encode()
  346. plt = info.get("plt", False)
  347. fd = -1
  348. if hasattr(fp, "fileno"):
  349. try:
  350. fd = fp.fileno()
  351. except Exception:
  352. fd = -1
  353. im.encoderconfig = (
  354. offset,
  355. tile_offset,
  356. tile_size,
  357. quality_mode,
  358. quality_layers,
  359. num_resolutions,
  360. cblk_size,
  361. precinct_size,
  362. irreversible,
  363. progression,
  364. cinema_mode,
  365. mct,
  366. signed,
  367. fd,
  368. comment,
  369. plt,
  370. )
  371. ImageFile._save(im, fp, [ImageFile._Tile("jpeg2k", (0, 0) + im.size, 0, kind)])
  372. # ------------------------------------------------------------
  373. # Registry stuff
  374. Image.register_open(Jpeg2KImageFile.format, Jpeg2KImageFile, _accept)
  375. Image.register_save(Jpeg2KImageFile.format, _save)
  376. Image.register_extensions(
  377. Jpeg2KImageFile.format, [".jp2", ".j2k", ".jpc", ".jpf", ".jpx", ".j2c"]
  378. )
  379. Image.register_mime(Jpeg2KImageFile.format, "image/jp2")