我正在尝试为iOS编写简单绘画应用作为第一个非平凡的项目。基本上,在每个触摸事件中,我需要在位图上打开图形上下文,在用户离开的位置上方绘制一些内容,然后将其关闭。
UIImage
是不可变的,所以它并不完全适合我的目的;我必须构建一个新的位图并将旧的位图绘制到新的位图中。我无法想象表现良好。在UIKit中是否存在任何类型的可变位图类,或者我将不得不归结为CGImageRef
?
答案 0 :(得分:1)
如果您愿意远离可可,我强烈建议您使用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 :(得分:-1)
每次创建新笔划时都不需要重新创建屏幕外的上下文。您可能会在某处累积笔划(NSMutableArray),当达到某个限制时,您可以通过首先将背景绘制到屏幕外上下文然后再累积在其上的笔划来展平这些累积的笔划。生成的屏幕外绘图将成为新背景,因此您可以清空包含笔划的数组并重新开始。这样你就可以采用一种混合方式,将所有笔画存储在内存中,每次重绘它们并不断重新创建屏幕外位图。
本书http://www.deitel.com/Books/iPhone/iPhoneforProgrammersAnAppDrivenApproach/tabid/3526/Default.aspx中有完整的章节(7)致力于创建一个简单的绘画应用程序。在那里你可以找到代码示例的链接。所采取的方法是将笔划存储在内存中,但这里是MainView.h和.m文件的修改版本,采用我所描述的方法,!!!但是请注意在两个文件底部的版权说明!!!:
// 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 deitel@deitel.com. *
*************************************************************************/
// 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 deitel@deitel.com. *
*************************************************************************/
因此,您基本上会替换项目中这些文件的原始版本以获得所需的行为