2011-05-06 31 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]            * 
*************************************************************************/ 

所以你基本上會替換這些文件的原始版本中的項目,從而獲得所需的行爲