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# This file is part of Xpra. 

2# Copyright (C) 2008 Nathaniel Smith <njs@pobox.com> 

3# Copyright (C) 2012-2020 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 

7import hashlib 

8from threading import Lock 

9 

10from xpra.net.mmap_pipe import mmap_read 

11from xpra.net import compression 

12from xpra.util import typedict, csv, envint, envbool, first_time 

13from xpra.codecs.loader import get_codec 

14from xpra.codecs.video_helper import getVideoHelper 

15from xpra.os_util import bytestostr 

16from xpra.common import ( 

17 NorthWestGravity, 

18 NorthGravity, 

19 NorthEastGravity, 

20 WestGravity, 

21 CenterGravity, 

22 EastGravity, 

23 SouthWestGravity, 

24 SouthGravity, 

25 SouthEastGravity, 

26 StaticGravity, 

27 GRAVITY_STR, 

28 ) 

29from xpra.log import Logger 

30 

31log = Logger("paint") 

32videolog = Logger("video", "paint") 

33 

34INTEGRITY_HASH = envbool("XPRA_INTEGRITY_HASH", False) 

35PAINT_BOX = envint("XPRA_PAINT_BOX", 0) or envint("XPRA_OPENGL_PAINT_BOX", 0) 

36WEBP_PILLOW = envbool("XPRA_WEBP_PILLOW", False) 

37SCROLL_ENCODING = envbool("XPRA_SCROLL_ENCODING", True) 

38REPAINT_ALL = envbool("XPRA_REPAINT_ALL", False) 

39 

40 

41#ie: 

42#CSC_OPTIONS = { "YUV420P" : {"RGBX" : [swscale.spec], "BGRX" : ...} } 

43CSC_OPTIONS = None 

44def load_csc_options(): 

45 global CSC_OPTIONS 

46 if CSC_OPTIONS is None: 

47 CSC_OPTIONS = {} 

48 vh = getVideoHelper() 

49 for csc_in in vh.get_csc_inputs(): 

50 CSC_OPTIONS[csc_in] = vh.get_csc_specs(csc_in) 

51 return CSC_OPTIONS 

52 

53#get the list of video encodings (and the module for each one): 

54VIDEO_DECODERS = None 

55def load_video_decoders(): 

56 global VIDEO_DECODERS 

57 if VIDEO_DECODERS is None: 

58 VIDEO_DECODERS = {} 

59 vh = getVideoHelper() 

60 for encoding in vh.get_decodings(): 

61 specs = vh.get_decoder_specs(encoding) 

62 for colorspace, decoders in specs.items(): 

63 log("%-5s decoders for %7s: %s", encoding, colorspace, csv([d.get_type() for _,d in decoders])) 

64 assert decoders 

65 #use the first one: 

66 _, decoder_module = decoders[0] 

67 VIDEO_DECODERS[encoding] = decoder_module 

68 log("video decoders: %s", dict((e,d.get_type()) for e,d in VIDEO_DECODERS.items())) 

69 return VIDEO_DECODERS 

70 

71 

72def fire_paint_callbacks(callbacks, success=True, message=""): 

73 for x in callbacks: 

74 try: 

75 x(success, message) 

76 except Exception: 

77 log.error("error calling %s(%s)", x, success, exc_info=True) 

78 

79 

80def verify_checksum(img_data, options): 

81 l = options.intget("z.len") 

82 if l: 

83 assert l==len(img_data), "compressed pixel data failed length integrity check: expected %i bytes but got %i" % (l, len(img_data)) 

84 try: 

85 chksum = options.get("z.md5") 

86 if chksum: 

87 h = hashlib.md5(img_data) 

88 except ValueError: 

89 chksum = options.get("z.sha1") 

90 if chksum: 

91 h = hashlib.sha1(img_data) 

92 if h: 

93 hd = h.hexdigest() 

94 assert chksum==hd, "pixel data failed compressed chksum integrity check: expected %s but got %s" % (chksum, hd) 

95 

96 

97""" 

98Generic superclass for all Backing code, 

99see CairoBackingBase and GTK2WindowBacking subclasses for actual implementations 

100""" 

101class WindowBackingBase: 

102 RGB_MODES = () 

103 

104 def __init__(self, wid : int, window_alpha : bool): 

