codec

package
v1.0.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 15, 2026 License: MIT Imports: 7 Imported by: 0

README

internal/codec

Bitmap codec implementation for RDP graphics decompression.

Specification References

Overview

This package implements RDP bitmap decompression and color conversion utilities. It supports multiple compression formats used by RDP servers:

  • NSCodec - Microsoft's Network Screen Codec (AYCoCg color space)
  • RDP6 Planar - Planar bitmap compression for 32bpp
  • Interleaved RLE - Run-length encoding for 8/15/16/24 bpp
  • Color Conversion - RGB555, RGB565, BGR24, BGRA32 to RGBA

For RemoteFX (RFX) wavelet codec, see the rfx/ subpackage.

For detailed technical documentation:

Files

File Purpose
NSCodec
decoder.go High-level NSCodec decoder API
nscodec.go NSCodec utilities (RLE, chroma, color space)
nscodec_test.go NSCodec unit tests
Planar
planar.go RDP6 Planar codec decompression
planar_test.go Planar codec tests
RLE
rle_common.go RLE constants and utilities
rle8.go 8-bit RLE decompression
rle15.go 15-bit RLE decompression
rle16.go 16-bit RLE decompression
rle24.go 24-bit RLE decompression
rle32.go 32-bit handling (delegates to planar)
rle_test.go, rle8_test.go RLE tests
Utilities
bitmap.go Flip, palette, color conversion
bitmap_test.go Bitmap utility tests
encode.go UTF-16 encoding utility
security.go Security flag wrapping
security_test.go Security tests

Architecture

┌─────────────────────────────────────────────────────────────────────┐
│                       ProcessBitmap()                                │
│                      (unified entry point)                           │
└───────────────────────────────┬─────────────────────────────────────┘
                                │
        ┌───────────────────────┼───────────────────────┐
        │                       │                       │
        ▼                       ▼                       ▼
┌───────────────┐     ┌─────────────────┐     ┌─────────────────────┐
│  NSCodec      │     │  Planar Codec   │     │  Interleaved RLE    │
│  Decode()     │     │  Decompress()   │     │  RLEDecompress*()   │
└───────┬───────┘     └────────┬────────┘     └──────────┬──────────┘
        │                      │                         │
        ▼                      │                         │
┌───────────────┐              │                         │
│ AYCoCg→RGBA   │              │                         │
│ Chroma upsamp │              │                         │
│ Color restore │              │                         │
└───────┬───────┘              │                         │
        │                      │                         │
        └──────────────────────┼─────────────────────────┘
                               │
                               ▼
                    ┌─────────────────────┐
                    │  Color Conversion   │
                    │  (to 32-bit RGBA)   │
                    └──────────┬──────────┘
                               │
                               ▼
                    ┌─────────────────────┐
                    │   FlipVertical()    │
                    │  (bottom-up fix)    │
                    └──────────┬──────────┘
                               │
                               ▼
                    ┌─────────────────────┐
                    │   RGBA Output       │
                    │  (4 bytes/pixel)    │
                    └─────────────────────┘

NSCodec

Microsoft's proprietary codec using YCoCg color space for efficient screen content compression.

Wire Format
NSCODEC_BITMAP_STREAM (20+ bytes header)
├── LumaPlaneByteCount         (4 bytes)
├── OrangeChromaPlaneByteCount (4 bytes)
├── GreenChromaPlaneByteCount  (4 bytes)
├── AlphaPlaneByteCount        (4 bytes)
├── ColorLossLevel             (1 byte, 1-7)
├── ChromaSubsamplingLevel     (1 byte)
├── Reserved                   (2 bytes)
└── Plane data...
Color Space Conversion
// YCoCg to RGB
t := Y - Cg
R := clamp(t + Co)
G := clamp(Y + Cg)
B := clamp(t - Co)
Usage
// High-level API
rgba, err := codec.Decode(nscodecData, width, height)

// Or parse first, then decode
stream, err := codec.ParseBitmapStream(data)
rgba, err := stream.Decode(width, height)

Planar Codec

RDP6 codec that separates color channels into planes.

Format
Header (1 byte):
├── Bits 7-6: Reserved (0)
├── Bit 5: NoAlpha (1 = no alpha plane)
└── Bit 4: RLE (1 = RLE compressed)

Planes (in order): Alpha, Red, Green, Blue
Usage
rgba := codec.DecompressPlanar(src, width, height)

Interleaved RLE

Classic RDP bitmap compression with multiple order codes.

