From b1867b37bf402e245196fc40abd812da4a0c3316 Mon Sep 17 00:00:00 2001 From: Andrew Lazarev Date: Thu, 30 Apr 2026 22:11:46 +0300 Subject: [PATCH 01/11] feat: Add a bunch of kernels Signed-off-by: Andrew Lazarev --- .../java/andrewla/kernels/AbstractKernel.java | 78 +++++++++++++++++++ src/main/java/andrewla/kernels/Emboss.java | 41 ++++++++++ .../java/andrewla/kernels/GaussianBlur.java | 46 +++++++++++ src/main/java/andrewla/kernels/Identity.java | 34 ++++++++ .../java/andrewla/kernels/MotionBlur.java | 45 +++++++++++ src/main/java/andrewla/kernels/Sharpen.java | 56 +++++++++++++ 6 files changed, 300 insertions(+) create mode 100644 src/main/java/andrewla/kernels/AbstractKernel.java create mode 100644 src/main/java/andrewla/kernels/Emboss.java create mode 100644 src/main/java/andrewla/kernels/GaussianBlur.java create mode 100644 src/main/java/andrewla/kernels/Identity.java create mode 100644 src/main/java/andrewla/kernels/MotionBlur.java create mode 100644 src/main/java/andrewla/kernels/Sharpen.java diff --git a/src/main/java/andrewla/kernels/AbstractKernel.java b/src/main/java/andrewla/kernels/AbstractKernel.java new file mode 100644 index 0000000..7112e58 --- /dev/null +++ b/src/main/java/andrewla/kernels/AbstractKernel.java @@ -0,0 +1,78 @@ +package andrewla.kernels; + +import andrewla.Kernel; + +import java.util.Objects; + +public abstract class AbstractKernel implements Kernel { + private final int size; + private final double[][] data; + + private double factor; + private double bias; + + protected AbstractKernel(int size) { + if (size % 2 == 0) { + throw new IllegalArgumentException("Size of kernel matrix should be odd"); + } + + this.size = size; + this.data = new double[size][size]; + this.factor = 1; + this.bias = 0; + } + + @Override + public int getSize() { + return size; + } + + @Override + public double getValue(int x, int y) { + if (x < 0 || x > size) { + throw new IllegalArgumentException("Invalid X coordinate"); + } + + if (y < 0 || y > size) { + throw new IllegalArgumentException("Invalid Y coordinate"); + } + + return data[y][x]; + } + + protected void setValue(int x, int y, double value) { + if (x < 0 || x > size) { + throw new IllegalArgumentException("Invalid X coordinate"); + } + + if (y < 0 || y > size) { + throw new IllegalArgumentException("Invalid Y coordinate"); + } + + data[y][x] = value; + } + + protected void setFactor() { + var sum = 0.0; + + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + sum += data[i][j]; + } + } + + factor = 1 / sum; + } + + protected void setBias(double bias) { + this.bias = bias; + } + + @Override + public boolean equals(Object other) { + if (other == null || getClass() != other.getClass()) return false; + + AbstractKernel that = (AbstractKernel) other; + return size == that.size && Double.compare(factor, that.factor) == 0 && Objects.deepEquals(data, that.data); + } +} diff --git a/src/main/java/andrewla/kernels/Emboss.java b/src/main/java/andrewla/kernels/Emboss.java new file mode 100644 index 0000000..4d48440 --- /dev/null +++ b/src/main/java/andrewla/kernels/Emboss.java @@ -0,0 +1,41 @@ +package andrewla.kernels; + +import andrewla.Kernel; + +public class Emboss extends AbstractKernel { + public Emboss(int size) { + super(size); + + var center = size / 2; + + for (int i = 0; i < size; i++) { + for (int j = 0; j <= i; j++) { + setValue(i, j, -1); + } + + setValue(i, size - 1 - i, 0); + + for (int j = i + 1; j < size; j++) { + setValue(i, j, 1); + } + } + + setFactor(); + setBias(128); + } + + @Override + public Kernel getResized(int newSize) { + return new Emboss(newSize); + } + + @Override + public Kernel getExpanded(int newSize) { + return null; + } + + @Override + public Kernel getComposed(Kernel other) { + return null; + } +} diff --git a/src/main/java/andrewla/kernels/GaussianBlur.java b/src/main/java/andrewla/kernels/GaussianBlur.java new file mode 100644 index 0000000..bea9e71 --- /dev/null +++ b/src/main/java/andrewla/kernels/GaussianBlur.java @@ -0,0 +1,46 @@ +package andrewla.kernels; + +import andrewla.Kernel; + +public class GaussianBlur extends AbstractKernel { + private final double blurRadius; + + public GaussianBlur(int size, double blurRadius) { + super(size); + this.blurRadius = blurRadius; + + var center = size / 2; + + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + var x = (double) (j - center); + var y = (double) (i - center); + + var s2 = 2 * blurRadius * blurRadius; + var r2 = x * x + y * y; + + var value = Math.exp(-r2 / s2) / (Math.PI * s2); + + setValue(i, j, value); + } + } + + setFactor(); + setBias(0); + } + + @Override + public Kernel getResized(int newSize) { + return new GaussianBlur(newSize, blurRadius); + } + + @Override + public Kernel getExpanded(int newSize) { + return null; + } + + @Override + public Kernel getComposed(Kernel other) { + return null; + } +} diff --git a/src/main/java/andrewla/kernels/Identity.java b/src/main/java/andrewla/kernels/Identity.java new file mode 100644 index 0000000..1c8477d --- /dev/null +++ b/src/main/java/andrewla/kernels/Identity.java @@ -0,0 +1,34 @@ +package andrewla.kernels; + +import andrewla.Kernel; + +public class Identity extends AbstractKernel { + public Identity(int size) { + super(size); + + var center = size / 2; + + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + setValue(i, j, 0); + } + } + + setValue(center, center, 1); + } + + @Override + public Kernel getResized(int newSize) { + return new Identity(newSize); + } + + @Override + public Kernel getExpanded(int newSize) { + return new Identity(newSize); + } + + @Override + public Kernel getComposed(Kernel other) { + return other; + } +} diff --git a/src/main/java/andrewla/kernels/MotionBlur.java b/src/main/java/andrewla/kernels/MotionBlur.java new file mode 100644 index 0000000..60297c8 --- /dev/null +++ b/src/main/java/andrewla/kernels/MotionBlur.java @@ -0,0 +1,45 @@ +package andrewla.kernels; + +import andrewla.Kernel; + +public class MotionBlur extends AbstractKernel { + private final double angle; + + public MotionBlur(int size, double angle) { + super(size); + this.angle = angle; + + var angleRadians = Math.toRadians(angle); + + var sin = Math.sin(angleRadians); + var cos = Math.cos(angleRadians); + + var center = size / 2; + + for (int i = 0; i < size; i++) { + int x = (int) Math.round((i - center) * cos); + int y = (int) Math.round((i - center) * sin); + if (Math.abs(x) <= center && Math.abs(y) <= center) { + setValue(x + center, y + center, 1); + } + } + + setFactor(); + setBias(0.0); + } + + @Override + public Kernel getResized(int newSize) { + return new MotionBlur(newSize, angle); + } + + @Override + public Kernel getExpanded(int newSize) { + return null; + } + + @Override + public Kernel getComposed(Kernel other) { + return null; + } +} diff --git a/src/main/java/andrewla/kernels/Sharpen.java b/src/main/java/andrewla/kernels/Sharpen.java new file mode 100644 index 0000000..93f1384 --- /dev/null +++ b/src/main/java/andrewla/kernels/Sharpen.java @@ -0,0 +1,56 @@ +package andrewla.kernels; + +import andrewla.Kernel; + +public class Sharpen extends AbstractKernel{ + public Sharpen(int size) { + super(size); + + if (size == 3) { + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + setValue(i, j, -1); + } + } + + setValue(1, 1, 9); + } + else if (size == 5) { + for (int i = 0; i < 5; i++) { + setValue(i, 0, -1); + setValue(i, 4, -1); + setValue(0, i, -1); + setValue(4, i, -1); + } + for (int i = 1; i < 4; i++) { + setValue(i, 1, 2); + setValue(i, 3, 2); + setValue(1, i, 2); + setValue(3, i, 2); + } + + setValue(2, 2, 8); + } + else { + throw new IllegalArgumentException("Size of sharpen kernel can be either 3 or 5"); + } + + setFactor(); + setBias(0); + } + + @Override + public Kernel getResized(int newSize) { + return new Sharpen(newSize); + } + + @Override + public Kernel getExpanded(int newSize) { + return null; + } + + @Override + public Kernel getComposed(Kernel other) { + return null; + } +} From bb8dfc32f6a49d44928f238bc3a9f9096f9d47a2 Mon Sep 17 00:00:00 2001 From: Andrew Lazarev Date: Thu, 30 Apr 2026 22:12:46 +0300 Subject: [PATCH 02/11] feat: Add pom.xml & gitignore Signed-off-by: Andrew Lazarev --- .gitignore | 38 ++++++++++++++++++++++++++++++++++++++ pom.xml | 17 +++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 .gitignore create mode 100644 pom.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..2f6d608 --- /dev/null +++ b/pom.xml @@ -0,0 +1,17 @@ + + + 4.0.0 + + org.andrewla + convolution + 1.0-SNAPSHOT + + + 11 + 11 + UTF-8 + + + From 7bd0bdf81b85ed0d8e41173a1094c15f8459f39a Mon Sep 17 00:00:00 2001 From: Andrew Lazarev Date: Thu, 30 Apr 2026 22:30:59 +0300 Subject: [PATCH 03/11] feat: Add kernel interface, implement expanding function Signed-off-by: Andrew Lazarev --- src/main/java/andrewla/Kernel.java | 15 ++++++ .../{AbstractKernel.java => BaseKernel.java} | 47 ++++++++++++++++--- src/main/java/andrewla/kernels/Emboss.java | 9 +--- .../java/andrewla/kernels/GaussianBlur.java | 9 +--- src/main/java/andrewla/kernels/Identity.java | 7 +-- .../java/andrewla/kernels/MotionBlur.java | 9 +--- src/main/java/andrewla/kernels/Sharpen.java | 9 +--- 7 files changed, 64 insertions(+), 41 deletions(-) create mode 100644 src/main/java/andrewla/Kernel.java rename src/main/java/andrewla/kernels/{AbstractKernel.java => BaseKernel.java} (63%) diff --git a/src/main/java/andrewla/Kernel.java b/src/main/java/andrewla/Kernel.java new file mode 100644 index 0000000..a0d1dd2 --- /dev/null +++ b/src/main/java/andrewla/Kernel.java @@ -0,0 +1,15 @@ +package andrewla; + +public interface Kernel { + int getSize(); + + double getValue(int x, int y); + + double getBias(); + + double getFactor(); + + Kernel getResized(int newSize); + + Kernel getExpanded(int newSize); +} diff --git a/src/main/java/andrewla/kernels/AbstractKernel.java b/src/main/java/andrewla/kernels/BaseKernel.java similarity index 63% rename from src/main/java/andrewla/kernels/AbstractKernel.java rename to src/main/java/andrewla/kernels/BaseKernel.java index 7112e58..ef5eb7e 100644 --- a/src/main/java/andrewla/kernels/AbstractKernel.java +++ b/src/main/java/andrewla/kernels/BaseKernel.java @@ -4,14 +4,15 @@ import java.util.Objects; -public abstract class AbstractKernel implements Kernel { - private final int size; - private final double[][] data; +public abstract class BaseKernel implements Kernel { + private int size; + + private double[][] data; private double factor; private double bias; - protected AbstractKernel(int size) { + protected BaseKernel(int size) { if (size % 2 == 0) { throw new IllegalArgumentException("Size of kernel matrix should be odd"); } @@ -52,6 +53,20 @@ protected void setValue(int x, int y, double value) { data[y][x] = value; } + @Override + public double getBias() { + return bias; + } + + protected void setBias(double bias) { + this.bias = bias; + } + + @Override + public double getFactor() { + return factor; + } + protected void setFactor() { var sum = 0.0; @@ -64,15 +79,33 @@ protected void setFactor() { factor = 1 / sum; } - protected void setBias(double bias) { - this.bias = bias; + protected Kernel expandWithZeros(int newSize) { + if (newSize <= this.size) { + throw new IllegalArgumentException("New size should be greater than the actual"); + } + + if (newSize % 2 == 0) { + throw new IllegalArgumentException("Size of kernel matrix should be odd"); + } + + var newData = new double[newSize][newSize]; + var expansion = newSize - size; + + for (int i = 0; i < size; i++) { + System.arraycopy(data[i], 0, newData[i + expansion], expansion, size); + } + + this.data = newData; + this.size = newSize; + + return this; } @Override public boolean equals(Object other) { if (other == null || getClass() != other.getClass()) return false; - AbstractKernel that = (AbstractKernel) other; + BaseKernel that = (BaseKernel) other; return size == that.size && Double.compare(factor, that.factor) == 0 && Objects.deepEquals(data, that.data); } } diff --git a/src/main/java/andrewla/kernels/Emboss.java b/src/main/java/andrewla/kernels/Emboss.java index 4d48440..5876c0d 100644 --- a/src/main/java/andrewla/kernels/Emboss.java +++ b/src/main/java/andrewla/kernels/Emboss.java @@ -2,7 +2,7 @@ import andrewla.Kernel; -public class Emboss extends AbstractKernel { +public class Emboss extends BaseKernel { public Emboss(int size) { super(size); @@ -31,11 +31,6 @@ public Kernel getResized(int newSize) { @Override public Kernel getExpanded(int newSize) { - return null; - } - - @Override - public Kernel getComposed(Kernel other) { - return null; + return new Emboss(getSize()).expandWithZeros(newSize); } } diff --git a/src/main/java/andrewla/kernels/GaussianBlur.java b/src/main/java/andrewla/kernels/GaussianBlur.java index bea9e71..92334f2 100644 --- a/src/main/java/andrewla/kernels/GaussianBlur.java +++ b/src/main/java/andrewla/kernels/GaussianBlur.java @@ -2,7 +2,7 @@ import andrewla.Kernel; -public class GaussianBlur extends AbstractKernel { +public class GaussianBlur extends BaseKernel { private final double blurRadius; public GaussianBlur(int size, double blurRadius) { @@ -36,11 +36,6 @@ public Kernel getResized(int newSize) { @Override public Kernel getExpanded(int newSize) { - return null; - } - - @Override - public Kernel getComposed(Kernel other) { - return null; + return new GaussianBlur(getSize(), blurRadius).expandWithZeros(newSize); } } diff --git a/src/main/java/andrewla/kernels/Identity.java b/src/main/java/andrewla/kernels/Identity.java index 1c8477d..628abdc 100644 --- a/src/main/java/andrewla/kernels/Identity.java +++ b/src/main/java/andrewla/kernels/Identity.java @@ -2,7 +2,7 @@ import andrewla.Kernel; -public class Identity extends AbstractKernel { +public class Identity extends BaseKernel { public Identity(int size) { super(size); @@ -26,9 +26,4 @@ public Kernel getResized(int newSize) { public Kernel getExpanded(int newSize) { return new Identity(newSize); } - - @Override - public Kernel getComposed(Kernel other) { - return other; - } } diff --git a/src/main/java/andrewla/kernels/MotionBlur.java b/src/main/java/andrewla/kernels/MotionBlur.java index 60297c8..f578422 100644 --- a/src/main/java/andrewla/kernels/MotionBlur.java +++ b/src/main/java/andrewla/kernels/MotionBlur.java @@ -2,7 +2,7 @@ import andrewla.Kernel; -public class MotionBlur extends AbstractKernel { +public class MotionBlur extends BaseKernel { private final double angle; public MotionBlur(int size, double angle) { @@ -35,11 +35,6 @@ public Kernel getResized(int newSize) { @Override public Kernel getExpanded(int newSize) { - return null; - } - - @Override - public Kernel getComposed(Kernel other) { - return null; + return new MotionBlur(newSize, angle).expandWithZeros(newSize); } } diff --git a/src/main/java/andrewla/kernels/Sharpen.java b/src/main/java/andrewla/kernels/Sharpen.java index 93f1384..773abf9 100644 --- a/src/main/java/andrewla/kernels/Sharpen.java +++ b/src/main/java/andrewla/kernels/Sharpen.java @@ -2,7 +2,7 @@ import andrewla.Kernel; -public class Sharpen extends AbstractKernel{ +public class Sharpen extends BaseKernel { public Sharpen(int size) { super(size); @@ -46,11 +46,6 @@ public Kernel getResized(int newSize) { @Override public Kernel getExpanded(int newSize) { - return null; - } - - @Override - public Kernel getComposed(Kernel other) { - return null; + return new Sharpen(getSize()).expandWithZeros(newSize); } } From a814dac288f0c2133f79c9f8489d9eeb4acb07e2 Mon Sep 17 00:00:00 2001 From: Andrew Lazarev Date: Thu, 30 Apr 2026 22:35:16 +0300 Subject: [PATCH 04/11] feat: add single image processor Signed-off-by: Andrew Lazarev --- src/main/java/andrewla/ImageProcessor.java | 13 +++++++++ .../processors/BaseSingleProcessor.java | 27 +++++++++++++++++++ .../processors/SequentialSingleProcessor.java | 18 +++++++++++++ 3 files changed, 58 insertions(+) create mode 100644 src/main/java/andrewla/ImageProcessor.java create mode 100644 src/main/java/andrewla/processors/BaseSingleProcessor.java create mode 100644 src/main/java/andrewla/processors/SequentialSingleProcessor.java diff --git a/src/main/java/andrewla/ImageProcessor.java b/src/main/java/andrewla/ImageProcessor.java new file mode 100644 index 0000000..97a5856 --- /dev/null +++ b/src/main/java/andrewla/ImageProcessor.java @@ -0,0 +1,13 @@ +package andrewla; + +import javax.imageio.ImageReader; +import java.awt.image.BufferedImage; +import java.util.List; + +public interface ImageProcessor { + void addKernel(Kernel kernel); + + void addImageReader(ImageReader reader); + + List applyFilters(); +} diff --git a/src/main/java/andrewla/processors/BaseSingleProcessor.java b/src/main/java/andrewla/processors/BaseSingleProcessor.java new file mode 100644 index 0000000..1e085d6 --- /dev/null +++ b/src/main/java/andrewla/processors/BaseSingleProcessor.java @@ -0,0 +1,27 @@ +package andrewla.processors; + +import andrewla.ImageProcessor; +import andrewla.Kernel; + +import javax.imageio.ImageReader; +import java.util.ArrayList; +import java.util.List; + +public abstract class BaseSingleProcessor implements ImageProcessor { + private final List kernels = new ArrayList<>(); + private ImageReader reader = null; + + @Override + public void addKernel(Kernel kernel) { + kernels.add(kernel); + } + + @Override + public void addImageReader(ImageReader reader) { + if (this.reader == null) { + this.reader = reader; + } else { + throw new RuntimeException("Image reader was already set"); + } + } +} diff --git a/src/main/java/andrewla/processors/SequentialSingleProcessor.java b/src/main/java/andrewla/processors/SequentialSingleProcessor.java new file mode 100644 index 0000000..345cf78 --- /dev/null +++ b/src/main/java/andrewla/processors/SequentialSingleProcessor.java @@ -0,0 +1,18 @@ +package andrewla.processors; + +import andrewla.Kernel; + +import javax.imageio.ImageReader; +import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.List; + +public class SequentialSingleProcessor extends BaseSingleProcessor { + private final List kernels = new ArrayList<>(); + private ImageReader reader = null; + + @Override + public List applyFilters() { + return List.of(); + } +} From 4a9d35092cdf42eecf5f0bab029ea622f373ceb1 Mon Sep 17 00:00:00 2001 From: Andrew Lazarev Date: Thu, 30 Apr 2026 22:40:22 +0300 Subject: [PATCH 05/11] feat: Add parallel processor Signed-off-by: Andrew Lazarev --- .../andrewla/processors/ParallelSingleProcessor.java | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/main/java/andrewla/processors/ParallelSingleProcessor.java diff --git a/src/main/java/andrewla/processors/ParallelSingleProcessor.java b/src/main/java/andrewla/processors/ParallelSingleProcessor.java new file mode 100644 index 0000000..a7175ed --- /dev/null +++ b/src/main/java/andrewla/processors/ParallelSingleProcessor.java @@ -0,0 +1,11 @@ +package andrewla.processors; + +import java.awt.image.BufferedImage; +import java.util.List; + +public class ParallelSingleProcessor extends BaseSingleProcessor { + @Override + public List applyFilters() { + return List.of(); + } +} From 0e80d81ad0fa2c9e9d300b24a187f6ee47b79f68 Mon Sep 17 00:00:00 2001 From: Andrew Lazarev Date: Thu, 30 Apr 2026 23:01:49 +0300 Subject: [PATCH 06/11] feat: complete sequential implementation Signed-off-by: Andrew Lazarev --- .../processors/SequentialSingleProcessor.java | 85 ++++++++++++++++++- 1 file changed, 82 insertions(+), 3 deletions(-) diff --git a/src/main/java/andrewla/processors/SequentialSingleProcessor.java b/src/main/java/andrewla/processors/SequentialSingleProcessor.java index 345cf78..581caae 100644 --- a/src/main/java/andrewla/processors/SequentialSingleProcessor.java +++ b/src/main/java/andrewla/processors/SequentialSingleProcessor.java @@ -4,15 +4,94 @@ import javax.imageio.ImageReader; import java.awt.image.BufferedImage; -import java.util.ArrayList; +import java.awt.image.ColorModel; +import java.awt.image.WritableRaster; +import java.io.IOException; +import java.util.LinkedList; import java.util.List; public class SequentialSingleProcessor extends BaseSingleProcessor { - private final List kernels = new ArrayList<>(); + private final List kernels = new LinkedList<>(); private ImageReader reader = null; + private static BufferedImage copyImage(BufferedImage bi) { + ColorModel cm = bi.getColorModel(); + boolean isAlphaMultiplied = cm.isAlphaPremultiplied(); + WritableRaster raster = bi.copyData(null); + return new BufferedImage(cm, raster, isAlphaMultiplied, null); + } + + private static int clamp(int v, int lo, int hi) { + return Math.min(hi, Math.max(lo, v)); + } + + private static int clampPixel(int v) { + return clamp(v, 0, 255); + } + @Override public List applyFilters() { - return List.of(); + BufferedImage src; + + try { + src = reader.read(0); + } catch (IOException e) { + throw new RuntimeException(e); + } + + final var width = src.getWidth() / 2; + final var height = src.getHeight() / 2; + + BufferedImage out = copyImage(src); + + for (var k : kernels) { + final var kSize = k.getSize(); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + var r = 0.0; + var g = 0.0; + var b = 0.0; + + for (int kx = 0; kx < kSize; kx++) { + for (int ky = 0; ky < kSize; ky++) { + final var px = clamp(x + kx, 0, width - 1); + final var py = clamp(y + ky, 0, height - 1); + + final var rgb = src.getRGB(px, py); + final var red = (rgb >> 16) & 0xFF; + final var green = (rgb >> 8) & 0xFF; + final var blue = rgb & 0xFF; + + final var value = k.getValue(kx + kSize, ky + kSize); + + r += red * value; + g += green * value; + b += blue * value; + } + } + + final var bias = k.getBias(); + final var factor = k.getFactor(); + + final var nr = clampPixel((int) (r * factor + bias)); + final var ng = clampPixel((int) (g * factor + bias)); + final var nb = clampPixel((int) (b * factor + bias)); + + var pixel = 0xFF000000; + + pixel |= nr << 16; + pixel |= ng << 8; + pixel |= nb; + + out.setRGB(x, y, pixel); + } + } + } + + final var result = new LinkedList(); + result.add(out); + + return result; } } From 0a8076a5e39aac0d0909c7a258f484ae38a63dd8 Mon Sep 17 00:00:00 2001 From: Andrew Lazarev Date: Thu, 30 Apr 2026 23:37:27 +0300 Subject: [PATCH 07/11] feat: Add parallel implementation Signed-off-by: Andrew Lazarev --- .../processors/BaseSingleProcessor.java | 22 ++- .../processors/ParallelSingleProcessor.java | 169 +++++++++++++++++- 2 files changed, 188 insertions(+), 3 deletions(-) diff --git a/src/main/java/andrewla/processors/BaseSingleProcessor.java b/src/main/java/andrewla/processors/BaseSingleProcessor.java index 1e085d6..6c6e2ae 100644 --- a/src/main/java/andrewla/processors/BaseSingleProcessor.java +++ b/src/main/java/andrewla/processors/BaseSingleProcessor.java @@ -4,12 +4,30 @@ import andrewla.Kernel; import javax.imageio.ImageReader; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.WritableRaster; import java.util.ArrayList; import java.util.List; public abstract class BaseSingleProcessor implements ImageProcessor { - private final List kernels = new ArrayList<>(); - private ImageReader reader = null; + protected final List kernels = new ArrayList<>(); + protected ImageReader reader = null; + + protected static BufferedImage copyImage(BufferedImage bi) { + ColorModel cm = bi.getColorModel(); + boolean isAlphaMultiplied = cm.isAlphaPremultiplied(); + WritableRaster raster = bi.copyData(null); + return new BufferedImage(cm, raster, isAlphaMultiplied, null); + } + + protected static int clamp(int v, int lo, int hi) { + return Math.min(hi, Math.max(lo, v)); + } + + protected static int clampPixel(int v) { + return clamp(v, 0, 255); + } @Override public void addKernel(Kernel kernel) { diff --git a/src/main/java/andrewla/processors/ParallelSingleProcessor.java b/src/main/java/andrewla/processors/ParallelSingleProcessor.java index a7175ed..853442a 100644 --- a/src/main/java/andrewla/processors/ParallelSingleProcessor.java +++ b/src/main/java/andrewla/processors/ParallelSingleProcessor.java @@ -1,11 +1,178 @@ package andrewla.processors; +import andrewla.Kernel; + import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.ArrayList; import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; public class ParallelSingleProcessor extends BaseSingleProcessor { + private final ParallelPolicy policy; + private final ExecutorService executor; + + public ParallelSingleProcessor(ParallelPolicy policy, ExecutorService executor) { + this.policy = policy; + this.executor = executor; + } + + public ParallelSingleProcessor(ParallelPolicy policy) { + this.policy = policy; + this.executor = Executors.newFixedThreadPool(16); + } + @Override public List applyFilters() { - return List.of(); + BufferedImage src; + + try { + src = reader.read(0); + } catch (IOException e) { + throw new RuntimeException(e); + } + + final var width = src.getWidth() / 2; + final var height = src.getHeight() / 2; + + BufferedImage out = copyImage(src); + + for (final var k : kernels) { + switch (policy) { + case ROW: + rows(src, out, width, height, k); + break; + case COLUMN: + columns(src, out, width, height, k); + break; + case PIXEL: + pixels(src, out, width, height, k); + break; + default: + throw new IllegalStateException("Invalid policy value"); + } + } + + return List.of(out); + } + + private void rows(BufferedImage src, BufferedImage out, int w, int h, Kernel kernel) { + final var threads = ((java.util.concurrent.ThreadPoolExecutor) executor).getMaximumPoolSize(); + final var chunkSize = (h + threads - 1) / threads; + + List> tasks = new ArrayList<>(); + + for (int startY = 0; startY < h; startY += chunkSize) { + final var from = startY; + final var to = Math.min(startY + chunkSize, h); + + tasks.add(() -> { + for (int y = from; y < to; y++) { + for (int x = 0; x < w; x++) { + processPixel(src, out, x, y, kernel); + } + } + return null; + }); + } + invokeAndWait(tasks); + } + + private void columns(BufferedImage src, BufferedImage out, int w, int h, Kernel kernel) { + final var threads = ((java.util.concurrent.ThreadPoolExecutor) executor).getMaximumPoolSize(); + final var chunkSize = (w + threads - 1) / threads; + + List> tasks = new ArrayList<>(); + + for (int startX = 0; startX < w; startX += chunkSize) { + final var from = startX; + final var to = Math.min(startX + chunkSize, w); + + tasks.add(() -> { + for (int y = 0; y < h; y++) { + for (int x = from; x < to; x++) { + processPixel(src, out, x, y, kernel); + } + } + return null; + }); + } + + invokeAndWait(tasks); + } + + private void pixels(BufferedImage src, BufferedImage out, int w, int h, Kernel kernel) { + List> tasks = new ArrayList<>(w * h); + + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + final int fx = x, fy = y; + tasks.add(() -> { + processPixel(src, out, fx, fy, kernel); + return null; + }); + } + } + + invokeAndWait(tasks); + } + + private void processPixel(BufferedImage src, BufferedImage out, int x, int y, Kernel kernel) { + final var width = src.getWidth(); + final var height = src.getHeight(); + + var r = 0.0; + var g = 0.0; + var b = 0.0; + + final var kSize = kernel.getSize(); + final var kCenter = kSize / 2; + + for (int ky = 0; ky < kSize; ky++) { + for (int kx = 0; kx < kSize; kx++) { + int px = clamp(x + kx, 0, width - 1); + int py = clamp(y + ky, 0, height - 1); + int rgb = src.getRGB(px, py); + int red = (rgb >> 16) & 0xFF; + int green = (rgb >> 8) & 0xFF; + int blue = rgb & 0xFF; + + final var value = kernel.getValue(kx - kCenter, ky - kCenter); + + r += red * value; + g += green * value; + b += blue * value; + } + } + + final var bias = kernel.getBias(); + final var factor = kernel.getFactor(); + + final var nr = clampPixel((int) (r * factor + bias)); + final var ng = clampPixel((int) (g * factor + bias)); + final var nb = clampPixel((int) (b * factor + bias)); + + var pixel = 0xFF000000; + + pixel |= nr << 16; + pixel |= ng << 8; + pixel |= nb; + + out.setRGB(x, y, pixel); + } + + private void invokeAndWait(List> tasks) { + try { + executor.invokeAll(tasks); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException("Parallel execution interrupted", e); + } + } + + public enum ParallelPolicy { + ROW, COLUMN, PIXEL, } } From c90ecfea0b7d882ca7931202eaee0c743ae4effa Mon Sep 17 00:00:00 2001 From: Andrew Lazarev Date: Fri, 1 May 2026 09:34:46 +0300 Subject: [PATCH 08/11] fix: Put everything to org package Signed-off-by: Andrew Lazarev --- .../{ => org}/andrewla/ImageProcessor.java | 2 +- src/main/java/{ => org}/andrewla/Kernel.java | 2 +- src/main/java/org/andrewla/Main.java | 7 ++++ .../andrewla/kernels/AbstractKernel.java} | 12 +++---- .../java/org/andrewla/kernels/BoxBlur.java | 22 ++++++++++++ .../{ => org}/andrewla/kernels/Emboss.java | 8 ++--- .../andrewla/kernels/GaussianBlur.java | 6 ++-- .../{ => org}/andrewla/kernels/Identity.java | 6 ++-- .../andrewla/kernels/MotionBlur.java | 6 ++-- .../{ => org}/andrewla/kernels/Sharpen.java | 6 ++-- .../processors/BaseMultipleProcessor.java | 23 +++++++++++++ .../processors/BaseSingleProcessor.java | 6 ++-- .../processors/ParallelSingleProcessor.java | 4 +-- .../processors/SequentialSingleProcessor.java | 34 +++---------------- 14 files changed, 84 insertions(+), 60 deletions(-) rename src/main/java/{ => org}/andrewla/ImageProcessor.java (92%) rename src/main/java/{ => org}/andrewla/Kernel.java (90%) create mode 100644 src/main/java/org/andrewla/Main.java rename src/main/java/{andrewla/kernels/BaseKernel.java => org/andrewla/kernels/AbstractKernel.java} (89%) create mode 100644 src/main/java/org/andrewla/kernels/BoxBlur.java rename src/main/java/{ => org}/andrewla/kernels/Emboss.java (83%) rename src/main/java/{ => org}/andrewla/kernels/GaussianBlur.java (89%) rename src/main/java/{ => org}/andrewla/kernels/Identity.java (82%) rename src/main/java/{ => org}/andrewla/kernels/MotionBlur.java (89%) rename src/main/java/{ => org}/andrewla/kernels/Sharpen.java (91%) create mode 100644 src/main/java/org/andrewla/processors/BaseMultipleProcessor.java rename src/main/java/{ => org}/andrewla/processors/BaseSingleProcessor.java (92%) rename src/main/java/{ => org}/andrewla/processors/ParallelSingleProcessor.java (98%) rename src/main/java/{ => org}/andrewla/processors/SequentialSingleProcessor.java (68%) diff --git a/src/main/java/andrewla/ImageProcessor.java b/src/main/java/org/andrewla/ImageProcessor.java similarity index 92% rename from src/main/java/andrewla/ImageProcessor.java rename to src/main/java/org/andrewla/ImageProcessor.java index 97a5856..ce7eaec 100644 --- a/src/main/java/andrewla/ImageProcessor.java +++ b/src/main/java/org/andrewla/ImageProcessor.java @@ -1,4 +1,4 @@ -package andrewla; +package org.andrewla; import javax.imageio.ImageReader; import java.awt.image.BufferedImage; diff --git a/src/main/java/andrewla/Kernel.java b/src/main/java/org/andrewla/Kernel.java similarity index 90% rename from src/main/java/andrewla/Kernel.java rename to src/main/java/org/andrewla/Kernel.java index a0d1dd2..0dcac28 100644 --- a/src/main/java/andrewla/Kernel.java +++ b/src/main/java/org/andrewla/Kernel.java @@ -1,4 +1,4 @@ -package andrewla; +package org.andrewla; public interface Kernel { int getSize(); diff --git a/src/main/java/org/andrewla/Main.java b/src/main/java/org/andrewla/Main.java new file mode 100644 index 0000000..12fb261 --- /dev/null +++ b/src/main/java/org/andrewla/Main.java @@ -0,0 +1,7 @@ +package org.andrewla; + +public class Main { + public static void main(String[] args) { + System.out.println("Image filters"); + } +} diff --git a/src/main/java/andrewla/kernels/BaseKernel.java b/src/main/java/org/andrewla/kernels/AbstractKernel.java similarity index 89% rename from src/main/java/andrewla/kernels/BaseKernel.java rename to src/main/java/org/andrewla/kernels/AbstractKernel.java index ef5eb7e..0e5457b 100644 --- a/src/main/java/andrewla/kernels/BaseKernel.java +++ b/src/main/java/org/andrewla/kernels/AbstractKernel.java @@ -1,10 +1,10 @@ -package andrewla.kernels; +package org.andrewla.kernels; -import andrewla.Kernel; +import org.andrewla.Kernel; import java.util.Objects; -public abstract class BaseKernel implements Kernel { +public abstract class AbstractKernel implements Kernel { private int size; private double[][] data; @@ -12,7 +12,7 @@ public abstract class BaseKernel implements Kernel { private double factor; private double bias; - protected BaseKernel(int size) { + protected AbstractKernel(int size) { if (size % 2 == 0) { throw new IllegalArgumentException("Size of kernel matrix should be odd"); } @@ -81,7 +81,7 @@ protected void setFactor() { protected Kernel expandWithZeros(int newSize) { if (newSize <= this.size) { - throw new IllegalArgumentException("New size should be greater than the actual"); + return this; } if (newSize % 2 == 0) { @@ -105,7 +105,7 @@ protected Kernel expandWithZeros(int newSize) { public boolean equals(Object other) { if (other == null || getClass() != other.getClass()) return false; - BaseKernel that = (BaseKernel) other; + AbstractKernel that = (AbstractKernel) other; return size == that.size && Double.compare(factor, that.factor) == 0 && Objects.deepEquals(data, that.data); } } diff --git a/src/main/java/org/andrewla/kernels/BoxBlur.java b/src/main/java/org/andrewla/kernels/BoxBlur.java new file mode 100644 index 0000000..94d19fe --- /dev/null +++ b/src/main/java/org/andrewla/kernels/BoxBlur.java @@ -0,0 +1,22 @@ +package org.andrewla.kernels; + +import org.andrewla.Kernel; + +public class BoxBlur extends AbstractKernel { + private final double blurRadius; + + public BoxBlur(int size, double blurRadius) { + super(size); + this.blurRadius = blurRadius; + } + + @Override + public Kernel getResized(int newSize) { + return null; + } + + @Override + public Kernel getExpanded(int newSize) { + return new BoxBlur(getSize(), blurRadius).expandWithZeros(newSize); + } +} diff --git a/src/main/java/andrewla/kernels/Emboss.java b/src/main/java/org/andrewla/kernels/Emboss.java similarity index 83% rename from src/main/java/andrewla/kernels/Emboss.java rename to src/main/java/org/andrewla/kernels/Emboss.java index 5876c0d..83ba514 100644 --- a/src/main/java/andrewla/kernels/Emboss.java +++ b/src/main/java/org/andrewla/kernels/Emboss.java @@ -1,13 +1,11 @@ -package andrewla.kernels; +package org.andrewla.kernels; -import andrewla.Kernel; +import org.andrewla.Kernel; -public class Emboss extends BaseKernel { +public class Emboss extends AbstractKernel { public Emboss(int size) { super(size); - var center = size / 2; - for (int i = 0; i < size; i++) { for (int j = 0; j <= i; j++) { setValue(i, j, -1); diff --git a/src/main/java/andrewla/kernels/GaussianBlur.java b/src/main/java/org/andrewla/kernels/GaussianBlur.java similarity index 89% rename from src/main/java/andrewla/kernels/GaussianBlur.java rename to src/main/java/org/andrewla/kernels/GaussianBlur.java index 92334f2..745795a 100644 --- a/src/main/java/andrewla/kernels/GaussianBlur.java +++ b/src/main/java/org/andrewla/kernels/GaussianBlur.java @@ -1,8 +1,8 @@ -package andrewla.kernels; +package org.andrewla.kernels; -import andrewla.Kernel; +import org.andrewla.Kernel; -public class GaussianBlur extends BaseKernel { +public class GaussianBlur extends AbstractKernel { private final double blurRadius; public GaussianBlur(int size, double blurRadius) { diff --git a/src/main/java/andrewla/kernels/Identity.java b/src/main/java/org/andrewla/kernels/Identity.java similarity index 82% rename from src/main/java/andrewla/kernels/Identity.java rename to src/main/java/org/andrewla/kernels/Identity.java index 628abdc..f896608 100644 --- a/src/main/java/andrewla/kernels/Identity.java +++ b/src/main/java/org/andrewla/kernels/Identity.java @@ -1,8 +1,8 @@ -package andrewla.kernels; +package org.andrewla.kernels; -import andrewla.Kernel; +import org.andrewla.Kernel; -public class Identity extends BaseKernel { +public class Identity extends AbstractKernel { public Identity(int size) { super(size); diff --git a/src/main/java/andrewla/kernels/MotionBlur.java b/src/main/java/org/andrewla/kernels/MotionBlur.java similarity index 89% rename from src/main/java/andrewla/kernels/MotionBlur.java rename to src/main/java/org/andrewla/kernels/MotionBlur.java index f578422..6330352 100644 --- a/src/main/java/andrewla/kernels/MotionBlur.java +++ b/src/main/java/org/andrewla/kernels/MotionBlur.java @@ -1,8 +1,8 @@ -package andrewla.kernels; +package org.andrewla.kernels; -import andrewla.Kernel; +import org.andrewla.Kernel; -public class MotionBlur extends BaseKernel { +public class MotionBlur extends AbstractKernel { private final double angle; public MotionBlur(int size, double angle) { diff --git a/src/main/java/andrewla/kernels/Sharpen.java b/src/main/java/org/andrewla/kernels/Sharpen.java similarity index 91% rename from src/main/java/andrewla/kernels/Sharpen.java rename to src/main/java/org/andrewla/kernels/Sharpen.java index 773abf9..740cd53 100644 --- a/src/main/java/andrewla/kernels/Sharpen.java +++ b/src/main/java/org/andrewla/kernels/Sharpen.java @@ -1,8 +1,8 @@ -package andrewla.kernels; +package org.andrewla.kernels; -import andrewla.Kernel; +import org.andrewla.Kernel; -public class Sharpen extends BaseKernel { +public class Sharpen extends AbstractKernel { public Sharpen(int size) { super(size); diff --git a/src/main/java/org/andrewla/processors/BaseMultipleProcessor.java b/src/main/java/org/andrewla/processors/BaseMultipleProcessor.java new file mode 100644 index 0000000..8447693 --- /dev/null +++ b/src/main/java/org/andrewla/processors/BaseMultipleProcessor.java @@ -0,0 +1,23 @@ +package org.andrewla.processors; + +import org.andrewla.ImageProcessor; +import org.andrewla.Kernel; + +import javax.imageio.ImageReader; +import java.util.ArrayList; +import java.util.List; + +public abstract class BaseMultipleProcessor implements ImageProcessor { + private final List kernels = new ArrayList<>(); + private final List readers = new ArrayList<>(); + + @Override + public void addKernel(Kernel kernel) { + kernels.add(kernel); + } + + @Override + public void addImageReader(ImageReader reader) { + readers.add(reader); + } +} diff --git a/src/main/java/andrewla/processors/BaseSingleProcessor.java b/src/main/java/org/andrewla/processors/BaseSingleProcessor.java similarity index 92% rename from src/main/java/andrewla/processors/BaseSingleProcessor.java rename to src/main/java/org/andrewla/processors/BaseSingleProcessor.java index 6c6e2ae..3c5608c 100644 --- a/src/main/java/andrewla/processors/BaseSingleProcessor.java +++ b/src/main/java/org/andrewla/processors/BaseSingleProcessor.java @@ -1,7 +1,7 @@ -package andrewla.processors; +package org.andrewla.processors; -import andrewla.ImageProcessor; -import andrewla.Kernel; +import org.andrewla.ImageProcessor; +import org.andrewla.Kernel; import javax.imageio.ImageReader; import java.awt.image.BufferedImage; diff --git a/src/main/java/andrewla/processors/ParallelSingleProcessor.java b/src/main/java/org/andrewla/processors/ParallelSingleProcessor.java similarity index 98% rename from src/main/java/andrewla/processors/ParallelSingleProcessor.java rename to src/main/java/org/andrewla/processors/ParallelSingleProcessor.java index 853442a..0622369 100644 --- a/src/main/java/andrewla/processors/ParallelSingleProcessor.java +++ b/src/main/java/org/andrewla/processors/ParallelSingleProcessor.java @@ -1,6 +1,6 @@ -package andrewla.processors; +package org.andrewla.processors; -import andrewla.Kernel; +import org.andrewla.Kernel; import java.awt.image.BufferedImage; import java.io.IOException; diff --git a/src/main/java/andrewla/processors/SequentialSingleProcessor.java b/src/main/java/org/andrewla/processors/SequentialSingleProcessor.java similarity index 68% rename from src/main/java/andrewla/processors/SequentialSingleProcessor.java rename to src/main/java/org/andrewla/processors/SequentialSingleProcessor.java index 581caae..65877f5 100644 --- a/src/main/java/andrewla/processors/SequentialSingleProcessor.java +++ b/src/main/java/org/andrewla/processors/SequentialSingleProcessor.java @@ -1,34 +1,10 @@ -package andrewla.processors; +package org.andrewla.processors; -import andrewla.Kernel; - -import javax.imageio.ImageReader; import java.awt.image.BufferedImage; -import java.awt.image.ColorModel; -import java.awt.image.WritableRaster; import java.io.IOException; -import java.util.LinkedList; import java.util.List; public class SequentialSingleProcessor extends BaseSingleProcessor { - private final List kernels = new LinkedList<>(); - private ImageReader reader = null; - - private static BufferedImage copyImage(BufferedImage bi) { - ColorModel cm = bi.getColorModel(); - boolean isAlphaMultiplied = cm.isAlphaPremultiplied(); - WritableRaster raster = bi.copyData(null); - return new BufferedImage(cm, raster, isAlphaMultiplied, null); - } - - private static int clamp(int v, int lo, int hi) { - return Math.min(hi, Math.max(lo, v)); - } - - private static int clampPixel(int v) { - return clamp(v, 0, 255); - } - @Override public List applyFilters() { BufferedImage src; @@ -46,6 +22,7 @@ public List applyFilters() { for (var k : kernels) { final var kSize = k.getSize(); + final var kCenter = kSize / 2; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { @@ -63,7 +40,7 @@ public List applyFilters() { final var green = (rgb >> 8) & 0xFF; final var blue = rgb & 0xFF; - final var value = k.getValue(kx + kSize, ky + kSize); + final var value = k.getValue(kx - kCenter, ky - kCenter); r += red * value; g += green * value; @@ -89,9 +66,6 @@ public List applyFilters() { } } - final var result = new LinkedList(); - result.add(out); - - return result; + return List.of(out); } } From a1bb5a7aabf61ad6d86d8a231aecc304ba8dac3d Mon Sep 17 00:00:00 2001 From: Andrew Lazarev Date: Fri, 1 May 2026 09:36:04 +0300 Subject: [PATCH 09/11] fix: remove parallel implementation from dev1 branch This reverts commit 0a8076a5e39aac0d0909c7a258f484ae38a63dd8. --- .../processors/BaseSingleProcessor.java | 22 ++----------------- .../processors/ParallelSingleProcessor.java | 4 ++-- 2 files changed, 4 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/andrewla/processors/BaseSingleProcessor.java b/src/main/java/org/andrewla/processors/BaseSingleProcessor.java index 3c5608c..3c0eb8a 100644 --- a/src/main/java/org/andrewla/processors/BaseSingleProcessor.java +++ b/src/main/java/org/andrewla/processors/BaseSingleProcessor.java @@ -4,30 +4,12 @@ import org.andrewla.Kernel; import javax.imageio.ImageReader; -import java.awt.image.BufferedImage; -import java.awt.image.ColorModel; -import java.awt.image.WritableRaster; import java.util.ArrayList; import java.util.List; public abstract class BaseSingleProcessor implements ImageProcessor { - protected final List kernels = new ArrayList<>(); - protected ImageReader reader = null; - - protected static BufferedImage copyImage(BufferedImage bi) { - ColorModel cm = bi.getColorModel(); - boolean isAlphaMultiplied = cm.isAlphaPremultiplied(); - WritableRaster raster = bi.copyData(null); - return new BufferedImage(cm, raster, isAlphaMultiplied, null); - } - - protected static int clamp(int v, int lo, int hi) { - return Math.min(hi, Math.max(lo, v)); - } - - protected static int clampPixel(int v) { - return clamp(v, 0, 255); - } + private final List kernels = new ArrayList<>(); + private ImageReader reader = null; @Override public void addKernel(Kernel kernel) { diff --git a/src/main/java/org/andrewla/processors/ParallelSingleProcessor.java b/src/main/java/org/andrewla/processors/ParallelSingleProcessor.java index 0622369..853442a 100644 --- a/src/main/java/org/andrewla/processors/ParallelSingleProcessor.java +++ b/src/main/java/org/andrewla/processors/ParallelSingleProcessor.java @@ -1,6 +1,6 @@ -package org.andrewla.processors; +package andrewla.processors; -import org.andrewla.Kernel; +import andrewla.Kernel; import java.awt.image.BufferedImage; import java.io.IOException; From 1a547cf4639c9d32d1b63aba28ebbeac28e51083 Mon Sep 17 00:00:00 2001 From: Andrew Lazarev Date: Fri, 1 May 2026 09:39:20 +0300 Subject: [PATCH 10/11] fix: remove parallel implementation from dev1 (v2) This reverts commit 0a8076a5e39aac0d0909c7a258f484ae38a63dd8. --- .../processors/ParallelSingleProcessor.java | 171 +----------------- 1 file changed, 2 insertions(+), 169 deletions(-) diff --git a/src/main/java/org/andrewla/processors/ParallelSingleProcessor.java b/src/main/java/org/andrewla/processors/ParallelSingleProcessor.java index 853442a..53cc1e4 100644 --- a/src/main/java/org/andrewla/processors/ParallelSingleProcessor.java +++ b/src/main/java/org/andrewla/processors/ParallelSingleProcessor.java @@ -1,178 +1,11 @@ -package andrewla.processors; - -import andrewla.Kernel; +package org.andrewla.processors; import java.awt.image.BufferedImage; -import java.io.IOException; -import java.util.ArrayList; import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; public class ParallelSingleProcessor extends BaseSingleProcessor { - private final ParallelPolicy policy; - private final ExecutorService executor; - - public ParallelSingleProcessor(ParallelPolicy policy, ExecutorService executor) { - this.policy = policy; - this.executor = executor; - } - - public ParallelSingleProcessor(ParallelPolicy policy) { - this.policy = policy; - this.executor = Executors.newFixedThreadPool(16); - } - @Override public List applyFilters() { - BufferedImage src; - - try { - src = reader.read(0); - } catch (IOException e) { - throw new RuntimeException(e); - } - - final var width = src.getWidth() / 2; - final var height = src.getHeight() / 2; - - BufferedImage out = copyImage(src); - - for (final var k : kernels) { - switch (policy) { - case ROW: - rows(src, out, width, height, k); - break; - case COLUMN: - columns(src, out, width, height, k); - break; - case PIXEL: - pixels(src, out, width, height, k); - break; - default: - throw new IllegalStateException("Invalid policy value"); - } - } - - return List.of(out); - } - - private void rows(BufferedImage src, BufferedImage out, int w, int h, Kernel kernel) { - final var threads = ((java.util.concurrent.ThreadPoolExecutor) executor).getMaximumPoolSize(); - final var chunkSize = (h + threads - 1) / threads; - - List> tasks = new ArrayList<>(); - - for (int startY = 0; startY < h; startY += chunkSize) { - final var from = startY; - final var to = Math.min(startY + chunkSize, h); - - tasks.add(() -> { - for (int y = from; y < to; y++) { - for (int x = 0; x < w; x++) { - processPixel(src, out, x, y, kernel); - } - } - return null; - }); - } - invokeAndWait(tasks); - } - - private void columns(BufferedImage src, BufferedImage out, int w, int h, Kernel kernel) { - final var threads = ((java.util.concurrent.ThreadPoolExecutor) executor).getMaximumPoolSize(); - final var chunkSize = (w + threads - 1) / threads; - - List> tasks = new ArrayList<>(); - - for (int startX = 0; startX < w; startX += chunkSize) { - final var from = startX; - final var to = Math.min(startX + chunkSize, w); - - tasks.add(() -> { - for (int y = 0; y < h; y++) { - for (int x = from; x < to; x++) { - processPixel(src, out, x, y, kernel); - } - } - return null; - }); - } - - invokeAndWait(tasks); - } - - private void pixels(BufferedImage src, BufferedImage out, int w, int h, Kernel kernel) { - List> tasks = new ArrayList<>(w * h); - - for (int y = 0; y < h; y++) { - for (int x = 0; x < w; x++) { - final int fx = x, fy = y; - tasks.add(() -> { - processPixel(src, out, fx, fy, kernel); - return null; - }); - } - } - - invokeAndWait(tasks); - } - - private void processPixel(BufferedImage src, BufferedImage out, int x, int y, Kernel kernel) { - final var width = src.getWidth(); - final var height = src.getHeight(); - - var r = 0.0; - var g = 0.0; - var b = 0.0; - - final var kSize = kernel.getSize(); - final var kCenter = kSize / 2; - - for (int ky = 0; ky < kSize; ky++) { - for (int kx = 0; kx < kSize; kx++) { - int px = clamp(x + kx, 0, width - 1); - int py = clamp(y + ky, 0, height - 1); - int rgb = src.getRGB(px, py); - int red = (rgb >> 16) & 0xFF; - int green = (rgb >> 8) & 0xFF; - int blue = rgb & 0xFF; - - final var value = kernel.getValue(kx - kCenter, ky - kCenter); - - r += red * value; - g += green * value; - b += blue * value; - } - } - - final var bias = kernel.getBias(); - final var factor = kernel.getFactor(); - - final var nr = clampPixel((int) (r * factor + bias)); - final var ng = clampPixel((int) (g * factor + bias)); - final var nb = clampPixel((int) (b * factor + bias)); - - var pixel = 0xFF000000; - - pixel |= nr << 16; - pixel |= ng << 8; - pixel |= nb; - - out.setRGB(x, y, pixel); - } - - private void invokeAndWait(List> tasks) { - try { - executor.invokeAll(tasks); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new RuntimeException("Parallel execution interrupted", e); - } - } - - public enum ParallelPolicy { - ROW, COLUMN, PIXEL, + return List.of(); } } From 0c6648ef65f1a09fb9166887aae46a6a2d0a4062 Mon Sep 17 00:00:00 2001 From: Andrew Lazarev Date: Wed, 27 May 2026 03:15:05 +0300 Subject: [PATCH 11/11] feat: Add CLI library to project Signed-off-by: Andrew Lazarev --- pom.xml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pom.xml b/pom.xml index 2f6d608..14d58de 100644 --- a/pom.xml +++ b/pom.xml @@ -14,4 +14,13 @@ UTF-8 + + + commons-cli + commons-cli + 1.11.0 + compile + + +