0

I am writing an app which would tile the images 256 * 256 and write those tile files back in the directory. I am updating my URL each time if there are any updates and tile those images back and store in the iphone folder. I am worried about two main things : 1) Memory consumption -- will the memory consumption for 5 images of size 200 KB a lot ? 2) How fast I can process my app if I have to tile 5 different URL with images at the same time ?

I have written a code to tile and save in the directory for one URL and would like to do the same for 5 URLs. Is it recommended to go with this approach or if anyone has a different approach?

    - (void)viewDidLoad
    {
      [super viewDidLoad];

      NSString *URLString = @"http://www.abc.com/abc.html?event=123";   
      NSURL *url = [[NSURL alloc] initWithString:URLString];
      NSData * dataImage = [NSData dataWithContentsOfURL:url];

      NSString *directoryPath = [[NSBundle mainBundle] bundlePath];

      UIImage *big = [UIImage imageWithData:dataImage];
      [self saveTilesOfSize:(CGSize){256,256} forImage:big toDirectory:directoryPath          usingPrefix:@"image_124_"];  

       TileView *tv = [[TileView alloc] initWithFrame:(CGRect){{0,0}, (CGSize){5000,5000}}];
      [tv setTileTag:@"image_110_"];
      [tv setTileDirectory:directoryPath];

      [scrollView addSubview:tv];

      [scrollView setContentSize:(CGSize){5000,5000}];
    }


     - (void)saveTilesOfSize:(CGSize)size 
        forImage:(UIImage*)image 
        toDirectory:(NSString*)directoryPath 
        usingPrefix:(NSString*)prefix
     {
      CGFloat cols = [image size].width / size.width;
      CGFloat rows = [image size].height / size.height;

      int fullColumns = floorf(cols);
      int fullRows = floorf(rows);

      CGFloat remainderWidth = [image size].width - 
                      (fullColumns * size.width);
      CGFloat remainderHeight = [image size].height - 
                      (fullRows * size.height);


      if (cols > fullColumns) fullColumns++;
      if (rows > fullRows) fullRows++;

      CGImageRef fullImage = [image CGImage];

      for (int y = 0; y < fullRows; ++y) {
       for (int x = 0; x < fullColumns; ++x) {
       CGSize tileSize = size;
       if (x + 1 == fullColumns && remainderWidth > 0) {
       // Last column
       tileSize.width = remainderWidth;
       }

       if (y + 1 == fullRows && remainderHeight > 0) {
       // Last row
       tileSize.height = remainderHeight;
       }

       CGImageRef tileImage = CGImageCreateWithImageInRect(fullImage, 
                                    (CGRect){{x*size.width, y*size.height}, 
                                      tileSize});

        NSData *imageData = UIImagePNGRepresentation([UIImage imageWithCGImage:tileImage]);
       NSString *path = [NSString stringWithFormat:@"%@/%d.png", 
                    directoryPath, prefix];
  [imageData writeToFile:path atomically:NO];
 }
}    
}
lifemoveson
  • 1,661
  • 8
  • 28
  • 55

1 Answers1

1

I have implemented solution for the similar problem(the difference is, I was not saving them in directory, those were for display purpose only.), with different approach.

In my problem, I have 84 images of 250x250 dimension with size 8KB each( I added them on scrollView and on scrolling I load them, a bit similar to google maps, but more smooth). At first I was using the same approach as yours, but performance was problem. So, I used asynchornous loading concept. I wrote an UIImageView subclass with connectiond delegates, so the UIImageView subclass was responsible for loading it's image. And as loading is asynchronous so performance is far better.

As you asked

1) Memory consumption -- will the memory consumption for 5 images of size 200 KB a lot?

Ans : 5x200KB = 1MB ~ 1.2MB or so(so you will need that much memory for displaying, if you have that much amount of memory then you should not worry.).. in my case 84x8KB = 672 ~ 900KB(as I was using some additional things like activity indicator for each imageview).

2) How fast I can process my app if I have to tile 5 different URL with images at the same time ?

Ans : As you are loading it in viewDidLoad ... or in main thread then performance will be an issue(blocking may happen, as I am not completely sure whether you are using threads or not).

Quick suggestion:

1. write an UIImageView subclass which has connection delegate methods.
2. have some method that you can call from outside to message this imageView to start loading.(give the url)
3. do proper deallocation of resources like responseData and connection object, once the downloading is complete.
4. when you move from this view to other view do proper deallocation and removal of all these imageviews.
5. use intruments to look for the allocations by this.

CODE :

TileImageView.h

@interface TileImageView : UIImageView 
{
    NSURLConnection *serverConnection;
    BOOL isImageRequested;
    NSMutableData *responseData;

}
-(void) startImageDownloading:(NSString *)pRequestURL
-(void) deallocateResources;
-(BOOL) isImageRequested;
-(void)cancelConnectionRequest;

-(void) addActivityIndicator;
-(void) removeActivityIndicator;
@end

TileImageView.m

@implementation TileImageView


- (id)initWithFrame:(CGRect)frame {

    self = [super initWithFrame:frame];
    if (self) 
    {
        // Initialization code.
        isImageRequested = NO;

    }
    return self;
}

-(BOOL) isImageRequested
{
    return isImageRequested;
}

-(void) startImageDownloading:(NSString *)pRequestURL
{


    if (!isImageRequested)
    {



        NSURL *pServerURL = [[NSURL alloc] initWithString:pRequestURL];
        if (pServerURL != nil) 
        {
            isImageRequested = YES;
            [self addActivityIndicator];
            [self setBackgroundColor:[UIColor lightGrayColor]];
            NSURLRequest *pServerRequest = [[NSURLRequest alloc]initWithURL:pServerURL];
            serverConnection = [[NSURLConnection alloc] initWithRequest:pServerRequest delegate:self];
            if(serverConnection)
            {
                responseData = [[NSMutableData alloc] init];
            }
            [pServerURL release];
            [pServerRequest release];
        }


    }

}

-(void) addActivityIndicator
{
    UIActivityIndicatorView *tempActivityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
    CGFloat size = self.frame.size.width*0.12;
    [tempActivityIndicator setFrame:CGRectMake(0, 0, size, size)];
    [tempActivityIndicator setCenter:CGPointMake(self.frame.size.width/2, self.frame.size.height/2)];
    [tempActivityIndicator setTag:1000];
    [tempActivityIndicator setHidesWhenStopped:YES];
    [tempActivityIndicator startAnimating];
    [self addSubview:tempActivityIndicator];
    [tempActivityIndicator release];
}

-(void) removeActivityIndicator
{
    UIActivityIndicatorView *tempActivityIndicator = (UIActivityIndicatorView *)[self viewWithTag:1000];
    if (tempActivityIndicator != nil)
    {
        [tempActivityIndicator stopAnimating];
        [tempActivityIndicator removeFromSuperview];
    }
}


-(void)cancelConnectionRequest
{
    if (isImageRequested && serverConnection != nil)
    {
        [serverConnection cancel];
        [self removeActivityIndicator];
        [self deallocateResources];
        isImageRequested = NO;
    }

}



// Name         :   connection: didReceiveAuthenticationChallenge:
// Description  :   NSURLConnectionDelegate method. Method that gets called when server sends an authentication challenge.
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge 
{
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
    {
        [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
    }
    [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}

// Name         :   connection: didReceiveResponse:
// Description  :   NSURLConnectionDelegate method. Method that gets called when response for the launched URL is received..
-(void) connection:(NSURLConnection *) connection didReceiveResponse:(NSURLResponse *) response 
{
    [responseData setLength:0]; 

}

// Name         :   connection: didReceiveData:
// Description  :   NSURLConnectionDelegate method. Method that gets called when data for the launched URL is received..
-(void) connection:(NSURLConnection *) connection didReceiveData:(NSData *) data 
{
    [responseData appendData:data];
}

// Name         :   connection: didFailWithError:
// Description  :   NSURLConnectionDelegate method. Method that gets called when an error for the launched URL is received..
-(void) connection:(NSURLConnection *) connection didFailWithError:(NSError *) error 
{
    NSLog(@"Error occured while loading image : %@",error);
    [self removeActivityIndicator];
    [self deallocateResources];


    UILabel *tempLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 150, 30)];
    [tempLabel setBackgroundColor:[UIColor clearColor]];
    [tempLabel setFont:[UIFont systemFontOfSize:11.0f]];
    [tempLabel setCenter:CGPointMake(self.frame.size.width/2, self.frame.size.height/2)];
    [tempLabel setText:@"Image not available."];
    [self addSubview:tempLabel];
    [tempLabel release];

}

// Name         :   connectionDidFinishLoading
// Description  :   NSURLConnectionDelegate method. Method that gets called when connection loading gets finished.
-(void) connectionDidFinishLoading:(NSURLConnection *) connection 
{
    [self removeActivityIndicator];
    UIImage *tempImage = [[UIImage alloc] initWithData:responseData];
    self.image = tempImage;
    [tempImage release];
    [self deallocateResources];
}

-(void) deallocateResources
{

    if (serverConnection != nil) 
    {
        [serverConnection release];
        serverConnection = nil;
    }
    if (responseData != nil)
    {
        [responseData release];
        responseData = nil;
    }
}

- (void)dealloc {
    [super dealloc];
}


@end

So, If you use above code then only thing you have to do is to add the object of TileImageView and just call method -(void) startImageDownloading:(NSString *)pRequestURL.

Please use instruments to track allocations.

Update :

**How do I add TileImageView on scrollView ? :**

//like this I add 84 images in a 2D shape( 12 x 7) grid ... and once Images are added I set scrollView's contentSize as per complete grid size.

