为什么在滚动 BufferedImage 时会出现这些奇怪的视觉伪影?我该如何防止它们发生?

问题描述 投票:0回答:1

我正在创建一个应用程序,涉及在

BufferedImage
方法中使用
JComponent
Graphics2D
绘制到
paintComponent(Graphics)

我添加了放大和缩小图像的功能。我正在绘制的

JComponent
包含在
JScrollPane
中。如果图像放大到足够大,就会出现滚动条。

每当我滚动

JScrollPane
时,我都会看到奇怪的视觉伪影。

这是我的图像的缩小版本。

这是我的图像的放大版本,没有视觉伪影。

这是我向下滚动一点,然后向右滚动时的样子。

发现这一点后,我添加了一个按钮,仅触发相关

repaint()
上的
revalidate()
JComponent
调用。这样做解决了它。

按右上角的重绘按钮之前

按右上角的重绘按钮后

这是处理将

BufferedImage
绘制到
JComponent
的代码的相关部分。

@Override
protected void paintComponent(final Graphics dontUse)
{

   super.paintComponent(dontUse);

   if (!(dontUse instanceof Graphics2D g))
   {
   
      throw new RuntimeException("Unknown graphics type = " + dontUse);
   
   }

   DRAW_DRAWN_PIXELS:
   {
   
      final Rectangle rectangle  = gui.drawingAreaScrollPane.getViewport().getViewRect();
   
      //We are only drawing a subsection because we may be working with GIGANTIC images.
      //If we attempt to draw the whole image, performance will drop like a rock.
      CALCULATE_SUBSECTION_TO_DRAW:
      {
      
         final int x = rectangle.x;
         final int y = rectangle.y;
         final int width = Math.min(rectangle.width, drawingArea.width);
         final int height = Math.min(rectangle.height, drawingArea.height);
      
         g.setBackground(gui.transparencyColor);
         g.clearRect(rectangle.x, rectangle.y, width, height);
      
      }
   
      DRAW_SUBSECTION_OF_IMAGE:
      {
      
         final int originalImageX;
         final int zoomedInImageX;
         final int originalImageY;
         final int zoomedInImageY;
      
         CALCULATE_ORIGINAL_POSITION:
         {
         
            final int quantizedX = quantize.applyAsInt(rectangle.x);
            final int quantizedY = quantize.applyAsInt(rectangle.y);
         
            zoomedInImageX = Math.max(quantizedX, 0);
            originalImageX = zoomedInImageX / gui.screenToImagePixelRatio;
         
            zoomedInImageY = Math.max(quantizedY, 0);
            originalImageY = zoomedInImageY / gui.screenToImagePixelRatio;
         
         }
      
         final int zoomedInImageWidth;
         final int originalImageWidth;
         final int zoomedInImageHeight;
         final int originalImageHeight;
      
         CALCULATE_ORIGINAL_DIMENSION:
         {
         
            final int minWidth = Math.min(rectangle.width, drawingArea.width);
            final int quantizedMinWidth = quantize.applyAsInt(minWidth);
            final int potentialWidth = quantizedMinWidth + gui.screenToImagePixelRatio;
            zoomedInImageWidth = Math.min(drawingArea.width, potentialWidth);
            originalImageWidth = zoomedInImageWidth / gui.screenToImagePixelRatio;
         
            final int minHeight = Math.min(rectangle.height, drawingArea.height);
            final int quantizedMinHeight = quantize.applyAsInt(minHeight);
            final int potentialHeight = quantizedMinHeight + gui.screenToImagePixelRatio;
            zoomedInImageHeight = Math.min(drawingArea.height, potentialHeight);
            originalImageHeight = zoomedInImageHeight / gui.screenToImagePixelRatio;
         
         }
      
         g.setPaint(gui.cursorColor);
      
         System.out.println(zoomedInImageX + " -- " + zoomedInImageY + " -- " + zoomedInImageWidth + " -- " + zoomedInImageHeight + " ---- " + originalImageX + " -- " + originalImageY + " -- " + originalImageWidth + " -- " + originalImageHeight + " ----- " + rectangle + " - " + drawingArea);
      
         g
            .drawImage
            (
               gui.image,
               zoomedInImageX,
               zoomedInImageY,
               zoomedInImageX + zoomedInImageWidth,
               zoomedInImageY + zoomedInImageHeight,
               originalImageX,
               originalImageY,
               originalImageX + originalImageWidth,
               originalImageY + originalImageHeight,
               //gui.transparencyColor,
               null
            )
            ;
      
      }
   
      DRAW_GRID_LINES:
      {
      
         if (gui.hasGridLines && gui.screenToImagePixelRatio > 1)
         {
         
            g.setPaint(gui.gridLinesColor);
            g.setStroke(new java.awt.BasicStroke(1));
         
            IntStream
               .range(rectangle.y, rectangle.y + rectangle.height)
               .forEach
               (
                  eachIndex ->
                  {
                  
                     if (eachIndex % gui.screenToImagePixelRatio == 0)
                     {
                     
                        g
                           .drawLine
                           (
                              rectangle.x,
                              eachIndex,
                              rectangle.x + rectangle.width,
                              eachIndex
                           )
                           ;
                     
                     }
                  
                  }
               )
               ;
         
            IntStream
               .rangeClosed(rectangle.x, rectangle.x + rectangle.width)
               .forEach
               (
                  eachIndex ->
                  {
                  
                     if (eachIndex % gui.screenToImagePixelRatio == 0)
                     {
                     
                        g
                           .drawLine
                           (
                              eachIndex,
                              rectangle.y,
                              eachIndex,
                              rectangle.y + rectangle.height
                           )
                           ;
                     
                     }
                  
                  }
               )
               ;
         
         }
      
      }
   
   }

   // gui.drawingAreaScrollPane.repaint();
   // gui.drawingAreaScrollPane.revalidate();

}

