/* Copyright (C) 2000-2003 Constantin Kaplinsky. All Rights Reserved. * Copyright 2004-2005 Cendio AB. * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this software; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, * USA. */ package com.iiordanov.tigervnc.rfb; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import com.iiordanov.bVNC.AbstractBitmapData; import com.iiordanov.bVNC.RemoteCanvas; import com.iiordanov.tigervnc.rdr.InStream; import com.iiordanov.tigervnc.rdr.ZlibInStream; import java.util.ArrayList; import java.io.InputStream; 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; BitmapFactory.Options bitmapopts; byte[] netbuf; int[] pix; byte[] bytebuf; int[] palette; byte[] tightPalette; byte[] prevRow; byte[] thisRow; byte[] bpix; int[] est; //final static Toolkit tk = Toolkit.getDefaultToolkit(); public TightDecoder(CMsgReader reader_, RemoteCanvas c) { bitmapopts = new BitmapFactory.Options(); bitmapopts.inPurgeable = false; bitmapopts.inDither = false; bitmapopts.inTempStorage = new byte[32768]; bitmapopts.inPreferredConfig= Bitmap.Config.RGB_565; bitmapopts.inScaled = false; reader = reader_; zis = new ZlibInStream[4]; for (int i = 0; i < 4; i++) zis[i] = new ZlibInStream(); netbuf = new byte[1024]; pix = new int[1]; bytebuf = new byte[3]; palette = new int[256]; tightPalette = new byte[256 * 3]; prevRow = new byte[TIGHT_MAX_WIDTH*3]; thisRow = new byte[TIGHT_MAX_WIDTH*3]; bpix = new byte[3]; est = new int[3]; vncCanvas = c; } public TightDecoder(CMsgReader reader_) { bitmapopts = new BitmapFactory.Options(); bitmapopts.inPurgeable = false; bitmapopts.inInputShareable = true; bitmapopts.inDither = false; bitmapopts.inTempStorage = new byte[32768]; reader = reader_; zis = new ZlibInStream[4]; for (int i = 0; i < 4; i++) zis[i] = new ZlibInStream(); netbuf = new byte[1024]; pix = new int[1]; bytebuf = new byte[3]; palette = new int[256]; tightPalette = new byte[256 * 3]; prevRow = new byte[TIGHT_MAX_WIDTH*3]; thisRow = new byte[TIGHT_MAX_WIDTH*3]; bpix = new byte[3]; est = new int[3]; } /* public void readRectNew(Rect r, CMsgHandler handler) { if (r.tl.x + r.width() > vncCanvas.bitmapData.bmWidth()) r.setXYWH(r.tl.x, r.tl.y, vncCanvas.bitmapData.bmWidth() - r.tl.x, r.height()); if (r.tl.y + r.height() > vncCanvas.bitmapData.bmHeight()) r.setXYWH(r.tl.x, r.tl.y, r.width(), vncCanvas.bitmapData.bmHeight() - r.tl.y); try { vncCanvas.handleTightRect(r.tl.x, r.tl.y, r.width(), r.height(), reader); } catch (java.lang.Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } */ 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; 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) { if (cutZeros) { 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; boolean useGradient = false; if ((comp_ctl & rfbTightExplicitFilter) != 0) { int filterId = is.readU8(); switch (filterId) { case rfbTightFilterPalette: palSize = is.readU8() + 1; if (cutZeros) { 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 if (dataSize > netbuf.length) 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(); // Allocate netbuf and read in data if (compressedLen > netbuf.length) netbuf = new byte[compressedLen]; is.readBytes(netbuf, 0, compressedLen); // Decode JPEG data Bitmap tightBitmap = BitmapFactory.decodeByteArray(netbuf, 0, compressedLen, bitmapopts); /* int w = r.width(); int h = r.height(); int[] buf = reader.getImageBuf(w*h); // Copy decoded data into buf. tightBitmap.getPixels(buf, 0, w, 0, 0, w, h); handler.imageRect(r, buf); */ handler.imageRect(r, tightBitmap); // To avoid running out of memory, recycle bitmap immediately. tightBitmap.recycle(); } final private void FilterGradient24(byte[] netbuf, int[] buf, int stride, Rect r) { int x, y, c; // 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++) { bpix[c] = (byte)(netbuf[y*rectWidth*3+c] + prevRow[c]); thisRow[c] = bpix[c]; } serverpf.bufferFromRGB(buf, y*stride, bpix, 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] + bpix[c] - prevRow[(x-1)*3+c]); if (est[c] > 0xFF) { est[c] = 0xFF; } else if (est[c] < 0) { est[c] = 0; } bpix[c] = (byte)(netbuf[(y*rectWidth+x)*3+c] + est[c]); thisRow[x*3+c] = bpix[c]; } serverpf.bufferFromRGB(buf, y*stride+x, bpix, 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; // 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(bpix, 0, netbuf, y*rectWidth, 1, cm); for (c = 0; c < 3; c++) bpix[c] += prevRow[c]; System.arraycopy(bpix, 0, thisRow, 0, bpix.length); serverpf.bufferFromRGB(buf, y*stride, bpix, 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] + bpix[c] - prevRow[(x-1)*3+c]); if (est[c] > 0xff) { est[c] = 0xff; } else if (est[c] < 0) { est[c] = 0; } } // FIXME //serverpf.rgbFromBuffer(bpix, 0, netbuf, y*rectWidth+x, 1, cm); for (c = 0; c < 3; c++) bpix[c] += est[c]; System.arraycopy(bpix, 0, thisRow, x*3, bpix.length); serverpf.bufferFromRGB(buf, y*stride+x, bpix, 0, 1); } System.arraycopy(thisRow, 0, prevRow, 0, prevRow.length); } } RemoteCanvas vncCanvas; private CMsgReader reader; private ZlibInStream[] zis; private PixelFormat serverpf; private PixelFormat clientpf; static LogWriter vlog = new LogWriter("TightDecoder"); }