1#
2# The Python Imaging Library.
3# $Id$
4#
5
6##
7# Image plugin for Palm pixmap images (output only).
8##
9from __future__ import annotations
10
11from typing import IO
12
13from . import Image, ImageFile
14from ._binary import o8
15from ._binary import o16be as o16b
16
17# fmt: off
18_Palm8BitColormapValues = (
19 (255, 255, 255), (255, 204, 255), (255, 153, 255), (255, 102, 255),
20 (255, 51, 255), (255, 0, 255), (255, 255, 204), (255, 204, 204),
21 (255, 153, 204), (255, 102, 204), (255, 51, 204), (255, 0, 204),
22 (255, 255, 153), (255, 204, 153), (255, 153, 153), (255, 102, 153),
23 (255, 51, 153), (255, 0, 153), (204, 255, 255), (204, 204, 255),
24 (204, 153, 255), (204, 102, 255), (204, 51, 255), (204, 0, 255),
25 (204, 255, 204), (204, 204, 204), (204, 153, 204), (204, 102, 204),
26 (204, 51, 204), (204, 0, 204), (204, 255, 153), (204, 204, 153),
27 (204, 153, 153), (204, 102, 153), (204, 51, 153), (204, 0, 153),
28 (153, 255, 255), (153, 204, 255), (153, 153, 255), (153, 102, 255),
29 (153, 51, 255), (153, 0, 255), (153, 255, 204), (153, 204, 204),
30 (153, 153, 204), (153, 102, 204), (153, 51, 204), (153, 0, 204),
31 (153, 255, 153), (153, 204, 153), (153, 153, 153), (153, 102, 153),
32 (153, 51, 153), (153, 0, 153), (102, 255, 255), (102, 204, 255),
33 (102, 153, 255), (102, 102, 255), (102, 51, 255), (102, 0, 255),
34 (102, 255, 204), (102, 204, 204), (102, 153, 204), (102, 102, 204),
35 (102, 51, 204), (102, 0, 204), (102, 255, 153), (102, 204, 153),
36 (102, 153, 153), (102, 102, 153), (102, 51, 153), (102, 0, 153),
37 (51, 255, 255), (51, 204, 255), (51, 153, 255), (51, 102, 255),
38 (51, 51, 255), (51, 0, 255), (51, 255, 204), (51, 204, 204),
39 (51, 153, 204), (51, 102, 204), (51, 51, 204), (51, 0, 204),
40 (51, 255, 153), (51, 204, 153), (51, 153, 153), (51, 102, 153),
41 (51, 51, 153), (51, 0, 153), (0, 255, 255), (0, 204, 255),
42 (0, 153, 255), (0, 102, 255), (0, 51, 255), (0, 0, 255),
43 (0, 255, 204), (0, 204, 204), (0, 153, 204), (0, 102, 204),
44 (0, 51, 204), (0, 0, 204), (0, 255, 153), (0, 204, 153),
45 (0, 153, 153), (0, 102, 153), (0, 51, 153), (0, 0, 153),
46 (255, 255, 102), (255, 204, 102), (255, 153, 102), (255, 102, 102),
47 (255, 51, 102), (255, 0, 102), (255, 255, 51), (255, 204, 51),
48 (255, 153, 51), (255, 102, 51), (255, 51, 51), (255, 0, 51),
49 (255, 255, 0), (255, 204, 0), (255, 153, 0), (255, 102, 0),
50 (255, 51, 0), (255, 0, 0), (204, 255, 102), (204, 204, 102),
51 (204, 153, 102), (204, 102, 102), (204, 51, 102), (204, 0, 102),
52 (204, 255, 51), (204, 204, 51), (204, 153, 51), (204, 102, 51),
53 (204, 51, 51), (204, 0, 51), (204, 255, 0), (204, 204, 0),
54 (204, 153, 0), (204, 102, 0), (204, 51, 0), (204, 0, 0),
55 (153, 255, 102), (153, 204, 102), (153, 153, 102), (153, 102, 102),
56 (153, 51, 102), (153, 0, 102), (153, 255, 51), (153, 204, 51),
57 (153, 153, 51), (153, 102, 51), (153, 51, 51), (153, 0, 51),
58 (153, 255, 0), (153, 204, 0), (153, 153, 0), (153, 102, 0),
59 (153, 51, 0), (153, 0, 0), (102, 255, 102), (102, 204, 102),
60 (102, 153, 102), (102, 102, 102), (102, 51, 102), (102, 0, 102),
61 (102, 255, 51), (102, 204, 51), (102, 153, 51), (102, 102, 51),
62 (102, 51, 51), (102, 0, 51), (102, 255, 0), (102, 204, 0),
63 (102, 153, 0), (102, 102, 0), (102, 51, 0), (102, 0, 0),
64 (51, 255, 102), (51, 204, 102), (51, 153, 102), (51, 102, 102),
65 (51, 51, 102), (51, 0, 102), (51, 255, 51), (51, 204, 51),
66 (51, 153, 51), (51, 102, 51), (51, 51, 51), (51, 0, 51),
67 (51, 255, 0), (51, 204, 0), (51, 153, 0), (51, 102, 0),
68 (51, 51, 0), (51, 0, 0), (0, 255, 102), (0, 204, 102),
69 (0, 153, 102), (0, 102, 102), (0, 51, 102), (0, 0, 102),
70 (0, 255, 51), (0, 204, 51), (0, 153, 51), (0, 102, 51),
71 (0, 51, 51), (0, 0, 51), (0, 255, 0), (0, 204, 0),
72 (0, 153, 0), (0, 102, 0), (0, 51, 0), (17, 17, 17),
73 (34, 34, 34), (68, 68, 68), (85, 85, 85), (119, 119, 119),
74 (136, 136, 136), (170, 170, 170), (187, 187, 187), (221, 221, 221),
75 (238, 238, 238), (192, 192, 192), (128, 0, 0), (128, 0, 128),
76 (0, 128, 0), (0, 128, 128), (0, 0, 0), (0, 0, 0),
77 (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0),
78 (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0),
79 (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0),
80 (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0),
81 (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0),
82 (0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0))
83# fmt: on
84
85
86# so build a prototype image to be used for palette resampling
87def build_prototype_image() -> Image.Image:
88 image = Image.new("L", (1, len(_Palm8BitColormapValues)))
89 image.putdata(list(range(len(_Palm8BitColormapValues))))
90 palettedata: tuple[int, ...] = ()
91 for colormapValue in _Palm8BitColormapValues:
92 palettedata += colormapValue
93 palettedata += (0, 0, 0) * (256 - len(_Palm8BitColormapValues))
94 image.putpalette(palettedata)
95 return image
96
97
98Palm8BitColormapImage = build_prototype_image()
99
100# OK, we now have in Palm8BitColormapImage,
101# a "P"-mode image with the right palette
102#
103# --------------------------------------------------------------------
104
105_FLAGS = {"custom-colormap": 0x4000, "is-compressed": 0x8000, "has-transparent": 0x2000}
106
107_COMPRESSION_TYPES = {"none": 0xFF, "rle": 0x01, "scanline": 0x00}
108
109
110#
111# --------------------------------------------------------------------
112
113##
114# (Internal) Image save plugin for the Palm format.
115
116
117def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
118 if im.mode == "P":
119 rawmode = "P"
120 bpp = 8
121 version = 1
122
123 elif im.mode == "L":
124 if im.encoderinfo.get("bpp") in (1, 2, 4):
125 # this is 8-bit grayscale, so we shift it to get the high-order bits,
126 # and invert it because
127 # Palm does grayscale from white (0) to black (1)
128 bpp = im.encoderinfo["bpp"]
129 maxval = (1 << bpp) - 1
130 shift = 8 - bpp
131 im = im.point(lambda x: maxval - (x >> shift))
132 elif im.info.get("bpp") in (1, 2, 4):
133 # here we assume that even though the inherent mode is 8-bit grayscale,
134 # only the lower bpp bits are significant.
135 # We invert them to match the Palm.
136 bpp = im.info["bpp"]
137 maxval = (1 << bpp) - 1
138 im = im.point(lambda x: maxval - (x & maxval))
139 else:
140 msg = f"cannot write mode {im.mode} as Palm"
141 raise OSError(msg)
142
143 # we ignore the palette here
144 im._mode = "P"
145 rawmode = f"P;{bpp}"
146 version = 1
147
148 elif im.mode == "1":
149 # monochrome -- write it inverted, as is the Palm standard
150 rawmode = "1;I"
151 bpp = 1
152 version = 0
153
154 else:
155 msg = f"cannot write mode {im.mode} as Palm"
156 raise OSError(msg)
157
158 #
159 # make sure image data is available
160 im.load()
161
162 # write header
163
164 cols = im.size[0]
165 rows = im.size[1]
166
167 rowbytes = int((cols + (16 // bpp - 1)) / (16 // bpp)) * 2
168 transparent_index = 0
169 compression_type = _COMPRESSION_TYPES["none"]
170
171 flags = 0
172 if im.mode == "P":
173 flags |= _FLAGS["custom-colormap"]
174 colormap = im.im.getpalette()
175 colors = len(colormap) // 3
176 colormapsize = 4 * colors + 2
177 else:
178 colormapsize = 0
179
180 if "offset" in im.info:
181 offset = (rowbytes * rows + 16 + 3 + colormapsize) // 4
182 else:
183 offset = 0
184
185 fp.write(o16b(cols) + o16b(rows) + o16b(rowbytes) + o16b(flags))
186 fp.write(o8(bpp))
187 fp.write(o8(version))
188 fp.write(o16b(offset))
189 fp.write(o8(transparent_index))
190 fp.write(o8(compression_type))
191 fp.write(o16b(0)) # reserved by Palm
192
193 # now write colormap if necessary
194
195 if colormapsize:
196 fp.write(o16b(colors))
197 for i in range(colors):
198 fp.write(o8(i))
199 fp.write(colormap[3 * i : 3 * i + 3])
200
201 # now convert data to raw form
202 ImageFile._save(
203 im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, (rawmode, rowbytes, 1))]
204 )
205
206 if hasattr(fp, "flush"):
207 fp.flush()
208
209
210#
211# --------------------------------------------------------------------
212
213Image.register_save("Palm", _save)
214
215Image.register_extension("Palm", ".palm")
216
217Image.register_mime("Palm", "image/palm")