Java Tips Weblog

  • Blog Stats

    • 2,571,321 hits
  • Categories

  • Archives

Stretch Icon

Posted by Darryl Burke on March 31, 2012

An implementation of the Icon interface reports its size via the two methods getIconWidth() and getIconHeight().  It’s normal to expect that the Icon’s paintIcon(Component, Graphics, int, int) method will respect these bounds.  If I had designed Swing’s interaction with Icons, I would have made sure of that by setting a clip to the Graphics passed to paintIcon(…).

Luckily for me, I didn’t design Swing, as that would have made StretchIcon impossible.

StretchIcon is an Icon that scales its image to fill the component area, excluding any border or insets, optionally maintaining the image’s aspect ratio by padding and centering the scaled image horizontally or vertically.  Since the component now determines the size of the Icon, rather than the other way round, StretchIcon can be used only in conjunction with a component whose size is determined by the size and layout of the container it is placed in; for example, a JLabel placed in the BorderLayout.CENTER of a JFrame.

Using a StretchIcon is a viable alternative to extending a JComponent or JPanel solely to paint a custom background.  The class can also improve the aesthetics of JLabels or JButtons in a resizable GridLayout, as in a game board. For both use cases, the image chosen should be of a size equal or near the maximum anticipated size of the component, since drawing an image larger than its natural size can lead to pixelation.

StretchIcon is a drop-in replacement for ImageIcon, which it extends, except that ImageIcon’s no-arg constructor isn’t supported. That’s because I feel that an ImageIcon without an Image is an oxymoron.

Get The Code

StretchIcon.java

See Also

Shrink Icon
Thumbnail Icon

Related Reading

Java API: javax.swing.Icon
Java API: javax.swing.ImageIcon
The Java™ Tutorials: How to Use Icons

