Quartz 2D高级绘图技巧

本文目录

1.前言
2.Quartz 2D 概述
3.基于图形上下文绘图
4.基于路径的绘图
5.Transform(Translate、Rotate、Scale)
6.绘制阴影
7.透明度图层
8.渐变色
9.重复绘图
10.核心图层绘图
11.消除锯齿渲染

前言

Quartz 2DCore GraphicsQuartzCoreCore Animation这几个东西经常分不清,看到都是一脸懵逼!!!

  • Quartz 2D 就是 Core Graphics, 框架中包含的类都是以CG开头, 是一个先进的二维绘图引擎,所以被叫做Quartz 2D
  • QuartzCoreCore Animation也是同一个东西、框架中的类以CA开头主要是对控件的layer层进行操作。比如layer的创建与layer的动画

Quartz 2D

屏幕快照 2017-11-29 18.01.22.png

QuartzCore

屏幕快照 2017-11-29 18.03.05.png

至此在使用类名以CG开头Quartz 2D之前必须明白Quartz 2D主要是做绘图工作
而类名以CA开头QuartzCore主要是对layer层的操作来实现一些动画等

一、Quartz 2D概述

Quartz 2D需要了解的几个知识点

1.CGContextRef
2.Opaque Data Types
3.Memory Management

1.CGContextRef 图形上下文:

CGContextRef是一个很重要的东西,可以说Quartz 2D的绘图功能都是要基于CGContextRef来完成。CGContextRef它是Quartz 2D使用绘制图像的输出装置,所述信息诸如PDF文件,位图,或者在显示器上的窗口, Quartz 2D的所有对象都被绘制或包含在图形上下文中,您可以将图形上下文视为一个绘图目标,没有这个绘图目标Quartz 2D就不能完成绘图工作。

2.Opaque Data Types 其他常用的绘图对象

Quartz 2DAPI中除CGContextRef外提供的用于绘图的对象包含

  • CGPathRef用于矢量图形来创建您填充或描边的路径
  • CGImageRef用于根据您提供的示例数据来表示位图图像和位图图像蒙版
  • CGLayerRef 用于表示可用于重复绘图(例如用于背景或图案)和用于离屏绘制的绘图层。
  • CGPatternRef用于重复绘制
  • CGShadingRef、CGGradientRef用于绘制渐变
  • CGFunctionRef用于定义采用任意数量的浮点参数的回调函数,在为阴影创建渐变时使用此数据类型
  • CGFontRef用来绘制文字
  • CGPDFDictionaryRef ...它提供访问PDF的元数据,PDF文档创建,查看和转换
3.Memory Management内存管理

Quartz使用Core Foundation内存管理模型,其中的对象是引用计数的。创建时,Core Foundation对象以引用计数1开始。可以通过调用保留该对象的函数来增加引用计数,并通过调用释放该对象的函数来减少引用计数。当引用计数递减到0时,该对象被释放。
如果您CreatCopy对象,则您拥有该对象,因此您必须将其Release。也就是说,一般情况下,如果您从名称中使用CreatCopy函数获得对象,则必须在完成时Release该对象。否则,会导致内存泄漏。

二、基于图形上下文基本图形绘制

根据需求不同选择不同的图形上下文,以下是五种输出:
1.用于界面显示(Window),这是我们80%的需求
2.离屏绘图(Layer),CGLayerRefCGBitmapContext好太多
3.位图(Bitmap)
4.PDF
5.Printer

image.png

图形上下文绘图的API都在CGContext

框架中的上下文类.png

来看看我们常用的API

2.1.设置绘画的一些属性(线条属性、绘画透明度、叠加模式等)
 /* 设置绘制直线、边框时的线条宽度*/
 void CGContextSetLineWidth(CGContextRef__nullable c, CGFloat width)
 
 /* 设置线段端点的绘制形状,支持三个枚举项*/
 void CGContextSetLineCap(CGContextRef__nullable c, CGLineCap cap)
 typedef CF_ENUM(int32_t, CGLineCap) {
     kCGLineCapButt, //该属性值指定不绘制端点,线条结尾处直接结束。这是默认值。
     kCGLineCapRound,//该属性值指定绘制圆形端点,线条结尾处绘制一个直径为线条宽度的半圆。
     kCGLineCapSquare//该属性值指定绘制方形端点。线条结尾处绘制半个边长为线条宽度的正方形。需要说明的是,这种形状的端点与“butt”形状的端点十分相似,只是采用这种形式的端点的线条略长一点而已
 };

 /* 设置线条连接点的样式,支持三个枚举项 */
 void CGContextSetLineJoin(CGContextRef__nullable c, CGLineJoin join)
 typedef CF_ENUM(int32_t, CGLineJoin) {
     kCGLineJoinMiter, // 这是默认的属性值。该方格的连接点形状
     kCGLineJoinRound, // 稍微圆角, 该方格的连接点形状
     kCGLineJoinBevel  // 斜角,该方格的连接点形状
 };
 
 
 /*当把连接点风格设为meter风格时,该方法用于控制锐角箭头的长度*/
 void CGContextSetMiterLimit(CGContextRef__nullable c, CGFloat limit)
 
 /*  Linedash pattern(虚线模式)允许我们沿着描边绘制虚线。我们通过在CGContextSetLineDash结构体中指定虚线数组和虚线相位来控制虚线的大小及位置。
  其中lengths属性指定了虚线段的长度,该值是在绘制片断与未绘制片断之间交替。phase属性指定虚线模式的起始点。*/
 void CGContextSetLineDash(CGContextRef__nullable c, CGFloat phase,const CGFloat *__nullable lengths, size_t count)
 
 /* 设置弯曲的路径中的图形上下文的准确性。*/
 void CGContextSetFlatness(CGContextRef__nullable c, CGFloat flatness)
 
 /* 设置全局透明度 */
 void CGContextSetAlpha(CGContextRef__nullable c, CGFloat alpha)
 
 /*设置CGContextRef的叠加模式。Quartz 2D支持多种叠加模 */
