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