├── .gitignore ├── BitmapCanvas.swift ├── BitmapCanvas.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── BitmapCanvas └── main.swift ├── LICENSE ├── README.md ├── X11Colors.swift ├── files ├── X11.clr.png ├── X11.clr.zip ├── e_2000000.txt ├── pi_1000000.txt ├── results.json ├── switzerland.gif ├── switzerland.json └── switzerland.png └── img ├── bezier.png ├── bitmap.png ├── cgcontext.png ├── e_walk.png ├── ellipse.png ├── file.png ├── fill.png ├── gradient.png ├── image.png ├── lines.png ├── pi_walk.png ├── points.png ├── polygon.png ├── rainbow_fall.png ├── rects.png ├── schotter.png ├── schotter_color.png ├── text.png └── voronoi.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata 19 | 20 | ## Other 21 | *.xccheckout 22 | *.moved-aside 23 | *.xcuserstate 24 | *.xcscmblueprint 25 | 26 | ## Obj-C/Swift specific 27 | *.hmap 28 | *.ipa 29 | 30 | ## Playgrounds 31 | timeline.xctimeline 32 | playground.xcworkspace 33 | 34 | # Swift Package Manager 35 | # 36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 37 | # Packages/ 38 | .build/ 39 | 40 | # CocoaPods 41 | # 42 | # We recommend against adding the Pods directory to your .gitignore. However 43 | # you should judge for yourself, the pros and cons are mentioned at: 44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 45 | # 46 | # Pods/ 47 | 48 | # Carthage 49 | # 50 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 51 | # Carthage/Checkouts 52 | 53 | Carthage/Build 54 | 55 | # fastlane 56 | # 57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 58 | # screenshots whenever they are needed. 59 | # For more information about the recommended setup visit: 60 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 61 | 62 | fastlane/report.xml 63 | fastlane/screenshots 64 | -------------------------------------------------------------------------------- /BitmapCanvas.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // BitmapCanvas 4 | // 5 | // Created by nst on 04/01/16. 6 | // Copyright © 2016 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | protocol GenericNumber { 12 | var cgFloat: CGFloat { get } 13 | var double: Double { get } 14 | } 15 | 16 | extension CGFloat: GenericNumber { 17 | var cgFloat: CGFloat { return self } 18 | var double: Double { return Double(self) } 19 | } 20 | 21 | extension Int: GenericNumber { 22 | var cgFloat: CGFloat { return CGFloat(self) } 23 | var double: Double { return Double(self) } 24 | } 25 | 26 | extension Double: GenericNumber { 27 | var cgFloat: CGFloat { return CGFloat(self) } 28 | var double: Double { return self } 29 | } 30 | 31 | extension Float: GenericNumber { 32 | var cgFloat: CGFloat { return CGFloat(self) } 33 | var double: Double { return Double(self) } 34 | } 35 | 36 | extension UInt32: GenericNumber { 37 | var cgFloat: CGFloat { return CGFloat(self) } 38 | var double: Double { return Double(self) } 39 | } 40 | 41 | func P(_ x:T, _ y:T) -> NSPoint { 42 | return NSMakePoint(x.cgFloat, y.cgFloat) 43 | } 44 | 45 | infix operator * : MultiplicationPrecedence 46 | 47 | func *(left:GenericNumber, right:GenericNumber) -> Double 48 | { return left.double * right.double } 49 | 50 | infix operator / : MultiplicationPrecedence 51 | 52 | func /(left:GenericNumber, right:GenericNumber) -> Double 53 | { return left.double / right.double } 54 | 55 | infix operator + : AdditionPrecedence 56 | 57 | func +(left:GenericNumber, right:GenericNumber) -> Double 58 | { return left.double + right.double } 59 | 60 | infix operator - : AdditionPrecedence 61 | 62 | func -(left:GenericNumber, right:GenericNumber) -> Double 63 | { return left.double - right.double } 64 | 65 | func RandomPoint(maxX:Int, maxY:Int) -> NSPoint { 66 | 67 | return P(arc4random_uniform(UInt32(maxX+1)), arc4random_uniform(UInt32(maxY+1))) 68 | } 69 | 70 | func R(_ x:T, _ y:T, _ w:T, _ h:T) -> NSRect { 71 | return NSMakeRect(x.cgFloat, y.cgFloat, w.cgFloat, h.cgFloat) 72 | } 73 | 74 | class BitmapCanvas { 75 | 76 | let bitmapImageRep : NSBitmapImageRep 77 | let context : NSGraphicsContext 78 | 79 | var cgContext : CGContext { 80 | return context.cgContext 81 | } 82 | 83 | var width : CGFloat { 84 | return bitmapImageRep.size.width 85 | } 86 | 87 | var height : CGFloat { 88 | return bitmapImageRep.size.height 89 | } 90 | 91 | func setAllowsAntialiasing(_ antialiasing : Bool) { 92 | cgContext.setAllowsAntialiasing(antialiasing) 93 | } 94 | 95 | init(_ width:Int, _ height:Int, _ background:ConvertibleToNSColor? = nil) { 96 | 97 | self.bitmapImageRep = NSBitmapImageRep( 98 | bitmapDataPlanes:nil, 99 | pixelsWide:width, 100 | pixelsHigh:height, 101 | bitsPerSample:8, 102 | samplesPerPixel:4, 103 | hasAlpha:true, 104 | isPlanar:false, 105 | colorSpaceName:NSColorSpaceName.deviceRGB, 106 | bytesPerRow:width*4, 107 | bitsPerPixel:32)! 108 | 109 | self.context = NSGraphicsContext(bitmapImageRep: bitmapImageRep)! 110 | 111 | NSGraphicsContext.current = context 112 | 113 | setAllowsAntialiasing(false) 114 | 115 | if let b = background { 116 | 117 | let rect = NSMakeRect(0, 0, CGFloat(width), CGFloat(height)) 118 | 119 | context.saveGraphicsState() 120 | 121 | b.color.setFill() 122 | NSBezierPath.fill(rect) 123 | 124 | context.restoreGraphicsState() 125 | } 126 | 127 | // makes coordinates start upper left 128 | cgContext.translateBy(x: 0, y: CGFloat(height)) 129 | cgContext.scaleBy(x: 1.0, y: -1.0) 130 | } 131 | 132 | fileprivate func _colorIsEqual(_ p:NSPoint, _ pixelBuffer:UnsafePointer, _ rgba:(UInt8,UInt8,UInt8,UInt8)) -> Bool { 133 | 134 | let offset = 4 * ((Int(self.width) * Int(p.y) + Int(p.x))) 135 | 136 | let r = pixelBuffer[offset] 137 | let g = pixelBuffer[offset+1] 138 | let b = pixelBuffer[offset+2] 139 | let a = pixelBuffer[offset+3] 140 | 141 | if r != rgba.0 { return false } 142 | if g != rgba.1 { return false } 143 | if b != rgba.2 { return false } 144 | if a != rgba.3 { return false } 145 | 146 | return true 147 | } 148 | 149 | fileprivate func _color(_ p:NSPoint, pixelBuffer:UnsafePointer) -> NSColor { 150 | 151 | let offset = 4 * ((Int(self.width) * Int(p.y) + Int(p.x))) 152 | 153 | let r = pixelBuffer[offset] 154 | let g = pixelBuffer[offset+1] 155 | let b = pixelBuffer[offset+2] 156 | let a = pixelBuffer[offset+3] 157 | 158 | return NSColor( 159 | calibratedRed: CGFloat(Double(r)/255.0), 160 | green: CGFloat(Double(g)/255.0), 161 | blue: CGFloat(Double(b)/255.0), 162 | alpha: CGFloat(Double(a)/255.0)) 163 | } 164 | 165 | func color(_ p:NSPoint) -> NSColor { 166 | 167 | guard let data = cgContext.data else { assertionFailure(); return NSColor.clear } 168 | 169 | let pixelBuffer = data.assumingMemoryBound(to: UInt8.self) 170 | 171 | return _color(p, pixelBuffer:pixelBuffer) 172 | } 173 | 174 | fileprivate func _setColor(_ p:NSPoint, pixelBuffer:UnsafeMutablePointer, normalizedColor:NSColor) { 175 | let offset = 4 * ((Int(self.width) * Int(p.y) + Int(p.x))) 176 | 177 | pixelBuffer[offset] = UInt8(normalizedColor.redComponent * 255.0) 178 | pixelBuffer[offset+1] = UInt8(normalizedColor.greenComponent * 255.0) 179 | pixelBuffer[offset+2] = UInt8(normalizedColor.blueComponent * 255.0) 180 | pixelBuffer[offset+3] = UInt8(normalizedColor.alphaComponent * 255.0) 181 | } 182 | 183 | func setColor(_ p:NSPoint, color color_:ConvertibleToNSColor) { 184 | 185 | let color = color_.color 186 | 187 | guard let normalizedColor = color.usingColorSpaceName(NSColorSpaceName.calibratedRGB) else { 188 | print("-- cannot normalize color \(color)") 189 | return 190 | } 191 | 192 | guard let data = cgContext.data else { assertionFailure(); return } 193 | 194 | let pixelBuffer = data.assumingMemoryBound(to: UInt8.self) 195 | 196 | _setColor(p, pixelBuffer:pixelBuffer, normalizedColor:normalizedColor) 197 | } 198 | 199 | subscript(x:Int, y:Int) -> ConvertibleToNSColor { 200 | 201 | get { 202 | let p = P(x, y) 203 | return color(p) 204 | } 205 | 206 | set { 207 | let p = P(x, y) 208 | setColor(p, color:newValue) 209 | } 210 | } 211 | 212 | func fill(_ p:NSPoint, color rawNewColor_:ConvertibleToNSColor) { 213 | // floodFillScanlineStack from http://lodev.org/cgtutor/floodfill.html 214 | 215 | let rawNewColor = rawNewColor_.color 216 | 217 | assert(p.x < width, "p.x \(p.x) out of range, must be < \(width)") 218 | assert(p.y < height, "p.y \(p.y) out of range, must be < \(height)") 219 | 220 | guard let data = cgContext.data else { assertionFailure(); return } 221 | 222 | let pixelBuffer = data.assumingMemoryBound(to: UInt8.self) 223 | 224 | guard let newColor = rawNewColor.usingColorSpaceName(NSColorSpaceName.calibratedRGB) else { 225 | print("-- cannot normalize color \(rawNewColor)") 226 | return 227 | } 228 | 229 | let oldColor = _color(p, pixelBuffer:pixelBuffer) 230 | 231 | if oldColor == newColor { return } 232 | 233 | // store rgba as [UInt8] to speed up comparisons 234 | var r : CGFloat = 0.0 235 | var g : CGFloat = 0.0 236 | var b : CGFloat = 0.0 237 | var a : CGFloat = 0.0 238 | 239 | oldColor.getRed(&r, green: &g, blue: &b, alpha: &a) 240 | 241 | let rgba = (UInt8(r*255),UInt8(g*255),UInt8(b*255),UInt8(a*255)) 242 | 243 | var stack : [NSPoint] = [p] 244 | 245 | while let pp = stack.popLast() { 246 | 247 | var x1 = pp.x 248 | 249 | while(x1 >= 0 && _color(P(x1, pp.y), pixelBuffer:pixelBuffer) == oldColor) { 250 | x1 -= 1 251 | } 252 | 253 | x1 += 1 254 | 255 | var spanAbove = false 256 | var spanBelow = false 257 | 258 | while(x1 < width && _colorIsEqual(P(x1, pp.y), pixelBuffer, rgba )) { 259 | 260 | _setColor(P(x1, pp.y), pixelBuffer:pixelBuffer, normalizedColor:newColor) 261 | 262 | let north = P(x1, pp.y-1) 263 | let south = P(x1, pp.y+1) 264 | 265 | if spanAbove == false && pp.y > 0 && _colorIsEqual(north, pixelBuffer, rgba) { 266 | stack.append(north) 267 | spanAbove = true 268 | } else if spanAbove && pp.y > 0 && !_colorIsEqual(north, pixelBuffer, rgba) { 269 | spanAbove = false 270 | } else if spanBelow == false && pp.y < height - 1 && _colorIsEqual(south, pixelBuffer, rgba) { 271 | stack.append(south) 272 | spanBelow = true 273 | } else if spanBelow && pp.y < height - 1 && !_colorIsEqual(south, pixelBuffer, rgba) { 274 | spanBelow = false 275 | } 276 | 277 | x1 += 1 278 | } 279 | } 280 | } 281 | 282 | func line(_ p1: NSPoint, _ p2: NSPoint, _ color_: ConvertibleToNSColor? = NSColor.black) { 283 | 284 | let color = color_?.color 285 | 286 | context.saveGraphicsState() 287 | 288 | // align to the pixel grid 289 | cgContext.translateBy(x: 0.5, y: 0.5) 290 | 291 | if let existingColor = color { 292 | cgContext.setStrokeColor(existingColor.cgColor); 293 | } 294 | 295 | cgContext.setLineCap(.square) 296 | cgContext.move(to: CGPoint(x: p1.x, y: p1.y)) 297 | cgContext.addLine(to: CGPoint(x: p2.x, y: p2.y)) 298 | cgContext.strokePath() 299 | 300 | context.restoreGraphicsState() 301 | } 302 | 303 | func line(_ p1: NSPoint, length: GenericNumber = 1.0, degreesCW: GenericNumber = 0.0, _ color_: ConvertibleToNSColor? = NSColor.black) -> NSPoint { 304 | let color = color_?.color 305 | let radians = degreesToRadians(degreesCW.double) 306 | let p2 = P(p1.x + sin(radians) * length.cgFloat, p1.y 307 | - cos(radians) * length.cgFloat) 308 | self.line(p1, p2, color) 309 | return p2 310 | } 311 | 312 | func lineVertical(_ p1: NSPoint, height: Double, _ color_: ConvertibleToNSColor? = nil) { 313 | let color = color_?.color 314 | let p2 = P(p1.x, p1.y + CGFloat(height - 1)) 315 | self.line(p1, p2, color) 316 | } 317 | 318 | func lineHorizontal(_ p1:NSPoint, width: Double, _ color_: ConvertibleToNSColor? = nil) { 319 | let color = color_?.color 320 | let p2 = P(p1.x + width - 1, p1.y.double) 321 | self.line(p1, p2, color) 322 | } 323 | 324 | func line(_ p1: NSPoint, deltaX: Double, deltaY: Double, _ color_: ConvertibleToNSColor? = nil) { 325 | let color = color_?.color 326 | let p2 = P(p1.x + deltaX, p1.y + deltaY) 327 | self.line(p1, p2, color) 328 | } 329 | 330 | func rectangle(_ rect:NSRect, stroke stroke_:ConvertibleToNSColor? = NSColor.black, fill fill_:ConvertibleToNSColor? = nil) { 331 | 332 | let stroke = stroke_?.color 333 | let fill = fill_?.color 334 | 335 | context.saveGraphicsState() 336 | 337 | // align to the pixel grid 338 | cgContext.translateBy(x: 0.5, y: 0.5) 339 | 340 | if let existingFillColor = fill { 341 | existingFillColor.setFill() 342 | NSBezierPath.fill(rect) 343 | } 344 | 345 | if let existingStrokeColor = stroke { 346 | existingStrokeColor.setStroke() 347 | NSBezierPath.stroke(rect) 348 | } 349 | 350 | context.restoreGraphicsState() 351 | } 352 | 353 | func polygon(_ points:[NSPoint], stroke stroke_:ConvertibleToNSColor? = NSColor.black, lineWidth:CGFloat=1.0, fill fill_:ConvertibleToNSColor? = nil) { 354 | 355 | guard points.count >= 3 else { 356 | assertionFailure("at least 3 points are needed") 357 | return 358 | } 359 | 360 | context.saveGraphicsState() 361 | 362 | let path = NSBezierPath() 363 | 364 | path.move(to: points[0]) 365 | 366 | for i in 1...points.count-1 { 367 | path.line(to: points[i]) 368 | } 369 | 370 | if let existingFillColor = fill_?.color { 371 | existingFillColor.setFill() 372 | path.fill() 373 | } 374 | 375 | path.close() 376 | 377 | if let existingStrokeColor = stroke_?.color { 378 | existingStrokeColor.setStroke() 379 | path.lineWidth = lineWidth 380 | path.stroke() 381 | } 382 | 383 | context.restoreGraphicsState() 384 | } 385 | 386 | func ellipse(_ rect:NSRect, stroke stroke_:ConvertibleToNSColor? = NSColor.black, fill fill_:ConvertibleToNSColor? = nil) { 387 | 388 | let strokeColor = stroke_?.color 389 | let fillColor = fill_?.color 390 | 391 | context.saveGraphicsState() 392 | 393 | // align to the pixel grid 394 | cgContext.translateBy(x: 0.5, y: 0.5) 395 | 396 | // fill 397 | if let existingFillColor = fillColor { 398 | existingFillColor.setFill() 399 | 400 | // reduce fillRect so that is doesn't cross the stoke 401 | let fillRect = R(rect.origin.x+1, rect.origin.y+1, rect.size.width-2, rect.size.height-2) 402 | cgContext.fillEllipse(in: fillRect) 403 | } 404 | 405 | // stroke 406 | if let existingStrokeColor = strokeColor { existingStrokeColor.setStroke() } 407 | cgContext.strokeEllipse(in: rect) 408 | 409 | context.restoreGraphicsState() 410 | } 411 | 412 | fileprivate func degreesToRadians(_ x:GenericNumber) -> Double { 413 | return (Double.pi * x / 180.0) 414 | } 415 | 416 | func rotate(center p: CGPoint, radians: GenericNumber, closure: () -> ()) { 417 | let c = self.cgContext 418 | c.saveGState() 419 | c.translateBy(x: p.x, y: p.y) 420 | c.rotate(by: radians.cgFloat) 421 | c.translateBy(x: p.x * -1.0, y: p.y * -1.0) 422 | closure() 423 | c.restoreGState() 424 | } 425 | 426 | func save(_ path:String, open:Bool=false) { 427 | guard let data = bitmapImageRep.representation(using: .png, properties: [:]) else { 428 | print("\(#file) \(#function) cannot get PNG data from bitmap") 429 | return 430 | } 431 | 432 | do { 433 | try data.write(to: URL(fileURLWithPath: path), options: []) 434 | if open { 435 | NSWorkspace.shared.openFile(path) 436 | } 437 | } catch let e { 438 | print(e) 439 | } 440 | } 441 | 442 | static func textWidth(_ text:NSString, font:NSFont) -> CGFloat { 443 | let maxSize : CGSize = CGSize(width: CGFloat.greatestFiniteMagnitude, height: font.pointSize) 444 | let textRect : CGRect = text.boundingRect( 445 | with: maxSize, 446 | options: NSString.DrawingOptions.usesLineFragmentOrigin, 447 | attributes: [.font: font], 448 | context: nil) 449 | return textRect.size.width 450 | } 451 | 452 | func image(fromPath path:String, _ p:NSPoint) { 453 | 454 | guard let data = try? Data(contentsOf: URL(fileURLWithPath: path)) else { 455 | print("\(#file) \(#function) cannot read data at \(path)"); 456 | return 457 | } 458 | 459 | guard let imgRep = NSBitmapImageRep(data: data) else { 460 | print("\(#file) \(#function) cannot create bitmap image rep from data at \(path)"); 461 | return 462 | } 463 | 464 | guard let cgImage = imgRep.cgImage else { 465 | print("\(#file) \(#function) cannot get cgImage out of imageRep from \(path)"); 466 | return 467 | } 468 | 469 | context.saveGraphicsState() 470 | 471 | cgContext.scaleBy(x: 1.0, y: -1.0) 472 | cgContext.translateBy(x: 0.0, y: -2.0 * p.y - imgRep.pixelsHigh.cgFloat) 473 | 474 | let rect = NSMakeRect(p.x, p.y, imgRep.pixelsWide.cgFloat, imgRep.pixelsHigh.cgFloat) 475 | 476 | cgContext.draw(cgImage, in: rect) 477 | 478 | context.restoreGraphicsState() 479 | } 480 | 481 | func text(_ text: String, _ p: NSPoint, rotationRadians: GenericNumber?, font: NSFont = NSFont(name: "Monaco", size: 10)!, color color_ : ConvertibleToNSColor = NSColor.black) { 482 | 483 | let color = color_.color 484 | 485 | context.saveGraphicsState() 486 | 487 | if let radians = rotationRadians { 488 | cgContext.translateBy(x: p.x, y: p.y); 489 | cgContext.rotate(by: radians.cgFloat) 490 | cgContext.translateBy(x: -p.x, y: -p.y); 491 | } 492 | 493 | cgContext.scaleBy(x: 1.0, y: -1.0) 494 | cgContext.translateBy(x: 0.0, y: -2.0 * p.y - font.pointSize) 495 | 496 | text.draw(at: p, withAttributes: [.font: font, .foregroundColor: color]) 497 | 498 | context.restoreGraphicsState() 499 | } 500 | 501 | func text(_ text: String, _ p: NSPoint, rotationDegrees degrees: GenericNumber = 0.0, font: NSFont = NSFont(name: "Monaco", size: 10)!, color: ConvertibleToNSColor = NSColor.black) { 502 | self.text(text, p, rotationRadians: degreesToRadians(degrees), font: font, color: color) 503 | } 504 | } 505 | -------------------------------------------------------------------------------- /BitmapCanvas.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 03E114E31C832C83000B8942 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E114E21C832C83000B8942 /* main.swift */; }; 11 | 03E114EA1C832C8B000B8942 /* BitmapCanvas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E114E91C832C8B000B8942 /* BitmapCanvas.swift */; }; 12 | 03E834F91C8C6AB300FAC6AD /* X11Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03E834F81C8C6AB300FAC6AD /* X11Colors.swift */; }; 13 | /* End PBXBuildFile section */ 14 | 15 | /* Begin PBXCopyFilesBuildPhase section */ 16 | 03E114DD1C832C83000B8942 /* CopyFiles */ = { 17 | isa = PBXCopyFilesBuildPhase; 18 | buildActionMask = 2147483647; 19 | dstPath = /usr/share/man/man1/; 20 | dstSubfolderSpec = 0; 21 | files = ( 22 | ); 23 | runOnlyForDeploymentPostprocessing = 1; 24 | }; 25 | /* End PBXCopyFilesBuildPhase section */ 26 | 27 | /* Begin PBXFileReference section */ 28 | 03E114DF1C832C83000B8942 /* BitmapCanvas */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = BitmapCanvas; sourceTree = BUILT_PRODUCTS_DIR; }; 29 | 03E114E21C832C83000B8942 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; 30 | 03E114E91C832C8B000B8942 /* BitmapCanvas.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BitmapCanvas.swift; sourceTree = ""; }; 31 | 03E114EB1C832F1A000B8942 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 32 | 03E834F81C8C6AB300FAC6AD /* X11Colors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = X11Colors.swift; sourceTree = ""; }; 33 | /* End PBXFileReference section */ 34 | 35 | /* Begin PBXFrameworksBuildPhase section */ 36 | 03E114DC1C832C83000B8942 /* Frameworks */ = { 37 | isa = PBXFrameworksBuildPhase; 38 | buildActionMask = 2147483647; 39 | files = ( 40 | ); 41 | runOnlyForDeploymentPostprocessing = 0; 42 | }; 43 | /* End PBXFrameworksBuildPhase section */ 44 | 45 | /* Begin PBXGroup section */ 46 | 03E114D61C832C83000B8942 = { 47 | isa = PBXGroup; 48 | children = ( 49 | 03E114EB1C832F1A000B8942 /* README.md */, 50 | 03E834F81C8C6AB300FAC6AD /* X11Colors.swift */, 51 | 03E114E91C832C8B000B8942 /* BitmapCanvas.swift */, 52 | 03E114E11C832C83000B8942 /* BitmapCanvas */, 53 | 03E114E01C832C83000B8942 /* Products */, 54 | ); 55 | sourceTree = ""; 56 | }; 57 | 03E114E01C832C83000B8942 /* Products */ = { 58 | isa = PBXGroup; 59 | children = ( 60 | 03E114DF1C832C83000B8942 /* BitmapCanvas */, 61 | ); 62 | name = Products; 63 | sourceTree = ""; 64 | }; 65 | 03E114E11C832C83000B8942 /* BitmapCanvas */ = { 66 | isa = PBXGroup; 67 | children = ( 68 | 03E114E21C832C83000B8942 /* main.swift */, 69 | ); 70 | path = BitmapCanvas; 71 | sourceTree = ""; 72 | }; 73 | /* End PBXGroup section */ 74 | 75 | /* Begin PBXNativeTarget section */ 76 | 03E114DE1C832C83000B8942 /* BitmapCanvas */ = { 77 | isa = PBXNativeTarget; 78 | buildConfigurationList = 03E114E61C832C83000B8942 /* Build configuration list for PBXNativeTarget "BitmapCanvas" */; 79 | buildPhases = ( 80 | 03E114DB1C832C83000B8942 /* Sources */, 81 | 03E114DC1C832C83000B8942 /* Frameworks */, 82 | 03E114DD1C832C83000B8942 /* CopyFiles */, 83 | ); 84 | buildRules = ( 85 | ); 86 | dependencies = ( 87 | ); 88 | name = BitmapCanvas; 89 | productName = BitmapCanvas; 90 | productReference = 03E114DF1C832C83000B8942 /* BitmapCanvas */; 91 | productType = "com.apple.product-type.tool"; 92 | }; 93 | /* End PBXNativeTarget section */ 94 | 95 | /* Begin PBXProject section */ 96 | 03E114D71C832C83000B8942 /* Project object */ = { 97 | isa = PBXProject; 98 | attributes = { 99 | LastSwiftUpdateCheck = 0720; 100 | LastUpgradeCheck = 0720; 101 | ORGANIZATIONNAME = "Nicolas Seriot"; 102 | TargetAttributes = { 103 | 03E114DE1C832C83000B8942 = { 104 | CreatedOnToolsVersion = 7.2.1; 105 | LastSwiftMigration = 1010; 106 | }; 107 | }; 108 | }; 109 | buildConfigurationList = 03E114DA1C832C83000B8942 /* Build configuration list for PBXProject "BitmapCanvas" */; 110 | compatibilityVersion = "Xcode 3.2"; 111 | developmentRegion = English; 112 | hasScannedForEncodings = 0; 113 | knownRegions = ( 114 | en, 115 | ); 116 | mainGroup = 03E114D61C832C83000B8942; 117 | productRefGroup = 03E114E01C832C83000B8942 /* Products */; 118 | projectDirPath = ""; 119 | projectRoot = ""; 120 | targets = ( 121 | 03E114DE1C832C83000B8942 /* BitmapCanvas */, 122 | ); 123 | }; 124 | /* End PBXProject section */ 125 | 126 | /* Begin PBXSourcesBuildPhase section */ 127 | 03E114DB1C832C83000B8942 /* Sources */ = { 128 | isa = PBXSourcesBuildPhase; 129 | buildActionMask = 2147483647; 130 | files = ( 131 | 03E114EA1C832C8B000B8942 /* BitmapCanvas.swift in Sources */, 132 | 03E834F91C8C6AB300FAC6AD /* X11Colors.swift in Sources */, 133 | 03E114E31C832C83000B8942 /* main.swift in Sources */, 134 | ); 135 | runOnlyForDeploymentPostprocessing = 0; 136 | }; 137 | /* End PBXSourcesBuildPhase section */ 138 | 139 | /* Begin XCBuildConfiguration section */ 140 | 03E114E41C832C83000B8942 /* Debug */ = { 141 | isa = XCBuildConfiguration; 142 | buildSettings = { 143 | ALWAYS_SEARCH_USER_PATHS = NO; 144 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 145 | CLANG_CXX_LIBRARY = "libc++"; 146 | CLANG_ENABLE_MODULES = YES; 147 | CLANG_ENABLE_OBJC_ARC = YES; 148 | CLANG_WARN_BOOL_CONVERSION = YES; 149 | CLANG_WARN_CONSTANT_CONVERSION = YES; 150 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 151 | CLANG_WARN_EMPTY_BODY = YES; 152 | CLANG_WARN_ENUM_CONVERSION = YES; 153 | CLANG_WARN_INT_CONVERSION = YES; 154 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 155 | CLANG_WARN_UNREACHABLE_CODE = YES; 156 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 157 | CODE_SIGN_IDENTITY = "-"; 158 | COPY_PHASE_STRIP = NO; 159 | DEBUG_INFORMATION_FORMAT = dwarf; 160 | ENABLE_STRICT_OBJC_MSGSEND = YES; 161 | ENABLE_TESTABILITY = YES; 162 | GCC_C_LANGUAGE_STANDARD = gnu99; 163 | GCC_DYNAMIC_NO_PIC = NO; 164 | GCC_NO_COMMON_BLOCKS = YES; 165 | GCC_OPTIMIZATION_LEVEL = 0; 166 | GCC_PREPROCESSOR_DEFINITIONS = ( 167 | "DEBUG=1", 168 | "$(inherited)", 169 | ); 170 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 171 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 172 | GCC_WARN_UNDECLARED_SELECTOR = YES; 173 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 174 | GCC_WARN_UNUSED_FUNCTION = YES; 175 | GCC_WARN_UNUSED_VARIABLE = YES; 176 | MACOSX_DEPLOYMENT_TARGET = 10.11; 177 | MTL_ENABLE_DEBUG_INFO = YES; 178 | ONLY_ACTIVE_ARCH = YES; 179 | SDKROOT = macosx; 180 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 181 | }; 182 | name = Debug; 183 | }; 184 | 03E114E51C832C83000B8942 /* Release */ = { 185 | isa = XCBuildConfiguration; 186 | buildSettings = { 187 | ALWAYS_SEARCH_USER_PATHS = NO; 188 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 189 | CLANG_CXX_LIBRARY = "libc++"; 190 | CLANG_ENABLE_MODULES = YES; 191 | CLANG_ENABLE_OBJC_ARC = YES; 192 | CLANG_WARN_BOOL_CONVERSION = YES; 193 | CLANG_WARN_CONSTANT_CONVERSION = YES; 194 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 195 | CLANG_WARN_EMPTY_BODY = YES; 196 | CLANG_WARN_ENUM_CONVERSION = YES; 197 | CLANG_WARN_INT_CONVERSION = YES; 198 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 199 | CLANG_WARN_UNREACHABLE_CODE = YES; 200 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 201 | CODE_SIGN_IDENTITY = "-"; 202 | COPY_PHASE_STRIP = NO; 203 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 204 | ENABLE_NS_ASSERTIONS = NO; 205 | ENABLE_STRICT_OBJC_MSGSEND = YES; 206 | GCC_C_LANGUAGE_STANDARD = gnu99; 207 | GCC_NO_COMMON_BLOCKS = YES; 208 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 209 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 210 | GCC_WARN_UNDECLARED_SELECTOR = YES; 211 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 212 | GCC_WARN_UNUSED_FUNCTION = YES; 213 | GCC_WARN_UNUSED_VARIABLE = YES; 214 | MACOSX_DEPLOYMENT_TARGET = 10.11; 215 | MTL_ENABLE_DEBUG_INFO = NO; 216 | SDKROOT = macosx; 217 | }; 218 | name = Release; 219 | }; 220 | 03E114E71C832C83000B8942 /* Debug */ = { 221 | isa = XCBuildConfiguration; 222 | buildSettings = { 223 | PRODUCT_NAME = "$(TARGET_NAME)"; 224 | SWIFT_VERSION = 4.2; 225 | }; 226 | name = Debug; 227 | }; 228 | 03E114E81C832C83000B8942 /* Release */ = { 229 | isa = XCBuildConfiguration; 230 | buildSettings = { 231 | PRODUCT_NAME = "$(TARGET_NAME)"; 232 | SWIFT_VERSION = 4.2; 233 | }; 234 | name = Release; 235 | }; 236 | /* End XCBuildConfiguration section */ 237 | 238 | /* Begin XCConfigurationList section */ 239 | 03E114DA1C832C83000B8942 /* Build configuration list for PBXProject "BitmapCanvas" */ = { 240 | isa = XCConfigurationList; 241 | buildConfigurations = ( 242 | 03E114E41C832C83000B8942 /* Debug */, 243 | 03E114E51C832C83000B8942 /* Release */, 244 | ); 245 | defaultConfigurationIsVisible = 0; 246 | defaultConfigurationName = Release; 247 | }; 248 | 03E114E61C832C83000B8942 /* Build configuration list for PBXNativeTarget "BitmapCanvas" */ = { 249 | isa = XCConfigurationList; 250 | buildConfigurations = ( 251 | 03E114E71C832C83000B8942 /* Debug */, 252 | 03E114E81C832C83000B8942 /* Release */, 253 | ); 254 | defaultConfigurationIsVisible = 0; 255 | defaultConfigurationName = Release; 256 | }; 257 | /* End XCConfigurationList section */ 258 | }; 259 | rootObject = 03E114D71C832C83000B8942 /* Project object */; 260 | } 261 | -------------------------------------------------------------------------------- /BitmapCanvas.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /BitmapCanvas.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /BitmapCanvas/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // BitmapCanvas 4 | // 5 | // Created by nst on 28/02/16. 6 | // Copyright © 2016 Nicolas Seriot. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import AppKit 11 | 12 | let PROJECT_PATH = "/Users/nst/Projects/BitmapCanvas" 13 | 14 | func switzerland() { 15 | 16 | guard let resultsData = try? Data(contentsOf: URL(fileURLWithPath: PROJECT_PATH+"/files/results.json")) else { return } 17 | guard let optResults = try? JSONSerialization.jsonObject(with: resultsData, options: []) as? [String:AnyObject] else { return } 18 | guard let results = optResults else { return } 19 | 20 | guard let switzerlandData = try? Data(contentsOf: URL(fileURLWithPath: PROJECT_PATH + "/files/switzerland.json")) else { return } 21 | guard let optSwitzerland = try? JSONSerialization.jsonObject(with: switzerlandData, options: []) as? [String:AnyObject] else { return } 22 | guard let switzerland = optSwitzerland else { return } 23 | 24 | let b = BitmapCanvas(365, 235, "white") 25 | 26 | b.image(fromPath: PROJECT_PATH+"/files/switzerland.gif", P(0,0)) 27 | 28 | b.text("2016-02-28 \"Pas de spéculation sur les denrées alimentaires\"", P(5,220)) 29 | 30 | let values : [Double] = results.compactMap { (k,v) in v as? Double } 31 | 32 | let positiveValues = values.filter { $0 >= 50.0 } 33 | let negativeValues = values.filter { $0 < 50.0 } 34 | 35 | let minPositive = positiveValues.min() ?? 0.0 36 | let maxPositive = positiveValues.max() ?? 0.01 37 | 38 | let minNegative = negativeValues.min() ?? 0.0 39 | let maxNegative = negativeValues.max() ?? 0.01 40 | 41 | let positiveRange = maxPositive - minPositive 42 | let negativeRange = maxNegative - minNegative 43 | 44 | print(minPositive, maxPositive) 45 | print(minNegative, maxNegative) 46 | 47 | for (k, cantonDict) in switzerland { 48 | 49 | guard let labelPoint = cantonDict["label"] as? [Int] else { continue } 50 | 51 | // fill color 52 | 53 | var fillColor: NSColor = .lightGray 54 | if let percent = results[k] as? Double { 55 | if percent < 50.0 { 56 | let i: Double = ((percent - minNegative) / negativeRange) - 0.15 57 | fillColor = NSColor(calibratedRed: 1.0, green: CGFloat(i), blue: i.cgFloat, alpha: 1.0) 58 | } else { 59 | let i: Double = (1.0 - (percent - minPositive) / positiveRange) - 0.15 60 | fillColor = NSColor(calibratedRed: i.cgFloat, green: 1.0, blue: CGFloat(i), alpha: 1.0) 61 | } 62 | } 63 | 64 | // fill cantons 65 | 66 | let fillPoints = cantonDict["fill"] as? [[Int]] ?? [labelPoint] 67 | 68 | for pts in fillPoints { 69 | let p = P(pts[0], pts[1]) 70 | b.fill(p, color: fillColor) 71 | } 72 | 73 | // draw labels 74 | 75 | let p = P(labelPoint[0], labelPoint[1]) 76 | b.text(k, p) 77 | } 78 | 79 | let path = "/tmp/out.png" 80 | 81 | b.save(path, open:true) 82 | } 83 | 84 | func bitmap() { 85 | 86 | let b = BitmapCanvas(32, 32, "PapayaWhip") 87 | 88 | b.save("/tmp/bitmap.png") 89 | } 90 | 91 | func points() { 92 | 93 | let b = BitmapCanvas(32, 32, "PapayaWhip") 94 | 95 | b[1,1] = NSColor.black 96 | 97 | b[1,3] = "red" 98 | b[2,3] = "#00FF00" 99 | b[3,3] = NSColor.blue 100 | 101 | b.save("/tmp/points.png") 102 | } 103 | 104 | func lines() { 105 | 106 | let b = BitmapCanvas(32, 32, "PapayaWhip") 107 | 108 | b.line(P(1,1), P(10,10)) 109 | 110 | b.line(P(1,10), P(10,19), "red") 111 | b.lineHorizontal(P(1,21), width:20) 112 | b.lineVertical(P(20, 1), height:19, "blue") 113 | 114 | b.save("/tmp/lines.png") 115 | } 116 | 117 | func rects() { 118 | 119 | let b = BitmapCanvas(32, 32, "PapayaWhip") 120 | 121 | b.rectangle(R(5,5,20,10)) 122 | 123 | b.rectangle(R(10,10,20,10), stroke:"blue", fill:"magenta") 124 | 125 | b.save("/tmp/rects.png") 126 | } 127 | 128 | func ellipse() { 129 | 130 | let b = BitmapCanvas(32, 32, "PapayaWhip") 131 | 132 | b.ellipse(R(5,5,20,10)) 133 | 134 | b.ellipse(R(10,10,18,21), stroke:"blue", fill:"magenta") 135 | 136 | b.save("/tmp/ellipse.png", open:true) 137 | } 138 | 139 | func fill() { 140 | 141 | let b = BitmapCanvas(32, 32, "PapayaWhip") 142 | 143 | b.line(P(10,0), P(25,31), "red") 144 | 145 | b.ellipse(R(15,10,15,15)) 146 | 147 | b.fill(P(30,1), color:"yellow") 148 | 149 | b.save("/tmp/fill.png") 150 | } 151 | 152 | func text() { 153 | 154 | let b = BitmapCanvas(32, 32, "PapayaWhip") 155 | 156 | b.text("hi", P(5,10)) 157 | 158 | b.text("hello", P(20,30), 159 | rotationDegrees: -90, 160 | font: NSFont(name: "Helvetica", size: 10)!, 161 | color: NSColor.red) 162 | 163 | b.save("/tmp/text.png") 164 | } 165 | 166 | func image() { 167 | 168 | let b = BitmapCanvas(32, 32, "PapayaWhip") 169 | 170 | b.image(fromPath:"/usr/share/httpd/icons/sphere2.png", P(0,0)) 171 | 172 | b.save("/tmp/image.png", open:true) 173 | } 174 | 175 | func polygon() { 176 | 177 | let b = BitmapCanvas(32, 32, "PapayaWhip") 178 | 179 | b.setAllowsAntialiasing(true) 180 | 181 | let points = [P(3,3), P(28,5), P(25,22), P(12,18)] 182 | 183 | b.polygon(points, stroke:"blue", fill:"SkyBlue") 184 | 185 | b.save("/tmp/polygon.png", open:true) 186 | } 187 | 188 | func bezier() { 189 | 190 | let b = BitmapCanvas(32, 32, "PapayaWhip") 191 | 192 | b.setAllowsAntialiasing(true) 193 | 194 | NSColor.orange.setFill() 195 | 196 | let bp = NSBezierPath() 197 | bp.move(to: P(2,2)) 198 | bp.curve(to: P(20,14), controlPoint1: P(14,30), controlPoint2: P(15,30)) 199 | bp.curve(to: P(32,13), controlPoint1: P(24,14), controlPoint2: P(24,19)) 200 | bp.close() 201 | bp.fill() 202 | bp.stroke() 203 | 204 | b.save("/tmp/bezier.png") 205 | } 206 | 207 | func cgContext() { 208 | 209 | let b = BitmapCanvas(32, 32, "PapayaWhip") 210 | let c = b.cgContext 211 | 212 | c.addEllipse(in: R(2, 2, 24, 24)) 213 | c.strokePath() 214 | 215 | b.setAllowsAntialiasing(true) 216 | 217 | c.setStrokeColor(NSColor.blue.cgColor) 218 | c.addEllipse(in: R(12, 12, 24, 24)) 219 | c.strokePath() 220 | 221 | b.save("/tmp/cgcontext.png") 222 | } 223 | 224 | func gradient() { 225 | 226 | let (w, h) = (255, 255) 227 | 228 | let b = BitmapCanvas(w, h) 229 | for i in 0.. 21 | 22 | 23 | 24 |
let b = BitmapCanvas(32, 32, "PapayaWhip")
25 | 26 | 27 | 28 | 29 | 30 |
b[1,1] = NSColor.black
 31 | 
 32 | b[1,3] = "red"
 33 | b[2,3] = "#00FF00"
 34 | b[3,3] = NSColor.blue
