I'm creating a maps application that needs to display a very large bitmap of a world map which is bigger than most screens and consumes a lot of memory.
To solve the problem, I'm using a similar idea to google maps and its app by splitting the map into smaller 256x256 pieces and then calculating the pieces that fall in the view area which on my device is 12 pieces at any one time.
The pieces are all stored in the assets folder which get loaded using the assetmanager and then decoded into a bitmap using bitmapfactory and then drawn onto my views canvas.
This is very slow and even after going further in the literature and having it run in a seperate thread, the graphics are jerky when scrolling around to new locations.
How do other games and apps (like google maps) dispaly graphics with such smooth scrolling?
After many attempts it seems that the problem was with the reading from the assets folder. I solved the problem by calculating the maximum number of map pieces visible at one time from the users screen size. The Draw procedure first searches for pieces in the array that are now off the screen and nulls the slots. It then looks for pieces that are now visible and loads them into an empty slot.
The app then uses the array as a source of data and it has reduced the assets folder reads down to 6 as opposed to the direct method loading 20 pieces per Draw.
The game now runs with the speed of popular mobile games!
Related
I'm making a card game for android and need to load in bitmaps for the entire deck of cards. Would it be more memory efficient to create a separate image for each card and load them in one by one, or to create one large image with all 52 cards and then load in the single, large image?
I wouldn't say it's necessarily faster or better to load in the images as a large sprite image. You'd then have to cut the bitmap up for each card and load the individual bitmaps onto the cards anyway if you're using android widgets. If you're rendering it all on canvas then it may be better to load a large bitmap image and cut it up as you need.
The only issue I'd be wary of, especially on Android, is out of memory exceptions. I have a few production apps and have seen in the real world the number of phones that crash because they run out of space to store bitmaps. It may well work nicely on your development phone but there are plenty of devices out there with much smaller screens, resolutions and therefore RAM. It may be in your interest in that case to load bitmaps individually as you can ensure only show ones currently visible, hence save on memory.
52 cards are 52 different images - I don't know many games where you would see all of them at the same time.
edit
What would really save memory would be if you just stored the suit symbols, numbers and special card icons (jack, queen, king, ace) and then arranged them on a card that you generate. Then you wouldn't have all the blank space around parts of the card taking up precious memory and you could reuse the special card icons for different suits by tinting the icons different colours.
The AndEngine framework for Android has a way to load textures into an atlas. The atlas way of loading textures was new to me and so I looked into it. From what I've read around the forums, an atlas is a large rectangular region of image data where the width and height have to be powers of 2 (although they don't have to be equal, i.e. you can have a 512x1024 if you want). Textures are loaded into this region and this is used to keep them all in memory during the lifetime of the app. It allocates all of that space even if you don't use some of it.
With that in mind, what process can I use to optimally place an arbitrary number of graphics into the same atlas? I don't plan to do this at runtime within the app, but once all the graphics for my game have been created, I was hoping I could run the images and their sizes through some algorithm to determine how big to make the atlas and where each image should be loaded in it. Once I know those things, I can hard code their location for my game.
This is not an AndEngine or even Android specific question. I'm sure there are other graphics oriented frameworks that load textures this way.
I was about to post the question on Math.StackExchange.com but instead I found that it was already asked and already answered. There's even a link to a CodeProject article that does this for CSS Sprites.
EDIT: What I ended up doing in the long run was using a program called TexturePacker. Not only did it pack the images, it produced files to be used in AndEngine (as well as other supported platforms) and made the whole process easier for me.
My question is how to efficiently display large custom maps in an offline Phonegap application, allowing them to be panned and zoomed smoothly while still supporting older mobile devices?
I’m developing a mobile application that involves using geolocation to navigate walking routes in remote areas where it’s likely the user won’t have a signal and therefore an internet connection. It’s important that the app works well with Android 2.2+ (so SVG is not an option) as well as iOS4+.
I’ve drawn custom vector maps using Adobe Illustrator at resolutions appropriate to each route, the average being about 2000x2000 pixels and the largest of which so far results in an image 4000x2400 pixels.
I’ve chosen to go with Phonegap/JQM rather than native simply because I come from a web programming background and it seemed the fastest way to get a user interface up and running without needing to delve into native code too much, although I’ve written a couple of Phonegap plugins using native code for the purposes of power and screen management.
The application needs to allow the user to pan around the map (by dragging) and zoom in/out (by pinching) between about 25% to 200% of the original image size.
Most of the testing I’ve done has been on an HTC Desire running Android 2.3.3 and an HTC Wildfire running Android 2.2 since these are likely to be some of the lowest spec devices the app is going to have to run on.
I’ve tried out various approaches to display the map (detailed below), but so far each has proved unfit for purpose either because the memory usage of the app is too great, the storage space required makes the app too large to download or the CPU usage is too intensive causing lag when panning/zooming.
Any suggestions much appreciated. Thanks in advance.
Approaches I’ve tried:
1. Display map as raster PNG using tag
This was the first approach I tried. Exporting the 4000x2400 pixel image from Illustrator as a 128 colour PNG-8 resulted in a 746Kb file. I panned the image by absolutely positioning it relative to the viewport and zoomed the image by scaling the width/height attributes of the tag.
The problem with this approach was that even at a 1:1 zoom level, the Android application used 60Mb of RAM for the image and zooming in to 200% caused this to increase 120Mb, causing the app to crash on the HTC Wildfire.
2. Display portions of raster PNG using HTML5 canvas
To avoid the problem of zooming-in causing a proportional increase in memory usage, I tried loading the image via JS then copying the portion of the image to be displayed to a canvas the size of the viewport, something like:
var canvas = $(‘canvas#mycanvas’);
canvas.width = $(window).width;
canvas.height = $(window).height;
...
var img = new Image();
img.src = “map.png”;
...
var context = canvas[0].getContext("2d");
context.drawImage(img, x, y, w, h, 0, 0, canvas.width, canvas.height);
where x,y is the top-left corner within the source image defined by panning
and w,h is the area size within the source image determined by zoom level
The problem here was that large map images were somehow losing quality while in memory (I can only assume there’s some upper memory limit which is resulting in dithering), causing the maps to look distorted in the app: see here for an example screenshot
3. Display map as vector using HTML5 canvas
A bit of Googling led me to discover ai2canvas, an Illustrator plugin that enables you to export artwork as vectors displayed in an HTML5 canvas. The result of the export is an html file containing a chunk of JS which represents all the paths in illustrator as bezier curves. Exporting my 4000x2400 map resulted in a 550Kb html file containing the vector paths.
In my test app, I rendered the entire map to an in-memory canvas (not attached to the DOM) of 4000x2400 pixels, then copied the relevant portions of it to a viewport-sized canvas using context.drawImage() with the in-memory canvas as the source.
On the HTC Wildfire, although the initial render of all the bezier curves to the in-memory canvas took around 2000ms, copying between canvases was fast enough to allow smooth panning and zooming. The problem was when I looked at the memory usage of the app, it was using 120Mb for the in-memory canvas once all the vectors had rendered.
I tried a second approach using the vector map; instead of rendering all the vectors to a large in-memory canvas, I made the app calculate which vector paths were visible within the viewport at the current pan position/zoom level during each drag/pinch event and only draw the visible vectors to the viewport-sized canvas. While this reduced the required memory usage to 10Mb, the CPU cycles required to perform these calculations on every drag/pinch cycle made the app lag so much on the old android phones it was unusable.
4. Display map using offline tiling
Using map tiler, I created PNG tiles for my maps at zoom levels from 25% to 100%. In my test app, I was then able to lazy load the tiles on demand reducing memory usage and producing a smooth pan/zoom experience even on the HTC Wildfire. I thought I’d found the solution until I looked at the size of the APK produced: for my 4000x2400 map, map tiler produced 4Mb of tile images. Most of my maps are around 2000x2000 pixels, resulting in about 2Mb of tiles. The code of my proper application plus the Phonegap overhead is another 2Mb.
My intention is to release a series of apps available on the Android/Apple markets, each with a set of around 10 maps, but with tiling each map weighs in at between 1-4Mb so the resulting app becomes a very large download.
In case this is of interest to anyone else, I solved this by using map tiling in the end, using a tool called pnqnq to create 8-bit PNGs constrained to 256 colours. The resulting set of tiles for my 4000x2000 map was about 800K in size as opposed to 4Mb for PNG-24, which was an acceptable size for assets in my Android and iOS applications.
I have a problem: (I wouldn't be here otherwise ;)
I am creating an app that has a feature for displaying "3D" models. This part is really a collection of images taken from many angles and allows the user to "rotate" the "model".
This idea is working fine, but the problem lie in the loading of the images.
I have found that there are two ways:
Load all the images into memory, and then simply switch them for the correct angle.
Load the images as we need them - we can load a few ahead of time.
However these have problems:
If I was lucky, the images would fit into memory, but they don't. They are about 1.5-2MB each and there are about 75-100 images per model. This brings the total size to about 115MB at the minimum.
If I was lucky, the image would load quicker than the user could "rotate", but they don't. The user can easily switch to an angle that is not loaded yet, resulting in a black screen for a few seconds.
I have created a loader that allows me to simply add the images that I need to a stack and the loader will then one-by-one load the images. This works fine if the user scrolls fairly slowly. My loader takes care of releasing memory when it is finished, so no matter how many images I load, the app usually won't crash as long as I specify the max images to store in memory. My loader can load the images very quickly, but there is still a few milliseconds (~250ms) for it to load the large image into memory.
Of course, the loader is on a different thread, and the loading in no way hinders UI response. That is why if the user swipes back and forth quickly, no image will actually display, as the loading and unloading are all working at the same time to result in no images :)
So, my problem: How do I provide a smooth and user-pleasing rotation of the images without loading all the images into memory?
Don't load (or store) resolution you don't need. If your user needs to zoom, an out-of-memory binary image pyramid is a cheap way to let you load only the level of zoom necessary. If your user needs to pan through an image larger than your display area, you can break your large image into smaller tiles, and only load the ones you need.
If you want to get fancy, you can write a UI-aware cache manager to preemptively load tiles you think you might need soon, and mark tiles you're pretty sure you won't need soon for preemption.
Better compression can fit more image data into memory, and speed up load time. So, pay attention to individual image compression, and don't load image quality you don't need, either.
As an extraordinary measure: since images from slightly different angles are similar to each other, you may be able to save time and space by representing the difference, instead -- look up lightfield compression. You will still need to convert from compressed form to a particular bitmap you can draw, but if the compression allows the dataset to remain in memory, you could potentially gain a lot of speed.
If you can't fit your compressed dataset into memory, there's a good chance the user will be able to swipe back and forth quickly enough to defeat your cache. So, if smoothness is your main goal, you could try for a "UI solution" by restricting the rotation rate (or the per-swipe rotation range?) to something your data loader can follow.
My only suggestion is for loading them efficiently. I assume that you are using techniques described here
If the images are of higher resolution than the screen you can calculate the sample size of the image you want to render then you can load an image that fits your screen rather than the full size image which will use much less memory. If you are already doing that then to me it seems like how you are doing it efficiently already. Perhaps you could show the user some kind of placeholder graphic while an image is loading so they won't just have blank space.
Thanks for the answers. I laughed at myself and then went to bed after reading the answers.
Let me share how I resolved this problem - it uses some pieces of the answers:
I was trying to cache the large images in memory - this is unnecessary, why not store a lower res version and then load the hi res when the user stops scrolling? Then the user can scroll as fast as he likes and there will always be images in memory to quickly paint. When the user stops/slows scrolling, we load the hi res image.
Because he will be scrolling fast, he won't be able to see the lower res' lower quality.
And, as there will only be one hi res to load, the ~250ms delay is hardly noticeable.
This really combines the best of both cases. And I can use the Android's methods for loading a lower res version of the Bitmap.
I created a map app that uses a very large image as my map. It shows high resolution tiles when the user zooms in to a certain degree and a lower-res bitmap of the whole image when zoomed out past 50%. It works OK, on my origional droid sometimes I can see a very slight lag due to the new tiles being drawn into and from the garbage collector.
Im now thinking about how OpenGL ES would be a better way to render. Ive never touched it before, but from what ive been reading in different turoials, it seems i could create a quad that has 8 faces or so, and enable culling so it only draws the images that are currently visible on the screen.
Would this eliminate the lag completely? In my test app now I have tiles being loaded on app startup but i can tell the lag is due to those tiles being drawn for the first time. Thanks.
I have written an OpenGL-based map view and it's bloody awesome if I say so myself. Sadly it's for a commercial project so I can't offer code. I can however tell you that it has 1 rendering thread, a pool of 8 tile downloading threads, and (most pertinent to your question) 1 storage thread that loads and saves tiles to the NAND flash or the SD card. Rendering is done one tile at a time, each tile being 2 triangles (there are no quads in ES). It's still blisteringly quick.