# Image Best Practices
# Objective
In this section, you will learn best practices for handling images within your Titanium apps.
# Contents
# File formats
You can use PNG, JPG, and GIF images in your Titanium apps. But which should you use?
GIF – GIF images are limited to 256 colors and are suitable to low-resolution line-art drawings and icons. GIF is a proprietary format and may not be compatible with some app's licenses. The animated GIF format is not supported on all platforms. In general, there are very few cases in which GIF is the appropriate format to use.
PNG – PNG images are in a lossless-compressed format that can support high-color images. This format is best suited to line-art, text, and icons. It is a better choice than GIF in almost all cases.
JPG – JPG (or JPEG) is lossy-compressed file format best suited for photographs. It is not well-suited for text, line drawings, or icons because of visual artifacts created during the compression process that will reduce quality and readability.
Keep in mind that JPG images are decompressed in memory when the photo is displayed. A JPG file itself might take a few dozen KB in storage. But, when rendered (whether visible on screen or not) it will be uncompressed in memory to hundreds of KB or higher. It is crucial that you don't display create too many JPG ImageViews at one time in your mobile apps or you could exhaust the device's memory. Removing an image from a view might not clear the memory used by that ImageView; null
it out as soon as you no longer need the image in memory.
In summary:
Photos? Use JPG
Text, line drawings, icons, button graphics? Use PNG
Flip-book style animations (for which animated GIFs would be the traditional choice)? Use the ImageView's
images
property and pass to it an array of PNG or optimized JPG files.
# Loading and unloading images to manage memory use
Consider a 640 x 480 pixel JPG image, which would fill the screen of a typical handset. On disk, such an image might consume a few dozen KB of storage. But in memory, its footprint will be significantly larger at upwards of 900 KB. When loaded into memory (so that it can be displayed or manipulated), the compressed JPG data is converted to a bitmap. Each pixel is represented by 24 bits (8 bits for each of red, green, and blue channels) or 3 bytes. (640 x 480 x 3) / 1024 = 900 KB
Keep in mind that the RAM available to your mobile app is limited by the platform. It can be as low as 12 MB. And that space is filled by your app's code, the Titanium framework components, and so forth. For that reason, it is imperative that you don't load lots of images into memory at once or you'll exhaust your app's memory. Consider these optimization techniques:
remove()
images from the view hierarchy when they're not "on the screen" to permit the operating system to free memory- Example:
myView.remove(myImageView);
- Example:
Set image views to
null
once you no longer need those objects to free memory and release proxies- Example:
myImageView = null;
- Example:
Resize and crop images to the final dimensions at which they'll be shown on screen so that you don't require the system to manipulate any more bytes than necessary
- Example: Using
imageAsResized
andimageAsCropped
on a Ti.Blob object.
- Example: Using
# Optimizing images
To minimize storage, distribution IPA/APK/XAP size, and reduce network (data) usage, you should optimize the images you use in your app. You should both resize and crop images, and optimize the resulting files to achieve a balance between best data compression and image quality.
Resize images prior to including them in your app. Keep in mind the screen dimensions and pixel densities of the devices on which your images will be shown. It is not efficient to use a 1024 x 768 image on a device that has a 640 x 480 screen. Crop or resize images prior to including them with your app's source code or publishing them to a URL from which your app will download them.
Both PNG and JPG files are compressed formats. However, the tools typically used to create those images might not provide optimally-compressed images. For example, a digital camera will create a much larger (though admittedly higher-quality) JPG image than the "optimize for web" routines offered by a program like Photoshop.
Platform | File type | Tool |
---|---|---|
Mac | PNG, JPG, & GIF | ImageOptim - https://imageoptim.com/mac (opens new window) |
Mac, Windows, Linux | PNG, JPG & GIF | ImageMagick - http://www.imagemagick.org (opens new window) |
Windows/DOS | PNG | PNGCrush - https://pmt.sourceforge.io/pngcrush (opens new window) |
Windows | JPG | Nikkho - https://sourceforge.net/projects/nikkhokkho (opens new window) |
# Caching remote images
You can display both local and remote images in an ImageView. When loading remote images, you should set the defaultImage
property to a local image, which will be displayed while the remote image is being downloaded. Remote images are cached automatically on the iOS-, Android- and Windows platform.
💡 Android Note
Android 6 and later uses runtime permissions to secure the user's privacy. Therefore, you should call Ti.Filesystem.requestStoragePermissions()
before attempting to load remote images.
Caching remote images helps improves your application's performance by loading images more quickly, and won't re-download the images, which consumes users' data quotas unnecessarily.
On the Android platform, the cache can even be limited to 25 MB and data remains for the lifetime of the application (as long as it's installed on device). On the iOS platform, the cache size is not predetermined (size cannot be guaranteed) and data remains there until iOS cleans the directory (needs more local storage). To manually clean the cache directory, delete the files in the applicationCacheDirectory
.
To manually cache remote images, below is a sample utility function that you can use to cache a remote images to the app's applicationDataDirectory
. (In addition to below, you'll find this code at https://gist.github.com/1901680 (opens new window)).
var Utils = {
/* modified version of https://gist.github.com/1243697 */
_getExtension: function(fn) {
// CREDITS: http://stackoverflow.com/a/680982/292947
var re = /(?:\.([^.]+))?$/;
var tmpext = re.exec(fn)[1];
return (tmpext) ? tmpext : '';
},
RemoteImage: function(a) {
a = a || {};
var md5;
var needsToSave = false;
var savedFile;
if (a.image !== null) {
md5 = Ti.Utils.md5HexDigest(a.image)+this._getExtension(a.image);
savedFile = Titanium.Filesystem.getFile(Titanium.Filesystem.applicationDataDirectory, md5);
if (savedFile.exists()) {
a.image = savedFile;
} else {
needsToSave = true;
}
}
var image = Ti.UI.createImageView(a);
if (needsToSave === true) {
function saveImage(e) {
image.removeEventListener('load', saveImage);
savedFile.write(
Ti.UI.createImageView({ image: image.image, width: Ti.UI.FILL, height: Ti.UI.FILL }).toImage()
);
}
image.addEventListener('load', saveImage);
}
return image;
}
};
// Example usage
var image = Utils.RemoteImage({
image: 'https://raw.githubusercontent.com/appcelerator/kitchensink-v2/master/app/assets/images/titanium-logo.png',
defaultImage:' myDefaultImage.png',
width: 300,
height: 200,
top: 20
});
var win = Ti.UI.createWindow();
win.add(image);
win.open();
# Summary
Optimizing images will help you create smaller installation files. The same is true of using defaultImage
placeholders and downloading/caching final images from a remote source. You'll also minimize your user's consumption of data bandwidth by cropping and optimizing images.