Java Graphics.fillPolygon: How to also render right and bottom edges?

后端 未结 3 1597
醉酒成梦
醉酒成梦 2021-01-13 09:09

When drawing polygons, Java2D leaves off the right and bottom edges. I understand why this is done. However, I would like to draw something that includes those edges. One

相关标签:
3条回答
  • 2021-01-13 09:40

    Sometimes "the graphics pen hangs down and to the right from the path it traverses", and sometimes it doesn't. I don't have any clear idea how to predict when it will or won't, but I have observed that RenderingHints.VALUE_STROKE_PURE can sometimes be used to alter the behavior, by trial and error. In particular, I found that if you turn on STROKE_PURE during your drawPolygon() calls in your program, it will make them match up with your fillPolygon() calls, as you desire.

    I did a little study showing the effect of the STROKE_CONTROL hint, which is one of:

    • STROKE_NORMALIZE (the default, on my system)
    • STROKE_PURE

    on the following calls:

    • drawLine()
    • drawPolygon()
    • fillPolygon()

    in both antialiasing modes:

    • ANTIALIASING_OFF
    • ANTIALIASING_ON

    And there is one more annoying dimension that apparently matters as well:

    • rendered directly to a JComponent on the screen
    • rendered to a BufferedImage.

    Here are the results when rendering directly to a visible JComponent:

    And here are the results when rendering into a BufferedImage:

    (Notice the two cases in which the two pictures differ, i.e. in which direct rendering differs from BufferedImage rendering: ANTIALIAS_OFF/STROKE_NORMALIZE/fillPolygon and ANTIALIAS_OFF/STROKE_PURE/drawPolygon.)

    Overall, there doesn't seem to be much rhyme or reason to the whole thing. But we can make the following specific observations based on the above pictures:

    Observation #1: If you want your antialiased drawPolygon()s and antialiased fillPolygon()s to match up well (the original question), then turn on STROKE_PURE during the antialiased drawPolygon() calls. (It doesn't matter whether it's on during the antialiased fillPolygon() calls.)

    Observation #2: If you want your antialiased fillPolygon()s and non-antialiased fillPolygon()s to match up (because, say, your app allows the user to switch antialiasing on and off, and you don't want the picture to jump northwest and southeast each time they do that), then turn on STROKE_PURE during the non-antialiased fillPolygon() calls.

    Here is the program I used to generate the pictures above. My results are from compiling and running it it with opensdk11 on linux; I'd be interested to know if anyone gets any different results on different platforms.

    /** Study the effect of STROKE_PURE. */
    
    import java.awt.*;
    import java.awt.event.*;
    import java.awt.image.*;
    import javax.swing.*;
    
    @SuppressWarnings("serial")
    public final class AntiAliasingStudy {
    
      // These can be fiddled with.
      final static int patchWidth = 24;  // keep this a multiple of 4 for sanity
      final static int patchHeight = 20;  // keep this a multiple of 4 for sanity
      final static int borderThickness = 4;
      final static int mag = 6;
    
      // derived quantities
      final static int totalWidth = 5*borderThickness + 4*patchWidth;
      final static int totalHeight = 4*borderThickness + 3*patchHeight;
    
      private static void drawLittleStudy(Graphics2D g2d,
                                          int x00, int y00,
                                          int patchWidth, int patchHeight, int borderThickness, int totalWidth, int totalHeight) {
        g2d.setColor(new java.awt.Color(240,240,240));
        g2d.fillRect(x00,y00,totalWidth, totalHeight);
    
        for (int row = 0; row < 3; ++row) {
          for (int col = 0; col < 4; ++col) {
            int x0 = x00 + borderThickness + col*(patchWidth+borderThickness);
            int y0 = y00 + borderThickness + row*(patchHeight+borderThickness);
            int x1 = x0 + patchWidth;
            int y1 = y0 + patchHeight;
            g2d.setColor(java.awt.Color.WHITE);
            g2d.fillRect(x0, y0, patchWidth, patchHeight);
    
            boolean antialias = (col >= 2);
            boolean pure = (col % 2 == 1);
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, antialias ? RenderingHints.VALUE_ANTIALIAS_ON : RenderingHints.VALUE_ANTIALIAS_OFF);
            g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, pure ? RenderingHints.VALUE_STROKE_PURE : RenderingHints.VALUE_STROKE_NORMALIZE);
    
            g2d.setColor(java.awt.Color.RED);
            if (row == 0) {
              // lines (drawLine)
              // diagonals
              g2d.drawLine(x0,y1, x1,y0);
              g2d.drawLine(x0,y0, x1,y1);
              // orthogonals
              g2d.drawLine((x0+patchWidth/4),y0, (x0+patchWidth*3/4),y0);
              g2d.drawLine((x0+patchWidth/4),y1, (x0+patchWidth*3/4),y1);
              g2d.drawLine(x0,(y0+patchHeight/4), x0,(y0+patchHeight*3/4));
              g2d.drawLine(x1,(y0+patchHeight/4), x1,(y0+patchHeight*3/4));
            } else if (row == 1) {
              // outlines (drawPolygon)
              // A stopsign
              g2d.drawPolygon(new int[] {x0+patchWidth/2-2, x0, x0, x0+patchWidth/2-2, x0+patchWidth/2+2, x1, x1, x0+patchWidth/2+2},
                              new int[] {y0, y0+patchHeight/2-2, y0+patchHeight/2+2, y1, y1, y0+patchHeight/2+2, y0+patchHeight/2-2, y0},
                              8);
            } else if (row == 2) {
              // fill (fillPolygon)
              // A stopsign
              g2d.fillPolygon(new int[] {x0+patchWidth/2-2, x0, x0, x0+patchWidth/2-2, x0+patchWidth/2+2, x1, x1, x0+patchWidth/2+2},
                              new int[] {y0, y0+patchHeight/2-2, y0+patchHeight/2+2, y1, y1, y0+patchHeight/2+2, y0+patchHeight/2-2, y0},
                              8);
            }
          }
        }
      }  // drawLittleStudy
    
      // Show a study, previously created by drawLittleStudy(), magnified and annotated.
      private static void showMagnifiedAndAnnotatedStudy(Graphics g,
                                                  BufferedImage studyImage,
                                                  int x00, int y00,
                                                  int patchWidth, int patchHeight, int borderThickness, int totalWidth, int totalHeight, int mag,
                                                  ImageObserver imageObserver) {
        // Magnify the image
        g.drawImage(studyImage,
                    /*dst*/ x00,y00,x00+totalWidth*mag,y00+totalHeight*mag,
                    /*src*/ 0,0,totalWidth,totalHeight,
                    imageObserver);
    
        // Draw annotations on each picture in black,
        // in the highest quality non-biased mode
        // (now that we know what that is!)
        g.setColor(java.awt.Color.BLACK);
        ((Graphics2D)g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        ((Graphics2D)g).setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
        for (int row = 0; row < 3; ++row) {
          for (int col = 0; col < 4; ++col) {
            int x0 = borderThickness + col*(patchWidth+borderThickness);
            int y0 = borderThickness + row*(patchHeight+borderThickness);
            int x1 = x0 + patchWidth;
            int y1 = y0 + patchHeight;
            if (false) {
              g.drawLine(x00+x0*mag,y00+y0*mag, x00+x1*mag,y00+y0*mag);
              g.drawLine(x00+x1*mag,y00+y0*mag, x00+x1*mag,y00+y1*mag);
              g.drawLine(x00+x1*mag,y00+y1*mag, x00+x0*mag,y00+y1*mag);
              g.drawLine(x00+x0*mag,y00+y1*mag, x00+x0*mag,y00+y0*mag);
            }
            if (row == 0) {
              // diagonals
              g.drawLine(x00+x0*mag,y00+y1*mag, x00+x1*mag,y00+y0*mag);
              g.drawLine(x00+x0*mag,y00+y0*mag, x00+x1*mag,y00+y1*mag);
              // orthogonals
              g.drawLine(x00+(x0+patchWidth/4)*mag,y00+y0*mag, x00+(x0+patchWidth*3/4)*mag,y00+y0*mag);
              g.drawLine(x00+(x0+patchWidth/4)*mag,y00+y1*mag, x00+(x0+patchWidth*3/4)*mag,y00+y1*mag);
              g.drawLine(x00+x0*mag,y00+(y0+patchHeight/4)*mag, x00+x0*mag,y00+(y0+patchHeight*3/4)*mag);
              g.drawLine(x00+x1*mag,y00+(y0+patchHeight/4)*mag, x00+x1*mag,y00+(y0+patchHeight*3/4)*mag);
            } else {  // row 1 or 2
              // A stopsign
              g.drawPolygon(new int[] {x00+(x0+patchWidth/2-2)*mag, x00+x0*mag, x00+x0*mag, x00+(x0+patchWidth/2-2)*mag, x00+(x0+patchWidth/2+2)*mag, x00+x1*mag, x00+x1*mag, x00+(x0+patchWidth/2+2)*mag},
                            new int[] {y00+y0*mag, y00+(y0+patchHeight/2-2)*mag, y00+(y0+patchHeight/2+2)*mag, y00+y1*mag, y00+y1*mag, y00+(y0+patchHeight/2+2)*mag, y00+(y0+patchHeight/2-2)*mag, y00+y0*mag},
                            8);
            }
          }
        }
        FontMetrics fm = g.getFontMetrics();
        {
          String[][] texts = {
              {"ANTIALIAS_OFF", "STROKE_NORMALIZE"},
              {"ANTIALIAS_OFF", "STROKE_PURE"},
              {"ANTIALIAS_ON", "STROKE_NORMALIZE"},
              {"ANTIALIAS_ON", "STROKE_PURE"},
          };
          for (int col = 0; col < 4; ++col) {
            int xCenter = borderThickness*mag + col*(patchWidth+borderThickness)*mag + patchWidth*mag/2;
            {
              int x = x00 + xCenter - fm.stringWidth(texts[col][0])/2;
              int y = y00 + 3*(patchHeight+borderThickness)*mag + fm.getAscent();
              g.drawString(texts[col][0], x,y);
              x = xCenter - fm.stringWidth(texts[col][1])/2;
              y += fm.getHeight();
              g.drawString(texts[col][1], x,y);
            }
          }
        }
        {
          String[] texts = {
            "drawLine",
            "drawPolygon",
            "fillPolygon",
          };
          for (int row = 0; row < 3; ++row) {
            int yCenter = y00 + borderThickness*mag + row*(patchHeight+borderThickness)*mag + patchHeight*mag/2;
            int x = x00 + 4*(patchWidth+borderThickness)*mag + 10;
            g.drawString(texts[row], x,yCenter);
          }
        }
      }  // showMagnifiedAndAnnotatedStudy
    
    
      private static Dimension figureOutPreferredSize(FontMetrics fm) {
        int preferredWidth = (totalWidth-borderThickness)*mag + 10 + fm.stringWidth("drawPolygon") + 9;
        int preferredHeight = fm.getHeight() + totalHeight + (totalHeight-borderThickness)*mag + 2*fm.getHeight() + 2;
        return new Dimension(preferredWidth, preferredHeight);
      }
    
      private static class IndirectExaminationView extends JComponent {
        public IndirectExaminationView() {
          setFont(new Font("Times", Font.PLAIN, 12));
          setPreferredSize(figureOutPreferredSize(getFontMetrics(getFont())));
        }
        @Override public void paintComponent(Graphics g) {
          FontMetrics fm = g.getFontMetrics();
          g.setColor(java.awt.Color.BLACK);
          g.drawString("through BufferedImage:", 0,fm.getAscent());
          // The following seem equivalent
          java.awt.image.BufferedImage studyImage = new java.awt.image.BufferedImage(totalWidth, totalHeight, java.awt.image.BufferedImage.TYPE_INT_ARGB);
          //java.awt.image.BufferedImage studyImage = (BufferedImage)this.createImage(totalWidth, totalHeight);
          drawLittleStudy(studyImage.createGraphics(),
                          0,0,
                          patchWidth, patchHeight, borderThickness, totalWidth, totalHeight);
                        Graphics2D studyImageGraphics2D = studyImage.createGraphics();
          g.drawImage(studyImage,
                      /*dst*/ 0,fm.getHeight(),totalWidth,fm.getHeight()+totalHeight,
                      /*src*/ 0,0,totalWidth,totalHeight,
                      this);
          showMagnifiedAndAnnotatedStudy(g, studyImage,
                                         0,fm.getHeight()+totalHeight,
                                         patchWidth, patchHeight, borderThickness, totalWidth, totalHeight, mag, this);
        }
      }  // DirectExaminationView
    
      private static class DirectExaminationView extends JComponent {
        public DirectExaminationView() {
          setFont(new Font("Times", Font.PLAIN, 12));
          setPreferredSize(figureOutPreferredSize(getFontMetrics(getFont())));
        }
        private BufferedImage imgFromTheRobot = null;
        @Override public void paintComponent(Graphics g) {
          final FontMetrics fm = g.getFontMetrics();
          g.setColor(java.awt.Color.BLACK);
          g.drawString("direct to JComponent:", 0,fm.getAscent());
          drawLittleStudy((Graphics2D)g,
                          0,fm.getHeight(),
                          patchWidth, patchHeight, borderThickness, totalWidth, totalHeight);
    
          if (imgFromTheRobot != null) {
            System.out.println("              drawing image from robot");
            showMagnifiedAndAnnotatedStudy(g, imgFromTheRobot,
                                           0, fm.getHeight()+totalHeight,
                                           patchWidth, patchHeight, borderThickness, totalWidth, totalHeight, mag, this);
            imgFromTheRobot = null;
          } else {
            System.out.println("              scheduling a robot");
            g.drawString("*** SCREEN CAPTURE PENDING ***", 0, fm.getHeight()+totalHeight+fm.getHeight()+fm.getHeight());
            // Most reliable way to do it seems to be to put it on a timer after a delay.
            Timer timer = new Timer(1000/2, new ActionListener() {
              @Override public void actionPerformed(ActionEvent ae) {
                System.out.println("            in timer callback");
                Robot robot;
                try {
                  robot = new Robot();
                } catch (AWTException e) {
                  System.err.println("caught AWTException: "+e);
                  throw new Error(e);
                }
    
                Point myTopLeftOnScreen = getLocationOnScreen();
                Rectangle rect = new Rectangle(
                  myTopLeftOnScreen.x, myTopLeftOnScreen.y + fm.getHeight(),
                  totalWidth,totalHeight);
                BufferedImage img = robot.createScreenCapture(rect);
                imgFromTheRobot = img;
                repaint();
                System.out.println("            out timer callback");
              }
            });
            timer.setRepeats(false);
            timer.start();
          }
        }
      }  // DirectExaminationView
    
      public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
          @Override public void run()
          {
            final JFrame directFrame = new JFrame("direct to JComponent") {{
              getContentPane().add(new DirectExaminationView());
              setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
              pack();
              setLocation(0,0);
              setVisible(true);
            }};
            new JFrame("through BufferedImage") {{
              getContentPane().add(new IndirectExaminationView());
              setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
              pack();
              setLocation(directFrame.getWidth(),0);
              setVisible(true);
            }};
          }
        });
      }
    }  // class AntiAliasingStudy
    
    0 讨论(0)
  • 2021-01-13 09:41

    Show us your code for your drawing in a simple compilable runnable program. For instance when I try to imitate your image and used RenderingHints, it seemed to produce an appropriate sized image with complete right/bottom edges:

    import java.awt.Color;
    import java.awt.Graphics2D;
    import java.awt.RenderingHints;
    import java.awt.image.BufferedImage;
    
    import javax.swing.BorderFactory;
    import javax.swing.ImageIcon;
    import javax.swing.JLabel;
    import javax.swing.JOptionPane;
    import javax.swing.JPanel;
    
    public class Foo002 {
       private static final int WIDTH = 20;
    
       public static void main(String[] args) {
          BufferedImage img = new BufferedImage(WIDTH, WIDTH,
                BufferedImage.TYPE_INT_ARGB);
          Graphics2D g2 = img.createGraphics();
          int[] xPoints = { WIDTH / 3, (2 * WIDTH) / 3, WIDTH / 3 };
          int[] yPoints = { 0, WIDTH / 2, WIDTH };
          g2.setColor(Color.black);
          g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
          g2.setRenderingHint(RenderingHints.KEY_RENDERING,
                RenderingHints.VALUE_RENDER_QUALITY);
          g2.fillPolygon(xPoints, yPoints, xPoints.length);
          g2.dispose();
    
          ImageIcon icon = new ImageIcon(img);
          JLabel label = new JLabel(icon);
          label.setBorder(BorderFactory.createLineBorder(Color.black));
          JPanel panel = new JPanel();
          panel.add(label);
    
          JOptionPane.showMessageDialog(null, panel);
       }
    }
    

    If you can show us a similar program that reproduces your problem, then we can give you better help.

    0 讨论(0)
  • 2021-01-13 09:42

    I like the convenience of ImageIcon, shown by @HFOE, but this variation may make it a little easier to see what's happening. From the Graphics API,

    Operations that draw the outline of a figure operate by traversing an infinitely thin path between pixels with a pixel-sized pen that hangs down and to the right of the anchor point on the path. Operations that fill a figure operate by filling the interior of that infinitely thin path.

    In contrast, Graphics2D must follow more complex rules for antialiasing, which allow it to "draw outside the lines."

    PixelView

    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.EventQueue;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.GridLayout;
    import java.awt.RenderingHints;
    import java.awt.image.BufferedImage;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    
    /** @see http://stackoverflow.com/questions/7701097 */
    public class PixelView extends JPanel {
    
        private static final int SIZE = 20;
        private static final int SCALE = 16;
        private BufferedImage img;
    
        public PixelView(Color fill) {
            this.setBackground(Color.white);
            this.setPreferredSize(new Dimension(SCALE * SIZE, SCALE * SIZE));
            img = new BufferedImage(SIZE, SIZE, BufferedImage.TYPE_INT_ARGB);
            Graphics2D g2 = img.createGraphics();
            int[] xPoints = {SIZE / 3, (2 * SIZE) / 3, SIZE / 3};
            int[] yPoints = {0, SIZE / 2, SIZE};
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
            g2.setColor(Color.green);
            g2.drawLine(0, SIZE - 1, SIZE, SIZE - 1);
            g2.drawLine(0, 0, SIZE, 0);
            g2.drawLine(SIZE / 3, 0, SIZE / 3, SIZE);
            g2.drawLine((2 * SIZE / 3), 0, (2 * SIZE / 3), SIZE);
            g2.setColor(Color.black);
            g2.drawPolygon(xPoints, yPoints, xPoints.length);
            g2.setColor(fill);
            g2.fillPolygon(xPoints, yPoints, xPoints.length);
            g2.dispose();
        }
    
        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            g.drawImage(img, 0, 0, getWidth(), getHeight(), null);
        }
    
        private static void display() {
            JFrame f = new JFrame("PixelView");
            f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            f.setLayout(new GridLayout(1, 0));
            f.add(new PixelView(Color.black));
            f.add(new PixelView(Color.red));
            f.pack();
            f.setLocationRelativeTo(null);
            f.setVisible(true);
        }
    
        public static void main(String[] args) {
            EventQueue.invokeLater(new Runnable() {
    
                @Override
                public void run() {
                    display();
                }
            });
        }
    }
    
    0 讨论(0)
提交回复
热议问题