Order Codes
Code Name Description
0x0 BG_RUN Background color run
0x1 FG_RUN Foreground color run
0x2 FGBG_IMAGE Bitmask-based image
0x3 COLOR_RUN Single color run
0x4 COLOR_IMAGE Raw pixel data
0xF0-0xF8 MEGA_MEGA Extended 16-bit lengths
0xFD WHITE White pixel
0xFE BLACK Black pixel
Usage
// Per bit-depth
ok := codec.RLEDecompress8(src, dst, rowDelta)
ok := codec.RLEDecompress16(src, dst, rowDelta)
ok := codec.RLEDecompress24(src, dst, rowDelta)

Color Conversion

Supported Formats
Source Function Bits
Palette Palette8ToRGBA() 8
RGB555 RGB555ToRGBA() 15
RGB565 RGB565ToRGBA() 16
BGR24 BGR24ToRGBA() 24
BGRA32 BGRA32ToRGBA() 32
Palette Management
// Set palette from server
codec.SetPalette(paletteData, 256)

// Convert paletted to RGBA
codec.Palette8ToRGBA(src, dst)

Unified Processing

// ProcessBitmap handles all formats uniformly
rgba := codec.ProcessBitmap(
    src,          // Compressed data
    width,        // Bitmap width
    height,       // Bitmap height
    bpp,          // 8, 15, 16, 24, or 32
    isCompressed, // true if RLE/Planar compressed
    rowDelta,     // Row stride for uncompressed
)

Key Design Decisions

Why bottom-up flip?

RDP servers send bitmaps in bottom-up order (like Windows BMP format). We flip to top-down for standard display APIs that expect top-left origin.

Why separate RLE files?

Each bit depth has different pixel read/write logic and special handling. Separating them improves code clarity and allows bit-depth-specific optimizations.

Why AYCoCg color space?

NSCodec uses YCoCg because:

  • Better decorrelation than RGB → higher compression
  • Integer-only math → fast conversion
  • Reversible → lossless mode supported

Testing

go test ./internal/codec/...
go test -cover ./internal/codec/...
  • internal/codec/rfx - RemoteFX wavelet codec (64×64 tiles)
  • internal/rdp - Uses codecs to process bitmap updates
  • internal/protocol/fastpath - Delivers compressed bitmaps
  • web/src/wasm - WASM version of codecs for browser

Documentation

Overview

Package codec implements RDP bitmap decompression algorithms including interleaved RLE, planar, and NSCodec as specified in MS-RDPBCGR and MS-RDPNSC.

Package nscodec implements the NSCodec bitmap codec decoder as specified in MS-RDPNSC. NSCodec compresses 24/32 bpp images using AYCoCg color space conversion and RLE compression.

Package codec implements RLE decompression for RDP bitmap data. This implements the Interleaved RLE algorithm as specified in MS-RDPBCGR section 2.2.9.1.1.3.1.2.4.

Index

Constants

View Source
const (
	// Format header flags
	PlanarFlagRLE     = 0x10 // Run Length Encoding
	PlanarFlagNoAlpha = 0x20 // No Alpha plane
)
View Source
const (
	RegularBgRun         = 0x0
	RegularFgRun         = 0x1
	RegularFgBgImage     = 0x2
	RegularColorRun      = 0x3
	RegularColorImage    = 0x4
	MegaMegaBgRun        = 0xF0
	MegaMegaFgRun        = 0xF1
	MegaMegaFgBgImage    = 0xF2
	MegaMegaColorRun     = 0xF3
	MegaMegaColorImage   = 0xF4
	MegaMegaSetFgRun     = 0xF6
	MegaMegaSetFgBgImage = 0xF7
	MegaMegaDitheredRun  = 0xF8
	LiteSetFgFgRun       = 0xC
	LiteSetFgFgBgImage   = 0xD
	LiteDitheredRun      = 0xE
	SpecialFgBg1         = 0xF9
	SpecialFgBg2         = 0xFA
	White                = 0xFD
	Black                = 0xFE
)

RLE compression order codes

Variables

View Source
var (
	ErrInvalidStream     = errors.New("nscodec: invalid bitmap stream")
	ErrInvalidPlaneSize  = errors.New("nscodec: invalid plane size")
	ErrInvalidColorLoss  = errors.New("nscodec: invalid color loss level")
	ErrDecompressionFail = errors.New("nscodec: RLE decompression failed")
)
View Source
var FgBgBitmasks = []byte{0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80}

FgBgBitmasks contains bit masks used for foreground/background image orders.

