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