参考文章
- 官方文档
- iOS中使用blend改变图片颜色
设置
- 设置勾画色CGContextSetRGBStrokeColor
void CGContextSetRGBStrokeColor (
CGContextRef c,
CGFloat red,
CGFloat green,
CGFloat blue,
CGFloat alpha
);
- 设置填充色CGContextSetRGBFillColor
void CGContextSetRGBFillColor (
CGContextRef c,
CGFloat red,
CGFloat green,
CGFloat blue,
CGFloat alpha
)
- 设置勾画宽度CGContextSetLineWidth
void CGContextSetLineWidth (
CGContextRef c,
CGFloat width
)
- 设置端点样式CGContextSetLineCap
void CGContextSetLineCap ( CGContextRef c, CGLineCap cap )
- 设置连接点样式CGContextSetLineJoin
void CGContextSetLineJoin ( CGContextRef c, CGLineJoin join )
- 设置虚线样式CGContextSetLineDash
void CGContextSetLineDash ( CGContextRef c, CGFloat phase, const CGFloat *lengths, size_t count );
phase设置第一段实线开始的长度,lengths是一个浮点型数组,设置一个重复段中实线虚线的长度,count用以说明lengths的个数.
如上图虚线样式1,phase为0,说明它一个实线段的起点为0,lengths数组表明它的重复段是这样的:长10的实线,长20的虚线,长10的实线,长30的虚线,count为4.
上图虚线样式2与1唯一的不同的是,它的phase为5,它的第一个实线段从5开始.
- 设置混合模式
void CGContextSetBlendMode ( CGContextRef c, CGBlendMode mode );
混合模式是由CGBlendMode枚举来设置,详情见官方文档中的枚举说明.这里解释下几个参数:
R is the premultiplied result
S is the source color, and includes alpha
D is the destination color, and includes alpha
Ra, Sa, and Da are the alpha components of R, S, and D;
比如,kCGBlendModeDestinationOver,它的混合模式是这样来计算的: R = S*(1 - Da) + D.其中,前一种颜色是destination,后一种颜色是source.在官方Demo中,代码是这样的
// Start with a background whose color we don't use in the demo
CGContextSetGrayFillColor(context, 0.2, 1.0);
CGContextFillRect(context, self.bounds);
// We want to just lay down the background without any blending so we use the Copy mode rather than Normal
CGContextSetBlendMode(context, kCGBlendModeCopy);
// Draw a rect with the "background" color - this is the "Destination" for the blending formulas
CGContextSetFillColorWithColor(context, self.destinationColor.CGColor);
CGContextFillRect(context, CGRectMake(110.0, 20.0, 100.0, 100.0));
// Set up our blend mode
CGContextSetBlendMode(context, self.blendMode);
// And draw a rect with the "foreground" color - this is the "Source" for the blending formulas
CGContextSetFillColorWithColor(context, self.sourceColor.CGColor);
CGContextFillRect(context, CGRectMake(60.0, 45.0, 200.0, 50.0));
代码中混合了两次,先是self.destinationColor.CGColor与一开始设置的gray,用kCGBlendModeCopy混合一次(这里,self.destinationColor.CGColor是source,gray是destination),然后,self.sourceColor.CGColor与self.destinationColor.CGColor,用self.blendMode混合一次.
创建Path
- 创建Path起始点CGContextMoveToPoint
void CGContextMoveToPoint (
CGContextRef c,
CGFloat x,
CGFloat y
)
- 添加线段CGContextAddLineToPoint
void CGContextAddLineToPoint (
CGContextRef c,
CGFloat x,
CGFloat y
)
- 添加一系列线段CGContextAddLines
void CGContextAddLines (
CGContextRef c,
const CGPoint *points,
size_t count
)
相当于
CGContextMoveToPoint (c, points[0].x, points[0].y);
for (k = 1; k < count; k++) {
CGContextAddLineToPoint (c, points[k].x, points[k].y);
}
示例1:勾画路径
CGContextSetRGBStrokeColor(context, 1.0, 1.0, 1.0, 1.0);
CGContextSetLineWidth(context, 2.0);
CGContextMoveToPoint(context, 10.0, 30.0);
CGContextAddLineToPoint(context, 310.0, 30.0);
CGContextStrokePath(context);
示例2:勾画路径
CGPoint addLines[] =
{
CGPointMake(10.0, 90.0),
CGPointMake(70.0, 60.0),
CGPointMake(130.0, 90.0),
CGPointMake(190.0, 60.0),
CGPointMake(250.0, 90.0),
CGPointMake(310.0, 60.0),
};
CGContextAddLines(context, addLines, sizeof(addLines)/sizeof(addLines[0]));
CGContextStrokePath(context);
- 添加矩形CGContextAddRect
void CGContextAddRect ( CGContextRef c, CGRect rect );
相当于
CGContextMoveToPoint (c, CGRectGetMinX(rect), CGRectGetMinY(rect));
CGContextAddLineToPoint (c, CGRectGetMaxX(rect), CGRectGetMinY(rect));
CGContextAddLineToPoint (c, CGRectGetMaxX(rect), CGRectGetMaxY(rect);
CGContextAddLineToPoint (c, CGRectGetMinX(rect), CGRectGetMaxY(rect));
CGContextClosePath (c);
- 添加一系列矩形CGContextAddRects
void CGContextAddRects ( CGContextRef c, const CGRect *rects, size_t count );
相当于
for (k = 0; k < count; k++) {
CGContextAddRect (c, rects[k]);
}
- 添加椭圆CGContextAddEllipseInRect
void CGContextAddEllipseInRect ( CGContextRef c, CGRect rect );
- 添加弧线
void CGContextAddArc ( CGContextRef c, CGFloat x, CGFloat y, CGFloat radius, CGFloat startAngle, CGFloat endAngle, int clockwise );
- 添加弧线2
void CGContextAddArcToPoint ( CGContextRef c, CGFloat x1, CGFloat y1, CGFloat x2, CGFloat y2, CGFloat radius );
示例3:添加弧线2
CGPoint p[3] =
{
CGPointMake(210.0, 30.0),
CGPointMake(210.0, 60.0),
CGPointMake(240.0, 60.0),
};
CGContextMoveToPoint(context, p[0].x, p[0].y);
CGContextAddArcToPoint(context, p[1].x, p[1].y, p[2].x, p[2].y, 30.0);
CGContextStrokePath(context);
- 二阶贝塞尔曲线
void CGContextAddQuadCurveToPoint ( CGContextRef c, CGFloat cpx, CGFloat cpy, CGFloat x, CGFloat y );
示例4:二阶贝塞尔曲线
CGContextSetRGBStrokeColor(context, 1.0, 1.0, 1.0, 1.0);
s = CGPointMake(30.0, 300.0);
e = CGPointMake(270.0, 300.0);
cp1 = CGPointMake(150.0, 180.0);
CGContextMoveToPoint(context, s.x, s.y);
CGContextAddQuadCurveToPoint(context, cp1.x, cp1.y, e.x, e.y);
CGContextStrokePath(context);
- 三阶贝塞尔曲线
void CGContextAddCurveToPoint ( CGContextRef c, CGFloat cp1x, CGFloat cp1y, CGFloat cp2x, CGFloat cp2y, CGFloat x, CGFloat y );
示例5:三阶贝塞尔曲线
CGContextSetRGBStrokeColor(context, 1.0, 1.0, 1.0, 1.0);
CGContextSetLineWidth(context, 2.0);
CGPoint s = CGPointMake(30.0, 120.0);
CGPoint e = CGPointMake(300.0, 120.0);
CGPoint cp1 = CGPointMake(120.0, 30.0);
CGPoint cp2 = CGPointMake(210.0, 210.0);
CGContextMoveToPoint(context, s.x, s.y);
CGContextAddCurveToPoint(context, cp1.x, cp1.y, cp2.x, cp2.y, e.x, e.y);
CGContextStrokePath(context);
贝塞尔曲线[知识补充]
绘制Path
- 勾画当前路径CGContextStrokePath,调用此函数后,会清除当前路径
void CGContextStrokePath (
CGContextRef c
)
- 填充路径CGContextFillPath,非零绕组规则,调用此函数后,会清除当前路径
void CGContextFillPath ( CGContextRef c )
- 勾画间隔的线段CGContextStrokeLineSegments,调用此函数后,会清除当前路径
void CGContextStrokeLineSegments (
CGContextRef c,
const CGPoint *points,
size_t count
)
相当于
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);
示例1:勾画间隔线段
CGPoint strokeSegments[] =
{
CGPointMake(10.0, 150.0),
CGPointMake(70.0, 120.0),
CGPointMake(130.0, 150.0),
CGPointMake(190.0, 120.0),
CGPointMake(250.0, 150.0),
CGPointMake(310.0, 120.0),
};
CGContextStrokeLineSegments(context, strokeSegments, sizeof(strokeSegments)/sizeof(strokeSegments[0]));
- 勾画矩形CGContextStrokeRect
void CGContextStrokeRect ( CGContextRef c, CGRect rect );
相当于
CGContextAddRect ( CGContextRef c, CGRect rect );
CGContextStrokePath(context);
- 按指定宽度勾画矩形CGContextStrokeRectWithWidth
void CGContextStrokeRectWithWidth ( CGContextRef c, CGRect rect, CGFloat width );
相当于
CGContextSetLineWidth ( CGContextRef c, CGFloat width );
CGContextAddRect ( CGContextRef c, CGRect rect );
CGContextStrokePath(context);
- 填充矩形CGContextFillRect
void CGContextFillRect ( CGContextRef c, CGRect rect );
相当于
CGContextAddRect ( CGContextRef c, CGRect rect );
CGContextFillPath(context);
- 填充一系列矩形CGContextFillRects
void CGContextFillRects ( CGContextRef c, const CGRect *rects, size_t count );
相当于
for (k = 0; k < count; k++) {
CGContextFillRect (c, rects[k]);
}
- 以指定的模式绘制路径CGContextDrawPath
void CGContextDrawPath ( CGContextRef c, CGPathDrawingMode mode );
介绍下这几个枚举值:
kCGPathFill, // 非零绕组填充
kCGPathEOFill, // 奇偶填充
kCGPathStroke, // 勾边
kCGPathFillStroke, // 非零绕组填充并勾边
kCGPathEOFillStroke // 奇偶填充并勾边
- 勾画椭圆CGContextStrokeEllipseInRect
void CGContextStrokeEllipseInRect ( CGContextRef c, CGRect rect );
相当于
CGContextAddEllipseInRect ( CGContextRef c, CGRect rect );
CGContextStrokePath(context);
- 填充椭圆CGContextFillEllipseInRect
void CGContextFillEllipseInRect ( CGContextRef c, CGRect rect );
非零绕组规则和奇偶规则[补充知识]
图形学的两个规则
如上图,左下角是按照奇偶规则填充,右下角按非零绕组规则填充。
(据我理解,这条曲线应该是一条闭合曲线)
奇偶规则:从一点射出任意一条指向无穷远的射线,曲线和射线的交点个数为奇数,则该点在曲线内,否则,在曲线外。
非零绕组规则:给这条曲线标注方向,从一点射出任意一条指向无穷远的射线,定义一个方向的交点(比如从左到右,或从上到下)为1,它的反向的交点(从右到左,或从下到上)为-1,如果绕组总数为0,表示该点在曲线外,非零,则在曲线内。
上图中,判定区里的点,按照奇偶规则,它与曲线有偶数个交点,所以在曲线外,按照非零绕组规则,如图中所标方向,并将箭头作为射线方向,它有两个同向(都是从上往下)的交点,故而非零,判定该点在曲线内。
存取上下文环境
- 存储当前绘制状态的备份到绘制状态栈中CGContextSaveGState
void CGContextSaveGState ( CGContextRef c )
- 将当前绘制状态切换到绘制栈顶的状态,栈顶的状态被删除CGContextRestoreGState
void CGContextRestoreGState ( CGContextRef c )
这两个方法的配对使用,主要用于:该绘制动作只想作用于该阶段,不想对继后的上下文产生影响.这时,先存储绘制状态,然后实行绘制操作A,然后再取出存储的状态.这样A操作不会对绘制状态产生影响.
可存储的图像状态[知识补充]
Each graphics context maintains a stack of graphics states. Note that not all aspects of the current drawing environment are elements of the graphics state. For example, the current path is not considered part of the graphics state and is therefore not saved when you call the CGContextSaveGState function. The graphics state parameters that are saved are:
CTM (current transformation matrix)
clip region
image interpolation quality
line width
line join
miter limit
line cap
line dash
flatness
should anti-alias
rendering intent
fill color space
stroke color space
fill color
stroke color
alpha value
font
font size
character spacing
text drawing mode
shadow parameters
the pattern phase
the font smoothing parameter
blend mode
示例1:切图操作
CGFloat height = self.bounds.size.height;
CGContextTranslateCTM(context, 0.0, height);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextSetRGBFillColor(context, 1.0, 0.0, 0.0, 1.0);
CGContextDrawImage(context, CGRectMake(10.0, height - 100.0, 90.0, 90.0), self.image);
CGContextSaveGState(context);
CGRect clips[] =
{
CGRectMake(110.0, height - 100.0, 35.0, 90.0),
CGRectMake(165.0, height - 100.0, 35.0, 90.0),
};
CGContextClipToRects(context, clips, sizeof(clips) / sizeof(clips[0]));
CGContextDrawImage(context, CGRectMake(110.0, height - 100.0, 90.0, 90.0), self.image);
CGContextRestoreGState(context);
修改剪切Path
在剪切路径中,需要理解交汇区(intersection),如CGContextClip函数中,是 the current path 和 the current clipping path 交汇的地方可以操作,在CGContextClipToRects函数中,是the clipping path 与 the current clipping path 交汇的地方可以操作.我们经常会需要利用CGContextSaveGState来存储当前的图像状态(会存储clip region) ,利用CGContextRestoreGState来取出存储的状态.
- 剪切当前Path CGContextClip,非零绕组规则
void CGContextClip ( CGContextRef c );
- 剪切当前Path CGContextClip,奇偶规则
void CGContextEOClip ( CGContextRef c );
- 矩形框剪切CGContextClipToRect
void CGContextClipToRect ( CGContextRef c, CGRect rect );
- 一系列矩形框剪切CGContextClipToRects
void CGContextClipToRects ( CGContextRef c, const CGRect *rects, size_t count );
- 获取剪切路径frame
CGRect CGContextGetClipBoundingBox ( CGContextRef c );
注:bounding box是包括了剪切路径中所有点的矩形.
- 创建Mask CGContextClipToMask
void CGContextClipToMask ( CGContextRef c, CGRect rect, CGImageRef mask );
遮罩分为两种:image mask 和 mask image.写到这儿,本来我想展开解释,但是苹果在mask方面的函数命名,和API解释,感觉都不统一,把人绕得云山雾罩.所以,我们还是抛开苹果来解释吧.
此函数就是给指定矩形与当前剪切区的重叠部分增加一个遮罩.上面提到的两种遮罩,就是指重叠区怎么展示,是重叠区展示,还是非重叠区展示(通过alpha通道来区分).
我们常用的就是给一个图片,图片中alpha为1的重叠区是全透过,alpha为0则不透过.
绘图
- 绘制图片
void CGContextDrawImage ( CGContextRef c, CGRect rect, CGImageRef image );
其中,rect指定图片尺寸和起始点.
- 平铺
void CGContextDrawTiledImage ( CGContextRef c, CGRect rect, CGImageRef image );
其中,rect指定平铺图片的尺寸和平铺起始点.
转换用户空间
- 合并转换CGContextConcatCTM
void CGContextConcatCTM ( CGContextRef c, CGAffineTransform transform );
- 获取当前的转换仿射矩阵CGContextGetCTM
CGAffineTransform CGContextGetCTM ( CGContextRef c );
- 旋转CGContextRotateCTM
void CGContextRotateCTM ( CGContextRef c, CGFloat angle );
- 缩放CGContextScaleCTM
void CGContextScaleCTM ( CGContextRef c, CGFloat sx, CGFloat sy );
- 平移CGContextTranslateCTM
void CGContextTranslateCTM ( CGContextRef c, CGFloat tx, CGFloat ty );
示例1:绘制PDF
// 我们期望PDF的绘制是以左下角作为坐标原点的,所以先将当前坐标矩阵下移,然后翻转
CGContextTranslateCTM(context, 0.0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
// Grab the first PDF page
CGPDFPageRef page = CGPDFDocumentGetPage(self.pdfDocument, 1);
// We're about to modify the context CTM to draw the PDF page where we want it, so save the graphics state in case we want to do more drawing
CGContextSaveGState(context);
// CGPDFPageGetDrawingTransform provides an easy way to get the transform for a PDF page. It will scale down to fit, including any
// base rotations necessary to display the PDF page correctly.
CGAffineTransform pdfTransform = CGPDFPageGetDrawingTransform(page, kCGPDFCropBox, self.bounds, 0, true);
// 合并仿射矩阵
CGContextConcatCTM(context, pdfTransform);
// Finally, we draw the page and restore the graphics state for further manipulations!
CGContextDrawPDFPage(context, page);
CGContextRestoreGState(context);
绘制文本
示例1:绘制文本
#define kTextString "Hello From Quartz"
#define kTextStringLength strlen(kTextString)
===========================================================================================
CGContextSetRGBFillColor(context, 1.0, 1.0, 1.0, 1.0);
CGContextSetRGBStrokeColor(context, 1.0, 0.0, 0.0, 1.0);
CGContextSelectFont(context, "Helvetica", 36.0, kCGEncodingMacRoman);
CGContextSetTextMatrix(context, CGAffineTransformMakeScale(1.0, -1.0));
CGContextSetTextDrawingMode(context, kCGTextFill);
CGContextShowTextAtPoint(context, 10.0, 30.0, kTextString, kTextStringLength);
CGContextSetTextDrawingMode(context, kCGTextStroke);
CGContextShowTextAtPoint(context, 10.0, 70.0, kTextString, kTextStringLength);
CGContextSetTextDrawingMode(context, kCGTextFillStroke);
CGContextShowTextAtPoint(context, 10.0, 110.0, kTextString, kTextStringLength);
CGFontRef helvetica = CGFontCreateWithFontName((CFStringRef)@"Helvetica");
CGContextSetFont(context, helvetica);
CGContextSetFontSize(context, 12.0);
CGContextSetTextDrawingMode(context, kCGTextFill);