2011-05-06 61 views
2

我想为iOS写一个简单的绘画应用程序作为第一个非平凡的项目。基本上,在每个触摸事件中,我需要在位图上打开一个图形上下文,在用户停止的地方绘制一些东西,然后关闭它。如何绘制一个可变的内存位图?

UIImage是不可变的,所以它不完全适合我的目的;我不得不建立一个新的位图,并将旧的位图绘制到新的位图中。我无法想象,表现很好。在UIKit中是否有任何可变的位图类,或者我将不得不下降到CGImageRef

+0

CGImage是你会什么最终使用。 – 2011-05-06 18:59:31

回答

0

如果你愿意冒险从可可,我强烈建议使用OpenGL来达到这个目的。 Apple提供了一个很好的示例应用程序(GLPaint),可以证明这一点。解决OpenGL的学习曲线肯定会在外观,性能和纯粹的灵活性方面带来回报。

但是,如果你不想那么做,那么另一种方法是创建一个新的CALayer子类覆盖drawInContext:,并在那里存储每个绘图描边(路径和线属性)。然后,您可以将每个'strokeLayer'添加到图纸视图的图层层次结构,并强制重绘每个图框。 CGLayers也可以用来提高性能(这可能会成为一个大问题 - 当用户描绘长时间的中风时,你会看到帧速率非常快地下降)。事实上,在任何情况下,您都可能最终使用CGLayer进行绘制。下面是一个drawRect:方法的一些代码可能有助于说明这个办法:

- (void)drawRect:(CGRect)rect { 
    // Setup the layer and it's context to use as a drawing buffer. 
    CGContextRef context = UIGraphicsGetCurrentContext(); 
    CGLayerRef drawingBuffer = CGLayerCreateWithContext(context, self.bounds.size, NULL); 
    CGContextRef bufferContext = CGLayerGetContext(drawingBuffer); 

    // Draw all sublayers into the drawing buffer, and display the buffer. 
    [self.layer renderInContext:bufferContext]; 
    CGContextDrawLayerAtPoint(context, CGPointZero, drawingBuffer); 
    CGLayerRelease(drawingBuffer); 
} 

至于易变性去,最明显的事情是制定在绘画笔触的背景色。这样一个橡皮擦笔画将与绘画笔画完全一样,只是一种不同的颜色。

您提到使用位图图像,这实际上开始暗示OpenGL渲染到纹理,其中一系列点精灵(形成一条线)可以以非常高的帧速率绘制到可变纹理上。我不想让事情发展缓慢,但是你将不可避免地使用Core Graphics/Quartz以这种方式进行绘图,从而造成性能瓶颈。

我希望这会有所帮助。

-1

每次创建新笔划时都不需要重新创建屏幕外上下文。你可能在某处(NSMutableArray)累积笔画,当达到一定的限制时,通过首先将背景绘制到屏幕外的上下文,然后在上面绘制笔画,可以将这些累积的笔画变平。最终的屏外绘图将成为新的背景,因此您可以清空包含笔画的数组并重新开始。这样你就可以将所有笔画存储在内存中,并每次重绘它们并不断重新创建离屏位图。

本书的全部章节(7)http://www.deitel.com/Books/iPhone/iPhoneforProgrammersAnAppDrivenApproach/tabid/3526/Default.aspx致力于创建一个简单的绘画应用程序。在那里你可以找到代码示例的链接。所采取的方法是将笔画存储在内存中,但这里是采用我描述的方法的MainView.h和.m文件的修改版本!但请注意版权声明AT这两个文件的底部!!!:

// MainView.m 
// View for the frontside of the Painter app. 
#import "MainView.h" 

const NSUInteger kThreshold = 2; 

@implementation MainView 

@synthesize color; // generate getters and setters for color 
@synthesize lineWidth; // generate getters and setters for lineWidth 

CGContextRef CreateBitmapContext(NSUInteger w, NSUInteger h); 

void * globalBitmapData = NULL; 

