保存 CGContextRef

发布于 2024-09-12 04:36:20 字数 1925 浏览 5 评论 0原文

我有一个绘图应用程序,我想在其中创建一个撤消方法。绘图发生在 TouchesMoved: 方法内。

我正在尝试创建一个 CGContextRef 并将其推送到堆栈或将其保存在稍后可以恢复的上下文属性中,但我没有任何运气。任何建议都会很棒。这就是我所拥有的...

UIImageView      *drawingSurface;
CGContextRef       undoContext;


- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
 UIGraphicsBeginImageContext(self.view.frame.size);
 CGContextRef context = UIGraphicsGetCurrentContext();
 [drawingSurface.image drawInRect:CGRectMake(0, 0, drawingSurface.image.size.width, drawingSurface.image.size.height)]; 
 UIGraphicsPushContext(context);

        // also tried but cant figure how to restore it
        undoContext = context;

 UIGraphicsEndImageContext();
}

然后我有一个由撤消按钮触发的方法...

- (IBAction)restoreUndoImage {
 UIGraphicsBeginImageContext(self.view.frame.size);
 UIGraphicsPopContext();
 drawingSurface.image = UIGraphicsGetImageFromCurrentImageContext();
 UIGraphicsEndImageContext();
}

当我运行这个方法时,我相信我的绘图表面被分配为零,因为它只是删除了图像中的所有内容。

我的猜测是我不能以这种方式使用 pop 和 push 。但我似乎无法弄清楚如何保存上下文然后将其推回到绘图表面上。嗯嗯。任何帮助都会……嗯……有帮助。提前致谢 -

并且,仅供参考,这是我正在做的绘制到屏幕上的操作,效果很好。这是我的 TouchesMoved 里面的:

 UIGraphicsBeginImageContext(self.view.frame.size);
 CGContextRef context = UIGraphicsGetCurrentContext();
 [drawingSurface.image drawInRect:CGRectMake(0, 0, drawingSurface.image.size.width, drawingSurface.image.size.height)]; 

 CGContextSetLineCap(context, kCGLineCapRound); //kCGLineCapSquare, kCGLineCapButt, kCGLineCapRound
 CGContextSetLineWidth(context, self.brush.size); // for size

 CGContextSetStrokeColorWithColor (context,[currentColor CGColor]);

 CGContextBeginPath(context);
 CGContextMoveToPoint(context, lastPoint.x, lastPoint.y);
 CGContextAddLineToPoint(context, currentPoint.x, currentPoint.y);
 CGContextStrokePath(context);
 drawingSurface.image = UIGraphicsGetImageFromCurrentImageContext();
 UIGraphicsEndImageContext();

I have a drawing app in which I would like to create an undo method. The drawing takes place inside the TouchesMoved: method.

I am trying to create a CGContextRef and push it to the stack OR save it in a context property that can be restored later but am not having any luck. Any advice would be great. Here is what I have ...

UIImageView      *drawingSurface;
CGContextRef       undoContext;


- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
 UIGraphicsBeginImageContext(self.view.frame.size);
 CGContextRef context = UIGraphicsGetCurrentContext();
 [drawingSurface.image drawInRect:CGRectMake(0, 0, drawingSurface.image.size.width, drawingSurface.image.size.height)]; 
 UIGraphicsPushContext(context);

        // also tried but cant figure how to restore it
        undoContext = context;

 UIGraphicsEndImageContext();
}

Then I have a method triggered by my undo button ...

- (IBAction)restoreUndoImage {
 UIGraphicsBeginImageContext(self.view.frame.size);
 UIGraphicsPopContext();
 drawingSurface.image = UIGraphicsGetImageFromCurrentImageContext();
 UIGraphicsEndImageContext();
}

When I run this, I believe my drawingSurface is being assigned nil because it just erases everything in the image.

My guess is I can't use pop and push this way. But I can't seem to figure out how to just save the context and then push it back onto the drawingSurface. Hmmmm. Any help would be ... well ... helpfull. Thanks in advance -

