package org.ebookdroid.core;
import org.ebookdroid.common.bitmaps.BitmapManager;
import org.ebookdroid.common.bitmaps.ByteBufferBitmap;
import org.ebookdroid.common.bitmaps.ByteBufferManager;
import org.ebookdroid.common.bitmaps.GLBitmaps;
import org.ebookdroid.common.cache.DocumentCacheFile.PageInfo;
import org.ebookdroid.common.settings.AppSettings;
import org.ebookdroid.common.settings.books.BookSettings;
import org.ebookdroid.core.codec.CodecPage;
import org.ebookdroid.core.models.DecodingProgressModel;
import org.ebookdroid.ui.viewer.IViewController;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.RectF;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.emdev.common.log.LogContext;
import org.emdev.ui.gl.GLCanvas;
import org.emdev.utils.MatrixUtils;
public class PageTreeNode implements DecodeService.DecodeCallback {
private static final LogContext LCTX = Page.LCTX;
final Page page;
final PageTreeNode parent;
final int id;
final PageTreeLevel level;
final String shortId;
final String fullId;
final AtomicBoolean decodingNow = new AtomicBoolean();
final BitmapHolder holder = new BitmapHolder();
final RectF localPageSliceBounds;
final RectF pageSliceBounds;
float bitmapZoom = 1;
private RectF autoCropping = null;
private RectF manualCropping = null;
public RectF getCropping() {
return manualCropping != null ? manualCropping : autoCropping;
}
public boolean hasManualCropping() {
return manualCropping != null;
}
public void setInitialCropping(final PageInfo pi) {
if (id != 0) {
return;
}
if (pi != null) {
autoCropping = pi.autoCropping != null ? new RectF(pi.autoCropping) : null;
manualCropping = pi.manualCropping != null ? new RectF(pi.manualCropping) : null;
} else {
autoCropping = null;
manualCropping = null;
}
page.updateAspectRatio();
}
public void setAutoCropping(final RectF r, final boolean commit) {
autoCropping = r;
if (id == 0) {
if (commit) {
page.base.getDocumentModel().updateAutoCropping(page, r);
}
page.updateAspectRatio();
}
}
public void setManualCropping(final RectF r, final boolean commit) {
manualCropping = r;
if (id == 0) {
if (commit) {
page.base.getDocumentModel().updateManualCropping(page, r);
}
page.updateAspectRatio();
}
}
PageTreeNode(final Page page) {
assert page != null;
this.page = page;
this.parent = null;
this.id = 0;
this.level = PageTreeLevel.ROOT;
this.shortId = page.index.viewIndex + ":0";
this.fullId = page.index + ":0";
this.localPageSliceBounds = page.type.getInitialRect();
this.pageSliceBounds = localPageSliceBounds;
this.autoCropping = null;
this.manualCropping = null;
}
PageTreeNode(final Page page, final PageTreeNode parent, final int id, final RectF localPageSliceBounds) {
assert id != 0;
assert page != null;
assert parent != null;
this.page = page;
this.parent = parent;
this.id = id;
this.level = parent.level.next;
this.shortId = page.index.viewIndex + ":" + id;
this.fullId = page.index + ":" + id;
this.localPageSliceBounds = localPageSliceBounds;
this.pageSliceBounds = evaluatePageSliceBounds(localPageSliceBounds, parent);
evaluateCroppedPageSliceBounds();
}
@Override
protected void finalize() throws Throwable {
holder.recycle(null);
}
public boolean recycle(final List<GLBitmaps> bitmapsToRecycle) {
stopDecodingThisNode("node recycling");
return holder.recycle(bitmapsToRecycle);
}
protected void decodePageTreeNode(final List<PageTreeNode> nodesToDecode, final ViewState viewState) {
if (this.decodingNow.compareAndSet(false, true)) {
bitmapZoom = viewState.zoom;
nodesToDecode.add(this);
}
}
void stopDecodingThisNode(final String reason) {
if (this.decodingNow.compareAndSet(true, false)) {
final DecodingProgressModel dpm = page.base.getDecodingProgressModel();
if (dpm != null) {
dpm.decrease();
}
if (reason != null) {
final DecodeService ds = page.base.getDecodeService();
if (ds != null) {
ds.stopDecoding(this, reason);
}
}
}
}
@Override
public void decodeComplete(final CodecPage codecPage, final ByteBufferBitmap bitmap, final RectF croppedPageBounds) {
try {
if (bitmap == null) {
stopDecodingThisNode(null);
return;
}
final AppSettings app = AppSettings.current();
final BookSettings bs = page.base.getBookSettings();
final boolean invert = bs != null ? bs.nightMode : app.nightMode;
final boolean tint = bs != null ? bs.tint : app.tint;
final int tintColor = bs != null ? bs.tintColor : app.tintColor;
if (bs != null) {
bitmap.applyEffects(bs);
}
if (invert) {
bitmap.invert();
}
final PagePaint paint = tint ? PagePaint.TintedDay(tintColor)
: (invert ? PagePaint.Night()
: PagePaint.Day());
final GLBitmaps bitmaps = new GLBitmaps(fullId, bitmap, paint);
holder.setBitmap(bitmaps);
stopDecodingThisNode(null);
final IViewController dc = page.base.getDocumentController();
if (dc instanceof AbstractViewController) {
EventPool.newEventChildLoaded((AbstractViewController) dc, PageTreeNode.this).process()
.release();
}
} catch (final OutOfMemoryError ex) {
LCTX.e("No memory: ", ex);
BitmapManager.clear("PageTreeNode OutOfMemoryError: ");
ByteBufferManager.clear("PageTreeNode OutOfMemoryError: ");
stopDecodingThisNode(null);
} finally {
ByteBufferManager.release(bitmap);
}
}
public RectF getTargetRect(final RectF pageBounds) {
return Page.getTargetRect(page.type, pageBounds, pageSliceBounds);
}
public static RectF evaluatePageSliceBounds(final RectF localPageSliceBounds, final PageTreeNode parent) {
final Matrix tmpMatrix = MatrixUtils.get();
tmpMatrix.postScale(parent.pageSliceBounds.width(), parent.pageSliceBounds.height());
tmpMatrix.postTranslate(parent.pageSliceBounds.left, parent.pageSliceBounds.top);
final RectF sliceBounds = new RectF();
tmpMatrix.mapRect(sliceBounds, localPageSliceBounds);
return sliceBounds;
}
public void evaluateCroppedPageSliceBounds() {
if (parent == null) {
return;
}
if (parent.getCropping() == null) {
parent.evaluateCroppedPageSliceBounds();
}
autoCropping = evaluateCroppedPageSliceBounds(parent.autoCropping, this.localPageSliceBounds);
manualCropping = evaluateCroppedPageSliceBounds(parent.manualCropping, this.localPageSliceBounds);
}
public static RectF evaluateCroppedPageSliceBounds(final RectF crop, final RectF slice) {
if (crop == null) {
return null;
}
final RectF sliceBounds = new RectF();
final Matrix tmpMatrix = MatrixUtils.get();
tmpMatrix.postScale(crop.width(), crop.height());
tmpMatrix.postTranslate(crop.left, crop.top);
tmpMatrix.mapRect(sliceBounds, slice);
return sliceBounds;
}
@Override
public int hashCode() {
return (page == null) ? 0 : page.index.viewIndex;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof PageTreeNode) {
final PageTreeNode that = (PageTreeNode) obj;
if (this.page == null) {
return that.page == null;
}
return this.page.index.viewIndex == that.page.index.viewIndex
&& this.pageSliceBounds.equals(that.pageSliceBounds);
}
return false;
}
@Override
public String toString() {
final StringBuilder buf = new StringBuilder("PageTreeNode");
buf.append("[");
buf.append("id").append("=").append(page.index.viewIndex).append(":").append(id);
buf.append(", ");
buf.append("rect").append("=").append(this.pageSliceBounds);
buf.append(", ");
buf.append("hasBitmap").append("=").append(holder.hasBitmaps());
buf.append("]");
return buf.toString();
}
class BitmapHolder {
final AtomicReference<GLBitmaps> ref = new AtomicReference<GLBitmaps>();
public boolean drawBitmap(final GLCanvas canvas, final PagePaint paint, final PointF viewBase,
final RectF targetRect, final RectF clipRect) {
final GLBitmaps bitmaps = ref.get();
return bitmaps != null ? bitmaps.drawGL(canvas, paint, viewBase, targetRect, clipRect) : false;
}
public boolean hasBitmaps() {
final GLBitmaps bitmaps = ref.get();
return bitmaps != null ? bitmaps.hasBitmaps() : false;
}
public boolean recycle(final List<GLBitmaps> bitmapsToRecycle) {
final GLBitmaps bitmaps = ref.getAndSet(null);
if (bitmaps != null) {
if (bitmapsToRecycle != null) {
bitmapsToRecycle.add(bitmaps);
} else {
ByteBufferManager.release(bitmaps);
}
return true;
}
return false;
}
public void setBitmap(final GLBitmaps bitmaps) {
if (bitmaps == null) {
return;
}
final GLBitmaps oldBitmaps = ref.getAndSet(bitmaps);
if (oldBitmaps != null && oldBitmaps != bitmaps) {
ByteBufferManager.release(oldBitmaps);
}
}
}
}