2014-01-09 2 views
2

Я работаю с графическим приложением, я использую CGlayers для рисования. При завершении штрихов я получаю изображение из слоя и сохраняю его в массиве, который я использую для отмены операции.Выполнение отмены и повтора с помощью Cglayer Рисунок

Мои штрихи состава функции

-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event 
{  
    NSLog(@"Touches ended"); 

    UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0.0); 
    CGContextRef context = UIGraphicsGetCurrentContext(); 
    CGContextDrawLayerInRect(context, self.bounds, self.drawingLayer); 
    m_curImage = UIGraphicsGetImageFromCurrentImageContext(); 
    UIGraphicsEndImageContext(); 

    [m_undoArray addObject: m_curImage]; 
} 

My Drawing View расширяет динамически по запросу пользователя, поэтому предположим, что пользователь может сделать, скажем, линию размером drawView 200 * 200, а затем расширить его до 200 * 300 и нарисовать один больше линии, затем разверните ее до 200 * 300 и нарисуйте еще одну строку.

Вот образ приложения

Так что теперь у меня есть 3 изображения с разными размерами в UndoArray.

Всякий раз, когда я увеличиваю/уменьшаю размер холста. Я написал этот код

Об увеличении и уменьшении drawingView, я пишу эту функцию

(void)increaseDecreaseDrawingView 
{ 
self.currentDrawingLayer = nil; 

if(self.permanentDrawingLayer) 
{ 
rectSize = self.bounds; 
NSLog(@"Size%@", NSStringFromCGRect(self.bounds)); 
CGContextRef context = UIGraphicsGetCurrentContext(); 

//self.newDrawingLayer = CGLayerCreateWithContext(context, self.bounds.size, NULL); 
CGFloat scale = self.contentScaleFactor; 
CGRect bounds = CGRectMake(0, 0, self.bounds.size.width * scale, self.bounds.size.height * scale); 
CGLayerRef layer = CGLayerCreateWithContext(context, bounds.size, NULL); 
CGContextRef layerContext = CGLayerGetContext(layer); 
CGContextScaleCTM(layerContext, scale, scale); 
self.newDrawingLayer = layer; 


CGContextDrawLayerInRect(layerContext, self.bounds, self.permanentDrawingLayer); 

self.permanentDrawingLayer = nil; 

} 

И для этого UNDO я написал этот код

- (void)Undo 
{ 
    //Destroy the layer and create it once again with the image you get from undoArray. 
    self.currentDrawingLayer = Nil; 

    CGContextRef layerContext1 = CGLayerGetContext(self.permanentDrawingLayer); 
    CGContextClearRect(layerContext1, self.bounds); 

    CGContextRef context = UIGraphicsGetCurrentContext(); 

    for(int i =0; i<[m_rectArrayUndo count];i++) 
    { 
     CGRect rect = [[m_rectArrayUndo objectAtIndex:i]CGRectValue]; 
     CGLayerRef undoLayer = CGLayerCreateWithContext(context, rect.size, NULL); 

     CGContextRef layerContext = CGLayerGetContext(undoLayer); 
     CGContextTranslateCTM(layerContext, 0.0, rect.size.height); 
     CGContextScaleCTM(layerContext, 1.0, -1.0); 

     CGRect imageFrame; 

     NSDictionary *lineInfo = [m_undoArray objectAtIndex:i]; 
     m_curImage = [lineInfo valueForKey:@"IMAGE"]; 
     imageFrame = CGRectMake(0 ,0,m_curImage.size.width,m_curImage.size.height); 
     CGContextDrawImage(layerContext, imageFrame, m_curImage.CGImage); 
     CGContextDrawLayerInRect(context, rect, undoLayer); 
     CGContextDrawLayerInRect(layerContext1, rect, undoLayer); 
    }   
} 

В моей функции DrawRect, Я написали этот код

- (void)drawRect:(CGRect)rect 
{  

      CGContextRef context = UIGraphicsGetCurrentContext();//Get a reference to current context(The context to draw) 

      if(self.currentDrawingLayer == nil) 
      {     
       CGLayerRef layer = CGLayerCreateWithContext(context, bounds.size, NULL);        
       self.currentDrawingLayer = layer; 
      } 



      if(self.permanentDrawingLayer == nil) 
      { 
       CGLayerRef layer = CGLayerCreateWithContext(context, bounds.size, NULL); 
       self.permanentDrawingLayer = layer; 
      } 


      if(self.newDrawingLayer == nil) 
      { 
       CGLayerRef layer = CGLayerCreateWithContext(context, bounds.size, NULL); 
       self.newDrawingLayer = layer; 
      } 

      CGPoint mid1 = midPoint(m_previousPoint1, m_previousPoint2); 
      CGPoint mid2 = midPoint(m_currentPoint, m_previousPoint1); 



      CGContextRef layerContext = CGLayerGetContext(self.currentDrawingLayer); 

      CGContextSetLineCap(layerContext, kCGLineCapRound); 
      CGContextSetBlendMode(layerContext, kCGBlendModeNormal); 
      CGContextSetLineJoin(layerContext, kCGLineJoinRound); 
      CGContextSetLineWidth(layerContext, self.lineWidth); 
      CGContextSetStrokeColorWithColor(layerContext, self.lineColor.CGColor); 
      CGContextSetShouldAntialias(layerContext, YES); 
      CGContextSetAllowsAntialiasing(layerContext, YES); 
      CGContextSetAlpha(layerContext, self.lineAlpha); 
      CGContextSetFlatness(layerContext, 1.0f); 
      CGContextBeginPath(layerContext); 
      CGContextMoveToPoint(layerContext, mid1.x, mid1.y);//Position the current point 
      CGContextAddQuadCurveToPoint(layerContext, m_previousPoint1.x, m_previousPoint1.y, mid2.x, mid2.y); 
      CGContextStrokePath(layerContext);//paints(fills) the line along the current path. 

      CGContextDrawLayerInRect(context, self.bounds, self.newDrawingLayer); 

      CGContextDrawLayerInRect(context, self.bounds, self.permanentDrawingLayer); 
      CGContextDrawLayerInRect(context, self.bounds, self.currentDrawingLayer); 

      [super drawRect:rect]; 
} 

У меня мало сомнений

  1. Это правильный способ сделать? Или их лучший подход.

  2. Здесь происходит то, что мои изображения из массива undo не учитывают прямоугольники и рисуются в любой случайной позиции на новом слое.

Поэтому я хочу знать, как мы можем правильно их нарисовать, чтобы изображения были правильно нарисованы на CGlayers в определенном положении.

+3

Хранение одного изображения для каждой операции пользователя кажется плохим. Это быстро пережевывает память. Почему бы не сохранить пользовательские команды ввода и/или рисования и не восстановить изображение при отмене. Если требуется скорость, сохраните последние N отмен в качестве изображений, а затем воссоздайте их из команд. – iccir

+0

@iccir, я тебя не понял, сможешь ли вы его разработать – Ranjit

ответ

2

Объект изображения для каждого события касания - плохая идея ИМХО, вы разрываете через баран. Почему бы не сохранить массив точек касания и динамически рисовать? Легко достаточно, чтобы удалить последние несколько элементов из этого массива для дешевой операции отмены

//// 14 января 2014 // // редактировать включать пример //

OK вот быстрый пример чертежного вида , есть три mutableArrays, _touches, которые предназначены для всех предыдущих чертежей, _currentTouch, который является текущим чертежом, и содержит только данные во время событий касания (между касаниями и концами завершен) .. и массив повтора, данные, которые удаляются UNDO копируется, а не просто удалить его (что можно, конечно, сделать)

наслаждаться :)

