diff options
Diffstat (limited to 'java/src/com/tigervnc/decoder')
-rw-r--r-- | java/src/com/tigervnc/decoder/CoRREDecoder.java | 85 | ||||
-rw-r--r-- | java/src/com/tigervnc/decoder/CopyRectDecoder.java | 50 | ||||
-rw-r--r-- | java/src/com/tigervnc/decoder/HextileDecoder.java | 228 | ||||
-rw-r--r-- | java/src/com/tigervnc/decoder/RREDecoder.java | 85 | ||||
-rw-r--r-- | java/src/com/tigervnc/decoder/RawDecoder.java | 223 | ||||
-rw-r--r-- | java/src/com/tigervnc/decoder/TightDecoder.java | 525 | ||||
-rw-r--r-- | java/src/com/tigervnc/decoder/ZRLEDecoder.java | 310 | ||||
-rw-r--r-- | java/src/com/tigervnc/decoder/ZlibDecoder.java | 103 | ||||
-rw-r--r-- | java/src/com/tigervnc/decoder/common/Repaintable.java | 7 |
9 files changed, 1616 insertions, 0 deletions
diff --git a/java/src/com/tigervnc/decoder/CoRREDecoder.java b/java/src/com/tigervnc/decoder/CoRREDecoder.java new file mode 100644 index 00000000..bc086686 --- /dev/null +++ b/java/src/com/tigervnc/decoder/CoRREDecoder.java @@ -0,0 +1,85 @@ +package com.tightvnc.decoder; + +import com.tightvnc.vncviewer.RfbInputStream; +import java.awt.Graphics; +import java.awt.Color; +import java.io.IOException; + +// +// Class that used for decoding CoRRE encoded data. +// + +public class CoRREDecoder extends RawDecoder { + + final static int EncodingCoRRE = 4; + + public CoRREDecoder(Graphics g, RfbInputStream is) { + super(g, is); + } + + public CoRREDecoder(Graphics g, RfbInputStream is, int frameBufferW, + int frameBufferH) { + super(g, is, frameBufferW, frameBufferH); + } + + // + // Override handleRect method to decode CoRRE encoded data insted of + // raw pixel data. + // + + public void handleRect(int x, int y, int w, int h) throws IOException { + + // + // Write encoding ID to record output stream + // + + if (dos != null) { + dos.writeInt(CoRREDecoder.EncodingCoRRE); + } + + int nSubrects = rfbis.readU32(); + + byte[] bg_buf = new byte[bytesPerPixel]; + rfbis.readFully(bg_buf); + Color pixel; + if (bytesPerPixel == 1) { + pixel = getColor256()[bg_buf[0] & 0xFF]; + } else { + pixel = new Color(bg_buf[2] & 0xFF, bg_buf[1] & 0xFF, bg_buf[0] & 0xFF); + } + graphics.setColor(pixel); + graphics.fillRect(x, y, w, h); + + byte[] buf = new byte[nSubrects * (bytesPerPixel + 4)]; + rfbis.readFully(buf); + + // + // Save decoded data to data output stream + // + + if (dos != null) { + dos.writeInt(nSubrects); + dos.write(bg_buf); + dos.write(buf); + } + + int sx, sy, sw, sh; + int i = 0; + + for (int j = 0; j < nSubrects; j++) { + if (bytesPerPixel == 1) { + pixel = getColor256()[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; + + graphics.setColor(pixel); + graphics.fillRect(sx, sy, sw, sh); + } + } +} diff --git a/java/src/com/tigervnc/decoder/CopyRectDecoder.java b/java/src/com/tigervnc/decoder/CopyRectDecoder.java new file mode 100644 index 00000000..07a14bdb --- /dev/null +++ b/java/src/com/tigervnc/decoder/CopyRectDecoder.java @@ -0,0 +1,50 @@ +package com.tightvnc.decoder; + +import com.tightvnc.vncviewer.RfbInputStream; +import java.awt.Graphics; +import java.io.IOException; + +// +// Class that used for decoding CopyRect encoded data. +// + +public class CopyRectDecoder extends RawDecoder { + + final static int EncodingCopyRect = 1; + + public CopyRectDecoder(Graphics g, RfbInputStream is) { + super(g, is); + } + + public CopyRectDecoder(Graphics g, RfbInputStream is, int frameBufferW, + int frameBufferH) { + super(g, is, frameBufferW, frameBufferH); + } + + // + // Override handleRect method handle CopyRect + // + + public void handleRect(int x, int y, int w, int h) throws IOException { + + // + // Write encoding ID to record output stream + // + + if (dos != null) { + dos.writeInt(CopyRectDecoder.EncodingCopyRect); + } + + int copyRectSrcX = rfbis.readU16(); + int copyRectSrcY = rfbis.readU16(); + + // If the session is being recorded: + if (dos != null) { + dos.writeShort(copyRectSrcX); + dos.writeShort(copyRectSrcY); + } + + graphics.copyArea(copyRectSrcX, copyRectSrcY, w, h, + x - copyRectSrcX, y - copyRectSrcY); + } +} diff --git a/java/src/com/tigervnc/decoder/HextileDecoder.java b/java/src/com/tigervnc/decoder/HextileDecoder.java new file mode 100644 index 00000000..da7e7781 --- /dev/null +++ b/java/src/com/tigervnc/decoder/HextileDecoder.java @@ -0,0 +1,228 @@ +package com.tightvnc.decoder; + +import com.tightvnc.decoder.common.Repaintable; +import com.tightvnc.vncviewer.RfbInputStream; +import java.awt.Color; +import java.awt.Graphics; +import java.io.IOException; + +// +// Class that used for decoding hextile encoded data. +// + +public class HextileDecoder extends RawDecoder { + + final static int EncodingHextile = 5; + + // Contstants used in the Hextile decoder + final static int + HextileRaw = 1, + HextileBackgroundSpecified = 2, + HextileForegroundSpecified = 4, + HextileAnySubrects = 8, + HextileSubrectsColoured = 16; + + public HextileDecoder(Graphics g, RfbInputStream is) { + super(g, is); + } + + public HextileDecoder(Graphics g, RfbInputStream is, int frameBufferW, + int frameBufferH) { + super(g, is, frameBufferW, frameBufferH); + } + + // + // Set private members methods + // + + public void setRepainableControl(Repaintable r) { + repainableControl = r; + } + + // + // Override handleRect method to decode Hextile encoded data insted of + // raw pixel data. + // + + public void handleRect(int x, int y, int w, int h) throws IOException, + Exception { + + // + // Write encoding ID to record output stream + // + + if (dos != null) { + dos.writeInt(HextileDecoder.EncodingHextile); + } + + 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); + } + if (repainableControl != null) + repainableControl.scheduleRepaint(x, y, w, h); + } + if (repainableControl != null) + repainableControl.scheduleRepaint(x, y, w, h); + } + + // + // Handle one tile in the Hextile-encoded data. + // + + private void handleHextileSubrect(int tx, int ty, int tw, int th) + throws IOException, Exception { + + int subencoding = rfbis.readU8(); + + // + // Save decoded data to data output stream + // + + if (dos != null) { + dos.writeByte((byte)subencoding); + } + + // Is it a raw-encoded sub-rectangle? + if ((subencoding & HextileRaw) != 0) { + // + // Disable encoding id writting to record stream + // in super (RawDecoder) class, cause we write subencoding ID + // in this class (see code above). + // + + super.enableEncodingRecordWritting(false); + super.handleRect(tx, ty, tw, th); + super.handleUpdatedPixels(tx, ty, tw, th); + super.enableEncodingRecordWritting(true); + return; + } + + // Read and draw the background if specified. + byte[] cbuf = new byte[bytesPerPixel]; + if ((subencoding & HextileBackgroundSpecified) != 0) { + rfbis.readFully(cbuf); + if (bytesPerPixel == 1) { + hextile_bg = getColor256()[cbuf[0] & 0xFF]; + } else { + hextile_bg = new Color(cbuf[2] & 0xFF, cbuf[1] & 0xFF, cbuf[0] & 0xFF); + } + + // + // Save decoded data to data output stream + // + + if (dos != null) { + dos.write(cbuf); + } + } + graphics.setColor(hextile_bg); + graphics.fillRect(tx, ty, tw, th); + + // Read the foreground color if specified. + if ((subencoding & HextileForegroundSpecified) != 0) { + rfbis.readFully(cbuf); + if (bytesPerPixel == 1) { + hextile_fg = getColor256()[cbuf[0] & 0xFF]; + } else { + hextile_fg = new Color(cbuf[2] & 0xFF, cbuf[1] & 0xFF, cbuf[0] & 0xFF); + } + + // + // Save decoded data to data output stream + // + + if (dos != null) { + dos.write(cbuf); + } + } + + // Done with this tile if there is no sub-rectangles. + if ((subencoding & HextileAnySubrects) == 0) + return; + + int nSubrects = rfbis.readU8(); + int bufsize = nSubrects * 2; + if ((subencoding & HextileSubrectsColoured) != 0) { + bufsize += nSubrects * bytesPerPixel; + } + byte[] buf = new byte[bufsize]; + rfbis.readFully(buf); + + // + // Save decoded data to data output stream + // + + if (dos != null) { + dos.writeByte((byte)nSubrects); + dos.write(buf); + } + + int b1, b2, sx, sy, sw, sh; + int i = 0; + + if ((subencoding & HextileSubrectsColoured) == 0) { + + // Sub-rectangles are all of the same color. + graphics.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; + graphics.fillRect(sx, sy, sw, sh); + } + } else if (bytesPerPixel == 1) { + + // BGR233 (8-bit color) version for colored sub-rectangles. + for (int j = 0; j < nSubrects; j++) { + hextile_fg = getColor256()[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; + graphics.setColor(hextile_fg); + graphics.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; + graphics.setColor(hextile_fg); + graphics.fillRect(sx, sy, sw, sh); + } + + } + } + + // These colors should be kept between handleHextileSubrect() calls. + private Color hextile_bg, hextile_fg; + // Repaitable object + private Repaintable repainableControl = null; +} diff --git a/java/src/com/tigervnc/decoder/RREDecoder.java b/java/src/com/tigervnc/decoder/RREDecoder.java new file mode 100644 index 00000000..02eb513f --- /dev/null +++ b/java/src/com/tigervnc/decoder/RREDecoder.java @@ -0,0 +1,85 @@ +package com.tightvnc.decoder; + +import com.tightvnc.vncviewer.RfbInputStream; +import java.awt.Graphics; +import java.awt.Color; +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; + +// +// Class that used for decoding RRE encoded data. +// + +public class RREDecoder extends RawDecoder { + + final static int EncodingRRE = 2; + + public RREDecoder(Graphics g, RfbInputStream is) { + super(g, is); + } + + public RREDecoder(Graphics g, RfbInputStream is, int frameBufferW, + int frameBufferH) { + super(g, is, frameBufferW, frameBufferH); + } + + // + // Override handleRect method to decode RRE encoded data insted of + // raw pixel data. + // + + public void handleRect(int x, int y, int w, int h) throws IOException { + + // + // Write encoding ID to record output stream + // + + if (dos != null) { + dos.writeInt(RREDecoder.EncodingRRE); + } + + int nSubrects = rfbis.readU32(); + byte[] bg_buf = new byte[bytesPerPixel]; + rfbis.readFully(bg_buf); + Color pixel; + if (bytesPerPixel == 1) { + pixel = getColor256()[bg_buf[0] & 0xFF]; + } else { + pixel = new Color(bg_buf[2] & 0xFF, bg_buf[1] & 0xFF, bg_buf[0] & 0xFF); + } + graphics.setColor(pixel); + graphics.fillRect(x, y, w, h); + byte[] buf = new byte[nSubrects * (bytesPerPixel + 8)]; + rfbis.readFully(buf); + DataInputStream ds = new DataInputStream(new ByteArrayInputStream(buf)); + + // + // Save decoded data to data output stream + // + if (dos != null) { + dos.writeInt(nSubrects); + dos.write(bg_buf); + dos.write(buf); + } + + int sx, sy, sw, sh; + for (int j = 0; j < nSubrects; j++) { + if (bytesPerPixel == 1) { + pixel = getColor256()[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(); + + graphics.setColor(pixel); + graphics.fillRect(sx, sy, sw, sh); + } + } +} diff --git a/java/src/com/tigervnc/decoder/RawDecoder.java b/java/src/com/tigervnc/decoder/RawDecoder.java new file mode 100644 index 00000000..9ef167a7 --- /dev/null +++ b/java/src/com/tigervnc/decoder/RawDecoder.java @@ -0,0 +1,223 @@ +package com.tightvnc.decoder; + +import com.tightvnc.vncviewer.RfbInputStream; +import java.io.IOException; +import java.io.DataOutput; +import java.awt.Graphics; +import java.awt.Image; +import java.awt.image.ColorModel; +import java.awt.image.DirectColorModel; +import java.awt.image.MemoryImageSource; +import java.awt.Color; +import java.awt.Toolkit; + +// +// This is base decoder class. +// Other classes will be childs of RawDecoder. +// + +public class RawDecoder { + final static int EncodingRaw = 0; + + public RawDecoder(Graphics g, RfbInputStream is) { + setGraphics(g); + setRfbInputStream(is); + } + + public RawDecoder(Graphics g, RfbInputStream is, int frameBufferW, + int frameBufferH) { + setGraphics(g); + setRfbInputStream(is); + setFrameBufferSize(frameBufferW, frameBufferH); + // FIXME: cm24 created in getColorModel24. + // Remove if no bugs + cm24 = new DirectColorModel(24, 0xFF0000, 0x00FF00, 0x0000FF); + } + + // + // Set methods to set value of non-static protected members of class + // + + public void setRfbInputStream(RfbInputStream is) { + rfbis = is; + } + + public void setGraphics(Graphics g) { + graphics = g; + } + + public void setBPP(int bpp) { + bytesPerPixel = bpp; + } + + public void setFrameBufferSize(int w, int h) { + framebufferWidth = w; + framebufferHeight = h; + } + + // + // FIXME: Rename this method after we don't need RecordInterface + // in RawDecoder class to record session + // + + public void setDataOutputStream(DataOutput os) { + dos = os; + } + + // + // Decodes Raw Pixels data and draw it into graphics + // + + public void handleRect(int x, int y, int w, int h) throws IOException, Exception { + + // + // Write encoding ID to record output stream + // + + if ((dos != null) && (enableEncodingRecordWritting)) { + dos.writeInt(RawDecoder.EncodingRaw); + } + + if (bytesPerPixel == 1) { + for (int dy = y; dy < y + h; dy++) { + if (pixels8 != null) { + rfbis.readFully(pixels8, dy * framebufferWidth + x, w); + } + // + // Save decoded data to record output stream + // + if (dos != null) { + dos.write(pixels8, dy * framebufferWidth + x, w); + } + } + } else { + byte[] buf = new byte[w * 4]; + int i, offset; + for (int dy = y; dy < y + h; dy++) { + rfbis.readFully(buf); + // + // Save decoded data to record output stream + // + if (dos != null) { + dos.write(buf); + } + offset = dy * framebufferWidth + x; + if (pixels24 != null) { + 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); + } //for + } // if + } // for + } // else + handleUpdatedPixels(x, y, w, h); + } // void + + // + // Display newly updated area of pixels. + // + + protected void handleUpdatedPixels(int x, int y, int w, int h) { + // Draw updated pixels of the off-screen image. + pixelsSource.newPixels(x, y, w, h); + graphics.setClip(x, y, w, h); + graphics.drawImage(rawPixelsImage, 0, 0, null); + graphics.setClip(0, 0, framebufferWidth, framebufferHeight); + } + + // + // Updates pixels data. + // This method must be called when framebuffer is resized + // or BPP is changed. + // + + public void update() { + // Images with raw pixels should be re-allocated on every change + // of geometry or pixel format. + int fbWidth = framebufferWidth; + int fbHeight = framebufferHeight; + + if (bytesPerPixel == 1) { + pixels24 = null; + pixels8 = new byte[fbWidth * fbHeight]; + pixelsSource = new MemoryImageSource(fbWidth, fbHeight, getColorModel8(), + pixels8, 0, fbWidth); + } else { + pixels8 = null; + pixels24 = new int[fbWidth * fbHeight]; + pixelsSource = + new MemoryImageSource(fbWidth, fbHeight, cm24, pixels24, 0, fbWidth); + } + pixelsSource.setAnimated(true); + rawPixelsImage = Toolkit.getDefaultToolkit().createImage(pixelsSource); + } + + // + // Private static members access methods + // + + protected ColorModel getColorModel8() { + if (cm8 == null) { + cm8 = cm8 = new DirectColorModel(8, 7, (7 << 3), (3 << 6)); + } + return cm8; + } + + protected ColorModel getColorModel24() { + if (cm24 == null) { + cm24 = new DirectColorModel(24, 0xFF0000, 0x00FF00, 0x0000FF); + } + return cm24; + } + + protected Color[]getColor256() { + if (color256 == null) { + color256 = new Color[256]; + for (int i = 0; i < 256; i++) + color256[i] = new Color(cm8.getRGB(i)); + } + return color256; + } + + // + // This method will be used by HextileDecoder to disable + // double writting encoding id to record stream. + // + // FIXME: Try to find better solution than this. + // + + protected void enableEncodingRecordWritting(boolean enable) { + enableEncodingRecordWritting = enable; + } + + // + // Unique data for every decoder (? maybe not ?) + // + + protected int bytesPerPixel = 4; + protected int framebufferWidth = 0; + protected int framebufferHeight = 0; + protected RfbInputStream rfbis = null; + protected Graphics graphics = null; + protected DataOutput dos = null; + protected boolean enableEncodingRecordWritting = true; + + // + // This data must be shared between decoders + // + + protected static byte []pixels8 = null; + protected static int []pixels24 = null; + protected static MemoryImageSource pixelsSource = null; + protected static Image rawPixelsImage = null; + + // + // Access to this static members only though protected methods + // + + private static ColorModel cm8 = null; + private static ColorModel cm24 = null; + private static Color []color256 = null; +} diff --git a/java/src/com/tigervnc/decoder/TightDecoder.java b/java/src/com/tigervnc/decoder/TightDecoder.java new file mode 100644 index 00000000..015f73cd --- /dev/null +++ b/java/src/com/tigervnc/decoder/TightDecoder.java @@ -0,0 +1,525 @@ +package com.tightvnc.decoder; + +import com.tightvnc.decoder.common.Repaintable; +import com.tightvnc.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; +} diff --git a/java/src/com/tigervnc/decoder/ZRLEDecoder.java b/java/src/com/tigervnc/decoder/ZRLEDecoder.java new file mode 100644 index 00000000..a22da908 --- /dev/null +++ b/java/src/com/tigervnc/decoder/ZRLEDecoder.java @@ -0,0 +1,310 @@ +package com.tightvnc.decoder; + +import com.tightvnc.vncviewer.InStream; +import com.tightvnc.vncviewer.RfbInputStream; +import com.tightvnc.vncviewer.ZlibInStream; +import java.awt.Graphics; +import com.tightvnc.vncviewer.MemInStream; +import java.awt.Color; +import java.awt.Toolkit; +import java.awt.image.MemoryImageSource; +import java.io.IOException; + +// +// Class that used for decoding ZRLE encoded data. +// + +public class ZRLEDecoder extends RawDecoder { + + final static int EncodingZRLE = 16; + + public ZRLEDecoder(Graphics g, RfbInputStream is) { + super(g, is); + } + + public ZRLEDecoder(Graphics g, RfbInputStream is, int frameBufferW, + int frameBufferH) { + super(g, is, frameBufferW, frameBufferH); + } + + // + // Handle a ZRLE-encoded rectangle. + // + // FIXME: Currently, session recording is not fully supported for ZRLE. + // + + public void handleRect(int x, int y, int w, int h) throws IOException, Exception { + + // + // Write encoding ID to record output stream + // + + if (dos != null) { + dos.writeInt(ZRLEDecoder.EncodingZRLE); + } + + if (zrleInStream == null) + zrleInStream = new ZlibInStream(); + + int nBytes = rfbis.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. + rfbis.readFully(zrleBuf, 0, nBytes); + + // + // Override handleRect method to decode RRE encoded data insted of + // raw pixel data. + // + + if (dos != null) { + 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 = (bytesPerPixel == 1) ? + getColor256()[pix] : new Color(0xFF000000 | pix); + graphics.setColor(c); + graphics.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(); + } + + // + // Override update() method cause we have own data that + // must be updated when framebuffer is resized or BPP is changed + // + + public void update() { + // Images with raw pixels should be re-allocated on every change + // of geometry or pixel format. + int fbWidth = framebufferWidth; + int fbHeight = framebufferHeight; + + if (bytesPerPixel == 1) { + RawDecoder.pixels24 = null; + RawDecoder.pixels8 = new byte[fbWidth * fbHeight]; + RawDecoder.pixelsSource = new MemoryImageSource(fbWidth, fbHeight, getColorModel8(), pixels8, 0, fbWidth); + zrleTilePixels24 = null; + zrleTilePixels8 = new byte[64 * 64]; + } else { + RawDecoder.pixels8 = null; + RawDecoder.pixels24 = new int[fbWidth * fbHeight]; + RawDecoder.pixelsSource = + new MemoryImageSource(fbWidth, fbHeight, getColorModel24(), pixels24, 0, fbWidth); + zrleTilePixels8 = null; + zrleTilePixels24 = new int[64 * 64]; + } + RawDecoder.pixelsSource.setAnimated(true); + RawDecoder.rawPixelsImage = Toolkit.getDefaultToolkit().createImage(pixelsSource); + } + + // + // Copy pixels from zrleTilePixels8 or zrleTilePixels24, then update. + // + + private void handleUpdatedZrleTile(int x, int y, int w, int h) { + Object src, dst; + if (bytesPerPixel == 1) { + src = zrleTilePixels8; dst = pixels8; + } else { + src = zrleTilePixels24; dst = pixels24; + } + int offsetSrc = 0; + int offsetDst = (y * framebufferWidth + x); + for (int j = 0; j < h; j++) { + System.arraycopy(src, offsetSrc, dst, offsetDst, w); + offsetSrc += w; + offsetDst += framebufferWidth; + } + handleUpdatedPixels(x, y, w, h); + } + + // + // Private methods for reading ZRLE data + // + + 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) { + 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)); + } + } + } + + private void readZrlePalette(int[] palette, int palSize) throws Exception { + readPixels(zrleInStream, palette, palSize); + } + + private void readZrleRawPixels(int tw, int th) throws Exception { + if (bytesPerPixel == 1) { + zrleInStream.readBytes(zrleTilePixels8, 0, tw * th); + } else { + readPixels(zrleInStream, zrleTilePixels24, 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; + + 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) { + zrleTilePixels8[ptr++] = (byte)palette[index]; + } else { + zrleTilePixels24[ptr++] = palette[index]; + } + } + } + } + + private 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 (bytesPerPixel == 1) { + while (len-- > 0) zrleTilePixels8[ptr++] = (byte)pix; + } else { + while (len-- > 0) zrleTilePixels24[ptr++] = pix; + } + } + } + + private 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 (bytesPerPixel == 1) { + while (len-- > 0) zrleTilePixels8[ptr++] = (byte)pix; + } else { + while (len-- > 0) zrleTilePixels24[ptr++] = pix; + } + } + } + + // + // ZRLE encoder's data. + // + + private byte[] zrleBuf; + private int zrleBufLen = 0; + private byte[] zrleTilePixels8; + private int[] zrleTilePixels24; + private ZlibInStream zrleInStream; + private boolean zrleRecWarningShown = false; +} diff --git a/java/src/com/tigervnc/decoder/ZlibDecoder.java b/java/src/com/tigervnc/decoder/ZlibDecoder.java new file mode 100644 index 00000000..1370da15 --- /dev/null +++ b/java/src/com/tigervnc/decoder/ZlibDecoder.java @@ -0,0 +1,103 @@ +package com.tightvnc.decoder; + +import com.tightvnc.vncviewer.RfbInputStream; +import java.awt.Graphics; +import java.io.IOException; +import java.util.zip.DataFormatException; +import java.util.zip.Inflater; + +// +// Class that used for decoding ZLib encoded data. +// + +public class ZlibDecoder extends RawDecoder { + + final static int EncodingZlib = 6; + + public ZlibDecoder(Graphics g, RfbInputStream is) { + super(g, is); + } + + public ZlibDecoder(Graphics g, RfbInputStream is, int frameBufferW, + int frameBufferH) { + super(g, is, frameBufferW, frameBufferH); + } + + // + // Override handleRect method to decode ZLib encoded data insted of + // raw pixel data. + // + + public void handleRect(int x, int y, int w, int h) throws IOException { + + // + // Write encoding ID to record output stream. + // Remark: we forced changed encoding from zlib to raw + // cause at this moment we cannot save data in zlib encoding. + // + + if (dos != null) { + dos.writeInt(RawDecoder.EncodingRaw); + } + + int nBytes = rfbis.readU32(); + + if (zlibBuf == null || zlibBufLen < nBytes) { + zlibBufLen = nBytes * 2; + zlibBuf = new byte[zlibBufLen]; + } + + rfbis.readFully(zlibBuf, 0, nBytes); + + if (zlibInflater == null) { + zlibInflater = new Inflater(); + } + zlibInflater.setInput(zlibBuf, 0, nBytes); + + try { + if (bytesPerPixel == 1) { + for (int dy = y; dy < y + h; dy++) { + zlibInflater.inflate(pixels8, dy * framebufferWidth + x, w); + + // + // Save decoded raw data to data output stream + // + + if (dos != null) + dos.write(pixels8, dy * 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 * framebufferWidth + x; + for (i = 0; i < w; i++) { + RawDecoder.pixels24[offset + i] = + (buf[i * 4 + 2] & 0xFF) << 16 | + (buf[i * 4 + 1] & 0xFF) << 8 | + (buf[i * 4] & 0xFF); + } + + // + // Save decoded raw data to data output stream + // + + if (dos != null) + dos.write(buf); + } + } + } catch (DataFormatException ex) { + ex.printStackTrace(); + } + handleUpdatedPixels(x, y, w, h); + } + + // + // Zlib encoder's data. + // + + protected byte[] zlibBuf; + protected int zlibBufLen = 0; + protected Inflater zlibInflater; +} diff --git a/java/src/com/tigervnc/decoder/common/Repaintable.java b/java/src/com/tigervnc/decoder/common/Repaintable.java new file mode 100644 index 00000000..de11f4c8 --- /dev/null +++ b/java/src/com/tigervnc/decoder/common/Repaintable.java @@ -0,0 +1,7 @@ +package com.tightvnc.decoder.common; + +public interface Repaintable { + + public void scheduleRepaint(int x, int y, int w, int h); + +} |