Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# -*- coding: utf-8 -*- 

2# This file is part of Xpra. 

3# Copyright (C) 2010-2019 Antoine Martin <antoine@xpra.org> 

4# Xpra is released under the terms of the GNU GPL v2, or, at your option, any 

5# later version. See the file COPYING for details. 

6 

7from xpra.net import compression 

8from xpra.codecs.loader import get_codec 

9from xpra.util import envbool, first_time 

10from xpra.codecs.rgb_transform import rgb_reformat 

11from xpra.os_util import memoryview_to_bytes, bytestostr, monotonic_time 

12from xpra.log import Logger 

13 

14#"pixels_to_bytes" gets patched up by the OSX shadow server 

15pixels_to_bytes = memoryview_to_bytes 

16try: 

17 from xpra.net.mmap_pipe import mmap_write 

18except ImportError: 

19 mmap_write = None #no mmap 

20 

21log = Logger("window", "encoding") 

22 

23WEBP_PILLOW = envbool("XPRA_WEBP_PILLOW", False) 

24 

25 

26def webp_encode(image, supports_transparency, quality, speed, content_type): 

27 stride = image.get_rowstride() 

28 pixel_format = image.get_pixel_format() 

29 enc_webp = get_codec("enc_webp") 

30 #log("WEBP_PILLOW=%s, enc_webp=%s, stride=%s, pixel_format=%s", WEBP_PILLOW, enc_webp, stride, pixel_format) 

31 if not WEBP_PILLOW and enc_webp and stride>0 and stride%4==0 and pixel_format in ("BGRA", "BGRX", "RGBA", "RGBX"): 

32 #prefer Cython module: 

33 cdata, client_options = enc_webp.encode(image, quality, speed, supports_transparency, content_type) 

34 return "webp", compression.Compressed("webp", cdata), client_options, image.get_width(), image.get_height(), 0, 24 

35 #fallback using Pillow: 

36 enc_pillow = get_codec("enc_pillow") 

37 if enc_pillow: 

38 if not WEBP_PILLOW: 

39 log.warn("Warning: using PIL fallback for webp") 

40 log.warn(" enc_webp=%s, stride=%s, pixel format=%s", enc_webp, stride, image.get_pixel_format()) 

41 for x in ("webp", "png"): 

42 if x in enc_pillow.get_encodings(): 

43 return enc_pillow.encode(x, image, quality, speed, supports_transparency) 

44 raise Exception("BUG: cannot use 'webp' encoding and none of the PIL fallbacks are available!") 

45 

46 

47def rgb_encode(coding, image, rgb_formats, supports_transparency, speed, rgb_zlib=True, rgb_lz4=True, rgb_lzo=False): 

48 pixel_format = bytestostr(image.get_pixel_format()) 

49 #log("rgb_encode%s pixel_format=%s, rgb_formats=%s", 

50 # (coding, image, rgb_formats, supports_transparency, speed, rgb_zlib, rgb_lz4), pixel_format, rgb_formats) 

51 if pixel_format not in rgb_formats: 

52 log("rgb_encode reformatting because %s not in %s, supports_transparency=%s", 

53 pixel_format, rgb_formats, supports_transparency) 

54 if not rgb_reformat(image, rgb_formats, supports_transparency): 

55 raise Exception("cannot find compatible rgb format to use for %s! (supported: %s)" % ( 

56 pixel_format, rgb_formats)) 

57 #get the new format: 

58 pixel_format = bytestostr(image.get_pixel_format()) 

59 #switch encoding if necessary: 

60 if len(pixel_format)==4: 

61 coding = "rgb32" 

62 elif len(pixel_format)==3: 

63 coding = "rgb24" 

64 else: 

65 raise Exception("invalid pixel format %s" % pixel_format) 

66 #we may still want to re-stride: 

67 image.may_restride() 

68 #always tell client which pixel format we are sending: 

69 options = {"rgb_format" : pixel_format} 

70 

71 #compress here and return a wrapper so network code knows it is already zlib compressed: 

72 pixels = image.get_pixels() 

73 assert pixels, "failed to get pixels from %s" % image 

74 width = image.get_width() 

75 height = image.get_height() 

76 stride = image.get_rowstride() 

77 

78 #compression stage: 

79 level = 0 

80 algo = "not" 

81 l = len(pixels) 

82 if l>=512 and speed<100: 

83 if l>=4096: 

84 #speed=99 -> level=1, speed=0 -> level=9 

85 level = 1+max(0, min(8, int(100-speed)//12)) 

86 else: 

87 #fewer pixels, make it more likely we won't bother compressing 

88 #and use a lower level (max=5) 

89 level = max(0, min(5, int(115-speed)//20)) 

90 if level>0: 

91 cwrapper = compression.compressed_wrapper(coding, pixels, level=level, 

92 zlib=rgb_zlib, lz4=rgb_lz4, lzo=rgb_lzo, 

93 brotli=False, none=True) 

94 algo = cwrapper.algorithm 

95 if algo=="none" or len(cwrapper)>=(len(pixels)-32): 

96 #no compression is enabled, or compressed is actually bigger! 

97 #(fall through to uncompressed) 

98 level = 0 

99 else: 

100 #add compressed marker: 

101 options[algo] = level 

102 #remove network layer compression marker 

103 #so that this data will be decompressed by the decode thread client side: 

104 cwrapper.level = 0 

105 if level==0: 

106 #can't pass a raw buffer to bencode / rencode, 

107 #and even if we could, the image containing those pixels may be freed by the time we get to the encoder 

108 algo = "not" 

109 cwrapper = compression.Compressed(coding, pixels_to_bytes(pixels), True) 

110 if pixel_format.find("A")>=0 or pixel_format.find("X")>=0: 

111 bpp = 32 

112 else: 

113 bpp = 24 

114 log("rgb_encode using level=%s for %5i bytes at %3i speed, %s compressed %4sx%-4s in %s/%s: %5s bytes down to %5s", 

115 level, l, speed, algo, image.get_width(), image.get_height(), coding, pixel_format, len(pixels), len(cwrapper.data)) 

116 #wrap it using "Compressed" so the network layer receiving it 

117 #won't decompress it (leave it to the client's draw thread) 

118 return coding, cwrapper, options, width, height, stride, bpp 

119 

120 

121def mmap_send(mmap, mmap_size, image, rgb_formats, supports_transparency): 

122 if mmap_write is None: 

123 if first_time("mmap_write missing"): 

124 log.warn("Warning: cannot use mmap, no write method support") 

125 return None 

126 if image.get_pixel_format() not in rgb_formats: 

127 if not rgb_reformat(image, rgb_formats, supports_transparency): 

128 warning_key = "mmap_send(%s)" % image.get_pixel_format() 

129 if first_time(warning_key): 

130 log.warn("Waening: cannot use mmap to send %s" % image.get_pixel_format()) 

131 return None 

132 start = monotonic_time() 

133 data = image.get_pixels() 

134 assert data, "failed to get pixels from %s" % image 

135 mmap_data, mmap_free_size = mmap_write(mmap, mmap_size, data) 

136 elapsed = monotonic_time()-start+0.000000001 #make sure never zero! 

137 log("%s MBytes/s - %s bytes written to mmap in %.1f ms", int(len(data)/elapsed/1024/1024), len(data), 1000*elapsed) 

138 if mmap_data is None: 

139 return None 

140 #replace pixels with mmap info: 

141 return mmap_data, mmap_free_size, len(data)