And, just for reference, here is what I am doing to draw to the screen, which is working great. This is inside my TouchesMoved:

 UIGraphicsBeginImageContext(self.view.frame.size);
 CGContextRef context = UIGraphicsGetCurrentContext();
 [drawingSurface.image drawInRect:CGRectMake(0, 0, drawingSurface.image.size.width, drawingSurface.image.size.height)]; 

 CGContextSetLineCap(context, kCGLineCapRound); //kCGLineCapSquare, kCGLineCapButt, kCGLineCapRound
 CGContextSetLineWidth(context, self.brush.size); // for size

 CGContextSetStrokeColorWithColor (context,[currentColor CGColor]);

 CGContextBeginPath(context);
 CGContextMoveToPoint(context, lastPoint.x, lastPoint.y);
 CGContextAddLineToPoint(context, currentPoint.x, currentPoint.y);
 CGContextStrokePath(context);
 drawingSurface.image = UIGraphicsGetImageFromCurrentImageContext();
 UIGraphicsEndImageContext();

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

滿滿的愛 2024-09-19 04:36:20

我认为您处理问题的方式是错误的并且上下文令人困惑。

在即时模式 API 中,您可以通过推送/弹出保存对象的“状态”,而不是图形表示。状态由线宽、颜色和位置等内容组成。图形表示是绘制操作的结果(位图),通常是您不想保存的内容。

相反,尝试保存用于创建绘图的“信息”。

我最初的建议是将形状创建和绘画分离。在 OSX 上,您可以使用 NSBezierPath,但对于 iOS,我们必须使用点数组。

例如,给定这个协议:

// ViewController.h
@protocol DrawSourceProtocol <NSObject>
- (NSArray*)pathsToDraw;
@end

@interface ViewController : UIViewController<DrawSourceProtocol>
@end

您可以实现这些功能:

// ViewController.m
@interface ViewController () {
  NSMutableArray *currentPath;  
  NSMutableArray *allPaths;
  MyView *view_;
}
@end

...

- (void)viewDidLoad {
  [super viewDidLoad];
  currentPath = [[NSMutableArray alloc] init];
  allPaths = [[NSMutableArray alloc] init];    
  view_ = (MyView*)self.view;
  view_.delegate = self;
}

- (NSArray*)pathsToDraw {  
  // Return the currently draw path too
  if (currentPath && currentPath.count) {
    NSMutableArray *allPathsPlusCurrent = [[NSMutableArray alloc] initWithArray:allPaths];
    [allPathsPlusCurrent addObject:currentPath];
    return allPathsPlusCurrent;
  }  
  return allPaths;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
  currentPath = [[NSMutableArray alloc] init];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
  // When a touch ends, save the current path
  [allPaths addObject:currentPath];
  currentPath = [[NSMutableArray alloc] init];
  [view_ setNeedsDisplay];    
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
  UITouch *touch = [touches anyObject];   
  CGPoint currentPoint = [touch locationInView:self.view];

  // We store the point with the help of NSValue
  [currentPath addObject:[NSValue valueWithCGPoint:currentPoint]];  

  // Update the view
  [view_ setNeedsDisplay];  
}

现在子类化您的视图(我在这里称之为我的 MyView)并实现如下所示的内容:

// MyView.h
#import "ViewController.h"

@protocol DrawSourceProtocol;

@interface MyView : UIView {
   __weak id<DrawSourceProtocol> delegate_; 
}
@property (weak) id<DrawSourceProtocol> delegate;
@end

// MyView.m

@synthesize delegate = delegate_;

...

- (void)drawRect:(CGRect)rect { 
  NSLog(@"Drawing!");

  // Setup a context
  CGContextRef context = UIGraphicsGetCurrentContext();   
  CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
  CGContextSetRGBFillColor(context, 0.0, 0.0, 1.0, 1.0);
  CGContextSetLineWidth(context, 2.0);

  // Get the paths
  NSArray *paths = [delegate_ pathsToDraw];

  for (NSArray *aPath in paths) {
    BOOL firstPoint = TRUE;
    for (NSValue *pointValue in aPath) {
      CGPoint point = [pointValue CGPointValue];

      // Always move to the first point
      if (firstPoint) {
        CGContextMoveToPoint(context, point.x, point.y);
        firstPoint = FALSE;
        continue;
      }

      // Draw a point
      CGContextAddLineToPoint(context, point.x, point.y);
    }
  }

  // Stroke!
  CGContextStrokePath(context);
}

