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) 2013-2018 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.util import roundup 

8from xpra.os_util import memoryview_to_bytes, monotonic_time 

9 

10def clone_plane(plane): 

11 if isinstance(plane, memoryview): 

12 return plane.tobytes() 

13 return plane[:] 

14 

15 

16class ImageWrapper: 

17 

18 PACKED = 0 

19 PLANAR_2 = 2 

20 PLANAR_3 = 3 

21 PLANAR_4 = 4 

22 PLANE_OPTIONS = (PACKED, PLANAR_2, PLANAR_3, PLANAR_4) 

23 PLANE_NAMES = { 

24 PACKED : "PACKED", 

25 PLANAR_3 : "3_PLANES", 

26 PLANAR_4 : "4_PLANES", 

27 } 

28 

29 def __init__(self, x : int, y : int, width : int, height : int, pixels, pixel_format, depth : int, rowstride : int, 

30 bytesperpixel : int=4, planes : int=PACKED, thread_safe : bool=True, palette=None): 

31 self.x = x 

32 self.y = y 

33 self.target_x = x 

34 self.target_y = y 

35 self.width = width 

36 self.height = height 

37 self.pixels = pixels 

38 self.pixel_format = pixel_format 

39 self.depth = depth 

40 self.rowstride = rowstride 

41 self.bytesperpixel = bytesperpixel 

42 self.planes = planes 

43 self.thread_safe = thread_safe 

44 self.freed = False 

45 self.timestamp = int(monotonic_time()*1000) 

46 self.palette = palette 

47 assert x>=0 and y>=0 and width>0 and height>0 

48 

49 def _cn(self): 

50 try: 

51 return type(self).__name__ 

52 except AttributeError: # pragma: no cover 

53 return type(self) 

54 

55 def __repr__(self): 

56 return "%s(%s:%s:%s)" % (self._cn(), self.pixel_format, self.get_geometry(), 

57 ImageWrapper.PLANE_NAMES.get(self.planes)) 

58 

59 def get_geometry(self): 

60 return self.x, self.y, self.width, self.height, self.depth 

61 

62 def get_x(self) -> int: 

63 return self.x 

64 

65 def get_y(self) -> int: 

66 return self.y 

67 

68 def get_target_x(self) -> int: 

69 return self.target_x 

70 

71 def get_target_y(self) -> int: 

72 return self.target_y 

73 

74 def set_target_x(self, target_x : int): 

75 self.target_x = target_x 

76 

77 def set_target_y(self, target_y : int): 

78 self.target_y = target_y 

79 

80 def get_width(self) -> int: 

81 return self.width 

82 

83 def get_height(self) -> int: 

84 return self.height 

85 

86 def get_rowstride(self) -> int: 

87 return self.rowstride 

88 

89 def get_depth(self) -> int: 

90 return self.depth 

91 

92 def get_bytesperpixel(self) -> int: 

93 return self.bytesperpixel 

94 

95 def get_size(self) -> int: 

96 return self.rowstride * self.height 

97 

98 def get_pixel_format(self): 

99 return self.pixel_format 

100 

101 def get_pixels(self): 

102 return self.pixels 

103 

104 def get_planes(self) -> int: 

105 return self.planes 

106 

107 def get_palette(self): 

108 return self.palette 

109 

110 def get_gpu_buffer(self): 

111 return None 

112 

113 def has_pixels(self) -> bool: 

114 return bool(self.pixels) 

115 

116 def is_thread_safe(self) -> bool: 

117 """ if True, free() and clone_pixel_data() can be called from any thread, 

118 if False, free() and clone_pixel_data() must be called from the same thread. 

119 Used by XImageWrapper to ensure X11 images are freed from the UI thread. 

120 """ 

121 return self.thread_safe 

122 

123 def get_timestamp(self) -> int: 

124 """ time in millis """ 

125 return self.timestamp 

126 

127 

128 def set_timestamp(self, timestamp : int): 

129 self.timestamp = timestamp 

130 

131 def set_planes(self, planes : int): 

132 self.planes = planes 

133 

134 def set_rowstride(self, rowstride : int): 

135 self.rowstride = rowstride 

136 

137 def set_pixel_format(self, pixel_format): 

