Sunday, December 6, 2015

Images and Icons

As I mentioned previously, I've been programming again for OS X and trying to learn Swift. Although I really like Xcode, I also seek the simplicity of running Swift programs from the command line. For reasons I will explain in a minute, I was thinking about images and drawing. I remembered an example with something similar (here, with references in the link), where we had an NSView that could be drawn without any window on screen---it was not an app with a GUI. NSView's dataWithPDFInsideRect can construct a PDF and then save it to a file. The first step was to repeat this approach in Swift (although I didn't get around to repeating the actual drawing that we had before).

Here is a screenshot from the playground:

The playground itself is on github here.

This actually works! One indicator which can be seen in the screenshot is the result returned by the last call: true, shown in the results panel on the right. The file we just wrote cannot itself be found in the playground (Show Project Navigator will not help). However, Spotlight does find it, and we can then paste the path to Terminal and view it in Preview:

> open -a Preview /Users/telliott/Library/Containers/com.apple.dt.playground.stub.OSX.pdfs-D6033292-DB23-46A3-AA4D-7C17D756519F/

This behavior is likely due to sandboxing of Playgrounds. To get around it, paste the same code into a file, my example is pdf.test.swift, and then do

> cd Desktop
> xcrun swift pdf.test.swift
>

The pdf then appears on the Desktop (or whatever the current directory is).

One reason for thinking about images was that I would like an icon for my SudokuBlocks app. According to the docs, Apple requires that the developer provide a bunch of icons (10) in different sizes.

It seemed a bit much for hobbyist programming. So what I might have done at this point was to use Preview's Tool -> Adjust Size ..

Instead, I wondered if I could write a Swift command line program (again not using Xcode) that would read an image file, resize it, and write the result to disk.

Reading the file is easy. In a playground, all you need to do is to show Project Navigator, and drag the file to the Resources folder. Then call NSImage(named:"x.png"). (This Playground and all the code and other resources for this post are on github here). The result is an optional.

At this point, I got help (once again) from Mike Ash here.

As he describes, although NSImage is a container that may have one or more representations of a particular image, including an NSBitmapImageRep, he recommends that the correct way to get at the data reliably is to first draw the image into a new NSBitmapImageRep and then look at that.

The initializer is crazy, with 10 different arguments (ref).

The result is again an optional.

The next step is to go through a vaguely recalled dance with NSGraphicsContext, saving the current context, setting up a new one using the NSBitmapImageRep.

Now, any drawing that takes place is done in the image rep. And that is where we do some resizing, calling:

img.drawInRect(dst, fromRect: src, operation: op, fraction: f)

The source src is a CGRect of the size of the image we just loaded (unless you want to clip or something), The destination dst is a rect of the size of image we want to produce. By changing dst we change the reduction or magnification of the image produced. The other two values are:

let op = NSCompositingOperation.CompositeCopy
let f = CGFloat(1.0)

There are a lot of choices for operation, e.g., see here

After that we just grab the data:

let data = imgRep.representationUsingType(.NSPNGFileType, properties: [:])

(notice the empty Dictionary [:]) and write it to disk:

data!.writeToFile("out.png", atomically: true)

The code is in images.swift in the github rep. It's a bit rough. I trimmed it down and added some command line parsing, and used it to resize square png images. That code is in resizer.swift

Usage:

xcrun swift resizer.swift filename sz
xcrun swift resizer.swift x.png 256

The last thing here is about the icon for SudokuBlocks. The official docs say that you should provide 10 different images. But it turns out that it is possible to copy a single image into the project and be done with it. Click on Assets.xcassets and then double click on Appicon and there will be a place to add the files. As a test I made a new project and dragged in a 256 x 256 image. When I build and run it the image will appear in the dock. Then I went to Products and found the app, did control click and view in Finder, and dragged it to the Desktop. It looks like this:
.

And here is a screenshot with the latest version of SudokuBlocks (you can even see it in the Dock):