KasmVNC/common/rfb/TightDecoder.cxx

458 lines
10 KiB
C++
Raw Normal View History

2020-09-20 14:16:44 +02:00
/* Copyright (C) 2000-2003 Constantin Kaplinsky. All Rights Reserved.
* Copyright 2004-2005 Cendio AB.
* Copyright 2009-2015 Pierre Ossman for Cendio AB
* Copyright (C) 2011 D. R. Commander. All Rights Reserved.
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
* USA.
*/
#include <assert.h>
#include <rdr/InStream.h>
#include <rdr/MemInStream.h>
#include <rdr/OutStream.h>
#include <rfb/ConnParams.h>
#include <rfb/Exception.h>
#include <rfb/PixelBuffer.h>
#include <rfb/TightConstants.h>
#include <rfb/TightDecoder.h>
using namespace rfb;
static const int TIGHT_MAX_WIDTH = 2048;
static const int TIGHT_MIN_TO_COMPRESS = 12;
#define BPP 8
#include <rfb/tightDecode.h>
#undef BPP
#define BPP 16
#include <rfb/tightDecode.h>
#undef BPP
#define BPP 32
#include <rfb/tightDecode.h>
#undef BPP
TightDecoder::TightDecoder() : Decoder(DecoderPartiallyOrdered)
{
}
TightDecoder::~TightDecoder()
{
}
void TightDecoder::readRect(const Rect& r, rdr::InStream* is,
const ConnParams& cp, rdr::OutStream* os)
{
rdr::U8 comp_ctl;
comp_ctl = is->readU8();
os->writeU8(comp_ctl);
comp_ctl >>= 4;
// "Fill" compression type.
if (comp_ctl == tightFill) {
if (cp.pf().is888())
os->copyBytes(is, 3);
else
os->copyBytes(is, cp.pf().bpp/8);
return;
}
// "JPEG" compression type.
if (comp_ctl == tightJpeg) {
rdr::U32 len;
len = readCompact(is);
os->writeOpaque32(len);
os->copyBytes(is, len);
return;
}
// Quit on unsupported compression type.
if (comp_ctl > tightMaxSubencoding)
throw Exception("TightDecoder: bad subencoding value received");
// "Basic" compression type.
int palSize = 0;
if (r.width() > TIGHT_MAX_WIDTH)
throw Exception("TightDecoder: too large rectangle (%d pixels)", r.width());
// Possible palette
if ((comp_ctl & tightExplicitFilter) != 0) {
rdr::U8 filterId;
filterId = is->readU8();
os->writeU8(filterId);
switch (filterId) {
case tightFilterPalette:
palSize = is->readU8() + 1;
os->writeU8(palSize - 1);
if (cp.pf().is888())
os->copyBytes(is, palSize * 3);
else
os->copyBytes(is, palSize * cp.pf().bpp/8);
break;
case tightFilterGradient:
if (cp.pf().bpp == 8)
throw Exception("TightDecoder: invalid BPP for gradient filter");
break;
case tightFilterCopy:
break;
default:
throw Exception("TightDecoder: unknown filter code received");
}
}
size_t rowSize, dataSize;
if (palSize != 0) {
if (palSize <= 2)
rowSize = (r.width() + 7) / 8;
else
rowSize = r.width();
} else if (cp.pf().is888()) {
rowSize = r.width() * 3;
} else {
rowSize = r.width() * cp.pf().bpp/8;
}
dataSize = r.height() * rowSize;
if (dataSize < TIGHT_MIN_TO_COMPRESS)
os->copyBytes(is, dataSize);
else {
rdr::U32 len;
len = readCompact(is);
os->writeOpaque32(len);
os->copyBytes(is, len);
}
}
bool TightDecoder::doRectsConflict(const Rect& rectA,
const void* bufferA,
size_t buflenA,
const Rect& rectB,
const void* bufferB,
size_t buflenB,
const ConnParams& cp)
{
rdr::U8 comp_ctl_a, comp_ctl_b;
assert(buflenA >= 1);
assert(buflenB >= 1);
comp_ctl_a = *(const rdr::U8*)bufferA;
comp_ctl_b = *(const rdr::U8*)bufferB;
// Resets or use of zlib pose the same problem, so merge them
if ((comp_ctl_a & 0x80) == 0x00)
comp_ctl_a |= 1 << ((comp_ctl_a >> 4) & 0x03);
if ((comp_ctl_b & 0x80) == 0x00)
comp_ctl_b |= 1 << ((comp_ctl_b >> 4) & 0x03);
if (((comp_ctl_a & 0x0f) & (comp_ctl_b & 0x0f)) != 0)
return true;
return false;
}
void TightDecoder::decodeRect(const Rect& r, const void* buffer,
size_t buflen, const ConnParams& cp,
ModifiablePixelBuffer* pb)
{
const rdr::U8* bufptr;
const PixelFormat& pf = cp.pf();
rdr::U8 comp_ctl;
bufptr = (const rdr::U8*)buffer;
assert(buflen >= 1);
comp_ctl = *bufptr;
bufptr += 1;
buflen -= 1;
// Reset zlib streams if we are told by the server to do so.
for (int i = 0; i < 4; i++) {
if (comp_ctl & 1) {
zis[i].reset();
}
comp_ctl >>= 1;
}
// "Fill" compression type.
if (comp_ctl == tightFill) {
if (pf.is888()) {
rdr::U8 pix[4];
assert(buflen >= 3);
pf.bufferFromRGB(pix, bufptr, 1);
pb->fillRect(pf, r, pix);
} else {
assert(buflen >= (size_t)pf.bpp/8);
pb->fillRect(pf, r, bufptr);
}
return;
}
// "JPEG" compression type.
if (comp_ctl == tightJpeg) {
rdr::U32 len;
int stride;
rdr::U8 *buf;
JpegDecompressor jd;
assert(buflen >= 4);
memcpy(&len, bufptr, 4);
bufptr += 4;
buflen -= 4;
// We always use direct decoding with JPEG images
buf = pb->getBufferRW(r, &stride);
jd.decompress(bufptr, len, buf, stride, r, pb->getPF());
pb->commitBufferRW(r);
return;
}
// Quit on unsupported compression type.
assert(comp_ctl <= tightMaxSubencoding);
// "Basic" compression type.
int palSize = 0;
rdr::U8 palette[256 * 4];
bool useGradient = false;
if ((comp_ctl & tightExplicitFilter) != 0) {
rdr::U8 filterId;
assert(buflen >= 1);
filterId = *bufptr;
bufptr += 1;
buflen -= 1;
switch (filterId) {
case tightFilterPalette:
assert(buflen >= 1);
palSize = *bufptr + 1;
bufptr += 1;
buflen -= 1;
if (pf.is888()) {
rdr::U8 tightPalette[palSize * 3];
assert(buflen >= sizeof(tightPalette));
memcpy(tightPalette, bufptr, sizeof(tightPalette));
bufptr += sizeof(tightPalette);
buflen -= sizeof(tightPalette);
pf.bufferFromRGB(palette, tightPalette, palSize);
} else {
size_t len;
len = palSize * pf.bpp/8;
assert(buflen >= len);
memcpy(palette, bufptr, len);
bufptr += len;
buflen -= len;
}
break;
case tightFilterGradient:
useGradient = true;
break;
case tightFilterCopy:
break;
default:
assert(false);
}
}
// Determine if the data should be decompressed or just copied.
size_t rowSize, dataSize;
rdr::U8* netbuf;
netbuf = NULL;
if (palSize != 0) {
if (palSize <= 2)
rowSize = (r.width() + 7) / 8;
else
rowSize = r.width();
} else if (pf.is888()) {
rowSize = r.width() * 3;
} else {
rowSize = r.width() * pf.bpp/8;
}
dataSize = r.height() * rowSize;
if (dataSize < TIGHT_MIN_TO_COMPRESS)
assert(buflen >= dataSize);
else {
rdr::U32 len;
int streamId;
rdr::MemInStream* ms;
assert(buflen >= 4);
memcpy(&len, bufptr, 4);
bufptr += 4;
buflen -= 4;
assert(buflen >= len);
streamId = comp_ctl & 0x03;
ms = new rdr::MemInStream(bufptr, len);
zis[streamId].setUnderlying(ms, len);
// Allocate buffer and decompress the data
netbuf = new rdr::U8[dataSize];
zis[streamId].readBytes(netbuf, dataSize);
zis[streamId].flushUnderlying();
zis[streamId].setUnderlying(NULL, 0);
2020-09-20 14:16:44 +02:00
delete ms;
bufptr = netbuf;
buflen = dataSize;
}
// Time to decode the actual data
bool directDecode;
rdr::U8* outbuf;
int stride;
if (pb->getPF().equal(pf)) {
// Decode directly into the framebuffer (fast path)
directDecode = true;
} else {
// Decode into an intermediate buffer and use pixel translation
directDecode = false;
}
if (directDecode)
outbuf = pb->getBufferRW(r, &stride);
else {
outbuf = new rdr::U8[r.area() * (pf.bpp/8)];
stride = r.width();
}
if (palSize == 0) {
// Truecolor data
if (useGradient) {
if (pf.is888())
FilterGradient24(bufptr, pf, (rdr::U32*)outbuf, stride, r);
else {
switch (pf.bpp) {
case 8:
assert(false);
break;
case 16:
FilterGradient(bufptr, pf, (rdr::U16*)outbuf, stride, r);
break;
case 32:
FilterGradient(bufptr, pf, (rdr::U32*)outbuf, stride, r);
break;
}
}
} else {
// Copy
rdr::U8* ptr = outbuf;
const rdr::U8* srcPtr = bufptr;
int w = r.width();
int h = r.height();
if (pf.is888()) {
while (h > 0) {
pf.bufferFromRGB(ptr, srcPtr, w);
ptr += stride * pf.bpp/8;
srcPtr += w * 3;
h--;
}
} else {
while (h > 0) {
memcpy(ptr, srcPtr, w * pf.bpp/8);
ptr += stride * pf.bpp/8;
srcPtr += w * pf.bpp/8;
h--;
}
}
}
} else {
// Indexed color
switch (pf.bpp) {
case 8:
FilterPalette((const rdr::U8*)palette, palSize,
bufptr, (rdr::U8*)outbuf, stride, r);
break;
case 16:
FilterPalette((const rdr::U16*)palette, palSize,
bufptr, (rdr::U16*)outbuf, stride, r);
break;
case 32:
FilterPalette((const rdr::U32*)palette, palSize,
bufptr, (rdr::U32*)outbuf, stride, r);
break;
}
}
if (directDecode)
pb->commitBufferRW(r);
else {
pb->imageRect(pf, r, outbuf);
delete [] outbuf;
}
delete [] netbuf;
}
rdr::U32 TightDecoder::readCompact(rdr::InStream* is)
{
rdr::U8 b;
rdr::U32 result;
b = is->readU8();
result = (int)b & 0x7F;
if (b & 0x80) {
b = is->readU8();
result |= ((int)b & 0x7F) << 7;
if (b & 0x80) {
b = is->readU8();
result |= ((int)b & 0xFF) << 14;
}
}
return result;
}