iOS_24_画画板(含取色板)

来源:互联网 发布:网络拓扑图服务器图标 编辑:程序博客网 时间:2024/04/27 13:31

最终效果如下:



一、简单说明 1、使用一个数组 strokesArr(笔画数组)记录所有笔画,数组中保存的是一个个的笔画字典,一个字典就是一个笔画,笔画字典中有三项:笔画的大小、颜色、pointsArrInOneStroke数组,(保存的是touch  begin时的落笔点和touch move过程中经过的点)  2、绘制的时候,从strokesArr(笔画数组)里取出每一个字典(一个字典就是一个笔画),根据字典中笔画的大小、颜色、笔画所经过的点坐标(pointsArrInOneStroke数组),使用UIBezierPath类完成笔画绘制  二、撤销和回撤 一个笔画就是一个字典。  撤销: 使用abandonedStrokesArr (被丢弃的笔画数组)保存要撤销的笔画,即所有笔画数组中的最后一划, 同时将 strokesArr 笔画数组中的最后一个元素删除。 反之,重做: 即将abandonedStrokesArr (被丢弃的笔画数组)中最后一个元素添加到所有笔画数组中,同时将(被丢弃的笔画数组)中的最后一个元素删除。


Main.storyboard





主控制器



Canvas类封装了画画的所有核心代码


方法列表


////  Canvas.h//  24_Canvas画画板////  Created by beyond on 14-8-26.//  Copyright (c) 2014年 com.beyond. All rights reserved./* 一、简单说明 1、使用一个数组 strokesArr(笔画数组)记录所有笔画,数组中保存的是一个个的笔画字典,一个字典就是一个笔画,笔画字典中有三项:笔画的大小、颜色、pointsArrInOneStroke数组,(保存的是touch  begin时的落笔点和touch move过程中经过的点)  2、绘制的时候,从strokesArr(笔画数组)里取出每一个字典(一个字典就是一个笔画),根据字典中笔画的大小、颜色、笔画所经过的点坐标(pointsArrInOneStroke数组),使用UIBezierPath类完成笔画绘制  二、撤销和回撤 一个笔画就是一个字典。  撤销: 使用abandonedStrokesArr (被丢弃的笔画数组)保存要撤销的笔画,即所有笔画数组中的最后一划, 同时将 strokesArr 笔画数组中的最后一个元素删除。 反之,重做: 即将abandonedStrokesArr (被丢弃的笔画数组)中最后一个元素添加到所有笔画数组中,同时将(被丢弃的笔画数组)中的最后一个元素删除。 */#import <UIKit/UIKit.h>// 自定义的颜色选择控制器,点击之后,它会告诉代理,选中了什么颜色@class ColorPickerController;@interface Canvas : UIView#pragma mark - 属性列表 // 标签,显示笔刷大小@property (nonatomic,retain) IBOutlet UILabel *labelSize;// 滑块 笔刷大小@property (nonatomic,retain) IBOutlet UISlider *sliderSize;// 三个按钮,分别是撤销、重做、清除@property (nonatomic,retain) IBOutlet UIBarButtonItem *undoBtn;@property (nonatomic,retain) IBOutlet UIBarButtonItem *redoBtn;@property (nonatomic,retain) IBOutlet UIBarButtonItem *clearBtn;// toolBar,目的是截图的时候,隐藏掉toolBar@property (nonatomic,retain) IBOutlet UIToolbar *toolBar;#pragma mark - 方法列表// 初始化所有的准备工作-(void) viewJustLoaded;// 选择相册 被点击-(IBAction) didClickChoosePhoto;// 滑块滑动,设置笔刷大小-(IBAction) setBrushSize:(UISlider*)sender;// 撤销 被点击-(IBAction) undo;// 重做 被点击-(IBAction) redo;// 清除画布 被点击-(IBAction) clearCanvas;// 保存图片 被点击-(IBAction) savePic;// 颜色选择 被点击- (IBAction) didClickColorButton;// 重要~~开放给另一个控制器调用,它在调用代理时,会传入参数:即选择好的颜色- (void) pickedColor:(UIColor*)color;@end


