
1. Overview
Collision detection is a vital component of game development, computer graphics, and simulation software. In Java, developers can detect image (or sprite) collisions using various methods, depending on the application’s complexity and performance needs.
In this tutorial, we’ll explore how collision detection between two images works, covering basic bounding shapes as well as more advanced pixel-based methods.
2. Java’s Swing and AWT Libraries
We’ll use Swing for the GUI scaffolding, such as JFrame and JPanel, and AWT for rendering and geometry. We also need to consider that this is not optimized for high-performance rendering. Some drawbacks are the lack of a loop system, slower image rendering in complex scenes, and manual implementation of all physics and collisions.
There are several ways to set up a loop system, such as a Timer or a Thread. For this article, we’ll use a Thread; therefore, let’s create a class that extends JPanel and implements Runnable:
public class Game extends JPanel implements Runnable
This will allow us to paint the components and implement the run method for our Thread:
gameThread = new Thread(this);
gameThread.start();
public void run() {
while (!collided) {
repaint();
try {
Thread.sleep(16);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
Thread.sleep(16) should give us about 60 frames per second.
We’ll also need a class to store game object data so we can easily manipulate the images:
public class GameObject
Let’s add a constructor and a few methods to help us out:
public GameObject(int x, int y, BufferedImage image) {
this.x = x;
this.y = y;
this.image = image;
this.width = image.getWidth();
this.height = image.getHeight();
}
public void move(int dx, int dy) {
x += dx;
y += dy;
}
public void draw(Graphics g) {
g.drawImage(image, x, y, null);
}
We’ll be creating an epic duel between Darth Vader and Luke Skywalker, so let’s load two buffered images and create our game objects to work with:
BufferedImage vaderImage = ImageIO.read(new File("src/main/resources/images/vader.png"));
BufferedImage lukeImage = ImageIO.read(new File("src/main/resources/images/luke.png"));
vader = new GameObject(170, 370, vaderImage);
luke = new GameObject(1600, 370, lukeImage);
The last thing we need to do is to implement the paintComponent() method to draw our images and make some text appear when the collision has occurred:
protected void paintComponent(Graphics g) {
super.paintComponent(g);
vader.draw(g);
luke.draw(g);
if (collided) {
g.setColor(Color.RED);
g.setFont(new Font("SansSerif", Font.BOLD, 50));
g.drawString("COLLISION!", getWidth() / 2 - 100, getHeight() / 2);
}
}
3. Bounding Shapes Collision Detection
With the images loaded and the Game class set up, let’s explore a bounding box as our first method to detect collisions.
3.1. Bounding Box
Bounding box detection is the simplest and fastest technique. It checks whether the rectangular areas (usually defined by x, y, width, and height) of two images overlap. Java’s Rectangle class in the AWT package makes this approach straightforward. Let’s make use of our GameObject class and add another method:
public Rectangle getRectangleBounds() {
return new Rectangle(x, y, width, height);
}
We can now create rectangles based on the image position and size, and check if they intersect. In our run() method, we’ll need to add a few things to move the images towards each other, as well as a check for when they collide:
vader.move(2, 0);
luke.move(-2, 0);
if (vader.getRectangleBounds().intersects(luke.getRectangleBounds())) {
collided = true;
}
Once we run the game, here’s what the bounding box collision looks like:

3.2. Area of Circles
We can increase the accuracy of our collision detection by changing the shape we use. It might make more sense to use Ellipse2D.
Let’s add another method to our GameObject class to help us out. It returns an Ellipse2D based on the position and size of the image:
public Ellipse2D getEllipseBounds() {
return new Ellipse2D.Double(x, y, width, height);
}
Because intersects only works with rectangles, we won’t be able to check collision between two ellipses directly. As such, we’ll have to use the Area class and update our method to return an area instead of an ellipse:
public Area getEllipseAreaBounds() {
Ellipse2D.Double coll = new Ellipse2D.Double(x, y, width, height);
return new Area(coll);
}
With the method ready, let’s create two new areas for our characters:
Area areaVader = vader.getEllipseAreaBounds();
Area areaLuke = luke.getEllipseAreaBounds();
Finally, we’ll use intersect on the first area and pass the second as the argument. This will mutate the original Area object, modifying areaVader so that it represents only the overlapping part between it and areaLuke.
If we have an intersection, areaVader now contains the overlapping region. If there’s no intersection, areaVader becomes empty.
As such, let’s check whether it’s empty. If it’s not empty, that means that we have a collision:
areaVader.intersect(areaLuke);
if (!areaVader.isEmpty()) {
collided = true;
}
As we can see, when we run the game and the ellipses intersect, a collision is detected, and the message appears. Additionally, our images are closer together now with this shape than before, so we’re getting more accurate:

3.3. Circle Distance Check
When it comes to using Area, building a high-performance game with more than 1,000 objects might get heavy, in which case we can use a circle distance check instead. This computation is quicker than a true intersection test but relies on some specific assumptions.
Specifically, we need to keep in mind that this only works for ellipses with equal width and height — a true circle:
double dx = circleVader.getCenterX() - circleLuke.getCenterX();
double dy = circleVader.getCenterY() - circleLuke.getCenterY();
double distance = Math.sqrt(dx * dx + dy * dy);
double radiusVader = circleVader.getWidth() / 2.0;
double radiusLuke = circleLuke.getWidth() / 2.0;
boolean collided = distance < radiusVader + radiusLuke;
We can assume that the two circles collide if the distance between their centers is less than the sum of their radii:

This method might not be perfect in representing the image collision, but it’s much better in terms of performance.
3.4. Polygon Collision
Now, let’s set up collision detection with a custom shape. In this case, we’ll want to use a polygon with multiple coordinates.
First, let’s create two arrays with x and y points to be used, as well as two more representing the offsets of the x and y values of the image:
int[] xPoints = new int[4];
int[] yPoints = new int[4];
int[] xOffsets = {100, 200, 100, 0};
int[] yOffsets = {0, 170, 340, 170};
The offsets are set to represent a rhombus shape for our images.
Next, we’ll create a method to help us out with the polygon bounds, as well as the two Polygon objects to use:
public Polygon getPolygonBounds(int imgX, int imgY) {
for (int i = 0; i < xOffsets.length; i++) {
xPoints[i] = imgX + xOffsets[i];
yPoints[i] = imgY + yOffsets[i];
}
return new Polygon(xPoints, yPoints, xOffsets.length);
}
Polygon polyVader = getPolygonBounds(vader.x, vader.y);
Polygon polyLuke = getPolygonBounds(luke.x, luke.y);
Finally, let’s introduce two Area objects for our polygons and check if they intersect:
Area areaVader = new Area(polyVader);
Area areaLuke = new Area(polyLuke);
areaVader.intersect(areaLuke);
if (!areaVader.isEmpty()) {
collided = true;
}
As a result, we will get a collision when the two shapes intersect:

By converting our shapes to Area, we can even compare different shapes. As a result, we can also easily check for collisions between an ellipse and a polygon.
4. Pixel-Perfect Collision
When visual accuracy is essential, bounding boxes fall short. That’s where pixel-perfect collision detection comes in. This method checks overlapping areas at the pixel level, detecting whether non-transparent pixels of two images intersect.
Each pixel is represented as a 32-bit integer, divided into four 8-bit components: red, green, blue, and alpha, which controls transparency.
Let’s start by creating a method for detecting the collision with help from the Math library:
public boolean isPixelCollision(BufferedImage img1, int x1, int y1, BufferedImage img2, int x2, int y2) {
int top = Math.max(y1, y2);
int bottom = Math.min(y1 + img1.getHeight(), y2 + img2.getHeight());
int left = Math.max(x1, x2);
int right = Math.min(x1 + img1.getWidth(), x2 + img2.getWidth());
if (right <= left || bottom <= top) return false;
for (int y = top; y < bottom; y++) {
for (int x = left; x < right; x++) {
int img1Pixel = img1.getRGB(x - x1, y - y1);
int img2Pixel = img2.getRGB(x - x2, y - y2);
if (((img1Pixel >> 24) & 0xff) != 0 && ((img2Pixel >> 24) & 0xff) != 0) {
return true;
}
}
}
return false;
}
First, we calculate the rectangle where both images are drawn on the screen and could overlap.
Second, we check if the two rectangles overlap to avoid unnecessary pixel-by-pixel checks.
Third, we start looping through the area where both images overlap in screen space and get the pixels inside the images relative to the top-left corner, translating from global screen space to image-local pixel space.
Last, we shift the pixel’s bits 24 places to isolate the alpha channel, which we then extract. If the alpha value is not zero, the pixel is considered opaque. We return true if both pixels are opaque at the same screen position.
Here’s how Luke defeated Darth Vader, with a clear lightsaber blow to the head:

5. Conclusion
In this article, we’ve examined the native Java Swing and AWT methods and covered ways to handle collision detection, ranging from simple shapes to polygons to pixel-perfect collisions.
It’s important to recognize that different collision detection methods, such as bounding boxes, circles, or pixel-perfect checks, directly impact accuracy and computational cost. As a result, we must consider these factors and make tradeoffs between accuracy and speed to best suit our application’s needs.
As always, the code can be found over on GitHub.
The post Collision Detection Between Two Images in Java first appeared on Baeldung.