前回報告した 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.swiftTestCG ディレクトリで、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))
}
}
}