-
Notifications
You must be signed in to change notification settings - Fork 13
QBufferedImage
This page discusses the QBufferedImage class.
The BufferedImage is a vital part of any Java app with a UI. Over the years I've added a few enhancements. These may be subtle, but I think they're worth sharing.
The QBufferedImage is serializable.
I have only tested this with int and byte backed images, but in theory it should work with all BufferedImages.
Specifically:
The QBufferedImage class implements Externalizable. When serialized it calls getRaster().getDataElements(..) to produce a large array of its data. This array gets serialized.
On deserialization: we create a new QBufferedImage of the same width/height/type, read the large array back, and call getRaster().setDataElements(..).
The deserialized image will render exactly the same as the serialized image. However there may be some minor internal differences. For example: if several BufferedImages are actually subimages of a master BufferedImage: that information is lost. When deserialized: all the subimages will be independent copies and their rasters will not share a parent raster like they used to.
(Marginally related: see the ConverterUtils class for static helper methods that help serialize/deserialize other AWT-ish objects that normally aren't serializable. Like AlphaComposites, BasicStrokes, Shapes, etc.)
The QBufferedImage class includes a setProperty(String, Object) method.
This is analogous to Image.getProperty(String, ImageObserver).
Now I can add small pieces of metadata to an image. For example: I can attach the filename it originated from. This is mentioned in the toString() method, so if it's mentioned in an exception I can immediately identify what image is associated with the error.
The hashing function looks at the RGB values of 5 points (the corners + the center).
And the equals method checks to make sure every pixel between two images is identical. Also for two QBufferedImages to be identical: they must use the same pixel format and have the same properties.
(Note: the ImagePixelIterator.equalPixels(Image, Image) also helps check image equivalency. It supports different pixel formats. For example: you can compare INT_ARGB with BYTE_BGR. As long as the images would render identically it will return true. It's also capable of comparing BufferedImages with generic Images.)
This may be a specific edge case, but if we ever call myBufferedImage.getSource(): it returns a sun.awt.image.OffScreenImageSource.
This ImageProducer doesn't call consumer.setHints(int). In my opinion this is a big deal, because that method signals to ImageConsumers whether you plan on delivering the pixels in top-down-left-right order (among other things). When I write a consumer I really need to know that.
Also this resolves this ticket about silently printing a NullPointerException to System.err. I submitted a PR for this, but even if it's accepted I want the Pumpernickel codebase to stay backwards compatible for several years at a time.
To be fair, though: it is very rare that I ever need to call getSource() on a BufferedImage. Especially since the Pumpernickel project includes ImagePixelIterators.
I override getScaledInstance to use the Scaling classes. (There's a whole separate wiki on the Scaling class.) In my opinion this makes the getScaledInstance method usable again.
You can easily convert an existing BufferedImage into a QBufferedImage by calling:
BufferedImage bi = [..];
QBufferedImage qbi = new QBufferedImage(bi);
This is technically a unique object, but it points to the same underlying raster and color model so it's fast.
Check out the new Showcase v1.03 app! It includes eyedroppers, color pickers, enhanced HTML support, misc ComponentUI's, and more.