// method is called when the view is created in a nib file 
- (id)initWithCoder:(NSCoder*)decoder 
{ 
    // if the superclass initializes properly 
    if (self = [super initWithCoder:decoder]) 
    { 
     // initialize squiggles and finishedSquiggles 
     squiggles = [[NSMutableDictionary alloc] init]; 
     finishedSquiggles = [[NSMutableArray alloc] init]; 

     // the starting color is black 
     color = [[UIColor alloc] initWithRed:0 green:0 blue:0 alpha:1]; 
     lineWidth = 5; // default line width 

     flattenedImage_ = NULL; 
    } // end if 

    return self; // return this objeoct 
} // end method initWithCoder: 

// clears all the drawings 
- (void)resetView 
{ 
    [squiggles removeAllObjects]; // clear the dictionary of squiggles 
    [finishedSquiggles removeAllObjects]; // clear the array of squiggles 
    [self setNeedsDisplay]; // refresh the display 
} // end method resetView 

// draw the view 
- (void)drawRect:(CGRect)rect 
{ 
    // get the current graphics context 
    CGContextRef context = UIGraphicsGetCurrentContext(); 

    if(flattenedImage_) 
    { 
     CGContextDrawImage(context, CGRectMake(0,0,CGRectGetWidth(self.bounds), CGRectGetHeight(self.bounds)), flattenedImage_); 
    } 

    // draw all the finished squiggles 

    for (Squiggle *squiggle in finishedSquiggles) 
     [self drawSquiggle:squiggle inContext:context]; 

    // draw all the squiggles currently in progress 
    for (NSString *key in squiggles) 
    { 
     Squiggle *squiggle = [squiggles valueForKey:key]; // get squiggle 
     [self drawSquiggle:squiggle inContext:context]; // draw squiggle 
    } // end for 
} // end method drawRect: 

// draws the given squiggle into the given context 
- (void)drawSquiggle:(Squiggle *)squiggle inContext:(CGContextRef)context 
{ 
    // set the drawing color to the squiggle's color 
    UIColor *squiggleColor = squiggle.strokeColor; // get squiggle's color 
    CGColorRef colorRef = [squiggleColor CGColor]; // get the CGColor 
    CGContextSetStrokeColorWithColor(context, colorRef); 

    // set the line width to the squiggle's line width 
    CGContextSetLineWidth(context, squiggle.lineWidth); 

    NSMutableArray *points = [squiggle points]; // get points from squiggle 

    // retrieve the NSValue object and store the value in firstPoint 
    CGPoint firstPoint; // declare a CGPoint 
    [[points objectAtIndex:0] getValue:&firstPoint]; 

    // move to the point 
    CGContextMoveToPoint(context, firstPoint.x, firstPoint.y); 

    // draw a line from each point to the next in order 
    for (int i = 1; i < [points count]; i++) 
    { 
     NSValue *value = [points objectAtIndex:i]; // get the next value 
     CGPoint point; // declare a new point 
     [value getValue:&point]; // store the value in point 

     // draw a line to the new point 
     CGContextAddLineToPoint(context, point.x, point.y); 
    } // end for 

    CGContextStrokePath(context); 
} // end method drawSquiggle:inContext: 

// called whenever the user places a finger on the screen 
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 
{ 
    NSArray *array = [touches allObjects]; // get all the new touches 

    // loop through each new touch 
    for (UITouch *touch in array) 
    { 
     // create and configure a new squiggle 
     Squiggle *squiggle = [[Squiggle alloc] init]; 
     [squiggle setStrokeColor:color]; // set squiggle's stroke color 
     [squiggle setLineWidth:lineWidth]; // set squiggle's line width 

     // add the location of the first touch to the squiggle 
     [squiggle addPoint:[touch locationInView:self]]; 

     // the key for each touch is the value of the pointer 
     NSValue *touchValue = [NSValue valueWithPointer:touch]; 
     NSString *key = [NSString stringWithFormat:@"%@", touchValue]; 

     // add the new touch to the dictionary under a unique key 
     [squiggles setValue:squiggle forKey:key]; 
     [squiggle release]; // we are done with squiggle so release it 
    } // end for 
} // end method touchesBegan:withEvent: 

