Skip to content

Commit

Permalink
#1232: add ability to record scroll data and replay it
Browse files Browse the repository at this point in the history
git-svn-id: https://xpra.org/svn/Xpra/trunk@12914 3bb7dfac-3a0b-4e04-842a-767bc560f471
  • Loading branch information
totaam committed Jun 26, 2016
1 parent 4712f8a commit 8558303
Show file tree
Hide file tree
Showing 2 changed files with 214 additions and 5 deletions.
184 changes: 184 additions & 0 deletions src/tests/xpra/clients/test_scroll_replay_dirs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
#!/usr/bin/env python
# This file is part of Xpra.
# Copyright (C) 2016 Antoine Martin <antoine@devloop.org.uk>
# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
# later version. See the file COPYING for details.

from cStringIO import StringIO
import sys, os
from xpra.log import Logger
log = Logger()

from xpra.util import typedict
from tests.xpra.clients.fake_gtk_client import FakeGTKClient, gtk_main
from xpra.codecs.loader import load_codecs

load_codecs(encoders=False, decoders=True, csc=False)


class WindowAnim(object):

def __init__(self, window_class, client, wid, W=630, H=480):
self.wid = wid
self.window = window_class(client, None, wid, 10, 10, W, H, W, H, typedict({}), False, typedict({}), 0, None)
self.window.show()
self.paint_rect(0, 0, W, H, chr(255)*4*W*H)

def paint_rect(self, x=200, y=200, w=32, h=32, img_data=None, options={}):
assert img_data
self.window.draw_region(x, y, w, h, "rgb32", img_data, w*4, 0, typedict(options), [])

def paint_png(self, pngdata, x, y, w, h, flush=0):
print("paint_png(%i bytes, %i, %i, %i, %i, %i)" % (len(pngdata), x, y, w, h, flush))
self.window.draw_region(x, y, w, h, "png", pngdata, w*4, 0, typedict({"flush" : flush}), [])

def scroll(self, x, y, w, h, xdelta, ydelta, flush=0):
W, H = self.window.get_size()
print("scroll%s" % ((x, y, w, h, xdelta, ydelta, flush), ))
scrolls = [(x, y, w, h, xdelta, ydelta)]
self.window.draw_region(0, 0, W, H, "scroll", scrolls, W*4, 0, typedict({"flush" : flush}), [])

def compare_png(self, actual, x, y, w, h):
backing = self.window._backing._backing
from gtk import gdk
pixbuf = gdk.Pixbuf(gdk.COLORSPACE_RGB, True, 8, w, h)
try:
pixbuf.get_from_drawable(backing, backing.get_colormap(), 0, 0, 0, 0, w, h)
except Exception as e:
print("cannot get drawable from %s: %s" % (backing, e))
return
from PIL import Image
result = Image.frombytes("RGBA", (w, h), pixbuf.get_pixels(), "raw", "RGBA", pixbuf.get_rowstride()).convert("RGB")
expected = PIL_open(actual)
from PIL import ImageChops
diff = ImageChops.difference(result, expected)
#print("compare_png: %s -> %s -> %s" % (backing, pixbuf, result))
output = StringIO()
diff.save(output, format="png")
contents = output.getvalue()
output.close()
self.paint_png(contents, x, y, w, h)


def parseintlist(v):
v = v.strip("(").strip(")")
return [int(i.strip(" ")) for i in v.split(",")]

def PIL_open(imagedata):
from PIL import Image
data = StringIO(imagedata)
return Image.open(data)

def frame_replay(dirname, windows, index=0, done_cb=None):
def loadbfile(filename):
return open(os.path.join(dirname, filename)).read()
#print("frame_replay(%s, %s)" % (dirname, show_old))
initial_image = loadbfile("old.png")
img = PIL_open(initial_image)
scrolls = []
#load scroll data:
try:
scroll_data = loadbfile("scrolls.txt")
for v in scroll_data.splitlines():
scrolls.append(parseintlist(v))
except:
pass
#load paint instructions:
paints = []
try:
paint_data = loadbfile("replay.txt")
for v in paint_data.splitlines():
filename,coords = v.split(" ", 1)
file_data = loadbfile(filename)
paints.append((file_data, parseintlist(coords)))
except:
pass
def print_start():
print("START OF %s" % dirname)
actions = [(print_start, )]
if index==0:
for window in windows:
actions.append([window.paint_png, initial_image, 0, 0] + list(img.size))
flush = len(paints) + len(scrolls)
if paints or scrolls:
#print("loaded all replay data:")
#print("initial image: %i bytes (%s)" % (len(initial_image), img.size))
#print("scrolls: %s" % (scrolls, ))
#print("paints: %s" % ([(len(v[0]), v[1]) for v in paints]))
for v in scrolls:
flush -= 1
for window in windows:
actions.append([window.scroll]+v+[flush])
for pngdata, coords in paints:
flush -= 1
for window in windows:
actions.append([window.paint_png, pngdata] + list(coords)+[flush])
actual = loadbfile("new.png")
for window in windows:
actions.append([window.compare_png, actual, 0, 0] + list(img.size))
for window in windows:
actions.append([window.paint_png, actual, 0, 0] + list(img.size))
def print_end():
print("END OF %s" % dirname)
actions.append((print_end, ))
return actions


