1#
2# The Python Imaging Library.
3# $Id$
4#
5# XPM File handling
6#
7# History:
8# 1996-12-29 fl Created
9# 2001-02-17 fl Use 're' instead of 'regex' (Python 2.1) (0.7)
10#
11# Copyright (c) Secret Labs AB 1997-2001.
12# Copyright (c) Fredrik Lundh 1996-2001.
13#
14# See the README file for information on usage and redistribution.
15#
16from __future__ import annotations
17
18import re
19
20from . import Image, ImageFile, ImagePalette
21from ._binary import o8
22
23# XPM header
24xpm_head = re.compile(b'"([0-9]*) ([0-9]*) ([0-9]*) ([0-9]*)')
25
26
27def _accept(prefix: bytes) -> bool:
28 return prefix.startswith(b"/* XPM */")
29
30
31##
32# Image plugin for X11 pixel maps.
33
34
35class XpmImageFile(ImageFile.ImageFile):
36 format = "XPM"
37 format_description = "X11 Pixel Map"
38
39 def _open(self) -> None:
40 if not _accept(self.fp.read(9)):
41 msg = "not an XPM file"
42 raise SyntaxError(msg)
43
44 # skip forward to next string
45 while True:
46 s = self.fp.readline()
47 if not s:
48 msg = "broken XPM file"
49 raise SyntaxError(msg)
50 m = xpm_head.match(s)
51 if m:
52 break
53
54 self._size = int(m.group(1)), int(m.group(2))
55
56 pal = int(m.group(3))
57 bpp = int(m.group(4))
58
59 if pal > 256 or bpp != 1:
60 msg = "cannot read this XPM file"
61 raise ValueError(msg)
62
63 #
64 # load palette description
65
66 palette = [b"\0\0\0"] * 256
67
68 for _ in range(pal):
69 s = self.fp.readline()
70 if s.endswith(b"\r\n"):
71 s = s[:-2]
72 elif s.endswith((b"\r", b"\n")):
73 s = s[:-1]
74
75 c = s[1]
76 s = s[2:-2].split()
77
78 for i in range(0, len(s), 2):
79 if s[i] == b"c":
80 # process colour key
81 rgb = s[i + 1]
82 if rgb == b"None":
83 self.info["transparency"] = c
84 elif rgb.startswith(b"#"):
85 # FIXME: handle colour names (see ImagePalette.py)
86 rgb = int(rgb[1:], 16)
87 palette[c] = (
88 o8((rgb >> 16) & 255) + o8((rgb >> 8) & 255) + o8(rgb & 255)
89 )
90 else:
91 # unknown colour
92 msg = "cannot read this XPM file"
93 raise ValueError(msg)
94 break
95
96 else:
97 # missing colour key
98 msg = "cannot read this XPM file"
99 raise ValueError(msg)
100
101 self._mode = "P"
102 self.palette = ImagePalette.raw("RGB", b"".join(palette))
103
104 self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, self.fp.tell(), "P")]
105
106 def load_read(self, read_bytes: int) -> bytes:
107 #
108 # load all image data in one chunk
109
110 xsize, ysize = self.size
111
112 s = [self.fp.readline()[1 : xsize + 1].ljust(xsize) for i in range(ysize)]
113
114 return b"".join(s)
115
116
117#
118# Registry
119
120
121Image.register_open(XpmImageFile.format, XpmImageFile, _accept)
122
123Image.register_extension(XpmImageFile.format, ".xpm")
124
125Image.register_mime(XpmImageFile.format, "image/xpm")