Skalierbare Icons bauen

Nun, dann klingt es eigentlich, als könnte man das recht einfach machen (insbesondere ohne irgendeine Riesen-Library): Das Icon könnte das zu zeichnende Objekt als Shape enthalten, und eine Farbe dazu. In der paint-Methode des Icons wird dann das Shape auf die passende Größe sakliert gezeichnet. Schnell getestet:

package bytewelt;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;

import javax.swing.Icon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;

public class ScaledIconPaintTest
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(() -> createAndShowGui());
    }

    private static void createAndShowGui()
    {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.getContentPane().setLayout(new BorderLayout());
        
        JPanel panel = new JPanel(new FlowLayout());

        CustomIcon customIcon = new CustomIcon(createArrowShape(), Color.RED);
        panel.add(new JLabel(customIcon));
        
        f.getContentPane().add(panel, BorderLayout.CENTER);
        
        JPanel controlPanel = new JPanel(new FlowLayout());
        JSlider slider = new JSlider(16, 64, 16);
        slider.addChangeListener(e -> 
        {
            int value = slider.getValue();
            customIcon.setIconWidth(value);
            customIcon.setIconHeight(value);
            panel.revalidate();
        });
        controlPanel.add(slider);
        
        f.getContentPane().add(controlPanel, BorderLayout.SOUTH);
        
        f.setSize(500, 300);
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }
    

    private static Shape createArrowShape()
    {
        Path2D path = new Path2D.Double();
        path.moveTo(0.0, 0.25);
        path.lineTo(0.5, 0.25);
        path.lineTo(0.5, 0.0);
        path.lineTo(1.0, 0.5);
        path.lineTo(0.5, 1.0);
        path.lineTo(0.5, 0.75);
        path.lineTo(0.0, 0.75);
        path.closePath();
        return path;
    }
    
    static class CustomIcon implements Icon
    {
        private Color backgroundColor;
        private int iconWidth;
        private int iconHeight;

        private final Shape shape;
        private final Color color;
        
        CustomIcon(Shape shape, Color color)
        {
            this.shape = shape;
            this.color = color;
            this.backgroundColor = Color.GREEN;
            this.iconWidth = 16;
            this.iconHeight = 16;
        }
        
        void setIconWidth(int iconWidth)
        {
            this.iconWidth = iconWidth;
        }
        
        void setIconHeight(int iconHeight)
        {
            this.iconHeight = iconHeight;
        }
        
        @Override
        public void paintIcon(Component c, Graphics gr, int x, int y)
        {
            Graphics2D g = (Graphics2D)gr;
            g.setColor(backgroundColor);
            g.setRenderingHint(
                RenderingHints.KEY_ANTIALIASING, 
                RenderingHints.VALUE_ANTIALIAS_ON);
            g.fillRect(x, y, getIconWidth(), getIconHeight());
            AffineTransform at = new AffineTransform();
            at.translate(x, y);
            at.scale(getIconWidth(), getIconHeight());
            
            Shape transformedShape = at.createTransformedShape(shape);
            g.setColor(color);
            g.fill(transformedShape);
            g.setColor(Color.BLACK);
            g.draw(transformedShape);
        }

        @Override
        public int getIconWidth()
        {
            return iconWidth;
        }

        @Override
        public int getIconHeight()
        {
            return iconHeight;
        }
        
    }
    
}

Aaaaber: Vermutlich kommt jetzt dazu: „Ja, das Icon in Rot, aber da soll noch was anderes, Schwarzes dazu“ (ich hatte eben schon eine Variante mit List<Shape> und List<Color>, aber solche Erweiterungen muss derjenige sich überlegen, der die Anforderungen genauer kennt).

Das ganze hat natürlich caveats: Je nach Inhalt und Skalierungsfaktor sehen die Icons ggf. nicht so „schön“ aus (d.h. etwas pixelig). „Schöne“ Icons der Größe 16x16 zu erstellen ist eine Kunst für sich…

Die Option, die Icons in 16x16, 32x32 und 64x64 als Images vorzuhalten und immer das passende auszuwählen sollte man IMHO auch im Auge behalten.

(BTW: Wild geratene Spekulation: Ihr habt da eine Swing-Anwendung, und neuerdings hat dort irgendein Anwender einen 4k-Monitor, und der hat sich beschwert, dass die Icons zu klein sind, richtig? Ich war jedenfalls etwas frustriert, als ich in einer Anwendung mit Schriftgröße 9 möglichst viele Infos auf den Bildschirm pressen wollte, um dann auf einem Laptop mit ~12-Zoll 4k (!)-Displaystatt des kompakten Textes nurnoch einen unlesbaren Fliegenschiß zu sehen :confused: )

1 „Gefällt mir“