105 load_csc_options() 

106 load_video_decoders() 

107 self.wid = wid 

108 self.size = 0, 0 

109 self.render_size = 0, 0 

110 #padding between the window contents and where we actually draw the backing 

111 #(ie: if the window is bigger than the backing, 

112 # we may be rendering the backing in the center of the window) 

113 self.offsets = 0, 0, 0, 0 #top,left,bottom,right 

114 self.gravity = 0 

115 self._alpha_enabled = window_alpha 

116 self._backing = None 

117 self._video_decoder = None 

118 self._csc_decoder = None 

119 self._decoder_lock = Lock() 

120 self._PIL_encodings = [] 

121 self.default_paint_box_line_width = PAINT_BOX or 1 

122 self.paint_box_line_width = PAINT_BOX 

123 self.pointer_overlay = None 

124 self.cursor_data = None 

125 self.default_cursor_data = None 

126 self.jpeg_decoder = None 

127 self.webp_decoder = None 

128 self.pil_decoder = get_codec("dec_pillow") 

129 if self.pil_decoder: 

130 self._PIL_encodings = self.pil_decoder.get_encodings() 

131 self.jpeg_decoder = get_codec("dec_jpeg") 

132 self.webp_decoder = get_codec("dec_webp") 

133 self.draw_needs_refresh = True 

134 self.repaint_all = REPAINT_ALL 

135 self.mmap = None 

136 self.mmap_enabled = False 

137 

138 def idle_add(self, *_args, **_kwargs): 

139 raise NotImplementedError() 

140 

141 def get_info(self): 

142 info = { 

143 "rgb-formats" : self.RGB_MODES, 

144 "transparency" : self._alpha_enabled, 

145 "mmap" : bool(self.mmap_enabled), 

146 "size" : self.size, 

147 "render-size" : self.render_size, 

148 "offsets" : self.offsets, 

149 } 

150 vd = self._video_decoder 

151 if vd: 

152 info["video-decoder"] = self._video_decoder.get_info() 

153 csc = self._csc_decoder 

154 if csc: 

155 info["csc"] = self._csc_decoder 

156 return info 

157 

158 

159 def enable_mmap(self, mmap_area): 

160 self.mmap = mmap_area 

161 self.mmap_enabled = True 

162 

163 def gravity_copy_coords(self, oldw, oldh, bw, bh): 

164 sx = sy = dx = dy = 0 

165 def center_y(): 

166 if bh>=oldh: 

167 #take the whole source, paste it in the middle 

168 return 0, (bh-oldh)//2 

169 #skip the edges of the source, paste all of it 

170 return (oldh-bh)//2, 0 

171 def center_x(): 

172 if bw>=oldw: 

173 return 0, (bw-oldw)//2 

174 return (oldw-bw)//2, 0 

175 def east_x(): 

176 if bw>=oldw: 

177 return 0, bw-oldw 

178 return oldw-bw, 0 

179 def west_x(): 

180 return 0, 0 

181 def north_y(): 

182 return 0, 0 

183 def south_y(): 

184 if bh>=oldh: 

185 return 0, bh-oldh 

186 return oldh-bh, 0 

187 g = self.gravity 

188 if not g or g==NorthWestGravity: 

189 #undefined (or 0), use NW 

190 sx, dx = west_x() 

191 sy, dy = north_y() 

192 elif g==NorthGravity: 

193 sx, dx = center_x() 

194 sy, dy = north_y() 

195 elif g==NorthEastGravity: 

196 sx, dx = east_x() 

197 sy, dy = north_y() 

198 elif g==WestGravity: 

199 sx, dx = west_x() 

200 sy, dy = center_y() 

201 elif g==CenterGravity: 

202 sx, dx = center_x() 

203 sy, dy = center_y() 

204 elif g==EastGravity: 

205 sx, dx = east_x() 

206 sy, dy = center_y() 

207 elif g==SouthWestGravity: 

208 sx, dx = west_x() 

209 sy, dy = south_y() 

210 elif g==SouthGravity: 

211 sx, dx = center_x() 

212 sy, dy = south_y() 

213 elif g==SouthEastGravity: 

214 sx, dx = east_x() 

215 sy, dy = south_y() 

216 elif g==StaticGravity: 

