Отрисовка Java Swing и мерцание события мыши

У меня есть класс, который расширяет JPanel (ниже), эта панель находится внутри JScrollPane. Он слушает (сам себя) события мыши и пытается изменить свое положение (при перетаскивании) и масштабировать себя (на колесе), чтобы имитировать движение мыши и масштабирование. Панель также отвечает за основной визуальный вывод моего приложения. Он хранит BufferedImage, который визуализируется в видимой области JScrollPane (но на графике панели). Размер и форма изображения поддерживаются в соответствии с видимой областью.

Мои проблемы как таковые;

1) При событиях мыши я получаю огромное количество мерцаний и падение производительности 2) Если я переопределяю методы рисования или paintComponent своим собственным методом рисования, что желательно, чтобы избавиться от мерцания и других проблем с рисованием, я все еще получаю такое же мерцание. эффект и графика, нарисованная из загруженных изображений, которые имеют прозрачную область, затем окрашивают эту область в черный цвет. Когда я вызываю свой метод рисования вручную, не переопределяя методы рисования и paintComponent, я все еще получаю мерцание, но прозрачные области отображаются правильно.

Я новичок в рисовании Swing и, очевидно, делаю что-то не так, может ли кто-нибудь указать мне правильное направление, чтобы исправить это?

Спасибо

    import jSim.simulation.Simulation;
    import java.awt.Color;
    import java.awt.Cursor;
    import java.awt.Dimension;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.Image;
    import java.awt.Point;
    import java.awt.Rectangle;
    import java.awt.Toolkit;
    import java.awt.event.MouseEvent;
    import java.awt.event.MouseWheelEvent;
    import java.awt.event.MouseWheelListener;
    import java.awt.image.BufferStrategy;
    import java.awt.image.BufferedImage;
    import javax.swing.JPanel;
    import javax.swing.JViewport;
    import javax.swing.event.MouseInputListener;

    public class SimPanel extends JPanel implements MouseWheelListener, MouseInputListener {
        //Simulation

        Simulation sim;
        //Viewer
        JViewport viewport;
        Dimension viewSize;
        BufferStrategy strat;
        //Drawing
        Image renderImage;
        Graphics2D g2d;
        boolean draw = true;
        double scale = 1.0;
        Object drawLock = new Object();
        //Mouse events
        int m_XDifference, m_YDifference;

        public SimPanel(JViewport viewport) {
            this.viewport = viewport;
            this.addMouseListener(this);
            this.addMouseMotionListener(this);
            this.addMouseWheelListener(this);

            //this.setup();
        }

        public SimPanel(Simulation sim, JViewport viewport) {
            this.sim = sim;
            this.viewport = viewport;
            this.addMouseListener(this);
            this.addMouseMotionListener(this);
            this.addMouseWheelListener(this);
            //this.setup();
        }

        //Used to initialise the buffered image once drawing begins
        private void setup() {
            synchronized (drawLock) {
                viewSize = viewport.getExtentSize();
                renderImage = new BufferedImage(viewSize.width, viewSize.height, BufferedImage.TYPE_INT_RGB);
                g2d = (Graphics2D) renderImage.getGraphics();
            }
        }

    //    @Override
    //    public void paint(Graphics g)
    //    {
    //        synchronized(drawLock) {
    //        //super.paintComponent(g);
    //        paintSimulation();
    //        }
    //    }
        //Paint the screen for a specific simulation
        public void paintSimulation(Simulation sim) {
            synchronized (drawLock) {
                setSimulation(sim);
                paintSimulation();
            }
        }

        //Paint the screen with the panels simulation
        public void paintSimulation() {
            synchronized (drawLock) {
                //if no image, then init
                if (renderImage == null) {
                    setup();
                }
                //clear the screen
                resetScreen();
                //draw the simulation if not null, to the image
                if (sim != null) {
                    sim.draw(this);
                }
                //paint the screen with the image
                paintScreen();
            }
        }

        private void resetScreen() {
            Dimension newSize = viewport.getExtentSize();
            if (viewSize.height != newSize.height || viewSize.width != newSize.width || renderImage == null) {
                //System.out.println("Screen Size Changed: " + viewSize + "   " + newSize);
                viewSize = newSize;
                renderImage = new BufferedImage(viewSize.width, viewSize.height, BufferedImage.TYPE_INT_RGB);
                g2d = (Graphics2D) renderImage.getGraphics();
            } else {
                g2d.setBackground(Color.DARK_GRAY);
                g2d.clearRect(0, 0, (int) (viewSize.width), (int) (viewSize.height));
            }
        }

        private void paintScreen() {
            Graphics g;
            Graphics2D g2;
            try {
                //g = viewport.getGraphics();
                g = this.getGraphics();
                g2 = (Graphics2D) g;
                if ((g != null) && (renderImage != null)) {
                    g2.drawImage(renderImage, (int) viewport.getViewPosition().getX(), (int) viewport.getViewPosition().getY(), null);
                }
                Toolkit.getDefaultToolkit().sync();  // sync the display on some systems
                g.dispose();
                g2.dispose();
                this.revalidate();
            } catch (Exception e) {
                System.out.println("Graphics context error: " + e);
            }
        }

        //Simulation makes calls to this method to draw items on the image
        public void draw(BufferedImage image, int x, int y, Color colour) {
            synchronized (drawLock) {
                Rectangle r = viewport.getViewRect();
                if (g2d != null && draw) {
                    Point p = new Point((int) (x * scale), (int) (y * scale));
                    if (r.contains(p)) {
                        if (scale < 1) {
                            Graphics2D g2 = (Graphics2D) image.getGraphics();
                            Image test = image.getScaledInstance((int) (image.getWidth(null) * scale), (int) (image.getHeight(null) * scale), Image.SCALE_FAST);
                            g2d.drawImage(test, (int) ((x * scale - r.x)), (int) ((y * scale - r.y)), null);
                        } else {
                            g2d.drawImage(image, x - r.x, y - r.y, null);
                        }
                    }
                }
            }
        }

        public void setDraw(boolean draw) {
            this.draw = draw;
        }

        public void setSimulation(Simulation sim) {
            synchronized (drawLock) {
                if (!(this.sim == sim)) {
                    this.sim = sim;
                }
            }
        }

        public void mouseWheelMoved(MouseWheelEvent e) {
            synchronized (drawLock) {
                updatePreferredSize(e.getWheelRotation(), e.getPoint());
            }
        }

        private void updatePreferredSize(int wheelRotation, Point stablePoint) {
            double scaleFactor = findScaleFactor(wheelRotation);
            if (scale * scaleFactor < 1 && scale * scaleFactor > 0.05) {
                scaleBy(scaleFactor);
                Point offset = findOffset(stablePoint, scaleFactor);
                offsetBy(offset);
                this.getParent().doLayout();
            }
        }

        private double findScaleFactor(int wheelRotation) {
            double d = wheelRotation * 1.08;
            return (d > 0) ? 1 / d : -d;
        }

        private void scaleBy(double scaleFactor) {
            int w = (int) (this.getWidth() * scaleFactor);
            int h = (int) (this.getHeight() * scaleFactor);
            this.setPreferredSize(new Dimension(w, h));
            this.scale = this.scale * scaleFactor;
        }

        private Point findOffset(Point stablePoint, double scaleFactor) {
            int x = (int) (stablePoint.x * scaleFactor) - stablePoint.x;
            int y = (int) (stablePoint.y * scaleFactor) - stablePoint.y;
            return new Point(x, y);
        }

        private void offsetBy(Point offset) {
            Point location = viewport.getViewPosition();
            //this.setLocation(location.x - offset.x, location.y - offset.y);
            viewport.setViewPosition(new Point(location.x + offset.x, location.y + offset.y));
        }

        public void mouseDragged(MouseEvent e) {
            synchronized (drawLock) {
                //Point p = this.getLocation();
                Point p = viewport.getViewPosition();
                int newX = p.x - (e.getX() - m_XDifference);
                int newY = p.y - (e.getY() - m_YDifference);
                //this.setLocation(newX, newY);
                viewport.setViewPosition(new Point(newX, newY));
                //this.getParent().doLayout();
            }
        }

        public void mousePressed(MouseEvent e) {
            synchronized (drawLock) {
                setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
                m_XDifference = e.getX();
                m_YDifference = e.getY();
            }
        }

        public void mouseReleased(MouseEvent e) {
            synchronized (drawLock) {
                setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
            }
        }

        public void mouseClicked(MouseEvent e) {
            //throw new UnsupportedOperationException("Not supported yet.");
        }

        public void mouseEntered(MouseEvent e) {
            //throw new UnsupportedOperationException("Not supported yet.");
        }

        public void mouseExited(MouseEvent e) {
            //throw new UnsupportedOperationException("Not supported yet.");
        }

        public void mouseMoved(MouseEvent e) {
            //throw new UnsupportedOperationException("Not supported yet.");
        }
    }