35 | 36 | 37 | 38 | 39 | 40 |
b.line(P(1,1), P(10,10))
 41 | 
 42 | b.line(P(1,10), P(10,19), "red")
 43 | b.lineHorizontal(P(1,21), width:20)
 44 | b.lineVertical(P(20, 1), height:19, "blue")
45 | 46 | 47 | 48 | 49 | 50 |
b.rectangle(R(5,5,20,10))
 51 | 
 52 | b.rectangle(R(10,10,20,10), stroke:"blue", fill:"magenta")
53 | 54 | 55 | 56 | 57 | 58 |
b.ellipse(R(5,5,20,10))
 59 | 
 60 | b.ellipse(R(10,10,18,21), stroke:"blue", fill:"magenta")
61 | 62 | 63 | 64 | 65 | 66 |
let b = BitmapCanvas(32, 32, "PapayaWhip")
 67 | 
 68 | b.line(P(10,0), P(25,31), "red")
 69 | 
 70 | b.ellipse(R(15,10,15,15))
 71 | 
 72 | b.fill(P(30,1), color:"yellow")
 73 | 
74 | 75 | 76 | 77 | 78 | 79 |
b.text("hi", P(5,10))
 80 | 
 81 | b.text("hello", P(20,30),
 82 |     rotationDegrees: -90,
 83 |     font: NSFont(name: "Helvetica", size: 10)!,
 84 |     color: NSColor.red)
