Files
netbird/client/vnc/server/copyrect_test.go
2026-05-17 15:48:15 +02:00

163 lines
4.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//go:build !js && !ios && !android
package server
import (
"image"
"testing"
)
// fillTile paints a tileSize×tileSize block of img at (x,y) with the colour
// derived from (r,g,b) so the test can construct distinct-content tiles.
func fillTile(img *image.RGBA, x, y, ts int, r, g, b byte) {
for row := 0; row < ts; row++ {
off := (y+row)*img.Stride + x*4
for col := 0; col < ts; col++ {
img.Pix[off+col*4+0] = r
img.Pix[off+col*4+1] = g
img.Pix[off+col*4+2] = b
img.Pix[off+col*4+3] = 0xff
}
}
}
// copyTile copies a tileSize×tileSize block from src(sx,sy) to dst(dx,dy).
func copyTile(dst, src *image.RGBA, sx, sy, dx, dy, ts int) {
for row := 0; row < ts; row++ {
srcOff := (sy+row)*src.Stride + sx*4
dstOff := (dy+row)*dst.Stride + dx*4
copy(dst.Pix[dstOff:dstOff+ts*4], src.Pix[srcOff:srcOff+ts*4])
}
}
func TestCopyRectDetector_DetectsVerticalScroll(t *testing.T) {
const w, h = 256, 192 // 4×3 tiles at 64px
const ts = 64
prev := image.NewRGBA(image.Rect(0, 0, w, h))
cur := image.NewRGBA(image.Rect(0, 0, w, h))
// prev: 12 tiles each with a unique colour.
for ty := 0; ty < 3; ty++ {
for tx := 0; tx < 4; tx++ {
fillTile(prev, tx*ts, ty*ts, ts, byte(tx*40), byte(ty*60), 0x80)
}
}
// cur: simulate a single-tile-row scroll upward — every tile copied from
// the row below in prev, top row is new content.
for ty := 0; ty < 2; ty++ {
for tx := 0; tx < 4; tx++ {
copyTile(cur, prev, tx*ts, (ty+1)*ts, tx*ts, ty*ts, ts)
}
}
// Bottom row of cur: new colour, not a match.
for tx := 0; tx < 4; tx++ {
fillTile(cur, tx*ts, 2*ts, ts, 0xff, 0xff, 0xff)
}
d := newCopyRectDetector(ts)
d.rebuild(prev, w, h)
tiles := diffTiles(prev, cur, w, h, ts)
moves, remaining := d.extractCopyRectTiles(cur, tiles)
// Expect 8 CopyRect moves (top two rows) and 4 residual tiles (bottom row).
if len(moves) != 8 {
t.Fatalf("moves: want 8, got %d", len(moves))
}
if len(remaining) != 4 {
t.Fatalf("remaining: want 4, got %d", len(remaining))
}
// Spot-check one move: cur (0, 0) should map to prev (0, 64).
var found bool
for _, m := range moves {
if m.dstX == 0 && m.dstY == 0 {
if m.srcX != 0 || m.srcY != ts {
t.Fatalf("move at (0,0): src=(%d,%d), want (0,%d)", m.srcX, m.srcY, ts)
}
found = true
}
}
if !found {
t.Fatalf("no move for dst (0,0)")
}
}
func TestCopyRectDetector_RejectsSelfMatch(t *testing.T) {
const w, h = 128, 128
const ts = 64
prev := image.NewRGBA(image.Rect(0, 0, w, h))
cur := image.NewRGBA(image.Rect(0, 0, w, h))
// prev: 4 tiles, all unique
fillTile(prev, 0, 0, ts, 0x10, 0x20, 0x30)
fillTile(prev, ts, 0, ts, 0x40, 0x50, 0x60)
fillTile(prev, 0, ts, ts, 0x70, 0x80, 0x90)
fillTile(prev, ts, ts, ts, 0xa0, 0xb0, 0xc0)
// cur: tile (0,0) unchanged, others changed but content same as prev's (0,0).
fillTile(cur, 0, 0, ts, 0x10, 0x20, 0x30) // self-match
fillTile(cur, ts, 0, ts, 0xff, 0xff, 0xff)
fillTile(cur, 0, ts, ts, 0xff, 0xff, 0xff)
fillTile(cur, ts, ts, ts, 0xff, 0xff, 0xff)
d := newCopyRectDetector(ts)
d.rebuild(prev, w, h)
// Tile (0,0) is not in the dirty list (it's unchanged) so it should not
// produce a move even though its hash matches prev (0,0).
tiles := diffTiles(prev, cur, w, h, ts)
moves, _ := d.extractCopyRectTiles(cur, tiles)
for _, m := range moves {
if m.dstX == 0 && m.dstY == 0 {
t.Fatalf("unexpected move at (0,0)")
}
}
}
func TestCopyRectDetector_PassThroughWhenNoMatch(t *testing.T) {
const w, h = 64, 64
const ts = 64
prev := image.NewRGBA(image.Rect(0, 0, w, h))
cur := image.NewRGBA(image.Rect(0, 0, w, h))
fillTile(prev, 0, 0, ts, 0x11, 0x22, 0x33)
fillTile(cur, 0, 0, ts, 0xaa, 0xbb, 0xcc) // wholly different
d := newCopyRectDetector(ts)
d.rebuild(prev, w, h)
tiles := diffTiles(prev, cur, w, h, ts)
moves, remaining := d.extractCopyRectTiles(cur, tiles)
if len(moves) != 0 {
t.Fatalf("expected 0 moves, got %d", len(moves))
}
if len(remaining) != 1 {
t.Fatalf("expected 1 residual tile, got %d", len(remaining))
}
}
func TestEncodeCopyRectBody_Layout(t *testing.T) {
got := encodeCopyRectBody(100, 200, 300, 400, 64, 48)
if len(got) != 16 {
t.Fatalf("CopyRect body length: want 16, got %d", len(got))
}
// Dest position
if got[0] != 0x01 || got[1] != 0x2c || got[2] != 0x01 || got[3] != 0x90 {
t.Fatalf("bad dest bytes: % x", got[0:4])
}
// Width, height
if got[4] != 0 || got[5] != 64 || got[6] != 0 || got[7] != 48 {
t.Fatalf("bad size bytes: % x", got[4:8])
}
// Encoding = 1
if got[11] != 0x01 {
t.Fatalf("bad encoding byte: 0x%02x", got[11])
}
// Source position
if got[12] != 0 || got[13] != 100 || got[14] != 0 || got[15] != 200 {
t.Fatalf("bad src bytes: % x", got[12:16])
}
}