def __init__(self, wid : int, window_alpha : bool): load_csc_options() load_video_decoders() self.wid = wid self.size = 0, 0 self.render_size = 0, 0 #padding between the window contents and where we actually draw the backing #(ie: if the window is bigger than the backing, # we may be rendering the backing in the center of the window) self.offsets = 0, 0, 0, 0 #top,left,bottom,right self.gravity = 0 self._alpha_enabled = window_alpha self._backing = None self._video_decoder = None self._csc_decoder = None self._decoder_lock = Lock() self._PIL_encodings = [] self.default_paint_box_line_width = PAINT_BOX or 1 self.paint_box_line_width = PAINT_BOX self.pointer_overlay = None self.cursor_data = None self.default_cursor_data = None self.jpeg_decoder = None self.webp_decoder = None self.pil_decoder = get_codec("dec_pillow") if self.pil_decoder: self._PIL_encodings = self.pil_decoder.get_encodings() self.jpeg_decoder = get_codec("dec_jpeg") self.webp_decoder = get_codec("dec_webp") self.draw_needs_refresh = True self.repaint_all = REPAINT_ALL self.mmap = None self.mmap_enabled = False
def __init__(self, wid, window_alpha): load_csc_options() load_video_decoders() self.wid = wid self.size = 0, 0 self.render_size = 0, 0 self.offsets = 0, 0, 0, 0 #top,left,bottom,right self._alpha_enabled = window_alpha self._backing = None self._delta_pixel_data = [None for _ in range(DELTA_BUCKETS)] self._video_decoder = None self._csc_decoder = None self._decoder_lock = Lock() self._PIL_encodings = [] self.default_paint_box_line_width = PAINT_BOX or 1 self.paint_box_line_width = PAINT_BOX self.pointer_overlay = None self.cursor_data = None self.default_cursor_data = None self.jpeg_decoder = None self.webp_decoder = None if is_loaded(): PIL = get_codec("dec_pillow") if PIL: self._PIL_encodings = PIL.get_encodings() self.jpeg_decoder = get_codec("dec_jpeg") self.webp_decoder = get_codec("dec_webp") self.draw_needs_refresh = True self.mmap = None self.mmap_enabled = False
def webp_encode(coding, image, supports_transparency, quality, speed, options): stride = image.get_rowstride() enc_webp = get_codec("enc_webp") if enc_webp and stride>0 and stride%4==0 and image.get_pixel_format() in ("BGRA", "BGRX"): #prefer Cython module: alpha = supports_transparency and image.get_pixel_format().find("A")>=0 w = image.get_width() h = image.get_height() if quality==100: #webp lossless is unbearibly slow for only marginal compression improvements, #so force max speed: speed = 100 else: #normalize speed for webp: avoid low speeds! speed = int(sqrt(speed) * 10) speed = max(0, min(100, speed)) cdata = enc_webp.compress(image.get_pixels(), w, h, stride=stride/4, quality=quality, speed=speed, has_alpha=alpha) client_options = {"speed" : speed} if quality>=0 and quality<=100: client_options["quality"] = quality if alpha: client_options["has_alpha"] = True return "webp", Compressed("webp", cdata), client_options, image.get_width(), image.get_height(), 0, 24 enc_webm = get_codec("enc_webm") webp_handlers = get_codec("webm_bitmap_handlers") if enc_webm and webp_handlers: return webm_encode(image, quality) #fallback to PIL return PIL_encode(coding, image, quality, speed, supports_transparency)
def webp_encode(image, supports_transparency, quality, speed, content_type): stride = image.get_rowstride() pixel_format = image.get_pixel_format() enc_webp = get_codec("enc_webp") #log("WEBP_PILLOW=%s, enc_webp=%s, stride=%s, pixel_format=%s", WEBP_PILLOW, enc_webp, stride, pixel_format) if not WEBP_PILLOW and enc_webp and stride > 0 and stride % 4 == 0 and pixel_format in ( "BGRA", "BGRX", "RGBA", "RGBX"): #prefer Cython module: cdata, client_options = enc_webp.encode(image, quality, speed, supports_transparency, content_type) return "webp", compression.Compressed( "webp", cdata), client_options, image.get_width( ), image.get_height(), 0, 24 #fallback using Pillow: enc_pillow = get_codec("enc_pillow") if enc_pillow: if not WEBP_PILLOW: log.warn("Warning: using PIL fallback for webp") log.warn(" enc_webp=%s, stride=%s, pixel format=%s", enc_webp, stride, image.get_pixel_format()) for x in ("webp", "png"): if x in enc_pillow.get_encodings(): return enc_pillow.encode(x, image, quality, speed, supports_transparency) raise Exception( "BUG: cannot use 'webp' encoding and none of the PIL fallbacks are available!" )
def webp_encode(coding, image, rgb_formats, supports_transparency, quality, speed, options): pixel_format = image.get_pixel_format() #log("rgb_encode%s pixel_format=%s, rgb_formats=%s", (coding, image, rgb_formats, supports_transparency, speed, rgb_zlib, rgb_lz4), pixel_format, rgb_formats) if pixel_format not in rgb_formats: if not rgb_reformat(image, rgb_formats, supports_transparency): raise Exception( "cannot find compatible rgb format to use for %s! (supported: %s)" % (pixel_format, rgb_formats)) #get the new format: pixel_format = image.get_pixel_format() stride = image.get_rowstride() enc_webp = get_codec("enc_webp") #log("webp_encode%s stride=%s, enc_webp=%s", (coding, image, rgb_formats, supports_transparency, quality, speed, options), stride, enc_webp) if enc_webp and stride > 0 and stride % 4 == 0 and image.get_pixel_format( ) in ("BGRA", "BGRX", "RGBA", "RGBX"): #prefer Cython module: alpha = supports_transparency and image.get_pixel_format().find( "A") >= 0 w = image.get_width() h = image.get_height() if quality == 100: #webp lossless is unbearibly slow for only marginal compression improvements, #so force max speed: speed = 100 else: #normalize speed for webp: avoid low speeds! speed = int(sqrt(speed) * 10) speed = max(0, min(100, speed)) pixels = image.get_pixels() assert pixels, "failed to get pixels from %s" % image cdata = enc_webp.compress(pixels, w, h, stride=stride / 4, quality=quality, speed=speed, has_alpha=alpha) client_options = {"speed": speed, "rgb_format": pixel_format} if quality >= 0 and quality <= 100: client_options["quality"] = quality if alpha: client_options["has_alpha"] = True return "webp", compression.Compressed( "webp", cdata), client_options, image.get_width( ), image.get_height(), 0, 24 #fallback to PIL enc_pillow = get_codec("enc_pillow") if enc_pillow: log( "using PIL fallback for webp: enc_webp=%s, stride=%s, pixel format=%s", enc_webp, stride, image.get_pixel_format()) for x in ("webp", "png"): if x in enc_pillow.get_encodings(): return enc_pillow.encode(x, image, quality, speed, supports_transparency) raise Exception( "BUG: cannot use 'webp' encoding and none of the PIL fallbacks are available!" )
def init_video_encoder_option(self, encoder_name): encoder_module = get_codec(encoder_name) log("init_video_encoder_option(%s)", encoder_name) log(" module=%s", encoder_module) if not encoder_module: log(" video encoder '%s' could not be loaded:", encoder_name) log(" %s", get_codec_error(encoder_name)) return encoder_type = encoder_module.get_type() try: encoder_module.init_module() self._cleanup_modules.append(encoder_module) except Exception as e: log(" exception in %s module %s initialization %s: %s", encoder_type, encoder_module.__name__, encoder_module.init_module, e, exc_info=True) raise encodings = encoder_module.get_encodings() log(" %s encodings=%s", encoder_type, csv(encodings)) for encoding in encodings: colorspaces = encoder_module.get_input_colorspaces(encoding) log(" %s input colorspaces for %s: %s", encoder_type, encoding, csv(colorspaces)) for colorspace in colorspaces: spec = encoder_module.get_spec(encoding, colorspace) self.add_encoder_spec(encoding, colorspace, spec)
def init_csc_option(self, csc_name): csc_module = get_codec(csc_name) log("init_csc_option(%s)", csc_name) log(" module=%s", csc_module) if csc_module is None: log(" csc module %s could not be loaded:", csc_name) log(" %s", get_codec_error(csc_name)) return csc_type = csc_module.get_type() try: csc_module.init_module() self._cleanup_modules.append(csc_module) except Exception as e: log("exception in %s module initialization %s: %s", csc_type, csc_module.init_module, e, exc_info=True) log.warn("Warning: cannot use %s module %s: %s", csc_type, csc_module, e) return in_cscs = csc_module.get_input_colorspaces() for in_csc in in_cscs: out_cscs = csc_module.get_output_colorspaces(in_csc) log("%s output colorspaces for %s: %s", csc_module.get_type(), in_csc, csv(out_cscs)) for out_csc in out_cscs: spec = csc_module.get_spec(in_csc, out_csc) self.add_csc_spec(in_csc, out_csc, spec)
def do_get_core_encodings(self): """ This method returns the actual encodings supported. ie: ["rgb24", "vp8", "webp", "png", "png/L", "png/P", "jpeg", "jpeg2000", "h264", "vpx"] It is often overriden in the actual client class implementations, where extra encodings can be added (generally just 'rgb32' for transparency), or removed if the toolkit implementation class is more limited. """ #we always support rgb: core_encodings = ["rgb24", "rgb32"] for codec in ("dec_pillow", "dec_webp"): if has_codec(codec): c = get_codec(codec) for e in c.get_encodings(): if e not in core_encodings: core_encodings.append(e) #we enable all the video decoders we know about, #what will actually get used by the server will still depend on the csc modes supported video_decodings = getVideoHelper().get_decodings() log("video_decodings=%s", video_decodings) for encoding in video_decodings: if encoding not in core_encodings: core_encodings.append(encoding) #remove duplicates and use prefered encoding order: core_encodings = [ x for x in PREFERED_ENCODING_ORDER if x in set(core_encodings) and x in self.allowed_encodings ] return core_encodings
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 if img.mode in ("P", "L"): transparency = options.get("transparency", -1) if transparency >= 0: img = img.convert("RGBA") else: 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_webp(self, img_data, x, y, width, height, options, callbacks): dec_webp = get_codec("dec_webp") if dec_webp: return self.paint_webp_using_cwebp(img_data, x, y, width, height, options, callbacks) return self.paint_webp_using_webm(img_data, x, y, width, height, options, callbacks)
def test_all_codecs_found(self): from xpra.codecs import loader #the self tests would swallow the exceptions and produce a warning: loader.RUN_SELF_TESTS = False loader.load_codecs() #test them all: for codec_name in loader.ALL_CODECS: codec = loader.get_codec(codec_name) if not codec: continue try: #try to suspend error logging for full tests, #as those may cause errors log = getattr(codec, "log", None) if SUSPEND_CODEC_ERROR_LOGGING and log: import logging log.logger.setLevel(logging.CRITICAL) init_module = getattr(codec, "init_module", None) #print("%s.init_module=%s" % (codec, init_module)) if init_module: try: init_module() except Exception as e: print("cannot initialize %s: %s" % (codec, e)) print(" test skipped") continue #print("found %s: %s" % (codec_name, codec)) selftest = getattr(codec, "selftest", None) #print("selftest(%s)=%s" % (codec_name, selftest)) if selftest: selftest(True) finally: if log: log.logger.setLevel(logging.DEBUG)
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 if img.mode in ("P", "L"): transparency = options.get("transparency", -1) if transparency>=0: img = img.convert("RGBA") else: 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 load_csc_options(): global CSC_OPTIONS if CSC_OPTIONS is None: CSC_OPTIONS = {} opts = [x.strip() for x in XPRA_CLIENT_CSC.split(",")] log("load_csc_options() module options=%s", opts) for opt in opts: csc_module = get_codec("csc_%s" % opt) if not csc_module: log.warn("csc module %s not found", opt) continue log("csc_module(%s)=%s", opt, csc_module) try: in_cscs = csc_module.get_input_colorspaces() log("input colorspaces(%s)=%s", csc_module, in_cscs) for in_csc in in_cscs: in_opts = CSC_OPTIONS.setdefault(in_csc, {}) out_cscs = csc_module.get_output_colorspaces(in_csc) log("output colorspaces(%s, %s)=%s", csc_module, in_csc, out_cscs) for out_csc in out_cscs: spec = csc_module.get_spec(in_csc, out_csc) specs = in_opts.setdefault(out_csc, []) specs.append(spec) log("specs(%s, %s)=%s", in_csc, out_csc, specs) except: log.warn("failed to load csc module %s", csc_module, exc_info=True)
def init_video_decoder_option(self, decoder_name): decoder_module = get_codec(decoder_name) log("init_video_decoder_option(%s)", decoder_name) log(" module=%s", decoder_module) if not decoder_module: log(" video decoder %s could not be loaded:", decoder_name) log(" %s", get_codec_error(decoder_name)) return decoder_type = decoder_module.get_type() try: decoder_module.init_module() self._cleanup_modules.append(decoder_module) except Exception as e: log("exception in %s module initialization %s: %s", decoder_type, decoder_module.init_module, e, exc_info=True) log.warn("Warning: cannot use %s module %s: %s", decoder_type, decoder_module, e, exc_info=True) return encodings = decoder_module.get_encodings() log(" %s encodings=%s", decoder_type, csv(encodings)) for encoding in encodings: colorspaces = decoder_module.get_input_colorspaces(encoding) log(" %s input colorspaces for %s: %s", decoder_type, encoding, csv(colorspaces)) for colorspace in colorspaces: output_colorspace = decoder_module.get_output_colorspace(encoding, colorspace) log(" %s output colorspace for %s/%s: %s", decoder_type, encoding, colorspace, output_colorspace) try: assert decoder_module.Decoder self.add_decoder(encoding, colorspace, decoder_name, decoder_module) except Exception as e: log.warn("failed to add decoder %s: %s", decoder_module, e) del e
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 init_video_decoder_option(self, decoder_name): decoder_module = get_codec(decoder_name) log("init_video_decoder_option(%s)", decoder_name) log(" module=%s", decoder_module) if not decoder_module: log(" video decoder %s could not be loaded:", decoder_name) log(" %s", get_codec_error(decoder_name)) return decoder_type = decoder_module.get_type() encodings = decoder_module.get_encodings() log(" %s encodings=%s", decoder_type, csv(encodings)) for encoding in encodings: colorspaces = decoder_module.get_input_colorspaces(encoding) log(" %s input colorspaces for %s: %s", decoder_type, encoding, csv(colorspaces)) for colorspace in colorspaces: output_colorspace = decoder_module.get_output_colorspace( encoding, colorspace) log(" %s output colorspace for %5s/%7s: %s", decoder_type, encoding, colorspace, output_colorspace) try: assert decoder_module.Decoder self.add_decoder(encoding, colorspace, decoder_name, decoder_module) except Exception as e: log.warn("failed to add decoder %s: %s", decoder_module, e) del e
def init_video_decoder_option(self, decoder_name): decoder_module = get_codec(decoder_name) log("init_video_decoder_option(%s)", decoder_name) log(" module=%s", decoder_module) if not decoder_module: log(" video decoder %s could not be loaded:", decoder_name) log(" %s", get_codec_error(decoder_name)) return decoder_type = decoder_module.get_type() try: decoder_module.init_module() self._cleanup_modules.append(decoder_module) except Exception as e: log("exception in %s module initialization %s: %s", decoder_type, decoder_module.init_module, e, exc_info=True) log.warn("Warning: cannot use %s module %s: %s", decoder_type, decoder_module, e, exc_info=True) return encodings = decoder_module.get_encodings() log(" %s encodings=%s", decoder_type, csv(encodings)) for encoding in encodings: colorspaces = decoder_module.get_input_colorspaces(encoding) log(" %s input colorspaces for %s: %s", decoder_type, encoding, csv(colorspaces)) for colorspace in colorspaces: output_colorspace = decoder_module.get_output_colorspace(encoding, colorspace) log(" %s output colorspace for %s/%s: %s", decoder_type, encoding, colorspace, output_colorspace) try: assert decoder_module.Decoder self.add_decoder(encoding, colorspace, decoder_name, decoder_module) except Exception as e: log.warn("failed to add decoder %s: %s", decoder_module, e)
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("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 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 rgb_reformat(image, rgb_formats, supports_transparency): """ convert the RGB pixel data into a format supported by the client """ #need to convert to a supported format! PIL = get_codec("PIL") pixel_format = image.get_pixel_format() pixels = image.get_pixels() assert pixels, "failed to get pixels from %s" % image if not PIL or pixel_format == "r210": #try to fallback to argb module #(required for r210 which is not handled by PIL directly) log("rgb_reformat: using argb_swap for %s", image) return argb_swap(image, rgb_formats, supports_transparency) if supports_transparency: modes = PIL_conv.get(pixel_format) else: modes = PIL_conv_noalpha.get(pixel_format) assert modes, "no PIL conversion from %s" % (pixel_format) target_rgb = [(im, om) for (im, om) in modes if om in rgb_formats] if len(target_rgb) == 0: log("rgb_reformat: no matching target modes for converting %s to %s", image, rgb_formats) #try argb module: if argb_swap(image, rgb_formats, supports_transparency): return True warning_key = "rgb_reformat(%s, %s, %s)" % (pixel_format, rgb_formats, supports_transparency) warn_encoding_once( warning_key, "cannot convert %s to one of: %s" % (pixel_format, rgb_formats)) return False input_format, target_format = target_rgb[0] start = time.time() w = image.get_width() h = image.get_height() #PIL cannot use the memoryview directly: if _memoryview and isinstance(pixels, _memoryview): pixels = pixels.tobytes() log("rgb_reformat: converting %s from %s to %s using PIL", image, input_format, target_format) img = PIL.Image.frombuffer(target_format, (w, h), pixels, "raw", input_format, image.get_rowstride()) rowstride = w * len( target_format) #number of characters is number of bytes per pixel! #use tobytes() if present, fallback to tostring(): data_fn = getattr(img, "tobytes", getattr(img, "tostring", None)) data = data_fn("raw", target_format) assert len( data) == rowstride * h, "expected %s bytes in %s format but got %s" % ( rowstride * h, len(data)) image.set_pixels(data) image.set_rowstride(rowstride) image.set_pixel_format(target_format) end = time.time() log( "rgb_reformat(%s, %s, %s) converted from %s (%s bytes) to %s (%s bytes) in %.1fms, rowstride=%s", image, rgb_formats, supports_transparency, pixel_format, len(pixels), target_format, len(data), (end - start) * 1000.0, rowstride) return True
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 get_DEFAULT_CSC_MODULES(): """ returns all the csc modules installed """ csc = [] for x in tuple(ALL_CSC_MODULE_OPTIONS): mod = get_csc_module_name(x) c = get_codec(mod) if c: csc.append(x) return csc
def get_DEFAULT_VIDEO_ENCODERS(): """ returns all the video encoders installed """ encoders = [] for x in list(ALL_VIDEO_ENCODER_OPTIONS): mod = get_encoder_module_name(x) c = get_codec(mod) if c: encoders.append(x) return encoders
def init_encodings(self): load_codecs(decoders=False) encs, core_encs = [], [] def add_encodings(encodings): for ce in encodings: e = {"rgb32" : "rgb", "rgb24" : "rgb"}.get(ce, ce) if self.allowed_encodings is not None and e not in self.allowed_encodings: #not in whitelist (if it exists) continue if e not in encs: encs.append(e) if ce not in core_encs: core_encs.append(ce) add_encodings(["rgb24", "rgb32"]) #video encoders (empty when first called - see threaded_init) ve = getVideoHelper().get_encodings() log("init_encodings() adding video encodings: %s", ve) add_encodings(ve) #ie: ["vp8", "h264"] #Pithon Imaging Libary: enc_pillow = get_codec("enc_pillow") if enc_pillow: pil_encs = enc_pillow.get_encodings() add_encodings(x for x in pil_encs if x!="webp") #Note: webp will only be enabled if we have a Python-PIL fallback #(either "webp" or "png") if has_codec("enc_webp") and ("webp" in pil_encs or "png" in pil_encs): add_encodings(["webp"]) if "webp" not in self.lossless_mode_encodings: self.lossless_mode_encodings.append("webp") #look for video encodings with lossless mode: for e in ve: for colorspace,especs in getVideoHelper().get_encoder_specs(e).items(): for espec in especs: if espec.has_lossless_mode: if e not in self.lossless_mode_encodings: log("found lossless mode for encoding %s with %s and colorspace %s", e, espec, colorspace) self.lossless_mode_encodings.append(e) break #now update the variables: self.encodings = encs self.core_encodings = core_encs self.lossless_encodings = [x for x in self.core_encodings if (x.startswith("png") or x.startswith("rgb") or x=="webp")] log("allowed encodings=%s, encodings=%s, core encodings=%s, lossless encodings=%s", self.allowed_encodings, encs, core_encs, self.lossless_encodings) pref = [x for x in PREFERED_ENCODING_ORDER if x in self.encodings] if pref: self.default_encoding = pref[0] else: self.default_encoding = None #default encoding: if not self.encoding or str(self.encoding).lower() in ("auto", "none"): self.default_encoding = None elif self.encoding in self.encodings: self.default_encoding = self.encoding else: log.warn("ignored invalid default encoding option: %s", self.encoding)
def get_DEFAULT_VIDEO_DECODERS(): """ returns all the video decoders installed """ decoders = [] for x in tuple(ALL_VIDEO_DECODER_OPTIONS): mod = get_decoder_module_name(x) c = get_codec(mod) if c: decoders.append(x) return decoders
def get_DEFAULT_CSC_MODULES(): """ returns all the csc modules installed """ csc = [] for x in list(ALL_CSC_MODULE_OPTIONS): mod = get_csc_module_name(x) c = get_codec(mod) if c: csc.append(x) return csc
def rgb_reformat(image, rgb_formats, supports_transparency): """ convert the RGB pixel data into a format supported by the client """ # need to convert to a supported format! PIL = get_codec("PIL") pixel_format = image.get_pixel_format() pixels = image.get_pixels() assert pixels, "failed to get pixels from %s" % image if not PIL or pixel_format == "r210": # try to fallback to argb module # (required for r210 which is not handled by PIL directly) log("rgb_reformat: using argb_swap for %s", image) return argb_swap(image, rgb_formats, supports_transparency) if supports_transparency: modes = PIL_conv.get(pixel_format) else: modes = PIL_conv_noalpha.get(pixel_format) assert modes, "no PIL conversion from %s" % (pixel_format) target_rgb = [(im, om) for (im, om) in modes if om in rgb_formats] if len(target_rgb) == 0: log("rgb_reformat: no matching target modes for converting %s to %s", image, rgb_formats) # try argb module: if argb_swap(image, rgb_formats, supports_transparency): return True warning_key = "rgb_reformat(%s, %s, %s)" % (pixel_format, rgb_formats, supports_transparency) warn_encoding_once(warning_key, "cannot convert %s to one of: %s" % (pixel_format, rgb_formats)) return False input_format, target_format = target_rgb[0] start = time.time() w = image.get_width() h = image.get_height() # PIL cannot use the memoryview directly: if isinstance(pixels, memoryview): pixels = pixels.tobytes() log("rgb_reformat: converting %s from %s to %s using PIL", image, input_format, target_format) img = PIL.Image.frombuffer(target_format, (w, h), pixels, "raw", input_format, image.get_rowstride()) rowstride = w * len(target_format) # number of characters is number of bytes per pixel! data = img.tobytes("raw", target_format) assert len(data) == rowstride * h, "expected %s bytes in %s format but got %s" % (rowstride * h, len(data)) image.set_pixels(data) image.set_rowstride(rowstride) image.set_pixel_format(target_format) end = time.time() log( "rgb_reformat(%s, %s, %s) converted from %s (%s bytes) to %s (%s bytes) in %.1fms, rowstride=%s", image, rgb_formats, supports_transparency, pixel_format, len(pixels), target_format, len(data), (end - start) * 1000.0, rowstride, ) return True
def webp_encode(coding, image, rgb_formats, supports_transparency, quality, speed, options): pixel_format = image.get_pixel_format() #log("rgb_encode%s pixel_format=%s, rgb_formats=%s", (coding, image, rgb_formats, supports_transparency, speed, rgb_zlib, rgb_lz4), pixel_format, rgb_formats) if pixel_format not in rgb_formats: if not rgb_reformat(image, rgb_formats, supports_transparency): raise Exception("cannot find compatible rgb format to use for %s! (supported: %s)" % (pixel_format, rgb_formats)) #get the new format: pixel_format = image.get_pixel_format() stride = image.get_rowstride() enc_webp = get_codec("enc_webp") #log("webp_encode%s stride=%s, enc_webp=%s", (coding, image, rgb_formats, supports_transparency, quality, speed, options), stride, enc_webp) if enc_webp and stride>0 and stride%4==0 and image.get_pixel_format() in ("BGRA", "BGRX", "RGBA", "RGBX"): #prefer Cython module: alpha = supports_transparency and image.get_pixel_format().find("A")>=0 w = image.get_width() h = image.get_height() if quality==100: #webp lossless is unbearibly slow for only marginal compression improvements, #so force max speed: speed = 100 else: #normalize speed for webp: avoid low speeds! speed = int(sqrt(speed) * 10) speed = max(0, min(100, speed)) pixels = image.get_pixels() assert pixels, "failed to get pixels from %s" % image cdata = enc_webp.compress(pixels, w, h, stride=stride/4, quality=quality, speed=speed, has_alpha=alpha) client_options = {"speed" : speed, "rgb_format" : pixel_format} if quality>=0 and quality<=100: client_options["quality"] = quality if alpha: client_options["has_alpha"] = True return "webp", compression.Compressed("webp", cdata), client_options, image.get_width(), image.get_height(), 0, 24 #fallback to PIL enc_pillow = get_codec("enc_pillow") if enc_pillow: log("using PIL fallback for webp: enc_webp=%s, stride=%s, pixel format=%s", enc_webp, stride, image.get_pixel_format()) for x in ("webp", "png"): if x in enc_pillow.get_encodings(): return enc_pillow.encode(x, image, quality, speed, supports_transparency) raise Exception("BUG: cannot use 'webp' encoding and none of the PIL fallbacks are available!")
def init_csc_option(self, csc_name): csc_module = get_codec(csc_name) debug("init_csc_option(%s) module=%s", csc_name, csc_module) if csc_module is None: return csc_type = csc_module.get_type() try: csc_module.init_module() except Exception, e: log.warn("cannot use %s module %s: %s", csc_type, csc_module, e, exc_info=True) return
def get_DEFAULT_VIDEO_ENCODERS(): """ returns all the video encoders installed """ encoders = [] for x in tuple(ALL_VIDEO_ENCODER_OPTIONS): mods = get_encoder_module_names(x) for mod in mods: c = get_codec(mod) if c: encoders.append(x) break return encoders
def init_video_encoder_option(self, encoder_name): encoder_module = get_codec(encoder_name) debug("init_video_encoder_option(%s) module=%s", encoder_name, encoder_module) if not encoder_module: return encoder_type = encoder_module.get_type() try: encoder_module.init_module() except Exception, e: log.warn("cannot use %s module %s: %s", encoder_type, encoder_module, e, exc_info=True) return
def bgr_to_rgb(self, img_data, width, height, rowstride, rgb_format, target_format): if not rgb_format.startswith("BGR"): return img_data, rowstride from xpra.codecs.loader import get_codec #use an rgb format name that PIL will recognize: in_format = rgb_format.replace("X", "A") PIL = get_codec("PIL") img = PIL.Image.frombuffer(target_format, (width, height), img_data, "raw", in_format, rowstride) img_data = img.tobytes("raw", target_format) log.warn("%s converted to %s", rgb_format, target_format) return img_data, width*len(target_format)
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.Image, "PIL.Image 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) rgb_format = img.mode if rgb_format == "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) elif rgb_format == "RGBA": rowstride = width * 4 img_data = self.process_delta(raw_data, width, height, rowstride, options) else: raise Exception("invalid image mode: %s" % img.mode) paint_options["rgb_format"] = rgb_format self.idle_add(self.do_paint_rgb, rgb_format, img_data, x, y, width, height, rowstride, paint_options, callbacks) return False
def load_video_decoders(): global VIDEO_DECODERS if VIDEO_DECODERS is None: VIDEO_DECODERS = {} for codec in ("vp8", "vp9", "h264"): #prefer native vpx ahead of avcodec: for module in ("dec_vpx", "dec_avcodec", "dec_avcodec2"): decoder = get_codec(module) if decoder and (codec in decoder.get_encodings()): VIDEO_DECODERS[codec] = module break log("video decoders: %s", VIDEO_DECODERS)
def init_csc_option(self, csc_name): csc_module = get_codec(csc_name) log("init_csc_option(%s) module=%s", csc_name, csc_module) if csc_module is None: log.warn("csc module %s could not be loaded: %s", csc_name, get_codec_error(csc_name)) return csc_type = csc_module.get_type() try: csc_module.init_module() self._cleanup_modules.append(csc_module) except Exception, e: log.warn("cannot use %s module %s: %s", csc_type, csc_module, e) return
def init_video_decoder_option(self, decoder_name): decoder_module = get_codec(decoder_name) log("init_video_decoder_option(%s) module=%s", decoder_name, decoder_module) if not decoder_module: log.warn("video decoder %s could not be loaded: %s", decoder_name, get_codec_error(decoder_name)) return encoder_type = decoder_module.get_type() try: decoder_module.init_module() self._cleanup_modules.append(decoder_module) except Exception, e: log.warn("cannot use %s module %s: %s", encoder_type, decoder_module, e, exc_info=True) return
def paint_webp_using_cwebp(self, img_data, x, y, width, height, options, callbacks): dec_webp = get_codec("dec_webp") has_alpha = options.get("has_alpha", False) buffer_wrapper, width, height, stride, has_alpha, rgb_format = dec_webp.decompress(img_data, has_alpha) options["rgb_format"] = rgb_format def free_buffer(*args): buffer_wrapper.free() callbacks.append(free_buffer) data = buffer_wrapper.get_pixels() if has_alpha: return self.paint_rgb32(data, x, y, width, height, stride, options, callbacks) else: return self.paint_rgb24(data, x, y, width, height, stride, options, callbacks)
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 paint_webp(self, img_data, x, y, width, height, options, callbacks): dec_webp = get_codec("dec_webp") if not dec_webp or WEBP_PILLOW: #if webp is enabled, then Pillow should be able to take care of it: return self.paint_image("webp", img_data, x, y, width, height, options, callbacks) has_alpha = options.get("has_alpha", False) buffer_wrapper, width, height, stride, has_alpha, rgb_format = dec_webp.decompress(img_data, has_alpha, options.get("rgb_format")) #replace with the actual rgb format we get from the decoder: options["rgb_format"] = rgb_format def free_buffer(*args): buffer_wrapper.free() callbacks.append(free_buffer) data = buffer_wrapper.get_pixels() if len(rgb_format)==4: return self.paint_rgb32(data, x, y, width, height, stride, options, callbacks) else: return self.paint_rgb24(data, x, y, width, height, stride, options, callbacks)
def paint_webp(self, img_data, x, y, width, height, options, callbacks): """ can be called from any thread """ dec_webp = get_codec("dec_webp") assert dec_webp is not None, "webp decoder not found" if options.get("has_alpha", False): decode = dec_webp.DecodeRGBA rowstride = width*4 paint_rgb = self.do_paint_rgb32 else: decode = dec_webp.DecodeRGB rowstride = width*3 paint_rgb = self.do_paint_rgb24 log("paint_webp(%s) using decode=%s, paint=%s", ("%s bytes" % len(img_data), x, y, width, height, options, callbacks), decode, paint_rgb) rgb_data = decode(img_data) pixels = str(rgb_data.bitmap) self.idle_add(paint_rgb, pixels, x, y, width, height, rowstride, options, callbacks) return False
def do_paint_rgb24(self, img_data, x, y, width, height, rowstride, options, callbacks): """ must be called from UI thread """ log("cairo.do_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 __init__(self, wid, window_alpha, idle_add): load_csc_options() load_video_decoders() self.wid = wid self.size = 0, 0 self.idle_add = idle_add self._alpha_enabled = window_alpha self._backing = None self._delta_pixel_data = [None for _ in range(DELTA_BUCKETS)] self._video_decoder = None self._csc_decoder = None self._decoder_lock = Lock() self._PIL_encodings = [] PIL = get_codec("dec_pillow") if PIL: self._PIL_encodings = PIL.get_encodings() self.draw_needs_refresh = True self.mmap = None self.mmap_enabled = False
def test_all_codecs_found(self): from xpra.codecs import loader #the self tests would swallow the exceptions and produce a warning: loader.RUN_SELF_TESTS = False loader.load_codecs() #test them all: for codec_name in loader.ALL_CODECS: codec = loader.get_codec(codec_name) if not codec: continue init_module = getattr(codec, "init_module", None) #print("%s.init_module=%s" % (codec, init_module)) if init_module: try: init_module() except Exception as e: print("cannot initialize %s: %s" % (codec, e)) print(" test skipped") continue #print("found %s: %s" % (codec_name, codec)) selftest = getattr(codec, "selftest", None) #print("selftest(%s)=%s" % (codec_name, selftest)) if selftest: selftest(True)
from xpra.codecs.argb.argb import bgra_to_rgb, bgra_to_rgba, argb_to_rgb, argb_to_rgba #@UnresolvedImport except Exception, e: log("cannot load argb module: %s", e) bgra_to_rgb, bgra_to_rgba, argb_to_rgb, argb_to_rgba = (None,)*4 from xpra.codecs.codec_constants import get_PIL_encodings from xpra.os_util import StringIOClass, memoryview_to_bytes from xpra.codecs.loader import get_codec, get_codec_version from xpra.os_util import builtins _memoryview = builtins.__dict__.get("memoryview") try: from xpra.net.mmap_pipe import mmap_write except: mmap_write = None #no mmap PIL = get_codec("PIL") PIL_VERSION = get_codec_version("PIL") PIL_can_optimize = PIL_VERSION>="2.2" #give warning message just once per key then ignore: encoding_warnings = set() def warn_encoding_once(key, message): global encoding_warnings if key not in encoding_warnings: log.warn("Warning: "+message) encoding_warnings.add(key) def webp_encode(coding, image, rgb_formats, supports_transparency, quality, speed, options): pixel_format = image.get_pixel_format() #log("rgb_encode%s pixel_format=%s, rgb_formats=%s", (coding, image, rgb_formats, supports_transparency, speed, rgb_zlib, rgb_lz4), pixel_format, rgb_formats)
def paint_with_video_decoder(self, decoder_name, coding, img_data, x, y, width, height, options, callbacks): assert x==0 and y==0 decoder_module = get_codec(decoder_name) assert decoder_module, "decoder module not found for %s" % decoder_name assert hasattr(decoder_module, "Decoder"), "decoder module %s does not have 'Decoder' factory function!" % decoder_module assert hasattr(decoder_module, "get_colorspaces"), "decoder module %s does not have 'get_colorspaces' function!" % decoder_module factory = getattr(decoder_module, "Decoder") get_colorspaces = getattr(decoder_module, "get_colorspaces") try: self._decoder_lock.acquire() if self._backing is None: log("window %s is already gone!", self.wid) fire_paint_callbacks(callbacks, False) return False enc_width, enc_height = options.get("scaled_size", (width, height)) input_colorspace = options.get("csc") if not input_colorspace: # Backwards compatibility with pre 0.10.x clients # We used to specify the colorspace as an avutil PixelFormat constant old_csc_fmt = options.get("csc_pixel_format") input_colorspace = get_colorspace_from_avutil_enum(old_csc_fmt) if input_colorspace is None: #completely broken and out of date clients (ie: v0.3.x): log.debug("csc was not specified and we cannot find a colorspace from csc_pixel_format=%s, assuming it is an old client and using YUV420P", old_csc_fmt) input_colorspace = "YUV420P" #do we need a prep step for decoders that cannot handle the input_colorspace directly? decoder_colorspaces = get_colorspaces() decoder_colorspace = input_colorspace if input_colorspace not in decoder_colorspaces: log("colorspace not supported by %s directly", decoder_module) assert input_colorspace in ("BGRA", "BGRX"), "colorspace %s cannot be handled directly or via a csc preparation step!" % input_colorspace decoder_colorspace = "YUV444P" if self._csc_prep: if self._csc_prep.get_src_format()!=input_colorspace: #this should not happen! log.warn("input colorspace has changed from %s to %s", self._csc_prep.get_src_format(), input_colorspace) self.do_clean_csc_prep() elif self._csc_prep.get_dst_format() not in decoder_colorspaces: #this should not happen! log.warn("csc prep colorspace %s is now invalid!?", self._csc_prep.get_dst_format()) self.do_clean_csc_prep() elif self._csc_prep.get_src_width()!=enc_width or self._csc_prep.get_src_height()!=enc_height: log("csc prep dimensions have changed from %s to %s", (self._csc_prep.get_src_width(), self._csc_prep.get_src_height()), (enc_width, enc_height)) self.do_clean_csc_prep() if self._csc_prep is None: csc_speed = 0 #always best quality self._csc_prep = self.make_csc(enc_width, enc_height, input_colorspace, width, height, [decoder_colorspace], csc_speed) log("csc preparation step: %s", self._csc_prep) elif self._csc_prep: #no longer needed? self.do_clean_csc_prep() if self._video_decoder: if self._video_decoder.get_encoding()!=coding: if DRAW_DEBUG: log.info("paint_with_video_decoder: encoding changed from %s to %s", self._video_decoder.get_encoding(), coding) self.do_clean_video_decoder() elif self._video_decoder.get_width()!=enc_width or self._video_decoder.get_height()!=enc_height: if DRAW_DEBUG: log.info("paint_with_video_decoder: window dimensions have changed from %s to %s", (self._video_decoder.get_width(), self._video_decoder.get_height()), (enc_width, enc_height)) self.do_clean_video_decoder() elif self._video_decoder.get_colorspace()!=decoder_colorspace: if DRAW_DEBUG: log.info("paint_with_video_decoder: colorspace changed from %s to %s", self._video_decoder.get_colorspace(), decoder_colorspace) self.do_clean_video_decoder() elif options.get("frame")==0: if DRAW_DEBUG: log.info("paint_with_video_decoder: first frame of new stream") self.do_clean_video_decoder() if self._video_decoder is None: if DRAW_DEBUG: log.info("paint_with_video_decoder: new %s(%s,%s,%s)", factory, width, height, decoder_colorspace) self._video_decoder = factory() self._video_decoder.init_context(coding, enc_width, enc_height, decoder_colorspace) if DRAW_DEBUG: log.info("paint_with_video_decoder: info=%s", self._video_decoder.get_info()) img = self._video_decoder.decompress_image(img_data, options) if not img: raise Exception("paint_with_video_decoder: wid=%s, %s decompression error on %s bytes of picture data for %sx%s pixels using %s, options=%s" % ( self.wid, coding, len(img_data), width, height, self._video_decoder, options)) self.do_video_paint(img, x, y, enc_width, enc_height, width, height, options, callbacks) finally: self._decoder_lock.release() if self._backing is None: self.close_decoder(True) return False
try: from xpra.codecs.argb.argb import bgra_to_rgb, bgra_to_rgba, argb_to_rgb, argb_to_rgba #@UnresolvedImport except Exception, e: log("cannot load argb module: %s", e) bgra_to_rgb, bgra_to_rgba, argb_to_rgb, argb_to_rgba = (None,)*4 from xpra.os_util import StringIOClass from xpra.codecs.loader import get_codec, get_codec_version, has_codec from xpra.os_util import builtins _memoryview = builtins.__dict__.get("memoryview") try: from xpra.net.mmap_pipe import mmap_write except: mmap_write = None #no mmap PIL = get_codec("PIL") PIL_VERSION = get_codec_version("PIL") enc_webm = get_codec("enc_webm") webp_handlers = get_codec("webm_bitmap_handlers") PIL_can_optimize = PIL_VERSION>="2.2" #give warning message just once per key then ignore: encoding_warnings = set() def warn_encoding_once(key, message): global encoding_warnings if key not in encoding_warnings: log.warn("Warning: "+message) encoding_warnings.add(key) def webp_encode(coding, image, supports_transparency, quality, speed, options):
def process_draw(self, packet): wid, x, y, width, height, encoding, pixels, _, rowstride, client_options = packet[1:11] #never modify mmap packets if encoding=="mmap": return True #we have a proxy video packet: rgb_format = client_options.get("rgb_format", "") enclog("proxy draw: client_options=%s", client_options) def send_updated(encoding, compressed_data, updated_client_options): #update the packet with actual encoding data used: packet[6] = encoding packet[7] = compressed_data packet[10] = updated_client_options enclog("returning %s bytes from %s, options=%s", len(compressed_data), len(pixels), updated_client_options) return (wid not in self.lost_windows) def passthrough(strip_alpha=True): enclog("proxy draw: %s passthrough (rowstride: %s vs %s, strip alpha=%s)", rgb_format, rowstride, client_options.get("rowstride", 0), strip_alpha) if strip_alpha: #passthrough as plain RGB: Xindex = rgb_format.upper().find("X") if Xindex>=0 and len(rgb_format)==4: #force clear alpha (which may be garbage): newdata = bytearray(pixels) for i in range(len(pixels)/4): newdata[i*4+Xindex] = chr(255) packet[9] = client_options.get("rowstride", 0) cdata = bytes(newdata) else: cdata = pixels new_client_options = {"rgb_format" : rgb_format} else: #preserve cdata = pixels new_client_options = client_options wrapped = Compressed("%s pixels" % encoding, cdata) #FIXME: we should not assume that rgb32 is supported here... #(we may have to convert to rgb24..) return send_updated("rgb32", wrapped, new_client_options) proxy_video = client_options.get("proxy", False) if PASSTHROUGH and (encoding in ("rgb32", "rgb24") or proxy_video): #we are dealing with rgb data, so we can pass it through: return passthrough(proxy_video) elif not self.video_encoder_types or not client_options or not proxy_video: #ensure we don't try to re-compress the pixel data in the network layer: #(re-add the "compressed" marker that gets lost when we re-assemble packets) packet[7] = Compressed("%s pixels" % encoding, packet[7]) return True #video encoding: find existing encoder ve = self.video_encoders.get(wid) if ve: if ve in self.lost_windows: #we cannot clean the video encoder here, there may be more frames queue up #"lost-window" in encode_loop will take care of it safely return False #we must verify that the encoder is still valid #and scrap it if not (ie: when window is resized) if ve.get_width()!=width or ve.get_height()!=height: enclog("closing existing video encoder %s because dimensions have changed from %sx%s to %sx%s", ve, ve.get_width(), ve.get_height(), width, height) ve.clean() ve = None elif ve.get_encoding()!=encoding: enclog("closing existing video encoder %s because encoding has changed from %s to %s", ve.get_encoding(), encoding) ve.clean() ve = None #scaling and depth are proxy-encoder attributes: scaling = client_options.get("scaling", (1, 1)) depth = client_options.get("depth", 24) rowstride = client_options.get("rowstride", rowstride) quality = client_options.get("quality", -1) speed = client_options.get("speed", -1) timestamp = client_options.get("timestamp") image = ImageWrapper(x, y, width, height, pixels, rgb_format, depth, rowstride, planes=ImageWrapper.PACKED) if timestamp is not None: image.set_timestamp(timestamp) #the encoder options are passed through: encoder_options = client_options.get("options", {}) if not ve: #make a new video encoder: spec = self._find_video_encoder(encoding, rgb_format) if spec is None: #no video encoder! enc_pillow = get_codec("enc_pillow") if not enc_pillow: from xpra.server.picture_encode import warn_encoding_once warn_encoding_once("no-video-no-PIL", "no video encoder found for rgb format %s, sending as plain RGB!" % rgb_format) return passthrough(True) enclog("no video encoder available: sending as jpeg") coding, compressed_data, client_options, _, _, _, _ = enc_pillow.encode("jpeg", image, quality, speed, False) return send_updated(coding, compressed_data, client_options) enclog("creating new video encoder %s for window %s", spec, wid) ve = spec.make_instance() #dst_formats is specified with first frame only: dst_formats = client_options.get("dst_formats") if dst_formats is not None: #save it in case we timeout the video encoder, #so we can instantiate it again, even from a frame no>1 self.video_encoders_dst_formats = dst_formats else: assert self.video_encoders_dst_formats, "BUG: dst_formats not specified for proxy and we don't have it either" dst_formats = self.video_encoders_dst_formats ve.init_context(width, height, rgb_format, dst_formats, encoding, quality, speed, scaling, {}) self.video_encoders[wid] = ve self.video_encoders_last_used_time[wid] = time.time() #just to make sure this is always set #actual video compression: enclog("proxy compression using %s with quality=%s, speed=%s", ve, quality, speed) data, out_options = ve.compress_image(image, quality, speed, encoder_options) #pass through some options if we don't have them from the encoder #(maybe we should also use the "pts" from the real server?) for k in ("timestamp", "rgb_format", "depth", "csc"): if k not in out_options and k in client_options: out_options[k] = client_options[k] self.video_encoders_last_used_time[wid] = time.time() return send_updated(ve.get_encoding(), Compressed(encoding, data), out_options)