217 if first_time("StaticGravity-%i" % self.wid): 

218 log.warn("Warning: static gravity is not handled") 

219 w = min(bw, oldw) 

220 h = min(bh, oldh) 

221 return sx, sy, dx, dy, w, h 

222 

223 def gravity_adjust(self, x, y, options): 

224 #if the window size has changed, 

225 #adjust the coordinates honouring the window gravity: 

226 window_size = options.inttupleget("window-size", None) 

227 g = self.gravity 

228 log("gravity_adjust%s window_size=%s, size=%s, gravity=%s", 

229 (x, y, options), window_size, self.size, GRAVITY_STR.get(g, "unknown")) 

230 if not window_size: 

231 return x, y 

232 window_size = tuple(window_size) 

233 if window_size==self.size: 

234 return x, y 

235 if g==0 or self.gravity==NorthWestGravity: 

236 return x, y 

237 oldw, oldh = window_size 

238 bw, bh = self.size 

239 def center_y(): 

240 if bh>=oldh: 

241 return y + (bh-oldh)//2 

242 return y - (oldh-bh)//2 

243 def center_x(): 

244 if bw>=oldw: 

245 return x + (bw-oldw)//2 

246 return x - (oldw-bw)//2 

247 def east_x(): 

248 if bw>=oldw: 

249 return x + (bw-oldw) 

250 return x - (oldw-bw) 

251 def west_x(): 

252 return x 

253 def north_y(): 

254 return y 

255 def south_y(): 

256 if bh>=oldh: 

257 return y + (bh-oldh) 

258 return y - (oldh-bh) 

259 if g==NorthGravity: 

260 return center_x(), north_y() 

261 if g==NorthEastGravity: 

262 return east_x(), north_y() 

263 if g==WestGravity: 

264 return west_x(), center_y() 

265 if g==CenterGravity: 

266 return center_x(), center_y() 

267 if g==EastGravity: 

268 return east_x(), center_y() 

269 if g==SouthWestGravity: 

270 return west_x(), south_y() 

271 if g==SouthGravity: 

272 return center_x(), south_y() 

273 if g==SouthEastGravity: 

274 return east_x(), south_y() 

275 #if self.gravity==StaticGravity: 

276 # pass 

277 return x, y 

278 

279 

280 def close(self): 

281 self._backing = None 

282 log("%s.close() video_decoder=%s", self, self._video_decoder) 

283 #try without blocking, if that fails then 

284 #the lock is held by the decoding thread, 

285 #and it will run the cleanup after releasing the lock 

286 #(it checks for self._backing None) 

287 self.close_decoder(False) 

288 

289 def close_decoder(self, blocking=False): 

290 videolog("close_decoder(%s)", blocking) 

291 dl = self._decoder_lock 

292 if dl is None or not dl.acquire(blocking): 

293 videolog("close_decoder(%s) lock %s not acquired", blocking, dl) 

294 return False 

295 try: 

296 self.do_clean_video_decoder() 

297 self.do_clean_csc_decoder() 

298 return True 

299 finally: 

300 dl.release() 

301 

302 def do_clean_video_decoder(self): 

303 if self._video_decoder: 

304 self._video_decoder.clean() 

305 self._video_decoder = None 

306 

307 def do_clean_csc_decoder(self): 

308 if self._csc_decoder: 

309 self._csc_decoder.clean() 

310 self._csc_decoder = None 

311 

312 

313 def get_encoding_properties(self): 

314 return { 

315 "encodings.rgb_formats" : self.RGB_MODES, 

316 "encoding.transparency" : self._alpha_enabled, 

317 "encoding.full_csc_modes" : self._get_full_csc_modes(self.RGB_MODES), 

318 "encoding.send-window-size" : True, 

319 "encoding.render-size" : self.render_size, 

320 } 

321 

322 def _get_full_csc_modes(self, rgb_modes): 

323 #calculate the server CSC modes the server is allowed to use 

324 #based on the client CSC modes we can convert to in the backing class we use 

325 #and trim the transparency if we cannot handle it 

326 target_rgb_modes = tuple(rgb_modes) 

327 if not self._alpha_enabled: 

328 target_rgb_modes = tuple(x for x in target_rgb_modes if x.find("A")<0) 

