为什么会有2倍,3倍图
网络上会有很多的解释,这些解释都是对的。
通俗点说,出现这种区别是因为,屏幕尺寸没有屏幕分辨率增长的快(所以更高清了)。所以,对于iPhone6来说,1pt(代码中的尺寸值单位,也可理解为屏幕物理尺寸的换算单位)代表2px。对于iPhone6P来说,1pt代表3px。
所以对于iPhone6P和iPhone6来说,我们如果都想在屏幕上显示一个100ptx100pt的图片。
iPhone6就需要加载一个尺寸 200px x 200px 的图片。
iPhone6P需要加载一个尺寸 300px x 300px 的图片。
因为如果iPhone6加载一个100px x 100px的图片,它实际上要填充的是200px x 200px像素的空间。所以会被放大,看起来就模糊了。
分2倍3倍图,是为了让不同设备显示图片能够更加清晰。
但是我们开发的时候,设计师通常会将3种大小的图都输出出来,我们也通常选择将3种规格的图片都拖到工程中(现在1x的设备快绝迹了,所以也可以只添加2x和3x)。
如果仅仅是为了显示更清晰,我们为什么不只用3倍图呢?
只用3倍图,不同手机加载之后也只会去缩小,可以避免因放大而导致的模糊。还会因为引入更少的图片,减少包的大小。
看起来挺好的,也没什么问题。
但是如果只用3倍图,可能会引入如下问题(按照上面例子):
- iPhone6加载3倍图时,首先会将3倍图数据加载到内存中,造成了一定的内存损耗,因为大多数情况下,2倍图会比3倍图尺寸小一些。
- iPhone6加载300px x 300px 的图片后,图片尺寸会变成 150pt x 150pt,如果显示要求 100pt x 100pt,需要给ImageView设定具体尺寸。而且缩放会增加一些计算量。
- iPhone6在将要显示图片的时候,会将图片发送到GPU,GPU会为图片分配计算空间。如果图片大了一些,会消耗过多的GPU计算量。
- 有一些GPU只允许传入长宽都是2的N次幂尺寸的图片,所以传入GPU前,需要将图片变大为长宽均为2的N次幂。如果图片大了一些,会增加很多计算量,也会急剧增加内存消耗。
如果同时引入3种尺寸的图片,能够避免上述问题。根据经验,多引入的2种尺寸图片,对ipa包体的大小影响极小。
获取当前屏幕px和pt的关系
可以使用 UIScreen.main.scale (OC: [UIScreen mainScreen].scale)来获取 1pt = ?px 的值。
iPhone6中 这个值为2。
iPhone6p中 这个值为3。
比如,我们想要绘制一条高度为1px的线。我们就可以这样写:
//这个 1.0 / UIScreen.main.scale 得到的就是1px对应的pt数值
let lineView = UIView(frame: CGRect(x: 0, y: 100, width: UIScreen.main.bounds.width, height: 1.0 / UIScreen.main.scale));
superView.addSubview(lineView);
复制代码
绘制图片
如果我们有如下需求,可能就需要自己绘制图片了。
- 需要对已存在的图片进行修改,比如尺寸修改,添加水印等。
- 需要根据不同的需求生成图片,比如将富文本变成图片。
- 需要将当前的某个View输出成图片。
绘制的基本代码如下:
func test(){
let size = CGSize(width: 100, height: 100)
var img = nil;
//开启绘制环境
//第一个参数 size 表示将要绘制的图片尺寸(单位是pt)。
//第二个参数 固定传false,如果置为true,则不会绘制透明区域,透明区域会变成黑色。
//第三个参数 表示输出的图片是几倍图。一般设为 UIScreen.main.scale,如果你设置的数值小于 UIScreen.main.scale,绘制出的图片可能会比较模糊(图片的物理尺寸小于size),如果大于UIScreen.main.scale,图片可能会被缩放(图片物理尺寸大于size)。这里物理尺寸指将生成的图片,输出成文件后的图片文件尺寸。
//另一个重载函数:UIGraphicsBeginImageContext(CGSize) scale值总是1。
UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.main.scale);
...具体绘制工作
//从环境中将绘制的图片输出到img中
img = UIGraphicsGetImageFromCurrentImageContext();
//结束绘制,释放资源
UIGraphicsEndImageContext();
// img 就是绘制后的图片
if let i = img {
let imgData = UIImagePNGRepresentation(i);
//若UIGraphicsBeginImageContextWithOptions第三个参数为scale,输出的sava.png的尺寸将为 (size.width x scale, size.height x scale),单位是px。
imgData?.write(to: URL.init(fileURLWithPath: "save.png"));
}
}
复制代码
细节请看上面注释,另外有2点需要注意。
- 整个过程所有计算尺寸都是pt。
- scale只与输出图片的大小有关,不参与任何尺寸计算。
内容就这么多,下面介绍几个常用的绘制图片的例子:
public class Test {
//缩放
public static func resize(srcImg: UIImage, toSize: CGSize) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(toSize, false, UIScreen.main.scale);
srcImg.draw(in: CGRect(x: 0, y: 0, width: toSize.width, height: toSize.height));
let img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return img;
}
//截图
public static func screenShot() -> UIImage? {
if let windowLayer = UIApplication.shared.keyWindow?.layer {
UIGraphicsBeginImageContextWithOptions(UIScreen.main.bounds.size, false, UIScreen.main.scale);
if let ctx = UIGraphicsGetCurrentContext() {
windowLayer.render(in: ctx);
}
let img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return img;
}
return nil;
}
//根据传入颜色生成图片
public static func createImage(color: UIColor, size: CGSize) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.main.scale);
if let ctx = UIGraphicsGetCurrentContext() {
let layer = CALayer.init();
layer.backgroundColor = color.cgColor;
layer.bounds = CGRect(x: 0, y: 0, width: size.width, height: size.height);
layer.render(in: ctx);
}
let img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return img;
}
//根据富文本生成图片
public static func createImage(attributedString: NSAttributedString, size: CGSize) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.main.scale);
attributedString.draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height));
let img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return img;
}
}
复制代码