1

I'm running into an issue with using CATiledLayer... I have a large image, a map, that is 4726 x 2701. Basically I'm needing to break this image into tiles and be able to zoom in to view a lot of detail, but also be able to zoom all the way out and see the entire map. Using my current implementation it works perfectly if the zoomlevel of the scrollview is set to 1.0 (the maximum zoomscale), but if you zoom out the tile are replaced incorrectly.

Here the zoomlevel is set to 1.0. The map looks perfect. enter image description here

But if the zoomlevel is all the way out (0.2 I believe) the map is all messed up. I should be seeing the entire map not just two tiles.

enter image description here

Here is how I'm getting the tiles from the large image:

- (UIImage *)tileForScale:(CGFloat)scale row:(int)row col:(int)col {
     float tileSize = 256.0f;
     CGRect subRect = CGRectMake(col*tileSize, row * tileSize, tileSize, tileSize);
     CGImageRef tiledImage = CGImageCreateWithImageInRect([mapImage CGImage], subRect);
     UIImage *tileImage = [UIImage imageWithCGImage: tiledImage];
     return tileImage;
}

I'm displaying the tiles exactly like Apple does in the PhotoScroller application.

- (void)drawRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGFloat scale = CGContextGetCTM(context).a;

    CATiledLayer *tiledLayer = (CATiledLayer *)[self layer];
    CGSize tileSize = tiledLayer.tileSize;
    tileSize.width /= scale;
    tileSize.height /= scale;

    int firstCol = floorf(CGRectGetMinX(rect) / tileSize.width);
    int lastCol = floorf((CGRectGetMaxX(rect)-1) / tileSize.width);
    int firstRow = floorf(CGRectGetMinY(rect) / tileSize.height);
    int lastRow = floorf((CGRectGetMaxY(rect)-1) / tileSize.height);

    for (int row = firstRow; row <= lastRow; row++) {
        for (int col = firstCol; col <= lastCol; col++) {
            UIImage *tile = [self tileForScale:scale row:row col:col];
            CGRect tileRect = CGRectMake(tileSize.width * col, tileSize.height * row,
                                     tileSize.width, tileSize.height);
            tileRect = CGRectIntersection(self.bounds, tileRect);

            [tile drawInRect:tileRect];
        }
    }
}

Also, here is the code where I'm setting the levels of detail, which returns 4 for my image:

- (id)initWithImageName:(NSString *)name andImage:(UIImage*)image size:(CGSize)size {
    self = [super initWithFrame:CGRectMake(0, 0, size.width, size.height)];
    if (self) {
        _imageName = [name retain];
        mapImage = [image retain];

        CATiledLayer *tiledLayer = (CATiledLayer *)[self layer];
        tiledLayer.levelsOfDetail = [self zoomLevelsForSize:mapImage.size];
    }
    return self;
}
- (NSUInteger)zoomLevelsForSize:(CGSize)imageSize {
    int zLevels = 1;
    while(YES) {
        imageSize.width /= 2.0f;
        imageSize.height /= 2.0f;
        if(imageSize.height < 256.0 || imageSize.width < 256.0) break;
        ++zLevels;
    }
    return zLevels;
}

I'm assuming it has something to do with the tile size and the zoom scale, but I really have no clue how to solve the problem. I can't seem to find any solutions to my issue on the Google. Any help would be great! :)

Homeschooldev
  • 405
  • 1
  • 7
  • 18
  • 1
    What is the value of `levelsOfDetail` property of your tiled layer? You're using the same source image for all scales, so `levelsOfDetail` should be set to 1 – Evgeny Shurakov Jan 18 '13 at 09:09
  • I updated the question to reflect what the levels of detail is. – Homeschooldev Jan 18 '13 at 18:14
  • As far as I understand you need to provide different images for each level of details and you're currently using the same one for all levels. – Evgeny Shurakov Jan 18 '13 at 19:48
  • I would split the images up by hand then use the pre-rendered tiles like Apple does, but I don't have that option. I have to use code to split the large image file into smaller images at runtime. Do you know how I should properly do this? – Homeschooldev Jan 18 '13 at 22:40
  • I should have just stopped talking and tried what you said. Setting the levelsOfDetail to 1 worked perfectly! :) – Homeschooldev Jan 19 '13 at 00:40

2 Answers2

1

You have a leak in - (UIImage *)tileForScale:(CGFloat)scale row:(int)row col:(int)col.

CGImageRef tiledImage = CGImageCreateWithImageInRect([mapImage CGImage], subRect);

Don't forget to release the CGImageRef: CGImageRelease(tiledImage);

steve1ae
  • 91
  • 4
0

I think there's a bug where the scale is incorrectly set in the sample code. I have the following:

CGFloat scale = CGContextGetCTM(context).a;
scale = 1.0f / roundf(1.0f / scale);
if (scale == INFINITY) {
    scale = 1.0f;
}
kevinl
  • 4,194
  • 6
  • 37
  • 55