329 full_csc_modes = getVideoHelper().get_server_full_csc_modes_for_rgb(*target_rgb_modes) 

330 full_csc_modes["webp"] = [x for x in rgb_modes if x in ("BGRX", "BGRA", "RGBX", "RGBA")] 

331 videolog("_get_full_csc_modes(%s) with target_rgb_modes=%s", rgb_modes, target_rgb_modes) 

332 for e in sorted(full_csc_modes.keys()): 

333 modes = full_csc_modes.get(e) 

334 videolog(" * %s : %s", e, modes) 

335 return full_csc_modes 

336 

337 

338 def unpremultiply(self, img_data): 

339 from xpra.codecs.argb.argb import unpremultiply_argb, unpremultiply_argb_in_place #@UnresolvedImport 

340 if not isinstance(img_data, str): 

341 try: 

342 unpremultiply_argb_in_place(img_data) 

343 return img_data 

344 except Exception: 

345 log.warn("failed to unpremultiply %s (len=%s)" % (type(img_data), len(img_data))) 

346 return unpremultiply_argb(img_data) 

347 

348 

349 def set_cursor_data(self, cursor_data): 

350 self.cursor_data = cursor_data 

351 

352 

353 def paint_jpeg(self, img_data, x, y, width, height, options, callbacks): 

354 img = self.jpeg_decoder.decompress_to_rgb("RGBX", img_data) 

355 rgb_format = img.get_pixel_format() 

356 img_data = img.get_pixels() 

357 rowstride = img.get_rowstride() 

358 w = img.get_width() 

359 h = img.get_height() 

360 self.idle_add(self.do_paint_rgb, rgb_format, img_data, 

361 x, y, w, h, width, height, rowstride, options, callbacks) 

362 

363 

364 def paint_image(self, coding, img_data, x, y, width, height, options, callbacks): 

365 # can be called from any thread 

366 rgb_format, img_data, iwidth, iheight, rowstride = self.pil_decoder.decompress(coding, img_data, options) 

367 self.idle_add(self.do_paint_rgb, rgb_format, img_data, 

368 x, y, iwidth, iheight, width, height, rowstride, options, callbacks) 

369 

370 def paint_webp(self, img_data, x, y, width, height, options, callbacks): 

371 if not self.webp_decoder or WEBP_PILLOW: 

372 #if webp is enabled, then Pillow should be able to take care of it: 

373 self.paint_image("webp", img_data, x, y, width, height, options, callbacks) 

374 return 

375 rgb_format = options.strget("rgb_format") 

376 has_alpha = options.boolget("has_alpha", False) 

377 ( 

378 buffer_wrapper, 

379 iwidth, iheight, stride, has_alpha, 

380 rgb_format, 

381 ) = self.webp_decoder.decompress(img_data, has_alpha, rgb_format, self.RGB_MODES) 

382 def free_buffer(*_args): 

383 buffer_wrapper.free() 

384 callbacks.append(free_buffer) 

385 data = buffer_wrapper.get_pixels() 

386 #if the backing can't handle this format, 

387 #ie: tray only supports RGBA 

388 if rgb_format not in self.RGB_MODES: 

389 from xpra.codecs.rgb_transform import rgb_reformat 

390 from xpra.codecs.image_wrapper import ImageWrapper 

391 img = ImageWrapper(x, y, iwidth, iheight, data, rgb_format, 

392 len(rgb_format)*8, stride, len(rgb_format), ImageWrapper.PACKED, True, None) 

393 rgb_reformat(img, self.RGB_MODES, has_alpha and self._alpha_enabled) 

394 rgb_format = img.get_pixel_format() 

395 data = img.get_pixels() 

396 stride = img.get_rowstride() 

397 #replace with the actual rgb format we get from the decoder: 

398 options[b"rgb_format"] = rgb_format 

399 self.idle_add(self.do_paint_rgb, rgb_format, data, 

400 x, y, iwidth, iheight, width, height, stride, options, callbacks) 

401 

402 def paint_rgb(self, rgb_format, raw_data, x, y, width, height, rowstride, options, callbacks): 

403 """ can be called from a non-UI thread """ 

404 iwidth, iheight = options.intpair("scaled-size", (width, height)) 

405 #was a compressor used? 

406 comp = tuple(x for x in compression.ALL_COMPRESSORS if options.intget(x, 0)) 