void CGContextSetBlendMode(CGContextRef __nullable c, CGBlendMode mode)
2.2颜色填充
 /** Primitive color functions. **/
    
    /*
     使用指定颜色来设置该CGContextRef的填充颜色*/
    void CGContextSetFillColorWithColor(CGContextRef__nullable c,
                                        CGColorRef __nullable color)
    
    /*
     使用指定颜色来设置该CGContextRef的线条颜色*/
    void CGContextSetStrokeColorWithColor(CGContextRef__nullable c,
                                          CGColorRef __nullable color)
    
    /** Color space functions. **/
    
    /* 颜色空间填充 */
    void CGContextSetFillColorSpace(CGContextRef__nullable c, CGColorSpaceRef__nullable space)
    
    /* 设置线框颜色空间 */
    
    void CGContextSetStrokeColorSpace(CGContextRef__nullable c,
                                      CGColorSpaceRef __nullable space)
    
    /** Color functions. **/
    
    /* 设置填充颜色空间 CGFloat redColor[4] = {1.0,0,0,1.0};*/
    void CGContextSetFillColor(CGContextRef__nullable c,const CGFloat *__nullable components(redColor))
    
    /* 设置画笔颜色 CGFloat redColor[4] = {1.0,0,0,1.0};*/
    void CGContextSetStrokeColor(CGContextRef__nullable c,const CGFloat *__nullable components(redColor))
    
    /** Pattern functions. **/
    
    /*
     设置该CGContextRef使用位图填充*/
    void CGContextSetFillPattern(CGContextRef__nullable c, CGPatternRef__nullable pattern,const CGFloat * __nullable components)
    
    /*
     设置该CGContextRef使用位图绘制线条、边框*/
    void CGContextSetStrokePattern(CGContextRef__nullable c, CGPatternRef__nullable pattern,const CGFloat * __nullable components)
    
    /*
     设置该CGContextRef采用位图填充的相位*/
    void CGContextSetPatternPhase(CGContextRef__nullable c, CGSize phase)
    
    /** Color convenience functions. **/
    
    /*
     使用灰色来设置该CGContextRef的填充颜色*/
    void CGContextSetGrayFillColor(CGContextRef__nullable c,
                                   CGFloat gray, CGFloat alpha)
    
    /*
     使用灰色来设置该CGContextRef的线条颜色*/
    void CGContextSetGrayStrokeColor(CGContextRef__nullable c,
                                     CGFloat gray, CGFloat alpha)
    
    /*
     使用RGB颜色模式来设置该CGContextRef的填充颜色*/
    void CGContextSetRGBFillColor(CGContextRef__nullable c, CGFloat red,
                                  CGFloat green, CGFloat blue, CGFloat alpha)
    
    /* 设置画笔颜色 */
    void CGContextSetRGBStrokeColor(CGContextRef__nullable c,
                                    CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha)
    
    /*
     使用CMYK颜色模式来设置该CGContextRef的填充颜色*/
    void CGContextSetCMYKFillColor(CGContextRef__nullable c,
                                   CGFloat cyan, CGFloat magenta, CGFloat yellow, CGFloat black, CGFloat alpha)
    
    /*
     使用CMYK颜色模式来设置该CGContextRef的线条颜色*/
    void CGContextSetCMYKStrokeColor(CGContextRef__nullable c,
                                     CGFloat cyan, CGFloat magenta, CGFloat yellow, CGFloat black, CGFloat alpha)
    
2.3 图型API(直线、曲线、圆、圆弧)
/* 开始创建路径. */
 void CGContextBeginPath(CGContextRef__nullable c)
 
 /* 开始一个新的子路径点 */
 void CGContextMoveToPoint(CGContextRef__nullable c,
                           CGFloat x, CGFloat y)
 
 /* 添加一条直线段从当前指向的(x,y)。 */
 void CGContextAddLineToPoint(CGContextRef__nullable c,
                              CGFloat x, CGFloat y)
 
 /**
  *  从当前添加一个三次Bezier曲线
  *  @param cp1x 控制点1 x坐标
  *  @param cp1y 控制点1 y坐标
  *  @param cp2x 控制点2 x坐标
  *  @param cp2y 控制点2 y坐标
  *  @param x    直线的终点 x坐标
  *  @param y    直线的终点 y坐标
  */
 void CGContextAddCurveToPoint(CGContextRef__nullable c, CGFloat cp1x, CGFloat cp1y, CGFloat cp2x, CGFloat cp2y, CGFloat x, CGFloat y)
 
 /**
  *  从当前添加一个二次Bezier曲线
  *  @param cpx 控制点 x坐标
  *  @param cpy 控制点 y坐标
  *  @param x   直线的终点 x坐标
  *  @param y   直线的终点 y坐标
  */
 void CGContextAddQuadCurveToPoint(CGContextRef__nullable c, CGFloat cpx, CGFloat cpy,CGFloat x,CGFloat y)
 
 /* 关闭当前上下文的子路径,且当前点和起点连接起来 */
 void CGContextClosePath(CGContextRef__nullable c)
 
 /** Path construction convenience functions. **/
 
 /* 添加一个矩形路径 */
 void CGContextAddRect(CGContextRef__nullable c, CGRect rect)
 
 /* 添加多个矩形路径 */
 void CGContextAddRects(CGContextRef__nullable c,
                        const CGRect * __nullable rects, size_t count)
 
 /* 添加多条直线路径*/
 void CGContextAddLines(CGContextRef__nullable c,
                        const CGPoint * __nullable points, size_t count)
 
 /* 根据一个矩形,绘制椭圆(圆形 */
 void CGContextAddEllipseInRect(CGContextRef__nullable c, CGRect rect)
 
 /**
  *  添加弧形对象
  *  @param x          中心点x坐标
  *  @param y          中心点y坐标
  *  @param radius     半径
  *  @param startAngle 起始弧度
  *  @param endAngle   终止弧度
  *  @param clockwise  是否逆时针绘制,0则顺时针绘制
  */
 void CGContextAddArc(CGContextRef__nullable c, CGFloat x,CGFloat y,CGFloat radius,CGFloat startAngle,CGFloat endAngle,int clockwise)
 
 
 /* 这个函数使用一个序列的三次贝塞尔曲线创建一个弧
  原理:首先画两条线,这两条线分别是 current point to (x1,y1)和(x1,y1) to (x2,y2).这样就是出现一个以(x1,y1)为顶点的两条射线,然后定义半径长度,这个半径是垂直于两条射线的,这样就能决定一个圆了,如果当前点和第一个切点的弧(起点)是不平等的,那么会添加一条直线段从当前指向第一个切点。弧的终点成为新的当前点的路径。*/
 
 void CGContextAddArcToPoint(CGContextRef__nullable c, CGFloat x1, CGFloat y1, CGFloat x2, CGFloat y2, CGFloat radius)
 
 /*添加路径到图形上下文 */
 
 void CGContextAddPath(CGContextRef__nullable c, CGPathRef__nullable path)
