Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/PIL/FpxImagePlugin.py: 3%

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

111 statements  

1# 

2# THIS IS WORK IN PROGRESS 

3# 

4# The Python Imaging Library. 

5# $Id$ 

6# 

7# FlashPix support for PIL 

8# 

9# History: 

10# 97-01-25 fl Created (reads uncompressed RGB images only) 

11# 

12# Copyright (c) Secret Labs AB 1997. 

13# Copyright (c) Fredrik Lundh 1997. 

14# 

15# See the README file for information on usage and redistribution. 

16# 

17from __future__ import annotations 

18 

19__lazy_modules__ = {"PIL._binary"} 

20 

21import olefile 

22 

23from . import Image, ImageFile 

24from ._binary import i32le as i32 

25 

26# we map from colour field tuples to (mode, rawmode) descriptors 

27MODES = { 

28 # opacity 

29 (0x00007FFE,): ("A", "L"), 

30 # monochrome 

31 (0x00010000,): ("L", "L"), 

32 (0x00018000, 0x00017FFE): ("RGBA", "LA"), 

33 # photo YCC 

34 (0x00020000, 0x00020001, 0x00020002): ("RGB", "YCC;P"), 

35 (0x00028000, 0x00028001, 0x00028002, 0x00027FFE): ("RGBA", "YCCA;P"), 

36 # standard RGB (NIFRGB) 

37 (0x00030000, 0x00030001, 0x00030002): ("RGB", "RGB"), 

38 (0x00038000, 0x00038001, 0x00038002, 0x00037FFE): ("RGBA", "RGBA"), 

39} 

40 

41 

42# 

43# -------------------------------------------------------------------- 

44 

45 

46def _accept(prefix: bytes) -> bool: 

47 return prefix.startswith(olefile.MAGIC) 

48 

49 

50## 

51# Image plugin for the FlashPix images. 

52 

53 

54class FpxImageFile(ImageFile.ImageFile): 

55 format = "FPX" 

56 format_description = "FlashPix" 

57 

58 def _open(self) -> None: 

59 # 

60 # read the OLE directory and see if this is a likely 

61 # to be a FlashPix file 

62 

63 assert self.fp is not None 

64 try: 

65 self.ole = olefile.OleFileIO(self.fp) 

66 except OSError as e: 

67 msg = "not an FPX file; invalid OLE file" 

68 raise SyntaxError(msg) from e 

69 

70 root = self.ole.root 

71 if not root or root.clsid != "56616700-C154-11CE-8553-00AA00A1F95B": 

72 msg = "not an FPX file; bad root CLSID" 

73 raise SyntaxError(msg) 

74 

75 self._open_index(1) 

76 

77 def _open_index(self, index: int = 1) -> None: 

78 # 

79 # get the Image Contents Property Set 

80 

81 prop = self.ole.getproperties( 

82 [f"Data Object Store {index:06d}", "\005Image Contents"] 

83 ) 

84 

85 # size (highest resolution) 

86 

87 assert isinstance(prop[0x1000002], int) 

88 assert isinstance(prop[0x1000003], int) 

89 self._size = prop[0x1000002], prop[0x1000003] 

90 

91 size = max(self.size) 

92 i = 1 

93 while size > 64: 

94 size = size // 2 

95 i += 1 

96 self.maxid = i - 1 

97 

98 # mode. instead of using a single field for this, flashpix 

99 # requires you to specify the mode for each channel in each 

100 # resolution subimage, and leaves it to the decoder to make 

101 # sure that they all match. for now, we'll cheat and assume 

102 # that this is always the case. 

103 

104 id = self.maxid << 16 

105 

106 s = prop[0x2000002 | id] 

107 

108 if not isinstance(s, bytes) or (bands := i32(s, 4)) > 4: 

109 msg = "Invalid number of bands" 

110 raise OSError(msg) 

111 

112 # note: for now, we ignore the "uncalibrated" flag 

113 colors = tuple(i32(s, 8 + i * 4) & 0x7FFFFFFF for i in range(bands)) 

114 

115 self._mode, self.rawmode = MODES[colors] 

116 

117 # load JPEG tables, if any 

118 self.jpeg = {} 

119 for i in range(256): 

120 id = 0x3000001 | (i << 16) 

121 if id in prop: 

122 self.jpeg[i] = prop[id] 

123 

