Monday April 16, 2007 Jim Clark
Recently I've spent some time revisiting and learning more about Ruby on
Rails. In doing so I wanted a decent editor to work with. This article
is about my "adventure" in finding an editor and/or IDE that I could use in
exploring and learning Ruby and Ruby on Rails and ultimately what I ended up
selecting.
Everything I read online talked about
TextMate the editor of
choice for Mac Ruby developers. I have a Windows machine so I didn't have that
option. I decided to stick close to home by seeing if IntelliJ (my favorite
Java editor) had a plug-in that supported Ruby. At the time no such plug-in
existed. I gave SciTE and FreeRIDE (the editors that come with the Ruby
distribution) a spin but wanted something a little closer to what I was use to
in a Java IDE. I remember hearing something about RadRails, a Ruby IDE based
on Eclipse. I was familiar enough with Eclipse so I decided to use that for my
dabbling. I used this for a while and it was OK for what I was doing.
In the meantime I came across a Windows TextMate-like editor called "e". I first installed a beta version of "e" which stands for the "E Text Editor ". I spent some time trying to make this editor work for me. I wanted to see what all the excitement was about in the TextMate world, even if it was on Windows :-) . To use "e" I first needed an updated installation of cygwin since "e" relies on cygwin to function. I wasn't overly pleased that "e" relied on cygwin to be a functional editor. Nevertheless I installed a recent copy of cygwin and was up and running with "e". "e" is in beta and with beta software comes annoyances and partially functional software. I really wanted to make this editor work. However, I ran in to a few bugs that prohibited me from using "e" and caused me to resume using RadRails. About this time, the IntelliJ folks released a Ruby plug-in.
The IntelliJ plug-in worked fairly well for what I needed. I could use it in an environment that I was very familiar with and was happy to be "home". Initially I experienced some minor bugs. The biggest problem I had was the plug-in can only be used in IntelliJ 6+ and I currently run 5.1 and wasn't prepared to upgrade. Unlike other editors I had looked at IntelliJ is relatively more expensive. After my 30 day license expired, I headed back to RadRails, but then I ran in to this blog post about the future of RadRails:
"The bottom line to all of this is that Matt and I can't provide the same kind of commitment that we once could. Like or not, RadRails is not a business. We've been working our asses off on a start-up for the past few months and RadRails has suffered as a result. I wish things were different and we could sit back and work on open source all day but that is just not the reality anymore."
I wanted to use software where I felt there was community support. I continued my search to see what else was available. I came across InType, a very basic editor that is suppose to be TextMate-like, however, at the time of the writing they were still in an alpha version and didn't seem to have much momentum. At the time I used the software it was still very immature (e.g. no tab support though you can open multiple windows). Then one day I read a blog entry about Ruby support in NetBeans. I’d always associated NetBeans with the so-so Java IDE currently produced by Sun.
I downloaded a milestone release of NetBeans release 6 and installed support for Ruby and Ruby on Rails. Almost instantly I was wowed by their support for Ruby and Rails. I had no trouble becoming productive with the IDE and have been happy with the product since. Current features include:
- Code completion
- Occurrence highlighting
- Integrated documentation pop-ups for Ruby API calls
- Parameter completion
- Mongrel support
- Rake migrations
- And much more
Additionally, NetBeans supports development in either the reference implementation of Ruby or in JRuby, a Java implementation of the Ruby interpreter. Most recently the NetBeans folks have begun adding support for TextMate bundles. They have also created a stripped down version of the IDE that is strictly for Ruby coding (though this still seems a little rough around the edges). As you can see the NetBeans folks are really putting forth a great deal of effort to create a solid, robust IDE for creating Ruby and Ruby on Rails applications. While the RadRails IDE is now supported by Aptana and IntelliJ 7 will include built in Ruby support (release expected by the end of the year), NetBeans has made a big leap ahead of the rest of the pack and nothing appears to be getting in their way. For now NetBeans is the choice for me when doing Ruby development.
Update: I forgot to mention this recent IDE comparison of NetBeans, IntelliJ and RadRails.
When debugging in IntelliJ you may need to inspect byte arrays as these byte arrays may contain meaningful text. Out of the box individual byte array values will be rendered as numeric byte values. This makes it difficult to decipher what it is you're debugging.
Fortunately IntelliJ gives a way to customize your data views when debugging. The way to handle this is create a custom Type Renderer. While inspecting values during a debug session, in the Frame tab right click and select "Customize Data Views...". A dialog will appear, select the Type Renderers tab. IntelliJ allows you to create a renderer for objects of a particular type. Apply renderer to a byte[] and use the expression "new String(this)".
This will render the byte array as a String:
For cases when you don't want this type renderer enabled, IntelliJ allows you to disable the renderer by unchecking the checkbox next to the renderer name.
For a recent release of our software we had a requirement to generate and store thumbnail images. We have documents that come in to our system with image attachments and we need to generate a thumbnail image for each of the attachments that have a supported MIME type (gif, jpg, and png). When I signed up for this assignment I thought it was a good opportunity to re-learn Java’s image APIs (it’s been almost seven years since doing any Swing or AWT work!). A lot of information is available on the web for generating thumbnail images, however, the information is scattered and there is more than one way to perform this task. Additionally, there are several gotchas that I experienced rather than reading about. Hopefully this article will get you started in the right direction and help you avoid some of the problems I faced.
Scaling Images
Before reading and writing images, I first needed to figure out how to actually scale an image, i.e. take an
original image and size it to an appropriate thumbnail size. Digging around the AWT package and the Image class
I found the java.awt.Image.getScaledInstance method. Piece of cake, just do something like:
Image scaledInstance = image.getScaledInstance(thumbWidth, thumbHeight, hint);
This worked fine and I used it for a couple of weeks when one of my coworkers pointed out that this was no longer the best way to scale images (see Bug 6196792) Instead, use the 2D Graphics API and an AffineTransform for a better solution.
public static BufferedImage getThumbnail(BufferedImage image,
int maxThumbWidth,
int maxThumbHeight,
RenderingHints hints)
{
BufferedImage thumbnail = null;
if(image != null)
{
AffineTransform tx = new AffineTransform();
// Determine scale so image is not larger than the max height and/or width.
double scale = scaleToFit(image.getWidth(),
image.getHeight(),
maxThumbWidth, maxThumbHeight);
tx.scale(scale, scale);
double d1 = (double) image.getWidth() * scale;
double d2 = (double) image.getHeight() * scale;
thumbnail = new BufferedImage(
((int) d1) < 1 ? 1 : (int)d1, // don't allow width to be less than 1
((int) d2) < 1 ? 1 : (int)d2, // don't allow height to be less than 1
image.getType() == BufferedImage.TYPE_CUSTOM ?
BufferedImage.TYPE_INT_RGB : image.getType());
Graphics2D g2d = thumbnail.createGraphics();
g2d.setRenderingHints(hints);
g2d.drawImage(image, tx, null);
g2d.dispose();
}
return thumbnail;
}
private static double scaleToFit(double w1, double h1, double w2, double h2) {
double scale = 1.0D;
if (w1 > h1) {
if (w1 > w2)
scale = w2 / w1;
h1 *= scale;
if (h1 > h2)
scale *= h2 / h1;
} else {
if (h1 > h2)
scale = h2 / h1;
w1 *= scale;
if (w1 > w2)
scale *= w2 / w1;
}
return scale;
}
Using this code I was able to scale an image, i.e. create a thumbnail, and now I needed to save it.
Storing Thumbnails
Since I was working with J2SE 1.4, which only allows for creating images in JPEG and PNG formats, we decided to use the JPEG format for
thumbnails for its smaller file size.
I needed a way to write my thumbnail to an output stream since we have a separate API for storing files. Fortunately
the javax.imageio.ImageIO package provided
an easy way to create JPEG images and put the image in to an OutputStream:
ImageIO.write(myImage, "jpg", myOutputStream);
I incorporated my changes in to the build and left for a week of training, everything seem to be working ok. Upon returning I found out that thumbnails were "killing" our system. Naturally, I questioned, "are you sure it’s thumbnails causing the problem?" It’s just image creation. Sure enough, the problem was caused by thumbnail generation. It turns out the garbage collector went in to full GC mode when thumbnails were being created, specifically when hundreds or thousands of thumbnails were being generated. Ugh, back to the drawing board.
After a little research, it was discovered that there are memory leaks in Sun’s ImageIO JPEG libraries (Bug 5015137 and Bug 4827358). As our system was creating hundreds of thumbnails, everything else came to a screeching halt thanks to garbage collection. What to do, what to do?
Supposedly these bugs are fixed in J2SE 1.5, however, upgrading wasn't an option.
One solution we looked at was creating thumbnails as PNG files. Fine, this works, no problems with performance. You’ll just need to go out and buy a few extra hard drives. I was finding the PNGs to be 4, 5, 6 times larger than JPEGs. I looked at ways to compress the PNGs via Java but didn’t have much luck getting them down to a reasonable size.
We also talked about having the thumbnail creation occur offline after the document was brought in to our system. This seemed more complicated than we wanted to make it. Plus we were running out of time as the code deadline was approaching.
Next a coworker sent a snippet of code, for encoding images to JPEG, that talked directly to com.sun
JPEG classes. This always seemed a no-no, i.e. Sun says: "Note that the classes in the com.sun.image.codec.jpeg
package are not part of the core Java APIs…".
Since we weren’t planning to upgrade the JDK any time soon we went with this approach for writing JPEGs.
JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(myByteArrayOutputStream);
JPEGEncodeParam encodeParams = encoder.getDefaultJPEGEncodeParam(myImage);
encodeParams.setQuality(0.8F, false);
encoder.setJPEGEncodeParam(encodeParams);
encoder.encode(myImage);
Everyone was happy; we could create JPEG thumbnails by circumventing the ImageIO API and talking directly to the com.sun classes. Until…
Reading Images
I noticed that certain thumbnails weren’t being rendered correctly; rather they were rendered as black rectangles.
Now what? Was this something to do with the usage of the JPEGImageEncoder, i.e. talking directly to the com.sun classes?
After a little research it turns out the problem existed previously when directly using ImageIO to encode and write images.
Therefore, no new problems were introduced. It seemed to be a problem only with GIF files. I ran a couple of tests on
Windows (dev environment) using my test GIFs and
they worked fine. I tested animated GIF files on Windows and they worked correctly. I tested the animated GIFs on Solaris.
These images were being rendered as black images. The animated GIF images that were "thumbnailed" on Solaris were not
created correctly. I was now able to repeat the problem.
To read images from the file system, the images I would create thumbnails from, I was using the AWT tookit:
Image image = Toolkit.getDefaultToolkit().createImage(myFileBytes);
Unlike Windows, the Solaris toolkit apparently didn’t know how to handle images that were made up of multiple images (GIF89a). I
needed a way to create images, in my case from a byte array, that didn't result in a black image. Back to the ImageIO class. Turns out they have a read
method that can take an InputStream
image = ImageIO.read(new ByteArrayInputStream(myImageByteArray));
However, I was a little leery of the ImageIO class due to the problems I had seen with the write method.
I did find a bug related to ImageIO.read method (Bug 4867874),
but it pertained to JPEGs.
I decided to use ImageIO.read for GIF files and continue using the AWT toolkit for JPEGs and other types. This was a fix
for our black image problem.
It seems as if our thumbnail processing is working as expected and we haven’t seen noticeable degradation in performance or the black image problem.
If you’ll be doing image processing, such as thumbnail creation, make sure your application can handle the processing requirements. Test a worse case scenario, e.g. create 1000 thumbnails. Test with many different image samples with different image formats (e.g. animated gifs) and sizes (large image files). Unfortunately you'll have to actually view the images to verify expected output, but now you are hopefully aware of some of the issues surrounding the generation of thumbnails using the ImageIO APIs and the AWT toolkit.

