blob: 4e9969c7247a972a64e895d8e24dab48a54a4c8c [file] [log] [blame]
package com.tigervnc.decoder;
import com.tigervnc.decoder.common.Repaintable;
import com.tigervnc.vncviewer.RfbInputStream;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.image.ImageObserver;
import java.io.IOException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
//
// Class that used for decoding Tight encoded data.
//
public class TightDecoder extends RawDecoder implements ImageObserver {
final static int EncodingTight = 7;
//
// Tight decoder constants
//
final static int TightExplicitFilter = 0x04;
final static int TightFill = 0x08;
final static int TightJpeg = 0x09;
final static int TightMaxSubencoding = 0x09;
final static int TightFilterCopy = 0x00;
final static int TightFilterPalette = 0x01;
final static int TightFilterGradient = 0x02;
final static int TightMinToCompress = 12;
// Tight encoder's data.
final static int tightZlibBufferSize = 512;
public TightDecoder(Graphics g, RfbInputStream is) {
super(g, is);
tightInflaters = new Inflater[4];
}
public TightDecoder(Graphics g, RfbInputStream is, int frameBufferW,
int frameBufferH) {
super(g, is, frameBufferW, frameBufferH);
tightInflaters = new Inflater[4];
}
//
// Set and get methods for private TightDecoder
//
public void setRepainableControl(Repaintable r) {
repainatableControl = r;
}
//
// JPEG processing statistic methods
//
public long getNumJPEGRects() {
return statNumRectsTightJPEG;
}
public void setNumJPEGRects(int v) {
statNumRectsTightJPEG = v;
}
//
// Tight processing statistic methods
//
public long getNumTightRects() {
return statNumRectsTight;
}
public void setNumTightRects(int v) {
statNumRectsTight = v;
}
//
// Handle a Tight-encoded rectangle.
//
public void handleRect(int x, int y, int w, int h) throws Exception {
//
// Write encoding ID to record output stream
//
if (dos != null) {
dos.writeInt(TightDecoder.EncodingTight);
}
int comp_ctl = rfbis.readU8();
if (dos != null) {
// Tell the decoder to flush each of the four zlib streams.
dos.writeByte(comp_ctl | 0x0F);
}
// Flush zlib streams if we are told by the server to do so.
for (int stream_id = 0; stream_id < 4; stream_id++) {
if ((comp_ctl & 1) != 0 && tightInflaters[stream_id] != null) {
tightInflaters[stream_id] = null;
}
comp_ctl >>= 1;
}
// Check correctness of subencoding value.
if (comp_ctl > TightDecoder.TightMaxSubencoding) {
throw new Exception("Incorrect tight subencoding: " + comp_ctl);
}
// Handle solid-color rectangles.
if (comp_ctl == TightDecoder.TightFill) {
if (bytesPerPixel == 1) {
int idx = rfbis.readU8();
graphics.setColor(getColor256()[idx]);
if (dos != null) {
dos.writeByte(idx);
}
} else {
byte[] buf = new byte[3];
rfbis.readFully(buf);
if (dos != null) {
dos.write(buf);
}
Color bg = new Color(0xFF000000 | (buf[0] & 0xFF) << 16 |
(buf[1] & 0xFF) << 8 | (buf[2] & 0xFF));
graphics.setColor(bg);
}
graphics.fillRect(x, y, w, h);
repainatableControl.scheduleRepaint(x, y, w, h);
return;
}
if (comp_ctl == TightDecoder.TightJpeg) {
statNumRectsTightJPEG++;
// Read JPEG data.
byte[] jpegData = new byte[rfbis.readCompactLen()];
rfbis.readFully(jpegData);
if (dos != null) {
recordCompactLen(jpegData.length);
dos.write(jpegData);
}
// Create an Image object from the JPEG data.
Image jpegImage = Toolkit.getDefaultToolkit().createImage(jpegData);
// Remember the rectangle where the image should be drawn.
jpegRect = new Rectangle(x, y, w, h);
// Let the imageUpdate() method do the actual drawing, here just
// wait until the image is fully loaded and drawn.
synchronized(jpegRect) {
Toolkit.getDefaultToolkit().prepareImage(jpegImage, -1, -1, this);
try {
// Wait no longer than three seconds.
jpegRect.wait(3000);
} catch (InterruptedException e) {
throw new Exception("Interrupted while decoding JPEG image");
}
}
// Done, jpegRect is not needed any more.
jpegRect = null;
return;
} else {
statNumRectsTight++;
}
// Read filter id and parameters.
int numColors = 0, rowSize = w;
byte[] palette8 = new byte[2];
int[] palette24 = new int[256];
boolean useGradient = false;
if ((comp_ctl & TightDecoder.TightExplicitFilter) != 0) {
int filter_id = rfbis.readU8();
if (dos != null) {
dos.writeByte(filter_id);
}
if (filter_id == TightDecoder.TightFilterPalette) {
numColors = rfbis.readU8() + 1;
if (dos != null) {
dos.writeByte((numColors - 1));
}
if (bytesPerPixel == 1) {
if (numColors != 2) {
throw new Exception("Incorrect tight palette size: " + numColors);
}
rfbis.readFully(palette8);
if (dos != null) {
dos.write(palette8);
}
} else {
byte[] buf = new byte[numColors * 3];
rfbis.readFully(buf);
if (dos != null) {
dos.write(buf);
}
for (int i = 0; i < numColors; i++) {
palette24[i] = ((buf[i * 3] & 0xFF) << 16 |
(buf[i * 3 + 1] & 0xFF) << 8 |
(buf[i * 3 + 2] & 0xFF));
}
}
if (numColors == 2) {
rowSize = (w + 7) / 8;
}
} else if (filter_id == TightDecoder.TightFilterGradient) {
useGradient = true;
} else if (filter_id != TightDecoder.TightFilterCopy) {
throw new Exception("Incorrect tight filter id: " + filter_id);
}
}
if (numColors == 0 && bytesPerPixel == 4)
rowSize *= 3;
// Read, optionally uncompress and decode data.
int dataSize = h * rowSize;
if (dataSize < TightDecoder.TightMinToCompress) {
// Data size is small - not compressed with zlib.
if (numColors != 0) {
// Indexed colors.
byte[] indexedData = new byte[dataSize];
rfbis.readFully(indexedData);
if (dos != null) {
dos.write(indexedData);
}
if (numColors == 2) {
// Two colors.
if (bytesPerPixel == 1) {
decodeMonoData(x, y, w, h, indexedData, palette8);
} else {
decodeMonoData(x, y, w, h, indexedData, palette24);
}
} else {
// 3..255 colors (assuming bytesPixel == 4).
int i = 0;
for (int dy = y; dy < y + h; dy++) {
for (int dx = x; dx < x + w; dx++) {
pixels24[dy * framebufferWidth + dx] =
palette24[indexedData[i++] & 0xFF];
}
}
}
} else if (useGradient) {
// "Gradient"-processed data
byte[] buf = new byte[w * h * 3];
rfbis.readFully(buf);
if (dos != null) {
dos.write(buf);
}
decodeGradientData(x, y, w, h, buf);
} else {
// Raw truecolor data.
if (bytesPerPixel == 1) {
for (int dy = y; dy < y + h; dy++) {
rfbis.readFully(pixels8, dy * framebufferWidth + x, w);
if (dos != null) {
dos.write(pixels8, dy * framebufferWidth + x, w);
}
}
} else {
byte[] buf = new byte[w * 3];
int i, offset;
for (int dy = y; dy < y + h; dy++) {
rfbis.readFully(buf);
if (dos != null) {
dos.write(buf);
}
offset = dy * framebufferWidth + x;
for (i = 0; i < w; i++) {
pixels24[offset + i] =
(buf[i * 3] & 0xFF) << 16 |
(buf[i * 3 + 1] & 0xFF) << 8 |
(buf[i * 3 + 2] & 0xFF);
}
}
}
}
} else {
// Data was compressed with zlib.
int zlibDataLen = rfbis.readCompactLen();
byte[] zlibData = new byte[zlibDataLen];
rfbis.readFully(zlibData);
int stream_id = comp_ctl & 0x03;
if (tightInflaters[stream_id] == null) {
tightInflaters[stream_id] = new Inflater();
}
Inflater myInflater = tightInflaters[stream_id];
myInflater.setInput(zlibData);
byte[] buf = new byte[dataSize];
myInflater.inflate(buf);
if (dos != null) {
recordCompressedData(buf);
}
if (numColors != 0) {
// Indexed colors.
if (numColors == 2) {
// Two colors.
if (bytesPerPixel == 1) {
decodeMonoData(x, y, w, h, buf, palette8);
} else {
decodeMonoData(x, y, w, h, buf, palette24);
}
} else {
// More than two colors (assuming bytesPixel == 4).
int i = 0;
for (int dy = y; dy < y + h; dy++) {
for (int dx = x; dx < x + w; dx++) {
pixels24[dy * framebufferWidth + dx] =
palette24[buf[i++] & 0xFF];
}
}
}
} else if (useGradient) {
// Compressed "Gradient"-filtered data (assuming bytesPixel == 4).
decodeGradientData(x, y, w, h, buf);
} else {
// Compressed truecolor data.
if (bytesPerPixel == 1) {
int destOffset = y * framebufferWidth + x;
for (int dy = 0; dy < h; dy++) {
System.arraycopy(buf, dy * w, pixels8, destOffset, w);
destOffset += framebufferWidth;
}
} else {
int srcOffset = 0;
int destOffset, i;
for (int dy = 0; dy < h; dy++) {
myInflater.inflate(buf);
destOffset = (y + dy) * framebufferWidth + x;
for (i = 0; i < w; i++) {
RawDecoder.pixels24[destOffset + i] =
(buf[srcOffset] & 0xFF) << 16 |
(buf[srcOffset + 1] & 0xFF) << 8 |
(buf[srcOffset + 2] & 0xFF);
srcOffset += 3;
}
}
}
}
}
handleUpdatedPixels(x, y, w, h);
}
//
// Decode 1bpp-encoded bi-color rectangle (8-bit and 24-bit versions).
//
private void decodeMonoData(int x, int y, int w, int h, byte[] src, byte[] palette) {
int dx, dy, n;
int i = y * framebufferWidth + x;
int rowBytes = (w + 7) / 8;
byte b;
for (dy = 0; dy < h; dy++) {
for (dx = 0; dx < w / 8; dx++) {
b = src[dy*rowBytes+dx];
for (n = 7; n >= 0; n--)
pixels8[i++] = palette[b >> n & 1];
}
for (n = 7; n >= 8 - w % 8; n--) {
pixels8[i++] = palette[src[dy*rowBytes+dx] >> n & 1];
}
i += (framebufferWidth - w);
}
}
private void decodeMonoData(int x, int y, int w, int h, byte[] src, int[] palette) {
int dx, dy, n;
int i = y * framebufferWidth + x;
int rowBytes = (w + 7) / 8;
byte b;
for (dy = 0; dy < h; dy++) {
for (dx = 0; dx < w / 8; dx++) {
b = src[dy*rowBytes+dx];
for (n = 7; n >= 0; n--)
pixels24[i++] = palette[b >> n & 1];
}
for (n = 7; n >= 8 - w % 8; n--) {
pixels24[i++] = palette[src[dy*rowBytes+dx] >> n & 1];
}
i += (framebufferWidth - w);
}
}
//
// Decode data processed with the "Gradient" filter.
//
private void decodeGradientData (int x, int y, int w, int h, byte[] buf) {
int dx, dy, c;
byte[] prevRow = new byte[w * 3];
byte[] thisRow = new byte[w * 3];
byte[] pix = new byte[3];
int[] est = new int[3];
int offset = y * framebufferWidth + x;
for (dy = 0; dy < h; dy++) {
/* First pixel in a row */
for (c = 0; c < 3; c++) {
pix[c] = (byte)(prevRow[c] + buf[dy * w * 3 + c]);
thisRow[c] = pix[c];
}
pixels24[offset++] =
(pix[0] & 0xFF) << 16 | (pix[1] & 0xFF) << 8 | (pix[2] & 0xFF);
/* Remaining pixels of a row */
for (dx = 1; dx < w; dx++) {
for (c = 0; c < 3; c++) {
est[c] = ((prevRow[dx * 3 + c] & 0xFF) + (pix[c] & 0xFF) -
(prevRow[(dx-1) * 3 + c] & 0xFF));
if (est[c] > 0xFF) {
est[c] = 0xFF;
} else if (est[c] < 0x00) {
est[c] = 0x00;
}
pix[c] = (byte)(est[c] + buf[(dy * w + dx) * 3 + c]);
thisRow[dx * 3 + c] = pix[c];
}
pixels24[offset++] =
(pix[0] & 0xFF) << 16 | (pix[1] & 0xFF) << 8 | (pix[2] & 0xFF);
}
System.arraycopy(thisRow, 0, prevRow, 0, w * 3);
offset += (framebufferWidth - w);
}
}
//
// Override the ImageObserver interface method to handle drawing of
// JPEG-encoded data.
//
public boolean imageUpdate(Image img, int infoflags,
int x, int y, int width, int height) {
if ((infoflags & (ALLBITS | ABORT)) == 0) {
return true; // We need more image data.
} else {
// If the whole image is available, draw it now.
if ((infoflags & ALLBITS) != 0) {
if (jpegRect != null) {
synchronized(jpegRect) {
graphics.drawImage(img, jpegRect.x, jpegRect.y, null);
repainatableControl.scheduleRepaint(jpegRect.x, jpegRect.y,
jpegRect.width, jpegRect.height);
jpegRect.notify();
}
}
}
return false; // All image data was processed.
}
}
//
// Write an integer in compact representation (1..3 bytes) into the
// recorded session file.
//
void recordCompactLen(int len) throws IOException {
byte[] buf = new byte[3];
int bytes = 0;
buf[bytes++] = (byte)(len & 0x7F);
if (len > 0x7F) {
buf[bytes-1] |= 0x80;
buf[bytes++] = (byte)(len >> 7 & 0x7F);
if (len > 0x3FFF) {
buf[bytes-1] |= 0x80;
buf[bytes++] = (byte)(len >> 14 & 0xFF);
}
}
if (dos != null) dos.write(buf, 0, bytes);
}
//
// Compress and write the data into the recorded session file.
//
void recordCompressedData(byte[] data, int off, int len) throws IOException {
Deflater deflater = new Deflater();
deflater.setInput(data, off, len);
int bufSize = len + len / 100 + 12;
byte[] buf = new byte[bufSize];
deflater.finish();
int compressedSize = deflater.deflate(buf);
recordCompactLen(compressedSize);
if (dos != null) dos.write(buf, 0, compressedSize);
}
void recordCompressedData(byte[] data) throws IOException {
recordCompressedData(data, 0, data.length);
}
//
// Private members
//
private Inflater[] tightInflaters;
// Since JPEG images are loaded asynchronously, we have to remember
// their position in the framebuffer. Also, this jpegRect object is
// used for synchronization between the rfbThread and a JVM's thread
// which decodes and loads JPEG images.
private Rectangle jpegRect;
private Repaintable repainatableControl = null;
// Jpeg decoding statistics
private long statNumRectsTightJPEG = 0;
// Tight decoding statistics
private long statNumRectsTight = 0;
}