Quartz2D --> 二维绘图引擎(五-数据管理、位图与遮罩)

一、Quartz 2D 中的数据管理

数据管理是每个图形应用程序必须执行的任务。在 Quartz2D 中数据管理涉及到为Quartz2D 提供数据和从 Quartz 2D 中获取数据。这里主要是对于image和PDF来进行数据管理。需要注意的是:图像数据读写的首选方法是使用Image I/O框架,请参考 Image I/O Programming Guide !!!

Quartz 可识别三种类型的数据源(data sources)和目标(destination)。

  • URL :通过 URL 指定的数据位置可以作为数据的提供者和接收者。我们使用Core Foundation 数据类型 CFURLRef 作为参数传递URL给 Quartz 函数。
  • CFData:CFDataRef 和 CFMutableDataRef 。
  • Raw data;可以为数据提供一个指向任何类型数据的指针,和一组处理这些数据基本内存管理的回调函数。
1、向 Quartz 2D传输数据
/* Create an image source reading from the data provider `provider'. The
 * `options' dictionary may be used to request additional creation options;
 * see the list of keys above for more information. */
CGImageSourceRef __nullable CGImageSourceCreateWithDataProvider(CGDataProviderRef __nonnull provider, CFDictionaryRef __nullable options) 

/* Create an image source reading from `data'.  The `options' dictionary
 * may be used to request additional creation options; see the list of keys
 * above for more information. */
CGImageSourceRef __nullable CGImageSourceCreateWithData(CFDataRef __nonnull data, CFDictionaryRef __nullable options) 

/* Create an image source reading from `url'. The `options' dictionary may
 * be used to request additional creation options; see the list of keys
 * above for more information. */
CGImageSourceRef __nullable CGImageSourceCreateWithURL(CFURLRef __nonnull url, CFDictionaryRef __nullable options) 

/* Create an incremental image source. No data is provided at creation
 * time; it is assumed that data will eventually be provided using
 * "CGImageSourceUpdateDataProvider" or "CGImageSourceUpdateData".  The
 * `options' dictionary may be used to request additional creation options;
 * see the list of keys above for more information. */
CGImageSourceRef __nonnull CGImageSourceCreateIncremental(CFDictionaryRef __nullable options)

#pragma  --------------------------------

/* Create a PDF document, using `provider' to obtain the document's data. */
CGPDFDocumentRef __nullable CGPDFDocumentCreateWithProvider(CGDataProviderRef cg_nullable provider)
    
/* Create a PDF document from `url'. */
CGPDFDocumentRef __nullable CGPDFDocumentCreateWithURL(CFURLRef cg_nullable url)
  
#pragma  --------------------------------

/* Create a sequential-access data provider using `callbacks' to provide the
   data. `info' is passed to each of the callback functions. */
CGDataProviderRef __nullable CGDataProviderCreateSequential(void * __nullable info, const CGDataProviderSequentialCallbacks * cg_nullable callbacks)
    
/* Create a direct-access data provider using `callbacks' to supply `size'
   bytes of data. `info' is passed to each of the callback functions.
   The underlying data must not change for the life of the data provider. */
CGDataProviderRef __nullable CGDataProviderCreateDirect(void * __nullable info, off_t size, const CGDataProviderDirectCallbacks * cg_nullable callbacks) 

/* Create a direct-access data provider using `data', an array of `size'
   bytes. `releaseData' is called when the data provider is freed, and is
   passed `info' as its first argument. */
CGDataProviderRef __nullable CGDataProviderCreateWithData(void * __nullable info, const void * cg_nullable data, size_t size, CGDataProviderReleaseDataCallback cg_nullable releaseData) 

/* Create a direct-access data provider which reads from `data'. */
CGDataProviderRef __nullable CGDataProviderCreateWithCFData(CFDataRef cg_nullable data) 

/* Create a data provider reading from `url'. */
CGDataProviderRef __nullable CGDataProviderCreateWithURL(CFURLRef cg_nullable url) 

