1322 lines
38 KiB
Go
1322 lines
38 KiB
Go
package rdpgfx
|
||
|
||
// RFX Progressive Codec decoder (MS-RDPEGFX 2.2.4), algorithm aligned with
|
||
// FreeRDP libfreerdp/codec/progressive.c. Handles RDPGFX_CODECID_CAPROGRESSIVE
|
||
// (0x0009) in WIRE_TO_SURFACE_PDU_1/2.
|
||
//
|
||
// Key points that differ from the plain RemoteFX codec (MS-RDPRFX):
|
||
// - The 5-byte quant table uses the RDPEGFX band order (LL3,HL3,LH3,HH3,
|
||
// HL2,LH2,HH2,HL1,LH1,HH1), which swaps LH3/HL3, HL2/LH2 and HL1/LH1
|
||
// compared to the RDPRFX order parsed by parseRfxQuant.
|
||
// - The effective dequant shift per band is (plain + progressive quant) - 1.
|
||
// - TILE_FIRST/TILE_SIMPLE carry the first pass of a tile; TILE_UPGRADE
|
||
// carries incremental bit-plane refinements using an SRL+RAW dual bit
|
||
// stream applied to the cached coefficients in extrapolate layout.
|
||
// - Region flag RFX_DWT_REDUCE_EXTRAPOLATE switches the IDWT to the
|
||
// extrapolate variant with irregular band sizes.
|
||
|
||
import (
|
||
"encoding/binary"
|
||
"fmt"
|
||
"log/slog"
|
||
"runtime"
|
||
"sync"
|
||
)
|
||
|
||
// Progressive block types (different from non-progressive WBT_* at same values!)
|
||
const (
|
||
progWBTSync = 0xCCC0
|
||
progWBTFrameBegin = 0xCCC1
|
||
progWBTFrameEnd = 0xCCC2
|
||
progWBTContext = 0xCCC3
|
||
progWBTRegion = 0xCCC4
|
||
progWBTTileSimple = 0xCCC5
|
||
progWBTTileFirst = 0xCCC6
|
||
progWBTTileUpgrade = 0xCCC7
|
||
)
|
||
|
||
// Region/context flags (MS-RDPEGFX progressive.h)
|
||
const (
|
||
progFlagSubbandDiffing = 0x01 // PROGRESSIVE_BLOCK_CONTEXT::flags
|
||
progFlagDWTReduceExtrapolate = 0x01 // PROGRESSIVE_BLOCK_REGION::flags
|
||
progFlagTileDifference = 0x01 // tile flags
|
||
)
|
||
|
||
const rfxTileSize = 64
|
||
|
||
// rfxQuant holds the 10 quantization values in RDPRFX order (standard codec).
|
||
type rfxQuant struct {
|
||
LL3, LH3, HL3, HH3 uint8
|
||
LH2, HL2, HH2 uint8
|
||
LH1, HL1, HH1 uint8
|
||
}
|
||
|
||
// parseRfxQuant parses a standard (MS-RDPRFX) 5-byte quant table.
|
||
// Used by the plain RemoteFX tileset decoder in rfx.go.
|
||
func parseRfxQuant(data []byte) rfxQuant {
|
||
return rfxQuant{
|
||
LL3: data[0] & 0x0F,
|
||
LH3: data[0] >> 4,
|
||
HL3: data[1] & 0x0F,
|
||
HH3: data[1] >> 4,
|
||
LH2: data[2] & 0x0F,
|
||
HL2: data[2] >> 4,
|
||
HH2: data[3] & 0x0F,
|
||
LH1: data[3] >> 4,
|
||
HL1: data[4] & 0x0F,
|
||
HH1: data[4] >> 4,
|
||
}
|
||
}
|
||
|
||
// progBandQuant holds the 10 band quant values in RDPEGFX order
|
||
// (RFX_COMPONENT_CODEC_QUANT in FreeRDP progressive.h).
|
||
type progBandQuant struct {
|
||
LL3, HL3, LH3, HH3 uint8
|
||
HL2, LH2, HH2 uint8
|
||
HL1, LH1, HH1 uint8
|
||
}
|
||
|
||
// parseProgBandQuant reads one 5-byte progressive quant component.
|
||
func parseProgBandQuant(data []byte) progBandQuant {
|
||
return progBandQuant{
|
||
LL3: data[0] & 0x0F,
|
||
HL3: data[0] >> 4,
|
||
LH3: data[1] & 0x0F,
|
||
HH3: data[1] >> 4,
|
||
HL2: data[2] & 0x0F,
|
||
LH2: data[2] >> 4,
|
||
HH2: data[3] & 0x0F,
|
||
HL1: data[3] >> 4,
|
||
LH1: data[4] & 0x0F,
|
||
HH1: data[4] >> 4,
|
||
}
|
||
}
|
||
|
||
// progCodecQuant is one 16-byte RFX_PROGRESSIVE_CODEC_QUANT entry.
|
||
type progCodecQuant struct {
|
||
quality byte
|
||
y, cb, cr progBandQuant
|
||
}
|
||
|
||
// progAdd returns a+b band-wise.
|
||
func progAdd(a, b progBandQuant) progBandQuant {
|
||
return progBandQuant{
|
||
a.LL3 + b.LL3, a.HL3 + b.HL3, a.LH3 + b.LH3, a.HH3 + b.HH3,
|
||
a.HL2 + b.HL2, a.LH2 + b.LH2, a.HH2 + b.HH2,
|
||
a.HL1 + b.HL1, a.LH1 + b.LH1, a.HH1 + b.HH1,
|
||
}
|
||
}
|
||
|
||
// progSub returns a-b band-wise, ok=false when any band would underflow.
|
||
func progSub(a, b progBandQuant) (progBandQuant, bool) {
|
||
if a.LL3 < b.LL3 || a.HL3 < b.HL3 || a.LH3 < b.LH3 || a.HH3 < b.HH3 ||
|
||
a.HL2 < b.HL2 || a.LH2 < b.LH2 || a.HH2 < b.HH2 ||
|
||
a.HL1 < b.HL1 || a.LH1 < b.LH1 || a.HH1 < b.HH1 {
|
||
return progBandQuant{}, false
|
||
}
|
||
return progBandQuant{
|
||
a.LL3 - b.LL3, a.HL3 - b.HL3, a.LH3 - b.LH3, a.HH3 - b.HH3,
|
||
a.HL2 - b.HL2, a.LH2 - b.LH2, a.HH2 - b.HH2,
|
||
a.HL1 - b.HL1, a.LH1 - b.LH1, a.HH1 - b.HH1,
|
||
}, true
|
||
}
|
||
|
||
// progLSub subtracts v from every band, ok=false on underflow or out-of-range v.
|
||
func progLSub(a progBandQuant, v int) (progBandQuant, bool) {
|
||
if v < 0 || v > 255 {
|
||
return progBandQuant{}, false
|
||
}
|
||
return progSub(a, progBandQuant{
|
||
LL3: uint8(v), HL3: uint8(v), LH3: uint8(v), HH3: uint8(v),
|
||
HL2: uint8(v), LH2: uint8(v), HH2: uint8(v),
|
||
HL1: uint8(v), LH1: uint8(v), HH1: uint8(v),
|
||
})
|
||
}
|
||
|
||
// progTileState caches the progressive state of one tile (per component:
|
||
// current coefficients and first-pass sign array).
|
||
type progTileState struct {
|
||
pass int
|
||
// per component: 0=Y, 1=Cb, 2=Cr
|
||
current [3]*coeffArr // accumulated band coefficients (extrapolate layout)
|
||
sign [3]*coeffArr // raw first-pass RLGR output (signs for upgrades)
|
||
yBitPos progBandQuant
|
||
cbBitPos progBandQuant
|
||
crBitPos progBandQuant
|
||
}
|
||
|
||
type rfxTileCoeffs = progTileState
|
||
|
||
type rfxProgTileWork struct {
|
||
tileType uint16
|
||
data []byte
|
||
}
|
||
|
||
type progRegionCtx struct {
|
||
quantVals []progBandQuant // numQuant entries (plain quants)
|
||
quantProgVals []progCodecQuant // numProgQuant entries
|
||
numQuant int
|
||
numProgQuant int
|
||
flags byte // RFX_DWT_REDUCE_EXTRAPOLATE
|
||
extrapolate bool
|
||
rects []rfxRect // 脏矩形:瓦片渲染必须裁剪到其并集内
|
||
}
|
||
|
||
type rfxProgressiveDecoder struct {
|
||
mu sync.RWMutex
|
||
tileCache map[uint32]*progTileState // key: yIdx<<16 | xIdx
|
||
rectsBuf []rfxRect
|
||
quantsBuf []progBandQuant
|
||
progQuantsBuf []progCodecQuant
|
||
tilesBuf []rfxProgTileWork
|
||
contextFlags byte // PROGRESSIVE_BLOCK_CONTEXT flags
|
||
logged [32]bool
|
||
}
|
||
|
||
// logOnce 每类失败只打第一条日志(复用 clearCodecCtx 的槽位思路)
|
||
func (d *rfxProgressiveDecoder) logOnce(slot int, msg string, args ...any) {
|
||
d.mu.Lock()
|
||
defer d.mu.Unlock()
|
||
if d.logged[slot] {
|
||
return
|
||
}
|
||
d.logged[slot] = true
|
||
slog.Warn("progressive:"+msg, args...)
|
||
}
|
||
|
||
func newRfxProgressiveDecoder() *rfxProgressiveDecoder {
|
||
return &rfxProgressiveDecoder{
|
||
tileCache: make(map[uint32]*progTileState),
|
||
}
|
||
}
|
||
|
||
// Reset discards the tile coefficient cache. Call this whenever the server
|
||
// starts a new progressive sequence (e.g. on RESET_GRAPHICS).
|
||
func (d *rfxProgressiveDecoder) Reset() {
|
||
d.mu.Lock()
|
||
old := d.tileCache
|
||
d.tileCache = make(map[uint32]*progTileState)
|
||
d.mu.Unlock()
|
||
for _, ts := range old {
|
||
progFreeTileState(ts)
|
||
}
|
||
}
|
||
|
||
func progFreeTileState(ts *progTileState) {
|
||
if ts == nil {
|
||
return
|
||
}
|
||
for c := 0; c < 3; c++ {
|
||
if ts.current[c] != nil {
|
||
coeffPool.Put(ts.current[c])
|
||
}
|
||
if ts.sign[c] != nil {
|
||
coeffPool.Put(ts.sign[c])
|
||
}
|
||
}
|
||
}
|
||
|
||
// rfxRect represents a rectangle of decoded tiles.
|
||
type rfxRect struct {
|
||
x, y, w, h int
|
||
}
|
||
|
||
// Decode processes RFX Progressive codec data, rendering tiles onto the
|
||
// provided surface buffer. Returns the bounding rectangles of decoded regions.
|
||
func (d *rfxProgressiveDecoder) Decode(data []byte, surfData []byte, width, height int) []rfxRect {
|
||
var rects []rfxRect
|
||
|
||
offset := 0
|
||
for offset+6 <= len(data) {
|
||
blockType := binary.LittleEndian.Uint16(data[offset:])
|
||
blockLen := binary.LittleEndian.Uint32(data[offset+2:])
|
||
|
||
if blockLen < 6 || offset+int(blockLen) > len(data) {
|
||
break
|
||
}
|
||
|
||
blockData := data[offset+6 : offset+int(blockLen)]
|
||
|
||
switch blockType {
|
||
case progWBTSync:
|
||
// magic + version — nothing to do.
|
||
case progWBTFrameBegin, progWBTFrameEnd:
|
||
// frame bookkeeping — nothing to do.
|
||
case progWBTContext:
|
||
// ctxId(1) + tileSize(2) + flags(1)
|
||
if len(blockData) >= 4 {
|
||
d.contextFlags = blockData[3]
|
||
}
|
||
case progWBTRegion:
|
||
regionRects, _ := d.parseRegion(blockData, surfData, width, height)
|
||
rects = append(rects, regionRects...)
|
||
default:
|
||
slog.Debug("RFX: unknown progressive block type", "type", blockType)
|
||
}
|
||
|
||
offset += int(blockLen)
|
||
}
|
||
|
||
return rects
|
||
}
|
||
|
||
// parseRegion extracts rects and quant tables from a PROGRESSIVE_WBT_REGION block,
|
||
// and decodes the tile sub-blocks embedded within it onto the surface.
|
||
func (d *rfxProgressiveDecoder) parseRegion(data []byte, surfData []byte, outW, outH int) ([]rfxRect, []progBandQuant) {
|
||
if len(data) < 12 {
|
||
return nil, nil
|
||
}
|
||
|
||
// tileSize := data[0]
|
||
numRects := int(binary.LittleEndian.Uint16(data[1:]))
|
||
numQuant := int(data[3])
|
||
numProgQuant := int(data[4])
|
||
flags := data[5]
|
||
numTiles := int(binary.LittleEndian.Uint16(data[6:]))
|
||
// tileDataSize := binary.LittleEndian.Uint32(data[8:])
|
||
|
||
offset := 12
|
||
extrapolate := flags&progFlagDWTReduceExtrapolate != 0
|
||
region := progRegionCtx{
|
||
numQuant: numQuant,
|
||
numProgQuant: numProgQuant,
|
||
flags: flags,
|
||
extrapolate: extrapolate,
|
||
}
|
||
|
||
// Parse rects (8 bytes each: x, y, width, height as uint16)
|
||
if cap(d.rectsBuf) >= numRects {
|
||
d.rectsBuf = d.rectsBuf[:numRects]
|
||
} else {
|
||
d.rectsBuf = make([]rfxRect, numRects)
|
||
}
|
||
rects := d.rectsBuf
|
||
for i := range numRects {
|
||
if offset+8 > len(data) {
|
||
return nil, nil
|
||
}
|
||
rx := int(binary.LittleEndian.Uint16(data[offset:]))
|
||
ry := int(binary.LittleEndian.Uint16(data[offset+2:]))
|
||
rw := int(binary.LittleEndian.Uint16(data[offset+4:]))
|
||
rh := int(binary.LittleEndian.Uint16(data[offset+6:]))
|
||
rects[i] = rfxRect{x: rx, y: ry, w: rw, h: rh}
|
||
offset += 8
|
||
}
|
||
region.rects = rects
|
||
|
||
// Parse plain quant values (5 bytes each, RDPEGFX band order)
|
||
if cap(d.quantsBuf) >= numQuant {
|
||
d.quantsBuf = d.quantsBuf[:numQuant]
|
||
} else {
|
||
d.quantsBuf = make([]progBandQuant, numQuant)
|
||
}
|
||
quants := d.quantsBuf
|
||
for i := range numQuant {
|
||
if offset+5 > len(data) {
|
||
return nil, nil
|
||
}
|
||
quants[i] = parseProgBandQuant(data[offset:])
|
||
offset += 5
|
||
}
|
||
region.quantVals = quants
|
||
|
||
// Parse progressive quant values (16 bytes each: quality + 3 components)
|
||
if cap(d.progQuantsBuf) >= numProgQuant {
|
||
d.progQuantsBuf = d.progQuantsBuf[:numProgQuant]
|
||
} else {
|
||
d.progQuantsBuf = make([]progCodecQuant, numProgQuant)
|
||
}
|
||
progQuants := d.progQuantsBuf
|
||
for i := range numProgQuant {
|
||
if offset+16 > len(data) {
|
||
return nil, nil
|
||
}
|
||
progQuants[i].quality = data[offset]
|
||
progQuants[i].y = parseProgBandQuant(data[offset+1:])
|
||
progQuants[i].cb = parseProgBandQuant(data[offset+6:])
|
||
progQuants[i].cr = parseProgBandQuant(data[offset+11:])
|
||
offset += 16
|
||
}
|
||
region.quantProgVals = progQuants
|
||
|
||
// Collect all decodable tiles before dispatching, so we can parallelise
|
||
// when there are enough to amortise goroutine overhead (same threshold as
|
||
// non-progressive decodeTileset in rfx.go).
|
||
if cap(d.tilesBuf) >= numTiles {
|
||
d.tilesBuf = d.tilesBuf[:0]
|
||
} else {
|
||
d.tilesBuf = make([]rfxProgTileWork, 0, numTiles)
|
||
}
|
||
tiles := d.tilesBuf
|
||
for offset+6 <= len(data) {
|
||
tileType := binary.LittleEndian.Uint16(data[offset:])
|
||
tileLen := binary.LittleEndian.Uint32(data[offset+2:])
|
||
if tileLen < 6 || offset+int(tileLen) > len(data) {
|
||
break
|
||
}
|
||
switch tileType {
|
||
case progWBTTileSimple, progWBTTileFirst, progWBTTileUpgrade:
|
||
tiles = append(tiles, rfxProgTileWork{tileType: tileType, data: data[offset+6 : offset+int(tileLen)]})
|
||
default:
|
||
slog.Debug("RFX: unknown progressive tile type", "type", tileType)
|
||
}
|
||
offset += int(tileLen)
|
||
}
|
||
d.tilesBuf = tiles
|
||
|
||
const parallelTileThreshold = 12
|
||
decodeTile := func(tw rfxProgTileWork, parallel bool) {
|
||
switch tw.tileType {
|
||
case progWBTTileSimple:
|
||
d.decodeTileSimple(tw.data, ®ion, surfData, outW, outH, parallel)
|
||
case progWBTTileFirst:
|
||
d.decodeTileFirst(tw.data, ®ion, surfData, outW, outH, parallel)
|
||
case progWBTTileUpgrade:
|
||
d.decodeTileUpgrade(tw.data, ®ion, surfData, outW, outH, parallel)
|
||
}
|
||
}
|
||
if len(tiles) >= parallelTileThreshold {
|
||
workers := min(runtime.NumCPU(), len(tiles))
|
||
ch := make(chan rfxProgTileWork, len(tiles))
|
||
for _, tw := range tiles {
|
||
ch <- tw
|
||
}
|
||
close(ch)
|
||
var wg sync.WaitGroup
|
||
for range workers {
|
||
wg.Go(func() {
|
||
defer func() {
|
||
if r := recover(); r != nil {
|
||
slog.Error("RFX progressive: tile decode panic", "err", r)
|
||
}
|
||
}()
|
||
for tw := range ch {
|
||
decodeTile(tw, false)
|
||
}
|
||
})
|
||
}
|
||
wg.Wait()
|
||
} else {
|
||
for _, tw := range tiles {
|
||
decodeTile(tw, true)
|
||
}
|
||
}
|
||
|
||
return rects, quants
|
||
}
|
||
|
||
// safeSlice returns data[offset:offset+length] when fully in range, else nil.
|
||
// Shared with the standard RemoteFX tileset decoder in rfx.go.
|
||
func safeSlice(data []byte, offset, length int) []byte {
|
||
if length <= 0 || offset < 0 || offset+length > len(data) {
|
||
return nil
|
||
}
|
||
return data[offset : offset+length]
|
||
}
|
||
|
||
// progTileHeader is the common prefix of all tile block headers.
|
||
type progTileHeader struct {
|
||
quantIdxY byte
|
||
quantIdxCb byte
|
||
quantIdxCr byte
|
||
xIdx int
|
||
yIdx int
|
||
flags byte
|
||
quality byte // 0xFF = full quality (quantProgValFull)
|
||
// simple/first
|
||
yLen, cbLen, crLen, tailLen int
|
||
// upgrade
|
||
ySrlLen, yRawLen int
|
||
cbSrlLen, cbRawLen int
|
||
crSrlLen, crRawLen int
|
||
}
|
||
|
||
// getProgTileState returns the cache entry for the tile, allocating a fresh
|
||
// state (releasing the old one) for a new SIMPLE/FIRST pass.
|
||
func (d *rfxProgressiveDecoder) getProgTileState(key uint32, firstPass bool) *progTileState {
|
||
d.mu.Lock()
|
||
defer d.mu.Unlock()
|
||
// 注意:FIRST pass 到达时不得销毁已有 state——服务器可能对同一瓦片
|
||
// 连续发送多个 FIRST(如 RFX_TILE_DIFFERENCE),其差分系数基于客户端
|
||
// 应持有的参考状态(Ref);重置会导致差分失去基准而产生花屏块。
|
||
ts := d.tileCache[key]
|
||
_ = firstPass
|
||
if ts == nil {
|
||
ts = &progTileState{}
|
||
for c := 0; c < 3; c++ {
|
||
ts.current[c] = coeffPool.Get().(*coeffArr)
|
||
ts.sign[c] = coeffPool.Get().(*coeffArr)
|
||
clear(ts.current[c][:])
|
||
clear(ts.sign[c][:])
|
||
}
|
||
ts.pass = 0
|
||
ts.yBitPos, ts.cbBitPos, ts.crBitPos = progBandQuant{}, progBandQuant{}, progBandQuant{}
|
||
d.tileCache[key] = ts
|
||
}
|
||
return ts
|
||
}
|
||
|
||
// selectQuant resolves the plain and progressive quant sets for a
|
||
// SIMPLE/FIRST tile. FIRST passes never consult the previous tile state:
|
||
// original tiles replace the reference and difference tiles add to it, so
|
||
// the numBits bookkeeping (an UPGRADE-only concept, FreeRDP computes it in
|
||
// progressive_rfx_upgrade_component only) must not gate FIRST decoding.
|
||
func selectQuant(region *progRegionCtx, hdr *progTileHeader,
|
||
which int) (plain, prog, shift progBandQuant, ok bool) {
|
||
var idx byte
|
||
switch which {
|
||
case 0:
|
||
idx = hdr.quantIdxY
|
||
case 1:
|
||
idx = hdr.quantIdxCb
|
||
default:
|
||
idx = hdr.quantIdxCr
|
||
}
|
||
if int(idx) >= len(region.quantVals) {
|
||
return progBandQuant{}, progBandQuant{}, progBandQuant{}, false
|
||
}
|
||
plain = region.quantVals[idx]
|
||
if hdr.quality == 0xFF {
|
||
prog = progBandQuant{} // quantProgValFull is all-zero in FreeRDP
|
||
} else {
|
||
if int(hdr.quality) >= len(region.quantProgVals) {
|
||
return progBandQuant{}, progBandQuant{}, progBandQuant{}, false
|
||
}
|
||
switch which {
|
||
case 0:
|
||
prog = region.quantProgVals[hdr.quality].y
|
||
case 1:
|
||
prog = region.quantProgVals[hdr.quality].cb
|
||
default:
|
||
prog = region.quantProgVals[hdr.quality].cr
|
||
}
|
||
}
|
||
combined := progAdd(plain, prog)
|
||
// 量化值为 0 的波段按规范不编码(MS-RDPRFX),该带 shift 不会应用到任何
|
||
// 系数;钳到 0 兼容组合值为 0 的区域量化,而不是丢弃整个瓦片。
|
||
shift = progShiftClamped(combined, 1)
|
||
return plain, prog, shift, true
|
||
}
|
||
|
||
// progShiftClamped returns max(a-v, 0) band-wise.
|
||
func progShiftClamped(a progBandQuant, v int) progBandQuant {
|
||
sub := func(x uint8) uint8 {
|
||
s := int(x) - v
|
||
if s < 0 {
|
||
return 0
|
||
}
|
||
return uint8(s)
|
||
}
|
||
return progBandQuant{
|
||
LL3: sub(a.LL3), HL3: sub(a.HL3), LH3: sub(a.LH3), HH3: sub(a.HH3),
|
||
HL2: sub(a.HL2), LH2: sub(a.LH2), HH2: sub(a.HH2),
|
||
HL1: sub(a.HL1), LH1: sub(a.LH1), HH1: sub(a.HH1),
|
||
}
|
||
}
|
||
|
||
// decodeTileSimple handles PROGRESSIVE_WBT_TILE_SIMPLE (0xCCC5).
|
||
func (d *rfxProgressiveDecoder) decodeTileSimple(data []byte, region *progRegionCtx, output []byte, outW, outH int, parallelComponents bool) {
|
||
d.decodeTileFirstPass(data, region, output, outW, outH, parallelComponents, progWBTTileSimple)
|
||
}
|
||
|
||
// decodeTileFirst handles PROGRESSIVE_WBT_TILE_FIRST (0xCCC6).
|
||
func (d *rfxProgressiveDecoder) decodeTileFirst(data []byte, region *progRegionCtx, output []byte, outW, outH int, parallelComponents bool) {
|
||
d.decodeTileFirstPass(data, region, output, outW, outH, parallelComponents, progWBTTileFirst)
|
||
}
|
||
|
||
// decodeTileFirstPass implements the shared SIMPLE/FIRST logic (first pass of
|
||
// a tile progression).
|
||
func (d *rfxProgressiveDecoder) decodeTileFirstPass(data []byte, region *progRegionCtx, output []byte, outW, outH int, parallelComponents bool, tileType uint16) {
|
||
hdrLen := 16
|
||
if tileType == progWBTTileFirst {
|
||
hdrLen = 17
|
||
}
|
||
if len(data) < hdrLen+1 {
|
||
return
|
||
}
|
||
hdr := progTileHeader{
|
||
quantIdxY: data[0],
|
||
quantIdxCb: data[1],
|
||
quantIdxCr: data[2],
|
||
xIdx: int(binary.LittleEndian.Uint16(data[3:])),
|
||
yIdx: int(binary.LittleEndian.Uint16(data[5:])),
|
||
flags: data[7],
|
||
quality: 0xFF,
|
||
}
|
||
// Length fields precede the payload: the SIMPLE header is 16 bytes
|
||
// (yLen@8, cbLen@10, crLen@12, tailLen@14), FIRST inserts quality@8 and
|
||
// shifts the lengths to offsets 9/11/13/15 with a 17-byte header.
|
||
if tileType == progWBTTileFirst {
|
||
hdr.quality = data[8]
|
||
hdr.yLen = int(binary.LittleEndian.Uint16(data[9:]))
|
||
hdr.cbLen = int(binary.LittleEndian.Uint16(data[11:]))
|
||
hdr.crLen = int(binary.LittleEndian.Uint16(data[13:]))
|
||
hdr.tailLen = int(binary.LittleEndian.Uint16(data[15:]))
|
||
} else {
|
||
hdr.yLen = int(binary.LittleEndian.Uint16(data[8:]))
|
||
hdr.cbLen = int(binary.LittleEndian.Uint16(data[10:]))
|
||
hdr.crLen = int(binary.LittleEndian.Uint16(data[12:]))
|
||
hdr.tailLen = int(binary.LittleEndian.Uint16(data[14:]))
|
||
}
|
||
off := hdrLen
|
||
|
||
yData := safeSlice(data, off, hdr.yLen)
|
||
off += hdr.yLen
|
||
cbData := safeSlice(data, off, hdr.cbLen)
|
||
off += hdr.cbLen
|
||
crData := safeSlice(data, off, hdr.crLen)
|
||
|
||
key := uint32(hdr.yIdx)<<16 | uint32(hdr.xIdx)
|
||
ts := d.getProgTileState(key, true)
|
||
|
||
var shifts, combineds [3]progBandQuant
|
||
for c := 0; c < 3; c++ {
|
||
plain, prog, shift, ok := selectQuant(region, &hdr, c)
|
||
if !ok {
|
||
return
|
||
}
|
||
shifts[c] = shift
|
||
combineds[c] = progAdd(plain, prog)
|
||
}
|
||
_ = combineds
|
||
|
||
coeffDiff := hdr.flags&progFlagTileDifference != 0
|
||
work := coeffPool.Get().(*coeffArr)
|
||
defer coeffPool.Put(work)
|
||
|
||
// Decode each component; the DWT output lands in `work`, which we snapshot
|
||
// per component before the next component reuses the buffer.
|
||
var spatial [3]*coeffArr
|
||
spatial[0] = coeffPool.Get().(*coeffArr)
|
||
spatial[1] = coeffPool.Get().(*coeffArr)
|
||
spatial[2] = coeffPool.Get().(*coeffArr)
|
||
for c := 0; c < 3; c++ {
|
||
var compData []byte
|
||
switch c {
|
||
case 0:
|
||
compData = yData
|
||
case 1:
|
||
compData = cbData
|
||
default:
|
||
compData = crData
|
||
}
|
||
progDecodeComponent(compData, shifts[c], work, ts.sign[c], ts.current[c], coeffDiff, region.extrapolate)
|
||
copy(spatial[c][:], work[:])
|
||
}
|
||
|
||
rfxPlaceTile(spatial[0][:], spatial[1][:], spatial[2][:], hdr.xIdx, hdr.yIdx, output, outW, outH, region.rects)
|
||
|
||
coeffPool.Put(spatial[0])
|
||
coeffPool.Put(spatial[1])
|
||
coeffPool.Put(spatial[2])
|
||
|
||
// FreeRDP: 每个 FIRST pass(含 DIFFERENCE)都把 pass 重置为 1,并把
|
||
// bitPos 记为 quant+quantProg(组合位位置);UPGRADE 用 bitPos 差计算
|
||
// numBits。
|
||
ts.pass = 1
|
||
ts.yBitPos = combineds[0]
|
||
ts.cbBitPos = combineds[1]
|
||
ts.crBitPos = combineds[2]
|
||
}
|
||
|
||
func arrMin(a []int16) int16 {
|
||
m := a[0]
|
||
for _, v := range a {
|
||
if v < m {
|
||
m = v
|
||
}
|
||
}
|
||
return m
|
||
}
|
||
|
||
func arrMax(a []int16) int16 {
|
||
m := a[0]
|
||
for _, v := range a {
|
||
if v > m {
|
||
m = v
|
||
}
|
||
}
|
||
return m
|
||
}
|
||
|
||
// decodeTileUpgrade handles PROGRESSIVE_WBT_TILE_UPGRADE (0xCCC7): a 20-byte
|
||
// header followed by SRL/RAW stream pairs per component.
|
||
func (d *rfxProgressiveDecoder) decodeTileUpgrade(data []byte, region *progRegionCtx, output []byte, outW, outH int, parallelComponents bool) {
|
||
const hdrLen = 20
|
||
if len(data) < hdrLen+1 {
|
||
return
|
||
}
|
||
hdr := progTileHeader{
|
||
quantIdxY: data[0],
|
||
quantIdxCb: data[1],
|
||
quantIdxCr: data[2],
|
||
xIdx: int(binary.LittleEndian.Uint16(data[3:])),
|
||
yIdx: int(binary.LittleEndian.Uint16(data[5:])),
|
||
quality: data[7],
|
||
}
|
||
hdr.ySrlLen = int(binary.LittleEndian.Uint16(data[8:]))
|
||
hdr.yRawLen = int(binary.LittleEndian.Uint16(data[10:]))
|
||
hdr.cbSrlLen = int(binary.LittleEndian.Uint16(data[12:]))
|
||
hdr.cbRawLen = int(binary.LittleEndian.Uint16(data[14:]))
|
||
hdr.crSrlLen = int(binary.LittleEndian.Uint16(data[16:]))
|
||
hdr.crRawLen = int(binary.LittleEndian.Uint16(data[18:]))
|
||
|
||
off := hdrLen
|
||
ySrl := safeSlice(data, off, hdr.ySrlLen)
|
||
off += hdr.ySrlLen
|
||
yRaw := safeSlice(data, off, hdr.yRawLen)
|
||
off += hdr.yRawLen
|
||
cbSrl := safeSlice(data, off, hdr.cbSrlLen)
|
||
off += hdr.cbSrlLen
|
||
cbRaw := safeSlice(data, off, hdr.cbRawLen)
|
||
off += hdr.cbRawLen
|
||
crSrl := safeSlice(data, off, hdr.crSrlLen)
|
||
off += hdr.crSrlLen
|
||
crRaw := safeSlice(data, off, hdr.crRawLen)
|
||
|
||
key := uint32(hdr.yIdx)<<16 | uint32(hdr.xIdx)
|
||
ts := d.getProgTileState(key, false)
|
||
if ts.pass == 0 {
|
||
// Upgrade for a tile we never saw the first pass of: nothing to
|
||
// refine — skip rather than corrupt the cache.
|
||
d.logOnce(20, "upgrade skipped: no first pass", "x", hdr.xIdx, "y", hdr.yIdx)
|
||
return
|
||
}
|
||
|
||
var shifts, numBitss, combineds [3]progBandQuant
|
||
for c := 0; c < 3; c++ {
|
||
plain, prog, shift, numBits, ok := selectQuantUpgrade(region, &hdr, ts, c)
|
||
if !ok {
|
||
d.logOnce(21, "upgrade quant resolve failed", "c", c, "quality", hdr.quality,
|
||
"quantIdxY", hdr.quantIdxY, "quantIdxCb", hdr.quantIdxCb, "quantIdxCr", hdr.quantIdxCr,
|
||
"nQuantVals", len(region.quantVals), "nQuantProgVals", len(region.quantProgVals),
|
||
"x", hdr.xIdx, "y", hdr.yIdx)
|
||
// 状态与 upgrade 目标不一致(此前的 pass 被丢弃或解析失败)。
|
||
// 重置该瓦片,让下一个 FIRST 以全新基准重建,避免永久陈旧内容。
|
||
ts.pass = 0
|
||
ts.yBitPos, ts.cbBitPos, ts.crBitPos = progBandQuant{}, progBandQuant{}, progBandQuant{}
|
||
return
|
||
}
|
||
shifts[c] = shift
|
||
numBitss[c] = numBits
|
||
combineds[c] = progAdd(plain, prog)
|
||
}
|
||
|
||
work := coeffPool.Get().(*coeffArr)
|
||
defer coeffPool.Put(work)
|
||
|
||
var spatial [3]*coeffArr
|
||
spatial[0] = coeffPool.Get().(*coeffArr)
|
||
spatial[1] = coeffPool.Get().(*coeffArr)
|
||
spatial[2] = coeffPool.Get().(*coeffArr)
|
||
|
||
for c := 0; c < 3; c++ {
|
||
var srlData, rawData []byte
|
||
switch c {
|
||
case 0:
|
||
srlData, rawData = ySrl, yRaw
|
||
case 1:
|
||
srlData, rawData = cbSrl, cbRaw
|
||
default:
|
||
srlData, rawData = crSrl, crRaw
|
||
}
|
||
progUpgradeComponent(work, ts.current[c], ts.sign[c], shifts[c], numBitss[c], srlData, rawData, region.extrapolate)
|
||
copy(spatial[c][:], work[:])
|
||
}
|
||
|
||
rfxPlaceTile(spatial[0][:], spatial[1][:], spatial[2][:], hdr.xIdx, hdr.yIdx, output, outW, outH, region.rects)
|
||
coeffPool.Put(spatial[0])
|
||
coeffPool.Put(spatial[1])
|
||
coeffPool.Put(spatial[2])
|
||
|
||
// 与 FIRST pass 相同:bitPos 记录组合位位置,供后续 UPGRADE 差分。
|
||
ts.yBitPos = combineds[0]
|
||
ts.cbBitPos = combineds[1]
|
||
ts.crBitPos = combineds[2]
|
||
ts.pass++
|
||
}
|
||
|
||
// selectQuantUpgrade resolves shift/numBits for an upgrade pass. numBits =
|
||
// previous bit position - new combined bit position (the newly significant
|
||
// bits delivered by the upgrade stream).
|
||
func selectQuantUpgrade(region *progRegionCtx, hdr *progTileHeader, ts *progTileState,
|
||
which int) (progBandQuant, progBandQuant, progBandQuant, progBandQuant, bool) {
|
||
var idx byte
|
||
switch which {
|
||
case 0:
|
||
idx = hdr.quantIdxY
|
||
case 1:
|
||
idx = hdr.quantIdxCb
|
||
default:
|
||
idx = hdr.quantIdxCr
|
||
}
|
||
if int(idx) >= len(region.quantVals) {
|
||
return progBandQuant{}, progBandQuant{}, progBandQuant{}, progBandQuant{}, false
|
||
}
|
||
plain := region.quantVals[idx]
|
||
var prog progBandQuant
|
||
if hdr.quality == 0xFF {
|
||
prog = progBandQuant{}
|
||
} else {
|
||
if int(hdr.quality) >= len(region.quantProgVals) {
|
||
return progBandQuant{}, progBandQuant{}, progBandQuant{}, progBandQuant{}, false
|
||
}
|
||
switch which {
|
||
case 0:
|
||
prog = region.quantProgVals[hdr.quality].y
|
||
case 1:
|
||
prog = region.quantProgVals[hdr.quality].cb
|
||
default:
|
||
prog = region.quantProgVals[hdr.quality].cr
|
||
}
|
||
}
|
||
combined := progAdd(plain, prog)
|
||
var prev progBandQuant
|
||
switch which {
|
||
case 0:
|
||
prev = ts.yBitPos
|
||
case 1:
|
||
prev = ts.cbBitPos
|
||
default:
|
||
prev = ts.crBitPos
|
||
}
|
||
shift, ok := progLSub(combined, 1)
|
||
if !ok {
|
||
dumpUpgradeQuantOnce("lsub-underflow", which, plain, prog, prev, combined)
|
||
return progBandQuant{}, progBandQuant{}, progBandQuant{}, progBandQuant{}, false
|
||
}
|
||
numBits, ok := progSub(prev, combined)
|
||
if !ok {
|
||
dumpUpgradeQuantOnce("numbits-underflow", which, plain, prog, prev, combined)
|
||
return progBandQuant{}, progBandQuant{}, progBandQuant{}, progBandQuant{}, false
|
||
}
|
||
return plain, prog, shift, numBits, true
|
||
}
|
||
|
||
// dumpUpgradeQuantOnce 诊断:UPGRADE 量化解析失败时转储全部带量化值。
|
||
// 仅首条生效,避免刷屏。
|
||
var upgradeQuantDumpOnce sync.Once
|
||
|
||
func dumpUpgradeQuantOnce(kind string, which int, plain, prog, prev, combined progBandQuant) {
|
||
upgradeQuantDumpOnce.Do(func() {
|
||
slog.Warn("progressive: upgrade quant detail",
|
||
"kind", kind, "comp", which,
|
||
"plain", bandQuantStr(plain),
|
||
"prog", bandQuantStr(prog),
|
||
"prev", bandQuantStr(prev),
|
||
"combined", bandQuantStr(combined))
|
||
})
|
||
}
|
||
|
||
func bandQuantStr(q progBandQuant) string {
|
||
return fmt.Sprintf("LL3=%d HL3=%d LH3=%d HH3=%d HL2=%d LH2=%d HH2=%d HL1=%d LH1=%d HH1=%d",
|
||
q.LL3, q.HL3, q.LH3, q.HH3, q.HL2, q.LH2, q.HH2, q.HL1, q.LH1, q.HH1)
|
||
}
|
||
|
||
// progDecodeComponent implements progressive_rfx_decode_component for the
|
||
// SIMPLE/FIRST first pass: RLGR decode → sign snapshot → LL3 differential →
|
||
// per-band left-shift dequant → current update → IDWT.
|
||
//
|
||
// MS-RDPEGFX 3.3.8.2.1.1:LL3 差分累积与按带反量化(DecProgQ*PQF)对
|
||
// ORIGINAL 与 DIFFERENCE 瓦片一视同仁;coeffDiff 只改变与参考状态 current
|
||
// 的合并方式(original 覆盖,difference 叠加)。与 FreeRDP
|
||
// progressive_rfx_decode_component / progressive_rfx_dwt_2d_decode 一致。
|
||
func progDecodeComponent(data []byte, shift progBandQuant, buf, sign, current *coeffArr, coeffDiff, extrapolate bool) {
|
||
b := buf[:]
|
||
if data == nil || len(data) == 0 {
|
||
clear(b)
|
||
} else {
|
||
rlgr1Decode(data, 4096, b)
|
||
}
|
||
copy(sign[:], b)
|
||
|
||
if !extrapolate {
|
||
progDiffDecode(b[4032:4096])
|
||
progDecodeBlock(b[0:1024], shift.HL1)
|
||
progDecodeBlock(b[1024:2048], shift.LH1)
|
||
progDecodeBlock(b[2048:3072], shift.HH1)
|
||
progDecodeBlock(b[3072:3328], shift.HL2)
|
||
progDecodeBlock(b[3328:3584], shift.LH2)
|
||
progDecodeBlock(b[3584:3840], shift.HH2)
|
||
progDecodeBlock(b[3840:3904], shift.HL3)
|
||
progDecodeBlock(b[3904:3968], shift.LH3)
|
||
progDecodeBlock(b[3968:4032], shift.HH3)
|
||
progDecodeBlock(b[4032:4096], shift.LL3)
|
||
} else {
|
||
progDiffDecode(b[4015:4096])
|
||
progDecodeBlock(b[0:1023], shift.HL1)
|
||
progDecodeBlock(b[1023:2046], shift.LH1)
|
||
progDecodeBlock(b[2046:3007], shift.HH1)
|
||
progDecodeBlock(b[3007:3279], shift.HL2)
|
||
progDecodeBlock(b[3279:3551], shift.LH2)
|
||
progDecodeBlock(b[3551:3807], shift.HH2)
|
||
progDecodeBlock(b[3807:3879], shift.HL3)
|
||
progDecodeBlock(b[3879:3951], shift.LH3)
|
||
progDecodeBlock(b[3951:4015], shift.HH3)
|
||
progDecodeBlock(b[4015:4096], shift.LL3)
|
||
}
|
||
|
||
if coeffDiff {
|
||
for i := range b {
|
||
current[i] += b[i]
|
||
}
|
||
copy(b, current[:])
|
||
} else {
|
||
copy(current[:], b)
|
||
}
|
||
if !extrapolate {
|
||
rfxInverseDWT2D(b)
|
||
} else {
|
||
progDWTExtrapolate(b)
|
||
}
|
||
}
|
||
|
||
// progDiffDecode is rfx_differential_decode (in-place cumulative sum).
|
||
func progDiffDecode(data []int16) {
|
||
for i := 1; i < len(data); i++ {
|
||
data[i] += data[i-1]
|
||
}
|
||
}
|
||
|
||
// progDecodeBlock is progressive_rfx_decode_block (left-shift dequant).
|
||
func progDecodeBlock(data []int16, shift uint8) {
|
||
if shift == 0 {
|
||
return
|
||
}
|
||
s := int16(shift)
|
||
for i := range data {
|
||
data[i] <<= s
|
||
}
|
||
}
|
||
|
||
// ── Upgrade bit streams ────────────────────────────────────────────────────
|
||
|
||
// progBitStream is an MSB-first bit reader matching FreeRDP's wBitStream
|
||
// semantics (zero-padding past the end).
|
||
type progBitStream struct {
|
||
data []byte
|
||
bytePos int
|
||
acc uint32
|
||
bits int
|
||
posBits int
|
||
}
|
||
|
||
func (b *progBitStream) fill() {
|
||
for b.bits <= 24 && b.bytePos < len(b.data) {
|
||
b.acc |= uint32(b.data[b.bytePos]) << uint(24-b.bits)
|
||
b.bits += 8
|
||
b.bytePos++
|
||
}
|
||
}
|
||
|
||
func (b *progBitStream) readBit() uint32 {
|
||
b.fill()
|
||
v := (b.acc >> 31) & 1
|
||
b.acc <<= 1
|
||
if b.bits > 0 {
|
||
b.bits--
|
||
}
|
||
b.posBits++
|
||
return v
|
||
}
|
||
|
||
func (b *progBitStream) readBits(n uint) uint32 {
|
||
if n == 0 {
|
||
return 0
|
||
}
|
||
b.fill()
|
||
var v uint32
|
||
if b.bits >= int(n) {
|
||
v = (b.acc >> uint(32-int(n))) & uint32((1<<n)-1)
|
||
b.acc <<= n
|
||
b.bits -= int(n)
|
||
} else {
|
||
avail := b.bits
|
||
if avail > 0 {
|
||
v = (b.acc >> uint(32-avail)) & uint32((1<<avail)-1)
|
||
}
|
||
v <<= n - uint(avail)
|
||
b.acc = 0
|
||
b.bits = 0
|
||
}
|
||
b.posBits += int(n)
|
||
return v
|
||
}
|
||
|
||
func (b *progBitStream) skip(n uint) {
|
||
b.posBits += int(n)
|
||
}
|
||
|
||
func (b *progBitStream) remaining() int {
|
||
return len(b.data)*8 - b.posBits
|
||
}
|
||
|
||
type progUpgradeState struct {
|
||
srl *progBitStream
|
||
raw *progBitStream
|
||
kp int
|
||
nz int
|
||
mode int
|
||
nonLL bool
|
||
}
|
||
|
||
// srlRead ports progressive_rfx_srl_read.
|
||
func (st *progUpgradeState) srlRead(numBits uint32) int16 {
|
||
if st.nz != 0 {
|
||
st.nz--
|
||
return 0
|
||
}
|
||
k := uint(st.kp / 8)
|
||
if st.mode == 0 {
|
||
// zero encoding
|
||
bit := st.srl.readBit()
|
||
if bit == 0 {
|
||
st.nz = 1 << k
|
||
st.kp += 4
|
||
if st.kp > 80 {
|
||
st.kp = 80
|
||
}
|
||
st.nz--
|
||
return 0
|
||
}
|
||
// '1' bit: nz comes from the next k bits
|
||
st.nz = 0
|
||
st.mode = 1
|
||
if k > 0 {
|
||
st.nz = int(st.srl.readBits(k))
|
||
}
|
||
if st.nz != 0 {
|
||
st.nz--
|
||
return 0
|
||
}
|
||
}
|
||
st.mode = 0
|
||
// unary encoding; read sign bit
|
||
sign := st.srl.readBit()
|
||
if st.kp < 6 {
|
||
st.kp = 0
|
||
} else {
|
||
st.kp -= 6
|
||
}
|
||
if numBits == 1 {
|
||
if sign != 0 {
|
||
return -1
|
||
}
|
||
return 1
|
||
}
|
||
mag := uint32(1)
|
||
max := uint32(1<<numBits) - 1
|
||
for mag < max {
|
||
bit := st.srl.readBit()
|
||
if bit != 0 {
|
||
break
|
||
}
|
||
mag++
|
||
}
|
||
if mag > 32767 {
|
||
mag = 32767
|
||
}
|
||
if sign != 0 {
|
||
return -int16(mag)
|
||
}
|
||
return int16(mag)
|
||
}
|
||
|
||
func progRawShift(raw *progBitStream, numBits uint32) int16 {
|
||
return int16(raw.readBits(uint(numBits)))
|
||
}
|
||
|
||
// progUpgradeBlock ports progressive_rfx_upgrade_block.
|
||
func progUpgradeBlock(st *progUpgradeState, buf, sign []int16, length uint32, shift, numBits uint32) {
|
||
if numBits < 1 {
|
||
return
|
||
}
|
||
raw := st.raw
|
||
if !st.nonLL {
|
||
for i := uint32(0); i < length; i++ {
|
||
input := progRawShift(raw, numBits)
|
||
buf[i] = int16(int32(buf[i]) + (int32(input) << shift))
|
||
}
|
||
return
|
||
}
|
||
for i := uint32(0); i < length; i++ {
|
||
var input int32
|
||
switch {
|
||
case sign[i] > 0:
|
||
input = int32(progRawShift(raw, numBits))
|
||
case sign[i] < 0:
|
||
input = -int32(progRawShift(raw, numBits))
|
||
default:
|
||
input = int32(st.srlRead(numBits))
|
||
sign[i] = int16(input)
|
||
}
|
||
buf[i] = int16(int32(buf[i]) + (input << shift))
|
||
}
|
||
}
|
||
|
||
// progUpgradeStateFinish ports progressive_rfx_upgrade_state_finish: byte-
|
||
// align both streams and drop a trailing 8-bit srl remainder.
|
||
func progUpgradeStateFinish(st *progUpgradeState) {
|
||
raw, srl := st.raw, st.srl
|
||
if pad := (8 - raw.posBits%8) % 8; pad > 0 {
|
||
raw.skip(uint(pad))
|
||
}
|
||
if pad := (8 - srl.posBits%8) % 8; pad > 0 {
|
||
srl.skip(uint(pad))
|
||
}
|
||
if srl.remaining() == 8 {
|
||
srl.skip(8)
|
||
}
|
||
}
|
||
|
||
// progUpgradeComponent ports progressive_rfx_upgrade_component: refines the
|
||
// cached coefficients (current) in extrapolate layout using an SRL stream
|
||
// (for sign==0 coefficients) and a RAW stream (for the rest).
|
||
func progUpgradeComponent(buf, current, sign *coeffArr, shift, numBits progBandQuant, srlData, rawData []byte, extrapolate bool) {
|
||
st := progUpgradeState{
|
||
kp: 8,
|
||
mode: 0,
|
||
srl: &progBitStream{data: srlData},
|
||
raw: &progBitStream{data: rawData},
|
||
}
|
||
cur := current[:]
|
||
sgn := sign[:]
|
||
|
||
st.nonLL = true
|
||
progUpgradeBlock(&st, cur[0:1023], sgn[0:1023], 1023, uint32(shift.HL1), uint32(numBits.HL1))
|
||
progUpgradeBlock(&st, cur[1023:2046], sgn[1023:2046], 1023, uint32(shift.LH1), uint32(numBits.LH1))
|
||
progUpgradeBlock(&st, cur[2046:3007], sgn[2046:3007], 961, uint32(shift.HH1), uint32(numBits.HH1))
|
||
progUpgradeBlock(&st, cur[3007:3279], sgn[3007:3279], 272, uint32(shift.HL2), uint32(numBits.HL2))
|
||
progUpgradeBlock(&st, cur[3279:3551], sgn[3279:3551], 272, uint32(shift.LH2), uint32(numBits.LH2))
|
||
progUpgradeBlock(&st, cur[3551:3807], sgn[3551:3807], 256, uint32(shift.HH2), uint32(numBits.HH2))
|
||
progUpgradeBlock(&st, cur[3807:3879], sgn[3807:3879], 72, uint32(shift.HL3), uint32(numBits.HL3))
|
||
progUpgradeBlock(&st, cur[3879:3951], sgn[3879:3951], 72, uint32(shift.LH3), uint32(numBits.LH3))
|
||
progUpgradeBlock(&st, cur[3951:4015], sgn[3951:4015], 64, uint32(shift.HH3), uint32(numBits.HH3))
|
||
|
||
st.nonLL = false
|
||
progUpgradeBlock(&st, cur[4015:4096], sgn[4015:4096], 81, uint32(shift.LL3), uint32(numBits.LL3))
|
||
|
||
progUpgradeStateFinish(&st)
|
||
|
||
// dwt_2d_decode(..., reverse=TRUE): buffer = current, then IDWT.
|
||
copy(buf[:], cur)
|
||
if !extrapolate {
|
||
rfxInverseDWT2D(buf[:])
|
||
} else {
|
||
progDWTExtrapolate(buf[:])
|
||
}
|
||
}
|
||
|
||
// ── Extrapolate IDWT (progressive_rfx_dwt_2d_decode_block) ─────────────────
|
||
|
||
func progBandLCount(level int) int { return (64 >> level) + 1 }
|
||
|
||
func progBandHCount(level int) int {
|
||
if level == 1 {
|
||
return (64 >> 1) - 1
|
||
}
|
||
return (64 + (1 << uint(level-1))) >> level
|
||
}
|
||
|
||
func progClamp16(v int32) int16 {
|
||
if v < -32768 {
|
||
return -32768
|
||
}
|
||
if v > 32767 {
|
||
return 32767
|
||
}
|
||
return int16(v)
|
||
}
|
||
|
||
// progDWTExtrapolate ports rfx_dwt_2d_extrapolate_decode: three irregular
|
||
// blocks at fixed offsets covering the extrapolate band layout.
|
||
func progDWTExtrapolate(buffer []int16) {
|
||
bufs := idwtBufPool.Get().(*idwtBufs)
|
||
tmp := bufs.tmp[:]
|
||
progDWT2DBlock(buffer[3807:], tmp, 3)
|
||
progDWT2DBlock(buffer[3007:], tmp, 2)
|
||
progDWT2DBlock(buffer[0:], tmp, 1)
|
||
idwtBufPool.Put(bufs)
|
||
}
|
||
|
||
// progDWT2DBlock decodes one extrapolate block in place.
|
||
func progDWT2DBlock(buffer, temp []int16, level int) {
|
||
nBandL := progBandLCount(level)
|
||
nBandH := progBandHCount(level)
|
||
|
||
hlLen := nBandH * nBandL
|
||
lhLen := nBandL * nBandH
|
||
hhLen := nBandH * nBandH
|
||
llLen := nBandL * nBandL
|
||
|
||
hl := buffer[0:hlLen]
|
||
lh := buffer[hlLen : hlLen+lhLen]
|
||
hh := buffer[hlLen+lhLen : hlLen+lhLen+hhLen]
|
||
ll := buffer[hlLen+lhLen+hhLen : hlLen+lhLen+hhLen+llLen]
|
||
|
||
dstStep := nBandL + nBandH
|
||
lBuf := temp[0 : nBandL*dstStep]
|
||
hBuf := temp[nBandL*dstStep : nBandL*dstStep+nBandH*dstStep]
|
||
|
||
progIDWTX(ll, nBandL, hl, nBandH, lBuf, dstStep, nBandL, nBandH, nBandL)
|
||
progIDWTX(lh, nBandL, hh, nBandH, hBuf, dstStep, nBandL, nBandH, nBandH)
|
||
progIDWTY(lBuf, dstStep, hBuf, dstStep, buffer, dstStep, nBandL, nBandH, nBandL+nBandH)
|
||
}
|
||
|
||
// progIDWTX ports progressive_rfx_idwt_x (horizontal 1-D IDWT of every row).
|
||
// Index arithmetic instead of slice reslicing: the C original walks pointers
|
||
// one element past the final read, which Go bounds checks reject.
|
||
func progIDWTX(low []int16, lowStep int, high []int16, highStep int, dst []int16, dstStep int, lowCount, highCount, dstCount int) {
|
||
for i := 0; i < dstCount; i++ {
|
||
lRow := low[i*lowStep:]
|
||
hRow := high[i*highStep:]
|
||
xRow := dst[i*dstStep:]
|
||
H0 := hRow[0]
|
||
L0 := lRow[0]
|
||
li, hi := 1, 1
|
||
xi := 0
|
||
X0 := progClamp16(int32(L0) - int32(H0))
|
||
X2 := X0
|
||
for j := 0; j < highCount-1; j++ {
|
||
H1 := hRow[hi]
|
||
hi++
|
||
L0 = lRow[li]
|
||
li++
|
||
X2 = progClamp16(int32(L0) - (int32(H0)+int32(H1))/2)
|
||
X1 := progClamp16((int32(X0)+int32(X2))/2 + 2*int32(H0))
|
||
xRow[xi] = X0
|
||
xRow[xi+1] = X1
|
||
xi += 2
|
||
X0 = X2
|
||
H0 = H1
|
||
}
|
||
switch {
|
||
case lowCount <= highCount:
|
||
xRow[xi] = X2
|
||
xRow[xi+1] = progClamp16(int32(X2) + 2*int32(H0))
|
||
case lowCount == highCount+1:
|
||
L0 = lRow[li]
|
||
X0t := progClamp16(int32(L0) - int32(H0))
|
||
xRow[xi] = X2
|
||
xRow[xi+1] = progClamp16((int32(X0t)+int32(X2))/2 + 2*int32(H0))
|
||
xRow[xi+2] = X0t
|
||
default:
|
||
L0 = lRow[li]
|
||
li++
|
||
X0t := progClamp16(int32(L0) - int32(H0)/2)
|
||
xRow[xi] = X2
|
||
xRow[xi+1] = progClamp16((int32(X0t)+int32(X2))/2 + 2*int32(H0))
|
||
xRow[xi+2] = X0t
|
||
L0 = lRow[li]
|
||
xRow[xi+3] = progClamp16((int32(X0t) + int32(L0)) / 2)
|
||
}
|
||
}
|
||
}
|
||
|
||
// progIDWTY ports progressive_rfx_idwt_y (vertical 1-D IDWT of every column).
|
||
// Index arithmetic instead of slice reslicing: the C original walks pointers
|
||
// one element past the final read, which Go bounds checks reject.
|
||
func progIDWTY(low []int16, lowStep int, high []int16, highStep int, dst []int16, dstStep int, lowCount, highCount, dstCount int) {
|
||
for i := 0; i < dstCount; i++ {
|
||
H0 := high[i]
|
||
L0 := low[i]
|
||
li, hi := 1, 1
|
||
xi := 0
|
||
X0 := progClamp16(int32(L0) - int32(H0))
|
||
X2 := X0
|
||
for j := 0; j < highCount-1; j++ {
|
||
H1 := high[i+hi*highStep]
|
||
hi++
|
||
L0 = low[i+li*lowStep]
|
||
li++
|
||
X2 = progClamp16(int32(L0) - (int32(H0)+int32(H1))/2)
|
||
X1 := progClamp16((int32(X0)+int32(X2))/2 + 2*int32(H0))
|
||
dst[i+xi] = X0
|
||
xi += dstStep
|
||
dst[i+xi] = X1
|
||
xi += dstStep
|
||
X0 = X2
|
||
H0 = H1
|
||
}
|
||
switch {
|
||
case lowCount <= highCount:
|
||
dst[i+xi] = X2
|
||
dst[i+xi+dstStep] = progClamp16(int32(X2) + 2*int32(H0))
|
||
case lowCount == highCount+1:
|
||
L0 = low[i+li*lowStep]
|
||
X0t := progClamp16(int32(L0) - int32(H0))
|
||
dst[i+xi] = X2
|
||
dst[i+xi+dstStep] = progClamp16((int32(X0t)+int32(X2))/2 + 2*int32(H0))
|
||
dst[i+xi+2*dstStep] = X0t
|
||
default:
|
||
L0 = low[i+li*lowStep]
|
||
li++
|
||
X0t := progClamp16(int32(L0) - int32(H0)/2)
|
||
dst[i+xi] = X2
|
||
dst[i+xi+dstStep] = progClamp16((int32(X0t)+int32(X2))/2 + 2*int32(H0))
|
||
dst[i+xi+2*dstStep] = X0t
|
||
L0 = low[i+li*lowStep]
|
||
dst[i+xi+3*dstStep] = progClamp16((int32(X0t) + int32(L0)) / 2)
|
||
}
|
||
}
|
||
}
|
||
|
||
// rfxPlaceTile converts YCbCr tile to BGRA using tile-grid indices (xIdx, yIdx).
|
||
// rfxPlaceTile 把解码后的瓦片绘制到表面。region.rects 非空时只绘制与矩形
|
||
// 并集相交的瓦片,但相交的瓦片必须整块 64×64 落屏(FreeRDP update_tiles
|
||
// 语义:rects 仅用于筛掉不相交的瓦片,tile 边界按 64 对齐外延,矩形外的
|
||
// 瓦片像素同样是本帧的有效内容)。实测 Win10 最小化动画:region 矩形
|
||
// (34,254 662x374) 的瓦片从 yIdx=3(y=192)开始,让出的条带 y=198..254
|
||
// 只存在于瓦片内——若按矩形交集裁剪,该条带永远不被重绘,留下残影。
|
||
func rfxPlaceTile(yCoeffs, cbCoeffs, crCoeffs []int16, xIdx, yIdx int, output []byte, outW, outH int, rects []rfxRect) {
|
||
tileX := xIdx * rfxTileSize
|
||
tileY := yIdx * rfxTileSize
|
||
if len(rects) > 0 {
|
||
intersects := false
|
||
for _, rc := range rects {
|
||
if tileX < rc.x+rc.w && tileX+rfxTileSize > rc.x &&
|
||
tileY < rc.y+rc.h && tileY+rfxTileSize > rc.y {
|
||
intersects = true
|
||
break
|
||
}
|
||
}
|
||
if !intersects {
|
||
return
|
||
}
|
||
}
|
||
rfxPlaceTileAbs(yCoeffs, cbCoeffs, crCoeffs, tileX, tileY, output, outW, outH)
|
||
}
|
||
|
||
// rfxPlaceTileAbs converts YCbCr tile to BGRA and writes into the output buffer
|
||
// at absolute pixel coordinates (tileX, tileY).
|
||
// Uses ICT (Irreversible Color Transform) from MS-RDPRFX.
|
||
func rfxPlaceTileAbs(yCoeffs, cbCoeffs, crCoeffs []int16, tileX, tileY int, output []byte, outW, outH int) {
|
||
tileW := rfxTileSize
|
||
tileH := rfxTileSize
|
||
if tileX+tileW > outW {
|
||
tileW = outW - tileX
|
||
}
|
||
if tileY+tileH > outH {
|
||
tileH = outH - tileY
|
||
}
|
||
if tileW <= 0 || tileH <= 0 {
|
||
return
|
||
}
|
||
|
||
for row := 0; row < tileH; row++ {
|
||
dstStart := ((tileY+row)*outW + tileX) * 4
|
||
dstEnd := dstStart + tileW*4
|
||
if dstStart < 0 || dstEnd > len(output) {
|
||
continue
|
||
}
|
||
dstRow := output[dstStart:dstEnd:dstEnd]
|
||
srcOff := row * rfxTileSize
|
||
ictToBGRA(
|
||
yCoeffs[srcOff:srcOff+tileW:srcOff+tileW],
|
||
cbCoeffs[srcOff:srcOff+tileW:srcOff+tileW],
|
||
crCoeffs[srcOff:srcOff+tileW:srcOff+tileW],
|
||
dstRow, tileW,
|
||
)
|
||
}
|
||
}
|