xpra icon
Bug tracker and wiki

Ticket #1426: scroll-cythonized-v3.patch

File scroll-cythonized-v3.patch, 34.5 KB (added by Antoine Martin, 4 years ago)

almost done - almost renders correctly

  • unittests/unit/server/motion_test.py

     
    11#!/usr/bin/env python
    22# This file is part of Xpra.
    3 # Copyright (C) 2011-2014 Antoine Martin <antoine@devloop.org.uk>
     3# Copyright (C) 2016-2017 Antoine Martin <antoine@devloop.org.uk>
    44# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
    55# later version. See the file COPYING for details.
    66
     
    2020
    2121class TestMotion(unittest.TestCase):
    2222
     23        def calculate_distances(self, array1, array2, min_hits=2, max_distance=1):
     24                sd = motion.scroll_distances(array1, array2, max_distance)
     25                return sd.get_best_scroll_values(min_hits)
     26
    2327        def test_match_distance(self):
    2428                def t(a1, a2, distance, matches):
    25                         lines = motion.match_distance(a1, a2, distance)
    26                         assert len(lines)==matches, "expected %i matches for distance=%i but got %i for a1=%s, a2=%s, result=%s" % (matches, distance, len(lines), a1, a2, lines)
     29                        sd = motion.scroll_distances(a1, a2)
     30                        line_defs = sd.match_distance(distance)
     31                        linecount = sum(line_defs.values())
     32                        assert linecount==matches, "expected %i matches for distance=%i but got %i for a1=%s, a2=%s, result=%s" % (matches, distance, linecount, a1, a2, line_defs)
    2733                for N in (1, 10, 100):
    28                         a = range(N)
     34                        a = range(1, N+1)
    2935                        t(a, a, 0, N)           #identity: all match
    3036
    31                         a = [0]*N
     37                        a = [1]*N
    3238                        t(a, a, 0, N)
    3339                        for M in range(N):
    3440                                t(a, a, M, N-M)
    3541
     42                #from a1 to a2: shift by 2, get 2 hits
     43                t([3, 4, 5, 6], [1, 2, 3, 4], 2, 2)
    3644                #from a2 to a1: shift by -2, get 2 hits
    37                 t([0, 1, 2, 3], [2, 3, 4, 5], -2, 2)
     45                t([1, 2, 3, 4], [3, 4, 5, 6], -2, 2)
    3846                N = 100
    39                 a1 = range(N)
    40                 for M in (1, 2, 10, 100):
     47                S = 1
     48                a1 = range(S, S+N)
     49                for M in (2, 3, 10, 100):
    4150                        a2 = range(M, M+N)
    42                         t(a1, a2, -M, N-abs(M))
    43                         a2 = range(-M, N-M)
    44                         t(a1, a2, M, N-abs(M))
    45                 for M in (-10, -20):
    46                         a2 = range(M, N+M)
    47                         t(a1, a2, -M, N-abs(M))
     51                        t(a1, a2, S-M, S+N-M)
     52                        t(a2, a1, M-S, S+N-M)
    4853
    49         def test_consecutive_lines(self):
    50                 def f(v):
    51                         try:
    52                                 motion.consecutive_lines(v)
    53                         except:
    54                                 pass
    55                         else:
    56                                 raise Exception("consecutive_lines should have failed for value %s" % v)
    57                 f(None)
    58                 f("")
    59                 f([])
    60                 f(1)
    61                 def t(v, e):
    62                         r = motion.consecutive_lines(v)
    63                         assert r==e, "expected %s but got %s for input=%s" % (e, r, v)
    64                 t([5, 10], [(5, 1), (10, 1)])
    65                 t([5], [(5, 1)])
    66                 t([1,2,3], [(1,3)])
    67                 t(range(100), [(0, 100),])
    68                 t([1,2,3,100,200,201], [(1,3), (100, 1), (200, 2)])
    69 
    7054        def test_calculate_distances(self):
    7155                array1 = [crc32(str(x)) for x in (1234, "abc", 99999)]
    72                 array2 = array1
    73                 d = motion.calculate_distances(array1, array2, 1)
    74                 assert len(d)==1 and d[0]==3
     56                array2 = array1[:]
     57                d = self.calculate_distances(array1, array2, 1)
     58                assert len(d)==1 and sum(d[0].values())==len(array1), "expected %i matches but got %s" % (len(array1), d[0])
    7559
    76                 array1 = range(0, 3)
    77                 array2 = range(1, 4)
    78                 d = motion.calculate_distances(array1, array2, 1)
    79                 assert len(d)==1, "expected 1 match but got: %s" % d
    80                 assert d.get(1)==2, "expected distance of 1 with 2 hits but got: %s" % d
     60                array1 = range(1, 5)
     61                array2 = range(2, 6)
     62                d = self.calculate_distances(array1, array2)
     63                assert len(d)==1, "expected 1 match but got: %s" % len(d)
     64                common = set(array1).intersection(set(array2))
     65                assert -1 in d, "expected distance of -1 but got: %s" % d.keys()
     66                linecount = sum(d.get(-1, {}).values())
     67                assert linecount==len(common), "expected %i hits but got: %s" % (len(common), linecount)
    8168                def cdf(v1, v2):
    8269                        try:
    83                                 motion.calculate_distances(v1, v2, 1)
     70                                self.calculate_distances(v1, v2, 1)
    8471                        except:
    8572                                return
    8673                        raise Exception("calculate_distances should have failed for values: %s" % (v1, v2))
     
    8875                cdf([], None)
    8976                cdf(None, [])
    9077                cdf([1, 2], [1])
    91                 assert len(motion.calculate_distances([], [], 1))==0
    9278
    9379                #performance:
    9480                N = 4096
     
    9581                start = time.time()
    9682                array1 = range(N)
    9783                array2 = [N*2-x*2 for x in range(N)]
    98                 d = motion.calculate_distances(array1, array2, 1)
     84                d = self.calculate_distances(array1, array2, 1)
    9985                end = time.time()
    10086                if SHOW_PERF:
    10187                        print("calculate_distances %4i^2 in %5.1f ms" % (N, (end-start)*1000))
     
    137123                                print("\nCRC_Image %ix%i (%.1fMB) in %4.2f ms" % (W, H, len(buf2)//1024//1024, 1000.0*(end-start)))
    138124                        assert len(ov2)==H
    139125                        start = time.time()
    140                         distances = motion.calculate_distances(ov1, ov2, min_score=1)
     126                        distances = self.calculate_distances(ov1, ov2, min_hits=1)
    141127                        end = time.time()
    142128                        if SHOW_PERF:
    143129                                print("calculate_distances %4i^2 in %5.2f ms" % (H, 1000.0*(end-start)))
    144                         linecount = distances.get(N, 0)
     130                        line_defs = distances.get(-N, {})
     131                        linecount = sum(line_defs.values())
    145132                        assert linecount>0, "could not find distance %i" % N
    146133                        assert linecount == (H-N), "expected to match %i lines but got %i" % (H-N, linecount)
    147134                if False:
     
    154141                        print("ov1:\n%s" % (ov1, ))
    155142                        print("ov2:\n%s" % (ov2, ))
    156143
     144        def test_csum_data(self):
     145                a1=[
     146                        5992220345606009987L, 15040563112965825180L, 420530012284267555L, 3380071419019115782L, 14243596304267993264L, 834861281570233459L, 10803583843784306120L, 1379296002677236226L,
     147                        11874402007024898787L, 18061820378193118025L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L,
     148                        14669207905734636057L, 14669207905734636057L, 6048597477520792617L, 2736806572525204051L, 16630099595908746458L, 10194355114249600963L, 16726784880639428445L, 10866892264854763364L,
     149                        6367321356510949102L, 16626509354687956371L, 6309605599425761357L, 6893409879058778343L, 5414245501850544038L, 10339135854757169820L, 8701041795744152980L, 3604633436491088815L,
     150                        9865399393235410477L, 10031306284568036792L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L,
     151                        14669207905734636057L, 14669207905734636057L, 11266963446837574547L, 17157005122993541799L, 5218869126146608853L, 13274228147453099388L, 16342723934713827717L, 2435034235422505275L,
     152                        3689766606612767057L, 13721141386368216492L, 14859793948180065358L, 6883776362280179367L, 14582348771255332968L, 15418692344756373599L, 10241123668249748621L, 197976484773286461L,
     153                        14610077842739908751L, 9629342716869811747L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L,
     154                        14669207905734636057L, 14669207905734636057L, 6301677547777858738L, 13481745547040629090L, 11082728931134194933L, 3515047519092751608L, 17530992646520472518L, 11525573497958613731L,
     155                        6186650688264051723L, 10053681394182111520L, 7507461626261938488L, 3136410141592758381L, 18320341500820189028L, 7224279069641644876L, 76220613438872403L, 12174575413544881100L,
     156                        7769327179604108765L, 4993163530803732307L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L,
     157                        14669207905734636057L, 14669207905734636057L, 1011212212406598056L, 12369511552952147752L, 17332471340354818353L, 5562967289984763417L, 7276816103432910616L, 9095502394548196500L,
     158                        3966866363266810705L, 15115893782344445994L, 2470115778756702218L, 11300572931034497831L, 13356453083734411092L, 12682463388000998283L, 12461900100761490812L, 16565659067973398797L,
     159                        16700371844333341655L, 13475749720883007409L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L, 15095743182479501355L, 16652551598896547263L,
     160                        18117428461752083731L, 16517651160080181273L, 16482769665263024512L, 16482769665263024512L, 16482769665263024512L, 16482769665263024512L, 16482769665263024512L, 16482769665263024512L,
     161                        16482769665263024512L, 16482769665263024512L, 16482769665263024512L, 16482769665263024512L, 2620400469557574299L, 7552116755125697612L, 3191732720857892986L, 15697817096682717297L,
     162                        14669207905734636057L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L, 14669207905734636057L,
     163                        ]
     164                a2 = [
     165                        16517651160080181273, 16482769665263024512, 16482769665263024512, 16482769665263024512, 16482769665263024512, 16482769665263024512, 16482769665263024512, 16482769665263024512,
     166                        16482769665263024512, 16482769665263024512, 16482769665263024512, 2620400469557574299, 7552116755125697612, 3191732720857892986, 15697817096682717297, 14669207905734636057,
     167                        14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057,
     168                        14669207905734636057, 7425237873317005741, 15881577514219781533, 5244943483479698162, 1645884179624549962, 6833306483329956671, 3142507118889544939, 14496593126061659900,
     169                        4782446320116037220, 11121580325383588737, 5128902123802403342, 14539804846999948736, 3999126996485638007, 6071163207581089360, 275311871111368509, 1419512211527079444,
     170                        16496147506624837932, 9366935943282992292, 16641602392096942222, 5312414525355881355, 6512670471206739810, 14669207905734636057, 14669207905734636057, 9515221130600033946,
     171                        14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057,
     172                        16962147477217322879, 17777684172941730501, 5134598006302276024, 4495650412094508491, 14496320858648784912, 5882882193233282408, 13142401013874562815, 17213868142308207279,
     173                        5589927236057965940, 4529401611344340209, 3205874171513572790, 9555164747562437240, 14669207905734636057, 14669207905734636057, 14669207905734636057, 9080427549593249618,
     174                        14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057, 8165205005918492527,
     175                        13352578771313229684, 11590125678725701957, 2006171165294962460, 5731472049560910928, 7815231195191982982, 5992220345606009987, 15040563112965825180, 420530012284267555,
     176                        3380071419019115782, 14243596304267993264, 834861281570233459, 10803583843784306120, 1379296002677236226, 11874402007024898787, 18061820378193118025, 14669207905734636057,
     177                        14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057, 6048597477520792617,
     178                        2736806572525204051, 16630099595908746458, 10194355114249600963, 16726784880639428445, 10866892264854763364, 6367321356510949102, 16626509354687956371, 6309605599425761357,
     179                        6893409879058778343, 5414245501850544038, 10339135854757169820, 8701041795744152980, 3604633436491088815, 9865399393235410477, 10031306284568036792, 14669207905734636057,
     180                        14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057, 14669207905734636057, 11266963446837574547,
     181                        17157005122993541799, 5218869126146608853, 13274228147453099388, 16342723934713827717, 2435034235422505275, 3689766606612767057, 13721141386368216492, 14859793948180065358,
     182                        ]
     183                #distances = motion.scroll_distances(a1[100:400], a2[100:400], 2, 1000)
     184                distances = motion.scroll_distances(a1, a2, 2, 1000)
     185                scroll, count = distances.get_best_match()
     186                wh = len(a1)
     187                print("best match: %s" % ((scroll, count),))
     188                x, y, w, h = 0, 0, 1050, 1151
     189                raw_scroll = distances.get_best_scroll_values()
     190                #non_scroll = distances.get_remaining_areas()
     191                scrolls = []
     192                def h(v):
     193                        return hex(v).lstrip("0x").rstrip("L")
     194                for i in range(wh):
     195                        print("%2i:     %16s    %16s" % (i, h(a1[i]), h(a2[i])))
     196                for scroll, line_defs in raw_scroll.items():
     197                        if scroll==0:
     198                                continue
     199                        for line, count in line_defs.items():
     200                                assert y+line+scroll>=0, "cannot scroll rectangle by %i lines from %i+%i" % (scroll, y, line)
     201                                assert y+line+scroll<=wh, "cannot scroll rectangle %i high by %i lines from %i+%i (window height is %i)" % (count, scroll, y, line, wh)
     202                                scrolls.append((x, y+line, w, count, 0, scroll))
     203
     204
     205
    157206def main():
    158207        if motion:
    159208                unittest.main()
  • xpra/server/window/motion.pyx

     
    99
    1010import os
    1111import time
     12import collections
    1213
    1314from xpra.util import envbool
    1415from xpra.log import Logger
    15 logger = Logger("encoding")
     16log = Logger("encoding", "scroll")
    1617
    17 from xpra.buffers.membuf cimport memalign
    18 from xpra.buffers.membuf cimport object_as_buffer
     18from xpra.buffers.membuf cimport memalign, object_as_buffer
    1919
    20 import zlib
     20cdef int DEBUG = envbool("XPRA_SCROLL_DEBUG", False)
    2121hashfn = None
    2222if envbool("XPRA_XXHASH", True):
    2323    try:
     
    2525        def hashfn(x):
    2626            return xxhash.xxh64(x).intdigest()
    2727    except ImportError as e:
    28         logger.warn("Warning: xxhash python bindings not found")
     28        log.warn("Warning: xxhash python bindings not found")
    2929else:
    30     logger.warn("Warning: xxhash disabled")
     30    log.warn("Warning: xxhash disabled")
    3131if hashfn is None:
    32     logger.warn(" no scrolling detection")
     32    log.warn(" no scrolling detection")
    3333
    3434
    35 cdef extern from "math.h":
    36     double log(double x)
     35from libc.stdint cimport int32_t, uint8_t, uint16_t, int16_t, uint32_t, int64_t
    3736
    38 from libc.stdint cimport int32_t, uint8_t, uint32_t, int64_t
    39 
    40 cdef extern from "stdlib.h":
    41     int abs(int number)
    42 
    4337cdef extern from "string.h":
    4438    void free(void * ptr) nogil
    4539    void *memset(void * ptr, int value, size_t num) nogil
    46     int memcmp(const void *a1, const void *a2, size_t size)
    4740
    4841
    4942def CRC_Image(pixels, unsigned int width, unsigned int height, unsigned int rowstride, unsigned char bpp=4):
     
    7366    #assert v>=0, "invalid int to cast: %s" % v
    7467    return v
    7568
    76 def calculate_distances(array1, array2, int min_score=0, int max_distance=1000):
    77     #print("calculate_distances(..)")
    78     assert len(array1)==len(array2)
    79     cdef int l = len(array1)
    80     cdef int i, y1, y2, miny, maxy, d
    81     #we want fast array access,
    82     #so cache both arrays in C arrays:
    83     assert sizeof(int64_t)==64//8, "uint64_t is not 64-bit: %i!" % sizeof(int64_t)
    84     cdef size_t asize = l*(sizeof(int64_t))
    85     cdef int64_t *a1 = NULL
    86     cdef int64_t *a2 = NULL
    87     cdef int64_t a2v = 0
    88     cdef int32_t *distances = NULL
    89     #print("calculate_distances(%s, %s, %i, %i)" % (array1, array2, elen, min_score))
    90     try:
    91         a1 = <int64_t*> memalign(asize)
    92         a2 = <int64_t*> memalign(asize)
    93         assert a1!=NULL and a2!=NULL, "failed to allocate %i bytes of scroll array memory" % asize
    94         for i in range(l):
    95             a1[i] = castint64(array1[i])
    96             a2[i] = castint64(array2[i])
     69cdef inline da(int64_t *a, uint16_t l):
     70    return [a[i] for i in range(l)]
     71
     72cdef inline dd(uint16_t *d, uint16_t l):
     73    return [d[i] for i in range(l)]
     74
     75cdef inline ds(int16_t *d, uint16_t l):
     76    return [d[i] for i in range(l)]
     77
     78
     79assert sizeof(int64_t)==64//8, "uint64_t is not 64-bit: %i!" % sizeof(int64_t)
     80
     81
     82cdef class ScrollDistances:
     83
     84    cdef object __weakref__
     85    #for each distance, keep track of the hit count:
     86    cdef uint16_t* distances
     87    cdef uint16_t l
     88    cdef int64_t *a1
     89    cdef int64_t *a2
     90
     91    def init(self, array1, array2, uint16_t max_distance=1000):
     92        assert len(array1)==len(array2)
     93        assert len(array1)<2**15 and len(array1)>0, "invalid array length: %i" % len(array1)
     94        self.l = len(array1)
     95        self.distances = <uint16_t*> memalign(2*self.l*sizeof(uint16_t))
     96        cdef size_t asize = self.l*(sizeof(int64_t))
     97        self.a1 = <int64_t*> memalign(asize)
     98        self.a2 = <int64_t*> memalign(asize)
     99        assert self.distances!=NULL and self.a1!=NULL and self.a2!=NULL, "scroll memory allocation failed"
     100        for i in range(self.l):
     101            self.a1[i] = castint64(array1[i])
     102            self.a2[i] = castint64(array2[i])
    97103        #now compare all the values
    98         distances = <int32_t*> memalign(2*l*sizeof(int32_t))
    99         assert distances!=NULL
     104        self.calculate(max_distance)
     105
     106    def __repr__(self):
     107        return "ScrollDistances(%i)" % self.l
     108
     109    cdef calculate(self, uint16_t max_distance=1000):
     110        cdef int64_t *a1 = self.a1
     111        cdef int64_t *a2 = self.a2
     112        cdef uint16_t l = self.l
     113        cdef uint16_t y1, y2
     114        cdef uint16_t miny=0, maxy=0
     115        cdef int64_t a2v
    100116        with nogil:
    101             memset(<void*> distances, 0, 2*l*sizeof(int32_t))
     117            memset(self.distances, 0, 2*self.l*sizeof(uint16_t))
    102118            for y2 in range(l):
    103                 miny = max(0, y2-max_distance)
    104                 maxy = min(l, y2+max_distance)
     119                #miny = max(0, y2-max_distance):
     120                if y2>max_distance:
     121                    miny = y2-max_distance
     122                else:
     123                    miny = 0
     124                #maxy = min(l, y2+max_distance)
     125                if y2+max_distance<l:
     126                    maxy = y2+max_distance
     127                else:
     128                    maxy = l
    105129                a2v = a2[y2]
    106130                if a2v==0:
    107131                    continue
     
    108132                for y1 in range(miny, maxy):
    109133                    if a1[y1]==a2v:
    110134                        #distance = y1-y2
    111                         distances[l+y1-y2] += 1
    112         r = {}
    113         for i in range(2*l):
    114             d = distances[i]
    115             if abs(d)>=min_score:
    116                 r[i-l] = d
    117         return r
    118     finally:
    119         if a1!=NULL:
    120             free(a1)
    121         if a2!=NULL:
    122             free(a2)
    123         if distances!=NULL:
    124             free(distances)
     135                        self.distances[l-(y1-y2)] += 1
     136        if DEBUG:
     137            log("ScrollDistance: l=%i, calculate(%s, %s, %i)=%s", self.l, da(self.a1, self.l), da(self.a2, self.l), max_distance, dd(self.distances, self.l*2))
    125138
    126 def match_distance(array1, array2, int distance):
    127     assert len(array1)==len(array2)
    128     l = len(array1)
    129     if distance>=0:
    130         return [i for i,v in enumerate(array1) if (i+distance)<l and array2[i+distance]==v]
    131     distance = abs(distance)
    132     return [i+distance for i,v in enumerate(array2) if (i+distance)<l and array1[i+distance]==v]
     139    def get_best_scroll_values(self, uint16_t min_hits=2):
     140        DEF MAX_MATCHES = 20
     141        cdef uint16_t m_arr[MAX_MATCHES]    #number of hits
     142        cdef int16_t s_arr[MAX_MATCHES]     #scroll distance
     143        cdef int16_t i
     144        cdef uint8_t j
     145        memset(m_arr, 0, MAX_MATCHES*sizeof(uint16_t))
     146        memset(s_arr, 0, MAX_MATCHES*sizeof(int16_t))
     147        cdef int16_t low = 0
     148        cdef int16_t matches
     149        cdef uint16_t* distances = self.distances
     150        cdef uint16_t l = self.l
     151        with nogil:
     152            for i in range(2*l):
     153                matches = distances[i]
     154                if matches>low and matches>min_hits:
     155                    #add this candidate match to the arrays:
     156                    for j in range(MAX_MATCHES):
     157                        if m_arr[j]==low:
     158                            break
     159                    m_arr[j] = matches
     160                    s_arr[j] = i-l
     161                    #find the new lowest value:
     162                    low = matches
     163                    for j in range(MAX_MATCHES):
     164                        if m_arr[j]<low:
     165                            low = m_arr[j]
     166                            if low==0:
     167                                break
     168        if DEBUG:
     169            log("get_best_scroll_values: arrays: matches=%s, scroll=%s", dd(m_arr, MAX_MATCHES), ds(s_arr, MAX_MATCHES))
     170        #first collect the list of distances sorted by highest number of matches:
     171        #(there can be more than one distance value for each match count):
     172        scroll_hits = {}
     173        for i in range(MAX_MATCHES):
     174            if m_arr[i]>min_hits:
     175                scroll_hits.setdefault(m_arr[i], []).append(s_arr[i])
     176        if DEBUG:
     177            log("scroll hits=%s", scroll_hits)
     178        #return a dict with the scroll distance as key,
     179        #and the list of matching lines in a dictionary:
     180        # {line-start : count, ..}
     181        #this is destructive as we clear the checksums after use in match_distance()
     182        scrolls = collections.OrderedDict()
     183        cdef uint16_t m
     184        #starting with the highest matches
     185        for m in reversed(sorted(scroll_hits.keys())):
     186            v = scroll_hits[m]
     187            for scroll in v:
     188                #find matching lines:
     189                line_defs = self.match_distance(scroll)
     190                if line_defs:
     191                    scrolls[scroll] = line_defs
     192        return scrolls
    133193
    134 def consecutive_lines(line_numbers):
    135     #print("line_numbers_to_rectangles(%s)" % (line_numbers, ))
    136     #aggregates consecutive lines:
    137     #[1,2,3,4,8,9] -> [(1,3), (8,1)]
    138     assert len(line_numbers)>0
    139     if len(line_numbers)==1:
    140         return [(line_numbers[0], 1)]
    141     cdef int start = line_numbers[0]
    142     cdef int last = start
    143     cdef int line = 0
    144     r = []
    145     for line in line_numbers[1:]:
    146         if line!=last+1:
    147             #new rectangle
    148             r.append((start, last-start+1))
    149             start = line
    150         last = line
    151     if last!=line_numbers[0]:
    152         r.append((start, last-start+1))
    153     return r
     194    def get_remaining_areas(self):
     195        #all the lines which have not been zeroed out
     196        #when we matched them in match_distance
     197        cdef int64_t *a2 = self.a2
     198        cdef uint16_t i, start = 0, count = 0
     199        line_defs = collections.OrderedDict()
     200        for i in range(self.l):
     201            if a2[i]!=0:
     202                if count==0:
     203                    start = i
     204                count += 1
     205            elif count>0:
     206                line_defs[start] = count
     207                count = 0
     208        if count>0:
     209            line_defs[start] = count
     210        return line_defs
     211
     212    def match_distance(self, int16_t distance):
     213        """ find the lines that match the given scroll distance """
     214        cdef int64_t *a1 = self.a1
     215        cdef int64_t *a2 = self.a2
     216        cdef char swap = 0
     217        if distance<0:
     218            #swap order:
     219            swap = 1
     220            a1 = self.a2
     221            a2 = self.a1
     222            distance = -distance
     223        if DEBUG:
     224            log("match_distance(%i) l=%i, a1=%s, a2=%s", distance, self.l, da(a1, self.l), da(a2, self.l))
     225        assert distance<self.l, "invalid distance %i for size %i" % (distance, self.l)
     226        cdef uint16_t i, start = 0, count = 0
     227        line_defs = collections.OrderedDict()
     228        for i in range(self.l-distance):
     229            #if DEBUG:
     230            #    log("%i: a1=%i / a2=%i", i, a1[i], a2[i+distance])
     231            if a1[i]!=0 and a1[i]==a2[i+distance]:
     232                #if DEBUG:
     233                #    log("match at %i: %i", i, a1[i])
     234                if count==0:
     235                    #first match
     236                    if swap:
     237                        start = i+distance
     238                    else:
     239                        start = i
     240                count += 1
     241                #mark the target line as dealt with:
     242                if swap:
     243                    a1[i] = 0
     244                else:
     245                    a2[i+distance] = 0
     246            elif count>0:
     247                #we had a match
     248                line_defs[start] = count
     249                count = 0
     250        if count>0:
     251            #last few lines ended as a match:
     252            line_defs[start] = count
     253        if DEBUG:
     254            log("match_distance(%i)=%s", distance, line_defs)
     255        return line_defs
     256
     257
     258    def get_best_match(self):
     259        cdef int16_t max = 0
     260        cdef int d = 0
     261        cdef unsigned int i
     262        for i in range(2*self.l):
     263            if self.distances[i]>max:
     264                max = self.distances[i]
     265                d = i-self.l
     266        return d, max
     267
     268    def __dealloc__(self):
     269        cdef void* ptr = <void*> self.distances
     270        if ptr:
     271            self.distances = NULL
     272            free(ptr)
     273        ptr = <void*> self.a1
     274        if ptr:
     275            self.a1 = NULL
     276            free(ptr)
     277        ptr = <void*> self.a2
     278        if ptr:
     279            self.a2 = NULL
     280            free(ptr)
     281       
     282
     283def scroll_distances(array1, array2, unsigned int min_score=0, uint16_t max_distance=1000):
     284    cdef ScrollDistances sd = ScrollDistances()
     285    sd.init(array1, array2, max_distance)
     286    return sd
  • xpra/server/window/window_video_source.py

     
    1414from xpra.codecs.codec_constants import TransientCodecException, RGB_FORMATS, PIXEL_SUBSAMPLING
    1515from xpra.server.window.window_source import WindowSource, STRICT_MODE, AUTO_REFRESH_SPEED, AUTO_REFRESH_QUALITY
    1616from xpra.server.window.region import rectangle, merge_all          #@UnresolvedImport
    17 from xpra.server.window.motion import match_distance, consecutive_lines, calculate_distances, CRC_Image #@UnresolvedImport
     17from xpra.server.window.motion import scroll_distances, CRC_Image #@UnresolvedImport
    1818from xpra.server.window.video_subregion import VideoSubregion, VIDEO_SUBREGION
    1919from xpra.server.window.video_scoring import get_pipeline_score
    2020from xpra.codecs.loader import PREFERED_ENCODING_ORDER, EDGE_ENCODING_ORDER
     
    14531453        return packet
    14541454
    14551455
    1456     def encode_scrolling(self, image, distances, old_csums, csums, options):
     1456    def encode_scrolling(self, image, distances, options, saved):
    14571457        start = time.time()
    14581458        try:
    14591459            del options["av-sync"]
     
    14601460        except:
    14611461            pass
    14621462        #tells make_data_packet not to invalidate the scroll data:
    1463         scrolllog("encode_scrolling(%s, {..}, [], [], %s) window-dimensions=%s", image, options, self.window_dimensions)
     1463        ww, wh = self.window_dimensions
     1464        scrolllog("encode_scrolling(%s, %s, [], [], %s) window-dimensions=%s", image, distances, options, (ww, wh))
    14641465        x, y, w, h = image.get_geometry()[:4]
    1465         yscroll_values = []
    1466         max_scroll_regions = 50
    1467         #process distances with the highest score first:
    1468         for hits in reversed(sorted(distances.values())):
    1469             for scroll in (d for d,v in distances.items() if v==hits):
    1470                 assert scroll<h
    1471                 yscroll_values.append(scroll)
    1472             if len(yscroll_values)>=max_scroll_regions:
    1473                 break
    1474         assert yscroll_values
    1475         #always add zero=no-change so we can drop those updates!
    1476         if 0 not in yscroll_values and 0 in distances:
    1477             #(but do this last so we don't end up cutting too many rectangles)
    1478             yscroll_values.append(0)
    1479         scrolllog(" will send scroll packets for yscroll=%s", yscroll_values)
    1480         #keep track of the lines we have handled already:
    1481         #(the same line may be available from multiple scroll directions)
    1482         handled = set()
     1466        raw_scroll = distances.get_best_scroll_values()
     1467        non_scroll = distances.get_remaining_areas()
     1468        scrolllog(" will send scroll data=%s, non-scroll=%s", raw_scroll, non_scroll)
     1469        flush = len(non_scroll)
     1470        assert raw_scroll, "failed to detect scroll values"
     1471        #convert to a screen rectangle list for the client:
    14831472        scrolls = []
    1484         max_scrolls = 1000
    1485         for s in yscroll_values:
    1486             #find all the lines that scroll by this much:
    1487             slines = match_distance(old_csums, csums, s)
    1488             assert slines, "no lines matching distance %i" % s
    1489             #remove any lines we have already handled:
    1490             lines = [v+s for v in slines if ((v+s) not in handled and v not in handled)]
    1491             if not lines:
     1473        for scroll, line_defs in raw_scroll.items():
     1474            if scroll==0:
    14921475                continue
    1493             #and them to the handled set so we don't try to paint them again:
    1494             handled = handled.union(set(lines))
    1495             if s==0:
    1496                 scrolllog(" %i lines have not changed", len(lines))
    1497             else:
    1498                 #things have actually moved
    1499                 #aggregate consecutive lines into rectangles:
    1500                 cl = consecutive_lines(lines)
    1501                 #scrolllog(" scroll groups for distance=%i : %s=%s", s, lines, cl)
    1502                 scrolllog(" scroll groups for distance=%i : %s", s, cl)
    1503                 for sy,sh in cl:
    1504                     #new rectangle
    1505                     scrolls.append((x, y+sy-s, w, sh, 0, s))
    1506                     if len(scrolls)>max_scrolls:
    1507                         break
    1508                 if len(scrolls)>max_scrolls:
    1509                     break
    1510 
    1511         non_scroll = []
    1512         remaining = set(range(h))-handled
    1513         if remaining:
    1514             damaged_lines = sorted(list(remaining))
    1515             non_scroll = consecutive_lines(damaged_lines)
    1516             scrolllog(" non scroll: %i packets: %s", len(non_scroll), non_scroll)
    1517         flush = len(non_scroll)
    1518         #send as scroll paints packets:
    1519         if scrolls:
     1476            for line, count in line_defs.items():
     1477                try:
     1478                    assert y+line+scroll>=0, "cannot scroll rectangle by %i lines from %i+%i" % (scroll, y, line)
     1479                    assert y+line+scroll<=wh, "cannot scroll rectangle %i high by %i lines from %i+%i (window height is %i)" % (count, scroll, y, line, wh)
     1480                except Exception as e:
     1481                    log.error("Error: invalid scroll data:")
     1482                    log.error("LAST=%s", saved[0])
     1483                    log.error("CURR=%s", saved[1])
     1484                scrolls.append((x, y+line, w, count, 0, scroll))
     1485        #send the scrolls if we have any
     1486        #(zero change scrolls have been removed - so maybe there are none)
     1487        if len(scrolls)>0:
    15201488            client_options = options.copy()
     1489            try:
     1490                del client_options["scroll"]
     1491            except:
     1492                pass
    15211493            if flush>0 and self.supports_flush:
    15221494                client_options["flush"] = flush
    15231495            coding = "scroll"
     
    15341506            else:
    15351507                #slower but can be lossless:
    15361508                encoding = self.get_video_fallback_encoding(PREFERED_ENCODING_ORDER)
     1509            client_options = options.copy()
    15371510            if encoding:
    15381511                encode_fn = self._encoders[encoding]
    1539                 for sy, sh in non_scroll:
     1512                for sy, sh in non_scroll.items():
     1513                    #s = time.time()
    15401514                    sub = image.get_sub_image(0, sy, w, sh)
    1541                     flush -= 1
     1515                    #scrolllog("sub_image %i pixels: %.1fms", (w*sh), (time.time()-s)*1000)
    15421516                    ret = encode_fn(encoding, sub, options)
    15431517                    if not ret:
    15441518                        #cancelled?
     
    15451519                        return None
    15461520                    coding, data, client_options, outw, outh, outstride, _ = ret
    15471521                    assert data
    1548                     client_options = options.copy()
    1549                     if flush>0 and self.supports_flush:
     1522                    flush -= 1
     1523                    if self.supports_flush and flush>0:
    15501524                        client_options["flush"] = flush
    15511525                    packet = self.make_draw_packet(sub.get_x(), sub.get_y(), outw, outh, coding, data, outstride, client_options, options)
    15521526                    self.queue_damage_packet(packet)
     
    15561530                    compresslog("compress: %5.1fms for %4ix%-4i pixels at %4i,%-4i for wid=%-5i using %6s with ratio %5.1f%%  (%5iKB to %5iKB), sequence %5i, client_options=%s",
    15571531                         (end-nsstart)*1000.0, w, sh, 0, sy, self.wid, coding, 100.0*csize/psize, psize/1024, csize/1024, self._damage_packet_sequence, client_options)
    15581532                scrolllog("non-scroll encoding using %s (quality=%i, speed=%i) took %ims for %i rectangles", encoding, self._current_quality, self._current_speed, (time.time()-nsstart)*1000, len(non_scroll))
     1533            else:
     1534                #we can't send the non-scroll areas, ouch!
     1535                flush = 0
    15591536        assert flush==0
    15601537        self.last_scroll_time = time.time()
    15611538        scrolllog("scroll encoding total time: %ims", (self.last_scroll_time-start)*1000)
     
    16331610                if csums:
    16341611                    self.scroll_data = rectangle(x, y, w, h), csums
    16351612                    options["scroll"] = True
    1636                     scrolllog("updated scroll data, previously set: %s", bool(lsd))
     1613                    scrolllog("updated scroll data with %s, previously set: %s", self.scroll_data[0], (lsd or [None])[0])
    16371614                if lsd and csums:
    16381615                    rect, lcsums = lsd
    16391616                    if rect.x!=x or rect.y!=y:
     
    16441621                    else:
    16451622                        #same size, try to find scrolling value
    16461623                        assert len(csums)==len(lcsums), "mismatch between checksums lists: %i vs %i items!" % (len(csums), len(lcsums))
    1647                         distances = calculate_distances(csums, lcsums, 2, 1000)
    1648                         if len(distances)==0:
     1624                        saved = lcsums[:], csums[:]
     1625                        max_distance = min(1000, (100-SCROLL_MIN_PERCENT)*h//100)
     1626                        distances = scroll_distances(lcsums, csums, 2, max_distance)
     1627                        scroll, count = distances.get_best_match()
     1628                        if count==0:
    16491629                            scrolllog("no scroll distances found")
    16501630                        else:
    1651                             best = max(distances.values())
    1652                             scroll = distances.keys()[distances.values().index(best)]
    16531631                            end = time.time()
    1654                             best_pct = int(100*best/h)
    1655                             scrolllog("best scroll guess took %ims, matches %i%% of %i lines: %s", (end-start)*1000, best_pct, h, scroll)
     1632                            match_pct = int(100*count/h)
     1633                            scrolllog("best scroll guess took %ims, matches %i%% of %i lines: %s", (end-start)*1000, match_pct, h, scroll)
    16561634                            #if enough scrolling is detected, use scroll encoding for this frame:
    1657                             if best_pct>=SCROLL_MIN_PERCENT:
    1658                                 return self.encode_scrolling(image, distances, lcsums, csums, options)
     1635                            if match_pct>=SCROLL_MIN_PERCENT:
     1636                                return self.encode_scrolling(image, distances, options, saved)
    16591637            except Exception:
    16601638                scrolllog.error("Error during scrolling detection!", exc_info=True)
    16611639