138 self.pixel_format = pixel_format 

139 

140 def set_palette(self, palette): 

141 self.palette = palette 

142 

143 def set_pixels(self, pixels): 

144 assert not self.freed 

145 self.pixels = pixels 

146 

147 def allocate_buffer(self, _buf_len, _free_existing=1): 

148 assert not self.freed 

149 #only defined for XImage wrappers: 

150 return 0 

151 

152 def may_restride(self) -> bool: 

153 newstride = roundup(self.width*self.bytesperpixel, 4) 

154 if self.rowstride>newstride: 

155 return self.restride(newstride) 

156 return False 

157 

158 def restride(self, rowstride : int) -> bool: 

159 assert not self.freed 

160 if self.planes>0: 

161 #not supported yet for planar images 

162 return False 

163 pixels = self.pixels 

164 assert pixels, "no pixel data to restride" 

165 oldstride = self.rowstride 

166 pos = 0 

167 lines = [] 

168 for _ in range(self.height): 

169 lines.append(memoryview_to_bytes(pixels[pos:pos+rowstride])) 

170 pos += oldstride 

171 if self.height>0 and oldstride<rowstride: 

172 #the last few lines may need padding if the new rowstride is bigger 

173 #(usually just the last line) 

174 #we do this here to avoid slowing down the main loop above 

175 #as this should be a rarer case 

176 for h in range(self.height): 

177 i = -(1+h) 

178 line = lines[i] 

179 if len(line)<rowstride: 

180 lines[i] = line + b"\0"*(rowstride-len(line)) 

181 else: 

182 break 

183 self.rowstride = rowstride 

184 self.pixels = b"".join(lines) 

185 return True 

186 

187 def freeze(self) -> bool: 

188 assert not self.freed 

189 #some wrappers (XShm) need to be told to stop updating the pixel buffer 

190 return False 

191 

192 def clone_pixel_data(self): 

193 assert not self.freed 

194 pixels = self.pixels 

195 planes = self.planes 

196 assert pixels, "no pixel data to clone" 

197 if planes == 0: 

198 #no planes, simple buffer: 

199 self.pixels = clone_plane(pixels) 

200 else: 

201 assert planes>0 

202 self.pixels = [clone_plane(pixels[i]) for i in range(planes)] 

203 self.thread_safe = True 

204 if self.freed: # pragma: no cover 

205 #could be a race since this can run threaded 

206 self.free() 

207 

208 def get_sub_image(self, x : int, y : int, w : int, h : int): 

209 #raise NotImplementedError("no sub-images for %s" % type(self)) 

210 assert w>0 and h>0, "invalid sub-image size: %ix%i" % (w, h) 

211 if x+w>self.width: 

212 raise Exception("invalid sub-image width: %i+%i greater than image width %i" % (x, w, self.width)) 

213 if y+h>self.height: 

214 raise Exception("invalid sub-image height: %i+%i greater than image height %i" % (y, h, self.height)) 

215 assert self.planes==0, "cannot sub-divide planar images!" 

216 if x==0 and y==0 and w==self.width and h==self.height: 

217 #same dimensions, use the same wrapper 

218 return self 

219 #copy to local variables: 

220 pixels = self.pixels 

221 oldstride = self.rowstride 

222 pos = y*oldstride + x*self.bytesperpixel 

223 newstride = w*self.bytesperpixel 

224 lines = [] 

225 for _ in range(h): 

226 lines.append(memoryview_to_bytes(pixels[pos:pos+newstride])) 

227 pos += oldstride 

228 image = ImageWrapper(self.x+x, self.y+y, w, h, b"".join(lines), self.pixel_format, self.depth, newstride, 

229 planes=self.planes, thread_safe=True, palette=self.palette) 

230 image.set_target_x(self.target_x+x) 

231 image.set_target_y(self.target_y+y) 

232 return image 

233 

234 def __del__(self): 

235 #print("ImageWrapper.__del__() calling %s" % self.free) 

236 self.free() 

237 

238 def free(self): 

239 #print("ImageWrapper.free()") 

240 if not self.freed: 

241 self.freed = True 

242 self.planes = None 

243 self.pixels = None 

244 self.pixel_format = None