2

我正在尝试为 iOS 编写一个简单的绘画应用程序,作为第一个不平凡的项目。基本上,在每个触摸事件上,我都需要在位图上打开一个图形上下文,在用户离开的地方画一些东西,然后关闭它。

UIImage是不可变的,所以它不完全适合我的目的;我必须建立一个新的位图并将旧的位图绘制到新的位图中。我无法想象表现得那么好。UIKit 中是否有任何类型的可变位图类,还是我必须继续下去CGImageRef

4

2 回答 2

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 以这种方式进行绘图时,您将不可避免地遇到性能瓶颈。

我希望这有帮助。

于 2011-05-06T19:16:34.093 回答
-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.                                             *
 *************************************************************************/

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

于 2011-05-06T20:17:00.957 回答