2.4 绘制到上下文中(Fill、Stroke、EOFill、Clear)
/** Path drawing functions. **/
 
 typedef CF_ENUM (int32_t, CGPathDrawingMode) {
     kCGPathFill,//只有填充(非零缠绕数填充),不绘制边框  如图1
     kCGPathEOFill,//奇偶规则填充(多条路径交叉时,奇数交叉填充,偶交叉不填充)如图2
     kCGPathStroke,        // 只有边框  如图3
     kCGPathFillStroke,    // 既有边框又有填充  如图4
     kCGPathEOFillStroke   // 奇偶填充并绘制边框  如图5
 };
 /*
  使用指定模式绘制当前CGContextRef中所包含的路径。CGPathDrawingMode 属性如上*/
 
 void CGContextDrawPath(CGContextRef__nullable c, CGPathDrawingMode mode)
 
 /** Path drawing convenience functions. **/
 
 /*
  填充该路径包围的区域*/
 void CGContextFillPath(CGContextRef__nullable c)
 
 /*
  使用奇偶规则来填充该路径包围的区域。奇偶规则指:如果某个点被路径包围了奇数次,系统绘制该点;如果被路径包围了偶数次,系统不绘制*/
 void CGContextEOFillPath(CGContextRef__nullable c)
 
 /*
  使用当前 CGContextRef设置的线宽绘制路径*/
 void CGContextStrokePath(CGContextRef__nullable c)
 
 /*
  填充rect代表的矩形*/
 void CGContextFillRect(CGContextRef__nullable c, CGRect rect)
 
 /*
  填充多个矩形
  */
 void CGContextFillRects(CGContextRef__nullable c,
                         const CGRect * __nullable rects, size_t count)
 
 /*
  使用当前 CGContextRef设置的线宽绘制矩形框*/
 void CGContextStrokeRect(CGContextRef__nullable c, CGRect rect)
 
 /*
  使用指定线宽绘制矩形框*/
 void CGContextStrokeRectWithWidth(CGContextRef__nullable c,
                                   CGRect rect, CGFloat width)
 
 /*
  擦除指定矩形区域上绘制的图形*/
 void CGContextClearRect(CGContextRef__nullable c, CGRect rect)
 
 /*
  填充rect矩形的内切椭圆区域*/
 void CGContextFillEllipseInRect(CGContextRef__nullable c,
                                 CGRect rect)
 
 /*
  使用当前 CGContextRef设置的线宽绘制rect矩形的内切椭圆*/
 void CGContextStrokeEllipseInRect(CGContextRef__nullable c, CGRect rect)
 
 /*
  CGContextBeginPath(context);
  for (k = 0; k < count; k += 2) {
  CGContextMoveToPoint(context, s[k].x, s[k].y);
  CGContextAddLineToPoint(context, s[k+1].x, s[k+1].y);
  }
  CGContextStrokePath(context);
  使用当前 CGContextRef设置的线宽绘制多条线段。该方法需要传入2N个CGPoint组成的数组,其中1、2个点组成第一条线段,3、4个点组成第2条线段,以此类推*/
 void CGContextStrokeLineSegments(CGContextRef__nullable c,
                                  const CGPoint * __nullable points, size_t count)
 

示例:绘制带虚线的椭圆

Quartz 2D的绘图都是需要图形上下文、以下是基于界面显示的绘图上下文获取

1.UIView及子类上下文获取
- (void)drawRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();
}
2.CALayer 上下文获取
- (void)drawInContext:(CGContextRef)ctx{}
效果:
image.png
代码:
- (void)drawRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextAddArc(context, self.center.x, self.center.y, 80, M_PI, 2*M_PI, NO);
    CGFloat lengths[2] = {2,5};
    CGContextSetLineDash(context, 0, lengths, 2);
    CGContextSetRGBStrokeColor(context, 1, 0, 0, 1);
    CGContextSetLineWidth(context, 10);
    CGContextStrokePath(context);
}

三、基于路径的绘图

为什么要使用路径绘画,CGContextRefAPI已经满足了我们的绘画需求?
如过我们不想轻易的丢掉或者重复绘制相同的路径,那么我们就应该使用路径绘画,在画相同的路径时可以再画图,就是使用CGPathRef保存一个路径。

路径CGPathRefCoreGraphics框架中的位置

image.png

CGPathRef常用的API

3.1 创建空的路径或者拷贝路径
/* 创建一个可变的空Path */    
CGMutablePathRef  CGPathCreateMutable(void)

/* Copy后得到一个不可变Path */    
CGPathRef __nullable CGPathCreateCopy(CGPathRef cg_nullable path) 

/* Copy后得到一个可变Path */
CGMutablePathRef __nullable CGPathCreateMutableCopy(CGPathRef cg_nullable path)

/* Copy一个Path带形变参数 */
CGPathRef __nullable CGPathCreateCopyByTransformingPath(CGPathRef cg_nullable path, const CGAffineTransform * __nullable transform)
 
/* Copy一个Path带形变参数 */
CGMutablePathRef __nullable CGPathCreateMutableCopyByTransformingPath(CGPathRef cg_nullable path, const CGAffineTransform * __nullable transform)
3.2创建带非空(圆、矩形、弧)路径
// 创建一个矩形的路径
CGPathRef  CGPathCreateWithRect(CGRect rect,const CGAffineTransform * __nullable transform)
  
// 创建一个椭圆路径
CGPathRef  CGPathCreateWithEllipseInRect(CGRect rect, const CGAffineTransform * __nullable transform)
   
// 创建一个圆路径
CGPathRef  CGPathCreateWithRoundedRect(CGRect rect, CGFloat cornerWidth, CGFloat cornerHeight, const CGAffineTransform * __nullable transform)
3.3 往路径中添加(线、圆、矩形、弧)和Context中添加图形一样
// 移动到某点
void CGPathMoveToPoint(CGMutablePathRef cg_nullable path, const CGAffineTransform * __nullable m, CGFloat x, CGFloat y)
    
// 画一条直线
void CGPathAddLineToPoint(CGMutablePathRef cg_nullable path, const CGAffineTransform * __nullable m, CGFloat x, CGFloat y)
   
// 画一个圆 
void CGPathAddRoundedRect(CGMutablePathRef cg_nullable path, const CGAffineTransform * __nullable transform, CGRect rect, CGFloat cornerWidth, CGFloat cornerHeight)
    
// 画一个二次Bezier曲线
void CGPathAddQuadCurveToPoint(CGMutablePathRef cg_nullable path, const CGAffineTransform *__nullable m, CGFloat cpx, CGFloat cpy,
                                             CGFloat x, CGFloat y)
// 画一个三次Bezier曲线  
void CGPathAddCurveToPoint(CGMutablePathRef cg_nullable path,const CGAffineTransform * __nullable m, CGFloat cp1x, CGFloat cp1y, CGFloat cp2x, CGFloat cp2y, CGFloat x, CGFloat y)
    
// 换一个矩形
void CGPathAddRect(CGMutablePathRef cg_nullable path,const CGAffineTransform * __nullable m, CGRect rect)
    
// 画多个矩形
void CGPathAddRects(CGMutablePathRef cg_nullable path, const CGAffineTransform * __nullable m, const CGRect * __nullable rects, size_t count)

// 画多条线
void CGPathAddLines(CGMutablePathRef cg_nullable path, const CGAffineTransform * __nullable m, const CGPoint * __nullable points,  size_t count)
    
// 画一个椭圆
void CGPathAddEllipseInRect(CGMutablePathRef cg_nullable path, const CGAffineTransform * __nullable m, CGRect rect)
  
// 画弧    
void CGPathAddArc(CGMutablePathRef cg_nullable path,const CGAffineTransform * __nullable m, CGFloat x, CGFloat y, CGFloat radius, CGFloat startAngle, CGFloat endAngle, bool clockwise)
    
// 三次贝塞尔曲线创建一个弧
void CGPathAddArcToPoint(CGMutablePathRef cg_nullable path, const CGAffineTransform * __nullable m, CGFloat x1, CGFloat y1, CGFloat x2, CGFloat y2, CGFloat radius)

3.4 Path相关属性设置
// 设置虚线
CGPathRef __nullable CGPathCreateCopyByDashingPath(CGPathRef cg_nullable path, const CGAffineTransform * __nullable transform, CGFloat phase, const CGFloat * __nullable lengths, size_t count)
    
// 设置路径线宽、路径头尾样式、路径交接处样式、连接处锐角箭头的长度
CGPathRef __nullable CGPathCreateCopyByStrokingPath(CGPathRef cg_nullable path, const CGAffineTransform * __nullable transform, CGFloat lineWidth, CGLineCap lineCap,CGLineJoin lineJoin, CGFloat miterLimit)

/* Line join styles. */
typedef CF_ENUM(int32_t, CGLineJoin) {
    kCGLineJoinMiter,
    kCGLineJoinRound,
    kCGLineJoinBevel
};
    
/* Line cap styles. */
typedef CF_ENUM(int32_t, CGLineCap) {
    kCGLineCapButt,
    kCGLineCapRound,
    kCGLineCapSquare
};
3.5 Path内存管理
CGPathRef cg_nullable CGPathRetain(CGPathRef cg_nullable path)
 
void CGPathRelease(CGPathRef cg_nullable path)
 
void CGPathCloseSubpath(CGMutablePathRef cg_nullable path)
3.6 添加一个Path到另外一个Path上
// 添加Path2到Path1上
void CGPathAddPath(CGMutablePathRef cg_nullable path1, const CGAffineTransform * __nullable m, CGPathRef cg_nullable path2)

示例:

效果
image.png

代码中path2,path3都是对path1的重用,这就体现了使用path的优势,不用在绘制相同的路径

Code:
CGContextRef context = UIGraphicsGetCurrentContext();
    // 创建路径1,2,3
    // path1
    CGMutablePathRef path1 = CGPathCreateMutable();
    CGPoint points[3] = {
        CGPointMake(100, 100),
        CGPointMake(70, 130),
        CGPointMake(130, 130)
    };
    CGPathAddLines(path1, nil, points, 3);
    
    // path2
    CGAffineTransform transform[1] = {
        CGAffineTransformTranslate(CGAffineTransformIdentity, 30, 30)
    };
    CGPathRef path2 = CGPathCreateMutableCopyByTransformingPath(path1, transform);
    
    // path3
    CGAffineTransform transform2[1] = {
        CGAffineTransformTranslate(CGAffineTransformIdentity, -30, 30)
    };
    CGPathRef path3 = CGPathCreateMutableCopyByTransformingPath(path1, transform2);
    
    // 填充路径
    CGContextAddPath(context, path1);
    CGContextSetRGBFillColor(context, 1, 1, 0, 1);
    CGContextFillPath(context);
    
    CGContextAddPath(context, path2);
    CGContextSetRGBFillColor(context, 1, 0, 0, 1);
    CGContextFillPath(context);
    
    CGContextAddPath(context, path3);
    CGContextSetRGBFillColor(context, 1, 0, 1, 1);
    CGContextFillPath(context);
    
    // 释放路径
    CGPathRelease(path1);
    CGPathRelease(path2);
    CGPathRelease(path3);

四、Transform(Translate、Rotate、Scale)

形变,UIView及子类可以直接使用,这个在开发中经常使用,就不再描述了。

五、绘制阴影

都封装咋了CALayer中,开发中也经常使用,不再描述。

六、透明度图层

先用两个图例来说明什么透明度图层的作用

图1:非透明度图层.png
图2:透明度图层.png

透明度层由被组合以产生复合图形的两个或更多的物体,生成的复合体被视为单个对象。当您想要将效果应用于一组对象时,透明度图层非常有用,例如应用于图2中三个圆圈的阴影。

绘画到透明图层需要三个步骤:

1.调用函数CGContextBeginTransparencyLayer
2.在透明图层中绘制要合成的项目
3.调用函数CGContextEndTransparencyLayer

代码:
 CGContextRef context = UIGraphicsGetCurrentContext();
 // 阴影代码不能写在步骤2中
 CGSize shadowOffset = CGSizeMake (-20, -5);
 CGContextSetShadow (context, shadowOffset, 10);
 
 // 步骤1
 CGContextBeginTransparencyLayer (context, NULL);
 
 // 步骤2
 CGContextSetRGBFillColor (context, 0, 0.5, 1, 1);
 CGContextFillEllipseInRect(context, CGRectMake(100, 100, 150, 150));
 CGContextSetRGBFillColor (context, 0, 1, 0.5, 1);
 CGContextFillEllipseInRect(context, CGRectMake(40, 160, 150, 150));
 CGContextSetRGBFillColor (context, 1, 1, 0, 1);
 CGContextFillEllipseInRect(context, CGRectMake(160, 160, 150, 150));
 
 // 步骤3
 CGContextEndTransparencyLayer (context);
效果:
image.png

七、渐变色

渐变分为两种渐变方式:
1.线性渐变:通过两个点组成的这条线,来做线性渐变
2.径向渐变:通过第一个点及半径和第一个点及半径组成的两个圆来做径向渐变

先来看看CGGradientRef如何实现渐变

7.1 CGGradientRef线性渐变

步骤:
1.获取上下文
2.获取渐变对象
3.绘制渐变

API:

1.获取上下文

CGContextRef context = UIGraphicsGetCurrentContext();

2.获取渐变对象

/**
 获取渐变对象

 @param space 设置颜色空间,iOS中使用 CGColorSpaceCreateDeviceRGB()获取颜色空间
 @param components 颜色组成,CGFloat的数组,一组颜色包含rgba 4个值,如果设置3组颜色那么CGFloat components[12]定义12长度
 @param locations 每组颜色的位置,定义长度有颜色组成决定,3组颜色 CGFloat locations[3]定义长度3
 @param count 颜色组成的长度,比如3
 */
CGGradientRef CGGradientCreateWithColorComponents(CGColorSpaceRef cg_nullable space,
                                                    const CGFloat * cg_nullable components,
                                                    const CGFloat * __nullable locations,
                                                    size_t count);

3.绘制线性渐变API

/**
 绘制线性渐变

 @param c 上下文 : UIGraphicsGetCurrentContext()获取
 @param gradient 渐变对象:CGGradientCreateWithColorComponents(...)API获取
 @param startPoint 渐变开始点 :CGPoint
 @param endPoint 渐变结束点 :CGPoint
 @param options 绘制的方式 枚举:CGGradientDrawingOptions 
 */
CGContextDrawLinearGradient(CGContextRef  _Nullable c,
                                CGGradientRef  _Nullable gradient,
                                CGPoint startPoint, CGPoint endPoint,
                                CGGradientDrawingOptions options)

4.绘制方式

/**
 绘制方式

 @param kCGGradientDrawsBeforeStartLocation 在开始点之前绘制使用开始点绘制颜色
 @param kCGGradientDrawsAfterEndLocation 结束点之后使用结束点颜色继续绘制
*/
typedef CF_OPTIONS (uint32_t, CGGradientDrawingOptions) {
  kCGGradientDrawsBeforeStartLocation = (1 << 0),
  kCGGradientDrawsAfterEndLocation = (1 << 1)
};
Code
 // 1.获取上下文
 CGContextRef myContext = UIGraphicsGetCurrentContext();
 // 2.获取颜色空间
 CGColorSpaceRef colorSpace=CGColorSpaceCreateDeviceRGB();
 // 3.颜色组成
 CGFloat compoents[12]={
     248.0/255.0,86.0/255.0,86.0/255.0,1,
     249.0/255.0,127.0/255.0,127.0/255.0,0.7,
     249.0/255.0,200.0/255.0,200.0/255.0,0.5,
 };
 // 4.颜色位置
 CGFloat locations[3]={0,0.5,1.0};   
 // 5.渐变对象
 CGGradientRef gradient= CGGradientCreateWithColorComponents(colorSpace, compoents, locations, 3);
 // 6.绘制线性渐变
 CGContextDrawLinearGradient(myContext, gradient, CGPointMake(100, 100), CGPointMake(200, 200), kCGGradientDrawsBeforeStartLocation|kCGGradientDrawsAfterEndLocation);
 // 7.释放颜色空间
 CGColorSpaceRelease(colorSpace);
效果

1.kCGGradientDrawsBeforeStartLocation绘制


kCGGradientDrawsBeforeStartLocation.png

2.kCGGradientDrawsAfterEndLocation绘制


kCGGradientDrawsAfterEndLocation.png

3.kCGGradientDrawsBeforeStartLocation|kCGGradientDrawsAfterEndLocation绘制


image.png
应用:横向线性渐变导航栏

1.code

- (void)drawRect:(CGRect)rect {
   // 1.获取上下文
   CGContextRef myContext = UIGraphicsGetCurrentContext();
   // 2.获取颜色空间
   CGColorSpaceRef colorSpace=CGColorSpaceCreateDeviceRGB();
   // 3.颜色组成
   CGFloat compoents[8]={
       248.0/255.0,86.0/255.0,86.0/255.0,1,
       249.0/255.0,200.0/255.0,200.0/255.0,0.5,
   };
   // 4.颜色位置
   CGFloat locations[2]={0,1.0};
   // 5.渐变对象
   CGGradientRef gradient= CGGradientCreateWithColorComponents(colorSpace, compoents, locations, 2);
   // 6.绘制线性渐变
   CGContextDrawLinearGradient(myContext, gradient, CGPointMake(0, self.frame.size.height/2), CGPointMake(self.frame.size.width, self.frame.size.height/2), kCGGradientDrawsAfterEndLocation);
   // 7.释放颜色空间
   CGColorSpaceRelease(colorSpace);
}

2.效果图


image.png
7.2 CGGradientRef径向渐变

与线性渐变绘制步骤相同

API
/**
 绘制径向渐变

 @param c 上下文 : UIGraphicsGetCurrentContext()获取
 @param gradient 渐变对象:CGGradientCreateWithColorComponents(...)API获取
 @param startCenter 开始中心点
 @param startRadius 开始半径
 @param endCenter 结束中心点
 @param endRadius 结束半径 
 @param options 绘制方式 枚举:CGGradientDrawingOptions
 */
void CGContextDrawRadialGradient(CGContextRef cg_nullable c,
                                 CGGradientRef cg_nullable gradient, CGPoint startCenter, CGFloat startRadius,
                                 CGPoint endCenter, CGFloat endRadius, CGGradientDrawingOptions options);
Case 1:

PointA(200,200)半径为20
PointB(200,400)半径为40
组成的径向渐变

code
- (void)drawRect:(CGRect)rect {
    // 1.获取上下文
    CGContextRef myContext = UIGraphicsGetCurrentContext();
    // 2.获取颜色空间
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    // 3.颜色组成
    CGFloat compoents[12]={
        248.0/255.0,86.0/255.0,86.0/255.0,1,
        249.0/255.0,127.0/255.0,127.0/255.0,1,
        249.0/255.0,200.0/255.0,200.0/255.0,1,
    };
    // 4.颜色位置
    CGFloat locations[3]={0,0.5,1.0};
    // 5.渐变对象
    CGGradientRef gradient= CGGradientCreateWithColorComponents(colorSpace, compoents, locations, 3);
    // 6.绘制径向渐变
    CGContextDrawRadialGradient(myContext, gradient, CGPointMake(200, 200),20, CGPointMake(200, 400), 40, kCGGradientDrawsBeforeStartLocation);
    // 7.释放颜色空间
    CGColorSpaceRelease(colorSpace);
}
  1. kCGGradientDrawsBeforeStartLocation绘制


    image.png

2.kCGGradientDrawsAfterEndLocation绘制


image.png

3.kCGGradientDrawsBeforeStartLocation|kCGGradientDrawsAfterEndLocation绘制


image.png
渐变区域理解
  • 从PointA到PointB投影之间的区域为渐变区域
  • 从PointB到PointA投影,PointA之外的区域使用开始点颜色绘制
  • 从PointA到PointB投影,PointB之外的区域使用结束点颜色绘制

这个理解对绘制圆形径向渐变非常有用

例子:圆形渐变,加深对渐变区域的理解
Case 2:

PointA(200,200)半径为20
PointB(200,200)半径为40
组成的径向渐变

code
    // 1-5,7步骤与之前相同
    // 6.绘制径向渐变
    CGContextDrawRadialGradient(myContext, gradient, CGPointMake(200, 200),20, CGPointMake(200, 200), 40, kCGGradientDrawsAfterEndLocation);
效果
  1. kCGGradientDrawsBeforeStartLocation绘制


    image.png
  2. kCGGradientDrawsAfterEndLocation绘制


    image.png
  3. kCGGradientDrawsBeforeStartLocation|kCGGradientDrawsAfterEndLocation绘制

image.png
Case 2:

PointA(170,170)半径为0
PointB(200,200)半径为60
组成的径向渐变

code
    // 1-5,7步骤与之前相同
    // 6.绘制径向渐变
    CGContextDrawRadialGradient(myContext, gradient, CGPointMake(170, 170),0, CGPointMake(200, 200), 60, kCGGradientDrawsBeforeStartLocation);
效果
image.png

这个效果是不是立体感很强

Case 3: 雷达效果实现

