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 } |