核心代码

////  Canvas.h//  24_Canvas画画板////  Created by beyond on 14-8-26.//  Copyright (c) 2014年 com.beyond. All rights reserved./*   这儿仅仅是做演示demo,直接让Canvas与控制器绑定,开始画画,监听事件   如果,要更好的抽取出来,则需要创建一个模型类(model)来提供数据源(比如_strokesArr,_abandonedStrokesArr),供CanvasView显示   UIView的setNeedsDisplay和setNeedsLayout方法  首先两个方法都是异步执行的。而setNeedsDisplay会调用自动调用drawRect方法,这样可以拿到  UIGraphicsGetCurrentContext,就可以画画了。   UIUserInterfaceIdiomPad   iPad上专用 */#import "Canvas.h"#import "ColorPickerController.h"#import "BeyondViewController.h"@interface Canvas ()<UIImagePickerControllerDelegate,UINavigationControllerDelegate>{    // 所有笔画NSMutableArray *_strokesArr;    // 丢弃(撤销)的笔画NSMutableArray *_abandonedStrokesArr;    // 当前笔刷颜色UIColor *_currentColor;    // 当前的笔刷大小float currentSize;    // 选中的图片UIImage *_pickedImg;    // 截屏图片UIImage *_screenImg;    // 自定义的 颜色选择控制器ColorPickerController *_colorPickerCtrl;    // 相片选择器UIImagePickerController *_imagePickerCtrl;}@end@implementation Canvas#pragma mark - 生命周期方法// 禁止多点触摸-(BOOL)isMultipleTouchEnabled {return NO;}// 最重要的画图方法- (void) drawRect: (CGRect) rect{    // 1.先把获取的图片,画到画布上    [self drawPickedImgToCanvas];    // 2.如果【笔画数组】有笔画字典,则按顺序将笔画取出,画到画布上    [self drawStrokesArrToCanvas];    }// 1.先把获取的图片,画到画布上- (void)drawPickedImgToCanvas{int width = _pickedImg.size.width;int height = _pickedImg.size.height;CGRect rectForImage = CGRectMake(0, 0, width, height);[_pickedImg drawInRect:rectForImage];}// 2.如果【笔画数组】有笔画字典,则按顺序将笔画取出,画到画布上- (void)drawStrokesArrToCanvas{    // 如果【笔画数组】为空,则直接返回if (_strokesArr.count == 0) return;    // 遍历【笔画数组】,取出每一个笔画字典,每一次迭代,画一个stroke    for (NSDictionary *oneStrokeDict in _strokesArr)    {        // 取出点数组        NSArray *pointsArr = [oneStrokeDict objectForKey:@"points"];        // 取出颜色        UIColor *color = [oneStrokeDict objectForKey:@"color"];        // 取出笔刷尺寸        float size = [[oneStrokeDict objectForKey:@"size"] floatValue];        // 设置颜色        [color set];        // line segments within a single stroke (path) has the same color and line width        // 画一个stroke, 一条接着一条,使用圆接头 round joint        // 创建一个贝塞尔路径        UIBezierPath* bezierPath = [UIBezierPath bezierPath];        // 点数组 中的第一个,就是 起点        CGPoint startPoint = CGPointFromString([pointsArr objectAtIndex:0]);        // 将路径移动到 起点        [bezierPath moveToPoint:startPoint];        // 遍历点数组,将每一个点,依次添加到 bezierPath        for (int i = 0; i < (pointsArr.count - 1); i++)        {            // 依次取出下一个点            CGPoint pointNext = CGPointFromString([pointsArr objectAtIndex:i+1]);            // 添加到路径            [bezierPath addLineToPoint:pointNext];        }        // 设置线宽        bezierPath.lineWidth = size;        // 线连接处为 圆结头        bezierPath.lineJoinStyle = kCGLineJoinRound;        // 线两端为 圆角        bezierPath.lineCapStyle = kCGLineCapRound;        // 调用路径的方法 画出一条线        [bezierPath stroke];    }}// 重要~~~初始化所有东东-(void) viewJustLoaded {    // 1.初始化颜色选择控制器    [self addColorPickerCtrl];// 2.初始化【相片选择器】    [self addUIImagePickerCtrl];    // 3.其他成员初始化    // 【笔画数组】_strokesArr = [NSMutableArray array];    // 【被丢弃的笔画数组】_abandonedStrokesArr = [NSMutableArray array];    // 笔画大小currentSize = 5.0;    // toolBar上笔画标签显示文字self.labelSize.text = @"Size: 5";    // 设置笔刷 黑色[self setStrokeColor:[UIColor blackColor]];            // 4.设置重做、撤销、清空三个按钮的状态    [self updateToolBarBtnStatus];}// 1.初始化颜色选择控制器- (void)addColorPickerCtrl{    // 1.添加【颜色选择控制器】ColorPickerController,因为要添加到主控制器中    BeyondViewController *mainVC = [BeyondViewController sharedBeyondViewController];// 初始化自己封装的颜色选择控制器,并设置好代理,目的是颜色设置好了之后,回调告诉当前的canvas画布_colorPickerCtrl = [[ColorPickerController alloc] init];_colorPickerCtrl.pickedColorDelegate = self;    // 控制器成为父子关系,视图也成为父子关系    [mainVC addChildViewController:_colorPickerCtrl];    [mainVC.view addSubview:_colorPickerCtrl.view];    // 暂时隐藏【颜色选择控制器】,只有在点击了ToolBar上面的按钮时候,才显示出来    _colorPickerCtrl.view.hidden = YES;}// 2.初始化【相片选择器】- (void)addUIImagePickerCtrl{if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]) {_imagePickerCtrl = [[UIImagePickerController alloc] init];_imagePickerCtrl.delegate = self;_imagePickerCtrl.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;        // 2) 设置允许修改        // [_imagePickerCtrl setAllowsEditing:YES];}}// 3.自定义方法,设置 撤销、重做、清空三个按钮的可点击状态- (void)updateToolBarBtnStatus{    _redoBtn.enabled = _abandonedStrokesArr.count > 0;    _undoBtn.enabled = _strokesArr.count > 0;    _clearBtn.enabled = _strokesArr.count > 0;    }#pragma mark - 控件连线方法// 滑块滑动- (IBAction)setBrushSize:(UISlider*)sender{currentSize = sender.value;self.labelSize.text = [NSString stringWithFormat:@"Size: %.0f",sender.value];}// 撤销按钮点击事件-(IBAction) undo {    // 如果笔画数组中有笔画字典if ([_strokesArr count]>0) {        // 最后一个笔画字典,即,被丢弃的笔画字典NSMutableDictionary* abandonedStrokeDict = [_strokesArr lastObject];        // 将最后一个笔画字典,添加到被丢弃的笔画字典数组里面保存,以供drawRect[_abandonedStrokesArr addObject:abandonedStrokeDict];        // 从所有笔画数组中移除掉最后一笔[_strokesArr removeLastObject];        // 重新调用drawRect进行绘制[self setNeedsDisplay];}                // 2.设置重做、撤销、清空三个按钮的状态    [self updateToolBarBtnStatus];}// 重做-(IBAction) redo {    // 如果 被丢弃的笔画数组,里面有值if ([_abandonedStrokesArr count]>0) {        // 取出最后一个被仍进来的 笔画字典,(即最先书写的,而且是在撤销的操作里面,最后被添加到【被丢弃的笔画数组】)NSMutableDictionary* redoStrokeDict = [_abandonedStrokesArr lastObject];        // 将需要重画的笔画字典,添加到【所有笔画数组】中[_strokesArr addObject:redoStrokeDict];        // 并且,从【被丢弃的笔画数组】中移除,该笔画字典[_abandonedStrokesArr removeLastObject];        // 重新调用drawRect进行绘制[self setNeedsDisplay];}                // 2.设置重做、撤销、清空三个按钮的状态    [self updateToolBarBtnStatus];}// 清空画布,只需清空【所有笔画数组】和【被丢弃的笔画数组】-(IBAction) clearCanvas {    // 建议不要将选择出来的背景图片清空,只清空没写好的笔画算了// _pickedImg = nil;[_strokesArr removeAllObjects];[_abandonedStrokesArr removeAllObjects];    // 重新调用drawRect进行绘制[self setNeedsDisplay];                // 2.设置重做、撤销、清空三个按钮的状态    [self updateToolBarBtnStatus];}// 保存图片-(IBAction) savePic {// 暂时移除 工具条//[_toolBar removeFromSuperview];    // 截图代码    // 1,开启上下文UIGraphicsBeginImageContext(self.bounds.size);    // 2.将图层渲染到上下文[self.layer renderInContext:UIGraphicsGetCurrentContext()];    // 开启上下文,使用参数之后,截出来的是原图(YES  0.0 质量高)    //UIGraphicsBeginImageContextWithOptions(self.frame.size, YES, 0.0);    // 3.从上下文中取出图片_screenImg = UIGraphicsGetImageFromCurrentImageContext();    // 4.关闭上下文UIGraphicsEndImageContext();// 重新添加 工具条,并置最上方//[self addSubview:_toolBar];//[self bringSubviewToFront:self.labelSize];// 调用自定义方法,保存截屏到相册[self performSelector:@selector(saveToPhoto) withObject:nil afterDelay:0.0];}// 自定义方法,保存截屏到相册-(void) saveToPhoto {// 一句话,写到相册UIImageWriteToSavedPhotosAlbum(_screenImg, nil, nil, nil);    // UIAlertView 提示成功UIAlertView* alertView= [[UIAlertView alloc] initWithTitle:nil message:@"Image Saved" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];[alertView show];    }// 点击选择颜色按钮- (IBAction) didClickColorButton {    // 显示或隐藏 自己的【颜色选择控制器】    _colorPickerCtrl.view.hidden = !_colorPickerCtrl.view.hidden;    }// 当_colorPickerCtrl选择颜色完毕,会调用代理 的本方法- (void) pickedColor:(UIColor*)color {    // 将【颜色选择控制器】,回调的颜色,设置到控件上,并隐藏 【颜色选择控制器】[self setStrokeColor:color];    _colorPickerCtrl.view.hidden = !_colorPickerCtrl.view.hidden;}// 重要,设置笔刷 新的颜色-(void) setStrokeColor:(UIColor*)newColor{_currentColor = newColor;}// 点击,选择相片按钮-(IBAction) didClickChoosePhoto {    // 展现,相片选择控制器    [self addSubview:_imagePickerCtrl.view];}#pragma mark - imagePicker代理方法- (void)imagePickerController:(UIImagePickerController *)pickerdidFinishPickingMediaWithInfo:(NSDictionary *)info{    // 必须手动,关闭照片选择器    [picker.view removeFromSuperview];    // 从info字典得到编辑后的照片【UIImagePickerControllerEditedImage】    _pickedImg = [info valueForKey:@"UIImagePickerControllerOriginalImage"];    // 将图片画到画板上去    [self setNeedsDisplay];}// 【相片选择器】的代理方法,点击取消时,也要隐藏相片选择器- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker{    [_imagePickerCtrl.view removeFromSuperview];}#pragma mark - 核心代码,重要~~~画布上手势处理// 手势开始(画笔落下)// 开始一个新的字典,为每一笔,包括点 和 颜色// Start new dictionary for each touch, with points and color- (void) touchesBegan:(NSSet *) touches withEvent:(UIEvent *) event{    // 一个笔画中的所有点,触摸开始时的【起点】NSMutableArray *pointsArrInOneStroke = [NSMutableArray array];NSMutableDictionary *strokeDict = [NSMutableDictionary dictionary];[strokeDict setObject:pointsArrInOneStroke forKey:@"points"];    // 笔的颜色[strokeDict setObject:_currentColor forKey:@"color"];    // 笔的大小[strokeDict setObject:[NSNumber numberWithFloat:currentSize] forKey:@"size"];    // 落笔点CGPoint point = [[touches anyObject] locationInView:self];[pointsArrInOneStroke addObject:NSStringFromCGPoint(point)];[_strokesArr addObject:strokeDict];}// 将每一个点添加到 点数组// Add each point to points array- (void) touchesMoved:(NSSet *) touches withEvent:(UIEvent *) event{    // 移动后的一个点CGPoint point = [[touches anyObject] locationInView:self];    // 前一个点CGPoint prevPoint = [[touches anyObject] previousLocationInView:self];    // 字典中先前的点数组NSMutableArray *pointsArrInOneStroke = [[_strokesArr lastObject] objectForKey:@"points"];    // 在后面追加 新的点[pointsArrInOneStroke addObject:NSStringFromCGPoint(point)];CGRect rectToRedraw = CGRectMake(\ ((prevPoint.x>point.x)?point.x:prevPoint.x)-currentSize,\ ((prevPoint.y>point.y)?point.y:prevPoint.y)-currentSize,\ fabs(point.x-prevPoint.x)+2*currentSize,\ fabs(point.y-prevPoint.y)+2*currentSize\ );[self setNeedsDisplayInRect:rectToRedraw];}// 手势结束(画笔抬起)// Send over new trace when the touch ends- (void) touchesEnded:(NSSet *) touches withEvent:(UIEvent *) event{[_abandonedStrokesArr removeAllObjects];            // 2.设置重做、撤销、清空三个按钮的状态    [self updateToolBarBtnStatus];    }@end



