마지막에 그려졌던 라인을 배열에서 삭제하고 시스템에 알려주면 끝~!
func undo(){
if lines.popLast() != nil {
self.needsDisplay = true;
}
}
우선, 펜의 타입을 정의합니다
enum PenType : Int32 {
case normal = 0
case highlighter = 1
case eraser = 2
}
지우개는 Canvas의 background를 캡쳐한 이미지를 펜의 Color로 사용합니다
이를 위해서 canvas의 background color를 set해주고 캡쳐하도록 했습니다
var backgroundImage: NSImage?
override func awakeFromNib() {
guard let rep = self.bitmapImageRepForCachingDisplay(in: self.bounds) else { return }
self.cacheDisplay(in: self.bounds, to: rep)
let image = NSImage(size: self.bounds.size)
image.addRepresentation(rep)
self.backgroundImage = image
}
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect);
NSColor.black.setFill()
bounds.fill()
self.drawLines();
}
이제 이 이미지를 선을 그릴 때 사용하도록 합니다
var penType : PenType = .normal
func drawLines(){
guard let context = NSGraphicsContext.current?.cgContext else { return }
context.setLineCap(.round);
context.setLineWidth(5);
context.setStrokeColor(NSColor.red.cgColor);
context.setBlendMode(.normal);
context.strokePath();
context.setAlpha(1);
context.setShouldAntialias(true);
if self.penType == .eraser {
guard let backgroundImage = self.backgroundImage else {
return
}
let color = NSColor(patternImage: backgroundImage)
context.setStrokeColor(color.cgColor)
context.setFillColor(color.cgColor)
}
lines.forEach { line in
for i in stride(from: 0, to: line.count, by: 3) {
if i != 0 {
context.move(to: line[i-3]);
context.addCurve(to: line[i], control1: line[i-2], control2: line[i-1]);
context.strokePath();
}
}
}
}
지워지긴 하지만 뭔가 이상합니다
펜이 지나간 영역의 다른 line도 eraser type으로 다시 그려지는 현상이 발생합니다
처음엔 각각의 line마다 type을 저장하는 방법을 생각했지만,
line을 그리는 동작이 끝날 때마다 화면을 이미지로 저장하고 이를 활용하기로 했습니다
( 전자칠판을 구현할때는 통신을 위해 line마다 type을 저장하기는 해야할 것 같습니다 )
이렇게 하면 line을 저장하는 배열도 한단계 간단해집니다
// var lines = [[CGPoint]]();
var line = [CGPoint]()
var bitmapImgs = [NSImage]() // bitmap data vs NSImage ???????
override func mouseDown(with event: NSEvent) {
let currentPoint = CGPoint(x: event.locationInWindow.x, y: event.locationInWindow.y)
line.append(currentPoint)
}
override func mouseDragged(with event: NSEvent) {
let newTouchPoint = CGPoint(x: event.locationInWindow.x, y: event.locationInWindow.y)
let count = line.count
line.append(newTouchPoint)
if count > 0 && count % 3 == 0 {
let rect = getRect(newTouchPoint, line[count-3], line[count-2], line[count-1])
self.setNeedsDisplay(rect)
}
}
override func mouseUp(with event: NSEvent) {
NSGraphicsContext.saveGraphicsState()
guard let rep = bitmapImageRepForCachingDisplay(in: self.bounds) else { return }
self.cacheDisplay(in: self.bounds, to: rep)
let image = NSImage(size: self.bounds.size)
image.addRepresentation(rep)
bitmapImgs.append(image)
line.removeAll()
NSGraphicsContext.restoreGraphicsState()
}
func drawLines(){
...
// lines.forEach { line in
for i in stride(from: 0, to: line.count, by: 3) {
if i != 0 {
context.move(to: line[i-3]);
context.addCurve(to: line[i], control1: line[i-2], control2: line[i-1]);
context.strokePath();
}
}
// }
}
undo도 다음과 같이 변경되어야 합니다
func undo(){
if bitmapImgs.popLast() != nil {
self.needsDisplay = true;
}
}
다른 일정이 있어 이정도 진행하고 마무리했습니다
상당히 재미있게 준비했던 프로젝트라 킥오프를 기다렸으나... :(
해당 프로젝트는 엎어졌지만, 좋은 경험이었다고 생각되어서 공유합니다