如何创build一个适用于视网膜显示的CGBitmapContext,而不是为正常显示浪费空间?
如果是在UIKit,包括drawRect
,Retina显示的高清方面是自动处理的吗? 那么这是否意味着在drawRect
,1024 x 768视图的当前graphics上下文实际上是2048 x 1536像素的位图上下文?
( 更新:如果我使用drawRect
的当前上下文创build图像并打印其大小:
CGContextRef context = UIGraphicsGetCurrentContext(); CGImageRef image = CGBitmapContextCreateImage(context); NSLog(@"width of context %i", (int) CGImageGetWidth(image)); NSLog(@"height of context %i", (int) CGImageGetHeight(image));
然后在新的iPad上,禁用状态栏,打印2048和1536,iPad 2将显示1024和768)
实际上,我们享受了为我们自动处理的1点= 4像素的奢华。
但是,如果我们使用CGBitmapContextCreate
,那么这些将是像素,而不是点? (至less如果我们为该位图提供数据缓冲区,则缓冲区的大小(字节数)显然不是为了更高的分辨率,而是用于标准分辨率,即使我们将NULL
作为缓冲区,以便CGBitmapContextCreate
处理缓冲区对于我们来说,大小可能与传入数据缓冲区的大小相同,只是标准分辨率,而不是Retina的分辨率)。
我们总是可以为iPad 1和iPad 2以及新iPad创build2048 x 1536,但是这会浪费内存,处理器和GPU功耗,因为只有新iPad需要。
所以我们必须使用if () { } else { }
来创build这样一个位图上下文,我们该怎么做呢? 我们所有的代码CGContextMoveToPoint
必须调整Retina显示使用x * 2
和y * 2
与非视网膜显示只使用x, y
? 这对代码来说可能相当麻烦。 (或者我们可以定义一个局部variablesscaleFactor
并将其设置为[[UIScreen mainScreen] scale]
因此标准分辨率为1,视网膜为2,所以我们的x
和y
总是x * scaleFactor
, y * scaleFactor
而不是当我们使用CGContextMoveToPoint
绘制时,只有x
和y
等)
似乎UIGraphicsBeginImageContextWithOptions
可以自动创build一个视网膜,如果0.0的规模被传入,但我不认为它可以使用,如果我需要创build上下文,并保持它(和使用IVAR或UIViewController属性来保存它)。 如果我不使用UIGraphicsEndImageContext
释放它,那么它将停留在graphics上下文栈中,所以看起来像我必须使用CGBitmapContextCreate
。 (或者我们只是让它留在堆栈的底部而不用担心呢?)
经过更多的研究,我发现了以下解决scheme:
如果您必须使用CGBitmapContextCreate
,那么有两个步骤可以使尺寸和坐标系统的上下文适应标准显示或Retina显示:
float scaleFactor = [[UIScreen mainScreen] scale]; CGSize size = CGSizeMake(768, 768); CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGContextRef context = CGBitmapContextCreate(NULL, size.width * scaleFactor, size.height * scaleFactor, 8, size.width * scaleFactor * 4, colorSpace, kCGImageAlphaPremultipliedFirst); CGContextScaleCTM(context, scaleFactor, scaleFactor);
该示例将创build一个768 x 768 点的区域,在新iPad上,它将是1536 x 1536 像素 。 在iPad 2上,它是768 x 768 像素 。
一个关键因素是, CGContextScaleCTM(context, scaleFactor, scaleFactor);
是用来调整坐标系的,所以Core Graphics的任何绘图,比如CGContextMoveToPoint
等都会自动工作,不pipe是标准分辨率还是Retina分辨率。
还有一点需要注意的是UIGraphicsBeginImageContext(CGSizeMake(300, 300));
将在Retina显示器上创build一个300 x 300 像素 ,而UIGraphicsBeginImageContextWithOptions(CGSizeMake(300, 300), NO, 0.0);
将在Retina显示屏上创build600 x 600 像素 。 0.0
是方法调用自动给标准显示或视网膜显示适当的大小。
也试试这个:
- (UIImage *)maskImageWithColor:(UIColor *)color { CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height); UIGraphicsBeginImageContextWithOptions(rect.size, NO, self.scale); CGContextRef c = UIGraphicsGetCurrentContext(); [self drawInRect:rect]; CGContextSetFillColorWithColor(c, [color CGColor]); CGContextSetBlendMode(c, kCGBlendModeSourceAtop); CGContextFillRect(c, rect); UIImage *result = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return result; }
开始新的图像上下文后,您可以使用UIGraphicsGetCurrentContext
检索它。 然后,如果你想坚持下去并重新使用它,就像保留任何CF对象一样保留它(并且记住按照规则完成后释放它)。 您仍然需要调用UIGraphicsEndImageContext
来从UIKit的上下文堆栈中popup它,但是如果您保留了上下文,那么上下文将继续存在,您应该可以继续使用它,直到您释放它为止。
后来,如果你想再次使用上下文(还没有发布它),一种方法是调用UIGraphicsPushContext
,这会将上下文推回上下文栈。
另一种使用上下文的方法是假定它是一个CGBitmapContext(UIKit文档称之为“基于位图的上下文”,但不要按名称说CGBitmapContext),并使用CGBitmapContextCreateImage
在绘制后从上下文中捕获新的图像。
主要区别在于,如果使用UIGraphicsCreateImageContextWithOptions
创build了上下文,则UIGraphicsGetImageFromCurrentImageContext
将返回一个UIImage,其scale
应与您创build上下文的值相匹配。 (我不知道如果popup上下文,那么缩放值是否保留,然后再推回去。) CGBitmapContextCreateImage
返回一个CGImage,而CGImage只知道像素。
另一个区别是,UIKit绘图API(例如UIBezierPath)在UIKit上下文堆栈中的当前上下文中工作。 因此,如果不推送上下文,则只能使用Quartz API来绘制上下文。
我没有testing任何上述,所以你应该在你将交付给用户的代码之前,自己彻底地testing它。
只需创build缩放比例为0.0的上下文,以获得主屏幕的:
UIGraphicsBeginImageContextWithOptions(size,NO,0.0); CGContextRef context = UIGraphicsGetCurrentContext();
没有第三步。