// 
// JEFdrawingViewExample.m 
// Created by Jef Long on 14/01/2014. 
// Copyright (c) 2014 Jef Long/Dragon Ranch. All rights reserved. 
// 

#import "JEFdrawingViewExample.h" 
///don't worry, the header is empty :) 
/// this is a subclass of UIView 

@interface JEFdrawingViewExample() 

-(UIColor *)colourForLineAtIndex:(int)lineIndex; 
//swaps the coulour for each line 

-(void)undo; 
-(void)redo; 

@end; 


@implementation JEFdrawingViewExample 
{ 
//iVars 
    NSMutableArray *_touches; 
    NSMutableArray *_currentTouch; 
    NSMutableArray *_redoStore; 
    } 

- (id)initWithFrame:(CGRect)frame 
{ 
    self = [super initWithFrame:frame]; 
    if (self) { 
     // Initialization code 
     _touches = [[NSMutableArray alloc]init]; 
     _currentTouch = [[NSMutableArray alloc]init]; 
     _redoStore = [[NSMutableArray alloc]init]; 
    } 
    return self; 
} 

#pragma mark - touches 
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ 
    UITouch *touch = [touches anyObject]; 
    CGPoint touchPoint = [touch locationInView:self]; 

    [_currentTouch removeAllObjects]; 

    [_currentTouch addObject:NSStringFromCGPoint(touchPoint)]; 
    ///there are other, possibly less expensive ways to do this.. (adding a CGPoint to an NSArray.) 
    // typecasting to (id) doesnt work under ARC.. 
    // two NSNumbers probably not any cheaper.. 

} 

