创建一个 swing gui 来逐个像素地操作 png

Create a swing gui to manipulate png pixel by pixel

正如我在问题标题中声明的那样,我将对给定 png 图像的特定区域进行某种编辑,通过单击它来逐个像素地更改颜色,也许可以帮助自己放大面积...

我被卡住了主要是因为我不知道,到目前为止我还没有找到一个解决方案来显示一个 "grid" 划分每个像素的 png。

我的意思是,一种类似填字游戏的细线可以 "highlight" 每个像素。

请指出正确的方向!

谢谢!

好的,基本上,这是一个非常 "simple" 的缩放过程。图像中的每个像素都由具有大小的 "cell" 表示。每个单元格都填充有像素的颜色。然后在上面覆盖一个简单的网格。

您可以使用滑块更改缩放比例(使网格变大或变小)。

该示例还利用工具提示支持来显示像素颜色

虽然这个例子不提供编辑。在EditorPane中添加一个MouseListener,使用与getToolTipText相同的算法,找到需要更新的像素是一件很简单的事情。

我的示例使用的是大型精灵 (177x345),旨在提供可变大小的精灵。较小或固定大小的精灵将提供更好的性能。

import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.JViewport;
import javax.swing.Scrollable;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class Main {

    public static void main(String[] args) {
        new Main();
    }

    public Main() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    try {
                        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                        ex.printStackTrace();
                    }

                    JFrame frame = new JFrame("Testing");
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.add(new SpriteEditorSpane());
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
        });
    }

    public class SpriteEditorSpane extends JPanel {

        private JLabel sprite;
        private JSlider zoom;
        private EditorPane editorPane;

        public SpriteEditorSpane() throws IOException {
            setLayout(new GridBagLayout());

            BufferedImage source = ImageIO.read(new File("sprites/Doctor-01.png"));
            sprite = new JLabel(new ImageIcon(source));

            editorPane = new EditorPane();
            editorPane.setSource(source);

            zoom = new JSlider(2, 10);
            zoom.addChangeListener(new ChangeListener() {
                @Override
                public void stateChanged(ChangeEvent e) {
                    editorPane.setGridSize(zoom.getValue());
                }
            });
            zoom.setValue(2);
            zoom.setPaintTicks(true);

            GridBagConstraints gbc = new GridBagConstraints();
            gbc.gridx = 0;
            gbc.gridy = 0;
            gbc.gridheight = GridBagConstraints.REMAINDER;
            add(sprite, gbc);

            gbc.gridx++;
            gbc.gridheight = 1;
            gbc.fill = GridBagConstraints.BOTH;
            gbc.weightx = 1;
            gbc.weighty = 1;
            add(new JScrollPane(editorPane), gbc);

            gbc.gridy++;
            gbc.fill = GridBagConstraints.HORIZONTAL;
            gbc.weightx = 1;
            gbc.weighty = 0;
            add(zoom, gbc);

        }

    }

    public class EditorPane extends JPanel implements Scrollable {

        private BufferedImage source;
        private BufferedImage gridBuffer;

        private int gridSize = 2;
        private Color gridColor;

        private Timer updateTimer;

        public EditorPane() {
            updateTimer = new Timer(250, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    doBufferUpdate();
                    revalidate();
                    repaint();
                }
            });
            updateTimer.setRepeats(false);
            addComponentListener(new ComponentAdapter() {
                @Override
                public void componentResized(ComponentEvent e) {
                    updateBuffer();
                }
            });
            setGridColor(new Color(128, 128, 128, 128));
            setToolTipText("Sprite");
        }

        @Override
        public Dimension getPreferredSize() {
            return source == null ? new Dimension(200, 200)
                    : new Dimension(source.getWidth() * gridSize, source.getHeight() * gridSize);
        }

        public void setGridColor(Color color) {
            if (color != gridColor) {
                this.gridColor = color;
                updateBuffer();
            }
        }

        public Color getGridColor() {
            return gridColor;
        }

        public void setSource(BufferedImage image) {
            if (image != source) {
                this.source = image;
                updateBuffer();
            }
        }

        public void setGridSize(int size) {
            if (size != gridSize) {
                this.gridSize = size;
                updateBuffer();
            }
        }

        public BufferedImage getSource() {
            return source;
        }

        public int getGridSize() {
            return gridSize;
        }

        @Override
        public String getToolTipText(MouseEvent event) {
            Point p = event.getPoint();
            int x = p.x / getGridSize();
            int y = p.y / getGridSize();

            BufferedImage source = getSource();
            String tip = null;
            if (x < source.getWidth() && y < source.getHeight()) {

                Color pixel = new Color(source.getRGB(x, y), true);
                StringBuilder sb = new StringBuilder(128);
                sb.append("<html><table><tr><td>");
                sb.append("R:").append(pixel.getRed());
                sb.append(" G:").append(pixel.getGreen());
                sb.append(" B:").append(pixel.getBlue());
                sb.append(" A:").append(pixel.getAlpha());
                String hex = String.format("#%02x%02x%02x%02x", pixel.getRed(), pixel.getGreen(), pixel.getBlue(), pixel.getAlpha());
                sb.append("</td></tr><tr><td bgcolor=").append(hex);
                sb.append("width=20 height=20>&nbsp;</td></tr></table>");

                tip = sb.toString();

            }

            return tip;
        }

        @Override
        public Point getToolTipLocation(MouseEvent event) {
            Point p = new Point(event.getPoint());
            p.x += 8;
            p.y += 8;
            return p;
        }

        protected void doBufferUpdate() {
            BufferedImage source = getSource();
            int gridSize = getGridSize();
            gridBuffer = null;
            if (source != null) {
                gridBuffer = new BufferedImage(source.getWidth() * gridSize, source.getHeight() * gridSize, BufferedImage.TYPE_INT_ARGB);
                Graphics2D g2d = gridBuffer.createGraphics();
                for (int row = 0; row < source.getHeight(); row++) {
                    for (int col = 0; col < source.getWidth(); col++) {
                        int xPos = col * gridSize;
                        int yPos = row * gridSize;
                        Color pixel = new Color(source.getRGB(col, row), true);
                        g2d.setColor(pixel);
                        g2d.fillRect(xPos, yPos, gridSize, gridSize);
                        g2d.setColor(getGridColor());
                        g2d.drawRect(xPos, yPos, gridSize, gridSize);
                    }
                }
                g2d.dispose();
            } else if (getWidth() > 0 && getHeight() > 0) {
                gridBuffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
                Graphics2D g2d = gridBuffer.createGraphics();
                g2d.setColor(gridColor);
                for (int xPos = 0; xPos < getWidth(); xPos += gridSize) {
                    g2d.drawLine(xPos, 0, xPos, getHeight());
                }
                for (int yPos = 0; yPos < getHeight(); yPos += gridSize) {
                    g2d.drawLine(0, yPos, getWidth(), yPos);
                }
                g2d.dispose();
            }
        }

        protected void updateBuffer() {
            updateTimer.restart();
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            if (gridBuffer != null) {
                g2d.drawImage(gridBuffer, 0, 0, this);
            }
            g2d.dispose();
        }

        @Override
        public Dimension getPreferredScrollableViewportSize() {
            return new Dimension(200, 200);
        }

        @Override
        public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
            return 128;
        }

        @Override
        public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
            return 128;
        }

        @Override
        public boolean getScrollableTracksViewportWidth() {
            Container parent = getParent();
            return parent instanceof JViewport
                    && parent.getWidth() > getPreferredSize().width;
        }

        @Override
        public boolean getScrollableTracksViewportHeight() {
            Container parent = getParent();
            return parent instanceof JViewport
                    && parent.getHeight() > getPreferredSize().height;
        }

    }
}

生成 "grid" 时整体性能相当慢,您可以使用 byte[] bytes = ((DataBufferByte)gridBuffer.getRaster().getDataBuffer()).getData() 来生成 byte 像素数组,但在我的测试中, 并没有太大的区别。

您可能还想看看 Zoom box for area around mouse location on screen