/* Create a data provider reading from `filename'. */
CGDataProviderRef __nullable CGDataProviderCreateWithFilename(const char * cg_nullable filename) 
  • 以上方法除了CGPDFDocumentCreateWithProvider()CGPDFDocumentCreateWithURL()外,其他均返回一个图像源(CGImageSourceRef)或者数据提供者(CGDataProviderRef)。
  • 图像源(CGImageSourceRef)是将图像数据移动到Quartz的首选方法。一个图像源代表很多的图像数据,它能包含不止一个图像,缩略图,图片属性和图像文件,当获取到图像源后可以使用之完成很多工作,例如:
    • 创建image(CGImageRef类型):使用函数CGImageSourceCreateImageAtIndex()CGImageSourceCreateThumbnailAtIndex(),一个CGImageRef类型代表一个单独的Quartz图片。
    • 为原图像源添加内容:使用函数CGImageSourceUpdateData()CGImageSourceUpdateDataProvider()
    • 从图像源获取信息。

具体可以参考ImageIO.frame中的CGImageSource.h文件!

  • 数据提供者(CGDataProviderRef)是有很多限制的比较老的机制。它们可用于获取图像或 PDF 数据。
2、获取 Quartz 2D 的数据
/* Create an image destination writing to the data consumer `consumer'.
 * The parameter `type' specifies the type identifier of the resulting
 * image file.  The parameter `count' specifies number of images (not
 * including thumbnails) that the image file will contain. The `options'
 * dictionary is reserved for future use; currently, you should pass NULL
 * for this parameter. */
CGImageDestinationRef __nullable CGImageDestinationCreateWithDataConsumer(CGDataConsumerRef __nonnull consumer, CFStringRef __nonnull type, size_t count, CFDictionaryRef __nullable options)  

/* Create an image destination writing to `data'. The parameter `type'
 * specifies the type identifier of the resulting image file.  The
 * parameter `count' specifies number of images (not including thumbnails)
 * that the image file will contain. The `options' dictionary is reserved
 * for future use; currently, you should pass NULL for this parameter. */
CGImageDestinationRef __nullable CGImageDestinationCreateWithData(CFMutableDataRef __nonnull data, CFStringRef __nonnull type, size_t count, CFDictionaryRef __nullable options)

/* Create an image destination writing to `url'. The parameter `type'
 * specifies the type identifier of the resulting image file.  The
 * parameter `count' specifies number of images (not including thumbnails)
 * that the image file will contain. The `options' dictionary is reserved
 * for future use; currently, you should pass NULL for this parameter.
 * Note that if `url' already exists, it will be overwritten. */
CGImageDestinationRef __nullable CGImageDestinationCreateWithURL(CFURLRef __nonnull url, CFStringRef __nonnull type, size_t count, CFDictionaryRef __nullable options)

#pragma --------------------------------

/* Create a PDF context for writing to `url'. This function behaves in the
   same manner as the above function, except that the output data will be
   written to `url'. */
CGContextRef __nullable CGPDFContextCreateWithURL(CFURLRef cg_nullable url, const CGRect * __nullable mediaBox, CFDictionaryRef __nullable auxiliaryInfo)

#pragma --------------------------------

/* Create a data consumer using `callbacks' to handle the data. `info' is
   passed to each of the callback functions. */
CGDataConsumerRef __nullable CGDataConsumerCreate(void * __nullable info, const CGDataConsumerCallbacks * cg_nullable cbks)

/* Create a data consumer which writes data to `url'. */
CGDataConsumerRef __nullable CGDataConsumerCreateWithURL(CFURLRef cg_nullable url)

