711 lines
19 KiB
Go
711 lines
19 KiB
Go
// 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
|
||
}
|