Plug-ins

Aperture: Plug-In SDK 2.1 Now Available

The Aperture 2.1 SDK is now available to ADC members. This opens the flood gates to third parties who want to write plug-ins that perform adjustments like Dodge and Burn provided with Aperture.
|

Aperture: Adjustment Plug-Ins Starting To Appear

dfx
Tiffen now has their Dfx digital filter suite available as a plug-in for Aperture 2.1:

Dfx digital filter suite offers precision adjustments over its range of effects that cannot be approached by any other digital filter software. A complete edition includes the most comprehensive array today, emulating more than 1,000 varied effects and gels, from factory pre-sets or custom effects, providing total creative and technical control with the most comprehensive and user-friendly filter-effect palette today.

They are offering a 15-day free trial with full availability in May.

Digital Film Tools has Light, Ozone, and Power Stroke, three tools that appear to be available right now.

Light: Using a pre-built light and texture library that includes windows, doors, leaves and abstract patterns, you can add realistic lighting and shadow to scenes just as if you were adding a light at the time of shooting.

Ozone: Inspired by Ansel Adams' Zone System for still photography, Ozone allows you to manipulate the color of an image with incredible flexibility and accuracy using a Digital Zone System. The Digital Zone System takes the spectrum of image values and divides them into 11 discrete zones. The color, brightness, contrast and gamma of each zone can be independently adjusted until you've painted a new picture.

Power Stroke introduces a simple, interactive stroke-based interface to quickly and intuitively perform targeted adjustments. Instead of meticulously selecting regions or hand-painting masks, regions of interest are isolated by drawing a few simple brush strokes with adjustments then made only in those areas. Strokes can be assigned multiple adjustments and effects such as color correction, recoloring or desaturation, colorization of black and white images, blur, fill light for dimly lit image areas and Diffusion/Glow.

|

Aperture: Aperture 2.1 Released -- Adds Image-Editing Plug-Ins

db1
Apple has added image-editing plug-ins to Aperture in this free update to owners of 2.0. The first plug-in is Dodge and Burn, but may more are coming. From the Aperture Resources page:

Aperture 2 includes a powerful plug-in architecture for the seamless integration of popular third-party image editing software, such as Nik Software's Viveza and PictureCode's Noise Ninja (both available soon).

Aperture 2.1 also includes an Apple developed plug-in, Dodge & Burn, that provides tools for making selective edits of images with dodge (lighten), burn (darken), blur, sharpen, and saturation effects.

The free Imaging Plug-in Software Developer Kit (SDK) for Aperture will be available through the Apple Developer Connection (ADC) soon. Interested developers should contact Apple at aperturedeveloper@apple.com.

There is a new movie that shows Dodge and Burn in operation.
This considerably opens up the the usefulness of Aperture and will create a whole ecosystem of very useful additions. I will be very interested to see the SDK when it arrives.

In addition to the plug-in, there are a host of other features added:

• Customize Default Adjustment Set. You can now specify which adjustments appear by default in the Adjustments Inspector/HUD.

• Updated Crop Tool. A simplified UI makes it easier to preserve an image’s original aspect ratio, match the aspect ratio of your display, or use one of the standard preset aspect ratios.

• Sorting in All Projects View. A contextual menu allows you to sort the All Projects view in ascending or descending date order.
• Show on Map A contextual menu allows you to choose the Show on Map by right-clicking (or Control-clicking) on an image that contains GPS
data.

• Access to Toolbar on Second Display. When using multiple displays in Full Screen mode, the Full Screen toolbar is now accessible on a second display.

• “Snapshots” book theme. This additional theme includes new “photo border” frames in which to place images.

• Flip Images. You can now flip images horizontally or vertically within Aperture.

• Vignette. The range of gamma and exposure settings available has been expanded.

• Save Books as JPEG or TIFF images. Automator workflows have been added to the PDF pop-up menu in the Print Book window to automatically generate JPEG or TIFF images from book pages.

• Update EXIF from Master. This command allows Aperture to reread EXIF from master images after they have been imported.

• 8-bit External Editor support. Preferences settings have been updated to allow you to send images to an external editor as either 8-bit or 16-bit TIFF or PSD files.

• Extended AppleScript support. The “Reveal” verb in the AppleScript dictionary has been extended to include containers such as projects and albums.

The update also includes fixes that impact a number of other areas, including import, Quick Preview, All Projects view, image adjustments, books, printing and export.To get the update (41 Mbytes), go to the Aperture Downloads page. Then check the Late Breaking News from the Help menu.

[Late updates:]

Rob Galbraith has a particularly good write-up on his site.

The catch with the plug-ins is that they work on a copy of the original image, just like round-tripping to an external application like photoshop -- they are not integrated into the RAW processing that Aperture does. This means that you have to separate your plug-in adjustment workflow from your Aperture adjustment workflow, deciding what you are going to do first, last, and in the middle.

The logic of this implementation is understandable when you consider what will happen long-term, and what Apple has control over. Apple controls its own built-in adjustments and processing so can guarantee that they will stay intact, or be enhanced (like the RAW 2.0 processing update we received recently). But the same guarantees cannot be made for third-party plug-ins, so they have to create and store the intermediate images.

The improved crop is still lacking a basic setting: Current. There is no way to scale (except by math and typing) an image that has been custom-cropped and keep the same aspect ratio. There is also no "flip" option that keeps the clip rectangle the same , but rotates it 90 degrees.
|

Aperture Export SDK: Missing Files

For anyone who is trying to make an export plug-in for Aperture: make sure you keep a copy of your Tiger development environment backed up somewhere. I installed Leopard and the dev tools and found that my Random Wok plug-in would not compile.

It failed at this line:

#import <PluginManager/PROAPIAccessing.h>

