/* Copyright (C) 2000-2003 Constantin Kaplinsky. All Rights Reserved.
* Copyright 2004-2005 Cendio AB.
* Copyright (C) 2011 D. R. Commander. All Rights Reserved.
* Copyright (C) 2011-2012 Brian P. Hinz
*
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*/
package com.tigervnc.rfb;
import com.tigervnc.rdr.InStream;
import com.tigervnc.rdr.MemInStream;
import com.tigervnc.rdr.OutStream;
import com.tigervnc.rdr.ZlibInStream;
import java.util.ArrayList;
import java.io.InputStream;
import java.awt.image.*;
import java.awt.*;
import java.math.BigInteger;
import java.io.*;
import java.nio.*;
import javax.imageio.*;
import javax.imageio.stream.*;
public class TightDecoder extends Decoder {
final static int TIGHT_MAX_WIDTH = 2048;
final static int TIGHT_MIN_TO_COMPRESS = 12;
// Compression control
final static int tightExplicitFilter = 0x04;
final static int tightFill = 0x08;
final static int tightJpeg = 0x09;
final static int tightMaxSubencoding = 0x09;
// Filters to improve compression efficiency
final static int tightFilterCopy = 0x00;
final static int tightFilterPalette = 0x01;
final static int tightFilterGradient = 0x02;
public TightDecoder() {
super(DecoderFlags.DecoderPartiallyOrdered);
zis = new ZlibInStream[4];
for (int i = 0; i < 4; i++)
zis[i] = new ZlibInStream();
}
public void readRect(Rect r, InStream is,
ConnParams cp, OutStream os)
{
int 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) {
int len;
len = readCompact(is);
os.writeOpaque32(len);
os.copyBytes(is, len);
return;
}
// Quit on unsupported compression type.
if (comp_ctl > tightMaxSubencoding)
throw new Exception("TightDecoder: bad subencoding value received");
// "Basic" compression type.
int palSize = 0;
if (r.width() > TIGHT_MAX_WIDTH)
throw new Exception("TightDecoder: too large rectangle ("+r.width()+" pixels)");
// Possible palette
if ((comp_ctl & tightExplicitFilter) != 0) {
int filterId;
filterId = is.readU8() & 0xff;
os.writeU8(filterId);
switch (filterId) {
case tightFilterPalette:
palSize = is.readU8() + 1;
os.writeU32(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 new Exception("TightDecoder: invalid BPP for gradient filter");
break;
case tightFilterCopy:
break;
default:
throw new Exception("TightDecoder: unknown filter code received");
}
}
int 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 {
int len;
len = readCompact(is);
os.writeOpaque32(len);
os.copyBytes(is, len);
}
}
public boolean doRectsConflict(Rect rectA,
Object bufferA,
int buflenA,
Rect rectB,
Object bufferB,
int buflenB,
ConnParams cp)
{
byte comp_ctl_a, comp_ctl_b;
assert(buflenA >= 1);
assert(buflenB >= 1);
comp_ctl_a = ((byte[])bufferA)[0];
comp_ctl_b = ((byte[])bufferB)[0];
// 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;
}
public void decodeRect(Rect r, Object buffer,
int buflen, ConnParams cp,
ModifiablePixelBuffer pb)
{
ByteBuffer bufptr;
PixelFormat pf = cp.pf();
int comp_ctl;
bufptr = ByteBuffer.wrap((byte[])buffer);
assert(buflen >= 1);
comp_ctl = bufptr.get() & 0xff;
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) != 0) {
zis[i].reset();
}
comp_ctl >>= 1;
}
// "Fill" compression type.
if (comp_ctl == tightFill) {
if (pf.is888()) {
ByteBuffer pix = ByteBuffer.allocate(4);
assert(buflen >= 3);
pf.bufferFromRGB(pix, bufptr, 1);
pb.fillRect(pf, r, pix.array());
} else {
assert(buflen >= pf.bpp/8);
byte[] pix = new byte[pf.bpp/8];
bufptr.get(pix);
pb.fillRect(pf, r, pix);
}
return;
}
// "JPEG" compression type.
if (comp_ctl == tightJpeg) {
int len;
WritableRaster buf;
JpegDecompressor jd = new JpegDecompressor();
assert(buflen >= 4);
len = bufptr.getInt();
buflen -= 4;
// We always use direct decoding with JPEG images
jd.decompress(bufptr, len, pb, r, pb.getPF());
pb.commitBufferRW(r);
return;
}
// Quit on unsupported compression type.
if (comp_ctl > tightMaxSubencoding)
throw new Exception("TightDecoder: bad subencoding value received");
// "Basic" compression type.
int palSize = 0;
ByteBuffer palette = ByteBuffer.allocate(256 * 4);
boolean useGradient = false;
if ((comp_ctl & tightExplicitFilter) != 0) {
int filterId;
assert(buflen >= 1);
filterId = bufptr.get();
switch (filterId) {
case tightFilterPalette:
assert(buflen >= 1);
palSize = bufptr.getInt() + 1;
buflen -= 4;
if (pf.is888()) {
ByteBuffer tightPalette = ByteBuffer.allocate(palSize * 3);
assert(buflen >= tightPalette.capacity());
bufptr.get(tightPalette.array(), 0, tightPalette.capacity());
buflen -= tightPalette.capacity();
pf.bufferFromRGB(palette.duplicate(), tightPalette, palSize);
} else {
int len;
len = palSize * pf.bpp/8;
assert(buflen >= len);
bufptr.get(palette.array(), 0, 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.
int rowSize, dataSize;
byte[] netbuf;
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 {
int len;
int streamId;
MemInStream ms;
assert(buflen >= 4);
len = bufptr.getInt();
buflen -= 4;
assert(buflen >= len);
streamId = comp_ctl & 0x03;
ms = new MemInStream(bufptr.array(), bufptr.position(), len);
zis[streamId].setUnderlying(ms, len);
// Allocate netbuf and read in data
netbuf = new byte[dataSize];
zis[streamId].readBytes(netbuf, 0, dataSize);
zis[streamId].removeUnderlying();
ms = null;
bufptr = ByteBuffer.wrap(netbuf);
buflen = dataSize;
}
ByteBuffer outbuf = ByteBuffer.allocate(r.area() * pf.bpp/8);
int stride = r.width();
if (palSize == 0) {
// Truecolor data.
if (useGradient) {
if (pf.is888()) {
FilterGradient24(bufptr, pf, outbuf, stride, r);
} else {
switch (pf.bpp) {
case 8:
assert(false);
break;
case 16:
FilterGradient(bufptr, pf, outbuf, stride, r);
break;
case 32:
FilterGradient(bufptr, pf, outbuf, stride, r);
break;
}
}
} else {
// Copy
ByteBuffer ptr = (ByteBuffer)outbuf.duplicate().mark();
ByteBuffer srcPtr = bufptr.duplicate();
int w = r.width();
int h = r.height();
if (pf.is888()) {
while (h > 0) {
pf.bufferFromRGB(ptr.duplicate(), srcPtr.duplicate(), w);
ptr.position(ptr.position() + stride * pf.bpp/8);
srcPtr.position(srcPtr.position() + w * 3);
h--;
}
} else {
while (h > 0) {
ptr.put(srcPtr.array(), srcPtr.position(), w * pf.bpp/8);
ptr.reset().position(ptr.position() + stride * pf.bpp/8).mark();
srcPtr.position(srcPtr.position() + w * pf.bpp/8);
h--;
}
}
}
} else {
// Indexed color
switch (pf.bpp) {
case 8:
FilterPalette8(palette, palSize,
bufptr, outbuf, stride, r);
break;
case 16:
FilterPalette16(palette.asShortBuffer(), palSize,
bufptr, outbuf.asShortBuffer(), stride, r);
break;
case 32:
FilterPalette32(palette.asIntBuffer(), palSize,
bufptr, outbuf.asIntBuffer(), stride, r);
break;
}
}
pb.imageRect(pf, r, outbuf.array());
}
final private void FilterGradient24(ByteBuffer inbuf,
PixelFormat pf, ByteBuffer outbuf,
int stride, Rect r)
{
int x, y, c;
byte[] prevRow = new byte[TIGHT_MAX_WIDTH*3];
byte[] thisRow = new byte[TIGHT_MAX_WIDTH*3];
ByteBuffer pix = ByteBuffer.allocate(3);
int[] est = new int[3];
// Set up shortcut variables
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.put(c, (byte)(inbuf.get(y*rectWidth*3+c) + prevRow[c]));
thisRow[c] = pix.get(c);
}
pf.bufferFromRGB((ByteBuffer)outbuf.position(y*stride), pix, 1);
/* Remaining pixels of a row */
for (x = 1; x < rectWidth; x++) {
for (c = 0; c < 3; c++) {
est[c] = prevRow[x*3+c] + pix.get(c) - prevRow[(x-1)*3+c];
if (est[c] > 0xff) {
est[c] = 0xff;
} else if (est[c] < 0) {
est[c] = 0;
}
pix.put(c, (byte)(inbuf.get((y*rectWidth+x)*3+c) + est[c]));
thisRow[x*3+c] = pix.get(c);
}
pf.bufferFromRGB((ByteBuffer)outbuf.position(y*stride+x), pix, 1);
}
System.arraycopy(thisRow, 0, prevRow, 0, prevRow.length);
}
}
final private void FilterGradient(ByteBuffer inbuf,
PixelFormat pf, ByteBuffer outbuf,
int stride, Rect r)
{
int x, y, c;
byte[] prevRow = new byte[TIGHT_MAX_WIDTH];
byte[] thisRow = new byte[TIGHT_MAX_WIDTH];
ByteBuffer pix = ByteBuffer.allocate(3);
int[] est = new int[3];
// Set up shortcut variables
int rectHeight = r.height();
int rectWidth = r.width();
for (y = 0; y < rectHeight; y++) {
/* First pixel in a row */
pf.rgbFromBuffer(pix, (ByteBuffer)inbuf.position(y*rectWidth), 1);
for (c = 0; c < 3; c++)
pix.put(c, (byte)(pix.get(c) + prevRow[c]));
System.arraycopy(pix.array(), 0, thisRow, 0, pix.capacity());
pf.bufferFromRGB((ByteBuffer)outbuf.position(y*stride), pix, 1);
/* Remaining pixels of a row */
for (x = 1; x < rectWidth; x++) {
for (c = 0; c < 3; c++) {
est[c] = prevRow[x*3+c] + pix.get(c) - prevRow[(x-1)*3+c];
if (est[c] > 0xff) {
est[c] = 0xff;
} else if (est[c] < 0) {
est[c] = 0;
}
}
pf.rgbFromBuffer(pix, (ByteBuffer)inbuf.position(y*rectWidth+x), 1);
for (c = 0; c < 3; c++)
pix.put(c, (byte)(pix.get(c) + est[c]));
System.arraycopy(pix.array(), 0, thisRow, x*3, pix.capacity());
pf.bufferFromRGB((ByteBuffer)outbuf.position(y*stride+x), pix, 1);
}
System.arraycopy(thisRow, 0, prevRow, 0, prevRow.length);
}
}
private void FilterPalette8(ByteBuffer palette, int palSize,
ByteBuffer inbuf, ByteBuffer outbuf,
int stride, Rect r)
{
// Indexed color
int x, h = r.height(), w = r.width(), b, pad = stride - w;
ByteBuffer ptr = outbuf.duplicate();
byte bits;
ByteBuffer srcPtr = inbuf.duplicate();
if (palSize <= 2) {
// 2-color palette
while (h > 0) {
for (x = 0; x < w / 8; x++) {
bits = srcPtr.get();
for (b = 7; b >= 0; b--) {
ptr.put(palette.get(bits >> b & 1));
}
}
if (w % 8 != 0) {
bits = srcPtr.get();
for (b = 7; b >= 8 - w % 8; b--) {
ptr.put(palette.get(bits >> b & 1));
}
}
ptr.position(ptr.position() + pad);
h--;
}
} else {
// 256-color palette
while (h > 0) {
int endOfRow = ptr.position() + w;
while (ptr.position() < endOfRow) {
ptr.put(palette.get(srcPtr.get()));
}
ptr.position(ptr.position() + pad);
h--;
}
}
}
private void FilterPalette16(ShortBuffer palette, int palSize,
ByteBuffer inbuf, ShortBuffer outbuf,
int stride, Rect r)
{
// Indexed color
int x, h = r.height(), w = r.width(), b, pad = stride - w;
ShortBuffer ptr = outbuf.duplicate();
byte bits;
ByteBuffer srcPtr = inbuf.duplicate();
if (palSize <= 2) {
// 2-color palette
while (h > 0) {
for (x = 0; x < w / 8; x++) {
bits = srcPtr.get();
for (b = 7; b >= 0; b--) {
ptr.put(palette.get(bits >> b & 1));
}
}
if (w % 8 != 0) {
bits = srcPtr.get();
for (b = 7; b >= 8 - w % 8; b--) {
ptr.put(palette.get(bits >> b & 1));
}
}
ptr.position(ptr.position() + pad);
h--;
}
} else {
// 256-color palette
while (h > 0) {
int endOfRow = ptr.position() + w;
while (ptr.position() < endOfRow) {
ptr.put(palette.get(srcPtr.get()));
}
ptr.position(ptr.position() + pad);
h--;
}
}
}
private void FilterPalette32(IntBuffer palette, int palSize,
ByteBuffer inbuf, IntBuffer outbuf,
int stride, Rect r)
{
// Indexed color
int x, h = r.height(), w = r.width(), b, pad = stride - w;
IntBuffer ptr = outbuf.duplicate();
byte bits;
ByteBuffer srcPtr = inbuf.duplicate();
if (palSize <= 2) {
// 2-color palette
while (h > 0) {
for (x = 0; x < w / 8; x++) {
bits = srcPtr.get();
for (b = 7; b >= 0; b--) {
ptr.put(palette.get(bits >> b & 1));
}
}
if (w % 8 != 0) {
bits = srcPtr.get();
for (b = 7; b >= 8 - w % 8; b--) {
ptr.put(palette.get(bits >> b & 1));
}
}
ptr.position(ptr.position() + pad);
h--;
}
} else {
// 256-color palette
while (h > 0) {
int endOfRow = ptr.position() + w;
while (ptr.position() < endOfRow) {
ptr.put(palette.get(srcPtr.get() & 0xff));
}
ptr.position(ptr.position() + pad);
h--;
}
}
}
public final int readCompact(InStream is) {
byte b;
int result;
b = (byte)is.readU8();
result = (int)b & 0x7F;
if ((b & 0x80) != 0) {
b = (byte)is.readU8();
result |= ((int)b & 0x7F) << 7;
if ((b & 0x80) != 0) {
b = (byte)is.readU8();
result |= ((int)b & 0xFF) << 14;
}
}
return result;
}
private ZlibInStream[] zis;
}