init: 自 zomaintain/backend/rdplib 平移独立成库; module path 从上游 github.com/nakagami/grdp 改为 git.zeroonesoft.cn/golib/rdplib
This commit is contained in:
@@ -0,0 +1,710 @@
|
||||
// clear.go 实现 MS-RDPEGFX 2.2.4 ClearCodec(TS_CLEARCODEC_BITMAP_STREAM)
|
||||
// 的完整解码,算法对齐 FreeRDP libfreerdp/codec/clear.c。
|
||||
//
|
||||
// 流结构:glyphFlags(1) + seqNumber(1) + [glyph 段] + residualByteCount(4) +
|
||||
// bandsByteCount(4) + subcodecByteCount(4) + 三段载荷。
|
||||
package rdpgfx
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
const (
|
||||
clearFlagGlyphIndex = 0x01
|
||||
clearFlagGlyphHit = 0x02
|
||||
clearFlagCacheReset = 0x04
|
||||
|
||||
clearVBarSize = 32768
|
||||
clearShortVBarSize = 16384
|
||||
clearGlyphSize = 4000
|
||||
)
|
||||
|
||||
// nsCodecDisabled 诊断开关:置 true 时丢弃 NSCodec 矩形
|
||||
var nsCodecDisabled = false
|
||||
|
||||
type clearCodecCtx struct {
|
||||
vBarStorage [clearVBarSize]vBarEntry
|
||||
shortVBarStorage [clearShortVBarSize]vBarEntry
|
||||
vBarCursor int
|
||||
shortVBarCursor int
|
||||
glyphCache [clearGlyphSize][]byte
|
||||
seqNumber uint32
|
||||
logged [24]bool
|
||||
}
|
||||
|
||||
// logOnce 每类失败只打第一条日志,避免刷屏;位序号定位失败类别
|
||||
func (ctx *clearCodecCtx) logOnce(slot int, msg string, args ...any) {
|
||||
if ctx.logged[slot] {
|
||||
return
|
||||
}
|
||||
ctx.logged[slot] = true
|
||||
slog.Warn("clear:"+msg, args...)
|
||||
}
|
||||
|
||||
func newClearCodecCtx() *clearCodecCtx {
|
||||
return &clearCodecCtx{}
|
||||
}
|
||||
|
||||
// decode 解出一张 w×h 的 BGRA 位图;流非法时返回 nil——调用方必须保留
|
||||
// surface 原内容(对齐 FreeRDP 的整帧拒绝语义)。服务器会发送子编码矩形
|
||||
// 完全越界的"空更新"流,若不拒绝会把清零缓冲 blit 到屏幕形成花屏块。
|
||||
func (ctx *clearCodecCtx) decode(data []byte, w, h int) []byte {
|
||||
out := make([]byte, w*h*4)
|
||||
if len(data) < 2 {
|
||||
return nil
|
||||
}
|
||||
off := 0
|
||||
glyphFlags := data[off]
|
||||
seqNumber := data[off+1]
|
||||
off += 2
|
||||
|
||||
// 序列号失步(丢包/上下文重置)时重新同步而不是丢弃整帧
|
||||
if ctx.seqNumber != uint32(seqNumber) {
|
||||
ctx.seqNumber = uint32(seqNumber)
|
||||
}
|
||||
ctx.seqNumber = (ctx.seqNumber + 1) % 256
|
||||
|
||||
if glyphFlags&clearFlagCacheReset != 0 {
|
||||
// FreeRDP 语义:重置游标并重新分配存储 → 所有缓存条目失效
|
||||
for i := range ctx.vBarStorage {
|
||||
ctx.vBarStorage[i] = vBarEntry{}
|
||||
}
|
||||
for i := range ctx.shortVBarStorage {
|
||||
ctx.shortVBarStorage[i] = vBarEntry{}
|
||||
}
|
||||
ctx.vBarCursor = 0
|
||||
ctx.shortVBarCursor = 0
|
||||
}
|
||||
|
||||
// MS-RDPEGFX/FreeRDP 语义:GLYPH_HIT 必须与 GLYPH_INDEX 同置(0x03=命中),
|
||||
// 单独的 HIT 是非法组合
|
||||
glyphIdx := -1
|
||||
if glyphFlags&clearFlagGlyphHit != 0 {
|
||||
if glyphFlags&clearFlagGlyphIndex == 0 {
|
||||
return nil
|
||||
}
|
||||
if off+2 > len(data) {
|
||||
return nil
|
||||
}
|
||||
idx := int(binary.LittleEndian.Uint16(data[off:]))
|
||||
off += 2
|
||||
if idx >= clearGlyphSize {
|
||||
return nil
|
||||
}
|
||||
if cached := ctx.glyphCache[idx]; len(cached) == len(out) {
|
||||
copy(out, cached)
|
||||
}
|
||||
return out
|
||||
}
|
||||
if glyphFlags&clearFlagGlyphIndex != 0 {
|
||||
if off+2 > len(data) {
|
||||
return nil
|
||||
}
|
||||
glyphIdx = int(binary.LittleEndian.Uint16(data[off:]))
|
||||
off += 2
|
||||
if glyphIdx >= clearGlyphSize {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if off+12 > len(data) {
|
||||
// FreeRDP:GLYPH_HIT|INDEX 的纯命中流允许没有 12 字节长度头
|
||||
return nil
|
||||
}
|
||||
residualLen := int(binary.LittleEndian.Uint32(data[off:]))
|
||||
bandsLen := int(binary.LittleEndian.Uint32(data[off+4:]))
|
||||
subcodecLen := int(binary.LittleEndian.Uint32(data[off+8:]))
|
||||
off += 12
|
||||
|
||||
if residualLen > 0 {
|
||||
if off+residualLen > len(data) {
|
||||
return nil
|
||||
}
|
||||
if !ctx.clearDecodeResidual(data[off:off+residualLen], w, h, out) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
off += residualLen
|
||||
if bandsLen > 0 {
|
||||
if off+bandsLen > len(data) {
|
||||
return nil
|
||||
}
|
||||
if !ctx.clearDecodeBands(data[off:off+bandsLen], w, h, out) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
off += bandsLen
|
||||
if subcodecLen > 0 {
|
||||
if off+subcodecLen > len(data) {
|
||||
return nil
|
||||
}
|
||||
if !ctx.clearDecodeSubcodecs(data[off:off+subcodecLen], w, h, out) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if glyphIdx >= 0 {
|
||||
cached := make([]byte, len(out))
|
||||
copy(cached, out)
|
||||
ctx.glyphCache[glyphIdx] = cached
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// clearDecodeResidual:(b,g,r,runLen) 游程填充,runLen 用 1/2/4 字节变长编码。
|
||||
// 返回 false 表示流非法(对齐 FreeRDP 的整帧拒绝语义)。
|
||||
func (ctx *clearCodecCtx) clearDecodeResidual(data []byte, w, h int, out []byte) bool {
|
||||
off, pixelIndex, pixelCount := 0, 0, w*h
|
||||
for off < len(data) {
|
||||
if off+4 > len(data) {
|
||||
ctx.logOnce(2, "residual truncated (entry hdr)")
|
||||
return false
|
||||
}
|
||||
b, g, r := data[off], data[off+1], data[off+2]
|
||||
run := int(data[off+3])
|
||||
off += 4
|
||||
if run >= 0xFF {
|
||||
if off+2 > len(data) {
|
||||
ctx.logOnce(2, "residual truncated (run16 hdr)")
|
||||
return false
|
||||
}
|
||||
run = int(binary.LittleEndian.Uint16(data[off:]))
|
||||
off += 2
|
||||
if run >= 0xFFFF {
|
||||
if off+4 > len(data) {
|
||||
ctx.logOnce(2, "residual truncated (run32 hdr)")
|
||||
return false
|
||||
}
|
||||
run = int(binary.LittleEndian.Uint32(data[off:]))
|
||||
off += 4
|
||||
}
|
||||
}
|
||||
if run > pixelCount-pixelIndex {
|
||||
ctx.logOnce(3, "residual run overflow", "run", run, "left", pixelCount-pixelIndex)
|
||||
return false
|
||||
}
|
||||
i := pixelIndex * 4
|
||||
for n := 0; n < run; n++ {
|
||||
out[i], out[i+1], out[i+2], out[i+3] = b, g, r, 0xFF
|
||||
i += 4
|
||||
}
|
||||
pixelIndex += run
|
||||
}
|
||||
if pixelIndex != pixelCount {
|
||||
// FreeRDP:residual 必须恰好铺满整幅,否则整帧失败
|
||||
ctx.logOnce(16, "residual incomplete", "covered", pixelIndex, "want", pixelCount)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// clearDecodeBands:vBar 列带解码(含短 vBar 缓存/整 vBar 缓存/背景合成)。
|
||||
// 返回 false 表示流非法(整帧拒绝)。
|
||||
func (ctx *clearCodecCtx) clearDecodeBands(data []byte, w, h int, out []byte) bool {
|
||||
off := 0
|
||||
for off+11 <= len(data) {
|
||||
xStart := int(binary.LittleEndian.Uint16(data[off:]))
|
||||
xEnd := int(binary.LittleEndian.Uint16(data[off+2:]))
|
||||
yStart := int(binary.LittleEndian.Uint16(data[off+4:]))
|
||||
yEnd := int(binary.LittleEndian.Uint16(data[off+6:]))
|
||||
cb, cg, cr := data[off+8], data[off+9], data[off+10]
|
||||
off += 11
|
||||
if xEnd < xStart || yEnd < yStart {
|
||||
ctx.logOnce(4, "bands bad rect", "xStart", xStart, "xEnd", xEnd, "yStart", yStart, "yEnd", yEnd)
|
||||
return false
|
||||
}
|
||||
colorBkg := [4]byte{cb, cg, cr, 0xFF}
|
||||
vBarCount := xEnd - xStart + 1
|
||||
vBarHeight := yEnd - yStart + 1
|
||||
if vBarHeight > 52 {
|
||||
ctx.logOnce(5, "bands vBarHeight>52", "h", vBarHeight)
|
||||
return false
|
||||
}
|
||||
|
||||
for i := 0; i < vBarCount; i++ {
|
||||
if off+2 > len(data) {
|
||||
return false
|
||||
}
|
||||
vBarHeader := binary.LittleEndian.Uint16(data[off:])
|
||||
off += 2
|
||||
|
||||
var entry *vBarEntry
|
||||
var shortEntry *vBarEntry
|
||||
vBarUpdate := false
|
||||
vBarYOn := 0
|
||||
vBarShortPixelCount := 0
|
||||
|
||||
switch {
|
||||
case vBarHeader&0xC000 == 0x4000: // SHORT_VBAR_CACHE_HIT
|
||||
idx := int(vBarHeader & 0x3FFF)
|
||||
if idx >= clearShortVBarSize {
|
||||
ctx.logOnce(6, "bands short idx range", "idx", idx)
|
||||
return false
|
||||
}
|
||||
shortEntry = &ctx.shortVBarStorage[idx]
|
||||
if off >= len(data) {
|
||||
return false
|
||||
}
|
||||
vBarYOn = int(data[off])
|
||||
off++
|
||||
vBarShortPixelCount = shortEntry.count
|
||||
vBarUpdate = true
|
||||
case vBarHeader&0xC000 == 0x0000: // SHORT_VBAR_CACHE_MISS
|
||||
vBarYOn = int(vBarHeader & 0xFF)
|
||||
vBarYOff := int((vBarHeader >> 8) & 0x3F)
|
||||
if vBarYOff < vBarYOn {
|
||||
ctx.logOnce(7, "bands yOff<yOn", "yOff", vBarYOff, "yOn", vBarYOn)
|
||||
return false
|
||||
}
|
||||
vBarShortPixelCount = vBarYOff - vBarYOn
|
||||
if vBarShortPixelCount > 52 {
|
||||
ctx.logOnce(8, "bands short count>52", "n", vBarShortPixelCount)
|
||||
return false
|
||||
}
|
||||
if off+vBarShortPixelCount*3 > len(data) {
|
||||
return false
|
||||
}
|
||||
shortEntry = &ctx.shortVBarStorage[ctx.shortVBarCursor]
|
||||
shortEntry.count = vBarShortPixelCount
|
||||
shortEntry.pixels = make([]byte, vBarShortPixelCount*4)
|
||||
for p := 0; p < vBarShortPixelCount; p++ {
|
||||
shortEntry.pixels[p*4] = data[off+p*3]
|
||||
shortEntry.pixels[p*4+1] = data[off+p*3+1]
|
||||
shortEntry.pixels[p*4+2] = data[off+p*3+2]
|
||||
shortEntry.pixels[p*4+3] = 0xFF
|
||||
}
|
||||
off += vBarShortPixelCount * 3
|
||||
ctx.shortVBarCursor = (ctx.shortVBarCursor + 1) % clearShortVBarSize
|
||||
vBarUpdate = true
|
||||
case vBarHeader&0x8000 == 0x8000: // VBAR_CACHE_HIT
|
||||
idx := int(vBarHeader & 0x7FFF)
|
||||
if idx >= clearVBarSize {
|
||||
ctx.logOnce(9, "bands vbar idx range", "idx", idx)
|
||||
return false
|
||||
}
|
||||
entry = &ctx.vBarStorage[idx]
|
||||
if entry.pixels == nil {
|
||||
// 缓存被重置后的命中:填充哑数据
|
||||
entry.count = vBarHeight
|
||||
entry.pixels = make([]byte, vBarHeight*4)
|
||||
}
|
||||
default:
|
||||
ctx.logOnce(10, "bands invalid vBarHeader", "hdr", vBarHeader)
|
||||
return false
|
||||
}
|
||||
|
||||
if vBarUpdate {
|
||||
ve := &ctx.vBarStorage[ctx.vBarCursor]
|
||||
ve.count = vBarHeight
|
||||
ve.pixels = make([]byte, vBarHeight*4)
|
||||
// 前段背景 [0, vBarYOn)
|
||||
bgFront := vBarYOn
|
||||
if bgFront > vBarHeight {
|
||||
bgFront = vBarHeight
|
||||
}
|
||||
for y2 := 0; y2 < bgFront; y2++ {
|
||||
copy(ve.pixels[y2*4:(y2+1)*4], colorBkg[:])
|
||||
}
|
||||
// 中段:短 vBar 像素 [vBarYOn, vBarYOn+vBarShortPixelCount)
|
||||
count := vBarShortPixelCount
|
||||
if vBarYOn+count > vBarHeight {
|
||||
count = vBarHeight - vBarYOn
|
||||
}
|
||||
if count < 0 {
|
||||
count = 0
|
||||
}
|
||||
if count > 0 && shortEntry != nil {
|
||||
copy(ve.pixels[vBarYOn*4:(vBarYOn+count)*4], shortEntry.pixels[0:count*4])
|
||||
}
|
||||
// 后段背景 [vBarYOn+vBarShortPixelCount, vBarHeight)
|
||||
start := vBarYOn + vBarShortPixelCount
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
if start > vBarHeight {
|
||||
start = vBarHeight
|
||||
}
|
||||
for y2 := start; y2 < vBarHeight; y2++ {
|
||||
copy(ve.pixels[y2*4:(y2+1)*4], colorBkg[:])
|
||||
}
|
||||
ctx.vBarCursor = (ctx.vBarCursor + 1) % clearVBarSize
|
||||
entry = ve
|
||||
}
|
||||
|
||||
if entry == nil || entry.count != vBarHeight {
|
||||
continue
|
||||
}
|
||||
|
||||
// 落到输出:第 i 列,从 yStart 起取 min(count, h-yStart) 像素
|
||||
x := xStart + i
|
||||
if x >= w {
|
||||
continue
|
||||
}
|
||||
count := entry.count
|
||||
if count > h-yStart {
|
||||
count = h - yStart
|
||||
}
|
||||
if count <= 0 {
|
||||
continue
|
||||
}
|
||||
for y := 0; y < count; y++ {
|
||||
dst := (yStart+y)*w*4 + x*4
|
||||
copy(out[dst:dst+4], entry.pixels[y*4:y*4+4])
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// clearDecodeSubcodecs:矩形级子编码(0=BGR24 原始,1=NSCodec,2=RLEX)。
|
||||
// 返回 false 表示流非法(整帧拒绝)。
|
||||
func (ctx *clearCodecCtx) clearDecodeSubcodecs(data []byte, w, h int, out []byte) bool {
|
||||
off := 0
|
||||
for off+13 <= len(data) {
|
||||
xStart := int(binary.LittleEndian.Uint16(data[off:]))
|
||||
yStart := int(binary.LittleEndian.Uint16(data[off+2:]))
|
||||
width := int(binary.LittleEndian.Uint16(data[off+4:]))
|
||||
height := int(binary.LittleEndian.Uint16(data[off+6:]))
|
||||
bmpLen := int(binary.LittleEndian.Uint32(data[off+8:]))
|
||||
subcodecId := data[off+12]
|
||||
off += 13
|
||||
if off+bmpLen > len(data) {
|
||||
ctx.logOnce(11, "subcodec payload truncated", "id", subcodecId, "want", bmpLen, "have", len(data)-off)
|
||||
return false
|
||||
}
|
||||
payload := data[off : off+bmpLen]
|
||||
off += bmpLen
|
||||
|
||||
// FreeRDP 语义:子编码矩形越界是非法流,整帧拒绝(否则服务器发来的
|
||||
// 完全越界"空更新"矩形会把清零缓冲 blit 到屏幕形成花屏块)
|
||||
if xStart >= w || yStart >= h || xStart+width > w || yStart+height > h {
|
||||
ctx.logOnce(17, "subcodec rect out of bounds",
|
||||
"id", subcodecId, "x", xStart, "y", yStart, "rw", width, "rh", height, "w", w, "h", h)
|
||||
return false
|
||||
}
|
||||
|
||||
switch subcodecId {
|
||||
case 0: // 未压缩 BGR24
|
||||
if len(payload) != width*height*3 {
|
||||
ctx.logOnce(18, "subcodec BGR24 size mismatch", "want", width*height*3, "have", len(payload))
|
||||
return false
|
||||
}
|
||||
clearWriteBGR24(payload, width, height, out, xStart, yStart, w, h)
|
||||
case 1: // NSCodec(YCoCg 色域压缩编码)
|
||||
if !clearDecodeNSCodec(payload, width, height, out, xStart, yStart, w, h) {
|
||||
return false
|
||||
}
|
||||
case 2: // RLEX
|
||||
if !ctx.clearDecodeRLEX(payload, width, height, out, xStart, yStart, w, h) {
|
||||
return false
|
||||
}
|
||||
default:
|
||||
ctx.logOnce(15, "unknown subcodec id", "id", subcodecId)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func clearWriteBGR24(data []byte, w, h int, out []byte, xDst, yDst, surfW, surfH int) {
|
||||
stride := w * 3
|
||||
if stride*h > len(data) {
|
||||
return
|
||||
}
|
||||
for y := 0; y < h; y++ {
|
||||
if yDst+y >= surfH {
|
||||
return
|
||||
}
|
||||
row := data[y*stride : (y+1)*stride]
|
||||
for x := 0; x < w; x++ {
|
||||
if xDst+x >= surfW {
|
||||
break
|
||||
}
|
||||
dst := (yDst+y)*surfW*4 + (xDst+x)*4
|
||||
out[dst], out[dst+1], out[dst+2], out[dst+3] = row[x*3], row[x*3+1], row[x*3+2], 0xFF
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// clearDecodeNSCodec 解码 NSCodec 子编码矩形,算法对齐 FreeRDP libfreerdp/codec/nsc.c:
|
||||
// 20 字节头(4×PlaneByteCount + ColorLossLevel + ChromaSubsamplingLevel + 保留 2 字节),
|
||||
// 4 个色度平面(Y/Co/Cg/A)RLE 解压后做色损恢复与 YCoCg→RGB 转换。
|
||||
|
||||
func clearDecodeNSCodec(data []byte, w, h int, out []byte, xDst, yDst, surfW, surfH int) bool {
|
||||
if len(data) < 20 {
|
||||
return false
|
||||
}
|
||||
var planeCount [4]int
|
||||
total := 0
|
||||
for i := 0; i < 4; i++ {
|
||||
planeCount[i] = int(binary.LittleEndian.Uint32(data[i*4:]))
|
||||
total += planeCount[i]
|
||||
}
|
||||
colorLossLevel := int(data[16])
|
||||
chroma := int(data[17])
|
||||
if colorLossLevel < 1 || colorLossLevel > 7 {
|
||||
return false
|
||||
}
|
||||
planes := data[20:]
|
||||
if len(planes) < total {
|
||||
return false
|
||||
}
|
||||
|
||||
rw := (w + 7) &^ 7
|
||||
rh := (h + 1) &^ 1
|
||||
// 原始平面字节数(OrgByteCount)
|
||||
org := [4]int{w * h, w * h, w * h, w * h}
|
||||
if chroma != 0 {
|
||||
org[0] = rw * h
|
||||
org[1] = (rw / 2) * (rh / 2)
|
||||
org[2] = org[1]
|
||||
}
|
||||
var pbuf [4][]byte
|
||||
for i := 0; i < 4; i++ {
|
||||
pbuf[i] = make([]byte, org[i])
|
||||
}
|
||||
|
||||
off := 0
|
||||
for i := 0; i < 4; i++ {
|
||||
psize := planeCount[i]
|
||||
plane := planes[off : off+psize]
|
||||
off += psize
|
||||
switch {
|
||||
case psize == 0:
|
||||
for j := range pbuf[i] {
|
||||
pbuf[i][j] = 0xFF
|
||||
}
|
||||
case psize < org[i]:
|
||||
if !nscRLEDecode(plane, pbuf[i]) {
|
||||
return false
|
||||
}
|
||||
default:
|
||||
copy(pbuf[i], plane[:org[i]])
|
||||
}
|
||||
}
|
||||
|
||||
// 色损恢复 + YCoCg→RGB:shift = ColorLossLevel-1
|
||||
shift := uint(colorLossLevel - 1)
|
||||
bmp := make([]byte, w*h*4)
|
||||
pos := 0
|
||||
for y := 0; y < h; y++ {
|
||||
var yplane, coplane, cgplane []byte
|
||||
if chroma != 0 {
|
||||
yplane = pbuf[0][y*rw:]
|
||||
coplane = pbuf[1][(y>>1)*(rw>>1):]
|
||||
cgplane = pbuf[2][(y>>1)*(rw>>1):]
|
||||
} else {
|
||||
yplane = pbuf[0][y*w:]
|
||||
coplane = pbuf[1][y*w:]
|
||||
cgplane = pbuf[2][y*w:]
|
||||
}
|
||||
// A 平面(pbuf[3])不参与显示:canvas putImageData 会保留 alpha,
|
||||
// 非 255 的 alpha 会呈现半透明色块,故强制输出不透明
|
||||
coIdx, cgIdx := 0, 0
|
||||
for x := 0; x < w; x++ {
|
||||
yv := int16(yplane[x])
|
||||
// C: (INT16)(INT8)(((INT16)u8) << shift) —— 16 位环绕后截断低 8 位再符号扩展
|
||||
cov := int16(int8(byte(int16(coplane[coIdx]) << shift)))
|
||||
cgv := int16(int8(byte(int16(cgplane[cgIdx]) << shift)))
|
||||
rv := yv + cov - cgv
|
||||
gv := yv + cgv
|
||||
bv := yv - cov - cgv
|
||||
bmp[pos] = nscClamp(bv)
|
||||
bmp[pos+1] = nscClamp(gv)
|
||||
bmp[pos+2] = nscClamp(rv)
|
||||
// A 平面值不作为透明度使用:RDP 桌面内容恒不透明,而 canvas
|
||||
// putImageData 会保留 alpha,若 A 平面含非 255 值会呈现半透明色块
|
||||
bmp[pos+3] = 0xFF
|
||||
pos += 4
|
||||
if chroma != 0 {
|
||||
if x%2 == 1 {
|
||||
coIdx++
|
||||
cgIdx++
|
||||
}
|
||||
} else {
|
||||
coIdx++
|
||||
cgIdx++
|
||||
}
|
||||
}
|
||||
}
|
||||
blitBGRA(bmp, w, h, out, xDst, yDst, surfW, surfH)
|
||||
return true
|
||||
}
|
||||
|
||||
// nscRLEDecode 单平面 NSC 游程解码,对齐 FreeRDP nsc_rle_decode
|
||||
func nscRLEDecode(in, out []byte) bool {
|
||||
inOff, outOff := 0, 0
|
||||
left := len(out)
|
||||
for left > 4 {
|
||||
if inOff >= len(in) {
|
||||
return false
|
||||
}
|
||||
value := in[inOff]
|
||||
inOff++
|
||||
if left == 5 {
|
||||
out[outOff] = value
|
||||
outOff++
|
||||
left--
|
||||
} else if inOff >= len(in) {
|
||||
return false
|
||||
} else if value == in[inOff] {
|
||||
inOff++
|
||||
if inOff >= len(in) {
|
||||
return false
|
||||
}
|
||||
var run int
|
||||
if in[inOff] < 0xFF {
|
||||
run = int(in[inOff]) + 2
|
||||
inOff++
|
||||
} else {
|
||||
if inOff+5 > len(in) {
|
||||
return false
|
||||
}
|
||||
inOff++
|
||||
run = int(in[inOff]) | int(in[inOff+1])<<8 | int(in[inOff+2])<<16 | int(in[inOff+3])<<24
|
||||
inOff += 4
|
||||
}
|
||||
if run > len(out)-outOff || run > left {
|
||||
return false
|
||||
}
|
||||
for j := 0; j < run; j++ {
|
||||
out[outOff+j] = value
|
||||
}
|
||||
outOff += run
|
||||
left -= run
|
||||
} else {
|
||||
out[outOff] = value
|
||||
outOff++
|
||||
left--
|
||||
}
|
||||
}
|
||||
if len(out)-outOff < 4 || left < 4 || len(in)-inOff < 4 {
|
||||
return false
|
||||
}
|
||||
copy(out[outOff:outOff+4], in[inOff:inOff+4])
|
||||
return true
|
||||
}
|
||||
|
||||
func nscClamp(v int16) byte {
|
||||
if v < 0 {
|
||||
return 0
|
||||
}
|
||||
if v > 255 {
|
||||
return 255
|
||||
}
|
||||
return byte(v)
|
||||
}
|
||||
|
||||
func blitBGRA(src []byte, w, h int, out []byte, xDst, yDst, surfW, surfH int) {
|
||||
for y := 0; y < h; y++ {
|
||||
dy := yDst + y
|
||||
if dy >= surfH {
|
||||
return
|
||||
}
|
||||
for x := 0; x < w; x++ {
|
||||
dx := xDst + x
|
||||
if dx >= surfW {
|
||||
break
|
||||
}
|
||||
copy(out[(dy*surfW+dx)*4:(dy*surfW+dx)*4+4], src[(y*w+x)*4:(y*w+x)*4+4])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// clearDecodeRLEX:调色板游程编码(套位打包索引)
|
||||
func (ctx *clearCodecCtx) clearDecodeRLEX(data []byte, w, h int, out []byte, xDst, yDst, surfW, surfH int) bool {
|
||||
if len(data) < 1 {
|
||||
return false
|
||||
}
|
||||
paletteCount := int(data[0])
|
||||
if paletteCount < 1 || paletteCount > 127 {
|
||||
return false
|
||||
}
|
||||
if 1+paletteCount*3 > len(data) {
|
||||
return false
|
||||
}
|
||||
palette := make([][4]byte, paletteCount)
|
||||
off := 1
|
||||
for i := 0; i < paletteCount; i++ {
|
||||
palette[i] = [4]byte{data[off], data[off+1], data[off+2], 0xFF} // b,g,r
|
||||
off += 3
|
||||
}
|
||||
|
||||
numBits := clearLog2Floor(paletteCount-1) + 1
|
||||
pixelCount := w * h
|
||||
pixelIndex := 0
|
||||
x, y := 0, 0
|
||||
putPixel := func(c [4]byte) {
|
||||
if xDst+x < surfW && yDst+y < surfH {
|
||||
dst := (yDst+y)*surfW*4 + (xDst+x)*4
|
||||
copy(out[dst:dst+4], c[:])
|
||||
}
|
||||
if x++; x >= w {
|
||||
y++
|
||||
x = 0
|
||||
}
|
||||
}
|
||||
for off+2 <= len(data) && pixelIndex < pixelCount {
|
||||
tmp := data[off]
|
||||
run := int(data[off+1])
|
||||
off += 2
|
||||
suiteDepth := int(tmp >> uint(numBits) & clear8BitMask(8-numBits))
|
||||
stopIndex := int(tmp & clear8BitMask(numBits))
|
||||
startIndex := stopIndex - suiteDepth
|
||||
if run >= 0xFF {
|
||||
if off+2 > len(data) {
|
||||
ctx.logOnce(13, "rlex truncated (run16 hdr)")
|
||||
return false
|
||||
}
|
||||
run = int(binary.LittleEndian.Uint16(data[off:]))
|
||||
off += 2
|
||||
if run >= 0xFFFF {
|
||||
if off+4 > len(data) {
|
||||
ctx.logOnce(13, "rlex truncated (run32 hdr)")
|
||||
return false
|
||||
}
|
||||
run = int(binary.LittleEndian.Uint32(data[off:]))
|
||||
off += 4
|
||||
}
|
||||
}
|
||||
if startIndex < 0 || startIndex >= paletteCount || stopIndex >= paletteCount {
|
||||
ctx.logOnce(12, "rlex bad palette index", "start", startIndex, "stop", stopIndex, "count", paletteCount)
|
||||
return false
|
||||
}
|
||||
if run > pixelCount-pixelIndex {
|
||||
ctx.logOnce(14, "rlex run overflow", "run", run, "left", pixelCount-pixelIndex)
|
||||
return false
|
||||
}
|
||||
for i := 0; i < run; i++ {
|
||||
putPixel(palette[startIndex])
|
||||
}
|
||||
pixelIndex += run
|
||||
for i := 0; i <= suiteDepth && pixelIndex < pixelCount; i++ {
|
||||
putPixel(palette[startIndex+i])
|
||||
pixelIndex++
|
||||
}
|
||||
}
|
||||
if pixelIndex != pixelCount {
|
||||
// FreeRDP:RLEX 必须恰好覆盖矩形,否则整帧失败
|
||||
ctx.logOnce(19, "rlex incomplete", "covered", pixelIndex, "want", pixelCount)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func clear8BitMask(bits int) byte {
|
||||
if bits <= 0 || bits > 8 {
|
||||
return 0
|
||||
}
|
||||
return byte((1 << uint(bits)) - 1)
|
||||
}
|
||||
|
||||
func clearLog2Floor(v int) int {
|
||||
log := 0
|
||||
for v > 1 {
|
||||
v >>= 1
|
||||
log++
|
||||
}
|
||||
return log
|
||||
}
|
||||
Reference in New Issue
Block a user