PointA(200,200)半径为2
PointB(200,200)半径为变化
组成的径向渐变

code
{
    CGFloat _alpha;
    CGFloat _radiu;
}
- (instancetype)initWithFrame:(CGRect)frame{
    self = [super initWithFrame:frame];
    if (self) {
        self.backgroundColor = [UIColor whiteColor];
        _alpha = 1;
        _radiu = 2;
        CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(update)];
        [link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
    }
    return self;
}

- (void)update{
    if (_radiu > 12) {
        _radiu = 2;
        _alpha = 1;
    }
    _radiu += 0.1;
    _alpha -= 0.02;
    if (_alpha <= 0) {
        _alpha = 0;
    }
    [self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect {
    // 1.获取上下文
    CGContextRef myContext = UIGraphicsGetCurrentContext();
    // 2.获取颜色空间
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    // 3.颜色组成
    CGFloat compoents[12] = {
        248.0/255.0,86.0/255.0,86.0/255.0,1,
        249.0/255.0,200.0/255.0,200.0/255.0,_alpha,
    };
    // 4.颜色位置
    CGFloat locations[2] = {0,1.0};
    // 5.渐变对象
    CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, compoents, locations, 2);
    // 6.绘制径向渐变
    CGContextDrawRadialGradient(myContext, gradient, CGPointMake(200, 200),2, CGPointMake(200, 200), _radiu, kCGGradientDrawsBeforeStartLocation);
    // 7.释放颜色空间
    CGColorSpaceRelease(colorSpace);
}

效果图
111.gif
Case 3: 特定的区域绘制渐变色

PointA(200,200)半径为2
PointB(200,200)半径为变化
组成的径向三角形渐变

Code
// 1.获取上下文
    CGContextRef myContext = UIGraphicsGetCurrentContext();
    // 2.获取颜色空间
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    // 3.颜色组成
    CGFloat compoents[12] = {
        248.0/255.0,86.0/255.0,86.0/255.0,1,
        249.0/255.0,200.0/255.0,200.0/255.0,0.5,
    };
    // 4.颜色位置
    CGFloat locations[2] = {0,1.0};
    // 5.渐变对象
    CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, compoents, locations, 2);
    // 6.绘图
    CGContextMoveToPoint(myContext ,self.center.x, self.center.y - 100);
    CGContextAddLineToPoint(myContext, self.center.x - 100, self.center.y + 100);
    CGContextAddLineToPoint(myContext, self.center.x + 100, self.center.y + 100);
    CGContextClosePath(myContext);
    CGContextClip(myContext);
    // 7.绘制径向渐变
    CGContextDrawRadialGradient(myContext, gradient, self.center,0, self.center, 200, kCGGradientDrawsBeforeStartLocation);
    // 8.释放颜色空间
    CGColorSpaceRelease(colorSpace);
效果
image.png

八、重复绘图

CGPatternRef绘制操作可以重复地绘制到一个图形上下文上,当使用CGPatternRef进行绘制时,Quartz会将页面划分为一组CGPatternRef单元格,每个单元格都是CGPatternRef图像的大小,并使用您提供的回调绘制每个单元格
如图:

Pattern.png

重复绘图API

填充:

/**
 填充绘制
 @param c 上下文
 @param pattern 单元图形
 @param components 颜色组成
 */
void CGContextSetFillPattern(CGContextRef cg_nullable c,
                             CGPatternRef cg_nullable pattern,
                             const CGFloat * cg_nullable components)

描边绘制:

/**
描边绘制
@param c 上下文
@param pattern 单元图形
@param components 颜色组成
*/
void CGContextSetStrokePattern(CGContextRef cg_nullable c,
                              CGPatternRef cg_nullable pattern,
                              const CGFloat * cg_nullable components)

上下文和颜色组成我们都知道,pattern 单元图形的生成需要下面的API

CGPatternRef pattern获取
/**
 @param info 是一个指针,指向我们要传递给绘制回调函数的数据
 @param bounds 指定模式单元格的大小
 @param matrix 指定模式矩阵,它将模式坐标系统映射到图形上下文的默认坐标系统
 @param xStep 水平间距
 @param yStep 竖直间距
 @param tiling 枚举 平铺模式
 @param isColored 模式单元格是着色模式(true)还是模板模式(false)
 @param callbacks 是一个指向CGPatternCallbacks结构体的指针
 @return CGPatternRef对象
 */
CGPatternRef __nullable CGPatternCreate(void * __nullable info,
                                        CGRect bounds,
                                        CGAffineTransform matrix,
                                        CGFloat xStep,
                                        CGFloat yStep,
                                        CGPatternTiling tiling,
                                        bool isColored,
                                        const CGPatternCallbacks * cg_nullable callbacks)

以上的参数我们唯一不知道的是CGPatternCallbacks * callbacks由来

CGPatternCallbacks * callbacks获取
struct CGPatternCallbacks {
    // 版本设置为0
    unsigned int version; 
    // 单元格绘制回调函数
    CGPatternDrawPatternCallback __nullable drawPattern;
    // 释放Pattern是回调,可以设置为NULL
    CGPatternReleaseInfoCallback __nullable releaseInfo;
};
CGPatternDrawPatternCallback drawPattern的获取

单元格绘制回调函数是类似于下面这种格式的一个回调函数

typedef void (*CGPatternDrawPatternCallback) (
                        void *info,
                        CGContextRef context
);
  1. info: 一个指向模式相关数据的指针。这个参数是可选的,可以传递NULL。传递给回调的数据与后面创建模式的数据是一样的。
  2. context: 绘制模式单元格的图形上下文