/* Create a data consumer which writes to `data'. */
CGDataConsumerRef __nullable CGDataConsumerCreateWithCFData(CFMutableDataRef cg_nullable data)
  • 以上方法除了CGPDFContextCreateWithURL ()外,其他均返回一个图像目标(CGImageDestinationRef)或者数据消费者(CGDataComsumerRef)。
  • 图像目标(CGImageDestinationRef)是将图像数据移出Quartz的首选方法,和图片源相似,一个图像目标代表很多的图像数据,它能包含不止一个图像,缩略图,图片属性和图像文件,当获取到图像目标(CGImageDestinationRef)后可以使用之完成很多工作,例如:
    • 添加图片(CGImageRef)到图像目标中:使用函数CGImageDestinationAddImage()CGImageDestinationAddImageFromSource()CGImageDestinationAddImageAndMetadata()
    • 设置属性:使用函数CGImageDestinationSetProperties()
    • 从图像目标获取信息。

具体可以参考ImageIO.frame中的CGImageDestination.h文件!

二、Bitmap Images and Image Masks(位图和图像遮罩)

首先开始就先强调一下,这节 非常重要!!! 位图与图像遮罩在 Quartz 中都是用 CGImageRef 数据类型来表示。

1、About Bitmap Images and Image Masks

位图(A bitmap image)是一个像素数组,每一个像素在图像中代表一个独立的点,JPEG, TIFF, PNG等文件都是位图,程序的icon也是位图,位图必须是矩形形状但是可以通过设置alpha(透明度)使之呈现各种形状,同样可以进行旋转、剪切等操作。
 位图中的每一个采样包含特定颜色空间下的一个或更多颜色组件,和一个额外的用于指定 alpha 值以表示透明度的组件。每个组件的bpc是1到32位的。
 Quartz同样支持图像遮罩。一个图像遮罩也是一个位图,它指定了一个没有颜色的绘制区域。一个颜色遮罩可以有 1 - 8 位的深度(bpp)。
 关于bpc、bpp可以参考Quartz2D --> 二维绘图引擎(二 - 路径、颜色、形变)中关于颜色部分的最后一节。

2、Bitmap Image Information(位图信息)

Quartz提供了很多图像格式并内建了多种常用的格式。在 iOS 中,这些格式包括 JPEG,GIF,PNG,TIF,ICO,GMP,XBM 和 CUR。其它的位图格式或专有格式需要我们指定图像格式的详细信息,以便 Quartz 能正确地解析图像。

(1、)当Quartz创建一个位图时可能会用到以下信息:
  • A bitmap data source:位图数据源 --- 可以是一个 Quartz 数据提供者(provider)或者是一个 Quartz 图像源(source)

  • An optional decode array:一个可选的解码数组 --- 解码数组包含每个颜色组件的 一对数值 用来将图像颜色值线性映射到其它指定颜色空间的颜色值。这对一个图像做去饱和效果处理或者反转颜色值等非常有用。例如:在 RGB 颜色空间中的一个图像的解码数组包含 6 个数值,每一组分别对应红、绿、蓝颜色组件。

  • An interpolation setting:插值设置 --- 这是一个布尔值,用于指定Quartz是否应用插值算法调整图像。

  • A rendering intent :渲染意图 --- 详细请参考Quartz2D --> 二维绘图引擎(二 - 路径、颜色、形变)中关于颜色部分的第四节。

  • The image dimensions : 图像尺寸。

  • The pixel format : 像素格式 --- 包括:

    • Bits per component :即在一个像素中每个独立颜色组件的位数。对于一个图像遮罩,这个值源在源像素中屏蔽位的数目。例如,如果源图片是 8-bit 的遮罩,则指定每个组件均是 8 位。

    • Bits per pixel:源像素的位的总数,该数目是每个像素的组件数 乘以 Bits per component。例如 在RGBA颜色空间中,bitsPerComponent为8,则bitsPerPixel为32。

    • Bytes per row:图像上一行的字节数。即bitsPerPixel 乘以 图像的像素宽度。

  • Color Spaces and Bitmap Layout : 颜色空间和位图布局 --- 描述了使用alpha 的位置和位图是否使用浮点值。图像遮罩不需要这个信息。为了确保Quartz正确解译每个像素的二进制位,必须指定一下几点:

    • 位图是否包含一个alpha通道:Quartz 支持 RGB,CMYK 和灰度颜色空间。同时支持 alpha或transparency,虽然并不是所有位图图像格式都支持 alpha 通道。但是当alpha 通道可用时,alpha 组件可以位于像素的高位或低位位置上。

    • 对于存在alpha的位图,颜色组件是否已经乘以了alpha的值:预乘 alpha(Premultiplied alpha)描述源颜色已将颜色组件乘以了 alpha 值。这种预处理通过消除每个颜色组件额外的乘法运算来加速图片的渲染。例如:在RGB颜色空间中,对于图像中的每一个像素来说,在渲染图片时使用预乘 alpha可以消除三个乘法操作 --- 红色乘以α,绿色乘以α,蓝色乘以α。

    • 采样的数据格式是整型还是浮点型。

