Зона генерации не любит прямые линии

Я использую код генерации и рисования области из Сглаживание зубчатого пути, чтобы создать области столкновений для спрайтов с черепицей в игре JRPG, которую я создаю в java - Это не крупная игра с полным раздувом, а просто доказательство концепции для меня, чтобы учиться.

Здания работают отлично, за исключением прямых линий: http://puu.sh/7wJA2.png http://puu.sh/7wJGF.png

Эти изображения имеют контур столкновений, открытый в Photoshop (в фоновом режиме), и область, которую генерирует код Java на основе красного цвета (на переднем плане). Как видно, большая красная линия в первом изображении не будет рисоваться, если у меня не будет красного пикселя под ним каждые 2 пикселя, как показано на втором изображении.


Я не внес никаких изменений в код (кроме добавления комментариев в попытке лучше понять его), кроме как удалить

Graphics2D g = imageOutline.createGraphics();
//...
g.dispose();

из областиOutline draw code, заменив его ссылкой на мою собственную переменную Graphics2D. Я тестировал код как с этими двумя строками, так и без них, и я не видел никакой разницы.

Я немного зациклен на том, что может вызвать проблему - это не серьезная проблема, связанная с игрой, так как я могу исправить это несколькими способами, но это означает, что я должен объяснить эту проблему всем, кто показывает как использовать его со своими собственными текстурами спрайтов.


Я создал SSCCE проблемы. Это показывает, что генерируемые области имеют вертикальные линии, генерируемые отлично, но горизонтальные - нет. Он принимает два изображения - квадратные 30x30 пикселей, но один имеет внутренние пиксели, каждый 2-секундный пиксель, в котором создается более точная область.

Когда вы нажимаете клавишу, программа переключается между двумя изображениями (и их областями), что позволяет прямое сравнение.

Следующие два изображения должны быть помещены в корневую папку программы.

square1.pngsquare2.png

Вы можете запустить программу, запустив "основной" метод.

import java.awt.*;
import javax.swing.*;
import java.awt.event.*;

import java.awt.image.*;
import java.awt.geom.*;

import java.io.*;
import javax.imageio.*;

public class AreaOutlineCanvas extends JFrame implements KeyListener
{
    private ImagePanel imagePanel;

    /**
     * Constructor for objects of class Skeleton
     */
    public AreaOutlineCanvas()
    {
        // initialise instance variables   
        addKeyListener( this );
        initUI();
    }

    public static void main( String[] args )
    {
        for( String s: args )
        {
            System.out.println( s );
        }

        SwingUtilities.invokeLater( new Runnable()
            {
                @Override
                public void run()
                {
                    AreaOutlineCanvas aOC = new AreaOutlineCanvas();
                    aOC.pack();
                    aOC.setVisible( true );
                    aOC.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
                }

            } );
    }

    private void initUI()
    {
        imagePanel = new ImagePanel();
        setPreferredSize( new Dimension( 100, 100 ) );

        setLocationRelativeTo( null );        

        add( imagePanel );

        imagePanel.setDoubleBuffered( true );

        pack();
    }

    @Override
    public void keyTyped( KeyEvent e )
    {

    }

    @Override
    public void keyPressed( KeyEvent e )
    {
        //@TODO: switch statment checking the menu Skeleton is in (currentMenu)
        if( imagePanel != null )
        {
            imagePanel.bDrawingFirstArea = !imagePanel.bDrawingFirstArea;
            imagePanel.repaint();
        }
    }

    @Override
    public void keyReleased(KeyEvent e)
    {
        //System.out.println( "Key released: " + e.getKeyChar() + " (" + e.getKeyCode() + ")" );
    }

    public class ImagePanel extends JPanel
    {
        /** Path for the location of this charactors sprite sheet */
        private String firstPath = "square1.png";
        private String secondPath = "square2.png";

        private BufferedImage firstImage;
        private BufferedImage secondImage;

        private Area firstImageArea;
        private Area secondImageArea;

        public boolean bDrawingFirstArea = true;

        /**
         * Constructor for objects of class Character
         */
        public ImagePanel()
        {
            loadImages();
            generateAreas();
        }

        private void loadImages()
        {
            try
            {
                firstImage = ImageIO.read( new File( firstPath ) );
                secondImage = ImageIO.read( new File( secondPath ) );
            }
            catch( IOException e )
            {
                e.printStackTrace();
            }
        }

        private void generateAreas()
        {
            Color c = new Color( 255, 0, 0 );
            firstImageArea = getOutline( c, firstImage );
            secondImageArea = getOutline( c, secondImage );
        }

