我想爲iOS寫一個簡單的繪畫應用程序作爲第一個非平凡的項目。基本上,在每個觸摸事件中,我需要在位圖上打開一個圖形上下文,在用戶停止的地方繪製一些東西,然後關閉它。如何繪製一個可變的內存位圖?
UIImage
是不可變的,所以它不完全適合我的目的;我不得不建立一個新的位圖,並將舊的位圖繪製到新的位圖中。我無法想象,表現很好。在UIKit中是否有任何可變的位圖類,或者我將不得不下降到CGImageRef
?
我想爲iOS寫一個簡單的繪畫應用程序作爲第一個非平凡的項目。基本上,在每個觸摸事件中,我需要在位圖上打開一個圖形上下文,在用戶停止的地方繪製一些東西,然後關閉它。如何繪製一個可變的內存位圖?
UIImage
是不可變的,所以它不完全適合我的目的;我不得不建立一個新的位圖,並將舊的位圖繪製到新的位圖中。我無法想象,表現很好。在UIKit中是否有任何可變的位圖類,或者我將不得不下降到CGImageRef
?
如果你願意冒險從可可,我強烈建議使用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以這種方式進行繪圖,從而造成性能瓶頸。
我希望這會有所幫助。
每次創建新筆劃時都不需要重新創建屏幕外上下文。你可能在某處(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] *
*************************************************************************/
所以你基本上會替換這些文件的原始版本中的項目,從而獲得所需的行爲
CGImage是你會什麼最終使用。 – 2011-05-06 18:59:31