(2、)当我们使用函数CGImageCreate()创建一个图像时,需要提供一个CGImageBitmapInfo类型的参数 --- bitmapInfo ,用于指定位图布局信息。该类型是一个枚举值:
typedef CF_OPTIONS(uint32_t, CGBitmapInfo) {
    kCGBitmapAlphaInfoMask = 0x1F,

    kCGBitmapFloatInfoMask = 0xF00,
    kCGBitmapFloatComponents = (1 << 8),

    kCGBitmapByteOrderMask     = kCGImageByteOrderMask,
    kCGBitmapByteOrderDefault  = (0 << 12),
    kCGBitmapByteOrder16Little = kCGImageByteOrder16Little,
    kCGBitmapByteOrder32Little = kCGImageByteOrder32Little,
    kCGBitmapByteOrder16Big    = kCGImageByteOrder16Big,
    kCGBitmapByteOrder32Big    = kCGImageByteOrder32Big
} CG_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);

它主要提供了三个方面的布局信息:

  • alpha 的信息;
  • 颜色组件是否为浮点数;
  • 像素格式的字节顺序。

I、其中关于alpha的信息主要会使用下面的枚举定义:

typedef CF_ENUM(uint32_t, CGImageAlphaInfo) {
    kCGImageAlphaNone,               /* For example, RGB. */ 相当于kCGImageAlphaNoneSkipLast
    kCGImageAlphaPremultipliedLast,  /* For example, premultiplied RGBA */ alpha存在,在最低位,已预乘
    kCGImageAlphaPremultipliedFirst, /* For example, premultiplied ARGB */ alpha存在,在最高位,已预乘
    kCGImageAlphaLast,               /* For example, non-premultiplied RGBA */ alpha存在,在最低位,无预乘
    kCGImageAlphaFirst,              /* For example, non-premultiplied ARGB */  alpha存在,在最高位,无预乘
    kCGImageAlphaNoneSkipLast,       /* For example, RBGX. */ 无alpha通道,如果总的像素大小超过了颜色空间中颜色组件的数量的需求空间,就会忽略最低有效位。
    kCGImageAlphaNoneSkipFirst,      /* For example, XRGB. */ 无alpha通道,如果总的像素大小超过了颜色空间中颜色组件的数量的需求空间,就会忽略最高有效位。
    kCGImageAlphaOnly                /* No color data, alpha data only */
};

它同样也提供了三个方面的 alpha 信息:

  • 是否包含 alpha ;
  • 如果包含 alpha ,那么 alpha 信息所处的位置,在像素的最低有效位,比如 RGBA ,还是最高有效位,比如 ARGB ;
  • 如果包含 alpha ,那么每个颜色分量是否已经乘以 alpha 的值,这种做法可以加速图片的渲染时间,因为它避免了渲染时的额外乘法运算。

II、关于颜色组件是否为浮点型
 我们使用常量 kCGBitmapFloatComponents来标识一个位图格式使用浮点值。对于浮点格式,我们将这个常量与上述描述的合适的常量进行按位与操作来组成 bitmapInfo 的参数例:

kCGImageAlphaPremultipliedLast | kCGBitmapFloatComponents