完整示例
static const CGFloat PATTERN_MARGIN_H = 30;
static const CGFloat PATTERN_MARGIN_V = 30;
static const CGSize PATTERN_SIZE = {10, 10};
// 绘制单元格
void DrawColoredPattern(void *info, CGContextRef context){
    CGContextSetRGBFillColor (context, 0, 0, 1, 0.5);
    CGContextFillRect (context, (CGRect){0,0, PATTERN_SIZE});
    
    CGContextSetRGBFillColor (context, 1, 0, 0, 0.5);
    CGContextFillRect (context, (CGRect){0,10, PATTERN_SIZE});
    
    CGContextSetRGBFillColor (context, 0, 1, 0, 0.5);
    CGContextFillRect (context, (CGRect){10,0, PATTERN_SIZE});
    
    CGContextSetRGBFillColor (context, .5, 0, .5, 0.5);
    CGContextFillRect (context, (CGRect){10,10, PATTERN_SIZE});
}


- (void)drawRect:(CGRect)rect {
    CGFloat alpha = 1;
    // 1.上下文
    CGContextRef myContext = UIGraphicsGetCurrentContext();
    // 2.颜色空间填充
    CGContextSetFillColorSpace (myContext, CGColorSpaceCreatePattern (NULL));
    // 3.回调
    static const CGPatternCallbacks callbacks = {0,&DrawColoredPattern,NULL};
    // 4.pattern对象
    CGPatternRef pattern = CGPatternCreate (NULL,
                               self.frame,
                               CGAffineTransformIdentity,
                               PATTERN_MARGIN_H,
                               PATTERN_MARGIN_V,
                               kCGPatternTilingConstantSpacing,
                               true,
                               &callbacks);
    // 5.pattern对象填充
    CGContextSetFillPattern (myContext, pattern, &alpha);
    // 6.绘制到指定rect
    CGContextFillRect (myContext, self.frame);
    
    // 7.内存释放
    CGPatternRelease (pattern);
}

效果图

image.png

图中:
1为单元格区域
2为竖向间距
3为横向间距

九、核心图层绘图CGLayerRef

如果说CGPatternRef是重复有规律的绘制图形,那么CGLayerRef�就是绘制无规律的重复图形

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

我们需要按照如下几个步骤来使用层对象进行绘制:

  1. 创建一个使用已存在的图形上下文初始化的CGLayerRef对象
  2. CGLayerRef对象获取图形上下文
  3. 绘制图形到CGLayer图形上下文
  4. CGLayerRef对象绘制到目标图形上下文
API
// 1.通过context创建CGLayer对象
CGContextRef context = UIGraphicsGetCurrentContext();
CGLayerRef layer = CGLayerCreateWithContext(context, CGSizeMake(100, 100), NULL);
// 2.为layer对象获取图形上下文
CGContextRef layerContext = CGLayerGetContext(layer);
// 3.绘制图形到layerContext
CGContextAddRect(layerContext, CGRectMake(0, 0, 50, 50));
// 4.将layer绘制到目标图形上下文
CGContextDrawLayerAtPoint(context, CGPointZero, layer);
示例
- (void)drawRect:(CGRect)rect {
    CGRect starBGRect = CGRectMake(0, 40, 110, 110);
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    // 1.蓝色背景
    CGContextSaveGState(context);
    CGContextSetRGBFillColor(context, 0, 0, 1, 1);
    CGContextFillRect(context, starBGRect);
    CGContextRestoreGState(context);
    
    // 2.画三角形
    CGContextSaveGState(context);
    // 2.1获取三角形CGLayerRef和三角形的CGContextRef
    CGLayerRef starLayer = CGLayerCreateWithContext(context, starBGRect.size, NULL);
    CGContextRef starContext = CGLayerGetContext(starLayer);
    
    // 2.2在三角形的CGContextRef中画图形(三角形)
    CGContextMoveToPoint(starContext, 5, 0);
    CGContextAddLineToPoint(starContext, 0, 10);
    CGContextAddLineToPoint(starContext, 10, 10);
    CGContextClosePath(starContext);
    CGContextSetRGBFillColor(starContext, 1, 0, 0, 1);
    CGContextFillPath(starContext);
    
    // 2.3将三角形的CGLayerRef画到上下文中(Layer在上下文中变换位置绘制,即在不同位置绘制相同图形)
    for (int i=0; i < 11; i++) {
        CGContextDrawLayerAtPoint(context, CGPointMake(10*i, 40+10*i), starLayer);
    }
    for (int i=0; i < 11; i++) {
        CGContextDrawLayerAtPoint(context, CGPointMake(10*i, 150-10*(i+1)), starLayer);
    }
    
    for (int i=0; i < 11; i++) {
        CGContextDrawLayerAtPoint(context, CGPointMake(10*i, 90), starLayer);
    }
    for (int i=0; i < 11; i++) {
        CGContextDrawLayerAtPoint(context, CGPointMake(50, 40+10*i), starLayer);
    }
    
    CGContextRestoreGState(context);
}
效果
image.png

十、消除锯齿渲染

位图Graphics Context支持反锯齿,这一操作是人为的较正在位图中绘制文本或形状时产生的锯齿边缘。当位图的分辩率明显低于人眼的分辩率时就会产生锯齿。为了使位图中的对象显得平滑,Quartz使用不同的颜色来填充形状周边的像素。通过这种方式来混合颜色,使形状看起来更平滑。如图2-4显示的效果。我们可以通过调用CGContextSetShouldAntialias来关闭位图Graphics Context的反锯齿效果。反锯齿设置是图形状态的一部分。

可以调用函数CGContextSetAllowsAntialiasing来控制一个特定Graphics Context是否支持反锯齿;false表示不支持。该设置不是图形状态的一部分。当上下文及图形状态设置为true时,Quartz执行反锯齿。
[图片上传失败...(image-9662c5-1513655208127)]

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

推荐阅读更多精彩内容