124 self._open_subimage(1, self.maxid) 

125 

126 def _open_subimage(self, index: int = 1, subimage: int = 0) -> None: 

127 # 

128 # setup tile descriptors for a given subimage 

129 

130 stream = [ 

131 f"Data Object Store {index:06d}", 

132 f"Resolution {subimage:04d}", 

133 "Subimage 0000 Header", 

134 ] 

135 

136 fp = self.ole.openstream(stream) 

137 

138 # skip prefix 

139 fp.read(28) 

140 

141 # header stream 

142 s = fp.read(36) 

143 

144 size = i32(s, 4), i32(s, 8) 

145 # tilecount = i32(s, 12) 

146 xtile, ytile = i32(s, 16), i32(s, 20) 

147 if xtile != 64 or ytile != 64: 

148 msg = "Tile must be 64 pixels by 64 pixels" 

149 raise ValueError(msg) 

150 

151 # channels = i32(s, 24) 

152 offset = i32(s, 28) 

153 length = i32(s, 32) 

154 

155 if size != self.size: 

156 msg = "subimage mismatch" 

157 raise OSError(msg) 

158 

159 # get tile descriptors 

160 fp.seek(28 + offset) 

161 s = fp.read(i32(s, 12) * length) 

162 

163 x = y = 0 

164 xsize, ysize = size 

165 self.tile = [] 

166 

167 for i in range(0, len(s), length): 

168 x1 = min(xsize, x + xtile) 

169 y1 = min(ysize, y + ytile) 

170 

171 compression = i32(s, i + 8) 

172 

173 if compression == 0: 

174 self.tile.append( 

175 ImageFile._Tile( 

176 "raw", 

177 (x, y, x1, y1), 

178 i32(s, i) + 28, 

179 self.rawmode, 

180 ) 

181 ) 

182 

183 elif compression == 1: 

184 # FIXME: the fill decoder is not implemented 

185 self.tile.append( 

186 ImageFile._Tile( 

187 "fill", 

188 (x, y, x1, y1), 

189 i32(s, i) + 28, 

190 (self.rawmode, s[12:16]), 

191 ) 

192 ) 

193 

194 elif compression == 2: 

195 internal_color_conversion = s[14] 

196 jpeg_tables = s[15] 

197 rawmode = self.rawmode 

198 

199 if internal_color_conversion: 

200 # The image is stored as usual (usually YCbCr). 

201 if rawmode == "RGBA": 

202 # For "RGBA", data is stored as YCbCrA based on 

203 # negative RGB. The following trick works around 

204 # this problem : 

205 jpegmode, rawmode = "YCbCrK", "CMYK" 

206 else: 

207 jpegmode = None # let the decoder decide 

208 

209 else: 

210 # The image is stored as defined by rawmode 

211 jpegmode = rawmode 

212 

213 self.tile.append( 

214 ImageFile._Tile( 

215 "jpeg", 

216 (x, y, x1, y1), 

217 i32(s, i) + 28, 

218 (rawmode, jpegmode), 

219 ) 

220 ) 

221 

222 # FIXME: jpeg tables are tile dependent; the prefix 

223 # data must be placed in the tile descriptor itself! 

224 

225 if jpeg_tables: 

226 self.tile_prefix = self.jpeg[jpeg_tables] 

227 

228 else: 

229 msg = "unknown/invalid compression" 

230 raise OSError(msg) 

231 

232 x += xtile 

233 if x >= xsize: 

234 x, y = 0, y + ytile 

235 if y >= ysize: 

236 break # isn't really required 

237 

238 assert self.fp is not None 

239 self.stream = stream 

240 self._fp = self.fp 

241 self.fp = None 

242 

243 def load(self) -> Image.core.PixelAccess | None: 

244 if not self.fp: 

245 self.fp = self.ole.openstream(self.stream[:2] + ["Subimage 0000 Data"]) 

246 

247 return ImageFile.ImageFile.load(self) 

248 

249 def close(self) -> None: 

250 self.ole.close() 

251 super().close() 

252 

253 def __exit__(self, *args: object) -> None: 

254 self.ole.close() 

255 super().__exit__() 

256 

257 

258# 

259# -------------------------------------------------------------------- 

260 

261 

262Image.register_open(FpxImageFile.format, FpxImageFile, _accept) 

263 

264Image.register_extension(FpxImageFile.format, ".fpx")