Upgrade to Pro — share decks privately, control downloads, hide ads and more …

AppKitでお絵描きをしてみよう / macOS native symposium #06

AppKitでお絵描きをしてみよう / macOS native symposium #06

AppKitにはNSBezierPathやNSGraphicsContextを用いた図形描画の方法が用意されています。今回は図形描画の基本を押さえながら、注意点や雑学をご紹介いたします。汎用性が高い図形描画の世界に足を踏み入れてみましょう。

このスライドはmacOS native Symposium #06で使用されたものです。

Kyome (Takuto Nakamura)

August 21, 2022
Tweet

More Decks by Kyome (Takuto Nakamura)

Other Decks in Programming

Transcript

  1. NBD04Ͱͷਤܗඳը • Core Graphics - CGPath (CGMutablePath) - CGContext •

    AppKit - NSBezierPath - NSGraphicsContext Կ͕ҧ͏ͷʁʁ😖
  2. NBD04Ͱͷਤܗඳը • Core Graphics - CGPath (CGMutablePath) - CGContext •

    AppKit - NSBezierPath - NSGraphicsContext ͦΕͧΕͷϥούʔʁ ͲͪΒ͔ͷํ͕ѻָ͍͕ͱ͍͏͜ͱ΋ͳ͍🤔
  3. /4#F[JFS1BUI class CustomView: NSView { override func draw(_ dirtyRect: NSRect)

    { super.draw(dirtyRect) let path = NSBezierPath() path.move(to: NSPoint(x: 10, y: 10)) path.line(to: NSPoint(x: 150, y: 120)) path.curve(to: NSPoint(x: 240, y: 10), controlPoint1: NSPoint(x: 225, y: 180), controlPoint2: NSPoint(x: 240, y: 165)) path.close() path.stroke() } } ͜Μͳײ͡ˠ ͱΓ͋͑ͣඳ͍ͯΈΑ͏ʢجຊʣ
  4. /4#F[JFS1BUI class CustomView: NSView { override func draw(_ dirtyRect: NSRect)

    { super.draw(dirtyRect) } } εςοϓ͝ͱʹ֬ೝͯ͠ΈΑ͏
  5. /4#F[JFS1BUI let path = NSBezierPath() path.move(to: NSPoint(x: 10, y: 10))

    εςοϓ͝ͱʹ֬ೝͯ͠ΈΑ͏ ඳ͖ग़͠ͷ࠲ඪΛҠಈ → ඳ͖ग़͠ͷ࠲ඪ ⤴︎
  6. /4#F[JFS1BUI path.curve(to: NSPoint(x: 240, y: 10), controlPoint1: NSPoint(x: 225, y:

    180), controlPoint2: NSPoint(x: 240, y: 165)) εςοϓ͝ͱʹ֬ೝͯ͠ΈΑ͏ ύεʹϕδΣۂઢΛ௥Ճ → ϕδΣۂઢͷऴ఺ͷ࠲ඪ ੍ޚ఺ͷ࠲ඪ
  7. /4#F[JFS1BUI // ௕ํܗ path.appendRect(rect:) // ପԁ path.appendOval(in:) // ؙ֯ͷ௕ํܗ path.appendRoundedRect(rect:,

    xRadius:, yRadius:) // ހ path.appendArc(from:, to:, radius:) path.appendArc(withCenter:, radius:, startAngle:, endAngle:) ଞʹ΋༷ʑͳύε௥Ճϝιου͕͋Δ
  8. /4#F[JFS1BUI let face = NSBezierPath(ovalIn: NSRect(x: 60, y: 40, width:

    80, height: 80)) let earL = NSBezierPath(ovalIn: NSRect(x: 50, y: 105, width: 40, height: 40)) let earR = NSBezierPath(ovalIn: NSRect(x: 110, y: 105, width: 40, height: 40)) // ύεʹύεΛೖΕΒΕΔ face.append(earL) face.append(earR) face.fill() ෳ߹ύεͷ࡞੡
  9. /4#F[JFS1BUI // ઢ෯ͷઃఆ path.lineWidth = 3 // ύεͷ୺఺ͷॲཧͷઃఆ path.lineCapStyle =

    .round // ύεͷηάϝϯτؒͷॲཧͷઃఆ path.lineJoinStyle = .bevel // ϚΠλʔݶքͷઃఆ path.miterLimit = 5 // ഁઢͷઃఆ path.setLineDash([8.0, 12.0], count: 2, phase: 0) ετϩʔΫͷΧελϚΠζ
  10. /4#F[JFS1BUI extension NSBezierPath { func printPathElement() { var points =

    [CGPoint](repeating: CGPoint.zero, count: 3) for i in (0 ..< self.elementCount) { switch self.element(at: i, associatedPoints: &points) { case .moveTo: print("move:", points[0]) case .lineTo: print("line to:", points[0]) case .curveTo: print("curve to:", points[2]) print("controlPoint1:", points[0]) print("controlPoint2:", points[1]) case.closePath: print("close") @unknown default: fatalError() } } } } ύεͷཁૉΛऔΓग़͢
  11. /4#F[JFS1BUI جຊతʹดͨ͡ύε͸൓࣌ܭճΓΛ৺͕͚Δ let path = NSBezierPath() path.appendRect(NSRect(x: 10, y: 10,

    width: 190, height: 150)) path.appendOval(in: NSRect(x: 40, y: 30, width: 70, height: 60)) NSColor.red.setFill() path.fill() ͜͏͍ͨ͠ ͜͏ͳͬͪΌ͏
  12. /4#F[JFS1BUI جຊతʹดͨ͡ύε͸൓࣌ܭճΓΛ৺͕͚Δ let path = NSBezierPath() path.appendRect(NSRect(x: 10, y: 10,

    width: 190, height: 150)) let ellipse = NSBezierPath() ellipse.appendOval(in: NSRect(x: 50, y: 50, width: 80, height: 70)) // ύεͷਐߦํ޲Λ൓సͤͯ͞௥Ճ͢Δ path.append(ellipse.reversed) NSColor.red.setFill() path.fill() ͘Γ͵͖OK
  13. /4#F[JFS1BUI ύεͷਐߦํ޲Λߟ͑ͳͯ͘΋ྑ͍ํ๏΋͋Δ let path = NSBezierPath() // ͓·͡ͳ͍ path.windingRule =

    .evenOdd path.appendRect(NSRect(x: 10, y: 10, width: 190, height: 150)) path.appendOval(in: NSRect(x: 50, y: 50, width: 80, height: 70)) NSColor.red.setFill() path.fill() ͍͍ײ͡ʹแؚؔ܎Λߟ͑ͯ͘Γൈ͍ͯ͘ΕΔ
  14. /4#F[JFS1BUI ΞϑΟϯม׵ let path = NSBezierPath() path.appendRoundedRect(NSRect(x: 0, y: 0,

    width: 100, height: 100), xRadius: 15, yRadius: 15) path.transform(using: AffineTransform(translationByX: -50, byY: -50)) path.transform(using: AffineTransform(scaleByX: 1.2, byY: 1.4)) path.transform(using: AffineTransform(rotationByRadians: CGFloat.pi / 3)) path.transform(using: AffineTransform(translationByX: 120, byY: 120)) path.fill() →
  15. /4#F[JFS1BUI class CustomView: NSView { override func draw(_ dirtyRect: NSRect)

    { super.draw(dirtyRect) let path = NSBezierPath() NSColor.black.set() path.windingRule = NSBezierPath.WindingRule.evenOdd path.appendRect(self.frame) let apple = NSBezierPath() // Apple apple.move(to: NSPoint(x: 110.89, y: 99.2)) apple.curve(to: NSPoint(x: 105.97, y: 108.09), controlPoint1: NSPoint(x: 109.5, y: 102.41), controlPoint2: NSPoint(x: 107.87, y: 105.37)) apple.curve(to: NSPoint(x: 99.64, y: 115.79), controlPoint1: NSPoint(x: 103.39, y: 111.8), controlPoint2: NSPoint(x: 101.27, y: 114.37)) apple.curve(to: NSPoint(x: 91.5, y: 119.4), controlPoint1: NSPoint(x: 97.11, y: 118.13), controlPoint2: NSPoint(x: 94.4, y: 119.33)) apple.curve(to: NSPoint(x: 83.99, y: 117.59), controlPoint1: NSPoint(x: 89.42, y: 119.4), controlPoint2: NSPoint(x: 86.91, y: 118.8)) apple.curve(to: NSPoint(x: 75.9, y: 115.79), controlPoint1: NSPoint(x: 81.06, y: 116.39), controlPoint2: NSPoint(x: 78.36, y: 115.79)) apple.curve(to: NSPoint(x: 67.58, y: 117.59), controlPoint1: NSPoint(x: 73.31, y: 115.79), controlPoint2: NSPoint(x: 70.54, y: 116.39)) apple.curve(to: NSPoint(x: 60.39, y: 119.49), controlPoint1: NSPoint(x: 64.61, y: 118.8), controlPoint2: NSPoint(x: 62.21, y: 119.43)) apple.curve(to: NSPoint(x: 52.07, y: 115.79), controlPoint1: NSPoint(x: 57.6, y: 119.61), controlPoint2: NSPoint(x: 54.83, y: 118.38)) apple.curve(to: NSPoint(x: 45.44, y: 107.82), controlPoint1: NSPoint(x: 50.3, y: 114.24), controlPoint2: NSPoint(x: 48.09, y: 111.58)) apple.curve(to: NSPoint(x: 38.44, y: 93.82), controlPoint1: NSPoint(x: 42.6, y: 103.8), controlPoint2: NSPoint(x: 40.27, y: 99.14)) apple.curve(to: NSPoint(x: 35.5, y: 77.15), controlPoint1: NSPoint(x: 36.48, y: 88.09), controlPoint2: NSPoint(x: 35.5, y: 82.53)) apple.curve(to: NSPoint(x: 39.48, y: 61.21), controlPoint1: NSPoint(x: 35.5, y: 70.98), controlPoint2: NSPoint(x: 36.82, y: 65.67)) apple.curve(to: NSPoint(x: 47.8, y: 52.74), controlPoint1: NSPoint(x: 41.56, y: 57.63), controlPoint2: NSPoint(x: 44.33, y: 54.81)) apple.curve(to: NSPoint(x: 59.06, y: 49.54), controlPoint1: NSPoint(x: 51.27, y: 50.67), controlPoint2: NSPoint(x: 55.02, y: 49.61)) apple.curve(to: NSPoint(x: 67.76, y: 51.58), controlPoint1: NSPoint(x: 61.27, y: 49.54), controlPoint2: NSPoint(x: 64.16, y: 50.23)) apple.curve(to: NSPoint(x: 74.67, y: 53.62), controlPoint1: NSPoint(x: 71.35, y: 52.94), controlPoint2: NSPoint(x: 73.66, y: 53.62)) apple.curve(to: NSPoint(x: 82.33, y: 51.22), controlPoint1: NSPoint(x: 75.42, y: 53.62), controlPoint2: NSPoint(x: 77.98, y: 52.82)) apple.curve(to: NSPoint(x: 92.73, y: 49.36), controlPoint1: NSPoint(x: 86.43, y: 49.73), controlPoint2: NSPoint(x: 89.9, y: 49.12)) apple.curve(to: NSPoint(x: 110.05, y: 58.53), controlPoint1: NSPoint(x: 100.43, y: 49.98), controlPoint2: NSPoint(x: 106.2, y: 53.03)) apple.curve(to: NSPoint(x: 99.83, y: 76.13), controlPoint1: NSPoint(x: 103.17, y: 62.72), controlPoint2: NSPoint(x: 99.77, y: 68.59)) apple.curve(to: NSPoint(x: 106.17, y: 90.76), controlPoint1: NSPoint(x: 99.89, y: 82), controlPoint2: NSPoint(x: 102.01, y: 86.88)) apple.curve(to: NSPoint(x: 112.5, y: 94.94), controlPoint1: NSPoint(x: 108.05, y: 92.56), controlPoint2: NSPoint(x: 110.16, y: 93.95)) apple.curve(to: NSPoint(x: 110.89, y: 99.2), controlPoint1: NSPoint(x: 111.99, y: 96.42), controlPoint2: NSPoint(x: 111.46, y: 97.84)) // Leaf apple.move(to: NSPoint(x: 93.25, y: 29.36)) apple.curve(to: NSPoint(x: 88.25, y: 42.23), controlPoint1: NSPoint(x: 93.25, y: 33.96), controlPoint2: NSPoint(x: 91.58, y: 38.26)) apple.curve(to: NSPoint(x: 74.1, y: 49.26), controlPoint1: NSPoint(x: 84.23, y: 46.96), controlPoint2: NSPoint(x: 79.37, y: 49.69)) apple.curve(to: NSPoint(x: 74, y: 47.52), controlPoint1: NSPoint(x: 74.03, y: 48.71), controlPoint2: NSPoint(x: 74, y: 48.13)) apple.curve(to: NSPoint(x: 79.3, y: 34.51), controlPoint1: NSPoint(x: 74, y: 43.1), controlPoint2: NSPoint(x: 75.91, y: 38.38)) apple.curve(to: NSPoint(x: 85.76, y: 29.63), controlPoint1: NSPoint(x: 80.99, y: 32.55), controlPoint2: NSPoint(x: 83.15, y: 30.93)) apple.curve(to: NSPoint(x: 93.15, y: 27.52), controlPoint1: NSPoint(x: 88.37, y: 28.35), controlPoint2: NSPoint(x: 90.83, y: 27.65)) apple.curve(to: NSPoint(x: 93.25, y: 29.36), controlPoint1: NSPoint(x: 93.22, y: 28.14), controlPoint2: NSPoint(x: 93.25, y: 28.75)) apple.line(to: NSPoint(x: 93.25, y: 29.36)) apple.close() apple.transform(using: AffineTransform(scaleByX: 1, byY: -1)) apple.transform(using: AffineTransform(translationByX: -75, byY: 77)) apple.transform(using: AffineTransform(scale: 2.0)) apple.transform(using: AffineTransform(translationByX: frame.midX, byY: frame.midY)) path.append(apple) path.fill() } }
  16. /4#F[JFS1BUI class CustomView: NSView { override func draw(_ dirtyRect: NSRect)

    { super.draw(dirtyRect) let path = NSBezierPath() NSColor.black.set() path.windingRule = NSBezierPath.WindingRule.evenOdd path.appendRect(self.frame) let apple = NSBezierPath() // Apple apple.move(to: NSPoint(x: 110.89, y: 99.2)) apple.curve(to: NSPoint(x: 105.97, y: 108.09), controlPoint1: NSPoint(x: 109.5, y: 102.41), controlPoint2: NSPoint(x: 107.87, y: 105.37)) apple.curve(to: NSPoint(x: 99.64, y: 115.79), controlPoint1: NSPoint(x: 103.39, y: 111.8), controlPoint2: NSPoint(x: 101.27, y: 114.37)) apple.curve(to: NSPoint(x: 91.5, y: 119.4), controlPoint1: NSPoint(x: 97.11, y: 118.13), controlPoint2: NSPoint(x: 94.4, y: 119.33)) apple.curve(to: NSPoint(x: 83.99, y: 117.59), controlPoint1: NSPoint(x: 89.42, y: 119.4), controlPoint2: NSPoint(x: 86.91, y: 118.8)) apple.curve(to: NSPoint(x: 75.9, y: 115.79), controlPoint1: NSPoint(x: 81.06, y: 116.39), controlPoint2: NSPoint(x: 78.36, y: 115.79)) apple.curve(to: NSPoint(x: 67.58, y: 117.59), controlPoint1: NSPoint(x: 73.31, y: 115.79), controlPoint2: NSPoint(x: 70.54, y: 116.39)) apple.curve(to: NSPoint(x: 60.39, y: 119.49), controlPoint1: NSPoint(x: 64.61, y: 118.8), controlPoint2: NSPoint(x: 62.21, y: 119.43)) apple.curve(to: NSPoint(x: 52.07, y: 115.79), controlPoint1: NSPoint(x: 57.6, y: 119.61), controlPoint2: NSPoint(x: 54.83, y: 118.38)) apple.curve(to: NSPoint(x: 45.44, y: 107.82), controlPoint1: NSPoint(x: 50.3, y: 114.24), controlPoint2: NSPoint(x: 48.09, y: 111.58)) apple.curve(to: NSPoint(x: 38.44, y: 93.82), controlPoint1: NSPoint(x: 42.6, y: 103.8), controlPoint2: NSPoint(x: 40.27, y: 99.14)) apple.curve(to: NSPoint(x: 35.5, y: 77.15), controlPoint1: NSPoint(x: 36.48, y: 88.09), controlPoint2: NSPoint(x: 35.5, y: 82.53)) apple.curve(to: NSPoint(x: 39.48, y: 61.21), controlPoint1: NSPoint(x: 35.5, y: 70.98), controlPoint2: NSPoint(x: 36.82, y: 65.67)) apple.curve(to: NSPoint(x: 47.8, y: 52.74), controlPoint1: NSPoint(x: 41.56, y: 57.63), controlPoint2: NSPoint(x: 44.33, y: 54.81)) apple.curve(to: NSPoint(x: 59.06, y: 49.54), controlPoint1: NSPoint(x: 51.27, y: 50.67), controlPoint2: NSPoint(x: 55.02, y: 49.61)) apple.curve(to: NSPoint(x: 67.76, y: 51.58), controlPoint1: NSPoint(x: 61.27, y: 49.54), controlPoint2: NSPoint(x: 64.16, y: 50.23)) apple.curve(to: NSPoint(x: 74.67, y: 53.62), controlPoint1: NSPoint(x: 71.35, y: 52.94), controlPoint2: NSPoint(x: 73.66, y: 53.62)) apple.curve(to: NSPoint(x: 82.33, y: 51.22), controlPoint1: NSPoint(x: 75.42, y: 53.62), controlPoint2: NSPoint(x: 77.98, y: 52.82)) apple.curve(to: NSPoint(x: 92.73, y: 49.36), controlPoint1: NSPoint(x: 86.43, y: 49.73), controlPoint2: NSPoint(x: 89.9, y: 49.12)) apple.curve(to: NSPoint(x: 110.05, y: 58.53), controlPoint1: NSPoint(x: 100.43, y: 49.98), controlPoint2: NSPoint(x: 106.2, y: 53.03)) apple.curve(to: NSPoint(x: 99.83, y: 76.13), controlPoint1: NSPoint(x: 103.17, y: 62.72), controlPoint2: NSPoint(x: 99.77, y: 68.59)) apple.curve(to: NSPoint(x: 106.17, y: 90.76), controlPoint1: NSPoint(x: 99.89, y: 82), controlPoint2: NSPoint(x: 102.01, y: 86.88)) apple.curve(to: NSPoint(x: 112.5, y: 94.94), controlPoint1: NSPoint(x: 108.05, y: 92.56), controlPoint2: NSPoint(x: 110.16, y: 93.95)) apple.curve(to: NSPoint(x: 110.89, y: 99.2), controlPoint1: NSPoint(x: 111.99, y: 96.42), controlPoint2: NSPoint(x: 111.46, y: 97.84)) // Leaf apple.move(to: NSPoint(x: 93.25, y: 29.36)) apple.curve(to: NSPoint(x: 88.25, y: 42.23), controlPoint1: NSPoint(x: 93.25, y: 33.96), controlPoint2: NSPoint(x: 91.58, y: 38.26)) apple.curve(to: NSPoint(x: 74.1, y: 49.26), controlPoint1: NSPoint(x: 84.23, y: 46.96), controlPoint2: NSPoint(x: 79.37, y: 49.69)) apple.curve(to: NSPoint(x: 74, y: 47.52), controlPoint1: NSPoint(x: 74.03, y: 48.71), controlPoint2: NSPoint(x: 74, y: 48.13)) apple.curve(to: NSPoint(x: 79.3, y: 34.51), controlPoint1: NSPoint(x: 74, y: 43.1), controlPoint2: NSPoint(x: 75.91, y: 38.38)) apple.curve(to: NSPoint(x: 85.76, y: 29.63), controlPoint1: NSPoint(x: 80.99, y: 32.55), controlPoint2: NSPoint(x: 83.15, y: 30.93)) apple.curve(to: NSPoint(x: 93.15, y: 27.52), controlPoint1: NSPoint(x: 88.37, y: 28.35), controlPoint2: NSPoint(x: 90.83, y: 27.65)) apple.curve(to: NSPoint(x: 93.25, y: 29.36), controlPoint1: NSPoint(x: 93.22, y: 28.14), controlPoint2: NSPoint(x: 93.25, y: 28.75)) apple.line(to: NSPoint(x: 93.25, y: 29.36)) apple.close() apple.transform(using: AffineTransform(scaleByX: 1, byY: -1)) apple.transform(using: AffineTransform(translationByX: -75, byY: 77)) apple.transform(using: AffineTransform(scale: 2.0)) apple.transform(using: AffineTransform(translationByX: frame.midX, byY: frame.midY)) path.append(apple) path.fill() } } ؤுΕ͹͜Μͳ͜ͱ΋Ͱ͖Δʂ
  17. /4(SBQIJDT$POUFYU άϥϑΟοΫεͷίϯςΩετΛ੍ޚͰ͖Δ w ඳըઌ͸8JOEPXͱ*NBHFͷͲͬͪ  w ృΓͷํ๏͸ͲΜͳ෩ʹ͢Δʁ w ӨΛ͚ͭΔʁ w

    จࣈΛϨϯμϦϯά͢Δͱ͖ͷϑΥϯτ͸ʁ w ΞϯνΤΠϦΞε͸Ͳ͏͢Δʁ w FUD ref. Apple Inc. "Graphics Contexts" (2012) https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CocoaDrawingGuide/GraphicsContexts/GraphicsContexts.html
  18. /4(SBQIJDT$POUFYU ύεͷॏͶృΓͷํ๏Λ৭ʑม͑ΒΕΔ class CustomView: NSView { override func draw(_ dirtyRect:

    NSRect) { super.draw(dirtyRect) guard let context = NSGraphicsContext.current else { return } context.saveGraphicsState() // Լ૚ͷύεΛඳ͘ let pathA = NSBezierPath() // ɾɾɾ // ృΓॏͶͷઃఆΛ͢Δ context.compositingOperation = NSCompositingOperation.clear // ্૚ͷύεΛඳ͘ let pathB = NSBezierPath() // ɾɾɾ context.restoreGraphicsState() } }
  19. ͓·͚ը૾ͰృΓͭͿ͠ ܧ͗໨ͷͳ͍ύλʔϯΛ༻͍Δ͜ͱͰςΫενϟΛషΕΔ let path = NSBezierPath() path.appendOval(in: NSRect(x: 10, y:

    10, width: 200, height: 200)) let pattern = NSImage(imageLiteralResourceName: "pattern") NSColor(patternImage: pattern).setFill() path.fill()
  20. ͓·͚/4(SBEJFOU μϝͳྫ /4$PMPSDMFBS͸࢖ͬͪΌμϝʂ let start = NSColor(red: 0.549, green: 0.757,

    blue: 0.847, alpha: 1.0) let endA = NSColor.clear let endB = start.withAlphaComponent(0.0) let gradientA = NSGradient(starting: start, ending: endA) gradientA?.draw(in: NSRect(x: 5, y: 5, width: 150, height: 150), angle: 90) let gradientB = NSGradient(starting: start, ending: endB) gradientB?.draw(in: NSRect(x: 160, y: 5, width: 150, height: 150), angle: 90) ྑ͍ྫ