def load_icon_from_file(filename): if filename.endswith("xpm"): try: from xpra.gtk_common.gobject_compat import import_pixbufloader from xpra.gtk_common.gtk_util import pixbuf_save_to_memory data = load_binary_file(filename) loader = import_pixbufloader()() loader.write(data) loader.close() pixbuf = loader.get_pixbuf() pngicondata = pixbuf_save_to_memory(pixbuf, "png") return pngicondata, "png" except Exception as e: log("pixbuf error loading %s", filename, exc_info=True) log.error("Error loading '%s':", filename) log.error(" %s", e) #try PIL: from PIL import Image try: img = Image.open(filename) except Exception as e: log("Image.open(%s)", filename, exc_info=True) log.error("Error loading '%s':", filename) log.error(" %s", e) return None buf = BytesIOClass() img.save(buf, "PNG") pngicondata = buf.getvalue() buf.close() return pngicondata, "png" icondata = load_binary_file(filename) if not icondata: return None log("got icon data from '%s': %i bytes", filename, len(icondata)) return icondata, os.path.splitext(filename)[1].rstrip(".")
def do_send_cursor(self, delay): self.cursor_timer = None cd = self.get_cursor_data_cb() if cd and cd[0]: cursor_data = list(cd[0]) cursor_sizes = cd[1] #skip first two fields (if present) as those are coordinates: if self.last_cursor_sent and self.last_cursor_sent[ 2:9] == cursor_data[2:9]: cursorlog( "do_send_cursor(..) cursor identical to the last one we sent, nothing to do" ) return self.last_cursor_sent = cursor_data[:9] w, h, _xhot, _yhot, serial, pixels, name = cursor_data[2:9] #compress pixels if needed: encoding = None if pixels is not None: #convert bytearray to string: cpixels = strtobytes(pixels) if "png" in self.cursor_encodings: from xpra.codecs.loader import get_codec PIL = get_codec("PIL") assert PIL cursorlog( "do_send_cursor() loading %i bytes of cursor pixel data for %ix%i cursor named '%s'", len(cpixels), w, h, name) img = PIL.Image.frombytes("RGBA", (w, h), cpixels, "raw", "BGRA", w * 4, 1) buf = BytesIOClass() img.save(buf, "PNG") pngdata = buf.getvalue() buf.close() cpixels = Compressed("png cursor", pngdata, can_inline=True) encoding = "png" if SAVE_CURSORS: with open("raw-cursor-%#x.png" % serial, "wb") as f: f.write(pngdata) elif len(cpixels) >= 256 and ("raw" in self.cursor_encodings or not self.cursor_encodings): cpixels = self.compressed_wrapper("cursor", pixels) cursorlog("do_send_cursor(..) pixels=%s ", cpixels) encoding = "raw" cursor_data[7] = cpixels cursorlog( "do_send_cursor(..) %sx%s %s cursor name='%s', serial=%#x with delay=%s (cursor_encodings=%s)", w, h, (encoding or "empty"), name, serial, delay, self.cursor_encodings) args = list(cursor_data[:9]) + [cursor_sizes[0]] + list( cursor_sizes[1]) if self.cursor_encodings and encoding: args = [encoding] + args else: cursorlog("do_send_cursor(..) sending empty cursor with delay=%s", delay) args = [""] self.last_cursor_sent = None self.send_more("cursor", *args)
def nasty_rgb_via_png_paint(self, cairo_format, has_alpha, img_data, x, y, width, height, rowstride, rgb_format): log("nasty_rgb_via_png_paint%s", (cairo_format, has_alpha, len(img_data), x, y, width, height, rowstride, rgb_format)) #PIL fallback PIL = get_codec("PIL") if has_alpha: oformat = "RGBA" else: oformat = "RGB" #use frombytes rather than frombuffer to be compatible with python3 new-style buffers #this is slower, but since this codepath is already dreadfully slow, we don't care bdata = strtobytes(memoryview_to_bytes(img_data)) try: img = PIL.Image.frombytes(oformat, (width,height), bdata, "raw", rgb_format.replace("X", "A"), rowstride, 1) except ValueError as e: raise Exception("failed to parse raw %s data to %s: %s" % (rgb_format, oformat, e)) #This is insane, the code below should work, but it doesn't: # img_data = bytearray(img.tostring('raw', oformat, 0, 1)) # pixbuf = pixbuf_new_from_data(img_data, COLORSPACE_RGB, True, 8, width, height, rowstride) # success = self.cairo_paint_pixbuf(pixbuf, x, y) #So we still rountrip via PNG: png = BytesIOClass() img.save(png, format="PNG") reader = BytesIOClass(png.getvalue()) png.close() img = cairo.ImageSurface.create_from_png(reader) self.cairo_paint_surface(img, x, y) return True
def image_data(img): buf = BytesIOClass() img.save(buf, "png") data = buf.getvalue() buf.close() w,h = img.size return ("png", w, h, data)
def paint_pil_image(self, pil_image, width, height, rowstride, options, callbacks): buf = BytesIOClass() pil_image.save(buf, format="PNG") png_data = buf.getvalue() buf.close() self.idle_add(self.paint_png, png_data, 0, 0, width, height, rowstride, options, callbacks)
def send_webcam_frame(self): if not self.webcam_lock.acquire(False): return False log("send_webcam_frame() webcam_device=%s", self.webcam_device) try: assert self.webcam_device_no>=0, "device number is not set" assert self.webcam_device, "no webcam device to capture from" from xpra.codecs.pillow.encode import get_encodings client_webcam_encodings = get_encodings() common_encodings = list(set(self.server_webcam_encodings).intersection(client_webcam_encodings)) log("common encodings (server=%s, client=%s): %s", csv(self.server_encodings), csv(client_webcam_encodings), csv(common_encodings)) if not common_encodings: log.error("Error: cannot send webcam image, no common formats") log.error(" the server supports: %s", csv(self.server_webcam_encodings)) log.error(" the client supports: %s", csv(client_webcam_encodings)) self.stop_sending_webcam() return False preferred_order = ["jpeg", "png", "png/L", "png/P", "webp"] formats = [x for x in preferred_order if x in common_encodings] + common_encodings encoding = formats[0] start = monotonic_time() import cv2 ret, frame = self.webcam_device.read() assert ret, "capture failed" assert frame.ndim==3, "invalid frame data" h, w, Bpp = frame.shape assert Bpp==3 and frame.size==w*h*Bpp rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) end = monotonic_time() log("webcam frame capture took %ims", (end-start)*1000) start = monotonic_time() from PIL import Image image = Image.fromarray(rgb) buf = BytesIOClass() image.save(buf, format=encoding) data = buf.getvalue() buf.close() end = monotonic_time() log("webcam frame compression to %s took %ims", encoding, (end-start)*1000) frame_no = self.webcam_frame_no self.webcam_frame_no += 1 self.send("webcam-frame", self.webcam_device_no, frame_no, encoding, w, h, compression.Compressed(encoding, data)) self.cancel_webcam_check_ack_timer() self.webcam_ack_check_timer = self.timeout_add(10*1000, self.webcam_check_acks) return True except Exception as e: log.error("webcam frame %i failed", self.webcam_frame_no, exc_info=True) log.error("Error sending webcam frame: %s", e) self.stop_sending_webcam() summary = "Webcam forwarding has failed" body = "The system encountered the following error:\n" + \ ("%s\n" % e) self.may_notify(XPRA_WEBCAM_NOTIFICATION_ID, summary, body, expire_timeout=10*1000, icon_name="webcam") return False finally: self.webcam_lock.release()
def test_webcam(self): if not POSIX or OSX: get_util_logger().info("webcam test skipped: %s not supported yet", sys.platform) return from xpra.platform.xposix.webcam import get_virtual_video_devices, check_virtual_dir if not check_virtual_dir(): get_util_logger().info( "webcam test skipped: no virtual video device directory") return devices = get_virtual_video_devices() if not devices: get_util_logger().info( "webcam test skipped: no virtual video devices found") return from xpra.server.source.webcam_mixin import WebcamMixin server = AdHocStruct() wm = WebcamMixin() server.webcam_enabled = True server.webcam_device = None server.webcam_encodings = ["png", "jpeg"] wm.init_from(None, server) wm.init_state() wm.hello_sent = True packets = [] def send(*args): packets.append(args) #wm.send = send wm.send_async = send try: assert wm.get_info() device_id = 0 w, h = 640, 480 assert wm.start_virtual_webcam(device_id, w, h) assert wm.get_info().get("webcam", {}).get("active-devices", 0) == 1 assert len(packets) == 1 #ack sent frame_no = 0 encoding = "png" buf = BytesIOClass() from PIL import Image image = Image.new('RGB', size=(w, h), color=(155, 0, 0)) image.save(buf, 'jpeg') data = buf.getvalue() buf.close() wm.process_webcam_frame(device_id, frame_no, encoding, w, h, data) assert len(packets) == 2 #ack sent wm.stop_virtual_webcam(device_id) finally: wm.cleanup()
def take_screenshot(self): x, y, w, h = get_virtualscreenmetrics() image = self.get_image(x, y, w, h) if not image: return None assert image.get_width()==w and image.get_height()==h assert image.get_pixel_format()=="BGRX" img = Image.frombuffer("RGB", (w, h), image.get_pixels(), "raw", "BGRX", 0, 1) out = BytesIOClass() img.save(out, format="PNG") screenshot = (img.width, img.height, "png", img.width*3, out.getvalue()) out.close() return screenshot
def _do_paint_rgb(self, cairo_format, has_alpha, img_data, x, y, width, height, rowstride, options): """ must be called from UI thread """ log("cairo._do_paint_rgb(%s, %s, %s bytes,%s,%s,%s,%s,%s,%s)", cairo_format, has_alpha, len(img_data), x, y, width, height, rowstride, options) rgb_format = options.strget("rgb_format", "RGB") if _memoryview and isinstance(img_data, _memoryview): #Pixbuf cannot use the memoryview directly: img_data = img_data.tobytes() #"cairo.ImageSurface.create_for_data" is not implemented in GTK3! ARGH! # http://cairographics.org/documentation/pycairo/3/reference/surfaces.html#cairo.ImageSurface.create_for_data # "Not yet available in Python 3" # #It is available in the cffi cairo bindings, which can be used instead of pycairo # but then we can't use it from the draw callbacks: # https://mail.gnome.org/archives/python-hackers-list/2011-December/msg00004.html # "PyGObject just lacks the glue code that allows it to pass the statically-wrapped # cairo.Pattern to introspected methods" if not is_gtk3() and rgb_format in ("ARGB", "XRGB"): #the pixel format is also what cairo expects #maybe we should also check that the stride is acceptable for cairo? #cairo_stride = cairo.ImageSurface.format_stride_for_width(cairo_format, width) #log("cairo_stride=%s, stride=%s", cairo_stride, rowstride) img_surface = cairo.ImageSurface.create_for_data(img_data, cairo_format, width, height, rowstride) return self.cairo_paint_surface(img_surface, x, y) if not is_gtk3() and rgb_format in ("RGBA", "RGBX"): #with GTK2, we can use a pixbuf from RGB(A) pixels if rgb_format=="RGBA": #we have to unpremultiply for pixbuf! img_data = self.unpremultiply(img_data) pixbuf = pixbuf_new_from_data(img_data, COLORSPACE_RGB, has_alpha, 8, width, height, rowstride) return self.cairo_paint_pixbuf(pixbuf, x, y) #PIL fallback PIL = get_codec("PIL") if has_alpha: oformat = "RGBA" else: oformat = "RGB" img = PIL.Image.frombuffer(oformat, (width,height), img_data, "raw", rgb_format, rowstride, 1) #This is insane, the code below should work, but it doesn't: # img_data = bytearray(img.tostring('raw', oformat, 0, 1)) # pixbuf = pixbuf_new_from_data(img_data, COLORSPACE_RGB, True, 8, width, height, rowstride) # success = self.cairo_paint_pixbuf(pixbuf, x, y) #So we still rountrip via PNG: png = BytesIOClass() img.save(png, format="PNG") reader = BytesIOClass(png.getvalue()) png.close() img = cairo.ImageSurface.create_from_png(reader) return self.cairo_paint_surface(img, x, y)
def selftest(full=False): global ENCODINGS import binascii from xpra.os_util import BytesIOClass #test data generated using the encoder: for encoding, hexdata in ( ('png', "89504e470d0a1a0a0000000d4948445200000020000000200806000000737a7af40000002849444154785eedd08100000000c3a0f9531fe4855061c0800103060c183060c0800103060cbc0f0c102000013337932a0000000049454e44ae426082"), ('png', "89504e470d0a1a0a0000000d4948445200000020000000200802000000fc18eda30000002549444154785eedd03101000000c2a0f54fed610d884061c0800103060c183060c080810f0c0c20000174754ae90000000049454e44ae426082"), ('png/L', "89504e470d0a1a0a0000000d4948445200000020000000200800000000561125280000000274524e5300ff5b9122b50000002049444154785e63fccf801f3011906718550009a1d170180d07e4bc323cd20300a33d013f95f841e70000000049454e44ae426082"), ('png/L', "89504e470d0a1a0a0000000d4948445200000020000000200800000000561125280000001549444154785e63601805a321301a02a321803d0400042000017854be5c0000000049454e44ae426082"), ('png/P', "89504e470d0a1a0a0000000d494844520000002000000020080300000044a48ac600000300504c5445000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b330f4880000010074524e53ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0053f707250000001c49444154785e63f84f00308c2a0087c068384012c268388ca87000003f68fc2e077ed1070000000049454e44ae426082"), ('png/P', "89504e470d0a1a0a0000000d494844520000002000000020080300000044a48ac600000300504c5445000000000000000000000000000000000000000000000000000000000000000000330000660000990000cc0000ff0000003300333300663300993300cc3300ff3300006600336600666600996600cc6600ff6600009900339900669900999900cc9900ff990000cc0033cc0066cc0099cc00cccc00ffcc0000ff0033ff0066ff0099ff00ccff00ffff00000033330033660033990033cc0033ff0033003333333333663333993333cc3333ff3333006633336633666633996633cc6633ff6633009933339933669933999933cc9933ff993300cc3333cc3366cc3399cc33cccc33ffcc3300ff3333ff3366ff3399ff33ccff33ffff33000066330066660066990066cc0066ff0066003366333366663366993366cc3366ff3366006666336666666666996666cc6666ff6666009966339966669966999966cc9966ff996600cc6633cc6666cc6699cc66cccc66ffcc6600ff6633ff6666ff6699ff66ccff66ffff66000099330099660099990099cc0099ff0099003399333399663399993399cc3399ff3399006699336699666699996699cc6699ff6699009999339999669999999999cc9999ff999900cc9933cc9966cc9999cc99cccc99ffcc9900ff9933ff9966ff9999ff99ccff99ffff990000cc3300cc6600cc9900cccc00ccff00cc0033cc3333cc6633cc9933cccc33ccff33cc0066cc3366cc6666cc9966cccc66ccff66cc0099cc3399cc6699cc9999cccc99ccff99cc00cccc33cccc66cccc99ccccccccccffcccc00ffcc33ffcc66ffcc99ffccccffccffffcc0000ff3300ff6600ff9900ffcc00ffff00ff0033ff3333ff6633ff9933ffcc33ffff33ff0066ff3366ff6666ff9966ffcc66ffff66ff0099ff3399ff6699ff9999ffcc99ffff99ff00ccff33ccff66ccff99ccffccccffffccff00ffff33ffff66ffff99ffffccffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023faca40000001549444154785e63601805a321301a02a321803d0400042000017854be5c0000000049454e44ae426082"), ('jpeg', "ffd8ffe000104a46494600010100000100010000ffdb004300100b0c0e0c0a100e0d0e1211101318281a181616183123251d283a333d3c3933383740485c4e404457453738506d51575f626768673e4d71797064785c656763ffdb0043011112121815182f1a1a2f634238426363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363ffc00011080020002003012200021101031101ffc4001500010100000000000000000000000000000007ffc40014100100000000000000000000000000000000ffc40014010100000000000000000000000000000000ffc40014110100000000000000000000000000000000ffda000c03010002110311003f009f800000000000ffd9"), ('jpeg', "ffd8ffe000104a46494600010100000100010000ffdb004300100b0c0e0c0a100e0d0e1211101318281a181616183123251d283a333d3c3933383740485c4e404457453738506d51575f626768673e4d71797064785c656763ffdb0043011112121815182f1a1a2f634238426363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363ffc00011080020002003012200021101031101ffc4001500010100000000000000000000000000000007ffc40014100100000000000000000000000000000000ffc40014010100000000000000000000000000000000ffc40014110100000000000000000000000000000000ffda000c03010002110311003f009f800000000000ffd9"), ('webp', "524946465c00000057454250565038580a000000100000001f00001f0000414c50480f00000001071011110012c2ffef7a44ff530f005650382026000000d002009d012a200020003ed162aa4fa825a3a2280801001a096900003da3a000fef39d800000"), ('webp', "524946465c00000057454250565038580a000000100000001f00001f0000414c50480f00000001071011110012c2ffef7a44ff530f005650382026000000d002009d012a200020003ed162aa4fa825a3a2280801001a096900003da3a000fef39d800000"), ): if encoding not in ENCODINGS: #removed already continue try: cdata = binascii.unhexlify(hexdata) buf = BytesIOClass(cdata) img = PIL.Image.open(buf) assert img, "failed to open image data" data_fn = getattr(img, "tobytes", getattr(img, "tostring", None)) raw_data = data_fn("raw", img.mode) assert raw_data #now try with junk: cdata = binascii.unhexlify("ABCD"+hexdata) buf = BytesIOClass(cdata) try: img = PIL.Image.open(buf) log.warn("Pillow failed to generate an error parsing invalid input") except Exception as e: log("correctly raised exception for invalid input: %s", e) except Exception as e: try: #py2k: datainfo = cdata.encode("string_escape") except: try: datainfo = cdata.encode("unicode_escape").decode() except: datainfo = str(hexdata) log.warn("Pillow error decoding %s with data=%s..", encoding, datainfo[:16]) log.warn(" %s", e, exc_info=True) ENCODINGS.remove(encoding)
def parse_hints(self, h): hints = {} for x in ("action-icons", "category", "desktop-entry", "resident", "transient", "x", "y", "urgency"): v = h.get(x) if v is not None: hints[x] = native_to_dbus(v) image_data = h.get("image-data") if image_data and bytestostr(image_data[0]) == "png": try: img_data = image_data[3] from PIL import Image buf = BytesIOClass(img_data) img = Image.open(buf) w, h = img.size channels = len(img.mode) rowstride = w * channels has_alpha = img.mode == "RGBA" pixel_data = bytearray(img.tobytes("raw", img.mode)) log.info("pixel_data=%s", type(pixel_data)) args = w, h, rowstride, has_alpha, 8, channels, pixel_data hints["image-data"] = tuple(native_to_dbus(x) for x in args) #hints["image-data"] = args except Exception as e: log("parse_hints(%s) error on image-data=%s", h, image_data, exc_info=True) log.error("Error parsing notification image:") log.error(" %s", e) log("parse_hints(%s)=%s", h, hints) #return dbus.types.Dictionary(hints, signature="sv") return hints
def paint_image(self, coding, img_data, x, y, width, height, options, callbacks): """ can be called from any thread """ #log("paint_image(%s, %s bytes, %s, %s, %s, %s, %s, %s)", coding, len(img_data), x, y, width, height, options, callbacks) assert PIL buf = BytesIOClass(img_data) img = PIL.Image.open(buf) assert img.mode in ("L", "P", "RGB", "RGBA"), "invalid image mode: %s" % img.mode if img.mode in ("P", "L"): #TODO: use RGB for images without transparency img = img.convert("RGB") raw_data = img.tostring("raw", img.mode) if img.mode == "RGB": #PIL flattens the data to a continuous straightforward RGB format: rowstride = width * 3 img_data = self.process_delta(raw_data, width, height, rowstride, options) self.idle_add(self.do_paint_rgb24, img_data, x, y, width, height, rowstride, options, callbacks) elif img.mode == "RGBA": rowstride = width * 4 img_data = self.process_delta(raw_data, width, height, rowstride, options) self.idle_add(self.do_paint_rgb32, img_data, x, y, width, height, rowstride, options, callbacks) return False
def paint_image(self, coding, img_data, x, y, width, height, options, callbacks): """ can be called from any thread """ #log("paint_image(%s, %s bytes, %s, %s, %s, %s, %s, %s)", coding, len(img_data), x, y, width, height, options, callbacks) PIL = get_codec("PIL") assert PIL, "PIL not found" buf = BytesIOClass(img_data) img = PIL.Image.open(buf) assert img.mode in ("L", "P", "RGB", "RGBA"), "invalid image mode: %s" % img.mode transparency = options.get("transparency", -1) if img.mode == "P": if transparency >= 0: #this deals with alpha without any extra work img = img.convert("RGBA") else: img = img.convert("RGB") elif img.mode == "L": if transparency >= 0: #why do we have to deal with alpha ourselves?? def mask_value(a): if a != transparency: return 255 return 0 mask = PIL.Image.eval(img, mask_value) mask = mask.convert("L") def nomask_value(a): if a != transparency: return a return 0 img = PIL.Image.eval(img, nomask_value) img = img.convert("RGBA") img.putalpha(mask) else: img = img.convert("RGB") #use tobytes() if present, fallback to tostring(): data_fn = getattr(img, "tobytes", getattr(img, "tostring", None)) raw_data = data_fn("raw", img.mode) paint_options = typedict(options) if img.mode == "RGB": #PIL flattens the data to a continuous straightforward RGB format: rowstride = width * 3 paint_options["rgb_format"] = "RGB" img_data = self.process_delta(raw_data, width, height, rowstride, options) self.idle_add(self.do_paint_rgb24, img_data, x, y, width, height, rowstride, paint_options, callbacks) elif img.mode == "RGBA": rowstride = width * 4 paint_options["rgb_format"] = "RGBA" img_data = self.process_delta(raw_data, width, height, rowstride, options) self.idle_add(self.do_paint_rgb32, img_data, x, y, width, height, rowstride, paint_options, callbacks) return False
def do_paint_rgb24(self, img_data, x, y, width, height, rowstride, options, callbacks): """ must be called from UI thread """ if DRAW_DEBUG: log.info("cairo_paint_rgb24(..,%s,%s,%s,%s,%s,%s,%s)", x, y, width, height, rowstride, options, callbacks) if self._backing is None: fire_paint_callbacks(callbacks, False) return False assert PIL, "cannot paint without PIL!" if rowstride==0: rowstride = width * 3 im = PIL.Image.frombuffer("RGB", (width, height), img_data, "raw", "RGB", rowstride) buf = BytesIOClass() im.save(buf, "PNG") data = buf.getvalue() buf.close() img_data = BytesIOClass(data) self.do_paint_png(img_data, x, y, width, height, rowstride, options, callbacks) return False
def process_webcam_frame(self, device_id, frame_no, encoding, w, h, data): webcam = self.webcam_forwarding_devices.get(device_id) log("process_webcam_frame: device %s, frame no %i: %s %ix%i, %i bytes, webcam=%s", device_id, frame_no, encoding, w, h, len(data), webcam) assert encoding and w and h and data if not webcam: log.error("Error: webcam forwarding is not active, dropping frame") self.send_webcam_stop(device_id, "not started") return try: from xpra.codecs.pillow.decode import get_encodings assert encoding in get_encodings(), "invalid encoding specified: %s (must be one of %s)" % (encoding, get_encodings()) rgb_pixel_format = "BGRX" #BGRX from PIL import Image buf = BytesIOClass(data) img = Image.open(buf) pixels = img.tobytes('raw', rgb_pixel_format) from xpra.codecs.image_wrapper import ImageWrapper bgrx_image = ImageWrapper(0, 0, w, h, pixels, rgb_pixel_format, 32, w*4, planes=ImageWrapper.PACKED) src_format = webcam.get_src_format() if not src_format: #closed / closing return #one of those two should be present try: csc_mod = "csc_libyuv" from xpra.codecs.csc_libyuv.colorspace_converter import get_input_colorspaces, get_output_colorspaces, ColorspaceConverter #@UnresolvedImport except ImportError: self.send_webcam_stop(device_id, "no csc module") return try: assert rgb_pixel_format in get_input_colorspaces(), "unsupported RGB pixel format %s" % rgb_pixel_format assert src_format in get_output_colorspaces(rgb_pixel_format), "unsupported output colourspace format %s" % src_format except Exception as e: log.error("Error: cannot convert %s to %s using %s:", rgb_pixel_format, src_format, csc_mod) log.error(" input-colorspaces: %s", csv(get_input_colorspaces())) log.error(" output-colorspaces: %s", csv(get_output_colorspaces(rgb_pixel_format))) self.send_webcam_stop(device_id, "csc format error") return tw = webcam.get_width() th = webcam.get_height() csc = ColorspaceConverter() csc.init_context(w, h, rgb_pixel_format, tw, th, src_format) image = csc.convert_image(bgrx_image) webcam.push_image(image) #tell the client all is good: self.send_webcam_ack(device_id, frame_no) except Exception as e: log("error on %ix%i frame %i using encoding %s", w, h, frame_no, encoding, exc_info=True) log.error("Error processing webcam frame:") msg = str(e) if not msg: msg = "unknown error" log.error(" %s error" % webcam, exc_info=True) log.error(" %s", msg) self.send_webcam_stop(device_id, msg) self.stop_virtual_webcam(device_id)
def get_notification_icon(self, icon_string): #the string may be: # * a path which we will load using pillow # * a name we lookup in the current them if not icon_string: return () img = None from PIL import Image if os.path.isabs(icon_string): if os.path.exists(icon_string) and os.path.isfile(icon_string): img = Image.open(icon_string) w, h = img.size else: #try to find it in the theme: theme = gtk.icon_theme_get_default() if theme: try: icon = theme.load_icon(icon_string, gtk.ICON_SIZE_BUTTON, 0) except Exception as e: notifylog( "failed to load icon '%s' from default theme: %s", icon_string, e) else: data = icon.get_pixels() w = icon.get_width() h = icon.get_height() rowstride = icon.get_rowstride() mode = "RGB" if icon.get_has_alpha(): mode = "RGBA" img = Image.frombytes(mode, (w, h), data, "raw", mode, rowstride) if img: if w > 256 or h > 256: img = img.resize((256, 256), Image.ANTIALIAS) w = h = 256 buf = BytesIOClass() img.save(buf, "PNG") cpixels = buf.getvalue() buf.close() return "png", w, h, cpixels return ()
def NetWMIcons(disp, data): icons = [] stream = BytesIOClass(data) while True: icon = _read_image(disp, stream) if icon is None: break icons.append(icon) if not icons: return None return icons
def test_webcam(self): if not POSIX or OSX: return from xpra.platform.xposix.webcam import get_virtual_video_devices, check_virtual_dir if not check_virtual_dir(): return devices = get_virtual_video_devices() if not devices: return from xpra.server.source.webcam_mixin import WebcamMixin wm = WebcamMixin(True, None, ["png", "jpeg"]) wm.init_state() wm.hello_sent = True packets = [] def send(*args): packets.append(args) #wm.send = send wm.send_async = send try: assert wm.get_info() device_id = 0 w, h = 640, 480 assert wm.start_virtual_webcam(device_id, w, h) assert wm.get_info().get("webcam", {}).get("active-devices", 0) == 1 assert len(packets) == 1 #ack sent frame_no = 0 encoding = "png" buf = BytesIOClass() from PIL import Image image = Image.new('RGB', size=(w, h), color=(155, 0, 0)) image.save(buf, 'jpeg') data = buf.getvalue() buf.close() wm.process_webcam_frame(device_id, frame_no, encoding, w, h, data) assert len(packets) == 2 #ack sent wm.stop_virtual_webcam(device_id) finally: wm.cleanup()
def NetWMIcons(disp, data): icons = [] stream = BytesIOClass(data) while True: size_image = _read_image(disp, stream) if size_image is None: break icons.append(size_image) if not icons: return None icons.sort() return icons[-1][1]
def nasty_rgb_via_png_paint(self, cairo_format, has_alpha, img_data, x, y, width, height, rowstride, rgb_format): log("nasty_rgb_via_png_paint%s", (cairo_format, has_alpha, len(img_data), x, y, width, height, rowstride, rgb_format)) #PIL fallback PIL = get_codec("PIL") if has_alpha: oformat = "RGBA" else: oformat = "RGB" #use frombytes rather than frombuffer to be compatible with python3 new-style buffers #this is slower, but since this codepath is already dreadfully slow, we don't care bdata = strtobytes(memoryview_to_bytes(img_data)) img = PIL.Image.frombytes(oformat, (width, height), bdata, "raw", rgb_format, rowstride, 1) #This is insane, the code below should work, but it doesn't: # img_data = bytearray(img.tostring('raw', oformat, 0, 1)) # pixbuf = pixbuf_new_from_data(img_data, COLORSPACE_RGB, True, 8, width, height, rowstride) # success = self.cairo_paint_pixbuf(pixbuf, x, y) #So we still rountrip via PNG: png = BytesIOClass() img.save(png, format="PNG") reader = BytesIOClass(png.getvalue()) png.close() img = cairo.ImageSurface.create_from_png(reader) self.cairo_paint_surface(img, x, y) return True
def nasty_rgb_via_png_paint(self, cairo_format, has_alpha, img_data, x, y, width, height, rowstride, rgb_format): log.warn("nasty_rgb_via_png_paint%s", (cairo_format, has_alpha, len(img_data), x, y, width, height, rowstride, rgb_format)) #PIL fallback from PIL import Image if has_alpha: oformat = "RGBA" else: oformat = "RGB" #use frombytes rather than frombuffer to be compatible with python3 new-style buffers #this is slower, but since this codepath is already dreadfully slow, we don't care bdata = strtobytes(memoryview_to_bytes(img_data)) src_format = rgb_format.replace("X", "A") try: img = Image.frombytes(oformat, (width,height), bdata, "raw", src_format, rowstride, 1) except ValueError as e: log("PIL Image frombytes:", exc_info=True) raise Exception("failed to parse raw %s data as %s to %s: %s" % (rgb_format, src_format, oformat, e)) #This is insane, the code below should work, but it doesn't: # img_data = bytearray(img.tostring('raw', oformat, 0, 1)) # pixbuf = pixbuf_new_from_data(img_data, COLORSPACE_RGB, True, 8, width, height, rowstride) # success = self.cairo_paint_pixbuf(pixbuf, x, y) #So we still rountrip via PNG: png = BytesIOClass() img.save(png, format="PNG") reader = BytesIOClass(png.getvalue()) png.close() img = cairo.ImageSurface.create_from_png(reader) self.cairo_paint_surface(img, x, y) return True
def command_callback(hwnd, cid): if cid == 1024: from xpra.platform.win32.win32_balloon import notify from xpra.os_util import BytesIOClass try: from PIL import Image #@UnresolvedImport img = Image.open("icons\\printer.png") buf = BytesIOClass() img.save(buf, "PNG") data = buf.getvalue() buf.close() icon = (b"png", img.size[0], img.size[1], data) except Exception as e: print("could not find icon: %s" % (e, )) icon = None else: pass notify(hwnd, 0, "hello", "world", timeout=1000, icon=icon) elif cid == 1025: print("Goodbye") DestroyWindow(hwnd) else: print("OnCommand for ID=%s" % cid)
def _do_paint_rgb(self, cairo_format, has_alpha, img_data, x, y, width, height, rowstride, options): """ must be called from UI thread """ log("cairo._do_paint_rgb(%s, %s, %s bytes,%s,%s,%s,%s,%s,%s)", cairo_format, has_alpha, len(img_data), x, y, width, height, rowstride, options) rgb_format = options.strget("rgb_format", "RGB") if _memoryview and isinstance(img_data, _memoryview): #Pixbuf cannot use the memoryview directly: img_data = img_data.tobytes() #"cairo.ImageSurface.create_for_data" is not implemented in GTK3! ARGH! # http://cairographics.org/documentation/pycairo/3/reference/surfaces.html#cairo.ImageSurface.create_for_data # "Not yet available in Python 3" # #It is available in the cffi cairo bindings, which can be used instead of pycairo # but then we can't use it from the draw callbacks: # https://mail.gnome.org/archives/python-hackers-list/2011-December/msg00004.html # "PyGObject just lacks the glue code that allows it to pass the statically-wrapped # cairo.Pattern to introspected methods" if not is_gtk3() and rgb_format in ("ARGB", "XRGB"): #the pixel format is also what cairo expects #maybe we should also check that the stride is acceptable for cairo? #cairo_stride = cairo.ImageSurface.format_stride_for_width(cairo_format, width) #log("cairo_stride=%s, stride=%s", cairo_stride, rowstride) img_surface = cairo.ImageSurface.create_for_data( img_data, cairo_format, width, height, rowstride) return self.cairo_paint_surface(img_surface, x, y) if not is_gtk3() and rgb_format in ("RGBA", "RGBX"): #with GTK2, we can use a pixbuf from RGB(A) pixels if rgb_format == "RGBA": #we have to unpremultiply for pixbuf! img_data = self.unpremultiply(img_data) pixbuf = pixbuf_new_from_data(img_data, COLORSPACE_RGB, has_alpha, 8, width, height, rowstride) return self.cairo_paint_pixbuf(pixbuf, x, y) #PIL fallback PIL = get_codec("PIL") if has_alpha: oformat = "RGBA" else: oformat = "RGB" img = PIL.Image.frombuffer(oformat, (width, height), img_data, "raw", rgb_format, rowstride, 1) #This is insane, the code below should work, but it doesn't: # img_data = bytearray(img.tostring('raw', oformat, 0, 1)) # pixbuf = pixbuf_new_from_data(img_data, COLORSPACE_RGB, True, 8, width, height, rowstride) # success = self.cairo_paint_pixbuf(pixbuf, x, y) #So we still rountrip via PNG: png = BytesIOClass() img.save(png, format="PNG") reader = BytesIOClass(png.getvalue()) png.close() img = cairo.ImageSurface.create_from_png(reader) return self.cairo_paint_surface(img, x, y)
def ui_paint_image(): if not self._backing: fire_paint_callbacks(callbacks, False) return try: if coding.startswith("png"): reader = BytesIOClass(img_data) img = cairo.ImageSurface.create_from_png(reader) success = self.cairo_paint_surface(img, x, y) else: assert coding == "jpeg" pbl = PixbufLoader() pbl.write(img_data) pbl.close() pixbuf = pbl.get_pixbuf() del pbl success = self.cairo_paint_pixbuf(pixbuf, x, y) except: log.error("cairo error during paint", exc_info=True) success = False fire_paint_callbacks(callbacks, success)
def notify(hwnd, app_id, title, message, timeout=5000, icon=None): nid = PyNOTIFYICONDATA() nid.hWnd = hwnd nid.uID = app_id nid.uFlags = NIF_INFO nid.guidItem = XPRA_GUID_BYTES nid.szInfo = chop_string(message, 255, False) #prevent overflow nid.szInfoTitle = chop_string(title, 63) if timeout<=0: timeout = 5000 nid.uTimeoutOrVersion = timeout #if no icon is supplied, we can use: # NIIF_INFO, NIIF_WARNING or NIIF_ERROR nid.dwInfoFlags = NIIF_INFO if icon: try: w, h, data = icon[1:4] buf = BytesIOClass(data) from PIL import Image #@UnresolvedImport from xpra.platform.win32.win32_NotifyIcon import image_to_ICONINFO img = Image.open(buf) iw = GetSystemMetrics(SM_CXSMICON) ih = GetSystemMetrics(SM_CYSMICON) if w!=iw or h!=ih: img = img.resize((iw, ih), Image.ANTIALIAS) log("notification icon resized to %s", img.size) hicon = image_to_ICONINFO(img) log("notify: image_to_ICONINFO(%s)=%#x", img, hicon) nid.hIcon = hicon nid.hBalloonIcon = hicon except Exception as e: log("notify%s", (hwnd, app_id, title, message, timeout, icon), exc_info=True) log.error("Error: failed to set notification icon:") log.error(" %s", e) else: nid.dwInfoFlags = NIIF_USER Shell_NotifyIcon = windll.shell32.Shell_NotifyIcon Shell_NotifyIcon(NIM_MODIFY, nid.pack()) log("notify using %s", Shell_NotifyIcon)
def do_paint_rgb24(self, img_data, x, y, width, height, rowstride, options, callbacks): """ must be called from UI thread """ if DRAW_DEBUG: log.info("cairo_paint_rgb24(..,%s,%s,%s,%s,%s,%s,%s)", x, y, width, height, rowstride, options, callbacks) if self._backing is None: fire_paint_callbacks(callbacks, False) return False PIL = get_codec("PIL") assert PIL, "cannot paint without PIL!" if rowstride==0: rowstride = width * 3 im = PIL.Image.frombuffer("RGB", (width, height), img_data, "raw", "RGB", rowstride) buf = BytesIOClass() im.save(buf, "PNG") data = buf.getvalue() buf.close() img_data = BytesIOClass(data) self.do_paint_png(img_data, x, y, width, height, rowstride, options, callbacks) return False
def paint_image(self, coding, img_data, x, y, width, height, options, callbacks): """ can be called from any thread """ buf = BytesIOClass(img_data) img = Image.open(buf) assert img.mode in ("L", "P", "RGB", "RGBA", "RGBX"), "invalid image mode: %s" % img.mode transparency = options.intget("transparency", -1) if img.mode=="P": if transparency>=0: #this deals with alpha without any extra work img = img.convert("RGBA") else: img = img.convert("RGB") elif img.mode=="L": if transparency>=0: #why do we have to deal with alpha ourselves?? def mask_value(a): if a!=transparency: return 255 return 0 mask = Image.eval(img, mask_value) mask = mask.convert("L") def nomask_value(a): if a!=transparency: return a return 0 img = Image.eval(img, nomask_value) img = img.convert("RGBA") img.putalpha(mask) else: img = img.convert("RGB") raw_data = img.tobytes("raw", img.mode) paint_options = typedict(options) if img.mode=="RGB": #PIL flattens the data to a continuous straightforward RGB format: rowstride = width*3 rgb_format = options.strget("rgb_format", "") rgb_format = rgb_format.replace("A", "").replace("X", "") #the webp encoder only takes BGRX input, #so we have to swap things around if it was fed "RGB": if rgb_format=="RGB": rgb_format = "BGR" else: rgb_format = "RGB" img_data = self.process_delta(raw_data, width, height, rowstride, options) elif img.mode in ("RGBA", "RGBX"): rowstride = width*4 rgb_format = options.strget("rgb_format", img.mode) if coding=="webp": #the webp encoder only takes BGRX input, #so we have to swap things around if it was fed "RGBA": if rgb_format=="RGBA": rgb_format = "BGRA" elif rgb_format=="RGBX": rgb_format = "BGRX" elif rgb_format=="BGRA": rgb_format = "RGBA" elif rgb_format=="BGRX": rgb_format = "RGBX" else: log.warn("Warning: unexpected RGB format '%s'", rgb_format) img_data = self.process_delta(raw_data, width, height, rowstride, options) else: raise Exception("invalid image mode: %s" % img.mode) self.idle_add(self.do_paint_rgb, rgb_format, img_data, x, y, width, height, rowstride, paint_options, callbacks) return False
def encode(coding, image, quality, speed, supports_transparency): pixel_format = image.get_pixel_format() w = image.get_width() h = image.get_height() rgb = { "XRGB" : "RGB", "BGRX" : "RGB", "RGBA" : "RGBA", "BGRA" : "RGBA", }.get(pixel_format, pixel_format) bpp = 32 #remove transparency if it cannot be handled: try: pixels = image.get_pixels() assert pixels, "failed to get pixels from %s" % image #PIL cannot use the memoryview directly: if type(pixels)!=_buffer: pixels = memoryview_to_bytes(pixels) #it is safe to use frombuffer() here since the convert() #calls below will not convert and modify the data in place #and we save the compressed data then discard the image im = PIL.Image.frombuffer(rgb, (w, h), pixels, "raw", pixel_format, image.get_rowstride()) if coding.startswith("png") and not supports_transparency and rgb=="RGBA": im = im.convert("RGB") rgb = "RGB" bpp = 24 except Exception: log.error("PIL_encode%s converting %s pixels from %s to %s failed", (w, h, coding, "%s bytes" % image.get_size(), pixel_format, image.get_rowstride()), type(pixels), pixel_format, rgb, exc_info=True) raise buf = BytesIOClass() client_options = {} #only optimize with Pillow>=2.2 and when speed is zero if coding in ("jpeg", "webp"): q = int(min(99, max(1, quality))) kwargs = im.info kwargs["quality"] = q client_options["quality"] = q if coding=="jpeg" and PIL_can_optimize and speed<70: #(optimizing jpeg is pretty cheap and worth doing) kwargs["optimize"] = True client_options["optimize"] = True im.save(buf, coding.upper(), **kwargs) else: assert coding in ("png", "png/P", "png/L"), "unsupported png encoding: %s" % coding if coding in ("png/L", "png/P") and supports_transparency and rgb=="RGBA": #grab alpha channel (the last one): #we use the last channel because we know it is RGBA, #otherwise we should do: alpha_index= image.getbands().index('A') alpha = im.split()[-1] #convert to simple on or off mask: #set all pixel values below 128 to 255, and the rest to 0 def mask_value(a): if a<=128: return 255 return 0 mask = PIL.Image.eval(alpha, mask_value) else: #no transparency mask = None if coding=="png/L": im = im.convert("L", palette=PIL.Image.ADAPTIVE, colors=255) bpp = 8 elif coding=="png/P": #I wanted to use the "better" adaptive method, #but this does NOT work (produces a black image instead): #im.convert("P", palette=Image.ADAPTIVE) im = im.convert("P", palette=PIL.Image.WEB, colors=255) bpp = 8 if mask: # paste the alpha mask to the color of index 255 im.paste(255, mask) kwargs = im.info if mask is not None: client_options["transparency"] = 255 kwargs["transparency"] = 255 if PIL_can_optimize and speed==0: #optimizing png is very rarely worth doing kwargs["optimize"] = True client_options["optimize"] = True #level can range from 0 to 9, but anything above 5 is way too slow for small gains: #76-100 -> 1 #51-76 -> 2 #etc level = max(1, min(5, (125-speed)//25)) kwargs["compress_level"] = level client_options["compress_level"] = level #default is good enough, no need to override, other options: #DEFAULT_STRATEGY, FILTERED, HUFFMAN_ONLY, RLE, FIXED #kwargs["compress_type"] = PIL.Image.DEFAULT_STRATEGY im.save(buf, "PNG", **kwargs) log("sending %sx%s %s as %s, mode=%s, options=%s", w, h, pixel_format, coding, im.mode, kwargs) data = buf.getvalue() buf.close() return coding, compression.Compressed(coding, data), client_options, image.get_width(), image.get_height(), 0, bpp
def compress_and_send_window_icon(self): #this runs in the work queue self.send_window_icon_timer = 0 idata = self.window_icon_data if not idata: return w, h, pixel_format, pixel_data = idata log("compress_and_send_window_icon() %ix%i in %s format, %i bytes", w, h, pixel_format, len(pixel_data)) assert pixel_format in ("BGRA", "RGBA", "png"), "invalid window icon format %s" % pixel_format if pixel_format=="BGRA": #BGRA data is always unpremultiplied #(that's what we get from NetWMIcons) from xpra.codecs.argb.argb import premultiply_argb #@UnresolvedImport pixel_data = premultiply_argb(pixel_data) max_w, max_h = self.window_icon_max_size #use png if supported and if "premult_argb32" is not supported by the client (ie: html5) #or if we must downscale it (bigger than what the client is willing to deal with), #or if we want to save window icons must_scale = w>max_w or h>max_h use_png = self.has_png and (must_scale or not self.has_premult or w*h>=MAX_ARGB_PIXELS) log("compress_and_send_window_icon: %sx%s (max-size=%s, standard-size=%s), sending as png=%s, pixel_format=%s", w, h, self.window_icon_max_size, self.window_icon_size, use_png, pixel_format) must_convert = (use_png and pixel_format!="png") or (pixel_format=="png" and not use_png) or (pixel_format=="BGRA" and not self.has_premult) log(" must convert=%s, must scale=%s", must_convert, must_scale) image = None if must_scale or must_convert or SAVE_WINDOW_ICONS: #we're going to need a PIL Image: if pixel_format=="png": image = Image.open(BytesIOClass(pixel_data)) else: #note: little endian makes this confusing.. RGBA for pillow is BGRA in memory if pixel_format=="RGBA": src_format = "BGRA" else: src_format = "RGBA" image = Image.frombuffer("RGBA", (w,h), memoryview_to_bytes(pixel_data), "raw", src_format, 0, 1) if must_scale: #scale the icon down to the size the client wants #(we should scale + paste to preserve the aspect ratio, meh) icon_w, icon_h = self.window_icon_size if float(w)/icon_w>=float(h)/icon_h: rh = min(max_h, h*icon_w//w) rw = icon_w else: rw = min(max_w, w*icon_h//h) rh = icon_h log("scaling window icon down to %sx%s", rw, rh) image = image.resize((rw, rh), Image.ANTIALIAS) if SAVE_WINDOW_ICONS: filename = "server-window-%i-icon-%i.png" % (self.wid, int(monotonic_time())) image.save(filename, 'PNG') log("server window icon saved to %s", filename) if use_png: if image: #image got converted or scaled, get the new pixel data: output = BytesIOClass() image.save(output, "png") pixel_data = output.getvalue() output.close() w, h = image.size wrapper = compression.Compressed("png", pixel_data) else: if image: pixel_data = image.tobytes("raw", "RGBA") wrapper = self.compressed_wrapper("premult_argb32", memoryview_to_bytes(pixel_data)) packet = ("window-icon", self.wid, w, h, wrapper.datatype, wrapper) log("queuing window icon update: %s", packet) self.queue_packet(packet, wait_for_more=True)
def encode(coding, image, quality, speed, supports_transparency): log("pillow.encode%s", (coding, image, quality, speed, supports_transparency)) pixel_format = bytestostr(image.get_pixel_format()) palette = None w = image.get_width() h = image.get_height() rgb = { "RLE8" : "P", "XRGB" : "RGB", "BGRX" : "RGB", "RGBX" : "RGB", "RGBA" : "RGBA", "BGRA" : "RGBA", "BGR" : "RGB", }.get(pixel_format, pixel_format) bpp = 32 #remove transparency if it cannot be handled, #and deal with non 24-bit formats: try: pixels = image.get_pixels() assert pixels, "failed to get pixels from %s" % image if pixel_format=="r210": from xpra.codecs.argb.argb import r210_to_rgba, r210_to_rgb #@UnresolvedImport if supports_transparency: pixels = r210_to_rgba(pixels) pixel_format = "RGBA" rgb = "RGBA" else: image.set_rowstride(image.get_rowstride()*3//4) pixels = r210_to_rgb(pixels) pixel_format = "RGB" rgb = "RGB" bpp = 24 elif pixel_format=="BGR565": from xpra.codecs.argb.argb import bgr565_to_rgbx, bgr565_to_rgb #@UnresolvedImport if supports_transparency: image.set_rowstride(image.get_rowstride()*2) pixels = bgr565_to_rgbx(pixels) pixel_format = "RGBA" rgb = "RGBA" else: image.set_rowstride(image.get_rowstride()*3//2) pixels = bgr565_to_rgb(pixels) pixel_format = "RGB" rgb = "RGB" bpp = 24 elif pixel_format=="RLE8": pixel_format = "P" palette = [] #pillow requires 8 bit palette values, #but we get 16-bit values from the image wrapper (X11 palettes are 16-bit): for r, g, b in image.get_palette(): palette.append((r>>8) & 0xFF) palette.append((g>>8) & 0xFF) palette.append((b>>8) & 0xFF) bpp = 8 #PIL cannot use the memoryview directly: if isinstance(pixels, memoryview): pixels = pixels.tobytes() #it is safe to use frombuffer() here since the convert() #calls below will not convert and modify the data in place #and we save the compressed data then discard the image im = Image.frombuffer(rgb, (w, h), pixels, "raw", pixel_format, image.get_rowstride(), 1) if palette: im.putpalette(palette) im.palette = ImagePalette.ImagePalette("RGB", palette = palette, size = len(palette)) if coding.startswith("png") and not supports_transparency and rgb=="RGBA": im = im.convert("RGB") rgb = "RGB" bpp = 24 except Exception: log.error("PIL_encode%s converting %s pixels from %s to %s failed", (w, h, coding, "%s bytes" % image.get_size(), pixel_format, image.get_rowstride()), type(pixels), pixel_format, rgb, exc_info=True) raise client_options = {} if coding in ("jpeg", "webp", "jpeg2000"): #newer versions of pillow require explicit conversion to non-alpha: if pixel_format.find("A")>=0: im = im.convert("RGB") q = int(min(100, max(1, quality))) kwargs = im.info kwargs["quality"] = q client_options["quality"] = q if coding=="jpeg2000": kwargs["quality_mode"] = "rates" #other option: "dB" kwargs["quality_layers"] = max(1, (100-q)*5) elif coding=="jpeg" and speed<50: #(optimizing jpeg is pretty cheap and worth doing) kwargs["optimize"] = True client_options["optimize"] = True elif coding=="webp" and q>=100: kwargs["lossless"] = 1 pil_fmt = coding.upper() else: assert coding in ("png", "png/P", "png/L"), "unsupported encoding: %s" % coding if coding in ("png/L", "png/P") and supports_transparency and rgb=="RGBA": #grab alpha channel (the last one): #we use the last channel because we know it is RGBA, #otherwise we should do: alpha_index= image.getbands().index('A') alpha = im.split()[-1] #convert to simple on or off mask: #set all pixel values below 128 to 255, and the rest to 0 def mask_value(a): if a<=128: return 255 return 0 mask = Image.eval(alpha, mask_value) else: #no transparency mask = None if coding=="png/L": im = im.convert("L", palette=Image.ADAPTIVE, colors=255) bpp = 8 elif coding=="png/P": #convert to 255 indexed colour if: # * we're not in palette mode yet (source is >8bpp) # * we need space for the mask (256 -> 255) if palette is None or mask: #I wanted to use the "better" adaptive method, #but this does NOT work (produces a black image instead): #im.convert("P", palette=Image.ADAPTIVE) im = im.convert("P", palette=Image.WEB, colors=255) bpp = 8 kwargs = im.info if mask: # paste the alpha mask to the color of index 255 im.paste(255, mask) client_options["transparency"] = 255 kwargs["transparency"] = 255 if speed==0: #optimizing png is very rarely worth doing kwargs["optimize"] = True client_options["optimize"] = True #level can range from 0 to 9, but anything above 5 is way too slow for small gains: #76-100 -> 1 #51-76 -> 2 #etc level = max(1, min(5, (125-speed)//25)) kwargs["compress_level"] = level #no need to expose to the client: #client_options["compress_level"] = level #default is good enough, no need to override, other options: #DEFAULT_STRATEGY, FILTERED, HUFFMAN_ONLY, RLE, FIXED #kwargs["compress_type"] = Image.DEFAULT_STRATEGY pil_fmt = "PNG" buf = BytesIOClass() im.save(buf, pil_fmt, **kwargs) if SAVE_TO_FILE: filename = "./%s.%s" % (time.time(), pil_fmt) im.save(filename, pil_fmt) log.info("saved %s to %s", coding, filename) log("sending %sx%s %s as %s, mode=%s, options=%s", w, h, pixel_format, coding, im.mode, kwargs) data = buf.getvalue() buf.close() return coding, Compressed(coding, data), client_options, image.get_width(), image.get_height(), 0, bpp
def compress_and_send_window_icon(self): #this runs in the work queue self.send_window_icon_timer = 0 idata = self.window_icon_data if not idata: return pixel_data, pixel_format, stride, w, h = idata PIL = get_codec("PIL") max_w, max_h = self.window_icon_max_size if stride != w * 4: #re-stride it (I don't think this ever fires?) pixel_data = b"".join(pixel_data[stride * y:stride * y + w * 4] for y in range(h)) stride = w * 4 #use png if supported and if "premult_argb32" is not supported by the client (ie: html5) #or if we must downscale it (bigger than what the client is willing to deal with), #or if we want to save window icons has_png = PIL and PNG_ICONS and ("png" in self.window_icon_encodings) has_premult = ARGB_ICONS and "premult_argb32" in self.window_icon_encodings use_png = has_png and (SAVE_WINDOW_ICONS or w > max_w or h > max_h or w * h >= 1024 or (not has_premult) or (pixel_format != "BGRA")) log( "compress_and_send_window_icon: %sx%s (max-size=%s, standard-size=%s), sending as png=%s, has_png=%s, has_premult=%s, pixel_format=%s", w, h, self.window_icon_max_size, self.window_icon_size, use_png, has_png, has_premult, pixel_format) if use_png: img = PIL.Image.frombuffer("RGBA", (w, h), pixel_data, "raw", pixel_format, 0, 1) if w > max_w or h > max_h: #scale the icon down to the size the client wants icon_w, icon_h = self.window_icon_size if float(w) / icon_w >= float(h) / icon_h: h = min(max_h, h * icon_w // w) w = icon_w else: w = min(max_w, w * icon_h // h) h = icon_h log("scaling window icon down to %sx%s", w, h) img = img.resize((w, h), PIL.Image.ANTIALIAS) output = BytesIOClass() img.save(output, 'PNG') compressed_data = output.getvalue() output.close() wrapper = compression.Compressed("png", compressed_data) if SAVE_WINDOW_ICONS: filename = "server-window-%i-icon-%i.png" % ( self.wid, int(monotonic_time())) img.save(filename, 'PNG') log("server window icon saved to %s", filename) elif ("premult_argb32" in self.window_icon_encodings) and pixel_format == "BGRA": wrapper = self.compressed_wrapper("premult_argb32", str(pixel_data)) else: log("cannot send window icon, supported encodings: %s", self.window_icon_encodings) return assert wrapper.datatype in ( "premult_argb32", "png"), "invalid wrapper datatype %s" % wrapper.datatype packet = ("window-icon", self.wid, w, h, wrapper.datatype, wrapper) log("queuing window icon update: %s", packet) self.queue_packet(packet, wait_for_more=True)
def selftest(_full=False): global ENCODINGS import binascii from xpra.os_util import BytesIOClass #test data generated using the encoder: for encoding, hexdata in ( ('png', "89504e470d0a1a0a0000000d4948445200000020000000200806000000737a7af40000002849444154785eedd08100000000c3a0f9531fe4855061c0800103060c183060c0800103060cbc0f0c102000013337932a0000000049454e44ae426082" ), ('png', "89504e470d0a1a0a0000000d4948445200000020000000200802000000fc18eda30000002549444154785eedd03101000000c2a0f54fed610d884061c0800103060c183060c080810f0c0c20000174754ae90000000049454e44ae426082" ), ('png/L', "89504e470d0a1a0a0000000d4948445200000020000000200800000000561125280000000274524e5300ff5b9122b50000002049444154785e63fccf801f3011906718550009a1d170180d07e4bc323cd20300a33d013f95f841e70000000049454e44ae426082" ), ('png/L', "89504e470d0a1a0a0000000d4948445200000020000000200800000000561125280000001549444154785e63601805a321301a02a321803d0400042000017854be5c0000000049454e44ae426082" ), ('png/P', "89504e470d0a1a0a0000000d494844520000002000000020080300000044a48ac600000300504c5445000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b330f4880000010074524e53ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0053f707250000001c49444154785e63f84f00308c2a0087c068384012c268388ca87000003f68fc2e077ed1070000000049454e44ae426082" ), ('png/P', "89504e470d0a1a0a0000000d494844520000002000000020080300000044a48ac600000300504c5445000000000000000000000000000000000000000000000000000000000000000000330000660000990000cc0000ff0000003300333300663300993300cc3300ff3300006600336600666600996600cc6600ff6600009900339900669900999900cc9900ff990000cc0033cc0066cc0099cc00cccc00ffcc0000ff0033ff0066ff0099ff00ccff00ffff00000033330033660033990033cc0033ff0033003333333333663333993333cc3333ff3333006633336633666633996633cc6633ff6633009933339933669933999933cc9933ff993300cc3333cc3366cc3399cc33cccc33ffcc3300ff3333ff3366ff3399ff33ccff33ffff33000066330066660066990066cc0066ff0066003366333366663366993366cc3366ff3366006666336666666666996666cc6666ff6666009966339966669966999966cc9966ff996600cc6633cc6666cc6699cc66cccc66ffcc6600ff6633ff6666ff6699ff66ccff66ffff66000099330099660099990099cc0099ff0099003399333399663399993399cc3399ff3399006699336699666699996699cc6699ff6699009999339999669999999999cc9999ff999900cc9933cc9966cc9999cc99cccc99ffcc9900ff9933ff9966ff9999ff99ccff99ffff990000cc3300cc6600cc9900cccc00ccff00cc0033cc3333cc6633cc9933cccc33ccff33cc0066cc3366cc6666cc9966cccc66ccff66cc0099cc3399cc6699cc9999cccc99ccff99cc00cccc33cccc66cccc99ccccccccccffcccc00ffcc33ffcc66ffcc99ffccccffccffffcc0000ff3300ff6600ff9900ffcc00ffff00ff0033ff3333ff6633ff9933ffcc33ffff33ff0066ff3366ff6666ff9966ffcc66ffff66ff0099ff3399ff6699ff9999ffcc99ffff99ff00ccff33ccff66ccff99ccffccccffffccff00ffff33ffff66ffff99ffffccffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023faca40000001549444154785e63601805a321301a02a321803d0400042000017854be5c0000000049454e44ae426082" ), ('jpeg', "ffd8ffe000104a46494600010100000100010000ffdb004300100b0c0e0c0a100e0d0e1211101318281a181616183123251d283a333d3c3933383740485c4e404457453738506d51575f626768673e4d71797064785c656763ffdb0043011112121815182f1a1a2f634238426363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363ffc00011080020002003012200021101031101ffc4001500010100000000000000000000000000000007ffc40014100100000000000000000000000000000000ffc40014010100000000000000000000000000000000ffc40014110100000000000000000000000000000000ffda000c03010002110311003f009f800000000000ffd9" ), ('jpeg', "ffd8ffe000104a46494600010100000100010000ffdb004300100b0c0e0c0a100e0d0e1211101318281a181616183123251d283a333d3c3933383740485c4e404457453738506d51575f626768673e4d71797064785c656763ffdb0043011112121815182f1a1a2f634238426363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363ffc00011080020002003012200021101031101ffc4001500010100000000000000000000000000000007ffc40014100100000000000000000000000000000000ffc40014010100000000000000000000000000000000ffc40014110100000000000000000000000000000000ffda000c03010002110311003f009f800000000000ffd9" ), ('webp', "524946465c00000057454250565038580a000000100000001f00001f0000414c50480f00000001071011110012c2ffef7a44ff530f005650382026000000d002009d012a200020003ed162aa4fa825a3a2280801001a096900003da3a000fef39d800000" ), ('webp', "524946465c00000057454250565038580a000000100000001f00001f0000414c50480f00000001071011110012c2ffef7a44ff530f005650382026000000d002009d012a200020003ed162aa4fa825a3a2280801001a096900003da3a000fef39d800000" ), ('jpeg2000', "0000000c6a5020200d0a870a00000014667479706a703220000000006a7032200000002d6a703268000000166968647200000020000000200003070700000000000f636f6c720100000000001000000d8b6a703263ff4fff51002f000000000020000000200000000000000000000000200000002000000000000000000003070101070101070101ff52000c00000001000504040001ff5c00134040484850484850484850484850484850ff640025000143726561746564206279204f70656e4a5045472076657273696f6e20322e332e30ff90000a000000000d040001ff93c7d4040977c1f20106c7d404049fc07c20c0f900c07c8040080107c03a0c010c0084070707c0f90140f900c03e1040057f0305c1f38483e70907d40a0d5baab30c1897bf0b2ccdf607c03a141f38487da0a003cf0e13572c01e2b616dfc1f38483e70907d40a0d6a9ce90c459c710ff00205bfc3ea1187d41d0fb44411e2c3e4379101f73b3459b6a61066ff7f07649d884d89d90068baf1b3327f143c7a1cb72bb9df9494fa4cd8b4aec09fc7da211f68943ed1300a1613b5a6ecf3266bc9eaa4aab16e2717f77070d476d1ec90c7455f4761dcf465f302db51faec9e1b5c1cc854f250334e36e95a9fc3ea108fb44a1f689012abc11315c3e36f7995c0c44a9f3a5917df22504861216db8faca9431194af1b32b16847435f00b2817291621b99fcd79e5cce3c7da7b1f69fc7e08a026d03038ccfa865709f8fa0ebea3622eaf4f68a3672f799b0c95f3f93d793feb3ec88a21c6faa1ad7d154b21e40d1788a2442808f0594ef8d945d2f5fd17f95dc4129d3aad41246afaff2b5b61dbcd75a8f85056654a1f8eb6276d776c82321ab42845d07fa64283529ca80c46189524f52f8dd6479ceabeb07916d90a1740e9c1455434770c78797532824a27bb3df6265724c4e34a5d1c7ef87b2810eebe19104476c5b1abc42d1f9c52e4ac8902cba5a676f57293bc7119de8d7f6f821b1edfc7da873f0448fc1240173329ccc8cc1e00bf57e27208b6c7d1143b43bd351840f54f08f51bdace2d423e66b6f24cf61fae9a247158f196c44b41eb2a8f9f1f960cd4f4bd7cbf4f2be8992c7f48a83e2c6f182eff5a602c57ce86daceec1bd379018825ec2264078e7dc7f7f6a496115619cde81b18a829e8df8a5b5e188a0e7f23c4fc728edcc78cf77ba2b5c0df967f302adc347b999d512564cebd7d89c66fc579a8dd05547a181e4be9079acb1d9aa0b160981da8d3c38f451fbdfc217689831681d206d039c4a36f7909fb1d11d7e978215cb03a9c0e3bc7da8b3f0449f9a580198f765dce94872bb3e08a4fc1c743931627796b4efc0012ae795767e9e08e6f4a4f16bca96d0581cb5e09f10e068ed53c26297d927cc57d03c38beb213b0ea513e82b4caf48ac318401d37f34e79411c323bbb89509dfe4b4e0aa036d87a46dff1e529788bc2048fc28907f04a1d85024d15a22e1fa3ecb1d4d5264f7d72f4fa7d51851074efb387f0760929f7134e14472776446154ac013368e3711f63d6392f856197dcc74af2cf37241e89a893c8a0e6704903c92f86f4f9a83fde7d961d1505efb7f589c06c46d06d1fb4b6cded97310dfcfc2fe9f8682cfceff0010e715ca18e9343f215b1b4f38cd8c23c020f122af4cc1b9d265644f425d9279a479e9cdea4eb33864945cca8bc7148026429e977a508a39bf44f0ea5e1ba474ad9131083f77af6394e471ff83e77c3465f8a37c98680fd9443662d3ded723aa65c3bf8f6f4fff045b1ef10e68b5f53b78ef6d863aded77d5ce9fc5db54b37b2a4e22bb8aefc776149ddd58b0292e9dccca564ac2cbe802b1ff5c6530c243fa50fa4043431f982604881dc430471d64a4a247d4cf58d10a1b8422509205df5a087468f25c6798329961a9ad22f1ed39782673c4c6ddd2f12276ee794063a73d8208c92708b37a5c00e64be8ee21c639511854f24636ea5d7896e6de25b81ab6e2ba7e3d494b2f1f6c812143a51c3bcd60e3665cab9316af52befe64f6040d0d6a3254aa6232d3e085260a1ff650110ca5e13ea208e46e1c18a0f1e8ecd702bb7400687b0b9a64b52b63ee8653fc2681eba192ec26449c155f4c0bc45340341f0552ffe49e9ff2743ee6b3c81c5e79d91e71af4b6e1be023879e6d294fe45e20a7b06a1c2c4d79b3974a9a02347d55a2bd6006c6eb5627af3105cbf106f1aaf75efb9d75f7102339b1f7e67929cfe7c94fcbff4b603b347cd61babe2af40ee461416152f1704152af09f0a2a32e6c21e43ad0eb8e3917d776687a80ac0ad691c6f45459d800ca65dc0cbe187fc0ad61d33a0d90404a6491ecb77a903d90f35135211934a060c11096eea69b989383ed43f71834dab91fda63a8dccf9b39a85d5df94a59b6a88bde98f95ca7a037a46023efb85e2230cd5d4ee126038cd43f26f4199532e48eaf6fb88a2ea710e9ac84c8631aa5ae3132c23efb223b77552b7fbac317b472744c1bea68f8e8963bf20a4341383a2eceff6186eac2bd07a6cd7ecbed28557288fdad0d2f72172ce71237166b9d1b6fc692bafce66ed62754850df9210de6b558eb5ed329ae1ae8c1d83c2a7abeeb4861593cb9aa60c3e685c41716c63ee9dcc197078446346440f1bd385df84935829bf5554afda187344c3c1f4d214479384651ea15c1eb08dfaf52941d61a39fc8755b35b8eea0f220467ab36b9c1fcfc2fa9f8687c7e1a1203625a30755cc1c5536012034f6376b5a5315f867c613c12ada7fb95bdb6b93f41a727b238d473c64060f59e99f81927422f7627504e02f34b17b2ca62fa85e5c17a87726d0a91996e5aeebc4224f26bdbdeaf35da463ebc009dc29ee8fb9d003b6282f515241df887c2a3df30ee0332ec78f672ca29a5f6893bb051b86763ae80214f2e1f3a1765380691d9836a8f8ab1e7c735fcc55a15d405f6eee202ec968f3c9bcc98cee2df70d509ab1e54578a79ef982fbfaeb5d2a6c1e060a1f5221c5ce7bf26bc00b5da7a1bcd34638dad21b0df045591f5090bd74fb2dff3b4e95b91091b573ddb5baa67ce3bd1dd0cd3330cea9ca65a0180633cdf31d2b721c22789aa7005af0db25e627bbb9b6662f2f54b3715cd1059f210d33a9711ce012596c3d7b47322a14577ffcdbd0ddf9a67729ba4665fd9e788b224dfabd912fde48c074d2326bdbc749018afbf777b9898a4e7c60901f2d288aa6e6a8b6073afd740cc98e50d9e8070f1afb0ca7657e17b653f8f3725e1f0b2a48f4572d6f589a11ea8aba88bbf7ec76ce923040aab560a78b61237ee74b17691e5690b45478229f9bfbca60f4928c6e255933e8c934fc68e4c8d087812b9fa4a63d0b44a124babc5d3646e6dc5eafb08452e359d369936a0c8eb75d26ff768a79cdfc4c6eac67544f9dc80fd2902ba04db49e5463b4c9b73efefaa3d7ce4c8963e514894d5d4b00760baba04bed35fe1c7362523104ccff67bef0b755a482f8d2c2812f1b1ee3cb10511f4f83e645dd5963c78f460064e60a326dc2f850cc1d326d9694d4ce99b0f0929dc9b11c8be766168b4b3aad54ee9fbbc90bcddfe54fcac6b6cc97eeaf43b49ddd17957ba002f2e6e55a1060cc89948c4b23d0ceaa7052210f61bca27186efd57283f47ac149e05b75329e304423f44836c12fa401bbd5ea84e7b1f5cba33f29763a3b1a91991d8ca42b8f3ef1379decaa917dc854cd58884d22f92fdaa37873420b6b8d1a02c8810dc17fe26081b3896bed67fc7fa5729d3d1c0342eb36e7087a211636aca5faf7cd6d0550bc2102bf6a712edc9bbe278e87323f54132383b6bd07a1da98ec9dcef396dc31e941425acbcfc341a7e1a313f3bfc0168671ec6f790cb71a9be74053f6b01518c63cb8379070ac84a10e0f0116a5d4e665526a1f36ab615359b64e5c91bb4d5027cd64d949f8b93f837b5edd8a4cb8257e3f3313fb0c114ac0db7e3a0d2d320352ae2d041843311e3684e5a6c4e4dd21dbb71547d082fd92be477ddb4c0183b93105a0da75a5ba61e2fa9ba4c75338ff1d27dd045b55d399fc86c2237a3028a33d1efba6b3f29566282cc75c5ac3dfa61e2d0b802e538906da154fa172130ccbe2ea11ea903c6e0b332866728b6a67f9f671693720fd88bf80f9f6f31644beb4c7409461053743a6ccd27cf8ab8e5052d9e017fe6fe793b67a96480cb70cfbb6a8f1c73ae0fb48040117dd2bf14e8faaed6e58fdbf00fe36df559074d73cb52c62ae894025a7247426317ea1e1522f9729838b8997ee26210e047d7658c35e58f863381f229265c8b2f8db03b11333b67c3b0b3e1665080d2c0943bae79d107c63f8516933ff6c5d45b3881fff16afa9585f5d0b1d834766a7a2cb3e474cab7782d2b687941a8ae33f3133af0b741f30598dca4e9384bad8c0848c5031d4cc219db1051a10f0273cadd88f37bb772babf5f44556e3c07e8282e65a48ce52f19986df89424ce1cb6759af2c53ed9bf05f1f8b5aca8f12d2f10334a063ef5fc88a45ef1f0a75ae85d6a17bacf5a2f221b4cf51eb5f92af0ca2610c687f127cae994072b63bb9a638b7816c9c7c1055e7f5ccbbb1311681dd7c79191325cf566f164cbb5d20a03b4e7a43f82db50faa8cfa0c3c56b98f4c83e4b4f35d172124e7fd1501eaf127b1f7b1ba9a71c03c56efc3e764868ccac0c45ad8782faac9fcd4bedba8350526e4609f7de69b06722f3ec35a29b23041714c0879a3591afe52c548d8201cfd92a555944f26e33c9938eb808241cb0091af358b41f7fd1347e4d618e9f759b6f51f646c6b5dbb1a98e3a0e0df7b11861fa438ed6c2a52d527b0a651491bd2cb9e170f8d5d1086ac7dc893bf6548ef4fc739457aa5de09a77e9261e8862c816c2f69f6ccb578532c314f97eb66e1779cda7f22c1caa0b2a54ec8fd6588cd9edd06ef06ce0b0806e35a88e92bdb9abc65f61024a48e06c8266fd42837fbdf00ebf3b5ede43b6391bfffd9" ), ('jpeg2000', "0000000c6a5020200d0a870a00000014667479706a703220000000006a7032200000002d6a703268000000166968647200000020000000200003070700000000000f636f6c720100000000001000000d8b6a703263ff4fff51002f000000000020000000200000000000000000000000200000002000000000000000000003070101070101070101ff52000c00000001000504040001ff5c00134040484850484850484850484850484850ff640025000143726561746564206279204f70656e4a5045472076657273696f6e20322e332e30ff90000a000000000d040001ff93c7d4040977c1f20106c7d404049fc07c20c0f900c07c8040080107c03a0c010c0084070707c0f90140f900c03e1040057f0305c1f38483e70907d40a0d5baab30c1897bf0b2ccdf607c03a141f38487da0a003cf0e13572c01e2b616dfc1f38483e70907d40a0d6a9ce90c459c710ff00205bfc3ea1187d41d0fb44411e2c3e4379101f73b3459b6a61066ff7f07649d884d89d90068baf1b3327f143c7a1cb72bb9df9494fa4cd8b4aec09fc7da211f68943ed1300a1613b5a6ecf3266bc9eaa4aab16e2717f77070d476d1ec90c7455f4761dcf465f302db51faec9e1b5c1cc854f250334e36e95a9fc3ea108fb44a1f689012abc11315c3e36f7995c0c44a9f3a5917df22504861216db8faca9431194af1b32b16847435f00b2817291621b99fcd79e5cce3c7da7b1f69fc7e08a026d03038ccfa865709f8fa0ebea3622eaf4f68a3672f799b0c95f3f93d793feb3ec88a21c6faa1ad7d154b21e40d1788a2442808f0594ef8d945d2f5fd17f95dc4129d3aad41246afaff2b5b61dbcd75a8f85056654a1f8eb6276d776c82321ab42845d07fa64283529ca80c46189524f52f8dd6479ceabeb07916d90a1740e9c1455434770c78797532824a27bb3df6265724c4e34a5d1c7ef87b2810eebe19104476c5b1abc42d1f9c52e4ac8902cba5a676f57293bc7119de8d7f6f821b1edfc7da873f0448fc1240173329ccc8cc1e00bf57e27208b6c7d1143b43bd351840f54f08f51bdace2d423e66b6f24cf61fae9a247158f196c44b41eb2a8f9f1f960cd4f4bd7cbf4f2be8992c7f48a83e2c6f182eff5a602c57ce86daceec1bd379018825ec2264078e7dc7f7f6a496115619cde81b18a829e8df8a5b5e188a0e7f23c4fc728edcc78cf77ba2b5c0df967f302adc347b999d512564cebd7d89c66fc579a8dd05547a181e4be9079acb1d9aa0b160981da8d3c38f451fbdfc217689831681d206d039c4a36f7909fb1d11d7e978215cb03a9c0e3bc7da8b3f0449f9a580198f765dce94872bb3e08a4fc1c743931627796b4efc0012ae795767e9e08e6f4a4f16bca96d0581cb5e09f10e068ed53c26297d927cc57d03c38beb213b0ea513e82b4caf48ac318401d37f34e79411c323bbb89509dfe4b4e0aa036d87a46dff1e529788bc2048fc28907f04a1d85024d15a22e1fa3ecb1d4d5264f7d72f4fa7d51851074efb387f0760929f7134e14472776446154ac013368e3711f63d6392f856197dcc74af2cf37241e89a893c8a0e6704903c92f86f4f9a83fde7d961d1505efb7f589c06c46d06d1fb4b6cded97310dfcfc2fe9f8682cfceff0010e715ca18e9343f215b1b4f38cd8c23c020f122af4cc1b9d265644f425d9279a479e9cdea4eb33864945cca8bc7148026429e977a508a39bf44f0ea5e1ba474ad9131083f77af6394e471ff83e77c3465f8a37c98680fd9443662d3ded723aa65c3bf8f6f4fff045b1ef10e68b5f53b78ef6d863aded77d5ce9fc5db54b37b2a4e22bb8aefc776149ddd58b0292e9dccca564ac2cbe802b1ff5c6530c243fa50fa4043431f982604881dc430471d64a4a247d4cf58d10a1b8422509205df5a087468f25c6798329961a9ad22f1ed39782673c4c6ddd2f12276ee794063a73d8208c92708b37a5c00e64be8ee21c639511854f24636ea5d7896e6de25b81ab6e2ba7e3d494b2f1f6c812143a51c3bcd60e3665cab9316af52befe64f6040d0d6a3254aa6232d3e085260a1ff650110ca5e13ea208e46e1c18a0f1e8ecd702bb7400687b0b9a64b52b63ee8653fc2681eba192ec26449c155f4c0bc45340341f0552ffe49e9ff2743ee6b3c81c5e79d91e71af4b6e1be023879e6d294fe45e20a7b06a1c2c4d79b3974a9a02347d55a2bd6006c6eb5627af3105cbf106f1aaf75efb9d75f7102339b1f7e67929cfe7c94fcbff4b603b347cd61babe2af40ee461416152f1704152af09f0a2a32e6c21e43ad0eb8e3917d776687a80ac0ad691c6f45459d800ca65dc0cbe187fc0ad61d33a0d90404a6491ecb77a903d90f35135211934a060c11096eea69b989383ed43f71834dab91fda63a8dccf9b39a85d5df94a59b6a88bde98f95ca7a037a46023efb85e2230cd5d4ee126038cd43f26f4199532e48eaf6fb88a2ea710e9ac84c8631aa5ae3132c23efb223b77552b7fbac317b472744c1bea68f8e8963bf20a4341383a2eceff6186eac2bd07a6cd7ecbed28557288fdad0d2f72172ce71237166b9d1b6fc692bafce66ed62754850df9210de6b558eb5ed329ae1ae8c1d83c2a7abeeb4861593cb9aa60c3e685c41716c63ee9dcc197078446346440f1bd385df84935829bf5554afda187344c3c1f4d214479384651ea15c1eb08dfaf52941d61a39fc8755b35b8eea0f220467ab36b9c1fcfc2fa9f8687c7e1a1203625a30755cc1c5536012034f6376b5a5315f867c613c12ada7fb95bdb6b93f41a727b238d473c64060f59e99f81927422f7627504e02f34b17b2ca62fa85e5c17a87726d0a91996e5aeebc4224f26bdbdeaf35da463ebc009dc29ee8fb9d003b6282f515241df887c2a3df30ee0332ec78f672ca29a5f6893bb051b86763ae80214f2e1f3a1765380691d9836a8f8ab1e7c735fcc55a15d405f6eee202ec968f3c9bcc98cee2df70d509ab1e54578a79ef982fbfaeb5d2a6c1e060a1f5221c5ce7bf26bc00b5da7a1bcd34638dad21b0df045591f5090bd74fb2dff3b4e95b91091b573ddb5baa67ce3bd1dd0cd3330cea9ca65a0180633cdf31d2b721c22789aa7005af0db25e627bbb9b6662f2f54b3715cd1059f210d33a9711ce012596c3d7b47322a14577ffcdbd0ddf9a67729ba4665fd9e788b224dfabd912fde48c074d2326bdbc749018afbf777b9898a4e7c60901f2d288aa6e6a8b6073afd740cc98e50d9e8070f1afb0ca7657e17b653f8f3725e1f0b2a48f4572d6f589a11ea8aba88bbf7ec76ce923040aab560a78b61237ee74b17691e5690b45478229f9bfbca60f4928c6e255933e8c934fc68e4c8d087812b9fa4a63d0b44a124babc5d3646e6dc5eafb08452e359d369936a0c8eb75d26ff768a79cdfc4c6eac67544f9dc80fd2902ba04db49e5463b4c9b73efefaa3d7ce4c8963e514894d5d4b00760baba04bed35fe1c7362523104ccff67bef0b755a482f8d2c2812f1b1ee3cb10511f4f83e645dd5963c78f460064e60a326dc2f850cc1d326d9694d4ce99b0f0929dc9b11c8be766168b4b3aad54ee9fbbc90bcddfe54fcac6b6cc97eeaf43b49ddd17957ba002f2e6e55a1060cc89948c4b23d0ceaa7052210f61bca27186efd57283f47ac149e05b75329e304423f44836c12fa401bbd5ea84e7b1f5cba33f29763a3b1a91991d8ca42b8f3ef1379decaa917dc854cd58884d22f92fdaa37873420b6b8d1a02c8810dc17fe26081b3896bed67fc7fa5729d3d1c0342eb36e7087a211636aca5faf7cd6d0550bc2102bf6a712edc9bbe278e87323f54132383b6bd07a1da98ec9dcef396dc31e941425acbcfc341a7e1a313f3bfc0168671ec6f790cb71a9be74053f6b01518c63cb8379070ac84a10e0f0116a5d4e665526a1f36ab615359b64e5c91bb4d5027cd64d949f8b93f837b5edd8a4cb8257e3f3313fb0c114ac0db7e3a0d2d320352ae2d041843311e3684e5a6c4e4dd21dbb71547d082fd92be477ddb4c0183b93105a0da75a5ba61e2fa9ba4c75338ff1d27dd045b55d399fc86c2237a3028a33d1efba6b3f29566282cc75c5ac3dfa61e2d0b802e538906da154fa172130ccbe2ea11ea903c6e0b332866728b6a67f9f671693720fd88bf80f9f6f31644beb4c7409461053743a6ccd27cf8ab8e5052d9e017fe6fe793b67a96480cb70cfbb6a8f1c73ae0fb48040117dd2bf14e8faaed6e58fdbf00fe36df559074d73cb52c62ae894025a7247426317ea1e1522f9729838b8997ee26210e047d7658c35e58f863381f229265c8b2f8db03b11333b67c3b0b3e1665080d2c0943bae79d107c63f8516933ff6c5d45b3881fff16afa9585f5d0b1d834766a7a2cb3e474cab7782d2b687941a8ae33f3133af0b741f30598dca4e9384bad8c0848c5031d4cc219db1051a10f0273cadd88f37bb772babf5f44556e3c07e8282e65a48ce52f19986df89424ce1cb6759af2c53ed9bf05f1f8b5aca8f12d2f10334a063ef5fc88a45ef1f0a75ae85d6a17bacf5a2f221b4cf51eb5f92af0ca2610c687f127cae994072b63bb9a638b7816c9c7c1055e7f5ccbbb1311681dd7c79191325cf566f164cbb5d20a03b4e7a43f82db50faa8cfa0c3c56b98f4c83e4b4f35d172124e7fd1501eaf127b1f7b1ba9a71c03c56efc3e764868ccac0c45ad8782faac9fcd4bedba8350526e4609f7de69b06722f3ec35a29b23041714c0879a3591afe52c548d8201cfd92a555944f26e33c9938eb808241cb0091af358b41f7fd1347e4d618e9f759b6f51f646c6b5dbb1a98e3a0e0df7b11861fa438ed6c2a52d527b0a651491bd2cb9e170f8d5d1086ac7dc893bf6548ef4fc739457aa5de09a77e9261e8862c816c2f69f6ccb578532c314f97eb66e1779cda7f22c1caa0b2a54ec8fd6588cd9edd06ef06ce0b0806e35a88e92bdb9abc65f61024a48e06c8266fd42837fbdf00ebf3b5ede43b6391bfffd9" ), ): if encoding not in ENCODINGS: #removed already continue try: cdata = binascii.unhexlify(hexdata) buf = BytesIOClass(cdata) img = PIL.Image.open(buf) assert img, "failed to open image data" raw_data = img.tobytes("raw", img.mode) assert raw_data #now try with junk: cdata = binascii.unhexlify("ABCD" + hexdata) buf = BytesIOClass(cdata) try: img = PIL.Image.open(buf) log.warn( "Pillow failed to generate an error parsing invalid input") except Exception as e: log("correctly raised exception for invalid input: %s", e) except Exception as e: try: #py2k: datainfo = cdata.encode("string_escape") except: try: datainfo = cdata.encode("unicode_escape").decode() except: datainfo = str(hexdata) if encoding == u"jpeg2000": l = log.debug else: l = log.warn l("Pillow error decoding %s with data=%s..", encoding, datainfo[:16]) l(" %s", e, exc_info=True) ENCODINGS.remove(encoding)
def make_cursor(self, cursor_data): #if present, try cursor ny name: display = display_get_default() cursorlog( "make_cursor: has-name=%s, has-cursor-types=%s, xscale=%s, yscale=%s, USE_LOCAL_CURSORS=%s", len(cursor_data) >= 10, bool(cursor_types), self.xscale, self.yscale, USE_LOCAL_CURSORS) #named cursors cannot be scaled (round to 10 to compare so 0.95 and 1.05 are considered the same as 1.0, no scaling): if len(cursor_data) >= 10 and cursor_types and iround( self.xscale * 10) == 10 and iround(self.yscale * 10) == 10: cursor_name = bytestostr(cursor_data[9]) if cursor_name and USE_LOCAL_CURSORS: gdk_cursor = cursor_types.get(cursor_name.upper()) if gdk_cursor is not None: cursorlog("setting new cursor by name: %s=%s", cursor_name, gdk_cursor) return new_Cursor_for_display(display, gdk_cursor) else: global missing_cursor_names if cursor_name not in missing_cursor_names: cursorlog("cursor name '%s' not found", cursor_name) missing_cursor_names.add(cursor_name) #create cursor from the pixel data: encoding, _, _, w, h, xhot, yhot, serial, pixels = cursor_data[0:9] if encoding == b"png": from xpra.os_util import BytesIOClass from PIL import Image buf = BytesIOClass(pixels) img = Image.open(buf) pixels = img.tobytes("raw", "BGRA") cursorlog("used PIL to convert png cursor to raw") elif encoding != b"raw": cursorlog.warn("Warning: invalid cursor encoding: %s", encoding) return None if len(pixels) < w * h * 4: import binascii cursorlog.warn( "not enough pixels provided in cursor data: %s needed and only %s bytes found (%s)", w * h * 4, len(pixels), binascii.hexlify(pixels)[:100]) return None pixbuf = get_pixbuf_from_data(pixels, True, w, h, w * 4) x = max(0, min(xhot, w - 1)) y = max(0, min(yhot, h - 1)) csize = display.get_default_cursor_size() cmaxw, cmaxh = display.get_maximal_cursor_size() if len(cursor_data) >= 12: ssize = cursor_data[10] smax = cursor_data[11] cursorlog("server cursor sizes: default=%s, max=%s", ssize, smax) cursorlog( "new %s cursor at %s,%s with serial=%s, dimensions: %sx%s, len(pixels)=%s, default cursor size is %s, maximum=%s", encoding, xhot, yhot, serial, w, h, len(pixels), csize, (cmaxw, cmaxh)) fw, fh = get_fixed_cursor_size() if fw > 0 and fh > 0 and (w != fw or h != fh): #OS wants a fixed cursor size! (win32 does, and GTK doesn't do this for us) if w <= fw and h <= fh: cursorlog( "pasting cursor of size %ix%i onto clear pixbuf of size %ix%i", w, h, fw, fh) cursor_pixbuf = get_pixbuf_from_data("\0" * fw * fh * 4, True, fw, fh, fw * 4) pixbuf.copy_area(0, 0, w, h, cursor_pixbuf, 0, 0) else: cursorlog("scaling cursor from %ix%i to fixed OS size %ix%i", w, h, fw, fh) cursor_pixbuf = pixbuf.scale_simple(fw, fh, INTERP_BILINEAR) xratio, yratio = float(w) / fw, float(h) / fh x, y = iround(x / xratio), iround(y / yratio) else: sx, sy, sw, sh = x, y, w, h #scale the cursors: if self.xscale != 1 or self.yscale != 1: sx, sy, sw, sh = self.srect(x, y, w, h) sw = max(1, sw) sh = max(1, sh) #ensure we honour the max size if there is one: if (cmaxw > 0 and sw > cmaxw) or (cmaxh > 0 and sh > cmaxh): ratio = 1.0 if cmaxw > 0: ratio = max(ratio, float(w) / cmaxw) if cmaxh > 0: ratio = max(ratio, float(h) / cmaxh) cursorlog("clamping cursor size to %ix%i using ratio=%s", cmaxw, cmaxh, ratio) sx, sy, sw, sh = iround(x / ratio), iround(y / ratio), min( cmaxw, iround(w / ratio)), min(cmaxh, iround(h / ratio)) if sw != w or sh != h: cursorlog( "scaling cursor from %ix%i hotspot at %ix%i to %ix%i hotspot at %ix%i", w, h, x, y, sw, sh, sx, sy) cursor_pixbuf = pixbuf.scale_simple(sw, sh, INTERP_BILINEAR) x, y = sx, sy else: cursor_pixbuf = pixbuf #clamp to pixbuf size: w = cursor_pixbuf.get_width() h = cursor_pixbuf.get_height() x = max(0, min(x, w - 1)) y = max(0, min(y, h - 1)) try: c = new_Cursor_from_pixbuf(display, cursor_pixbuf, x, y) except RuntimeError as e: log.error("Error: failed to create cursor:") log.error(" %s", e) log.error(" using %s of size %ix%i with hotspot at %ix%i", cursor_pixbuf, w, h, x, y) c = None return c