def main():
dirname = sys.argv[1]
if not os.path.exists(dirname):
print("cannot find %s" % dirname)
sys.exit(1)
skip = 0
try:
skip = int(sys.argv[2])
except:
pass
count = 999
try:
count = int(sys.argv[3])
except:
pass
W = 1024
H = 1200
window_classes = []
try:
from xpra.client.gl.gtk2.gl_client_window import GLClientWindow
window_classes.append(GLClientWindow)
except Exception as e:
print("no opengl window: %s" % e)
try:
from xpra.client.gtk2.border_client_window import BorderClientWindow
window_classes.append(BorderClientWindow)
except Exception as e:
print("no pixmap window: %s" % e)
client = FakeGTKClient()
client.log_events = False
windows = []
for window_class in window_classes:
windows.append(WindowAnim(window_class, client, 1, W, H))

actions = []
all_dirs = sorted(os.listdir(dirname))[skip:skip+count]
print("all dirs=%s", (all_dirs,))
for i,d in enumerate(all_dirs):
d = os.path.join(dirname, d)
actions += frame_replay(d, windows, i)

actions = list(reversed(actions))
def handle_key_action(window, event):
if event.pressed:
a = actions.pop()
#print("handle_key_action: action=%s" % (a[0], ))
a[0](*a[1:])

#print("actions=%s" % ([x[0] for x in actions], ))
client.handle_key_action = handle_key_action
try:
gtk_main()
except KeyboardInterrupt:
pass


if __name__ == "__main__":
main()
35 changes: 30 additions & 5 deletions src/xpra/server/window/window_video_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,11 @@ def envint(name, d):
SCALING = os.environ.get("XPRA_SCALING", "1")=="1"
SCALING_HARDCODED = parse_scaling_value(os.environ.get("XPRA_SCALING_HARDCODED", ""))

VIDEO_SUBREGION = os.environ.get("XPRA_VIDEO_SUBREGION", "1")=="1"
B_FRAMES = os.environ.get("XPRA_B_FRAMES", "1")=="1"
VIDEO_SKIP_EDGE = os.environ.get("XPRA_VIDEO_SKIP_EDGE", "0")=="1"
SCROLL_ENCODING = os.environ.get("XPRA_SCROLL_ENCODING", "1")=="1"
VIDEO_SUBREGION = envint("XPRA_VIDEO_SUBREGION", 1)==1
B_FRAMES = envint("XPRA_B_FRAMES", 1)==1
VIDEO_SKIP_EDGE = envint("XPRA_VIDEO_SKIP_EDGE", 0)==1
SCROLL_ENCODING = envint("XPRA_SCROLL_ENCODING", 0)==1
SAVE_SCROLL = envint("XPRA_SAVE_SCROLL", 0)==1


class WindowVideoSource(WindowSource):
Expand Down Expand Up @@ -1314,6 +1315,21 @@ def setup_pipeline_option(self, width, height, src_format,
def encode_scrolling(self, last_image, image, distances, old_csums, csums, options):
tstart = time.time()
scrolllog("encode_scrolling(%s, %s, {..}, [], [], %s)", last_image, image, options)
if SAVE_SCROLL:
now = time.time()
dirname = "scroll-%i" % int(1000*now)
os.mkdir(dirname)
def save_data(filename, data):
path = os.path.join(dirname, filename)
with open(path, "wb") as f:
f.write(data)
def save_pic(basename, img):
coding, data = self.video_fallback(img, options)[:2]
filename = "%s.%s" % (basename, coding)
save_data(filename, data.data)
return filename
save_pic("old", last_image)
save_pic("new", image)
x, y, w, h = image.get_geometry()[:4]
yscroll_values = []
max_scroll_regions = 50
Expand Down Expand Up @@ -1365,16 +1381,20 @@ def encode_scrolling(self, last_image, image, distances, old_csums, csums, optio
damaged_lines = sorted(list(remaining))
non_scroll = consecutive_lines(damaged_lines)
scrolllog(" non scroll: %i packets: %s", len(non_scroll), non_scroll)
flush = len(non_scroll)
nscount = len(non_scroll)
flush = nscount
#send as scroll paints packets:
if scrolls:
client_options = options.copy()
if flush>0:
client_options["flush"] = flush
packet = self.make_draw_packet(x, y, w, h, "scroll", LargeStructure("scroll data", scrolls), 0, client_options)
if SAVE_SCROLL:
save_data("scrolls.txt", "\n".join(repr(v) for v in scrolls))
self.queue_damage_packet(packet)
#send the rest as rectangles:
if non_scroll:
replay = []
for start, count in non_scroll:
sub = image.get_sub_image(0, start, w, count)
flush -= 1
Expand All @@ -1389,6 +1409,11 @@ def encode_scrolling(self, last_image, image, distances, old_csums, csums, optio
client_options["flush"] = flush
packet = self.make_draw_packet(sub.get_x(), sub.get_y(), outw, outh, coding, data, outstride, client_options)
self.queue_damage_packet(packet)
if SAVE_SCROLL:
filename = save_pic("%s" % (nscount-flush), sub)
replay.append("%s %s" % (filename, (sub.get_x(), sub.get_y(), outw, outh)))
if SAVE_SCROLL:
save_data("replay.txt", "\n".join(replay))
assert flush==0
tend = time.time()
scrolllog("scroll encoding took %ims", (tend-tstart)*1000)
Expand Down

0 comments on commit 8558303

Please sign in to comment.