// called whenever the user drags a finger on the screen 
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event 
{ 
    NSArray *array = [touches allObjects]; // get all the moved touches 

    // loop through all the touches 
    for (UITouch *touch in array) 
    { 
     // get the unique key for this touch 
     NSValue *touchValue = [NSValue valueWithPointer:touch]; 

     // fetch the squiggle this touch should be added to using the key 
     Squiggle *squiggle = [squiggles valueForKey: 
     [NSString stringWithFormat:@"%@", touchValue]]; 

     // get the current and previous touch locations 
     CGPoint current = [touch locationInView:self]; 
     CGPoint previous = [touch previousLocationInView:self]; 
     [squiggle addPoint:current]; // add the new point to the squiggle 

     // Create two points: one with the smaller x and y values and one 
     // with the larger. This is used to determine exactly where on the 
     // screen needs to be redrawn. 
     CGPoint lower, higher; 
     lower.x = (previous.x > current.x ? current.x : previous.x); 
     lower.y = (previous.y > current.y ? current.y : previous.y); 
     higher.x = (previous.x < current.x ? current.x : previous.x); 
     higher.y = (previous.y < current.y ? current.y : previous.y); 

     // redraw the screen in the required region 
     [self setNeedsDisplayInRect:CGRectMake(lower.x-lineWidth, 
     lower.y-lineWidth, higher.x - lower.x + lineWidth*2, 
     higher.y - lower.y + lineWidth * 2)]; 
    } // end for 
} // end method touchesMoved:withEvent: 

// called when the user lifts a finger from the screen 
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event 
{ 
    // loop through the touches 
    for (UITouch *touch in touches) 
    { 
     // get the unique key for the touch 
     NSValue *touchValue = [NSValue valueWithPointer:touch]; 
     NSString *key = [NSString stringWithFormat:@"%@", touchValue]; 

     // retrieve the squiggle for this touch using the key 
     Squiggle *squiggle = [squiggles valueForKey:key]; 

     // remove the squiggle from the dictionary and place it in an array 
     // of finished squiggles 
     [finishedSquiggles addObject:squiggle]; // add to finishedSquiggles 
     [squiggles removeObjectForKey:key]; // remove from squiggles 

    if([finishedSquiggles count] > kThreshold) 
    { 
     CGContextRef context = CreateBitmapContext(CGRectGetWidth(self.bounds), CGRectGetHeight(self.bounds)); 

     if(flattenedImage_) 
     { 
      CGContextDrawImage(context, CGRectMake(0,0,CGRectGetWidth(self.bounds), CGRectGetHeight(self.bounds)), flattenedImage_); 
     } 

     for (Squiggle *squiggle in finishedSquiggles) 
      [self drawSquiggle:squiggle inContext:context]; 

     CGImageRef imgRef = CGBitmapContextCreateImage(context); 
     CGContextRelease(context); 
     if(flattenedImage_ != NULL) 
      CFRelease(flattenedImage_); 

     flattenedImage_ = imgRef; 

     [finishedSquiggles removeAllObjects]; 
    } 
    } // end for 
} // end method touchesEnded:withEvent: 

// called when a motion event, such as a shake, ends 
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event 
{ 
    // if a shake event ended 
    if (event.subtype == UIEventSubtypeMotionShake) 
    { 
     // create an alert prompting the user about clearing the painting 
     NSString *message = @"Are you sure you want to clear the painting?"; 
     UIAlertView *alert = [[UIAlertView alloc] initWithTitle: 
     @"Clear painting" message:message delegate:self 
     cancelButtonTitle:@"Cancel" otherButtonTitles:@"Clear", nil]; 
     [alert show]; // show the alert 
     [alert release]; // release the alert UIAlertView 
    } // end if 

    // call the superclass's moetionEnded:withEvent: method 
    [super motionEnded:motion withEvent:event]; 
} // end method motionEnded:withEvent: 

// clear the painting if the user touched the "Clear" button 
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex: 
    (NSInteger)buttonIndex 
{ 
    // if the user touched the Clear button 
    if (buttonIndex == 1) 
     [self resetView]; // clear the screen 
} // end method alertView:clickedButtonAtIndex: 

// determines if this view can become the first responder 
- (BOOL)canBecomeFirstResponder 
{ 
    return YES; // this view can be the first responder 
} // end method canBecomeFirstResponder 

