Internal
Public Access
Add VNC Tight decoding
Implements RFC 6143's Tight encoding: a compression-control byte (low 4 bits reset one of 4 persistent zlib streams; high nibble selects Fill/JPEG/Basic mode) followed by Fill's 3-byte solid color, JPEG's compact-length-prefixed baseline JPEG covering the whole rectangle (decoded via libjpeg-turbo directly, not QImage's plugin, to avoid a packaging-dependent runtime failure mode), or Basic mode's compact-length-prefixed zlib payload plus a filter (Copy, Palette, or Gradient) applied after decompression. Unlike Hextile/ZRLE, Tight has no internal tiling -- one rectangle is one filtered/compressed unit. The three filters live in vnc_pixel_codecs.h/.cpp alongside the Hextile/ZRLE decoders. Adds find_package(JPEG REQUIRED) + JPEG::JPEG as a new build dependency (confirmed available via libjpeg-turbo on this dev machine). 5 new tests cover Fill, Basic+Copy, Basic+Palette, JPEG (round-tripped through a real libjpeg-turbo-encoded fixture, compared with tolerance since JPEG is lossy), and the stream-reset flag correctly tearing down and reinitializing a targeted stream rather than erroring on stale state. Known, documented gap: this decoder always treats Basic-mode payloads as zlib-compressed; the real protocol allows very small payloads to skip compression, which couldn't be verified with confidence against the RFC text alone and is narrow enough in practice (tiny solid areas are virtually always sent as Fill instead) to leave unhandled for now -- it fails that one rectangle's decode cleanly rather than misinterpreting it silently. The Gradient filter is implemented from the spec description but is the least exercised of the three in this pass. Live-verified against the TightVNC test server that nothing regressed; that server still consistently chose Raw for actual framebuffer content regardless of announced encodings, so Tight's live decode path isn't independently confirmed against a real server here either -- the unit tests are the primary evidence. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
bb022edcf2
commit
4fca8fce41
8 files changed
+901
-26
No files matched your search
@@ -251,4 +251,119 @@ int decodeZrleTile(const QByteArray& data, int offset, int tileWidth, int tileHe
|
||||
return -1;
|
||||
}
|
||||
|
||||
QVector<QRgb> decodeTightCopyFilter(const QByteArray& data, int width, int height)
|
||||
{
|
||||
const int pixelCount = width * height;
|
||||
QVector<QRgb> pixels;
|
||||
if (pixelCount <= 0 || data.size() < pixelCount * 3) {
|
||||
return pixels;
|
||||
}
|
||||
pixels.reserve(pixelCount);
|
||||
const auto* bytes = reinterpret_cast<const uchar*>(data.constData());
|
||||
for (int i = 0; i < pixelCount; ++i) {
|
||||
pixels.append(rgbFromPixelBytes(bytes + (i * 3)));
|
||||
}
|
||||
return pixels;
|
||||
}
|
||||
|
||||
QVector<QRgb> decodeTightPaletteFilter(const QByteArray& data, int width, int height)
|
||||
{
|
||||
QVector<QRgb> pixels;
|
||||
const int pixelCount = width * height;
|
||||
if (pixelCount <= 0 || data.isEmpty()) {
|
||||
return pixels;
|
||||
}
|
||||
|
||||
const auto* bytes = reinterpret_cast<const uchar*>(data.constData());
|
||||
const int size = data.size();
|
||||
int pos = 0;
|
||||
const int paletteSize = static_cast<int>(bytes[pos]) + 1; // 1-256 colors
|
||||
++pos;
|
||||
if (pos + (paletteSize * 3) > size) {
|
||||
return pixels;
|
||||
}
|
||||
|
||||
QVector<QRgb> palette;
|
||||
palette.reserve(paletteSize);
|
||||
for (int i = 0; i < paletteSize; ++i) {
|
||||
palette.append(rgbFromPixelBytes(bytes + pos));
|
||||
pos += 3;
|
||||
}
|
||||
|
||||
int bitsPerPixel = 8;
|
||||
if (paletteSize <= 2) {
|
||||
bitsPerPixel = 1;
|
||||
} else if (paletteSize <= 4) {
|
||||
bitsPerPixel = 2;
|
||||
} else if (paletteSize <= 16) {
|
||||
bitsPerPixel = 4;
|
||||
}
|
||||
|
||||
pixels.reserve(pixelCount);
|
||||
int bitPos = 0;
|
||||
for (int i = 0; i < pixelCount; ++i) {
|
||||
const int byteIndex = pos + (bitPos / 8);
|
||||
if (byteIndex >= size) {
|
||||
return QVector<QRgb>();
|
||||
}
|
||||
const int shift = 8 - (bitPos % 8) - bitsPerPixel;
|
||||
const int mask = (1 << bitsPerPixel) - 1;
|
||||
const int index = (bytes[byteIndex] >> shift) & mask;
|
||||
if (index >= palette.size()) {
|
||||
return QVector<QRgb>();
|
||||
}
|
||||
pixels.append(palette.at(index));
|
||||
bitPos += bitsPerPixel;
|
||||
}
|
||||
return pixels;
|
||||
}
|
||||
|
||||
QVector<QRgb> decodeTightGradientFilter(const QByteArray& data, int width, int height)
|
||||
{
|
||||
const int pixelCount = width * height;
|
||||
QVector<QRgb> pixels;
|
||||
if (pixelCount <= 0 || data.size() < pixelCount * 3) {
|
||||
return pixels;
|
||||
}
|
||||
|
||||
const auto* bytes = reinterpret_cast<const uchar*>(data.constData());
|
||||
auto predict = [](int left, int up, int upLeft) {
|
||||
return qBound(0, left + up - upLeft, 255);
|
||||
};
|
||||
|
||||
QVector<int> rChan(pixelCount);
|
||||
QVector<int> gChan(pixelCount);
|
||||
QVector<int> bChan(pixelCount);
|
||||
for (int y = 0; y < height; ++y) {
|
||||
for (int x = 0; x < width; ++x) {
|
||||
const int idx = (y * width) + x;
|
||||
const uchar* px = bytes + (idx * 3);
|
||||
// TPIXEL order matches rgbFromPixelBytes: B,G,R.
|
||||
const int deltaB = px[0];
|
||||
const int deltaG = px[1];
|
||||
const int deltaR = px[2];
|
||||
|
||||
const int leftR = (x > 0) ? rChan[idx - 1] : 0;
|
||||
const int leftG = (x > 0) ? gChan[idx - 1] : 0;
|
||||
const int leftB = (x > 0) ? bChan[idx - 1] : 0;
|
||||
const int upR = (y > 0) ? rChan[idx - width] : 0;
|
||||
const int upG = (y > 0) ? gChan[idx - width] : 0;
|
||||
const int upB = (y > 0) ? bChan[idx - width] : 0;
|
||||
const int upLeftR = (x > 0 && y > 0) ? rChan[idx - width - 1] : 0;
|
||||
const int upLeftG = (x > 0 && y > 0) ? gChan[idx - width - 1] : 0;
|
||||
const int upLeftB = (x > 0 && y > 0) ? bChan[idx - width - 1] : 0;
|
||||
|
||||
rChan[idx] = (predict(leftR, upR, upLeftR) + deltaR) & 0xFF;
|
||||
gChan[idx] = (predict(leftG, upG, upLeftG) + deltaG) & 0xFF;
|
||||
bChan[idx] = (predict(leftB, upB, upLeftB) + deltaB) & 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
pixels.reserve(pixelCount);
|
||||
for (int i = 0; i < pixelCount; ++i) {
|
||||
pixels.append(qRgb(rChan.at(i), gChan.at(i), bChan.at(i)));
|
||||
}
|
||||
return pixels;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user