Back to project page WorldMap.
The source code is released under:
Apache License
If you think the Android project WorldMap listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.
package com.sigseg.android.view; // ww w . ja v a2 s . co m import android.graphics.*; import android.graphics.Bitmap.Config; import android.os.Debug; import android.util.Log; /* * +-------------------------------------------------------------------+ * | | | * | +------------------------+ | | * | | | | | * | | | | | * | | | | | * | | Viewport | | | * | +------------------------+ | | * | | | * | | | * | | | * | Cache | | * |----------------------------------------+ | * | | * | | * | | * | | * | | * | Entire bitmap -- too big for memory | * +-------------------------------------------------------------------+ */ /** * Keeps track of an entire Scene -- a bitmap (or virtual bitmap) that is much too large * to fit into memory. Clients subclass this class and extend its abstract methods to * actually return the necessary bitmaps. */ public abstract class Scene { private final String TAG = "Scene"; private final static int MINIMUM_PIXELS_IN_VIEW = 50; /** The size of the Scene */ private Point size = new Point(); /** The viewport */ private final Viewport viewport = new Viewport(); /** The cache */ private final Cache cache = new Cache(); //region [gs]etSceneSize /** Set the size of the scene */ public void setSceneSize(int width, int height){ size.set(width, height); } /** Returns a Point representing the size of the scene. Don't modify the returned Point! */ public Point getSceneSize(){ return size; } /** Set the passed-in point to the size of the scene */ public void getSceneSize(Point point){ point.set(size.x, size.y); } //endregion //region getViewport() public Viewport getViewport(){return viewport;} //endregion //region initialize/start/stop/suspend/invalidate the cache /** Initializes the cache */ public void initialize(){ if (cache.getState()==CacheState.UNINITIALIZED){ synchronized(cache){ cache.setState(CacheState.INITIALIZED); } } } /** Starts the cache thread */ public void start(){ cache.start(); } /** Stops the cache thread */ public void stop(){ cache.stop(); } /** * Suspends or unsuspends the cache thread. This can be * used to temporarily stop the cache from updating * during a fling event. * @param suspend True to suspend the cache. False to unsuspend. */ public void setSuspend(boolean suspend){ if (suspend) { synchronized(cache){ cache.setState(CacheState.SUSPEND); } } else { if (cache.getState()==CacheState.SUSPEND) { synchronized(cache){ cache.setState(CacheState.INITIALIZED); } } } } /** Invalidate the cache. This causes it to refill */ @SuppressWarnings("unused") public void invalidate(){ cache.invalidate(); } //endregion //region void draw(Canvas c) /** * Draw the scene to the canvas. This operation fills the canvas with * the bitmap referenced by the viewport's location within the Scene. * If the cache already has the data (and is not suspended), then the * high resolution bitmap from the cache is used. If it's not available, * then the lower resolution bitmap from the sample is used. */ public void draw(Canvas c){ viewport.draw(c); } //endregion //region protected abstract /** * This method must return a high resolution Bitmap that the Scene * will use to fill out the viewport bitmap upon request. This bitmap * is normally larger than the viewport so that the viewport can be * scrolled without having to refresh the cache. This method runs * on a thread other than the UI thread, and it is not under a lock, so * it is expected that this method can run for a long time (seconds?). * @param rectOfCache The Rect representing the area of the Scene that * the Scene wants cached. * @return the Bitmap representing the requested area of the larger bitmap */ protected abstract Bitmap fillCache(Rect rectOfCache); /** * The memory allocation you just did in fillCache caused an OutOfMemoryError. * You can attempt to recover. Experience shows that when we get an * OutOfMemoryError, we're pretty hosed and are going down. For instance, if * we're trying to decode a bitmap region with * {@link android.graphics.BitmapRegionDecoder} and we run out of memory, * we're going to die somewhere in the C code with a SIGSEGV. * @param error The OutOfMemoryError exception data */ protected abstract void fillCacheOutOfMemoryError( OutOfMemoryError error ); /** * Calculate the Rect of the cache's window based on the current viewportRect. * The returned Rect must at least contain the viewportRect, but it can be * larger if the system believes a bitmap of the returned size will fit into * memory. This function must be fast as it happens while the cache lock is held. * @param viewportRect The returned must be able to contain this Rect * @return The Rect that will be used to fill the cache */ protected abstract Rect calculateCacheWindow(Rect viewportRect); /** * This method fills the passed-in bitmap with sample data. This function must * return as fast as possible so it shouldn't have to do any IO at all -- the * quality of the user experience rests on the speed of this function. * @param bitmap The Bitmap to fill * @param rectOfSample Rectangle within the Scene that this bitmap represents. */ protected abstract void drawSampleRectIntoBitmap(Bitmap bitmap, Rect rectOfSample); /** * The Cache is done drawing the bitmap -- time to add the finishing touches * @param canvas a canvas on which to draw */ protected abstract void drawComplete(Canvas canvas); //endregion //region class Viewport public class Viewport { /** The bitmap of the current viewport */ Bitmap bitmap = null; /** A Rect that defines where the Viewport is within the scene */ final Rect window = new Rect(0,0,0,0); float zoom = 1.0f; public void setOrigin(int x, int y){ synchronized(this){ int w = window.width(); int h = window.height(); // check bounds if (x < 0) x = 0; if (y < 0) y = 0; if (x + w > size.x) x = size.x - w; if (y + h > size.y) y = size.y - h; window.set(x, y, x+w, y+h); } } public void setSize( int w, int h ){ synchronized (this) { if (bitmap !=null){ bitmap.recycle(); bitmap = null; } bitmap = Bitmap.createBitmap(w, h, Config.RGB_565); window.set( window.left, window.top, window.left + w, window.top + h); } } public void getOrigin(Point p){ synchronized (this) { p.set(window.left, window.top); } } public void getSize(Point p){ synchronized (this) { p.x = window.width(); p.y = window.height(); } } public void getPhysicalSize(Point p){ synchronized (this){ p.x = getPhysicalWidth(); p.y = getPhysicalHeight(); } } public int getPhysicalWidth(){ return bitmap.getWidth(); } public int getPhysicalHeight(){ return bitmap.getHeight(); } public float getZoom(){ return zoom; } public void zoom(float factor, PointF screenFocus){ if (factor!=1.0){ PointF screenSize = new PointF(bitmap.getWidth(),bitmap.getHeight()); PointF sceneSize = new PointF(getSceneSize()); float screenWidthToHeight = screenSize.x / screenSize.y; float screenHeightToWidth = screenSize.y / screenSize.x; synchronized (this){ float newZoom = zoom * factor; RectF w1 = new RectF(window); RectF w2 = new RectF(); PointF sceneFocus = new PointF( w1.left + (screenFocus.x/screenSize.x)*w1.width(), w1.top + (screenFocus.y/screenSize.y)*w1.height() ); float w2Width = getPhysicalWidth() * newZoom; if (w2Width > sceneSize.x){ w2Width = sceneSize.x; newZoom = w2Width / getPhysicalWidth(); } if (w2Width < MINIMUM_PIXELS_IN_VIEW){ w2Width = MINIMUM_PIXELS_IN_VIEW; newZoom = w2Width / getPhysicalWidth(); } float w2Height = w2Width * screenHeightToWidth; if (w2Height > sceneSize.y){ w2Height = sceneSize.y; w2Width = w2Height * screenWidthToHeight; newZoom = w2Width / getPhysicalWidth(); } if (w2Height < MINIMUM_PIXELS_IN_VIEW){ w2Height = MINIMUM_PIXELS_IN_VIEW; w2Width = w2Height * screenWidthToHeight; newZoom = w2Width / getPhysicalWidth(); } w2.left = sceneFocus.x - ((screenFocus.x/screenSize.x) * w2Width); w2.top = sceneFocus.y - ((screenFocus.y/screenSize.y) * w2Height); if (w2.left<0) w2.left=0; if (w2.top<0) w2.top=0; w2.right = w2.left+w2Width; w2.bottom= w2.top+w2Height; if (w2.right>sceneSize.x){ w2.right=sceneSize.x; w2.left=w2.right-w2Width; } if (w2.bottom>sceneSize.y){ w2.bottom=sceneSize.y; w2.top=w2.bottom-w2Height; } window.set((int)w2.left,(int)w2.top,(int)w2.right,(int)w2.bottom); zoom = newZoom; // Log.d(TAG,String.format( // "f=%.2f, z=%.2f, scrf(%.0f,%.0f), scnf(%.0f,%.0f) w1s(%.0f,%.0f) w2s(%.0f,%.0f) w1(%.0f,%.0f,%.0f,%.0f) w2(%.0f,%.0f,%.0f,%.0f)", // factor, // zoom, // screenFocus.x, // screenFocus.y, // sceneFocus.x, // sceneFocus.y, // w1.width(),w1.height(), // w2Width, w2Height, // w1.left,w1.top,w1.right,w1.bottom, // w2.left,w2.top,w2.right,w2.bottom // )); } } } void draw(Canvas c){ cache.update(this); synchronized (this){ if (c!=null && bitmap !=null){ c.drawBitmap(bitmap, 0F, 0F, null); drawComplete(c); } } } } //endregion //region class Cache private enum CacheState {UNINITIALIZED,INITIALIZED,START_UPDATE,IN_UPDATE,READY,SUSPEND} /** * Keep track of the cached bitmap */ private class Cache { /** A Rect that defines where the Cache is within the scene */ final Rect window = new Rect(0,0,0,0); /** The bitmap of the current cache */ Bitmap bitmapRef = null; CacheState state = CacheState.UNINITIALIZED; void setState(CacheState newState){ if (Debug.isDebuggerConnected()) Log.i(TAG,String.format("cacheState old=%s new=%s",state.toString(),newState.toString())); state = newState; } CacheState getState(){ return state; } /** Our load from disk thread */ CacheThread cacheThread; void start(){ if (cacheThread!=null){ cacheThread.setRunning(false); cacheThread.interrupt(); cacheThread = null; } cacheThread = new CacheThread(this); cacheThread.setName("cacheThread"); cacheThread.start(); } void stop(){ cacheThread.running = false; cacheThread.interrupt(); boolean retry = true; while (retry) { try { cacheThread.join(); retry = false; } catch (InterruptedException e) { // we will try it again and again... } } cacheThread = null; } void invalidate(){ synchronized(this){ setState(CacheState.INITIALIZED); cacheThread.interrupt(); } } /** Fill the bitmap with the part of the scene referenced by the viewport Rect */ void update(Viewport viewport){ Bitmap bitmap = null; // If this is null at the bottom, then load from the sample synchronized(this){ switch(getState()){ case UNINITIALIZED: // nothing can be done -- should never get here return; case INITIALIZED: // time to cache some data setState(CacheState.START_UPDATE); cacheThread.interrupt(); break; case START_UPDATE: // I already told the thread to start break; case IN_UPDATE: // Already reading some data, just use the sample break; case SUSPEND: // Loading from cache suspended. break; case READY: // I have some data to show if (bitmapRef==null){ // Start the cache off right if (Debug.isDebuggerConnected()) Log.d(TAG,"bitmapRef is null"); setState(CacheState.START_UPDATE); cacheThread.interrupt(); } else if (!window.contains(viewport.window)){ if (Debug.isDebuggerConnected()) Log.d(TAG,"viewport not in cache"); setState(CacheState.START_UPDATE); cacheThread.interrupt(); } else { // Happy case -- the cache already contains the Viewport bitmap = bitmapRef; } break; } } if (bitmap==null) loadSampleIntoViewport(); else loadBitmapIntoViewport(bitmap); } void loadBitmapIntoViewport(Bitmap bitmap){ if (bitmap!=null){ synchronized(viewport){ int left = viewport.window.left - window.left; int top = viewport.window.top - window.top; int right = left + viewport.window.width(); int bottom = top + viewport.window.height(); viewport.getPhysicalSize(dstSize); srcRect.set( left, top, right, bottom ); dstRect.set(0, 0, dstSize.x, dstSize.y); Canvas c = new Canvas(viewport.bitmap); c.drawColor(Color.BLACK); c.drawBitmap( bitmap, srcRect, dstRect, null); // try { // FileOutputStream fos = new FileOutputStream("/sdcard/viewport.png"); // viewport.bitmap.compress(Bitmap.CompressFormat.PNG, 99, fos); // Thread.sleep(1000); // } catch (Exception e){ // System.out.print(e.getMessage()); // } } } } final Rect srcRect = new Rect(0,0,0,0); final Rect dstRect = new Rect(0,0,0,0); final Point dstSize = new Point(); void loadSampleIntoViewport(){ if (getState()!=CacheState.UNINITIALIZED){ synchronized(viewport){ drawSampleRectIntoBitmap( viewport.bitmap, viewport.window ); } } } } //endregion //region class CacheThread /** * <p>The CacheThread's job is to wait until the {@link Cache#state} is * {@link CacheState#START_UPDATE} and then update the {@link Cache} given * the current {@link Viewport#window}. It does not want to hold the cache * lock during the call to {@link Scene#fillCache(Rect)} because the call * can take a long time. If we hold the lock, the user experience is very * jumpy.</p> * <p>The CacheThread and the {@link Cache} work hand in hand, both using the * cache itself to synchronize on and using the {@link Cache#state}. * The {@link Cache} is free to update any part of the cache object as long * as it holds the lock. The CacheThread is careful to make sure that it is * the {@link Cache#state} is {@link CacheState#IN_UPDATE} as it updates * the {@link Cache}. It locks and unlocks the cache all along the way, but * makes sure that the cache is not locked when it calls * {@link Scene#fillCache(Rect)}. */ class CacheThread extends Thread { final Cache cache; boolean running = false; void setRunning(boolean value){ running = value; } CacheThread(Cache cache){ this.cache = cache; } @Override public void run() { running=true; Rect viewportRect = new Rect(0,0,0,0); while(running){ while(running && cache.getState()!=CacheState.START_UPDATE) try { // Sleep until we have something to do Thread.sleep(Integer.MAX_VALUE); } catch (InterruptedException ignored) {} if (!running) return; long start = System.currentTimeMillis(); boolean cont = false; synchronized (cache) { if (cache.getState()==CacheState.START_UPDATE){ cache.setState(CacheState.IN_UPDATE); cache.bitmapRef = null; cont = true; } } if (cont){ synchronized(viewport){ viewportRect.set(viewport.window); } synchronized (cache) { if (cache.getState()==CacheState.IN_UPDATE) //cache.setWindowRect(viewportRect); cache.window.set(calculateCacheWindow(viewportRect)); else cont = false; } if (cont){ try{ Bitmap bitmap = fillCache(cache.window); if (bitmap!=null){ synchronized (cache){ if (cache.getState()==CacheState.IN_UPDATE){ cache.bitmapRef = bitmap; cache.setState(CacheState.READY); } else { Log.w(TAG,"fillCache operation aborted"); } } } long done = System.currentTimeMillis(); if (Debug.isDebuggerConnected()) Log.d(TAG,String.format("fillCache in %dms",done-start)); } catch (OutOfMemoryError e){ Log.d(TAG,"CacheThread out of memory"); /* * Attempt to recover. Experience shows that if we * do get an OutOfMemoryError, we're pretty hosed and are going down. */ synchronized (cache){ fillCacheOutOfMemoryError(e); if (cache.getState()==CacheState.IN_UPDATE){ cache.setState(CacheState.START_UPDATE); } } } } } } } } //endregion }