View Source
var NSCodecGUID = [16]byte{
	0xB9, 0x1B, 0x8D, 0xCA, 0x0F, 0x00, 0x4F, 0x15,
	0x58, 0x9F, 0xAE, 0x2D, 0x1A, 0x87, 0xE2, 0xD6,
}

NSCodec GUID: CA8D1BB9-000F-154F-589F-AE2D1A87E2D6

View Source
var Pixel15 = PixelFormat[uint16]{
	BytesPerPixel: 2,
	WhitePixel:    0x7FFF,
	BlackPixel:    0x0000,
	ReadPixel: func(data []byte, idx int) uint16 {
		if idx+1 >= len(data) {
			return 0
		}
		return uint16(data[idx]) | (uint16(data[idx+1]) << 8)
	},
	WritePixel: func(data []byte, idx int, pixel uint16) {
		if idx+1 >= len(data) {
			return
		}
		data[idx] = byte(pixel & 0xFF)
		data[idx+1] = byte((pixel >> 8) & 0xFF)
	},
}

Pixel15 defines the 15-bit pixel format (2 bytes per pixel, RGB555).

View Source
var Pixel16 = PixelFormat[uint16]{
	BytesPerPixel: 2,
	WhitePixel:    0xFFFF,
	BlackPixel:    0x0000,
	ReadPixel: func(data []byte, idx int) uint16 {
		if idx+1 >= len(data) {
			return 0
		}
		return uint16(data[idx]) | (uint16(data[idx+1]) << 8)
	},
	WritePixel: func(data []byte, idx int, pixel uint16) {
		if idx+1 >= len(data) {
			return
		}
		data[idx] = byte(pixel & 0xFF)
		data[idx+1] = byte((pixel >> 8) & 0xFF)
	},
}

Pixel16 defines the 16-bit pixel format (2 bytes per pixel, RGB565).

View Source
var Pixel24 = PixelFormat[uint32]{
	BytesPerPixel: 3,
	WhitePixel:    0xFFFFFF,
	BlackPixel:    0x000000,
	ReadPixel: func(data []byte, idx int) uint32 {
		if idx+2 >= len(data) {
			return 0
		}
		return uint32(data[idx]) | (uint32(data[idx+1]) << 8) | (uint32(data[idx+2]) << 16)
	},
	WritePixel: func(data []byte, idx int, pixel uint32) {
		if idx+2 >= len(data) {
			return
		}
		data[idx] = byte(pixel & 0xFF)
		data[idx+1] = byte((pixel >> 8) & 0xFF)
		data[idx+2] = byte((pixel >> 16) & 0xFF)
	},
}

Pixel24 defines the 24-bit pixel format (3 bytes per pixel, RGB888).

View Source
var Pixel32 = PixelFormat[uint32]{
	BytesPerPixel: 4,
	WhitePixel:    0xFFFFFFFF,
	BlackPixel:    0x00000000,
	ReadPixel: func(data []byte, idx int) uint32 {
		if idx+3 >= len(data) {
			return 0
		}
		return uint32(data[idx]) | (uint32(data[idx+1]) << 8) |
			(uint32(data[idx+2]) << 16) | (uint32(data[idx+3]) << 24)
	},
	WritePixel: func(data []byte, idx int, pixel uint32) {
		if idx+3 >= len(data) {
			return
		}
		data[idx] = byte(pixel & 0xFF)
		data[idx+1] = byte((pixel >> 8) & 0xFF)
		data[idx+2] = byte((pixel >> 16) & 0xFF)
		data[idx+3] = byte((pixel >> 24) & 0xFF)
	},
}

Pixel32 defines the 32-bit pixel format (4 bytes per pixel, RGBA8888).

View Source
var Pixel8 = PixelFormat[uint8]{
	BytesPerPixel: 1,
	WhitePixel:    0xFF,
	BlackPixel:    0x00,
	ReadPixel: func(data []byte, idx int) uint8 {
		if idx >= len(data) {
			return 0
		}
		return data[idx]
	},
	WritePixel: func(data []byte, idx int, pixel uint8) {
		if idx >= len(data) {
			return
		}
		data[idx] = pixel
	},
}

Pixel8 defines the 8-bit pixel format (1 byte per pixel).

Functions

func AYCoCgToRGBA

func AYCoCgToRGBA(luma, co, cg, alpha []byte, planeWidth, planeHeight, imgWidth, imgHeight int) []byte

AYCoCgToRGBA converts AYCoCg color space to RGBA

func BGR24ToRGBA

func BGR24ToRGBA(src []byte, dst []byte)

BGR24ToRGBA converts 24-bit BGR to 32-bit RGBA

func BGRA32ToRGBA

func BGRA32ToRGBA(src []byte, dst []byte)

