如何在Java中裁剪图像?

83
我想使用鼠标手动裁剪图像。
假设该图像具有一些文本,我想从图像中选择一些文本,那么为此我想使用鼠标裁剪该区域。

以下有几个好的想法。另请参阅http://java.sun.com/docs/books/tutorial/2d/。 - trashgod
Hussain:请给这些详细的答案中的一个打勾。 - IcedDante
7个回答

171

我发现裁剪缓冲图像最有用的解决方案使用了getSubImage(x,y,w,h)方法;

我的裁剪例程最终看起来像这样:

  private BufferedImage cropImage(BufferedImage src, Rectangle rect) {
      BufferedImage dest = src.getSubimage(0, 0, rect.width, rect.height);
      return dest; 
   }

73
为什么不同时包括X和Y坐标? BufferedImage dest = src.getSubimage(rect.x, rect.y, rect.width, rect.height); - Sorter

47

针对这个问题的领先答案存在两个可能的主要问题。首先,根据文档:

public BufferedImage getSubimage(int x, int y, int w, int h)

返回由指定矩形区域定义的子图像。返回的 BufferedImage 与原始图像共享同一数据数组。

实质上,这意味着从 getSubimage 得到的结果充当一个指针,它指向原始图像的子部分。

为什么这很重要?好吧,如果你计划编辑子图像,任何原因都会发生编辑同时也会反映在原始图像中。例如,当我在单独的窗口中使用较小的图像来放大原始图像时(有点像放大镜)就遇到了这个问题。我让颜色倒转以更轻松地查看某些细节,但是"缩放" 的区域在原始图像中也被倒转了!因此,原始图像中有一小部分具有反转的颜色,而其余部分则保持正常。在许多情况下,这并不重要,但如果您想编辑图像或者只想要截取部分的副本,您可能需要考虑其他方法。

这就引出了第二个问题。幸运的是,它不像第一个问题那样严重。getSubImage 与原始图像共享相同的数据数组。这意味着整个原始图像仍然存储在内存中。假设你实际上想要一个较小的图像,那么你将需要将其重新绘制为一个新的图像,而不仅仅是获取子图像。

试试这个:

BufferedImage img = image.getSubimage(startX, startY, endX, endY); //fill in the corners of the desired crop location here
BufferedImage copyOfImage = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics g = copyOfImage.createGraphics();
g.drawImage(img, 0, 0, null);
return copyOfImage; //or use it however you want

使用这种技术,您将获得所需的裁剪图像,而不需要链接回原始图像。这将保留原始图像的完整性,并节省存储较大图像的内存开销。(如果您稍后放弃原始图像)


1
你可以简单地使用Object类的clone()函数。 - Skaldebane

16

这是一个可行的方法:

import java.awt.image.BufferedImage;
import java.awt.Rectangle;
import java.awt.Color;
import java.awt.Graphics;

public BufferedImage crop(BufferedImage src, Rectangle rect)
{
    BufferedImage dest = new BufferedImage(rect.getWidth(), rect.getHeight(), BufferedImage.TYPE_ARGB_PRE);
    Graphics g = dest.getGraphics();
    g.drawImage(src, 0, 0, rect.getWidth(), rect.getHeight(), rect.getX(), rect.getY(), rect.getX() + rect.getWidth(), rect.getY() + rect.getHeight(), null);
    g.dispose();
    return dest;
}

当然你必须创建自己的JComponent:

import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import java.awt.Rectangle;
import java.awt.Graphics;
import javax.swing.JComponent;

public class JImageCropComponent extends JComponent implements MouseListener, MouseMotionListener
{
   private BufferedImage img;
   private int x1, y1, x2, y2;

   public JImageCropComponent(BufferedImage img)
   {
       this.img = img;
       this.addMouseListener(this);
       this.addMouseMotionListener(this);
   }

   public void setImage(BufferedImage img)
   {
       this.img = img;
   }

   public BufferedImage getImage()
   {
       return this;
   }

   @Override
   public void paintComponent(Graphics g)
   {
      g.drawImage(img, 0, 0, this);
      if (cropping)
      {
          // Paint the area we are going to crop.
          g.setColor(Color.RED);
          g.drawRect(Math.min(x1, x2), Math.min(y1, y2), Math.max(x1, x2), Math.max(y1, y2));
      }
   }

   @Override
   public void mousePressed(MouseEvent evt)
   {
       this.x1 = evt.getX();
       this.y1 = evt.getY();
   }

   @Override
   public void mouseReleased(MouseEvent evt)
   {
       this.cropping = false;
       // Now we crop the image;
       // This is the method a wrote in the other snipped
       BufferedImage cropped = crop(new Rectangle(Math.min(x1, x2), Math.min(y1, y2), Math.max(x1, x2), Math.max(y1, y2));
       // Now you have the cropped image;
       // You have to choose what you want to do with it
       this.img = cropped;
   }

   @Override
   public void mouseDragged(MouseEvent evt)
   {
       cropping = true;
       this.x2 = evt.getX();
       this.y2 = evt.getY();
   }

   //TODO: Implement the other unused methods from Mouse(Motion)Listener

}

我没有测试过,可能会有一些错误(关于所有的导入内容我不确定)。


你可以将crop(img, rect)方法放在这个类中。 希望这能帮到你。


2
谢谢您的回复,但是它给出了很多错误! 您能否告诉我一个简单的代码来裁剪图像? - Hussain

15
File fileToWrite = new File(filePath, "url");

BufferedImage bufferedImage = cropImage(fileToWrite, x, y, w, h);

private BufferedImage cropImage(File filePath, int x, int y, int w, int h){
    
    try {
        BufferedImage originalImgage = ImageIO.read(filePath);
        
        BufferedImage subImgage = originalImgage.getSubimage(x, y, w, h);           
        return subImgage;
    } catch (IOException e) {
        e.printStackTrace();
        return null;
    }
}

5

这个问题提供的信息不足以回答。一个通用的解决方案(取决于您的GUI框架):添加一个鼠标事件处理程序来捕获点击和鼠标移动。这将给你(x, y)坐标。接下来使用这些坐标来裁剪您的图像。


2
您需要了解Java Image API和与鼠标相关的API,可能在java.awt.event包下某个位置。
首先,您需要能够加载并显示图像到屏幕上,也许您会使用JPanel
然后,您将尝试实现鼠标运动监听器接口和其他相关接口。也许您会在mouseDragged方法上卡住...
对于mousedragged操作,您将获得由拖动形成的矩形的坐标...
然后从这些坐标中,您将获取您所拥有的图像的子图像,并重新绘制它...
然后显示裁剪后的图像... 我不知道这是否有效,只是我的想象... 只是一种想法!

1
我提供这个例子是因为它适用于我的使用情况。
我试图使用AWS Rekognition API。该API返回一个BoundingBox对象:
BoundingBox boundingBox = faceDetail.getBoundingBox();

下面的代码使用它来裁剪图像:
import com.amazonaws.services.rekognition.model.BoundingBox;

private BufferedImage cropImage(BufferedImage image, BoundingBox box) {
        Rectangle goal = new Rectangle(Math.round(box.getLeft()* image.getWidth()),Math.round(box.getTop()* image.getHeight()),Math.round(box.getWidth() * image.getWidth()), Math.round(box.getHeight() * image.getHeight()));

        Rectangle clip = goal.intersection(new Rectangle(image.getWidth(), image.getHeight()));

        BufferedImage clippedImg = image.getSubimage(clip.x, clip.y , clip.width, clip.height);

        return clippedImg;
    }

1
请更好地解释一下这个BoundingBox是如何传递给函数的,我无法使用您的函数。 - vijay
1
抱歉 @vijay,回复晚了。boundingBox对象是AWS rekognition API中的一个对象。 - Eduardo Briguenti Vieira

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接