blob: dd0c4d973ec206fa26db769a7bddbc1651a4f986 [file] [log] [blame]
/* Copyright (C) 2000-2003 Constantin Kaplinsky. All Rights Reserved.
* Copyright (C) 2004-2005 Cendio AB. 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.
*/
//
// Tight decoding functions.
//
// This file is #included after having set the following macros:
// BPP - 8, 16 or 32
// EXTRA_ARGS - optional extra arguments
// FILL_RECT - fill a rectangle with a single color
// IMAGE_RECT - draw a rectangle of pixel data from a buffer
#include <rdr/InStream.h>
#include <rdr/ZlibInStream.h>
#include <rfb/Exception.h>
#include <assert.h>
namespace rfb {
// CONCAT2E concatenates its arguments, expanding them if they are macros
#ifndef CONCAT2E
#define CONCAT2(a,b) a##b
#define CONCAT2E(a,b) CONCAT2(a,b)
#endif
#define PIXEL_T rdr::CONCAT2E(U,BPP)
#define READ_PIXEL CONCAT2E(readOpaque,BPP)
#define TIGHT_DECODE CONCAT2E(tightDecode,BPP)
#define TIGHT_MIN_TO_COMPRESS 12
static bool DecompressJpegRect(const Rect& r, rdr::InStream* is,
PIXEL_T* buf, CMsgHandler* handler);
static void FilterGradient(const Rect& r, rdr::InStream* is, int dataSize,
PIXEL_T* buf, CMsgHandler* handler);
#if BPP == 32
static void FilterGradient24(const Rect& r, rdr::InStream* is, int dataSize,
PIXEL_T* buf, CMsgHandler* handler);
#endif
// Main function implementing Tight decoder
void TIGHT_DECODE (const Rect& r, rdr::InStream* is,
rdr::ZlibInStream zis[], PIXEL_T* buf
#ifdef EXTRA_ARGS
, EXTRA_ARGS
#endif
)
{
rdr::U8 *bytebuf = (rdr::U8*) buf;
bool cutZeros = false;
const rfb::PixelFormat& myFormat = handler->cp.pf();
#if BPP == 32
if (myFormat.depth == 24 && myFormat.redMax == 0xFF &&
myFormat.greenMax == 0xFF && myFormat.blueMax == 0xFF) {
cutZeros = true;
}
#endif
rdr::U8 comp_ctl = is->readU8();
// Flush 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 == rfbTightFill) {
PIXEL_T pix;
if (cutZeros) {
is->readBytes(bytebuf, 3);
pix = RGB24_TO_PIXEL32(bytebuf[0], bytebuf[1], bytebuf[2]);
} else {
pix = is->READ_PIXEL();
}
FILL_RECT(r, pix);
return;
}
// "JPEG" compression type.
if (comp_ctl == rfbTightJpeg) {
DecompressJpegRect(r, is, buf, handler);
return;
}
// Quit on unsupported compression type.
if (comp_ctl > rfbTightMaxSubencoding) {
throw Exception("TightDecoder: bad subencoding value received");
return;
}
// "Basic" compression type.
int palSize = 0;
static PIXEL_T palette[256];
bool useGradient = false;
if ((comp_ctl & rfbTightExplicitFilter) != 0) {
rdr::U8 filterId = is->readU8();
switch (filterId) {
case rfbTightFilterPalette:
palSize = is->readU8() + 1;
if (cutZeros) {
rdr::U8 *tightPalette = (rdr::U8*) palette;
is->readBytes(tightPalette, palSize*3);
for (int i = palSize - 1; i >= 0; i--) {
palette[i] = RGB24_TO_PIXEL32(tightPalette[i*3],
tightPalette[i*3+1],
tightPalette[i*3+2]);
}
} else {
for (int i = 0; i < palSize; i++)
palette[i] = is->READ_PIXEL();
}
break;
case rfbTightFilterGradient:
useGradient = true;
break;
case rfbTightFilterCopy:
break;
default:
throw Exception("TightDecoder: unknown filter code received");
return;
}
}
int bppp = BPP;
if (palSize != 0) {
bppp = (palSize <= 2) ? 1 : 8;
} else if (cutZeros) {
bppp = 24;
}
// Determine if the data should be decompressed or just copied.
int rowSize = (r.width() * bppp + 7) / 8;
int dataSize = r.height() * rowSize;
int streamId = -1;
rdr::InStream *input;
if (dataSize < TIGHT_MIN_TO_COMPRESS) {
input = is;
} else {
int length = is->readCompactLength();
streamId = comp_ctl & 0x03;
zis[streamId].setUnderlying(is, length);
input = &zis[streamId];
}
if (palSize == 0) {
// Truecolor data
if (useGradient) {
#if BPP == 32
if (cutZeros) {
FilterGradient24(r, input, dataSize, buf, handler);
} else
#endif
{
FilterGradient(r, input, dataSize, buf, handler);
}
} else {
input->readBytes(buf, dataSize);
if (cutZeros) {
for (int p = r.height() * r.width() - 1; p >= 0; p--) {
buf[p] = RGB24_TO_PIXEL32(bytebuf[p*3],
bytebuf[p*3+1],
bytebuf[p*3+2]);
}
}
}
} else {
int x, y, b, w;
PIXEL_T *ptr = buf;
rdr::U8 bits;
if (palSize <= 2) {
// 2-color palette
w = (r.width() + 7) / 8;
for (y = 0; y < r.height(); y++) {
for (x = 0; x < r.width() / 8; x++) {
bits = input->readU8();
for (b = 7; b >= 0; b--) {
*ptr++ = palette[bits >> b & 1];
}
}
if (r.width() % 8 != 0) {
bits = input->readU8();
for (b = 7; b >= 8 - r.width() % 8; b--) {
*ptr++ = palette[bits >> b & 1];
}
}
}
} else {
// 256-color palette
for (y = 0; y < r.height(); y++) {
for (x = 0; x < r.width(); x++) {
*ptr++ = palette[input->readU8()];
}
}
}
}
IMAGE_RECT(r, buf);
if (streamId != -1) {
zis[streamId].reset();
}
}
static bool
DecompressJpegRect(const Rect& r, rdr::InStream* is,
PIXEL_T* buf, CMsgHandler* handler)
{
struct jpeg_decompress_struct cinfo;
struct jpeg_error_mgr jerr;
PIXEL_T *pixelPtr = buf;
static rdr::U8 scanline_buffer[TIGHT_MAX_WIDTH*3];
JSAMPROW scanline = scanline_buffer;
// Read length
int compressedLen = is->readCompactLength();
if (compressedLen <= 0) {
throw Exception("Incorrect data received from the server.\n");
}
// Allocate netbuf and read in data
rdr::U8* netbuf = new rdr::U8[compressedLen];
if (!netbuf) {
throw Exception("rfb::tightDecode unable to allocate buffer");
}
is->readBytes(netbuf, compressedLen);
// Set up JPEG decompression
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_decompress(&cinfo);
JpegSetSrcManager(&cinfo, (char*)netbuf, compressedLen);
jpeg_read_header(&cinfo, TRUE);
cinfo.out_color_space = JCS_RGB;
jpeg_start_decompress(&cinfo);
if (cinfo.output_width != (unsigned)r.width() || cinfo.output_height != (unsigned)r.height() ||
cinfo.output_components != 3) {
jpeg_destroy_decompress(&cinfo);
throw Exception("Tight Encoding: Wrong JPEG data received.\n");
}
// Decompress
const rfb::PixelFormat& myFormat = handler->cp.pf();
while (cinfo.output_scanline < cinfo.output_height) {
jpeg_read_scanlines(&cinfo, &scanline, 1);
if (jpegError) {
break;
}
for (int dx = 0; dx < r.width(); dx++) {
*pixelPtr++ =
RGB24_TO_PIXEL(scanline[dx*3], scanline[dx*3+1], scanline[dx*3+2]);
}
}
IMAGE_RECT(r, buf);
if (!jpegError) {
jpeg_finish_decompress(&cinfo);
}
jpeg_destroy_decompress(&cinfo);
delete [] netbuf;
return !jpegError;
}
#if BPP == 32
static void
FilterGradient24(const Rect& r, rdr::InStream* is, int dataSize,
PIXEL_T* buf, CMsgHandler* handler)
{
int x, y, c;
static rdr::U8 prevRow[TIGHT_MAX_WIDTH*3];
static rdr::U8 thisRow[TIGHT_MAX_WIDTH*3];
rdr::U8 pix[3];
int est[3];
memset(prevRow, 0, sizeof(prevRow));
// Allocate netbuf and read in data
rdr::U8 *netbuf = new rdr::U8[dataSize];
if (!netbuf) {
throw Exception("rfb::tightDecode unable to allocate buffer");
}
is->readBytes(netbuf, dataSize);
// Set up shortcut variables
const rfb::PixelFormat& myFormat = handler->cp.pf();
int rectHeight = r.height();
int rectWidth = r.width();
for (y = 0; y < rectHeight; y++) {
/* First pixel in a row */
for (c = 0; c < 3; c++) {
pix[c] = netbuf[y*rectWidth*3+c] + prevRow[c];
thisRow[c] = pix[c];
}
buf[y*rectWidth] = RGB24_TO_PIXEL32(pix[0], pix[1], pix[2]);
/* Remaining pixels of a row */
for (x = 1; x < rectWidth; x++) {
for (c = 0; c < 3; c++) {
est[c] = prevRow[x*3+c] + pix[c] - prevRow[(x-1)*3+c];
if (est[c] > 0xff) {
est[c] = 0xff;
} else if (est[c] < 0) {
est[c] = 0;
}
pix[c] = netbuf[(y*rectWidth+x)*3+c] + est[c];
thisRow[x*3+c] = pix[c];
}
buf[y*rectWidth+x] = RGB24_TO_PIXEL32(pix[0], pix[1], pix[2]);
}
memcpy(prevRow, thisRow, sizeof(prevRow));
}
delete [] netbuf;
}
#endif
static void
FilterGradient(const Rect& r, rdr::InStream* is, int dataSize,
PIXEL_T* buf, CMsgHandler* handler)
{
int x, y, c;
static rdr::U8 prevRow[TIGHT_MAX_WIDTH*sizeof(PIXEL_T)];
static rdr::U8 thisRow[TIGHT_MAX_WIDTH*sizeof(PIXEL_T)];
int pix[3];
int max[3];
int shift[3];
int est[3];
memset(prevRow, 0, sizeof(prevRow));
// Allocate netbuf and read in data
PIXEL_T *netbuf = (PIXEL_T*)new rdr::U8[dataSize];
if (!netbuf) {
throw Exception("rfb::tightDecode unable to allocate buffer");
}
is->readBytes(netbuf, dataSize);
// Set up shortcut variables
const rfb::PixelFormat& myFormat = handler->cp.pf();
max[0] = myFormat.redMax;
max[1] = myFormat.greenMax;
max[2] = myFormat.blueMax;
shift[0] = myFormat.redShift;
shift[1] = myFormat.greenShift;
shift[2] = myFormat.blueShift;
int rectHeight = r.height();
int rectWidth = r.width();
for (y = 0; y < rectHeight; y++) {
/* First pixel in a row */
for (c = 0; c < 3; c++) {
pix[c] = (netbuf[y*rectWidth] >> shift[c]) + prevRow[c] & max[c];
thisRow[c] = pix[c];
}
buf[y*rectWidth] = RGB_TO_PIXEL(pix[0], pix[1], pix[2]);
/* Remaining pixels of a row */
for (x = 1; x < rectWidth; x++) {
for (c = 0; c < 3; c++) {
est[c] = prevRow[x*3+c] + pix[c] - prevRow[(x-1)*3+c];
if (est[c] > max[c]) {
est[c] = max[c];
} else if (est[c] < 0) {
est[c] = 0;
}
pix[c] = (netbuf[y*rectWidth+x] >> shift[c]) + est[c] & max[c];
thisRow[x*3+c] = pix[c];
}
buf[y*rectWidth+x] = RGB_TO_PIXEL(pix[0], pix[1], pix[2]);
}
memcpy(prevRow, thisRow, sizeof(prevRow));
}
delete [] netbuf;
}
#undef TIGHT_MIN_TO_COMPRESS
#undef TIGHT_DECODE
#undef READ_PIXEL
#undef PIXEL_T
}