BGRA32ToRGBA converts 32-bit BGRA to 32-bit RGBA

func ChromaSuperSample

func ChromaSuperSample(plane []byte, srcWidth, srcHeight, dstWidth, dstHeight int) []byte

ChromaSuperSample upsamples chroma planes from subsampled to full resolution

func Decode

func Decode(data []byte, width, height int) ([]byte, error)

Decode decodes an NSCodec bitmap stream to RGBA pixels. Width and height specify the image dimensions. Returns RGBA pixel data (4 bytes per pixel).

func DecodeNSCodecToRGBA

func DecodeNSCodecToRGBA(data []byte, width, height int) []byte

DecodeNSCodecToRGBA decodes an NSCodec bitmap stream to RGBA pixels

func DecompressPlanar

func DecompressPlanar(src []byte, width, height int) []byte

DecompressPlanar decompresses RDP6 Planar codec data to RGBA

func Encode

func Encode(s string) []byte

Encode converts a string to UTF-16LE encoded bytes.

func ExtractCodeID

func ExtractCodeID(bOrderHdr byte) uint

ExtractCodeID extracts the order code from a header byte

func ExtractRunLength

func ExtractRunLength(code uint, src []byte, idx int) (length int, nextIdx int)

ExtractRunLength extracts the run length from the source buffer at the given index

func FlipVertical

func FlipVertical(data []byte, width, height, bytesPerPixel int)

FlipVertical flips bitmap data vertically (in-place). RDP sends bitmaps bottom-up, this flips them to top-down.

func IsLiteCode

func IsLiteCode(code uint) bool

IsLiteCode returns true if the code is a lite order code

func IsMegaMegaCode

func IsMegaMegaCode(code uint) bool

IsMegaMegaCode returns true if the code is a mega-mega order code

func IsRegularCode

func IsRegularCode(code uint) bool

IsRegularCode returns true if the code is a regular order code

func NSCodecRLEDecompress

func NSCodecRLEDecompress(data []byte, expectedSize int) []byte

NSCodecRLEDecompress decompresses NSCodec RLE data for a single plane. This is different from bitmap RLE - NSCodec uses a simpler format with run segments and literal segments. The last 4 bytes of compressed data are raw (not RLE-encoded) and appended directly to the output.

func Palette8ToRGBA

func Palette8ToRGBA(src []byte, dst []byte)

Palette8ToRGBA converts 8-bit paletted data to 32-bit RGBA using current palette Thread-safe: uses mutex for concurrent access.

func ProcessBitmap

func ProcessBitmap(src []byte, width, height, bpp int, isCompressed bool, rowDelta int, noHdr bool) []byte

ProcessBitmap handles decompression, flip, and color conversion in one call. Returns the RGBA output buffer on success, nil on failure. The noHdr flag indicates NO_BITMAP_COMPRESSION_HDR was set — for 32bpp compressed, this means RDP6 Planar codec; without it, 32bpp uses 24-bit interleaved RLE.

func RGB555ToRGBA

func RGB555ToRGBA(src []byte, dst []byte)

RGB555ToRGBA converts 15-bit RGB555 to 32-bit RGBA

func RGB565ToRGBA

func RGB565ToRGBA(src []byte, dst []byte)

RGB565ToRGBA converts 16-bit RGB565 to 32-bit RGBA

func RLEDecompress

func RLEDecompress[T uint8 | uint16 | uint32](pf PixelFormat[T], src []byte, dest []byte, rowDelta int) bool

RLEDecompress decompresses RLE-encoded bitmap data using the specified pixel format.

func RLEDecompress8

func RLEDecompress8(src []byte, dest []byte, rowDelta int) bool

RLEDecompress8 decompresses 8-bit RLE compressed bitmap data

func RLEDecompress15

func RLEDecompress15(src []byte, dest []byte, rowDelta int) bool

RLEDecompress15 decompresses 15-bit RLE compressed bitmap data

func RLEDecompress16

func RLEDecompress16(src []byte, dest []byte, rowDelta int) bool

RLEDecompress16 decompresses 16-bit RLE compressed bitmap data

func RLEDecompress24

func RLEDecompress24(src []byte, dest []byte, rowDelta int) bool

RLEDecompress24 decompresses 24-bit RLE compressed bitmap data

func RLEDecompress32

func RLEDecompress32(src []byte, dest []byte, rowDelta int) bool

RLEDecompress32 decompresses 32-bit RLE compressed bitmap data

func ReadPixel15

func ReadPixel15(data []byte, idx int) uint16