这是一个完整的可运行示例。


import javax.imageio.*;
import javax.imageio.event.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.colorchooser.*;
import javax.swing.filechooser.FileNameExtensionFilter;
import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.io.File;
import java.time.format.DateTimeFormatter;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.*;
import java.util.stream.*;

public class GUI
{

   private static final Function<String, Border> TITLED_BORDER =
      title ->
         BorderFactory
            .createTitledBorder
            (
               null,
               title,
               TitledBorder.CENTER,
               TitledBorder.TOP
            )
            ;

   private static final Color CLEAR = new Color(0, 0, 0, 0);
   private static final Point OFF_SCREEN = new Point(-1, -1);

   public static final int ARBITRARY_VIEW_BUFFER = 200;
   private static final int MIN_PEN_SIZE = 1;
   private static final int MAX_PEN_SIZE = 10;
   private static final int MIN_SCREEN_TO_IMAGE_PIXEL_RATIO = 1;
   private static final int MAX_SCREEN_TO_IMAGE_PIXEL_RATIO = 30;
   private static final int DEFAULT_IMAGE_PIXEL_ROWS = 26;
   private static final int DEFAULT_IMAGE_PIXEL_COLUMNS = 24;

   private final JFrame frame;
   private final JScrollPane drawingAreaScrollPane = new JScrollPane();

   private BufferedImage image;
   private Color transparencyColor = Color.WHITE;
   private Color cursorColor = Color.BLACK;
   private Color gridLinesColor = Color.GRAY;
   private boolean hasGridLines = true;
   private int penSize = 1;
   private int screenToImagePixelRatio = 10;

   public static void main(final String[] args)
   {
   
      final BufferedImage image = new BufferedImage(20, 20, BufferedImage.TYPE_INT_ARGB);
   
      IntStream
         .range(0, 20)
         .forEach(i -> image.setRGB(i, i, Color.RED.getRGB()))
         ;
   
      new GUI(image);
   
   }

   public GUI()
   {
   
      this(DEFAULT_IMAGE_PIXEL_ROWS, DEFAULT_IMAGE_PIXEL_COLUMNS);
   
   }

   public GUI(final int numImagePixelRows, final int numImagePixelColumns)
   {
   
      this(new BufferedImage(numImagePixelColumns, numImagePixelRows, BufferedImage.TYPE_INT_ARGB));
   
   }

   public GUI(final BufferedImage image)
   {
   
      Objects.requireNonNull(image);
   
      try
      {
      
         UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
      
      }
      
      catch (Exception e)
      {
      
         throw new RuntimeException(e);
      
      }
   
      INITALIZING_METADATA_INSTANCE_FIELDS:
      {
      
         this.image = image;
      
      }
   
      this.frame = new JFrame();
   
      this.frame.setTitle("Paint");
      this.frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
   
      this.frame.add(this.createMainPanel());
   
      this.frame.pack();
      this.frame.setLocationByPlatform(true);
      this.frame.setVisible(true);
   
   }

   private JPanel createMainPanel()
   {
   
      final JPanel mainPanel = new JPanel(new BorderLayout());
   
      mainPanel.add(this.createCenterPanel(),   BorderLayout.CENTER);
   
      return mainPanel;
   
   }

   private JPanel createCenterPanel()
   {
   
      final GUI gui = this; //useful when trying to differentiate between different this'.
   
      final JPanel mainPanel;
   
      CREATE_MAIN_PANEL:
      {
      
         mainPanel = new JPanel();
         mainPanel.setLayout(new BorderLayout());
      
      }
   
      final JPanel drawingPanel;
   
      final Runnable UPDATE_DRAWING_PANEL_BORDER_TEXT;
      final Runnable RECREATE_DRAWING_AREA_FRESH;
      final Runnable REPAINT_DRAWING_PANEL;
   
      final BiFunction<Point, Integer, Point> originalToZoomed =
         (original, ratio) ->
            new Point(original.x * ratio, original.y * ratio)
            ;
   
      final BiFunction<Point, Integer, Point> zoomedToOriginal =
         (zoomed, ratio) ->
            new Point
            (
               (zoomed.x - (zoomed.x % ratio)) / ratio,
               (zoomed.y - (zoomed.y % ratio)) / ratio
            )
            ;
   
      CREATE_DRAWING_PANEL:
      {
      
         SET_UP_DRAWING_PANEL:
         {
         
            drawingPanel = new JPanel();
         
            drawingPanel.setLayout(new BoxLayout(drawingPanel, BoxLayout.PAGE_AXIS));
         
            drawingPanel.add(Box.createRigidArea(new Dimension(123, 456)));
         
         }
      
         REPAINT_DRAWING_PANEL =
            () ->
            {
            
               drawingPanel.repaint();
               drawingPanel.revalidate();
            
            }
            ;
      
         RECREATE_DRAWING_AREA_FRESH =
            () ->
            {
            
               final var thingToFocus = drawingPanel;
            
               drawingPanel.removeAll();
            
               final Dimension drawingArea = gui.deriveDrawingAreaDimensions();
            
               final IntUnaryOperator quantize =
                  num ->
                     num - (num % gui.screenToImagePixelRatio)
                     ;
            
               final Box.Filler box =
                  new Box.Filler(drawingArea, drawingArea, drawingArea)
                  {
                  
                     @Override
                     protected void paintComponent(final Graphics dontUse)
                     {
                     
                        super.paintComponent(dontUse);
                     
                        if (!(dontUse instanceof Graphics2D g))
                        {
                        
                           throw new RuntimeException("Unknown graphics type = " + dontUse);
                        
                        }
                     
                        DRAW_DRAWN_PIXELS:
                        {
                        
                           final Rectangle rectangle  = gui.drawingAreaScrollPane.getViewport().getViewRect();
                        
                           //We are only drawing a subsection because we may be working with GIGANTIC images.
                           //If we attempt to draw the whole image, performance will drop like a rock.
                           CALCULATE_SUBSECTION_TO_DRAW:
                           {
                           
                              final int x = rectangle.x;
                              final int y = rectangle.y;
                              final int width = Math.min(rectangle.width, drawingArea.width);
                              final int height = Math.min(rectangle.height, drawingArea.height);
                           
                              g.setBackground(gui.transparencyColor);
                              g.clearRect(rectangle.x, rectangle.y, width, height);
                           
                           }
                        
                           DRAW_SUBSECTION_OF_IMAGE:
                           {
                           
                              final int originalImageX;
                              final int zoomedInImageX;
                              final int originalImageY;
                              final int zoomedInImageY;
                           
                              CALCULATE_ORIGINAL_POSITION:
                              {
                              
                                 final int quantizedX = quantize.applyAsInt(rectangle.x);
                                 final int quantizedY = quantize.applyAsInt(rectangle.y);
                              
                                 zoomedInImageX = Math.max(quantizedX, 0);
                                 originalImageX = zoomedInImageX / gui.screenToImagePixelRatio;
                              
                                 zoomedInImageY = Math.max(quantizedY, 0);
                                 originalImageY = zoomedInImageY / gui.screenToImagePixelRatio;
                              
                              }
                           
                              final int zoomedInImageWidth;
                              final int originalImageWidth;
                              final int zoomedInImageHeight;
                              final int originalImageHeight;
                           
                              CALCULATE_ORIGINAL_DIMENSION:
                              {
                              
                                 final int minWidth = Math.min(rectangle.width, drawingArea.width);
                                 final int quantizedMinWidth = quantize.applyAsInt(minWidth);
                                 final int potentialWidth = quantizedMinWidth + gui.screenToImagePixelRatio;
                                 zoomedInImageWidth = Math.min(drawingArea.width, potentialWidth);
                                 originalImageWidth = zoomedInImageWidth / gui.screenToImagePixelRatio;
                              
                                 final int minHeight = Math.min(rectangle.height, drawingArea.height);
                                 final int quantizedMinHeight = quantize.applyAsInt(minHeight);
                                 final int potentialHeight = quantizedMinHeight + gui.screenToImagePixelRatio;
                                 zoomedInImageHeight = Math.min(drawingArea.height, potentialHeight);
                                 originalImageHeight = zoomedInImageHeight / gui.screenToImagePixelRatio;
                              
                              }
                           
                              g.setPaint(gui.cursorColor);
                           
                              System.out.println(zoomedInImageX + " -- " + zoomedInImageY + " -- " + zoomedInImageWidth + " -- " + zoomedInImageHeight + " ---- " + originalImageX + " -- " + originalImageY + " -- " + originalImageWidth + " -- " + originalImageHeight + " ----- " + rectangle + " - " + drawingArea);
                           
                              g
                                 .drawImage
                                 (
                                    gui.image,
                                    zoomedInImageX,
                                    zoomedInImageY,
                                    zoomedInImageX + zoomedInImageWidth,
                                    zoomedInImageY + zoomedInImageHeight,
                                    originalImageX,
                                    originalImageY,
                                    originalImageX + originalImageWidth,
                                    originalImageY + originalImageHeight,
                                    //gui.transparencyColor,
                                    null
                                 )
                                 ;
                           
                           }
                        
                           DRAW_GRID_LINES:
                           {
                           
                              if (gui.hasGridLines && gui.screenToImagePixelRatio > 1)
                              {
                              
                                 g.setPaint(gui.gridLinesColor);
                                 g.setStroke(new java.awt.BasicStroke(1));
                              
                                 IntStream
                                    .range(rectangle.y, rectangle.y + rectangle.height)
                                    .forEach
                                    (
                                       eachIndex ->
                                       {
                                       
                                          if (eachIndex % gui.screenToImagePixelRatio == 0)
                                          {
                                          
                                             g
                                                .drawLine
                                                (
                                                   rectangle.x,
                                                   eachIndex,
                                                   rectangle.x + rectangle.width,
                                                   eachIndex
                                                )
                                                ;
                                          
                                          }
                                       
                                       }
                                    )
                                    ;
                              
                                 IntStream
                                    .rangeClosed(rectangle.x, rectangle.x + rectangle.width)
                                    .forEach
                                    (
                                       eachIndex ->
                                       {
                                       
                                          if (eachIndex % gui.screenToImagePixelRatio == 0)
                                          {
                                          
                                             g
                                                .drawLine
                                                (
                                                   eachIndex,
                                                   rectangle.y,
                                                   eachIndex,
                                                   rectangle.y + rectangle.height
                                                )
                                                ;
                                          
                                          }
                                       
                                       }
                                    )
                                    ;
                              
                              }
                           
                           }
                        
                        }
                     
                        // gui.drawingAreaScrollPane.repaint();
                        // gui.drawingAreaScrollPane.revalidate();
                     
                     }
                  
                  }
                  ;
            
               box.setBorder(BorderFactory.createLineBorder(Color.BLACK, 1));
            
               drawingPanel.add(Box.createHorizontalGlue());
               drawingPanel.add(box);
               drawingPanel.add(Box.createHorizontalGlue());
            
               REPAINT_DRAWING_PANEL.run();
            
            }
            ;
      
         RECREATE_DRAWING_AREA_FRESH.run();
      
      }
   
      final JPanel drawingSettingsPanel;
   
      CREATE_DRAWING_SETTINGS_PANEL:
      {
      
         drawingSettingsPanel = new JPanel();
         drawingSettingsPanel.setLayout(new BoxLayout(drawingSettingsPanel, BoxLayout.LINE_AXIS));
      
         final JSpinner screenToImagePixelRatioDropDownMenu;
      
         SCREEN_TO_IMAGE_PIXEL_RATIO_DROP_DOWN_MENU:
         {
         
            screenToImagePixelRatioDropDownMenu =
               new JSpinner
               (
                  new SpinnerNumberModel
                  (
                     this.screenToImagePixelRatio,
                     MIN_SCREEN_TO_IMAGE_PIXEL_RATIO,
                     MAX_SCREEN_TO_IMAGE_PIXEL_RATIO,
                     1
                  )
               )
               ;
         
            screenToImagePixelRatioDropDownMenu
               .addChangeListener
               (
                  event ->
                  {
                  
                     if (!(screenToImagePixelRatioDropDownMenu.getValue() instanceof Integer iii))
                     {
                     
                        throw new IllegalStateException();
                     
                     }
                  
                     this.screenToImagePixelRatio = iii;
                  
                     RECREATE_DRAWING_AREA_FRESH.run();
                  
                  }
               );
         
         }
      
         final JCheckBox hasGridLinesCheckBox;
      
         HAS_GRID_LINES_CHECK_BOX:
         {
         
            hasGridLinesCheckBox = new JCheckBox();
            hasGridLinesCheckBox.setText("Activate Grid Lines");
            hasGridLinesCheckBox.setSelected(true);
            hasGridLinesCheckBox
               .addActionListener
               (
                  event ->
                  {
                  
                     this.hasGridLines = hasGridLinesCheckBox.isSelected();
                  
                     REPAINT_DRAWING_PANEL.run();
                  
                  }
               )
               ;
         
         }
         
         final JButton repaint = new JButton("REPAINT");
         repaint.addActionListener(event -> REPAINT_DRAWING_PANEL.run());
      
         drawingSettingsPanel.add(Box.createHorizontalGlue());
         drawingSettingsPanel.add(screenToImagePixelRatioDropDownMenu);
         drawingSettingsPanel.add(new JLabel("SCREEN pixels = 1 IMAGE pixel"));
         drawingSettingsPanel.add(Box.createHorizontalStrut(10));
         drawingSettingsPanel.add(hasGridLinesCheckBox);
         drawingSettingsPanel.add(Box.createHorizontalStrut(10));
         drawingSettingsPanel.add(repaint);
         drawingSettingsPanel.add(Box.createHorizontalGlue());
      
      }
   
      CREATE_CENTERED_DRAWING_PANEL:
      {
      
         final JPanel centeredDrawingPanel = new JPanel();
         centeredDrawingPanel.setLayout(new BoxLayout(centeredDrawingPanel, BoxLayout.PAGE_AXIS));
         centeredDrawingPanel.add(Box.createVerticalGlue());
         centeredDrawingPanel.add(drawingPanel);
         centeredDrawingPanel.add(Box.createVerticalGlue());
      
         UPDATE_DRAWING_PANEL_BORDER_TEXT =
            () ->
               this.drawingAreaScrollPane
                  .setBorder
                  (
                     BorderFactory
                        .createCompoundBorder
                        (
                           TITLED_BORDER
                              .apply
                              (
                                 "Drawing Area -- "
                                 + gui.image.getHeight()
                                 + " rows and "
                                 + gui.image.getWidth()
                                 + " columns"
                              ),
                           BorderFactory.createLineBorder(Color.BLACK, 1)
                        )
                  )
                  ;
      
         UPDATE_DRAWING_PANEL_BORDER_TEXT.run();
      
         this.drawingAreaScrollPane.setViewportView(centeredDrawingPanel);
      
      }
   
      mainPanel.add(drawingSettingsPanel, BorderLayout.NORTH);
      mainPanel.add(this.drawingAreaScrollPane, BorderLayout.CENTER);
   
      return mainPanel;
   
   }

