CATiledLayer drawing with incorrect scale
Originator: | chris | ||
Number: | rdar://8503490 | Date Originated: | 01-Oct-2010 03:38 PM |
Status: | Open | Resolved: | |
Product: | iOS | Product Version: | 4 |
Classification: | Serious | Reproducible: | Always |
Summary: Certain sizes will cause CATiledLayer to ask for its tiles to be drawn with an incorrect scale and rect. The error appears to be caused by loss of accuracy in floating point calculations. For example, with a size of {width = 4509, height = 7282}, the CATiledLayer will provide CTM scales of [0.124861, 0.249945, 0.499889, 1.000000] instead of [0.125, 0.25, 0.5, 1.0]. Steps to Reproduce: 1. Set a CATiledLayer's size to something like {4509, 7282}. Many other values cause the same behavior. Expected Results: When asked to draw, the CTM's scale be set to a reasonable number like 0.125, 0.25, 0.5, or 1.0. Actual Results: When asked to draw, the CTM's scale is a number like 0.124861 instead of 0.125. Additionally, the rect you are asked to draw in is slightly off. This results in the tiles not correctly matching up, usually causing lines (gaps) to appear between rows of tiles. Regression: I have tested on iOS 4.0 and 4.1, both versions exhibit this problem. This issue arises based on the size of the layer, particularly with larger sizes. Notes: I have made two minimal changes to the PhotoScroller sample code (http://chrisfarber.net/CATiledLayerBug.zip) that will cause it to show this problem. They are: 1. In the ImageData.plist file, I changed the size of the first photo to {width = 4509, height = 7282} and left all other data the same. 2. In -[TilingView drawRect:] I commented the two lines that would load and draw the tile image. Instead, I added two lines to fill the rect that the image would have been drawn into with solid red. For the first photo that I changed the size of, black lines appear on the screen where there are gaps between the red tiles. The remaining photos whose sizes were not changed do not exhibit this issue as they are being provided correct scales when drawing. To the best of my ability I have not been able to devise a workaround that fully ameliorates this behavior without side effects.
Comments
Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!
Solution
Below is code that solves this problem. This code would go in Apple's PhotoScroller Demo app, in TilingView.m, replacing the two methods: drawRect: and tileForScale:row:col:
The substance of the problem is as has been pointed out that the scale value is slightly below the expected value. The double inversion with an int cast solves that. And the second part of the problem is that the x and y ("a" and "d" values in the affine matrix) scale values are not always both slightly off, so using just the x ("a") value results horizontal banding when the y ("d") value is not the same. The solution is simply to use both the x and y scale values to determine the CGRect to draw in to.
Zephyr
- (void)drawRect:(CGRect)rect { CGContextRef context = UIGraphicsGetCurrentContext();
}
- (UIImage *)tileForScale:(CGFloat)_scale row:(int)row col:(int)col { //this accounts for a bug somewhere upstream that returns the scale as a floating point number just below the required value: 0.249... instead of 0.2500
}
maybe a solution?
Not sure if you're still working on this. I'm having my own zooming problems setting up CATiledLayer in my app (zoomscales drawing on top of each other during paging.......any ideas?), but I think I might have a fix for this part. I'm still a bit of a noob, so sorry if this isn't 100% right.
Basically, I've set up my tiling view implementation to switch with the inverse zoom factor (1, 2, 4, 8) instead of the scale (1, .5, .25, .125). Taking the inverse of scale creates a float "slightly larger" than what you want (ie 1/.249 = 4.016) and then casting as an int truncates the fractional part according to ANSI C standards. Later on, I use this scale string with the row and column strings to fetch the right tile according to my naming convention ZZZCC_RR.jpg
enum { kZoom100 = 1, kZoom050 = 2, kZoom025 = 4, kZoom125 = 8 };
Hope this helps, Nick