/* Copyright (C) 2000-2003 Constantin Kaplinsky. All Rights Reserved. * Copyright 2004-2005 Cendio AB. * Copyright (C) 2011 D. R. Commander. All Rights Reserved. * Copyright (C) 2011-2012 Brian P. Hinz * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA. */ package com.tigervnc.rfb; import com.tigervnc.rdr.InStream; import com.tigervnc.rdr.ZlibInStream; import java.util.ArrayList; import java.io.InputStream; import java.awt.image.*; import java.awt.*; public class TightDecoder extends Decoder { final static int TIGHT_MAX_WIDTH = 2048; // Compression control final static int rfbTightExplicitFilter = 0x04; final static int rfbTightFill = 0x08; final static int rfbTightJpeg = 0x09; final static int rfbTightMaxSubencoding = 0x09; // Filters to improve compression efficiency final static int rfbTightFilterCopy = 0x00; final static int rfbTightFilterPalette = 0x01; final static int rfbTightFilterGradient = 0x02; final static int rfbTightMinToCompress = 12; final static Toolkit tk = Toolkit.getDefaultToolkit(); public TightDecoder(CMsgReader reader_) { reader = reader_; zis = new ZlibInStream[4]; for (int i = 0; i < 4; i++) zis[i] = new ZlibInStream(); } public void readRect(Rect r, CMsgHandler handler) { InStream is = reader.getInStream(); boolean cutZeros = false; clientpf = handler.getPreferredPF(); serverpf = handler.cp.pf(); int bpp = serverpf.bpp; cutZeros = false; if (bpp == 32) { if (serverpf.is888()) { cutZeros = true; } } int comp_ctl = is.readU8(); boolean bigEndian = handler.cp.pf().bigEndian; // Flush zlib streams if we are told by the server to do so. for (int i = 0; i < 4; i++) { if ((comp_ctl & 1) != 0) { zis[i].reset(); } comp_ctl >>= 1; } // "Fill" compression type. if (comp_ctl == rfbTightFill) { int[] pix = new int[1]; if (cutZeros) { byte[] bytebuf = new byte[3]; is.readBytes(bytebuf, 0, 3); serverpf.bufferFromRGB(pix, 0, bytebuf, 0, 1); } else { pix[0] = is.readPixel(serverpf.bpp/8, serverpf.bigEndian); } handler.fillRect(r, pix[0]); return; } // "JPEG" compression type. if (comp_ctl == rfbTightJpeg) { DECOMPRESS_JPEG_RECT(r, is, handler); return; } // Quit on unsupported compression type. if (comp_ctl > rfbTightMaxSubencoding) { throw new Exception("TightDecoder: bad subencoding value received"); } // "Basic" compression type. int palSize = 0; int[] palette = new int[256]; boolean useGradient = false; if ((comp_ctl & rfbTightExplicitFilter) != 0) { int filterId = is.readU8(); switch (filterId) { case rfbTightFilterPalette: palSize = is.readU8() + 1; byte[] tightPalette; if (cutZeros) { tightPalette = new byte[256 * 3]; is.readBytes(tightPalette, 0, palSize * 3); serverpf.bufferFromRGB(palette, 0, tightPalette, 0, palSize); } else { is.readPixels(palette, palSize, serverpf.bpp/8, serverpf.bigEndian); } break; case rfbTightFilterGradient: useGradient = true; break; case rfbTightFilterCopy: break; default: throw new Exception("TightDecoder: unknown filter code recieved"); } } int bppp = bpp; if (palSize != 0) { bppp = (palSize <= 2) ? 1 : 8; } else if (cutZeros) { bppp = 24; } // Determine if the data should be decompressed or just copied. int rowSize = (r.width() * bppp + 7) / 8; int dataSize = r.height() * rowSize; int streamId = -1; InStream input; if (dataSize < rfbTightMinToCompress) { input = is; } else { int length = is.readCompactLength(); streamId = comp_ctl & 0x03; zis[streamId].setUnderlying(is, length); 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) { if (bpp == 32 && cutZeros) { FilterGradient24(netbuf, buf, stride, r); } else { FilterGradient(netbuf, buf, stride, r); } } else { // Copy int h = r.height(); int ptr = 0; int srcPtr = 0; int w = r.width(); if (cutZeros) { serverpf.bufferFromRGB(buf, ptr, netbuf, srcPtr, w*h); } else { int pixelSize = (bpp >= 24) ? 3 : bpp/8; while (h > 0) { for (int i = 0; i < w; i++) { if (bpp == 8) { buf[ptr+i] = netbuf[srcPtr+i] & 0xff; } else { for (int j = pixelSize-1; j >= 0; j--) buf[ptr+i] |= ((netbuf[srcPtr+i+j] & 0xff) << j*8); } } ptr += stride; srcPtr += w * pixelSize; h--; } } } } else { // 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 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 (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 while (h > 0) { int endOfRow = ptr + w; while (ptr < endOfRow) { buf[ptr++] = palette[netbuf[srcPtr++] & 0xff]; } ptr += pad; h--; } } } handler.imageRect(r, buf); if (streamId != -1) { zis[streamId].reset(); } } 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."); // Allocate netbuf and read in data byte[] netbuf = new byte[compressedLen]; is.readBytes(netbuf, 0, compressedLen); // Create an Image object from the JPEG data. Image jpeg = tk.createImage(netbuf); jpeg.setAccelerationPriority(1); handler.imageRect(r, jpeg); jpeg.flush(); } final private void FilterGradient24(byte[] netbuf, int[] buf, int stride, Rect r) { int x, y, c; 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]; // 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] = (byte)(netbuf[y*rectWidth*3+c] + prevRow[c]); thisRow[c] = pix[c]; } 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] = (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] = (byte)(netbuf[(y*rectWidth+x)*3+c] + est[c]); thisRow[x*3+c] = pix[c]; } serverpf.bufferFromRGB(buf, y*stride+x, pix, 0, 1); } System.arraycopy(thisRow, 0, prevRow, 0, prevRow.length); } } final private void FilterGradient(byte[] netbuf, int[] buf, int stride, Rect r) { int x, y, c; byte[] prevRow = new byte[TIGHT_MAX_WIDTH]; byte[] thisRow = new byte[TIGHT_MAX_WIDTH]; byte[] pix = new byte[3]; int[] est = new int[3]; // Set up shortcut variables int rectHeight = r.height(); int rectWidth = r.width(); for (y = 0; y < rectHeight; y++) { /* First pixel in a row */ // 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] = (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 //serverpf.rgbFromBuffer(pix, 0, netbuf, y*rectWidth+x, 1, cm); for (c = 0; c < 3; c++) pix[c] += est[c]; System.arraycopy(pix, 0, thisRow, x*3, pix.length); 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"); }