/*
* Orion Viewer - pdf, djvu, xps and cbz file viewer for android devices
*
* Copyright (C) 2011-2013 Michael Bogdanov & Co
*
* This program 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 3 of the License, or
* (at your option) any later version.
*
* This program 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 program. If not, see <http://www.gnu.org/licenses/>.
*/
package universe.constellation.orion.viewer;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.os.Debug;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import universe.constellation.orion.viewer.view.Renderer;
import universe.constellation.orion.viewer.view.Scene;
/**
* User: mike
* Date: 19.10.11
* Time: 9:52
*/
public class RenderThread extends Thread implements Renderer {
private static final int CACHE_SIZE = 4;
private static final int FUTURE_COUNT = 1;
protected LayoutStrategy layout;
private LinkedList<CacheInfo> cachedBitmaps = new LinkedList<>();
private LayoutPosition currentPosition;
private LayoutPosition lastEvent;
protected DocumentWrapper doc;
private boolean executeInSeparateThread;
private boolean stopped;
private OrionViewerActivity activity;
private Scene fullScene;
RenderThread(OrionViewerActivity activity, LayoutStrategy layout, DocumentWrapper doc, Scene fullScene) {
this(activity, layout, doc, true, fullScene);
}
public RenderThread(OrionViewerActivity activity, LayoutStrategy layout, DocumentWrapper doc, boolean executeInSeparateThread, Scene scene) {
this.layout = layout;
this.doc = doc;
this.activity = activity;
this.executeInSeparateThread = executeInSeparateThread;
this.fullScene = scene;
Common.d("RenderThread was created successfully");
}
@Override
public void invalidateCache() {
synchronized (this) {
for (CacheInfo next : cachedBitmaps) {
next.setValid(false);
}
Common.d("Cache invalidated");
}
}
@Override
public void startRenreder() {
Common.d("Starting renderer");
start();
}
public void cleanCache() {
synchronized (this) {
//if(clearCache) {
// clearCache = false;
for (CacheInfo cacheInfo : cachedBitmaps) {
//cacheInfo.bitmap.recycle();
cacheInfo.bitmap = null;
}
Common.d("Allocated heap size: " + Common.memoryInMB(Debug.getNativeHeapAllocatedSize() - Debug.getNativeHeapFreeSize()));
cachedBitmaps.clear();
Common.d("Cache is cleared!");
//}
currentPosition = null;
//clearCache = true;
}
}
@Override
public void stopRenderer() {
synchronized (this) {
stopped = true;
cleanCache();
notify();
}
}
@Override
public void onPause() {
// synchronized (this) {
// paused = true;
// }
}
@Override
public void onResume() {
synchronized (this) {
notify();
}
}
public void run() {
int futureIndex = 0;
LayoutPosition curPos = null;
while (!stopped) {
Common.d("Allocated heap size " + Common.memoryInMB(Debug.getNativeHeapAllocatedSize() - Debug.getNativeHeapFreeSize()));
int rotation;
synchronized (this) {
if (lastEvent != null) {
currentPosition = lastEvent;
lastEvent = null;
futureIndex = 0;
curPos = currentPosition;
}
//keep it here
rotation = layout.getRotation();
if (currentPosition == null || futureIndex > FUTURE_COUNT || currentPosition.screenWidth == 0 || currentPosition.screenHeight == 0) {
try {
Common.d("WAITING...");
wait();
} catch (InterruptedException e) {
Common.d(e);
}
Common.d("AWAKENING!!!");
continue;
} else {
//will cache next page
Common.d("Future index is " + futureIndex);
if (futureIndex != 0) {
curPos = curPos.clone();
layout.nextPage(curPos);
}
}
}
Common.d("rotation = " + rotation);
renderInCurrentThread(futureIndex == 0, curPos, rotation);
futureIndex++;
}
}
protected Bitmap renderInCurrentThread(boolean flushBitmap, LayoutPosition curPos, int rotation) {
CacheInfo resultEntry = null;
Common.d("Orion: rendering position: " + curPos);
if (curPos != null) {
//try to find result in cache
for (Iterator<CacheInfo> iterator = cachedBitmaps.iterator(); iterator.hasNext(); ) {
CacheInfo cacheInfo = iterator.next();
if (cacheInfo.isValid() && cacheInfo.info.equals(curPos)) {
resultEntry = cacheInfo;
//relocate info to end of cache
iterator.remove();
cachedBitmaps.add(cacheInfo);
break;
}
}
if (resultEntry == null) {
//render page
resultEntry = render(curPos, rotation);
synchronized (this) {
cachedBitmaps.add(resultEntry);
}
}
if (flushBitmap) {
final Bitmap bitmap = resultEntry.bitmap;
Common.d("Sending Bitmap");
final CountDownLatch mutex = new CountDownLatch(1);
final LayoutPosition info = curPos;
if (!executeInSeparateThread) {
fullScene.onNewImage(bitmap, info, mutex);
activity.getDevice().flushBitmap();
mutex.countDown();
} else {
activity.runOnUiThread(new Runnable() {
public void run() {
fullScene.onNewImage(bitmap, info, mutex);
activity.getDevice().flushBitmap();
}
});
}
try {
mutex.await(1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Common.d(e);
}
}
}
return resultEntry.bitmap;
}
private CacheInfo render(LayoutPosition curPos, int rotation) {
CacheInfo resultEntry;
int width = curPos.x.screenDimension;
int height = curPos.y.screenDimension;
int screenWidth = curPos.screenWidth;
int screenHeight = curPos.screenHeight;
Bitmap bitmap = null;
if (cachedBitmaps.size() >= CACHE_SIZE) {
CacheInfo info = cachedBitmaps.removeFirst();
info.setValid(true);
if (screenWidth == info.bitmap.getWidth() && screenHeight == info.bitmap.getHeight() /*|| rotation != 0 && width == info.bitmap.getHeight() && height == info.bitmap.getWidth()*/) {
bitmap = info.bitmap;
} else {
info.bitmap.recycle(); //todo recycle from ui
info.bitmap = null;
}
}
if (bitmap == null) {
bitmap = Bitmap.createBitmap(screenWidth, screenHeight, Bitmap.Config.ARGB_8888);
} else {
Common.d("Using cached bitmap " + bitmap);
}
Point leftTopCorner = layout.convertToPoint(curPos);
doc.renderPage(curPos.pageNumber, bitmap, curPos.docZoom, leftTopCorner.x, leftTopCorner.y, leftTopCorner.x + width, leftTopCorner.y + height);
resultEntry = new CacheInfo(curPos, bitmap);
return resultEntry;
}
@Override
public void render(LayoutPosition lastInfo) {
lastInfo = lastInfo.clone();
synchronized (this) {
lastEvent = lastInfo;
notify();
}
}
static class CacheInfo {
CacheInfo(LayoutPosition info, Bitmap bitmap) {
this.info = info;
this.bitmap = bitmap;
}
private LayoutPosition info;
private Bitmap bitmap;
private boolean valid = true;
public LayoutPosition getInfo() {
return info;
}
public Bitmap getBitmap() {
return bitmap;
}
boolean isValid() {
return valid;
}
void setValid(boolean valid) {
this.valid = valid;
}
}
}