iOS 绘图学习实战(一) -- 绘图基础

1. 使用Quartz的API 创建Context,并且绘图

完全使用Quartz创建Context, 并且使用Quartz绘图的API, 此时, context坐标系是left-low原点, 并且Quartz绘制方法认为图片的坐标系也是LLO.

/**
 CoreGraphic API -> LLO坐标系

 CGBitmapContextCreate -> 创建 bitmap Context
 CGBitmapContextCreateImage -> 获取 bitmap Image CGImageRef -> 转化成UIImage

 用 CoreGraphic CGContextDrawImage    绘图
 */
-(void)draw1{
    // 1. 创建颜色空间
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    float width = self.bounds.size.width;
    float height = self.bounds.size.height;
    int bitsPerComponent = 8;
    //RGBA(的bytes) * bitsPerComponent *width
    int bytesPerRow = 4 * 8 * bitsPerComponent * width;
    // 2. 创建bitmapContext, 使用Quartz创建的Context,因此坐标系是LLO, 坐标原点是左下角
    CGContextRef context = CGBitmapContextCreate(NULL, width, height, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast|kCGBitmapByteOrderDefault);

    // 3. 绘制背景
    CGContextFillRect(context, self.bounds);
    CGContextSetRGBFillColor(context, 1, 0, 0, 1);
    CGContextSetRGBStrokeColor(context, 0, 1, 0, 1);
    CGContextFillRect(context, CGRectMake(0, 0, 400, 400));
    CGContextStrokeRect(context, CGRectMake(0, 0, width, height));

    // 4. 获取UIKit图片, 并且使用Quartz绘制图片. 注意使用Quartz相关的api绘制图片时,只能传入UIImage的底层CGImage.
    //    并且此时由于绘制图片API `CGContextDrawImage` 是Quartz API, 因此绘制方法表示图片的坐标原点也是LLO
    UIImage *img=[UIImage imageNamed:@"1.jpg"];
    CGContextDrawImage(context, CGRectMake(0, 0, 100, 100), img.CGImage);

    // 5. 从context中获取CGImage, 并创建UIImage
    CGImageRef cgimg = CGBitmapContextCreateImage(context);
    UIImage *resultImg = [UIImage imageWithCGImage:cgimg];

    // 6. 清理CoreGraphic 资源
    CGContextRelease(context);
    CGColorSpaceRelease(colorSpace);
    CGImageRelease(cgimg);

    self.imageView.image = resultImg;
}

绘制结果

image.png

2. 使用UIKit的API 创建Context, 并且绘图

UIKit绘图时, 通过UIKit的API创建Context, 此时Context的坐标原点是ULO, UIKit绘图的API认为图像的原点也是ULO. 值得注意的是,与Quartz API不同的是UIKit会维护一个Context Stack, 用于记录当前UIKit中各种状态(填充颜色 setFill, 绘线颜色setStroke等等)是作用于哪个Context的.

/**
 UIKit API -> ULO 原点

 UIGraphicsBeginImageContextWithOptions -> 创建bitmap Context, 并push 入 stack
 UIGraphicsGetImageFromCurrentImageContext -> 获取 bitmap

 */
-(void)draw2 {
    // 1. 创建 context, 并且将context push到UIKit维护的Context Stack
    UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0);
    // 2. 创建Path, 绘制背景和填充颜色
    UIBezierPath *rect = [UIBezierPath bezierPathWithRect:self.bounds];
    [[UIColor redColor] setFill]; // 作用于Context Stack 栈顶stack
    [rect fill];
    [[UIColor greenColor] setStroke]; // 作用于Context Stack 栈顶
    [rect stroke];

    // 3. 使用 UIKit 的drawing method,因此需要坐标系是 ULO, 这里使用 UIKit方法创建, 因此绘制的文字都是正的. 坐标原点是ULO
    NSString *text= @"文字";
    UIFont *font=[UIFont systemFontOfSize:14];
    [text drawAtPoint:CGPointMake(100, 100) withAttributes:font.fontDescriptor.fontAttributes];

    // 4. 用UIKit 绘制图像, context是UIKit创建的, 因此是ULO坐标系, (0,0,100,100)在左上角,并且图片是正向
    UIImage *img=[UIImage imageNamed:@"1.jpg"];
    [img drawInRect:CGRectMake(0, 0, 100, 100)];

    // 5. 从当前context获取UIImage
    UIImage *resultImg = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    self.imageView.image = resultImg;
}
image.png

