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 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 image_data(img): buf = BytesIOClass() img.save(buf, "png") data = buf.getvalue() buf.close() w,h = img.size return ("png", w, h, data)
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 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 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 _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 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 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 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 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 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 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 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)