def _emit_buffer(self, data, metadata={}): if self.stream_compressor and data: data = compressed_wrapper("sound", data, level=9, zlib=False, lz4=(self.stream_compressor=="lz4"), lzo=(self.stream_compressor=="lzo"), can_inline=True) #log("compressed using %s from %i bytes down to %i bytes", self.stream_compressor, len(odata), len(data)) metadata["compress"] = self.stream_compressor f = self.file if f: for x in self.pending_metadata: self.file.write(x) if data: self.file.write(data) self.file.flush() if self.state=="stopped": #don't bother return 0 if JITTER>0: #will actually emit the buffer after a random delay if self.jitter_queue.empty(): #queue was empty, schedule a timer to flush it from random import randint jitter = randint(1, JITTER) self.timeout_add(jitter, self.flush_jitter_queue) log("emit_buffer: will flush jitter queue in %ims", jitter) for x in self.pending_metadata: self.jitter_queue.put((x, {})) self.pending_metadata = [] self.jitter_queue.put((data, metadata)) return 0 log("emit_buffer data=%s, len=%i, metadata=%s", type(data), len(data), metadata) return self.do_emit_buffer(data, metadata)
def compressed_wrapper(self, datatype, data): # FIXME: ugly assumptions here, should pass by name! from xpra.net import compression zlib = "zlib" in self.server_compressors and compression.use_zlib lz4 = "lz4" in self.server_compressors and compression.use_lz4 lzo = "lzo" in self.server_compressors and compression.use_lzo if zlib or lz4 or lzo: return compression.compressed_wrapper(datatype, data, zlib=zlib, lz4=lz4, lzo=lzo, can_inline=False) # we can't compress, so at least avoid warnings in the protocol layer: return compression.Compressed("raw %s" % datatype, data, can_inline=True)
def _packet_recompress(self, packet, index, name): if len(packet) > index: data = packet[index] if len(data) < MIN_COMPRESS_SIZE: return #this is ugly and not generic! kw = dict((k, self.caps.boolget(k)) for k in ("zlib", "lz4")) packet[index] = compressed_wrapper(name, data, can_inline=False, **kw)
def compress_clipboard(self, packet): from xpra.net.compression import Compressible, compressed_wrapper #Note: this runs in the 'encode' thread! packet = list(packet) for i, item in enumerate(packet): if isinstance(item, Compressible): if self.brotli: packet[i] = compressed_wrapper(item.datatype, item.data, level=9, brotli=True, can_inline=False) else: packet[i] = self.compressed_wrapper(item.datatype, item.data) self.queue_packet(packet)
def compressed_wrapper(self, datatype, data, level=5): #FIXME: ugly assumptions here, should pass by name! zlib = "zlib" in self.server_compressors and compression.use_zlib lz4 = "lz4" in self.server_compressors and compression.use_lz4 lzo = "lzo" in self.server_compressors and compression.use_lzo if level>0 and len(data)>=256 and (zlib or lz4 or lzo): cw = compression.compressed_wrapper(datatype, data, level=level, zlib=zlib, lz4=lz4, lzo=lzo, can_inline=False) if len(cw)<len(data): #the compressed version is smaller, use it: return cw #we can't compress, so at least avoid warnings in the protocol layer: return compression.Compressed("raw %s" % datatype, data, can_inline=True)
def compressed_wrapper(self, datatype, data, min_saving=128): if self.zlib or self.lz4 or self.lzo: cw = compressed_wrapper(datatype, data, zlib=self.zlib, lz4=self.lz4, lzo=self.lzo, can_inline=False) if len(cw) + min_saving <= len(data): #the compressed version is smaller, use it: return cw #skip compressed version: fall through #we can't compress, so at least avoid warnings in the protocol layer: return Compressed(datatype, data, can_inline=True)
def compress_clipboard(): log( "compress_clipboard() compressing %s, server compressors=%s", compressible, self.server_compressors) from xpra.net import compression if "brotli" in self.server_compressors and compression.use_brotli: return compression.compressed_wrapper( compressible.datatype, compressible.data, level=9, brotli=True, can_inline=False) return self.compressed_wrapper(compressible.datatype, compressible.data)
def test_compressed_wrapper(self): try: compression.compressed_wrapper("test", b"abc") except compression.InvalidCompressionException: pass else: raise Exception("should not be able to use the wrapper without enabling a compressor") for x in ("lz4", "lzo", "brotli", "zlib", "none"): if not compression.use(x): continue kwargs = {x : True} for level in (0, 1, 5, 10): for data in ( b"0"*1024, b"0"*16, b"\0", memoryview(b"hello"), b"1"*1024*1024*16, ): v = compression.compressed_wrapper("test", data, level=level, **kwargs) assert v assert repr(v) assert compression.get_compression_type(v.level) #and back: try: d = compression.decompress_by_name(v.data, v.algorithm) assert d if x!="none": #we can't do none, #because it would be mistaken for "zlib" #(for historical reasons - 'zlib' uses level=0, and 'none' has no level) d = compression.decompress(v.data, v.level) assert d except Exception: print("error decompressing %s - generated with settings: %s" % (v, kwargs)) raise
def _packet_recompress(self, packet, index, name): if len(packet)>index: data = packet[index] if len(data)<512: packet[index] = str(data) return #FIXME: this is ugly and not generic! zlib = compression.use_zlib and self.caps.boolget("zlib", True) lz4 = compression.use_lz4 and self.caps.boolget("lz4", False) lzo = compression.use_lzo and self.caps.boolget("lzo", False) if zlib or lz4 or lzo: packet[index] = compressed_wrapper(name, data, zlib=zlib, lz4=lz4, lzo=lzo, can_inline=False) else: #prevent warnings about large uncompressed data packet[index] = Compressed("raw %s" % name, data, can_inline=True)
def compressed_wrapper(self, datatype, data): #FIXME: ugly assumptions here, should pass by name! from xpra.net import compression zlib = "zlib" in self.server_compressors and compression.use_zlib lz4 = "lz4" in self.server_compressors and compression.use_lz4 lzo = "lzo" in self.server_compressors and compression.use_lzo if zlib or lz4 or lzo: return compression.compressed_wrapper(datatype, data, zlib=zlib, lz4=lz4, lzo=lzo, can_inline=False) #we can't compress, so at least avoid warnings in the protocol layer: return compression.Compressed("raw %s" % datatype, data, can_inline=True)
def compressed_wrapper(self, datatype, data, level=5, **kwargs): if level>0 and len(data)>=256: kw = {} #brotli is not enabled by default as a generic compressor #but callers may choose to enable it via kwargs: for algo, defval in { "zlib" : True, "lz4" : True, "brotli" : False, }.items(): kw[algo] = algo in self.server_compressors and compression.use(algo) and kwargs.get(algo, defval) cw = compression.compressed_wrapper(datatype, data, level=level, can_inline=False, **kw) if len(cw)<len(data): #the compressed version is smaller, use it: return cw #we can't compress, so at least avoid warnings in the protocol layer: return compression.Compressed("raw %s" % datatype, data, can_inline=True)
def _emit_buffer(self, data, metadata={}): if self.stream_compressor and data: cdata = compressed_wrapper("sound", data, level=9, zlib=False, lz4=(self.stream_compressor == "lz4"), lzo=(self.stream_compressor == "lzo"), can_inline=True) if len(cdata) < len(data) * 90 // 100: log("compressed using %s from %i bytes down to %i bytes", self.stream_compressor, len(data), len(cdata)) metadata["compress"] = self.stream_compressor data = cdata else: log( "skipped inefficient %s stream compression: %i bytes down to %i bytes", self.stream_compressor, len(data), len(cdata)) f = self.file if f: for x in self.pending_metadata: self.file.write(x) if data: self.file.write(data) self.file.flush() if self.state == "stopped": #don't bother return 0 if JITTER > 0: #will actually emit the buffer after a random delay if self.jitter_queue.empty(): #queue was empty, schedule a timer to flush it from random import randint jitter = randint(1, JITTER) self.timeout_add(jitter, self.flush_jitter_queue) log("emit_buffer: will flush jitter queue in %ims", jitter) for x in self.pending_metadata: self.jitter_queue.put((x, {})) self.pending_metadata = [] self.jitter_queue.put((data, metadata)) return 0 log("emit_buffer data=%s, len=%i, metadata=%s", type(data), len(data), metadata) return self.do_emit_buffer(data, metadata)
def compressed_wrapper(self, datatype, data, level=5): if level > 0 and len(data) >= 256: #ugly assumptions here, should pass by name zlib = "zlib" in self.server_compressors lz4 = "lz4" in self.server_compressors lzo = "lzo" in self.server_compressors #never use brotli as a generic compressor #brotli = "brotli" in self.server_compressors and compression.use_brotli cw = compression.compressed_wrapper(datatype, data, level=level, zlib=zlib, lz4=lz4, lzo=lzo, brotli=False, none=True, can_inline=False) if len(cw) < len(data): #the compressed version is smaller, use it: return cw #we can't compress, so at least avoid warnings in the protocol layer: return compression.Compressed("raw %s" % datatype, data, can_inline=True)
def compressed_wrapper(self, datatype, data, **kwargs): #set compression flags based on self.zlib and self.lz4: kw = dict((k, getattr(self, k, False)) for k in ("zlib", "lz4")) kw.update(kwargs) return compressed_wrapper(datatype, data, can_inline=False, **kw)
def rgb_encode(coding, image, rgb_formats, supports_transparency, speed, rgb_zlib=True, rgb_lz4=True, rgb_lzo=False): 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: log("rgb_encode reformatting because %s not in %s", pixel_format, 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() #switch encoding if necessary: if len(pixel_format)==4 and coding=="rgb24": coding = "rgb32" elif len(pixel_format)==3 and coding=="rgb32": coding = "rgb24" #always tell client which pixel format we are sending: options = {"rgb_format" : pixel_format} #we may want to re-stride: image.restride() #compress here and return a wrapper so network code knows it is already zlib compressed: pixels = image.get_pixels() assert pixels, "failed to get pixels from %s" % image width = image.get_width() height = image.get_height() stride = image.get_rowstride() #compression stage: level = 0 algo = "not" if len(pixels)>=256: level = max(0, min(5, int(115-speed)/20)) if len(pixels)<1024: #fewer pixels, make it more likely we won't bother compressing: level = level // 2 if level>0: if rgb_lz4 and compression.use_lz4: cwrapper = compression.compressed_wrapper(coding, pixels, lz4=True) algo = "lz4" level = 1 elif rgb_lzo and compression.use_lzo: cwrapper = compression.compressed_wrapper(coding, pixels, lzo=True) algo = "lzo" level = 1 elif rgb_zlib and compression.use_zlib: cwrapper = compression.compressed_wrapper(coding, pixels, zlib=True, level=level) algo = "zlib" else: cwrapper = None if cwrapper is None or len(cwrapper)>=(len(pixels)-32): #no compression is enabled, or compressed is actually bigger! #(fall through to uncompressed) level = 0 else: #add compressed marker: options[algo] = level #remove network layer compression marker #so that this data will be decompressed by the decode thread client side: cwrapper.level = 0 if level==0: #can't pass a raw buffer to bencode / rencode, #and even if we could, the image containing those pixels may be freed by the time we get to the encoder cwrapper = compression.Compressed(coding, pixels_to_bytes(pixels), True) if pixel_format.upper().find("A")>=0 or pixel_format.upper().find("X")>=0: bpp = 32 else: bpp = 24 log("rgb_encode using level=%s, %s compressed %sx%s in %s/%s: %s bytes down to %s", level, algo, image.get_width(), image.get_height(), coding, pixel_format, len(pixels), len(cwrapper.data)) #wrap it using "Compressed" so the network layer receiving it #won't decompress it (leave it to the client's draw thread) return coding, cwrapper, options, width, height, stride, bpp
def rgb_encode(coding, image, rgb_formats, supports_transparency, speed, rgb_zlib=True, rgb_lz4=True, rgb_lzo=False): pixel_format = bytestostr(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: log( "rgb_encode reformatting because %s not in %s, supports_transparency=%s", pixel_format, rgb_formats, supports_transparency) 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 = bytestostr(image.get_pixel_format()) #switch encoding if necessary: if len(pixel_format) == 4: coding = "rgb32" elif len(pixel_format) == 3: coding = "rgb24" else: raise Exception("invalid pixel format %s" % pixel_format) #we may still want to re-stride: image.may_restride() #always tell client which pixel format we are sending: options = {"rgb_format": pixel_format} #compress here and return a wrapper so network code knows it is already zlib compressed: pixels = image.get_pixels() assert pixels, "failed to get pixels from %s" % image width = image.get_width() height = image.get_height() stride = image.get_rowstride() #compression stage: level = 0 algo = "not" l = len(pixels) if l >= 512 and speed < 100: if l >= 4096: #speed=99 -> level=1, speed=0 -> level=9 level = 1 + max(0, min(8, int(100 - speed) // 12)) else: #fewer pixels, make it more likely we won't bother compressing #and use a lower level (max=5) level = max(0, min(5, int(115 - speed) // 20)) if level > 0: cwrapper = compression.compressed_wrapper(coding, pixels, level=level, zlib=rgb_zlib, lz4=rgb_lz4, lzo=rgb_lzo, brotli=False, none=True) algo = cwrapper.algorithm if algo == "none" or len(cwrapper) >= (len(pixels) - 32): #no compression is enabled, or compressed is actually bigger! #(fall through to uncompressed) level = 0 else: #add compressed marker: options[algo] = level #remove network layer compression marker #so that this data will be decompressed by the decode thread client side: cwrapper.level = 0 if level == 0: #can't pass a raw buffer to bencode / rencode, #and even if we could, the image containing those pixels may be freed by the time we get to the encoder algo = "not" cwrapper = compression.Compressed(coding, pixels_to_bytes(pixels), True) if pixel_format.find("A") >= 0 or pixel_format.find("X") >= 0: bpp = 32 else: bpp = 24 log( "rgb_encode using level=%s for %5i bytes at %3i speed, %s compressed %4sx%-4s in %s/%s: %5s bytes down to %5s", level, l, speed, algo, image.get_width(), image.get_height(), coding, pixel_format, len(pixels), len(cwrapper.data)) #wrap it using "Compressed" so the network layer receiving it #won't decompress it (leave it to the client's draw thread) return coding, cwrapper, options, width, height, stride, bpp
def process_server_packet(self, proto, packet): packet_type = packet[0] log("process_server_packet: %s", packet_type) if packet_type==Protocol.CONNECTION_LOST: self.stop("server connection lost", proto) return elif packet_type=="disconnect": log("got disconnect from server: %s", packet[1]) if self.exit: self.server_protocol.close() else: self.stop("disconnect from server: %s" % packet[1]) elif packet_type=="hello": c = typedict(packet[1]) maxw, maxh = c.intpair("max_desktop_size", (4096, 4096)) proto.max_packet_size = maxw*maxh*4*4 caps = self.filter_server_caps(c) #add new encryption caps: if self.cipher: auth_caps = new_cipher_caps(self.client_protocol, self.cipher, self.encryption_key) caps.update(auth_caps) packet = ("hello", caps) elif packet_type=="info-response": #adds proxy info: #note: this is only seen by the client application #"xpra info" is a new connection, which talks to the proxy server... info = packet[1] info.update(self.get_proxy_info(proto)) elif packet_type=="lost-window": wid = packet[1] #mark it as lost so we can drop any current/pending frames self.lost_windows.add(wid) #queue it so it gets cleaned safely (for video encoders mostly): self.encode_queue.put(packet) #and fall through so tell the client immediately elif packet_type=="draw": #use encoder thread: self.encode_queue.put(packet) #which will queue the packet itself when done: return #we do want to reformat cursor packets... #as they will have been uncompressed by the network layer already: elif packet_type=="cursor": #packet = ["cursor", x, y, width, height, xhot, yhot, serial, pixels, name] #or: #packet = ["cursor", ""] if len(packet)>=9: pixels = packet[8] if len(pixels)<512: packet[8] = str(pixels) else: #FIXME: this is ugly and not generic! zlib = compression.use_zlib and self.caps.get("zlib", True) lz4 = compression.use_lz4 and self.caps.get("lz4", False) lzo = compression.use_lzo and self.caps.get("lzo", False) if zlib or lz4 or lzo: packet[8] = compressed_wrapper("cursor", pixels, zlib=zlib, lz4=lz4, lzo=lzo, can_inline=False) else: #prevent warnings about large uncompressed data packet[8] = Compressed("raw cursor", pixels, can_inline=True) self.queue_client_packet(packet)
def encode(coding: str, image, options: dict): 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 in ("BGRX", "BGRA", "RGBA"): rgb_formats = options.get("rgb_formats", ("BGRX", "BGRA", "RGBA")) elif pixel_format in ("RGB", "BGR"): rgb_formats = options.get("rgb_formats", ("RGB", "BGR")) else: raise Exception("unsupported pixel format %s" % pixel_format) supports_transparency = options.get("alpha", True) if pixel_format not in rgb_formats: log( "rgb_encode reformatting because %s not in %s, supports_transparency=%s", pixel_format, rgb_formats, supports_transparency) 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() #switch encoding if necessary: if len(pixel_format) == 4: coding = "rgb32" elif len(pixel_format) == 3: coding = "rgb24" else: raise Exception("invalid pixel format %s" % pixel_format) #we may still want to re-stride: image.may_restride() #always tell client which pixel format we are sending: client_options = {"rgb_format": pixel_format} #compress here and return a wrapper so network code knows it is already zlib compressed: pixels = image.get_pixels() assert pixels, "failed to get pixels from %s" % image width = image.get_width() height = image.get_height() stride = image.get_rowstride() speed = options.get("speed", 50) #compression stage: level = 0 algo = "not" l = len(pixels) lz4 = options.get("lz4", False) if l >= 512 and (lz4 or speed < 100): if l >= 4096: level = 1 + max(0, min(7, int(100 - speed) // 14)) else: #fewer pixels, make it more likely we won't bother compressing #and use a lower level (max=3) level = max(0, min(3, int(125 - speed) // 35)) if level > 0: zlib = options.get("zlib", False) can_inline = l <= 32768 cwrapper = compressed_wrapper(coding, pixels, level=level, zlib=zlib, lz4=lz4, brotli=False, none=False, can_inline=can_inline) if isinstance(cwrapper, LevelCompressed): #add compressed marker: client_options[cwrapper.algorithm] = cwrapper.level & 0xF #remove network layer compression marker #so that this data will be decompressed by the decode thread client side: cwrapper.level = 0 elif can_inline and isinstance(pixels, memoryview): assert isinstance(cwrapper, Compressed) assert cwrapper.data == pixels #compression either did not work or was not enabled #and memoryview pixel data cannot be handled by the packet encoders #so we convert it to bytes so it can still be inlined with the packet data: cwrapper.data = rgb_transform.pixels_to_bytes(pixels) else: #can't pass a raw buffer to bencode / rencode, #and even if we could, the image containing those pixels may be freed by the time we get to the encoder algo = "not" cwrapper = Compressed(coding, rgb_transform.pixels_to_bytes(pixels), True) if pixel_format.find("A") >= 0 or pixel_format.find("X") >= 0: bpp = 32 else: bpp = 24 log( "rgb_encode using level=%s for %5i bytes at %3i speed, %s compressed %4sx%-4s in %s/%s: %5s bytes down to %5s", level, l, speed, algo, width, height, coding, pixel_format, len(pixels), len(cwrapper.data)) #wrap it using "Compressed" so the network layer receiving it #won't decompress it (leave it to the client's draw thread) return coding, cwrapper, client_options, width, height, stride, bpp
def process_server_packet(self, proto, packet): packet_type = packet[0] log("process_server_packet: %s", packet_type) if packet_type == Protocol.CONNECTION_LOST: self.stop("server connection lost", proto) return elif packet_type == "disconnect": log("got disconnect from server: %s", packet[1]) if self.exit: self.server_protocol.close() else: self.stop("disconnect from server: %s" % packet[1]) elif packet_type == "hello": c = typedict(packet[1]) maxw, maxh = c.intpair("max_desktop_size", (4096, 4096)) proto.max_packet_size = maxw * maxh * 4 * 4 caps = self.filter_server_caps(c) #add new encryption caps: if self.cipher: auth_caps = new_cipher_caps(self.client_protocol, self.cipher, self.encryption_key) caps.update(auth_caps) packet = ("hello", caps) elif packet_type == "info-response": #adds proxy info: #note: this is only seen by the client application #"xpra info" is a new connection, which talks to the proxy server... info = packet[1] info.update(self.get_proxy_info(proto)) elif packet_type == "lost-window": wid = packet[1] #mark it as lost so we can drop any current/pending frames self.lost_windows.add(wid) #queue it so it gets cleaned safely (for video encoders mostly): self.encode_queue.put(packet) #and fall through so tell the client immediately elif packet_type == "draw": #use encoder thread: self.encode_queue.put(packet) #which will queue the packet itself when done: return #we do want to reformat cursor packets... #as they will have been uncompressed by the network layer already: elif packet_type == "cursor": #packet = ["cursor", x, y, width, height, xhot, yhot, serial, pixels, name] #or: #packet = ["cursor", ""] if len(packet) >= 9: pixels = packet[8] if len(pixels) < 512: packet[8] = str(pixels) else: #FIXME: this is ugly and not generic! zlib = compression.use_zlib and self.caps.get("zlib", True) lz4 = compression.use_lz4 and self.caps.get("lz4", False) lzo = compression.use_lzo and self.caps.get("lzo", False) if zlib or lz4 or lzo: packet[8] = compressed_wrapper("cursor", pixels, zlib=zlib, lz4=lz4, lzo=lzo, can_inline=False) else: #prevent warnings about large uncompressed data packet[8] = Compressed("raw cursor", pixels, can_inline=True) self.queue_client_packet(packet)
def rgb_encode(coding, image, rgb_formats, supports_transparency, speed, rgb_zlib=True, rgb_lz4=True, rgb_lzo=False, encoding_client_options=True, supports_rgb24zlib=True): 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, encoding_client_options, supports_rgb24zlib), 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() #switch encoding if necessary: if len(pixel_format) == 4 and coding == "rgb24": coding = "rgb32" elif len(pixel_format) == 3 and coding == "rgb32": coding = "rgb24" #always tell client which pixel format we are sending: options = {"rgb_format": pixel_format} #compress here and return a wrapper so network code knows it is already zlib compressed: pixels = image.get_pixels() #special case for when rowstride is so much bigger than the width #that we would end up sending large chunks of padding with each row of pixels #this happens with XShm pixel data (the default) stride = image.get_rowstride() width = image.get_width() rstride = roundup(width * len(pixel_format), 4) #a reasonable stride: rounded up to 4 height = image.get_height() if stride > 8 and rstride < stride: al = len(pixels) #current buffer size el = rstride * height #desirable size we could have if al - el > 1024 and el * 110 / 100 < al: #is it worth re-striding to save space? #we'll save at least 1KB and 10%, do it #Note: we could also change the pixel format whilst we're at it # and convert BGRX to RGB for example (assuming RGB is also supported by the client) rows = [] for y in range(height): rows.append( memoryview_to_bytes(pixels[stride * y:stride * y + rstride])) pixels = "".join(rows) log( "rgb_encode: %s pixels re-stride saving %i%% from %s (%s bytes) to %s (%s bytes)", pixel_format, 100 - 100 * el / al, stride, al, rstride, el) stride = rstride #compression #by default, wire=raw: raw_data = str(pixels) wire_data = raw_data level = 0 algo = "not" if len(pixels) >= 256 and (rgb_zlib and compression.use_zlib) or ( rgb_lz4 and compression.use_lz4) or (rgb_lzo and compression.use_lzo): level = max(0, min(5, int(115 - speed) / 20)) if len(pixels) < 1024: #fewer pixels, make it more likely we won't bother compressing: level = level / 2 if level > 0: if rgb_lz4 and compression.use_lz4: wire_data = compression.compressed_wrapper(coding, pixels, lz4=True) algo = "lz4" level = 1 elif rgb_lzo and compression.use_lzo: wire_data = compression.compressed_wrapper(coding, pixels, lzo=True) algo = "lzo" level = 1 else: assert rgb_zlib and compression.use_zlib wire_data = compression.compressed_wrapper(coding, pixels, zlib=True, level=level) algo = "zlib" raw_data = wire_data.data #log("%s/%s data compressed from %s bytes down to %s (%s%%) with lz4=%s", # coding, pixel_format, len(pixels), len(raw_data), int(100.0*len(raw_data)/len(pixels)), self.rgb_lz4) if len(raw_data) >= (len(pixels) - 32): #compressed is actually bigger! (use uncompressed) level = 0 wire_data = str(pixels) raw_data = wire_data else: #add compressed marker: options[algo] = level if pixel_format.upper().find("A") >= 0 or pixel_format.upper().find( "X") >= 0: bpp = 32 else: bpp = 24 log( "rgb_encode using level=%s, %s compressed %sx%s in %s/%s: %s bytes down to %s", level, algo, image.get_width(), image.get_height(), coding, pixel_format, len(pixels), len(raw_data)) if not encoding_client_options or not supports_rgb24zlib: return coding, wire_data, {}, width, height, stride, bpp #wrap it using "Compressed" so the network layer receiving it #won't decompress it (leave it to the client's draw thread) return coding, compression.Compressed( coding, raw_data, True), options, width, height, stride, bpp
def rgb_encode(coding, image, rgb_formats, supports_transparency, speed, rgb_zlib=True, rgb_lz4=True, rgb_lzo=False, encoding_client_options=True, supports_rgb24zlib=True): 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, encoding_client_options, supports_rgb24zlib), 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() #switch encoding if necessary: if len(pixel_format)==4 and coding=="rgb24": coding = "rgb32" elif len(pixel_format)==3 and coding=="rgb32": coding = "rgb24" #always tell client which pixel format we are sending: options = {"rgb_format" : pixel_format} #compress here and return a wrapper so network code knows it is already zlib compressed: pixels = image.get_pixels() #special case for when rowstride is so much bigger than the width #that we would end up sending large chunks of padding with each row of pixels #this happens with XShm pixel data (the default) stride = image.get_rowstride() width = image.get_width() rstride = roundup(width*len(pixel_format), 4) #a reasonable stride: rounded up to 4 height = image.get_height() if stride>8 and rstride<stride: al = len(pixels) #current buffer size el = rstride*height #desirable size we could have if al-el>1024 and el*110/100<al: #is it worth re-striding to save space? #we'll save at least 1KB and 10%, do it #Note: we could also change the pixel format whilst we're at it # and convert BGRX to RGB for example (assuming RGB is also supported by the client) rows = [] for y in range(height): rows.append(memoryview_to_bytes(pixels[stride*y:stride*y+rstride])) pixels = "".join(rows) log("rgb_encode: %s pixels re-stride saving %i%% from %s (%s bytes) to %s (%s bytes)", pixel_format, 100-100*el/al, stride, al, rstride, el) stride = rstride #compression #by default, wire=raw: raw_data = str(pixels) wire_data = raw_data level = 0 algo = "not" if len(pixels)>=256 and (rgb_zlib and compression.use_zlib) or (rgb_lz4 and compression.use_lz4) or (rgb_lzo and compression.use_lzo): level = max(0, min(5, int(115-speed)/20)) if len(pixels)<1024: #fewer pixels, make it more likely we won't bother compressing: level = level / 2 if level>0: if rgb_lz4 and compression.use_lz4: wire_data = compression.compressed_wrapper(coding, pixels, lz4=True) algo = "lz4" level = 1 elif rgb_lzo and compression.use_lzo: wire_data = compression.compressed_wrapper(coding, pixels, lzo=True) algo = "lzo" level = 1 else: assert rgb_zlib and compression.use_zlib wire_data = compression.compressed_wrapper(coding, pixels, zlib=True, level=level) algo = "zlib" raw_data = wire_data.data #log("%s/%s data compressed from %s bytes down to %s (%s%%) with lz4=%s", # coding, pixel_format, len(pixels), len(raw_data), int(100.0*len(raw_data)/len(pixels)), self.rgb_lz4) if len(raw_data)>=(len(pixels)-32): #compressed is actually bigger! (use uncompressed) level = 0 wire_data = str(pixels) raw_data = wire_data else: #add compressed marker: options[algo] = level if pixel_format.upper().find("A")>=0 or pixel_format.upper().find("X")>=0: bpp = 32 else: bpp = 24 log("rgb_encode using level=%s, %s compressed %sx%s in %s/%s: %s bytes down to %s", level, algo, image.get_width(), image.get_height(), coding, pixel_format, len(pixels), len(raw_data)) if not encoding_client_options or not supports_rgb24zlib: return coding, wire_data, {}, width, height, stride, bpp #wrap it using "Compressed" so the network layer receiving it #won't decompress it (leave it to the client's draw thread) return coding, compression.Compressed(coding, raw_data, True), options, width, height, stride, bpp
def rgb_encode(coding, image, rgb_formats, supports_transparency, speed, rgb_zlib=True, rgb_lz4=True, rgb_lzo=False): 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: log("rgb_encode reformatting because %s not in %s", pixel_format, 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() #switch encoding if necessary: if len(pixel_format) == 4 and coding == "rgb24": coding = "rgb32" elif len(pixel_format) == 3 and coding == "rgb32": coding = "rgb24" #always tell client which pixel format we are sending: options = {"rgb_format": pixel_format} #we may want to re-stride: image.may_restride() #compress here and return a wrapper so network code knows it is already zlib compressed: pixels = image.get_pixels() assert pixels, "failed to get pixels from %s" % image width = image.get_width() height = image.get_height() stride = image.get_rowstride() #compression stage: level = 0 algo = "not" if len(pixels) >= 256: level = max(0, min(5, int(115 - speed) / 20)) if len(pixels) < 1024: #fewer pixels, make it more likely we won't bother compressing: level = level // 2 if level > 0: if rgb_lz4 and compression.use_lz4: cwrapper = compression.compressed_wrapper(coding, pixels, lz4=True) algo = "lz4" level = 1 elif rgb_lzo and compression.use_lzo: cwrapper = compression.compressed_wrapper(coding, pixels, lzo=True) algo = "lzo" level = 1 elif rgb_zlib and compression.use_zlib: cwrapper = compression.compressed_wrapper(coding, pixels, zlib=True, level=level) algo = "zlib" else: cwrapper = None if cwrapper is None or len(cwrapper) >= (len(pixels) - 32): #no compression is enabled, or compressed is actually bigger! #(fall through to uncompressed) level = 0 else: #add compressed marker: options[algo] = level #remove network layer compression marker #so that this data will be decompressed by the decode thread client side: cwrapper.level = 0 if level == 0: #can't pass a raw buffer to bencode / rencode, #and even if we could, the image containing those pixels may be freed by the time we get to the encoder algo = "not" cwrapper = compression.Compressed(coding, pixels_to_bytes(pixels), True) if pixel_format.upper().find("A") >= 0 or pixel_format.upper().find( "X") >= 0: bpp = 32 else: bpp = 24 log( "rgb_encode using level=%s, %s compressed %sx%s in %s/%s: %s bytes down to %s", level, algo, image.get_width(), image.get_height(), coding, pixel_format, len(pixels), len(cwrapper.data)) #wrap it using "Compressed" so the network layer receiving it #won't decompress it (leave it to the client's draw thread) return coding, cwrapper, options, width, height, stride, bpp