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 send_webcam_frame(self): if not self.webcam_lock.acquire(False): return False log("send_webcam_frame() webcam_device=%s", self.webcam_device) try: assert self.webcam_device_no>=0, "device number is not set" assert self.webcam_device, "no webcam device to capture from" from xpra.codecs.pillow.encoder import get_encodings client_webcam_encodings = get_encodings() common_encodings = list(set(self.server_webcam_encodings).intersection(client_webcam_encodings)) log("common encodings (server=%s, client=%s): %s", csv(self.server_encodings), csv(client_webcam_encodings), csv(common_encodings)) if not common_encodings: log.error("Error: cannot send webcam image, no common formats") log.error(" the server supports: %s", csv(self.server_webcam_encodings)) log.error(" the client supports: %s", csv(client_webcam_encodings)) self.stop_sending_webcam() return False preferred_order = ["jpeg", "png", "png/L", "png/P", "webp"] formats = [x for x in preferred_order if x in common_encodings] + common_encodings encoding = formats[0] start = monotonic_time() import cv2 ret, frame = self.webcam_device.read() assert ret, "capture failed" assert frame.ndim==3, "invalid frame data" h, w, Bpp = frame.shape assert Bpp==3 and frame.size==w*h*Bpp rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) # @UndefinedVariable end = monotonic_time() log("webcam frame capture took %ims", (end-start)*1000) start = monotonic_time() from PIL import Image from io import BytesIO image = Image.fromarray(rgb) buf = BytesIO() image.save(buf, format=encoding) data = buf.getvalue() buf.close() end = monotonic_time() log("webcam frame compression to %s took %ims", encoding, (end-start)*1000) frame_no = self.webcam_frame_no self.webcam_frame_no += 1 self.send("webcam-frame", self.webcam_device_no, frame_no, encoding, w, h, compression.Compressed(encoding, data)) self.cancel_webcam_check_ack_timer() self.webcam_ack_check_timer = self.timeout_add(10*1000, self.webcam_check_acks) return True except Exception as e: log.error("webcam frame %i failed", self.webcam_frame_no, exc_info=True) log.error("Error sending webcam frame: %s", e) self.stop_sending_webcam() summary = "Webcam forwarding has failed" body = "The system encountered the following error:\n" + \ ("%s\n" % e) self.may_notify(XPRA_WEBCAM_NOTIFICATION_ID, summary, body, expire_timeout=10*1000, icon_name="webcam") return False finally: self.webcam_lock.release()
def 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, 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 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 webp_encode(image, rgb_formats, supports_transparency, quality, speed): pixel_format = image.get_pixel_format() #the native webp encoder only takes this pixel format as input: if pixel_format not in ("BGRA", "BGRX", "RGBX", "RGBA"): watnted_formats = [ x for x in rgb_formats if x in ("BGRA", "BGRX", "RGBA", "RGBX") ] if not rgb_reformat(image, watnted_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_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.compress(image, quality, speed, supports_transparency) 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 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 encode(coding, image, quality, speed, supports_transparency): pixel_format = image.get_pixel_format() w = image.get_width() h = image.get_height() rgb = { "XRGB" : "RGB", "BGRX" : "RGB", "RGBA" : "RGBA", "BGRA" : "RGBA", }.get(pixel_format, pixel_format) bpp = 32 #remove transparency if it cannot be handled: try: pixels = image.get_pixels() assert pixels, "failed to get pixels from %s" % image #PIL cannot use the memoryview directly: if type(pixels)!=_buffer: pixels = memoryview_to_bytes(pixels) #it is safe to use frombuffer() here since the convert() #calls below will not convert and modify the data in place #and we save the compressed data then discard the image im = PIL.Image.frombuffer(rgb, (w, h), pixels, "raw", pixel_format, image.get_rowstride()) if coding.startswith("png") and not supports_transparency and rgb=="RGBA": im = im.convert("RGB") rgb = "RGB" bpp = 24 except Exception: log.error("PIL_encode%s converting %s pixels from %s to %s failed", (w, h, coding, "%s bytes" % image.get_size(), pixel_format, image.get_rowstride()), type(pixels), pixel_format, rgb, exc_info=True) raise buf = BytesIOClass() client_options = {} #only optimize with Pillow>=2.2 and when speed is zero if coding in ("jpeg", "webp"): q = int(min(99, max(1, quality))) kwargs = im.info kwargs["quality"] = q client_options["quality"] = q if coding=="jpeg" and PIL_can_optimize and speed<70: #(optimizing jpeg is pretty cheap and worth doing) kwargs["optimize"] = True client_options["optimize"] = True im.save(buf, coding.upper(), **kwargs) else: assert coding in ("png", "png/P", "png/L"), "unsupported png encoding: %s" % coding if coding in ("png/L", "png/P") and supports_transparency and rgb=="RGBA": #grab alpha channel (the last one): #we use the last channel because we know it is RGBA, #otherwise we should do: alpha_index= image.getbands().index('A') alpha = im.split()[-1] #convert to simple on or off mask: #set all pixel values below 128 to 255, and the rest to 0 def mask_value(a): if a<=128: return 255 return 0 mask = PIL.Image.eval(alpha, mask_value) else: #no transparency mask = None if coding=="png/L": im = im.convert("L", palette=PIL.Image.ADAPTIVE, colors=255) bpp = 8 elif coding=="png/P": #I wanted to use the "better" adaptive method, #but this does NOT work (produces a black image instead): #im.convert("P", palette=Image.ADAPTIVE) im = im.convert("P", palette=PIL.Image.WEB, colors=255) bpp = 8 if mask: # paste the alpha mask to the color of index 255 im.paste(255, mask) kwargs = im.info if mask is not None: client_options["transparency"] = 255 kwargs["transparency"] = 255 if PIL_can_optimize and speed==0: #optimizing png is very rarely worth doing kwargs["optimize"] = True client_options["optimize"] = True #level can range from 0 to 9, but anything above 5 is way too slow for small gains: #76-100 -> 1 #51-76 -> 2 #etc level = max(1, min(5, (125-speed)//25)) kwargs["compress_level"] = level client_options["compress_level"] = level #default is good enough, no need to override, other options: #DEFAULT_STRATEGY, FILTERED, HUFFMAN_ONLY, RLE, FIXED #kwargs["compress_type"] = PIL.Image.DEFAULT_STRATEGY im.save(buf, "PNG", **kwargs) log("sending %sx%s %s as %s, mode=%s, options=%s", w, h, pixel_format, coding, im.mode, kwargs) data = buf.getvalue() buf.close() return coding, compression.Compressed(coding, data), client_options, image.get_width(), image.get_height(), 0, bpp
def compress_and_send_window_icon(self): #this runs in the work queue self.send_window_icon_timer = 0 idata = self.window_icon_data if not idata or not self.has_png: return w, h, pixel_format, pixel_data = idata log("compress_and_send_window_icon() %ix%i in %s format, %i bytes for wid=%i", w, h, pixel_format, len(pixel_data), self.wid) assert pixel_format in ("BGRA", "RGBA", "png"), "invalid window icon format %s" % pixel_format if pixel_format=="BGRA": #BGRA data is always unpremultiplied #(that's what we get from NetWMIcons) from xpra.codecs.argb.argb import premultiply_argb #@UnresolvedImport pixel_data = premultiply_argb(pixel_data) max_w, max_h = self.window_icon_max_size #use png if supported and if "premult_argb32" is not supported by the client (ie: html5) #or if we must downscale it (bigger than what the client is willing to deal with), #or if we want to save window icons must_scale = w>max_w or h>max_h log("compress_and_send_window_icon: %sx%s (max-size=%s, standard-size=%s), pixel_format=%s", w, h, self.window_icon_max_size, self.window_icon_size, pixel_format) must_convert = pixel_format!="png" log(" must convert=%s, must scale=%s", must_convert, must_scale) image = None if must_scale or must_convert or SAVE_WINDOW_ICONS: #we're going to need a PIL Image: if pixel_format=="png": image = Image.open(BytesIO(pixel_data)) else: image = Image.frombuffer("RGBA", (w,h), memoryview_to_bytes(pixel_data), "raw", pixel_format, 0, 1) if must_scale: #scale the icon down to the size the client wants #(we should scale + paste to preserve the aspect ratio, meh) icon_w, icon_h = self.window_icon_size if float(w)/icon_w>=float(h)/icon_h: rh = min(max_h, h*icon_w//w) rw = icon_w else: rw = min(max_w, w*icon_h//h) rh = icon_h log("scaling window icon down to %sx%s", rw, rh) image = image.resize((rw, rh), Image.ANTIALIAS) if SAVE_WINDOW_ICONS: filename = "server-window-%i-icon-%i.png" % (self.wid, int(monotonic_time())) image.save(filename, 'PNG') log("server window icon saved to %s", filename) if image: #image got converted or scaled, get the new pixel data: output = BytesIO() image.save(output, "png") pixel_data = output.getvalue() output.close() w, h = image.size wrapper = compression.Compressed("png", pixel_data) packet = ("window-icon", self.wid, w, h, wrapper.datatype, wrapper) log("queuing window icon update: %s", packet) self.queue_packet(packet, wait_for_more=True)
def 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 compress_and_send_window_icon(self): #this runs in the work queue self.send_window_icon_timer = 0 idata = self.window_icon_data if not idata: return pixel_data, pixel_format, stride, w, h = idata PIL = get_codec("PIL") max_w, max_h = self.window_icon_max_size if stride != w * 4: #re-stride it (I don't think this ever fires?) pixel_data = b"".join(pixel_data[stride * y:stride * y + w * 4] for y in range(h)) stride = w * 4 #use png if supported and if "premult_argb32" is not supported by the client (ie: html5) #or if we must downscale it (bigger than what the client is willing to deal with), #or if we want to save window icons has_png = PIL and PNG_ICONS and ("png" in self.window_icon_encodings) has_premult = ARGB_ICONS and "premult_argb32" in self.window_icon_encodings use_png = has_png and (SAVE_WINDOW_ICONS or w > max_w or h > max_h or w * h >= 1024 or (not has_premult) or (pixel_format != "BGRA")) log( "compress_and_send_window_icon: %sx%s (max-size=%s, standard-size=%s), sending as png=%s, has_png=%s, has_premult=%s, pixel_format=%s", w, h, self.window_icon_max_size, self.window_icon_size, use_png, has_png, has_premult, pixel_format) if use_png: img = PIL.Image.frombuffer("RGBA", (w, h), pixel_data, "raw", pixel_format, 0, 1) if w > max_w or h > max_h: #scale the icon down to the size the client wants icon_w, icon_h = self.window_icon_size if float(w) / icon_w >= float(h) / icon_h: h = min(max_h, h * icon_w // w) w = icon_w else: w = min(max_w, w * icon_h // h) h = icon_h log("scaling window icon down to %sx%s", w, h) img = img.resize((w, h), PIL.Image.ANTIALIAS) output = BytesIOClass() img.save(output, 'PNG') compressed_data = output.getvalue() output.close() wrapper = compression.Compressed("png", compressed_data) if SAVE_WINDOW_ICONS: filename = "server-window-%i-icon-%i.png" % ( self.wid, int(monotonic_time())) img.save(filename, 'PNG') log("server window icon saved to %s", filename) elif ("premult_argb32" in self.window_icon_encodings) and pixel_format == "BGRA": wrapper = self.compressed_wrapper("premult_argb32", str(pixel_data)) else: log("cannot send window icon, supported encodings: %s", self.window_icon_encodings) return assert wrapper.datatype in ( "premult_argb32", "png"), "invalid wrapper datatype %s" % wrapper.datatype packet = ("window-icon", self.wid, w, h, wrapper.datatype, wrapper) log("queuing window icon update: %s", packet) self.queue_packet(packet, wait_for_more=True)
def 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
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
#76-100 -> 1 #51-76 -> 2 #etc level = max(1, min(5, (125 - speed) // 25)) kwargs["compress_level"] = level client_options["compress_level"] = level #default is good enough, no need to override, other options: #DEFAULT_STRATEGY, FILTERED, HUFFMAN_ONLY, RLE, FIXED #kwargs["compress_type"] = PIL.Image.DEFAULT_STRATEGY im.save(buf, "PNG", **kwargs) log("sending %sx%s %s as %s, mode=%s, options=%s", w, h, pixel_format, coding, im.mode, kwargs) data = buf.getvalue() buf.close() return coding, compression.Compressed( coding, data), client_options, image.get_width(), image.get_height(), 0, bpp def argb_swap(image, rgb_formats, supports_transparency): """ use the argb codec to do the RGB byte swapping """ pixel_format = image.get_pixel_format() if None in (bgra_to_rgb, bgra_to_rgba, argb_to_rgb, argb_to_rgba): warn_encoding_once( "argb-module-missing", "no argb module, cannot convert %s to one of: %s" % (pixel_format, rgb_formats)) return False #try to fallback to argb module #if we have one of the target pixel formats: