ImageLoaderLibrary – Load Large Images Efficiently

Overview

This is an Android library used for loading bitmaps efficiently from local disk, If you have big images on disk and want to load it in an ImageView, then you could use this library to load a smaller resolution bitmap from this image. You could use this library to avoid the OutOfMemoryError Exception that occurs when you load high resolution images.
If you have an image with resolution 2048×1536, this would allocate about 12MB in memory to load a full image, you could use this library to load a smaller version of this image with a predefined height and width. This library will load a smaller resolution bitmap version for you. The library will not destroy the aspect ratio of the image.

This library will load the image in its original orientation also by reading the ExifInterface of this image and rotate it appropriately.

The source code of the library is available over github

When to use the ImagesLoaderLibrary

ImagesLoaderLibrary is helpful when you are trying to load Bitmaps of high resolution images from your local disk in Android. Lets say you need to load an image that would take more than 20MB in memory and that image your are loading is saved on disk in a rotated format (some images are written on disk in a rotated format, and you need to read the Exif attributes to display the image in its original orientation), In this case ImagesLoaderLibrary will load it efficiently and in its original orientation. Aspect ratio will be the same as the original image.

How to use ImagesLoaderLibrary

    1. Download ImagesLoaderLibrary.java
    2. Add this class to your project
    3. Call LoadImage method from ImagesLoaderLibrary class like the following example:

[code language=”java”]
ImageView mImageView = findViewById(R.id.mImageView);
Bitmap mBitmap = ImageLoaderLibrary.loadImage(“someImagePath”, 512, 512);
mImageView.setImageBitmap(mBitmap);
[/code]

  1. Set the following parameters
    • imagePath: the image path you want to load from disk.
    • reqWidth: required width for the lower resolution bitmap to load.
    • reqHeight: required height for the lower resolution bitmap to load.

Note: reqWidth and reqHeight should be as small as possible to be able to load a lower resolution image that could fit in memory.

API Reference

[code language=”java”]
Class ImageLoaderLibrary{
/**
* Use this method to load subsampled bitmap of the image specified by the imagePath. The result bitmap will have the
* original aspect ratio and the original orientation of the image.
* @param imagePath the path of image to be loaded
* @param reqWidth required width of the subsampled version
* @param reqHeight required height of the subsampled version
*
* @return Bitmap returns the subsampled bitmap of the image
*/
public static Bitmap loadImage(String imagePath, int reqWidth, int reqHeight);
}
[/code]

Code Guide

This code is written following  Google Android developers guide

Reading Image Height & Width

Firstly, Image size should be read by using the options you could send to decodeFile method of BitmapFactory class in Android SDK, this could be done by setting inJustDecodeBounds property for true.

[code language=”java”]
BitmapFactory.Options bmOptions = new BitmapFactory.Options();
bmOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(imagePath, bmOptions);
int photoW = bmOptions.outWidth;
int photoH = bmOptions.outHeight;
[/code]

Calculate Scale Factor

Scale factor is calculated based on which dimension should be reduced more (height or width). This will create an efficient  smaller resolution version of the image while keeping the aspect ratio fixed.

[code language=”java”]
float scaleFactor;
scaleFactor = Math.max(photoW / imageViewWidth, photoH/ imageViewHeight);
bmOptions.inJustDecodeBounds = false;
bmOptions.inSampleSize = (int) Math.ceil(scaleFactor);
[/code]

Get Correct Orientation

Images saved on disk have Exif properties that needs to be processed to load images in its correct orientation.

[code language=”java”]
ExifInterface exif = new ExifInterface(imagePath);
int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1);
Matrix matrix = new Matrix();
if (orientation == ExifInterface.ORIENTATION_ROTATE_90) {
matrix.postRotate(90);
} else if (orientation == ExifInterface.ORIENTATION_ROTATE_180) {
matrix.postRotate(180);
} else if (orientation == ExifInterface.ORIENTATION_ROTATE_270) {
matrix.postRotate(270);
}

[/code]

The source code of the library is available over github

UIHierarchyView for Objective C – iOS platform

UIHierarchyView is an Objective C library to visualize any hierarchy tree structure.
It supports you with an api to create your tree structure and to add your custom cell views.

UIHierarchyView

Components:

The Library components

  1. HierarchyScrollView
  2. TreeViewer
  3. TreeViewerDelegate

Sample code for testing

  1. Node
  2. TreeViewerViewController

When to use the Hierarchy View:

Consider yourself trying to visualize a supervision hierarchy in a company or any other tree hierarchy, and each node may have multi-children.
Here we can use this library, it supports you with a view that visualize your tree.
You can also add a custom cell view for each node.

How to use the Hierarchy View:

To use the Hierarchy View you need to

  1. Create Node class that adopts <TreeViewerDelegate>.
  2. Create your custom view for each node.
  3. Create your tree structure.
  4. Finally Initialize our Hierarchy View with the root node of the tree.

Here is the protocol that your node should adopt

[code language=”objc”]
@protocol TreeViewerDelegate

#pragma mark – view parameters:
// a custom view that user want for each node.
@property (nonatomic,strong) UIView * nodeView ;
// the weight of the node (number of leaf children).
@property (nonatomic) int weight;
// start x position of node.
@property (nonatomic) int startX;

// background of the container view of nodes
@property (nonatomic,strong) UIColor * backgroundColor ;

#pragma mark – data parameters:
// this is a unique identifier for the node
@property (nonatomic,strong) NSString *identifier;
// array of node children
@property (nonatomic,strong) NSMutableArray * children;

@required
#pragma mark – listeners:
// here we handle tapping the view
– (void)viewTapped:(UITapGestureRecognizer *)recognizer ;

@end

[/code]

The protocol properties:

  • identifier: here you must set a unique identifier for each node.
  • children: here you must set the children nodes for each node.
  • backgroundColor: here you can set the background color for the view.
  • nodeView: a custom view for each node (below an example for a custom view).

The listeners:

  • viewTapped: here you can handle tapping any view in the tree.

Create Node class:

here is the header file of our Node.h

[code language=”objc”]
@interface Node : NSObject

-(instancetype) initWithIdentifier:(NSString *)identifier andInfo:(NSDictionary *)info;
-(instancetype) initWithChildren:(NSArray *)children andIdentifier:(NSString *) identifier andInfo:(NSDictionary *)info;

//data

@property (nonatomic,strong) NSString *name;
@property (nonatomic,strong) NSString *position;
@property (nonatomic,strong) NSString *imageName;

@end
[/code]

as you see the Node adopts <TreeViewerDelegate>
and here we put some data properties: name, position and imageName.

here is the implementation file of our Node.m

[code language=”objc”]
@implementation Node

@synthesize weight=_weight;
@synthesize startX=_startX;
@synthesize identifier=_identifier;
@synthesize children=_children;
@synthesize nodeView=_nodeView;
@synthesize backgroundColor=_backgroundColor;

– (void)viewTapped:(UITapGestureRecognizer *)recognizer {
NSLog(@”%@”,self.identifier);
}

-(instancetype) initWithIdentifier:(NSString *)identifier andInfo:(NSDictionary *)info{
self=[super init];
if(self){
self.name=[info objectForKey:@”Name”];
self.imageName=[info objectForKey:@”ImageName”];
self.position=[info objectForKey:@”Position”];

self.identifier=identifier;
self.children=[[NSMutableArray alloc]init];
}
return self;
}
-(instancetype) initWithChildren:(NSArray *)children andIdentifier:(NSString *) identifier andInfo:(NSDictionary *)info{
self=[super init];
if(self){
self.name=[info objectForKey:@”Name”];
self.imageName=[info objectForKey:@”ImageName”];
self.position=[info objectForKey:@”Position”];

self.identifier=identifier;
self.children=[[NSMutableArray alloc]initWithCapacity:children.count];
for(id child in children){
if([child isKindOfClass:[Node class]])
[self.children addObject:child];
}
}
return self;
}
@end
[/code]

as you see we synthesize the protocol properties to use it
and here we have the viewTapped listener and an initialization methods for the node with Info.

Create your custom view:

In your ViewController where you will use our HierarchyView, you can create any custom view (for each node) and pass it to your id<TreeViewerDelegate> and this view will be the node view.
we created a custom view in the storyboard and used it as our cell custom view.
here we create a view that has image in the left and 2 labels in the right.
Consider the node is an employee node and the image is the employee image, the upper label is his name and the lower label is his position.

