/**
* Copyright (C) 2012 Iordan Iordanov
* Copyright (C) 2010 Michael A. MacDonald
* Copyright (C) 2004 Horizon Wimba. All Rights Reserved.
* Copyright (C) 2001-2003 HorizonLive.com, Inc. All Rights Reserved.
* Copyright (C) 2001,2002 Constantin Kaplinsky. All Rights Reserved.
* Copyright (C) 2000 Tridia Corporation. All Rights Reserved.
* Copyright (C) 1999 AT&T Laboratories Cambridge. 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.
*/
package com.iiordanov.bVNC;
import java.io.IOException;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;
import com.iiordanov.bVNC.input.RemotePointer;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Paint.Style;
import android.graphics.RectF;
import android.util.Log;
public class Decoder {
private final static String TAG = "Decoder";
// Color Model settings
private COLORMODEL pendingColorModel = COLORMODEL.C24bit;
private COLORMODEL colorModel = null;
private int bytesPerPixel = 0;
private int[] colorPalette = null;
// Tight decoder's data.
private Inflater[] tightInflaters = new Inflater[4];
private Paint handleTightRectPaint = new Paint();
private byte[] solidColorBuf = new byte[3];
private byte[] tightPalette8 = new byte[2];
private int[] tightPalette24 = new int[256];
private byte[] colorBuf = new byte[768];
private byte[] uncompDataBuf = new byte[RfbProto.TightMinToCompress*3];
private byte[] zlibData = new byte[4096];
private byte[] inflBuf = new byte[8192];
private BitmapFactory.Options bitmapopts = new BitmapFactory.Options();
private boolean valid, useGradient;
private int c, dx, dy, offset, boffset, idx, stream_id, comp_ctl, numColors, rowSize, dataSize, jpegDataLen;
// ZRLE decoder's data.
private byte[] zrleBuf;
private int[] zrleTilePixels;
private ZlibInStream zrleInStream;
private Paint handleZRLERectPaint = new Paint();
private int[] handleZRLERectPalette = new int[128];
private byte[] readPixelsBuffer = new byte[128];
// Zlib decoder's data.
private byte[] zlibBuf;
private Inflater zlibInflater;
private byte[] handleZlibRectBuffer = new byte[128];
// RRE decoder's data.
private Paint handleRREPaint = new Paint();
private byte[] bg_buf = new byte[4];
private byte[] rre_buf = new byte[128];
// Raw decoder's data.
private byte[] handleRawRectBuffer = new byte[128];
// Hextile decoder's data.
// These colors should be kept between handleHextileSubrect() calls.
private int hextile_bg, hextile_fg;
private Paint handleHextileSubrectPaint = new Paint();
private byte[] backgroundColorBuffer = new byte[4];
private AbstractBitmapData bitmapData;
private RemoteCanvas vncCanvas;
public Decoder (RemoteCanvas v) {
handleRREPaint.setStyle(Style.FILL);
handleTightRectPaint.setStyle(Style.FILL);
bitmapopts.inPurgeable = false;
bitmapopts.inDither = false;
bitmapopts.inTempStorage = new byte[32768];
bitmapopts.inPreferredConfig= Bitmap.Config.RGB_565;
bitmapopts.inScaled = false;
vncCanvas = v;
}
void setBitmapData (AbstractBitmapData b) {
bitmapData = b;
}
void setPixelFormat(RfbProto rfb) throws IOException {
pendingColorModel.setPixelFormat(rfb);
bytesPerPixel = pendingColorModel.bpp();
colorPalette = pendingColorModel.palette();
colorModel = pendingColorModel;
pendingColorModel = null;
}
public void setColorModel(COLORMODEL cm) {
// Only update if color model changes
if (colorModel == null || !colorModel.equals(cm))
pendingColorModel = cm;
}
public COLORMODEL getColorModel() {
return colorModel;
}
public boolean isChangedColorModel() {
return (pendingColorModel != null);
}
void handleRawRect(RfbProto rfb, int x, int y, int w, int h) throws IOException {
handleRawRect(rfb, x, y, w, h, true);
}
void handleRawRect(RfbProto rfb, int x, int y, int w, int h, boolean paint) throws IOException {
boolean valid=bitmapData.validDraw(x, y, w, h);
int[] pixels=bitmapData.bitmapPixels;
if (bytesPerPixel == 1) {
// 1 byte per pixel. Use palette lookup table.
if (w > handleRawRectBuffer.length) {
handleRawRectBuffer = new byte[w];
}
int i, offset;
for (int dy = y; dy < y + h; dy++) {
rfb.readFully(handleRawRectBuffer, 0, w);
if ( ! valid)
continue;
offset = bitmapData.offset(x, dy);
for (i = 0; i < w; i++) {
pixels[offset + i] = colorPalette[0xFF & handleRawRectBuffer[i]];
}
}
} else {
// 4 bytes per pixel (argb) 24-bit color
final int l = w * 4;
if (l>handleRawRectBuffer.length) {
handleRawRectBuffer = new byte[l];
}
int i, offset;
for (int dy = y; dy < y + h; dy++) {
rfb.readFully(handleRawRectBuffer, 0, l);
if ( ! valid)
continue;
offset = bitmapData.offset(x, dy);
for (i = 0; i < w; i++) {
final int idx = i*4;
pixels[offset + i] = // 0xFF << 24 |
(handleRawRectBuffer[idx + 2] & 0xff) << 16 | (handleRawRectBuffer[idx + 1] & 0xff) << 8 | (handleRawRectBuffer[idx] & 0xff);
}
}
}
if ( ! valid)
return;
bitmapData.updateBitmap(x, y, w, h);
if (paint)
vncCanvas.reDraw(x, y, w, h);
}
//
// Handle a CopyRect rectangle.
//
void handleCopyRect(RfbProto rfb, int x, int y, int w, int h) throws IOException {
// Read the source coordinates.
rfb.readCopyRect();
if (!bitmapData.validDraw(x, y, w, h))
return;
bitmapData.copyRect(rfb.copyRectSrcX, rfb.copyRectSrcY, x, y, w, h);
vncCanvas.reDraw(x, y, w, h);
}
//
// Handle an RRE-encoded rectangle.
//
void handleRRERect(RfbProto rfb, int x, int y, int w, int h) throws IOException {
boolean valid=bitmapData.validDraw(x, y, w, h);
int nSubrects = rfb.is.readInt();
rfb.readFully(bg_buf, 0, bytesPerPixel);
int pixel;
if (bytesPerPixel == 1) {
pixel = colorPalette[0xFF & bg_buf[0]];
} else {
pixel = Color.rgb(bg_buf[2] & 0xFF, bg_buf[1] & 0xFF, bg_buf[0] & 0xFF);
}
handleRREPaint.setColor(pixel);
if ( valid)
bitmapData.drawRect(x, y, w, h, handleRREPaint);
int len = nSubrects * (bytesPerPixel + 8);
if (len > rre_buf.length)
rre_buf = new byte[len];
rfb.readFully(rre_buf, 0, len);
if ( ! valid)
return;
int sx, sy, sw, sh;
int i = 0;
for (int j = 0; j < nSubrects; j++) {
if (bytesPerPixel == 1) {
pixel = colorPalette[0xFF & rre_buf[i++]];
} else {
pixel = Color.rgb(rre_buf[i + 2] & 0xFF, rre_buf[i + 1] & 0xFF, rre_buf[i] & 0xFF);
i += 4;
}
sx = x + ((rre_buf[i] & 0xff) << 8) + (rre_buf[i+1] & 0xff); i+=2;
sy = y + ((rre_buf[i] & 0xff) << 8) + (rre_buf[i+1] & 0xff); i+=2;
sw = ((rre_buf[i] & 0xff) << 8) + (rre_buf[i+1] & 0xff); i+=2;
sh = ((rre_buf[i] & 0xff) << 8) + (rre_buf[i+1] & 0xff); i+=2;
handleRREPaint.setColor(pixel);
bitmapData.drawRect(sx, sy, sw, sh, handleRREPaint);
}
vncCanvas.reDraw(x, y, w, h);
}
//
// Handle a CoRRE-encoded rectangle.
//
void handleCoRRERect(RfbProto rfb, int x, int y, int w, int h) throws IOException {
boolean valid=bitmapData.validDraw(x, y, w, h);
int nSubrects = rfb.is.readInt();
rfb.readFully(bg_buf, 0, bytesPerPixel);
int pixel;
if (bytesPerPixel == 1) {
pixel = colorPalette[0xFF & bg_buf[0]];
} else {
pixel = Color.rgb(bg_buf[2] & 0xFF, bg_buf[1] & 0xFF, bg_buf[0] & 0xFF);
}
handleRREPaint.setColor(pixel);
if ( valid)
bitmapData.drawRect(x, y, w, h, handleRREPaint);
int len = nSubrects * (bytesPerPixel + 8);
if (len > rre_buf.length)
rre_buf = new byte[len];
rfb.readFully(rre_buf, 0, len);
if ( ! valid)
return;
int sx, sy, sw, sh;
int i = 0;
for (int j = 0; j < nSubrects; j++) {
if (bytesPerPixel == 1) {
pixel = colorPalette[0xFF & rre_buf[i++]];
} else {
pixel = Color.rgb(rre_buf[i + 2] & 0xFF, rre_buf[i + 1] & 0xFF, rre_buf[i] & 0xFF);
i += 4;
}
sx = x + (rre_buf[i++] & 0xFF);
sy = y + (rre_buf[i++] & 0xFF);
sw = rre_buf[i++] & 0xFF;
sh = rre_buf[i++] & 0xFF;
handleRREPaint.setColor(pixel);
bitmapData.drawRect(sx, sy, sw, sh, handleRREPaint);
}
vncCanvas.reDraw(x, y, w, h);
}
//
// Handle a Hextile-encoded rectangle.
//
void handleHextileRect(RfbProto rfb, int x, int y, int w, int h) throws IOException {
hextile_bg = Color.BLACK;
hextile_fg = Color.BLACK;
for (int ty = y; ty < y + h; ty += 16) {
int th = 16;
if (y + h - ty < 16)
th = y + h - ty;
for (int tx = x; tx < x + w; tx += 16) {
int tw = 16;
if (x + w - tx < 16)
tw = x + w - tx;
handleHextileSubrect(rfb, tx, ty, tw, th);
}
// Finished with a row of tiles, now let's show it.
vncCanvas.reDraw(x, y, w, h);
}
}
//
// Handle one tile in the Hextile-encoded data.
//
private void handleHextileSubrect(RfbProto rfb, int tx, int ty, int tw, int th) throws IOException {
int subencoding = rfb.is.readUnsignedByte();
// Is it a raw-encoded sub-rectangle?
if ((subencoding & RfbProto.HextileRaw) != 0) {
handleRawRect(rfb, tx, ty, tw, th, false);
return;
}
boolean valid=bitmapData.validDraw(tx, ty, tw, th);
// Read and draw the background if specified.
if (bytesPerPixel > backgroundColorBuffer.length) {
throw new RuntimeException("impossible colordepth");
}
if ((subencoding & RfbProto.HextileBackgroundSpecified) != 0) {
rfb.readFully(backgroundColorBuffer, 0, bytesPerPixel);
if (bytesPerPixel == 1) {
hextile_bg = colorPalette[0xFF & backgroundColorBuffer[0]];
} else {
hextile_bg = Color.rgb(backgroundColorBuffer[2] & 0xFF, backgroundColorBuffer[1] & 0xFF, backgroundColorBuffer[0] & 0xFF);
}
}
handleHextileSubrectPaint.setColor(hextile_bg);
handleHextileSubrectPaint.setStyle(Paint.Style.FILL);
if ( valid )
bitmapData.drawRect(tx, ty, tw, th, handleHextileSubrectPaint);
// Read the foreground color if specified.
if ((subencoding & RfbProto.HextileForegroundSpecified) != 0) {
rfb.readFully(backgroundColorBuffer, 0, bytesPerPixel);
if (bytesPerPixel == 1) {
hextile_fg = colorPalette[0xFF & backgroundColorBuffer[0]];
} else {
hextile_fg = Color.rgb(backgroundColorBuffer[2] & 0xFF, backgroundColorBuffer[1] & 0xFF, backgroundColorBuffer[0] & 0xFF);
}
}
// Done with this tile if there is no sub-rectangles.
if ((subencoding & RfbProto.HextileAnySubrects) == 0)
return;
int nSubrects = rfb.is.readUnsignedByte();
int bufsize = nSubrects * 2;
if ((subencoding & RfbProto.HextileSubrectsColoured) != 0) {
bufsize += nSubrects * bytesPerPixel;
}
if (rre_buf.length < bufsize)
rre_buf = new byte[bufsize];
rfb.readFully(rre_buf, 0, bufsize);
int b1, b2, sx, sy, sw, sh;
int i = 0;
if ((subencoding & RfbProto.HextileSubrectsColoured) == 0) {
// Sub-rectangles are all of the same color.
handleHextileSubrectPaint.setColor(hextile_fg);
for (int j = 0; j < nSubrects; j++) {
b1 = rre_buf[i++] & 0xFF;
b2 = rre_buf[i++] & 0xFF;
sx = tx + (b1 >> 4);
sy = ty + (b1 & 0xf);
sw = (b2 >> 4) + 1;
sh = (b2 & 0xf) + 1;
if ( valid)
bitmapData.drawRect(sx, sy, sw, sh, handleHextileSubrectPaint);
}
} else if (bytesPerPixel == 1) {
// BGR233 (8-bit color) version for colored sub-rectangles.
for (int j = 0; j < nSubrects; j++) {
hextile_fg = colorPalette[0xFF & rre_buf[i++]];
b1 = rre_buf[i++] & 0xFF;
b2 = rre_buf[i++] & 0xFF;
sx = tx + (b1 >> 4);
sy = ty + (b1 & 0xf);
sw = (b2 >> 4) + 1;
sh = (b2 & 0xf) + 1;
handleHextileSubrectPaint.setColor(hextile_fg);
if ( valid)
bitmapData.drawRect(sx, sy, sw, sh, handleHextileSubrectPaint);
}
} else {
// Full-color (24-bit) version for colored sub-rectangles.
for (int j = 0; j < nSubrects; j++) {
hextile_fg = Color.rgb(rre_buf[i + 2] & 0xFF, rre_buf[i + 1] & 0xFF, rre_buf[i] & 0xFF);
i += 4;
b1 = rre_buf[i++] & 0xFF;
b2 = rre_buf[i++] & 0xFF;
sx = tx + (b1 >> 4);
sy = ty + (b1 & 0xf);
sw = (b2 >> 4) + 1;
sh = (b2 & 0xf) + 1;
handleHextileSubrectPaint.setColor(hextile_fg);
if ( valid )
bitmapData.drawRect(sx, sy, sw, sh, handleHextileSubrectPaint);
}
}
}
//
// Handle a ZRLE-encoded rectangle.
//
void handleZRLERect(RfbProto rfb, int x, int y, int w, int h) throws Exception {
if (zrleInStream == null)
zrleInStream = new ZlibInStream();
int nBytes = rfb.is.readInt();
if (nBytes > 64 * 1024 * 1024)
throw new Exception("ZRLE decoder: illegal compressed data size");
if (zrleBuf == null || zrleBuf.length < nBytes) {
zrleBuf = new byte[nBytes+4096];
}
rfb.readFully(zrleBuf, 0, nBytes);
zrleInStream.setUnderlying(new MemInStream(zrleBuf, 0, nBytes), nBytes);
boolean valid=bitmapData.validDraw(x, y, w, h);
for (int ty = y; ty < y + h; ty += 64) {
int th = Math.min(y + h - ty, 64);
for (int tx = x; tx < x + w; tx += 64) {
int tw = Math.min(x + w - tx, 64);
int mode = zrleInStream.readU8();
boolean rle = (mode & 128) != 0;
int palSize = mode & 127;
readZrlePalette(handleZRLERectPalette, palSize);
if (palSize == 1) {
int pix = handleZRLERectPalette[0];
int c = (bytesPerPixel == 1) ? colorPalette[0xFF & pix] : (0xFF000000 | pix);
handleZRLERectPaint.setColor(c);
handleZRLERectPaint.setStyle(Paint.Style.FILL);
if ( valid)
bitmapData.drawRect(tx, ty, tw, th, handleZRLERectPaint);
continue;
}
if (!rle) {
if (palSize == 0) {
readZrleRawPixels(tw, th);
} else {
readZrlePackedPixels(tw, th, handleZRLERectPalette, palSize);
}
} else {
if (palSize == 0) {
readZrlePlainRLEPixels(tw, th);
} else {
readZrlePackedRLEPixels(tw, th, handleZRLERectPalette);
}
}
if ( valid )
handleUpdatedZrleTile(tx, ty, tw, th);
}
}
zrleInStream.reset();
vncCanvas.reDraw(x, y, w, h);
}
//
// Handle a Zlib-encoded rectangle.
//
void handleZlibRect(RfbProto rfb, int x, int y, int w, int h) throws Exception {
boolean valid = bitmapData.validDraw(x, y, w, h);
int nBytes = rfb.is.readInt();
if (zlibBuf == null || zlibBuf.length < nBytes) {
zlibBuf = new byte[nBytes*2];
}
rfb.readFully(zlibBuf, 0, nBytes);
if (zlibInflater == null) {
zlibInflater = new Inflater();
}
zlibInflater.setInput(zlibBuf, 0, nBytes);
int[] pixels=bitmapData.bitmapPixels;
if (bytesPerPixel == 1) {
// 1 byte per pixel. Use palette lookup table.
if (w > handleZlibRectBuffer.length) {
handleZlibRectBuffer = new byte[w];
}
int i, offset;
for (int dy = y; dy < y + h; dy++) {
zlibInflater.inflate(handleZlibRectBuffer, 0, w);
if ( ! valid)
continue;
offset = bitmapData.offset(x, dy);
for (i = 0; i < w; i++) {
pixels[offset + i] = colorPalette[0xFF & handleZlibRectBuffer[i]];
}
}
} else {
// 24-bit color (ARGB) 4 bytes per pixel.
final int l = w*4;
if (l > handleZlibRectBuffer.length) {
handleZlibRectBuffer = new byte[l];
}
int i, offset;
for (int dy = y; dy < y + h; dy++) {
zlibInflater.inflate(handleZlibRectBuffer, 0, l);
if ( ! valid)
continue;
offset = bitmapData.offset(x, dy);
for (i = 0; i < w; i++) {
final int idx = i*4;
pixels[offset + i] = (handleZlibRectBuffer[idx + 2] & 0xFF) << 16 | (handleZlibRectBuffer[idx + 1] & 0xFF) << 8 | (handleZlibRectBuffer[idx] & 0xFF);
}
}
}
if ( ! valid)
return;
bitmapData.updateBitmap(x, y, w, h);
vncCanvas.reDraw(x, y, w, h);
}
private int readPixel(InStream is) throws Exception {
int pix;
if (bytesPerPixel == 1) {
pix = is.readU8();
} else {
int p1 = is.readU8();
int p2 = is.readU8();
int p3 = is.readU8();
pix = (p3 & 0xFF) << 16 | (p2 & 0xFF) << 8 | (p1 & 0xFF);
}
return pix;
}
private void readPixels(InStream is, int[] dst, int count) throws Exception {
if (bytesPerPixel == 1) {
if (count > readPixelsBuffer.length) {
readPixelsBuffer = new byte[count];
}
is.readBytes(readPixelsBuffer, 0, count);
for (int i = 0; i < count; i++) {
dst[i] = (int) readPixelsBuffer[i] & 0xFF;
}
} else {
final int l = count * 3;
if (l > readPixelsBuffer.length) {
readPixelsBuffer = new byte[l];
}
is.readBytes(readPixelsBuffer, 0, l);
for (int i = 0; i < count; i++) {
final int idx = i*3;
dst[i] = ((readPixelsBuffer[idx + 2] & 0xFF) << 16 | (readPixelsBuffer[idx + 1] & 0xFF) << 8 | (readPixelsBuffer[idx] & 0xFF));
}
}
}
private void readZrlePalette(int[] palette, int palSize) throws Exception {
readPixels(zrleInStream, palette, palSize);
}
private void readZrleRawPixels(int tw, int th) throws Exception {
int len = tw * th;
if (zrleTilePixels == null || len > zrleTilePixels.length)
zrleTilePixels = new int[len];
readPixels(zrleInStream, zrleTilePixels, tw * th); // /
}
private void readZrlePackedPixels(int tw, int th, int[] palette, int palSize) throws Exception {
int bppp = ((palSize > 16) ? 8 : ((palSize > 4) ? 4 : ((palSize > 2) ? 2 : 1)));
int ptr = 0;
int len = tw * th;
if (zrleTilePixels == null || len > zrleTilePixels.length)
zrleTilePixels = new int[len];
for (int i = 0; i < th; i++) {
int eol = ptr + tw;
int b = 0;
int nbits = 0;
while (ptr < eol) {
if (nbits == 0) {
b = zrleInStream.readU8();
nbits = 8;
}
nbits -= bppp;
int index = (b >> nbits) & ((1 << bppp) - 1) & 127;
if (bytesPerPixel == 1) {
if (index >= colorPalette.length)
Log.e(TAG, "zrlePlainRLEPixels palette lookup out of bounds " + index + " (0x" + Integer.toHexString(index) + ")");
zrleTilePixels[ptr++] = colorPalette[0xFF & palette[index]];
} else {
zrleTilePixels[ptr++] = palette[index];
}
}
}
}
private void readZrlePlainRLEPixels(int tw, int th) throws Exception {
int ptr = 0;
int end = ptr + tw * th;
if (zrleTilePixels == null || end > zrleTilePixels.length)
zrleTilePixels = new int[end];
while (ptr < end) {
int pix = readPixel(zrleInStream);
int len = 1;
int b;
do {
b = zrleInStream.readU8();
len += b;
} while (b == 255);
if (!(len <= end - ptr))
throw new Exception("ZRLE decoder: assertion failed" + " (len <= end-ptr)");
if (bytesPerPixel == 1) {
while (len-- > 0)
zrleTilePixels[ptr++] = colorPalette[0xFF & pix];
} else {
while (len-- > 0)
zrleTilePixels[ptr++] = pix;
}
}
}
private void readZrlePackedRLEPixels(int tw, int th, int[] palette) throws Exception {
int ptr = 0;
int end = ptr + tw * th;
if (zrleTilePixels == null || end > zrleTilePixels.length)
zrleTilePixels = new int[end];
while (ptr < end) {
int index = zrleInStream.readU8();
int len = 1;
if ((index & 128) != 0) {
int b;
do {
b = zrleInStream.readU8();
len += b;
} while (b == 255);
if (!(len <= end - ptr))
throw new Exception("ZRLE decoder: assertion failed" + " (len <= end - ptr)");
}
index &= 127;
int pix = palette[index];
if (bytesPerPixel == 1) {
while (len-- > 0)
zrleTilePixels[ptr++] = colorPalette[0xFF & pix];
} else {
while (len-- > 0)
zrleTilePixels[ptr++] = pix;
}
}
}
//
// Copy pixels from zrleTilePixels8 or zrleTilePixels24, then update.
//
private void handleUpdatedZrleTile(int x, int y, int w, int h) {
int offsetSrc = 0;
int[] destPixels=bitmapData.bitmapPixels;
for (int j = 0; j < h; j++) {
System.arraycopy(zrleTilePixels, offsetSrc, destPixels, bitmapData.offset(x, y + j), w);
offsetSrc += w;
}
bitmapData.updateBitmap(x, y, w, h);
}
//
// Handle a Tight-encoded rectangle.
//
void handleTightRect(RfbProto rfb, int x, int y, int w, int h) throws Exception {
int[] pixels = bitmapData.bitmapPixels;
valid = bitmapData.validDraw(x, y, w, h);
comp_ctl = rfb.is.readUnsignedByte();
rowSize = w;
boffset = 0;
numColors = 0;
useGradient = false;
// Flush zlib streams if we are told by the server to do so.
for (stream_id = 0; stream_id < 4; stream_id++) {
if ((comp_ctl & 1) != 0) {
tightInflaters[stream_id] = null;
}
comp_ctl >>= 1;
}
// Check correctness of sub-encoding value.
if (comp_ctl > RfbProto.TightMaxSubencoding) {
throw new Exception("Incorrect tight subencoding: " + comp_ctl);
}
// Handle solid-color rectangles.
if (comp_ctl == RfbProto.TightFill) {
if (bytesPerPixel == 1) {
idx = rfb.is.readUnsignedByte();
handleTightRectPaint.setColor(colorPalette[0xFF & idx]);
} else {
rfb.readFully(solidColorBuf, 0, 3);
handleTightRectPaint.setColor(0xFF000000 | (solidColorBuf[0] & 0xFF) << 16
| (solidColorBuf[1] & 0xFF) << 8 | (solidColorBuf[2] & 0xFF));
}
if (valid) {
bitmapData.drawRect(x, y, w, h, handleTightRectPaint);
vncCanvas.reDraw(x, y, w, h);
}
return;
}
if (comp_ctl == RfbProto.TightJpeg) {
// Read JPEG data.
jpegDataLen = rfb.readCompactLen();
if (jpegDataLen > inflBuf.length) {
inflBuf = new byte[2*jpegDataLen];
}
rfb.readFully(inflBuf, 0, jpegDataLen);
if (!valid)
return;
// Decode JPEG data
Bitmap tightBitmap = BitmapFactory.decodeByteArray(inflBuf, 0, jpegDataLen, bitmapopts);
// Copy decoded data into bitmapData and recycle bitmap.
//tightBitmap.getPixels(pixels, bitmapData.offset(x, y), bitmapData.bitmapwidth, 0, 0, w, h);
bitmapData.updateBitmap(tightBitmap, x, y, w, h);
vncCanvas.reDraw(x, y, w, h);
// To avoid running out of memory, recycle bitmap immediately.
tightBitmap.recycle();
return;
}
// Read filter id and parameters.
if ((comp_ctl & RfbProto.TightExplicitFilter) != 0) {
int filter_id = rfb.is.readUnsignedByte();
if (filter_id == RfbProto.TightFilterPalette) {
numColors = rfb.is.readUnsignedByte() + 1;
if (bytesPerPixel == 1) {
if (numColors != 2) {
throw new Exception("Incorrect tight palette size: " + numColors);
}
rfb.readFully(tightPalette8, 0, 2);
} else {
rfb.readFully(colorBuf, 0, numColors*3);
for (c = 0; c < numColors; c++) {
idx = c*3;
tightPalette24[c] = ((colorBuf[idx] & 0xFF) << 16 |
(colorBuf[idx + 1] & 0xFF) << 8 |
(colorBuf[idx + 2] & 0xFF));
}
}
if (numColors == 2)
rowSize = (w + 7) / 8;
} else if (filter_id == RfbProto.TightFilterGradient) {
useGradient = true;
} else if (filter_id != RfbProto.TightFilterCopy) {
throw new Exception("Incorrect tight filter id: " + filter_id);
}
}
if (numColors == 0 && bytesPerPixel == 4)
rowSize *= 3;
// Read, optionally uncompress and decode data.
dataSize = h * rowSize;
if (dataSize < RfbProto.TightMinToCompress) {
// Data size is small - not compressed with zlib.
rfb.readFully(uncompDataBuf, 0, dataSize);
if (!valid)
return;
if (numColors != 0) {
// Indexed colors.
if (numColors == 2) {
// Two colors.
if (bytesPerPixel == 1) {
decodeMonoData(x, y, w, h, uncompDataBuf, tightPalette8);
} else {
decodeMonoData(x, y, w, h, uncompDataBuf, tightPalette24);
}
} else {
// 3..255 colors (assuming bytesPerPixel == 4).
boffset = 0;
for (dy = y; dy < y + h; dy++) {
offset = bitmapData.offset(x, dy);
for (dx = x; dx < x + w; dx++) {
pixels[offset++] = tightPalette24[uncompDataBuf[boffset++] & 0xFF];
}
}
}
} else if (useGradient) {
// "Gradient"-processed data
decodeGradientData(x, y, w, h, uncompDataBuf);
} else {
boffset = 0;
// Raw true-color data.
if (bytesPerPixel == 1) {
for (dy = y; dy < y + h; dy++) {
offset = bitmapData.offset(x, dy);
for (dx = 0; dx < w; dx++) {
pixels[offset++] = colorPalette[0xFF & uncompDataBuf[boffset++]];
}
}
} else {
for (dy = y; dy < y + h; dy++) {
offset = bitmapData.offset(x, dy);
for (dx = 0; dx < w; dx++) {
idx = boffset*3; boffset++;
pixels[offset++] = (uncompDataBuf[idx] & 0xFF) << 16 |
(uncompDataBuf[idx + 1] & 0xFF) << 8 |
(uncompDataBuf[idx + 2] & 0xFF);
}
}
}
}
} else {
// Data was compressed with zlib.
int zlibDataLen = rfb.readCompactLen();
if (zlibDataLen > zlibData.length) {
zlibData = new byte[zlibDataLen*2];
}
rfb.readFully(zlibData, 0, zlibDataLen);
stream_id = comp_ctl & 0x03;
if (tightInflaters[stream_id] == null) {
tightInflaters[stream_id] = new Inflater();
}
Inflater myInflater = tightInflaters[stream_id];
myInflater.setInput(zlibData, 0, zlibDataLen);
if (dataSize > inflBuf.length) {
inflBuf = new byte[dataSize*2];
}
try {
myInflater.inflate(inflBuf, 0, dataSize);
} catch (DataFormatException e) {
e.printStackTrace();
}
if (!valid)
return;
if (numColors != 0) {
// Indexed colors.
if (numColors == 2) {
// Two colors.
if (bytesPerPixel == 1) {
decodeMonoData(x, y, w, h, inflBuf, tightPalette8);
} else {
decodeMonoData(x, y, w, h, inflBuf, tightPalette24);
}
} else {
// More than two colors (assuming bytesPerPixel == 4).
boffset = 0;
for (dy = y; dy < y + h; dy++) {
offset = bitmapData.offset(x, dy);
for (dx = x; dx < x + w; dx++) {
pixels[offset++] = tightPalette24[inflBuf[boffset++] & 0xFF];
}
}
}
} else if (useGradient) {
// Compressed "Gradient"-filtered data (assuming bytesPerPixel == 4).
decodeGradientData(x, y, w, h, inflBuf);
} else {
boffset = 0;
// Compressed true-color data.
if (bytesPerPixel == 1) {
for (dy = y; dy < y + h; dy++) {
offset = bitmapData.offset(x, dy);
for (dx = 0; dx < w; dx++) {
pixels[offset++] = colorPalette[0xFF & inflBuf[boffset++]];
}
}
} else {
for (dy = y; dy < y + h; dy++) {
offset = bitmapData.offset(x, dy);
for (dx = 0; dx < w; dx++) {
idx = boffset*3; boffset++;
pixels[offset++] = (inflBuf[idx] & 0xFF) << 16 |
(inflBuf[idx + 1] & 0xFF) << 8 |
(inflBuf[idx + 2] & 0xFF);
}
}
}
}
}
bitmapData.updateBitmap(x, y, w, h);
vncCanvas.reDraw(x, y, w, h);
}
//
// Decode 1bpp-encoded bi-color rectangle (8-bit and 24-bit versions).
//
void decodeMonoData(int x, int y, int w, int h, byte[] src, byte[] palette) {
int dx, dy, n;
int i = bitmapData.offset(x, y);
int[] pixels = bitmapData.bitmapPixels;
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--) {
pixels[i++] = colorPalette[0xFF & palette[b >> n & 1]];
}
}
for (n = 7; n >= 8 - w % 8; n--) {
pixels[i++] = colorPalette[0xFF & palette[src[dy*rowBytes+dx] >> n & 1]];
}
i += (bitmapData.bitmapwidth - w);
}
}
void decodeMonoData(int x, int y, int w, int h, byte[] src, int[] palette) {
int dx, dy, n;
int i = bitmapData.offset(x, y);
int[] pixels = bitmapData.bitmapPixels;
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--) {
pixels[i++] = palette[b >> n & 1];
}
}
for (n = 7; n >= 8 - w % 8; n--) {
pixels[i++] = palette[src[dy*rowBytes+dx] >> n & 1];
}
i += (bitmapData.bitmapwidth - w);
}
}
//
// Decode data processed with the "Gradient" filter.
//
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[] pixels = bitmapData.bitmapPixels;
int offset = bitmapData.offset(x, y);
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];
}
pixels[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];
}
pixels[offset++] = (pix[0] & 0xFF) << 16 | (pix[1] & 0xFF) << 8 | (pix[2] & 0xFF);
}
System.arraycopy(thisRow, 0, prevRow, 0, w * 3);
offset += (bitmapData.bitmapwidth - w);
}
}
/**
* Handles cursor shape update (XCursor and RichCursor encodings).
*/
synchronized void
handleCursorShapeUpdate(RfbProto rfb, int encodingType, int hotX, int hotY, int w, int h) throws IOException {
RemotePointer p = vncCanvas.getPointer();
int x = p.getX();
int y = p.getY();
if (w * h == 0)
return;
// Ignore cursor shape data if requested by user.
/*if (ignoreCursorUpdates) {
int bytesPerRow = (w + 7) / 8;
int bytesMaskData = bytesPerRow * h;
if (encodingType == RfbProto.EncodingXCursor) {
rfb.is.skipBytes(6 + bytesMaskData * 2);
} else {
// RfbProto.EncodingRichCursor
rfb.is.skipBytes(w * h * bytesPerPixel + bytesMaskData);
}
return;
}*/
// Set cursor rectangle.
bitmapData.setCursorRect(x, y, w, h, hotX, hotY);
// Decode cursor pixel data, and set pixel data into bitmap drawable.
bitmapData.setSoftCursor (decodeCursorShape(rfb, encodingType, w, h));
// Show the cursor.
RectF r = bitmapData.getCursorRect();
vncCanvas.reDraw(r.left, r.top, r.width(), r.height());
}
/**
* Decode cursor pixel data and return it in an int array.
* @param encodingType
* @param width
* @param height
* @return
* @throws IOException
*/
synchronized int[] decodeCursorShape(RfbProto rfb, int encodingType, int width, int height) throws IOException {
int bytesPerRow = (width + 7) / 8;
int bytesMaskData = bytesPerRow * height;
int[] softCursorPixels = new int[width * height];
if (encodingType == RfbProto.EncodingXCursor) {
// Read foreground and background colors of the cursor.
byte[] rgb = new byte[6];
rfb.readFully(rgb);
int[] colors = { (0xFF000000 | (rgb[3] & 0xFF) << 16 |
(rgb[4] & 0xFF) << 8 | (rgb[5] & 0xFF)),
(0xFF000000 | (rgb[0] & 0xFF) << 16 |
(rgb[1] & 0xFF) << 8 | (rgb[2] & 0xFF)) };
// Read pixel and mask data.
byte[] pixBuf = new byte[bytesMaskData];
rfb.readFully(pixBuf);
byte[] maskBuf = new byte[bytesMaskData];
rfb.readFully(maskBuf);
// Decode pixel data into softCursorPixels[].
byte pixByte, maskByte;
int x, y, n, result;
int i = 0;
for (y = 0; y < height; y++) {
for (x = 0; x < width / 8; x++) {
pixByte = pixBuf[y * bytesPerRow + x];
maskByte = maskBuf[y * bytesPerRow + x];
for (n = 7; n >= 0; n--) {
if ((maskByte >> n & 1) != 0) {
result = colors[pixByte >> n & 1];
} else {
result = 0; // Transparent pixel
}
softCursorPixels[i++] = result;
}
}
for (n = 7; n >= 8 - width % 8; n--) {
if ((maskBuf[y * bytesPerRow + x] >> n & 1) != 0) {
result = colors[pixBuf[y * bytesPerRow + x] >> n & 1];
} else {
result = 0; // Transparent pixel
}
softCursorPixels[i++] = result;
}
}
} else {
// encodingType == rfb.EncodingRichCursor
// Read pixel and mask data.
byte[] pixBuf = new byte[width * height * bytesPerPixel];
rfb.readFully(pixBuf);
byte[] maskBuf = new byte[bytesMaskData];
rfb.readFully(maskBuf);
// Decode pixel data into softCursorPixels[].
byte pixByte, maskByte;
int x, y, n, result;
int i = 0;
for (y = 0; y < height; y++) {
for (x = 0; x < width / 8; x++) {
maskByte = maskBuf[y * bytesPerRow + x];
for (n = 7; n >= 0; n--) {
if ((maskByte >> n & 1) != 0) {
if (bytesPerPixel == 1) {
result = colorPalette[0xFF & pixBuf[i]];
} else {
result = 0xFF000000 |
(pixBuf[i * 4 + 2] & 0xFF) << 16 |
(pixBuf[i * 4 + 1] & 0xFF) << 8 |
(pixBuf[i * 4] & 0xFF);
}
} else {
result = 0; // Transparent pixel
}
softCursorPixels[i++] = result;
}
}
for (n = 7; n >= 8 - width % 8; n--) {
if ((maskBuf[y * bytesPerRow + x] >> n & 1) != 0) {
if (bytesPerPixel == 1) {
result = colorPalette[0xFF & pixBuf[i]];
} else {
result = 0xFF000000 |
(pixBuf[i * 4 + 2] & 0xFF) << 16 |
(pixBuf[i * 4 + 1] & 0xFF) << 8 |
(pixBuf[i * 4] & 0xFF);
}
} else {
result = 0; // Transparent pixel
}
softCursorPixels[i++] = result;
}
}
}
return softCursorPixels;
}
}