III、关于像素格式的字节顺序,主要会使用下面的枚举定义:

typedef CF_ENUM(uint32_t, CGImageByteOrderInfo) {
    kCGImageByteOrderMask     = 0x7000,
    kCGImageByteOrder16Little = (1 << 12),
    kCGImageByteOrder32Little = (2 << 12),
    kCGImageByteOrder16Big    = (3 << 12),
    kCGImageByteOrder32Big    = (4 << 12)
} CG_AVAILABLE_STARTING(__MAC_10_12, __IPHONE_10_0);

但是,在CGBitmapInfo枚举中可以看到,CGBitmapInfo是完全兼容CGImageByteOrderInfo的,所以可以直接使用CGBitmapInfo的常量。它主要提供了两个方面的字节顺序信息:

对于 iPhone 来说,采用的是小端模式,但是为了保证应用的向后兼容性,我们可以使用系统提供的宏:

#ifdef __BIG_ENDIAN__
# define kCGBitmapByteOrder16Host kCGBitmapByteOrder16Big
# define kCGBitmapByteOrder32Host kCGBitmapByteOrder32Big
#else    /* Little endian. */
# define kCGBitmapByteOrder16Host kCGBitmapByteOrder16Little
# define kCGBitmapByteOrder32Host kCGBitmapByteOrder32Little
#endif

在这里我们最常用的一般是 --- kCGBitmapByteOrder32Host。

(3、)一些关于不同参数的不同位图布局。
3、Creating Images

下表中(表一)罗列了常用创建CGImage对象的函数,及关于函数的描述。图像创建函数的选择取决于图像数据的来源。

函数 描述
CGImageCreate() 最灵活的函数,它可以从任何类型的位图数据来创建图像。但是它也是最复杂的函数使用,因为你必须指定所有位图信息。要使用这个功能,您需要熟悉所有的位图图像信息
CGImageCreateCopy() 创建图像的副本,只复制图像结构本身不复制基础数据
CGImageCreateWithImageInRect() 根据提供的rect截取原始图片的一部分生成新的图片
CGImageCreateWithMask() 基于mask截取原始图片的一部分生成新的图片
CGImageCreateCopyWithColorSpace() 使用新的颜色空间去取代图片原始的颜色空间。如果参数image是一个遮罩或者颜色空间组件数量不一致则会返回NULL
*** ***
CGBitmapContextCreateImage() 根据上下文生成图片,如果该上下文不是位图上下文或者其他原因无法创建图像,则返回NULL。这是一个“复制”操作——后续对上下文的更改不会影响返回的图像内容。
*** ***
CGImageSourceCreateImageAtIndex() 根据图像源创建一个图像。图像来源可以包含多个图像。
CGImageSourceCreateThumbnailAtIndex() 创建与图像源中指定图片相关联的缩略图
  • 如果想从一个使用标准的图像格式如PNG和JPEG的图像文件中创建一个CGImage对象,最简单的解决方案是:调用函数CGImageSourceCreateWithURL()来创建一个图像源,然后调用函数CGImageSourceCreateImageAtIndex()从含有一个特定索引的图像源的图像数据中创建图像。如果原始图像文件只包含一个图像,提供0作为索引。如果图像包含多个图像,需要提供索引以得到适当的图像。记住,索引值从0开始。
  • 不管如何创建一个CGImage对象,都可以使用函数CGContextDrawImage()画图形上下文的图像。CGImage对象是不可变的,当不再需要一个CGImage对象,通过调用函数CGImageRelease()释放它。

(1)Creating an Image From Part of a Larger Image
 主要是使用CGImageCreateWithImageInRect()这一函数去截取已知图片上的一部分。
e.g:

- (void)drawRect:(CGRect)rect {
    [super drawRect:rect];
    
    CGContextRef currentContext = UIGraphicsGetCurrentContext();
    UIImage * image = [UIImage imageNamed:@"poem"];
    [image drawInRect:self.bounds];
    CGImageRef newImage = CGImageCreateWithImageInRect(image.CGImage, CGRectMake(375, 130, 285, 520));
//    UIImage * newUIImage = [UIImage imageWithCGImage:newImage];
//    [newUIImage drawInRect:CGRectMake(50, 70, 100, 150)];
    
    CGContextSaveGState(currentContext);
    //调整坐标系
    CGContextTranslateCTM(currentContext, 0.0, self.bounds.size.height);
    CGContextScaleCTM(currentContext, 1.0, -1.0);
    CGContextDrawImage(currentContext, CGRectMake(50, self.bounds.size.height-70-150, 100, 150), newImage);
    CGContextRestoreGState(currentContext);
    CGImageRelease(newImage);
}

上面注释部分代码与下方的是等价的,得到的结果相同!!!

效果图

(2)Creating an Image from a Bitmap Graphics Context
 主要是使用CGBitmapContextCreateImage()这一函数去获取上下文生成图片。
e.g:

- (void)drawRect:(CGRect)rect {
    [super drawRect:rect];
    
    UIImage * image = [UIImage imageNamed:@"poem"];
    [image drawInRect:self.bounds];
    
    UIGraphicsBeginImageContextWithOptions(self.bounds.size, YES, 1.0);
    
    CGContextRef currentContext = UIGraphicsGetCurrentContext();
    CGContextTranslateCTM(currentContext, 0.0, self.bounds.size.height);
    CGContextScaleCTM(currentContext, 1.0, -1.0);
    CGContextDrawImage(currentContext, CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height), image.CGImage);
//    UIImage * newImage = UIGraphicsGetImageFromCurrentImageContext();
    CGImageRef imageRef = CGBitmapContextCreateImage(currentContext);
    UIImage * newImage = [UIImage imageWithCGImage:imageRef];
    CGImageRelease(imageRef);
    UIGraphicsEndImageContext();
    
    UIImageView * imageView = [[UIImageView alloc] initWithFrame:CGRectMake(50, 70, 150, 250)];
    imageView.image = newImage;
    imageView.layer.borderWidth = 1.0;
    imageView.layer.borderColor = [UIColor blackColor].CGColor;
    [self addSubview:imageView];
}
效果图
4、Creating an Image Mask

位图图像遮罩决定了如何转换颜色,而不是使用哪些颜色。图像遮罩中的每个采样值指定了在特定位置中,当前填充颜色值被遮罩的数量。采样值指定了遮罩的不透明程度。值越大,表示越不透明,Quartz 在指定位置绘制的颜色越少。1是透明,0是不透明。
 图像遮罩的每个组件可能是 1/2/4/8 位。1bit 的遮罩,要么完全遮挡,要么完全显示。2/4/8bit 的遮罩代表灰度值,每个组件使用以下的公式值映射到 [0, 1] 之间。

公式

创建位图遮罩一般使用函数:

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

5、Masking Images(使用遮罩的图像)

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

  • 对图像使用图像遮罩(image mask)。我们也可以把图像(image)作为遮罩图,以获取同使用图像遮罩相反的效果。
  • 使用颜色来遮罩部分图像。其中包含被称为色键遮罩的技术。
  • 当Quartz在被剪切的上下文上绘制时,对图像或者图像遮罩剪切上下文以有效的屏蔽图像。

(1)Masking an Image with an Image Mask - 使用图像遮罩掩蔽图像
 通过使用函数CGImageRef __nullable CGImageCreateWithMask(CGImageRef cg_nullable image, CGImageRef cg_nullable mask)返回一个应用了图像遮罩的图像,该函数含有两个参数:

  • image:想要加遮罩的图像,该图像不能是一个遮罩图像( image mask)或者存在有与之关联的遮罩颜色。
  • mask:通过调用函数CGImageMaskCreate ()来创建一个图像遮罩。也可以通过提供一个图像来代替图像遮罩,但是这将获得同使用图像遮罩相反的效果。

