您的位置:首页 > 其它

CATiledLayer讲解Part2

2015-11-02 16:26 459 查看
原文地址:http://www.mlsite.net/blog/?p=1884

Let’s add on to our previous 
CATiledLayer
 demo
by implementing zooming. This is one of those things that worked a lot better than I thought it would, but, arguably, still didn’t work well enough. The built-in zoom support in 
CATiledLayer
 integrates
well (i.e., easily) with a 
UIScrollView
,
but it doesn’t quite work the way I’d like, and it’s not obvious how it might be tweaked to work better. Let’s take a look at some code, and I’ll show you what I mean. (You can download the complete project for this week’s demo here.)


Continuing On

I’m going to pick up from last week’s demo, which you can download here.
The changes are pretty straightforward. Begin by opening up 
zoomdemoViewController.m
,
and adding this
#import
:
#import <QuartzCore/QuartzCore.h>


Next, add a minimal 
viewForZoomingInScrollView:
 method:
- (UIView*)viewForZoomingInScrollView:(UIScrollView*)scrollView
{
return content;
}


Now we’re ready to set up levels of detail in the 
CATiledLayer
.


Levels of Detail

Recode the 
sizeContent
 method in 
zoomdemoViewController.m
 to
look like this:
- (void)sizeContent
{
self.content.frame = CGRectMake(0, 0, world.width*scale, world.height*scale);
self.scrollView.contentSize = CGSizeMake(CGRectGetMaxX(self.content.frame),
CGRectGetMaxY(self.content.frame));

CGSize vpSize = self.scrollView.frame.size;
CGFloat zoomOutLevels = MAX(ceil(log2(MAX(world.width*scale/vpSize.width, world.height*scale/vpSize.height))), 0);
CGFloat zoomInLevels = 10;

[(CATiledLayer*)self.content.layer setLevelsOfDetail:zoomOutLevels+zoomInLevels+1];
[(CATiledLayer*)self.content.layer setLevelsOfDetailBias:zoomInLevels];

self.scrollView.minimumZoomScale = pow(2, -zoomOutLevels);
self.scrollView.maximumZoomScale = pow(2, zoomInLevels);

[self.content.layer setNeedsDisplay];
}


Let me say a word about what’s going on here.

There are two distinct-but-related zooming mechanisms in play in this code: The
UIScrollView
 has
a minimum and maximum zoom level that controls the range of scale factors applied to its content (or, anyway, to the 
UIView
 returned
by
viewForZoomingInScrollView:
 — an exact description of what’s
going on is surprisingly hard to pin down), while the 
CATiledLayer
 inside
the 
TiledView
 has a range of levels
of detail that it uses to render different tiles at different zoom levels.

So, if you configure the 
UIScrollView
 with a 
minimumZoomScale
 of
0.125 and a 
maximumZoomScale
of 8, you’ll be able to zoom in or
out by a factor of 8. In such a case, you’d want to configure the 
CATiledLayer
 with
levelsOfDetail
 of 7, and a 
levelsOfDetailBias
 of
3. This configuration creates 7 levels of detail, where each level is half the resolution of its predecessor, the “first” 3 levels of detail are “magnified”, the 4th is “normal” resolution, and the “last” 3 are at reduced resolutions. Continuing the example,
if you had a 1024×1024 
TiledView
, the just-described 7 levels
of detail would render the view at:
Level 08192×8192
Level 14096×4096
Level 22048×2048
Level 31024×1024
Level 4512×512
Level 5256×256
Level 6128×128
When you apply a scaling transform to a 
CATiledLayer
, the effect
is a little like combining a camera’s optical and digital zoom. The 
CATiledLayer
 selects
the “best” level of detail to use for the given scale factor, and renders tiles at that level of detail. For instance, if you were applying a scale factor of 0.4 to the layer, then the tiles would probably be rendered from the 50% level of detail (Level 4
in our running example) and a 0.8 scale factor applied to those tiles. This arrangement will tend to produce a higher-quality, more efficient image than would be obtained by simply applying a raw scaling transform to the full-resolution tiles.

With all that said, we can understand 
sizeContent
 a bit better.
This function:

Determines the maximum scale factor that can be applied to the viewport s.t. it doesn’t exceed the size of the world in at least one dimension (remember that scaling up the viewport is the same thing as scaling down the world)

Finds the smallest power-of-two greater than or equal to that scale factor

Floors the preceding result at 0; this is the number of “minified”, or “zoom out” levels of detail

