eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

Course – Summer Sale 2026 – NPI EA (cat= Baeldung)
announcement - icon

Yes, we're now running our only Summer Sale. All Courses are 30% off until 20th July, 2026:

>> EXPLORE ACCESS NOW

Course – Summer Sale 2026 – NPI (cat=Baeldung)
announcement - icon

Yes, we're now running our only Summer Sale. All Courses are 30% off until 20th July, 2026:

>> EXPLORE ACCESS NOW

1. Introduction

Gaussian blur is a widely used image processing technique for reducing image noise and detail. However, achieving accurate Gaussian blur can be computationally expensive, especially for high-resolution images with large blur radii.

In this tutorial, we’ll explore a faster, more computationally efficient solution for implementing Gaussian blur in Java.

2. Problem Overview

We implement a standard Gaussian blur on a grayscale image by convolving it with a 2D Gaussian kernel. Here, for each pixel in an image, the algorithm first examines a surrounding grid of pixels (the kernel). Then, it multiplies each pixel’s intensity by a specific weight. Finally, it sums them up.

For an image with n pixels and a blur radius of r, our kernel size is (2r + 1)  x  (2r + 1). Thus, the standard 2D convolution algorithm has a time complexity of O(n.r2). As the blur radius r grows, the performance degrades quadratically. Thus, it is not optimal for real-time processing over large images.

3. The Fast Approximation Algorithm

Our fast approximations make two key enhancements to the standard Gaussian 2D blur method.

3.1. Separable Filters

A 2D Gaussian kernel can be seen and used as two 1D Gaussian vectors. In other words, we can separate them as horizontal and vertical vectors. So, instead of applying a 2D matrix to every pixel of the image, we can achieve the same result by applying a horizontal 1D blur across all rows, followed by a vertical 1D blur down all columns.

This simple separation reduces our average-case time complexity from O(n.r2) to O(n.r).

3.2. Repeated Box Blurs

We repeat a specific blur operation (box blur) to achieve the desired effect.

We define box blur as the simplest form of blurring, assigning equal weight to every pixel within the blur radius. Then, we make use of the Central Limit Theorem to approximate a valid and matching Gaussian distribution. According to the Central Limit Theorem, applying a simple Box Blur multiple times closely approximates an accurate Gaussian distribution. We run five consecutive passes of a box blur to get a visually indistinguishable result from a strict Gaussian blur with a fraction of the computational overhead.

Furthermore, we make all weights in a Box Blur equal, so we don’t need to re-sum the entire radius for every pixel. This gives us the freedom to use a sliding window or a moving average. As the window shifts to the right by one pixel, we subtract the leaving pixel and add the entering pixel. This drops our average time complexity to O(n), making the performance entirely independent of the blur radius r!

4. Solution

Let’s move to the solution.

4.1. The Sliding Window Box Blur

First, we define a function, horizontalBoxBlur(), that performs a 1D horizontal blur using the sliding-window technique.

Here, we process the image row by row. We don’t recalculate the pixel sum at every position. Instead, we initialize windowSum for the very first pixel in a row. As it moves left to right, it subtracts the value of the pixel beyond the trailing edge of the radius and adds the value of the pixel at the leading edge.

Lastly, we clamp edges to ensure that if the radius extends beyond the image boundaries, our logic duplicates the edge pixels rather than throwing an ArrayIndexOutOfBoundsException.

private static void horizontalBoxBlur(int[] source, int[] target, int width, int height, int radius) {
    double scale = 1.0 / (radius * 2 + 1);
    for (int y = 0; y < height; y++) {
        int windowSum = 0;
        int offset = y * width;        
        for (int x = -radius; x <= radius; x++) {
            int safeX = Math.min(Math.max(x, 0), width - 1);
            windowSum += source[offset + safeX];
        }        
        for (int x = 0; x < width; x++) {
            target[offset + x] = (int) Math.round(windowSum * scale);
            int leftX = Math.max(x - radius, 0);
            int rightX = Math.min(x + radius + 1, width - 1);
            windowSum -= source[offset + leftX];
            windowSum += source[offset + rightX];
        }
    }
}

The verticalBoxBlur() is identical in logic, but here we traverse columns rather than rows (top to bottom):

private static void verticalBoxBlur(int[] source, int[] target, int width, int height, int radius) {
    double scale = 1.0 / (radius * 2 + 1);
    for (int x = 0; x < width; x++) {
        int windowSum = 0;
        for (int y = -radius; y <= radius; y++) {
            int safeY = Math.min(Math.max(y, 0), height - 1);
            windowSum += source[safeY * width + x];
        }
        for (int y = 0; y < height; y++) {
            target[y * width + x] = (int) Math.round(windowSum * scale);
            int topY = Math.max(y - radius, 0);
            int bottomY = Math.min(y + radius + 1, height - 1);
            windowSum -= source[topY * width + x];
            windowSum += source[bottomY * width + x];
        }
    }
}