person James    schedule 16.05.2011    source источник
comment
Зачем синхронизировать все ваши методы? Я работал со Swing в течение многих лет, и мне никогда не приходилось этого делать.   -  person Paul    schedule 16.05.2011
comment
Извините, я добавил их только для того, чтобы посмотреть, имеет ли это какое-то значение после того, как попробовал много других вещей, и забыл их убрать. Я просто хотел убедиться, что не происходит ничего глупого, например, изменение размера панели в середине рисования или что-то подобное.   -  person James    schedule 16.05.2011
comment
Отрисовка Swing происходит в EDT — потоке отправки событий. Событие панели изменения размера не будет обрабатываться до завершения рисования, поскольку они оба обрабатываются в одном потоке. Вот почему, если событие приводит к длительному процессу, вы должны запустить этот процесс в другом потоке, чтобы ваш пользовательский интерфейс Swing по-прежнему реагировал и перерисовывался.   -  person Paul    schedule 16.05.2011
comment
Как вы думаете, поможет ли в этом случае перемещение слушателя мыши за пределы этого класса? Я не думал, что это будет долгий процесс. Процесс, который, по моему мнению, займет больше всего времени, — это вызов sim.draw(), так как он отвечает за фактическую отрисовку всей графики в буферное изображение. Но, видя, что когда никакое событие мыши не зарегистрировано (т. е. панели не изменяются в размере или не перемещаются), дисплей работает плавно, возможно, это имеет смысл.   -  person James    schedule 16.05.2011


