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

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

106 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 

19import olefile 

20 

21from . import Image, ImageFile 

22from ._binary import i32le as i32 

23 

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

25MODES = { 

26 # opacity 

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

28 # monochrome 

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

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

31 # photo YCC 

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

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

34 # standard RGB (NIFRGB) 

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

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

37} 

38 

39 

40# 

41# -------------------------------------------------------------------- 

42 

43 

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

45 return prefix.startswith(olefile.MAGIC) 

46 

47 

48## 

49# Image plugin for the FlashPix images. 

50 

51 

52class FpxImageFile(ImageFile.ImageFile): 

53 format = "FPX" 

54 format_description = "FlashPix" 

55 

56 def _open(self) -> None: 

57 # 

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

59 # to be a FlashPix file 

60 

61 try: 

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

63 except OSError as e: 

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

65 raise SyntaxError(msg) from e 

66 

67 root = self.ole.root 

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

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

70 raise SyntaxError(msg) 

71 

72 self._open_index(1) 

73 

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

75 # 

76 # get the Image Contents Property Set 

77 

78 prop = self.ole.getproperties( 

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

80 ) 

81 

82 # size (highest resolution) 

83 

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

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

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

87 

88 size = max(self.size) 

89 i = 1 

90 while size > 64: 

91 size = size // 2 

92 i += 1 

93 self.maxid = i - 1 

94 

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

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

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

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

99 # that this is always the case. 

100 

101 id = self.maxid << 16 

102 

103 s = prop[0x2000002 | id] 

104 

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

106 msg = "Invalid number of bands" 

107 raise OSError(msg) 

108 

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

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

111 

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

113 

114 # load JPEG tables, if any 

115 self.jpeg = {} 

116 for i in range(256): 

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

118 if id in prop: 

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

120 

121 self._open_subimage(1, self.maxid) 

122 

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

124 # 

125 # setup tile descriptors for a given subimage 

126 

127 stream = [ 

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

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

130 "Subimage 0000 Header", 

131 ] 

132 

133 fp = self.ole.openstream(stream) 

134 

135 # skip prefix 

136 fp.read(28) 

137 

138 # header stream 

139 s = fp.read(36) 

140 

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

142 # tilecount = i32(s, 12) 

143 tilesize = i32(s, 16), i32(s, 20) 

144 # channels = i32(s, 24) 

145 offset = i32(s, 28) 

146 length = i32(s, 32) 

147 

148 if size != self.size: 

149 msg = "subimage mismatch" 

150 raise OSError(msg) 

151 

152 # get tile descriptors 

153 fp.seek(28 + offset) 

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

155 

156 x = y = 0 

157 xsize, ysize = size 

158 xtile, ytile = tilesize 

159 self.tile = [] 

160 

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

162 x1 = min(xsize, x + xtile) 

163 y1 = min(ysize, y + ytile) 

164 

165 compression = i32(s, i + 8) 

166 

167 if compression == 0: 

168 self.tile.append( 

169 ImageFile._Tile( 

170 "raw", 

171 (x, y, x1, y1), 

172 i32(s, i) + 28, 

173 self.rawmode, 

174 ) 

175 ) 

176 

177 elif compression == 1: 

178 # FIXME: the fill decoder is not implemented 

179 self.tile.append( 

180 ImageFile._Tile( 

181 "fill", 

182 (x, y, x1, y1), 

183 i32(s, i) + 28, 

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

185 ) 

186 ) 

187 

188 elif compression == 2: 

189 internal_color_conversion = s[14] 

190 jpeg_tables = s[15] 

191 rawmode = self.rawmode 

192 

193 if internal_color_conversion: 

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

195 if rawmode == "RGBA": 

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

197 # negative RGB. The following trick works around 

198 # this problem : 

199 jpegmode, rawmode = "YCbCrK", "CMYK" 

200 else: 

201 jpegmode = None # let the decoder decide 

202 

203 else: 

204 # The image is stored as defined by rawmode 

205 jpegmode = rawmode 

206 

207 self.tile.append( 

208 ImageFile._Tile( 

209 "jpeg", 

210 (x, y, x1, y1), 

211 i32(s, i) + 28, 

212 (rawmode, jpegmode), 

213 ) 

214 ) 

215 

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

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

218 

219 if jpeg_tables: 

220 self.tile_prefix = self.jpeg[jpeg_tables] 

221 

222 else: 

223 msg = "unknown/invalid compression" 

224 raise OSError(msg) 

225 

226 x = x + xtile 

227 if x >= xsize: 

228 x, y = 0, y + ytile 

229 if y >= ysize: 

230 break # isn't really required 

231 

232 self.stream = stream 

233 self._fp = self.fp 

234 self.fp = None 

235 

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

237 if not self.fp: 

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

239 

240 return ImageFile.ImageFile.load(self) 

241 

242 def close(self) -> None: 

243 self.ole.close() 

244 super().close() 

245 

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

247 self.ole.close() 

248 super().__exit__() 

249 

250 

251# 

252# -------------------------------------------------------------------- 

253 

254 

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

256 

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