in ApertureExportPlugIn.h. The PROAPIAccessing.h file is missing because it and PROPlugInBundleRegistration.h are not included in either Leopard or the ApertureSDK 1.5.5.

To fix it , I copied across the Headers folder that contains those two from a Tiger back up. The full path is /Library/Frameworks/PluginManager.framework/Versions/B/Headers. There is already a soft link for Headers that is correct. It should look like this:
aperturesdk
|

Random Wok On Aperture 2.0

My Aperture export plug-in, Random Wok, runs just fine [Update: not quite; see below] with Aperture 2.0 and Leopard. The version I'm using internally has some additional features and I have recompiled it using XCode 3.0, idea being to release an update as soon as I can muster the time. My big hang-up was trying to localize the plug-in. This is a tremendous amount of work and requires lining up volunteers to do the translation and much on the management side of things. So going forward I have decided to not support localization.

Note that Aperture 2.0 no longer displays a sheet to show progress. Instead, it exports in the background and progress is shown in Aperture's tasks window. Look at the bottom of the window next to the tools to see what is happening in the background:
export1
Clicking on the spinner shows more detail:
export2
A new feature in Aperture 2.0 is the ability to pause activity. That plus background operation makes exporting so much nicer.

[Update: A couple of problems have appeared if Random Wok 1.0 is used with Aperture 2.0. See below]

If used with Aperture 2.0 the behavior with respect to clashes with existing files has changed. With Aperture 1.5 Aperture would never supply an existing file name. However with 2.0 it does not check, and so plug-in exports will overwrite existing files with the same version name. Export to empty folders to work around.

If used with Aperture 2.0 the behavior with respect to clashes with existing random files has changed. Random Wok 1.0 will issue a warning if random files are exported that have a name clash, as before. However with 2.0, Aperture does not think that the export has finished and will show the export continuing forever in the background. This is usually only an issue if Freeze is used and repeated exports to the same place are performed. Quit Aperture and relaunch to fix.
|

Aperture: Übermind Releases ÜberUpload FTP Export Plug-in

Übermind has added another Aperture plug-in to its line-up: ÜberUpload. It's an FTP uploader with a host of added features:

• FTP and SFTP support
• Bonjour support for auto-detecting servers on the network
• Safari-like organizable Favorites
• Finder-style browsing of remote servers with Column and Outline views
• Narrow down a list of files on remote servers with Spotlight-like search
• Permissions on uploaded images and new folders simplified
• Zip your images, individually or as one, on upload
|

Aperture: How Do I Set Metadata Views Back To Defaults?

qandasmall
Do you know how to reset all the metadata views back to their default designs?


The metadata view settings are kept in the user's Application Support folder. If you delete the file MetadataSets.plist, Aperture will create a new one with the default settings.

The Application Support folder in the Library which is inside the user's home folder. Aperture has its own folder. Mine looks like this:

applicationsupport
This is also the home of many other settings for Aperture, including the keyword list, watermark images, and plug-ins. Sometimes these files are the cause of mysterious crashes on launch, so it can be a good test to rename the folder and relaunch Aperture to see if the problem goes away.

All these settings only apply to this user, note. There is another Application Support folder with an Aperture folder inside the Library on the boot disk. Here is mine:
applicationsupport2
BorderFX is here because it has an installer that put the plug-in in this central location. This gives access to the plug-in to all users of the machine. On my machine the Sample Projects folder is empty. That's because I trashed its contents after I had played with the images provided. It's worth checking to see if yours is wasting space and trashing the contents if it is.
|

Aperture: SmugMug Export Plug-In Available

aperturetosmugmug
David Michael Holmes has an Aperture Export plug-in for SmugMug called ApertureToSmugMug. While that's the only one that interests me, others may find his other plug-ins for Phanfare, PBase, and ZenFolio useful.
|

Aperture: DNGExport Plug-in

dngexport
Micah Walter has released a beta Aperture plug-in that exports images as DNGs. It's a wrapper that calls Adobe's DNG Converter application, so that pretty much defines its functionality. You'll need to download the converter from Adobe as well as the plug-in.
|

Aperture 1.5.6 Provides Compatibility With Leopard

Apple has just released an update to Aperture 1.5.6 (130MB):

Recommended for all licensed Aperture customers, the Aperture 1.5.6 Update addresses issues related to performance, improves overall stability, and supports compatibility with Mac OS X Leopard v10.5.

• Resolves some minor compatibility issues with iPhoto 7.1, which organizes images by Event rather than Roll.
• Addresses issues related to metadata and sort order when sharing previews with iLife Media Browser.
• Improves reliability of queries based on Import Session.
• Addresses reliability when recovering an Aperture Library from a Vault.

Update: Later today Apple will release the Aperture Export SDK 1.5.5. This provides only minor changes. Notably there are no new APIs provided. I am seeing reports that Aperture is fast on Leopard. I'll get a chance to try it out later today or tomorrow when I have Leopard in my hands.
|

Übermind Releases Aperture to Picasa Web Albums 1.3

aperturetopicasa-icon
Übermind has a number of Aperture export plug-ins available. They have just updated Aperture To Picasa Web Albums to 1.3, increasing the upload speed.
|

Intimidated By Keywording -- Use Captions Instead

Fraser Speirs, author of FlickrExport plug-in for iPhoto and Aperture, is intimidated by keywording:

I’ve never really known why I couldn’t get into it, but last night I realised: Aperture’s beautiful hierarchical keywording system paralyses some part of my brain.

I made a reply in the comments that I reproduce here:

Better than keywording is captioning. The problem with keywording is that there is a temptation to worry about creating consistency, planning for the future, not duplicating things etc. It’s like trying to organize a chest of drawers when you have more kinds of things than you have drawers and you know that you will have to accomodate more things in the future that you’ve never even heard of. Keywording really exists to help other people.

