Как бы я нарисовал изменяемое растровое изображение в памяти? - PullRequest
2 голосов
/ 06 мая 2011

Я пытаюсь написать простое приложение для рисования для iOS как первый нетривиальный проект.По сути, при каждом сенсорном событии мне нужно открывать графический контекст на растровом изображении, рисовать что-то поверх того места, где остановился пользователь, и закрывать его.

UIImage является неизменным, поэтому он не совсем подходитдля моих целей;Я должен был бы создать новый растровый рисунок и нарисовать старый в новом.Я не могу представить, что это хорошо.Есть ли в UIKit какой-либо изменяемый класс растровых изображений, или мне придется перейти на CGImageRef?

Ответы [ 2 ]

1 голос
/ 06 мая 2011

Если вы готовы отойти от какао, я настоятельно рекомендую использовать 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, чтобы рисовать таким образом.

Надеюсь, это поможет.

0 голосов
/ 07 мая 2011

Вам не нужно воссоздавать закадровый контекст каждый раз, когда делается новый штрих.Вы можете где-то накапливать обводки (NSMutableArray), и когда будет достигнут определенный предел, вы сгладите эти накопленные обводки, сначала рисуя фон в закадровом контексте, а затем обводки, которые вы накопили поверх него.результирующий закадровый рисунок станет новым фоном, поэтому вы можете очистить массив, содержащий штрихи, и начать заново.Таким образом, вы используете гибридный подход между хранением всех штрихов в памяти + перерисовкой их каждый раз и постоянным воссозданием закадрового изображения.

В этой книге есть целая глава (7) http://www.deitel.com/Books/iPhone/iPhoneforProgrammersAnAppDrivenApproach/tabid/3526/Default.aspx, посвященная созданию простого приложения для рисования.там вы можете найти ссылку на примеры кода.выбранный подход хранит штрихи в памяти, но здесь есть модифицированные версии файлов 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.                                             *
 *************************************************************************/

Таким образом, вы в основном замените исходные версии этих файлов в проекте, чтобы получить желаемое поведение

...