4.2.  Integration

This is our orchestrator.

Leveraging the Central Limit Theorem, we apply a horizontal and vertical box blur three times (configurable). This smooths out peaky regions, thus creating a near-perfect approximation of a true Gaussian bell curve. It uses a temp array (temp) to safely ping-pong the data between passes without corrupting the source:

public static int[] applyFastGaussianBlur(int[] source, int numPasses, int width, int height, int radius) {
    int[] target = new int[source.length];
    int[] temp = new int[source.length];
    System.arraycopy(source, 0, target, 0, source.length);
    for (int i = 0; i < numPasses; i++) {
        horizontalBoxBlur(target, temp, width, height, radius);
        verticalBoxBlur(temp, target, width, height, radius);
    }
    return target;
}

5. Test

5.1. Test Using Impulse Image

First, let’s test this method using an impulse image (artificial images with a single black pixel). Our main objective is to verify if  our algorithm handles edges correctly and applies a smoothing effect:

void givenSharpImage_whenAppliedBlur_thenCenterIsSmoothed() {
    int width = 5;
    int height = 5;
    int numPasses = 5;
    int[] image = new int[width * height];
    image[12] = 255; 
    int[] blurredImage = FastGaussianBlur.applyFastGaussianBlur(image, numPasses, width, height, 1);
    assertTrue(blurredImage[12] < 255);
    assertTrue(blurredImage[12] > 0);
    assertTrue(blurredImage[11] > 0); // Left neighbor
    assertTrue(blurredImage[13] > 0); // Right neighbor
}

We begin by creating a 5×5 image (25 pixels) and set all pixels to 0, making them pure black. Then we set the exact center pixel (index 12) to 255, making it pure white. This sharp point is our impulse or peak. Then we apply the fast blur algorithm with a radius of 1.

Our main claim is that blur, when applied correctly, spreads the brightness of the central pixel outwards into the surrounding black pixels. Further, assertTrue(blurredImage[12] < 255) confirms the center pixel has dimmed because it shared its brightness, and assertTrue(blurredImage[11] > 0) and assertTrue(blurredImage[13] > 0) confirm that the immediate left and right neighbors (both black) have absorbed that dispersed brightness.

5.2. Test Using Real Image

We take an open-source image and blur it using our algorithm. Here is the sample image:

Kodim17 Original Face

Please check the blurred version:

Blurred Kodim17 Face

We used a single channel of integer values for our impulse image (grayscale) test case. For this case, we use real images. Each real image is packed with 32-bit ARGB (Alpha, Red, Green, Blue) data. So, here we extract the ARGB channels, then blur each of the RGB channels independently, and finally pack them back together:

public static BufferedImage blurRealImage(@Nonnull BufferedImage image, int radius, int numPasses) {
    int width = image.getWidth();
    int height = image.getHeight();
    int[] pixels = image.getRGB(0, 0, width, height, null, 0, width);

    int[] a = new int[pixels.length];
    int[] r = new int[pixels.length];
    int[] g = new int[pixels.length];
    int[] b = new int[pixels.length];

    for (int i = 0; i < pixels.length; i++) {
        a[i] = (pixels[i] >> 24) & 0xff;
        r[i] = (pixels[i] >> 16) & 0xff;
        g[i] = (pixels[i] >> 8) & 0xff;
        b[i] = pixels[i] & 0xff;
    }

    r = FastGaussianBlur.applyFastGaussianBlur(r, width, height, radius, numPasses);
    g = FastGaussianBlur.applyFastGaussianBlur(g, width, height, radius, numPasses);
    b = FastGaussianBlur.applyFastGaussianBlur(b, width, height, radius, numPasses);

    int[] resultPixels = new int[pixels.length];
    for (int i = 0; i < pixels.length; i++) {
        resultPixels[i] = (a[i] << 24) | (r[i] << 16) | (g[i] << 8) | b[i];
    }

    BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
    result.setRGB(0, 0, width, height, resultPixels, 0, width);
    return result;
}

6. Conclusion

In this article, we learned a faster Gaussian blur method in Java. We made two key enhancements to the standard Gaussian blur implementation to achieve linear time complexity. First, we decomposed a 2D convolution matrix into separable 1D arrays, and second, we used a moving-average sliding window across three box-blur passes. Thus, our solution works in a constant processing time that is independent of the blur radius.

As always, the complete code examples are available over on GitHub.

Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

Course – Summer Sale 2026 – NPI EA (cat= Baeldung)
announcement - icon

Yes, we're now running our only Summer Sale. All Courses are 30% off until 20th July, 2026:

>> EXPLORE ACCESS NOW

Course – Summer Sale 2026 – NPI (All)
announcement - icon

Yes, we're now running our only Summer Sale. All Courses are 30% off until 20th July, 2026:

>> EXPLORE ACCESS NOW

eBook Jackson – NPI EA – 3 (cat = Jackson)