So caption instead. Aperture allows you to layer captions. Caption everything with “Beach trip with Brian and Jan”, then add “In the car”, “On the beach”, “In the sea”, and “Evening bonfire” as appropriate. Then caption some of those with “Down the winding path” and others with “Falling into the water”, adding “Silly face”, “Too much beer”, and “Not enough beer” to others.

Captions are far richer and will capture much more of what is going on. Do you really have keywords for Silly, Face, Beer, Beach, Too much, Not enough, Winding, Path, etc.? They’re not noun-bound as keywords tend to be. And there is no structure. Why should there be? No use for it. Captions are there to help you. Think of them as evidence, not proof.

Really. Don't go overboard with rigidity. The takeaway is simple: keywords are for other people; captions are for you. Ask yourself why you are applying metadata.
|

Aperture: A French Aperture Site

logoaperture20060925
I thought I knew all the Aperture sites by now, but this recently proved not to be the case. Fran
çois Couderc emailed me to let me know that he has a French-language site dedicated to Aperture. It includes tutorials, a large number of videos, plug-ins, Automator workflows, and a forum. The other French-language site I know of is Aperweb.
|

Extending Aperture 1.5

Apple has a 32 minute seminar on extending Aperture posted on their site. It looks at Applescript, Automator, export plug-ins, and iLife integration.
|

Aperture: BorderFX Export Plug-In

exportfx7
BorderFX is a new Aperture export plug-in from Reinhard Uebel. It's a beta, and it's free, but you can donate on his web site. You can leave comments on his blog as well.

BorderFX comes with an installer that appeared to freeze when I ran it, but apparently it was just waiting for my disks to spin up so it could display them, so give it a little time. The installer puts the plug-in into the system's Application Support/Aperture folder hierarchy, so it requires and Admin password, but is consequently available to all users on the machine.

The window looks like this with a preview of one of the selected images on the right:
exportfx1
The cog menu saves and restores the settings as presets so you can define different borders for different uses:
exportfx3
The Output Sizes menu can also be customized:
exportfx2
The main part of the controls is tabbed and has settings for the image:
exportfx5
And for the border:
exportfx4
I found the controls a little quirky. It was quite hard to get what I wanted and I never did fully figure out how everything interacted. With no scaling option it was hard to see what was happening. I could drag the image around, but I could not figure out what the horizontal yellow lines meant:
exportfx8
It's a beta, so I expect some rough edges. It certainly looks useful.
|

Übermind Releases Aperture to Picasa Web Albums

aperturetopicasa-sshot
Übermind continues to crank out new Aperture plug-ins. The latest release is Aperture To Picasa Web Albums. It automates the process of uploading from Aperture to Picasa.
|

Aperture Plugin: Integrating Localized Data Part 2

cocoasmall
Now the strings in my code and my image are localized into French as well and English, I can move on to the strings in the interface. So far the French nib is just a copy of The English nib, created when I made the French nib localization. I used nibtool to extract the strings before translation, and I use nibtool again to put the translated strings back.

To do this I fire up Terminal, cd to the French.lproj folder and use the following command line:

nibtool -w new.nib -d file.strings Random_Wok.nib

This creates a new nib file with the English strings replaced by French ones. I use the Finder to replace the old nib with the new one and I am done. Now my resources look like this:
rwok344
But if I run the plug-in in French, some of the strings no longer fit:
rwok345
This is unfortunately typical for English. With its huge vocabulary, English can take up as little as 50% of the space of other languages. So my nice tight interface needs adjusting.

And there is a problem with subversion. After checking all of this in I find that my repository does not contain the French files and the NoImage.tiff file is in both the localized and the main folder:
rwok346
To fix this I do two subversion things: svn delete the extra TIFF and svn add the folder and its contents. Svnx could do the delete, but not the add. After a fair amount of trying things that did not consistently work, I eventually went to the command line, did a svn add of the French.lproj folder, then quit and relaunched Xcode, then finally did a commit. That worked and now everything is synchronized.

I fix the layout with IB, but don't neaten it up yet. That's because I want to run it past my translator again to make sure nothing weird has happened that I won't spot. Once the translator has OKed it, I'll peek the pixels and straighten everything.

And then there is the "Images Selected" binding. I have this set up with two display patterns, one for the number of images and one for the pluralization string. For French I have to put the second string in twice since both words gain an "s" in the plural:
rwok347
When I come to add German, this will break. The German strings are "Bild ausgewählt" and "Bilder ausgewählt". There is an "er" added in the plural, not an "s". Japanese is easy: no plurals exist in the language. A better solution is to put both singular and plural strings in the strings file and do all of this in code.

The other parts of this series can be found via the Cocoa page.
|

O'Reilly Podcast On Export Plug-Ins

podcast_tile_170x115
O'Reilly Digital Media has a 21 minute podcast with Richard Kerris (Apple), Micah Walter (Aperture Plugged In), and David Schloss (Aperture Users Professional Network) talking about Aperture plug-ins. You can also subscribe to the Inside Aperture podcast.
|

Übermind Aperture to Picasa Web Albums Beta

aperturetopicasa
Übermind has released a beta of the Aperture to Picasa Web Albums export plug-in. They now have several Aperture plug-ins and are looking for ideas for more.
|

Aperture Plugin: Integrating Localized Data Part 1

