project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/MapPreviewGenerator.java
changeset 7508 763d3961400b
child 7582 714310efad8f
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/MapPreviewGenerator.java	Sat Aug 18 00:47:51 2012 +0200
@@ -0,0 +1,200 @@
+package org.hedgewars.hedgeroid;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+
+import org.hedgewars.hedgeroid.Datastructures.MapFile;
+import org.hedgewars.hedgeroid.Datastructures.MapRecipe;
+import org.hedgewars.hedgeroid.EngineProtocol.PascalExports;
+import org.hedgewars.hedgeroid.frontlib.Flib;
+import org.hedgewars.hedgeroid.frontlib.Frontlib;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.MapRecipePtr;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.MapconnPtr;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.MapimageCallback;
+import org.hedgewars.hedgeroid.frontlib.Frontlib.StrCallback;
+import org.hedgewars.hedgeroid.util.FileUtils;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Color;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+import com.sun.jna.Pointer;
+
+/**
+ * A class that asynchronously generates a map preview from a MapRecipe.
+ * 
+ * For named maps, this will load the preview image from the filesystem. For others,
+ * it will call the engine to generate a preview image.
+ */
+public final class MapPreviewGenerator implements Runnable {
+	private static final String TAG = MapPreviewGenerator.class.getSimpleName();
+	private static final Handler mainHandler = new Handler(Looper.getMainLooper());
+
+	private final Context appContext;
+	private final MapRecipe map;
+	private final Listener listener;
+	
+	private boolean resultAvailable;
+	private Drawable result;
+	
+	public static interface Listener {
+		/**
+		 * This is called on the UI thread once the preview is ready or failed.
+		 * In case of failure, null is passed.
+		 */
+		void onMapPreviewResult(Drawable preview);
+	}
+
+	private MapPreviewGenerator(Context appContext, MapRecipe map, Listener listener) {
+		this.appContext = appContext;
+		this.map = map;
+		this.listener = listener;
+	}
+	
+	public void run() {
+		if (map.mapgen == Frontlib.MAPGEN_NAMED) {
+			postToListener(loadPreviewFromFile(appContext, map.name));
+		} else {
+			resultAvailable = false;
+			result = null;
+			MapconnPtr conn = Flib.INSTANCE.flib_mapconn_create(MapRecipePtr.createJavaOwned(map));
+			if (conn == null) {
+				postToListener(null);
+				return;
+			}
+			try {
+				int port = Flib.INSTANCE.flib_mapconn_getport(conn);
+				Flib.INSTANCE.flib_mapconn_onSuccess(conn, successCb, null);
+				Flib.INSTANCE.flib_mapconn_onFailure(conn, failureCb, null);
+	
+				String configPath;
+				try {
+					configPath = FileUtils.getCachePath(appContext).getAbsolutePath();
+				} catch(FileNotFoundException e) {
+					return;
+				}
+				
+				startEngine(configPath, port);
+				long startTime = System.nanoTime();
+				do {
+					Flib.INSTANCE.flib_mapconn_tick(conn);
+					try {
+						Thread.sleep(50);
+					} catch (InterruptedException e) {
+						// ignore
+					}
+				} while(!resultAvailable && System.nanoTime()-startTime < 15000000000l); // 15 seconds timeout
+			} finally {
+				Flib.INSTANCE.flib_mapconn_destroy(conn);
+				postToListener(result);
+			}
+		}
+	}
+	
+	public static void startPreviewGeneration(Context appContext, MapRecipe map, Listener listener) {
+		new Thread(new MapPreviewGenerator(appContext, map, listener)).start();
+	}
+	
+	private static Drawable loadPreviewFromFile(Context appContext, String mapName) {
+		if(!mapName.startsWith("+")) {
+			try {
+				File previewFile = MapFile.getPreviewFile(appContext, mapName);
+				return Drawable.createFromPath(previewFile.getAbsolutePath());
+			} catch (FileNotFoundException e) {
+				Log.w("MapPreviewGenerator", "Preview for map "+mapName+" not found.");
+			}
+		}
+		return null;
+	}
+	
+	private static void startEngine(final String configPath, final int port) {
+		new Thread(new Runnable() {
+			public void run() {
+				Log.d(TAG, "Starting engine "+port);
+				synchronized(PascalExports.engineMutex) {
+					PascalExports.HWGenLandPreview(port);
+				}
+				Log.d(TAG, "Engine finished");
+			}
+		}).start();
+	}
+	
+	private void postToListener(final Drawable result) {
+		mainHandler.post(new Runnable() {
+			public void run() {
+				listener.onMapPreviewResult(result);
+			}
+		});
+	}
+	
+	/**
+	 * Let's be extra nice here and clip off the left and right sides, so the preview is centered...
+	 * Since the image is present in bytes, we can save some effort by checking entire byte-columns first.
+	 */
+	private final MapimageCallback successCb = new MapimageCallback() {
+		public void callback(Pointer context, Pointer buffer, int hedgehogCount) {
+			Log.d(TAG, "Running success handler");
+			byte[] mapdata = buffer.getByteArray(0, Frontlib.MAPIMAGE_BYTES);
+			
+			int leftmostPixel = Frontlib.MAPIMAGE_WIDTH;
+			int rightmostPixel = -1;
+			int bytesPerLine = Frontlib.MAPIMAGE_WIDTH/8;
+			
+			// Find the leftmost pixel
+			for(int xbyte=0; xbyte<bytesPerLine; xbyte++) {
+				for(int y=0; y<Frontlib.MAPIMAGE_HEIGHT; y++) {
+					int b = 0xff&mapdata[xbyte+y*bytesPerLine];
+					if(b != 0) {
+						leftmostPixel = Math.min(leftmostPixel, Integer.numberOfLeadingZeros(b)-24+xbyte*8);
+					}
+				}
+				if(leftmostPixel!=Frontlib.MAPIMAGE_WIDTH) break;
+			}
+			
+			// Find the rightmost pixel
+			for(int xbyte=bytesPerLine-1; xbyte>=0; xbyte--) {
+				for(int y=0; y<Frontlib.MAPIMAGE_HEIGHT; y++) {
+					int b = mapdata[xbyte+y*bytesPerLine];
+					if(b != 0) {
+						rightmostPixel = Math.max(rightmostPixel, xbyte*8+7-Integer.numberOfTrailingZeros(b));
+					}
+				}
+				if(rightmostPixel!=-1) break;
+			}
+		
+			// No pixel was set at all -> use default width
+			if(rightmostPixel==-1) {
+				leftmostPixel = 0;
+				rightmostPixel = Frontlib.MAPIMAGE_WIDTH-1;
+			}
+			
+			Bitmap bitmap = Bitmap.createBitmap(rightmostPixel-leftmostPixel+1, Frontlib.MAPIMAGE_HEIGHT, Config.ARGB_8888);
+			for(int y=0; y<Frontlib.MAPIMAGE_HEIGHT; y++) {
+				for(int x=0; x<bitmap.getWidth(); x++) {
+					bitmap.setPixel(x, y, isPixelSet(mapdata, x+leftmostPixel, y) ? Color.YELLOW : Color.TRANSPARENT);
+				}
+			}
+			result = new BitmapDrawable(bitmap);
+			resultAvailable = true;
+		}
+	};
+	
+	private static boolean isPixelSet(byte[] imgdata, int x, int y) {
+		int pixelnum = x+Frontlib.MAPIMAGE_WIDTH*y;
+		return (imgdata[pixelnum>>3] & (128>>(pixelnum&7))) != 0;
+	}
+	
+	private final StrCallback failureCb = new StrCallback() {
+		public void callback(Pointer context, String reason) {
+			Log.e(TAG, "Error generating map preview: "+reason);
+			result = null;
+			resultAvailable = true;
+		}
+	};
+}
\ No newline at end of file