blob: 1d8acc0eee5557adea91e73b6dd6088ab3e3f861 [file] [log] [blame]
/* Copyright (C) 2000-2003 Constantin Kaplinsky. All Rights Reserved.
* Copyright (C) 2011 D. R. Commander. All Rights Reserved.
* Copyright 2014 Pierre Ossman for Cendio AB.
*
* 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.
*/
//
// tightEncode.h - Tight encoding function.
//
// This file is #included after having set the following macro:
// BPP - 8, 16 or 32
//
#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 TIGHT_ENCODE TightEncoder::CONCAT2E(tightEncode,BPP)
#define HASH_FUNCTION CONCAT2E(HASH_FUNC,BPP)
#define PACK_PIXELS TightEncoder::CONCAT2E(packPixels,BPP)
#define ENCODE_SOLID_RECT TightEncoder::CONCAT2E(encodeSolidRect,BPP)
#define ENCODE_FULLCOLOR_RECT TightEncoder::CONCAT2E(encodeFullColorRect,BPP)
#define ENCODE_MONO_RECT TightEncoder::CONCAT2E(encodeMonoRect,BPP)
#define ENCODE_INDEXED_RECT TightEncoder::CONCAT2E(encodeIndexedRect,BPP)
#define ENCODE_JPEG_RECT TightEncoder::CONCAT2E(encodeJpegRect,BPP)
#define FAST_FILL_PALETTE TightEncoder::CONCAT2E(fastFillPalette,BPP)
#define FILL_PALETTE TightEncoder::CONCAT2E(fillPalette,BPP)
#define CHECK_SOLID_TILE TightEncoder::CONCAT2E(checkSolidTile,BPP)
#ifndef TIGHT_ONCE
#define TIGHT_ONCE
//
// Compress the data (but do not perform actual compression if the data
// size is less than TIGHT_MIN_TO_COMPRESS bytes.
//
void TightEncoder::compressData(const void *buf, unsigned int length,
rdr::ZlibOutStream *zos, int zlibLevel,
rdr::OutStream *os)
{
if (length < TIGHT_MIN_TO_COMPRESS) {
os->writeBytes(buf, length);
} else {
// FIXME: Using a temporary MemOutStream may be not efficient.
// Maybe use the same static object used in the JPEG coder?
int maxBeforeSize = pconf->maxRectSize * (clientpf.bpp / 8);
int maxAfterSize = maxBeforeSize + (maxBeforeSize + 99) / 100 + 12;
rdr::MemOutStream mem_os(maxAfterSize);
zos->setUnderlying(&mem_os);
zos->setCompressionLevel(zlibLevel);
zos->writeBytes(buf, length);
zos->flush();
zos->setUnderlying(NULL);
writeCompact(os, mem_os.length());
os->writeBytes(mem_os.data(), mem_os.length());
}
}
#endif // #ifndef TIGHT_ONCE
//
// Convert 32-bit color samples into 24-bit colors, in place.
// Performs packing only when redMax, greenMax and blueMax are all 255.
// Color components are assumed to be byte-aligned.
//
unsigned int PACK_PIXELS (PIXEL_T *buf, unsigned int count)
{
#if (BPP != 32)
return count * sizeof(PIXEL_T);
#else
if (!pack24)
return count * sizeof(PIXEL_T);
rdr::U32 pix;
rdr::U8 *dst = (rdr::U8 *)buf;
for (unsigned int i = 0; i < count; i++) {
pix = *buf++;
clientpf.rgbFromBuffer(dst, (rdr::U8*)&pix, 1);
dst += 3;
}
return count * 3;
#endif
}
//
// Main function of the Tight encoder
//
void TIGHT_ENCODE (const Rect& r, rdr::OutStream *os, bool forceSolid)
{
int stride;
rdr::U32 solidColor;
const PIXEL_T *rawPixels = (const PIXEL_T *)pb->getBuffer(r, &stride);
PIXEL_T *pixels = NULL;
bool grayScaleJPEG = (jpegSubsampling == subsampleGray && jpegQuality != -1);
#if (BPP == 32)
// Check if it's necessary to pack 24-bit pixels, and
// compute appropriate shift values if necessary.
pack24 = clientpf.is888();
#endif
if (forceSolid) {
// Subrectangle has already been determined to be solid.
clientpf.bufferFromBuffer((rdr::U8*)&solidColor, serverpf,
(const rdr::U8*)rawPixels, 1);
pixels = (PIXEL_T *)&solidColor;
palette.clear();
palette.insert(solidColor, 1);
} else {
// Analyze subrectangle's colors to determine best encoding method.
palMaxColors = r.area() / pconf->idxMaxColorsDivisor;
if (jpegQuality != -1)
palMaxColors = pconf->palMaxColorsWithJPEG;
if (palMaxColors < 2 && r.area() >= pconf->monoMinRectSize)
palMaxColors = 2;
if (clientpf.equal(serverpf) && clientpf.bpp >= 16) {
// Count the colors in the raw buffer, so we can avoid unnecessary pixel
// translation when encoding with JPEG.
if (grayScaleJPEG) palette.clear();
else FAST_FILL_PALETTE(rawPixels, stride, r);
// JPEG can read from the raw buffer, but for the other methods, we need
// to translate the raw pixels into an intermediate buffer.
if(palette.size() != 0 || jpegQuality == -1) {
pixels = (PIXEL_T *)conn->writer()->getImageBuf(r.area());
stride = r.width();
pb->getImage(clientpf, pixels, r);
}
} else {
// Pixel translation will be required, so create an intermediate buffer,
// translate the raw pixels into it, and count its colors.
pixels = (PIXEL_T *)conn->writer()->getImageBuf(r.area());
stride = r.width();
pb->getImage(clientpf, pixels, r);
if (grayScaleJPEG) palette.clear();
else FILL_PALETTE(pixels, r.area());
}
}
switch (palette.size()) {
case 0:
// Truecolor image
#if (BPP != 8)
if (jpegQuality != -1) {
if (pixels)
ENCODE_JPEG_RECT(pixels, stride, r, os);
else
ENCODE_JPEG_RECT((PIXEL_T *)rawPixels, stride, r, os);
break;
}
#endif
ENCODE_FULLCOLOR_RECT(pixels, r, os);
break;
case 1:
// Solid rectangle
ENCODE_SOLID_RECT(pixels, os);
break;
case 2:
// Two-color rectangle
ENCODE_MONO_RECT(pixels, r, os);
break;
#if (BPP != 8)
default:
// Up to 256 different colors
ENCODE_INDEXED_RECT(pixels, r, os);
#endif
}
}
//
// Subencoding implementations.
//
void ENCODE_SOLID_RECT (PIXEL_T *buf, rdr::OutStream *os)
{
os->writeU8(0x08 << 4);
int length = PACK_PIXELS(buf, 1);
os->writeBytes(buf, length);
}
void ENCODE_FULLCOLOR_RECT (PIXEL_T *buf, const Rect& r, rdr::OutStream *os)
{
const int streamId = 0;
os->writeU8(streamId << 4);
int length = PACK_PIXELS(buf, r.area());
compressData(buf, length, &zos[streamId], pconf->rawZlibLevel, os);
}
void ENCODE_MONO_RECT (PIXEL_T *buf, const Rect& r, rdr::OutStream *os)
{
const int streamId = 1;
os->writeU8((streamId | 0x04) << 4);
os->writeU8(0x01);
// Write the palette
PIXEL_T pal[2] = { (PIXEL_T)palette.getColour(0),
(PIXEL_T)palette.getColour(1) };
os->writeU8(1);
os->writeBytes(pal, PACK_PIXELS(pal, 2));
// Encode the data in-place
PIXEL_T *src = buf;
rdr::U8 *dst = (rdr::U8 *)buf;
int w = r.width();
int h = r.height();
PIXEL_T bg;
unsigned int value, mask;
int aligned_width;
int x, y, bg_bits;
bg = (PIXEL_T) pal[0];
aligned_width = w - w % 8;
for (y = 0; y < h; y++) {
for (x = 0; x < aligned_width; x += 8) {
for (bg_bits = 0; bg_bits < 8; bg_bits++) {
if (*src++ != bg)
break;
}
if (bg_bits == 8) {
*dst++ = 0;
continue;
}
mask = 0x80 >> bg_bits;
value = mask;
for (bg_bits++; bg_bits < 8; bg_bits++) {
mask >>= 1;
if (*src++ != bg) {
value |= mask;
}
}
*dst++ = (rdr::U8)value;
}
mask = 0x80;
value = 0;
if (x >= w)
continue;
for (; x < w; x++) {
if (*src++ != bg) {
value |= mask;
}
mask >>= 1;
}
*dst++ = (rdr::U8)value;
}
// Write the data
int length = (w + 7) / 8;
length *= h;
compressData(buf, length, &zos[streamId], pconf->monoZlibLevel, os);
}
#if (BPP != 8)
void ENCODE_INDEXED_RECT (PIXEL_T *buf, const Rect& r, rdr::OutStream *os)
{
const int streamId = 2;
os->writeU8((streamId | 0x04) << 4);
os->writeU8(0x01);
// Write the palette
{
PIXEL_T pal[256];
for (int i = 0; i < palette.size(); i++)
pal[i] = (PIXEL_T)palette.getColour(i);
os->writeU8((rdr::U8)(palette.size() - 1));
os->writeBytes(pal, PACK_PIXELS(pal, palette.size()));
}
// Encode data in-place
PIXEL_T *src = buf;
rdr::U8 *dst = (rdr::U8 *)buf;
int count = r.area();
PIXEL_T rgb;
int rep = 0;
unsigned char idx;
while (count--) {
rgb = *src++;
while (count && *src == rgb) {
rep++, src++, count--;
}
idx = palette.lookup(rgb);
*dst++ = idx;
while (rep) {
*dst++ = idx;
rep--;
}
}
// Write the data
compressData(buf, r.area(), &zos[streamId], pconf->idxZlibLevel, os);
}
#endif // #if (BPP != 8)
//
// JPEG compression.
//
#if (BPP != 8)
void ENCODE_JPEG_RECT (PIXEL_T *buf, int stride, const Rect& r,
rdr::OutStream *os)
{
jc.clear();
jc.compress((rdr::U8 *)buf, stride, r, clientpf,
jpegQuality, jpegSubsampling);
os->writeU8(0x09 << 4);
writeCompact(os, jc.length());
os->writeBytes(jc.data(), jc.length());
}
#endif // #if (BPP != 8)
//
// Determine the number of colors in the rectangle, and fill in the palette.
//
#if (BPP == 8)
void FILL_PALETTE (PIXEL_T *data, int count)
{
PIXEL_T c0, c1;
int i, n0, n1;
palette.clear();
c0 = data[0];
for (i = 1; i < count && data[i] == c0; i++);
if (i == count) {
palette.insert(c0, i);
return; // Solid rectangle
}
if (palMaxColors < 2)
return;
n0 = i;
c1 = data[i];
n1 = 0;
for (i++; i < count; i++) {
if (data[i] == c0) {
n0++;
} else if (data[i] == c1) {
n1++;
} else
break;
}
if (i == count) {
palette.insert(c0, n0); // Two colors
palette.insert(c1, n1);
}
}
void FAST_FILL_PALETTE (const PIXEL_T *data, int stride, const Rect& r)
{
}
#else // (BPP != 8)
void FILL_PALETTE (PIXEL_T *data, int count)
{
PIXEL_T c0, c1, ci = 0;
int i, n0, n1, ni;
palette.clear();
c0 = data[0];
for (i = 1; i < count && data[i] == c0; i++);
if (i >= count) {
palette.insert(c0, i); // Solid rectangle
return;
}
if (palMaxColors < 2)
return; // Full-color format preferred
n0 = i;
c1 = data[i];
n1 = 0;
for (i++; i < count; i++) {
ci = data[i];
if (ci == c0) {
n0++;
} else if (ci == c1) {
n1++;
} else
break;
}
palette.insert(c0, n0);
palette.insert(c1, n1);
if (i >= count)
return; // Two colors
ni = 1;
for (i++; i < count; i++) {
if (data[i] == ci) {
ni++;
} else {
if (!palette.insert (ci, ni) || (palette.size() > palMaxColors)) {
palette.clear();
return;
}
ci = data[i];
ni = 1;
}
}
if (!palette.insert (ci, ni) || (palette.size() > palMaxColors))
palette.clear();
}
void FAST_FILL_PALETTE (const PIXEL_T *data, int stride, const Rect& r)
{
PIXEL_T c0, c1, ci = 0, mask, c0t, c1t, cit;
int n0, n1, ni;
int w = r.width(), h = r.height();
const PIXEL_T *rowptr, *colptr, *rowptr2, *colptr2,
*dataend = &data[stride * h];
bool willTransform = !serverpf.equal(clientpf);
serverpf.bufferFromPixel((rdr::U8*)&mask, ~0);
palette.clear();
c0 = data[0] & mask;
n0 = 0;
for (rowptr = data; rowptr < dataend; rowptr += stride) {
for (colptr = rowptr; colptr < &rowptr[w]; colptr++) {
if (((*colptr) & mask) != c0)
goto soliddone;
n0++;
}
}
soliddone:
if (rowptr >= dataend) {
palette.insert(c0, 1); // Solid rectangle
return;
}
if (palMaxColors < 2)
return; // Full-color format preferred
c1 = *colptr & mask;
n1 = 0;
colptr++;
if (colptr >= &rowptr[w]) {
rowptr += stride; colptr = rowptr;
}
colptr2 = colptr;
for (rowptr2 = rowptr; rowptr2 < dataend;) {
for (; colptr2 < &rowptr2[w]; colptr2++) {
ci = (*colptr2) & mask;
if (ci == c0) {
n0++;
} else if (ci == c1) {
n1++;
} else
goto monodone;
}
rowptr2 += stride;
colptr2 = rowptr2;
}
monodone:
if (willTransform) {
clientpf.bufferFromBuffer((rdr::U8*)&c0t, serverpf, (rdr::U8*)&c0, 1);
clientpf.bufferFromBuffer((rdr::U8*)&c1t, serverpf, (rdr::U8*)&c1, 1);
}
else {
c0t = c0; c1t = c1;
}
palette.insert(c0t, n0);
palette.insert(c1t, n1);
if (colptr2 >= dataend)
return; // Two colors
ni = 1;
colptr2++;
if (colptr2 >= &rowptr2[w]) {
rowptr2 += stride; colptr2 = rowptr2;
}
colptr = colptr2;
for (rowptr = rowptr2; rowptr < dataend;) {
for (; colptr < &rowptr[w]; colptr++) {
if (((*colptr) & mask) == ci) {
ni++;
} else {
if (willTransform)
clientpf.bufferFromBuffer((rdr::U8*)&cit, serverpf, (rdr::U8*)&ci, 1);
else
cit = ci;
if (!palette.insert (cit, ni) || (palette.size() > palMaxColors)) {
palette.clear();
return;
}
ci = (*colptr) & mask;
ni = 1;
}
}
rowptr += stride;
colptr = rowptr;
}
clientpf.bufferFromBuffer((rdr::U8*)&cit, serverpf, (rdr::U8*)&ci, 1);
if (!palette.insert (cit, ni) || (palette.size() > palMaxColors))
palette.clear();
}
#endif // #if (BPP == 8)
bool CHECK_SOLID_TILE(Rect& r, rdr::U32 *colorPtr, bool needSameColor)
{
const PIXEL_T *buf;
PIXEL_T colorValue;
int w = r.width(), h = r.height();
int stride = w;
buf = (const PIXEL_T *)pb->getBuffer(r, &stride);
colorValue = *buf;
if (needSameColor && (rdr::U32)colorValue != *colorPtr)
return false;
int bufPad = stride - w;
while (h > 0) {
const PIXEL_T *bufEndOfRow = buf + w;
while (buf < bufEndOfRow) {
if (colorValue != *(buf++))
return false;
}
buf += bufPad;
h--;
}
*colorPtr = (rdr::U32)colorValue;
return true;
}
#undef PIXEL_T
#undef TIGHT_ENCODE
#undef HASH_FUNCTION
#undef PACK_PIXELS
#undef ENCODE_SOLID_RECT
#undef ENCODE_FULLCOLOR_RECT
#undef ENCODE_MONO_RECT
#undef ENCODE_INDEXED_RECT
#undef ENCODE_JPEG_RECT
#undef FAST_FILL_PALETTE
#undef FILL_PALETTE
#undef CHECK_SOLID_TILE
}