One thing I rarely ever do in Java is program anything that requires a desktop UI. If I'm doing any Java programming, it's generally server-side related. Recently that changed when I began working on a Java applet that allows users to upload images (screenshots) in their clipboards directly to the server.
Building the basic applet was pretty straightforward. Even signing the applet (so you can actually query the operating systems clipboard) is pretty straightforward—made even easier once I created an ANT build script.
The applet was progressing nicely, well that's until I tested things on the Mac.
The idea of write-once, run-many is a great idea but that's the one big hype Sun pushed in the early Java days that never came to be. Oh, don't get me wrong; there are some really great cross-platform Java applications, but if you're doing anything beyond the basics you start having to branch code or do some OS testing to get things to work properly.
When you grab data from the clipboard in Java, the object is a type of DataFlavor. Essentially this is a way to determine what type of data is in the clipboard—image, plain text, rich text, etc. To test to see if something is an image, you should be able to test to see if it's a DataFlavor.imageFlavor.
This works great on the PC, but the Mac OS X rolls up it's own DataFlavor. The PC DataFlavor looks like this:
While the Mac image DataFlavor is:
No big deal I figured, there's got to be an easy way to convert this to something I can actually use in Java. However, after much research I could only find one decent method which involves using QuickTime for Java.
The problem is QT for Java is sort of a mess. From doing some reading, it may or may not be installed correctly on the Mac. When installing QuickTime on the Mac OS it's supposed to install the QT for Java library, but on the Mac OS X 10.3 machine I had handy, I actually had to reinstall QuickTime 7 to get things to work.
There's another problem with using the QT for Java libraries. In order to get things to compile and execute correctly on machines without QT for Java, you need to use reflection to create instances and run methods on the QT classes. That leaves you with some pretty pretty ugly code. Fortunately, someone much smarter than me already wrote the code up in a getImageFromPictStream() method.
I was finally making some progress on the applet. I could now grab the contents of the Mac OS clipboard and I could display in my PreviewPane (which extends the Canvas object.) I figured now that I could display it, converting the image to PNG or JPG ByteArrayOutputStream would be pretty straightforward.
Once again, I was wrong.
Turns out the QT for Java classes generate an object of apple.awt.OSXImage. It also turns out this object appears to be completely undocumented. There's almost no information on that class that I could find anywhere. The only thing I could find were other people complaining about the same problem I was having.
For the past couple of days, I've been mucking around trying to find a way to convert this apple.awt.OSXImage to some derivative of the java.awt.Image class. Because I'm not used to programming GUI elements in Java, I think it's the reason I kept overlooking the obvious solution of drawing it on a Graphics2D object. I kept trying to find ways to convert that apple.awt.OSXImage to a BufferedImage using introspection and reflection. I was really making it way more complicated than it really is.
Finally the light bulb went off this afternoon when I thought "Hey, since I can display on the screen, can't I just convert that into a usable image?"
After banging my head trying to do something with this applet.awt.OSXImage object off and on for a couple of days, the solution was quite simple:
if( img == null ) return null;
int w = img.getWidth(null);
int h = img.getHeight(null);
// draw original image to thumbnail image object and
// scale it to the new size on-the-fly
BufferedImage bufimg = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = bufimg.createGraphics();
g2.drawImage(img, 0, 0, w, h, null);
This very basic method will take any generic Image object and return a BufferedImage object. It's amazing how simple the solution turned out to be. I hate when I make things way more complicated than they need to be!