Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package com.baeldung.algorithms.fastgaussianblur;

/**
* Fast Gaussian Blur approximation using sliding window over rows and columns
*/
public class FastGaussianBlur {
/**
* Horizontal box blur over rows
*
* @param source Source image row.
* @param target Target image row.
* @param width Image width.
* @param height Image height.
* @param radius Blur radius.
*/
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;

// 1. We initialize the sliding window for the first pixel in the row
for (int x = -radius; x <= radius; x++) {
int safeX = Math.min(Math.max(x, 0), width - 1);
windowSum += source[offset + safeX];
}

// 2. We slide the window across the row
for (int x = 0; x < width; x++) {
target[offset + x] = (int) Math.round(windowSum * scale);

// 2a. We subtract the leaving pixel and add the entering pixel
int leftX = Math.max(x - radius, 0);
int rightX = Math.min(x + radius + 1, width - 1);

// 2b. We update the sliding window
windowSum -= source[offset + leftX];
windowSum += source[offset + rightX];
}
}
}

/**
* Vertical box blur over columns. It is identical in logic, but we just
* traverse columns instead of rows
*
* @param source Source image row.
* @param target Target image row.
* @param width Image width.
* @param height Image height.
* @param radius Blur radius.
*/
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];
}
}
}

/**
* Main orchestrator to call Fast Gaussian 2D Blur by separating it into
* two 1D operations
* @param source Source image row.
* @param width Image width
* @param height Image height
* @param radius Blur radius
* @param numPasses Number of Passes to Gaussian Approximate
*/
public static int[] applyFastGaussianBlur(int[] source, int width, int height, int radius, int numPasses) {
int[] target = new int[source.length];
int[] temp = new int[source.length];

// 1. Copy original image data to target
System.arraycopy(source, 0, target, 0, source.length);

// 2. Run (numPasses = 5) iterations for the Gaussian approximation
for (int i = 0; i < numPasses; i++) {
horizontalBoxBlur(target, temp, width, height, radius);
verticalBoxBlur(temp, target, width, height, radius);
}
return target;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package com.baeldung.algorithms.fastgaussianblur;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.InputStream;
import javax.imageio.ImageIO;

import javax.annotation.Nonnull;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Fast Gaussian Blur approximation tester on a real RGB image
*/
public class FastGaussianBlurRealImageTester {

private static final Logger LOGGER = LoggerFactory.getLogger(FastGaussianBlurRealImageTester.class);

@Nonnull
public static BufferedImage blurRealImage(@Nonnull BufferedImage image, int radius, int numPasses) {
int width = image.getWidth();
int height = image.getHeight();

// 1. We extract 1D array of 32-bit ARGB pixels
int[] pixels = image.getRGB(0, 0, width, height, null, 0, width);

// 2. We define arrays to hold independent color channels
int[] a = new int[pixels.length];
int[] r = new int[pixels.length];
int[] g = new int[pixels.length];
int[] b = new int[pixels.length];

// 3. We unpack the 32-bit integers into separate channels
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;
}

// 4. We apply our O(n) FastGaussianBlur algorithm to each color channel independently
r = FastGaussianBlur.applyFastGaussianBlur(r, width, height, radius, numPasses);
g = FastGaussianBlur.applyFastGaussianBlur(g, width, height, radius, numPasses);
b = FastGaussianBlur.applyFastGaussianBlur(b, width, height, radius, numPasses);

// 5. We repack the channels back into a 32-bit integer array
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];
}

// 6. We create a new BufferedImage and set the blurred pixels
BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
result.setRGB(0, 0, width, height, resultPixels, 0, width);

// 7. We return the result
return result;
}

public static void main(String[] args) throws Exception {
long startTime = System.currentTimeMillis();
int radius = 1;
int numPasses = 5;

// 1. We create a thread and read sapmple.jpg from ../src/man/resources
InputStream is = Thread.currentThread()
.getContextClassLoader()
.getResourceAsStream("sample.jpg");

if (is == null) {
throw new RuntimeException("Resource not found: sample.jpg");
}

BufferedImage originalImage = ImageIO.read(is);
assert originalImage != null;

// 2. We run our Fast Blur algorithm
BufferedImage blurredImage = blurRealImage(originalImage, radius, numPasses); // Radius 10
long endTime = System.currentTimeMillis();

LOGGER.debug("Blur completed in: " + (endTime - startTime) + " ms");

//3. We save result image sample_blurred.jpg under algorithms-numeric/target
File outputFile = new File("algorithms-numeric/target/sample_blurred.jpg");
boolean status = ImageIO.write(blurredImage, "png", outputFile);
LOGGER.debug("Blur Operation: " + status + " Saved to: " + outputFile.getAbsolutePath());
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.baeldung.algorithms.fastgaussianblur;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertTrue;


/**
* Fast Gaussian Blur JUnit Test case
*/
class FastGaussianBlurUnitTest {
@Test
void givenSharpImage_whenAppliedBlur_thenCenterIsSmoothed() {
int width = 5;
int height = 5;
int numPasses = 5;
int[] image = new int[width * height];

// 1. We create an impulse image with all indices black image except one bright white pixel in the center.
image[12] = 255;

int[] blurredImage = FastGaussianBlur.applyFastGaussianBlur(image, width, height, 1, numPasses);

// 2. We expect the center pixel should lose intensity
// as it gets spread to its neighbors
assertTrue(blurredImage[12] < 255);
assertTrue(blurredImage[12] > 0);

// 3. We expect that its immediate neighbors should have
// gained intensity
assertTrue(blurredImage[11] > 0); // Left neighbor
assertTrue(blurredImage[13] > 0); // Right neighbor
}
}