Пиксельная графика в Java качели / awt - PullRequest
1 голос
/ 29 мая 2020

Есть ли способ добиться безупречной графики в компонентах Swing / AWT?

Я реализую настраиваемую границу для кнопки, расширяя AbstractBorder. Для демонстрационных целей толщина всегда равна 3, и каждая линия окрашивается в другой цвет.

import javax.swing.*;
import javax.swing.border.AbstractBorder;
import javax.swing.border.Border;
import javax.swing.plaf.basic.BasicLookAndFeel;
import java.awt.*;

public class ExampleFrame extends JFrame {

    public ExampleFrame() {
        init();
    }

    public static void main(String[] args) throws UnsupportedLookAndFeelException 
    {
        UIManager.setLookAndFeel(new CustomLookAndFeel());

        ExampleFrame frame = new ExampleFrame();
        frame.setVisible(true);
    }

    private void init() {
        setLayout(new BorderLayout());

        JPanel panel = new JPanel();
        panel.setLayout(new GridBagLayout());
        getContentPane().add(panel, BorderLayout.NORTH);

        GridBagConstraints constraints = new GridBagConstraints();

        JButton button1 = new JButton("aaa");
        button1.setVerticalTextPosition(JButton.BOTTOM);
        button1.setHorizontalTextPosition(JButton.CENTER);
        button1.setFocusable(false);

        constraints.insets = new Insets(2, 2, 2, 2);
        constraints.gridx = 0;
        constraints.gridy = 0;
        constraints.gridwidth = 1;
        constraints.gridheight = 3;
        constraints.fill = GridBagConstraints.BOTH;
        constraints.anchor = GridBagConstraints.LINE_START;
        panel.add(button1, constraints);

        JButton button2 = new JButton("bbb");
        button2.setHorizontalAlignment(JButton.CENTER);
        button2.setFocusable(false);

        constraints.gridx = 1;
        constraints.gridy = 0;
        constraints.gridwidth = 1;
        constraints.gridheight = 1;
        constraints.fill = GridBagConstraints.BOTH;
        constraints.anchor = GridBagConstraints.LINE_START;
        panel.add(button2, constraints);

        JButton button3 = new JButton("eee");
        button3.setHorizontalAlignment(JButton.CENTER);
        button3.setFocusable(false);

        constraints.gridx = 1;
        constraints.gridy = 1;
        constraints.gridwidth = 1;
        constraints.gridheight = 1;
        constraints.fill = GridBagConstraints.HORIZONTAL;
        constraints.anchor = GridBagConstraints.LINE_START;
        panel.add(button3, constraints);

        JButton button4 = new JButton("ddd");
        button4.setHorizontalAlignment(JButton.CENTER);
        button4.setFocusable(false);

        constraints.gridx = 1;
        constraints.gridy = 2;
        constraints.gridwidth = 1;
        constraints.gridheight = 1;
        constraints.fill = GridBagConstraints.HORIZONTAL;
        constraints.anchor = GridBagConstraints.LINE_START;
        panel.add(button4, constraints);

        pack();
    }

    private static final class CustomBorder extends AbstractBorder {

        private final int thickness;

        public CustomBorder() {
            this(1);
        }

        public CustomBorder(int thickness) {
            this.thickness = thickness;
        }

        @Override
        public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
            ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
            //            ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE);
            ((Graphics2D) g).setStroke(new BasicStroke(1.0f));

            for (int i = 0; i < thickness; i++) {
                switch (i) {
                    case 0: {
                        g.setColor(Color.RED);
                    }
                    break;
                    case 1: {
                        g.setColor(Color.GREEN);
                    }
                    break;
                    case 2: {
                        g.setColor(Color.BLUE);
                    }
                    break;
                }

                // top-left -> top-right
                g.drawLine(x, y + i, x + width, y + i);
                // top-left > bottom-left
                g.drawLine(x + i, y, x + i, y + height);
            }
        }

        @Override
        public Insets getBorderInsets(Component c, Insets insets) {
            insets.top = thickness;
            insets.bottom = thickness;
            insets.left = thickness;
            insets.right = thickness;
            return insets;
        }
    }

    private static final class CustomLookAndFeel extends BasicLookAndFeel {

        @Override
        protected void initComponentDefaults(UIDefaults table) {
            super.initComponentDefaults(table);

            // button
            Border buttonBorder = new CustomBorder(3);
            table.put("Button.border", buttonBorder);
            table.put("Button.background", new Color(150, 150, 182));
            table.put("Button.foreground", Color.BLACK);
        }

        @Override
        public String getName() {
            return "custom";
        }

        @Override
        public String getID() {
            return "custom";
        }

        @Override
        public String getDescription() {
            return "custom";
        }

        @Override
        public boolean isNativeLookAndFeel() {
            return false;
        }

        @Override
        public boolean isSupportedLookAndFeel() {
            return true;
        }
    }
}

Несмотря на то, что все 3 кнопки имеют одинаковые параметры границы, я получаю разные ширину границы и отображаемые цвета.

Подсказки VALUE_STROKE_PURE и VALUE_STROKE_NORMALIZE дают разные результаты, но ни один из них не является точным в пикселях.

VALUE_STROKE_PURE VALUE_STROKE_NORMALIZE

Кнопки «bbb», «eee» и «ddd» на правой стороне имеют одинаковую ширину и высоту, но все же цвета и общая ширина границы различаются. Вдобавок используемый по умолчанию штрих имеет ширину 1.0f.

Я предполагаю, что это происходит, потому что Java 2D-геометрия работает с числами с плавающей запятой. Есть ли способ обойти это ограничение?

Я пробовал разные процедуры, например, drawRect и разные подсказки сглаживания, но безуспешно.

...