画笔-新方案2

数据模型配置

将线条属性移至DrawPageData模型中。

1
2
3
4
5
@interface DrawPageData : NSObject
@property(nonatomic,strong) NSMutableArray *nowPointArray; //当前点
@property(nonatomic,strong) NSMutableArray *lineColorsArray;//每条线对应的颜色
@property(nonatomic,strong) NSMutableArray *linePointArray; //所有线
@end

CLDrawTool 中 新增两属性用来记录所有图层以及图层所对应的划线数据模型。
1
2
@property(nonatomic,strong) NSMutableArray *drawImageViews; //[imageView]
@property(nonatomic,strong) NSMutableArray *drawPages; //[DrawPageData]

撤销:
1
2
3
_lastDrawPage.linePointArray removeLastObject 移除最后一条线;
_lastDrawPage.lineColorsArray removeLastObject 移除颜色;
clearTopDrawImageView 移除最顶层的画笔图片层。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
  @property(nonatomic,strong) DrawPageData _lastDrawPage; //定义一个当前页记录模型

### 画笔撤销事件
-(void)eraserButtonDidTap:(UIButton *)btn{
if (_lastDrawPage.linePointArray && _lastDrawPage.linePointArray.count >= 1) {
[_lastDrawPage.linePointArray removeLastObject];
[_lastDrawPage.lineColorsArray removeLastObject];
[self drawAllPanLine:_drawingView byDrawPage:_lastDrawPage];

//当前画笔层所有线都移除了的情况下,自动移除当前画笔层
if(_lastDrawPage.linePointArray.count == 0){
//当只有一页的情况下不做处理。
if(self.drawImageViews.count > 1){
[self clearTopDrawImageView];
}
}
}else{
//当只有一页的情况下不做处理。
if(self.drawPages.count > 1){
[self clearTopDrawImageView];
[self eraserButtonDidTap:nil]; //递归移除空的drawImageView
}
}
}

//清除顶层的画笔 imageview
-(void)clearTopDrawImageView{
[self.drawPages removeLastObject];
[((UIImageView *)self.drawImageViews.lastObject) removeFromSuperview];
[self.drawImageViews removeLastObject];

//重置当前的层级page
_lastDrawPage = self.drawPages.lastObject;
_drawingView = self.drawImageViews.lastObject;

}

画笔图层渲染

  • 现在的渲染逻辑是每个 DrawPage 里面的 linePointArray 只会有一条线,外层 for 循环其实可以省略。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//传入drawImageView 和 drawPageData 进行划线
-(void)drawAllPanLine:(UIImageView *)drawImageView byDrawPage:(DrawPageData *)lastDrawPage
{
CGSize size = drawImageView.frame.size;
UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);

//遍历每一条线
for (int i = 0 ; i < lastDrawPage.linePointArray.count ; i ++ ) {
NSMutableArray *pointArray = [lastDrawPage.linePointArray objectAtIndex:i];

CGContextRef context = UIGraphicsGetCurrentContext();
CGFloat strokeWidth = 6.5;
CGContextSetLineWidth(context, strokeWidth);
CGContextSetLineJoin(context, kCGLineJoinRound);
CGContextSetLineCap(context, kCGLineCapRound);


UIColor *strokeColor = lastDrawPage.lineColorsArray[i]; //得出每条线的颜色
CGContextSetStrokeColorWithColor(context, strokeColor.CGColor);

//一条线的每个点
for (int j = 0 ; j < pointArray.count ; j ++ ) {
NSValue *value = [pointArray objectAtIndex:j];
CGPoint p = [value CGPointValue];
if (j == 0) {
CGContextMoveToPoint(context, p.x, p.y);
CGContextAddLineToPoint(context, p.x, p.y);
}else{
CGContextAddLineToPoint(context, p.x, p.y);
}

}
CGContextStrokePath(context);
}

drawImageView.image = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();
}