        public Area getOutline(Color target, BufferedImage bi) {
            // construct the GeneralPath
            GeneralPath gp = new GeneralPath();

            boolean cont = false;
            int targetRGB = target.getRGB();
            for (int xx=0; xx<bi.getWidth(); xx++) {
                for (int yy=0; yy<bi.getHeight(); yy++) {
                    if (bi.getRGB(xx,yy)==targetRGB) {
                        if (cont) {
                            gp.lineTo(xx,yy);
                            gp.lineTo(xx,yy+1);
                            gp.lineTo(xx+1,yy+1);
                            gp.lineTo(xx+1,yy);
                            gp.lineTo(xx,yy);
                        } else {
                            gp.moveTo(xx,yy);
                        }
                        cont = true;
                    } else {
                        cont = false;
                    }
                }
                cont = false;
            }
            gp.closePath();

            // construct the Area from the GP & return it
            return new Area(gp);
        }

        @Override
        public void paintComponent( Graphics g )
        {                
            super.paintComponent( g );
            drawImages(g);
        }   

        private void drawImages( Graphics g )
        {
            Graphics2D g2d = (Graphics2D) g;

            if( bDrawingFirstArea )
            {
                g2d.drawImage( firstImage, 50, 0, this );
                g2d.draw( firstImageArea );
            }
            else
            {
                g2d.drawImage( secondImage, 50, 0, this );
                g2d.draw( secondImageArea );
            }

            Toolkit.getDefaultToolkit().sync();
        }
    }
}

Для удобства я отправил код из Сглаживания неровного пути здесь

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.awt.geom.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;

/* Gain the outline of an image for further processing. */
class ImageOutline {

    private BufferedImage image;

    private TwoToneImageFilter twoToneFilter;
    private BufferedImage imageTwoTone;
    private JLabel labelTwoTone;

    private BufferedImage imageOutline;
    private Area areaOutline = null;
    private JLabel labelOutline;

    private JLabel targetColor;
    private JSlider tolerance;

    private JProgressBar progress;
    private SwingWorker sw;

    public ImageOutline(BufferedImage image) {
        this.image = image;
        imageTwoTone = new BufferedImage(
            image.getWidth(),
            image.getHeight(),
            BufferedImage.TYPE_INT_RGB);
    }

    public void drawOutline() {
        if (areaOutline!=null) {
            Graphics2D g = imageOutline.createGraphics();
            g.setColor(Color.WHITE);
            g.fillRect(0,0,imageOutline.getWidth(),imageOutline.getHeight());

            g.setColor(Color.RED);
            g.setClip(areaOutline);
            g.fillRect(0,0,imageOutline.getWidth(),imageOutline.getHeight());
            g.setColor(Color.BLACK);
            g.setClip(null);
            g.draw(areaOutline);

            g.dispose();
        }
    }

    public Area getOutline(Color target, BufferedImage bi) {
        // construct the GeneralPath
        GeneralPath gp = new GeneralPath();

        boolean cont = false;
        int targetRGB = target.getRGB();
        for (int xx=0; xx<bi.getWidth(); xx++) {
            for (int yy=0; yy<bi.getHeight(); yy++) {
                if (bi.getRGB(xx,yy)==targetRGB) {
                    if (cont) {
                        gp.lineTo(xx,yy);
                        gp.lineTo(xx,yy+1);
                        gp.lineTo(xx+1,yy+1);
                        gp.lineTo(xx+1,yy);
                        gp.lineTo(xx,yy);
                    } else {
                        gp.moveTo(xx,yy);
                    }
                    cont = true;
                } else {
                    cont = false;
                }
            }
            cont = false;
        }
        gp.closePath();

        // construct the Area from the GP & return it
        return new Area(gp);
    }

