> DOWN_SAMPLE_SHIFT);
70 | int top = (rectOfSample.top>> DOWN_SAMPLE_SHIFT);
71 | int right = left + (rectOfSample.width()>> DOWN_SAMPLE_SHIFT);
72 | int bottom = top + (rectOfSample.height()>> DOWN_SAMPLE_SHIFT);
73 | Rect srcRect = new Rect( left, top, right, bottom );
74 | Rect identity= new Rect(0,0,c.getWidth(),c.getHeight());
75 | c.drawBitmap(
76 | sampleBitmap,
77 | srcRect,
78 | identity,
79 | null
80 | );
81 | // c.drawLine(0L,0L,c.getWidth(),c.getHeight(),red);
82 | }
83 | }
84 |
85 | // @Override
86 | // protected Rect calculateCacheWindow(Rect viewportRect) {
87 | // // Simplest implementation
88 | // return viewportRect;
89 | // }
90 |
91 | private Rect calculatedCacheWindowRect = new Rect();
92 | @Override
93 | protected Rect calculateCacheWindow(Rect viewportRect) {
94 | long bytesToUse = Runtime.getRuntime().maxMemory() * percent / 100;
95 | Point size = getSceneSize();
96 |
97 | int vw = viewportRect.width();
98 | int vh = viewportRect.height();
99 |
100 | // Calculate the max size of the margins to fit in our memory budget
101 | int tw=0;
102 | int th=0;
103 | int mw = tw;
104 | int mh = th;
105 | while((vw+tw) * (vh+th) * BYTES_PER_PIXEL < bytesToUse){
106 | mw = tw++;
107 | mh = th++;
108 | }
109 |
110 | // Trim the margins if they're too big.
111 | if (vw+mw > size.x) // viewport width + margin width > width of the image
112 | mw = Math.max(0, size.x-vw);
113 | if (vh+mh > size.y) // viewport height + margin height > height of the image
114 | mh = Math.max(0, size.y-vh);
115 |
116 | // Figure out the left & right based on the margin. We assume our viewportRect
117 | // is <= our size. If that's not the case, then this logic breaks.
118 | int left = viewportRect.left - (mw>>1);
119 | int right = viewportRect.right + (mw>>1);
120 | if (left<0){
121 | right = right - left; // Add's the overage on the left side back to the right
122 | left = 0;
123 | }
124 | if (right>size.x){
125 | left = left - (right-size.x); // Adds overage on right side back to left
126 | right = size.x;
127 | }
128 |
129 | // Figure out the top & bottom based on the margin. We assume our viewportRect
130 | // is <= our size. If that's not the case, then this logic breaks.
131 | int top = viewportRect.top - (mh>>1);
132 | int bottom = viewportRect.bottom + (mh>>1);
133 | if (top<0){
134 | bottom = bottom - top; // Add's the overage on the top back to the bottom
135 | top = 0;
136 | }
137 | if (bottom>size.y){
138 | top = top - (bottom-size.y); // Adds overage on bottom back to top
139 | bottom = size.y;
140 | }
141 |
142 | // Set the origin based on our new calculated values.
143 | calculatedCacheWindowRect.set(left, top, right, bottom);
144 | if (DEBUG) Log.d(TAG,"new cache.originRect = "+calculatedCacheWindowRect.toShortString()+" size="+size.toString());
145 | return calculatedCacheWindowRect;
146 | }
147 |
148 | @Override
149 | protected void fillCacheOutOfMemoryError(OutOfMemoryError error) {
150 | if (percent>0)
151 | percent -= 1;
152 | Log.e(TAG,String.format("caught oom -- cache now at %d percent.",percent));
153 | }
154 |
155 | @Override
156 | protected void drawComplete(Canvas canvas) {
157 | // TODO Auto-generated method stub
158 |
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/library/src/com/sigseg/android/view/Scene.java:
--------------------------------------------------------------------------------
1 | package com.sigseg.android.view;
2 |
3 | import android.graphics.*;
4 | import android.graphics.Bitmap.Config;
5 | import android.os.Debug;
6 | import android.util.Log;
7 |
8 | /*
9 | * +-------------------------------------------------------------------+
10 | * | | |
11 | * | +------------------------+ | |
12 | * | | | | |
13 | * | | | | |
14 | * | | | | |
15 | * | | Viewport | | |
16 | * | +------------------------+ | |
17 | * | | |
18 | * | | |
19 | * | | |
20 | * | Cache | |
21 | * |----------------------------------------+ |
22 | * | |
23 | * | |
24 | * | |
25 | * | |
26 | * | |
27 | * | Entire bitmap -- too big for memory |
28 | * +-------------------------------------------------------------------+
29 | */
30 | /**
31 | * Keeps track of an entire Scene -- a bitmap (or virtual bitmap) that is much too large
32 | * to fit into memory. Clients subclass this class and extend its abstract methods to
33 | * actually return the necessary bitmaps.
34 | */
35 | public abstract class Scene {
36 | private final String TAG = "Scene";
37 |
38 | private final static int MINIMUM_PIXELS_IN_VIEW = 50;
39 |
40 | /** The size of the Scene */
41 | private Point size = new Point();
42 | /** The viewport */
43 | private final Viewport viewport = new Viewport();
44 | /** The cache */
45 | private final Cache cache = new Cache();
46 |
47 | //region [gs]etSceneSize
48 | /** Set the size of the scene */
49 | public void setSceneSize(int width, int height){
50 | size.set(width, height);
51 | }
52 | /** Returns a Point representing the size of the scene. Don't modify the returned Point! */
53 | public Point getSceneSize(){
54 | return size;
55 | }
56 | /** Set the passed-in point to the size of the scene */
57 | public void getSceneSize(Point point){
58 | point.set(size.x, size.y);
59 | }
60 | //endregion
61 |
62 | //region getViewport()
63 | public Viewport getViewport(){return viewport;}
64 | //endregion
65 |
66 | //region initialize/start/stop/suspend/invalidate the cache
67 | /** Initializes the cache */
68 | public void initialize(){
69 | if (cache.getState()==CacheState.UNINITIALIZED){
70 | synchronized(cache){
71 | cache.setState(CacheState.INITIALIZED);
72 | }
73 | }
74 | }
75 | /** Starts the cache thread */
76 | public void start(){
77 | cache.start();
78 | }
79 | /** Stops the cache thread */
80 | public void stop(){
81 | cache.stop();
82 | }
83 | /**
84 | * Suspends or unsuspends the cache thread. This can be
85 | * used to temporarily stop the cache from updating
86 | * during a fling event.
87 | * @param suspend True to suspend the cache. False to unsuspend.
88 | */
89 | public void setSuspend(boolean suspend){
90 | if (suspend) {
91 | synchronized(cache){
92 | cache.setState(CacheState.SUSPEND);
93 | }
94 | } else {
95 | if (cache.getState()==CacheState.SUSPEND) {
96 | synchronized(cache){
97 | cache.setState(CacheState.INITIALIZED);
98 | }
99 | }
100 | }
101 | }
102 | /** Invalidate the cache. This causes it to refill */
103 | @SuppressWarnings("unused")
104 | public void invalidate(){
105 | cache.invalidate();
106 | }
107 | //endregion
108 |
109 | //region void draw(Canvas c)
110 | /**
111 | * Draw the scene to the canvas. This operation fills the canvas with
112 | * the bitmap referenced by the viewport's location within the Scene.
113 | * If the cache already has the data (and is not suspended), then the
114 | * high resolution bitmap from the cache is used. If it's not available,
115 | * then the lower resolution bitmap from the sample is used.
116 | */
117 | public void draw(Canvas c){
118 | viewport.draw(c);
119 | }
120 | //endregion
121 |
122 | //region protected abstract
123 | /**
124 | * This method must return a high resolution Bitmap that the Scene
125 | * will use to fill out the viewport bitmap upon request. This bitmap
126 | * is normally larger than the viewport so that the viewport can be
127 | * scrolled without having to refresh the cache. This method runs
128 | * on a thread other than the UI thread, and it is not under a lock, so
129 | * it is expected that this method can run for a long time (seconds?).
130 | * @param rectOfCache The Rect representing the area of the Scene that
131 | * the Scene wants cached.
132 | * @return the Bitmap representing the requested area of the larger bitmap
133 | */
134 | protected abstract Bitmap fillCache(Rect rectOfCache);
135 | /**
136 | * The memory allocation you just did in fillCache caused an OutOfMemoryError.
137 | * You can attempt to recover. Experience shows that when we get an
138 | * OutOfMemoryError, we're pretty hosed and are going down. For instance, if
139 | * we're trying to decode a bitmap region with
140 | * {@link android.graphics.BitmapRegionDecoder} and we run out of memory,
141 | * we're going to die somewhere in the C code with a SIGSEGV.
142 | * @param error The OutOfMemoryError exception data
143 | */
144 | protected abstract void fillCacheOutOfMemoryError( OutOfMemoryError error );
145 | /**
146 | * Calculate the Rect of the cache's window based on the current viewportRect.
147 | * The returned Rect must at least contain the viewportRect, but it can be
148 | * larger if the system believes a bitmap of the returned size will fit into
149 | * memory. This function must be fast as it happens while the cache lock is held.
150 | * @param viewportRect The returned must be able to contain this Rect
151 | * @return The Rect that will be used to fill the cache
152 | */
153 | protected abstract Rect calculateCacheWindow(Rect viewportRect);
154 | /**
155 | * This method fills the passed-in bitmap with sample data. This function must
156 | * return as fast as possible so it shouldn't have to do any IO at all -- the
157 | * quality of the user experience rests on the speed of this function.
158 | * @param bitmap The Bitmap to fill
159 | * @param rectOfSample Rectangle within the Scene that this bitmap represents.
160 | */
161 | protected abstract void drawSampleRectIntoBitmap(Bitmap bitmap, Rect rectOfSample);
162 | /**
163 | * The Cache is done drawing the bitmap -- time to add the finishing touches
164 | * @param canvas a canvas on which to draw
165 | */
166 | protected abstract void drawComplete(Canvas canvas);
167 | //endregion
168 |
169 | //region class Viewport
170 |
171 | public class Viewport {
172 | /** The bitmap of the current viewport */
173 | Bitmap bitmap = null;
174 | /** A Rect that defines where the Viewport is within the scene */
175 | final Rect window = new Rect(0,0,0,0);
176 | float zoom = 1.0f;
177 |
178 | public void setOrigin(int x, int y){
179 | synchronized(this){
180 | int w = window.width();
181 | int h = window.height();
182 |
183 | // check bounds
184 | if (x < 0)
185 | x = 0;
186 |
187 | if (y < 0)
188 | y = 0;
189 |
190 | if (x + w > size.x)
191 | x = size.x - w;
192 |
193 | if (y + h > size.y)
194 | y = size.y - h;
195 |
196 | window.set(x, y, x+w, y+h);
197 | }
198 | }
199 | public void setSize( int w, int h ){
200 | synchronized (this) {
201 | if (bitmap !=null){
202 | bitmap.recycle();
203 | bitmap = null;
204 | }
205 | bitmap = Bitmap.createBitmap(w, h, Config.RGB_565);
206 | window.set(
207 | window.left,
208 | window.top,
209 | window.left + w,
210 | window.top + h);
211 | }
212 | }
213 | public void getOrigin(Point p){
214 | synchronized (this) {
215 | p.set(window.left, window.top);
216 | }
217 | }
218 | public void getSize(Point p){
219 | synchronized (this) {
220 | p.x = window.width();
221 | p.y = window.height();
222 | }
223 | }
224 | public void getPhysicalSize(Point p){
225 | synchronized (this){
226 | p.x = getPhysicalWidth();
227 | p.y = getPhysicalHeight();
228 | }
229 | }
230 | public int getPhysicalWidth(){
231 | return bitmap.getWidth();
232 | }
233 | public int getPhysicalHeight(){
234 | return bitmap.getHeight();
235 | }
236 | public float getZoom(){
237 | return zoom;
238 | }
239 | public void zoom(float factor, PointF screenFocus){
240 | if (factor!=1.0){
241 |
242 | PointF screenSize = new PointF(bitmap.getWidth(),bitmap.getHeight());
243 | PointF sceneSize = new PointF(getSceneSize());
244 | float screenWidthToHeight = screenSize.x / screenSize.y;
245 | float screenHeightToWidth = screenSize.y / screenSize.x;
246 | synchronized (this){
247 | float newZoom = zoom * factor;
248 | RectF w1 = new RectF(window);
249 | RectF w2 = new RectF();
250 | PointF sceneFocus = new PointF(
251 | w1.left + (screenFocus.x/screenSize.x)*w1.width(),
252 | w1.top + (screenFocus.y/screenSize.y)*w1.height()
253 | );
254 | float w2Width = getPhysicalWidth() * newZoom;
255 | if (w2Width > sceneSize.x){
256 | w2Width = sceneSize.x;
257 | newZoom = w2Width / getPhysicalWidth();
258 | }
259 | if (w2Width < MINIMUM_PIXELS_IN_VIEW){
260 | w2Width = MINIMUM_PIXELS_IN_VIEW;
261 | newZoom = w2Width / getPhysicalWidth();
262 | }
263 | float w2Height = w2Width * screenHeightToWidth;
264 | if (w2Height > sceneSize.y){
265 | w2Height = sceneSize.y;
266 | w2Width = w2Height * screenWidthToHeight;
267 | newZoom = w2Width / getPhysicalWidth();
268 | }
269 | if (w2Height < MINIMUM_PIXELS_IN_VIEW){
270 | w2Height = MINIMUM_PIXELS_IN_VIEW;
271 | w2Width = w2Height * screenWidthToHeight;
272 | newZoom = w2Width / getPhysicalWidth();
273 | }
274 | w2.left = sceneFocus.x - ((screenFocus.x/screenSize.x) * w2Width);
275 | w2.top = sceneFocus.y - ((screenFocus.y/screenSize.y) * w2Height);
276 | if (w2.left<0)
277 | w2.left=0;
278 | if (w2.top<0)
279 | w2.top=0;
280 | w2.right = w2.left+w2Width;
281 | w2.bottom= w2.top+w2Height;
282 | if (w2.right>sceneSize.x){
283 | w2.right=sceneSize.x;
284 | w2.left=w2.right-w2Width;
285 | }
286 | if (w2.bottom>sceneSize.y){
287 | w2.bottom=sceneSize.y;
288 | w2.top=w2.bottom-w2Height;
289 | }
290 | window.set((int)w2.left,(int)w2.top,(int)w2.right,(int)w2.bottom);
291 | zoom = newZoom;
292 | // Log.d(TAG,String.format(
293 | // "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)",
294 | // factor,
295 | // zoom,
296 | // screenFocus.x,
297 | // screenFocus.y,
298 | // sceneFocus.x,
299 | // sceneFocus.y,
300 | // w1.width(),w1.height(),
301 | // w2Width, w2Height,
302 | // w1.left,w1.top,w1.right,w1.bottom,
303 | // w2.left,w2.top,w2.right,w2.bottom
304 | // ));
305 | }
306 | }
307 | }
308 | void draw(Canvas c){
309 | cache.update(this);
310 | synchronized (this){
311 | if (c!=null && bitmap !=null){
312 | c.drawBitmap(bitmap, 0F, 0F, null);
313 | drawComplete(c);
314 | }
315 | }
316 | }
317 | }
318 | //endregion
319 |
320 | //region class Cache
321 |
322 | private enum CacheState {UNINITIALIZED,INITIALIZED,START_UPDATE,IN_UPDATE,READY,SUSPEND}
323 | /**
324 | * Keep track of the cached bitmap
325 | */
326 | private class Cache {
327 | /** A Rect that defines where the Cache is within the scene */
328 | final Rect window = new Rect(0,0,0,0);
329 | /** The bitmap of the current cache */
330 | Bitmap bitmapRef = null;
331 | CacheState state = CacheState.UNINITIALIZED;
332 |
333 | void setState(CacheState newState){
334 | if (Debug.isDebuggerConnected())
335 | Log.i(TAG,String.format("cacheState old=%s new=%s",state.toString(),newState.toString()));
336 | state = newState;
337 | }
338 | CacheState getState(){ return state; }
339 |
340 | /** Our load from disk thread */
341 | CacheThread cacheThread;
342 |
343 | void start(){
344 | if (cacheThread!=null){
345 | cacheThread.setRunning(false);
346 | cacheThread.interrupt();
347 | cacheThread = null;
348 | }
349 | cacheThread = new CacheThread(this);
350 | cacheThread.setName("cacheThread");
351 | cacheThread.start();
352 | }
353 |
354 | void stop(){
355 | cacheThread.running = false;
356 | cacheThread.interrupt();
357 |
358 | boolean retry = true;
359 | while (retry) {
360 | try {
361 | cacheThread.join();
362 | retry = false;
363 | } catch (InterruptedException e) {
364 | // we will try it again and again...
365 | }
366 | }
367 | cacheThread = null;
368 | }
369 | void invalidate(){
370 | synchronized(this){
371 | setState(CacheState.INITIALIZED);
372 | cacheThread.interrupt();
373 | }
374 | }
375 |
376 | /** Fill the bitmap with the part of the scene referenced by the viewport Rect */
377 | void update(Viewport viewport){
378 | Bitmap bitmap = null; // If this is null at the bottom, then load from the sample
379 | synchronized(this){
380 | switch(getState()){
381 | case UNINITIALIZED:
382 | // nothing can be done -- should never get here
383 | return;
384 | case INITIALIZED:
385 | // time to cache some data
386 | setState(CacheState.START_UPDATE);
387 | cacheThread.interrupt();
388 | break;
389 | case START_UPDATE:
390 | // I already told the thread to start
391 | break;
392 | case IN_UPDATE:
393 | // Already reading some data, just use the sample
394 | break;
395 | case SUSPEND:
396 | // Loading from cache suspended.
397 | break;
398 | case READY:
399 | // I have some data to show
400 | if (bitmapRef==null){
401 | // Start the cache off right
402 | if (Debug.isDebuggerConnected())
403 | Log.d(TAG,"bitmapRef is null");
404 | setState(CacheState.START_UPDATE);
405 | cacheThread.interrupt();
406 | } else if (!window.contains(viewport.window)){
407 | if (Debug.isDebuggerConnected())
408 | Log.d(TAG,"viewport not in cache");
409 | setState(CacheState.START_UPDATE);
410 | cacheThread.interrupt();
411 | } else {
412 | // Happy case -- the cache already contains the Viewport
413 | bitmap = bitmapRef;
414 | }
415 | break;
416 | }
417 | }
418 | if (bitmap==null)
419 | loadSampleIntoViewport();
420 | else
421 | loadBitmapIntoViewport(bitmap);
422 | }
423 |
424 | void loadBitmapIntoViewport(Bitmap bitmap){
425 | if (bitmap!=null){
426 | synchronized(viewport){
427 | int left = viewport.window.left - window.left;
428 | int top = viewport.window.top - window.top;
429 | int right = left + viewport.window.width();
430 | int bottom = top + viewport.window.height();
431 | viewport.getPhysicalSize(dstSize);
432 | srcRect.set( left, top, right, bottom );
433 | dstRect.set(0, 0, dstSize.x, dstSize.y);
434 | Canvas c = new Canvas(viewport.bitmap);
435 | c.drawColor(Color.BLACK);
436 | c.drawBitmap(
437 | bitmap,
438 | srcRect,
439 | dstRect,
440 | null);
441 | // try {
442 | // FileOutputStream fos = new FileOutputStream("/sdcard/viewport.png");
443 | // viewport.bitmap.compress(Bitmap.CompressFormat.PNG, 99, fos);
444 | // Thread.sleep(1000);
445 | // } catch (Exception e){
446 | // System.out.print(e.getMessage());
447 | // }
448 | }
449 | }
450 | }
451 | final Rect srcRect = new Rect(0,0,0,0);
452 | final Rect dstRect = new Rect(0,0,0,0);
453 | final Point dstSize = new Point();
454 |
455 | void loadSampleIntoViewport(){
456 | if (getState()!=CacheState.UNINITIALIZED){
457 | synchronized(viewport){
458 | drawSampleRectIntoBitmap(
459 | viewport.bitmap,
460 | viewport.window
461 | );
462 | }
463 | }
464 | }
465 | }
466 | //endregion
467 |
468 | //region class CacheThread
469 | /**
470 | * The CacheThread's job is to wait until the {@link Cache#state} is
471 | * {@link CacheState#START_UPDATE} and then update the {@link Cache} given
472 | * the current {@link Viewport#window}. It does not want to hold the cache
473 | * lock during the call to {@link Scene#fillCache(Rect)} because the call
474 | * can take a long time. If we hold the lock, the user experience is very
475 | * jumpy.
476 | * The CacheThread and the {@link Cache} work hand in hand, both using the
477 | * cache itself to synchronize on and using the {@link Cache#state}.
478 | * The {@link Cache} is free to update any part of the cache object as long
479 | * as it holds the lock. The CacheThread is careful to make sure that it is
480 | * the {@link Cache#state} is {@link CacheState#IN_UPDATE} as it updates
481 | * the {@link Cache}. It locks and unlocks the cache all along the way, but
482 | * makes sure that the cache is not locked when it calls
483 | * {@link Scene#fillCache(Rect)}.
484 | */
485 | class CacheThread extends Thread {
486 | final Cache cache;
487 | boolean running = false;
488 | void setRunning(boolean value){ running = value; }
489 |
490 | CacheThread(Cache cache){ this.cache = cache; }
491 |
492 | @Override
493 | public void run() {
494 | running=true;
495 | Rect viewportRect = new Rect(0,0,0,0);
496 | while(running){
497 | while(running && cache.getState()!=CacheState.START_UPDATE)
498 | try {
499 | // Sleep until we have something to do
500 | Thread.sleep(Integer.MAX_VALUE);
501 | } catch (InterruptedException ignored) {}
502 | if (!running)
503 | return;
504 | long start = System.currentTimeMillis();
505 | boolean cont = false;
506 | synchronized (cache) {
507 | if (cache.getState()==CacheState.START_UPDATE){
508 | cache.setState(CacheState.IN_UPDATE);
509 | cache.bitmapRef = null;
510 | cont = true;
511 | }
512 | }
513 | if (cont){
514 | synchronized(viewport){
515 | viewportRect.set(viewport.window);
516 | }
517 | synchronized (cache) {
518 | if (cache.getState()==CacheState.IN_UPDATE)
519 | //cache.setWindowRect(viewportRect);
520 | cache.window.set(calculateCacheWindow(viewportRect));
521 | else
522 | cont = false;
523 | }
524 | if (cont){
525 | try{
526 | Bitmap bitmap = fillCache(cache.window);
527 | if (bitmap!=null){
528 | synchronized (cache){
529 | if (cache.getState()==CacheState.IN_UPDATE){
530 | cache.bitmapRef = bitmap;
531 | cache.setState(CacheState.READY);
532 | } else {
533 | Log.w(TAG,"fillCache operation aborted");
534 | }
535 | }
536 | }
537 | long done = System.currentTimeMillis();
538 | if (Debug.isDebuggerConnected())
539 | Log.d(TAG,String.format("fillCache in %dms",done-start));
540 | } catch (OutOfMemoryError e){
541 | Log.d(TAG,"CacheThread out of memory");
542 | /*
543 | * Attempt to recover. Experience shows that if we
544 | * do get an OutOfMemoryError, we're pretty hosed and are going down.
545 | */
546 | synchronized (cache){
547 | fillCacheOutOfMemoryError(e);
548 | if (cache.getState()==CacheState.IN_UPDATE){
549 | cache.setState(CacheState.START_UPDATE);
550 | }
551 | }
552 | }
553 | }
554 | }
555 | }
556 | }
557 | }
558 | //endregion
559 | }
560 |
--------------------------------------------------------------------------------
/v3/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 |
--------------------------------------------------------------------------------
/v3/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/v3/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'org.jetbrains.kotlin.android'
4 | }
5 |
6 | android {
7 | namespace 'com.sigseg.android.worldmap'
8 | compileSdk 34
9 |
10 | defaultConfig {
11 | applicationId "com.sigseg.android.worldmap"
12 | minSdkVersion 24
13 | targetSdkVersion 34
14 | versionCode 12
15 | versionName "3.1"
16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
17 | }
18 | buildTypes {
19 | release {
20 | minifyEnabled false
21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
22 | }
23 | }
24 | compileOptions {
25 | sourceCompatibility JavaVersion.VERSION_1_8
26 | targetCompatibility JavaVersion.VERSION_1_8
27 | }
28 | kotlinOptions {
29 | jvmTarget = "1.8"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/v3/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/v3/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
21 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/v3/app/src/main/assets/world.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/v3/app/src/main/assets/world.jpg
--------------------------------------------------------------------------------
/v3/app/src/main/java/com/sigseg/android/map/ImageSurfaceView.java:
--------------------------------------------------------------------------------
1 | package com.sigseg.android.map;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.Point;
6 | import android.graphics.PointF;
7 | import android.util.AttributeSet;
8 | import android.util.Log;
9 | import android.view.GestureDetector;
10 | import android.view.GestureDetector.OnGestureListener;
11 | import android.view.MotionEvent;
12 | import android.view.ScaleGestureDetector;
13 | import android.view.SurfaceHolder;
14 | import android.view.SurfaceView;
15 |
16 | import com.sigseg.android.view.InputStreamScene;
17 |
18 | import java.io.IOException;
19 | import java.io.InputStream;
20 |
21 | public class ImageSurfaceView extends SurfaceView implements SurfaceHolder.Callback, OnGestureListener {
22 | private final static String TAG = ImageSurfaceView.class.getSimpleName();
23 |
24 | private InputStreamScene scene;
25 | private final TouchController touch;
26 | private GestureDetector gestureDectector;
27 | private ScaleGestureDetector scaleGestureDetector;
28 | private long lastScaleTime = 0;
29 | private long SCALE_MOVE_GUARD = 500; // milliseconds after scale to ignore move events
30 |
31 | private DrawThread drawThread;
32 |
33 | //region getters and setters
34 | public void getViewport(Point p){
35 | scene.getViewport().getOrigin(p);
36 | }
37 |
38 | public void setViewport(Point viewport){
39 | scene.getViewport().setOrigin(viewport.x, viewport.y);
40 | }
41 |
42 | public void setViewportCenter() {
43 | Point viewportSize = new Point();
44 | Point sceneSize = scene.getSceneSize();
45 | scene.getViewport().getSize(viewportSize);
46 |
47 | int x = (sceneSize.x - viewportSize.x) / 2;
48 | int y = (sceneSize.y - viewportSize.y) / 2;
49 | scene.getViewport().setOrigin(x, y);
50 | }
51 |
52 | public void setInputStream(InputStream inputStream) throws IOException {
53 | scene = new InputStreamScene(inputStream);
54 | }
55 |
56 | //endregion
57 |
58 | //region extends SurfaceView
59 | @Override
60 | public boolean onTouchEvent(MotionEvent me) {
61 | boolean consumed = gestureDectector.onTouchEvent(me);
62 | if (consumed)
63 | return true;
64 | scaleGestureDetector.onTouchEvent(me);
65 | switch (me.getAction() & MotionEvent.ACTION_MASK) {
66 | case MotionEvent.ACTION_DOWN: return touch.down(me);
67 | case MotionEvent.ACTION_MOVE:
68 | if (scaleGestureDetector.isInProgress() || System.currentTimeMillis()-lastScaleTimescene, this::invalidate);
82 | init(context);
83 | }
84 |
85 | public ImageSurfaceView(Context context, AttributeSet attrs, int defStyle) {
86 | super(context, attrs, defStyle);
87 | touch = new TouchController(context, ()->scene, this::invalidate);
88 | init(context);
89 | }
90 |
91 | public ImageSurfaceView(Context context, AttributeSet attrs) {
92 | super(context, attrs);
93 | touch = new TouchController(context, ()->scene, this::invalidate);
94 | init(context);
95 | }
96 |
97 | private void init(Context context){
98 | gestureDectector = new GestureDetector(context,this);
99 | getHolder().addCallback(this);
100 | scaleGestureDetector = new ScaleGestureDetector(context, new ScaleListener());
101 | }
102 | //endregion
103 |
104 | //region class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener
105 | private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
106 | private PointF screenFocus = new PointF();
107 | @Override
108 | public boolean onScale(ScaleGestureDetector detector) {
109 | float scaleFactor = detector.getScaleFactor();
110 | if (scaleFactor!=0f && scaleFactor!=1.0f){
111 | scaleFactor = 1/scaleFactor;
112 | screenFocus.set(detector.getFocusX(),detector.getFocusY());
113 | scene.getViewport().zoom(
114 | scaleFactor,
115 | screenFocus);
116 | invalidate();
117 | }
118 | lastScaleTime = System.currentTimeMillis();
119 | return true;
120 | }
121 | }
122 |
123 | //endregion
124 |
125 |
126 | //region implements SurfaceHolder.Callback
127 | @Override
128 | public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
129 | scene.getViewport().setSize(width, height);
130 | Log.d(TAG,String.format("onSizeChanged(w=%d,h=%d)",width,height));
131 | }
132 |
133 | @Override
134 | public void surfaceCreated(SurfaceHolder holder) {
135 | drawThread = new DrawThread(holder);
136 | drawThread.setName("drawThread");
137 | drawThread.setRunning(true);
138 | drawThread.start();
139 | scene.start();
140 | touch.start();
141 | }
142 |
143 | @Override
144 | public void surfaceDestroyed(SurfaceHolder holder) {
145 | touch.stop();
146 | scene.stop();
147 | drawThread.setRunning(false);
148 | boolean retry = true;
149 | while (retry) {
150 | try {
151 | drawThread.join();
152 | retry = false;
153 | } catch (InterruptedException e) {
154 | // we will try it again and again...
155 | }
156 | }
157 | }
158 | //endregion
159 |
160 | //region implements OnGestureListener
161 | @Override
162 | public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
163 | return touch.fling(velocityX, velocityY);
164 | }
165 | //region the rest are defaults
166 | @Override
167 | public boolean onDown(MotionEvent e) {
168 | return false;
169 | }
170 |
171 | @Override
172 | public void onLongPress(MotionEvent e) {
173 | }
174 |
175 | @Override
176 | public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
177 | return false;
178 | }
179 |
180 | @Override
181 | public void onShowPress(MotionEvent e) {
182 | }
183 |
184 | @Override
185 | public boolean onSingleTapUp(MotionEvent e) {
186 | return false;
187 | }
188 | //endregion
189 |
190 | //endregion
191 |
192 | //region class DrawThread
193 |
194 | class DrawThread extends Thread {
195 | private SurfaceHolder surfaceHolder;
196 |
197 | private boolean running = false;
198 | public void setRunning(boolean value){ running = value; }
199 |
200 | public DrawThread(SurfaceHolder surfaceHolder){
201 | this.surfaceHolder = surfaceHolder;
202 | }
203 |
204 | @Override
205 | public void run() {
206 | Canvas c;
207 | while (running) {
208 | try {
209 | // Don't hog the entire CPU
210 | Thread.sleep(5);
211 | } catch (InterruptedException e) {}
212 | c = null;
213 | try {
214 | c = surfaceHolder.lockCanvas();
215 | if (c!=null){
216 | synchronized (surfaceHolder) {
217 | scene.draw(c);// draw it
218 | }
219 | }
220 | } finally {
221 | if (c != null) {
222 | surfaceHolder.unlockCanvasAndPost(c);
223 | }
224 | }
225 | }
226 | }
227 | }
228 | //endregion
229 |
230 | //region class Touch
231 |
232 | //endregion
233 |
234 | }
235 |
--------------------------------------------------------------------------------
/v3/app/src/main/java/com/sigseg/android/map/ImageViewerActivity.kt:
--------------------------------------------------------------------------------
1 | package com.sigseg.android.map
2 |
3 | import android.app.Activity
4 | import android.graphics.Point
5 | import android.os.Bundle
6 | import android.view.Window
7 | import android.view.WindowManager
8 | import com.sigseg.android.worldmap.R
9 |
10 | private const val KEY_X = "X"
11 | private const val KEY_Y = "Y"
12 | private const val MAP_FILE = "world.jpg"
13 |
14 | class ImageViewerActivity : Activity() {
15 | private val imageSurfaceView by lazy { findViewById(R.id.worldview) }
16 |
17 | override fun onCreate(bundle: Bundle?) {
18 | super.onCreate(bundle)
19 | requestWindowFeature(Window.FEATURE_NO_TITLE)
20 | window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
21 | setContentView(R.layout.main)
22 | with(imageSurfaceView) {
23 | setInputStream(assets.open(MAP_FILE))
24 | post {
25 | val p = bundle?.takeIf { it.containsKey(KEY_X) && it.containsKey(KEY_Y) }?.let {
26 | Point(it.getInt(KEY_X), it.getInt(KEY_Y))
27 | }
28 | if (p != null) {
29 | imageSurfaceView.setViewport(p)
30 | } else {
31 | imageSurfaceView.setViewportCenter()
32 | }
33 | }
34 | }
35 | }
36 |
37 | override fun onSaveInstanceState(outState: Bundle) {
38 | val p = Point().apply { imageSurfaceView.getViewport(this) }
39 | outState.putInt(KEY_X, p.x)
40 | outState.putInt(KEY_Y, p.y)
41 | super.onSaveInstanceState(outState)
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/v3/app/src/main/java/com/sigseg/android/map/TouchController.kt:
--------------------------------------------------------------------------------
1 | package com.sigseg.android.map
2 |
3 | import android.content.Context
4 | import android.graphics.Point
5 | import android.view.MotionEvent
6 | import android.widget.Scroller
7 | import com.sigseg.android.view.Scene
8 |
9 | internal class TouchController(
10 | context: Context,
11 | private val scene: () -> Scene,
12 | private val doInvalidate: Runnable
13 | ) {
14 | private var state = TouchState.UNTOUCHED
15 |
16 | /** Where on the view did we initially touch */
17 | private val viewDown = Point(0, 0)
18 |
19 | /** What was the coordinates of the viewport origin? */
20 | private val viewportOriginAtDown = Point(0, 0)
21 | private val scroller = Scroller(context)
22 | private var touchThread: TouchThread? = null
23 |
24 | fun start() {
25 | touchThread = TouchThread(this).apply {
26 | start()
27 | }
28 | }
29 |
30 | fun stop() {
31 | val thread = touchThread
32 | if (thread!= null) {
33 | thread.stopThread()
34 | var retry = true
35 | while (retry) {
36 | try {
37 | thread.join()
38 | retry = false
39 | } catch (e: InterruptedException) {
40 | // we will try it again and again...
41 | }
42 | }
43 | touchThread = null
44 | }
45 | }
46 |
47 |
48 | fun fling(velocityX: Float, velocityY: Float): Boolean {
49 | val thread = touchThread
50 | if (thread != null ){
51 | val origin = Point().apply { scene().viewport.getOrigin(this) }
52 | val viewSize = Point().apply { scene().viewport.getSize(this) }
53 | val sceneSize = Point().apply { scene().getSceneSize(this) }
54 |
55 | synchronized(this) {
56 | state = TouchState.START_FLING
57 | scene().setSuspend(true)
58 | scroller.fling(
59 | origin.x,
60 | origin.y, -velocityX.toInt(), -velocityY.toInt(),
61 | 0,
62 | sceneSize.x - viewSize.x,
63 | 0,
64 | sceneSize.y - viewSize.y
65 | )
66 | thread.interrupt()
67 | }
68 | }
69 | return true
70 | }
71 |
72 | fun down(event: MotionEvent): Boolean {
73 | scene().setSuspend(false) // If we were suspended because of a fling
74 | synchronized(this) {
75 | state = TouchState.IN_TOUCH
76 | viewDown.x = event.x.toInt()
77 | viewDown.y = event.y.toInt()
78 | val p = Point()
79 | scene().viewport.getOrigin(p)
80 | viewportOriginAtDown[p.x] = p.y
81 | }
82 | return true
83 | }
84 |
85 | fun move(event: MotionEvent): Boolean {
86 | if (state == TouchState.IN_TOUCH) {
87 | val zoom = scene().viewport.zoom
88 | val deltaX = zoom * (event.x - viewDown.x)
89 | val deltaY = zoom * (event.y - viewDown.y)
90 | scene().viewport.setOrigin(
91 | (viewportOriginAtDown.x - deltaX).toInt(),
92 | (viewportOriginAtDown.y - deltaY).toInt()
93 | )
94 | doInvalidate.run()
95 | }
96 | return true
97 | }
98 |
99 | fun up(): Boolean {
100 | if (state == TouchState.IN_TOUCH) {
101 | state = TouchState.UNTOUCHED
102 | }
103 | return true
104 | }
105 |
106 | fun cancel(): Boolean {
107 | if (state == TouchState.IN_TOUCH) {
108 | state = TouchState.UNTOUCHED
109 | }
110 | return true
111 | }
112 |
113 | fun inFling() = state in setOf(TouchState.START_FLING, TouchState.IN_FLING)
114 |
115 | fun startFling() {
116 | synchronized(this) {
117 | if (state == TouchState.START_FLING) {
118 | state = TouchState.IN_FLING
119 | }
120 | }
121 | if (state == TouchState.IN_FLING) {
122 | scroller.computeScrollOffset()
123 | scene().viewport.setOrigin(scroller.currX, scroller.currY)
124 | if (scroller.isFinished) {
125 | scene().setSuspend(false)
126 | synchronized(this) {
127 | state = TouchState.UNTOUCHED
128 | try {
129 | Thread.sleep(5)
130 | } catch (e: InterruptedException) {
131 | }
132 | }
133 | }
134 | }
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/v3/app/src/main/java/com/sigseg/android/map/TouchState.java:
--------------------------------------------------------------------------------
1 | package com.sigseg.android.map;
2 |
3 | enum TouchState {UNTOUCHED, IN_TOUCH, START_FLING, IN_FLING}
4 |
--------------------------------------------------------------------------------
/v3/app/src/main/java/com/sigseg/android/map/TouchThread.kt:
--------------------------------------------------------------------------------
1 | package com.sigseg.android.map
2 |
3 | internal class TouchThread(
4 | private val touch: TouchController
5 | ) : Thread() {
6 | private var running = false
7 |
8 | init {
9 | name = "TouchThread"
10 | }
11 |
12 | override fun start() {
13 | running = true
14 | super.start()
15 | }
16 |
17 | fun stopThread() {
18 | running = false
19 | interrupt()
20 | }
21 |
22 | override fun run() {
23 | while (running) {
24 | while (!touch.inFling()) {
25 | try { sleep(Long.MAX_VALUE) } catch (_: InterruptedException) { }
26 | if (!running) return
27 | }
28 | touch.startFling()
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/v3/app/src/main/java/com/sigseg/android/view/InputStreamScene.java:
--------------------------------------------------------------------------------
1 | package com.sigseg.android.view;
2 |
3 | import java.io.IOException;
4 | import java.io.InputStream;
5 |
6 | import android.graphics.*;
7 | import android.util.Log;
8 |
9 | public class InputStreamScene extends Scene {
10 | private static final String TAG=InputStreamScene.class.getSimpleName();
11 |
12 | private static final boolean DEBUG = false;
13 | private static final BitmapFactory.Options options = new BitmapFactory.Options();
14 |
15 | /** What is the downsample size for the sample image? 1=1/2, 2=1/4 3=1/8, etc */
16 | private static final int DOWN_SAMPLE_SHIFT = 2;
17 |
18 | /** How many bytes does one pixel use? */
19 | private final int BYTES_PER_PIXEL = 4;
20 |
21 | /** What percent of total memory should we use for the cache? The bigger the cache,
22 | * the longer it takes to read -- 1.2 secs for 25%, 600ms for 10%, 500ms for 5%.
23 | * User experience seems to be best for smaller values.
24 | */
25 | private int percent = 5; // Above 25 and we get OOMs
26 |
27 | private BitmapRegionDecoder decoder;
28 | private Bitmap sampleBitmap;
29 |
30 | static {
31 | options.inPreferredConfig = Bitmap.Config.RGB_565;
32 | }
33 |
34 | public InputStreamScene(InputStream inputStream) throws IOException {
35 | BitmapFactory.Options tmpOptions = new BitmapFactory.Options();
36 |
37 | this.decoder = BitmapRegionDecoder.newInstance(inputStream, false);
38 |
39 | // Grab the bounds for the scene dimensions
40 | tmpOptions.inJustDecodeBounds = true;
41 | inputStream.reset();
42 | BitmapFactory.decodeStream(inputStream, null, tmpOptions);
43 | inputStream.reset();
44 | setSceneSize(tmpOptions.outWidth, tmpOptions.outHeight);
45 |
46 | // Create the sample image
47 | tmpOptions.inJustDecodeBounds = false;
48 | tmpOptions.inSampleSize = (1<< DOWN_SAMPLE_SHIFT);
49 | sampleBitmap = BitmapFactory.decodeStream(inputStream, null, tmpOptions);
50 |
51 | initialize();
52 | }
53 |
54 | @Override
55 | protected Bitmap fillCache(Rect origin) {
56 | Bitmap bitmap = null;
57 | if (decoder!=null)
58 | bitmap = decoder.decodeRegion( origin, options );
59 | return bitmap;
60 | }
61 |
62 | private static Paint red = new Paint();
63 | static{
64 | red.setColor(Color.RED);
65 | red.setStrokeWidth(5L);
66 | }
67 | @Override
68 | protected void drawSampleRectIntoBitmap(Bitmap bitmap, Rect rectOfSample) {
69 | if (bitmap!=null){
70 | Canvas c = new Canvas(bitmap);
71 | int left = (rectOfSample.left>> DOWN_SAMPLE_SHIFT);
72 | int top = (rectOfSample.top>> DOWN_SAMPLE_SHIFT);
73 | int right = left + (rectOfSample.width()>> DOWN_SAMPLE_SHIFT);
74 | int bottom = top + (rectOfSample.height()>> DOWN_SAMPLE_SHIFT);
75 | Rect srcRect = new Rect( left, top, right, bottom );
76 | Rect identity= new Rect(0,0,c.getWidth(),c.getHeight());
77 | c.drawBitmap(
78 | sampleBitmap,
79 | srcRect,
80 | identity,
81 | null
82 | );
83 | // c.drawLine(0L,0L,c.getWidth(),c.getHeight(),red);
84 | }
85 | }
86 |
87 | // @Override
88 | // protected Rect calculateCacheWindow(Rect viewportRect) {
89 | // // Simplest implementation
90 | // return viewportRect;
91 | // }
92 |
93 | private Rect calculatedCacheWindowRect = new Rect();
94 | @Override
95 | protected Rect calculateCacheWindow(Rect viewportRect) {
96 | long bytesToUse = Runtime.getRuntime().maxMemory() * percent / 100;
97 | Point size = getSceneSize();
98 |
99 | int vw = viewportRect.width();
100 | int vh = viewportRect.height();
101 |
102 | // Calculate the max size of the margins to fit in our memory budget
103 | int tw=0;
104 | int th=0;
105 | int mw = tw;
106 | int mh = th;
107 | while((vw+tw) * (vh+th) * BYTES_PER_PIXEL < bytesToUse){
108 | mw = tw++;
109 | mh = th++;
110 | }
111 |
112 | // Trim the margins if they're too big.
113 | if (vw+mw > size.x) // viewport width + margin width > width of the image
114 | mw = Math.max(0, size.x-vw);
115 | if (vh+mh > size.y) // viewport height + margin height > height of the image
116 | mh = Math.max(0, size.y-vh);
117 |
118 | // Figure out the left & right based on the margin. We assume our viewportRect
119 | // is <= our size. If that's not the case, then this logic breaks.
120 | int left = viewportRect.left - (mw>>1);
121 | int right = viewportRect.right + (mw>>1);
122 | if (left<0){
123 | right = right - left; // Add's the overage on the left side back to the right
124 | left = 0;
125 | }
126 | if (right>size.x){
127 | left = left - (right-size.x); // Adds overage on right side back to left
128 | right = size.x;
129 | }
130 |
131 | // Figure out the top & bottom based on the margin. We assume our viewportRect
132 | // is <= our size. If that's not the case, then this logic breaks.
133 | int top = viewportRect.top - (mh>>1);
134 | int bottom = viewportRect.bottom + (mh>>1);
135 | if (top<0){
136 | bottom = bottom - top; // Add's the overage on the top back to the bottom
137 | top = 0;
138 | }
139 | if (bottom>size.y){
140 | top = top - (bottom-size.y); // Adds overage on bottom back to top
141 | bottom = size.y;
142 | }
143 |
144 | // Set the origin based on our new calculated values.
145 | calculatedCacheWindowRect.set(left, top, right, bottom);
146 | if (DEBUG) Log.d(TAG,"new cache.originRect = "+calculatedCacheWindowRect.toShortString()+" size="+size.toString());
147 | return calculatedCacheWindowRect;
148 | }
149 |
150 | @Override
151 | protected void fillCacheOutOfMemoryError(OutOfMemoryError error) {
152 | if (percent>0)
153 | percent -= 1;
154 | Log.e(TAG,String.format("caught oom -- cache now at %d percent.",percent));
155 | }
156 |
157 | @Override
158 | protected void drawComplete(Canvas canvas) {
159 | // TODO Auto-generated method stub
160 |
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/v3/app/src/main/java/com/sigseg/android/view/Scene.java:
--------------------------------------------------------------------------------
1 | package com.sigseg.android.view;
2 |
3 | import android.graphics.*;
4 | import android.graphics.Bitmap.Config;
5 | import android.os.Debug;
6 | import android.util.Log;
7 |
8 | /*
9 | * +-------------------------------------------------------------------+
10 | * | | |
11 | * | +------------------------+ | |
12 | * | | | | |
13 | * | | | | |
14 | * | | | | |
15 | * | | Viewport | | |
16 | * | +------------------------+ | |
17 | * | | |
18 | * | | |
19 | * | | |
20 | * | Cache | |
21 | * |----------------------------------------+ |
22 | * | |
23 | * | |
24 | * | |
25 | * | |
26 | * | |
27 | * | Entire bitmap -- too big for memory |
28 | * +-------------------------------------------------------------------+
29 | */
30 | /**
31 | * Keeps track of an entire Scene -- a bitmap (or virtual bitmap) that is much too large
32 | * to fit into memory. Clients subclass this class and extend its abstract methods to
33 | * actually return the necessary bitmaps.
34 | */
35 | public abstract class Scene {
36 | private final String TAG = "Scene";
37 |
38 | private final static int MINIMUM_PIXELS_IN_VIEW = 50;
39 |
40 | /** The size of the Scene */
41 | private Point size = new Point();
42 | /** The viewport */
43 | private final Viewport viewport = new Viewport();
44 | /** The cache */
45 | private final Cache cache = new Cache();
46 |
47 | //region [gs]etSceneSize
48 | /** Set the size of the scene */
49 | public void setSceneSize(int width, int height){
50 | size.set(width, height);
51 | }
52 | /** Returns a Point representing the size of the scene. Don't modify the returned Point! */
53 | public Point getSceneSize(){
54 | return size;
55 | }
56 | /** Set the passed-in point to the size of the scene */
57 | public void getSceneSize(Point point){
58 | point.set(size.x, size.y);
59 | }
60 | //endregion
61 |
62 | //region getViewport()
63 | public Viewport getViewport(){return viewport;}
64 | //endregion
65 |
66 | //region initialize/start/stop/suspend/invalidate the cache
67 | /** Initializes the cache */
68 | public void initialize(){
69 | if (cache.getState()==CacheState.UNINITIALIZED){
70 | synchronized(cache){
71 | cache.setState(CacheState.INITIALIZED);
72 | }
73 | }
74 | }
75 | /** Starts the cache thread */
76 | public void start(){
77 | cache.start();
78 | }
79 | /** Stops the cache thread */
80 | public void stop(){
81 | cache.stop();
82 | }
83 | /**
84 | * Suspends or unsuspends the cache thread. This can be
85 | * used to temporarily stop the cache from updating
86 | * during a fling event.
87 | * @param suspend True to suspend the cache. False to unsuspend.
88 | */
89 | public void setSuspend(boolean suspend){
90 | if (suspend) {
91 | synchronized(cache){
92 | cache.setState(CacheState.SUSPEND);
93 | }
94 | } else {
95 | if (cache.getState()==CacheState.SUSPEND) {
96 | synchronized(cache){
97 | cache.setState(CacheState.INITIALIZED);
98 | }
99 | }
100 | }
101 | }
102 | /** Invalidate the cache. This causes it to refill */
103 | @SuppressWarnings("unused")
104 | public void invalidate(){
105 | cache.invalidate();
106 | }
107 | //endregion
108 |
109 | //region void draw(Canvas c)
110 | /**
111 | * Draw the scene to the canvas. This operation fills the canvas with
112 | * the bitmap referenced by the viewport's location within the Scene.
113 | * If the cache already has the data (and is not suspended), then the
114 | * high resolution bitmap from the cache is used. If it's not available,
115 | * then the lower resolution bitmap from the sample is used.
116 | */
117 | public void draw(Canvas c){
118 | viewport.draw(c);
119 | }
120 | //endregion
121 |
122 | //region protected abstract
123 | /**
124 | * This method must return a high resolution Bitmap that the Scene
125 | * will use to fill out the viewport bitmap upon request. This bitmap
126 | * is normally larger than the viewport so that the viewport can be
127 | * scrolled without having to refresh the cache. This method runs
128 | * on a thread other than the UI thread, and it is not under a lock, so
129 | * it is expected that this method can run for a long time (seconds?).
130 | * @param rectOfCache The Rect representing the area of the Scene that
131 | * the Scene wants cached.
132 | * @return the Bitmap representing the requested area of the larger bitmap
133 | */
134 | protected abstract Bitmap fillCache(Rect rectOfCache);
135 | /**
136 | * The memory allocation you just did in fillCache caused an OutOfMemoryError.
137 | * You can attempt to recover. Experience shows that when we get an
138 | * OutOfMemoryError, we're pretty hosed and are going down. For instance, if
139 | * we're trying to decode a bitmap region with
140 | * {@link android.graphics.BitmapRegionDecoder} and we run out of memory,
141 | * we're going to die somewhere in the C code with a SIGSEGV.
142 | * @param error The OutOfMemoryError exception data
143 | */
144 | protected abstract void fillCacheOutOfMemoryError( OutOfMemoryError error );
145 | /**
146 | * Calculate the Rect of the cache's window based on the current viewportRect.
147 | * The returned Rect must at least contain the viewportRect, but it can be
148 | * larger if the system believes a bitmap of the returned size will fit into
149 | * memory. This function must be fast as it happens while the cache lock is held.
150 | * @param viewportRect The returned must be able to contain this Rect
151 | * @return The Rect that will be used to fill the cache
152 | */
153 | protected abstract Rect calculateCacheWindow(Rect viewportRect);
154 | /**
155 | * This method fills the passed-in bitmap with sample data. This function must
156 | * return as fast as possible so it shouldn't have to do any IO at all -- the
157 | * quality of the user experience rests on the speed of this function.
158 | * @param bitmap The Bitmap to fill
159 | * @param rectOfSample Rectangle within the Scene that this bitmap represents.
160 | */
161 | protected abstract void drawSampleRectIntoBitmap(Bitmap bitmap, Rect rectOfSample);
162 | /**
163 | * The Cache is done drawing the bitmap -- time to add the finishing touches
164 | * @param canvas a canvas on which to draw
165 | */
166 | protected abstract void drawComplete(Canvas canvas);
167 | //endregion
168 |
169 | //region class Viewport
170 |
171 | public class Viewport {
172 | /** The bitmap of the current viewport */
173 | Bitmap bitmap = null;
174 | /** A Rect that defines where the Viewport is within the scene */
175 | final Rect window = new Rect(0,0,0,0);
176 | float zoom = 1.0f;
177 |
178 | public void setOrigin(int x, int y){
179 | synchronized(this){
180 | int w = window.width();
181 | int h = window.height();
182 |
183 | // check bounds
184 | if (x < 0)
185 | x = 0;
186 |
187 | if (y < 0)
188 | y = 0;
189 |
190 | if (x + w > size.x)
191 | x = size.x - w;
192 |
193 | if (y + h > size.y)
194 | y = size.y - h;
195 |
196 | window.set(x, y, x+w, y+h);
197 | }
198 | }
199 | public void setSize( int w, int h ){
200 | synchronized (this) {
201 | if (bitmap !=null){
202 | bitmap.recycle();
203 | bitmap = null;
204 | }
205 | bitmap = Bitmap.createBitmap(w, h, Config.RGB_565);
206 | window.set(
207 | window.left,
208 | window.top,
209 | window.left + w,
210 | window.top + h);
211 | }
212 | }
213 | public void getOrigin(Point p){
214 | synchronized (this) {
215 | p.set(window.left, window.top);
216 | }
217 | }
218 | public void getSize(Point p){
219 | synchronized (this) {
220 | p.x = window.width();
221 | p.y = window.height();
222 | }
223 | }
224 | public void getPhysicalSize(Point p){
225 | synchronized (this){
226 | p.x = getPhysicalWidth();
227 | p.y = getPhysicalHeight();
228 | }
229 | }
230 | public int getPhysicalWidth(){
231 | return bitmap.getWidth();
232 | }
233 | public int getPhysicalHeight(){
234 | return bitmap.getHeight();
235 | }
236 | public float getZoom(){
237 | return zoom;
238 | }
239 | public void zoom(float factor, PointF screenFocus){
240 | if (factor!=1.0){
241 |
242 | PointF screenSize = new PointF(bitmap.getWidth(),bitmap.getHeight());
243 | PointF sceneSize = new PointF(getSceneSize());
244 | float screenWidthToHeight = screenSize.x / screenSize.y;
245 | float screenHeightToWidth = screenSize.y / screenSize.x;
246 | synchronized (this){
247 | float newZoom = zoom * factor;
248 | RectF w1 = new RectF(window);
249 | RectF w2 = new RectF();
250 | PointF sceneFocus = new PointF(
251 | w1.left + (screenFocus.x/screenSize.x)*w1.width(),
252 | w1.top + (screenFocus.y/screenSize.y)*w1.height()
253 | );
254 | float w2Width = getPhysicalWidth() * newZoom;
255 | if (w2Width > sceneSize.x){
256 | w2Width = sceneSize.x;
257 | newZoom = w2Width / getPhysicalWidth();
258 | }
259 | if (w2Width < MINIMUM_PIXELS_IN_VIEW){
260 | w2Width = MINIMUM_PIXELS_IN_VIEW;
261 | newZoom = w2Width / getPhysicalWidth();
262 | }
263 | float w2Height = w2Width * screenHeightToWidth;
264 | if (w2Height > sceneSize.y){
265 | w2Height = sceneSize.y;
266 | w2Width = w2Height * screenWidthToHeight;
267 | newZoom = w2Width / getPhysicalWidth();
268 | }
269 | if (w2Height < MINIMUM_PIXELS_IN_VIEW){
270 | w2Height = MINIMUM_PIXELS_IN_VIEW;
271 | w2Width = w2Height * screenWidthToHeight;
272 | newZoom = w2Width / getPhysicalWidth();
273 | }
274 | w2.left = sceneFocus.x - ((screenFocus.x/screenSize.x) * w2Width);
275 | w2.top = sceneFocus.y - ((screenFocus.y/screenSize.y) * w2Height);
276 | if (w2.left<0)
277 | w2.left=0;
278 | if (w2.top<0)
279 | w2.top=0;
280 | w2.right = w2.left+w2Width;
281 | w2.bottom= w2.top+w2Height;
282 | if (w2.right>sceneSize.x){
283 | w2.right=sceneSize.x;
284 | w2.left=w2.right-w2Width;
285 | }
286 | if (w2.bottom>sceneSize.y){
287 | w2.bottom=sceneSize.y;
288 | w2.top=w2.bottom-w2Height;
289 | }
290 | window.set((int)w2.left,(int)w2.top,(int)w2.right,(int)w2.bottom);
291 | zoom = newZoom;
292 | // Log.d(TAG,String.format(
293 | // "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)",
294 | // factor,
295 | // zoom,
296 | // screenFocus.x,
297 | // screenFocus.y,
298 | // sceneFocus.x,
299 | // sceneFocus.y,
300 | // w1.width(),w1.height(),
301 | // w2Width, w2Height,
302 | // w1.left,w1.top,w1.right,w1.bottom,
303 | // w2.left,w2.top,w2.right,w2.bottom
304 | // ));
305 | }
306 | }
307 | }
308 | void draw(Canvas c){
309 | cache.update(this);
310 | synchronized (this){
311 | if (c!=null && bitmap !=null){
312 | c.drawBitmap(bitmap, 0F, 0F, null);
313 | drawComplete(c);
314 | }
315 | }
316 | }
317 | }
318 | //endregion
319 |
320 | //region class Cache
321 |
322 | private enum CacheState {UNINITIALIZED,INITIALIZED,START_UPDATE,IN_UPDATE,READY,SUSPEND}
323 | /**
324 | * Keep track of the cached bitmap
325 | */
326 | private class Cache {
327 | /** A Rect that defines where the Cache is within the scene */
328 | final Rect window = new Rect(0,0,0,0);
329 | /** The bitmap of the current cache */
330 | Bitmap bitmapRef = null;
331 | CacheState state = CacheState.UNINITIALIZED;
332 |
333 | void setState(CacheState newState){
334 | if (Debug.isDebuggerConnected())
335 | Log.i(TAG,String.format("cacheState old=%s new=%s",state.toString(),newState.toString()));
336 | state = newState;
337 | }
338 | CacheState getState(){ return state; }
339 |
340 | /** Our load from disk thread */
341 | CacheThread cacheThread;
342 |
343 | void start(){
344 | if (cacheThread!=null){
345 | cacheThread.setRunning(false);
346 | cacheThread.interrupt();
347 | cacheThread = null;
348 | }
349 | cacheThread = new CacheThread(this);
350 | cacheThread.setName("cacheThread");
351 | cacheThread.start();
352 | }
353 |
354 | void stop(){
355 | cacheThread.running = false;
356 | cacheThread.interrupt();
357 |
358 | boolean retry = true;
359 | while (retry) {
360 | try {
361 | cacheThread.join();
362 | retry = false;
363 | } catch (InterruptedException e) {
364 | // we will try it again and again...
365 | }
366 | }
367 | cacheThread = null;
368 | }
369 | void invalidate(){
370 | synchronized(this){
371 | setState(CacheState.INITIALIZED);
372 | cacheThread.interrupt();
373 | }
374 | }
375 |
376 | /** Fill the bitmap with the part of the scene referenced by the viewport Rect */
377 | void update(Viewport viewport){
378 | Bitmap bitmap = null; // If this is null at the bottom, then load from the sample
379 | synchronized(this){
380 | switch(getState()){
381 | case UNINITIALIZED:
382 | // nothing can be done -- should never get here
383 | return;
384 | case INITIALIZED:
385 | // time to cache some data
386 | setState(CacheState.START_UPDATE);
387 | cacheThread.interrupt();
388 | break;
389 | case START_UPDATE:
390 | // I already told the thread to start
391 | break;
392 | case IN_UPDATE:
393 | // Already reading some data, just use the sample
394 | break;
395 | case SUSPEND:
396 | // Loading from cache suspended.
397 | break;
398 | case READY:
399 | // I have some data to show
400 | if (bitmapRef==null){
401 | // Start the cache off right
402 | if (Debug.isDebuggerConnected())
403 | Log.d(TAG,"bitmapRef is null");
404 | setState(CacheState.START_UPDATE);
405 | cacheThread.interrupt();
406 | } else if (!window.contains(viewport.window)){
407 | if (Debug.isDebuggerConnected())
408 | Log.d(TAG,"viewport not in cache");
409 | setState(CacheState.START_UPDATE);
410 | cacheThread.interrupt();
411 | } else {
412 | // Happy case -- the cache already contains the Viewport
413 | bitmap = bitmapRef;
414 | }
415 | break;
416 | }
417 | }
418 | if (bitmap==null)
419 | loadSampleIntoViewport();
420 | else
421 | loadBitmapIntoViewport(bitmap);
422 | }
423 |
424 | void loadBitmapIntoViewport(Bitmap bitmap){
425 | if (bitmap!=null){
426 | synchronized(viewport){
427 | int left = viewport.window.left - window.left;
428 | int top = viewport.window.top - window.top;
429 | int right = left + viewport.window.width();
430 | int bottom = top + viewport.window.height();
431 | viewport.getPhysicalSize(dstSize);
432 | srcRect.set( left, top, right, bottom );
433 | dstRect.set(0, 0, dstSize.x, dstSize.y);
434 | Canvas c = new Canvas(viewport.bitmap);
435 | c.drawBitmap(
436 | bitmap,
437 | srcRect,
438 | dstRect,
439 | null);
440 | // try {
441 | // FileOutputStream fos = new FileOutputStream("/sdcard/viewport.png");
442 | // viewport.bitmap.compress(Bitmap.CompressFormat.PNG, 99, fos);
443 | // Thread.sleep(1000);
444 | // } catch (Exception e){
445 | // System.out.print(e.getMessage());
446 | // }
447 | }
448 | }
449 | }
450 | final Rect srcRect = new Rect(0,0,0,0);
451 | final Rect dstRect = new Rect(0,0,0,0);
452 | final Point dstSize = new Point();
453 |
454 | void loadSampleIntoViewport(){
455 | if (getState()!=CacheState.UNINITIALIZED){
456 | synchronized(viewport){
457 | drawSampleRectIntoBitmap(
458 | viewport.bitmap,
459 | viewport.window
460 | );
461 | }
462 | }
463 | }
464 | }
465 | //endregion
466 |
467 | //region class CacheThread
468 | /**
469 | * The CacheThread's job is to wait until the {@link Cache#state} is
470 | * {@link CacheState#START_UPDATE} and then update the {@link Cache} given
471 | * the current {@link Viewport#window}. It does not want to hold the cache
472 | * lock during the call to {@link Scene#fillCache(Rect)} because the call
473 | * can take a long time. If we hold the lock, the user experience is very
474 | * jumpy.
475 | * The CacheThread and the {@link Cache} work hand in hand, both using the
476 | * cache itself to synchronize on and using the {@link Cache#state}.
477 | * The {@link Cache} is free to update any part of the cache object as long
478 | * as it holds the lock. The CacheThread is careful to make sure that it is
479 | * the {@link Cache#state} is {@link CacheState#IN_UPDATE} as it updates
480 | * the {@link Cache}. It locks and unlocks the cache all along the way, but
481 | * makes sure that the cache is not locked when it calls
482 | * {@link Scene#fillCache(Rect)}.
483 | */
484 | class CacheThread extends Thread {
485 | final Cache cache;
486 | boolean running = false;
487 | void setRunning(boolean value){ running = value; }
488 |
489 | CacheThread(Cache cache){ this.cache = cache; }
490 |
491 | @Override
492 | public void run() {
493 | running=true;
494 | Rect viewportRect = new Rect(0,0,0,0);
495 | while(running){
496 | while(running && cache.getState()!=CacheState.START_UPDATE)
497 | try {
498 | // Sleep until we have something to do
499 | Thread.sleep(Integer.MAX_VALUE);
500 | } catch (InterruptedException ignored) {}
501 | if (!running)
502 | return;
503 | long start = System.currentTimeMillis();
504 | boolean cont = false;
505 | synchronized (cache) {
506 | if (cache.getState()==CacheState.START_UPDATE){
507 | cache.setState(CacheState.IN_UPDATE);
508 | cache.bitmapRef = null;
509 | cont = true;
510 | }
511 | }
512 | if (cont){
513 | synchronized(viewport){
514 | viewportRect.set(viewport.window);
515 | }
516 | synchronized (cache) {
517 | if (cache.getState()==CacheState.IN_UPDATE)
518 | //cache.setWindowRect(viewportRect);
519 | cache.window.set(calculateCacheWindow(viewportRect));
520 | else
521 | cont = false;
522 | }
523 | if (cont){
524 | try{
525 | Bitmap bitmap = fillCache(cache.window);
526 | if (bitmap!=null){
527 | synchronized (cache){
528 | if (cache.getState()==CacheState.IN_UPDATE){
529 | cache.bitmapRef = bitmap;
530 | cache.setState(CacheState.READY);
531 | } else {
532 | Log.w(TAG,"fillCache operation aborted");
533 | }
534 | }
535 | }
536 | long done = System.currentTimeMillis();
537 | if (Debug.isDebuggerConnected())
538 | Log.d(TAG,String.format("fillCache in %dms",done-start));
539 | } catch (OutOfMemoryError e){
540 | Log.d(TAG,"CacheThread out of memory");
541 | /*
542 | * Attempt to recover. Experience shows that if we
543 | * do get an OutOfMemoryError, we're pretty hosed and are going down.
544 | */
545 | synchronized (cache){
546 | fillCacheOutOfMemoryError(e);
547 | if (cache.getState()==CacheState.IN_UPDATE){
548 | cache.setState(CacheState.START_UPDATE);
549 | }
550 | }
551 | }
552 | }
553 | }
554 | }
555 | }
556 | }
557 | //endregion
558 | }
559 |
--------------------------------------------------------------------------------
/v3/app/src/main/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/v3/app/src/main/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/v3/app/src/main/res/drawable-ldpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/v3/app/src/main/res/drawable-ldpi/ic_launcher.png
--------------------------------------------------------------------------------
/v3/app/src/main/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/v3/app/src/main/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/v3/app/src/main/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/v3/app/src/main/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/v3/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/v3/app/src/main/res/layout/main.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
12 |
--------------------------------------------------------------------------------
/v3/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #008577
4 | #00574B
5 | #D81B60
6 |
7 |
--------------------------------------------------------------------------------
/v3/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | WorldMap
4 |
--------------------------------------------------------------------------------
/v3/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/v3/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | plugins {
3 | id 'com.android.application' version '8.2.0' apply false
4 | id 'org.jetbrains.kotlin.android' version '1.9.0' apply false
5 | }
--------------------------------------------------------------------------------
/v3/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/v3/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/v3/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/v3/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Sep 28 11:19:31 PDT 2019
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/v3/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/v3/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/v3/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | gradlePluginPortal()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | }
15 |
16 | include ':app'
17 | rootProject.name='WorldMap'
18 |
--------------------------------------------------------------------------------
/worldmap/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
22 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/worldmap/art/4973-1209-east-sea.pxm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/worldmap/art/4973-1209-east-sea.pxm
--------------------------------------------------------------------------------
/worldmap/assets/layers/ko/4973-1209-east-sea.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/worldmap/assets/layers/ko/4973-1209-east-sea.jpg
--------------------------------------------------------------------------------
/worldmap/assets/world.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/worldmap/assets/world.jpg
--------------------------------------------------------------------------------
/worldmap/build.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
29 |
30 |
31 |
35 |
36 |
37 |
38 |
39 |
40 |
49 |
50 |
51 |
52 |
56 |
57 |
69 |
70 |
71 |
89 |
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/worldmap/proguard.cfg:
--------------------------------------------------------------------------------
1 | -optimizationpasses 5
2 | -dontusemixedcaseclassnames
3 | -dontskipnonpubliclibraryclasses
4 | -dontpreverify
5 | -verbose
6 | -optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
7 |
8 | -keep public class * extends android.app.Activity
9 | -keep public class * extends android.app.Application
10 | -keep public class * extends android.app.Service
11 | -keep public class * extends android.content.BroadcastReceiver
12 | -keep public class * extends android.content.ContentProvider
13 | -keep public class * extends android.app.backup.BackupAgentHelper
14 | -keep public class * extends android.preference.Preference
15 | -keep public class com.android.vending.licensing.ILicensingService
16 |
17 | -keepclasseswithmembernames class * {
18 | native ;
19 | }
20 |
21 | -keepclasseswithmembers class * {
22 | public (android.content.Context, android.util.AttributeSet);
23 | }
24 |
25 | -keepclasseswithmembers class * {
26 | public (android.content.Context, android.util.AttributeSet, int);
27 | }
28 |
29 | -keepclassmembers class * extends android.app.Activity {
30 | public void *(android.view.View);
31 | }
32 |
33 | -keepclassmembers enum * {
34 | public static **[] values();
35 | public static ** valueOf(java.lang.String);
36 | }
37 |
38 | -keep class * implements android.os.Parcelable {
39 | public static final android.os.Parcelable$Creator *;
40 | }
41 |
--------------------------------------------------------------------------------
/worldmap/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system use,
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 |
10 | # Project target.
11 | target=android-17
12 | android.library.reference.1=../library
13 |
--------------------------------------------------------------------------------
/worldmap/release/01/WorldMap.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/worldmap/release/01/WorldMap.apk
--------------------------------------------------------------------------------
/worldmap/release/01/icon-512x512.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/worldmap/release/01/icon-512x512.jpg
--------------------------------------------------------------------------------
/worldmap/release/01/shot01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/worldmap/release/01/shot01.png
--------------------------------------------------------------------------------
/worldmap/release/01/shot02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/worldmap/release/01/shot02.png
--------------------------------------------------------------------------------
/worldmap/release/02/WorldMap.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/worldmap/release/02/WorldMap.apk
--------------------------------------------------------------------------------
/worldmap/release/02/worldmap-36x36.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/worldmap/release/02/worldmap-36x36.png
--------------------------------------------------------------------------------
/worldmap/release/02/worldmap-48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/worldmap/release/02/worldmap-48x48.png
--------------------------------------------------------------------------------
/worldmap/release/02/worldmap-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/worldmap/release/02/worldmap-512x512.png
--------------------------------------------------------------------------------
/worldmap/release/02/worldmap-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/worldmap/release/02/worldmap-72x72.png
--------------------------------------------------------------------------------
/worldmap/release/02/worldmap-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/worldmap/release/02/worldmap-96x96.png
--------------------------------------------------------------------------------
/worldmap/release/02/worldmap-icon.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/worldmap/release/02/worldmap-icon.xcf
--------------------------------------------------------------------------------
/worldmap/release/03/WorldMap.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/worldmap/release/03/WorldMap.apk
--------------------------------------------------------------------------------
/worldmap/release/04/WorldMap-release.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/worldmap/release/04/WorldMap-release.apk
--------------------------------------------------------------------------------
/worldmap/release/05/WorldMap-release.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/worldmap/release/05/WorldMap-release.apk
--------------------------------------------------------------------------------
/worldmap/release/10/release/app-release.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/worldmap/release/10/release/app-release.apk
--------------------------------------------------------------------------------
/worldmap/release/10/release/output.json:
--------------------------------------------------------------------------------
1 | [{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":10,"versionName":"3.0","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}]
--------------------------------------------------------------------------------