ReadPixel15 reads a 15-bit pixel from the buffer

func ReadPixel16

func ReadPixel16(data []byte, idx int) uint16

ReadPixel16 reads a 16-bit pixel from the buffer

func ReadPixel24

func ReadPixel24(data []byte, idx int) uint32

ReadPixel24 reads a 24-bit pixel from the buffer

func ReadPixel32

func ReadPixel32(data []byte, idx int) uint32

ReadPixel32 reads a 32-bit pixel from the buffer

func RestoreColorLoss

func RestoreColorLoss(plane []byte, colorLossLevel uint8) []byte

RestoreColorLoss restores color values that were quantized during compression

func SetPalette

func SetPalette(data []byte, numColors int)

SetPalette updates the current palette from server data RDP palette format: array of RGB entries (R, G, B - 3 bytes each) Thread-safe: uses mutex for concurrent access.

func UnwrapSecurityFlag

func UnwrapSecurityFlag(wire io.Reader) (uint16, error)

UnwrapSecurityFlag reads and returns the security flag from an RDP security header.

func WrapSecurityFlag

func WrapSecurityFlag(flag uint16, data []byte) []byte

WrapSecurityFlag wraps data with an RDP security header containing the specified flag.

func WriteFgBgImage8

func WriteFgBgImage8(dest []byte, destIdx int, rowDelta int, bitmask byte, fgPel byte, cBits int, firstLine bool) int

WriteFgBgImage8 writes a foreground/background image for 8-bit color

func WriteFgBgImage15

func WriteFgBgImage15(dest []byte, destIdx int, rowDelta int, bitmask byte, fgPel uint16, cBits int, firstLine bool) int

WriteFgBgImage15 writes a foreground/background image for 15-bit color

func WriteFgBgImage16

func WriteFgBgImage16(dest []byte, destIdx int, rowDelta int, bitmask byte, fgPel uint16, cBits int, firstLine bool) int

WriteFgBgImage16 writes a foreground/background image for 16-bit color

func WriteFgBgImage24

func WriteFgBgImage24(dest []byte, destIdx int, rowDelta int, bitmask byte, fgPel uint32, cBits int, firstLine bool) int

WriteFgBgImage24 writes a foreground/background image for 24-bit color

func WriteFgBgImage32

func WriteFgBgImage32(dest []byte, destIdx int, rowDelta int, bitmask byte, fgPel uint32, cBits int, firstLine bool) int

WriteFgBgImage32 writes a foreground/background image for 32-bit color

func WritePixel15

func WritePixel15(data []byte, idx int, pixel uint16)

WritePixel15 writes a 15-bit pixel to the buffer

func WritePixel16

func WritePixel16(data []byte, idx int, pixel uint16)

WritePixel16 writes a 16-bit pixel to the buffer

func WritePixel24

func WritePixel24(data []byte, idx int, pixel uint32)

WritePixel24 writes a 24-bit pixel to the buffer

func WritePixel32

func WritePixel32(data []byte, idx int, pixel uint32)

WritePixel32 writes a 32-bit pixel to the buffer

Types

type BitmapStream

type BitmapStream struct {
	LumaPlaneByteCount         uint32
	OrangeChromaPlaneByteCount uint32
	GreenChromaPlaneByteCount  uint32
	AlphaPlaneByteCount        uint32
	ColorLossLevel             uint8
	ChromaSubsamplingLevel     uint8
	LumaPlane                  []byte
	OrangeChromaPlane          []byte
	GreenChromaPlane           []byte
	AlphaPlane                 []byte
}

BitmapStream represents the NSCODEC_BITMAP_STREAM structure as specified in MS-RDPNSC.

func ParseBitmapStream

func ParseBitmapStream(data []byte) (*BitmapStream, error)

ParseBitmapStream parses an NSCODEC_BITMAP_STREAM from raw bytes.

func (*BitmapStream) Decode

func (s *BitmapStream) Decode(width, height int) ([]byte, error)

Decode decodes the bitmap stream to RGBA pixels

type PixelFormat

type PixelFormat[T uint8 | uint16 | uint32] struct {
	BytesPerPixel int
	WhitePixel    T
	BlackPixel    T
	ReadPixel     func(data []byte, idx int) T
	WritePixel    func(data []byte, idx int, pixel T)
}

PixelFormat defines the operations for a specific pixel bit depth in RLE decompression.

Directories

Path Synopsis
Package rfx implements the RemoteFX (RFX) codec decoder as specified in MS-RDPRFX.
Package rfx implements the RemoteFX (RFX) codec decoder as specified in MS-RDPRFX.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL