view src/myVncProxy/VncCanvas.java @ 107:a8b2712de4c5

ZRLEE extension
author Shinji KONO <kono@ie.u-ryukyu.ac.jp>
date Fri, 05 Aug 2011 08:57:00 +0900
parents 712a047908df
children 4199efcc4260
line wrap: on
line source

package myVncProxy;
//
//  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.
//

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.io.*;
import java.util.zip.*;


import javax.imageio.ImageIO;

//
// VncCanvas is a subclass of Canvas which draws a VNC desktop on it.
//

class VncCanvas extends Canvas implements KeyListener, MouseListener,
		MouseMotionListener {

	VncViewer viewer;
	MyRfbProto rfb;
	ColorModel cm8, cm24;
	Color[] colors;
	int bytesPixel;

	int maxWidth = 0, maxHeight = 0;
	int scalingFactor;
	int scaledWidth, scaledHeight;

	Image memImage;
	Graphics memGraphics;
	
	Image rawPixelsImage;
//	BufferedImage rawPixelsImaage;
	BufferedImage bimg;

	MemoryImageSource pixelsSource;
	byte[] pixels8;
	int[] pixels24;

	// Update statistics.
	long statStartTime; // time on first framebufferUpdateRequest
	int statNumUpdates; // counter for FramebufferUpdate messages
	int statNumTotalRects; // rectangles in FramebufferUpdate messages
	int statNumPixelRects; // the same, but excluding pseudo-rectangles
	int statNumRectsTight; // Tight-encoded rectangles (including JPEG)
	int statNumRectsTightJPEG; // JPEG-compressed Tight-encoded rectangles
	int statNumRectsZRLE; // ZRLE-encoded rectangles
	int statNumRectsHextile; // Hextile-encoded rectangles
	int statNumRectsRaw; // Raw-encoded rectangles
	int statNumRectsCopy; // CopyRect rectangles
	int statNumBytesEncoded; // number of bytes in updates, as received
	int statNumBytesDecoded; // number of bytes, as if Raw encoding was used

	// ZRLE encoder's data.
	byte[] zrleBuf;
	int zrleBufLen = 0;
	byte[] zrleTilePixels8;
	int[] zrleTilePixels24;
	ZlibInStream zrleInStream;
	boolean zrleRecWarningShown = false;

	// Zlib encoder's data.
	byte[] zlibBuf;
	int zlibBufLen = 0;
	Inflater zlibInflater;

	// Tight encoder's data.
	final static int tightZlibBufferSize = 512;
	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.
	Rectangle jpegRect;

	// True if we process keyboard and mouse events.
	boolean inputEnabled;
	
	//
	// The constructors.
	//

	public VncCanvas(VncViewer v, int maxWidth_, int maxHeight_)
			throws IOException {

		viewer = v;
		maxWidth = maxWidth_;
		maxHeight = maxHeight_;

		rfb = viewer.rfb;
		scalingFactor = viewer.options.scalingFactor;
		
		tightInflaters = new Inflater[4];

		cm8 = new DirectColorModel(8, 7, (7 << 3), (3 << 6));
		cm24 = new DirectColorModel(24, 0xFF0000, 0x00FF00, 0x0000FF);

		colors = new Color[256];
		for (int i = 0; i < 256; i++)
			colors[i] = new Color(cm8.getRGB(i));

		setPixelFormat();

		inputEnabled = false;
		if (!viewer.options.viewOnly)
			enableInput(true);

		// Keyboard listener is enabled even in view-only mode, to catch
		// 'r' or 'R' key presses used to request screen update.
		addKeyListener(this);
	}

	public VncCanvas(VncViewer v) throws IOException {
		this(v, 0, 0);
	}

	//
	// Callback methods to determine geometry of our Component.
	//

	public Dimension getPreferredSize() {
		return new Dimension(scaledWidth, scaledHeight);
	}

	public Dimension getMinimumSize() {
		return new Dimension(scaledWidth, scaledHeight);
	}

	public Dimension getMaximumSize() {
		return new Dimension(scaledWidth, scaledHeight);
	}

	//
	// All painting is performed here.
	//

	public void update(Graphics g) {
		paint(g);
	}

	public void paint(Graphics g) {
		synchronized (memImage) {
			if (rfb.framebufferWidth == scaledWidth) {
				g.drawImage(memImage, 0, 0, null);
			} else {
				paintScaledFrameBuffer(g);
			}
		}
		if (showSoftCursor) {
			int x0 = cursorX - hotX, y0 = cursorY - hotY;
			Rectangle r = new Rectangle(x0, y0, cursorWidth, cursorHeight);
			if (r.intersects(g.getClipBounds())) {
				g.drawImage(softCursor, x0, y0, null);
			}
		}
	}

	public void paintScaledFrameBuffer(Graphics g) {
		g.drawImage(memImage, 0, 0, scaledWidth, scaledHeight, null);
	}

	//
	// 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) {
						memGraphics
								.drawImage(img, jpegRect.x, jpegRect.y, null);
						scheduleRepaint(jpegRect.x, jpegRect.y, jpegRect.width,
								jpegRect.height);
						jpegRect.notify();
					}
				}
			}
			return false; // All image data was processed.
		}
	}

	//
	// Start/stop receiving mouse events. Keyboard events are received
	// even in view-only mode, because we want to map the 'r' key to the
	// screen refreshing function.
	//

	public synchronized void enableInput(boolean enable) {
		if (enable && !inputEnabled) {
			inputEnabled = true;
			addMouseListener(this);
			addMouseMotionListener(this);
			if (viewer.showControls) {
				viewer.buttonPanel.enableRemoteAccessControls(true);
			}
			createSoftCursor(); // scaled cursor
		} else if (!enable && inputEnabled) {
			inputEnabled = false;
			removeMouseListener(this);
			removeMouseMotionListener(this);
			if (viewer.showControls) {
				viewer.buttonPanel.enableRemoteAccessControls(false);
			}
			createSoftCursor(); // non-scaled cursor
		}
	}

	public void setPixelFormat() throws IOException {
		if (viewer.options.eightBitColors) {
			rfb.writeSetPixelFormat(8, 8, false, true, 7, 7, 3, 0, 3, 6);
			bytesPixel = 1;
		} else {
			rfb.writeSetPixelFormat(32, 24, false, true, 255, 255, 255, 16, 8,
					0);
			bytesPixel = 4;
		}
		updateFramebufferSize();
	}

	void updateFramebufferSize() {

		// Useful shortcuts.
		int fbWidth = rfb.framebufferWidth;
		int fbHeight = rfb.framebufferHeight;

		// Calculate scaling factor for auto scaling.
		if (maxWidth > 0 && maxHeight > 0) {
			int f1 = maxWidth * 100 / fbWidth;
			int f2 = maxHeight * 100 / fbHeight;
			scalingFactor = Math.min(f1, f2);
			if (scalingFactor > 100)
				scalingFactor = 100;
			System.out.println("Scaling desktop at " + scalingFactor + "%");
		}

		// Update scaled framebuffer geometry.
		scaledWidth = (fbWidth * scalingFactor + 50) / 100;
		scaledHeight = (fbHeight * scalingFactor + 50) / 100;

		// Create new off-screen image either if it does not exist, or if
		// its geometry should be changed. It's not necessary to replace
		// existing image if only pixel format should be changed.
		if (memImage == null) {
			memImage = viewer.vncContainer.createImage(fbWidth, fbHeight);
			memGraphics = memImage.getGraphics();
		} else if (memImage.getWidth(null) != fbWidth
				|| memImage.getHeight(null) != fbHeight) {
			synchronized (memImage) {
				memImage = viewer.vncContainer.createImage(fbWidth, fbHeight);
				memGraphics = memImage.getGraphics();
			}
		}

		// Images with raw pixels should be re-allocated on every change
		// of geometry or pixel format.
		if (bytesPixel == 1) {

			pixels24 = null;
			pixels8 = new byte[fbWidth * fbHeight];

			pixelsSource = new MemoryImageSource(fbWidth, fbHeight, cm8,
					pixels8, 0, fbWidth);

			zrleTilePixels24 = null;
			zrleTilePixels8 = new byte[64 * 64];

		} else {

			pixels8 = null;
			pixels24 = new int[fbWidth * fbHeight];

			pixelsSource = new MemoryImageSource(fbWidth, fbHeight, cm24,
					pixels24, 0, fbWidth);

			zrleTilePixels8 = null;
			zrleTilePixels24 = new int[64 * 64];

		}
		pixelsSource.setAnimated(true);
		rawPixelsImage = Toolkit.getDefaultToolkit().createImage(pixelsSource);
//		rawPixelsImage = (BufferedImage) Toolkit.getDefaultToolkit().createImage(pixelsSource);
		
		// Update the size of desktop containers.
		if (viewer.inSeparateFrame) {
			if (viewer.desktopScrollPane != null)
				resizeDesktopFrame();
		} else {
			setSize(scaledWidth, scaledHeight);
		}
		viewer.moveFocusToDesktop();
	}

	void resizeDesktopFrame() {
		setSize(scaledWidth, scaledHeight);

		// FIXME: Find a better way to determine correct size of a
		// ScrollPane. -- const
		Insets insets = viewer.desktopScrollPane.getInsets();
		viewer.desktopScrollPane.setSize(
				scaledWidth + 2 * Math.min(insets.left, insets.right),
				scaledHeight + 2 * Math.min(insets.top, insets.bottom));

		viewer.vncFrame.pack();

		// Try to limit the frame size to the screen size.

		Dimension screenSize = viewer.vncFrame.getToolkit().getScreenSize();
		Dimension frameSize = viewer.vncFrame.getSize();
		Dimension newSize = frameSize;

		// Reduce Screen Size by 30 pixels in each direction;
		// This is a (poor) attempt to account for
		// 1) Menu bar on Macintosh (should really also account for
		// Dock on OSX). Usually 22px on top of screen.
		// 2) Taxkbar on Windows (usually about 28 px on bottom)
		// 3) Other obstructions.

		screenSize.height -= 30;
		screenSize.width -= 30;

		boolean needToResizeFrame = false;
		if (frameSize.height > screenSize.height) {
			newSize.height = screenSize.height;
			needToResizeFrame = true;
		}
		if (frameSize.width > screenSize.width) {
			newSize.width = screenSize.width;
			needToResizeFrame = true;
		}
		if (needToResizeFrame) {
			viewer.vncFrame.setSize(newSize);
		}

		viewer.desktopScrollPane.doLayout();
	}

	//
	// processNormalProtocol() - executed by the rfbThread to deal with the
	// RFB socket.
	//

	public void processNormalProtocol() throws Exception {

		// Start/stop session recording if necessary.
		viewer.checkRecordingStatus();

		rfb.writeFramebufferUpdateRequest(0, 0, rfb.framebufferWidth,
				rfb.framebufferHeight, false);

		resetStats();
		boolean statsRestarted = false;

		//
		// main dispatch loop
		//

		long count = 0;


		while (true) {

			
//			System.out.println("\ncount=" + count);
			count++;
			
			
			/*
			 *  read Data from parents and send Data to Client.
			 *  
			 */
			rfb.sendDataToClient();		
			
//			rfb.printNumBytesRead();
			long bufSize = rfb.getNumBytesRead();			
			// Read message type from the server.
			int msgType = rfb.readServerMessageType();

			// Process the message depending on its type.
			switch (msgType) {
			case RfbProto.FramebufferUpdate:

				if (statNumUpdates == viewer.debugStatsExcludeUpdates
						&& !statsRestarted) {
					resetStats();
					statsRestarted = true;
				} else if (statNumUpdates == viewer.debugStatsMeasureUpdates
						&& statsRestarted) {
					viewer.disconnect();
				}

				rfb.readFramebufferUpdate();
				statNumUpdates++;

				boolean cursorPosReceived = false;

				for (int i = 0; i < rfb.updateNRects; i++) {

					rfb.readFramebufferUpdateRectHdr();
					statNumTotalRects++;
					int rx = rfb.updateRectX, ry = rfb.updateRectY;
					int rw = rfb.updateRectW, rh = rfb.updateRectH;

					if (rfb.updateRectEncoding == rfb.EncodingLastRect)
						break;

					if (rfb.updateRectEncoding == rfb.EncodingNewFBSize) {
						rfb.setFramebufferSize(rw, rh);
						updateFramebufferSize();
						break;
					}

					if (rfb.updateRectEncoding == rfb.EncodingXCursor
							|| rfb.updateRectEncoding == rfb.EncodingRichCursor) {
						handleCursorShapeUpdate(rfb.updateRectEncoding, rx, ry,
								rw, rh);
						continue;
					}

					if (rfb.updateRectEncoding == rfb.EncodingPointerPos) {
						softCursorMove(rx, ry);
						cursorPosReceived = true;
						continue;
					}

					long numBytesReadBefore = rfb.getNumBytesRead();

					rfb.startTiming();

					switch (rfb.updateRectEncoding) {
					case RfbProto.EncodingRaw:
						statNumRectsRaw++;
						handleRawRect(rx, ry, rw, rh);
						break;
					case RfbProto.EncodingCopyRect:
						statNumRectsCopy++;
						handleCopyRect(rx, ry, rw, rh);
						break;
					case RfbProto.EncodingRRE:
						handleRRERect(rx, ry, rw, rh);
						break;
					case RfbProto.EncodingCoRRE:
						handleCoRRERect(rx, ry, rw, rh);
						break;
					case RfbProto.EncodingHextile:
						statNumRectsHextile++;
						handleHextileRect(rx, ry, rw, rh);
						break;
					case RfbProto.EncodingZRLE:
						statNumRectsZRLE++;
						handleZRLERect(rx, ry, rw, rh);
						break;
					case RfbProto.EncodingZlib:
						handleZlibRect(rx, ry, rw, rh);
						break;
					case RfbProto.EncodingTight:
						statNumRectsTight++;
						handleTightRect(rx, ry, rw, rh);
						break;
					default:
						throw new Exception("Unknown RFB rectangle encoding "
								+ rfb.updateRectEncoding);
					}

					rfb.stopTiming();

					statNumPixelRects++;
					statNumBytesDecoded += rw * rh * bytesPixel;
					statNumBytesEncoded += (int) (rfb.getNumBytesRead() - numBytesReadBefore);
				}

				boolean fullUpdateNeeded = false;

				// Start/stop session recording if necessary. Request full
				// update if a new session file was opened.
				if (viewer.checkRecordingStatus())
					fullUpdateNeeded = true;

				// Defer framebuffer update request if necessary. But wake up
				// immediately on keyboard or mouse event. Also, don't sleep
				// if there is some data to receive, or if the last update
				// included a PointerPos message.
				if (viewer.deferUpdateRequests > 0 && rfb.available() == 0
						&& !cursorPosReceived) {
					synchronized (rfb) {
						try {
							rfb.wait(viewer.deferUpdateRequests);
						} catch (InterruptedException e) {
						}
					}
				}

				viewer.autoSelectEncodings();

				// Before requesting framebuffer update, check if the pixel
				// format should be changed.
				if (viewer.options.eightBitColors != (bytesPixel == 1)) {
					// Pixel format should be changed.
					setPixelFormat();
					fullUpdateNeeded = true;
				}

				// Request framebuffer update if needed.
				int w = rfb.framebufferWidth;
				int h = rfb.framebufferHeight;
				rfb.writeFramebufferUpdateRequest(0, 0, w, h, !fullUpdateNeeded);

				break;

			case RfbProto.SetColourMapEntries:
				throw new Exception("Can't handle SetColourMapEntries message");

			case RfbProto.Bell:
				Toolkit.getDefaultToolkit().beep();
				break;

			case RfbProto.ServerCutText:
				String s = rfb.readServerCutText();
				viewer.clipboard.setCutText(s);
				break;

			default:
				throw new Exception("Unknown RFB message type " + msgType);
			}
			bufSize = rfb.getNumBytesRead() - bufSize;
//			System.out.println("bufSize="+bufSize);
//			rfb.bufResetSend((int)bufSize);

			if(rfb.createBimgFlag){
//				bimg = createBufferedImage(rawPixelsImage);
				bimg = createBufferedImage(memImage);
				rfb.createPngBytes(bimg);
				rfb.createBimgFlag = false;
				boolean result = false;
				try{
					result = ImageIO.write(bimg, "png", new File("sample.png"));
				}catch(Exception e){
					e.printStackTrace();
					result = false;
				}
			}
			
			

		}
	}
	

	//
	// Handle a raw rectangle. The second form with paint==false is used
	// by the Hextile decoder for raw-encoded tiles.
	//

	void handleRawRect(int x, int y, int w, int h) throws IOException {
		handleRawRect(x, y, w, h, true);
	}

	void handleRawRect(int x, int y, int w, int h, boolean paint)
			throws IOException {

		if (bytesPixel == 1) {
			for (int dy = y; dy < y + h; dy++) {
				rfb.readFully(pixels8, dy * rfb.framebufferWidth + x, w);
				if (rfb.rec != null) {
					rfb.rec.write(pixels8, dy * rfb.framebufferWidth + x, w);
				}
			}
		} else {
			byte[] buf = new byte[w * 4];
			int i, offset;
			for (int dy = y; dy < y + h; dy++) {
				rfb.readFully(buf);
				if (rfb.rec != null) {
					rfb.rec.write(buf);
				}
				offset = dy * rfb.framebufferWidth + x;
				for (i = 0; i < w; i++) {
					pixels24[offset + i] = (buf[i * 4 + 2] & 0xFF) << 16
							| (buf[i * 4 + 1] & 0xFF) << 8
							| (buf[i * 4] & 0xFF);
				}
			}
		}

		handleUpdatedPixels(x, y, w, h);
		if (paint)
			scheduleRepaint(x, y, w, h);
	}

	//
	// Handle a CopyRect rectangle.
	//

	void handleCopyRect(int x, int y, int w, int h) throws IOException {

		rfb.readCopyRect();
		memGraphics.copyArea(rfb.copyRectSrcX, rfb.copyRectSrcY, w, h, x
				- rfb.copyRectSrcX, y - rfb.copyRectSrcY);

		scheduleRepaint(x, y, w, h);
	}

	//
	// Handle an RRE-encoded rectangle.
	//

	void handleRRERect(int x, int y, int w, int h) throws IOException {

		int nSubrects = rfb.readU32();

		byte[] bg_buf = new byte[bytesPixel];
		rfb.readFully(bg_buf);
		Color pixel;
		if (bytesPixel == 1) {
			pixel = colors[bg_buf[0] & 0xFF];
		} else {
			pixel = new Color(bg_buf[2] & 0xFF, bg_buf[1] & 0xFF,
					bg_buf[0] & 0xFF);
		}
		memGraphics.setColor(pixel);
		memGraphics.fillRect(x, y, w, h);

		byte[] buf = new byte[nSubrects * (bytesPixel + 8)];
		rfb.readFully(buf);
		DataInputStream ds = new DataInputStream(new ByteArrayInputStream(buf));

		if (rfb.rec != null) {
			rfb.rec.writeIntBE(nSubrects);
			rfb.rec.write(bg_buf);
			rfb.rec.write(buf);
		}

		int sx, sy, sw, sh;

		for (int j = 0; j < nSubrects; j++) {
			if (bytesPixel == 1) {
				pixel = colors[ds.readUnsignedByte()];
			} else {
				ds.skip(4);
				pixel = new Color(buf[j * 12 + 2] & 0xFF,
						buf[j * 12 + 1] & 0xFF, buf[j * 12] & 0xFF);
			}
			sx = x + ds.readUnsignedShort();
			sy = y + ds.readUnsignedShort();
			sw = ds.readUnsignedShort();
			sh = ds.readUnsignedShort();

			memGraphics.setColor(pixel);
			memGraphics.fillRect(sx, sy, sw, sh);
		}

		scheduleRepaint(x, y, w, h);
	}

	//
	// Handle a CoRRE-encoded rectangle.
	//

	void handleCoRRERect(int x, int y, int w, int h) throws IOException {
		int nSubrects = rfb.readU32();

		byte[] bg_buf = new byte[bytesPixel];
		rfb.readFully(bg_buf);
		Color pixel;
		if (bytesPixel == 1) {
			pixel = colors[bg_buf[0] & 0xFF];
		} else {
			pixel = new Color(bg_buf[2] & 0xFF, bg_buf[1] & 0xFF,
					bg_buf[0] & 0xFF);
		}
		memGraphics.setColor(pixel);
		memGraphics.fillRect(x, y, w, h);

		byte[] buf = new byte[nSubrects * (bytesPixel + 4)];
		rfb.readFully(buf);

		if (rfb.rec != null) {
			rfb.rec.writeIntBE(nSubrects);
			rfb.rec.write(bg_buf);
			rfb.rec.write(buf);
		}

		int sx, sy, sw, sh;
		int i = 0;

		for (int j = 0; j < nSubrects; j++) {
			if (bytesPixel == 1) {
				pixel = colors[buf[i++] & 0xFF];
			} else {
				pixel = new Color(buf[i + 2] & 0xFF, buf[i + 1] & 0xFF,
						buf[i] & 0xFF);
				i += 4;
			}
			sx = x + (buf[i++] & 0xFF);
			sy = y + (buf[i++] & 0xFF);
			sw = buf[i++] & 0xFF;
			sh = buf[i++] & 0xFF;

			memGraphics.setColor(pixel);
			memGraphics.fillRect(sx, sy, sw, sh);
		}

		scheduleRepaint(x, y, w, h);
	}

	//
	// Handle a Hextile-encoded rectangle.
	//

	// These colors should be kept between handleHextileSubrect() calls.
	private Color hextile_bg, hextile_fg;
	boolean noZRLEdecode = false;

	void handleHextileRect(int x, int y, int w, int h) throws IOException {

		hextile_bg = new Color(0);
		hextile_fg = new Color(0);

		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(tx, ty, tw, th);
			}

			// Finished with a row of tiles, now let's show it.
			scheduleRepaint(x, y, w, h);
		}
	}

	//
	// Handle one tile in the Hextile-encoded data.
	//

	void handleHextileSubrect(int tx, int ty, int tw, int th)
			throws IOException {

		int subencoding = rfb.readU8();
		if (rfb.rec != null) {
			rfb.rec.writeByte(subencoding);
		}

		// Is it a raw-encoded sub-rectangle?
		if ((subencoding & rfb.HextileRaw) != 0) {
			handleRawRect(tx, ty, tw, th, false);
			return;
		}

		// Read and draw the background if specified.
		byte[] cbuf = new byte[bytesPixel];
		if ((subencoding & rfb.HextileBackgroundSpecified) != 0) {
			rfb.readFully(cbuf);
			if (bytesPixel == 1) {
				hextile_bg = colors[cbuf[0] & 0xFF];
			} else {
				hextile_bg = new Color(cbuf[2] & 0xFF, cbuf[1] & 0xFF,
						cbuf[0] & 0xFF);
			}
			if (rfb.rec != null) {
				rfb.rec.write(cbuf);
			}
		}
		memGraphics.setColor(hextile_bg);
		memGraphics.fillRect(tx, ty, tw, th);

		// Read the foreground color if specified.
		if ((subencoding & rfb.HextileForegroundSpecified) != 0) {
			rfb.readFully(cbuf);
			if (bytesPixel == 1) {
				hextile_fg = colors[cbuf[0] & 0xFF];
			} else {
				hextile_fg = new Color(cbuf[2] & 0xFF, cbuf[1] & 0xFF,
						cbuf[0] & 0xFF);
			}
			if (rfb.rec != null) {
				rfb.rec.write(cbuf);
			}
		}

		// Done with this tile if there is no sub-rectangles.
		if ((subencoding & rfb.HextileAnySubrects) == 0)
			return;

		int nSubrects = rfb.readU8();
		int bufsize = nSubrects * 2;
		if ((subencoding & rfb.HextileSubrectsColoured) != 0) {
			bufsize += nSubrects * bytesPixel;
		}
		byte[] buf = new byte[bufsize];
		rfb.readFully(buf);
		if (rfb.rec != null) {
			rfb.rec.writeByte(nSubrects);
			rfb.rec.write(buf);
		}

		int b1, b2, sx, sy, sw, sh;
		int i = 0;

		if ((subencoding & rfb.HextileSubrectsColoured) == 0) {

			// Sub-rectangles are all of the same color.
			memGraphics.setColor(hextile_fg);
			for (int j = 0; j < nSubrects; j++) {
				b1 = buf[i++] & 0xFF;
				b2 = buf[i++] & 0xFF;
				sx = tx + (b1 >> 4);
				sy = ty + (b1 & 0xf);
				sw = (b2 >> 4) + 1;
				sh = (b2 & 0xf) + 1;
				memGraphics.fillRect(sx, sy, sw, sh);
			}
		} else if (bytesPixel == 1) {

			// BGR233 (8-bit color) version for colored sub-rectangles.
			for (int j = 0; j < nSubrects; j++) {
				hextile_fg = colors[buf[i++] & 0xFF];
				b1 = buf[i++] & 0xFF;
				b2 = buf[i++] & 0xFF;
				sx = tx + (b1 >> 4);
				sy = ty + (b1 & 0xf);
				sw = (b2 >> 4) + 1;
				sh = (b2 & 0xf) + 1;
				memGraphics.setColor(hextile_fg);
				memGraphics.fillRect(sx, sy, sw, sh);
			}

		} else {

			// Full-color (24-bit) version for colored sub-rectangles.
			for (int j = 0; j < nSubrects; j++) {
				hextile_fg = new Color(buf[i + 2] & 0xFF, buf[i + 1] & 0xFF,
						buf[i] & 0xFF);
				i += 4;
				b1 = buf[i++] & 0xFF;
				b2 = buf[i++] & 0xFF;
				sx = tx + (b1 >> 4);
				sy = ty + (b1 & 0xf);
				sw = (b2 >> 4) + 1;
				sh = (b2 & 0xf) + 1;
				memGraphics.setColor(hextile_fg);
				memGraphics.fillRect(sx, sy, sw, sh);
			}

		}
	}

	//
	// Handle a ZRLE-encoded rectangle.
	//
	// FIXME: Currently, session recording is not fully supported for ZRLE.
	//

	void handleZRLERect(int x, int y, int w, int h) throws Exception {
		if (noZRLEdecode) return;
		if (zrleInStream == null)
			zrleInStream = new ZlibInStream();
//		System.out.println("zrleInStream.end="+zrleInStream.inflater.off);			
		
		int nBytes = rfb.readU32();
		if (nBytes > 64 * 1024 * 1024)
			throw new Exception("ZRLE decoder: illegal compressed data size");

		if (zrleBuf == null || zrleBufLen < nBytes) {
			zrleBufLen = nBytes + 4096;
			zrleBuf = new byte[zrleBufLen];
		}

		// FIXME: Do not wait for all the data before decompression.
		rfb.readFully(zrleBuf, 0, nBytes);

		if (rfb.rec != null) {
			if (rfb.recordFromBeginning) {
				rfb.rec.writeIntBE(nBytes);
				rfb.rec.write(zrleBuf, 0, nBytes);
			} else if (!zrleRecWarningShown) {
				System.out.println("Warning: ZRLE session can be recorded"
						+ " only from the beginning");
				System.out.println("Warning: Recorded file may be corrupted");
				zrleRecWarningShown = true;
			}

		}

		zrleInStream.setUnderlying(new MemInStream(zrleBuf, 0, nBytes), nBytes);

		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;
				int[] palette = new int[128];

				readZrlePalette(palette, palSize);

				if (palSize == 1) {
					int pix = palette[0];
					Color c = (bytesPixel == 1) ? colors[pix] : new Color(
							0xFF000000 | pix);
					memGraphics.setColor(c);
					memGraphics.fillRect(tx, ty, tw, th);
					continue;
				}

				if (!rle) {
					if (palSize == 0) {
						readZrleRawPixels(tw, th);
					} else {
						readZrlePackedPixels(tw, th, palette, palSize);
					}
				} else {
					if (palSize == 0) {
						readZrlePlainRLEPixels(tw, th);
					} else {
						readZrlePackedRLEPixels(tw, th, palette);
					}
				}
				handleUpdatedZrleTile(tx, ty, tw, th);
			}
		}

		zrleInStream.reset();

		scheduleRepaint(x, y, w, h);
	}

	int readPixel(InStream is) throws Exception {
		int pix;

		if (bytesPixel == 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;
	}

	void readPixels(InStream is, int[] dst, int count) throws Exception {
		int pix;
		if (bytesPixel == 1) {
			byte[] buf = new byte[count];
			is.readBytes(buf, 0, count);
			for (int i = 0; i < count; i++) {
				dst[i] = (int) buf[i] & 0xFF;
			}
		} else {
			byte[] buf = new byte[count * 3];
			is.readBytes(buf, 0, count * 3);
			for (int i = 0; i < count; i++) {
				dst[i] = ((buf[i * 3 + 2] & 0xFF) << 16
						| (buf[i * 3 + 1] & 0xFF) << 8 | (buf[i * 3] & 0xFF));
			}
		}
	}

	void readZrlePalette(int[] palette, int palSize) throws Exception {
		readPixels(zrleInStream, palette, palSize);
	}

	void readZrleRawPixels(int tw, int th) throws Exception {
		if (bytesPixel == 1) {
			zrleInStream.readBytes(zrleTilePixels8, 0, tw * th);
		} else {
			readPixels(zrleInStream, zrleTilePixels24, tw * th); // /
		}
	}

	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;

		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 (bytesPixel == 1) {
					zrleTilePixels8[ptr++] = (byte) palette[index];
				} else {
					zrleTilePixels24[ptr++] = palette[index];
				}
			}
		}
	}

	void readZrlePlainRLEPixels(int tw, int th) throws Exception {
		int ptr = 0;
		int end = ptr + tw * th;
		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 (bytesPixel == 1) {
				while (len-- > 0)
					zrleTilePixels8[ptr++] = (byte) pix;
			} else {
				while (len-- > 0)
					zrleTilePixels24[ptr++] = pix;
			}
		}
	}

	void readZrlePackedRLEPixels(int tw, int th, int[] palette)
			throws Exception {

		int ptr = 0;
		int end = ptr + tw * th;
		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 (bytesPixel == 1) {
				while (len-- > 0)
					zrleTilePixels8[ptr++] = (byte) pix;
			} else {
				while (len-- > 0)
					zrleTilePixels24[ptr++] = pix;
			}
		}
	}

	//
	// Copy pixels from zrleTilePixels8 or zrleTilePixels24, then update.
	//

	void handleUpdatedZrleTile(int x, int y, int w, int h) {
		Object src, dst;
		if (bytesPixel == 1) {
			src = zrleTilePixels8;
			dst = pixels8;
		} else {
			src = zrleTilePixels24;
			dst = pixels24;
		}
		int offsetSrc = 0;
		int offsetDst = (y * rfb.framebufferWidth + x);
		for (int j = 0; j < h; j++) {
			System.arraycopy(src, offsetSrc, dst, offsetDst, w);
			offsetSrc += w;
			offsetDst += rfb.framebufferWidth;
		}
		handleUpdatedPixels(x, y, w, h);
	}

	//
	// Handle a Zlib-encoded rectangle.
	//

	void handleZlibRect(int x, int y, int w, int h) throws Exception {

		int nBytes = rfb.readU32();

		if (zlibBuf == null || zlibBufLen < nBytes) {
			zlibBufLen = nBytes * 2;
			zlibBuf = new byte[zlibBufLen];
		}

		rfb.readFully(zlibBuf, 0, nBytes);

		if (rfb.rec != null && rfb.recordFromBeginning) {
			rfb.rec.writeIntBE(nBytes);
			rfb.rec.write(zlibBuf, 0, nBytes);
		}

		if (zlibInflater == null) {
			zlibInflater = new Inflater();
		}
		zlibInflater.setInput(zlibBuf, 0, nBytes);

		if (bytesPixel == 1) {
			for (int dy = y; dy < y + h; dy++) {
				zlibInflater.inflate(pixels8, dy * rfb.framebufferWidth + x, w);
				if (rfb.rec != null && !rfb.recordFromBeginning)
					rfb.rec.write(pixels8, dy * rfb.framebufferWidth + x, w);
			}
		} else {
			byte[] buf = new byte[w * 4];
			int i, offset;
			for (int dy = y; dy < y + h; dy++) {
				zlibInflater.inflate(buf);
				offset = dy * rfb.framebufferWidth + x;
				for (i = 0; i < w; i++) {
					pixels24[offset + i] = (buf[i * 4 + 2] & 0xFF) << 16
							| (buf[i * 4 + 1] & 0xFF) << 8
							| (buf[i * 4] & 0xFF);
				}
				if (rfb.rec != null && !rfb.recordFromBeginning)
					rfb.rec.write(buf);
			}
		}

		handleUpdatedPixels(x, y, w, h);
		scheduleRepaint(x, y, w, h);
	}

	//
	// Handle a Tight-encoded rectangle.
	//

	void handleTightRect(int x, int y, int w, int h) throws Exception {

		int comp_ctl = rfb.readU8();
		if (rfb.rec != null) {
			if (rfb.recordFromBeginning || comp_ctl == (rfb.TightFill << 4)
					|| comp_ctl == (rfb.TightJpeg << 4)) {
				// Send data exactly as received.
				rfb.rec.writeByte(comp_ctl);
			} else {
				// Tell the decoder to flush each of the four zlib streams.
				rfb.rec.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 > rfb.TightMaxSubencoding) {
			throw new Exception("Incorrect tight subencoding: " + comp_ctl);
		}

		// Handle solid-color rectangles.
		if (comp_ctl == rfb.TightFill) {

			if (bytesPixel == 1) {
				int idx = rfb.readU8();
				memGraphics.setColor(colors[idx]);
				if (rfb.rec != null) {
					rfb.rec.writeByte(idx);
				}
			} else {
				byte[] buf = new byte[3];
				rfb.readFully(buf);
				if (rfb.rec != null) {
					rfb.rec.write(buf);
				}
				Color bg = new Color(0xFF000000 | (buf[0] & 0xFF) << 16
						| (buf[1] & 0xFF) << 8 | (buf[2] & 0xFF));
				memGraphics.setColor(bg);
			}
			memGraphics.fillRect(x, y, w, h);
			scheduleRepaint(x, y, w, h);
			return;

		}

		if (comp_ctl == rfb.TightJpeg) {

			statNumRectsTightJPEG++;

			// Read JPEG data.
			byte[] jpegData = new byte[rfb.readCompactLen()];
			rfb.readFully(jpegData);
			if (rfb.rec != null) {
				if (!rfb.recordFromBeginning) {
					rfb.recordCompactLen(jpegData.length);
				}
				rfb.rec.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;

		}

		// 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 & rfb.TightExplicitFilter) != 0) {
			int filter_id = rfb.readU8();
			if (rfb.rec != null) {
				rfb.rec.writeByte(filter_id);
			}
			if (filter_id == rfb.TightFilterPalette) {
				numColors = rfb.readU8() + 1;
				if (rfb.rec != null) {
					rfb.rec.writeByte(numColors - 1);
				}
				if (bytesPixel == 1) {
					if (numColors != 2) {
						throw new Exception("Incorrect tight palette size: "
								+ numColors);
					}
					rfb.readFully(palette8);
					if (rfb.rec != null) {
						rfb.rec.write(palette8);
					}
				} else {
					byte[] buf = new byte[numColors * 3];
					rfb.readFully(buf);
					if (rfb.rec != null) {
						rfb.rec.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 == rfb.TightFilterGradient) {
				useGradient = true;
			} else if (filter_id != rfb.TightFilterCopy) {
				throw new Exception("Incorrect tight filter id: " + filter_id);
			}
		}
		if (numColors == 0 && bytesPixel == 4)
			rowSize *= 3;

		// Read, optionally uncompress and decode data.
		int dataSize = h * rowSize;
		if (dataSize < rfb.TightMinToCompress) {
			// Data size is small - not compressed with zlib.
			if (numColors != 0) {
				// Indexed colors.
				byte[] indexedData = new byte[dataSize];
				rfb.readFully(indexedData);
				if (rfb.rec != null) {
					rfb.rec.write(indexedData);
				}
				if (numColors == 2) {
					// Two colors.
					if (bytesPixel == 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 * rfb.framebufferWidth + dx] = palette24[indexedData[i++] & 0xFF];
						}
					}
				}
			} else if (useGradient) {
				// "Gradient"-processed data
				byte[] buf = new byte[w * h * 3];
				rfb.readFully(buf);
				if (rfb.rec != null) {
					rfb.rec.write(buf);
				}
				decodeGradientData(x, y, w, h, buf);
			} else {
				// Raw truecolor data.
				if (bytesPixel == 1) {
					for (int dy = y; dy < y + h; dy++) {
						rfb.readFully(pixels8, dy * rfb.framebufferWidth + x, w);
						if (rfb.rec != null) {
							rfb.rec.write(pixels8, dy * rfb.framebufferWidth
									+ x, w);
						}
					}
				} else {
					byte[] buf = new byte[w * 3];
					int i, offset;
					for (int dy = y; dy < y + h; dy++) {
						rfb.readFully(buf);
						if (rfb.rec != null) {
							rfb.rec.write(buf);
						}
						offset = dy * rfb.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 = rfb.readCompactLen();
			byte[] zlibData = new byte[zlibDataLen];
			rfb.readFully(zlibData);
			if (rfb.rec != null && rfb.recordFromBeginning) {
				rfb.rec.write(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 (rfb.rec != null && !rfb.recordFromBeginning) {
				rfb.recordCompressedData(buf);
			}

			if (numColors != 0) {
				// Indexed colors.
				if (numColors == 2) {
					// Two colors.
					if (bytesPixel == 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 * rfb.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 (bytesPixel == 1) {
					int destOffset = y * rfb.framebufferWidth + x;
					for (int dy = 0; dy < h; dy++) {
						System.arraycopy(buf, dy * w, pixels8, destOffset, w);
						destOffset += rfb.framebufferWidth;
					}
				} else {
					int srcOffset = 0;
					int destOffset, i;
					for (int dy = 0; dy < h; dy++) {
						myInflater.inflate(buf);
						destOffset = (y + dy) * rfb.framebufferWidth + x;
						for (i = 0; i < w; i++) {
							pixels24[destOffset + i] = (buf[srcOffset] & 0xFF) << 16
									| (buf[srcOffset + 1] & 0xFF) << 8
									| (buf[srcOffset + 2] & 0xFF);
							srcOffset += 3;
						}
					}
				}
			}
		}

		handleUpdatedPixels(x, y, w, h);
		scheduleRepaint(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 = y * rfb.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 += (rfb.framebufferWidth - w);
		}
	}

	void decodeMonoData(int x, int y, int w, int h, byte[] src, int[] palette) {

		int dx, dy, n;
		int i = y * rfb.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 += (rfb.framebufferWidth - 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 offset = y * rfb.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 += (rfb.framebufferWidth - w);
		}
	}

	//
	// Display newly updated area of pixels.
	//

	void handleUpdatedPixels(int x, int y, int w, int h) {

		// Draw updated pixels of the off-screen image.
		pixelsSource.newPixels(x, y, w, h);
		memGraphics.setClip(x, y, w, h);
		memGraphics.drawImage(rawPixelsImage, 0, 0, null);
		memGraphics.setClip(0, 0, rfb.framebufferWidth, rfb.framebufferHeight);
	}

	//
	// Tell JVM to repaint specified desktop area.
	//

	void scheduleRepaint(int x, int y, int w, int h) {
		// Request repaint, deferred if necessary.
		if (rfb.framebufferWidth == scaledWidth) {
			repaint(viewer.deferScreenUpdates, x, y, w, h);
		} else {
			int sx = x * scalingFactor / 100;
			int sy = y * scalingFactor / 100;
			int sw = ((x + w) * scalingFactor + 49) / 100 - sx + 1;
			int sh = ((y + h) * scalingFactor + 49) / 100 - sy + 1;
			repaint(viewer.deferScreenUpdates, sx, sy, sw, sh);
		}
	}

	//
	// Handle events.
	//

	public void keyPressed(KeyEvent evt) {
		processLocalKeyEvent(evt);
	}

	public void keyReleased(KeyEvent evt) {
		processLocalKeyEvent(evt);
	}

	public void keyTyped(KeyEvent evt) {
		evt.consume();
	}

	public void mousePressed(MouseEvent evt) {
		processLocalMouseEvent(evt, false);
	}

	public void mouseReleased(MouseEvent evt) {
		processLocalMouseEvent(evt, false);
	}

	public void mouseMoved(MouseEvent evt) {
		processLocalMouseEvent(evt, true);
	}

	public void mouseDragged(MouseEvent evt) {
		processLocalMouseEvent(evt, true);
	}

	public void processLocalKeyEvent(KeyEvent evt) {
		if (viewer.rfb != null && rfb.inNormalProtocol) {
			if (!inputEnabled) {
				if ((evt.getKeyChar() == 'r' || evt.getKeyChar() == 'R')
						&& evt.getID() == KeyEvent.KEY_PRESSED) {
					// Request screen update.
					try {
						rfb.writeFramebufferUpdateRequest(0, 0,
								rfb.framebufferWidth, rfb.framebufferHeight,
								false);
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			} else {
				// Input enabled.
				synchronized (rfb) {
					try {
						rfb.writeKeyEvent(evt);
					} catch (Exception e) {
						e.printStackTrace();
					}
					rfb.notify();
				}
			}
		}
		// Don't ever pass keyboard events to AWT for default processing.
		// Otherwise, pressing Tab would switch focus to ButtonPanel etc.
		evt.consume();
	}

	public void processLocalMouseEvent(MouseEvent evt, boolean moved) {
		if (viewer.rfb != null && rfb.inNormalProtocol) {
			if (moved) {
				softCursorMove(evt.getX(), evt.getY());
			}
			if (rfb.framebufferWidth != scaledWidth) {
				int sx = (evt.getX() * 100 + scalingFactor / 2) / scalingFactor;
				int sy = (evt.getY() * 100 + scalingFactor / 2) / scalingFactor;
				evt.translatePoint(sx - evt.getX(), sy - evt.getY());
			}
			synchronized (rfb) {
				try {
					rfb.writePointerEvent(evt);
				} catch (Exception e) {
					e.printStackTrace();
				}
				rfb.notify();
			}
		}
	}

	//
	// Ignored events.
	//

	public void mouseClicked(MouseEvent evt) {
	}

	public void mouseEntered(MouseEvent evt) {
	}

	public void mouseExited(MouseEvent evt) {
	}

	//
	// Reset update statistics.
	//

	void resetStats() {
		statStartTime = System.currentTimeMillis();
		statNumUpdates = 0;
		statNumTotalRects = 0;
		statNumPixelRects = 0;
		statNumRectsTight = 0;
		statNumRectsTightJPEG = 0;
		statNumRectsZRLE = 0;
		statNumRectsHextile = 0;
		statNumRectsRaw = 0;
		statNumRectsCopy = 0;
		statNumBytesEncoded = 0;
		statNumBytesDecoded = 0;
	}

	// ////////////////////////////////////////////////////////////////
	//
	// Handle cursor shape updates (XCursor and RichCursor encodings).
	//

	boolean showSoftCursor = false;

	MemoryImageSource softCursorSource;
	Image softCursor;

	int cursorX = 0, cursorY = 0;
	int cursorWidth, cursorHeight;
	int origCursorWidth, origCursorHeight;
	int hotX, hotY;
	int origHotX, origHotY;

	//
	// Handle cursor shape update (XCursor and RichCursor encodings).
	//

	synchronized void handleCursorShapeUpdate(int encodingType, int xhot,
			int yhot, int width, int height) throws IOException {

		softCursorFree();

		if (width * height == 0)
			return;

		// Ignore cursor shape data if requested by user.
		if (viewer.options.ignoreCursorUpdates) {
			int bytesPerRow = (width + 7) / 8;
			int bytesMaskData = bytesPerRow * height;

			if (encodingType == rfb.EncodingXCursor) {
				rfb.skipBytes(6 + bytesMaskData * 2);
			} else {
				// rfb.EncodingRichCursor
				rfb.skipBytes(width * height * bytesPixel + bytesMaskData);
			}
			return;
		}

		// Decode cursor pixel data.
		softCursorSource = decodeCursorShape(encodingType, width, height);

		// Set original (non-scaled) cursor dimensions.
		origCursorWidth = width;
		origCursorHeight = height;
		origHotX = xhot;
		origHotY = yhot;

		// Create off-screen cursor image.
		createSoftCursor();

		// Show the cursor.
		showSoftCursor = true;
		repaint(viewer.deferCursorUpdates, cursorX - hotX, cursorY - hotY,
				cursorWidth, cursorHeight);
	}

	//
	// decodeCursorShape(). Decode cursor pixel data and return
	// corresponding MemoryImageSource instance.
	//

	synchronized MemoryImageSource decodeCursorShape(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 == rfb.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 * bytesPixel];
			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 (bytesPixel == 1) {
								result = cm8.getRGB(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 (bytesPixel == 1) {
							result = cm8.getRGB(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 new MemoryImageSource(width, height, softCursorPixels, 0, width);
	}

	//
	// createSoftCursor(). Assign softCursor new Image (scaled if necessary).
	// Uses softCursorSource as a source for new cursor image.
	//

	synchronized void createSoftCursor() {

		if (softCursorSource == null)
			return;

		int scaleCursor = viewer.options.scaleCursor;
		if (scaleCursor == 0 || !inputEnabled)
			scaleCursor = 100;

		// Save original cursor coordinates.
		int x = cursorX - hotX;
		int y = cursorY - hotY;
		int w = cursorWidth;
		int h = cursorHeight;

		cursorWidth = (origCursorWidth * scaleCursor + 50) / 100;
		cursorHeight = (origCursorHeight * scaleCursor + 50) / 100;
		hotX = (origHotX * scaleCursor + 50) / 100;
		hotY = (origHotY * scaleCursor + 50) / 100;
		softCursor = Toolkit.getDefaultToolkit().createImage(softCursorSource);

		if (scaleCursor != 100) {
			softCursor = softCursor.getScaledInstance(cursorWidth,
					cursorHeight, Image.SCALE_SMOOTH);
		}

		if (showSoftCursor) {
			// Compute screen area to update.
			x = Math.min(x, cursorX - hotX);
			y = Math.min(y, cursorY - hotY);
			w = Math.max(w, cursorWidth);
			h = Math.max(h, cursorHeight);

			repaint(viewer.deferCursorUpdates, x, y, w, h);
		}
	}

	//
	// softCursorMove(). Moves soft cursor into a particular location.
	//

	synchronized void softCursorMove(int x, int y) {
		int oldX = cursorX;
		int oldY = cursorY;
		cursorX = x;
		cursorY = y;
		if (showSoftCursor) {
			repaint(viewer.deferCursorUpdates, oldX - hotX, oldY - hotY,
					cursorWidth, cursorHeight);
			repaint(viewer.deferCursorUpdates, cursorX - hotX, cursorY - hotY,
					cursorWidth, cursorHeight);
		}
	}

	//
	// softCursorFree(). Remove soft cursor, dispose resources.
	//

	synchronized void softCursorFree() {
		if (showSoftCursor) {
			showSoftCursor = false;
			softCursor = null;
			softCursorSource = null;

			repaint(viewer.deferCursorUpdates, cursorX - hotX, cursorY - hotY,
					cursorWidth, cursorHeight);
		}
	}

	BufferedImage createBufferedImage(Image img){
		BufferedImage bimg = new BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_INT_RGB  );
		System.out.println("img.getWidth="+img.getWidth(null));
		
		Graphics g = bimg.getGraphics();
		g.drawImage(img, 0, 0, null);
		g.dispose();
		return bimg;
	}
	
	byte[] getBytes(BufferedImage img)throws IOException { 
		byte[] b = getImageBytes(img, "png");
		return b;
	}
	
	byte[] getImageBytes(BufferedImage image, String imageFormat) throws IOException {
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		BufferedOutputStream os = new BufferedOutputStream(bos);
		image.flush();
		ImageIO.write(image, imageFormat, os);
		os.flush();
		os.close();
		return bos.toByteArray();
	}
}