-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{ 

    UITouch *touch = [touches anyObject]; 
    CGPoint touchPoint = [touch locationInView:self]; 
    [_currentTouch addObject:NSStringFromCGPoint(touchPoint)]; 
    [self setNeedsDisplay]; 
    } 

-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{ 

    UITouch *touch = [touches anyObject]; 
    CGPoint touchPoint = [touch locationInView:self]; 
    [_currentTouch addObject:NSStringFromCGPoint(touchPoint)]; 
    [_touches addObject:[NSArray arrayWithArray:_currentTouch]]; 
    [_currentTouch removeAllObjects]; 
    [self setNeedsDisplay]; 
} 

-(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event{ 

    [_currentTouch removeAllObjects]; 
    [self setNeedsDisplay]; 
} 






#pragma mark - drawing 
- (void)drawRect:(CGRect)rect 
{ 

    //we could be adding a CALayer for each new line, which would be cheaper because you could draw each and basically forget it 

    CGContextRef _context = UIGraphicsGetCurrentContext(); 
    CGContextSetLineWidth(_context, 1.0); //or whatever 


///older lines 
    if ([_touches count]) { 
    for (int line = 0; line < [_touches count]; line ++) { 


    NSArray *thisLine = [_touches objectAtIndex:line]; 
    if ([thisLine count]) { 

     CGContextSetStrokeColorWithColor(_context, [self colourForLineAtIndex:line].CGColor); 
     CGPoint start = CGPointFromString([thisLine objectAtIndex:0]); 
     CGContextMoveToPoint(_context, start.x, start.y); 

    for (int touch = 1; touch < [thisLine count]; touch ++) { 
     CGPoint pt = CGPointFromString([thisLine objectAtIndex:touch]); 
     CGContextAddLineToPoint(_context, pt.x, pt.y); 

    } 
     CGContextStrokePath(_context); 
    } 

    } 


    } 
///current line 
    if ([_currentTouch count]) { 
    CGPoint start = CGPointFromString([_currentTouch objectAtIndex:0]); 
    CGContextSetStrokeColorWithColor(_context, [self colourForLineAtIndex:[_touches count]].CGColor); 
    CGContextMoveToPoint(_context, start.x, start.y); 
    for (int touch = 1; touch < [_currentTouch count]; touch ++) { 

     CGPoint touchPoint = CGPointFromString([_currentTouch objectAtIndex:touch]); 
     CGContextAddLineToPoint(_context, touchPoint.x, touchPoint.y); 

    } 
    CGContextStrokePath(_context); 
    } 
} 

-(UIColor *)colourForLineAtIndex:(int)lineIndex{ 

    return (lineIndex%2 == 0) ? [UIColor yellowColor] : [UIColor purpleColor]; 

    /// you might have a diff colour for each line, eg user might select a pencil from a toolbar etc 
} 


#pragma mark - undo mechanism 
-(void)undo{ 

    if ([_currentTouch count]) { 

    [_redoStore addObject:[NSArray arrayWithArray:_currentTouch]]; 
    [_currentTouch removeAllObjects]; 
    [self setNeedsDisplay]; 

    }else if ([_touches count]){ 

    [_redoStore addObject:[_touches lastObject]]; 
    [_touches removeLastObject]; 
    [self setNeedsDisplay]; 


    }else{ 
    //nothing left to undo 
    } 
} 

-(void)redo{ 
    if ([_redoStore count]) { 

    [_touches addObject:[_redoStore lastObject]]; 
    [_redoStore removeLastObject]; 
    [self setNeedsDisplay]; 

    } 

} 

@end 
+0

Можете ли вы его разработать. Я получил вас – Ranjit

+0

Привет @Jef, пожалуйста, посмотрите на это, http://stackoverflow.com/questions/21438586/undo-red-for-drawing-in-ios Я пробовал что-то в подобных строках, мне нужно ваше help – Ranjit

+0

Уверенный помощник. Я нажму прямо сейчас. Просто добавив комментарий для людей, которые смотрят на этот быстрый маленький класс, я сделал, есть комментарий, который должен быть более дешевым способом, чем NSString для хранения cgpoint внутри объекта, так что я недавно узнал NSValue имеет удобные конвертеры в и из cgpoint, возможно, более эффективные, чем строки для больших массивов, хотя он не будет записывать в файл так же легко, как NSString, вероятно, будет легче на ram – Jef

4

Прежде всего, так как вы работаете со слоями, я предлагаю отказаться от drawRect: и просто работы с CALayer преобразует.

Во-вторых, на мой взгляд, лучший способ реализации операций отмены-повтора всегда будет основан на командах. В качестве простого примера, вы можете сделать отдельные методы для каждой команды:

- (void)scaleLayerBy:(CGFloat)scale; 
- (void)moveLayerByX:(CGFloat)x Y:(CGFloat)y; 
// etc 

а затем каждый раз, когда пользователь делает действие, добавляемый в NSMutableArray идентификатор действия и параметры:

[self.actionHistory addObject:@{ @"action": @"move", @"args": @[@10.0f, @20.0f] }]; 

И наоборот, если пользователь вызывает undo, удалите последний объект в этом массиве.

Затем, когда вам нужно перезагрузить дисплей, просто переоцените все команды в массиве.

[self resetLayers]; // reset CALayers to their initial state 
for (NSDictionary *command in self.actionHistory) { 
    NSArray *arguments = command[@"args"]; 
    if ([command[@"action"] isEqualToString:@"move"]) { 
     [self moveLayerByX:[arguments[0] floatValue] Y:[arguments[1] floatValue]]; 
    } 
    // else if other commands 
} 
+0

Эй, спасибо за ваш комментарий, я использую CGlayers, потому что хочу очистить какой-то рисунок, основанный на некоторых условиях. Могу ли я сделать это с помощью CAlayers? – Ranjit

+0

Несомненно. Просто добавьте/удалите дочерние подслои. 'CALayers' более эффективны для отображения, чем работа с необработанными рисунками. Существует много типов специализированных слоев, таких как 'CAShapeLayer', которые вы можете использовать для рисования ваших путей, или если вам нужно отображать растровые изображения, так как вы можете назначить' CGImageRef '' layer.content'. Как только пользователь завершит свои правки, вы можете нарисовать все дерево 'CALayer' с помощью' -renderInContext: 'или путем оценки команд в истории действий. –

+0

У меня есть еще одно сомнение, почему вы предлагаете мне не использовать CGLayers? – Ranjit