    public JPanel getGui() {
        JPanel images = new JPanel(new GridLayout(2,2,2,2));
        JPanel  gui = new JPanel(new BorderLayout(3,3));

        JPanel originalImage =  new JPanel(new BorderLayout(2,2));
        final JLabel originalLabel = new JLabel(new ImageIcon(image));
        targetColor = new JLabel("Target Color");
        targetColor.setForeground(Color.RED);
        targetColor.setBackground(Color.WHITE);
        targetColor.setBorder(new LineBorder(Color.BLACK));
        targetColor.setOpaque(true);

        JPanel controls = new JPanel(new BorderLayout());
        controls.add(targetColor, BorderLayout.WEST);
        originalLabel.addMouseListener( new MouseAdapter() {
            @Override
            public void mouseEntered(MouseEvent me) {
                originalLabel.setCursor(
                    Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
            }

            @Override
            public void mouseExited(MouseEvent me) {
                originalLabel.setCursor(Cursor.getDefaultCursor());
            }

            @Override
            public void mouseClicked(MouseEvent me) {
                int x = me.getX();
                int y = me.getY();

                Color c = new Color( image.getRGB(x,y) );
                targetColor.setBackground( c );

                updateImages();
            }
        });
        originalImage.add(originalLabel);

        tolerance = new JSlider(
            JSlider.HORIZONTAL,
            0,
            255,
            104
            );
        tolerance.addChangeListener( new ChangeListener() {
            public void stateChanged(ChangeEvent ce) {
                updateImages();
            }
        });
        controls.add(tolerance, BorderLayout.CENTER);
        gui.add(controls,BorderLayout.NORTH);

        images.add(originalImage);

        labelTwoTone = new JLabel(new ImageIcon(imageTwoTone));

        images.add(labelTwoTone);

        images.add(new JLabel("Smoothed Outline"));

        imageOutline = new BufferedImage(
            image.getWidth(),
            image.getHeight(),
            BufferedImage.TYPE_INT_RGB
            );

        labelOutline = new JLabel(new ImageIcon(imageOutline));
        images.add(labelOutline);

        updateImages();

        progress = new JProgressBar();

        gui.add(images, BorderLayout.CENTER);
        gui.add(progress, BorderLayout.SOUTH);

        return gui;
    }

    private void updateImages() {
        if (sw!=null) {
            sw.cancel(true);
        }
        sw = new SwingWorker() {
            @Override
            public String doInBackground() {
                progress.setIndeterminate(true);
                adjustTwoToneImage();
                labelTwoTone.repaint();
                areaOutline = getOutline(Color.BLACK, imageTwoTone);

                drawOutline();

                return "";
            }

            @Override
            protected void done() {
                labelOutline.repaint();
                progress.setIndeterminate(false);
            }
        };
        sw.execute();
    }

    public void adjustTwoToneImage() {
        twoToneFilter = new TwoToneImageFilter(
            targetColor.getBackground(),
            tolerance.getValue());

        Graphics2D g = imageTwoTone.createGraphics();
        g.drawImage(image, twoToneFilter, 0, 0);

        g.dispose();
    }

    public static void main(String[] args) throws Exception {
        int size = 150;
        final BufferedImage outline =
            new BufferedImage(size,size,BufferedImage.TYPE_INT_RGB);
        Graphics2D g = outline.createGraphics();
        g.setColor(Color.WHITE);
        g.fillRect(0,0,size,size);
        g.setRenderingHint(
            RenderingHints.KEY_DITHERING,
            RenderingHints.VALUE_DITHER_ENABLE);
        g.setRenderingHint(
            RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);

        Polygon p = new Polygon();
        p.addPoint(size/2, size/10);
        p.addPoint(size-10, size-10);
        p.addPoint(10, size-10);
        Area a = new Area(p);

        Rectangle r = new Rectangle(size/4, 8*size/10, size/2, 2*size/10);
        a.subtract(new Area(r));

        int radius = size/10;
        Ellipse2D.Double c = new Ellipse2D.Double(
            (size/2)-radius,
            (size/2)-radius,
            2*radius,
            2*radius
            );
        a.subtract(new Area(c));

        g.setColor(Color.BLACK);
        g.fill(a);

        ImageOutline io = new ImageOutline(outline);

        JFrame f = new JFrame("Image Outline");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.add(io.getGui());
        f.pack();
        f.setResizable(false);
        f.setLocationByPlatform(true);
        f.setVisible(true);
    }
}

class TwoToneImageFilter implements BufferedImageOp {

    Color target;
    int tolerance;

    TwoToneImageFilter(Color target, int tolerance) {
        this.target = target;
        this.tolerance = tolerance;
    }

    private boolean isIncluded(Color pixel) {
        int rT = target.getRed();
        int gT = target.getGreen();
        int bT = target.getBlue();
        int rP = pixel.getRed();
        int gP = pixel.getGreen();
        int bP = pixel.getBlue();
        return(
            (rP-tolerance<=rT) && (rT<=rP+tolerance) &&
            (gP-tolerance<=gT) && (gT<=gP+tolerance) &&
            (bP-tolerance<=bT) && (bT<=bP+tolerance) );
    }

    public BufferedImage createCompatibleDestImage(
        BufferedImage src,
        ColorModel destCM) {
        BufferedImage bi = new BufferedImage(
            src.getWidth(),
            src.getHeight(),
            BufferedImage.TYPE_INT_RGB);
        return bi;
    }

    public BufferedImage filter(
        BufferedImage src,
        BufferedImage dest) {

        if (dest==null) {
            dest = createCompatibleDestImage(src, null);
        }

        for (int x=0; x<src.getWidth(); x++) {
            for (int y=0; y<src.getHeight(); y++) {
                Color pixel = new Color(src.getRGB(x,y));
                Color write = Color.BLACK;
                if (isIncluded(pixel)) {
                    write = Color.WHITE;
                }
                dest.setRGB(x,y,write.getRGB());
            }
        }

        return dest;
    }

    public Rectangle2D getBounds2D(BufferedImage src) {
        return new Rectangle2D.Double(0, 0, src.getWidth(), src.getHeight());
    }

    public Point2D getPoint2D(
        Point2D srcPt,
        Point2D dstPt) {
        // no co-ord translation
        return srcPt;
    }

    public RenderingHints getRenderingHints() {
        return null;
    }
}