IOS_24 _ Drawing Board (including color palette)
The final result is as follows:
1. Simple Description 1. Use an array strokesArr (stroke array) to record all strokes. The array stores a stroke dictionary. One dictionary is a stroke, and there are three strokes in the dictionary: stroke size, color, pointsArrInOneStroke array (Save the points passed by touch begin and touch move) 2. When drawing, from strokesArr (stroke array) take out each Dictionary (a dictionary is a stroke), and take the pointsArrInOneStroke array according to the size, color, and coordinate of the stroke in the dictionary ), use the UIBezierPath class to draw strokes. 2. Undo and undo a stroke is a dictionary. Undo: Use abandonedStrokesArr (an array of discarded strokes) to save the stroke to be undo, that is, the last stroke in all stroke arrays, and delete the last element in the strokesArr stroke array. Otherwise, redo: add the last element in the abandonedStrokesArr (discarded stroke array) to all stroke arrays, and delete the last element in the discarded stroke array.
Main. storyboard
<喎?http: www.bkjia.com kf ware vc " target="_blank" class="keylink"> VcD4KPHA + PGJyPgo8L3A + CjxwPtb3v9jWxsb3PC9wPgo8cD48aW1nIHNyYz0 = "http://www.2cto.com/uploadfile/Collfiles/20140828/20140828084655121.png" alt = "\">
The Canvas class encapsulates all the core code for painting.
Method list
//// Canvas. h // 24_Canvas canvas /// Created by beyond on 14-8-26. // Copyright (c) 2014 com. beyond. all rights reserved. /* 1. Simple Description 1. Use an array strokesArr (stroke array) to record all strokes. The array stores a stroke dictionary. A dictionary is a stroke, there are three strokes in the dictionary: The stroke size, color, pointsArrInOneStroke array, which stores the points passed during touch in. 2. When drawing, extract each Dictionary (a dictionary is a stroke) from the strokesArr (stroke array), and take the pointsArrInOneStroke Array Based on the size, color, and coordinate of the stroke in the dictionary ), use the UIBezierPath class to draw a stroke. 2. Undo and undo a stroke. . Undo: Use abandonedStrokesArr (an array of discarded strokes) to save the stroke to be undo, that is, the last stroke in all stroke arrays, and delete the last element in the strokesArr stroke array. Otherwise, redo: add the last element in the abandonedStrokesArr (discarded stroke array) to all stroke arrays, and delete the last element in the discarded stroke array. */# Import
// Custom color selection controller. After clicking it, it will tell the proxy what color is selected @ class ColorPickerController; @ interface Canvas: UIView # pragma mark-attribute list // tag, show brush size @ property (nonatomic, retain) IBOutlet UILabel * labelSize; // slider brush size @ property (nonatomic, retain) IBOutlet UISlider * sliderSize; // three buttons, they are undo, redo, and clear @ property (nonatomic, retain) IBOutlet UIBarButtonItem * undoBtn; @ property (nonatomic, retain) IBOutlet UIBarButtonItem * redoBtn; @ property (Nonatomic, retain) IBOutlet UIBarButtonItem * clearBtn; // toolBar. when the goal is to hide toolBar @ property (nonatomic, retain) IBOutlet UIToolbar * toolBar; # pragma mark-method list // Initialize all preparations-(void) viewJustLoaded; // select the album to be clicked-(IBAction) didClickChoosePhoto; // slide the slider, set the brush size-(IBAction) setBrushSize :( UISlider *) sender; // undo-(IBAction) undo; // redo-(IBAction) redo; // clear the canvas click-(IBAction) clearCanvas; // Save the image click-(IBAct) Ion) savePic; // select the color to be clicked-(IBAction) didClickColorButton; // important ~~ Open to another controller call, it will pass in the parameter when calling the Proxy: that is, the selected color-(void) pickedColor :( UIColor *) color; @ end
Core code
//// Canvas. h // 24_Canvas canvas /// Created by beyond on 14-8-26. // Copyright (c) 2014 com. beyond. all rights reserved. /* here is just a demo. You can bind the Canvas to the Controller and start painting. If you want to listen to events for better extraction, you need to create a model class) to provide data sources (such as _ strokesArr, _ abandonedStrokesArr) for CanvasView to display the setNeedsDisplay and setNeedsLayout methods of UIView. First, both methods are executed asynchronously. SetNeedsDisplay will call the drawRect method automatically, so that you can get UIGraphicsGetCurrentContext and draw a picture. UIUserInterfaceIdiomPad for iPad */# import "Canvas. h" # import "ColorPickerController. h" # import "BeyondViewController. h" @ interface Canvas ()
{// All strokes NSMutableArray * _ strokesArr; // discard (UNDO) The Strokes NSMutableArray * _ abandonedStrokesArr; // The current paint color UIColor * _ currentColor; // float currentSize of the current paint brush; // UIImage * _ pickedImg of the selected image; // UIImage * _ screenImg Of The screenshot image; // custom color selection controller ColorPickerController * _ colorPickerCtrl; // photo selector UIImagePickerController * _ imagePickerCtrl ;} @ end @ implementation Canvas # pragma mark-lifecycle method // disable multi-point touch-(BOOL) isMultipleTouchEnabled {Return NO;} // The Most Important Plotting Method-(void) drawRect: (CGRect) rect {// 1. first, draw the obtained image to the canvas [self drawPickedImgToCanvas]; // 2. if the [stroke array] has a stroke dictionary, the strokes are taken out in sequence and painted to the canvas [self drawStrokesArrToCanvas];} // 1. first, draw the obtained image to the canvas-(void) drawPickedImgToCanvas {int width = _ pickedImg. size. width; int height = _ pickedImg. size. height; CGRect rectForImage = CGRectMake (0, 0, width, height); [_ pickedImg drawInRect: rectForImage];} // 2. if the [stroke array] has a stroke dictionary, The Strokes are taken out in order and painted to the canvas-(void) drawStrokesArrToCanvas {// if the [stroke array] is empty, the if (_ strokesArr. count = 0) return; // traverse the [stroke array] to retrieve each stroke dictionary and draw a stroke for (NSDictionary * oneStrokeDict in _ strokesArr) for each iteration) {// retrieve the Dot Array NSArray * pointsArr = [oneStrokeDict objectForKey: @ "points"]; // retrieve the color UIColor * color = [oneStrokeDict objectForKey: @ "color"]; // retrieve the brush size float size = [[oneStrokeDict objectForKey: @ "size"] floatValue]; // Set the color [color set]; // line segments within a single stroke (path) has the same color and line width // draw a stroke, one after the other, use the round joint // to create a sel path UIBezierPath * bezierPath = [UIBezierPath bezierPath]; // the first entry in the vertex array is the starting point CGPoint startPoint = CGPointFromString ([pointsArr objectAtIndex: 0]); // move the path to the starting point [bezierPath moveToPoint: startPoint]; // traverse the vertex array and add each vertex to bezierPath for (int I = 0; I <(p OintsArr. count-1); I ++) {// sequentially retrieve the next vertex CGPoint pointNext = CGPointFromString ([pointsArr objectAtIndex: I + 1]); // Add it to the path [bezierPath addLineToPoint: pointNext];} // set the bezierPath. lineWidth = size; // bezierPath is the circle header at the line link. lineJoinStyle = kCGLineJoinRound; // the two ends of the line are rounded bezierPath. lineCapStyle = kCGLineCapRound; // call the path to draw a line [bezierPath stroke]; }}// important ~~~ Initialize all Dongdong-(void) viewJustLoaded {// 1. initialization color selection controller [self addColorPickerCtrl]; // 2. initialize the [Photo selector] [self addUIImagePickerCtrl]; // 3. other member initialization // [stroke array] _ strokesArr = [NSMutableArray array]; // [discarded stroke array] _ abandonedStrokesArr = [NSMutableArray array]; // stroke size currentSize = 5.0; // The stroke label on the toolBar displays the text self. labelSize. text = @ "Size: 5"; // set the brush black [self setStrokeColor: [UIColor blackColor]; // 4. set the status of the Redo, cancel, and clear buttons [self Updatemedilbarbtnstatus];} // 1. initialize color select controller-(void) addColorPickerCtrl {// 1. add the color selection controller ColorPickerController because BeyondViewController * mainVC = [BeyondViewController sharedBeyondViewController] is added to the master controller. // initialize the color selection controller of your own encapsulation and set the proxy, the purpose is to notify the current canvas _ colorPickerCtrl = [[ColorPickerController alloc] init]; _ colorPickerCtrl after the color is set. pickedColorDelegate = self; // The Controller becomes a parent-child relationship, and the view also becomes a parent-child relationship [mainVC addChildViewCont Roroller: _ colorPickerCtrl]; [mainVC. view addSubview: _ colorPickerCtrl. view]; // temporarily hide the color selection controller. It is displayed only when you click the button on the ToolBar. view. hidden = YES;} // 2. initialize [Photo selector]-(void) addUIImagePickerCtrl {if ([UIImagePickerController isSourceTypeAvailable: available]) {_ imagePickerCtrl = [[UIImagePickerController alloc] init]; _ imagePickerCtrl. delegate = self; _ imagePicke RCtrl. sourceType = UIImagePickerControllerSourceTypePhotoLibrary; // 2) The setting allows modification. // [_ imagePickerCtrl setAllowsEditing: YES]; }}// 3. custom method: Set the clickable status of the UNDO, redo, and clear buttons-(void) updatemedilbarbtnstatus {_ redoBtn. enabled = _ abandonedStrokesArr. count> 0; _ undoBtn. enabled = _ strokesArr. count> 0; _ clearBtn. enabled = _ strokesArr. count> 0 ;}# pragma mark-control line method // slide slider-(IBAction) setBrushSize :( UISlider *) sender {curr EntSize = sender. value; self. labelSize. text = [NSString stringWithFormat: @ "Size: %. 0f ", sender. value];} // click the undo button to click the event-(IBAction) undo {// if the stroke array contains a stroke dictionary if ([_ strokesArr count]> 0) {// the last stroke dictionary, that is, the discarded stroke dictionary NSMutableDictionary * abandonedStrokeDict = [_ strokesArr lastObject]; // you can specify the last stroke dictionary, add it to the discarded stroke dictionary array and save it for drawRect [_ abandonedStrokesArr addObject: abandonedStrokeDict]; // remove the last [_ strokesArr removeLa StObject]; // call drawRect again to draw [self setNeedsDisplay];} // 2. set the status of the redo, cancel, and clear buttons [self updatemedilbarbtnstatus];} // redo-(IBAction) redo {// If the row array is discarded, contains the value if ([_ abandonedStrokesArr count]> 0) {// retrieves the last stroke dictionary that is still in progress (that is, the first written and in the Undo operation, finally, it is added to the [discarded stroke array]) NSMutableDictionary * redoStrokeDict = [_ abandonedStrokesArr lastObject]; // you can specify a dictionary of strokes to be repainted, add to [_ strokesArr addObject: redoStrokeDict] in [all strokes array]; // and Array removed, the stroke dictionary [_ abandonedStrokesArr removeLastObject]; // call drawRect again to draw [self setNeedsDisplay];} // 2. set the status of the Redo, cancel, and clear buttons [self updatemedilbarbtnstatus];} // clear the canvas, just clear the [all stroke arrays] and [discarded stroke arrays]-(IBAction) clearCanvas {// we do not recommend clearing the selected background image, only clear the unwritten strokes. // _ pickedImg = nil; [_ strokesArr removeAllObjects]; [_ abandonedStrokesArr removeAllObjects]; // call drawRect again to draw [self setNeedsDisplay]; // 2. set the status of the Redo, cancel, and clear buttons [se Lf updatemedilbarbtnstatus];} // Save the image-(IBAction) savePic {// temporarily remove the toolBar // [_ toolBar removeFromSuperview]; // code // 1, enable the context UIGraphicsBeginImageContext (self. bounds. size); // 2. render the layer to the context [self. layer renderInContext: UIGraphicsGetCurrentContext ()]; // enable context. After using the parameter, the source image (YES 0.0 high quality) is captured. // uigraphicsbeginimagecontextwitexceptions (self. frame. size, YES, 0.0); // 3. retrieve image _ screenImg = UIGraphicsGetImageFromCurrent from context ImageContext (); // 4. disable the context UIGraphicsEndImageContext (); // Add a toolBar and move it to the top of the toolBar. // [self addSubview: _ toolBar]; // [self bringSubviewToFront: self. labelSize]; // call the custom method to save the screenshot to the album [self capture mselector: @ selector (saveToPhoto) withObject: nil afterDelay: 0.0];} // custom method, save the screenshot to the album-(void) saveToPhoto {// in one sentence, and write it to the album watermark (_ screenImg, nil); // UIAlertView indicates that the image is successfully displayed. Ew alloc] initWithTitle: nil message: @ "Image Saved" delegate: nil cancelButtonTitle: @ "OK" otherButtonTitles: nil]; [alertView show];} // click the select color button-(IBAction) didClickColorButton {// display or hide your own [color selection controller] _ colorPickerCtrl. view. hidden =! _ ColorPickerCtrl. view. hidden;} // when _ colorPickerCtrl selects the color, this method of the proxy is called-(void) pickedColor :( UIColor *) color {// set [color selection controller ], the color of the callback, set it to the control, and hide [color selection controller] [self setStrokeColor: color]; _ colorPickerCtrl. view. hidden =! _ ColorPickerCtrl. view. hidden;} // important. Set the new color of the brush-(void) setStrokeColor :( UIColor *) newColor {_ currentColor = newColor;} // click and select the photo button-(IBAction) didClickChoosePhoto {// display, select controller [self addSubview: _ imagePickerCtrl. view] ;}# pragma mark-imagePicker proxy method-(void) imagePickerController :( UIImagePickerController *) pickerdidFinishPickingMediaWithInfo :( NSDictionary *) info {// must be manual, disable the photo picker. view removeFromSup Erview]; // obtain the edited photo from the info dictionary [UIImagePickerControllerEditedImage] _ pickedImg = [info valueForKey: @ "UIImagePickerControllerOriginalImage"]; // draw the image to the canvas [self setNeedsDisplay];} // proxy method of the [Photo selector]. When you click Cancel, you must also hide the photo selector-(void) imagePickerControllerDidCancel :( UIImagePickerController *) picker {[_ imagePickerCtrl. view removeFromSuperview] ;}# pragma mark-core code, important ~~~ Gesture processing on the canvas // gesture Start (Brush falling) // Start a new dictionary for each stroke, including vertices and colors // Start new dictionary for each touch, with points and color-(void) touchesBegan :( NSSet *) touches withEvent :( UIEvent *) event {// all vertices in a stroke, start Point: NSMutableArray * pointsArrInOneStroke = [NSMutableArray array]; optional * strokeDict = [dictionary]; [strokeDict setObject: pointsArrInOneStroke forKey: @ "points"]; // stroke color [stro KeDict setObject: _ currentColor forKey: @ "color"]; // The pen size [strokeDict setObject: [NSNumber numberWithFloat: currentSize] forKey: @ "size"]; // write point CGPoint point = [[touches anyObject] locationInView: self]; [pointsArrInOneStroke addObject: NSStringFromCGPoint (point)]; [_ strokesArr addObject: strokeDict];} // Add each vertex to the vertex array // Add each point to points array-(void) touchesMoved :( NSSet *) touches withEvent :( UIEvent *) Event {// a point after moving CGPoint = [[touches anyObject] locationInView: self]; // The previous CGPoint prevPoint = [[touches anyObject] previuslocationinview: self]; // The Dot Array before the dictionary NSMutableArray * pointsArrInOneStroke = [[_ strokesArr lastObject] objectForKey: @ "points"]; // append a new vertex [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];} // gesture ended (Brush lifted) // Send over new trace when the touch ends-(void) touchesEnded :( NSSet *) touches withEvent :( UIEvent *) event {[_ abandonedStrokesArr removeAllObjects]; // 2. set the status of the Redo, cancel, and clear buttons [self updatemedilbarbtnstatus];} @ end
Color Selection Controller
ColorPickerController
//// ColorPickerController. h // 24_Canvas canvas /// Created by beyond on 14-8-26. // Copyright (c) 2014 com. beyond. all rights reserved. // # import
@ Interface ColorPickerController: UIViewController # pragma mark-attribute list // imgView @ property (nonatomic, retain) IBOutlet UIImageView * imgView; // The proxy uses weak @ property (weak) id pickedColorDelegate; # pragma mark-method list // core, create a context object (CGContextRef) createARGBBitmapContextFromImage (CGImageRef) inImage Based on the bitmap reference; // core, based on the touch point, extract the color value of the corresponding position pixel from the context-(UIColor *) getPixelColorAtLocation :( CGPoint) point; @ end
Core code
//// ColorPickerController. m // 24_Canvas 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-click end-(void) touchesEnded :( NSSet *) touches withEvent :( UIEvent *) event {UITouch * touch = [touches anyObject]; // CGPoint point = [touch locationInView: self. im GView]; // 1. call the custom method to obtain the color UIColor * selectedColor = [self getPixelColorAtLocation: point] from [point]; // 2. tell the proxy the parsed color [_ pickedColorDelegate pickedColor: selectedColor] ;}// core code: For more details about the following two methods, please refer to [iOS Developer Library] # pragma mark-core code, write the image to the memory, and then use the color-(UIColor *) getPixelColorAtLocation (CGPoint) in [point) point {UIColor * color = nil; // obtain the reference CGImageRef colorImage = _ imgView. image. CGImage; // Create off SC Reen bitmap context to draw the image. format ARGB is 4 bytes for each pixel: Alpa, Red, Green, Blue // call the custom method: reference the image in _ imgView, create and return the corresponding context CGContextRef contexRef = [self createARGBBitmapContextFromImage: colorImage]; // if the context corresponding to the image creation fails if (contexRef = NULL) {NSLog (@ "Color Image Retrieval-An error occurred while creating the corresponding context ~ "); Return nil;} // prepare to write the color image to the context size_t w = CGImageGetWidth (colorImage) just created. // problem! Size_t h = CGImageGetHeight (colorImage); CGRect rect = {0, 0}, {w, h}; log_rect (rect) // debug output rect: -- {0, 0 },{ 225,250} int bytesPerRow = CGBitmapContextGetBytesPerRow (contexRef); log_int (bytesPerRow) // debug the output 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. // bitmap Write (rendering) the allocated memory area CGContextDrawImage (contexRef, rect, colorImage); // obtain the first address of the bitmap context memory data block. Remember it with a pointer, as the base address unsigned char * dataPoint = CGBitmapContextGetData (contexRef); NSLog (@ "---- first address, pointer % p", dataPoint); // ---- first address, pointer 0x8b3f000 if (dataPoint! = NULL) {// offset: locate a specific pixel in the memory space of the map based on the xy of the touch point. // 4 indicates each pixel, 4 bytes in size // w indicates the total number of all vertices in each row // calculate the offset address in the memory block based on the column where the row is located, and multiply it by 4, because each point occupies four bytes in the memory int offset = 4 * (w * round (point. y) + round (point. x); // alpha is the memory base address + offset address int alpha = dataPoint [offset]; // red is the memory base address + offset address + 1 other similar int red = dataPoint [offset + 1]; int green = dataPoint [offset + 2]; int blue = dataPoint [offset + 3]; NSLog (@ "offset address: % I colors: RGBA % I", offset, Red, green, blue, alpha); // offset: 150908 colors: rgb a 255 0 254 255 // color object generated based on RGBA color = [UIColor colorWithRed :( red/255.0f) green :( green/255.0f) blue :( blue/255.0f) alpha :( alpha/255.0f)];} // after the operation is complete, release the context object CGContextRelease (contexRef ); // release the image data loaded to the memory from the memory if (dataPoint) {free (dataPoint);} return color ;}// custom Method 2: create and return the corresponding context-(CGContextRef) createARGBBitmapContextFromImage through the reference of the image in _ imgView :( CGImageRef) inImage {// context to be created CGContextRef context = NULL; // Color Space CGColorSpaceRef colorSpace; // The first address of the bitmap data in the memory space void * bitmapData; // The number of bytes in each row int bitmapBytesPerRow; // The total number of bytes of the image int bitmapByteCount; // obtain the width and height of the image. The entire image will be used, create the context size_t pixelsWide = CGImageGetWidth (inImage); size_t pixelsHigh = CGImageGetHeight (inImage); // The number of bytes each row occupies. each pixel in the color image occupies 4 bytes. // the transparency of each pixel occupies one byte (the value range of 8 digits is 0 ~ 255) // The number of bytes in each row, because each pixel occupies 4 bytes (including RGBA) (One R is a byte, which occupies 8 digits, the value ranges from 8 to 0 ~ 255) bitmapBytesPerRow = (pixelsWide * 4); // The total number of bytes of the image bitmapByteCount = (bitmapBytesPerRow * pixelsHigh); // use the Specified Color Space (RGB) colorSpace = CGColorSpaceCreateDeviceRGB (); if (colorSpace = NULL) {fprintf (stderr, "An error occurred while creating and allocating the color space \ n"); return NULL ;} // This is the destination in memory // where any drawing to the bitmap context will be rendered. // allocate all memory space for color image data. // all operations that draw the image context will be rendered to this memory space. bitmapData = mall Oc (bitmapByteCount); if (bitmapData = NULL) {fprintf (stderr, "memory space allocation failed ~ "); CGColorSpaceRelease (colorSpace); return NULL;} // creates a bitmap context. when pre-multiplied ARGB is used, each member of ARGB occupies 8 bits, that is, one byte, A pixel occupies 4 bytes in total // no matter what the original color image format is (CMYK or Grayscale), The CGBitmapContextCreate method is used to convert it to the specified ARGB format context = CGBitmapContextCreate (bitmapData, pixelsWide, pixelsHigh, 8, // bits per component bitmapBytesPerRow, colorSpace, kCGImageAlphaPremultipliedFirst); if (context = NULL) {free (bitmapData); fprintf (st Derr, "An error occurred while creating the bitmap context ~ ") ;}// Remember to release the color space CGColorSpaceRelease (colorSpace); return context ;}@ end before returning the context.