Also see the list of articles, none to be taken seriously.
Now looking for weapons of mass digestion.
(Sorry.)
Today’s hike up Mt. Monadnock in New Hampshire, with the the Boston AMC YM group: 25 pictures.
To continue on the recent image resizing theme (probably of interest to Python scripters only), I made some changes as a result of upgrading to Panther last week. I wanted to use the new built-in Mac OS X version of Python 2.3 (plus the MacPython Extras from Jack Jansen—thanks, Jack!). But a problem with the initial Package Manger distribution of the Python Imaging Library made me look at a new Panther feature that let Python scripts use the native Quartz graphics library directly. (The hitch with PIL was that it was built to require a Fink install of libjpeg for full JPEG support. A quick compile of libjpeg and placement of it and its headers into Fink’s preferred locations didn’t work, and either installing Fink or compiling PIL from source would have taken a while.)
That was as good a reason as any to explore Panther’s new Quartz scripting feature. So I read what I could find on Quartz, and modified my photo album code to use Quartz if available. It still uses PIL to gather EXIF and size information, which works even without libjpeg, but then it uses Quartz to manipulate the actual image content.
The results were terrific, mostly. In real-world testing on an 800 MHz PowerBook G4, the PIL-only version spat out 8 JPEGs per minute, and the Quartz version spat out 65 JPEGs per minute. That’s a welcome improvement, especially when you multiply my typical batch of 100 photos by 3 sizes apiece.
The one problem is that I don’t yet know how to set the quality level. There’s a parameter that should contain this number, but as far as I can tell it isn’t documented anywhere. All of the supplied examples save as PNG or PDF, rather than JPEG, and the function isn’t documented along with the rest of Quartz because it’s not a real Quartz function—the release notes say that image export is actually handled through QuickTime. (This will be the first public mention in the history of the world, as far as Google is concerned, of the Core Graphics function that the API summary says it calls: CGBitmapContextWriteToFile. The last parameter, vaguely named “params” and defaulting to a zero-length string, is where a data structure including the quality level would obviously go.)
So for now it’s using a default JPEG quality level, which, whatever it is, is noticeably worse than the quality=90 setting I used with PIL, especially on thumbnails. Though I haven’t done a controlled side-by-side test, it seemed that lower quality levels resulted in some low-frequency blurriness, which looked much less objectionable than the high-frequency ringing (making macroblock boundaries visible) that PIL tended to show. It looked bad enough that I couldn’t really run PIL with anything below quality=90. And because of the lower quality setting, the file sizes on the Quartz side were half that of the PIL versions.
Here’s all the code the deals with Quartz in the new photo album. newImagesInfo holds a list of destination file paths and pre-calculated pixel dimensions.
def resizeImagesQuartz(origFilename, newImagesInfo): # newImagesInfo is a list of # (newFilename, newWidth, newHeight) tuples if not newImagesInfo: return import CoreGraphics origImage = CoreGraphics.CGImageCreateWithJPEGDataProvider( CoreGraphics.CGDataProviderCreateWithFilename(origFilename), [0,1,0,1,0,1], 1, CoreGraphics.kCGRenderingIntentDefault) for newFilename, newWidth, newHeight in newImagesInfo: print "Resizing image with Quartz: ", newFilename, \ newWidth, newHeight cs = CoreGraphics.CGColorSpaceCreateDeviceRGB() c = CoreGraphics.CGBitmapContextCreateWithColor( newWidth, newHeight, cs, (0,0,0,0)) c.setInterpolationQuality(CoreGraphics.kCGInterpolationHigh) newRect = CoreGraphics.CGRectMake(0, 0, newWidth, newHeight) c.drawImage(newRect, origImage) c.writeToFile(newFilename, CoreGraphics.kCGImageFormatJPEG) # final params parameter?
If you’re on a Panther machine with the Developer Tools installed, you can find the examples I started with in:
/Developer/Examples/Quartz/Python/
Seems obvious where they would be in retrospect. Thanks to the folks on the MacPython channel in iChat for pointing me to them.
It meant getting up at 4:10 in the morning to make the drive from Providence, but yesterday I hiked up Mt. Chocorua in New Hampshire with the Boston AMC Young Members group. At least half of us brought cameras, leading to many entries in the Photographs of Photographers series. 105 pictures.
My XMLFilter package was mentioned in Uche Ogbuji’s latest Python XML article on xml.com:
XMLFilter is one of those great examples of a unglamorous but extremely valuable program. Based on its description (and I expect to try it out and report on it in this column soon), it is a must-have for anyone building SAX programs. It provides a fallback SAX parser/driver to avoid SAXReaderNotAvailable errors that users encounter on some platforms. It also offers a safety net against the XMLGenerator bug that bit me earlier in this series. Its main feature, however, is a framework for SAX filters. See Andrew Shearer’s announcement.
Thanks, Uche!
A few days ago, I made changes to my photo album software. Now all current and past photo albums have an optional “large” size with double the pixel count, preserving more detail for users with large screens.
(There are also some other minor improvements, such as a photo count for each album, links to the next and previous albums by date, and more links to related sites.)
The reporter’s sense of irony in this news article pushed it to #1 on Daypop:
Bush told his senior aides Tuesday that he “didn’t want to see any stories” quoting unnamed administration officials in the media anymore, and that if he did, there would be consequences, said a senior administration official who asked that his name not be used.
Tim Bray is looking for a better way to post photos to his web site. To judge from the sample photo, his current method doesn’t antialias the image, so sharp edges in the original look jagged when reduced in size.
I went through the same thing with iPhoto, which has an HTML Export feature that is similarly broken—it doesn’t antialias at all. It’s a strange limitation, considering that the Mac OS X graphics system has fast, high-quality antialiasing everywhere else, including fonts and Dock icons. It’s as if Apple turned off a global switch in iPhoto for better performance when displaying large number of images onscreen, but forgot to turn it back on for HTML exporting, where quality should count for much more.
In any case, the quality of iPhoto’s exports was poor, so I wrote a Python script to handle the export using the Python Imaging Library. (Contact me if you’d like the code. So far, I’ve publicly released only the general-purpose plist parser that I wrote to handle the AlbumData.xml file.)
The script reads the titles and comments assigned in iPhoto, and parses them for category and other tagging information I’ve appended to the comments. Then it generates date-based and category-based HTML page hierarchies for all the albums whose names start with "Web-", and generates any thumbnails or medium-sized images that are missing.
The Python Imaging Library, or PIL, is very easy to install with MacPython 2.3’s Package Manager.
There are some drawbacks, though:
On the positive side, the antialiasing looks good, and PIL can also read embedded EXIF data. Images that I’ve tagged as deserving more info automatically get the aperture and shutter speed printed on the page.
The code for actually reducing and saving the image, ignoring the EXIF and album manipulations for now, is as simple as this:
if not os.path.exists(newPath): shrunkImage = im.resize(size, resample = PIL.Image.ANTIALIAS) shrunkImage.save(newPath, 'JPEG', quality = 90)
You can see samples in my Pictures section. Check out the first batch of Providence photos for some night examples with shutter speeds and apertures shown, and the Providence and Boston kayaking photos for examples of pictures with lots of edges that would have looked much worse without antialiasing.
I nearly went to Jacob Reider’s Medicine and Weblogs session at BloggerCon yesterday, but the weather was so nice that I went kayaking in Boston instead.
The RICKA-organized trip went from Magazine Beach down the Charles River into Boston Harbor, and was a great day’s paddle. (...Even though I only had 4 hours of sleep the night before, because of a web server power supply failure that meant rigging up other servers to stand in during the replacement.)
After eliminating, or just not taking, many pictures due to a lens fogging problem that struck my camera’s waterproof case for the first time (meaning that I hadn’t thought to bring any defogging solution), I still have more than 100 pictures.
Some good quotes here from a recent speech.