27 Responses to “Stretch Icon”

  1. Gildas said

    That worked great, thank you!

  2. Bill h said

    So worked great on windows, exactly what I needed. Having a little issue on Mac OS X though. It seems that when you press and hold on a button with the StretchIcon in it, there is a phantom image in the background that becomes visible. It seems like it is something from ImageIcon that maybe the StretchIcon doesn’t take into account?!?! It’s a bit strange….trying to see if I can figure out a work around currently. Thanks again though for the original code!!

    • Bill H said

      Tried to fix it and boiled it down to the MacOS System Look and Feel. No other OS or L&F caused this issue (tried Linux, Metal, Nimbus, Motif, Windows 8). For now I just switched those buttons to be Nimbus when the OS is Mac as those are pretty close looking. Would love to figure this out, but couldn’t figure out how to dig deeper into the ImageIcon class and the L&F behavior when a JButton is pressed.

  3. bspkrs said

    Thanks for making this, saved me from having to figure out the math myself! I did make one small alteration that is highlighted in this gist: https://gist.github.com/bspkrs/bd4b368204d710256bc7#file-stretchicon-java-L319-L330

    This change produces better quality scaled images :D

  4. Thanks for sharing your code. Could you please add a suitable license to it, so it can be used by others without worry?

  5. if (proportionate) {
    int iw = image.getWidth(c);
    int ih = image.getHeight(c);

    if (iw * h < ih * w) {
    iw = (h * iw) / ih;
    x += (w – iw) / 2;
    w = iw;
    } else {
    ih = (w * ih) / iw;
    y += (h – ih) / 2;
    h = ih;
    }
    }

    Can you please explain this math logic ? i tried to understand it but couldn't.

  6. Thomas said

    Thanks for this. One problem using JButton though. It seems that it tries to keep the icon /inside/ the margins. This is a problem if I want the Icon to cover the button wall to wall, while /keeping/ the margins. Setting the JButton margins to 0/0/0/0 doesn’t help (in, say, FlowLayout), because then I predictably get an icon just as large as the text itself only (That’s the true size of the Jbutton).

    Can I get the icon to spread to the edges of the button, while ignoring the margin insets?

    • Thomas said

      I’m sorry, what I wrote might have been confusing. The mention of FlowLayout was just my testing vehicle….what’s important here is that the (JButton).setMargin(new Insets(0,0,0,0)) will cause a button that is precisely the size of the text, as it should.

      If I keep the insets there, the icon is still the size of the text: It seems to be taking its size cues from AbstractButton in such a way that the size given it is not including the space around it.

      I would like the margins to be there still (so that there is a gutter around the text), but have the icon flood to the edges.

      • Read the second sentence of the third paragraph at the top of this page carefully. Yes, the icon *is* taking its size cues from the button.

        For your second requirement, maybe you could put something together using Rob Camick’s TextIcon and my DualIcoan.

        Text Icon

        Dual Icon

        Darryl

      • Thomas said

        Yes, but it’s that exact sentence that is the problem with my one (and only) requirement. The layout manager /always/ determines how the object is sized and placed, even the “null layout manager” does this. It turns out that BorderLayout is one of the few that completely ignores the requests of the component (getPreferredSize(), getMin/MaxSize()) entirely. If I use a GridBagLayout for instance, I can stretch the button, but if the button has required specific sizes, it’ll obey those sizes and simply pad around the button within a stretched space. This is different from BorderLayout which IMHO is a bad example.

        However even in the case of FlowLayout, if I have default JButtons, they appear as standard height. The height is managed properly. If I then plop in a StretchIcon, it attempts to make the icon the size of the text only.

        Do you know where within the AbstractButton/JButton hierarchy the icon size is determined? It’s likely /that/ place that needs overriding in my case. (I think).

        But then, I’m rapidly closing in on the amount of effort it would take to simply grow my own JButton (or AbstractButton) subclass and blast BufferedImages over the background. That would likely better match my needs anyway. Icons are a crazy sugar coating around them anyway, probably born of what happened before BufferedImage, and other Images that no longer require observers within the component, existed.

      • Thomas said

        Thanks for your replies, but you know what, looking at how icons were always intended to stay within the margins anyway, StretchIcon is similarly going to be the wrong approach.

        I’ve grown customized buttons many times before; it’s easy compared to this.

        It stems from this: Obviously, the text size doesn’t automatically stretch (for good reason). This means that any button margin along with the font size will determine the overall size of the icon (outside of BorderLayout and similar). I’ve misunderstood the (limited) model employed by the AbstractButton).

      • Thomas said

        I mean that StretchIcon is going to be the wrong approach for me and my needs, and similarly that growing my own button is comparatively easy for my needs. Not that StretchIcon is borked in someway.

    • Thomas said

      I mean that StretchIcon is going to be the wrong approach for me and my needs, and similarly that growing my own button is comparatively easy for my needs. Not that StretchIcon is borked in someway.

  7. Anonymous said

    sorry im a beginner. how to use this?

  8. Thomas Fritz said

    Using either StretchIcon or ShrinkIcon instead of normal ImageIcons I get no images drawn onto my JLabels. With ImageIcons though the icons my professor gave are displayed way too largely though.
    Basically I need to write an interface for the game Connect Four, only with variable grid sizes for the board. So the icons are placed onto my main JPanel as JLables based on the grid size chosen by the player. The bigger the board, the more icons will need to be displayed.

    So yeah, any idea why it doesn’t work for me? Because right now I have absolutely none :P

    Warm Regards,

    Thomas Fritz

    • Read the third paragraph at the top of the page:

      … StretchIcon can be used only in conjunction with a component whose size is determined by the size and layout of the container it is placed in …

      I suspect this is not happening with your choice of Layout Manager(s).

      Darryl

  9. Anonymous said

    Just a random dude willing to say thanks for this little piece of code: saved me a lot of work! :)

    • Thank you for the kind words. It’s good to know that so many years on, this page is still found by people who have use for StretchIcon.

      Darryl

      • Anonymous said

        Here’s the random dude again
        Absolutely love your solution :)
        I’ve stumbled upon an issue: I’m using your StretchIcon class to add an image on my JButtons. My JButtons are inside a JPanel with a GridLayout, and this JPanel is in the center of another JPanel with BorderLayout.
        The problem is when I setEnabled(false) a JButton: the stretchIcon on it stops being resized.
        So I digged a little bit on this JButton.setEnabled(boolean b) function and I saw JButton extends the abstract class AbstractButton, where the method is defined like this:

        public void setEnabled(boolean b) {
        if (!b && model.isRollover()) {
        model.setRollover(false);
        }
        super.setEnabled(b);
        model.setEnabled(b);
        }

        So I digged a little more on that super.setEnabled(b) call and I saw that it’s executing the following code in the abstract class JComponent:

        public void setEnabled(boolean enabled) {
        boolean oldEnabled = isEnabled();
        super.setEnabled(enabled);
        firePropertyChange(“enabled”, oldEnabled, enabled);
        if (enabled != oldEnabled) {
        repaint();
        }

        So I thought “ah ahh, it’s repainting the button, this must be the problem!” and then I created a class that extends JButton overriding the method setEnabled(boolean b) in order to avoid that repaint part. I’ve tested it and… argh, still the same issue: the StretchIcon on MyJButton class stops being resized when the button is setEnabled(false).

        Sorry to ask, but… any clue? Is it possible to mantain the resized StretchIcon image on a JButton when the latter is disabled?

  10. Kudos to you for researching the source code, but I think you’re barking up the wrong tree. Changing the enabled state of a JComponent does usually need a reapint() and it doesn’t normally affect the size.

    Go through the API for AbstractButton and check out the other setXxxIcon methods. I’m guessing that if you setDisabledIcon(….) to another or the same StretchIcon, your problem may go away.

    If that’s not it, I suggest you find an active Java forum (Coderanch.com is one) and ask for help there. Illustrating the issue with a SSCCE (look it up) usually gets you better help sooner. And don’t forget to post a link to this page so people there know what you’re talking about.

    Darryl

    • Anonymous said

      …and you were right, it’s exactly how you said: there’s the setDisabledIcon method in the AbstractButton, working as expected.
      A huge, gargantuan thank you for your class and your hint! :)

  11. Anonymous said

    Thank you!
    Still, after almost 10 years, this works like a charm!

Leave a comment