TileImageView *tileImageView  = [[TileImageView alloc]initWithFrame:<myFrameAsPerMyNeeds>];
[tileImageView setTag:<this is the identifier I use for recognizing the image>];
[myImageScrollView addSubView:tileImageView];
[tileImageView release];

..later in code when user scroll's and other imageviews come in visibility.I use following code...

TileImageView *loadableImageView = (TileImageView *)[myImageScrollView viewWithTag:]; [loadableImageView startImageDownloading:];

I do not need to do anything in drawRect: , as I have no need to do custome drawing.

For Image names you can use tag property from imageView, but if you need some different name that are more like string then you can put another property in imageView for image name and set it while adding the image view. for saving data you can call your method once the image is downloaded in didFinishLoading method of TileImageView, where you can use that name.

SECODN UPDATE

How I add TileImageView on ScrollView

gridCount = 0;
rows = 7;
columns = 12;
totalGrids = rows*columns;
//*above : all are NSInteger type variable declared at class level

chunkWidth = 250;
chunkHeight = 250;
contentWidth = 0.0;
contentHeight = 0.0;
//*above : all are CGFloat type variable declared at class level
for (int i=0; i<rows; i++) 
{
    contentWidth = 0.0;
    for (int j=0 ; j<columns; j++)
    {

        gridCount++;
        CGRect frame = CGRectMake(contentWidth, contentHeight, chunkWidth, chunkHeight);
        [self addNewImageViewWithTag:gridCount frame:frame];
        contentWidth += chunkWidth;
    }
    contentHeight += chunkHeight;
}

[imageScrollView setContentSize:CGSizeMake(contentWidth, contentHeight)];
[imageScrollView setContentOffset:CGPointMake(0, 0)];
[imageScrollView setUserInteractionEnabled:YES];

And in ScrollViewDelegate method.

- (void) scrollViewDidScroll:(UIScrollView *)scrollView
{
    if (isZoomed)
    {
        xOffset = scrollView.contentOffset.x;
        yOffset = scrollView.contentOffset.y;
        //*above : both are CGFloat type variable declared at class level

        visibleColumn = xOffset/chunkWidth+1;
        visibleRow = yOffset/chunkHeight+1;
        gridNumber = (visibleRow-1)*columns+visibleColumn;
        adjGrid1 = gridNumber+1;
        adjGrid2 = gridNumber+columns;
        adjGrid3 = adjGrid2+1;
        //*above : all are NSInteger type variable declared at class level
        if (gridNumber ==1)
        {
            [self createAndSendScrollRequest:gridNumber];
        }
        if (adjGrid1 > 0 && adjGrid1 <= totalGrids) 
        {
            [self createAndSendScrollRequest:adjGrid1];
        }
        if (adjGrid2 > 0 && adjGrid2 <= totalGrids) 
        {
            [self createAndSendScrollRequest:adjGrid2];
        }
        if (adjGrid3 > 0 && adjGrid3 <= totalGrids) 
        {
            [self createAndSendScrollRequest:adjGrid3];
        }
    }


}

And this is how createAndSendScrollRequest is implemented.

- (void) createAndSendScrollRequest:(NSInteger)chunkId
{

    TileImageView *loadingImageView = (TileImageView *)[imageScrollView viewWithTag:chunkId];
    if ([loadingImageView image]==nil) 
    {
        [loadingImageView startImageDownloading:<and here I pass url my url is based on tag so In reality I dont pass anything I just use it from the imageview's tag property>];
    }

}

Thanks,

Ravin
  • 8,544
  • 3
  • 20
  • 19
  • Ravin. Sorry for such a late reply. So in the above code you basically download the data and dont store or save it as various image names. Then how are you using to drawRect on your TileView ? Since I have a function saveTilesOfSize:(CGSize)size forImage:(UIImage*)image toDirectory:(NSString*)directoryPath usingPrefix:(NSString*)prefix which needs name of the UIImage. so how you loading the file on the scrollview then ? – lifemoveson May 13 '11 at 21:33
  • Ravin. Thank you for the updates. So if you not drawing those images then how are you loading 84 images ? Because from one URL I will receive one image of size 3000 * 3000. I need to tile that image which I receive from URL and load up those images. I have used photoscroller example from apple store and accordingly we need to have those images stored somewhere or have you manually created those 84 images ? – lifemoveson May 17 '11 at 15:26
  • Please see the code under SECOND Update, we have image server for images when we hit it with chunkId it returns us image and we use it. – Ravin May 19 '11 at 06:29
  • Ravin. Thank you for the updates. I am basically doing the same thing but instead of calling the imagescrollview each time when the view appears, I tile the images and write them on the disk. I guess you doing the same in your code above about drawing the content grid using two for loops. So you loading the URL each time your imageview appears in the view ? – lifemoveson May 20 '11 at 19:48
  • No, I am loading image only once. When the imageview apears I start loading its image and isImageRequested is used to check about whether request is made or not. Also, currently I am giving it three attempts to download the image. [Sorry, for this "too late" response.] – Ravin Jul 13 '11 at 04:47