[code language=”objc”]
@interface CustomNodeView ()

@property (strong,nonatomic) IBOutlet UIImageView *nodeImageView;
@property (strong,nonatomic) IBOutlet UIImageView *employeeImageView;
@property (strong,nonatomic) IBOutlet UILabel *titleLabel;
@property (strong,nonatomic) IBOutlet UILabel *positionLabel;

@end

@implementation CustomNodeView

-(void)createViewWithNode :(Node *) node{
UIImage *image;
if([node.position isEqual:@”Co-founder & CEO”])
image=[UIImage imageNamed:@”5″];
else if([node.position isEqual:@”Android Developer”])
image=[UIImage imageNamed:@”1″];
else if([node.position isEqual:@”Team Leader”])
image=[UIImage imageNamed:@”3″];
else if([node.position isEqual:@”Co-founder”])
image=[UIImage imageNamed:@”4″];
else if([node.position isEqual:@”iOS Developer”])
image=[UIImage imageNamed:@”6″];
else
image=[UIImage imageNamed:@”2″];
self.nodeImageView.image = image;

UIImage *nodeImage=[UIImage imageNamed:node.imageName];
self.employeeImageView.image =nodeImage;

self.employeeImageView.layer.cornerRadius = self.employeeImageView.frame.size.width / 2.f;
self.employeeImageView.layer.masksToBounds = YES;

self.titleLabel.text=node.name;
self.positionLabel.text=node.position;

}

– (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {

}
return self;
}

@end
[/code]

Create your Tree structure:

You can create your own tree structure
here we have 9 nodes and we added children like in the example
then we assign the node view with the pre-created custom view.

[code language=”objc”]
-(id<TreeViewerDelegate>) setup{
// Our Tree structure is in plist file.
NSURL *employeesFile = [[NSBundle mainBundle] URLForResource:@”EmployeesTree” withExtension:@”plist”];
NSDictionary *rootInfo = [NSDictionary dictionaryWithContentsOfURL:employeesFile];

//traverse the tree from plist and create node.
int count=0;
NSMutableArray *visited=[[NSMutableArray alloc]init];
// a queue to insert visited nodes in.

Node * root= [[Node alloc] initWithIdentifier:[[NSString alloc]initWithFormat:@”i_%d”,count] andInfo:rootInfo];
NSMutableArray *queue=[[NSMutableArray alloc]init];
NSMutableArray *nodeQueue=[[NSMutableArray alloc]init];
NSMutableArray *nodes=[[NSMutableArray alloc]init];

[nodeQueue addObject:root];
[queue addObject:rootInfo];
[visited addObject:rootInfo];

while(queue.count>0){
NSDictionary *nodeInfo=[queue objectAtIndex:0];
Node *parent=[nodeQueue objectAtIndex:0];
[nodes addObject:parent];
[queue removeObjectAtIndex:0];
[nodeQueue removeObjectAtIndex:0];
for(id childInfo in [nodeInfo objectForKey:@”Children”]){
if(![visited containsObject:childInfo]){
count++;
Node * node= [[Node alloc] initWithIdentifier:[[NSString alloc]initWithFormat:@”i_%d”,count] andInfo:childInfo];
[parent.children addObject:node];
[nodeQueue addObject:node];
[queue addObject:childInfo];
[visited addObject:childInfo];
}
}
}

UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@”Main_iPhone” bundle:nil];

for (int i=0;i<[nodes count];i++) {
UIViewController *vc = [storyboard instantiateViewControllerWithIdentifier:@”CustomViewConroller”];
CustomNodeView * customView = (CustomNodeView *)[vc.view viewWithTag:25];
[customView createViewWithNode:((Node *)nodes[i])];
((Node *)nodes[i]).nodeView = customView;
}

return root;
}

[/code]

Create our Hierarchy view:

Here we initialize our HierarchyScrollView with root node.

[code language=”objc”]
– (void)viewDidLoad
{
[super viewDidLoad];
id root = [self setup];

HierarchyScrollView *view =[[HierarchyScrollView alloc]initWithFrame:self.view.bounds andWithRoot:root];
[self.view addSubview:view];
}

[/code]