Pan 手势的事件处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
- (void)drawingViewDidPan:(UIPanGestureRecognizer*)sender
{
if([[self.editor currentEditTool] isKindOfClass:[CLMosaicTool class]]){
//手势功能传递,所有tool图层都叠在一起。因马赛克图层在最底层,涂鸦在马赛克上层,文字层在最顶层。
CLMosaicTool *mosaicTool = [self.editor.usedToolDic objectForKey:@"CLMosaicTool"];
[mosaicTool drawingViewDidPan:sender];
return;
}

CGPoint currentDraggingPosition = [sender locationInView:_drawingView];

if(sender.state == UIGestureRecognizerStateBegan){
_lastDrawPage.nowPointArray = [[NSMutableArray alloc] init];
UIColor *currentLineStrokeColor = _strokePreview.backgroundColor; //记录当前画线颜色,因为用户可能切换颜色。
[_lastDrawPage.lineColorsArray addObject:currentLineStrokeColor];
[_lastDrawPage.linePointArray addObject:_lastDrawPage.nowPointArray];
_prevDraggingPosition = currentDraggingPosition;
}

//记录当前的点
NSValue *point = [NSValue valueWithCGPoint:currentDraggingPosition];
[_lastDrawPage.nowPointArray addObject:point];

[self drawAllPanLine:_drawingView byDrawPage:_lastDrawPage];

_prevDraggingPosition = currentDraggingPosition;

if(sender.state == UIGestureRecognizerStateEnded){
//结束的时候,再创建一张图drawImageView。
[self generateDrawImageView]; //如果要在一张图上画多条线,则屏蔽掉此局。
}
}
  • 画完一条线生成一张图层
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
-(void)generateDrawImageView{
DrawPageData *pageData = [[DrawPageData alloc] init];
[self.drawPages addObject:pageData];
_lastDrawPage = pageData;

UIImageView *lastDrawImageView = [[UIImageView alloc] initWithFrame:self.editor.imageView.bounds];
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(drawingViewDidPan:)];
panGesture.maximumNumberOfTouches = 1;
lastDrawImageView.userInteractionEnabled = YES;
lastDrawImageView.tag = 100 + self.drawImageViews.count;
[lastDrawImageView addGestureRecognizer:panGesture];

if(self.drawImageViews.count){
[self.editor.imageView insertSubview:lastDrawImageView aboveSubview:_drawingView];
}else{
[self.editor.imageView addSubview:lastDrawImageView];
}

[self.drawImageViews addObject:lastDrawImageView];
_drawingView = lastDrawImageView;
}

画笔图层图片旋转

以下代码在CLClipping旋转或裁剪操作完成时候的回调需要对涂鸦图层做的image和frame处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
CLDrawTool *_drawTool = [self.editor.usedToolDic objectForKey:@"CLDrawTool"];
if(_drawTool){
//将多张画笔图进行图片源旋转。
for(UIImageView *drawingView in _drawTool.drawImageViews){
UIImage *roateDrawImage = [self buildRoteImage:drawingView.image];
//需要对imageviews进行旋转
CGFloat roateZoomScale = _rotateImageView.width / roateDrawImage.size.width;
CGRect roateRct = _gridView.clippingRect;
roateRct.size.width /= roateZoomScale;
roateRct.size.height /= roateZoomScale;
roateRct.origin.x /= roateZoomScale;
roateRct.origin.y /= roateZoomScale;
UIImage *roateCropDrawImage = [roateDrawImage crop:roateRct];
drawingView.image = roateCropDrawImage; //设置裁剪后的图片。
[self.editor resetDrawImageViewFrame:roateCropDrawImage withDrawImageView:drawingView];
}
}

马赛克-新方案

撤销的操作(新)

新方案的撤销是和画笔流程解决方案一致,都是每完成一次触摸涂抹马赛克,则在表面覆盖一张图片.

在MosaicPageData 数据源基础上新增两个layer属性。
CAShapeLayer shapeLayer;
CALayer
imageLayer;

操作原理:通过设置当前PageData的 shapeLayer.path 来实现路径涂抹。然后将imageLayer渲染到画布上[lastMosaicPage.imageLayer renderInContext:context];得到image赋值给当前上层的drawImageView.

buildImage:图片合成。
先将原图绘制,然后将每一张马赛克图层的image进行绘制。最后得到合成的马赛克图片。具体参照CLMosialTool buildImage方法。

//准备好原图的马赛克虚化图片。

1
2
3
4
5
6
7
8
9
10
11
12
13
 _mohuImage = [XRGBTool getMosaicImageWith:image level:0];

_shapeLayer = [CAShapeLayer layer];
_shapeLayer.frame = _drawingView.bounds;
_shapeLayer.lineCap = kCALineCapRound;
_shapeLayer.lineJoin = kCALineJoinRound;
_shapeLayer.lineWidth = 15;
_shapeLayer.strokeColor = [UIColor blueColor].CGColor;
_shapeLayer.fillColor = nil;//此处必须设为nil,否则后边添加addLine的时候会自动填充
_imageLayer = [CALayer layer];
_imageLayer.frame = _drawingView.bounds;
_imageLayer.contents = (id)_mohuImage.CGImage;
_imageLayer.mask = _shapeLayer;

