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 # we assume this is a color Palm image with the standard colormap,
120 # unless the "info" dict has a "custom-colormap" field
121
122 rawmode = "P"
123 bpp = 8
124 version = 1
125
126 elif im.mode == "L":
127 if im.encoderinfo.get("bpp") in (1, 2, 4):
128 # this is 8-bit grayscale, so we shift it to get the high-order bits,
129 # and invert it because
130 # Palm does grayscale from white (0) to black (1)
131 bpp = im.encoderinfo["bpp"]
132 maxval = (1 << bpp) - 1
133 shift = 8 - bpp
134 im = im.point(lambda x: maxval - (x >> shift))
135 elif im.info.get("bpp") in (1, 2, 4):
136 # here we assume that even though the inherent mode is 8-bit grayscale,
137 # only the lower bpp bits are significant.
138 # We invert them to match the Palm.
139 bpp = im.info["bpp"]
140 maxval = (1 << bpp) - 1
141 im = im.point(lambda x: maxval - (x & maxval))
142 else:
143 msg = f"cannot write mode {im.mode} as Palm"
144 raise OSError(msg)
145
146 # we ignore the palette here
147 im._mode = "P"
148 rawmode = f"P;{bpp}"
149 version = 1
150
151 elif im.mode == "1":
152 # monochrome -- write it inverted, as is the Palm standard
153 rawmode = "1;I"
154 bpp = 1
155 version = 0
156
157 else:
158 msg = f"cannot write mode {im.mode} as Palm"
159 raise OSError(msg)
160
161 #
162 # make sure image data is available
163 im.load()
164
165 # write header
166
167 cols = im.size[0]
168 rows = im.size[1]
169
170 rowbytes = int((cols + (16 // bpp - 1)) / (16 // bpp)) * 2
171 transparent_index = 0
172 compression_type = _COMPRESSION_TYPES["none"]
173
174 flags = 0
175 if im.mode == "P" and "custom-colormap" in im.info:
176 flags = flags & _FLAGS["custom-colormap"]
177 colormapsize = 4 * 256 + 2
178 colormapmode = im.palette.mode
179 colormap = im.getdata().getpalette()
180 else:
181 colormapsize = 0
182
183 if "offset" in im.info:
184 offset = (rowbytes * rows + 16 + 3 + colormapsize) // 4
185 else:
186 offset = 0
187
188 fp.write(o16b(cols) + o16b(rows) + o16b(rowbytes) + o16b(flags))
189 fp.write(o8(bpp))
190 fp.write(o8(version))
191 fp.write(o16b(offset))
192 fp.write(o8(transparent_index))
193 fp.write(o8(compression_type))
194 fp.write(o16b(0)) # reserved by Palm
195
196 # now write colormap if necessary
197
198 if colormapsize > 0:
199 fp.write(o16b(256))
200 for i in range(256):
201 fp.write(o8(i))
202 if colormapmode == "RGB":
203 fp.write(
204 o8(colormap[3 * i])
205 + o8(colormap[3 * i + 1])
206 + o8(colormap[3 * i + 2])
207 )
208 elif colormapmode == "RGBA":
209 fp.write(
210 o8(colormap[4 * i])
211 + o8(colormap[4 * i + 1])
212 + o8(colormap[4 * i + 2])
213 )
214
215 # now convert data to raw form
216 ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, rowbytes, 1))])
217
218 if hasattr(fp, "flush"):
219 fp.flush()
220
221
222#
223# --------------------------------------------------------------------
224
225Image.register_save("Palm", _save)
226
227Image.register_extension("Palm", ".palm")
228
229Image.register_mime("Palm", "image/palm")