// free MainView's memory 
- (void)dealloc 
{ 
    [squiggles release]; // release the squiggles NSMutableDictionary 
    [finishedSquiggles release]; // release finishedSquiggles 
    [color release]; // release the color UIColor 
    [super dealloc]; 
} // end method dealloc 
@end 

CGContextRef CreateBitmapContext(NSUInteger w, NSUInteger h) 
{ 
    CGContextRef context = NULL; 

    int    bitmapByteCount; 
    int    bitmapBytesPerRow; 

    bitmapBytesPerRow = (w * 4); 
    bitmapByteCount  = (bitmapBytesPerRow * h); 

    if(globalBitmapData == NULL) 
     globalBitmapData = malloc(bitmapByteCount); 
    memset(globalBitmapData, 0, sizeof(globalBitmapData)); 
    if (globalBitmapData == NULL) 
    { 
     return nil; 
    } 

    CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB(); 

    context = CGBitmapContextCreate (globalBitmapData,w,h,8,bitmapBytesPerRow, 
            colorspace,kCGImageAlphaPremultipliedLast); 
    CGColorSpaceRelease(colorspace); 

    return context; 
} 



/************************************************************************** 
* (C) Copyright 2010 by Deitel & Associates, Inc. All Rights Reserved. * 
*                  * 
* DISCLAIMER: The authors and publisher of this book have used their  * 
* best efforts in preparing the book. These efforts include the   * 
* development, research, and testing of the theories and programs  * 
* to determine their effectiveness. The authors and publisher make  * 
* no warranty of any kind, expressed or implied, with regard to these * 
* programs or to the documentation contained in these books. The authors * 
* and publisher shall not be liable in any event for incidental or  * 
* consequential damages in connection with, or arising out of, the  * 
* furnishing, performance, or use of these programs.      * 
*                  * 
* As a user of the book, Deitel & Associates, Inc. grants you the  * 
* nonexclusive right to copy, distribute, display the code, and create * 
* derivative apps based on the code for noncommercial purposes only--so * 
* long as you attribute the code to Deitel & Associates, Inc. and  * 
* reference www.deitel.com/books/iPhoneFP/. If you have any questions, * 
* or specifically would like to use our code for commercial purposes, * 
* contact [email protected]            * 
*************************************************************************/ 




    // MainView.h 
// View for the frontside of the Painter app. 
// Implementation in MainView.m 
#import <UIKit/UIKit.h> 
#import "Squiggle.h" 

@interface MainView : UIView 
{ 
    NSMutableDictionary *squiggles; // squiggles in progress 
    NSMutableArray *finishedSquiggles; // finished squiggles 
    UIColor *color; // the current drawing color 
    float lineWidth; // the current drawing line width 

    CGImageRef flattenedImage_; 
} // end instance variable declaration 

// declare color and lineWidth as properties 
@property(nonatomic, retain) UIColor *color; 
@property float lineWidth; 

// draw the given Squiggle into the given graphics context 
- (void)drawSquiggle:(Squiggle *)squiggle inContext:(CGContextRef)context; 
- (void)resetView; // clear all squiggles from the view 
@end // end interface MainView 

/************************************************************************** 
* (C) Copyright 2010 by Deitel & Associates, Inc. All Rights Reserved. * 
*                  * 
* DISCLAIMER: The authors and publisher of this book have used their  * 
* best efforts in preparing the book. These efforts include the   * 
* development, research, and testing of the theories and programs  * 
* to determine their effectiveness. The authors and publisher make  * 
* no warranty of any kind, expressed or implied, with regard to these * 
* programs or to the documentation contained in these books. The authors * 
* and publisher shall not be liable in any event for incidental or  * 
* consequential damages in connection with, or arising out of, the  * 
* furnishing, performance, or use of these programs.      * 
*                  * 
* As a user of the book, Deitel & Associates, Inc. grants you the  * 
* nonexclusive right to copy, distribute, display the code, and create * 
* derivative apps based on the code for noncommercial purposes only--so * 
* long as you attribute the code to Deitel & Associates, Inc. and  * 
* reference www.deitel.com/books/iPhoneFP/. If you have any questions, * 
* or specifically would like to use our code for commercial purposes, * 
* contact [email protected]            * 
*************************************************************************/ 

所以你基本上会替换这些文件的原始版本中的项目,从而获得所需的行为