Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/codecs/pillow/decoder.py : 60%
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.
6import os
7import struct
8from io import BytesIO
9import PIL #@UnresolvedImport
10from PIL import Image #@UnresolvedImport
12from xpra.util import csv
13from xpra.os_util import hexstr
14from xpra.log import Logger
16log = Logger("encoder", "pillow")
18DECODE_FORMATS = os.environ.get("XPRA_PILLOW_DECODE_FORMATS", "png,png/L,png/P,jpeg,webp").split(",")
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
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
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 }
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
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)
78def get_version():
79 return PIL.__version__
81def get_type() -> str:
82 return "pillow"
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
95def get_encodings():
96 return ENCODINGS
98ENCODINGS = do_get_encodings()
100def get_info() -> dict:
101 return {
102 "version" : get_version(),
103 "encodings" : get_encodings(),
104 }
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")
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
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)