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.

179 lines
5.1 KiB

4 years ago
  1. #
  2. # The Python Imaging Library.
  3. # $Id$
  4. #
  5. # PCX file handling
  6. #
  7. # This format was originally used by ZSoft's popular PaintBrush
  8. # program for the IBM PC. It is also supported by many MS-DOS and
  9. # Windows applications, including the Windows PaintBrush program in
  10. # Windows 3.
  11. #
  12. # history:
  13. # 1995-09-01 fl Created
  14. # 1996-05-20 fl Fixed RGB support
  15. # 1997-01-03 fl Fixed 2-bit and 4-bit support
  16. # 1999-02-03 fl Fixed 8-bit support (broken in 1.0b1)
  17. # 1999-02-07 fl Added write support
  18. # 2002-06-09 fl Made 2-bit and 4-bit support a bit more robust
  19. # 2002-07-30 fl Seek from to current position, not beginning of file
  20. # 2003-06-03 fl Extract DPI settings (info["dpi"])
  21. #
  22. # Copyright (c) 1997-2003 by Secret Labs AB.
  23. # Copyright (c) 1995-2003 by Fredrik Lundh.
  24. #
  25. # See the README file for information on usage and redistribution.
  26. #
  27. import logging
  28. from . import Image, ImageFile, ImagePalette
  29. from ._binary import i8, i16le as i16, o8, o16le as o16
  30. logger = logging.getLogger(__name__)
  31. __version__ = "0.6"
  32. def _accept(prefix):
  33. return i8(prefix[0]) == 10 and i8(prefix[1]) in [0, 2, 3, 5]
  34. ##
  35. # Image plugin for Paintbrush images.
  36. class PcxImageFile(ImageFile.ImageFile):
  37. format = "PCX"
  38. format_description = "Paintbrush"
  39. def _open(self):
  40. # header
  41. s = self.fp.read(128)
  42. if not _accept(s):
  43. raise SyntaxError("not a PCX file")
  44. # image
  45. bbox = i16(s, 4), i16(s, 6), i16(s, 8)+1, i16(s, 10)+1
  46. if bbox[2] <= bbox[0] or bbox[3] <= bbox[1]:
  47. raise SyntaxError("bad PCX image size")
  48. logger.debug("BBox: %s %s %s %s", *bbox)
  49. # format
  50. version = i8(s[1])
  51. bits = i8(s[3])
  52. planes = i8(s[65])
  53. stride = i16(s, 66)
  54. logger.debug("PCX version %s, bits %s, planes %s, stride %s",
  55. version, bits, planes, stride)
  56. self.info["dpi"] = i16(s, 12), i16(s, 14)
  57. if bits == 1 and planes == 1:
  58. mode = rawmode = "1"
  59. elif bits == 1 and planes in (2, 4):
  60. mode = "P"
  61. rawmode = "P;%dL" % planes
  62. self.palette = ImagePalette.raw("RGB", s[16:64])
  63. elif version == 5 and bits == 8 and planes == 1:
  64. mode = rawmode = "L"
  65. # FIXME: hey, this doesn't work with the incremental loader !!!
  66. self.fp.seek(-769, 2)
  67. s = self.fp.read(769)
  68. if len(s) == 769 and i8(s[0]) == 12:
  69. # check if the palette is linear greyscale
  70. for i in range(256):
  71. if s[i*3+1:i*3+4] != o8(i)*3:
  72. mode = rawmode = "P"
  73. break
  74. if mode == "P":
  75. self.palette = ImagePalette.raw("RGB", s[1:])
  76. self.fp.seek(128)
  77. elif version == 5 and bits == 8 and planes == 3:
  78. mode = "RGB"
  79. rawmode = "RGB;L"
  80. else:
  81. raise IOError("unknown PCX mode")
  82. self.mode = mode
  83. self._size = bbox[2]-bbox[0], bbox[3]-bbox[1]
  84. bbox = (0, 0) + self.size
  85. logger.debug("size: %sx%s", *self.size)
  86. self.tile = [("pcx", bbox, self.fp.tell(), (rawmode, planes * stride))]
  87. # --------------------------------------------------------------------
  88. # save PCX files
  89. SAVE = {
  90. # mode: (version, bits, planes, raw mode)
  91. "1": (2, 1, 1, "1"),
  92. "L": (5, 8, 1, "L"),
  93. "P": (5, 8, 1, "P"),
  94. "RGB": (5, 8, 3, "RGB;L"),
  95. }
  96. def _save(im, fp, filename):
  97. try:
  98. version, bits, planes, rawmode = SAVE[im.mode]
  99. except KeyError:
  100. raise ValueError("Cannot save %s images as PCX" % im.mode)
  101. # bytes per plane
  102. stride = (im.size[0] * bits + 7) // 8
  103. # stride should be even
  104. stride += stride % 2
  105. # Stride needs to be kept in sync with the PcxEncode.c version.
  106. # Ideally it should be passed in in the state, but the bytes value
  107. # gets overwritten.
  108. logger.debug("PcxImagePlugin._save: xwidth: %d, bits: %d, stride: %d",
  109. im.size[0], bits, stride)
  110. # under windows, we could determine the current screen size with
  111. # "Image.core.display_mode()[1]", but I think that's overkill...
  112. screen = im.size
  113. dpi = 100, 100
  114. # PCX header
  115. fp.write(
  116. o8(10) + o8(version) + o8(1) + o8(bits) + o16(0) +
  117. o16(0) + o16(im.size[0]-1) + o16(im.size[1]-1) + o16(dpi[0]) +
  118. o16(dpi[1]) + b"\0"*24 + b"\xFF"*24 + b"\0" + o8(planes) +
  119. o16(stride) + o16(1) + o16(screen[0]) + o16(screen[1]) +
  120. b"\0"*54
  121. )
  122. assert fp.tell() == 128
  123. ImageFile._save(im, fp, [("pcx", (0, 0)+im.size, 0,
  124. (rawmode, bits*planes))])
  125. if im.mode == "P":
  126. # colour palette
  127. fp.write(o8(12))
  128. fp.write(im.im.getpalette("RGB", "RGB")) # 768 bytes
  129. elif im.mode == "L":
  130. # greyscale palette
  131. fp.write(o8(12))
  132. for i in range(256):
  133. fp.write(o8(i)*3)
  134. # --------------------------------------------------------------------
  135. # registry
  136. Image.register_open(PcxImageFile.format, PcxImageFile, _accept)
  137. Image.register_save(PcxImageFile.format, _save)
  138. Image.register_extension(PcxImageFile.format, ".pcx")