平时生活和工作中的iOS绘制ios

Quartz2D 编程指南(四)位图与图像遮罩、CoreGrap

2016-05-13  本文已影响1735人  xuyafei86
  1. 概览
  2. 图形上下文
  3. 路径
  4. 颜色与颜色空间
  5. 变换
  6. 图案
  7. 阴影
  8. 渐变
  9. 透明层
  10. Quartz 2D 中的数据管理
  11. 位图与图像遮罩
  12. CoreGraphics 绘制 Layer

位图与图像遮罩

简介

位图和图像遮罩

位图信息

CGImageRef __nullable CGImageCreate(size_t width, size_t height,
    size_t bitsPerComponent, size_t bitsPerPixel, size_t bytesPerRow,
    CGColorSpaceRef __nullable space, CGBitmapInfo bitmapInfo,
    CGDataProviderRef __nullable provider,
    const CGFloat * __nullable decode, bool shouldInterpolate,
    CGColorRenderingIntent intent);
  1. 位图数据源:可以是一个 Quartz 数据提供者(provider)或者是一个 Quartz 图像源(source)。
  2. 可选的解码数组。
  3. 插值设置:这是一个布尔值,指定 Quartz 在重置图像大小时是否使用插值算法。
  4. 渲染意图:指定如何映射位于图形上下文中的目标颜色空间中的颜色。该值在图像遮罩中不需要。
  5. 图像尺寸
  6. 像素格式:包括每个分量中的位数,每个像素的位数和每行中的字节数。
  7. 颜色空间和位图布局信息:描述了 alpha 的位置和位置是否使用浮点值。图像遮罩不需要这个信息。

解码数组

像素格式

  1. 每个分量的位数:即在一个像素中每个独立颜色分量的位数。对于一个图像遮罩,这个值是源像素中遮罩 bit 的数目。例如,如果源图片是 8-bit 的遮罩,则指定每个分量是 8 位。
  2. 每个像素的位数:即一个源像素所占的总的位数。这个值必须至少是每个分量的位数乘以每个像素中分量的数目。
  3. 每行的字节数:即图像中水平行的字节数。

颜色空间和位图布局

  1. 位图是否包含 alpha 通道。Quartz 支持 RGB,CMYK 和灰度颜色空间。同时支持 alpha,或 transparency,虽然并不是所有位图图像格式都支持 alpha 通道。alpha 通道可用时,alpha 分量可以位于像素的高位或地位。
  2. 对于有 alpha 分量的位图,指定颜色分量是否已经乘以了 alpha 值。预乘 alpha(Premultiplied alpha)表示一个已将颜色分量乘以了 alpha 值的源颜色。这种预处理通过消除每个颜色分量的额外的乘法运算来加速图片的渲染。
  3. 采样的数据格式是整型还是浮点型。
  1. kCGImageAlphaLast:alpha 分量存储在每个像素中的低位,如RGBA。
  2. kCGImageAlphaFirst:alpha 分量存储在每个像素中的高位,如ARGB。
  3. kCGImageAlphaPremultipliedLast:alpha 分量存储在每个像素中的低位,同时颜色分量已经乘以了 alpha 值。
  4. kCGImageAlphaPremultipliedFirst:alpha 分量存储在每个像素中的高位,同时颜色分量已经乘以了 alpha 值。
  5. kCGImageAlphaNoneSkipLast:没有 alpha 分量。如果像素的总大小大于颜色空间中颜色分量数目所需要的空间,则低位将被忽略。
  6. kCGImageAlphaNoneSkipFirst:没有 alpha 分量。如果像素的总大小大于颜色空间中颜色分量数目所需要的空间,则高位将被忽略。
  7. kCGImageAlphaNone:等于kCGImageAlphaNoneSkipLast。
typedef CF_OPTIONS(uint32_t, CGBitmapInfo) {
  kCGBitmapAlphaInfoMask = 0x1F,
  
  kCGBitmapFloatInfoMask = 0xF00,
  kCGBitmapFloatComponents = (1 << 8),
  
  kCGBitmapByteOrderMask = 0x7000,
  kCGBitmapByteOrderDefault = (0 << 12),
  kCGBitmapByteOrder16Little = (1 << 12),
  kCGBitmapByteOrder32Little = (2 << 12),
  kCGBitmapByteOrder16Big = (3 << 12),
  kCGBitmapByteOrder32Big = (4 << 12)
};
kCGImageAlphaPremultipliedLast | kCGBitmapFloatComponents

创建图像

CGImageRef imageRef = [UIImage imageNamed:@"image"].CGImage;
CGDataProviderRef dataProvider = CGImageGetDataProvider(imageRef);
CGImageRef image = CGImageCreate(80, 80, 8, 32, 320,
                                 CGColorSpaceCreateDeviceRGB(),
                                 kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big,
                                 dataProvider,
                                 NULL, true,
                                 kCGRenderingIntentPerceptual);
//A flexible function for creating an image. You must specify all the bitmap information that is discussed in Bitmap Image Information.

NSString *path = [[NSBundle mainBundle] pathForResource:@"image" ofType:@"png"];
NSURL *url = [NSURL fileURLWithPath:path];
CGImageSourceRef imageSource = CGImageSourceCreateWithURL((__bridge CFURLRef)url, NULL);

image = CGImageSourceCreateImageAtIndex(imageSource, 0, NULL);
// Creates an image from an image source. Image sources can contain more than one image. See Data Management in Quartz 2D for information on creating an image source.

CFStringRef string[] = {kCGImageSourceShouldCache};
CFBooleanRef boolean[] = {kCFBooleanTrue};
CFDictionaryRef dic = CFDictionaryCreate(kCFAllocatorDefault, (void *)string, (void *)boolean, 1, NULL, NULL);
image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, dic);
// Creates a thumbnail image of an image that is associated with an image source. See Data Management in Quartz 2D for information on creating an image source.

image = CGBitmapContextCreateImage(context);
// Creates an image by copying the bits from a bitmap graphics context.

image = CGImageCreateWithImageInRect(imageRef, CGRectMake(0, 0, 80, 80));
// Creates an image from the data contained within a sub-rectangle of an image.

image = CGImageCreateCopy(imageRef);
// A utility function that creates a copy of an image.

image = CGImageCreateCopyWithColorSpace(imageRef, CGColorSpaceCreateDeviceRGB());
// A utility function that creates a copy of an image and replaces its color space.

CGContextDrawImage(context, CGRectMake(0, 0, 80, 80), image);

image = CGBitmapContextCreateImage(context);
CGImageRef imageRef = [UIImage imageNamed:@"image"].CGImage;
size_t width = CGImageGetWidth(imageRef);
size_t height = CGImageGetHeight(imageRef);
size_t bitsPerComponent = CGImageGetBitsPerComponent(imageRef);
size_t bitsPerPixel = CGImageGetBitsPerPixel(imageRef);
size_t bitsPerRow = CGImageGetBytesPerRow(imageRef);
CGColorSpaceRef colorSpace = CGImageGetColorSpace(imageRef);
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
CGDataProviderRef dataProvider = CGImageGetDataProvider(imageRef);
CGFloat *decode = CGImageGetDecode(imageRef);
bool shouldInterpolate = CGImageGetShouldInterpolate(imageRef);
CGColorRenderingIntent renderingIntent = CGImageGetRenderingIntent(imageRef);

从大图像中创建裁剪的图像

从上下文中获取图片

创建图像遮罩

CG_EXTERN CGImageRef __nullable CGImageMaskCreate(
    size_t width, 
    size_t height,
    size_t bitsPerComponent, 
    size_t bitsPerPixel, 
    size_t bytesPerRow,
    CGDataProviderRef __nullable provider, 
    const CGFloat * __nullable decode,
    bool shouldInterpolate
);

遮罩图像

遮罩技术可以让我们通过控制图片的哪一部分被绘制,以生成很多有趣的效果,我们可以:

  1. 使用图像遮罩来遮罩图像。我们也可以把图像作为遮罩图,以获取同使用图像遮罩相反的效果。
  2. 使用颜色来遮罩图像。其中包含被称为颜色遮罩的技术。
  3. 使用图像或图像遮罩来裁剪上下文。实际是裁剪 Quartz 绘制内容到裁剪的图形上下文时的图片。

使用图像遮罩来遮罩图像

  1. 原始图像,遮罩将用于其上。这个图像不能是图像遮罩,也不能有与之相关的遮罩颜色。
  2. 图像遮罩,通过调用 CGImageMaskCreate 函数创建。也可以提供一个图像来替代图像遮罩,但这将获得同使用图像遮罩相反的效果。
  1. 原图


  2. 使用 CGImageMaskCreate 创建的图像遮罩


  3. 原图被遮罩后的图像


使用图像来遮罩图像

使用颜色来遮罩图像

  1. 一个图像,它不能是遮罩图像,也不能是使用过图像遮罩或颜色遮罩的图像。
  2. 一个颜色分量数组,指定了一个颜色或一组颜色值,以用于遮罩图像。
const CGFloat myMaskingColors[6] = {124, 255, 68, 222, 0, 165};
CGImageRef myColorMaskedImage = CGImageCreateWithMaskingColors(image, myMaskingColors);
CGContextDrawImage(context, myContextRect, myColorMaskedImage);
const CGFloat myMaskingColors[6] = {0, 124, 0, 68, 0, 0};
CGImageRef myColorMaskedImage = CGImageCreateWithMaskingColors(image, myMaskingColors);
CGContextSetRGBFillColor (myContext, 0.6373,0.6373, 0, 1);
CGContextFillRect(context, rect);
CGContextDrawImage(context, rect, myColorMaskedImage);

通过裁减上下文来遮罩图片

  1. 需要裁减的图形上下文
  2. 要使用遮罩的矩形区域
  3. 一个图像遮罩,通过 CGImageMaskCreate 函数创建。我们可以使用图像来替代图像遮罩以达到相反的效果。图像必须使用 Quartz 图像创建函数来创建,但不能是使用过图像遮罩或颜色遮罩的图像。
  1. 通过 CGImageMaskCreate 创建的图像遮罩。


  2. 被裁剪后的绘制到图形上下文的图片。


  3. 当遮罩图像是使用 CGImageCreate 创建,而不是使用 CGImageMaskCreate 创建的时,可以得到相反的效果。


在图像中使用混合模式

CoreGraphics 绘制 Layer

简介

  1. 高质量离屏渲染,以绘制我们想重用的图形。例如,我们可能要建立一个场景并重用相同的背景。将背景场景绘制于一个层上,然后在需要的时候再绘制层。绘制层的一个额外的好处是我们不需要知道颜色空间或其它设备依赖的信息。
  2. 重复绘制。例如,我们可能想创建一个由相同元素反复绘制而组成的图案。将元素绘制到一个层中,然后重复绘制这个层。任何我们重复绘制的 Quartz 对象,包括 CGPath, CGShading 和 CGPDFPage,都可以通过将其绘制到 CGLayer 来优化性能。需要注意的是层不仅仅是用于在屏幕上绘制;我们也可以将其用于那些不是面向屏幕的图形上下文,如 PDF 图形上下文。
  3. 缓存。虽然我们可以将层用于此目的,但通常不需要这样做,因为 Quartz Compositor 已经做了此事。如果我们必须绘制一个缓存,则使用层来代替位图图形上下文。

Layer 绘制原理

  1. 调用 CGLayerCreateWithContext 从图形上下文中创建 CGLayer。这个 CGLayer 具有图形上下文的所有特性(包括分辨率,颜色空间和图形状态设置)。如果我们不想使用图形上下文的大小,也可以指定 CGLayer 的小大。

  2. 绘制层之前需要调用 CGLayerGetContext 来获取与层相关的图形上下文。这个图形上下文与用于创建 CGLayer 的图形上下文差不多。只要用于创建层的图形上下文是 Window 图形上下文,则 CGLayer 图形上下文会尽可能地被缓存到 GPU 中。

  3. 调用 CGContextDrawLayerInRect 或 CGContextDrawLayerAtPoint 将层绘制到图形上下文。通常情况下,我们会将层绘制到创建层对象的图形上下文中,但这不是必须的。我们可以将层绘制到任意的图形上下文,但层带有创建层对象的图形上下文的所有特性,这会使原始图形上下文的所有限制都会反映到我们的绘图中(性能、分辨率)。例如,与屏幕关联的层可能会被缓存到显卡中。如果目标上下文是打印机或 PDF 上下文,则可能需要将层对象从显卡中取出并放到内存中,从而导致性能很差。

使用层来绘制

  1. 创建从已存在的图形上下文初始化的 CGLayer 对象
  2. 为 CGLayer 获取图形上下文
  3. 绘制到 CGLayer 图形上下文
  4. 将 CGLayer 绘制到目标图形上下文

创建从已存在的图形上下文初始化的 CGLayer 对象

CGLayerRef CGLayerCreateWithContext(
    CGContextRef context,
    CGSize size, 
    CFDictionaryRef auxiliaryInfo)
  1. 用于创建 CGLayer 的图形上下文。通常我们传递一个 Window 图形上下文以便后面可以离屏绘制层。
  2. 层相对于图形上下文的大小。层的大小可以和图形上下文一样,或者更小。如果想要获得层的大小,我们可以调用函数 CGLayerGetSize(CGLayerRef layer)。
  3. 辅助字典。这个参数现在已经不用了,所以传递 NULL 即可。

为 CGLayer 获取图形上下文

绘制到 CGLayer 图形上下文

CGContextFillRect(myLayerContext, myRect)

将层绘制到目标图形上下文

  1. CGContextDrawLayerInRect:将层绘制到图形上下文中指定的矩形内。
  2. CGContextDrawLayerAtPoint:将层绘制到图形上下文中指定的点。

使用多个 CGLayer 对象来绘制星条旗

  1. 红色条纹和白色条纹的图案。我们可以将这个图案分解为一个单一的红色条纹,因为对于屏幕绘制来说,我们可以设置其背景颜色为白色。我们创建一个红色矩形,然后以变化的偏移量来重复绘制这个矩形,以创建美国国旗上的七条红色条纹。我们将红色矩形绘制到一个层,然后将其绘制到屏幕上七次。
  2. 一个蓝色矩形。我们只需要一个蓝色矩形,所以没有必要使用层。当绘制蓝色矩形时,直接将其绘制到屏幕上。
  3. 50 个白色星星的图案。与红色条纹一样,可以使用层来绘制星星。我们创建星星边框的一个路径,然后使用白条来填充。将一个星星绘制到层,然后重复 50 次绘制这个层,每次绘制时适当调整偏移量。
void myDrawFlag (CGContextRef context, CGRect* contextRect) {
    int i, j, num_six_star_rows = 5, num_five_star_rows = 4;
    CGFloat start_x = 5.0, start_y = 108.0, red_stripe_spacing = 34.0, h_spacing = 26.0, v_spacing = 22.0;
    CGContextRef myLayerContext1, myLayerContext2;
    CGLayerRef stripeLayer, starLayer;
    CGRect myBoundingBox, stripeRect, starField;
    
    // ***** Setting up the primitives *****
    CGPoint point1 = {5, 5}, point2 = {10, 15}, point3 = {10, 15}, point4 = {15, 5};
    CGPoint point5 = {15, 5}, point6 = {2.5, 11}, point7 = {2.5, 11}, point8 = {16.5, 11};
    CGPoint point9 = {16.5, 11}, point10 = {5, 5};
    const CGPoint myStarPoints[] = {point1, point2,
                                    point3, point4,
                                    point5, point6,
                                    point7, point8,
                                    point9, point10};
    
    stripeRect = CGRectMake(0, 0, 400, 17); // stripe
    starField = CGRectMake(0, 102, 160, 119); // star field
    
    myBoundingBox = CGRectMake (0, 0, contextRect->size.width, contextRect->size.height);
    
    // ***** Creating layers and drawing to them *****
    stripeLayer = CGLayerCreateWithContext(context, stripeRect.size, NULL);
    myLayerContext1 = CGLayerGetContext(stripeLayer);
    
    CGContextSetRGBFillColor(myLayerContext1, 1, 0 , 0, 1);
    CGContextFillRect(myLayerContext1, stripeRect);
    
    starLayer = CGLayerCreateWithContext(context, starField.size, NULL);
    myLayerContext2 = CGLayerGetContext(starLayer);
    CGContextSetRGBFillColor(myLayerContext2, 1.0, 1.0, 1.0, 1);
    CGContextAddLines(myLayerContext2, myStarPoints, 10);
    CGContextFillPath(myLayerContext2);
    
    // ***** Drawing to the window graphics context *****
    CGContextSaveGState(context);
    for (i = 0; i < 7; i++) {
        CGContextDrawLayerAtPoint(context, CGPointZero, stripeLayer);
        CGContextTranslateCTM(context, 0.0, red_stripe_spacing);
    }
    CGContextRestoreGState(context);
    
    CGContextSetRGBFillColor(context, 0, 0, 0.329, 1.0);
    CGContextFillRect(context, starField);
    
    CGContextSaveGState (context);
    CGContextTranslateCTM (context, start_x, start_y);
    for (j = 0; j < num_six_star_rows; j++) {
        for (i = 0; i < 6; i++) {
            CGContextDrawLayerAtPoint(context,CGPointZero, starLayer);
            CGContextTranslateCTM(context, h_spacing, 0);
        }
        CGContextTranslateCTM(context, (-i * h_spacing), v_spacing);
    }
    CGContextRestoreGState(context);
    
    CGContextSaveGState(context);
    CGContextTranslateCTM(context, start_x + h_spacing/2, start_y + v_spacing/2);
    for (j = 0; j < num_five_star_rows; j++) {
        for (i = 0; i < 5; i++) {
            CGContextDrawLayerAtPoint(context, CGPointZero, tarLayer);
            CGContextTranslateCTM(context, h_spacing, 0);
        }
        CGContextTranslateCTM(context, (-i * h_spacing), v_spacing);
    }
    CGContextRestoreGState(context);
    
    CGLayerRelease(stripeLayer);
    CGLayerRelease(starLayer);
}

博客:xuyafei.cn
简书:jianshu.com/users/2555924d8c6e
微博:weibo.com/xuyafei86
Github:github.com/xiaofei86

参考资料

上一篇 下一篇

猜你喜欢

热点阅读