407 if comp: 

408 assert len(comp)==1, "more than one compressor specified: %s" % str(comp) 

409 rgb_data = compression.decompress_by_name(raw_data, algo=comp[0]) 

410 else: 

411 rgb_data = raw_data 

412 self.idle_add(self.do_paint_rgb, rgb_format, rgb_data, 

413 x, y, iwidth, iheight, width, height, rowstride, options, callbacks) 

414 

415 def do_paint_rgb(self, rgb_format, img_data, 

416 x, y, width, height, render_width, render_height, rowstride, options, callbacks): 

417 """ must be called from the UI thread 

418 this method is only here to ensure that we always fire the callbacks, 

419 the actual paint code is in _do_paint_rgb[24|32] 

420 """ 

421 x, y = self.gravity_adjust(x, y, options) 

422 try: 

423 if not options.boolget("paint", True): 

424 fire_paint_callbacks(callbacks) 

425 return 

426 if self._backing is None: 

427 fire_paint_callbacks(callbacks, -1, "no backing") 

428 return 

429 if rgb_format=="r210": 

430 bpp = 30 

431 elif rgb_format=="BGR565": 

432 bpp = 16 

433 else: 

434 bpp = len(rgb_format)*8 #ie: "BGRA" -> 32 

435 if bpp==16: 

436 paint_fn = self._do_paint_rgb16 

437 elif bpp==24: 

438 paint_fn = self._do_paint_rgb24 

439 elif bpp==30: 

440 paint_fn = self._do_paint_rgb30 

441 elif bpp==32: 

442 paint_fn = self._do_paint_rgb32 

443 else: 

444 raise Exception("invalid rgb format '%s'" % rgb_format) 

445 options[b"rgb_format"] = rgb_format 

446 success = paint_fn(img_data, x, y, width, height, render_width, render_height, rowstride, options) 

447 fire_paint_callbacks(callbacks, success) 

448 except Exception as e: 

449 if not self._backing: 

450 fire_paint_callbacks(callbacks, -1, "paint error on closed backing ignored") 

451 else: 

452 log.error("Error painting rgb%s", bpp, exc_info=True) 

453 message = "paint rgb%s error: %s" % (bpp, e) 

454 fire_paint_callbacks(callbacks, False, message) 

455 

456 def _do_paint_rgb16(self, img_data, x, y, width, height, render_width, render_height, rowstride, options): 

457 raise Exception("override me!") 

458 

459 def _do_paint_rgb24(self, img_data, x, y, width, height, render_width, render_height, rowstride, options): 

460 raise Exception("override me!") 

461 

462 def _do_paint_rgb30(self, img_data, x, y, width, height, render_width, render_height, rowstride, options): 

463 raise Exception("override me!") 

464 

465 def _do_paint_rgb32(self, img_data, x, y, width, height, render_width, render_height, rowstride, options): 

466 raise Exception("override me!") 

467 

468 

469 def eos(self): 

470 dl = self._decoder_lock 

471 with dl: 

472 self.do_clean_csc_decoder() 

473 self.do_clean_video_decoder() 

474 

475 

476 def make_csc(self, src_width, src_height, src_format, 

477 dst_width, dst_height, dst_format_options, speed): 

478 global CSC_OPTIONS 

479 in_options = CSC_OPTIONS.get(src_format, {}) 

480 if not in_options: 

481 log.error("Error: no csc options for '%s' input, only found:", src_format) 

482 for k,v in CSC_OPTIONS.items(): 

483 log.error(" * %-8s : %s", k, csv(v)) 

484 raise Exception("no csc options for '%s' input in %s" % (src_format, csv(CSC_OPTIONS.keys()))) 

485 videolog("make_csc%s", 

486 (src_width, src_height, src_format, dst_width, dst_height, dst_format_options, speed)) 

487 for dst_format in dst_format_options: 

488 specs = in_options.get(dst_format) 

489 videolog("make_csc specs(%s)=%s", dst_format, specs) 

490 if not specs: 

491 continue 

492 for spec in specs: 

493 v = self.validate_csc_size(spec, src_width, src_height, dst_width, dst_height) 

494 if v: 

495 continue 

496 try: 

497 csc = spec.make_instance() 