Pan 手势的事件处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
- (void)drawingViewDidPan:(UIPanGestureRecognizer*)sender
{
CGPoint currentDraggingPosition = [sender locationInView:_drawingView];

if(sender.state == UIGestureRecognizerStateBegan){
_prevDraggingPosition = currentDraggingPosition;
_lastMosaicPage.nowPointArray = [[NSMutableArray alloc] init];
[_lastMosaicPage.linePointArray addObject:_lastMosaicPage.nowPointArray];

//无撤销时候的单次马赛克涂抹
// CGPathMoveToPoint(_path, nil, _prevDraggingPosition.x, _prevDraggingPosition.y);
// _shapeLayer.path = _path;
}

// if(sender.state != UIGestureRecognizerStateEnded){
//
// CGPathAddLineToPoint(_path, nil, currentDraggingPosition.x, currentDraggingPosition.y);
// _shapeLayer.path = _path;
// }

NSValue *point = [NSValue valueWithCGPoint:currentDraggingPosition];
[_lastMosaicPage.nowPointArray addObject:point];
[self drawAllPanLine:_drawingView byDrawPage:_lastMosaicPage];
_prevDraggingPosition = currentDraggingPosition;

if(sender.state == UIGestureRecognizerStateEnded){
//结束的时候,再创建一张图drawImageView。
[self generateDrawImageView]; //如果要在一张图上画多条线,则屏蔽掉此局。
}
}

-(void)generateDrawImageView{
MosaicPageData *pageData = [[MosaicPageData alloc] init];
[self.mosaicPages addObject:pageData];
_lastMosaicPage = pageData;

UIImage *_mohuImage = [XRGBTool getMosaicImageWith:self.editor.imageView.image level:0];
UIImageView *drawingView = [[UIImageView alloc] initWithFrame:self.editor.imageView.bounds];
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(drawingViewDidPan:)];
panGesture.maximumNumberOfTouches = 1;
drawingView.userInteractionEnabled = YES;
drawingView.tag = 1000 + self.drawImageViews.count;
[drawingView addGestureRecognizer:panGesture];
if(self.drawImageViews.count){
// for(NSInteger tagIndex = 0; tagIndex < self.drawImageViews.count ; tagIndex++){
// UIImageView *imageView = self.drawImageViews[tagIndex];
// [imageView removeFromSuperview];
// }

[self.editor.imageView.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSLog(@"子imageview的层次tag:%d",obj.tag);
}];

// for(NSInteger tagIndex = 0; tagIndex < self.drawImageViews.count ; tagIndex++){
// UIImageView *imageView = self.drawImageViews[tagIndex];
// if(tagIndex == 0){
// [self.editor.imageView insertSubview:imageView atIndex:0]; //底层第一张
// }else{
// [self.editor.imageView insertSubview:imageView aboveSubview:_drawingView];
// }
// }

[self.editor.imageView insertSubview:drawingView aboveSubview:_drawingView];

}else{
[self.editor.imageView insertSubview:drawingView atIndex:0]; //底层第一张
}
// [self.editor.imageView addSubview:drawingView];
[self.drawImageViews addObject:drawingView];
_drawingView = drawingView;


//重新创建?手势停止时候,创建不断往imageview.layer创建 。最终的结果是熏染imageLayer集合渲染在图片上。
CALayer *imageLayer = [CALayer layer];
imageLayer.frame = drawingView.bounds; //self.editor.imageView.bounds;
// [self.editor.imageView.layer insertSublayer:_imageLayer atIndex:0]; //确定马赛克在最底部

// [_drawingView.layer addSublayer:_imageLayer];
imageLayer.contents = (id)_mohuImage.CGImage;

CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.frame = drawingView.bounds; //self.editor.imageView.bounds;
shapeLayer.lineCap = kCALineCapRound;
shapeLayer.lineJoin = kCALineJoinRound;
shapeLayer.lineWidth = 15;
shapeLayer.strokeColor = [UIColor blueColor].CGColor;
shapeLayer.fillColor = nil;//此处必须设为nil,否则后边添加addLine的时候会自动填充
imageLayer.mask = shapeLayer;
_lastMosaicPage.shapeLayer = shapeLayer;
_lastMosaicPage.imageLayer = imageLayer;
}

马赛克图层渲染

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
-(void)drawAllPanLine:(UIImageView *)drawImageView byDrawPage:(MosaicPageData *)lastMosaicPage
{

CGMutablePathRef _path = CGPathCreateMutable();

//遍历每一条线
for (int i = 0 ; i < lastMosaicPage.linePointArray.count ; i ++ ) {
NSMutableArray *pointArray = [lastMosaicPage.linePointArray objectAtIndex:i];

//一条线的每个点
CGMutablePathRef _onePath = CGPathCreateMutable();
for (int j = 0 ; j < pointArray.count ; j ++ ) {
NSValue *value = [pointArray objectAtIndex:j];
CGPoint p = [value CGPointValue];
if (j == 0) {
CGPathMoveToPoint(_onePath, nil, p.x, p.y);
}else{
CGPathAddLineToPoint(_onePath, nil, p.x, p.y);
}
}
CGPathAddPath(_path, nil, _onePath);
}
lastMosaicPage.shapeLayer.path = _path; /* 手指的涂抹路径 */

//画图
CGFloat scale = self.editor.imageView.image.size.width / self.editor.imageView.width;
UIGraphicsBeginImageContextWithOptions(self.editor.imageView.image.size, NO, self.editor.imageView.image.scale);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextScaleCTM(UIGraphicsGetCurrentContext(), scale, scale);
[lastMosaicPage.imageLayer renderInContext:context];
UIImage *tmp = UIGraphicsGetImageFromCurrentImageContext();
drawImageView.image = tmp;
UIGraphicsEndImageContext();
}