3-1 使用Quartz创建Context, 使用UIKit绘图

使用Quartz创建的Context坐标系是LLO, 而使用UIKit的绘图API(绘制文字, 绘制图像) 依赖的坐标系是ULO. 隐藏图像和文字会倒置.

/**
 CoreGraphic 创建 Context  -> LLO

 绘图使用 UIKit 绘制
 */
-(void)draw3_1 {
    // 1. 创建CGContext使用的颜色空间
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    float width = self.bounds.size.width;
    float height = self.bounds.size.height;
    int bitsPerComponent = 8;
    //RGBA*8*width
    int bytesPerRow = 4 * 8 * bitsPerComponent * width;
    // 2. 使用Quartz API创建CGContext, 坐标系是 LLO
    CGContextRef context = CGBitmapContextCreate(NULL, width, height, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast|kCGBitmapByteOrderDefault);

    // 3. UIKit API的绘图方法. 需要首先将当前context push到 当前UIKit维护的context stack中!!!!!(此时绘图方法才知道绘制到哪个context)
    UIGraphicsPushContext(context);

    // 4. 填充颜色, 配置部分状态
    CGContextFillRect(context, self.bounds);
    CGContextSetRGBFillColor(context, 1, 0, 0, 1);
    CGContextSetRGBStrokeColor(context, 0, 1, 0, 1);
    CGContextFillRect(context, CGRectMake(0, 0, 400, 400));
    CGContextStrokeRect(context, CGRectMake(0, 0, width, height));
    [[UIColor redColor] setFill];

    // 5. 使用UIKit方法绘制文字.
    NSString *text= @"文字";
    UIFont *font=[UIFont systemFontOfSize:14];
    [text drawAtPoint:CGPointMake(100, 100) withAttributes:font.fontDescriptor.fontAttributes];

    // 6. 使用UIKit方法绘制图像
    UIImage *img=[UIImage imageNamed:@"1.jpg"];
    [img drawInRect:CGRectMake(0, 0, 100, 100)];

    // 7. 从当前context中获取UIImage
    CGImageRef cgimg = CGBitmapContextCreateImage(context);
    UIImage *resultImg = [UIImage imageWithCGImage:cgimg];
    UIGraphicsPopContext();

    // 8. 清理资源
    CGContextRelease(context);
    CGColorSpaceRelease(colorSpace);
    CGImageRelease(cgimg);

    self.imageView.image = resultImg;
}
3-1.png

3-2 使用Quartz创建Context, 使用UIKit绘图(修正坐标系)

3-1中由于Quartz创建的Context坐标系是LLO, 而UIKit绘制坐标系要求ULO, 只要两者不匹配. 因此在绘制之前要保证. 当前Context的坐标系与绘制方法依赖的坐标系相同. 因此在调用UIKit时,需要将Context坐标系转换成ULO, 绘制完成以后. 最好将该坐标系恢复原始坐标系.


-(void)draw3_2 {
    // 1. 创建CGContext使用的颜色空间
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    float width = self.bounds.size.width;
    float height = self.bounds.size.height;
    int bitsPerComponent = 8;
    //RGBA*8*width
    int bytesPerRow = 4 * 8 * bitsPerComponent * width;
    // 2. 使用Quartz API创建CGContext, 坐标系是 LLO
    CGContextRef context = CGBitmapContextCreate(NULL, width, height, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast|kCGBitmapByteOrderDefault);

    // 3. UIKit API的绘图方法. 需要首先将当前context push到 当前UIKit维护的context stack中!!!!!(此时绘图方法才知道绘制到哪个context)
    UIGraphicsPushContext(context);

    // 4. 填充颜色, 配置部分状态
    CGContextFillRect(context, self.bounds);
    CGContextSetRGBFillColor(context, 1, 0, 0, 1);
    CGContextSetRGBStrokeColor(context, 0, 1, 0, 1);
    CGContextFillRect(context, CGRectMake(0, 0, 400, 400));
    CGContextStrokeRect(context, CGRectMake(0, 0, width, height));
    [[UIColor redColor] setFill];

    // 5. 在使用UIKit绘制方法进行绘制之前,先将当前context的坐标系调整成ULO, 具体步骤如下: 翻转画布 -> 先将画布向上移动 height, 然后将沿着y翻转坐标轴
    CGContextTranslateCTM(context, 0, height);
    CGContextScaleCTM(context, 1.0, -1.0);

    // 6.1 使用UIKit方法绘制文字, 由于context的坐标系统已经是ULO.此时绘制的内容是通过ULO为坐标系的
    NSString *text= @"文字1";
    UIFont *font=[UIFont systemFontOfSize:14];
    [text drawAtPoint:CGPointMake(100, 100) withAttributes:font.fontDescriptor.fontAttributes];

    // 6.2. 使用UIKit方法绘制图像 ,同上
    UIImage *img=[UIImage imageNamed:@"1.jpg"];
    [img drawInRect:CGRectMake(0, 0, 100, 100)];

    // 6.3 还原之前的context的坐标系, 转化成 LLO
    CGContextTranslateCTM(context, 0, height);
    CGContextScaleCTM(context, 1.0, -1.0);

    // 6.4 用UIKit 绘制文字和图片. 此时看的出来 坐标系是LLO, 图片和蚊子位置正确. 具体表现同 3-1
    text= @"文字2";
    [text drawAtPoint:CGPointMake(100, 100) withAttributes:font.fontDescriptor.fontAttributes];
    [img drawInRect:CGRectMake(0, 0, 100, 100)];

    // 7. 从当前context中获取UIImage
    CGImageRef cgimg = CGBitmapContextCreateImage(context);
    UIImage *resultImg = [UIImage imageWithCGImage:cgimg];
    UIGraphicsPopContext();

    // 8. 清理资源
    CGContextRelease(context);
    CGColorSpaceRelease(colorSpace);
    CGImageRelease(cgimg);

    self.imageView.image = resultImg;
}
3-2.png

4-1 使用UIKit创建Context, 使用Quartz 绘制API

-(void)draw4_1{

    // 1. UIKit方法会创建Context, 并且将该Context push到UIKit维护的Context stack, 并且UIKit会自动将Context的坐标调整为ULO,
    /*
     UIGraphicsBeginImageContextWithOptions() 相当于 Quartz 以下API:
        1. CGBitmapContextCreate -> 创建 CGContext
        2. UIGraphicsPushContext(context) -> 将context push进入UIKit的 context stack
        3. CGContextTranslateCTM(context, 0, height); CGContextScaleCTM(context, 1.0, -1.0);  -> 调整context的坐标系为ULO
     */
    UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0);

    // 2. UIKit 的相关方法填充背景. 绘制边界
    UIBezierPath *rect = [UIBezierPath bezierPathWithRect:self.bounds];
    [[UIColor redColor] setFill];
    [rect fill];
    [[UIColor greenColor] setStroke];
    [rect stroke];

    // 3. UIKit通过`UIGraphicsGetCurrentContext`获取 CGContext 对象(Quartz API使用)
    CGContextRef context = UIGraphicsGetCurrentContext();

    // 4. Quartz 的绘制方法, 要求Context坐标系是LLO, 此时和当前context不匹配. 绘制是倒置的图像
    UIImage *img = [UIImage imageNamed:@"1.jpg"];
    CGContextDrawImage(context, CGRectMake(0, 0, 100, 100), img.CGImage);

    UIImage *resultImg = UIGraphicsGetImageFromCurrentImageContext();

  // 5 清理CGContext
    UIGraphicsEndImageContext();

    self.imageView.image = resultImg;
}
4-1.png