一个图像遮罩的源样品充当一个相反的的透明度值;图像遮罩采样值 S = 1 时,则不会绘制对应的图像样本。S = 0 时,则允许完全绘制对应的图像样本。S = 0 ~ 1 时,则让对应的图像样本的 alpha 的值为(1-S)。

示例:

想要添加mask的image
由于没有合适的图片自己生成的一张maskImage

示例代码:

- (void)drawRect:(CGRect)rect {
    [super drawRect:rect];
    
    UIImage * image = [UIImage imageNamed:@"loli"];
    // 生成能凑合着用的maskImage
    UIImage * maskImage = [UIImage imageNamed:@"poem"];
    UIGraphicsBeginImageContextWithOptions(self.bounds.size, YES, 1.0);
    CGContextRef currentContext = UIGraphicsGetCurrentContext();
    CGContextTranslateCTM(currentContext, 0.0, self.bounds.size.height);
    CGContextScaleCTM(currentContext, 1.0, -1.0);
    CGContextDrawImage(currentContext, CGRectMake(0, self.bounds.size.height-240, 120, 240), maskImage.CGImage);
    UIImage * newImage = UIGraphicsGetImageFromCurrentImageContext();
    CGImageRef newImageRef = newImage.CGImage;
//    CGImageRef newImageRef = CGBitmapContextCreateImage(currentContext);
    UIGraphicsEndImageContext();
    CGImageRef imageMask = CGImageMaskCreate(CGImageGetWidth(newImageRef), CGImageGetHeight(newImageRef), CGImageGetBitsPerComponent(newImageRef), CGImageGetBitsPerPixel(newImageRef), CGImageGetBytesPerRow(newImageRef), CGImageGetDataProvider(newImageRef), NULL, NO);
    
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    // 对无alpha通道的图片添加alpha通道
    CGImageRef sourceImageRef = image.CGImage;
    CGImageRef imageWithAlphaRef = sourceImageRef;
    if (CGImageGetAlphaInfo(sourceImageRef) == kCGImageAlphaNone | CGImageGetAlphaInfo(sourceImageRef) == kCGImageAlphaNoneSkipLast | CGImageGetAlphaInfo(sourceImageRef) == kCGImageAlphaNoneSkipFirst) {
        CGContextRef bitmapContext = CGBitmapContextCreate(NULL, CGImageGetWidth(sourceImageRef), CGImageGetHeight(sourceImageRef), CGImageGetBitsPerComponent(sourceImageRef), CGImageGetBytesPerRow(sourceImageRef), colorSpace, kCGImageAlphaPremultipliedLast);
        if (bitmapContext != NULL) {
            CGContextDrawImage(bitmapContext, CGRectMake(0, 0, CGImageGetWidth(sourceImageRef), CGImageGetHeight(sourceImageRef)), sourceImageRef);
            imageWithAlphaRef = CGBitmapContextCreateImage(bitmapContext);
        }
        CGColorSpaceRelease(colorSpace);
        CGContextRelease(bitmapContext);
    }
    // 生成maskedImage
    CGImageRef maskedImage = CGImageCreateWithMask(imageWithAlphaRef, imageMask);
    UIImage * resultImage = [UIImage imageWithCGImage:maskedImage];
    CGImageRelease(maskedImage);
    
    // 显示
    UIImageView * imageView = [[UIImageView alloc] initWithFrame:self.bounds];
    imageView.image = resultImage;
    imageView.layer.borderWidth = 1.0;
    imageView.layer.borderColor = [UIColor blackColor].CGColor;
    [self addSubview:imageView];
}
效果图

因为没有合适的图片,所以就成了这么low的样子~~凑合着看吧🙏🙏🙏

(2)Masking an Image with an Image - 使用图像充当遮罩
 即CGImageCreateWithMask(CGImageRef cg_nullable image, CGImageRef cg_nullable mask)的参数中mask不是由函数CGImageMaskCreate()生成,而是CGImageCreate()或者其他image生成函数生成的。会取得与使用图像遮罩相反的效果。

