1
0

Jpeg2KImagePlugin.py 14 KB

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