颜色选择控制器

ColorPickerController



////  ColorPickerController.h//  24_Canvas画画板////  Created by beyond on 14-8-26.//  Copyright (c) 2014年 com.beyond. All rights reserved.//#import <UIKit/UIKit.h>@interface ColorPickerController : UIViewController #pragma mark - 属性列表// xib上的imgView@property (nonatomic,retain) IBOutlet UIImageView *imgView;// 代理用weak@property (weak) id pickedColorDelegate;#pragma mark - 方法列表// 核心,根据位图引用 创建基于该位图的上下文对象- (CGContextRef) createARGBBitmapContextFromImage:(CGImageRef)inImage;// 核心,根据触摸点,从上下文中取出对应位置像素点的颜色值- (UIColor*) getPixelColorAtLocation:(CGPoint)point;@end



核心代码

////  ColorPickerController.m//  24_Canvas画画板////  Created by beyond on 14-8-26.//  Copyright (c) 2014年 com.beyond. All rights reserved.//#import "ColorPickerController.h"#import "Canvas.h"@implementation ColorPickerController#pragma mark - 点击结束- (void) touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {    UITouch* touch = [touches anyObject];    // tap点击的位置CGPoint point = [touch locationInView:self.imgView];        // 1.调用自定义方法,从【点】中取颜色UIColor *selectedColor = [self getPixelColorAtLocation:point];    // 2.告诉代理,解析出来的颜色[_pickedColorDelegate pickedColor:selectedColor];}// 核心代码:关于下面两个方法更多的详细资料,敬请查阅【iOS Developer Library 】#pragma mark - 核心代码,将图片写入内存,再依据【点】中取颜色- (UIColor *) getPixelColorAtLocation:(CGPoint)point{UIColor *color = nil;    // 得到取色图片的引用CGImageRef colorImage = _imgView.image.CGImage;    // Create off screen bitmap context to draw the image into. Format ARGB is 4 bytes for each pixel: Alpa, Red, Green, Blue        // 调用自定义方法:从_imgView里面的image的引用,创建并返回对应的上下文CGContextRef contexRef = [self createARGBBitmapContextFromImage:colorImage];    // 如果创建该图片对应的上下文失败if (contexRef == NULL){        NSLog(@"取色图片--创建对应的上下文失败~");        return nil;    }// 准备将【取色图片】写入刚才创建出来的上下文    size_t w = CGImageGetWidth(colorImage);// problem!size_t h = CGImageGetHeight(colorImage);CGRect rect = {{0,0},{w,h}};     log_rect(rect)    // 调试输出rect:--{{0, 0}, {225, 250}}        int bytesPerRow = CGBitmapContextGetBytesPerRow(contexRef);    log_int(bytesPerRow) //调试输出int:--900            // Draw the image to the bitmap context. Once we draw, the memory // allocated for the context for rendering will then contain the // raw image data in the specified color space.    // 将位图写入(渲染)已经分配好的内存区域CGContextDrawImage(contexRef, rect, colorImage);    // 得到位图上下文 内存数据块的首地址,用指针记住,作为基地址unsigned char* dataPoint = CGBitmapContextGetData (contexRef);    NSLog(@"----首地址,指针%p",dataPoint);    // ----首地址,指针0x8b3f000            if (dataPoint != NULL) {//offset 即:根据触摸点的xy,定位到位图内存空间中的一个特定像素//4 的意思是每一个像素点,占4个字节        // w是每一行所有点的总数        // 根据所在行,所在列,算出在内存块中的偏移地址,然后乘以4,因为每一个点在内存中占四个字节int offset = 4*((w*round(point.y))+round(point.x));        // alpha 为内存基地址+偏移地址int alpha =  dataPoint[offset];        // red 为内存基地址+偏移地址+1   其他类似int red = dataPoint[offset+1];int green = dataPoint[offset+2];int blue = dataPoint[offset+3];        NSLog(@"偏移地址: %i colors: RGBA %i %i %i  %i",offset,red,green,blue,alpha);        // offset: 150908 colors: RGB A 255 0 254  255                // 根据RGBA 生成颜色对象color = [UIColor colorWithRed:(red/255.0f) green:(green/255.0f) blue:(blue/255.0f) alpha:(alpha/255.0f)];}// 操作完成后,释放上下文对象CGContextRelease(contexRef); // 从内存中释放掉 加载到内存的图像数据if (dataPoint) {        free(dataPoint);    }return color;}// 自定义方法2:通过_imgView里面的image的引用,创建并返回对应的上下文- (CGContextRef) createARGBBitmapContextFromImage:(CGImageRef) inImage{    // 要创建的上下文CGContextRef    context = NULL;    // 色彩空间CGColorSpaceRef colorSpace;    // 位图数据在内存空间的首地址void *          bitmapData;    // 每一行的字节数int             bitmapBytesPerRow;    // 图片总的占的字节数int             bitmapByteCount;    // 得到图片的宽度和高度,将要使用整个图片,创建上下文size_t pixelsWide = CGImageGetWidth(inImage);size_t pixelsHigh = CGImageGetHeight(inImage);// 每一行占多少字节. 本取色图片中的每一个像素点占4个字节;    // 红 绿 蓝 透明度 各占一个字节(8位  取值范围0~255)    // 每一行的字节数,因为每一个像素点占4个字节(包含RGBA)(其中一个R就是一个字节,占8位,取值是2的8次方 0~255)bitmapBytesPerRow   = (pixelsWide * 4);    // 图片总的占的字节数bitmapByteCount     = (bitmapBytesPerRow * pixelsHigh);// 使用指定的 色彩空间(RGB)colorSpace = CGColorSpaceCreateDeviceRGB();if (colorSpace == NULL){fprintf(stderr, "创建并分配色彩空间 出错\n");return NULL;}// This is the destination in memory// where any drawing to the bitmap context will be rendered.    // 为取色图片数据  分配所有的内存空间    // 所有画到取色图片上下文的操作,都将被渲染到此内存空间bitmapData = malloc( bitmapByteCount );if (bitmapData == NULL) {fprintf (stderr, "内存空间分配失败~");CGColorSpaceRelease( colorSpace );return NULL;}// 创建位图上下文. 使用 pre-multiplied ARGB, ARGB中的每一个成员都占8个bit位,即一字节,一个像素共占4个字节// 无论原取色图片的格式是什么(CMYK或Grayscale),都将通过CGBitmapContextCreate方法,转成指定的ARGB格式context = CGBitmapContextCreate (bitmapData, pixelsWide, pixelsHigh, 8,      // bits per component bitmapBytesPerRow, colorSpace,                                    kCGImageAlphaPremultipliedFirst);if (context == NULL){free (bitmapData);fprintf (stderr, "位图上下文创建失败~");}// 在返回上下文之前 必须记得释放 色彩空间CGColorSpaceRelease( colorSpace );return context;}@end








1 0
原创粉丝点击