/**
* Copyright (C) 2012 Iordan Iordanov
* Copyright (C) 2009 Michael A. MacDonald
*
* 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.bVNC;
import com.iiordanov.android.drawing.OverlappingCopy;
import com.iiordanov.android.drawing.RectList;
import com.iiordanov.util.ObjectPool;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.Log;
class LargeBitmapData extends AbstractBitmapData {
/**
* Multiply this times total number of pixels to get estimate of process size with all buffers plus
* safety factor
*/
static int CAPACITY_MULTIPLIER = 18;
double scaleMultiplier = 0;
int scrolledToX;
int scrolledToY;
private Rect bitmapRect;
private Paint defaultPaint;
private RectList invalidList;
private RectList pendingList;
private int capacity;
private int displayWidth;
private int displayHeight;
/**
* Pool of temporary rectangle objects. Need to synchronize externally access from
* multiple threads.
*/
private static ObjectPool<Rect> rectPool = new ObjectPool<Rect>() {
/* (non-Javadoc)
* @see com.antlersoft.util.ObjectPool#itemForPool()
*/
@Override
protected Rect itemForPool() {
return new Rect();
}
};
class LargeBitmapDrawable extends AbstractBitmapDrawable {
LargeBitmapDrawable() {
super(LargeBitmapData.this);
}
/* (non-Javadoc)
* @see android.graphics.drawable.DrawableContainer#draw(android.graphics.Canvas)
*/
@Override
public void draw(Canvas canvas) {
//android.util.Log.i("LBM", "Drawing "+xoffset+" "+yoffset);
int xoff, yoff;
synchronized ( LargeBitmapData.this ) {
xoff=xoffset;
yoff=yoffset;
}
draw(canvas, xoff, yoff);
}
}
/**
*
* @param p Protocol implementation
* @param c View that will display screen
* @param displayWidth
* @param displayHeight
* @param capacity Max process heap size in bytes
*/
LargeBitmapData(RfbConnectable p, RemoteCanvas c, int displayWidth, int displayHeight, int capacity) {
super(p,c);
this.capacity = capacity;
this.displayWidth = displayWidth;
this.displayHeight = displayHeight;
initializeLargeBitmapData();
}
@Override
AbstractBitmapDrawable createDrawable() {
return new LargeBitmapDrawable();
}
/**
*
* @return The smallest scale supported by the implementation; the scale at which
* the bitmap would be smaller than the screen
*/
float getMinimumScale() {
return Math.max((float)vncCanvas.getWidth()/bitmapwidth, (float)vncCanvas.getHeight()/bitmapheight);
}
/* (non-Javadoc)
* @see com.iiordanov.bVNC.AbstractBitmapData#copyRect(android.graphics.Rect, android.graphics.Rect, android.graphics.Paint)
*/
@Override
public void copyRect(int sx, int sy, int dx, int dy, int w, int h) {
int srcOffset, dstOffset;
int dstH = h;
int dstW = w;
int xo, yo;
int startSrcY, endSrcY, dstY, deltaY;
if (sy > dy) {
startSrcY = sy;
endSrcY = sy + dstH;
dstY = dy;
deltaY = +1;
} else {
startSrcY = sy + dstH - 1;
endSrcY = sy - 1;
dstY = dy + dstH - 1;
deltaY = -1;
}
for (int y = startSrcY; y != endSrcY; y += deltaY) {
srcOffset = offset(sx, y);
dstOffset = offset(dx, dstY);
xo = sx-xoffset;
if (xo < 0) xo = 0;
yo = y-yoffset;
if (yo < 0) yo = 0;
if (sx + dstW > bitmapwidth) dstW = bitmapwidth - sx;
try {
mbitmap.getPixels(bitmapPixels, srcOffset, bitmapwidth, xo, yo, dstW, 1);
System.arraycopy(bitmapPixels, srcOffset, bitmapPixels, dstOffset, dstW);
} catch (Exception e) {
// There was an index out of bounds exception, but we continue copying what we can.
e.printStackTrace();
}
dstY += deltaY;
}
updateBitmap(dx, dy, dstW, dstH);
}
/* (non-Javadoc)
* @see com.iiordanov.bVNC.AbstractBitmapData#drawRect(int, int, int, int, android.graphics.Paint)
*/
@Override
void drawRect(int x, int y, int w, int h, Paint paint) {
x-=xoffset;
y-=yoffset;
memGraphics.drawRect(x, y, x+w, y+h, paint);
}
/* (non-Javadoc)
* @see com.iiordanov.bVNC.AbstractBitmapData#offset(int, int)
*/
@Override
public int offset(int x, int y) {
return (y - yoffset) * bitmapwidth + x - xoffset;
}
/* (non-Javadoc)
* @see com.iiordanov.bVNC.AbstractBitmapData#scrollChanged(int, int)
*/
@Override
synchronized void scrollChanged(int newx, int newy) {
//android.util.Log.i("LBM","scroll "+newx+" "+newy);
int newScrolledToX = scrolledToX;
int newScrolledToY = scrolledToY;
int visibleWidth = vncCanvas.getVisibleWidth();
int visibleHeight = vncCanvas.getVisibleHeight();
if (newx - xoffset < 0) {
newScrolledToX = newx + visibleWidth / 2 - bitmapwidth / 2;
if (newScrolledToX < 0)
newScrolledToX = 0;
} else if (newx - xoffset + visibleWidth > bitmapwidth) {
newScrolledToX = newx + visibleWidth / 2 - bitmapwidth / 2;
if (newScrolledToX + bitmapwidth > framebufferwidth)
newScrolledToX = framebufferwidth - bitmapwidth;
}
if (newy - yoffset < 0 ) {
newScrolledToY = newy + visibleHeight / 2 - bitmapheight / 2;
if (newScrolledToY < 0)
newScrolledToY = 0;
} else if (newy - yoffset + visibleHeight > bitmapheight) {
newScrolledToY = newy + visibleHeight / 2 - bitmapheight / 2;
if (newScrolledToY + bitmapheight > framebufferheight)
newScrolledToY = framebufferheight - bitmapheight;
}
if (newScrolledToX != scrolledToX || newScrolledToY != scrolledToY) {
scrolledToX = newScrolledToX;
scrolledToY = newScrolledToY;
if ( waitingForInput)
syncScroll();
}
}
/* (non-Javadoc)
* @see com.iiordanov.bVNC.AbstractBitmapData#updateBitmap(int, int, int, int)
*/
@Override
public void updateBitmap(int x, int y, int w, int h) {
int xo = x-xoffset;
if (xo < 0) xo = 0;
int yo = y-yoffset;
if (yo < 0) yo = 0;
if (x + w > xoffset + bitmapwidth) w = xoffset + bitmapwidth - x;
if (y + h > yoffset + bitmapheight) h = yoffset + bitmapheight - y;
try {
mbitmap.setPixels(bitmapPixels, offset(x,y), bitmapwidth, xo, yo, w, h);
} catch (IllegalArgumentException e) {
// Do not update the bitmap if the coordinates are out of bounds.
e.printStackTrace();
}
}
/* (non-Javadoc)
* @see com.iiordanov.bVNC.AbstractBitmapData#updateBitmap(Bitmap, int, int, int, int)
*/
@Override
public void updateBitmap(Bitmap b, int x, int y, int w, int h) {
memGraphics.drawBitmap(b, x - xoffset, y - yoffset, null);
}
/* (non-Javadoc)
* @see com.iiordanov.bVNC.AbstractBitmapData#validDraw(int, int, int, int)
*/
@Override
public synchronized boolean validDraw(int x, int y, int w, int h) {
boolean result = x-xoffset>=0 && x-xoffset+w<=bitmapwidth && y-yoffset>=0 && y-yoffset+h<=bitmapheight;
//android.util.Log.e("LBM", "Validate Drawing x:"+x+" y:"+y+" w:"+w+" h:"+h+" xoff:"+xoffset+" yoff:"+yoffset+" "+(x-xoffset>=0 && x-xoffset+w<=bitmapwidth && y-yoffset>=0 && y-yoffset+h<=bitmapheight));
ObjectPool.Entry<Rect> entry = rectPool.reserve();
Rect r = entry.get();
r.set(x, y, x+w, y+h);
pendingList.subtract(r);
if (!result)
invalidList.add(r);
else
invalidList.subtract(r);
rectPool.release(entry);
return result;
}
/* (non-Javadoc)
* @see com.iiordanov.bVNC.AbstractBitmapData#prepareFullUpdateRequest(boolean)
*/
@Override
public synchronized void prepareFullUpdateRequest(boolean incremental) {
if (! incremental) {
ObjectPool.Entry<Rect> entry = rectPool.reserve();
Rect r = entry.get();
r.left=xoffset;
r.top=yoffset;
r.right=xoffset + bitmapwidth;
r.bottom=yoffset + bitmapheight;
pendingList.add(r);
invalidList.add(r);
rectPool.release(entry);
}
}
/* (non-Javadoc)
* @see com.iiordanov.bVNC.AbstractBitmapData#syncScroll()
*/
@Override
synchronized void syncScroll() {
int deltaX = xoffset - scrolledToX;
int deltaY = yoffset - scrolledToY;
xoffset=scrolledToX;
yoffset=scrolledToY;
bitmapRect.top=scrolledToY;
bitmapRect.bottom=scrolledToY+bitmapheight;
bitmapRect.left=scrolledToX;
bitmapRect.right=scrolledToX+bitmapwidth;
invalidList.intersect(bitmapRect);
if ( deltaX != 0 || deltaY != 0)
{
boolean didOverlapping = false;
if (Math.abs(deltaX) < bitmapwidth && Math.abs(deltaY) < bitmapheight) {
ObjectPool.Entry<Rect> sourceEntry = rectPool.reserve();
ObjectPool.Entry<Rect> addedEntry = rectPool.reserve();
try
{
Rect added = addedEntry.get();
Rect sourceRect = sourceEntry.get();
sourceRect.set(deltaX<0 ? -deltaX : 0,
deltaY<0 ? -deltaY : 0,
deltaX<0 ? bitmapwidth : bitmapwidth - deltaX,
deltaY < 0 ? bitmapheight : bitmapheight - deltaY);
if (! invalidList.testIntersect(sourceRect)) {
didOverlapping = true;
OverlappingCopy.Copy(mbitmap, memGraphics, defaultPaint, sourceRect, deltaX + sourceRect.left, deltaY + sourceRect.top, rectPool);
// Write request for side pixels
if (deltaX != 0) {
added.left = deltaX < 0 ? bitmapRect.right + deltaX : bitmapRect.left;
added.right = added.left + Math.abs(deltaX);
added.top = bitmapRect.top;
added.bottom = bitmapRect.bottom;
invalidList.add(added);
}
if (deltaY != 0) {
added.left = deltaX < 0 ? bitmapRect.left : bitmapRect.left + deltaX;
added.top = deltaY < 0 ? bitmapRect.bottom + deltaY : bitmapRect.top;
added.right = added.left + bitmapwidth - Math.abs(deltaX);
added.bottom = added.top + Math.abs(deltaY);
invalidList.add(added);
}
}
}
finally {
rectPool.release(addedEntry);
rectPool.release(sourceEntry);
}
}
if (! didOverlapping)
{
mbitmap.eraseColor(Color.GREEN);
vncCanvas.writeFullUpdateRequest(false);
}
}
int size = pendingList.getSize();
for (int i=0; i<size; i++) {
invalidList.subtract(pendingList.get(i));
}
size = invalidList.getSize();
for (int i=0; i<size; i++) {
Rect invalidRect = invalidList.get(i);
rfb.writeFramebufferUpdateRequest(invalidRect.left, invalidRect.top, invalidRect.right-invalidRect.left, invalidRect.bottom-invalidRect.top, false);
pendingList.add(invalidRect);
}
waitingForInput=true;
//android.util.Log.i("LBM", "pending "+pendingList.toString() + "invalid "+invalidList.toString());
}
/* (non-Javadoc)
* @see com.iiordanov.bVNC.AbstractBitmapData#frameBufferSizeChanged(RfbProto)
*/
@Override
public void frameBufferSizeChanged () {
xoffset = 0;
yoffset = 0;
scrolledToX = 0;
scrolledToY = 0;
framebufferwidth = rfb.framebufferWidth();
framebufferheight = rfb.framebufferHeight();
initializeLargeBitmapData();
}
/**
* This function initializes the LBM, increasing the CAPACITY_MULTIPLIER until it doesn't run out of memory while
* initializing.
*/
void initializeLargeBitmapData() {
boolean tryAgain = true;
while (CAPACITY_MULTIPLIER <= 30) {
try {
allocateObjects();
// If we got to this point without throwing an out of memory exception, we break out of the loop.
tryAgain = false;
break;
} catch (Throwable e) {
// We ran out of memory, so try adjusting CAPACITY_MULTIPLIER
CAPACITY_MULTIPLIER = CAPACITY_MULTIPLIER + 10;
// Try to free up some memory.
System.gc();
// Wait a second for the system to recover.
try { Thread.sleep(500); } catch (InterruptedException e1) { }
}
}
if (tryAgain) {
// Try forcing scaleMultiplier to be 1
CAPACITY_MULTIPLIER = 1000;
allocateObjects();
}
}
void allocateObjects () {
dispose();
invalidList = null;
pendingList = null;
bitmapRect = null;
defaultPaint = null;
// Try to free up some memory.
System.gc();
scaleMultiplier = Math.sqrt((double)(capacity * 1024 * 1024) /
(double)(CAPACITY_MULTIPLIER * framebufferwidth * framebufferheight));
if (scaleMultiplier > 1)
scaleMultiplier = 1;
bitmapwidth=(int)((double)framebufferwidth * scaleMultiplier);
if (bitmapwidth < displayWidth*1.2)
bitmapwidth = (int)(displayWidth*1.2);
bitmapheight=(int)((double)framebufferheight * scaleMultiplier);
if (bitmapheight < displayHeight*1.2)
bitmapheight = (int)(displayHeight*1.2);
android.util.Log.i("LBM", "bitmapsize = ("+bitmapwidth+","+bitmapheight+")");
mbitmap = Bitmap.createBitmap(bitmapwidth, bitmapheight, Bitmap.Config.RGB_565);
memGraphics = new Canvas(mbitmap);
bitmapPixels = new int[bitmapwidth * bitmapheight];
invalidList = new RectList(rectPool);
pendingList = new RectList(rectPool);
bitmapRect = new Rect(0, 0, bitmapwidth, bitmapheight);
defaultPaint = new Paint();
drawable = createDrawable();
drawable.startDrawing();
}
}