Arbitrarily sets the number of “magnified” or “zoom in” levels to 10

Sets the number of levels of detail to 1, plus the number of “zoom out” levels, plus the number of “zoom in” levels

Sets the levels of detail bias to the number of “zoom in” levels

Calculates the 
UIScrollView's
 minimum and maximum zoom from the
number of “zoom in” and “zoom out” levels


TiledView

The big change to 
TiledView
 has to do with tile index calculation.
As mentioned last time, tile indices aren’t really used for anything critical (just debugging) but this is an opportunity to get some insight into how things work. To begin with, change the “Calculate tile index” section of 
drawLayer:inContext:
 to
this:
CGSize tileSize = [(CATiledLayer*)layer tileSize];
CGRect tbox = CGRectApplyAffineTransform(CGRectMake(0, 0, tileSize.width, tileSize.height),
CGAffineTransformInvert(CGContextGetCTM(context)));
CGFloat x = box.origin.x / tbox.size.width;
CGFloat y = box.origin.y / tbox.size.height;
CGPoint tile = CGPointMake(x, y);


And, just to keep things clear, you should probably update the comment at the top of this method, as well:
- (void)drawLayer:(CALayer*)layer inContext:(CGContextRef)context
{
// Fetch clip box in *world* space; context's CTM is preconfigured for world space->tile pixel space transform
CGRect box = CGContextGetClipBoundingBox(context);

…


The issue here is that once we start using (and zooming) a 
CATiledLayer
 we
lose the nice world/view/tile space hierarchy that we saw before. The CTMs of
the 
CGContextRefs
 passed to 
drawLayer:inContext:
 are
set up to do a transformation from world space to a tile pixelspace. On the one hand, it’s nice that the CTMs are set up for us, and that we can just
pass world coordinates into them, but the loss of an explicit view space and the device-dependent (and zoom-dependent) world-space size of the tiles make it more difficult to reason about what’s going on.

In order to calculate the index of the tile being rendered, we need to divide the tile’s world-space origin by the world-space size of a tile. We can’t consistently get this size from the tile’s clip box, because the leftmost and bottommost tiles may be smaller
than normal if they are themselves clipped against the layer’s bounds. Therefore, we have to convert a full-size tile from tile pixel space into world space; we do this by inverting the context’s CTM and applying the result to a tile’s pixel-space rectangle.

And that’s pretty much it. You can run the demo, and see the zooming goodness. As I said at the top, however, I’m not entirely satisfied with the results.


Complaints

To begin with the least important objection, this arrangement doesn’t provide the effectively infinite zoom we’ve seen in earlier demos. This is probably ok; we should be able to calculate a broad “interesting” range of zooms in almost all cases.

More seriously, the integration of the zooming of 
CATiledLayer
 and 
UIScrollView
 subverts
the goal of only rendering those pixels that are displayed; if the 
UIScrollView
 has
a
zoomScale
 of 0.51, then almost twice as many pixels will be rendered
as are displayed, since this zoom isn’t quite low enough to trigger the next-lowest level of detail. Interestingly, the
CATiledLayer
 always
seems to select the level of detail that renders at least as many pixels as are to be displayed — at least this avoids ugly “pixelation” artifacts.

To return to a point mentioned above: the arrangement of components in this demo upsets the nice hierarchy of spaces that we’ve seen before. In particular, the client space of the 
TiledView
 (or 
CATiledLayer
)
is, effectively, world space. Aside from being slightly confusing, this makes the process of resizing the world — which might make sense for certain applications
— potentially more complex.

Finally, performance — at least in the iPhone4 simulator — is a little scary. Things seem to run ok in standard resolution (on either simulated or actual hardware) but high-res tiling can take a long time to fill up the screen. I’m not sure what’s behind this.
Also, attempts to force the demo to run in standard resolution in high-res environments weren’t successful; setting either the 
UIView's
 
contentScaleFactor
 or
the 
CATiledLayer's
 
contentsScale
 properties
didn’t seem to do anything.

Part of the performance problem seems to be that the simulator is rendering (roughly) between 5×7 and 10×13 tiles per screen; at a 256×256 tile size, that’s between 1280×1792 and 2560×3328 pixels drawn per 640×920 pixel screen, translating to an overdraw of
between 4x and 14x. Either I’m missing something, or the simulator (at least) has some serious problems. (At a guess, I’d say those problems have to do with doubling the doubled resolution, due to the atypical specification of tile size in pixels, rather than
points.)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: