Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# This file is part of Xpra. 

2# Copyright (C) 2014-2020 Antoine Martin <antoine@xpra.org> 

3# Xpra is released under the terms of the GNU GPL v2, or, at your option, any 

4# later version. See the file COPYING for details. 

5 

6import os 

7import struct 

8from io import BytesIO 

9import PIL #@UnresolvedImport 

10from PIL import Image #@UnresolvedImport 

11 

12from xpra.util import csv 

13from xpra.os_util import hexstr 

14from xpra.log import Logger 

15 

16log = Logger("encoder", "pillow") 

17 

18DECODE_FORMATS = os.environ.get("XPRA_PILLOW_DECODE_FORMATS", "png,png/L,png/P,jpeg,webp").split(",") 

19 

20PNG_HEADER = struct.pack("BBBBBBBB", 137, 80, 78, 71, 13, 10, 26, 10) 

21def is_png(data): 

22 return data.startswith(PNG_HEADER) 

23RIFF_HEADER = b"RIFF" 

24WEBP_HEADER = b"WEBP" 

25def is_webp(data): 

26 return data[:4]==RIFF_HEADER and data[8:12]==WEBP_HEADER 

27JPEG_HEADER = struct.pack("BBB", 0xFF, 0xD8, 0xFF) 

28def is_jpeg(data): 

29 #the jpeg header is actually more complicated than this, 

30 #but in practice all the data we receive from the server 

31 #will have this type of header 

32 return data[:3]==JPEG_HEADER 

33def is_svg(data): 

34 if data[:5]!="<?xml" and data[:4]!="<svg": 

35 return False 

36 return True 

37XPM_HEADER = b"/* XPM */" 

38def is_xpm(data): 

39 return data[:9]==XPM_HEADER 

40 

41def is_tiff(data): 

42 if data[:2]==b"II": 

43 return data[2]==42 and data[3]==0 

44 if data[:2]==b"MM": 

45 return data[2]==0 and data[3]==42 

46 return False 

47 

48 

49HEADERS = { 

50 is_png : "png", 

51 is_webp : "webp", 

52 is_jpeg : "jpeg", 

53 is_svg : "svg", 

54 is_xpm : "xpm", 

55 is_tiff : "tiff", 

56 } 

57 

58def get_image_type(data) -> str: 

59 if not data: 

60 return None 

61 if len(data)<32: 

62 return None 

63 for fn, encoding in HEADERS.items(): 

64 if fn(data): 

65 return encoding 

66 return None 

67 

68 

69def open_only(data, types=("png", "jpeg", "webp")): 

70 itype = get_image_type(data) 

71 if itype not in types: 

72 raise Exception("invalid data: %s, not recognized as %s, header: %s" % ( 

73 (itype or "unknown"), csv(types), hexstr(data[:64]))) 

74 buf = BytesIO(data) 

75 return Image.open(buf) 

76 

77 

78def get_version(): 

79 return PIL.__version__ 

80 

81def get_type() -> str: 

82 return "pillow" 

83 

84def do_get_encodings(): 

85 log("PIL.Image.OPEN=%s", Image.OPEN) 

86 encodings = [] 

87 for encoding in DECODE_FORMATS: 

88 #strip suffix (so "png/L" -> "png") 

89 stripped = encoding.split("/")[0].upper() 

90 if stripped in Image.OPEN: 

91 encodings.append(encoding) 

92 log("do_get_encodings()=%s", encodings) 

93 return encodings 

94 

95def get_encodings(): 

96 return ENCODINGS 

97 

98ENCODINGS = do_get_encodings() 

99 

100def get_info() -> dict: 

101 return { 

102 "version" : get_version(), 

103 "encodings" : get_encodings(), 

104 } 

105 

106def decompress(coding, img_data, options): 

107 # can be called from any thread 

108 actual = get_image_type(img_data) 

109 if not actual or not coding.startswith(actual): 

110 raise Exception("expected %s image data but received %s" % (coding, actual or "unknown")) 

111 buf = BytesIO(img_data) 

112 img = Image.open(buf) 

113 assert img.mode in ("L", "P", "RGB", "RGBA", "RGBX"), "invalid image mode: %s" % img.mode 

114 transparency = options.intget("transparency", -1) 

115 if img.mode=="P": 

116 if transparency>=0: 

117 #this deals with alpha without any extra work 

118 img = img.convert("RGBA") 

119 else: 

120 img = img.convert("RGB") 

121 elif img.mode=="L": 

