From 13dbd6bb3df3f5b1f8d90f6e1b9c9b474611e9b2 Mon Sep 17 00:00:00 2001 From: Brian Hinz Date: Sun, 27 Nov 2011 20:43:47 +0000 Subject: [PATCH] Sync up java Tight decoder with recent changes to C client as much as possible. These changes should also fix the 16bpp issue reported in bug #3429667. I think there are probably errors in the FilterGradient* code but I can't get any servers to actually send this type of data to test it. git-svn-id: svn://svn.code.sf.net/p/tigervnc/code/trunk@4819 3789f03b-4d11-0410-bbf8-ca57d06f2519 --- java/com/tigervnc/rdr/InStream.java | 10 - java/com/tigervnc/rdr/JavaInStream.java | 10 +- java/com/tigervnc/rfb/CMsgHandler.java | 4 +- java/com/tigervnc/rfb/PixelFormat.java | 148 +++++++++++- java/com/tigervnc/rfb/TightDecoder.java | 224 +++++++++--------- java/com/tigervnc/vncviewer/CConn.java | 8 +- .../com/tigervnc/vncviewer/DesktopWindow.java | 4 + .../tigervnc/vncviewer/PixelBufferImage.java | 2 +- 8 files changed, 282 insertions(+), 128 deletions(-) diff --git a/java/com/tigervnc/rdr/InStream.java b/java/com/tigervnc/rdr/InStream.java index 60967991..a18ea4ed 100644 --- a/java/com/tigervnc/rdr/InStream.java +++ b/java/com/tigervnc/rdr/InStream.java @@ -107,16 +107,6 @@ abstract public class InStream { } } - public void readBytes(int[] data, int dataPtr, int length) { - int dataEnd = dataPtr + length; - while (dataPtr < dataEnd) { - int n = check(1, dataEnd - dataPtr, true); - System.arraycopy(b, ptr, data, dataPtr, n); - ptr += n; - dataPtr += n; - } - } - // readOpaqueN() reads a quantity "without byte-swapping". Because java has // no byte-ordering, we just use big-endian. diff --git a/java/com/tigervnc/rdr/JavaInStream.java b/java/com/tigervnc/rdr/JavaInStream.java index d2eda520..faa968ac 100644 --- a/java/com/tigervnc/rdr/JavaInStream.java +++ b/java/com/tigervnc/rdr/JavaInStream.java @@ -98,7 +98,14 @@ public class JavaInStream extends InStream { ptr = 0; while (end < itemSize) { - int n = read(b, end, bufSize - end, wait); + int bytes_to_read = bufSize - end; + + if (!timing) { + bytes_to_read = Math.min(bytes_to_read, Math.max(itemSize*nItems, 8)); + } + + int n = read(b, end, bytes_to_read, wait); + end += n; } @@ -121,6 +128,7 @@ public class JavaInStream extends InStream { } if (n < 0) throw new EndOfStream(); + if (n == 0) return 0; if (timing) { long after = System.nanoTime(); diff --git a/java/com/tigervnc/rfb/CMsgHandler.java b/java/com/tigervnc/rfb/CMsgHandler.java index 81fd2a1b..31182003 100644 --- a/java/com/tigervnc/rfb/CMsgHandler.java +++ b/java/com/tigervnc/rfb/CMsgHandler.java @@ -22,7 +22,7 @@ package com.tigervnc.rfb; -public class CMsgHandler { +abstract public class CMsgHandler { public CMsgHandler() { cp = new ConnParams(); @@ -82,6 +82,8 @@ public class CMsgHandler { public void imageRect(Rect r, int[] pixels) {} public void copyRect(Rect r, int srcX, int srcY) {} + abstract public PixelFormat getPreferredPF(); + public ConnParams cp; static LogWriter vlog = new LogWriter("CMsgHandler"); diff --git a/java/com/tigervnc/rfb/PixelFormat.java b/java/com/tigervnc/rfb/PixelFormat.java index a8ab5f11..07f8487e 100644 --- a/java/com/tigervnc/rfb/PixelFormat.java +++ b/java/com/tigervnc/rfb/PixelFormat.java @@ -23,6 +23,7 @@ package com.tigervnc.rfb; import com.tigervnc.rdr.*; +import java.awt.image.ColorModel; public class PixelFormat { @@ -102,14 +103,123 @@ public class PixelFormat { return true; } - public void bufferFromRGB(int dst, byte[] src) { + public int pixelFromRGB(int red, int green, int blue, ColorModel cm) + { + if (trueColour) { + int r = (red * redMax + 32767) / 65535; + int g = (green * greenMax + 32767) / 65535; + int b = (blue * blueMax + 32767) / 65535; + + return (r << redShift) | (g << greenShift) | (b << blueShift); + } else if (cm != null) { + // Try to find the closest pixel by Cartesian distance + int colours = 1 << depth; + int diff = 256 * 256 * 4; + int col = 0; + for (int i=0; i> 8; + int gd = (g-green) >> 8; + int bd = (b-blue) >> 8; + int d = rd*rd + gd*gd + bd*bd; + if (d < diff) { + col = i; + diff = d; + } + } + return col; + } + // XXX just return 0 for colour map? + return 0; + } + + public void bufferFromRGB(int[] dst, int dstPtr, byte[] src, + int srcPtr, int pixels) { + if (is888()) { + // Optimised common case + int r, g, b; + + for (int i=srcPtr; i < pixels; i++) { + if (bigEndian) { + r = (src[3*i+0] & 0xff) << (24 - redShift); + g = (src[3*i+1] & 0xff) << (24 - greenShift); + b = (src[3*i+2] & 0xff) << (24 - blueShift); + dst[dstPtr+i] = r | g | b | 0xff; + } else { + r = (src[3*i+0] & 0xff) << redShift; + g = (src[3*i+1] & 0xff) << greenShift; + b = (src[3*i+2] & 0xff) << blueShift; + dst[dstPtr+i] = (0xff << 24) | r | g | b; + } + } + } else { + // Generic code + int p, r, g, b; + int[] rgb = new int[4]; + + int i = srcPtr; int j = dstPtr; + while (i < pixels) { + r = src[i++] & 0xff; + g = src[i++] & 0xff; + b = src[i++] & 0xff; + + //p = pixelFromRGB(r, g, b, cm); + p = ColorModel.getRGBdefault().getDataElement(new int[] {0xff, r, g, b}, 0); + + bufferFromPixel(dst, j, p); + j += bpp/8; + } + } + } + + public void rgbFromBuffer(byte[] dst, int dstPtr, byte[] src, int srcPtr, int pixels, ColorModel cm) + { + int p; + byte r, g, b; + + for (int i=0; i < pixels; i++) { + p = pixelFromBuffer(src, srcPtr); + srcPtr += bpp/8; + + dst[dstPtr++] = (byte)cm.getRed(p); + dst[dstPtr++] = (byte)cm.getGreen(p); + dst[dstPtr++] = (byte)cm.getBlue(p); + } + } + + public int pixelFromBuffer(byte[] buffer, int bufferPtr) + { + int p; + + p = 0; + if (bigEndian) { - dst = - (src[0] & 0xFF) << 16 | (src[1] & 0xFF) << 8 | (src[2] & 0xFF) | 0xFF << 24; + switch (bpp) { + case 32: + p = (buffer[0] & 0xff) << 24 | (buffer[1] & 0xff) << 16 | (buffer[2] & 0xff) << 8 | 0xff; + break; + case 16: + p = (buffer[0] & 0xff) << 8 | (buffer[1] & 0xff); + break; + case 8: + p = (buffer[0] & 0xff); + break; + } } else { - dst = - (src[2] & 0xFF) << 16 | (src[1] & 0xFF) << 8 | (src[0] & 0xFF) | 0xFF << 24; + p = (buffer[0] & 0xff); + if (bpp >= 16) { + p |= (buffer[1] & 0xff) << 8; + if (bpp == 32) { + p |= (buffer[2] & 0xff) << 16; + p |= (buffer[3] & 0xff) << 24; + } + } } + + return p; } public String print() { @@ -150,6 +260,34 @@ public class PixelFormat { return s.toString(); } + public void bufferFromPixel(int[] buffer, int bufPtr, int p) + { + if (bigEndian) { + switch (bpp) { + case 32: + buffer[bufPtr++] = (p >> 24) & 0xff; + buffer[bufPtr++] = (p >> 16) & 0xff; + break; + case 16: + buffer[bufPtr++] = (p >> 8) & 0xff; + break; + case 8: + buffer[bufPtr++] = (p >> 0) & 0xff; + break; + } + } else { + buffer[0] = (p >> 0) & 0xff; + if (bpp >= 16) { + buffer[1] = (p >> 8) & 0xff; + if (bpp == 32) { + buffer[2] = (p >> 16) & 0xff; + buffer[3] = (p >> 24) & 0xff; + } + } + } + } + + public int bpp; public int depth; public boolean bigEndian; diff --git a/java/com/tigervnc/rfb/TightDecoder.java b/java/com/tigervnc/rfb/TightDecoder.java index 76986277..6f87ffdc 100644 --- a/java/com/tigervnc/rfb/TightDecoder.java +++ b/java/com/tigervnc/rfb/TightDecoder.java @@ -52,12 +52,13 @@ public class TightDecoder extends Decoder { public void readRect(Rect r, CMsgHandler handler) { InStream is = reader.getInStream(); - int[] buf = reader.getImageBuf(r.width() * r.height()); boolean cutZeros = false; - PixelFormat myFormat = handler.cp.pf(); - int bpp = handler.cp.pf().bpp; + clientpf = handler.getPreferredPF(); + serverpf = handler.cp.pf(); + int bpp = serverpf.bpp; + cutZeros = false; if (bpp == 32) { - if (myFormat.is888()) { + if (serverpf.is888()) { cutZeros = true; } } @@ -76,38 +77,21 @@ public class TightDecoder extends Decoder { // "Fill" compression type. if (comp_ctl == rfbTightFill) { - int pix; + int[] pix = new int[1]; if (cutZeros) { - pix = is.readPixel(3, !bigEndian); + byte[] bytebuf = new byte[3]; + is.readBytes(bytebuf, 0, 3); + serverpf.bufferFromRGB(pix, 0, bytebuf, 0, 1); } else { - pix = (bpp == 8) ? is.readOpaque8() : is.readOpaque24B(); + pix[0] = is.readPixel(serverpf.bpp/8, serverpf.bigEndian); } - handler.fillRect(r, pix); + handler.fillRect(r, pix[0]); return; } // "JPEG" compression type. if (comp_ctl == rfbTightJpeg) { - // Read length - int compressedLen = is.readCompactLength(); - if (compressedLen <= 0) - vlog.info("Incorrect data received from the server."); - - // Allocate netbuf and read in data - byte[] netbuf = new byte[compressedLen]; - is.readBytes(netbuf, 0, compressedLen); - - // Create an Image object from the JPEG data. - BufferedImage jpeg = new BufferedImage(r.width(), r.height(), BufferedImage.TYPE_4BYTE_ABGR_PRE); - jpeg.setAccelerationPriority(1); - try { - jpeg = ImageIO.read(new ByteArrayInputStream(netbuf)); - } catch (java.io.IOException e) { - e.printStackTrace(); - } - jpeg.getRGB(0, 0, r.width(), r.height(), buf, 0, r.width()); - jpeg = null; - handler.imageRect(r, buf); + DECOMPRESS_JPEG_RECT(r, is, handler); return; } @@ -127,12 +111,13 @@ public class TightDecoder extends Decoder { switch (filterId) { case rfbTightFilterPalette: palSize = is.readU8() + 1; + byte[] tightPalette; if (cutZeros) { - is.readPixels(palette, palSize, 3, !bigEndian); + tightPalette = new byte[256 * 3]; + is.readBytes(tightPalette, 0, palSize * 3); + serverpf.bufferFromRGB(palette, 0, tightPalette, 0, palSize); } else { - for (int i = 0; i < palSize; i++) { - palette[i] = (bpp == 8) ? is.readOpaque8() : is.readOpaque24B(); - } + is.readPixels(palette, palSize, serverpf.bpp/8, serverpf.bigEndian); } break; case rfbTightFilterGradient: @@ -166,56 +151,66 @@ public class TightDecoder extends Decoder { input = (ZlibInStream)zis[streamId]; } + // Allocate netbuf and read in data + byte[] netbuf = new byte[dataSize]; + input.readBytes(netbuf, 0, dataSize); + + int stride = r.width(); + int[] buf = reader.getImageBuf(r.area()); + + if (palSize == 0) { // Truecolor data. if (useGradient) { - vlog.info("useGradient"); if (bpp == 32 && cutZeros) { - vlog.info("FilterGradient24"); - FilterGradient24(r, input, dataSize, buf, handler); + FilterGradient24(netbuf, buf, stride, r); } else { - vlog.info("FilterGradient"); - FilterGradient(r, input, dataSize, buf, handler); + FilterGradient(netbuf, buf, stride, r); } } else { + // Copy + int h = r.height(); + int w = r.width(); if (cutZeros) { - input.readPixels(buf, r.area(), 3, !bigEndian); + serverpf.bufferFromRGB(buf, 0, netbuf, 0, w*h); } else { - byte[] netbuf = new byte[dataSize]; - input.readBytes(netbuf, 0, dataSize); for (int i = 0; i < dataSize; i++) buf[i] = netbuf[i] & 0xff; } } } else { - int x, y, b; - int ptr = 0; - int bits; + // Indexed color + int x, h = r.height(), w = r.width(), b, pad = stride - w; + int ptr = 0; + int srcPtr = 0, bits; if (palSize <= 2) { // 2-color palette - int height = r.height(); - int width = r.width(); - for (y = 0; y < height; y++) { - for (x = 0; x < width / 8; x++) { - bits = input.readU8(); + while (h > 0) { + for (x = 0; x < w / 8; x++) { + bits = netbuf[srcPtr++]; for(b = 7; b >= 0; b--) { buf[ptr++] = palette[bits >> b & 1]; } } - if (width % 8 != 0) { - bits = input.readU8(); - for (b = 7; b >= 8 - width % 8; b--) { + if (w % 8 != 0) { + bits = netbuf[srcPtr++]; + for (b = 7; b >= 8 - w % 8; b--) { buf[ptr++] = palette[bits >> b & 1]; } } + ptr += pad; + h--; } } else { // 256-color palette - int area = r.area(); - byte[] netbuf = new byte[area]; - input.readBytes(netbuf, 0, area); - for (int i = 0; i < area; i++) - buf[ptr++] = palette[netbuf[i] & 0xff]; + while (h > 0) { + int endOfRow = ptr + w; + while (ptr < endOfRow) { + buf[ptr++] = palette[netbuf[srcPtr++] & 0xff]; + } + ptr += pad; + h--; + } } } @@ -226,118 +221,129 @@ public class TightDecoder extends Decoder { } } - private CMsgReader reader; - private ZlibInStream[] zis; - static LogWriter vlog = new LogWriter("TightDecoder"); + final private void DECOMPRESS_JPEG_RECT(Rect r, InStream is, CMsgHandler handler) + { + // Read length + int compressedLen = is.readCompactLength(); + if (compressedLen <= 0) + vlog.info("Incorrect data received from the server."); - // - // Decode data processed with the "Gradient" filter. - // + // Allocate netbuf and read in data + byte[] netbuf = new byte[compressedLen]; + is.readBytes(netbuf, 0, compressedLen); + + // Create an Image object from the JPEG data. + int imageType = BufferedImage.TYPE_4BYTE_ABGR_PRE; + + BufferedImage jpeg = + new BufferedImage(r.width(), r.height(), imageType); + jpeg.setAccelerationPriority(1); + try { + jpeg = ImageIO.read(new ByteArrayInputStream(netbuf)); + } catch (java.io.IOException e) { + e.printStackTrace(); + } + int[] buf = reader.getImageBuf(r.area()); + jpeg.getRGB(0, 0, r.width(), r.height(), buf, 0, r.width()); + jpeg = null; + handler.imageRect(r, buf); + } - final private void FilterGradient24(Rect r, InStream is, int dataSize, int[] buf, CMsgHandler handler) { + final private void FilterGradient24(byte[] netbuf, int[] buf, int stride, + Rect r) + { int x, y, c; - int[] prevRow = new int[TIGHT_MAX_WIDTH * 3]; - int[] thisRow = new int[TIGHT_MAX_WIDTH * 3]; - int[] pix = new int[3]; + byte[] prevRow = new byte[TIGHT_MAX_WIDTH*3]; + byte[] thisRow = new byte[TIGHT_MAX_WIDTH*3]; + byte[] pix = new byte[3]; int[] est = new int[3]; - PixelFormat myFormat = handler.cp.pf(); - - // Allocate netbuf and read in data - int[] netbuf = new int[dataSize]; - is.readBytes(netbuf, 0, dataSize); + // Set up shortcut variables int rectHeight = r.height(); int rectWidth = r.width(); for (y = 0; y < rectHeight; y++) { /* First pixel in a row */ for (c = 0; c < 3; c++) { - pix[c] = netbuf[y*rectWidth*3+c] + prevRow[c]; + pix[c] = (byte)(netbuf[y*rectWidth*3+c] + prevRow[c]); thisRow[c] = pix[c]; } - if (myFormat.bigEndian) { - buf[y*rectWidth] = 0xff000000 | (pix[2] & 0xff)<<16 | (pix[1] & 0xff)<<8 | (pix[0] & 0xff); - } else { - buf[y*rectWidth] = 0xff000000 | (pix[0] & 0xff)<<16 | (pix[1] & 0xff)<<8 | (pix[2] & 0xff); - } + serverpf.bufferFromRGB(buf, y*stride, pix, 0, 1); /* Remaining pixels of a row */ for (x = 1; x < rectWidth; x++) { for (c = 0; c < 3; c++) { - est[c] = prevRow[x*3+c] + pix[c] - prevRow[(x-1)*3+c]; + est[c] = (int)(prevRow[x*3+c] + pix[c] - prevRow[(x-1)*3+c]); if (est[c] > 0xFF) { est[c] = 0xFF; } else if (est[c] < 0) { est[c] = 0; } - pix[c] = netbuf[(y*rectWidth+x)*3+c] + est[c]; + pix[c] = (byte)(netbuf[(y*rectWidth+x)*3+c] + est[c]); thisRow[x*3+c] = pix[c]; } - if (myFormat.bigEndian) { - buf[y*rectWidth+x] = 0xff000000 | (pix[2] & 0xff)<<16 | (pix[1] & 0xff)<<8 | (pix[0] & 0xff); - } else { - buf[y*rectWidth+x] = 0xff000000 | (pix[0] & 0xff)<<16 | (pix[1] & 0xff)<<8 | (pix[2] & 0xff); - } + serverpf.bufferFromRGB(buf, y*stride+x, pix, 0, 1); } System.arraycopy(thisRow, 0, prevRow, 0, prevRow.length); } } - final private void FilterGradient(Rect r, InStream is, int dataSize, int[] buf, CMsgHandler handler) { + final private void FilterGradient(byte[] netbuf, int[] buf, int stride, + Rect r) + { int x, y, c; - int[] prevRow = new int[TIGHT_MAX_WIDTH]; - int[] thisRow = new int[TIGHT_MAX_WIDTH]; - int[] pix = new int[3]; + byte[] prevRow = new byte[TIGHT_MAX_WIDTH]; + byte[] thisRow = new byte[TIGHT_MAX_WIDTH]; + byte[] pix = new byte[3]; int[] est = new int[3]; - PixelFormat myFormat = handler.cp.pf(); - - // Allocate netbuf and read in data - int[] netbuf = new int[dataSize]; - is.readBytes(netbuf, 0, dataSize); + // Set up shortcut variables int rectHeight = r.height(); int rectWidth = r.width(); for (y = 0; y < rectHeight; y++) { /* First pixel in a row */ - if (myFormat.bigEndian) { - buf[y*rectWidth] = 0xff000000 | (pix[2] & 0xff)<<16 | (pix[1] & 0xff)<<8 | (pix[0] & 0xff); - } else { - buf[y*rectWidth] = 0xff000000 | (pix[0] & 0xff)<<16 | (pix[1] & 0xff)<<8 | (pix[2] & 0xff); - } + // FIXME + //serverpf.rgbFromBuffer(pix, 0, netbuf, y*rectWidth, 1, cm); for (c = 0; c < 3; c++) pix[c] += prevRow[c]; + System.arraycopy(pix, 0, thisRow, 0, pix.length); + + serverpf.bufferFromRGB(buf, y*stride, pix, 0, 1); + /* Remaining pixels of a row */ for (x = 1; x < rectWidth; x++) { for (c = 0; c < 3; c++) { - est[c] = prevRow[x*3+c] + pix[c] - prevRow[(x-1)*3+c]; - if (est[c] > 255) { - est[c] = 255; + est[c] = (int)(prevRow[x*3+c] + pix[c] - prevRow[(x-1)*3+c]); + if (est[c] > 0xff) { + est[c] = 0xff; } else if (est[c] < 0) { est[c] = 0; } } - // FIXME? - System.arraycopy(pix, 0, netbuf, 0, netbuf.length); + // FIXME + //serverpf.rgbFromBuffer(pix, 0, netbuf, y*rectWidth+x, 1, cm); for (c = 0; c < 3; c++) pix[c] += est[c]; - System.arraycopy(thisRow, x*3, pix, 0, pix.length); + System.arraycopy(pix, 0, thisRow, x*3, pix.length); - if (myFormat.bigEndian) { - buf[y*rectWidth+x] = 0xff000000 | (pix[2] & 0xff)<<16 | (pix[1] & 0xff)<<8 | (pix[0] & 0xff); - } else { - buf[y*rectWidth+x] = 0xff000000 | (pix[0] & 0xff)<<16 | (pix[1] & 0xff)<<8 | (pix[2] & 0xff); - } + serverpf.bufferFromRGB(buf, y*stride+x, pix, 0, 1); } System.arraycopy(thisRow, 0, prevRow, 0, prevRow.length); } } + private CMsgReader reader; + private ZlibInStream[] zis; + private PixelFormat serverpf; + private PixelFormat clientpf; + static LogWriter vlog = new LogWriter("TightDecoder"); + } diff --git a/java/com/tigervnc/vncviewer/CConn.java b/java/com/tigervnc/vncviewer/CConn.java index 136ad7c5..ae03b524 100644 --- a/java/com/tigervnc/vncviewer/CConn.java +++ b/java/com/tigervnc/vncviewer/CConn.java @@ -309,7 +309,7 @@ public class CConn extends CConnection desktop = new DesktopWindow(cp.width, cp.height, serverPF, this); //desktopEventHandler = desktop.setEventHandler(this); //desktop.addEventMask(KeyPressMask | KeyReleaseMask); - fullColourPF = desktop.getPF(); + fullColourPF = desktop.getPreferredPF(); if (!serverPF.trueColour) fullColour = true; recreateViewport(); @@ -468,13 +468,19 @@ public class CConn extends CConnection public void fillRect(Rect r, int p) { desktop.fillRect(r.tl.x, r.tl.y, r.width(), r.height(), p); } + public void imageRect(Rect r, int[] p) { desktop.imageRect(r.tl.x, r.tl.y, r.width(), r.height(), p); } + public void copyRect(Rect r, int sx, int sy) { desktop.copyRect(r.tl.x, r.tl.y, r.width(), r.height(), sx, sy); } + public PixelFormat getPreferredPF() { + return fullColourPF; + } + public void setCursor(int width, int height, Point hotspot, int[] data, byte[] mask) { desktop.setCursor(width, height, hotspot, data, mask); diff --git a/java/com/tigervnc/vncviewer/DesktopWindow.java b/java/com/tigervnc/vncviewer/DesktopWindow.java index c0071dad..087b58fd 100644 --- a/java/com/tigervnc/vncviewer/DesktopWindow.java +++ b/java/com/tigervnc/vncviewer/DesktopWindow.java @@ -179,6 +179,10 @@ class DesktopWindow extends JPanel implements return; } + public PixelFormat getPreferredPF() { + return im.getNativePF(); + } + // setColourMapEntries() changes some of the entries in the colourmap. // Unfortunately these messages are often sent one at a time, so we delay the // settings taking effect unless the whole colourmap has changed. This is diff --git a/java/com/tigervnc/vncviewer/PixelBufferImage.java b/java/com/tigervnc/vncviewer/PixelBufferImage.java index 78697d8d..648e6388 100644 --- a/java/com/tigervnc/vncviewer/PixelBufferImage.java +++ b/java/com/tigervnc/vncviewer/PixelBufferImage.java @@ -72,7 +72,7 @@ public class PixelBufferImage extends PixelBuffer implements ImageProducer data, width() * i, copyWidth); } - private PixelFormat getNativePF() { + public PixelFormat getNativePF() { PixelFormat pf; cm = java.awt.Toolkit.getDefaultToolkit().getColorModel(); if (cm.getColorSpace().getType() == java.awt.color.ColorSpace.TYPE_RGB) { -- 2.39.5