4-2 使用UIKit创建Context, 使用Quartz 绘制API(坐标系校正)

-(void)draw4_2{

    // 1. UIKit方法会创建Context, 并且将该Context push到UIKit维护的Context stack, 并且UIKit会自动将Context的坐标调整为ULO,
    /*
     UIGraphicsBeginImageContextWithOptions() 相当于 Quartz 以下API:
     1. CGBitmapContextCreate -> 创建 CGContext
     2. UIGraphicsPushContext(context) -> 将context push进入UIKit的 context stack
     3. CGContextTranslateCTM(context, 0, height); CGContextScaleCTM(context, 1.0, -1.0);  -> 调整context的坐标系为ULO
     */
    UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0);

    // 2. UIKit 的相关方法填充背景. 绘制边界
    UIBezierPath *rect = [UIBezierPath bezierPathWithRect:self.bounds];
    [[UIColor redColor] setFill];
    [rect fill];
    [[UIColor greenColor] setStroke];
    [rect stroke];

    // 3. UIKit通过`UIGraphicsGetCurrentContext`获取 CGContext 对象(Quartz API使用)
    CGContextRef context = UIGraphicsGetCurrentContext();


    // 4. 在调用Quartz方法绘制之前,需要将 坐标系满足 LLO
    CGContextTranslateCTM(context, 0, self.bounds.size.height);
    CGContextScaleCTM(context, 1.0, -1.0);

    // 5.1 具体的Quartz 绘制方法, 此时绘制的图像位于左下角
    // CGContextDrawXXXX 方法 -> 会以 Context 的LLO为坐标系
    UIImage *img=[UIImage imageNamed:@"1.jpg"];
    CGContextDrawImage(context, CGRectMake(0, 0, 100, 100), img.CGImage);


    // 5.2 将context坐标重新转化成ULO, 然后继续绘制. 绘制的图像位于左上角
    CGContextTranslateCTM(context, 0, self.bounds.size.height);
    CGContextScaleCTM(context, 1.0, -1.0);
    [img drawInRect:CGRectMake(0, 0, 100, 100)];

    UIImage *resultImg = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    self.imageView.image = resultImg;
}
4-2.png

5. drawRect: 或者 drawLayer:inContext中绘图

这里使用自定义view的drawRect举例,

/**
 drawRect 会帮我们做如下事情(类似于UIGraphicsBeginImageContextWithOptions,但产生的context并非 bitmapContext):
    1. 创建一个context(注意这个context 并非 BitmapContext, 也就是说绘制的目的不是得到绘制图片, 而是直接渲染在view底层的layer 的content上)
    2. 会隐士调用UIGraphicsPushContext(context), 将这个创建的 context push 进入UIKit context stack
    3. 自动帮我们调整context坐标系是ULO

 */
- (void)drawRect:(CGRect)rect {
    // 1. 直接通过UIKit API 获取当前的绘制所在的context
    CGContextRef context = UIGraphicsGetCurrentContext();

    // 2. 设置背景等等
    CGContextSetRGBFillColor(context, 1, 0, 0, 1);
    CGContextFillRect(context, rect);

    // 3. 使用UIKit API 进行绘制
    UIImage *img = [UIImage imageNamed:@"1.jpg"];
    [img drawInRect:CGRectMake(0, 0, 100, 100)];
    NSString *text = @"文字";
    UIFont *font = [UIFont systemFontOfSize:14];
    [text drawAtPoint:CGPointMake(100, 100) withAttributes:font.fontDescriptor.fontAttributes];

    // ps: 无需清理
}
image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容