### 画笔撤销事件
```objectivec
//撤回上一步
-(void)eraserButtonDidTap:(UIButton *)btn{

if (_lastMosaicPage.linePointArray && _lastMosaicPage.linePointArray.count >= 1) {
[_lastMosaicPage.linePointArray removeLastObject];
[self drawAllPanLine:_drawingView byDrawPage:_lastMosaicPage];

//当前画笔层所有线都移除了的情况下,自动移除当前画笔层
if(_lastMosaicPage.linePointArray.count == 0){
//当只有一页的情况下不做处理。
if(self.drawImageViews.count > 1){
[self clearTopDrawImageView];
}
}
}else{
//当只有一页的情况下不做处理。
if(self.mosaicPages.count > 1){
[self clearTopDrawImageView];
[self eraserButtonDidTap:nil]; //递归移除空的drawImageView
}
}

//单层撤销
// if (_lastDrawPage.linePointArray && _lastDrawPage.linePointArray.count >= 1) {
// [_lastDrawPage.linePointArray removeLastObject];
// [self drawAllPanLine];
// }
}

//清除顶层的画笔imageview
-(void)clearTopDrawImageView{
[self.mosaicPages removeLastObject];
[((UIImageView *)self.drawImageViews.lastObject) removeFromSuperview];
[self.drawImageViews removeLastObject];

//重置当前的层级page
_lastMosaicPage = self.mosaicPages.lastObject;
_drawingView = self.drawImageViews.lastObject;
// [self.editor.imageView bringSubviewToFront:_drawingView]; //将下一层放到最顶层, 移除此句。会遮挡画笔层。
}

- (UIImage*)buildImage
{
CGSize originalImageSize = self.editor.imageView.image.size;
UIGraphicsBeginImageContextWithOptions(originalImageSize, NO, self.editor.imageView.image.scale);
[self.editor.imageView.image drawAtPoint:CGPointZero];

//循环_drawingView数组。
for(UIImageView *eachDrawImageView in self.drawImageViews){
if(eachDrawImageView.image){
[eachDrawImageView.image drawInRect:CGRectMake(0, 0, originalImageSize.width, originalImageSize.height)];
}
}

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

return tmp;
}

文字帖view容器旋转

对workingView 容器进行旋转,设置其transform.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
- (void)resetTextWorkingViewFrame:(UIView *)workingView byResultImage:(UIImage *)image transform:(CGAffineTransform )transform
{
CGRect originFrame = workingView.frame;
CLTextTool *textTool = [self.usedToolDic objectForKey:@"CLTextTool"];
CGFloat offsetBottom = textTool.workingViewBottomOffset;
originFrame.size.height += offsetBottom;
workingView.frame = originFrame; //加上底部的菜单偏离差
_textToolNeedAddBottomFlag = NO;
if(offsetBottom == 0){
_textToolNeedAddBottomFlag = YES;
}

CGSize size = image ? image.size : workingView.frame.size;
CGAffineTransform resultTransform = transform;
if(!CGAffineTransformIsIdentity(workingView.transform)){
//判断是否进行旋转过,然后进行叠加
resultTransform = CGAffineTransformConcat(workingView.transform,transform);
}
workingView.transform = resultTransform; //692, 409,旋转了两次,这个不对。
CGRect workingViewWillFrame = workingView.frame; //409 , 692

if(size.width>0 && size.height>0){
CGFloat ratio = MIN(_scrollView.frame.size.width / size.width, _scrollView.frame.size.height / size.height);
CGFloat W = ratio * size.width * _scrollView.zoomScale;
CGFloat H = ratio * size.height * _scrollView.zoomScale;

CGFloat scaleX = W/workingViewWillFrame.size.width;
CGFloat scaleY = H/(workingViewWillFrame.size.height);
CGFloat willScale = MIN(scaleX, scaleY); //拿到旋转后的缩放比
workingView.transform =CGAffineTransformIdentity; //旋转,加缩放系数。
workingView.transform = CGAffineTransformScale(resultTransform, willScale, willScale);
}

评论