Saturday 2 May 2015

Drawing into bitmaps and saving as a PNG in Swift on OS X

Not an in depth post today. For a small iOS Swift/SpriteKit game I'm writing for fun I wanted a very basic grass sprite that could be scrolled; to create a parallax effect. This amounts to a 800x400 bitmap which contains sequential isosceles triangles of 40 pixels with random heights (of up to 400 pixels) and coloured using a lawn green colour.

Initially I was creating an SKShapeNode and creating the triangles above but when scrolling the redrawing of these hurt performance, especially when running on the iOS Simulator hence the desire to use a sprite.

I had a go at creating these with Photoshop. Whilst switching to a sprite improved performance the look of the triangles drawn by hand wasn't as good as the randomly generated ones. Therefore I thought I'd generate the sprite.

It wasn't really practical to do this on iOS as the file was needed in Xcode so I thought I'd try experimenting with a command line OS X (Cocoa) program in Swift. A GUI would possibly be nice to preview the results (and re-generate if needed) and to select the save-to file location but this solution sufficed.

I'd not done any non-iOS Swift development and never generated PNGs so various amounts of Googling and StackOverflow-ing was needed. Whilst the results of these searches were very helpful I didn't come across anything showing a complete program to create a bitmap, draw into it and then save so the finished program is presented below. It's also available as a gist.


1:  import Cocoa  
2:    
3:  private func saveAsPNGWithName(fileName: String, bitMap: NSBitmapImageRep) -> Bool  
4:  {  
5:      let props: [NSObject:AnyObject] = [:]  
6:      let imageData = bitMap.representationUsingType(NSBitmapImageFileType.NSPNGFileType, properties: props)  
7:        
8:      return imageData!.writeToFile(fileName, atomically: false)  
9:  }  
10:    
11:  private func drawGrassIntoBitmap(bitmap: NSBitmapImageRep)  
12:  {  
13:      var ctx = NSGraphicsContext(bitmapImageRep: bitmap)  
14:        
15:      NSGraphicsContext.setCurrentContext(ctx)  
16:        
17:      NSColor(red: 124 / 255, green: 252 / 255, blue: 0, alpha: 1.0).set()  
18:        
19:      let path = NSBezierPath()  
20:        
21:      path.moveToPoint(NSPoint(x: 0, y: 0))  
22:        
23:      for i in stride(from: 0, through: SIZE.width, by: 40)  
24:      {  
25:          path.lineToPoint(NSPoint(x: CGFloat(i + 20), y: CGFloat(arc4random_uniform(400))))  
26:          path.lineToPoint(NSPoint(x: i + 40, y: 0))  
27:      }  
28:        
29:      path.stroke()  
30:      path.fill()  
31:        
32:  }  
33:    
34:  let SIZE = CGSize(width: 800, height: 400)  
35:    
36:  if Process.arguments.count != 2  
37:  {  
38:      println("usage: grass <file>")  
39:      exit(1)  
40:  }  
41:    
42:  let grass = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: Int(SIZE.width), pixelsHigh: Int(SIZE.height), bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: NSDeviceRGBColorSpace, bytesPerRow: 0, bitsPerPixel: 0)  
43:        
44:  drawGrassIntoBitmap(grass!)  
45:  saveAsPNGWithName(Process.arguments[1], grass!)  
46: