4.5 拉伸过滤
最后我们再来谈谈minificationFilter
和magnificationFilter
属性。总得来讲当我们视图显示一个图片的时候都应该正确地显示这个图片意即以正确的比例和正确的11像素显示在屏幕上。原因如下
- 能够显示最好的画质像素既没有被压缩也没有被拉伸。
- 能更好的使用内存因为这就是所有你要存储的东西。
- 最好的性能表现CPU不需要为此额外的计算。
不过有时候显示一个非真实大小的图片确实是我们需要的效果。比如说一个头像或是图片的缩略图再比如说一个可以被拖拽和伸缩的大图。这些情况下为同一图片的不同大小存储不同的图片显得又不切实际。
当图片需要显示不同的大小的时候有一种叫做拉伸过滤的算法就起到作用了。它作用于原图的像素上并根据需要生成新的像素显示在屏幕上。
事实上重绘图片大小也没有一个统一的通用算法。这取决于需要拉伸的内容放大或是缩小的需求等这些因素。CALayer
为此提供了三种拉伸过滤方法他们是
- kCAFilterLinear
- kCAFilterNearest
- kCAFilterTrilinear
minification缩小图片和magnification放大图片默认的过滤器都是kCAFilterLinear
这个过滤器采用双线性滤波算法它在大多数情况下都表现良好。双线性滤波算法通过对多个像素取样最终生成新的值得到一个平滑的表现不错的拉伸。但是当放大倍数比较大的时候图片就模糊不清了。
kCAFilterTrilinear
和kCAFilterLinear
非常相似大部分情况下二者都看不出来有什么差别。但是较双线性滤波算法而言三线性滤波算法存储了多个大小情况下的图片也叫多重贴图并三维取样同时结合大图和小图的存储进而得到最后的结果。
这个方法的好处在于算法能够从一系列已经接近于最终大小的图片中得到想要的结果也就是说不要对很多像素同步取样。这不仅提高了性能也避免了小概率因舍入错误引起的取样失灵的问题
图4.14 对于大图来说双线性滤波和三线性滤波表现得更出色
kCAFilterNearest
是一种比较武断的方法。从名字不难看出这个算法也叫最近过滤就是取样最近的单像素点而不管其他的颜色。这样做非常快也不会使图片模糊。但是最明显的效果就是会使得压缩图片更糟图片放大之后也显得块状或是马赛克严重。
图4.15 对于没有斜线的小图来说最近过滤算法要好很多
总的来说对于比较小的图或者是差异特别明显极少斜线的大图最近过滤算法会保留这种差异明显的特质以呈现更好的结果。但是对于大多数的图尤其是有很多斜线或是曲线轮廓的图片来说最近过滤算法会导致更差的结果。换句话说线性过滤保留了形状最近过滤则保留了像素的差异。
让我们来实验一下。我们对第三章的时钟项目改动一下用LCD风格的数字方式显示。我们用简单的像素字体一种用像素构成字符的字体而非矢量图形创造数字显示方式用图片存储起来而且用第二章介绍过的拼合技术来显示如图4.16。
图4.16 一个简单的运用拼合技术显示的LCD数字风格的像素字体
我们在Interface Builder中放置了六个视图小时、分钟、秒钟各两个图4.17显示了这六个视图是如何在Interface Builder中放置的。如果每个都用一个淡出的outlets对象就会显得太多了所以我们就用了一个IBOutletCollection
对象把他们和控制器联系起来这样我们就可以以数组的方式访问视图了。清单4.6是代码实现。
清单4.6 显示一个LCD风格的时钟
@interface ViewController ()
@property (nonatomic, strong) IBOutletCollection(UIView) NSArray *digitViews;
@property (nonatomic, weak) NSTimer *timer;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad]; //get spritesheet image
UIImage *digits = [UIImage imageNamed:@"Digits.png"];
//set up digit views
for (UIView *view in self.digitViews) {
//set contents
view.layer.contents = (__bridge id)digits.CGImage;
view.layer.contentsRect = CGRectMake(0, 0, 0.1, 1.0);
view.layer.contentsGravity = kCAGravityResizeAspect;
}
//start timer
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(tick) userInfo:nil repeats:YES];
//set initial clock time
[self tick];
}
- (void)setDigit:(NSInteger)digit forView:(UIView *)view
{
//adjust contentsRect to select correct digit
view.layer.contentsRect = CGRectMake(digit * 0.1, 0, 0.1, 1.0);
}
- (void)tick
{
//convert time to hours, minutes and seconds
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier: NSGregorianCalendar];
NSUInteger units = NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit;
NSDateComponents *components = [calendar components:units fromDate:[NSDate date]];
//set hours
[self setDigit:components.hour / 10 forView:self.digitViews[0]];
[self setDigit:components.hour % 10 forView:self.digitViews[1]];
//set minutes
[self setDigit:components.minute / 10 forView:self.digitViews[2]];
[self setDigit:components.minute % 10 forView:self.digitViews[3]];
//set seconds
[self setDigit:components.second / 10 forView:self.digitViews[4]];
[self setDigit:components.second % 10 forView:self.digitViews[5]];
}
@end
如图4.18这样做的确起了效果但是图片看起来模糊了。看起来默认的kCAFilterLinear
选项让我们失望了。
图4.18 一个模糊的时钟由默认的kCAFilterLinear
引起
为了能像图4.19中那样我们需要在for循环中加入如下代码
view.layer.magnificationFilter = kCAFilterNearest;
图4.19 设置了最近过滤之后的清晰显示