This document is intended to give a detailed overview (if possible) of the ScalableIcon interface in the org.jdesktop.swingx.icon package. While the JavaDoc for that package and sub packages are mostly complete and in depth, this document is meant as a tutorial-esque runthrough of the features available in these classes and APIs.
To continue learning about the rest of the org.jdesktop.swingx.icon package and scalable icons see these other pages for information (they will be filled in over time):
Before reading this document it is probably a good idea to be fairly knowlegable about the javax.swing.Icon interface and also know about Java2D and images.
Icons in java are generally of a fixed size. In fact the documentation states that the Icon is The ScalableIcon interface and its accompanying APIs are designed to extend this definintion to essentially remove the 'fixed size' bit and as such increase the number of use-cases that the icon can be applied to.
Hopefully by the end of this document you will be in a position where you wonder how you ever got along without scalable icons (ok, well maybe not but they should come in useful sometimes) and should be able to write your own.
There is really only one way to answer this question, it's an Icon that can be scaled. Or to elaberate a bit it's a number of painting commands (a picture or painter) that has no fixed size, unlike a non-scalable icon. Now that is not to say that it doesn't have a size that it prefers (default size) but it doesn't mind being shown in other sizes too.
Now, there are three ways to look at providing this functionality; and off the top of my head I can think of examples where each have been implemented, one of them in the core JRE. I'm going to talk about them all so that you have a general understanding of the decisions made in the API and why I felt they were the best choice for the job.
The first method of scaling icons is possibly the simplest but also the hardest (for me at least) to get your head around, this is also the one that has examples in the JRE. The basic principle goes like this:
There are a number of problems with this approach. You can see an example of this in the JRE if you look at the com.sun.java.swing.plaf.windows.WindowsInternalFrameTitlePane$ScalableIconUIResource class which gets around the Buffering problem above by painting scaled icons that cancel out when the graphics transform is applied.
This approach is probably the most common (and was the first approach adopted when I first wrote the ScalableIcon interface) and involves providing essentially extra setter methods on top of Icons getIconWidth and getIconHeight getters. These setters allow you to change the dimensions of the icon which can then paint itself at the required dimensions when called to do so.
One of the big advanteges of this approach is that it is very simple to implement and adds minimal conceptual overhead to what you already know in the Icon class. In fact this approach is extreemly competent for the simple common case we use almost every day, where an Icon is used in only one place. Unfortunately this approach does not scale particularly well, imagine the series of steps needed to paint the same icon in two different places and at two different sizes. Now while that looks fairly simple, bear in mind that by setting the size of the icon you are changing it everywhere so really you should be resetting the size back when your done with it: Still not that bad? Now try to add notification of changes in the size of the icon, you'll have to add steps like disable/enable notification support. Or try adding decoration to the icon, want a border around your icon, it gets really complicated. The more steps there are the more likely you are that you'll forget something and then all sort of errors can pop in.
The final approach is similar to the Setters approach above but does not require that the icons size be changed. In fact the Icons size should never be changed, the size becomes decoupled from the painting logic of the icon and instead becomes more of a guideline or a preferred size. The painting logic of the Icon instead receives the bounding rectangle that the icon should be painted into, including the width and height, and it's up to the icon to honour these parameters.
While the size of the icon has become seperate from the painting logic there still needs to be a connection, the icon still needs a way to say "I need to be this aspect ratio" or "I cant go smaller than this" or "I only scale horizontally." These use-cases are provided by a secondary API that allows the icon to define what its preferred size would be if it were given a certain domain area. The conversation looks something like this: Anyway, you get the point.
This is the approach used in this ScalableIcon framework.
The ScalableIcon interface provides the API required to implement the Painting scale method listed above; it provides two extra methods on top of the default Icon interface, one for painting and one for size constraints: The first one provides the painting support and the second one the scaling hints, that's it.
An example use would be to create a component that simply painted an icon over it's contents: As you can see it should be a relatively easy task to center the icon in the area or position it in the bottom right or anywhere as required. It should also be easy to spot that you can restrict the size for the icon to any size you choose by changing the width and height passed to the fitInto method while still respecting the icons wishes for size constraints. The use of ScalableIcons is generally the same for every use, there is no save-state-set-state-paint-reset-state process, only what you see above.
Well as using the interface is so simple lets get on to how to implement your own scalable icons. There are three things that you need to think about when implementing this interface When you decide that you need a new Scalable Icon the thing you generally think of the first is the picture that the icon will represent, this is the reason for having the icon in the first place after all. This will be made up of mostly your own painting logic and is mostly left up to you.
The second thing to think about is the preferred size of the icon, now a lot of the time you won't have a preferred size as you've written the icon to be truly scalable, however the API requires it so generally I find supplying a constructor that takes the preferred width and height as arguments is a reasonable approach to defining these.
The third thing to think about is the scale constraints that the icon may have. Does it grow to fill all available space, does it keep it's aspect ratio, does it have a maximum or minimum size? These are all questions that can be answered by implementing the fitInto method. Lukily for us there are also helper classes to aid with these implementations.
todo: maybe move this into it's own document
As most icons follow a finite set of scaling rules the ScalePolicy class has been designed to provide these rules for you. If you think of a scale constraint as a combination of dimension constraints and size constraints then you can define the vast majority of usecases very easily. The ScalePolicy class does this for you in the form of the enums DimensionPolicy and ResizePolicy.
This enum defines the constraints that can be applied to dimensions, the width and height, of an area. It defines:
This enum defines how to scale within the area and can be used as an aid to define minimum and maximum sizes. It defines:
The ScalePolicy class also provides some simple caching mechanism for the different combinations of scale
constraints via the
ScalePolicy.valueOf(DimensionPolicy, ResizePolicy) method. For example we can
define the following scale constraints:
Note that every call to
valueOf with the same parameters will result in the same instance of the
The api for using the ScalePolicy class is very similar to the
ScalableIcon.fitInto API. There are
slight differences though as the ScalePolicy is essentially stateless and has no size. The primary API is
Dimension fitInto(Dimension source, Dimension target, Dimension result) where the source is the area
we are attempting to scale, the target is the area we are trying to scale into and result is an optional Dimension
where the results can be placed for performance reasons. The result will be the source area scaled into the target
area restricted by the DimensionPolicy and ResizePolicy passed on the construction of the ScalePolicy.
If we take the examples from the ScalableIcon.fitInto above we have the following: As you can imagine the ScalePolicy class can come in very useful when trying to define how your icon will scale and takes away much of the repeated code that is common when scaling icons and images.
The AbstractScalableIcon is designed to take the common use cases for Icons and require that the user write the
least code to get done what they want to get done. If you extend the AbstractScalableIcon class instead of
implementing the ScalableIcon interface directly you will be reuired to implement exatly 3 methods, exatcly the same
number as if you were writing a standard Icon. You are required to implement you painting logic via
paintIconImpl and provide the preferred size for the icon via
getIconWidth, that's it.
There are other benifits to using the AbstractScalableIcon class too, you get PropertyChangeListener support, Graphics configuration for properties like anti-aliasing and interpolation and all you have to do to change the scaling constraints is provide a new ScalePolicy instance for AbstractScalableIcon to work with.
Ok, so enough background and API descriptions, you should be able to get most of that from the JavaDoc anyway; lets build our first ScalableIcon.
The icon we are going to build is a simple ImageIcon replacement called ScalableImageIcon, it's not going to be as hi-tech as ImageIcon in that it will require the image be loaded beforehand but it will show off the basics of how to create scalable icons.
To create our Icon we will extend the AbstractScalableIcon interface and implement the basic APIs required of us. To be honest that's it, there's no more to it. Sure you could add getters, setters, property change support to the image and null checking but that's it, you now have a scalable icon that respects the images aspect ratio; it can't get much simpler than that.
Hopefully by this point you will have a (mostly) clear understanding of what the ScalableIcon APIs are about and why certain decisions have been made. You should be able to create your own implementations of simple scalable icons and should be able to use these icons in your own components.
In other articles the knowledge is built upon to make it easier to do other common but more complicated icon tasks like animation, decoration, composition and lazy loading (todo: fill with links as the articles become available).