cocoasmall
Now my translators have sent back localized versions of Localizable.strings, file.strings, and images that I sent to them, I can integrate them into my project. Here is how my project is organized right now:
rwok330
To localize the NoImage.tiff image, I select NoImage.tiff and get Info, then click on Make File Localizable:
rwok335
This changes the image into a group and shows the targets that it is associated with:
rwok336
The Resources have been rearranged like this:
rwok337
Clicking on the General tab shows the languages that the image is localized for:
rwok338
I'm going to add French, so I click Add Localization and select French. The French image created by Xcode is just a copy of the English image at this stage:
rwok339
To get my French image in, I change its name from PasDimage.tiff to NoImage.tiff so that the code will be able to access it with the same file name and replace the current image in the French.lprog folder via the Finder. Xcode has a handy contextual menu item called Reveal File In Finder to help with this.

To Localize the strings I do the same sequence, this time putting my English Localizable.strings into the English localization as well as the French Localizable.strings file into the French localization.

I localize the nib file too, creating the localization, but just leaving it as a duplicate of the English for now. I want to see how Random Wok works in French with what I have to far. Only a few things will be French at this stage: the progress message and the missing thumbnail image will show me that things are working correctly. But how to run in French?

I go to the International preference pane and move French to the top:
rwok340
Now when I run Aperture it will be French.

But when I do, I find that the NoImage image is not there. And when I run it in English it is not there either. The problem is this code:
rwok341
I get the TIFF file using a path that does not take into account the localization folders (English, French). So the initWithContentsOfFile method fails and returns nil. The fix is to use -pathForResource:ofType:inDirectory with a nil directory name:
rwok342
And now the image shows up correctly when I run in French or English:
rwok343
Next is fixing the interface strings.

The other parts of this series can be found via the Cocoa page.
|

Aperture at WWDC

532173685_ec866721be_o
The Aperture Users Professional Network is having a get-together on Tuesday at WWDC. Click on the image to RSVP. I might be there.
|

Aperture Plugin: Instructions To Localizers

When I sent the materials to my localizers I included a lot of information and some detailed instructions. The idea was to preempt any questions, delays, and mistakes. I put all the localizable files into a folder and ZIPped it for sending. Included in that package were the localized string files from Aperture itself. In that way the translators could see how Apple had expressed the terminology and maintain consistency.

The first item in the instructions was a series of screen shots of the plug-in. Several were needed to show the basic interface plus the pop-up menus popped up. The screen shots give the strings context and make it possible for localizers to work with the plug-in even if they cannot run it.

Here are the instructions I provided:

Enclosures
Enclosed is a ZIP file with a 
Localizable.strings file that was created by the genstrings utility. It contains all the strings that are localized in the application code. Also included is a file.strings file that has all the strings from the nib (user interface) and a TIFF image.

There is a folder for each language enclosed that contains a copy of the strings file used by Aperture. You can use this as a reference to see how Apple has translated things like Version Name. This will help consistency. 


What To Do
You will need to use an editor that can handle UTF-16 encoding. TextWrangler can do this as can many others. Apple recommends this simple editor:

ftp://ftp.apple.com/developer/Tool_Chest/Localization_Tools/ADViewer_2.1.dmg

Edit the text in the strings files to make the second string the translation. For example if I were doing a British English translation:

/* NSMenuItem : (oid:278) */
"Period" = "
Period";

becomes:

/* NSMenuItem : (oid:278) */
"Period" = "
Full stop";

Do not alter the comments or the other strings in the files. Be aware of the semicolon at the end of each line.

The Localizable.strings file is a slightly different format with the comment and the first string telling you what the string is used for:

/* Continue renaming after failure */
"rename-error-continue" = "Continue";

This helps distinguish some subtle differences in meaning or tense that may not be conveyed by English.

The strings may include substitutions. These are identified by a leading %. For example:

"%@
のコピー %d";

The substitutions should be left alone and just the text translated. Notice that some strings have numbers prefixing the substituted arguments. The numbers define the order of the substitutions in the first string:

/* Message in alert dialog when something fails */
"%@ Error! %@ failed!" = "%2$@ blah blim, %1$@ bloo!";

The translated message above reverses the arguments, as may be needed in some languages.

You can enter characters directly into the second string and can use Unicode by prefixing with \U. For example \U0020 is a unicode space.

In addition translations are needed for:

"
No Image"
This appears in the table (as a graphic) when no thumbnail image is available for display. I will create a graphic for each language unless you want to do that. I have enclosed the graphic I currently use.

Please do translate "Random Wok". If you can!

One part of the interface has a plural that is used if more than one image is present: "3 images selected", but "1 image selected". Please provide both the singular and plural translations for this phrase. I handle the pluralizing in code, and depending on the language will have to change the way I do this.

What To Deliver
The files you deliver (Localizable.strings, file.strings, and a file containing the translation for No Image and the two forms of Images Selected) must be encoded
UTF-16. Please ZIP the files together so they don't get mangled by email systems.

For technical background information see Apple's documentation:
http://developer.apple.com/documentation/MacOSX/Conceptual/BPInternational/index.html

Specifically this page:
http://developer.apple.com/documentation/MacOSX/Conceptual/BPInternational/Articles/
NotesForLocalizers.html#//apple_ref/doc/uid/20000044


What Happens Next
I will take your strings files and put them into the project. For those that live in the nib, I duplicate the nib file and then will adjust the layout so that things fit. It is likely, English being such a terse language, that things will have to move somewhat. That is OK, but finding translations of similar size would be most helpful.

I will send you a beta of the plugin when it is ready so you can check that is still OK. Your comments on the beta as well as what I have done with your strings will be welcome.

There were actually some problems. I had mistakenly left a macro in my source files that contained strings that were nothing to do with my plug-in, so that was translated unnecessarily. Despite my efforts with the screen shots, not all of the meaning was clear and I had to answer some questions about some words. One of the ZIP files came back empty for unknown reasons. I also realized that the way I have implemented the pluralization of the number images selected -- providing a method to add the pluralising string "s" -- is unlikely to work in all languages. So I will need to change that.