122 if transparency>=0: 

123 #why do we have to deal with alpha ourselves?? 

124 def mask_value(a): 

125 if a!=transparency: 

126 return 255 

127 return 0 

128 mask = Image.eval(img, mask_value) 

129 mask = mask.convert("L") 

130 def nomask_value(a): 

131 if a!=transparency: 

132 return a 

133 return 0 

134 img = Image.eval(img, nomask_value) 

135 img = img.convert("RGBA") 

136 img.putalpha(mask) 

137 else: 

138 img = img.convert("RGB") 

139 

140 width, height = img.size 

141 if img.mode=="RGB": 

142 #PIL flattens the data to a continuous straightforward RGB format: 

143 rowstride = width*3 

144 rgb_format = options.strget("rgb_format", "") 

145 rgb_format = rgb_format.replace("A", "").replace("X", "") 

146 #the webp encoder only takes BGRX input, 

147 #so we have to swap things around if it was fed "RGB": 

148 if rgb_format=="RGB": 

149 rgb_format = "BGR" 

150 else: 

151 rgb_format = "RGB" 

152 elif img.mode in ("RGBA", "RGBX"): 

153 rowstride = width*4 

154 rgb_format = options.strget("rgb_format", img.mode) 

155 if coding=="webp": 

156 #the webp encoder only takes BGRX input, 

157 #so we have to swap things around if it was fed "RGBA": 

158 if rgb_format=="RGBA": 

159 rgb_format = "BGRA" 

160 elif rgb_format=="RGBX": 

161 rgb_format = "BGRX" 

162 elif rgb_format=="BGRA": 

163 rgb_format = "RGBA" 

164 elif rgb_format=="BGRX": 

165 rgb_format = "RGBX" 

166 else: 

167 log.warn("Warning: unexpected RGB format '%s'", rgb_format) 

168 else: 

169 raise Exception("invalid image mode: %s" % img.mode) 

170 raw_data = img.tobytes("raw", img.mode) 

171 log("pillow decoded %i bytes of %s data to %i bytes of %s", len(img_data), coding, len(raw_data), rgb_format) 

172 return rgb_format, raw_data, width, height, rowstride 

173 

174 

175def selftest(_full=False): 

176 global ENCODINGS 

177 import binascii 

178 #test data generated using the encoder: 

179 for encoding, hexdata in ( 

180 ('png', "89504e470d0a1a0a0000000d4948445200000020000000200806000000737a7af40000002849444154785eedd08100000000c3a0f9531fe4855061c0800103060c183060c0800103060cbc0f0c102000013337932a0000000049454e44ae426082"), 

181 ('png', "89504e470d0a1a0a0000000d4948445200000020000000200802000000fc18eda30000002549444154785eedd03101000000c2a0f54fed610d884061c0800103060c183060c080810f0c0c20000174754ae90000000049454e44ae426082"), 

182 ('png/L', "89504e470d0a1a0a0000000d4948445200000020000000200800000000561125280000000274524e5300ff5b9122b50000002049444154785e63fccf801f3011906718550009a1d170180d07e4bc323cd20300a33d013f95f841e70000000049454e44ae426082"), 

183 ('png/L', "89504e470d0a1a0a0000000d4948445200000020000000200800000000561125280000001549444154785e63601805a321301a02a321803d0400042000017854be5c0000000049454e44ae426082"), 

184 ('png/P', "89504e470d0a1a0a0000000d494844520000002000000020080300000044a48ac600000300504c5445000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b330f4880000010074524e53ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0053f707250000001c49444154785e63f84f00308c2a0087c068384012c268388ca87000003f68fc2e077ed1070000000049454e44ae426082"), 

185 ('png/P', "89504e470d0a1a0a0000000d494844520000002000000020080300000044a48ac600000300504c5445000000000000000000000000000000000000000000000000000000000000000000330000660000990000cc0000ff0000003300333300663300993300cc3300ff3300006600336600666600996600cc6600ff6600009900339900669900999900cc9900ff990000cc0033cc0066cc0099cc00cccc00ffcc0000ff0033ff0066ff0099ff00ccff00ffff00000033330033660033990033cc0033ff0033003333333333663333993333cc3333ff3333006633336633666633996633cc6633ff6633009933339933669933999933cc9933ff993300cc3333cc3366cc3399cc33cccc33ffcc3300ff3333ff3366ff3399ff33ccff33ffff33000066330066660066990066cc0066ff0066003366333366663366993366cc3366ff3366006666336666666666996666cc6666ff6666009966339966669966999966cc9966ff996600cc6633cc6666cc6699cc66cccc66ffcc6600ff6633ff6666ff6699ff66ccff66ffff66000099330099660099990099cc0099ff0099003399333399663399993399cc3399ff3399006699336699666699996699cc6699ff6699009999339999669999999999cc9999ff999900cc9933cc9966cc9999cc99cccc99ffcc9900ff9933ff9966ff9999ff99ccff99ffff990000cc3300cc6600cc9900cccc00ccff00cc0033cc3333cc6633cc9933cccc33ccff33cc0066cc3366cc6666cc9966cccc66ccff66cc0099cc3399cc6699cc9999cccc99ccff99cc00cccc33cccc66cccc99ccccccccccffcccc00ffcc33ffcc66ffcc99ffccccffccffffcc0000ff3300ff6600ff9900ffcc00ffff00ff0033ff3333ff6633ff9933ffcc33ffff33ff0066ff3366ff6666ff9966ffcc66ffff66ff0099ff3399ff6699ff9999ffcc99ffff99ff00ccff33ccff66ccff99ccffccccffffccff00ffff33ffff66ffff99ffffccffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023faca40000001549444154785e63601805a321301a02a321803d0400042000017854be5c0000000049454e44ae426082"), 

186 ('jpeg', "ffd8ffe000104a46494600010100000100010000ffdb004300100b0c0e0c0a100e0d0e1211101318281a181616183123251d283a333d3c3933383740485c4e404457453738506d51575f626768673e4d71797064785c656763ffdb0043011112121815182f1a1a2f634238426363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363ffc00011080020002003012200021101031101ffc4001500010100000000000000000000000000000007ffc40014100100000000000000000000000000000000ffc40014010100000000000000000000000000000000ffc40014110100000000000000000000000000000000ffda000c03010002110311003f009f800000000000ffd9"), 

187 ('jpeg', "ffd8ffe000104a46494600010100000100010000ffdb004300100b0c0e0c0a100e0d0e1211101318281a181616183123251d283a333d3c3933383740485c4e404457453738506d51575f626768673e4d71797064785c656763ffdb0043011112121815182f1a1a2f634238426363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363ffc00011080020002003012200021101031101ffc4001500010100000000000000000000000000000007ffc40014100100000000000000000000000000000000ffc40014010100000000000000000000000000000000ffc40014110100000000000000000000000000000000ffda000c03010002110311003f009f800000000000ffd9"), 

188 ('webp', "524946465c00000057454250565038580a000000100000001f00001f0000414c50480f00000001071011110012c2ffef7a44ff530f005650382026000000d002009d012a200020003ed162aa4fa825a3a2280801001a096900003da3a000fef39d800000"), 

189 ('webp', "524946465c00000057454250565038580a000000100000001f00001f0000414c50480f00000001071011110012c2ffef7a44ff530f005650382026000000d002009d012a200020003ed162aa4fa825a3a2280801001a096900003da3a000fef39d800000"), 

190 ): 

191 if encoding not in ENCODINGS: 

192 #removed already 

193 continue 

194 try: 

195 cdata = binascii.unhexlify(hexdata) 

196 buf = BytesIO(cdata) 

197 img = PIL.Image.open(buf) 

198 assert img, "failed to open image data" 

199 raw_data = img.tobytes("raw", img.mode) 

200 assert raw_data 

201 #now try with junk: 

202 cdata = binascii.unhexlify("ABCD"+hexdata) 

203 buf = BytesIO(cdata) 

204 try: 

205 img = PIL.Image.open(buf) 

206 log.warn("Pillow failed to generate an error parsing invalid input") 

207 except Exception as e: 

208 log("correctly raised exception for invalid input: %s", e) 

209 except Exception as e: 

210 log("selftest:", exc_info=True) 

211 try: 

212 #py2k: 

213 datainfo = cdata.encode("string_escape") 

214 except Exception: 

215 try: 

216 datainfo = cdata.encode("unicode_escape").decode() 

217 except Exception: 

218 datainfo = str(hexdata) 

219 log.error("Pillow error decoding %s with data=%s..", encoding, datainfo[:16]) 

220 from xpra.os_util import is_CentOS 

221 #don't log a backtrace for webp on CentOS: 

222 exc_info = not (is_CentOS() and encoding=="webp") 

223 log.error(" %s", e, exc_info=exc_info) 

224 ENCODINGS.remove(encoding)