一、矩阵操作 - 仿射变换
- 缩放 void CGContextScaleCTM(CGContextRef c, CGFloat sx, CGFloat sy)
- 旋转 void CGContextRotateCTM(CGContextRef c, CGFloat angle)
- 平移 void CGContextTranslateCTM(CGContextRef c, CGFloat tx, CGFloat ty)
#import "HMView.h"@implementation HMView
- (void)drawRect:(CGRect)rect
{// 1.获取上下文CGContextRef ctx = UIGraphicsGetCurrentContext();// 矩阵操作// Current graphics state's Transformation Matrix// 旋转// CGContextRotateCTM(ctx, M_PI_4);// 缩放// CGContextScaleCTM(ctx, 1, 0.5);// 平移// CGContextTranslateCTM(ctx, 150, 150);// 2.拼接路径 同时 把路径添加到上下文当中// CGContextAddArc(ctx, 150, 150, 100, 0, 2 * M_PI, 1);// CGContextMoveToPoint(ctx, 0, 0);// CGContextAddLineToPoint(ctx, 300, 300);CGMutablePathRef path = CGPathCreateMutable();CGPathAddArc(path, NULL, 150, 150, 100, 0, 2 * M_PI, 1);CGPathMoveToPoint(path, NULL, 0, 0);CGPathAddLineToPoint(path, NULL, 300, 300);// 添加路径到上下文当中CGContextAddPath(ctx, path);// 设置线宽CGContextSetLineWidth(ctx, 10);// 3.渲染CGContextStrokePath(ctx);
}
@end
二、图形上下文栈的操作
0. 图形上下文栈是什么?
每一个“图形上下文”对象都包含一个“栈”结构,这个栈结构用来存储当前图形上下文的状态信息。(每个图形上下文对象中都包含:1>“图形状态”; 2> 路径信息; 3> 输出目标)
1. 为什么要学习图形上下文栈?
1. 第一次画图后修改了颜色, 后面的图形要再恢复到原来的颜色
2. 第一次画图的时候旋转了上下文, 后面的图形不要旋转
3. 第一次画图的时候修改了线宽,线头样式等,后面画图的时候不要这些样式,要默认的样子
总结:前面绘图的时候修改了上下文,后面绘图的时候要再次使用被修改前的上下文对象
1、先绘制图形,设置颜色,线宽,旋转。
2、然后再绘制其他的图形。(其他图形默认也会使用相同的设置(颜色、线宽、旋转等))
解决:
1. 在第一次绘制图形前,保存上下文。
2. 在第二次绘制图形前,恢复上下文。
图形上下文栈的操作
将当前【图形上下文】中的“绘图状态”信息保存到“栈”中
void CGContextSaveGState(CGContextRef c)
将栈顶的“绘图状态”出栈, 替换掉当前的“图形上下文”中的“绘图状态”
void CGContextRestoreGState(CGContextRef c)
三、Quartz2D的内存管理
- 使用含有“Create”或“Copy”的函数创建的对象,使用完后必须释放,否则将导致内存泄露
- 使用不含有“Create”或“Copy”的函数获取的对象,则不需要释放
- 如果retain了一个对象,不再使用时,需要将其release掉
- 可以使用Quartz 2D的函数来指定retain和release一个对象。例如,如果创建了一个CGColorSpace对象,则使用函数CGColorSpaceRetain和CGColorSpaceRelease来retain和release对象。
- 也可以使用Core Foundation的CFRetain和CFRelease。注意不能传递NULL值给这些函数
- 演示通过CGMutablePathRef实现绘图。通过 Product -> Analyze 来进行静态分析。
使用Path 对象时的内存管理问题:
1> 凡是遇到 retain 、 copy 、 create 出的对象, 都需要进行 release
2> 但是CGPathCreateMutable()不是 OC 方法, 所以不是调用 某个对象的 release方法
3> CGXxxxxCreate 对应的就有 CGXxxxxRelease。
4> 通过 CFRelease(任何类型);可以释放任何类型。
四、绘制“文字”
通过 UIKit 框架来绘制
- [str drawAtPoint:] 从某一个点开始绘制
- [str drawInRect:] 绘制到指定的区域
删除背景颜色 可以让阴影的地方显示 文字
将underlineStyleAttributeName 删掉 阴影直接消失 没有下划线参数就没有阴影 参数传为0 就没有下划线
五、绘制”图片”
UIImage *imgIcon = [UIImage imageNamed:@"002"];[imgIcon drawAtPoint:CGPointMake(20, 20)];[imgIcon drawInRect:rect];[imgIcon drawAsPatternInRect:rect];
设置 UIView 的背景色为某个图片的屏幕效果。通过[UIColor colorWithXxxxxx:图片]来实现。
六、自定义一个 View 模拟图片框
ViewController.M
#import "ViewController.h"
#import "HMImageView.h"@interface ViewController ()@property (nonatomic, weak) HMImageView* imageView;
//@property (nonatomic, weak) UIImageView* imageView;
@end@implementation ViewController- (void)viewDidLoad
{[super viewDidLoad];HMImageView* imageView = [[HMImageView alloc] initWithImage:[UIImage imageNamed:@"me"]];self.imageView = imageView;[self.view addSubview:imageView];// -------// HMImageView* imageView = [[HMImageView alloc] init];// imageView.frame = CGRectMake(0, 0, 200, 200);// imageView.image = [UIImage imageNamed:@"me"];// self.imageView = imageView;// [self.view addSubview:imageView];// *******// UIImageView* imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"me"]];// self.imageView = imageView;// [self.view addSubview:imageView];// // -------// UIImageView* imageView = [[UIImageView alloc] init];// imageView.frame = CGRectMake(0, 0, 200, 200);// imageView.image = [UIImage imageNamed:@"me"];// self.imageView = imageView;// [self.view addSubview:imageView];
}- (void)touchesBegan:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event
{self.imageView.image = [UIImage imageNamed:@"Press"];
}@end
HMImageView.M
#import "HMImageView.h"@implementation HMImageView
- (instancetype)initWithImage:(UIImage*)image{self = [super initWithFrame:CGRectMake(0, 0, image.size.width, image.size.height)];if (self) {self.image = image;}return self;
}
- (void)setImage:(UIImage*)image{_image = image;// 重绘[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect{// 把传过来的图片绘制到屏幕上[self.image drawInRect:rect];
}
@end
设置 UIView 的背景为一张图片
1、通过[UIColor colorWithPatternImage:]实现
2、通过绘制一张图片到 UIView 上实现
裁剪 只是裁剪上下文的显示区域 上下文本身依然存在且大小不变
七、图片裁剪
核心代码
void CGContextClip(CGContextRef c)
将当前上下所绘制的路径裁剪出来(超出这个裁剪区域的都不能显示)
图片裁剪思路:
1》 在上下文中绘制一个要裁减的图形
2》调用void CGContextClip(CGContextRef c)进行裁剪
3》在裁剪好的上下文中再把图片绘制上去。(注意绘制图片的时候,必须绘制到已经裁剪出的图形位置,否则不显示)
在 UIView 上显示一个裁剪后的图片
1、获取 UIView 的图形上下文对象
2、在图形上下文对象上绘制一个圆形
3、执行裁剪操作(裁剪的意思是告诉系统,将来只有在被裁减出的区域内绘制的图形才会显示)
4、把图片绘制到上下文上(直接调用 UIImage 对象的绘图方法即可)
直接裁剪图片并保存
- 1、加载要裁减的图片
- 2、根据要裁剪的图片大小开启一个Bitmap 的图形上下文
- 3、在这个图形上下文上绘制一个圆形(这个圆的圆心应为这个图形上下文的中心点,半径应为这个图形上下文的最短的边的一半)
- 4、执行裁剪操作
- 5、把图片绘制到当前的“图形上下文”中(因为这个图形上下文的大小是按照要裁减的图片的大小来创建的,所以绘图的时候直接从(0,0)开始绘制即可)
- 6、从图形上下文中获取图片对象
- 7、将图片对象转换为 NSData 类型UIImagePNGRepresentation(img)
- 8、保存图片对象到目标位置(沙盒或者相册)
图片类型的图形上下文
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView* imageView;
@end@implementation ViewController
- (void)viewDidLoad{[super viewDidLoad];// Do any additional setup after loading the view, typically from a nib.
}
- (void)touchesBegan:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event{// 开启图片类型的图形上下文UIGraphicsBeginImageContext(CGSizeMake(300, 300));// 获取当前的上下文(图片类型)CGContextRef ctx = UIGraphicsGetCurrentContext();// 拼接路径 同时 把路径添加上上下文当中CGContextMoveToPoint(ctx, 50, 50);CGContextAddLineToPoint(ctx, 100, 100);// 渲染// CGContextStrokePath(ctx);// 通过图片类型的图形上下文 获取图片对象UIImage* image = UIGraphicsGetImageFromCurrentImageContext();// 关闭图片类型的图形上下文UIGraphicsEndImageContext();// 把获取到的图片 放到 图片框上self.imageView.image = image;
}
保存到沙盒
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView* imageView;
@end
@implementation ViewController
- (void)viewDidLoad{[super viewDidLoad];
}
- (void)touchesBegan:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event{// 开启图片类型的图形上下文UIGraphicsBeginImageContext(CGSizeMake(300, 300));// 获取当前的上下文(图片类型)CGContextRef ctx = UIGraphicsGetCurrentContext();// 拼接路径 同时 把路径添加上上下文当中CGContextMoveToPoint(ctx, 50, 50);CGContextAddLineToPoint(ctx, 100, 100);// 渲染CGContextStrokePath(ctx);// 通过图片类型的图形上下文 获取图片对象UIImage* image = UIGraphicsGetImageFromCurrentImageContext()// 关闭图片类型的图形上下文UIGraphicsEndImageContext();// 把获取到的图片 放到 图片框上self.imageView.image = image;// 获取 doc 路径NSString* docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];// 获取文件路径NSString* filePath = [docPath stringByAppendingPathComponent:@"xx.png"];// 1.把 image 对象转化成 nsdata// NSData* data = UIImagePNGRepresentation(image);NSData* data = UIImageJPEGRepresentation(image, 0);// 2.通过 data 的 write to file 写入到沙盒中[data writeToFile:filePath atomically:YES];
}
@end
开启图片类型的上下文 with option
UIGraphicsBeginImageContextWithOptions(CGSizeMake(300, 300), NO, [UIScreen mainscreen].scale);< == > UIGraphicsBeginImageContextWithOptions(CGSizeMake(300, 300), NO, 0);
第三个参数最重要 缩放因子 传0表示不缩放 即在不同手机的不同屏幕不缩放
获取裁剪过后的图片
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView* imageView;
@end
@implementation ViewController
- (void)viewDidLoad{[super viewDidLoad];
}
- (void)touchesBegan:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event{// 3.获取图片UIImage* image = [UIImage imageNamed:@"me"];// 1.开启图片类型的图形上下文UIGraphicsBeginImageContextWithOptions(image.size, NO, 0);// 5.获取上下文CGContextRef ctx = UIGraphicsGetCurrentContext();// 4.画一个裁剪的图片CGContextAddArc(ctx, image.size.width * 0.5, image.size.height * 0.5, image.size.width * 0.5, 0, 2 * M_PI, 1);// 6.裁剪CGContextClip(ctx);// 7.把图片画上去[image drawAtPoint:CGPointZero];// 8.取出来UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();// 2.关闭图片类型的图形上下文UIGraphicsEndImageContext();// 测试self.imageView.image = newImage;
}
@end
获取裁剪后的图片
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView* imageView;
@end
@implementation ViewController
- (void)viewDidLoad{[super viewDidLoad];
}
- (void)touchesBegan:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event{// 3.获取图片UIImage* image = [UIImage imageNamed:@"me"];// 1.开启图片类型的图形上下文UIGraphicsBeginImageContextWithOptions(image.size, NO, 0);// 5.获取上下文CGContextRef ctx = UIGraphicsGetCurrentContext();// 4.画一个裁剪的图片CGContextAddArc(ctx, image.size.width * 0.5, image.size.height * 0.5, image.size.width * 0.5, 0, 2 * M_PI, 1);// 6.裁剪CGContextClip(ctx);// 7.把图片画上去[image drawAtPoint:CGPointZero];// 8.取出来UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();// 2.关闭图片类型的图形上下文UIGraphicsEndImageContext();// 测试self.imageView.image = newImage;// 保存到相册UIImageWriteToSavedPhotosAlbum(newImage, self, @selector(image:didFinishSavingWithError:contextInfo:), @"123123123");
}
// 保存到相册的回调
- (void)image:(UIImage*)image didFinishSavingWithError:(NSError*)error contextInfo:(void*)contextInfo{NSLog(@"保存完成 - %@", contextInfo);
}
裁剪一个带圆环的图片
- // 加载要裁剪的图片
- // 创建一个比原始图片略大的基于 Bitmap 的“图形上下文”
- // 获取当前上下文
- // ------------------------------------ 开始绘制一个圆环
-
/ /确定圆心
-
// 确定半径
-
// 开始绘制圆环路径
- // 渲染 -------------------------------- 开始裁剪一个圆
- // ------------------------------------ 开始绘制图片
- // 从绘图上下文中获取图片
- // 结束图形上下文
- // 保存到相册
- // 保存到沙盒
- // 保存图片
- (void)viewDidLoad{[super viewDidLoad];// 3.获取图片UIImage* image = [UIImage imageNamed:@"me"];// 4.marginCGFloat margin = 20;// 5.计算图片类型的图形上下文的大小CGSize ctxSize = CGSizeMake(image.size.width + 2 * margin, image.size.height + 2 * margin);// 1.开启图片类型的图形上下文UIGraphicsBeginImageContextWithOptions(ctxSize, NO, 0);// 6.获取上下文CGContextRef ctx = UIGraphicsGetCurrentContext();// 8.计算圆心CGPoint arcCenter = CGPointMake(ctxSize.width * 0.5, ctxSize.height * 0.5);// 9.计算半径CGFloat radius = (image.size.width + margin) * 0.5;// 7.画圆环CGContextAddArc(ctx, arcCenter.x, arcCenter.y, radius, 0, 2 * M_PI, 1);// 10.设置宽度CGContextSetLineWidth(ctx, margin);// 11.渲染圆环CGContextStrokePath(ctx);// 12.画头像显示的区域CGContextAddArc(ctx, arcCenter.x, arcCenter.y, image.size.width * 0.5, 0, 2 * M_PI, 1);// 13.裁剪显示区域CGContextClip(ctx);// 14.画图片[image drawAtPoint:CGPointMake(margin, margin)];// 获取图片image = UIGraphicsGetImageFromCurrentImageContext();// 2.关闭图片类型的图形上下文UIGraphicsEndImageContext();// 保存到相册UIImageWriteToSavedPhotosAlbum(image, NULL, NULL, NULL);
}
图片水印
水印:在图片上加的防止他人盗图的半透明logo、文字、图标
实现方式:利用Quartz2D,将水印(文字、LOGO)画到图片的右下角
核心代码
开启一个基于位图的图形上下文
void UIGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale)
从上下文中取得图片
(UIImage) UIImage* UIGraphicsGetImageFromCurrentImageContext();
结束基于位图的图形上下文
void UIGraphicsEndImageContext();
图片加水印基本思路
1、添加文字水印
1> 创建位图上下文
2> 把图片画上去
3> 把文字画上去
4> 从上下文中取出图片
2、添加图片水印
1> 创建位图上下文
2> 把图片画上去
3> 加载 logo 图片(水印图片),把水印图片也画上去
4> 从上下文中取出图片
- (void)touchesBegan:(NSSet<UITouch*>*)touches withEvent:(UIEvent*)event{// 3.获取图片UIImage* image = [UIImage imageNamed:@"scene"];// 1.开启图片类型的图形上下文UIGraphicsBeginImageContextWithOptions(image.size, NO, 0);// 6.画大图[image drawAtPoint:CGPointZero];// 4.文字NSString* str = @"黑马13期";// 5.画文字水印[str drawAtPoint:CGPointMake(20, 20) withAttributes:@{ NSFontAttributeName : [UIFont systemFontOfSize:20] }];// 7.图片UIImage* logo = [UIImage imageNamed:@"logo"];// 8.画图片水印[logo drawAtPoint:CGPointMake(image.size.width * 0.6, image.size.height * 0.7)];// 取图片image = UIGraphicsGetImageFromCurrentImageContext();// 保存到相册UIImageWriteToSavedPhotosAlbum(image, NULL, NULL, NULL);// 2.关闭图片类型的图形上下文UIGraphicsEndImageContext();NSLog(@"test");
}
屏幕截图
核心代码
- (void)renderInContext:(CGContextRef)ctx;
调用某个view的layer的renderInContext:方法即可
截图基本思路:
1、获取控件的 layer 对象
2、调用 layer 对象的 renderInContext:方法渲染到上下文中
**注意:UISegmentedControl 渲染时有问题
- (void)viewDidLoad{// 开启图片类型的图形上下文UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, NO, 0);// 获取当前的上下文CGContextRef ctx = UIGraphicsGetCurrentContext();// 截图 把view 的内容 放到上下文中 然后 渲染[self.view.layer renderInContext:ctx];// 取图片UIImage* image = UIGraphicsGetImageFromCurrentImageContext();// 关闭上下文UIGraphicsEndImageContext();// 保存到相册UIImageWriteToSavedPhotosAlbum(image, NULL, NULL, NULL);
}
// --- 旋转缩放平移// 对图形上下文进行旋转平移缩放的操作// 缩放
// 第一个参数:需要缩放的上下文
// 第二个参数:x轴缩放的比例
// 第三个参数:y轴缩放的比例
CGContextScaleCTM(ctx, 1, 0.5);// 平移
// 第一个参数:需要平移的上下文
// 第二个参数:x轴的偏移量
// 第三个参数:y轴的偏移量
CGContextTranslateCTM(ctx, 150, 150);// 旋转
// 第一个参数:需要旋转的上下文
// 第二个参数:顺时针旋转的角度
CGContextRotateCTM(ctx, M_PI_4);// --- 图形上下文栈// 图形上下文栈 只是保存 状态信息(样式)// 保存状态信息
CGContextSaveGState(ctx);// 恢复状态信息
CGContextRestoreGState(ctx);// --- quartz2d 内存管理
// 如果包含 create copy 关键字的时候 记得释放
// 释放 1
CGPathRelease(path);
// 释放 2
CFRelease(path);// --- 绘制文字
// 绘制 - 从(0,0)点开始画
[str drawAtPoint:CGPointZero withAttributes:dict];// 绘制 - 绘制到指定的区域
[str drawInRect:rect withAttributes:nil];// AttributeName - key 在 UIKit下的 NSAttributeName.h 里面
// 个人建议 记其中一个// shadow
NSShadow* s = [[NSShadow alloc] init];
s.shadowOffset = CGSizeMake(100, 100); // 偏移量
s.shadowBlurRadius = 0; // 越小越不模糊 高斯模糊
s.shadowColor = [UIColor yellowColor]; // 阴影的颜色// --- 绘制图片
// 绘制 - 从某个点开始绘制
[image drawAtPoint:CGPointZero];
// 绘制 - 绘制到某一个区域
[image drawInRect:rect];
// 绘制 - 平铺
[image drawAsPatternInRect:rect]; // 可以考虑用来设置某个view的背景图片// --- 裁剪上下文显示的区域
// 前提:画一个图形
// 裁剪图片
CGContextClip(ctx);// 告诉系统 已这个图形来裁剪图片 渲染 然后显示出来
// 所谓裁剪并不是裁剪掉上下文 只是单纯的 裁剪出来希望显示的区域而已!!!// --- bitmap上下文// 创建上下文(图片)
UIGraphicsBeginImageContext(CGSizeMake(300, 300));
// 创建上下文 (大小,不透明,缩放:0)
UIGraphicsBeginImageContextWithOptions(CGSizeMake(200, 200), NO, 0);// 关闭上下文
UIGraphicsEndImageContext();// 通过图片的图形上下文获取图片
UIImage* image = UIGraphicsGetImageFromCurrentImageContext();// 保存到沙盒当中
NSString* path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
path = [path stringByAppendingPathComponent:@"xx.png"];
// 把image转化成 NSData类型
NSData* data = UIImagePNGRepresentation(image);
NSLog(@"%ld", data.length);
// 然后在通过data对象 write to file 来写入到沙盒中
[data writeToFile:path atomically:YES];// --- 裁剪圆形图片
// 保存到相册
// 第一个:图片
// 第二个 第三个 监听 注意:@selector 不能随便写 使用"注释"当中的方法
// 第四个: 可以把它当做一个tag来使用 参数在监听的方法当中会传入
UIImageWriteToSavedPhotosAlbum(newImage, self, @selector(image:didFinishSavingWithError:contextInfo:), @"hello1111");// --- 屏幕截图
// 对控制器的view的layer属性 进行操作
[self.view.layer renderInContext:ctx];