(3)Masking an Image with Color - 使用颜色来遮罩图像
 通过使用函数CGImageCreateWithMaskingColors(CGImageRef cg_nullable image, const CGFloat * cg_nullable components)给图像添加颜色遮罩。这里要特别注意参数components!

  • 是一个含有2N个元素的字符数组,N是所在颜色空间的组件数量。
  • 每两个数规定了一个颜色组件的范围。如果image有整型元素组件则每个组件必须在范围[1 - (2^bitsPerComponent-1)]内,若是浮点型组件则必须是有效的元素组件值。

示例代码:

- (void)drawRect:(CGRect)rect {
    [super drawRect:rect];
    
    UIImage * image = [UIImage imageNamed:@"loli"];
    UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 1.0);
    CGContextRef currentContext = UIGraphicsGetCurrentContext();
    const CGFloat myMaskingColors[6] = {0, 55,  0, 55, 0, 55};
    CGImageRef maskedImage = CGImageCreateWithMaskingColors(image.CGImage, myMaskingColors);
    CGContextSetRGBFillColor (currentContext, 1.0, 0, 0, 1);
    CGContextFillRect(currentContext, self.bounds);
    CGContextTranslateCTM(currentContext, 0.0, self.bounds.size.height);
    CGContextScaleCTM(currentContext, 1.0, -1.0);
    CGContextDrawImage(currentContext, self.bounds, maskedImage);
    UIImage * resultImage = UIGraphicsGetImageFromCurrentImageContext();
    CGImageRelease(maskedImage);
    UIGraphicsEndImageContext();
    // 显示
    UIImageView * imageView = [[UIImageView alloc] initWithFrame:self.bounds];
    imageView.image = resultImage;
    imageView.layer.borderWidth = 1.0;
    imageView.layer.borderColor = [UIColor blackColor].CGColor;
    [self addSubview:imageView];
}
效果图

(4)Masking an Image by Clipping the Context - 通过剪切上下文进行遮罩的设置
 通过函数CGContextClipToMask(CGContextRef cg_nullable c, CGRect rect, CGImageRef cg_nullable mask)将遮罩映射到一个矩形与上下文相交的剪切区域。关于参数mask与上面遮罩使用情况大概相似,既可以是imageMask也可以是image,但是值得注意的是如果mask是image那么它必须是 设备灰度颜色空间(DeviceGray color space)!!!

示例代码:

- (void)drawRect:(CGRect)rect {
    [super drawRect:rect];
    UIImage * image = [UIImage imageNamed:@"loli"];
    
    UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 1.0);
    CGContextRef currentContext = UIGraphicsGetCurrentContext();
    [[UIColor whiteColor] setFill];
    UIBezierPath * path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(150, 200, 150, 220)cornerRadius:10];
    CGContextAddPath(currentContext, path.CGPath);
    CGContextDrawPath(currentContext, kCGPathFill);
    CGImageRef newImageRef = CGBitmapContextCreateImage(currentContext);
    UIGraphicsEndImageContext();
    CGImageRef imageMask = CGImageMaskCreate(CGImageGetWidth(newImageRef), CGImageGetHeight(newImageRef), CGImageGetBitsPerComponent(newImageRef), CGImageGetBitsPerPixel(newImageRef), CGImageGetBytesPerRow(newImageRef), CGImageGetDataProvider(newImageRef), NULL, NO);
//    CGImageRef maskImage = CGImageCreateCopy(newImageRef);
    CGImageRelease(newImageRef);
    
    CGContextRef currentContext_next = UIGraphicsGetCurrentContext();
    CGContextTranslateCTM(currentContext_next, 0.0, self.bounds.size.height);
    CGContextScaleCTM(currentContext_next, 1.0, -1.0);
    CGContextClipToMask(currentContext_next, self.bounds, imageMask);
    CGContextDrawImage(currentContext_next, self.bounds, image.CGImage);
}
遮罩图样式
使用imageMask作为mask的效果图
使用image作为mask的效果图
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容