The name itself, Random Wok turns out to be hard to translate. A Wok is literally a Chinese Pot in Japanese. So I opted for the more phonetic Randamu Wokku ランダム・ウオック. And the phrase No Image is seven characters that I am supposed to keep on one line inside a small button. So I had to request a different meaning for that.
|

Aperture Plugin: Preparing For Localization

cocoasmall
Random Wok 1.0 is currently not localized: the only language it supports is English. Before I release the 1.1 version I am going to add translations for the languages that Aperture supports: French, German, and Japanese. This requires extra files in the plugin's bundle that provide the language information.

To prepare, I replace all the messages and strings that are generated by the program and are human-readable with macros that retrieve the localized version. Code like this:
rwok240
is replaced by code like this:
rwok241
I use the NSLocalizedStringWithDefaultValue macro because it allows me to provide a key (exporting-images in this case) that is not the same as the string Exporting Images.... It also supports use of a specific bundle. I need that because Random Wok is a plugin and otherwise Aperture's main bundle would be used.

Once I have replaced all the strings in my code with macros, I use the terminal to run the genstrings utility on all the source files in my project:
rwok321
I get this entry in the text file Localizable.strings created by genstrings for the exporting-images string above:
rwok242
To provide for other languages the Localizable.strings file is duplicated and the string on the right replaced by the translation. When the plugin is run, the correct language files in the bundle are accessed and the key used in the code (exporting-images) matched with the entry in the file. Since the file is encoded UTF-16, the right hand string can contain ASCII and any unicode characters.

There are two other sources of strings that I need to worry about for localization in my plugin. First, the nib file contains all the strings used in the interface and it is currently English-only. To fix that I will need a new nib file for each language and will probably have to adjust the placement of some interface elements due to the size of the new strings. Second, I have an image that is displayed when there is no thumbnail available that says "No Image". That will have to be replaced with a new image for each language.

Apple provides a tool for helping with the translation of nibs called nibtool. Nibtool used with the -L option extracts all the strings from a nib file and sends them to stdout. The Random Wok nib file generates entries like these:
rwok325
As before, a translation replaces the right hand string. Nibtool is used again to replace the strings in copies of the nib file with the translated versions.

The other parts of this series can be found via the Cocoa page.
|

CocoaHeads May 10: Write An Aperture Plug-In

cocoaheads
CocoaHeads on May 10th at the Apple campus in Cupertino features John Anon giving a presentation on writing Aperture Plug-Ins. More information on Scott Stevenson's site.
|

Aperture Plugin Writer Needed

logo3
Fotki, a photo sharing and printing site, has contacted me looking for someone who can write an Aperture export plugin for their service. If you are interested, email mail me and I will forward to Fotki. I have some travel coming up, so you will not get a response for a few days.
|

Aperture Plugin: Problems With Arrays and Key Presses

cocoasmall
A problem that I encountered along the way as I implemented my cache was that any change to the data always caused the array controller to load all the elements. This was exactly what I was trying to avoid with a cache, yet it was happening.

After much hair-pulling (and posting to Apple's Cocoa mailing list) I figured that the array controller believed that my array (implemented by methods in my Random_Wok class) was immutable, and therefore any observed change must mean that the entire array had changed and so need a reload. The fix was to make the array controller believe that the array was mutable. To do this I added three more methods:
rwok320
These are the mutable array methods. I didn't even have to write any code for them because they are never called. They are just there so that the array controller knows that my array is mutable and so will allow updates to individual elements.

Another problem I found that was while the Page Up and Page Down keys worked on the NSTableView, the Home and End keys did not. A little odd. To fix this I subclassed NSTableView and overrode -keyDown:.

I created a custom class called BTKeydownTableView and told Interface Builder to use it instead of NSTableView. Here is the interface:
rwok256
The implementation is very simple. I read the first character from the event queue and act on it:
rwok257
To scroll the window to the right place I tell the view to scroll to the first or last rows, as required.

The other parts of this series can be found via the Cocoa page.
|

Aperture Plugin: Caching Table Data

cocoasmall
Retrieving the thumbnail image and generating the text for the table is time-consuming and called very often by the array controller that is controlling the table of images. This results in very slow scrolling of my table. To solve this problem, I added caching; the idea being that repeated requests for the same data come out of memory and do so quickly.

Adding caching was relatively simple, at least to implement simply. Here is the code:
rwok314
The cache needs a key to cache on. For this I turn the index number into a string and use that as the key. The cache itself is a mutable dictionary that holds dictionaries:
rwok315
This stores the data I get from Aperture fine and the interface is fast again. But now I have another problem. When the random file name parameters are changed, the cached data becomes stale. So each time a change occurs, I must update the cached text:
rwok316
And the cached images:
rwok317
This code enumerates through the cached data replacing the cached text and thumbnail objects. Bindings take care of the table updates. -updateCachedFilenames is called whenever anything changes that could affect the file name, such as a change of alpha case:
rwok318
-updateCachedImages is called when the type of image changes:
rwok319
This all works well, and is how Random Wok 1.0 was released. But it still has a problem. The cache grows forever, eating memory as it goes. And as the cache grows, the time taken to update the cached data grows. What is really needed is a cache that throws away the oldest entry once it reaches a certain limit and more data is added. So that is what I implemented next.

The other parts of this series can be found via the Cocoa page.
|

Aperture Plugin: Translations Needed

rwok21
My next release of Random Wok will be localized. Since Aperture supports English, French, German, and Japanese my plugin cannot successfully support any more languages than those.

Are there any readers interested in volunteering their language skills to this task? There are probably less than 20 translations needed in all, maybe 150 words total. I will provide plain text files for translation: there is no need to have XCode or any development tools.
|

Aperture Plugin: Improving Image Table Performance

cocoasmall
My first attempt at providing table thumbnails was to load an NSMutableArray with all the images that the user has selected when the plugin starts. Unfortunately there can be a significant delay while they are retrieved from Aperture: above a few hundred the delay becomes irritating. Once the data was in my array, access was fast and the table scrolled up and down smoothly.

To improve start-up performance I replaced my array ivar imageTable with two methods: -countOfImageTableData and -objectInImageTableDataAtIndex:. Now, through the magic of KVC the array controller instantiated in the nib will access these two methods instead of the array. In fact it was trying to do that all along, failing, and falling back to accessing the ivar directly. The idea is that by generating the data on demand, the start-up performance problem will go away and the time taken distributed across all the images as they are viewed in the table.

Here is the code for the first method:
rwok311
The check for the API version exists because my code simply does not support it. There used to be a less efficient way to retrieve thumbnails that I have no intention of coding. The result of this is that the image table is blank if an old API is detected. I also log a message to that effect on initialization, so it is not a complete mystery to the user.

The second method is very simple:
rwok312
The code for -textForImageAtIndex: and -thumbnailForImageAtIndex: is not simple, however. But it is straight forward. The text code gets the properties of the image from Aperture and formats it appropriately. The thumbnail code is much easier to follow:
rwok313
Master images (and potentially any image) don't have thumbnails, so I have to substitute. I do that with a small TIFF image that I put into the plugin bundle that says "No Image".

How did I do? Start-up was fast, even for thousands of images, so that problem was fixed. But there were other problems. Scrolling was dog slow. Logging showed that -countOfImageTableData and -objectInImageTableDataAtIndex: were being called for practically every pixel of scrolling I was doing, retrieving the image and generating the text each time. Horrible.

So the next step was to implement caching: retaining the images and text in memory and delivering those instead of getting them from Aperture each time they are needed.

The other parts of this series can be found via the Cocoa page.
|

Aperture Plugin: Implementing A Table Of Images

cocoasmall
At this point in the project I decided two things: that I was going to release the plugin as 1.0 in a month, and that I needed to change the interface. I had already switched from a vertical arrangement with the prefix on top, to a horizontal arrangement with the prefix on the left. The trigger for this next change was the unsatisfactory display of the example random filename and the need to distinguish between what happens to the random naming when the freeze feature is used and is not.

The final version I decided to go with features a table that shows a thumbnail, plus the image version name, the image caption, and the new random name. This is the clearest way to show what is going to happen: show it happening.
rw1.0mm
By using the propertiesWithoutThumbnailForImageAtIndex: and thumbnailForImageAtIndex: methods I can get the data I need from Aperture for each table entry. So my plan was to bind the table to an array controller and bind the array controller to an array in the Random_Wok object that contained all the thumbnails and text. Updating the thumbnails and text would cause changes to propagate through the controller to the view as needed.

So I added an array controller to the nib and called it ImageTable:
rwok305
I then bound it to the model like this:
rwok306
and set up like this:
rwok307
The model key is imageTableData and I use the keys thumb and text to access the data for the columns. So to support the needs of the array controller I implement an array called imageTableData as an ivar in Random_Wok and fill it with dictionaries with keys thumb and text, corresponding to NSImage and NSString objects I want to display.

I set up the table view like this:
rwok300
Notice that I actually used a custom class for this. More on that later. The first (image) column is set up like this:
rwok301
To display the image, I drag in an image cell. The properties of the image cell is accessed via the small triangle top right:
rwok302
The inspector shows it as an NSImageCell, set up this way:
rwok303
Now onto the bindings. The first column is bound to the array controller and uses the thumb key to get data from the model:
rwok304
The second column is set up this way:
rwok308
And bound to the text key through the array controller like this:
rwok309

As I discovered while attempting to bind each table column to a separate array, setting up table column bindings also automatically sets up the the table bindings. This makes it impossible to bind separate columns to separate array objects: they have to all go to one object and then use the key path to get data from separate places. This pretty much means that you need an array of dictionaries to drive a table.

To provide data for the model, I loaded all the thumbnails and text into the array during the plugin initialization. Through the bindings I was able to change the dictionary contents and have the table update automatically. This all worked fine for a small selection of images.

However if I selected 500 images, the array would take a long time to fill. Worse, this was happening before anything was displayed (since the array controller was set to prepare content), making it look like the plugin has frozen. And changes to the text caused by changing the parameters for the random file names were also very slow because they too would be performed 500 times on all the items in the array.

So another approach was needed.

The other parts of this series can be found via the Cocoa page.
|

Aperture Plugin: Better Logging

cocoasmall
The logging I have been doing is very primitive. I call NSLog() to see what I want to see and have to manually insert and remove these calls (or mess with comments) to control what is going on. It's time for something better. Looking around, I found a handy logging class at Borkware that does much of what I want called MLog.

It implements a logging system that includes the line number and source file name with each message. This is very useful for understanding what is happening when reading the logs. The change I made to that code was to add control over the logging level.

Here are the definitions for my version of MLog.h. I implement seven levels and add the ability to revert to standard NSLog() calls, or to remove all the logging code completely:
rwok250
Macros take care of inserting the correct code and extracting the file name and line number from the preprocessor. The class implements two class methods: one for actually logging messages, and one for setting the current log level. The idea of the level control is that the minimum log level can be set either by an environment variable or by code and only messages logged at that level or above will be shown.

The Implementation includes a static variable that holds the current log level:
rwok251
Initialization is done in the class initializer:
rwok252
It reads the environment variable MLogMinLevel and uses that to set the initial level. The logging code compares the logging level passed to the method with the current level and ignores those below the minimum:
rwok253
The level setter code is very simple:
rwok254
To use the logging, I add lines like this to my code:
rwok260
and it provides messages that look like this that include the level, the file name and the line number:
rwok261
Since I provide logging control with an environment variable, I can quite easily create build configurations that behave differently. If I go to the Project menu and select Edit Project Settings I can duplicate the current Debug and Release configurations and make two new ones:
rwok130
Clicking on on the Build tab lets me set these up:
rwok131
I edit the preprocessor macros to include the symbols I need to control the logging system. The Release No Logging configuration sets __BTREMOVE-LOGGING, for instance. Once set up I can change the current configuration I want to build and run from the main XCode window just by selecting it:
rwok137
For this to fully work I edit the custom scripts I added to copy the executable and run Aperture, since the build names have changed and there are more of them.

The other parts of this series can be found via the Cocoa page.
|

Aperture Plugin: Creating A Universal Binary

cocoasmall
Random Wok is only being compiled for Intel right now. I need a universal binary so that it will run on the PowerPC architecture as well. This is easy to change. I select the Random Wok target:
rwok230
Then click on the Info button, and under the Architectures tab, select what I need:
rwok231
That's all there is to it. Everything happens behind the scenes.

The other parts of this series can be found via the Cocoa page.
|

Aperture Plugin: Automating Builds And Using The Debugger

cocoasmall
So far in this project all my debugging has been done with NSLog() calls since the code is pretty simple. To run my plugin each time I have been dragging the binary from the Build folder to Aperture's export plugins folder, launching Aperture, and then selecting Random Wok from the File > Export menu.

So how about debugging with the debugger? If I do a debug build, go through the same steps, and the run Aperture, my breakpoints are never hit. What is going on?

This is happening because the application, Aperture, is not being run by the debugger, and so my plugin is not being run by the debugger. To make XCode run Aperture I modified the instructions I found in a technical Q & A on Apple's developer site that shows how to handle this situation with a Web Kit plugin. In my case I create a new custom executable in the Projects folder on the left side of the XCode window and set it up this way:
rwok220
Then I make sure that my build options for the debug build are set correctly: no optimization, generate all symbols, don't strip:
rwok221
Now I can set breakpoints and have them hit:
rwok222
I still have to copy the executable and run Aperture manually. But there is a way to fix that. I add a new run script :
rwok224
And set it up like this:
rwok223
The debug version is set up with a symbolic link and the release version with a copy. Here is the full text:
# clean up any previous products/symbolic links in the target folder
if [ -a "${USER_LIBRARY_DIR}/Application Support/Aperture/Plug-Ins/Export/${FULL_PRODUCT_NAME}" ]; then
rm -Rf "${USER_LIBRARY_DIR}/Application Support/Aperture/Plug-Ins/Export/${FULL_PRODUCT_NAME}"
fi

# Depending on the build configuration, either copy or link to the most recent product
if [ "${CONFIGURATION}" == "Debug" ]; then
# if we're debugging, add a symbolic link to the plug-in
ln -sf "${TARGET_BUILD_DIR}/${FULL_PRODUCT_NAME}" \
"${USER_LIBRARY_DIR}/Application Support/Aperture/Plug-Ins/Export/${FULL_PRODUCT_NAME}"
elif [ "${CONFIGURATION}" == "Release" ]; then
# if we're compiling for release, just copy the plugin to the Internet Plug-ins folder
cp -Rfv "${TARGET_BUILD_DIR}/${FULL_PRODUCT_NAME}" \
"${USER_LIBRARY_DIR}/Application Support/Aperture/Plug-Ins/Export/${FULL_PRODUCT_NAME}"
fi

Here is how the debug version looks in the Export folder:
rwok225
Now when I compile and run or compile and debug, the script is run and Aperture is launched. Any breakpoints I have set work.

The other parts of this series can be found via the Cocoa page.
|

Frasier Spiers Talks About The Aperture Export API

garland_logo
Late Night Cocoa has just published a podcast featuring Frasier Spiers. He talks about the Aperture export plugin API.
|

Aperture Plugin: Displaying The Image Count With Bindings

cocoasmall
To give some feedback to the user I want to include a display of the number of images that will be exported. I can do that by using bindings: by binding text on the window to an ivar in my Random_Wok object.

I create an ivar called imagesToProcess and an accessor to set it:
rwok207
In -willBeActivated, I add some code to set it up before it is used:
rwok209
And since changes in the image type (master or version) can change the number of images, I have to set it each time the export type changes:
rwok210
That is all the code except for this method:
rwok208
It returns "s" if imagesToProcess is more than one, otherwise an empty string. I need that in order to implement correct pluralization of my display string.

To display the image count on the window I add an NSTextField in the corner like this:
rwok211
Its value is unimportant because I will be constructing it dynamically with bindings, but it helps to have a descriptive string there. I set up its bindings like this:
rwok212
The Display Pattern string is what does the magic. Value1 and Value2 are bound to different key paths. The first to the value given by imagesToProcess, and the second to the plural string given by pluralImagesToProcess:
rwok213
When these are substituted into the Display Pattern string, the result is what the user needs to see how many images are selected:
rwok214

Because I implemented the accessors and I use them to change the value, bindings take care of doing this display updates automatically. If I select images in Aperture that include some with multiple versions of one master, the image count displayed changes when I click Master and then Version, just as it should.

The other parts of this series can be found via the Cocoa page.
|

Aperture Plugin: Making A Customized Button

cocoasmall
The next feature I want to add is a Bagelturf badge that can be clicked to launch a browser to my home page. For that I need a graphic and some way of making a click open a URL. Since the badge won't obviously be a button, or have a button action, I want the cursor to change to a pointing hand to show that it is clickable.
rwok230
After a lot of messing about with tracking rectangles I discovered that the correct way to implement the pointing hand was by using -resetCursorRects. I have to subclass NSButton and then override -resetCursorRects to define the cursor shape that I want when the cursor is inside my button.

First I drag a button in Interface Builder onto my window and select the Rounded bevel Button type. Then I create a new file and declare it as a subclass of NSButton using this code:
rwok200
Then I add the implementation code:
rwok201
To use the custom class in my nib file, I save those files and drag the .h file onto my nib file. This makes the nib file aware of the custom class and selects the class:
rwok202
Now I can select my button and set its custom class to BTPointingHandButton:
rwok203
By making a small image in Photoshop that has transparent background and saving it as a TIFF, I can have the image lay on the window background. To add the image to my XCode project I drag it onto the Resources folder and opt to copy it in.
rwok231
Clicking on the image and using the inspector shows me what is there:
rwok204
By dragging the image onto the button, the image is automatically set:
rwok205
And finally I can add an action to File's owner via the attributes pane called bagelturfAction and hook it up to the button with control-drag. To make the action open a URL I add the action code to Random_Wok.m and use NSWorkspace's openURL method:
rwok206
Done. Now if I hover over the button the cursor changes and a click launches Safari and goes to my home page.

The other parts of this series can be found via the Cocoa page.
|

Aperture Plugin: Saving The Default Folder

cocoasmall
As currently written the plugin defaults to the same folder each time I run it. This is not convenient. There is a very good chance that I will want to go back to the same folder each time, so I added the exported file path to the defaults that are written to a read from the prefs file.

As part of doing that I changed -defaultDirectory to this code:
rwok199
Here I check to see if the value read from the defaults (_defaultExportPath) is a valid folder using NSFileManager. If it is, then I use it, otherwise I use the built-in default of ~/Documents. This deals with the user moving or renaming the folder between runs.

Another change I have made is to make the Generate button do something. I decided that using the current date and time would make for a good salt value:
rwok197
And here is the latest interface:
rwok198
Note that since I am posting this after the plugin was released, this interface design is no longer current.

The other parts of this series can be found via the Cocoa page.
|

Aperture Plugin: Storing The Settings Between Runs

cocoasmall
[Note: my blogging this project is some way behind the development. Random Wok 1.0 has already been released.]

An inconvenience with the current version is that it always presents blank text fields to the user. I would like to store the prefix, postfix, salt, use salt, random format, random length, and alpha case selection somewhere so that their settings are retained from one run to the next.

Implementing this turned out to be harder than I thought it would be. After reading up on NSUserDefaults and looking at several examples, it all looked straight forward enough. But something very odd happened: the values I were successfully saving and restoring between runs was being stored somewhere, but not in the com.bagelturf.Aperture.Export.Random_Wok file as I had been expecting. Not only were they not in the expected file, the expected file did not exist. It was never created.

The values I was storing were actually getting put into Aperture's own preferences file in ~/Library/Preferences. While under some circumstances this would have been the desired behavior (such as writing my own plugins for my own application), in mine it was not. I could not use most of the NSUserDefaults methods because of this side-effect.

So I created two methods: -getDefaults and -setDefaults and inside them used -persistentDomainForName: and -setPersistentDomain:forName to read and write the complete plist file as a dictionary.

Setting the defaults (that is writing the file) is done like this:
rwok190
It first accesses the shared instance of user defaults and then uses the bundle identifier to retrieve the settings into a mutable dictionary. That dictionary is then updated with the latest values from the ivars, suitably encoded, and written back to the file. This allows older versions of the plugin to work with newer versions of the plist file: anything not used is simply left alone.

Reading the defaults is much more involved. The extra work comes from the need to set up the file if it does not initially exist and to manage plugin version changes. The first part is much like before, with the addition of reading the version from the plugin bundle:
rwok191
Then I deal with the first run. This is indicated by an empty dictionary:
rwok192
This results in a file on the disk, and a mutable variable defaultsDict with the same information. Now I am ready to deal with a change of version number:
rwok193
I have nothing to do: there is only one version so far. If the version has changed, then the new version number is written to the dictionary and out to the file. Finally I am ready to set up the ivars from the dictionary values:
rwok194
I call -getDefaults from the -initWithAPIManager method so the ivars are ready to go when the window is created. Then in -willBeActivated I set up the various elements of the view:
rwok195
Finally in -willBeDeactivated I call -setDefaults to write the current state of the interface to the plist file.

Here is what the final com.bagelturf.Aperture.Export.Random_Wok.plist file looks like:
rwok196
The other parts of this series can be found via the Cocoa page.
|

Random Wok 1.0 Released

rwicon
Random Wok 1.0 has now been released and is available on the Downloads page. It's a Universal binary for Aperture 1.5 or later and Mac OS X 10.4.8 or later. Random Wok exports images from Aperture and gives them random names. For more information on Random Wok see its product page.
|

Aperture Plugin: Dealing With Duplicate Random Names

cocoasmall
Now that I am finally exporting with random file names I feel like I am on the home straight. But there is plenty more still to do: looking for problems, for instance.

There are two things that can go wrong with the export. First there could be an existing file in the folder I am exporting to. This is actually quite likely since repeated exports will create the same names for the same files unless the salt is changed. To solve that I add some code to display an alert if the rename fails. I modify the call to movePath by adding a handler. If there is an error with the rename, the handler method will be called:
rwok137
To support the handler, I added two methods:
rwok138
I had to incorporate a workaround for a bug: the return value of fileManager:shouldProceedAfterError is actually ignored by movePath:toPath:. NO always comes back. So I