498 csc.init_context(src_width, src_height, src_format, 

499 dst_width, dst_height, dst_format, speed) 

500 return csc 

501 except Exception as e: 

502 videolog("make_csc%s", 

503 (src_width, src_height, src_format, dst_width, dst_height, dst_format_options, speed), 

504 exc_info=True) 

505 videolog.error("Error: failed to create csc instance %s", spec.codec_class) 

506 videolog.error(" for %s to %s: %s", src_format, dst_format, e) 

507 videolog.error("Error: no matching CSC module found") 

508 videolog.error(" for %ix%i %s source format,", src_width, src_height, src_format) 

509 videolog.error(" to %ix%i %s", dst_width, dst_height, " or ".join(dst_format_options)) 

510 videolog.error(" with options=%s, speed=%i", dst_format_options, speed) 

511 videolog.error(" tested:") 

512 for dst_format in dst_format_options: 

513 specs = in_options.get(dst_format) 

514 if not specs: 

515 continue 

516 videolog.error(" * %s:", dst_format) 

517 for spec in specs: 

518 videolog.error(" - %s:", spec) 

519 v = self.validate_csc_size(spec, src_width, src_height, dst_width, dst_height) 

520 if v: 

521 videolog.error(" "+v[0], *v[1:]) 

522 raise Exception("no csc module found for wid %i %s(%sx%s) to %s(%sx%s) in %s" % 

523 (self.wid, src_format, src_width, src_height, " or ".join(dst_format_options), 

524 dst_width, dst_height, CSC_OPTIONS)) 

525 

526 def validate_csc_size(self, spec, src_width, src_height, dst_width, dst_height): 

527 if not spec.can_scale and (src_width!=dst_width or src_height!=dst_height): 

528 return "scaling not suported" 

529 if src_width<spec.min_w: 

530 return "source width %i is out of range: minimum is %i", src_width, spec.min_w 

531 if src_height<spec.min_h: 

532 return "source height %i is out of range: minimum is %i", src_height, spec.min_h 

533 if dst_width<spec.min_w: 

534 return "target width %i is out of range: minimum is %i", dst_width, spec.min_w 

535 if dst_height<spec.min_h: 

536 return "target height %i is out of range: minimum is %i", dst_height, spec.min_h 

537 if src_width>spec.max_w: 

538 return "source width %i is out of range: maximum is %i", src_width, spec.max_w 

539 if src_height>spec.max_h: 

540 return "source height %i is out of range: maximum is %i", src_height, spec.max_h 

541 if dst_width>spec.max_w: 

542 return "target width %i is out of range: maximum is %i", dst_width, spec.max_w 

543 if dst_height>spec.max_h: 

544 return "target height %i is out of range: maximum is %i", dst_height, spec.max_h 

545 return None 

546 

547 def paint_with_video_decoder(self, decoder_module, coding, img_data, x, y, width, height, options, callbacks): 

548 assert decoder_module, "decoder module not found for %s" % coding 

549 dl = self._decoder_lock 

550 if dl is None: 

551 fire_paint_callbacks(callbacks, False, "no lock - retry") 

552 return 

553 with dl: 

554 if self._backing is None: 

555 message = "window %s is already gone!" % self.wid 

556 log(message) 

557 fire_paint_callbacks(callbacks, -1, message) 

558 return 

559 enc_width, enc_height = options.intpair("scaled_size", (width, height)) 

560 input_colorspace = options.strget("csc") 

561 if not input_colorspace: 

562 message = "csc mode is missing from the video options!" 

563 log.error(message) 

564 fire_paint_callbacks(callbacks, False, message) 

565 return 

566 #do we need a prep step for decoders that cannot handle the input_colorspace directly? 

567 decoder_colorspaces = decoder_module.get_input_colorspaces(coding) 

568 assert input_colorspace in decoder_colorspaces, "decoder %s does not support %s for %s" % ( 

569 decoder_module.get_type(), input_colorspace, coding) 

570 

571 vd = self._video_decoder 

572 if vd: 

573 if options.intget("frame", -1)==0: 

574 videolog("paint_with_video_decoder: first frame of new stream") 

575 self.do_clean_video_decoder() 

576 elif vd.get_encoding()!=coding: 

