阅读 1822

关于@2x,@3x的区别及图片的绘制

为什么会有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倍图,可能会引入如下问题(按照上面例子):

  1. iPhone6加载3倍图时,首先会将3倍图数据加载到内存中,造成了一定的内存损耗,因为大多数情况下,2倍图会比3倍图尺寸小一些。
  2. iPhone6加载300px x 300px 的图片后,图片尺寸会变成 150pt x 150pt,如果显示要求 100pt x 100pt,需要给ImageView设定具体尺寸。而且缩放会增加一些计算量。
  3. iPhone6在将要显示图片的时候,会将图片发送到GPU,GPU会为图片分配计算空间。如果图片大了一些,会消耗过多的GPU计算量。
  4. 有一些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);

复制代码

绘制图片

如果我们有如下需求,可能就需要自己绘制图片了。

  1. 需要对已存在的图片进行修改,比如尺寸修改,添加水印等。
  2. 需要根据不同的需求生成图片,比如将富文本变成图片。
  3. 需要将当前的某个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点需要注意。

  1. 整个过程所有计算尺寸都是pt。
  2. 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;
    }
}
复制代码
文章分类
iOS
文章标签