Java 图像裁剪不准确

Posted

技术标签:

【中文标题】Java 图像裁剪不准确【英文标题】:Java Image Cropping Not Accurate 【发布时间】:2012-09-19 00:01:22 【问题描述】:

这是我的代码,当您选择要裁剪的区域然后按 Enter 时,裁剪后的图像与原始选定区域的大小/图片不同。

public class DragNDrop extends JFrame implements DropTargetListener 

private static final long serialVersionUID = 1872019741456690593L;

private Graphics g;
private BufferedImage image, origiImage;
private Rectangle area;
private Rectangle currentRect;
private Rectangle rectToDraw = null;
private Image buffer;

public static void main(String args[]) 
    new DragNDrop();


public DragNDrop() 
    super("Drop Test");
    setSize(300, 300);
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    setDropTarget(new DropTarget(getContentPane(), this));
    setVisible(true);

    CaptureListener listener = new CaptureListener();
    addMouseListener(listener);
    addMouseMotionListener(listener);


public void drop(DropTargetDropEvent dtde) 
    try 
        Transferable tr = dtde.getTransferable();
        DataFlavor[] flavors = tr.getTransferDataFlavors();
        dtde.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
        Object list = tr.getTransferData(flavors[0]);
    list = list.toString().substring(1, list.toString().length()-1);
        if (isValidImage(list)) 
            Image droppedImage = Toolkit.getDefaultToolkit().getImage(list.toString());
            image = toBufferedImage(droppedImage);
            origiImage = toBufferedImage(droppedImage);
            area = new Rectangle(image.getWidth(), image.getHeight());
            if (droppedImage != null) 
                setSize(image.getWidth(), image.getHeight());
                dtde.dropComplete(true);
                addKeyListener(new KeyListener() 

                    @Override
                    public void keyTyped(KeyEvent e) 
                    

                    @Override
                    public void keyReleased(KeyEvent e) 
                        if (e.getKeyCode() == 10) 
                            capture();
                        
                    

                    @Override
                    public void keyPressed(KeyEvent e) 

                    
                );
                return;
            
        
        dtde.rejectDrop();
     catch (Exception e) 
        dtde.rejectDrop();
    


public void paint() 
    if (area != null && image != null) 
        g.clearRect(area.x, area.y, area.width, area.height);
        g.drawImage(image, 0, 0, null);
    
    if (currentRect != null) 
        g.setColor(Color.RED);
        g.drawRect(rectToDraw.x, rectToDraw.y, rectToDraw.width, rectToDraw.height);
        g.setColor(new Color(255,255,255,150));
        g.fillRect(rectToDraw.x, rectToDraw.y, rectToDraw.width, rectToDraw.height);
    


@Override
public void paint(Graphics gr) 
    if (buffer == null && area != null) 
        this.buffer = createImage(area.width, area.height);
        this.g = buffer.getGraphics();
    
    paint();
    if (buffer != null)
        gr.drawImage(buffer, 0, 0, this);


public boolean isValidImage(Object list) 
    System.out.println(list.toString());
    for (String string : ImageIO.getReaderFormatNames())
        if (list.toString().contains(string))
            return true;
    return false;


public BufferedImage toBufferedImage(Image image) 
    if (image instanceof BufferedImage) 
        return (BufferedImage) image;
    
    image = new ImageIcon(image).getImage();
    boolean hasAlpha = hasAlpha(image);
    BufferedImage bimage = null;
    GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
    int transparency = Transparency.OPAQUE;
    if (hasAlpha == true) 
        transparency = Transparency.BITMASK;
    
    GraphicsDevice gs = ge.getDefaultScreenDevice();
    GraphicsConfiguration gc = gs.getDefaultConfiguration();
    bimage = gc.createCompatibleImage(image.getWidth(null), image.getHeight(null), transparency);
    if (bimage == null) 
        int type = BufferedImage.TYPE_INT_RGB;
        if (hasAlpha == true) 
            type = BufferedImage.TYPE_INT_ARGB;
        
        bimage = new BufferedImage(image.getWidth(null), image.getHeight(null), type);
    
    Graphics g = bimage.createGraphics();
    g.drawImage(image, 0, 0, null);
    g.dispose();
    return bimage;


public static boolean hasAlpha(Image image) 
    if (image instanceof BufferedImage) 
        return ((BufferedImage) image).getColorModel().hasAlpha();
    
    PixelGrabber pg = new PixelGrabber(image, 0, 0, 1, 1, false);
    try 
        pg.grabPixels();
     catch (InterruptedException e) 
    
    return pg.getColorModel().hasAlpha();


private void updateRectangle(int compWidth, int compHeight) 
    int x = currentRect.x;
    int y = currentRect.y;
    int width = currentRect.width;
    int height = currentRect.height;
    if (width < 0) 
        width = 0 - width;
        x = x - width + 1;
        if (x < 0) 
            width += x;
            x = 0;
        
    
    if (height < 0) 
        height = 0 - height;
        y = y - height + 1;
        if (y < 0) 
            height += y;
            y = 0;
        
    
    if ((x + width) > compWidth) 
        width = compWidth - x;
    
    if ((y + height) > compHeight) 
        height = compHeight - y;
    
    if (rectToDraw != null) 
        rectToDraw.setBounds(x, y, width, height);
     else 
        rectToDraw = new Rectangle(x, y, width, height);
    


public void capture() 
    BufferedImage croppedImage = origiImage.getSubimage(rectToDraw.x, rectToDraw.y, rectToDraw.width, rectToDraw.height);
    setSize(rectToDraw.width, rectToDraw.height);
    image = croppedImage;


public void upload(BufferedImage image) 
    String IMGUR_POST_URI = "http://api.imgur.com/2/upload.xml";
    String IMGUR_API_KEY = "b84e430b4a65d16a6955358141f21a61";
    String readLine = null;
    try 
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ImageIO.write(image, "png", outputStream);
        URL url = new URL(IMGUR_POST_URI);

        String data = URLEncoder.encode("image", "UTF-8") + "=" + URLEncoder.encode(Base64.encodeBase64String(outputStream.toByteArray()).toString(), "UTF-8") + "&" + URLEncoder.encode("key", "UTF-8") + "=" + URLEncoder.encode(IMGUR_API_KEY, "UTF-8");

        URLConnection urlConnection = url.openConnection();
        urlConnection.setDoOutput(true);
        OutputStreamWriter wr = new OutputStreamWriter(urlConnection.getOutputStream());
        wr.write(data);
        wr.flush();
        // Get the response
        InputStream inputStream;
        if (((HttpURLConnection) urlConnection).getResponseCode() == 400) 
            inputStream = ((HttpURLConnection) urlConnection).getErrorStream();
         else 
            inputStream = urlConnection.getInputStream();
        
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        String line;
        while ((line = reader.readLine()) != null) 
            readLine = line;
        
        wr.close();
        reader.close();
     catch(Exception e)
        e.printStackTrace();
    
    //Prints the url link of the image uploaded
    System.out.println(readLine.substring(readLine.indexOf("<original>") + 10, readLine.indexOf("</original>")));


public void dragEnter(DropTargetDragEvent dtde) 



public void dragExit(DropTargetEvent dte) 



public void dragOver(DropTargetDragEvent dtde) 



public void dropActionChanged(DropTargetDragEvent dtde) 



private class CaptureListener extends MouseInputAdapter 
    public void mouseDragged(MouseEvent e) 
        updateSize(e);
    

    public void mousePressed(MouseEvent e) 
        int x = e.getX();
        int y = e.getY();
        currentRect = new Rectangle(x, y, 0, 0);
        updateRectangle(getWidth(), getHeight());
        repaint();
    

    public void mouseReleased(MouseEvent e) 
        updateSize(e);
    

    public void updateSize(MouseEvent e) 
        currentRect.setSize(e.getX() - currentRect.x, e.getY() - currentRect.y);
        updateRectangle(getWidth(), getHeight());
        repaint();
    


 

有人可以查看我的代码并找出为什么它没有裁剪出完全相同的大小吗?

【问题讨论】:

您能否为您遇到的问题提供一个最小示例?例如,一个只拍摄照片并裁剪指定区域的短程序。让别人调试你的整个代码既不是一种好的方式,也不是很快得到有用答案的方法。 如您所见,这里是我选择的区域。 i.imgur.com/2uwzD.jpg 这是结果。 i.imgur.com/dhTOU.png 截图是个好主意。他们给了我可能出现问题的第一个提示。然而,这不是我所说的“最小的例子”。我宁愿想到一个较小版本的程序来说明问题。您的程序中有很多与裁剪问题无关的代码。我希望它在我的回答中变得更清楚一些。让我知道它是否对您有帮助(并询问是否有任何不清楚的地方)。 【参考方案1】:

你的裁剪逻辑没有问题,问题在于你的绘画逻辑。

首先,你不应该(好吧,后面)覆盖***容器上的paint(比如JFrame)。原因有很多,而您只是发现了其中一个。

其次,您应该始终致电super.paintXxx(g),不这样做是一个非常糟糕的主意。

老实说,我不知道这段代码试图做什么(除了双缓冲),如果你使用像JPanel 这样的东西就不需要了

public void paint() 
    if (area != null && image != null) 
        g.clearRect(area.x, area.y, area.width, area.height);
        g.drawImage(image, 0, 0, null);
    
    if (currentRect != null) 
        g.setColor(Color.RED);
        g.drawRect(rectToDraw.x, rectToDraw.y, rectToDraw.width, rectToDraw.height);
        g.setColor(new Color(255,255,255,150));
        g.fillRect(rectToDraw.x, rectToDraw.y, rectToDraw.width, rectToDraw.height);
    


@Override
public void paint(Graphics gr) 
    if (buffer == null && area != null) 
        this.buffer = createImage(area.width, area.height);
        this.g = buffer.getGraphics();
    
    paint();
    if (buffer != null)
        gr.drawImage(buffer, 0, 0, this);

这不允许框架实际上为框架装饰留出空间(0x0 实际上是窗口的最左上角,而不是内部绘图表面)...

所以我把你的代码拿来重写了……

public class DragNDrop extends JFrame 

    private static final long serialVersionUID = 1872019741456690593L;

    public static void main(String args[]) 
        new DragNDrop();
    

    public DragNDrop() 
        super("Drop Test");
        setLayout(new BorderLayout());
        add(new ImagePane());
        setSize(300, 300);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setVisible(true);
    

    protected class ImagePane extends JPanel implements DropTargetListener 

        private BufferedImage image, origiImage;
        private Rectangle currentRect;
        private Rectangle rectToDraw = null;

        public ImagePane() 

            setDropTarget(new DropTarget(getContentPane(), this));
            CaptureListener listener = new CaptureListener();
            addMouseListener(listener);
            addMouseMotionListener(listener);

            setFocusable(true);
            requestFocusInWindow();

            // Keybindings are better the KeyListeners, as the generally work...
            InputMap im = getInputMap(WHEN_FOCUSED);
            ActionMap am = getActionMap();

            im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "Capture");
            am.put("Capture", new AbstractAction() 
                @Override
                public void actionPerformed(ActionEvent e) 
                    capture();
                
            );

        

        @Override
        protected void paintComponent(Graphics g) 
            // Look how simple this is...

            super.paintComponent(g);

            Graphics2D g2d = (Graphics2D) g;
            if (image != null) 
                g2d.drawImage(image, 0, 0, this);
            

            if (currentRect != null) 
                g2d.setColor(Color.RED);
                g2d.drawRect(rectToDraw.x, rectToDraw.y, rectToDraw.width, rectToDraw.height);
                g2d.setColor(new Color(255, 255, 255, 150));
                g2d.fillRect(rectToDraw.x, rectToDraw.y, rectToDraw.width, rectToDraw.height);
            
        

        public void drop(DropTargetDropEvent dtde) 
            try 
                Transferable tr = dtde.getTransferable();
                DataFlavor[] flavors = tr.getTransferDataFlavors();
                dtde.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
                Object list = tr.getTransferData(flavors[0]);
                list = list.toString().substring(1, list.toString().length() - 1);
                if (isValidImage(list)) 
                    Image droppedImage = Toolkit.getDefaultToolkit().getImage(list.toString());
                    image = toBufferedImage(droppedImage);
                    origiImage = toBufferedImage(droppedImage);
                    if (droppedImage != null) 
                        setSize(image.getWidth(), image.getHeight());
                        dtde.dropComplete(true);
                        // This is a bad idea, apart from KeyListeners are just a bad idea,
                        // Each time you drop a new image, your registering a new key listener :P
//                        addKeyListener(new KeyListener() 
//                            @Override
//                            public void keyTyped(KeyEvent e) 
//                            
//
//                            @Override
//                            public void keyReleased(KeyEvent e) 
//                                if (e.getKeyCode() == 10) 
//                                    capture();
//                                
//                            
//
//                            @Override
//                            public void keyPressed(KeyEvent e) 
//                            
//                        );
                        return;
                    
                
                dtde.rejectDrop();
             catch (Exception e) 
                dtde.rejectDrop();
            
        

        public boolean isValidImage(Object list) 
            System.out.println(list.toString());
            for (String string : ImageIO.getReaderFormatNames()) 
                if (list.toString().contains(string)) 
                    return true;
                
            
            return false;
        

        public BufferedImage toBufferedImage(Image image) 
            if (image instanceof BufferedImage) 
                return (BufferedImage) image;
            
            image = new ImageIcon(image).getImage();
            boolean hasAlpha = hasAlpha(image);
            BufferedImage bimage = null;
            GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
            int transparency = Transparency.OPAQUE;
            if (hasAlpha == true) 
                transparency = Transparency.BITMASK;
            
            GraphicsDevice gs = ge.getDefaultScreenDevice();
            GraphicsConfiguration gc = gs.getDefaultConfiguration();
            bimage = gc.createCompatibleImage(image.getWidth(null), image.getHeight(null), transparency);
            if (bimage == null) 
                int type = BufferedImage.TYPE_INT_RGB;
                if (hasAlpha == true) 
                    type = BufferedImage.TYPE_INT_ARGB;
                
                bimage = new BufferedImage(image.getWidth(null), image.getHeight(null), type);
            
            Graphics g = bimage.createGraphics();
            g.drawImage(image, 0, 0, null);
            g.dispose();
            return bimage;
        

        public boolean hasAlpha(Image image) 
            if (image instanceof BufferedImage) 
                return ((BufferedImage) image).getColorModel().hasAlpha();
            
            PixelGrabber pg = new PixelGrabber(image, 0, 0, 1, 1, false);
            try 
                pg.grabPixels();
             catch (InterruptedException e) 
            
            return pg.getColorModel().hasAlpha();
        

        private void updateRectangle(int compWidth, int compHeight) 
            int x = currentRect.x;
            int y = currentRect.y;
            int width = currentRect.width;
            int height = currentRect.height;
            if (width < 0) 
                width = 0 - width;
                x = x - width + 1;
                if (x < 0) 
                    width += x;
                    x = 0;
                
            
            if (height < 0) 
                height = 0 - height;
                y = y - height + 1;
                if (y < 0) 
                    height += y;
                    y = 0;
                
            
            if ((x + width) > compWidth) 
                width = compWidth - x;
            
            if ((y + height) > compHeight) 
                height = compHeight - y;
            
            if (rectToDraw != null) 
                rectToDraw.setBounds(x, y, width, height);
             else 
                rectToDraw = new Rectangle(x, y, width, height);
            
        

        public void capture() 
            BufferedImage croppedImage = origiImage.getSubimage(rectToDraw.x, rectToDraw.y, rectToDraw.width, rectToDraw.height);
            setSize(rectToDraw.width, rectToDraw.height);
            image = croppedImage;

            currentRect = null;

            repaint();
        

        public void upload(BufferedImage image) 
          // Sorry, you can uncomment this can't you
//        String IMGUR_POST_URI = "http://api.imgur.com/2/upload.xml";
//        String IMGUR_API_KEY = "b84e430b4a65d16a6955358141f21a61";
//        String readLine = null;
//        try 
//            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
//            ImageIO.write(image, "png", outputStream);
//            URL url = new URL(IMGUR_POST_URI);
//
//            String data = URLEncoder.encode("image", "UTF-8") + "=" + URLEncoder.encode(Base64.encodeBase64String(outputStream.toByteArray()).toString(), "UTF-8") + "&" + URLEncoder.encode("key", "UTF-8") + "=" + URLEncoder.encode(IMGUR_API_KEY, "UTF-8");
//
//            URLConnection urlConnection = url.openConnection();
//            urlConnection.setDoOutput(true);
//            OutputStreamWriter wr = new OutputStreamWriter(urlConnection.getOutputStream());
//            wr.write(data);
//            wr.flush();
//            // Get the response
//            InputStream inputStream;
//            if (((HttpURLConnection) urlConnection).getResponseCode() == 400) 
//                inputStream = ((HttpURLConnection) urlConnection).getErrorStream();
//             else 
//                inputStream = urlConnection.getInputStream();
//            
//            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
//            String line;
//            while ((line = reader.readLine()) != null) 
//                readLine = line;
//            
//            wr.close();
//            reader.close();
//         catch (Exception e) 
//            e.printStackTrace();
//        
//        //Prints the url link of the image uploaded
//        System.out.println(readLine.substring(readLine.indexOf("<original>") + 10, readLine.indexOf("</original>")));
        

        public void dragEnter(DropTargetDragEvent dtde) 
        

        public void dragExit(DropTargetEvent dte) 
        

        public void dragOver(DropTargetDragEvent dtde) 
        

        public void dropActionChanged(DropTargetDragEvent dtde) 
        

        protected class CaptureListener extends MouseInputAdapter 

            public void mouseDragged(MouseEvent e) 
                updateSize(e);
            

            public void mousePressed(MouseEvent e) 

                requestFocusInWindow();

                int x = e.getX();
                int y = e.getY();
                currentRect = new Rectangle(x, y, 0, 0);
                updateRectangle(getWidth(), getHeight());
                repaint();
            

            public void mouseReleased(MouseEvent e) 
                updateSize(e);
            

            public void updateSize(MouseEvent e) 
                if (currentRect != null) 
                    currentRect.setSize(e.getX() - currentRect.x, e.getY() - currentRect.y);
                    updateRectangle(getWidth(), getHeight());
                    repaint();
                
            
        
    

现在可以了……

【讨论】:

【参考方案2】:

快速解答

您询问的问题是窗口装饰(“框架”、标题栏等)覆盖了裁剪图像的一部分。 (这就是为什么我认为与预期结果或上传/保存到文件的结果进行比较可能会有所帮助)。

就解决方案而言,您基本上必须确保不会像外部边界那样调整窗口大小,而是为内容提供所需的空间。有几种方法可以做到这一点。 MadProgrammer 将您的大部分代码移至面板类,并将其实例添加为 JFrame 的内容。

或者,您可以继续使用 JFrame contentPane 并确保在其上设置大小而不是框架:

getContentPane().setPreferredSize(new Dimension(newWidth, newHeight));
pack();

(在您的 JFrame 实例上调用方法,该解决方案从 Java 5 开始有效)


自己寻找答案

图像裁剪本身很好。您的问题出在其他地方,我想帮助您自己找到它。

尽量减少问题

您必须缩小代码中“奇怪的事情”发生的位置。所以不要盯着你的所有代码,而是复制一份,作为第一步,扔掉裁剪不需要的东西。 扔掉上传的东西,你不需要它来裁剪。 您的许多代码都涉及通过拖放获取文件名。将其缩短为命令行选项或纯硬编码字符串(硬编码的东西通常不好,但调试它没关系)。以此类推。

实际上,裁剪不需要任何 GUI,真的。那么,如果将裁剪后的图像写入文件而不是在窗口中显示会发生什么?

知道会发生什么

GUI 与否,列出您选择的位置(x,y)和尺寸(宽度,高度)。现在,您的程序裁剪的图像与使用 gimp/ImageMagick/... 以相同参数裁剪的相同图像相比如何?图片的哪些部分丢失了?

问好问题

按照这些步骤并提出这些问题,您应该知道您的程序出了什么问题。至少,您将能够就您的问题提出一个简短而准确的问题。而在 *** 上,说不定会有人来解答!

【讨论】:

问题是,我已经调试了好几个小时了,似乎找不到问题所在。

以上是关于Java 图像裁剪不准确的主要内容,如果未能解决你的问题,请参考以下文章

图像裁剪,使用 Java 删除图像中不需要的白色部分 [重复]

OCR:图像到文本?

在 Java 中裁剪图像

在 Android 中裁剪以适应图像

使用 Java 裁剪上传的图像

使用 jQuery/Java 裁剪和上传图像