project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/MapPreviewGenerator.java
changeset 7508 763d3961400b
child 7582 714310efad8f
equal deleted inserted replaced
7504:ed1d52c5aa94 7508:763d3961400b
       
     1 package org.hedgewars.hedgeroid;
       
     2 
       
     3 import java.io.File;
       
     4 import java.io.FileNotFoundException;
       
     5 
       
     6 import org.hedgewars.hedgeroid.Datastructures.MapFile;
       
     7 import org.hedgewars.hedgeroid.Datastructures.MapRecipe;
       
     8 import org.hedgewars.hedgeroid.EngineProtocol.PascalExports;
       
     9 import org.hedgewars.hedgeroid.frontlib.Flib;
       
    10 import org.hedgewars.hedgeroid.frontlib.Frontlib;
       
    11 import org.hedgewars.hedgeroid.frontlib.Frontlib.MapRecipePtr;
       
    12 import org.hedgewars.hedgeroid.frontlib.Frontlib.MapconnPtr;
       
    13 import org.hedgewars.hedgeroid.frontlib.Frontlib.MapimageCallback;
       
    14 import org.hedgewars.hedgeroid.frontlib.Frontlib.StrCallback;
       
    15 import org.hedgewars.hedgeroid.util.FileUtils;
       
    16 
       
    17 import android.content.Context;
       
    18 import android.graphics.Bitmap;
       
    19 import android.graphics.Bitmap.Config;
       
    20 import android.graphics.Color;
       
    21 import android.graphics.drawable.BitmapDrawable;
       
    22 import android.graphics.drawable.Drawable;
       
    23 import android.os.Handler;
       
    24 import android.os.Looper;
       
    25 import android.util.Log;
       
    26 
       
    27 import com.sun.jna.Pointer;
       
    28 
       
    29 /**
       
    30  * A class that asynchronously generates a map preview from a MapRecipe.
       
    31  * 
       
    32  * For named maps, this will load the preview image from the filesystem. For others,
       
    33  * it will call the engine to generate a preview image.
       
    34  */
       
    35 public final class MapPreviewGenerator implements Runnable {
       
    36 	private static final String TAG = MapPreviewGenerator.class.getSimpleName();
       
    37 	private static final Handler mainHandler = new Handler(Looper.getMainLooper());
       
    38 
       
    39 	private final Context appContext;
       
    40 	private final MapRecipe map;
       
    41 	private final Listener listener;
       
    42 	
       
    43 	private boolean resultAvailable;
       
    44 	private Drawable result;
       
    45 	
       
    46 	public static interface Listener {
       
    47 		/**
       
    48 		 * This is called on the UI thread once the preview is ready or failed.
       
    49 		 * In case of failure, null is passed.
       
    50 		 */
       
    51 		void onMapPreviewResult(Drawable preview);
       
    52 	}
       
    53 
       
    54 	private MapPreviewGenerator(Context appContext, MapRecipe map, Listener listener) {
       
    55 		this.appContext = appContext;
       
    56 		this.map = map;
       
    57 		this.listener = listener;
       
    58 	}
       
    59 	
       
    60 	public void run() {
       
    61 		if (map.mapgen == Frontlib.MAPGEN_NAMED) {
       
    62 			postToListener(loadPreviewFromFile(appContext, map.name));
       
    63 		} else {
       
    64 			resultAvailable = false;
       
    65 			result = null;
       
    66 			MapconnPtr conn = Flib.INSTANCE.flib_mapconn_create(MapRecipePtr.createJavaOwned(map));
       
    67 			if (conn == null) {
       
    68 				postToListener(null);
       
    69 				return;
       
    70 			}
       
    71 			try {
       
    72 				int port = Flib.INSTANCE.flib_mapconn_getport(conn);
       
    73 				Flib.INSTANCE.flib_mapconn_onSuccess(conn, successCb, null);
       
    74 				Flib.INSTANCE.flib_mapconn_onFailure(conn, failureCb, null);
       
    75 	
       
    76 				String configPath;
       
    77 				try {
       
    78 					configPath = FileUtils.getCachePath(appContext).getAbsolutePath();
       
    79 				} catch(FileNotFoundException e) {
       
    80 					return;
       
    81 				}
       
    82 				
       
    83 				startEngine(configPath, port);
       
    84 				long startTime = System.nanoTime();
       
    85 				do {
       
    86 					Flib.INSTANCE.flib_mapconn_tick(conn);
       
    87 					try {
       
    88 						Thread.sleep(50);
       
    89 					} catch (InterruptedException e) {
       
    90 						// ignore
       
    91 					}
       
    92 				} while(!resultAvailable && System.nanoTime()-startTime < 15000000000l); // 15 seconds timeout
       
    93 			} finally {
       
    94 				Flib.INSTANCE.flib_mapconn_destroy(conn);
       
    95 				postToListener(result);
       
    96 			}
       
    97 		}
       
    98 	}
       
    99 	
       
   100 	public static void startPreviewGeneration(Context appContext, MapRecipe map, Listener listener) {
       
   101 		new Thread(new MapPreviewGenerator(appContext, map, listener)).start();
       
   102 	}
       
   103 	
       
   104 	private static Drawable loadPreviewFromFile(Context appContext, String mapName) {
       
   105 		if(!mapName.startsWith("+")) {
       
   106 			try {
       
   107 				File previewFile = MapFile.getPreviewFile(appContext, mapName);
       
   108 				return Drawable.createFromPath(previewFile.getAbsolutePath());
       
   109 			} catch (FileNotFoundException e) {
       
   110 				Log.w("MapPreviewGenerator", "Preview for map "+mapName+" not found.");
       
   111 			}
       
   112 		}
       
   113 		return null;
       
   114 	}
       
   115 	
       
   116 	private static void startEngine(final String configPath, final int port) {
       
   117 		new Thread(new Runnable() {
       
   118 			public void run() {
       
   119 				Log.d(TAG, "Starting engine "+port);
       
   120 				synchronized(PascalExports.engineMutex) {
       
   121 					PascalExports.HWGenLandPreview(port);
       
   122 				}
       
   123 				Log.d(TAG, "Engine finished");
       
   124 			}
       
   125 		}).start();
       
   126 	}
       
   127 	
       
   128 	private void postToListener(final Drawable result) {
       
   129 		mainHandler.post(new Runnable() {
       
   130 			public void run() {
       
   131 				listener.onMapPreviewResult(result);
       
   132 			}
       
   133 		});
       
   134 	}
       
   135 	
       
   136 	/**
       
   137 	 * Let's be extra nice here and clip off the left and right sides, so the preview is centered...
       
   138 	 * Since the image is present in bytes, we can save some effort by checking entire byte-columns first.
       
   139 	 */
       
   140 	private final MapimageCallback successCb = new MapimageCallback() {
       
   141 		public void callback(Pointer context, Pointer buffer, int hedgehogCount) {
       
   142 			Log.d(TAG, "Running success handler");
       
   143 			byte[] mapdata = buffer.getByteArray(0, Frontlib.MAPIMAGE_BYTES);
       
   144 			
       
   145 			int leftmostPixel = Frontlib.MAPIMAGE_WIDTH;
       
   146 			int rightmostPixel = -1;
       
   147 			int bytesPerLine = Frontlib.MAPIMAGE_WIDTH/8;
       
   148 			
       
   149 			// Find the leftmost pixel
       
   150 			for(int xbyte=0; xbyte<bytesPerLine; xbyte++) {
       
   151 				for(int y=0; y<Frontlib.MAPIMAGE_HEIGHT; y++) {
       
   152 					int b = 0xff&mapdata[xbyte+y*bytesPerLine];
       
   153 					if(b != 0) {
       
   154 						leftmostPixel = Math.min(leftmostPixel, Integer.numberOfLeadingZeros(b)-24+xbyte*8);
       
   155 					}
       
   156 				}
       
   157 				if(leftmostPixel!=Frontlib.MAPIMAGE_WIDTH) break;
       
   158 			}
       
   159 			
       
   160 			// Find the rightmost pixel
       
   161 			for(int xbyte=bytesPerLine-1; xbyte>=0; xbyte--) {
       
   162 				for(int y=0; y<Frontlib.MAPIMAGE_HEIGHT; y++) {
       
   163 					int b = mapdata[xbyte+y*bytesPerLine];
       
   164 					if(b != 0) {
       
   165 						rightmostPixel = Math.max(rightmostPixel, xbyte*8+7-Integer.numberOfTrailingZeros(b));
       
   166 					}
       
   167 				}
       
   168 				if(rightmostPixel!=-1) break;
       
   169 			}
       
   170 		
       
   171 			// No pixel was set at all -> use default width
       
   172 			if(rightmostPixel==-1) {
       
   173 				leftmostPixel = 0;
       
   174 				rightmostPixel = Frontlib.MAPIMAGE_WIDTH-1;
       
   175 			}
       
   176 			
       
   177 			Bitmap bitmap = Bitmap.createBitmap(rightmostPixel-leftmostPixel+1, Frontlib.MAPIMAGE_HEIGHT, Config.ARGB_8888);
       
   178 			for(int y=0; y<Frontlib.MAPIMAGE_HEIGHT; y++) {
       
   179 				for(int x=0; x<bitmap.getWidth(); x++) {
       
   180 					bitmap.setPixel(x, y, isPixelSet(mapdata, x+leftmostPixel, y) ? Color.YELLOW : Color.TRANSPARENT);
       
   181 				}
       
   182 			}
       
   183 			result = new BitmapDrawable(bitmap);
       
   184 			resultAvailable = true;
       
   185 		}
       
   186 	};
       
   187 	
       
   188 	private static boolean isPixelSet(byte[] imgdata, int x, int y) {
       
   189 		int pixelnum = x+Frontlib.MAPIMAGE_WIDTH*y;
       
   190 		return (imgdata[pixelnum>>3] & (128>>(pixelnum&7))) != 0;
       
   191 	}
       
   192 	
       
   193 	private final StrCallback failureCb = new StrCallback() {
       
   194 		public void callback(Pointer context, String reason) {
       
   195 			Log.e(TAG, "Error generating map preview: "+reason);
       
   196 			result = null;
       
   197 			resultAvailable = true;
       
   198 		}
       
   199 	};
       
   200 }