Ответы (2)


Короче говоря, ищите двойную буферизацию.

Длинный ответ...

Переопределить paintComponent. Создайте внеэкранный графический объект. Сделайте свою картину на этом объекте. Скопируйте это в графические объекты, переданные в метод рисования.

О, и избавиться от всей синхронизации. Вам это не нужно.

person Paul    schedule 16.05.2011
comment
Это то, что я уже делаю, не так ли? Я рисую изображение, а затем весь мой метод рисования рисует содержимое этого изображения в графическом объекте JPanels. - person James; 16.05.2011
comment
Я оговорился выше... переопределить paintComponent. Также переопределите обновление следующим образом: public void update(Graphics g) { paint(g); } обновление стирает панель по умолчанию - вызов paint предотвратит это. - person Paul; 16.05.2011
comment
Еще раз спасибо за комментарий, но я уже пробовал выше безрезультатно. Есть еще идеи? Это сводило меня с ума. Мне нужно больше узнать о Swing и рисовании. - person James; 16.05.2011
comment
Пожалуйста, обновите свой вопрос, указав код, в котором вы пробовали приведенные выше предложения. Спасибо. - person Paul; 16.05.2011
comment
Ах, здорово, это было решение. Я просто забыл, что у меня есть еще одна точка в моем коде, которая напрямую вызывает мой метод рисования вместо перерисовки. Я подумал, что это было немного странно в первый раз, когда я попробовал это, потому что переопределение метода paintComponent должно было означать, что все вызовы рисования проходят через него, и если производительность не была плохой, все должно выглядеть гладко. В любом случае, спасибо за помощь. - person James; 17.05.2011

Если вы добавите setOpaque(true) в область просмотра, вы сообщите Swing, что вы будете рисовать все (особенно фон) самостоятельно. Это уже может немного помочь.

ИЗМЕНИТЬ

Я еще немного осмотрелся и думаю, что вам следует переопределить paintComponent.

У вас может быть 2 изображения и четыре ссылки:

  • изображениеToPaint = ноль
  • imageToWriteTo = ноль
  • bufferImageOne инициализируется BufferedImage соответствующего размера.
  • bufferImageTwo инициализируется BufferedImage соответствующего размера.

Вы бы переопределили paintComponent для рисования фона, а затем drawImage(imageToPaint) (если он не равен нулю, чего не должно быть)

У вас будет поток, который выполняет пользовательскую прорисовку imageToWriteTo. В конце он меняет местами imageToPaint и imageToWriteTo.

Затем вы вызываете перерисовку(). Это запрашивает перерисовку с дополнительным преимуществом, заключающимся в том, что все запросы перерисовки в очереди Swing собираются вместе и приводят к одной перерисовке. Пожалуйста, не перепроверяйте и не синхронизируйте. Эта перерисовка автоматически выполняется во втором потоке, потоке отправки событий.

Таким образом, обновление симуляционного изображения отделено от фактического рисования, и обновлениям рисования не нужно ждать, пока симуляция завершит рисование. Это может быть связано с немного устаревшими изображениями (немного неявно при использовании буферизации), но это должно давать лучшие результаты.

Короче говоря, в imageToWriteTo выполняется дорогостоящая запись. Рисование выполняется с помощью imageToPaint. Дорогостоящее написание заканчивается заменой imageToWriteTo и imageToPaint.

person extraneon    schedule 16.05.2011
comment
Спасибо за ответ, я уже пробовал это, но это не имело никакого значения. - person James; 16.05.2011
comment
@James немного обновил мой ответ. - person extraneon; 16.05.2011