   private static void print(final String text)
   {
   
      System.out.println(LocalDateTime.now() + " -- " + text);
   
   }

   private Dimension deriveDrawingAreaDimensions()
   {
   
      return
         new Dimension
         (
            this.image.getWidth() * this.screenToImagePixelRatio,
            this.image.getHeight() * this.screenToImagePixelRatio
         )
         ;
   
   }

}

java swing jscrollpane graphics2d visual-glitch
1个回答
0
投票

忘记了我周围有一些旧代码(我在某处找到的),展示了如何使用 AffineTransform 来旋转/缩放/平移图像:

import java.awt.*;
import java.util.*;
import javax.swing.*;
import java.io.*;
import javax.imageio.*;
import java.awt.geom.*;
import java.awt.image.*;

public class RotateAndScale2 extends JPanel
{
    private Image image;
    private float scale = 10.0f;

    public RotateAndScale2(Image image)
    {
        this.image = image;
    }

    @Override
    protected void paintComponent(Graphics g)
    {
         super.paintComponent(g);

         // create the transform, note that the transformations happen
         // in reversed order (so check them backwards)
         AffineTransform at = new AffineTransform();

         // 4. translate it to the center of the component
         at.translate(getWidth() / 2, getHeight() / 2);

         // 3. do the actual rotation
//       at.rotate(Math.toRadians(45));

         // 2. scale the image
         at.scale(scale, scale);

         // 1. translate the object to rotate around the center
         at.translate(-image.getWidth(this) / 2, -image.getHeight(this) / 2);

         // draw the image
         Graphics2D g2 = (Graphics2D)g.create();
         g2.drawImage(image, at, null);
         g2.dispose();

         // continue drawing other stuff (non-transformed)
         //...
    }

    @Override
    public Dimension getPreferredSize()
    {
        int width = image.getWidth(this);
        int height = image.getHeight(this);

        return new Dimension((int)(width * scale), (int)(height * scale));
    }

    private static void createAndShowGUI()
    {
        try
        {
            BufferedImage image = ImageIO.read(new File("splash.gif"));

            JFrame frame = new JFrame("SSCCE");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.add( new JScrollPane( new RotateAndScale2(image)));
            frame.setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
            frame.setSize(400, 400);
            frame.setLocationByPlatform( true );
            frame.setVisible( true );
        }
        catch(Exception e) { System.out.println(e); }
    }

    public static void main(String[] args) throws Exception
    {
        java.awt.EventQueue.invokeLater( () -> createAndShowGUI() );
    }
}

更好的实现是使用 setRotate(...) 和 setScale(...) 方法来调整图像的属性。然后,这两种方法都会调用 revalidate() 和 repaint() 以确保使用新属性绘制图像。

© www.soinside.com 2019 - 2024. All rights reserved.