577 videolog("paint_with_video_decoder: encoding changed from %s to %s", vd.get_encoding(), coding) 

578 self.do_clean_video_decoder() 

579 elif vd.get_width()!=enc_width or vd.get_height()!=enc_height: 

580 videolog("paint_with_video_decoder: video dimensions have changed from %s to %s", 

581 (vd.get_width(), vd.get_height()), (enc_width, enc_height)) 

582 self.do_clean_video_decoder() 

583 elif vd.get_colorspace()!=input_colorspace: 

584 #this should only happen on encoder restart, which means this should be the first frame: 

585 videolog.warn("Warning: colorspace unexpectedly changed from %s to %s", 

586 vd.get_colorspace(), input_colorspace) 

587 self.do_clean_video_decoder() 

588 if self._video_decoder is None: 

589 videolog("paint_with_video_decoder: new %s(%s,%s,%s)", 

590 decoder_module.Decoder, width, height, input_colorspace) 

591 vd = decoder_module.Decoder() 

592 vd.init_context(coding, enc_width, enc_height, input_colorspace) 

593 self._video_decoder = vd 

594 videolog("paint_with_video_decoder: info=%s", vd.get_info()) 

595 

596 img = vd.decompress_image(img_data, options) 

597 if not img: 

598 if options.intget("delayed", 0)>0: 

599 #there are further frames queued up, 

600 #and this frame references those, so assume all is well: 

601 fire_paint_callbacks(callbacks) 

602 else: 

603 fire_paint_callbacks(callbacks, False, 

604 "video decoder %s failed to decode %i bytes of %s data" % ( 

605 vd.get_type(), len(img_data), coding)) 

606 videolog.error("Error: decode failed on %s bytes of %s data", len(img_data), coding) 

607 videolog.error(" %sx%s pixels using %s", width, height, vd.get_type()) 

608 videolog.error(" frame options:") 

609 for k,v in options.items(): 

610 if isinstance(v, bytes): 

611 v = bytestostr(v) 

612 videolog.error(" %s=%s", bytestostr(k), v) 

613 return 

614 

615 x, y = self.gravity_adjust(x, y, options) 

616 self.do_video_paint(img, x, y, enc_width, enc_height, width, height, options, callbacks) 

617 if self._backing is None: 

618 self.close_decoder(True) 

619 

620 def do_video_paint(self, img, x, y, enc_width, enc_height, width, height, options, callbacks): 

621 target_rgb_formats = self.RGB_MODES 

622 #as some video formats like vpx can forward transparency 

623 #also we could skip the csc step in some cases: 

624 pixel_format = img.get_pixel_format() 

625 cd = self._csc_decoder 

626 if cd is not None: 

627 if cd.get_src_format()!=pixel_format: 

628 videolog("do_video_paint csc: switching src format from %s to %s", cd.get_src_format(), pixel_format) 

629 self.do_clean_csc_decoder() 

630 elif cd.get_dst_format() not in target_rgb_formats: 

631 videolog("do_video_paint csc: switching dst format from %s to %s", cd.get_dst_format(), target_rgb_formats) 

632 self.do_clean_csc_decoder() 

633 elif cd.get_src_width()!=enc_width or cd.get_src_height()!=enc_height: 

634 videolog("do_video_paint csc: switching src size from %sx%s to %sx%s", 

635 enc_width, enc_height, cd.get_src_width(), cd.get_src_height()) 

636 self.do_clean_csc_decoder() 

637 elif cd.get_dst_width()!=width or cd.get_dst_height()!=height: 

638 videolog("do_video_paint csc: switching src size from %sx%s to %sx%s", 

639 width, height, cd.get_dst_width(), cd.get_dst_height()) 

640 self.do_clean_csc_decoder() 

641 if self._csc_decoder is None: 

642 #use higher quality csc to compensate for lower quality source 

643 #(which generally means that we downscaled via YUV422P or lower) 

644 #or when upscaling the video: 

645 q = options.intget("quality", 50) 

646 csc_speed = int(min(100, 100-q, 100.0 * (enc_width*enc_height) / (width*height))) 

647 cd = self.make_csc(enc_width, enc_height, pixel_format, 

648 width, height, target_rgb_formats, csc_speed) 

649 videolog("do_video_paint new csc decoder: %s", cd) 

