in vncclient/encoding.go [678:850]
func (t *TightEncoding) readBasicCompression(c *ClientConn, rect *Rectangle, r io.Reader, readFilterID bool, stream uint8) (enc Encoding, e error) {
var filterID uint8
if readFilterID {
// If the compression type is BasicCompression and bit 6 (the
// read-filter-id bit) of the compression-control byte was set
// to 1, then the next (second) byte specifies filter-id which
// tells the decoder what filter type was used by the encoder
// to pre-process pixel data before the compression.
if err := binary.Read(r, binary.BigEndian, &filterID); err != nil {
return nil, err
}
t.size++
} else {
// If bit 6 of the compression-control byte is set to 0 (no
// filter-id byte), then the CopyFilter is used.
filterID = 0
}
// The filter-id byte can be one of the following:
//
// +--------------+------+---------+------------------------+
// | No. of bytes | Type | [Value] | Description |
// +--------------+------+---------+------------------------+
// | 1 | U8 | | filter-id |
// +--------------+------+---------+------------------------+
// | | | 0 | CopyFilter (no filter) |
// +--------------+------+---------+------------------------+
// | | | 1 | PaletteFilter |
// +--------------+------+---------+------------------------+
// | | | 2 | GradientFilter |
// +--------------+------+---------+------------------------+
log.Debug("stream: ", stream)
switch filterID {
// CopyFilter
case 0:
log.Debug("CopyFilter")
// When the CopyFilter is active, raw pixel values in TPIXEL
// format will be compressed.
size := rect.Area() * 3
r, err := t.basicCompressionReader(r, size, stream)
if err != nil {
return nil, err
}
t.buf.Reset()
colors, err := t.readTPixels(r, size/3)
// Copy the colors slice. It uses the same underlying memory as
// t.buf, but when it is used to update a screen later we might
// have already modified t.buf while reading a new frame.
return &TightEncoding{Colors: append([]Color{}, colors...), size: t.size}, err
// PaletteFilter
case 1:
log.Debug("PaletteFilter")
// The PaletteFilter converts true-color pixel data to indexed
// colors and a palette which can consist of 2..256 colors.
//
// When the PaletteFilter is used, the palette is sent before
// the pixel data. The palette begins with an unsigned byte
// which value is the number of colors in the palette minus 1
// (i.e. 1 means 2 colors, 255 means 256 colors in the palette).
// Then follows the palette itself which consist of pixel values
// in TPIXEL format.
var p uint8
if err := binary.Read(r, binary.BigEndian, &p); err != nil {
return nil, err
}
paletteSize := int(p) + 1
t.buf.Reset()
palette, err := t.readTPixels(r, paletteSize)
if err != nil {
return nil, err
}
palette = append([]Color{}, palette...)
// If the number of colors is 2, then each pixel is encoded in
// 1 bit, otherwise 8 bits are used to encode one pixel. 1-bit
// encoding is performed such way that the most significant
// bits correspond to the leftmost pixels, and each row of
// pixels is aligned to the byte boundary.
size := rect.Area()
if paletteSize == 2 {
size = ((int(rect.Width) + 7) / 8) * int(rect.Height)
}
r, err := t.basicCompressionReader(r, size, stream)
if err != nil {
return nil, err
}
if err = t.readToBuf(r, size); err != nil {
return nil, err
}
buf := t.buf.Bytes()
colors := make([]Color, rect.Area())
if paletteSize == 2 {
offset := uint8(8)
index := -1
for i := range colors {
if offset == 0 || i%int(rect.Width) == 0 {
offset = 8
index++
}
offset--
colors[i] = palette[(buf[index]>>offset)&0x01]
}
} else {
for i := range colors {
if int(buf[i]) >= paletteSize {
return nil, errors.Errorf("invalid index %d in palette of size %d", buf[i], paletteSize)
}
colors[i] = palette[uint8(buf[i])]
}
}
return &TightEncoding{Colors: colors, size: t.size}, nil
// GradientFilter
case 2:
log.Debug("GradientFilter")
// Note: The GradientFilter may only be used when bits-per-
// pixel is either 16 or 32.
if c.PixelFormat.BPP != 16 && c.PixelFormat.BPP != 32 {
return nil, errors.Errorf("can't use GradientFilter with bitsPerPixel of %v", c.PixelFormat.BPP)
}
size := rect.Area() * 3
r, err := t.basicCompressionReader(r, size, stream)
if err != nil {
return nil, err
}
t.buf.Reset()
diffs, err := t.readTPixels(r, size)
if err != nil {
return nil, err
}
// The GradientFilter pre-processes pixel data with a simple
// algorithm which converts each color component to a
// difference between a "predicted" intensity and the actual
// intensity. Such a technique does not affect uncompressed
// data size, but helps to compress photo-like images better.
// Pseudo-code for converting intensities to differences
// follows:
//
// P[i,j] := V[i-1,j] + V[i,j-1] - V[i-1,j-1];
// if (P[i,j] < 0) then P[i,j] := 0;
// if (P[i,j] > MAX) then P[i,j] := MAX;
// D[i,j] := V[i,j] - P[i,j];
//
// Here V[i,j] is the intensity of a color component for a
// pixel at coordinates (i,j). For pixels outside the current
// rectangle, V[i,j] is assumed to be zero (which is relevant
// for P[i,0] and P[0,j]). MAX is the maximum intensity value
// for a color component.
colors := make([]Color, size/3)
cr := colorRect{width: int(rect.Width), colors: colors}
for i := 0; i < int(rect.Height); i++ {
for j := 0; j < int(rect.Width); j++ {
for c := 0; c < 3; c++ {
p := cr.at(i-1, j, c) + cr.at(i, j-1, c) - cr.at(i-1, j-1, c)
if p < 0 {
p = 0
}
if p > 255 {
p = 255
}
*component(colors[i], c) = *component(diffs[i], c) + p
}
}
}
return &TightEncoding{Colors: colors, size: t.size}, nil
default:
return nil, errors.Errorf("invalid filter-id byte: %b", filterID)
}
}