WmfImagePlugin.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. #
  2. # The Python Imaging Library
  3. # $Id$
  4. #
  5. # WMF stub codec
  6. #
  7. # history:
  8. # 1996-12-14 fl Created
  9. # 2004-02-22 fl Turned into a stub driver
  10. # 2004-02-23 fl Added EMF support
  11. #
  12. # Copyright (c) Secret Labs AB 1997-2004. All rights reserved.
  13. # Copyright (c) Fredrik Lundh 1996.
  14. #
  15. # See the README file for information on usage and redistribution.
  16. #
  17. # WMF/EMF reference documentation:
  18. # https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-WMF/[MS-WMF].pdf
  19. # http://wvware.sourceforge.net/caolan/index.html
  20. # http://wvware.sourceforge.net/caolan/ora-wmf.html
  21. from __future__ import annotations
  22. from typing import IO
  23. from . import Image, ImageFile
  24. from ._binary import i16le as word
  25. from ._binary import si16le as short
  26. from ._binary import si32le as _long
  27. _handler = None
  28. def register_handler(handler: ImageFile.StubHandler | None) -> None:
  29. """
  30. Install application-specific WMF image handler.
  31. :param handler: Handler object.
  32. """
  33. global _handler
  34. _handler = handler
  35. if hasattr(Image.core, "drawwmf"):
  36. # install default handler (windows only)
  37. class WmfHandler(ImageFile.StubHandler):
  38. def open(self, im: ImageFile.StubImageFile) -> None:
  39. im._mode = "RGB"
  40. self.bbox = im.info["wmf_bbox"]
  41. def load(self, im: ImageFile.StubImageFile) -> Image.Image:
  42. im.fp.seek(0) # rewind
  43. return Image.frombytes(
  44. "RGB",
  45. im.size,
  46. Image.core.drawwmf(im.fp.read(), im.size, self.bbox),
  47. "raw",
  48. "BGR",
  49. (im.size[0] * 3 + 3) & -4,
  50. -1,
  51. )
  52. register_handler(WmfHandler())
  53. #
  54. # --------------------------------------------------------------------
  55. # Read WMF file
  56. def _accept(prefix: bytes) -> bool:
  57. return prefix.startswith((b"\xd7\xcd\xc6\x9a\x00\x00", b"\x01\x00\x00\x00"))
  58. ##
  59. # Image plugin for Windows metafiles.
  60. class WmfStubImageFile(ImageFile.StubImageFile):
  61. format = "WMF"
  62. format_description = "Windows Metafile"
  63. def _open(self) -> None:
  64. # check placeable header
  65. s = self.fp.read(44)
  66. if s.startswith(b"\xd7\xcd\xc6\x9a\x00\x00"):
  67. # placeable windows metafile
  68. # get units per inch
  69. inch = word(s, 14)
  70. if inch == 0:
  71. msg = "Invalid inch"
  72. raise ValueError(msg)
  73. self._inch: tuple[float, float] = inch, inch
  74. # get bounding box
  75. x0 = short(s, 6)
  76. y0 = short(s, 8)
  77. x1 = short(s, 10)
  78. y1 = short(s, 12)
  79. # normalize size to 72 dots per inch
  80. self.info["dpi"] = 72
  81. size = (
  82. (x1 - x0) * self.info["dpi"] // inch,
  83. (y1 - y0) * self.info["dpi"] // inch,
  84. )
  85. self.info["wmf_bbox"] = x0, y0, x1, y1
  86. # sanity check (standard metafile header)
  87. if s[22:26] != b"\x01\x00\t\x00":
  88. msg = "Unsupported WMF file format"
  89. raise SyntaxError(msg)
  90. elif s.startswith(b"\x01\x00\x00\x00") and s[40:44] == b" EMF":
  91. # enhanced metafile
  92. # get bounding box
  93. x0 = _long(s, 8)
  94. y0 = _long(s, 12)
  95. x1 = _long(s, 16)
  96. y1 = _long(s, 20)
  97. # get frame (in 0.01 millimeter units)
  98. frame = _long(s, 24), _long(s, 28), _long(s, 32), _long(s, 36)
  99. size = x1 - x0, y1 - y0
  100. # calculate dots per inch from bbox and frame
  101. xdpi = 2540.0 * (x1 - x0) / (frame[2] - frame[0])
  102. ydpi = 2540.0 * (y1 - y0) / (frame[3] - frame[1])
  103. self.info["wmf_bbox"] = x0, y0, x1, y1
  104. if xdpi == ydpi:
  105. self.info["dpi"] = xdpi
  106. else:
  107. self.info["dpi"] = xdpi, ydpi
  108. self._inch = xdpi, ydpi
  109. else:
  110. msg = "Unsupported file format"
  111. raise SyntaxError(msg)
  112. self._mode = "RGB"
  113. self._size = size
  114. loader = self._load()
  115. if loader:
  116. loader.open(self)
  117. def _load(self) -> ImageFile.StubHandler | None:
  118. return _handler
  119. def load(
  120. self, dpi: float | tuple[float, float] | None = None
  121. ) -> Image.core.PixelAccess | None:
  122. if dpi is not None:
  123. self.info["dpi"] = dpi
  124. x0, y0, x1, y1 = self.info["wmf_bbox"]
  125. if not isinstance(dpi, tuple):
  126. dpi = dpi, dpi
  127. self._size = (
  128. int((x1 - x0) * dpi[0] / self._inch[0]),
  129. int((y1 - y0) * dpi[1] / self._inch[1]),
  130. )
  131. return super().load()
  132. def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
  133. if _handler is None or not hasattr(_handler, "save"):
  134. msg = "WMF save handler not installed"
  135. raise OSError(msg)
  136. _handler.save(im, fp, filename)
  137. #
  138. # --------------------------------------------------------------------
  139. # Registry stuff
  140. Image.register_open(WmfStubImageFile.format, WmfStubImageFile, _accept)
  141. Image.register_save(WmfStubImageFile.format, _save)
  142. Image.register_extensions(WmfStubImageFile.format, [".wmf", ".emf"])