85 | 86 | 87 | 88 | 89 | 90 |
b.image(fromPath:"/usr/share/httpd/icons/sphere2.png", P(0,0))
91 | 92 | 93 | 94 | 95 | 96 |
let b = BitmapCanvas(32, 32, "PapayaWhip")
 97 | 
 98 | b.setAllowsAntialiasing(true)
 99 | 
100 | let points = [P(3,3), P(28,5), P(25,22), P(12,18)]
101 | 
102 | b.polygon(points, stroke:"blue", fill:"SkyBlue")
103 | 
104 | b.save("/tmp/polygon.png", open:true)
105 | 106 | 107 | 108 | 109 | 110 |
b.setAllowsAntialiasing(true)
111 | 
112 | NSColor.orangeColor().setFill()
113 | 
114 | let bp = NSBezierPath()
115 | bp.moveToPoint(P(2,2))
116 | bp.curveToPoint(P(20,14), controlPoint1: P(14,30), controlPoint2: P(15,30))
117 | bp.curveToPoint(P(32,13), controlPoint1: P(24,14), controlPoint2: P(24,19))
118 | bp.closePath()
119 | bp.fill()
120 | bp.stroke()
121 | 122 | 123 | 124 | 125 | 126 |
let b = BitmapCanvas(32, 32, "PapayaWhip")
127 | let c = b.cgContext
128 | 
129 | c.addEllipse(in: R(2, 2, 24, 24))
130 | c.strokePath()
131 | 
132 | b.setAllowsAntialiasing(true)
133 | 
134 | c.setStrokeColor(NSColor.blue.cgColor)
135 | c.addEllipse(in: R(12, 12, 24, 24))
136 | c.strokePath()
137 | 
138 | b.save("/tmp/cgcontext.png")
139 | 140 | 141 | 142 | 143 | 144 |
b.save("/tmp/image.png", open:true)
145 | 146 | 147 | 148 | 149 | 150 | You can also dump the X11 color list with: 151 | 152 | X11Colors.dump("/opt/X11/share/X11/rgb.txt", outPath:"/tmp/X11.clr") 153 | 154 | or download the file directly [X11.clr.zip](https://raw.githubusercontent.com/nst/BitmapCanvas/master/files/X11.clr.zip) 155 | 156 | ![X11 Color List](files/X11.clr.png) 157 | 158 | Other images with sample code: 159 | 160 | ![gradient](img/gradient.png "gradient") 161 | 162 | ```Swift 163 | let (w, h) = (255, 255) 164 | 165 | let b = BitmapCanvas(w, h) 166 | for i in 0..