650 self._csc_decoder = cd 

651 rgb_format = cd.get_dst_format() 

652 rgb = cd.convert_image(img) 

653 videolog("do_video_paint rgb using %s.convert_image(%s)=%s", cd, img, rgb) 

654 img.free() 

655 assert rgb.get_planes()==0, "invalid number of planes for %s: %s" % (rgb_format, rgb.get_planes()) 

656 #make a new options dict and set the rgb format: 

657 paint_options = typedict(options) 

658 #this will also take care of firing callbacks (from the UI thread): 

659 def paint(): 

660 data = rgb.get_pixels() 

661 rowstride = rgb.get_rowstride() 

662 try: 

663 self.do_paint_rgb(rgb_format, data, 

664 x, y, width, height, width, height, rowstride, paint_options, callbacks) 

665 finally: 

666 rgb.free() 

667 self.idle_add(paint) 

668 

669 def paint_mmap(self, img_data, x, y, width, height, rowstride, options, callbacks): 

670 """ must be called from UI thread 

671 see _mmap_send() in server.py for details """ 

672 assert self.mmap_enabled 

673 data = mmap_read(self.mmap, *img_data) 

674 rgb_format = options.strget(b"rgb_format", b"RGB") 

675 #Note: BGR(A) is only handled by gl_window_backing 

676 x, y = self.gravity_adjust(x, y, options) 

677 self.do_paint_rgb(rgb_format, data, x, y, width, height, width, height, rowstride, options, callbacks) 

678 

679 def paint_scroll(self, img_data, options, callbacks): 

680 log("paint_scroll%s", (img_data, options, callbacks)) 

681 raise NotImplementedError("no paint scroll on %s" % type(self)) 

682 

683 

684 def draw_region(self, x, y, width, height, coding, img_data, rowstride, options, callbacks): 

685 """ dispatches the paint to one of the paint_XXXX methods """ 

686 try: 

687 assert self._backing is not None 

688 log("draw_region(%s, %s, %s, %s, %s, %s bytes, %s, %s, %s)", 

689 x, y, width, height, coding, len(img_data), rowstride, options, callbacks) 

690 coding = bytestostr(coding) 

691 options["encoding"] = coding #used for choosing the color of the paint box 

692 if INTEGRITY_HASH: 

693 verify_checksum(img_data, options) 

694 if coding == "mmap": 

695 self.idle_add(self.paint_mmap, img_data, x, y, width, height, rowstride, options, callbacks) 

696 elif coding in ("rgb24", "rgb32"): 

697 #avoid confusion over how many bytes-per-pixel we may have: 

698 rgb_format = options.strget(b"rgb_format") 

699 if not rgb_format: 

700 rgb_format = { 

701 "rgb24" : "RGB", 

702 "rgb32" : "RGBX", 

703 }.get(coding) 

704 if rowstride==0: 

705 rowstride = width * len(rgb_format) 

706 self.paint_rgb(rgb_format, img_data, x, y, width, height, rowstride, options, callbacks) 

707 elif coding in VIDEO_DECODERS: 

708 self.paint_with_video_decoder(VIDEO_DECODERS.get(coding), 

709 coding, 

710 img_data, x, y, width, height, options, callbacks) 

711 elif self.jpeg_decoder and coding=="jpeg": 

712 self.paint_jpeg(img_data, x, y, width, height, options, callbacks) 

713 elif coding == "webp": 

714 self.paint_webp(img_data, x, y, width, height, options, callbacks) 

715 elif coding in self._PIL_encodings: 

716 self.paint_image(coding, img_data, x, y, width, height, options, callbacks) 

717 elif coding == "scroll": 

718 self.paint_scroll(img_data, options, callbacks) 

719 else: 

720 self.do_draw_region(x, y, width, height, coding, img_data, rowstride, options, callbacks) 

721 except Exception: 

722 if self._backing is None: 

723 fire_paint_callbacks(callbacks, -1, "this backing is closed - retry?") 

724 else: 

725 raise 

726 

727 def do_draw_region(self, _x, _y, _width, _height, coding, _img_data, _rowstride, _options, callbacks): 

728 msg = "invalid encoding: '%s'" % coding 

729 log.error("Error: %s", msg) 

730 fire_paint_callbacks(callbacks, False, msg)