这里唯一需要注意的是 setNeedsDisplay 的性能不是很好。最好使用 setNeedsDisplayInRect:,请参阅我关于 一种确定“绘制”矩形的有效方法

至于撤消?您的撤消操作只是从 allPaths 数组中弹出最后一个对象。这个练习我将留给你:)

希望这有帮助!

I think you're approaching the problem the wrong way and confusing contexts.

In an immediate mode API you save the 'state' of objects with push/pop, not the graphical representation. The state consists of things like line widths, colours and positions. The graphical representation is the result of a paint operation (a bitmap) and generally something you don't want to save.

Instead try to save 'information' you use to create the drawing.

My initial suggestion would be to decouple your shape creation and painting. On OSX you can use NSBezierPath, but for iOS we have to use an array of points.

For example given this protocol:

// ViewController.h
@protocol DrawSourceProtocol <NSObject>
- (NSArray*)pathsToDraw;
@end

@interface ViewController : UIViewController<DrawSourceProtocol>
@end

You can implement these functions:

// ViewController.m
@interface ViewController () {
  NSMutableArray *currentPath;  
  NSMutableArray *allPaths;
  MyView *view_;
}
@end

...

- (void)viewDidLoad {
  [super viewDidLoad];
  currentPath = [[NSMutableArray alloc] init];
  allPaths = [[NSMutableArray alloc] init];    
  view_ = (MyView*)self.view;
  view_.delegate = self;
}

- (NSArray*)pathsToDraw {  
  // Return the currently draw path too
  if (currentPath && currentPath.count) {
    NSMutableArray *allPathsPlusCurrent = [[NSMutableArray alloc] initWithArray:allPaths];
    [allPathsPlusCurrent addObject:currentPath];
    return allPathsPlusCurrent;
  }  
  return allPaths;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
  currentPath = [[NSMutableArray alloc] init];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
  // When a touch ends, save the current path
  [allPaths addObject:currentPath];
  currentPath = [[NSMutableArray alloc] init];
  [view_ setNeedsDisplay];    
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
  UITouch *touch = [touches anyObject];   
  CGPoint currentPoint = [touch locationInView:self.view];

  // We store the point with the help of NSValue
  [currentPath addObject:[NSValue valueWithCGPoint:currentPoint]];  

  // Update the view
  [view_ setNeedsDisplay];  
}

Now subclass your view (I call mine MyView here) and implement something like this:

// MyView.h
#import "ViewController.h"

@protocol DrawSourceProtocol;

@interface MyView : UIView {
   __weak id<DrawSourceProtocol> delegate_; 
}
@property (weak) id<DrawSourceProtocol> delegate;
@end

// MyView.m

@synthesize delegate = delegate_;

...

- (void)drawRect:(CGRect)rect { 
  NSLog(@"Drawing!");

  // Setup a context
  CGContextRef context = UIGraphicsGetCurrentContext();   
  CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
  CGContextSetRGBFillColor(context, 0.0, 0.0, 1.0, 1.0);
  CGContextSetLineWidth(context, 2.0);

  // Get the paths
  NSArray *paths = [delegate_ pathsToDraw];

  for (NSArray *aPath in paths) {
    BOOL firstPoint = TRUE;
    for (NSValue *pointValue in aPath) {
      CGPoint point = [pointValue CGPointValue];

      // Always move to the first point
      if (firstPoint) {
        CGContextMoveToPoint(context, point.x, point.y);
        firstPoint = FALSE;
        continue;
      }

      // Draw a point
      CGContextAddLineToPoint(context, point.x, point.y);
    }
  }

  // Stroke!
  CGContextStrokePath(context);
}

The only cavet here is that setNeedsDisplay isn't very performant. It's better to use setNeedsDisplayInRect:, see my last post regarding an efficient way of determining the 'drawn' rect.

As for undo? Your undo operation is merely popping the last object from the allPaths array. This exercise I'll leave you to :)

Hope this helps!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文