Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/PIL/XpmImagePlugin.py: 20%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
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
18__lazy_modules__ = {"PIL._binary"}
20import re
22from . import Image, ImageFile, ImagePalette
23from ._binary import o8
25# XPM header
26xpm_head = re.compile(rb'"([0-9]*)\s+([0-9]*)\s+([0-9]*)\s+([0-9]*)')
29def _accept(prefix: bytes) -> bool:
30 return prefix.startswith(b"/* XPM */")
33##
34# Image plugin for X11 pixel maps.
37class XpmImageFile(ImageFile.ImageFile):
38 format = "XPM"
39 format_description = "X11 Pixel Map"
41 def _open(self) -> None:
42 assert self.fp is not None
43 if not _accept(self.fp.read(9)):
44 msg = "not an XPM file"
45 raise SyntaxError(msg)
47 # skip forward to next string
48 while True:
49 line = self.fp.readline()
50 if not line:
51 msg = "broken XPM file"
52 raise SyntaxError(msg)
53 m = xpm_head.match(line)
54 if m:
55 break
57 self._size = int(m.group(1)), int(m.group(2))
59 palette_length = int(m.group(3))
60 bpp = int(m.group(4))
62 #
63 # load palette description
65 palette = {}
66 transparent_key = None
68 for _ in range(palette_length):
69 line = self.fp.readline().rstrip()
71 c = line[1 : bpp + 1]
72 s = line[bpp + 1 : -2].split()
74 for i in range(0, len(s), 2):
75 if s[i] == b"c":
76 # process colour key
77 rgb = s[i + 1]
78 if rgb == b"None":
79 transparent_key = c
80 palette[c] = b"\0\0\0"
81 elif rgb.startswith(b"#"):
82 rgb_int = int(rgb[1:], 16)
83 palette[c] = (
84 o8((rgb_int >> 16) & 255)
85 + o8((rgb_int >> 8) & 255)
86 + o8(rgb_int & 255)
87 )
88 else:
89 # unknown colour
90 msg = "cannot read this XPM file"
91 raise ValueError(msg)
92 break
94 else:
95 # missing colour key
96 msg = "cannot read this XPM file"
97 raise ValueError(msg)
99 args: tuple[int, dict[bytes, bytes] | tuple[bytes, ...]]
100 if palette_length > 256:
101 if transparent_key is not None:
102 self._mode = "RGBA"
103 palette = {
104 c: rgb + (b"\x00" if c == transparent_key else b"\xff")
105 for c, rgb in palette.items()
106 }
107 else:
108 self._mode = "RGB"
109 args = (bpp, palette)
110 else:
111 self._mode = "P"
112 self.palette = ImagePalette.raw("RGB", b"".join(palette.values()))
113 palette_keys = tuple(palette.keys())
114 args = (bpp, palette_keys)
115 if transparent_key is not None:
116 self.info["transparency"] = palette_keys.index(transparent_key)
118 self.tile = [ImageFile._Tile("xpm", None, self.fp.tell(), args)]
120 def load_read(self, read_bytes: int) -> bytes:
121 #
122 # load all image data in one chunk
124 xsize, ysize = self.size
126 assert self.fp is not None
127 s = [self.fp.readline()[1 : xsize + 1].ljust(xsize) for i in range(ysize)]
129 return b"".join(s)
132class XpmDecoder(ImageFile.PyDecoder):
133 _pulls_fd = True
135 def decode(self, buffer: Image.DecoderInput) -> tuple[int, int]:
136 assert self.fd is not None
138 data = bytearray()
139 bpp, palette = self.args
140 dest_length = self.state.xsize * self.state.ysize
141 if self.mode in {"RGB", "RGBA"}:
142 dest_length *= len(self.mode)
143 pixel_header = False
144 while len(data) < dest_length:
145 line = self.fd.readline()
146 if not line:
147 break
148 if line.rstrip() == b"/* pixels */" and not pixel_header:
149 pixel_header = True
150 continue
151 line = b'"'.join(line.split(b'"')[1:-1])
152 for i in range(0, len(line), bpp):
153 key = line[i : i + bpp]
154 try:
155 if self.mode in {"RGB", "RGBA"}:
156 data += palette[key]
157 else:
158 data += o8(palette.index(key))
159 except (KeyError, ValueError):
160 msg = "unknown color"
161 raise ValueError(msg)
162 self.set_as_raw(bytes(data))
163 return -1, 0
166#
167# Registry
170Image.register_open(XpmImageFile.format, XpmImageFile, _accept)
171Image.register_decoder("xpm", XpmDecoder)
173Image.register_extension(XpmImageFile.format, ".xpm")
175Image.register_mime(XpmImageFile.format, "image/xpm")