前回報告した GNUstep CoreGraphics は、「今更 GNUstep ?」感がありますので、同様な事を Swift で行ってみました。
Linux(Lubuntu 22.04) の Swift(v5.8) を使用しています。
(Swift のインストールは、UbuntuでSwiftの環境構築を行う方法を参照。
バージョンは Ubuntu バージョンに揃えます。)
cairo ライブラリで描画した画像を、OpenGLで texture として描画します。
OpenGLは glut ライブラリを用い、
Use a C library in Swift on Linux - Stack Overflow
に記述されている方法で利用します。
cairo ライブラリは、
AppKid
GitHub - smumriak/AppKid: UI toolkit for Linux in Swift. Powered by Vulkan
にあるCairoGraphics 関連を参照しました。
実行結果
起動時の画面 (Draw Rects) とメニュー(右クリックで表示)
Draw Circles
Draw Paths
Draw Clip
プログラム
ディレクトリ構成
TestCG Package.swift Sources CCairo module.modulemap CFreeGLUT module.modulemap COpenGL module.modulemap COpenGLU module.modulemap main.swift CGContext.swift
TestCG ディレクトリで、swift package init --type executable を実行する。
CCairo 等の各 module は、ディレクトリを作り modulemap ファイルを作成する。
CCairo
module.modulemap
module CCairo [system] { module CairoXlib { header "/usr/include/cairo/cairo-xlib.h" } module Cairo { header "/usr/include/cairo/cairo.h" } link "cairo" export * }
CFreeGLUT
module.modulemap
module CFreeGLUT [system] { header "/usr/include/GL/freeglut.h" link "glut" export * }
COpenGL
module.modulemap
module COpenGL [system] { header "/usr/include/GL/gl.h" link "GL" export * }
COpenGLU
module.modulemap
module COpenGLU [system] { header "/usr/include/GL/glu.h" link "GLU" export * }
Package.swift
// swift-tools-version: 5.8 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription let package = Package( name: "TestCG", targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. // Targets can depend on other targets in this package and products from dependencies. .executableTarget( name: "TestCG", dependencies: ["COpenGL", "COpenGLU", "CFreeGLUT", "CCairo"], path: "Sources"), .systemLibrary( name: "COpenGL"), .systemLibrary( name: "COpenGLU"), .systemLibrary( name: "CFreeGLUT"), .systemLibrary( name: "CCairo"), ] )
main.swift
// The Swift Programming Language // https://docs.swift.org/swift-book //print("Hello, world!") import COpenGL import COpenGLU import CFreeGLUT import CCairo.Cairo import Foundation let width = 640 let height = 480 var drawFlag = 1 func drawRandomPaths(_ context: CGContext, _ width: CGFloat, _ height: CGFloat) -> Void { // Draw random paths (some stroked, some filled) for i in 0...19 { let numberOfSegments = Int.random(in: 0...7) let sx = CGFloat.random(in: 0...1) * width let sy = CGFloat.random(in: 0...1) * height context.move(to: CGPoint( x:CGFloat.random(in: 0...1)*width, y:CGFloat.random(in: 0...1)*height )) for j in 0...numberOfSegments { if (j % 2 == 0) { context.addLine(to: CGPoint( x:CGFloat.random(in: 0...1)*width, y:CGFloat.random(in: 0...1)*height )) } else { context.addCurve( to: CGPoint( x:CGFloat.random(in: 0...1)*height, y:CGFloat.random(in: 0...1)*height ), control1: CGPoint( x:CGFloat.random(in: 0...1)*width, y:CGFloat.random(in: 0...1)*height ), control2: CGPoint( x:CGFloat.random(in: 0...1)*width, y:CGFloat.random(in: 0...1)*height ) ) } } if (i % 2 == 0) { context.addCurve( to: CGPoint(x:sx, y:sy), control1: CGPoint( x:CGFloat.random(in: 0...1)*width, y:CGFloat.random(in: 0...1)*height ), control2: CGPoint( x:CGFloat.random(in: 0...1)*width, y:CGFloat.random(in: 0...1)*height ) ) context.closePath() context.setColor( Double.random(in: 0...1), Double.random(in: 0...1), Double.random(in: 0...1), Double.random(in: 0...1)) context.fillPath() } else { context.lineWidth = CGFloat.random(in: 0...10) + 2 context.setColor( Double.random(in: 0...1), Double.random(in: 0...1), Double.random(in: 0...1), Double.random(in: 0...1)) context.strokePath() } } } func draw(_ context: CGContext, _ width: CGFloat, _ height: CGFloat) -> Void { // background context.setColor(1, 1, 1, 1) context.addRect(CGRect(x:0, y:0, width:width, height:height)) context.fillPath() switch (drawFlag) { case 1: // Draw random rects (some stroked, some filled) for i in 0...19 { if (i % 2 == 0) { context.setColor( Double.random(in: 0...1), Double.random(in: 0...1), Double.random(in: 0...1), Double.random(in: 0...1)) context.addRect(CGRect( x:CGFloat.random(in: 0...1)*width, y:CGFloat.random(in: 0...1)*height, width:CGFloat.random(in: 0...1)*width, height:CGFloat.random(in: 0...1)*height)) context.fillPath() } else { context.lineWidth = CGFloat.random(in: 0...10) + 2 context.setColor( Double.random(in: 0...1), Double.random(in: 0...1), Double.random(in: 0...1), Double.random(in: 0...1)) context.addRect(CGRect( x:CGFloat.random(in: 0...1)*width, y:CGFloat.random(in: 0...1)*height, width:CGFloat.random(in: 0...1)*width, height:CGFloat.random(in: 0...1)*height)) context.strokePath() } } case 2: // Draw random circles (some stroked, some filled) for i in 0...19 { context.addArc( center: CGPoint(x:CGFloat.random(in: 0...1)*CGFloat(width), y:CGFloat.random(in: 0...1)*CGFloat(height)), radius: CGFloat.random(in: 0...1)*((width>height) ? CGFloat(height) : CGFloat(width)), startAngle: 0, endAngle: 2*CGFloat.pi, clockwise: false) if (i % 2 == 0) { context.setColor( Double.random(in: 0...1), Double.random(in: 0...1), Double.random(in: 0...1), Double.random(in: 0...1)) context.fillPath() } else { context.lineWidth = CGFloat.random(in: 0...10) + 2 context.setColor( Double.random(in: 0...1), Double.random(in: 0...1), Double.random(in: 0...1), Double.random(in: 0...1)) context.strokePath() } } case 3: // Draw random paths (some stroked, some filled) drawRandomPaths(context, width, height) case 4: // Cliped: Draw random paths (some stroked, some filled) context.addArc( center: CGPoint(x:width/2, y:height/2), radius: (width>height) ? height/2 : width/2, startAngle: 0, endAngle: 2*CGFloat.pi, clockwise: false) context.closePath() context.clip() drawRandomPaths(context, width, height) context.lineWidth = 1 context.setColor(0, 0, 0, 1) context.strokePath() default: break } } // Menu func showMenuItem(_ val: Int32) { switch (val) { case 1: drawFlag = 1 case 2: drawFlag = 2 case 3: drawFlag = 3 case 4: drawFlag = 4 case 999: glutLeaveMainLoop() default: break } } func setupMenus() { glutCreateMenu(showMenuItem) glutAddMenuEntry("Draw Rects", 1) glutAddMenuEntry("Draw Circles", 2) glutAddMenuEntry("Draw Paths", 3) glutAddMenuEntry("Draw Clip", 4) glutAddMenuEntry("Exit", 999) glutAttachMenu(GLUT_RIGHT_BUTTON) } func display() { let cs = cairo_image_surface_create( CAIRO_FORMAT_ARGB32, Int32(width), Int32(height) ) let c = CGContext(surface: cs, width: width, height: height) // draw draw(c, CGFloat(width), CGFloat(height)) var _texture: GLuint = 0 glGenTextures(1, &_texture); glBindTexture(UInt32(GL_TEXTURE_2D), _texture); glPixelStorei(UInt32(GL_UNPACK_ALIGNMENT), 1); let data = cairo_image_surface_get_data(cs); gluBuild2DMipmaps(UInt32(GL_TEXTURE_2D), Int32(GL_RGBA), Int32(width), Int32(height), UInt32(GL_RGBA), UInt32(GL_UNSIGNED_BYTE), data); glClearColor(0.0, 0.0, 0.0, 0.0) glClear(UInt32(GL_COLOR_BUFFER_BIT)) glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0) glEnable(UInt32(GL_TEXTURE_2D)); glBindTexture(UInt32(GL_TEXTURE_2D), _texture); glColor3f(1,1,1); glBegin(UInt32(GL_QUADS)); glTexCoord2f(0.0, 1.0); glVertex2f(-1.0, -1.0); glTexCoord2f(0.0, 0.0); glVertex2f(-1.0, 1.0); glTexCoord2f(1.0, 0.0); glVertex2f(1.0, 1.0); glTexCoord2f(1.0, 1.0); glVertex2f(1.0, -1.0); glEnd(); glFlush() } var localArgc = CommandLine.argc glutInit(&localArgc, CommandLine.unsafeArgv) glutInitDisplayMode(UInt32(GLUT_SINGLE | GLUT_RGB)) glutInitWindowPosition(80, 80) glutInitWindowSize(Int32(width), Int32(height)) glutCreateWindow("CairoGraphics") glutDisplayFunc(display) setupMenus() glutMainLoop()
CGContext.swift
// The Swift Programming Language // https://docs.swift.org/swift-book // // CGContext.swift // CairoGraphics // // Created by Serhii Mumriak on 03.02.2020. // import Foundation import CCairo.Cairo open class CGContext { public var surface: Optional<OpaquePointer> public var context: Optional<OpaquePointer> public internal(set) var height: Int = 0 public internal(set) var width: Int = 0 public init(surface: Optional<OpaquePointer>, width: Int, height: Int) { self.context = cairo_create(surface) self.surface = surface self.width = width self.height = height } } public extension CGContext { func flush() { cairo_surface_flush(surface) } } public extension CGContext { func beginPath() { cairo_new_path(context) } func closePath() { cairo_close_path(context) } var currentPointOfPath: CGPoint { var x: Double = .zero var y: Double = .zero cairo_get_current_point(context, &x, &y) return CGPoint(x: x, y: y) } var boundingBoxOfPath: CGRect { var x1: Double = .zero var y1: Double = .zero var x2: Double = .zero var y2: Double = .zero cairo_path_extents(context, &x1, &y1, &x2, &y2) if x1.isZero && y1.isZero && x2.isZero && y2.isZero { return .null } else { return CGRect(x: min(x1, x2), y: min(y1, y2), width: max(x1, x2) - min(x1, x2), height: max(y1, y2) - min(y1, y2)) } } } public extension CGContext { func move(to point: CGPoint) { cairo_move_to(context, Double(point.x), Double(point.y)) } func addLine(to point: CGPoint) { cairo_line_to(context, Double(point.x), Double(point.y)) } func addRect(_ rect: CGRect) { cairo_rectangle(context, Double(rect.origin.x), Double(rect.origin.y), Double(rect.width), Double(rect.height)) } func addCurve(to end: CGPoint, control1: CGPoint, control2: CGPoint) { cairo_curve_to(context, Double(control1.x), Double(control1.y), Double(control2.x), Double(control2.y), Double(end.x), Double(end.y)) } func addQuadCurve(to end: CGPoint, control: CGPoint) { let current = currentPointOfPath let control1 = CGPoint(x: (current.x / 3.0) + (2.0 * control.x / 3.0), y: (current.y / 3.0) + (2.0 * control.y / 3.0)) let control2 = CGPoint(x: (2.0 * control.x / 3.0) + (end.x / 3.0), y: (2.0 * control.y / 3.0) + (end.y / 3.0)) addCurve(to: end, control1: control1, control2: control2) } func addLines(between points: [CGPoint]) { if points.count == 0 { return } move(to: points[0]) for i in 1..<points.count { addLine(to: points[i]) } } func addRects(_ rects: [CGRect]) { for rect in rects { addRect(rect) } } func addArc(center: CGPoint, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat, clockwise: Bool) { if clockwise { cairo_arc_negative(context, Double(center.x), Double(center.y), Double(radius), Double(startAngle), Double(endAngle)) } else { cairo_arc(context, Double(center.x), Double(center.y), Double(radius), Double(startAngle), Double(endAngle)) } } } public extension CGContext { func fillPath() { cairo_fill(context) } func clip() { cairo_clip(context) } func resetClip() { cairo_reset_clip(context) } func strokePath() { cairo_stroke(context) } } public extension CGContext { func fill(_ rect: CGRect) { beginPath() addRect(rect) closePath() } func stroke(_ rect: CGRect) { beginPath() addRect(rect) closePath() strokePath() } } public extension CGContext { func setColor(_ r:Double, _ g:Double, _ b:Double, _ a:Double) { cairo_set_source_rgba(context, r, g, b, a); } var lineWidth: CGFloat { get { return CGFloat(cairo_get_line_width(context)) } set { cairo_set_line_width(context, Double(newValue)) } } }