【问题标题】:how to handle tiling of images on the fly如何动态处理图像平铺
【发布时间】:2011-05-12 22:59:34
【问题描述】:

我正在编写一个应用程序,它将图像 256 * 256 平铺并将这些平铺文件写回目录中。如果有任何更新,我每次都会更新我的 URL,并将这些图像平铺回来并存储在 iphone 文件夹中。我担心两个主要的事情: 1) 内存消耗 - 5 张 200 KB 大小的图像的内存消耗会很大吗? 2) 如果我必须同时将 5 个不同的 URL 与图像平铺,我可以多快处理我的应用程序?

我已经编写了一个代码来平铺并保存一个 URL 的目录,并希望对 5 个 URL 执行相同的操作。是否建议采用这种方法,或者如果有人有不同的方法?

    - (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];
 }
}    
}

【问题讨论】:

    标签: iphone objective-c cgimage catiledlayer


    【解决方案1】:

    我已经为类似问题实现了解决方案(不同的是,我没有将它们保存在目录中,这些仅用于显示目的。),采用不同的方法。

    在我的问题中,I have 84 images of 250x250 dimension with size 8KB each(我将它们添加到scrollView 并在滚动时加载它们,有点类似于谷歌地图,但更流畅)。起初我使用与您相同的方法,但性能存在问题。所以,我使用了异步加载概念。我写了一个带有连接委托的 UIImageView 子类,所以 UIImageView 子类负责加载它的图像。由于加载是异步的,因此性能要好得多。

    如你所愿

    1) 内存消耗 -- 5 张 200 KB 的图像的内存消耗会很大吗?

    Ans : 5x200KB = 1MB ~ 1.2MB 左右(所以你需要那么多内存来显示,如果你有那么多内存那么你不用担心。).. 在我的情况下 84x8KB = 672 ~ 900KB(因为我为每个图像视图使用了一些额外的东西,比如活动指示器)。

    2) 如果我必须同时将 5 个不同的 URL 与图像平铺,我可以多快处理我的应用程序?

    Ans :当您在 viewDidLoad ... 或主线程中加载它时,性能将是一个问题(可能会发生阻塞,因为我不完全确定您是否使用线程)。

    快速建议:

    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.
    

    代码:

    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
    

    所以,如果您使用上面的代码,那么您只需添加 TileImageView 的对象并调用方法-(void) startImageDownloading:(NSString *)pRequestURL

    Please use instruments to track allocations.
    

    更新:

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

    //像这样,我在 2D 形状(12 x 7)网格中添加 84 个图像...一旦添加图像,我将 scrollView 的 contentSize 设置为完整的网格大小。

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

    ..稍后在用户滚动和其他图像视图出现时的代码中。我使用以下代码...

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

    drawRect: 我不需要做任何事情,因为我不需要做客户绘图。

    对于图像名称,您可以使用imageView 中的标记属性,但如果您需要一些更像字符串的不同名称,那么您可以在imageView 中放置另一个属性作为图像名称,并在添加图像视图时设置它。为了保存数据,您可以在图像下载到TileImageViewdidFinishLoading 方法后调用您的方法,您可以在其中使用该名称。

    第二次更新

    我如何在ScrollView 上添加TileImageView

    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];
    

    ScrollViewDelegate 方法中。

    - (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];
            }
        }
    
    
    }
    

    这就是createAndSendScrollRequest 的实现方式。

    - (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>];
        }
    
    }
    

    谢谢,

    【讨论】:

    • 拉文。很抱歉这么晚才回复。因此,在上面的代码中,您基本上是下载数据而不是将其存储或保存为各种图像名称。那么你是如何在你的 TileView 上使用 drawRect 的呢?因为我有一个函数 saveTilesOfSize:(CGSize)size forImage:(UIImage*)image toDirectory:(NSString*)directoryPath usingPrefix:(NSString*)prefix 需要 UIImage 的名称。那么你如何在滚动视图上加载文件呢?
    • 拉文。感谢您的更新。因此,如果您不绘制这些图像,那么您如何加载 84 张图像?因为从一个 URL 我将收到一张大小为 3000 * 3000 的图像。我需要平铺从 URL 接收到的图像并加载这些图像。我使用了苹果商店的照片卷轴示例,因此我们需要将这些图像存储在某个地方,或者您是否手动创建了这 84 张图像?
    • 请看第二次更新下的代码,我们有图像的图像服务器,当我们用 chunkId 访问它时,它会返回我们的图像并使用它。
    • 拉文。感谢您的更新。我基本上在做同样的事情,但不是每次出现视图时都调用 imagescrollview,而是平铺图像并将它们写入磁盘。我猜你在上面的代码中使用两个 for 循环绘制内容网格也是如此。所以每次你的 imageview 出现在视图中时你都会加载 URL 吗?
    • 不,我只加载一次图像。当 imageview 出现时,我开始加载它的图像,并且 isImageRequested 用于检查是否发出请求。另外,目前我正在尝试下载图像三次。 [抱歉,这个“为时已晚”的回复。]
    猜你喜欢
    • 1970-01-01
    • 2011-02-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-26
    • 2018-04-21
    相关资源
    最近更新 更多