36998


現在ペイント用のコンポーネントを作成しているのですが、  
スクロールを高速にするために現在見えている部分だけを描画するようにしたいと思っています。

クリップ領域や JViewport#getViewRect などを使用して  
描画する範囲を計算しているのですが、スクロール時に端が見切れてしまいます  
これを修正する方法はありますか？

スクロールしていない状態：  
[![画像の説明をここに入力](https://i.stack.imgur.com/mgkpr.png)](https://i.stack.imgur.com/mgkpr.png)

スクロールした状態：  
(プログラムの方でこの縦線を描画しているわけではありません)  
[![画像の説明をここに入力](https://i.stack.imgur.com/Gyehu.png)](https://i.stack.imgur.com/Gyehu.png)

下記が現在のソースです

[code]

    package gridtest;
    
    import java.awt.AWTEvent;
    import java.awt.BorderLayout;
    import java.awt.Color;
    import java.awt.Container;
    import java.awt.Dimension;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.Point;
    import java.awt.Rectangle;
    import java.awt.event.MouseEvent;
    import java.util.Arrays;
    import javax.swing.Box;
    import javax.swing.JCheckBox;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.JScrollPane;
    import javax.swing.JViewport;
    import javax.swing.Scrollable;
    import javax.swing.SwingConstants;
    import javax.swing.SwingUtilities;
    
    /**
     * グリッドのスクロールをテストします.
     *
     * @author twagniws
     */
    public class GridTest {
    
        /**
         * @param args the command line arguments
         */
        public static void main(String[] args) {
            JFrame window = new JFrame();
            //テーブルの作成
            JCheckBox fastPaintCheckBox = new JCheckBox("Fast Paint", true);
            GridView grid = new GridView(1024, 1024);
            Box checkHBox = Box.createHorizontalBox();
            {
                checkHBox.add(Box.createHorizontalGlue());
                checkHBox.add(fastPaintCheckBox);
            }
            Box rootVBox = Box.createVerticalBox();
            {
                rootVBox.add(checkHBox);    
            }
            //イベントの作成
            fastPaintCheckBox.addActionListener((e) -> grid.setFastPaint(fastPaintCheckBox.isSelected()));
            //ウィンドウ表示
            window.add(rootVBox, BorderLayout.NORTH);
            window.add(new JScrollPane(grid), BorderLayout.CENTER);
            window.setSize(256, 256);
            window.setLocationRelativeTo(null);
            window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            SwingUtilities.invokeLater(() -> {
                window.setVisible(true);
            });
        }
    
        /**
         * グリッドを描画するコンポーネント.
         */
        public static class GridView extends JPanel implements Scrollable {
    
            private int rowCount;
            private int columnCount;
            private int cellWidth;
            private int cellHeight;
            private boolean fastPaint;
            private Color[][] data;
    
            public GridView(int rowCount, int columnCount) {
                this.rowCount = rowCount;
                this.columnCount = columnCount;
                setCellWidth(8);
                setCellHeight(8);
                setFastPaint(true);
                this.data = new Color[rowCount][columnCount];
                //真っ黒に埋める
                for (int i = 0; i < rowCount; i++) {
                    Arrays.fill(data[i], 0, columnCount, Color.BLACK);
                }
                //マウスイベントを受信
                enableEvents(AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
            }
    
            @Override
            public Dimension getPreferredSize() {
                return new Dimension(
                        columnCount * getCellWidth(),
                        rowCount * getCellHeight()
                );
            }
    
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                int startRow = 0;
                int endRow = rowCount;
                int startCol = 0;
                int endCol = columnCount;
                if(isFastPaint()) {
                    //クリップ領域から計算する場合
                    //殆どまともに描画されない
                    /*
                    Rectangle clipRect = g.getClipBounds();
                    if (clipRect != null) {
                        startRow = clipRect.x / getCellWidth();
                        endRow = startRow + (clipRect.width / getCellWidth());
                        startCol = clipRect.y / getCellHeight();
                        endRow = startCol + (clipRect.height / getCellHeight());
                    }
                    //*/
    
                    //スクロール領域から計算する場合
                    //端が切れる
                    //*
                    Container parent = getParent();
                    if (parent instanceof JViewport) {
                        JViewport port = (JViewport) parent;
                        Rectangle visibleRect = port.getViewRect();
                        //セルの描画範囲を計算
                        startRow = visibleRect.y / getCellHeight();
                        endRow = startRow + (visibleRect.height / getCellHeight());
                        startCol = visibleRect.x / getCellWidth();
                        endCol = startCol + (visibleRect.width / getCellWidth());
                    }
                    //*/
                }
    
    
                //どちらも使用しない場合、
                //描画は問題ないが、
                //クリックでの描画やスクロールが重い
    
                //一応修正
                startRow = clampRow(startRow);
                endRow = clampRow(endRow);
                startCol = clampCol(startCol);
                endCol = clampCol(endCol);
                //描画
                Graphics2D g2 = (Graphics2D) g;
                Color saveColor = g2.getColor();
                Rectangle cellRect = new Rectangle(0, 0, getCellWidth(), getCellHeight());
                for (int i = startRow; i < endRow; i++) {
                    cellRect.y = i * getCellHeight();
                    for (int j = startCol; j < endCol; j++) {
                        cellRect.x = j * getCellWidth();
                        Color dataAt = data[i][j];
                        g2.setColor(dataAt);
                        g2.fill(cellRect);
                    }
                }
                g2.setColor(saveColor);
            }
    
            @Override
            protected void processMouseEvent(MouseEvent e) {
                super.processMouseEvent(e);
                paintCell(e);
            }
    
            @Override
            protected void processMouseMotionEvent(MouseEvent e) {
                super.processMouseMotionEvent(e);
                paintCell(e);
            }
    
            private void paintCell(MouseEvent e) {
                if (e.getID() != MouseEvent.MOUSE_PRESSED
                        && e.getID() != MouseEvent.MOUSE_DRAGGED) {
                    return;
                }
                Point modelPoint = viewToModel(e.getPoint());
                //左クリック=黄色 右クリック=白
                Color color = Color.YELLOW;
                if (SwingUtilities.isRightMouseButton(e)) {
                    color = Color.WHITE;
                }
                data[modelPoint.y][modelPoint.x] = color;
                //Rectangle rect = new Rectangle();
                //rect.setLocation(modelPoint);
                //rect.width = getCellWidth();
                //rect.height = getCellHeight();
                //repaint(rect);
                repaint();
            }
    
            private Point viewToModel(Point pt) {
                Point ret = new Point();
                ret.x = pt.x / getCellWidth();
                ret.y = pt.y / getCellHeight();
                ret.x = Math.min(columnCount - 1, Math.max(0, ret.x));
                ret.y = Math.min(rowCount - 1, Math.max(0, ret.y));
                return ret;
            }
    
            private int clampRow(int row) { 
                return Math.min(rowCount - 1, Math.max(0, row)); 
            }
    
            private int clampCol(int col) { 
                return Math.min(columnCount - 1, Math.max(0, col)); 
            }
    
            //
            //プロパティ
            //
    
            public void setCellWidth(int cellWidth) {
                this.cellWidth = cellWidth;
                repaint();
                revalidate();
            }
    
            public int getCellWidth() {
                return cellWidth;
            }
    
            public void setCellHeight(int cellHeight) {
                this.cellHeight = cellHeight;
                repaint();
                revalidate();
            }
    
            public int getCellHeight() {
                return cellHeight;
            }
    
            public void setFastPaint(boolean fastPaint) {
                this.fastPaint = fastPaint;
            }
    
            public boolean isFastPaint() {
                return fastPaint;
            }
    
            //
            //Scrollableの実装
            //
    
            @Override
            public Dimension getPreferredScrollableViewportSize() {
                return getPreferredSize();
            }
    
            @Override
            public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
                switch (orientation) {
                    case SwingConstants.VERTICAL:
                        return getCellHeight();
                    case SwingConstants.HORIZONTAL:
                        return getCellWidth();
                    default:
                        throw new IllegalArgumentException();
                }
            }
    
            @Override
            public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
                switch (orientation) {
                    case SwingConstants.VERTICAL:
                        return visibleRect.height;
                    case SwingConstants.HORIZONTAL:
                        return visibleRect.width;
                    default:
                        throw new IllegalArgumentException();
                }
            }
    
            @Override
            public boolean getScrollableTracksViewportWidth() {
                Container parent = SwingUtilities.getUnwrappedParent(this);
                if (parent instanceof JViewport) {
                    return parent.getWidth() > (getCellWidth() * columnCount);
                }
                return false;
            }
    
            @Override
            public boolean getScrollableTracksViewportHeight() {
                Container parent = SwingUtilities.getUnwrappedParent(this);
                if (parent instanceof JViewport) {
                    return parent.getHeight() > (getCellHeight() * rowCount);
                }
                return false;
            }
    
        }
    }
    
[/code]


CC BY-SA 3.0
2017-08-06T03:57:01.637
36973
2017-08-07T10:10:39.867
10677
question
java swing
JScrollPaneの可視領域のみ描画したい

[code]

    //セルの描画範囲を計算
    startRow = visibleRect.y / getCellHeight();
    endRow = startRow + (visibleRect.height / getCellHeight());
    startCol = visibleRect.x / getCellWidth();
    endCol = startCol + (visibleRect.width / getCellWidth());
    
[/code]

おそらく、`/`で割った時の余りが切り捨てられることで、誤差が出ているのではないかと思います。割り切れない場合(`visibleRect.height %
getCellHeight() != 0`)は、+1するとかで誤差は無くなりませんか？


CC BY-SA 3.0
2017-08-07T08:55:37.967
36996
2017-08-07T08:55:37.967
21092
36973
answer


> スクロール領域から計算する場合端が切れる

これは、`clampRow`メソッドなどで`rowCount - 1`としているため、端が1セル分切れるようです。以下のように修正する必要があります。

[code]

    private int clampRow(int row) {
      //return Math.min(rowCount - 1, Math.max(0, row));
      return Math.min(rowCount, Math.max(0, row));
    }
    
[/code]

> スクロールしたときに線が描画される

こちらは、@KoheiTAMURA さんがすでに指摘されていますが、例えば以下のように、外側1セル分だけ余分に描画すると良さそうです。

[code]

    //セルの描画範囲を計算
    startRow = visibleRect.y / getCellHeight();
    endRow = clampRow(startRow + (visibleRect.height / getCellHeight()) + getCellHeight());
    startCol = visibleRect.x / getCellWidth();
    endCol = clampCol(startCol + (visibleRect.width / getCellWidth()) + getCellWidth());
    
[/code]

[code]

    package gridtest;
    
    import java.awt.*;
    import java.awt.event.*;
    import java.util.Arrays;
    import javax.swing.*;
    
    /**
     * グリッドのスクロールをテストします.
     *
     * @author twagniws
     */
    public class GridTest2 {
      public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
          JFrame window = new JFrame();
          //テーブルの作成
          GridView grid = new GridView(1024, 1024);
          //ウィンドウ表示
          window.add(new JScrollPane(grid), BorderLayout.CENTER);
          window.setSize(256, 256);
          window.setLocationRelativeTo(null);
          window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
          window.setVisible(true);
        });
      }
    
      /**
       * グリッドを描画するコンポーネント.
       */
      public static class GridView extends JPanel implements Scrollable {
        private int rowCount;
        private int columnCount;
        private int cellWidth;
        private int cellHeight;
        private boolean fastPaint;
        private Color[][] data;
        private transient MouseAdapter mouseHandler;
    
        public GridView(int rowCount, int columnCount) {
          this.rowCount = rowCount;
          this.columnCount = columnCount;
          setCellWidth(8);
          setCellHeight(8);
          setFastPaint(true);
          this.data = new Color[rowCount][columnCount];
          //真っ黒に埋める
          for (int i = 0; i < rowCount; i++) {
            Arrays.fill(data[i], 0, columnCount, Color.BLACK);
          }
        }
    
        @Override public void updateUI() {
          removeMouseListener(mouseHandler);
          removeMouseMotionListener(mouseHandler);
          super.updateUI();
          mouseHandler = new MouseAdapter() {
            @Override public void mousePressed(MouseEvent e) {
              paintCell(e);
            }
            @Override public void mouseDragged(MouseEvent e) {
              paintCell(e);
            }
          };
          addMouseListener(mouseHandler);
          addMouseMotionListener(mouseHandler);
        }
    
        @Override
        public Dimension getPreferredSize() {
          return new Dimension(columnCount * getCellWidth(), rowCount * getCellHeight());
        }
    
        @Override
        protected void paintComponent(Graphics g) {
          super.paintComponent(g);
          Graphics2D g2 = (Graphics2D) g.create();
          int startRow = 0;
          int endRow = rowCount;
          int startCol = 0;
          int endCol = columnCount;
          Container parent = getParent();
          if (parent instanceof JViewport) {
            JViewport port = (JViewport) parent;
            Rectangle visibleRect = port.getViewRect();
            //セルの描画範囲を計算
            startRow = visibleRect.y / getCellHeight();
            endRow = clampRow(startRow + (visibleRect.height / getCellHeight()) + getCellHeight());
            startCol = visibleRect.x / getCellWidth();
            endCol = clampCol(startCol + (visibleRect.width / getCellWidth()) + getCellWidth());
          }
          Rectangle cellRect = new Rectangle(0, 0, getCellWidth(), getCellHeight());
          for (int i = startRow; i < endRow; i++) {
            cellRect.y = i * getCellHeight();
            for (int j = startCol; j < endCol; j++) {
              cellRect.x = j * getCellWidth();
              Color dataAt = data[i][j];
              g2.setColor(dataAt);
              g2.fill(cellRect);
            }
          }
          g2.dispose();
        }
        private void paintCell(MouseEvent e) {
          Point modelPoint = viewToModel(e.getPoint());
          //左クリック=黄色 右クリック=白
          Color color = Color.YELLOW;
          if (SwingUtilities.isRightMouseButton(e)) {
            color = Color.WHITE;
          }
          data[modelPoint.y][modelPoint.x] = color;
          //Rectangle rect = new Rectangle();
          //rect.setLocation(modelPoint);
          //rect.width = getCellWidth();
          //rect.height = getCellHeight();
          //repaint(rect);
          //repaint();
          Container parent = getParent();
          if (parent instanceof JViewport) {
            repaint(((JViewport) parent).getViewRect());
          }
        }
    
        private Point viewToModel(Point pt) {
          Point ret = new Point();
          ret.x = pt.x / getCellWidth();
          ret.y = pt.y / getCellHeight();
          ret.x = Math.min(columnCount - 1, Math.max(0, ret.x));
          ret.y = Math.min(rowCount - 1, Math.max(0, ret.y));
          return ret;
        }
    
        private int clampRow(int row) {
          //return Math.min(rowCount - 1, Math.max(0, row));
          return Math.min(rowCount, Math.max(0, row));
        }
    
        private int clampCol(int col) {
          //return Math.min(columnCount - 1, Math.max(0, col));
          return Math.min(columnCount, Math.max(0, col));
        }
    
        //
        //プロパティ
        //
    
        public void setCellWidth(int cellWidth) {
          this.cellWidth = cellWidth;
          repaint();
          revalidate();
        }
    
        public int getCellWidth() {
          return cellWidth;
        }
    
        public void setCellHeight(int cellHeight) {
          this.cellHeight = cellHeight;
          repaint();
          revalidate();
        }
    
        public int getCellHeight() {
          return cellHeight;
        }
    
        public void setFastPaint(boolean fastPaint) {
          this.fastPaint = fastPaint;
        }
    
        public boolean isFastPaint() {
          return fastPaint;
        }
    
        //
        //Scrollableの実装
        //
    
        @Override
        public Dimension getPreferredScrollableViewportSize() {
          return getPreferredSize();
        }
    
        @Override
        public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
          switch (orientation) {
          case SwingConstants.VERTICAL:
            return getCellHeight();
          case SwingConstants.HORIZONTAL:
            return getCellWidth();
          default:
            throw new IllegalArgumentException();
          }
        }
    
        @Override
        public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
          switch (orientation) {
          case SwingConstants.VERTICAL:
            return visibleRect.height;
          case SwingConstants.HORIZONTAL:
            return visibleRect.width;
          default:
            throw new IllegalArgumentException();
          }
        }
    
        @Override
        public boolean getScrollableTracksViewportWidth() {
          Container parent = SwingUtilities.getUnwrappedParent(this);
          if (parent instanceof JViewport) {
            return parent.getWidth() > (getCellWidth() * columnCount);
          }
          return false;
        }
    
        @Override
        public boolean getScrollableTracksViewportHeight() {
          Container parent = SwingUtilities.getUnwrappedParent(this);
          if (parent instanceof JViewport) {
            return parent.getHeight() > (getCellHeight() * rowCount);
          }
          return false;
        }
      }
    }
    
[/code]


CC BY-SA 3.0
2017-08-07T10:10:39.867
36998
2017-08-07T10:10:39.867
46
36973
answer