Search code examples
javaswinglook-and-feel

How to query current anti-aliasing settings in Swing L&F?


TL;DR: What is the easiest and the most correct way to set up a Graphics instance to use default settings to render strings? Or how to render a string using default anti-aliasing settings, so that it looks like a JLabel? More detailed description of the issue follows...

I'm trying to create a custom JComponent subclass. In fact, it's a sort of a TableCellRenderer that is able display rich text. Extending JEditorPane is too heavy and slow (tried it actually), and JLabel can't display rich text, so I decided to implement my own, lightweight and fast. Now, it (obviously) needs to draw some text in paintComponent(), and I would like this text to look like in all other text components, like JLabel.

However, when I do it, it seems to use different anti-aliasing settings from the rest of the app, so it looks rather ugly. I realize that I can just cast Graphics to Graphics2D to use the appropriate API, but the question is, what exact settings to use? That is, what to pass to setRenderingHint() as the second parameter?

I can get it look fine on my system by playing with various AA values, but then won't it suddenly look awful on some other system with different default AA settings?

I tried to look at JLabel and LabelUI sources, but they seem to use a lot of black magic, like querying some occult properties using JComponent.getClientProperty() and SwingUtilities2 which isn't even a part of the official Swing. Of course, I could try to mimic that, but that's a) too tedious and b) bound to use some not-too-documented features that aren't event guaranteed to have a stable API.

Or maybe there is a way to reuse some existing UI delegate? Basically, I just want my text to look exactly as displayed by a JLabel. I could even use an instance of JLabel or its subclass as a sort of "rubber stamp" to draw the text, but that looks a bit ugly and I'm not sure about performance. I realize that JTable actually does exactly that, but using a JLabel to draw an entire cell is one thing, using it to draw parts of the cell just doesn't feel right. For example, it could happen that some L&F decorates these JLabels in a special way that will just look ugly.

Ideally, I would like to have something like this (imaginary code):

((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING,
    JLabel.getDefaultUI().getRenderingHint());

Or better yet (set up everything, not just AA):

JLabel.getDefaultUI().setupGraphics(g); // this sets up g just like for drawing a JLabel

But there is seems to be no such simple thing as far as I can see.


Solution

  • Sometimes we can't see the forest for the trees...

    and JLabel can't display rich text

    If your need is having rich text in a table renderer, then (maybe) that's the question! JLabel in fact does render rich text through HTML [Doc], and even you can use JXLabel from SwingX for further functionality (rotation! line break!).

    Considering that DefaultTableRenderer is in fact a JLabel maybe you can extend it and do a simple modification:

    public class AATableRenderer extends DefaultTableCellRenderer {
    
        public Component getTableCellRendererComponent(JTable table, Object value,
                boolean isSelected, boolean hasFocus, int row, int column) {
            DefaultTableCellRenderer c = (DefaultTableCellRenderer) super
                    .getTableCellRendererComponent(table, value, isSelected,
                            hasFocus, row, column);
    
            String text = c.getText();
    
            // Do some style transformations maybe...
    
            c.setText("<html>" + text + "</html>");
    
            return c;
        }
    
    }
    

    or you can modify your JTable in the prepareRenderer() method

    public class AATable extends JTable {
    
        public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
            Component c = super.prepareRenderer(renderer, row, column);
    
            if (c instanceof JLabel) {
                JLabel l = (JLabel) c;
                String text = l.getText();
    
                // Do some style transformations maybe...
    
                l.setText("<html>" + text + "</html>");
            }
    
            return c;
        }
    
    }
    

    But maybe you can't use the HTML thing... In that case, if you have to stick to your custom component you might have to do it per look and feel as each is managed on their own way. For instance note that sometimes this properties are set in the system and most Swing L&Fs respect them (Windows 7 configures this in My Computer > Properties > Advanced System Configuration > Performance > Smooth fonts (a check in the list)).

    I don't think you can reuse the LabelUI (the basic one) because maybe all that magic interferres in the way you want to paint when you just want the smoothing... Not sure, it's not impossible but I can't tell.

    THIS IS THE ANSWER TO THE ORIGINAL QUESTION

    Short answer

    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

    Long answer

    The reason why your component uses a different settings is because Swing uses a delegation architecture for the painting: the L&F holds the delegate prototypes for each type of component, when a component is instantiated the delegate is created and injected, and there the painting (and behavior) is done. When you just implement the paintComponent(Graphics) method you're skipping all that architecture and doing the painting directly and you're missing those settings that might be done in the delegates*. [Doc]

    The most proper ("proper" is not always the best solution!) way to do your custom component is building your UI delegate, registering it and doing the appropriate procedures. [Doc]

    Anyway this is a long tedious work, so let's be practical: If your application just uses one look and feel, if you're not going to reuse this component, if your time constraints do not allow you to spend a lot of time developing a single component... if you just need to set your component anti-aliased, use this code snippet:

    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.RenderingHints;
    import javax.swing.JComponent;
    
    public class AAComponent extends JComponent {
    
        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g); // Maybe you want to skip this, up to your needs...
    
            Graphics2D g2 = null;
            try {
                g2 = (Graphics2D) g.create();
    
                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    
                // Your painting procedures
            } finally {
                if (g2 != null) {
                    g2.dispose();
                }
            }
        }
    
    }
    

    Maybe the instantiation of g2 looks a bit weird but it's quite optimal, it's a way not to propagate changes in your Graphics and the computational cost is less than minimal. Also if you'd want to propagate the antialiasing to the super class painting (in this case JComponent does nothing) just call the super implementation after setting the rendering hint.

    * This is a bit inaccurate because the configuration does not necessarily rely on the L&F but in the combination with delegates, but I think it's a good way to illustrate what happens.